class MainWindow(QMainWindow): def __init__(self, parent=None, viewtype=None): """ viewtype option specifies the file browser type of view ('tree' and 'table' are accepted, 'tree' is the default) :type viewtype: str """ super(MainWindow, self).__init__(parent) # # MODEL (is the default PyQt QFileSystemModel) # model = QFileSystemModel() model.setRootPath(QDir.currentPath()) # needed to fetch files model.setReadOnly(False) # # VIEW # # Select the view QWidget based on command prompt parameter # (table and tree view supported as far as now) if viewtype == 'table': view = TableView(self) elif viewtype == 'tree': view = TreeView(self) else: raise ValueError(u"'{}' view is not recognized. 'tree' or 'table' expected.".format(viewtype)) # passed self to use MainWindow's methods (see signal's TODO) logging.info(u'{}View'.format(viewtype.capitalize())) view.setModel(model) # If you set the root of the tree to the current path # you start much more quickly than leaving the default, that cause # detection of all drives on Windows (slow) if last_visited_directory: startpath = last_visited_directory else: startpath = QDir.currentPath() self.curpath = startpath view.setRootIndex(model.index(startpath)) # A generic 'view' attribute name is used. Don't care about the view # is a TreeView or a TableView or... self.view = view self.setCentralWidget(self.view) # STATUS BAR status = self.statusBar() status.setSizeGripEnabled(False) status.showMessage(tr('SB', "Ready"), 2000) self.status_bar = status # TODO: add message to the log wintitle = u'{} {}'.format(__title__, __date__.replace(u'-', u'.')) self.setWindowTitle(wintitle) # Selection behavior: SelectRows # (important if the view is a QTableView) self.view.setSelectionBehavior(view.SelectRows) # Set extended selection mode (ExtendedSelection is a standard) self.view.setSelectionMode(QTreeView.ExtendedSelection) # Enable the context menu self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(self.view, SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menu) # Double click on the view self.view.doubleClicked.connect(self.doubleclick) # Selection changed self.connect(self.view, SIGNAL('sel_changed(PyQt_PyObject)'), self.selChanged) # Creating window to show the textual content of files self.text_window = QTextEdit() # hidden for now (no method show() called) self.addOns = [] # let's populate self.addOns list with dynamically created AddOn instances of scripts for script in py_addons: modulename = path.splitext(path.split(script)[-1])[0] exec 'from scripts import {}'.format(modulename) try: exec 'self.addOns.append({}.AddOn(self))'.format(modulename) except BaseException as err: message = u'Error on script {}: {}'.format(err, modulename) print(message, args) self.status_bar.showMessage(message) logging.error(message) else: print(u"Loaded add-on '{}'".format(modulename)) @property def selection(self): """ Property that returns selected objects in a dict like this: {filePath1: {u'Name': fileName, u'Type': u'Drive', u'Size': u'4,3 MB', u'Date Modified': u'13/12/2011 15:35:46'}} So, to have only the selected filepaths: a = self.selection a.keys() -> filepaths This property works well with all types of selection behaviors. Type examples: u'Drive', u'File Folder', u'png File' """ # Get "polymorphically" the selection from 'view' indexes = self.view.selectedIndexes() return Selection(self._indexes2fileinfos(indexes)) def _indexes2fileinfos(self, indexes): """ from indexes extracts the meta informations of files in a dict where the keys are the file paths and the values are dictionaries like {PyQt4.QtCore.QString(u'Date Modified'): PyQt4.QtCore.QString(u'03/09/2012 14:22:10'), PyQt4.QtCore.QString(u'Name'): PyQt4.QtCore.QString(u'ba.dna'), PyQt4.QtCore.QString(u'Size'): PyQt4.QtCore.QString(u'58 KB'), PyQt4.QtCore.QString(u'Type'): PyQt4.QtCore.QString(u'dna File')}, """ res = dict() for index in indexes: col = index.column() fpath = self.view.model().filePath(index) res.setdefault(fpath, dict()) value = index.data().toString() header_col = self.view.model().headerData(col, Qt.Horizontal) hval = header_col.toString() res[fpath][hval] = value return res def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): action = QAction(text, self) if icon is not None: icon_path = "./icons/{0}.png".format(icon) if path.exists(icon_path): action.setIcon(QIcon(icon_path)) else: logging.warning('Warning: icon {} does not exist'.format(icon_path)) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def doubleclick(self, index): # double click handler fpath = self.view.model().filePath(index) self.open_path(fpath) #============================================== CONTEXT MENU ======================================================= def on_context_menu(self, point): sel = self.selection popMenu = QMenu() # describing objects desc = desc_objects(sel) # Copy selected objects editCopyAction = self.createAction(u"&Copy {}".format(desc), self.edit_copy, QKeySequence.Copy, icon='icon_copy', tip=u'Copy selected files to clipboard') popMenu.addAction(editCopyAction) # AddOns actions #popMenu.addSeparator() for addon in self.addOns: addon.on_context_menu(popMenu, sel) # Python command-line scripts popMenu.addSeparator() for py_fpath in py_scripts: dirpath, filename = path.split(py_fpath) name, ext = path.splitext(filename) menuItemText = u'Run {}'.format(py_fpath) scriptAction = self.createAction(menuItemText, slot=self.run_script, icon='icon_python', tip=u'Run {} on {}'.format(py_fpath, desc)) popMenu.addAction(scriptAction) popMenu.exec_(self.view.mapToGlobal(point)) #=================================================================================================================== def selChanged(self, newSel, oldSel): """ Method called from view when the selection changes. Give some information about currently selected objects """ # ignoring newSel and oldSel sel = self.selection desc = desc_objects(sel) #print(n, 'oggetti', newSelection.__class__.__name__, type(newSelection)) msg = u'{} selected'.format(desc) # Update the status bar with the new selection self.status_bar.showMessage(tr('SB', msg)) self.connect(self.view, SIGNAL('sel_changed(PyQt_PyObject)'), self.selChanged) def keyPressEvent(self, event): if type(event) == QKeyEvent and event.key() == Qt.Key_Backspace: # Backspace -> upper directory self.go_up() # ========================================================================= # Methods not directly called from GUI # ========================================================================= def open_path(self, path): qfi = QFileInfo(path) if qfi.isDir(): self.open_dir(path) elif qfi.isFile(): self.open_file(path) def open_dir(self, path): index = self.view.model().index(path) message = u'Opening directory {}'.format(path) logging.info(message) self.view.model().setRootPath(path) self.view.setRootIndex(index) self.curpath = path def open_file(self, filePath): """ Try to read the currently selected file and print it out to the stdout. """ fileInfo = QFileInfo(filePath) fname = unicode(fileInfo.fileName()) name, ext = path.splitext(fname) if ext.lower() in u'.bmp .gif .ico .jpg .png .tif .xbm'.split(): # apertura come immagine message = u'Opening image {}...'.format(filePath) logging.info(message) self.status_bar.showMessage(message) pixmap = QPixmap(filePath) self.image_window = QScrollArea() self.image_label = QLabel() self.image_label.setPixmap(pixmap) self.image_window.setWidget(self.image_label) self.image_window.show() self.image_label.show() message = u'Image size: {}x{}'.format(pixmap.width(), pixmap.height()) self.status_bar.showMessage(message) else: message = u'Opening file {}...'.format(filePath) logging.info(message) self.status_bar.showMessage(message) nchars = 1000000 # number of first characters showed (temp) try: fobj = open(unicode(filePath), 'rb') except Exception as err: # [Errno 22] invalid mode ('r') or filename: PyQt4.QtCore.QString(u'F:/\u65e5\u672c.txt') print('open error: {}'.format(err)) logging.error(err) self.status_bar.showMessage(str(err)) else: try: data = fobj.read(nchars) except Exception as err: print('read error: {}'.format(err)) logging.error(err) self.status_bar.showMessage(str(err)) else: #sts, old_output = getstatusoutput('chardetect.py "{}"'.format(filePath)) dec = data.decode('utf-8') # override existing text window self.text_window.setPlainText(dec) if not self.text_window.isVisible(): self.text_window.show() status_msg = u"'{}' file read.".format(fname) if len(data) == nchars: status_msg += u' (NB: showed {:,} first bytes only!)'.format(nchars) self.status_bar.showMessage(status_msg) finally: fobj.close() def go_up(self): """ Open upper directory. """ upper_path = upper_dir(self.curpath) if upper_path != self.curpath: self.open_dir(upper_path) else: if windows: # Windows: get drives when the root of unit is reached self.open_dir(u'') def edit_copy(self): """ Copy selected objects to clipboard """ # Options sep = ';' # list of paths separator sel = self.selection sel_desc = desc_objects(sel) #selpaths = sel['paths'] self.clipboard = sel # Copy to the system clipboard sys_clipboard = QApplication.clipboard() sys_clipboard.setText(QStringList(sel.paths).join(sep)) # example: "F:/Dropbox/progetti/dyr/dyr.py;F:/Dropbox/progetti/dyr/dyr.pyc;F:/Dropbox/progetti/dyr/README.rst" self.status_bar.showMessage(u'{} copied'.format(sel_desc)) def run_script(self): """ Run on selection a Python script without importing it (no interactions except capturing its output) and printing its output on the shell. """ def menuItemText2scriptPath(mit): # mit = menu item text (es. 'Run script/path.py') return u'{}'.format(mit[4:]) menuItem = self.sender() # metodo fondamentale per sapere chi ha # inviato il segnale e quindi quale script da eseguire รจ stato selezionato # (risale allo script a partire dal testo della voce di menu) py_path = menuItemText2scriptPath(menuItem.text()) selpaths = self.selection.keys() args = ' '.join(['"{}"'.format(sel_path) for sel_path in selpaths]) # TODO: editable script options cmd = u'python {} {}'.format(py_path, args) message = u'Running {} on {} object(s)...'.format(py_path, len(selpaths)) self.status_bar.showMessage(message) logging.info(message) # temp: automatically log when updating status bar # RUN t0 = crono() sts, output = getstatusoutput(cmd) t = crono() - t0 output_lines = output.splitlines() last_output_line = output_lines[-1] withOrWithout = {False:'out', True:''}[sts is not 0] message = u'{} completed with{} errors in {:.03f} s'.format(py_path, withOrWithout, t) logging.info(message) # temp: automatically log when updating status bar self.status_bar.showMessage(message) try: print(u'Output captured -> [{}]'.format(output)) except UnicodeError: print(repr(output)) print(u'Exit status: {}'.format(sts)) if sts: # Report the error in a warning dialog, displaying the exit code and the last output line QMessageBox.warning(self, u'Script error: exit status {}'.format(sts), u'{} says:\n{}'.format(py_path, last_output_line), 'bah?') else: QMessageBox.information(self, u'{} output'.format(py_path), '\n'.join(output_lines[-10:]), 'bah?')