class FilenamePrompt(_BasePrompt): """A prompt for a filename.""" def __init__(self, question, parent=None): super().__init__(question, parent) self._init_texts(question) self._init_fileview() self._set_fileview_root(question.default) self._lineedit = LineEdit(self) if question.default: self._lineedit.setText(question.default) self._lineedit.textEdited.connect(self._set_fileview_root) self._vbox.addWidget(self._lineedit) self.setFocusProxy(self._lineedit) self._init_key_label() if config.get('ui', 'prompt-filebrowser'): self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) @pyqtSlot(str) def _set_fileview_root(self, path, *, tabbed=False): """Set the root path for the file display.""" separators = os.sep if os.altsep is not None: separators += os.altsep dirname = os.path.dirname(path) try: if not path: pass elif path in separators and os.path.isdir(path): # Input "/" -> don't strip anything pass elif path[-1] in separators and os.path.isdir(path): # Input like /foo/bar/ -> show /foo/bar/ contents path = path.rstrip(separators) elif os.path.isdir(dirname) and not tabbed: # Input like /foo/ba -> show /foo contents path = dirname else: return except OSError: log.prompt.exception("Failed to get directory information") return root = self._file_model.setRootPath(path) self._file_view.setRootIndex(root) @pyqtSlot(QModelIndex) def _insert_path(self, index, *, clicked=True): """Handle an element selection. Args: index: The QModelIndex of the selected element. clicked: Whether the element was clicked. """ path = os.path.normpath(self._file_model.filePath(index)) if clicked: path += os.sep else: # On Windows, when we have C:\foo and tab over .., we get C:\ path = path.rstrip(os.sep) log.prompt.debug('Inserting path {}'.format(path)) self._lineedit.setText(path) self._lineedit.setFocus() self._set_fileview_root(path, tabbed=True) if clicked: # Avoid having a ..-subtree highlighted self._file_view.setCurrentIndex(QModelIndex()) def _init_fileview(self): self._file_view = QTreeView(self) self._file_model = QFileSystemModel(self) self._file_view.setModel(self._file_model) self._file_view.clicked.connect(self._insert_path) if config.get('ui', 'prompt-filebrowser'): self._vbox.addWidget(self._file_view) else: self._file_view.hide() # Only show name self._file_view.setHeaderHidden(True) for col in range(1, 4): self._file_view.setColumnHidden(col, True) # Nothing selected initially self._file_view.setCurrentIndex(QModelIndex()) # The model needs to be sorted so we get the correct first/last index self._file_model.directoryLoaded.connect( lambda: self._file_model.sort(0)) def accept(self, value=None): text = value if value is not None else self._lineedit.text() text = downloads.transform_path(text) if text is None: message.error("Invalid filename") return False self.question.answer = text return True def item_focus(self, which): # This duplicates some completion code, but I don't see a nicer way... assert which in ['prev', 'next'], which selmodel = self._file_view.selectionModel() parent = self._file_view.rootIndex() first_index = self._file_model.index(0, 0, parent) row = self._file_model.rowCount(parent) - 1 last_index = self._file_model.index(row, 0, parent) if not first_index.isValid(): # No entries return assert last_index.isValid() idx = selmodel.currentIndex() if not idx.isValid(): # No item selected yet idx = last_index if which == 'prev' else first_index elif which == 'prev': idx = self._file_view.indexAbove(idx) else: assert which == 'next', which idx = self._file_view.indexBelow(idx) # wrap around if we arrived at beginning/end if not idx.isValid(): idx = last_index if which == 'prev' else first_index selmodel.setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self._insert_path(idx, clicked=False) def _allowed_commands(self): return [('prompt-accept', 'Accept'), ('leave-mode', 'Abort')]
def _mock_view_index(model, category_idx, child_idx, qtbot): """Create a tree view from a model and set the current index. Args: model: model to create a fake view for. category_idx: index of the category to select. child_idx: index of the child item under that category to select. """ view = QTreeView() qtbot.add_widget(view) view.setModel(model) idx = model.indexFromItem(model.item(category_idx).child(child_idx)) view.setCurrentIndex(idx) return view
class QTreeSelection(QToolButton): currentIndexChanged = pyqtSignal(QModelIndex, QModelIndex) currentDataChanged = pyqtSignal(object) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setPopupMode(QToolButton.MenuButtonPopup) self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.tree = QTreeView(self) self.tree.setMinimumWidth(640) self.tree.setSelectionMode(QTreeView.SingleSelection) self.tree.setSelectionBehavior(QTreeView.SelectRows) act = QWidgetAction(self) act.setDefaultWidget(self.tree) self.menu = QMenu(self) self.menu.addAction(act) self.setMenu(self.menu) self.clicked.connect(self.showMenu) def _currentIndexChanged(self, newindex, oldindex): self.menu.close() selected = newindex.sibling(newindex.row(), 0) display = selected.data(Qt.DisplayRole) icon = selected.data(Qt.DecorationRole) self.setText(display or "") self.setIcon(icon or QIcon()) self.currentIndexChanged.emit(newindex, oldindex) self.currentDataChanged.emit(newindex.data(Qt.UserRole)) def currentIndex(self): return self.tree.currentIndex() def currentData(self): return self.currentIndex().data(Qt.UserRole) def setCurrentIndex(self, index): return self.tree.setCurrentIndex(index) def model(self): return self.tree.model() def setModel(self, model): self.tree.setModel(model) self.tree.selectionModel().currentChanged.connect( self._currentIndexChanged) def keyPressEvent(self, event): if (event.key() in (Qt.Key_Up, Qt.Key_Down) and not (event.modifiers() & (Qt.ShiftModifier | Qt.AltModifier | Qt.ControlModifier))): self.tree.keyPressEvent(event) return super().keyPressEvent(event)
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect( self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel( self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect( self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect( self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction( core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action( "mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu( ).aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos))
class Widget(QWidget): def __init__(self, panel): super(Widget, self).__init__(panel) layout = QVBoxLayout() self.setLayout(layout) layout.setSpacing(0) self.searchEntry = SearchLineEdit() self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu) self.textView = QTextBrowser() applyButton = QToolButton(autoRaise=True) editButton = QToolButton(autoRaise=True) addButton = QToolButton(autoRaise=True) self.menuButton = QPushButton(flat=True) menu = QMenu(self.menuButton) self.menuButton.setMenu(menu) splitter = QSplitter(Qt.Vertical) top = QHBoxLayout() layout.addLayout(top) splitter.addWidget(self.treeView) splitter.addWidget(self.textView) layout.addWidget(splitter) splitter.setSizes([200, 100]) splitter.setCollapsible(0, False) top.addWidget(self.searchEntry) top.addWidget(applyButton) top.addSpacing(10) top.addWidget(addButton) top.addWidget(editButton) top.addWidget(self.menuButton) # action generator for actions added to search entry def act(slot, icon=None): a = QAction(self, triggered=slot) self.addAction(a) a.setShortcutContext(Qt.WidgetWithChildrenShortcut) icon and a.setIcon(icons.get(icon)) return a # hide if ESC pressed in lineedit a = act(self.slotEscapePressed) a.setShortcut(QKeySequence(Qt.Key_Escape)) # import action a = self.importAction = act(self.slotImport, 'document-open') menu.addAction(a) # export action a = self.exportAction = act(self.slotExport, 'document-save-as') menu.addAction(a) # apply button a = self.applyAction = act(self.slotApply, 'edit-paste') applyButton.setDefaultAction(a) menu.addSeparator() menu.addAction(a) # add button a = self.addAction_ = act(self.slotAdd, 'list-add') a.setShortcut(QKeySequence(Qt.Key_Insert)) addButton.setDefaultAction(a) menu.addSeparator() menu.addAction(a) # edit button a = self.editAction = act(self.slotEdit, 'document-edit') a.setShortcut(QKeySequence(Qt.Key_F2)) editButton.setDefaultAction(a) menu.addAction(a) # set shortcut action a = self.shortcutAction = act(self.slotShortcut, 'preferences-desktop-keyboard-shortcuts') menu.addAction(a) # delete action a = self.deleteAction = act(self.slotDelete, 'list-remove') a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete)) menu.addAction(a) # restore action a = self.restoreAction = act(self.slotRestore) menu.addSeparator() menu.addAction(a) # help button a = self.helpAction = act(self.slotHelp, 'help-contents') menu.addSeparator() menu.addAction(a) self.treeView.setSelectionBehavior(QTreeView.SelectRows) self.treeView.setSelectionMode(QTreeView.ExtendedSelection) self.treeView.setRootIsDecorated(False) self.treeView.setAllColumnsShowFocus(True) self.treeView.setModel(model.model()) self.treeView.setCurrentIndex(QModelIndex()) # signals self.searchEntry.returnPressed.connect(self.slotReturnPressed) self.searchEntry.textChanged.connect(self.updateFilter) self.treeView.doubleClicked.connect(self.slotDoubleClicked) self.treeView.customContextMenuRequested.connect(self.showContextMenu) self.treeView.selectionModel().currentChanged.connect(self.updateText) self.treeView.model().dataChanged.connect(self.updateFilter) # highlight text self.highlighter = highlight.Highlighter(self.textView.document()) # complete on snippet variables self.searchEntry.setCompleter(QCompleter([ ':icon', ':indent', ':menu', ':name', ':python', ':selection', ':set', ':symbol', ':template', ':template-run'], self.searchEntry)) self.readSettings() app.settingsChanged.connect(self.readSettings) app.translateUI(self) self.updateColumnSizes() self.setAcceptDrops(True) def dropEvent(self, ev): if not ev.source() and ev.mimeData().hasUrls(): filename = ev.mimeData().urls()[0].toLocalFile() if filename: ev.accept() from . import import_export import_export.load(filename, self) def dragEnterEvent(self, ev): if not ev.source() and ev.mimeData().hasUrls(): ev.accept() def translateUI(self): try: self.searchEntry.setPlaceholderText(_("Search...")) except AttributeError: pass # not in Qt 4.6 shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText) self.menuButton.setText(_("&Menu")) self.addAction_.setText(_("&Add...")) self.addAction_.setToolTip( _("Add a new snippet. ({key})").format(key=shortcut(self.addAction_))) self.editAction.setText(_("&Edit...")) self.editAction.setToolTip( _("Edit the current snippet. ({key})").format(key=shortcut(self.editAction))) self.shortcutAction.setText(_("Configure Keyboard &Shortcut...")) self.deleteAction.setText(_("&Remove")) self.deleteAction.setToolTip(_("Remove the selected snippets.")) self.applyAction.setText(_("A&pply")) self.applyAction.setToolTip(_("Apply the current snippet.")) self.importAction.setText(_("&Import...")) self.importAction.setToolTip(_("Import snippets from a file.")) self.exportAction.setText(_("E&xport...")) self.exportAction.setToolTip(_("Export snippets to a file.")) self.restoreAction.setText(_("Restore &Built-in Snippets...")) self.restoreAction.setToolTip( _("Restore deleted or changed built-in snippets.")) self.helpAction.setText(_("&Help")) self.searchEntry.setToolTip(_( "Enter text to search in the snippets list.\n" "See \"What's This\" for more information.")) self.searchEntry.setWhatsThis(''.join(map("<p>{0}</p>\n".format, ( _("Enter text to search in the snippets list, and " "press Enter to apply the currently selected snippet."), _("If the search text fully matches the value of the '{name}' variable " "of a snippet, that snippet is selected.").format(name="name"), _("If the search text starts with a colon ':', the rest of the " "search text filters snippets that define the given variable. " "After a space a value can also be entered, snippets will then " "match if the value of the given variable contains the text after " "the space."), _("E.g. entering {menu} will show all snippets that are displayed " "in the insert menu.").format(menu="<code>:menu</code>"), )))) def sizeHint(self): return self.parent().mainwindow().size() / 4 def readSettings(self): data = textformats.formatData('editor') self.textView.setFont(data.font) self.textView.setPalette(data.palette()) def showContextMenu(self, pos): """Called when the user right-clicks the tree view.""" self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos)) def slotReturnPressed(self): """Called when the user presses Return in the search entry. Applies current snippet.""" name = self.currentSnippet() if name: view = self.parent().mainwindow().currentView() insert.insert(name, view) self.parent().hide() # make configurable? view.setFocus() def slotEscapePressed(self): """Called when the user presses ESC in the search entry. Hides the panel.""" self.parent().hide() self.parent().mainwindow().currentView().setFocus() def slotDoubleClicked(self, index): name = self.treeView.model().name(index) view = self.parent().mainwindow().currentView() insert.insert(name, view) def slotAdd(self): """Called when the user wants to add a new snippet.""" edit.Edit(self, None) def slotEdit(self): """Called when the user wants to edit a snippet.""" name = self.currentSnippet() if name: edit.Edit(self, name) def slotShortcut(self): """Called when the user selects the Configure Shortcut action.""" from widgets import shortcuteditdialog name = self.currentSnippet() if name: collection = self.parent().snippetActions action = actions.action(name, None, collection) default = collection.defaults().get(name) mgr = actioncollectionmanager.manager(self.parent().mainwindow()) cb = mgr.findShortcutConflict dlg = shortcuteditdialog.ShortcutEditDialog(self, cb, (collection, name)) if dlg.editAction(action, default): mgr.removeShortcuts(action.shortcuts()) collection.setShortcuts(name, action.shortcuts()) self.treeView.update() def slotDelete(self): """Called when the user wants to delete the selected rows.""" rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()), reverse=True) if rows: for row in rows: name = self.treeView.model().names()[row] self.parent().snippetActions.setShortcuts(name, []) self.treeView.model().removeRow(row) self.updateFilter() def slotApply(self): """Called when the user clicks the apply button. Applies current snippet.""" name = self.currentSnippet() if name: view = self.parent().mainwindow().currentView() insert.insert(name, view) def slotImport(self): """Called when the user activates the import action.""" filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files")) caption = app.caption(_("dialog title", "Import Snippets")) filename = None filename = QFileDialog.getOpenFileName(self, caption, filename, filetypes)[0] if filename: from . import import_export import_export.load(filename, self) def slotExport(self): """Called when the user activates the export action.""" allrows = [row for row in range(model.model().rowCount()) if not self.treeView.isRowHidden(row, QModelIndex())] selectedrows = [i.row() for i in self.treeView.selectedIndexes() if i.column() == 0 and i.row() in allrows] names = self.treeView.model().names() names = [names[row] for row in selectedrows or allrows] filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files")) n = len(names) caption = app.caption(_("dialog title", "Export {num} Snippet", "Export {num} Snippets", n).format(num=n)) filename = QFileDialog.getSaveFileName(self, caption, None, filetypes)[0] if filename: from . import import_export try: import_export.save(names, filename) except (IOError, OSError) as e: QMessageBox.critical(self, _("Error"), _( "Can't write to destination:\n\n{url}\n\n{error}").format( url=filename, error=e.strerror)) def slotRestore(self): """Called when the user activates the Restore action.""" from . import restore dlg = restore.RestoreDialog(self) dlg.setWindowModality(Qt.WindowModal) dlg.populate() dlg.show() dlg.finished.connect(dlg.deleteLater) def slotHelp(self): """Called when the user clicks the small help button.""" userguide.show("snippets") def currentSnippet(self): """Returns the name of the current snippet if it is visible.""" row = self.treeView.currentIndex().row() if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()): return self.treeView.model().names()[row] def updateFilter(self): """Called when the text in the entry changes, updates search results.""" text = self.searchEntry.text() ltext = text.lower() filterVars = text.startswith(':') if filterVars: try: fvar, fval = text[1:].split(None, 1) fhide = lambda v: v.get(fvar) in (True, None) or fval not in v.get(fvar) except ValueError: fvar = text[1:].strip() fhide = lambda v: not v.get(fvar) for row in range(self.treeView.model().rowCount()): name = self.treeView.model().names()[row] nameid = snippets.get(name).variables.get('name', '') if filterVars: hide = fhide(snippets.get(name).variables) elif nameid == text: i = self.treeView.model().createIndex(row, 0) self.treeView.selectionModel().setCurrentIndex(i, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) hide = False elif nameid.lower().startswith(ltext): hide = False elif ltext in snippets.title(name).lower(): hide = False else: hide = True self.treeView.setRowHidden(row, QModelIndex(), hide) self.updateText() def updateText(self): """Called when the current snippet changes.""" name = self.currentSnippet() self.textView.clear() if name: s = snippets.get(name) self.highlighter.setPython('python' in s.variables) self.textView.setPlainText(s.text) def updateColumnSizes(self): self.treeView.resizeColumnToContents(0) self.treeView.resizeColumnToContents(1)
class AddBookmarkDialog(QDialog, Ui_AddBookmarkDialog): """ Class implementing a dialog to add a bookmark or a bookmark folder. """ def __init__(self, parent=None, bookmarksManager=None): """ Constructor @param parent reference to the parent widget (QWidget) @param bookmarksManager reference to the bookmarks manager object (BookmarksManager) """ super(AddBookmarkDialog, self).__init__(parent) self.setupUi(self) self.__bookmarksManager = bookmarksManager self.__addedNode = None self.__addFolder = False if self.__bookmarksManager is None: import Helpviewer.HelpWindow self.__bookmarksManager = \ Helpviewer.HelpWindow.HelpWindow.bookmarksManager() self.__proxyModel = AddBookmarkProxyModel(self) model = self.__bookmarksManager.bookmarksModel() self.__proxyModel.setSourceModel(model) self.__treeView = QTreeView(self) self.__treeView.setModel(self.__proxyModel) self.__treeView.expandAll() self.__treeView.header().setStretchLastSection(True) self.__treeView.header().hide() self.__treeView.setItemsExpandable(False) self.__treeView.setRootIsDecorated(False) self.__treeView.setIndentation(10) self.__treeView.show() self.locationCombo.setModel(self.__proxyModel) self.locationCombo.setView(self.__treeView) self.addressEdit.setInactiveText(self.tr("Url")) self.nameEdit.setInactiveText(self.tr("Title")) self.resize(self.sizeHint()) def setUrl(self, url): """ Public slot to set the URL of the new bookmark. @param url URL of the bookmark (string) """ self.addressEdit.setText(url) self.resize(self.sizeHint()) def url(self): """ Public method to get the URL of the bookmark. @return URL of the bookmark (string) """ return self.addressEdit.text() def setTitle(self, title): """ Public method to set the title of the new bookmark. @param title title of the bookmark (string) """ self.nameEdit.setText(title) def title(self): """ Public method to get the title of the bookmark. @return title of the bookmark (string) """ return self.nameEdit.text() def setDescription(self, description): """ Public method to set the description of the new bookmark. @param description description of the bookamrk (string) """ self.descriptionEdit.setPlainText(description) def description(self): """ Public method to get the description of the bookmark. @return description of the bookamrk (string) """ return self.descriptionEdit.toPlainText() def setCurrentIndex(self, idx): """ Public method to set the current index. @param idx current index to be set (QModelIndex) """ proxyIndex = self.__proxyModel.mapFromSource(idx) self.__treeView.setCurrentIndex(proxyIndex) self.locationCombo.setCurrentIndex(proxyIndex.row()) def currentIndex(self): """ Public method to get the current index. @return current index (QModelIndex) """ idx = self.locationCombo.view().currentIndex() idx = self.__proxyModel.mapToSource(idx) return idx def setFolder(self, folder): """ Public method to set the dialog to "Add Folder" mode. @param folder flag indicating "Add Folder" mode (boolean) """ self.__addFolder = folder if folder: self.setWindowTitle(self.tr("Add Folder")) self.addressEdit.setVisible(False) else: self.setWindowTitle(self.tr("Add Bookmark")) self.addressEdit.setVisible(True) self.resize(self.sizeHint()) def isFolder(self): """ Public method to test, if the dialog is in "Add Folder" mode. @return flag indicating "Add Folder" mode (boolean) """ return self.__addFolder def addedNode(self): """ Public method to get a reference to the added bookmark node. @return reference to the added bookmark node (BookmarkNode) """ return self.__addedNode def accept(self): """ Public slot handling the acceptance of the dialog. """ if (not self.__addFolder and not self.addressEdit.text()) or \ not self.nameEdit.text(): super(AddBookmarkDialog, self).accept() return from .BookmarkNode import BookmarkNode idx = self.currentIndex() if not idx.isValid(): idx = self.__bookmarksManager.bookmarksModel().index(0, 0) parent = self.__bookmarksManager.bookmarksModel().node(idx) if self.__addFolder: type_ = BookmarkNode.Folder else: type_ = BookmarkNode.Bookmark bookmark = BookmarkNode(type_) bookmark.title = self.nameEdit.text() if not self.__addFolder: bookmark.url = self.addressEdit.text() bookmark.desc = self.descriptionEdit.toPlainText() self.__bookmarksManager.addBookmark(parent, bookmark) self.__addedNode = bookmark super(AddBookmarkDialog, self).accept()
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel(self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos))
class PYCFScape(QMainWindow): def __init__(self): super().__init__() # We determine where the script is placed, for acessing files we use (such as the icon) self.we_live_in = sys.argv[0] self.we_live_in = os.path.split(self.we_live_in)[0] self.build_interface() self.opened_file = None # stores the filepath self.opened_vpk = None # stores the opened vpk self.internal_directory_understanding = { } # a dictionary version of the paths self.vpk_loaded = False # whether or not we have a vpk file open self.export_paths = [ ] # Paths we want to export when file->export is selected def build_interface(self): self.setWindowTitle("PYCFScape") self.setWindowIcon( QIcon(os.path.join(self.we_live_in, 'res/Icon64.ico'))) self.main_content = QWidget() self.main_content_layout = QVBoxLayout() self.main_content.setLayout(self.main_content_layout) # set up the interface parts self.output_console = QTextEdit() # Will basically just copy stdout self.output_console.setReadOnly(True) sys.stdout = bob_logger(self.output_console, False, sys.stdout) sys.stderr = bob_logger(self.output_console, True, sys.stderr) self.vpk_tree = QTreeView() # Displays the tree of the vpk's content self.vpk_tree_model = QStandardItemModel(self.vpk_tree) self.vpk_tree.setModel(self.vpk_tree_model) self.vpk_tree._mousePressEvent = self.vpk_tree.mousePressEvent # store it so we can call it self.vpk_tree.mousePressEvent = self.vpk_tree_item_clicked self.vpk_tree.setHeaderHidden(True) # We use a QTreeView to also display headers. # We however, still treat it as a list view. self.dir_list = QTreeView( ) # Displays the list of the current vpk's directory's content self.dir_list_model = QStandardItemModel(self.dir_list) self.dir_list.setModel(self.dir_list_model) self.dir_list.doubleClicked.connect(self.dir_list_item_double_clicked) self.dir_list_model.itemChanged.connect(self.dir_list_item_updated) self.dir_list.setContextMenuPolicy(Qt.CustomContextMenu) self.dir_list.customContextMenuRequested.connect( self.dir_list_context_menu) self.dir_list_model.setColumnCount(2) self.dir_list_model.setHorizontalHeaderLabels(["Name", "Type"]) self.dir_list.header().resizeSection(0, 250) # The tool bar - WARNING: MESSY CODE! self.actions_toolbar = self.addToolBar("") # OPEN BUTTON self.open_button = self.actions_toolbar.addAction( QIcon.fromTheme("document-open"), "Open File") self.open_button.triggered.connect(self.open_file) self.actions_toolbar.addSeparator() self.back_button = QToolButton() # GO BACK BUTTON self.back_button.setIcon(QIcon.fromTheme("go-previous")) self.back_button.setDisabled(True) self.actions_toolbar.addWidget(self.back_button) self.forward_button = QToolButton() # GO FORWARD BUTTON self.forward_button.setIcon(QIcon.fromTheme("go-next")) self.forward_button.setDisabled(True) self.actions_toolbar.addWidget(self.forward_button) self.up_button = QToolButton() # GO UP BUTTON self.up_button.setIcon(QIcon.fromTheme("go-up")) self.up_button.setDisabled(True) self.actions_toolbar.addWidget(self.up_button) self.actions_toolbar.addSeparator() # FIND BUTTON self.search_button = self.actions_toolbar.addAction( QIcon.fromTheme("system-search"), "Find in file") self.search_button.setDisabled(True) self.search_button.triggered.connect(self.search_for_file) self.actions_toolbar.addSeparator() # EXPORT BUTTON self.export_button = self.actions_toolbar.addAction( QIcon.fromTheme("extract-archive"), "Export Selection") self.export_button.setDisabled(False) self.export_button.triggered.connect(self.export_selection) self.main_content_layout.addWidget(self.actions_toolbar) # now we want the menubar self.menu_bar = self.menuBar() self.file_menu = self.menu_bar.addMenu("&File") self.edit_menu = self.menu_bar.addMenu("&Edit") self.menu_bar.addSeparator() self.help_menu = self.menu_bar.addMenu("&Help") self.file_menu.addActions([self.open_button]) self.close_button = self.file_menu.addAction( QIcon.fromTheme("document-close"), "Close File" # bit redundant, i actually see no use ) self.close_button.triggered.connect(self.close_vpk) self.file_menu.addSeparator() self.file_menu.addAction(QIcon.fromTheme("application-exit"), "Exit").triggered.connect(self.close) self.edit_menu.addActions([self.search_button]) self.help_menu.addAction(QIcon.fromTheme("help-about"), "About && License").triggered.connect( self.about) # the statusbar self.status_bar = self.statusBar() # set up the splitters # horizontal self.horz_splitter_container = QWidget() self.horz_splitter_container_layout = QVBoxLayout() self.horz_splitter_container.setLayout( self.horz_splitter_container_layout) self.horz_splitter = QSplitter(Qt.Horizontal) self.horz_splitter.addWidget(self.vpk_tree) self.horz_splitter.addWidget(self.dir_list) self.horz_splitter.setSizes([50, 200]) self.horz_splitter_container_layout.addWidget(self.horz_splitter) # vertical self.vert_splitter = QSplitter(Qt.Vertical) self.vert_splitter.addWidget(self.horz_splitter_container) self.vert_splitter.addWidget(self.output_console) self.vert_splitter.setSizes([200, 50]) self.main_content_layout.addWidget(self.vert_splitter) self.setCentralWidget(self.main_content) self.show() ## # Update Functions ## def update_console(self, text, is_stdout): colour = Qt.Red if not is_stdout else Qt.black self.output_console.setTextColor(colour) self.output_console.moveCursor(QTextCursor.End) self.output_console.insertPlainText(text) def update_interface(self): # update the tree view self.update_vpk_tree() # update the list view self.update_dir_list() self.search_button.setDisabled(not self.vpk_loaded) def update_vpk_tree(self): self.vpk_tree_model.removeRows(0, self.vpk_tree_model.rowCount()) if self.opened_vpk: self.tree_magic(self.internal_directory_understanding) def update_dir_list(self): self.dir_list_model.removeRows(0, self.dir_list_model.rowCount( )) # remove anyway (for instances such as opening a new file) selected = self.vpk_tree.selectedIndexes() if not selected: return selected = selected[0] selected_item = self.vpk_tree_model.itemFromIndex(selected) if not selected_item: return if not selected_item.is_dir: return path = selected_item.path understanding = self.get_understanding_from( self.internal_directory_understanding, path) self.list_magic(understanding, path) ## # Events ## def vpk_tree_item_clicked(self, event): self.vpk_tree._mousePressEvent(event) index = self.vpk_tree.indexAt(event.pos()) # We can rather safely assume any items in the vpk tree will have OUR special attributes if index.isValid(): item = self.vpk_tree_model.itemFromIndex(index) print("selected", item.path) if item.is_dir: self.update_dir_list() def dir_list_item_double_clicked(self, index): item = self.dir_list_model.itemFromIndex(index) if not item.column() == 0: return print("double clicked", item.path) if item.is_dir: print("is dir") # this is probably a REALLY **REALLY** awful way of doing this, but you're welcome to PR a better way. :) index_in_tree = self.find_in_model(self.vpk_tree_model, item.path) if index_in_tree.isValid(): self.vpk_tree.setCurrentIndex(index_in_tree) self.update_dir_list() else: self.status_bar.showMessage(MSG_EXPORT) # we clearly wanna export the file and open it bits = self.opened_vpk[item.path[1:]].read() path = compat.write_to_temp(bits, os.path.split(item.path)[1]) print("Wrote to", path) compat.tell_os_open(path) self.status_bar.clearMessage() def dir_list_item_updated(self, item): if item.checkState() == Qt.Checked: if not item.is_dir: self.export_paths.append(item.path) else: index_in_tree = self.find_in_model(self.vpk_tree_model, item.path) if index_in_tree.isValid(): paths = self.recursively_get_paths_from_dir_index_item( index_in_tree, self.vpk_tree_model) self.export_paths += paths elif item.checkState() == Qt.Unchecked: if not item.is_dir: if item.path in self.export_paths: self.export_paths.remove(item.path) else: index_in_tree = self.find_in_model(self.vpk_tree_model, item.path) if index_in_tree.isValid(): paths = self.recursively_get_paths_from_dir_index_item( index_in_tree, self.vpk_tree_model) for path in paths: if path in self.export_paths: self.export_paths.remove(path) ## # The next 3 functions are the original pathtodir but merged with the program ## def nested_dict(self): return defaultdict(self.nested_dict) def nested_dict_to_regular(self, d): if not isinstance(d, defaultdict): return d return {k: self.nested_dict_to_regular(v) for k, v in d.items()} def understand_directories(self, list_of_paths): use_dict = defaultdict(self.nested_dict) for path in list_of_paths: parts = path.split('/') if parts: marcher = use_dict for key in parts[:-1]: marcher = marcher[key] marcher[parts[-1]] = parts[-1] return dict(use_dict) def get_understanding_from(self, understanding, path): keys = path.split('/') if keys[0] == '': keys = keys[1:] if keys: marcher = understanding for key in keys: marcher = marcher[key] # we can now assume marcher is the understanding we want return marcher ## # Utility ## def tree_magic(self, dict_of_things, parent=None, root=''): if not parent: parent = self.vpk_tree_model # Stack overflow 14478170 for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)): path = root + '/{}'.format(thing) thing_item = QStandardItem() thing_item.setText(thing) thing_item.setEditable(False) thing_item.path = path thing_item.is_dir = False icon, _ = self.get_info_from_path(path) thing_item.setIcon(icon) if isinstance(dict_of_things[thing], dict): thing_item.setIcon(QIcon.fromTheme("folder")) self.tree_magic(dict_of_things[thing], thing_item, path) thing_item.is_dir = True parent.appendRow(thing_item) def list_magic(self, dict_of_things, root=''): # like tree_magic but operates on dir_list for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)): path = root + '/{}'.format(thing) thing_item = QStandardItem() thing_item.setText(thing) thing_item.setEditable(False) thing_item.setCheckable(True) thing_item_type = QStandardItem( ) # This doesn't actually do anything but convey more information to the user thing_item_type.setEditable(False) if path in self.export_paths: thing_item.setCheckState(Qt.Checked) thing_item.path = path thing_item.is_dir = False icon, desc = self.get_info_from_path(path) thing_item.setIcon(icon) thing_item_type.setText(desc) if isinstance(dict_of_things[thing], dict): thing_item.setIcon(QIcon.fromTheme("folder")) thing_item.is_dir = True thing_item_type.setText("Directory") self.dir_list_model.appendRow([thing_item, thing_item_type]) def get_info_from_path(self, path): # returns the icon AND description string icon = None desc = None # first we test against mimetype # probably bad code, but it works! thing_mimetype = mimetypes.guess_type(path)[0] if thing_mimetype: if thing_mimetype[:6] == "audio/": icon = QIcon.fromTheme("audio-x-generic") elif thing_mimetype[:12] == "application/": icon = QIcon.fromTheme("application-x-generic") elif thing_mimetype[:5] == "text/": icon = QIcon.fromTheme("text-x-generic") elif thing_mimetype[:6] == "image/": icon = QIcon.fromTheme("image-x-generic") elif thing_mimetype[:6] == "video/": icon = QIcon.fromTheme("video-x-generic") desc = thing_mimetype # well ok, maybe that didn't work, let's test the filepath ourselves. file_ext = os.path.splitext(path)[1] if file_ext: if file_ext in [".vtf"]: icon = QIcon.fromTheme("image-x-generic") desc = "Valve Texture File" elif file_ext in [".vmt"]: icon = QIcon.fromTheme("text-x-generic") desc = "Valve Material File" elif file_ext in [ ".pcf" ]: # we can safely assume they are not fonts in this context, but rather icon = QIcon.fromTheme("text-x-script") desc = "Valve DMX Implementation" # TODO: is there a better name elif file_ext in [".bsp"]: icon = QIcon.fromTheme("text-x-generic") desc = "Binary Space Partition" elif file_ext in [".res"]: icon = QIcon.fromTheme("text-x-generic") desc = "Valve Key Value" elif file_ext in [".vcd"]: icon = QIcon.fromTheme("text-x-generic") desc = "Valve Choreography Data" if not icon: # If all else fails, display SOMETHING icon = QIcon.fromTheme("text-x-generic") if not desc: desc = "File" return icon, desc def find_in_model(self, model: QStandardItemModel, path): for i in range(0, model.rowCount()): index_in_tree = model.index(i, 0) if model.itemFromIndex(index_in_tree).path == path: return index_in_tree if model.itemFromIndex(index_in_tree).is_dir: index_in_tree = self.find_in_model_parent(model, path, parent=index_in_tree) if not index_in_tree.isValid(): continue if model.itemFromIndex(index_in_tree).path == path: return index_in_tree def find_in_model_parent(self, model: QStandardItemModel, path, parent): for i in range(0, model.rowCount(parent)): index_in_tree = model.index(i, 0, parent) if model.itemFromIndex(index_in_tree).path == path: return index_in_tree if model.itemFromIndex(index_in_tree).is_dir: index_in_tree = self.find_in_model_parent(model, path, parent=index_in_tree) if not index_in_tree.isValid(): continue if model.itemFromIndex(index_in_tree).path == path: return index_in_tree return QModelIndex() def export_file(self, path, out_dir): filepath = os.path.split(path)[0] if not os.path.isdir('{}{}'.format(out_dir, filepath)): os.makedirs('{}{}'.format(out_dir, filepath)) print("Attempting to export to", "{}{}".format(out_dir, filepath), "from", path[1:], "in the vpk") outcontents = self.opened_vpk.get_file(path[1:]).read() outfile = open('{}{}'.format(out_dir, path), 'wb') outfile.write(outcontents) outfile.close() def recursively_get_paths_from_dir_index_item(self, index_in_tree, model): paths = [] for i in range( self.vpk_tree_model.itemFromIndex(index_in_tree).rowCount()): index = self.vpk_tree_model.index(i, 0, index_in_tree) index_item = self.vpk_tree_model.itemFromIndex(index) if not index_item.is_dir: paths.append(index_item.path) else: paths += self.recursively_get_paths_from_dir_index_item( index, model) return paths def close_vpk(self): # We trash everything! self.vpk_loaded = False self.opened_file = None self.opened_vpk = {} self.export_paths = [] self.internal_directory_understanding = {} self.status_bar.showMessage(MSG_UPDATE_UI) self.update_interface() self.status_bar.clearMessage() def open_vpk(self, vpk_path): if self.vpk_loaded: # if we already have a file open, close it. self.close_vpk() self.status_bar.showMessage(MSG_OPEN_VPK) if not os.path.exists(vpk_path): print( "Attempted to open {}, which doesn't exist.".format(vpk_path)) return self.opened_file = vpk_path try: self.opened_vpk = vpk.open(vpk_path) except Exception as e: print("Ran into an error from the VPK Library.") sys.stdout.write(str(e)) self.error_box(str(e)) return self.vpk_loaded = True self.status_bar.showMessage(MSG_UNDERSTAND_VPK) # Now we attempt to understand the vpk self.internal_directory_understanding = self.understand_directories( self.opened_vpk) self.status_bar.showMessage(MSG_UPDATE_UI) self.update_interface() self.status_bar.clearMessage() ## # Dialogs ## def open_dialog(self): fn = QFileDialog.getOpenFileName(None, "Open Package", str(pathlib.Path.home()), filter=("Valve Pak Files (*.vpk)")) return fn def open_dir_dialog(self, title="Open Directory"): fn = QFileDialog.getExistingDirectory(None, title, str(pathlib.Path.home())) return fn def error_box(self, text="...", title="Error"): box = QMessageBox() box.setIcon(QMessageBox.Critical) box.setText(text) box.setWindowTitle(title) box.setStandardButtons(QMessageBox.Ok) return box.exec() def info_box(self, text="...", title="Info"): box = QMessageBox() box.setIcon(QMessageBox.Information) box.setText(text) box.setWindowTitle(title) box.setStandardButtons(QMessageBox.Ok) return box.exec() def dir_list_context_menu(self, event): menu = QMenu(self) selected = self.dir_list.selectedIndexes() if not selected: return selected = selected[0] selected_item = self.dir_list_model.itemFromIndex(selected) path = selected_item.path extract = menu.addAction(QIcon.fromTheme("extract-archive"), "Extract") validate = menu.addAction(QIcon.fromTheme("view-certificate"), "Validate") # TODO: better validation icon menu.addSeparator() gotodirectory = menu.addAction(QIcon.fromTheme("folder"), "Go To Directory") menu.addSeparator() properties = menu.addAction(QIcon.fromTheme("settings-configure"), "Properties") extract.setDisabled(selected_item.is_dir) validate.setDisabled(selected_item.is_dir) action = menu.exec_(self.dir_list.mapToGlobal(event)) if action == extract: self.export_selected_file(path) elif action == validate: self.validate_file(path) elif action in [gotodirectory, properties]: self.info_box( "I'm not sure what this does in the original GCFScape.\nIf you know, please make an issue on github!" ) def about(self): box = QMessageBox() box.setWindowTitle( "About PYCFScape") # TODO: what do we version and how box.setText("""PYCFScape Version 0 MIT LICENSE V1.00 OR LATER Python {} QT {} AUTHORS (Current Version): ACBob - https://acbob.gitlab.io Project Homepage https://github.com/acbob/pycfscape""".format(sys.version, QT_VERSION_STR)) box.setIcon(QMessageBox.Information) box.exec() ## # Actions ## def open_file(self): self.status_bar.showMessage(MSG_USER_WAIT) fn = self.open_dialog()[0] if not fn: self.status_bar.clearMessage() return self.open_vpk(fn) def search_for_file(self, event): print(event) def export_selection(self): if not self.export_paths: self.info_box( "You can't export nothing!\n(Please select some items to export.)" ) return self.status_bar.showMessage(MSG_USER_WAIT) output_dir = self.open_dir_dialog("Export to...") if output_dir: self.status_bar.showMessage(MSG_EXPORT) print("attempting export to", output_dir) for file in self.export_paths: self.export_file(file, output_dir) self.status_bar.clearMessage() def export_selected_file(self, file): self.status_bar.showMessage(MSG_USER_WAIT) output_dir = self.open_dir_dialog("Export to...") if output_dir: self.status_bar.showMessage(MSG_EXPORT) print("attempting export to", output_dir) self.export_file(file, output_dir) self.status_bar.clearMessage() def validate_file(self, file): filetoverify = self.opened_vpk.get_file(file[1:]) if filetoverify: verified = filetoverify.verify() if verified: self.info_box("{} is a perfectly healthy file.".format(file), "All's good.") else: self.error_box("{} is not valid!".format(file), "Uh oh.") else: print("What? file doesn't exist? HOW IS THIS MAN")
class StatsGui(QWidget): ''' This class accepts a glue data collection object, and builds an interactive window to display basic statistics (e.g. mean, median, mode) about each dataset ''' released = QtCore.pyqtSignal(object) def __init__(self, dc): # Initialize the object as a QWidget QWidget.__init__(self) #Save the datacollection object as an attribute of class StatsGui self.dc = dc #Set the title of the main GUI window self.setWindowTitle('Statistics') # Set up dicts for row indices self.subset_dict = dict() self.component_dict = dict() self.selected_dict = dict() self.selected_indices = [] #Set up tree view and fix it to the top half of the window self.treeview = QTreeView(self) # Set the default clicking behavior to be row selection self.treeview.setSelectionBehavior(QAbstractItemView.SelectRows) # Set up expand all, collapse all, select all and deselect all buttons # Layout for expand/collapse/select/deselect layout_left_options = QHBoxLayout() self.expand_data = QToolButton(self) self.expand_data.setText("Expand all data and subsets") self.expand_data.clicked.connect(self.expandClicked) layout_left_options.addWidget(self.expand_data) self.all = QToolButton(self) self.all.setText('Select all') self.all.clicked.connect(self.allClicked) layout_left_options.addWidget(self.all) self.none = QToolButton(self) self.none.setText('Deselect all') self.none.clicked.connect(self.noneClicked) layout_left_options.addWidget(self.none) # Set default significant figures to 5 getcontext().prec = 5 # Set up past selected items self.past_selected = [] # Sort by subsets as a default self.sortBySubsets() # Set up the combo box for users to choose the number of significant figures in the table # Set up bottom options layout layout_bottom_options = QHBoxLayout() self.siglabel = QLabel(self) self.siglabel.setText('Number of significant figures:') layout_bottom_options.addWidget(self.siglabel) self.sigfig = QSpinBox(self) self.sigfig.setRange(1, 10) self.sigfig.setValue(5) self.sigfig.valueChanged.connect(self.sigchange) layout_bottom_options.addWidget(self.sigfig) # Export to file button self.export = QPushButton(self) self.export.setText('Export to file') self.export.clicked.connect(self.exportToFile) layout_bottom_options.addWidget(self.export) # Set up the toggle button to switch tree sorting modes self.switch_mode = QToolButton(self) self.switch_mode.setText('Sort tree by components') self.switch_mode.clicked.connect(self.switchMode) layout_left_options.addWidget(self.switch_mode) # Add instructions to sort the table self.how = QLabel(self) self.how.setText('Click each header to sort table') layout_left_options.addWidget(self.how) #################Set up the QTableView Widget############################# self.table = QTableView(self) self.table.setSortingEnabled(True) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.table.verticalHeader().setVisible(False) #Set the table headings self.headings = ('Subset', 'Dataset', 'Component', 'Mean', 'Median', 'Minimum', 'Maximum', 'Sum') self.data_frame = pd.DataFrame(columns=self.headings) self.data_accurate = pd.DataFrame(columns=self.headings) self.model = pandasModel(self.data_frame, self.dc) self.table.setModel(self.model) layout_table = QHBoxLayout() layout_table.addWidget(self.table) layout_table.stretch(10) # Finish nesting all the layouts main_layout = QVBoxLayout() main_layout.addWidget(self.treeview) main_layout.addLayout(layout_left_options) main_layout.addLayout(layout_table) main_layout.addLayout(layout_bottom_options) self.setLayout(main_layout) # Set up dict for caching self.cache_stash = dict() def myPressedEvent(self, currentQModelIndex): ''' Every time a row (or rows) in the tree view is clicked: if it is selected, add it to the table if it is deselected, remove it from the table ''' # Get the indexes of all the selected components self.selected_indices = self.treeview.selectionModel().selectedRows() newly_selected = np.setdiff1d(self.selected_indices, self.past_selected) for index in range(0, len(newly_selected)): # Check which view mode the tree is in to get the correct indices if self.switch_mode.text() == 'Sort tree by components': data_i = newly_selected[index].parent().row() comp_i = newly_selected[index].row() subset_i = newly_selected[index].parent().parent().row() else: data_i = newly_selected[index].parent().parent().row() comp_i = newly_selected[index].parent().row() subset_i = newly_selected[index].row() - 1 is_subset = newly_selected[index].parent().parent().parent().row( ) == 1 or (self.switch_mode.text() == 'Sort tree by subsets' and subset_i != -1) # Check if its a subset and if so run subset stats if is_subset: self.runSubsetStats(subset_i, data_i, comp_i) else: # Run standard data stats self.runDataStats(data_i, comp_i) newly_dropped = np.setdiff1d(self.past_selected, self.selected_indices) for index in range(0, len(newly_dropped)): # Check which view mode the tree is in to get the correct indices if self.switch_mode.text() == 'Sort tree by components': data_i = newly_dropped[index].parent().row() comp_i = newly_dropped[index].row() subset_i = newly_dropped[index].parent().parent().row() else: data_i = newly_dropped[index].parent().parent().row() comp_i = newly_dropped[index].parent().row() subset_i = newly_dropped[index].row() - 1 is_subset = newly_dropped[index].parent().parent().parent().row( ) == 1 or (self.switch_mode.text() == 'Sort tree by subsets' and subset_i != -1) if is_subset: try: # Get the indices that match the component, dataset, and subset requirements idx_c = np.where(self.data_frame['Component'] == self.dc[data_i].components[comp_i].label) idx_d = np.where( self.data_frame['Dataset'] == self.dc[data_i].label) idx_s = np.where(self.data_frame['Subset'] == self.dc[data_i].subsets[subset_i].label) idx1 = np.intersect1d(idx_c, idx_d) idx2 = np.intersect1d(idx1, idx_s) self.data_frame = self.data_frame.drop(idx2) except: pass else: try: # Find the index in the table of the unchecked element, if it's in the table # Find the matching component and dataset indices and intersect them to get the unique index idx_c = np.where(self.data_frame['Component'] == self.dc[data_i].components[comp_i].label) idx_d = np.where( self.data_frame['Dataset'] == self.dc[data_i].label) idx_s = np.where(self.data_frame['Subset'] == '--') idx1 = np.intersect1d(idx_c, idx_d) idx2 = np.intersect1d(idx1, idx_s) self.data_frame = self.data_frame.drop(idx2) except: pass # Update the past selected indices self.past_selected = self.selected_indices model = pandasModel(self.data_frame, self.dc) self.table.setModel(model) self.table.setSortingEnabled(True) self.table.setShowGrid(False) def runDataStats(self, data_i, comp_i): ''' Runs statistics for the component comp_i of data set data_i ''' subset_label = "--" data_label = dc[data_i].label comp_label = self.dc[data_i].components[ comp_i].label # add to the name array to build the table # Build the cache key cache_key = subset_label + data_label + comp_label # See if the values have already been cached try: column_data = self.cache_stash[cache_key] except: # Find the stat values # Save the data in the cache mean_val = self.dc[data_i].compute_statistic( 'mean', self.dc[data_i].components[comp_i]) median_val = self.dc[data_i].compute_statistic( 'median', self.dc[data_i].components[comp_i]) min_val = self.dc[data_i].compute_statistic( 'minimum', self.dc[data_i].components[comp_i]) max_val = self.dc[data_i].compute_statistic( 'maximum', self.dc[data_i].components[comp_i]) sum_val = self.dc[data_i].compute_statistic( 'sum', self.dc[data_i].components[comp_i]) column_data = np.asarray([[subset_label], [data_label], [comp_label], [mean_val], [median_val], [min_val], [max_val], [sum_val]]).transpose() self.cache_stash[cache_key] = column_data # Save the accurate data in self.data_accurate column_df = pd.DataFrame(column_data, columns=self.headings) self.data_accurate = self.data_accurate.append(column_df, ignore_index=True) # Round the values according to the number of significant figures set by the user mean_val = Decimal(float(column_data[0][3])) * Decimal(1) median_val = Decimal(float(column_data[0][4])) * Decimal(1) min_val = Decimal(float(column_data[0][5])) * Decimal(1) max_val = Decimal(float(column_data[0][6])) * Decimal(1) sum_val = Decimal(float(column_data[0][7])) * Decimal(1) # Create the column data array and append it to the data frame column_data = np.asarray([[subset_label], [data_label], [comp_label], [mean_val], [median_val], [min_val], [max_val], [sum_val]]).transpose() column_df = pd.DataFrame(column_data, columns=self.headings) self.data_frame = self.data_frame.append(column_df, ignore_index=True) def runSubsetStats(self, subset_i, data_i, comp_i): ''' Runs statistics for the subset subset_i with respect to the component comp_i of data set data_i ''' subset_label = dc[data_i].subsets[subset_i].label data_label = dc[data_i].label comp_label = self.dc[data_i].components[ comp_i].label # add to the name array to build the table # Build the cache key cache_key = subset_label + data_label + comp_label # See if the statistics are already in the cache try: column_data = self.cache_stash[cache_key] # Find the stats if not in the cache # Save in the cache except: mean_val = self.dc[data_i].compute_statistic( 'mean', self.dc[data_i].subsets[subset_i].components[comp_i], subset_state=self.dc[data_i].subsets[subset_i].subset_state) median_val = self.dc[data_i].compute_statistic( 'median', self.dc[data_i].subsets[subset_i].components[comp_i], subset_state=self.dc.subset_groups[subset_i].subset_state) min_val = self.dc[data_i].compute_statistic( 'minimum', self.dc[data_i].subsets[subset_i].components[comp_i], subset_state=self.dc.subset_groups[subset_i].subset_state) max_val = self.dc[data_i].compute_statistic( 'maximum', self.dc[data_i].subsets[subset_i].components[comp_i], subset_state=self.dc.subset_groups[subset_i].subset_state) sum_val = self.dc[data_i].compute_statistic( 'sum', self.dc[data_i].subsets[subset_i].components[comp_i], subset_state=self.dc.subset_groups[subset_i].subset_state) column_data = np.asarray([[subset_label], [data_label], [comp_label], [mean_val], [median_val], [min_val], [max_val], [sum_val]]).transpose() self.cache_stash[cache_key] = column_data # Save the data in self.data_accurate column_df = pd.DataFrame(column_data, columns=self.headings) self.data_accurate = self.data_accurate.append(column_df, ignore_index=True) # Round the values according to the number of significant figures set by the user mean_val = Decimal(float(column_data[0][3])) * Decimal(1) median_val = Decimal(float(column_data[0][4])) * Decimal(1) min_val = Decimal(float(column_data[0][5])) * Decimal(1) max_val = Decimal(float(column_data[0][6])) * Decimal(1) sum_val = Decimal(float(column_data[0][7])) * Decimal(1) # Create the column data array and append it to the data frame column_data = np.asarray([[subset_label], [data_label], [comp_label], [mean_val], [median_val], [min_val], [max_val], [sum_val]]).transpose() column_df = pd.DataFrame(column_data, columns=self.headings) self.data_frame = self.data_frame.append(column_df, ignore_index=True) def sigchange(self, i): # Set the number of significant figures according to what the user selects getcontext().prec = i # Retrospectively change the number of significant figures in the table data_labels = self.data_frame['Dataset'] comp_labels = self.data_frame['Component'] subset_labels = self.data_frame['Subset'] mean_vals = [] median_vals = [] min_vals = [] max_vals = [] sum_vals = [] # Get the values from the self.data_accurate array and append them for i in range(0, len(self.data_frame)): mean_vals.append( Decimal(self.data_accurate['Mean'][i]) * Decimal(1)) median_vals.append( Decimal(self.data_accurate['Median'][i]) * Decimal(1)) min_vals.append( Decimal(self.data_accurate['Minimum'][i]) * Decimal(1)) max_vals.append( Decimal(self.data_accurate['Maximum'][i]) * Decimal(1)) sum_vals.append(Decimal(self.data_accurate['Sum'][i]) * Decimal(1)) column_data = np.asarray([ subset_labels, data_labels, comp_labels, mean_vals, median_vals, min_vals, max_vals, sum_vals ]).transpose() self.data_frame = pd.DataFrame(column_data, columns=self.headings) model = pandasModel(self.data_frame, self.dc) self.table.setModel(model) self.table.setSortingEnabled(True) self.table.setShowGrid(False) def expandClicked(self): if self.expand_data.text() == "Expand all data and subsets": self.treeview.expandAll() self.expand_data.setText("Collapse all data and subsets") else: self.treeview.collapseAll() self.expand_data.setText("Expand all data and subsets") def allClicked(self): # Select all components of the treeview if checked and fill the table with newly checked items # Does not deselect if user unclicks it original_idx = self.treeview.selectionModel().selectedRows() self.treeview.selectAll() end_idx = self.treeview.selectionModel().selectedRows() for index in end_idx: if index not in original_idx: # Check to see if the clicked item is a subset component or a data component if index.parent().parent().parent().row() != 1: self.runDataStats(index.parent().row(), index.row()) else: self.runSubsetStats(index.parent().parent().row(), index.parent().row(), index.row()) # Set the table to display the correct data frame model = pandasModel(self.data_frame, self.dc) self.table.setModel(model) self.table.setSortingEnabled(True) self.table.setShowGrid(False) def noneClicked(self): self.treeview.clearSelection() self.data_frame = pd.DataFrame(columns=self.headings) model = pandasModel(self.data_frame, self.dc) self.table.setModel(model) self.table.setSortingEnabled(True) self.table.setShowGrid(False) def exportToFile(self): file_name, fltr = compat.getsavefilename( caption="Choose an output filename") try: self.data_frame.to_csv(str(file_name), index=False) except: print("passed") pass def switchMode(self): # if the user clicks to sort by components, change the text to "sort by subsets" and sort tree by components if self.switch_mode.text() == 'Sort tree by components': self.sortByComponents() self.switch_mode.setText('Sort tree by subsets') # otherwise the user wants to sort by subsets, change text to "sort by components" and sort tree by subsets else: self.sortBySubsets() self.switch_mode.setText('Sort tree by components') def sizeHint(self): return QSize(600, 800) def sortBySubsets(self): ''' Sorts the treeview by subsets- Dataset then subset then component. What we originally had as the default ''' # Save the selected rows from the component view try: selected = dict() for i in range(0, len(self.selected_indices)): item = self.model_components.itemFromIndex( self.selected_indices[i]) if item.row() != 0: key = item.text() + " (" + item.parent().parent().text( ) + ")" + item.parent().text() selected[key] = item.index() else: key = item.text() + item.parent().text() selected[key] = item.index() except: pass # Set Expand/collapse button to "expand all" self.expand_data.setText("Expand all data and subsets") #Allow the user to select multiple rows at a time self.selection_model = QAbstractItemView.MultiSelection self.treeview.setSelectionMode(self.selection_model) # See if the model already exists instead of regenerating try: self.treeview.setModel(self.model_subsets) except: self.model_subsets = QStandardItemModel() self.model_subsets.setHorizontalHeaderLabels(['']) self.treeview.setModel(self.model_subsets) self.treeview.setUniformRowHeights(True) # populate the tree # Make all the datasets be parents, and make it so they are not selectable parent_data = QStandardItem('{}'.format('Data')) parent_data.setEditable(False) parent_data.setSelectable(False) for i in range(0, len(self.dc)): parent = QStandardItem('{}'.format(self.dc.labels[i])) parent.setIcon(helpers.layer_icon(self.dc[i])) parent.setEditable(False) parent.setSelectable(False) # Make all the data components be children, nested under their parent for j in range(0, len(self.dc[i].components)): child = QStandardItem('{}'.format( str(self.dc[i].components[j]))) child.setEditable(False) # Add to the subset_dict key = self.dc[i].label + self.dc[i].components[ j].label + "All data-" + self.dc[i].label self.subset_dict[key] = child.index() parent.appendRow(child) parent_data.appendRow(parent) #Add the parents with their children to the QStandardItemModel self.model_subsets.appendRow(parent_data) parent_subset = QStandardItem('{}'.format('Subsets')) parent_subset.setEditable(False) parent_subset.setSelectable(False) # Set up the subsets as Subsets > choose subset > choose data set > choose component for j in range(0, len(self.dc.subset_groups)): grandparent = QStandardItem('{}'.format( self.dc.subset_groups[j].label)) grandparent.setIcon( helpers.layer_icon(self.dc.subset_groups[j])) grandparent.setEditable(False) grandparent.setSelectable(False) for i in range(0, len(self.dc)): parent = QStandardItem( '{}'.format(self.dc.subset_groups[j].label) + ' (' + '{}'.format(self.dc[i].label) + ')') # Set up the circles parent.setIcon(helpers.layer_icon( self.dc.subset_groups[j])) parent.setEditable(False) parent.setSelectable(False) try: self.dc[i].compute_statistic( 'mean', self.dc[i].subsets[j].components[0], subset_state=self.dc[i].subsets[j].subset_state) except: parent.setForeground(QtGui.QBrush(Qt.gray)) for k in range(0, len(self.dc[i].components)): child = QStandardItem('{}'.format( str(self.dc[i].components[k]))) child.setEditable(False) # Update the dict to keep track of row indices key = self.dc[i].label + self.dc[i].components[ k].label + self.dc[i].subsets[j].label self.subset_dict[key] = child.index() parent.appendRow(child) # Make gray and unselectable components that aren't defined for a subset try: self.dc[i].compute_statistic( 'mean', self.dc[i].subsets[j].components[k], subset_state=self.dc[i].subsets[j].subset_state ) except: # print("Glue has raised an Incompatible Attribute error on this component. Let's do this instead.") child.setEditable(False) child.setSelectable(False) child.setForeground(QtGui.QBrush(Qt.gray)) grandparent.appendRow(parent) parent_subset.appendRow(grandparent) self.model_subsets.appendRow(parent_subset) # Fill out the dict now that the indices are connected to the QStandardItemModel # Full datasets for i in range(0, parent_data.rowCount()): for j in range(0, parent_data.child(i).rowCount()): key = "All data (" + parent_data.child( i).text() + ")" + parent_data.child(i).child(j).text() self.subset_dict[key] = parent_data.child(i).child( j).index() # Subsets for i in range(0, parent_subset.rowCount()): for j in range(0, parent_subset.child(i).rowCount()): for k in range(0, parent_subset.child(i).child(j).rowCount()): key = parent_subset.child(i).child(j).text( ) + parent_subset.child(i).child(j).child(k).text() self.subset_dict[key] = parent_subset.child(i).child( j).child(k).index() self.treeview.setUniformRowHeights(True) selection_model = QItemSelectionModel(self.model_subsets) self.treeview.setSelectionModel(selection_model) selection_model.selectionChanged.connect(self.myPressedEvent) # Select rows that should be selected sel_mod = self.treeview.selectionModel() for i in range(0, len(selected)): # key = list(self.selected_dict.keys())[list(self.selected_dict.values()).index(self.selected_dict[i])] key = list(selected.keys())[i] index = self.subset_dict[key] print(index.parent().row(), index.row()) # print(index, type(index)) # print(type(self.treeview.selectionModel().select(index, QItemSelectionModel.Select))) # sel_mod.select(index, QItemSelectionModel.Select|QItemSelectionModel.Rows) self.treeview.setCurrentIndex(index) self.treeview.setSelectionModel(sel_mod) def sortByComponents(self): ''' Sorts the treeview by components- Dataset then component then subsets ''' # Save the selected rows from the subset view if applicable try: selected = dict() for i in range(0, len(self.selected_indices)): item = self.model_subsets.itemFromIndex( self.selected_indices[i]) if item.parent().parent().text() == "Data": key = "All data (" + item.parent().text( ) + ")" + item.text() selected[key] = item.index() else: key = item.parent().text() + item.text() selected[key] = item.index() except: pass # Set Expand/collapse button to "expand all" self.expand_data.setText("Expand all data and subsets") self.selection_model = QAbstractItemView.MultiSelection self.treeview.setSelectionMode(self.selection_model) # See if the model already exists try: self.treeview.setModel(self.model_components) except: self.model_components = QStandardItemModel() self.model_components.setHorizontalHeaderLabels(['']) self.treeview.setModel(self.model_components) self.treeview.setUniformRowHeights(True) # populate the tree # Make all the datasets be parents, and make it so they are not selectable for i in range(0, len(dc)): grandparent = QStandardItem('{}'.format(self.dc.labels[i])) grandparent.setIcon(helpers.layer_icon(self.dc[i])) grandparent.setEditable(False) grandparent.setSelectable(False) # Make all the data components be children, nested under their parent for k in range(0, len(self.dc[i].components)): parent = QStandardItem('{}'.format( str(self.dc[i].components[k]))) parent.setEditable(False) parent.setSelectable(False) child = QStandardItem('{}'.format('All data (' + self.dc.labels[i] + ')')) child.setIcon(helpers.layer_icon(self.dc[i])) child.setEditable(False) parent.appendRow(child) for j in range(0, len(self.dc.subset_groups)): child = QStandardItem('{}'.format( self.dc.subset_groups[j].label)) child.setEditable(False) child.setIcon( helpers.layer_icon(self.dc.subset_groups[j])) try: self.dc[i].compute_statistic( 'mean', self.dc[i].subsets[j].components[k], subset_state=self.dc[i].subsets[j].subset_state ) except: # print("Glue has raised an Incompatible Attribute error on this component. Let's do this instead.") child.setEditable(False) child.setSelectable(False) child.setForeground(QtGui.QBrush(Qt.gray)) parent.appendRow(child) grandparent.appendRow(parent) self.model_components.appendRow(grandparent) # Fill out the dict now that the indices are connected to the QStandardItemModel for i in range(0, grandparent.rowCount()): for j in range(0, grandparent.child(i).rowCount()): if grandparent.child(i).child(j).row() == 0: key = grandparent.child(i).child( j).text() + grandparent.child(i).text() self.component_dict[key] = grandparent.child( i).child(j).index() else: key = grandparent.child(i).child( j).text() + " (" + grandparent.text( ) + ")" + grandparent.child(i).text() self.component_dict[key] = grandparent.child( i).child(j).index() self.treeview.setUniformRowHeights(True) selection_model = QItemSelectionModel(self.model_components) self.treeview.setSelectionModel(selection_model) selection_model.selectionChanged.connect(self.myPressedEvent) # Select the rows that should be selected sel_mod = self.treeview.selectionModel() for i in range(0, len(selected)): key = list(selected.keys())[i] index = self.component_dict[key] # self.treeview.selectionModel().select(index, QItemSelectionModel.Select) print(index.parent().row(), index.row()) # This causes an error when it runs # sel_mod.select(index, QItemSelectionModel.Select|QItemSelectionModel.Rows) self.treeview.setCurrentIndex(index) self.treeview.setSelectionModel(sel_mod)
class TreeView(QWidget): def __init__(self, table, parent): QWidget.__init__(self, parent) self.window = parent self.tree = QTreeView(self) indent = self.tree.indentation() self.tree.setIndentation(indent / 2) self.model = DataModel(table) self.sorter = sorter = FilterModel(self) sorter.setSourceModel(self.model) self.tree.setModel(sorter) for col in range(3, 9): self.tree.setItemDelegateForColumn(col, PercentDelegate(self)) self.tree.header().setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tree.header().customContextMenuRequested.connect( self._on_header_menu) self.tree.setSortingEnabled(True) self.tree.setAutoExpandDelay(0) self.tree.resizeColumnToContents(0) self.tree.resizeColumnToContents(NAME_COLUMN) self.tree.expand(self.sorter.index(0, 0)) #self.tree.expandAll() self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect(self._on_tree_menu) searchbox = QHBoxLayout() self.search = QLineEdit(self) searchbox.addWidget(self.search) self.search_type = QComboBox(self) self.search_type.addItem("Contains", SEARCH_CONTAINS) self.search_type.addItem("Exact", SEARCH_EXACT) self.search_type.addItem("Reg.Exp", SEARCH_REGEXP) searchbox.addWidget(self.search_type) btn = QPushButton("&Search", self) searchbox.addWidget(btn) btn.clicked.connect(self._on_search) btn = QPushButton("&Next", self) searchbox.addWidget(btn) btn.clicked.connect(self._on_search_next) filterbox = QHBoxLayout() label = QLabel("Time Individual", self) filterbox.addWidget(label) self.individual_time = QSpinBox(self) self.individual_time.setMinimum(0) self.individual_time.setMaximum(100) self.individual_time.setSuffix(" %") filterbox.addWidget(self.individual_time) label = QLabel("Alloc Individual", self) filterbox.addWidget(label) self.individual_alloc = QSpinBox(self) self.individual_alloc.setMinimum(0) self.individual_alloc.setMaximum(100) self.individual_alloc.setSuffix(" %") filterbox.addWidget(self.individual_alloc) label = QLabel("Time Inherited", self) filterbox.addWidget(label) self.inherited_time = QSpinBox(self) self.inherited_time.setMinimum(0) self.inherited_time.setMaximum(100) self.inherited_time.setSuffix(" %") filterbox.addWidget(self.inherited_time) label = QLabel("Alloc Inherited", self) filterbox.addWidget(label) self.inherited_alloc = QSpinBox(self) self.inherited_alloc.setMinimum(0) self.inherited_alloc.setMaximum(100) self.inherited_alloc.setSuffix(" %") filterbox.addWidget(self.inherited_alloc) btn = QPushButton("&Filter", self) btn.clicked.connect(self._on_filter) filterbox.addWidget(btn) btn = QPushButton("&Reset", self) filterbox.addWidget(btn) btn.clicked.connect(self._on_reset_filter) vbox = QVBoxLayout() vbox.addLayout(searchbox) vbox.addLayout(filterbox) vbox.addWidget(self.tree) self.setLayout(vbox) self._search_idxs = None self._search_idx_no = 0 def _expand_to(self, idx): idxs = [idx] parent = idx while parent and parent.isValid(): parent = self.sorter.parent(parent) idxs.append(parent) #print(idxs) for idx in reversed(idxs[:-1]): data = self.sorter.data(idx, QtCore.Qt.DisplayRole) #print(data) self.tree.expand(idx) def _on_search(self): text = self.search.text() selected = self.tree.selectedIndexes() # if selected: # start = selected[0] # else: start = self.sorter.index(0, NAME_COLUMN) search_type = self.search_type.currentData() if search_type == SEARCH_EXACT: method = QtCore.Qt.MatchFixedString elif search_type == SEARCH_CONTAINS: method = QtCore.Qt.MatchContains else: method = QtCore.Qt.MatchRegExp self._search_idxs = idxs = self.sorter.search(start, text, search_type) if idxs: self.window.statusBar().showMessage( "Found: {} occurence(s)".format(len(idxs))) self._search_idx_no = 0 idx = idxs[0] self._locate(idx) else: self.window.statusBar().showMessage("Not found") def _locate(self, idx): self.tree.resizeColumnToContents(0) self.tree.resizeColumnToContents(NAME_COLUMN) self._expand_to(idx) self.tree.setCurrentIndex(idx) #self.tree.selectionModel().select(idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Current | QItemSelectionModel.Rows) #self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter) def _on_search_next(self): if self._search_idxs: n = len(self._search_idxs) self._search_idx_no = (self._search_idx_no + 1) % n idx = self._search_idxs[self._search_idx_no] self.window.statusBar().showMessage("Occurence {} of {}".format( self._search_idx_no, n)) self._locate(idx) else: self.window.statusBar().showMessage("No search results") def _on_filter(self): self.sorter.setFilter(self.search.text(), self.individual_time.value(), self.individual_alloc.value(), self.inherited_time.value(), self.inherited_alloc.value()) def _on_reset_filter(self): self.sorter.reset() def _on_header_menu(self, pos): menu = make_header_menu(self.tree) menu.exec_(self.mapToGlobal(pos)) def _on_tree_menu(self, pos): index = self.tree.indexAt(pos) #print("index: {}".format(index)) if index.isValid(): record = self.sorter.data(index, QtCore.Qt.UserRole + 1) #print("okay?..") #print("context: {}".format(record)) menu = self.window.make_item_menu(self.model, record) menu.exec_(self.tree.viewport().mapToGlobal(pos))
class DebugWindow(QMainWindow): """A main window to edit text and examine the generated token structure. Example:: from PyQt5.Qt import * a=QApplication([]) from parceqt.debug import DebugWindow w = DebugWindow() w.resize(1200,900) w.show() w.set_theme("default", True) from parce.lang.css import * w.set_root_lexicon(Css.root) w.set_text(open("path/to/parce/themes/default.css").read()) In the debug window you can edit the text at the left and directly at the right examine the tree structure. Along the top of the window the path to the token at the current cursor position is displayed, from the root lexicon upto the token, from which the action is displayed. Clicking a button selects the associated range of the context or token in the text view. Clicking an item in the tree also selects that range in the text. Moving the cursor in the text updates the current item in the tree, and the displayed ancestor path. """ show_updated_region_enabled = False def __init__(self, parent=None): super().__init__(parent, windowTitle="parceqt debugger") f = self._updated_format = QTextCharFormat() c = QColor("palegreen") c.setAlpha(64) f.setBackground(c) f = self._currentline_format = QTextCharFormat() f.setProperty(QTextCharFormat.FullWidthSelection, True) self._actions = Actions(self) self._actions.add_menus(self.menuBar()) widget = QWidget(self) self.setCentralWidget(widget) layout = QVBoxLayout(margin=4, spacing=2) widget.setLayout(layout) top_layout = QHBoxLayout(margin=0, spacing=0) self.guessButton = QToolButton(self, clicked=self.guess_root_lexicon, toolTip="Guess Language", icon=self.style().standardIcon( QStyle.SP_BrowserReload)) self.lexiconChooser = LexiconChooser(self) self.ancestorView = AncestorView(self) top_layout.addWidget(self.guessButton) top_layout.addWidget(self.lexiconChooser) top_layout.addWidget(self.ancestorView) top_layout.addStretch(10) layout.addLayout(top_layout) self.guessButton.setFixedHeight( self.lexiconChooser.sizeHint().height()) splitter = QSplitter(self, orientation=Qt.Horizontal) layout.addWidget(splitter, 100) self.textEdit = QPlainTextEdit(lineWrapMode=QPlainTextEdit.NoWrap, cursorWidth=2) self.treeView = QTreeView() splitter.addWidget(self.textEdit) splitter.addWidget(self.treeView) splitter.setStretchFactor(0, 3) splitter.setStretchFactor(1, 2) self.extraSelectionManager = ExtraSelectionManager(self.textEdit) self.document = d = self.textEdit.document() self.textEdit.setDocument(self.document) self.worker = w = parceqt.worker(d) self.builder = b = w.builder() w.debugging = True self.setStatusBar(QStatusBar()) self.create_model() # signal connections self.textEdit.viewport().installEventFilter(self) self.textEdit.installEventFilter(self) self.lexiconChooser.lexicon_changed.connect( self.slot_root_lexicon_changed) self.ancestorView.node_clicked.connect(self.slot_node_clicked) w.started.connect(self.slot_build_started) w.tree_updated.connect(self.slot_build_updated) self.textEdit.cursorPositionChanged.connect( self.slot_cursor_position_changed) self.treeView.clicked.connect(self.slot_item_clicked) self.textEdit.setFocus() self.set_theme() # somewhat larger font by default font = self.textEdit.font() font.setPointSizeF(11) self.textEdit.setFont(font) def create_model(self): """Instantiate a tree model for the tree view.""" m = self.treeView.model() if not m: m = parceqt.treemodel.TreeModel(self.builder.root) m.connect_debugging_builder(self.builder) self.treeView.setModel(m) def delete_model(self): """Delete the model and remove it from the tree.""" m = self.treeView.model() if m: m.disconnect_debugging_builder(self.builder) self.treeView.setModel(None) m.deleteLater() def set_text(self, text): """Set the text in the text edit.""" self.document.setPlainText(text) def set_root_lexicon(self, lexicon): """Set the root lexicon to use.""" self.lexiconChooser.set_root_lexicon(lexicon) def guess_root_lexicon(self): """Again choose the root lexicon based on the text.""" text = self.document.toPlainText() if text: self.set_root_lexicon(parce.find(contents=text)) def open_file(self, filename): """Read a file from disk and guess the language.""" text = read_file(filename) root_lexicon = parce.find(filename=filename, contents=text) self.set_text(text) self.set_root_lexicon(root_lexicon) c = self.textEdit.textCursor() c.setPosition(0) self.textEdit.setTextCursor(c) def set_theme(self, theme="default", adjust_widget=True): """Set the theme to use for the text edit.""" if isinstance(theme, str): theme = parce.theme_by_name(theme) formatter = parceqt.formatter.Formatter(theme) if theme else None if adjust_widget: if formatter: font = formatter.font(self) self.textEdit.setPalette(formatter.palette(self)) else: font = QApplication.font(self) self.textEdit.setPalette(QApplication.palette(self)) font.setPointSizeF(self.textEdit.font().pointSizeF()) # keep size self.textEdit.setFont(font) self.highlight_current_line() h = parceqt.highlighter.SyntaxHighlighter.instance(self.worker) h.set_formatter(formatter) def slot_build_started(self): """Called when the tree builder has started a build.""" self.treeView.setCursor(Qt.BusyCursor) def slot_build_updated(self): """Called when the tree builder has finished a build.""" self.treeView.unsetCursor() self.slot_cursor_position_changed() self.statusBar().showMessage(", ".join( lexicon_names(self.builder.lexicons))) tree = self.worker.get_root() self.lexiconChooser.setToolTip( parceqt.treemodel.TreeModel.node_tooltip(tree)) if self.show_updated_region_enabled: self.show_updated_region() def slot_cursor_position_changed(self): """Called when the text cursor moved.""" tree = self.worker.get_root() if tree: pos = self.textEdit.textCursor().position() doc = parceqt.document.Document(self.document) token = doc.token(pos) if token: self.ancestorView.set_token_path(token) model = self.treeView.model() if model: index = model.get_model_index(token) self.treeView.setCurrentIndex(index) elif tree is not None: self.ancestorView.clear() self.highlight_current_line() def slot_item_clicked(self, index): """Called when a node in the tree view is clicked.""" tree = self.worker.get_root() if tree: model = self.treeView.model() if model: node = self.treeView.model().get_node(index) cursor = self.textEdit.textCursor() cursor.setPosition(node.end) cursor.setPosition(node.pos, QTextCursor.KeepAnchor) self.textEdit.setTextCursor(cursor) self.textEdit.setFocus() def slot_node_clicked(self, node): """Called when a button in the ancestor view is clicked.""" tree = self.worker.get_root() if tree and node.root() is tree: cursor = self.textEdit.textCursor() cursor.setPosition(node.end) cursor.setPosition(node.pos, QTextCursor.KeepAnchor) self.textEdit.setTextCursor(cursor) self.textEdit.setFocus() model = self.treeView.model() if model: index = model.get_model_index(node) self.treeView.expand(index) self.treeView.setCurrentIndex(index) def slot_root_lexicon_changed(self, lexicon): """Called when the root lexicon is changed.""" parceqt.set_root_lexicon(self.document, lexicon) def highlight_current_line(self): """Highlight the current line.""" group = QPalette.Active if self.textEdit.hasFocus( ) else QPalette.Inactive p = self.textEdit.palette() color = p.color(group, QPalette.AlternateBase) self._currentline_format.setBackground(color) if color != p.color(group, QPalette.Base): c = self.textEdit.textCursor() c.clearSelection() self.extraSelectionManager.highlight(self._currentline_format, [c]) else: self.extraSelectionManager.clear(self._currentline_format) def show_updated_region(self): """Highlight the updated region for 2 seconds.""" end = self.builder.end if end >= self.document.characterCount() - 1: end = self.document.characterCount() - 1 if self.builder.start == 0: return c = QTextCursor(self.document) c.setPosition(end) c.setPosition(self.builder.start, QTextCursor.KeepAnchor) self.extraSelectionManager.highlight(self._updated_format, [c], msec=2000) def clear_updated_region(self): self.extraSelectionManager.clear(self._updated_format) def eventFilter(self, obj, ev): """Implemented to support Ctrl+wheel zooming and keybfocus handling.""" if obj == self.textEdit: if ev.type() in (QEvent.FocusIn, QEvent.FocusOut): self.highlight_current_line() else: # viewport if ev.type() == QEvent.Wheel and ev.modifiers( ) == Qt.ControlModifier: if ev.angleDelta().y() > 0: self.textEdit.zoomIn() elif ev.angleDelta().y() < 0: self.textEdit.zoomOut() return True return False
class App(QWidget): def __init__(self): super().__init__() self.left = 100 self.top = 100 self.width = 1200 self.height = 720 self.setWindowTitle('Mammogram Prediction') self.setGeometry(self.left, self.top, self.width, self.height) self.initUI() def initUI(self): # Widget for showing picture. The QLabel gets a Pixmap added to it to show picture self.picture_name_label = QLabel(currently_selected_picture) self.picture_label = QLabel() self.prediction_text = QTextEdit() self.prediction_text.setReadOnly(True) self.model_label = QLabel() self.init_picture_and_predictions() self.resized_picture = self.picture.scaled(299, 299, Qt.KeepAspectRatio, Qt.FastTransformation) self.picture_label.setPixmap(self.resized_picture) self.picture_label.setMinimumWidth(299) self.picture_label.setMinimumHeight(299) self.picture_label.setContentsMargins(0, 19, 0, 0) # Tree and List view for file directory overview of pictures self.picture_directory_label = QLabel('Select a Picture:') picture_dir_path = 'pictures\\' picture_file_path = 'pictures\\' self.treeview_picture = QTreeView() self.listview_picture = QListView() self.dirModel_picture = QFileSystemModel() self.dirModel_picture.setRootPath(picture_dir_path) self.dirModel_picture.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs) self.fileModel_picture = QFileSystemModel() self.fileModel_picture.setRootPath(picture_file_path) # self.fileModel_picture.setFilter(QDir.NoDotAndDotDot | QDir.Files) self.treeview_picture.setModel(self.dirModel_picture) self.listview_picture.setModel(self.fileModel_picture) self.treeview_picture.setRootIndex( self.dirModel_picture.index(picture_dir_path)) self.listview_picture.setRootIndex( self.fileModel_picture.index(picture_file_path)) # self.treeview_picture.setCurrentIndex( self.dirModel_picture.index(0, 0)) self.treeview_picture.clicked.connect( self.on_picture_treeview_clicked) self.listview_picture.clicked.connect( self.on_picture_listview_clicked) self.treeview_picture.setColumnHidden(1, True) self.treeview_picture.setColumnWidth(0, 275) self.treeview_picture.setMinimumWidth(500) self.listview_picture.setMinimumWidth(500) # List view for file directory overview of five label models self.model_directory_label = QLabel('Select a Five Label Model:') model_path = 'trained_five_Models\\' self.listview_model = QListView() self.dirModel_model = QFileSystemModel() self.dirModel_model.setRootPath(model_path) self.dirModel_model.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs) self.listview_model.setModel(self.dirModel_model) self.listview_model.setRootIndex( self.dirModel_model.index(model_path)) self.listview_model.clicked.connect(self.on_model_listview_clicked) # List view for file directory overview of binary models self.model_binary_directory_label = QLabel( 'Select a Binary Model:') model_binary_path = 'trained_binary_Models\\' self.listview_model_binary = QListView() self.dirModel_model_binary = QFileSystemModel() self.dirModel_model_binary.setRootPath(model_binary_path) self.dirModel_model_binary.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs) self.listview_model_binary.setModel(self.dirModel_model_binary) self.listview_model_binary.setRootIndex( self.dirModel_model_binary.index(model_binary_path)) self.listview_model_binary.setSelectionMode( QAbstractItemView.MultiSelection) self.listview_model_binary.clicked.connect( self.on_model_binary_listview_clicked) # Layout handling. # self.gridlayout = QGridLayout() # self.gridlayout.addWidget(self.model_directory_label, 0, 0) # self.gridlayout.setColumnStretch(-15, -11) # self.gridlayout.addWidget(self.listview_model, 1, 0) # self.gridlayout.addWidget(self.picture_directory_label, 2, 0) # self.gridlayout.addWidget(self.treeview_picture, 3, 0) # # self.gridlayout.addWidget(self.model_binary_directory_label, 0, 1) # self.gridlayout.addWidget(self.listview_model_binary, 1, 1) # self.gridlayout.addWidget(self.listview_picture, 3, 1) # # self.gridlayout.addWidget(self.picture_label, 0, 2) # self.gridlayout.addWidget(self.picture_name_label, 1, 2) # self.gridlayout.addWidget(self.prediction_text, 3, 2) self.vbox = QVBoxLayout() self.vbox_left = QVBoxLayout() self.vbox_right = QVBoxLayout() self.hbox_outer = QHBoxLayout() self.hbox_top = QHBoxLayout() self.hbox_buttom = QHBoxLayout() self.vbox_five_label = QVBoxLayout() self.vbox_binary = QVBoxLayout() self.vbox.addLayout(self.hbox_outer) self.hbox_outer.addLayout(self.vbox_left) self.hbox_outer.addLayout(self.vbox_right) self.vbox_left.addLayout(self.hbox_top) self.hbox_top.addLayout(self.vbox_five_label) self.hbox_top.addLayout(self.vbox_binary) self.vbox_five_label.addWidget(self.model_directory_label) self.vbox_five_label.addWidget(self.listview_model) self.vbox_binary.addWidget(self.model_binary_directory_label) self.vbox_binary.addWidget(self.listview_model_binary) self.vbox_left.addWidget(self.picture_directory_label) self.vbox_left.addLayout(self.hbox_buttom) self.hbox_buttom.addWidget(self.treeview_picture) self.hbox_buttom.addWidget(self.listview_picture) self.vbox_right.addWidget(self.picture_label, alignment=Qt.AlignHCenter) self.vbox_right.addWidget(self.picture_name_label, alignment=Qt.AlignHCenter) self.vbox_right.addWidget(self.model_label, alignment=Qt.AlignHCenter) self.vbox_right.addWidget(self.prediction_text) self.vbox_right.setAlignment(Qt.AlignCenter) self.setLayout(self.vbox) self.sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.setSizePolicy(self.sizePolicy) self.show() def init_picture_and_predictions(self): if currently_selected_picture == 'Currently No Image Selected': self.picture = QtGui.QPixmap('GUI/no_image_selected.png') self.prediction_text.setText('') def on_picture_treeview_clicked(self, index): pathof_selected_dir = self.dirModel_picture.fileInfo( index).absoluteFilePath() self.listview_picture.setRootIndex( self.fileModel_picture.setRootPath(pathof_selected_dir)) def on_picture_listview_clicked(self, index): global currently_selected_model global currently_selected_model_name global currently_selected_picture currently_selected_picture = self.fileModel_picture.fileInfo( index).absoluteFilePath() try: Image.open(currently_selected_picture) self.picture_name_label.setText(currently_selected_picture) self.picture_label.setPixmap( QtGui.QPixmap(currently_selected_picture)) except IOError: print('Exception: Chosen file is not a picture') # Checks if the selected picture has size 299 image_in = cv2.imread(currently_selected_picture) size = image_in.shape[:2] if size[0] == 299: if currently_selected_model is not None: for model in currently_selected_model: new_prediction = self.makePrediction( model, self.convertPictureToNumpy( currently_selected_picture)) split = currently_selected_model_name.split('_') if split[4] in ('neg', 'bc', 'bm', 'mc', 'mm'): self.show_binary_prediction( new_prediction, split[4]) else: self.show_five_prediction(new_prediction) else: self.prediction_text.setText( 'No Model is Chosen for Prediction. Choose one to the left.' ) # If the selected picture is not size 299 it will be padded and cropped else: cropped_images = resize_image_padding( currently_selected_picture) self.listview_picture.setRootIndex( self.fileModel_picture.setRootPath('pictures/cropped/%s' % cropped_images)) def on_model_listview_clicked(self, index): global currently_selected_model global currently_selected_picture currently_selected_model = [] selected_model_path = self.dirModel_model.fileInfo( index).absoluteFilePath() currently_selected_model.append(self.getModel(selected_model_path)) self.model_label.setText(currently_selected_model_name[0]) if currently_selected_picture != 'Currently No Image Selected': for model in currently_selected_model: new_prediction = self.makePrediction( model, self.convertPictureToNumpy(currently_selected_picture)) self.show_five_prediction(new_prediction) def on_model_binary_listview_clicked(self): global currently_selected_model global currently_selected_picture currently_selected_model = [] self.prediction_text.setText('') self.model_label.setText('') for x in self.listview_model_binary.selectedIndexes(): selected_model_path = self.dirModel_model_binary.fileInfo( x).absoluteFilePath() currently_selected_model.append( self.getModel(selected_model_path)) # if not currently_selected_model_name: # self.model_label.setText(currently_selected_model_name) if currently_selected_picture != 'Currently No Image Selected': for y in currently_selected_model: new_prediction = self.makePrediction( y, self.convertPictureToNumpy(currently_selected_picture)) self.show_binary_prediction(new_prediction, y.category) def getModel(self, model_path): global currently_selected_model_name currently_selected_model_name = [] split = os.path.split(model_path)[1].split('_') model_version = split[0] + '_' + split[1] + '_' + split[ 2] + '_' + split[3] currently_selected_model_name.append(os.path.split(model_path)[1]) model = getattr(sys.modules[__name__], model_version)() checkpoint_path = model_path + '/cp.ckpt' model.load_weights(checkpoint_path) return model def makePrediction(self, input_model, input_picture): image = tf.reshape(input_picture, [-1, 299, 299, 1]) image = tf.cast(image, tf.float32) image = image / 255.0 return input_model.predict(image) def convertPictureToNumpy(self, filename): img = Image.open(filename) np_array = np.array(img, dtype='uint8') return np_array def show_five_prediction(self, prediction): self.prediction_text.setText( "Probability of Negative: %s" % prediction[0, 0] + "\n\nProbability of Benign Calcification: %s" % prediction[0, 1] + "\n\nProbability of Benign Mass: %s" % prediction[0, 2] + "\n\nProbability of Malignant Calcification: %s" % prediction[0, 3] + "\n\nProbability of Malignant Mass: %s" % prediction[0, 4]) def show_binary_prediction(self, prediction, category): if category == 'neg': self.prediction_text.append( "Probability of Negative: %s %s \n" % (prediction[0, 0], prediction[0, 1])) elif category == 'bc': self.prediction_text.append( "Probability of Benign Calcification: %s %s \n" % (prediction[0, 0], prediction[0, 1])) elif category == 'bm': self.prediction_text.append( "Probability of Benign Mass: %s %s \n" % (prediction[0, 0], prediction[0, 1])) elif category == 'mc': self.prediction_text.append( "Probability of Malignant Calcification: %s %s \n" % (prediction[0, 0], prediction[0, 1])) elif category == 'mm': self.prediction_text.append( "Probability of Malignant Mass: %s %s \n" % (prediction[0, 0], prediction[0, 1])) else: self.prediction_text.append( "Probability of ????: %s %s \n" % (prediction[0, 0], prediction[0, 1])) def openFileDialog(self): fileName, _ = QFileDialog.getOpenFileName( self, "QFileDialog.getOpenFileName()", "", "All Files (*);;Python Files (*.py)") if fileName: return fileName def is_png(data): return data[:8] == '\x89PNG\x0d\x0a\x1a\x0a'
class _LocatorDialog(QDialog): """Locator widget and implementation """ def __init__(self, parent, commandClasses): QDialog.__init__(self, parent) self._terminated = False self._commandClasses = commandClasses self._createUi() self._loadingTimer = QTimer(self) self._loadingTimer.setSingleShot(True) self._loadingTimer.setInterval(200) self._loadingTimer.timeout.connect(self._applyLoadingCompleter) self._completerLoaderThread = _CompleterLoaderThread(self) self.finished.connect(self._terminate) self._command = None self._updateCurrentCommand() def _createUi(self): self.setWindowTitle(core.project().path().replace(os.sep, '/') or 'Locator') self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(1) biggerFont = self.font() biggerFont.setPointSizeF(biggerFont.pointSizeF() * 2) self.setFont(biggerFont) self._edit = _CompletableLineEdit(self) self._edit.updateCurrentCommand.connect(self._updateCurrentCommand) self._edit.enterPressed.connect(self._onEnterPressed) self._edit.installEventFilter(self) # catch Up, Down self._edit.setFont(biggerFont) self.layout().addWidget(self._edit) self.setFocusProxy(self._edit) self._table = QTreeView(self) self._table.setFont(biggerFont) self._model = _CompleterModel() self._table.setModel(self._model) self._table.setItemDelegate(HTMLDelegate(self._table)) self._table.setRootIsDecorated(False) self._table.setHeaderHidden(True) self._table.clicked.connect(self._onItemClicked) self._table.setAlternatingRowColors(True) self._table.installEventFilter(self) # catch focus and give to the edit self.layout().addWidget(self._table) width = QFontMetrics(self.font()).width('x' * 64) # width of 64 'x' letters self.resize(width, width * 0.62) def _terminate(self): if not self._terminated: if self._command is not None: self._command.terminate() self._command = None self._edit.terminate() self._completerLoaderThread.terminate() if self._model: self._model.terminate() core.workspace().focusCurrentDocument() self._terminated = True def _updateCurrentCommand(self): """Try to parse line edit text and set current command """ if self._terminated: return newCommand = self._parseCurrentCommand() if newCommand is not self._command: if self._command is not None: self._command.updateCompleter.disconnect(self._updateCompletion) self._command.terminate() self._command = newCommand if self._command is not None: self._command.updateCompleter.connect(self._updateCompletion) self._updateCompletion() def _updateCompletion(self): """User edited text or moved cursor. Update inline and TreeView completion """ if self._command is not None: completer = self._command.completer() if completer is not None and completer.mustBeLoaded: self._loadingTimer.start() self._completerLoaderThread.loadCompleter(self._command, completer) else: self._applyCompleter(self._command, completer) else: self._applyCompleter(None, _HelpCompleter(self._commandClasses)) def _applyLoadingCompleter(self): """Set 'Loading...' message """ self._applyCompleter(None, StatusCompleter('<i>Loading...</i>')) def onCompleterLoaded(self, command, completer): """The method called from _CompleterLoaderThread when the completer is ready This code works in the GUI thread """ self._applyCompleter(command, completer) def _applyCompleter(self, command, completer): """Apply completer. Called by _updateCompletion or by thread function when Completer is constructed """ self._loadingTimer.stop() if command is not None: command.onCompleterLoaded(completer) if completer is None: completer = _HelpCompleter([command]) if self._edit.cursorPosition() == len(self._edit.text()): # if cursor at the end of text self._edit.setInlineCompletion(completer.inline()) self._model.setCompleter(completer) if completer.columnCount() > 1: self._table.resizeColumnToContents(0) self._table.setColumnWidth(0, self._table.columnWidth(0) + 20) # 20 px spacing between columns selItem = completer.autoSelectItem() if selItem: index = self._model.createIndex(selItem[0], selItem[1]) self._table.setCurrentIndex(index) def _onItemClicked(self, index): """Item in the TreeView has been clicked. Open file, if user selected it """ if self._command is not None: fullText = self._model.completer.getFullText(index.row()) if fullText is not None: self._command.onItemClicked(fullText) if self._tryExecCurrentCommand(): self.accept() return else: self._edit.setText(self._command.lineEditText()) self._updateCurrentCommand() self._edit.setFocus() def _onEnterPressed(self): """User pressed Enter or clicked item. Execute command, if possible """ if self._table.currentIndex().isValid(): self._onItemClicked(self._table.currentIndex()) else: self._tryExecCurrentCommand() def _tryExecCurrentCommand(self): if self._command is not None and self._command.isReadyToExecute(): self._command.execute() self.accept() return True else: return False def _chooseCommand(self, words): for cmd in self._commandClasses: if cmd.command == words[0]: return cmd, words[1:] isPath = words and (words[0].startswith('/') or words[0].startswith('./') or words[0].startswith('../') or words[0].startswith('~/') or words[0][1:3] == ':\\' or words[0][1:3] == ':/' ) isNumber = len(words) == 1 and all([c.isdigit() for c in words[0]]) def matches(cmd): if isPath: return cmd.isDefaultPathCommand elif isNumber: return cmd.isDefaultNumericCommand else: return cmd.isDefaultCommand for cmd in self._commandClasses: if matches(cmd): return cmd, words def _parseCurrentCommand(self): """ Parse text and try to get (command, completable word index) Return None if failed to parse """ # Split line text = self._edit.commandText() words = splitLine(text) if not words: return None # Find command cmdClass, args = self._chooseCommand(words) if isinstance(self._command, cmdClass): command = self._command else: command = cmdClass() # Try to make command object try: command.setArgs(args) except InvalidCmdArgs: return None else: return command def eventFilter(self, obj, event): if obj is self._edit: if event.type() == QEvent.KeyPress and \ event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown): return self._table.event(event) elif obj is self._table: if event.type() == QEvent.FocusIn: self._edit.setFocus() return True return False
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect( self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel( self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect( self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) # Add auto-hide capability. self._waitForCtrlRelease = False core.actionManager().action("mNavigation/aNext").triggered.connect( self._setWaitForCtrlRelease) core.actionManager().action("mNavigation/aPrevious").triggered.connect( self._setWaitForCtrlRelease) QApplication.instance().installEventFilter(self) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") QApplication.instance().removeEventFilter(self) def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect( self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction( core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action( "mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu( ).aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos)) def _setWaitForCtrlRelease(self): # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten # by the QAction and don't even show up in the event filter below. We # want to avoid waiting for a Ctrl release if the menu item brought us # here. As a workaround, check that Ctrl is pressed. If so, it's # unlikely to be the menu item. if QApplication.instance().keyboardModifiers() & Qt.ControlModifier: self._waitForCtrlRelease = True self.show() else: # If this was a menu selection, then update the MRU list. We can't # do this now, since the current document hasn't been changed yet. QTimer.singleShot(0, self.model.sortDocuments) def eventFilter(self, obj, event): """An event filter that looks for ctrl key releases and focus out events.""" # Wait for the user to release the Ctrl key. if (self._waitForCtrlRelease and event.type() == QEvent.KeyRelease and event.key() == Qt.Key_Control and event.modifiers() == Qt.NoModifier): self.model.sortDocuments() self._waitForCtrlRelease = False if not self.isPinned(): self.hide() # Look for a focus out event sent by the containing widget's focus # proxy. if event.type() == QEvent.FocusOut and obj == self.focusProxy(): self.model.sortDocuments() return QObject.eventFilter(self, obj, event)
class ClientList(QWidget): def __init__(self, data: ()): super().__init__() self.tree_view = QTreeView() self.model = QStandardItemModel() self.rootNode = self.model.invisibleRootItem() self.applyModel() buttons = QHBoxLayout() add = QPushButton("Add") add.clicked.connect(self.addClient) remove = QPushButton("Remove") remove.clicked.connect(self.removeClient) buttons.addWidget(add) buttons.addWidget(remove) layout = QVBoxLayout() layout.addLayout(buttons) layout.addWidget(self.tree_view) self.setLayout(layout) if data: self.setData(data) self.in_progress = False def addClient(self): branch = [QStandardItem(), QStandardItem()] branch[0].setText("/") branch[1].setText("client_") self.model.appendRow(branch) index = self.model.index(self.model.rowCount() - 1, 0) self.tree_view.setCurrentIndex(index) self.applyModel() def removeClient(self): index = self.tree_view.currentIndex() if index.isValid(): self.model.removeRow(index.row()) self.applyModel() def applyModel(self): self.model.setHeaderData(0, Qt.Horizontal, "Path") self.model.setHeaderData(1, Qt.Horizontal, "Client variable") self.tree_view.setModel(self.model) def getData(self): data = {} for row in range(self.model.rowCount()): path = self.model.data(self.model.index(row, 0)) client = self.model.data(self.model.index(row, 1)) if path and client: data[path] = client return data def setData(self, data): for path, client in data.items(): branch = [QStandardItem(), QStandardItem()] branch[0].setText(path) branch[1].setText(client) self.model.appendRow(branch) self.applyModel()
class VisualStates(QMainWindow): def __init__(self, parent=None): super(QMainWindow, self).__init__() self.setWindowTitle("VisualStates") self.configDialog = None # root state self.globalNamespace = Namespace('', '') self.localNamespace = Namespace('', '') self.rootState = State(0, "root", True, self.localNamespace) self.activeState = self.rootState self.activeNamespace = self.localNamespace self.statusBar() self.createMenu() self.createTreeView() self.createStateCanvas() self.setGeometry(0, 0, 800, 600) self.show() self.fileManager = FileManager() self.importManager = ImportManager() self.automataPath = None self.libraries = [] self.config = None def createMenu(self): # create actions newAction = QAction('&New', self) newAction.setShortcut('Ctrl+N') newAction.setStatusTip('Create New Visual States') newAction.triggered.connect(self.newAction) openAction = QAction('&Open', self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Open Visual States') openAction.triggered.connect(self.openAction) importAction = QAction('&Import', self) openAction.setShortcut('Ctrl+I') importAction.setStatusTip('Import A State') importAction.triggered.connect(self.importAction) saveAction = QAction('&Save', self) saveAction.setShortcut('Ctrl+S') saveAction.setStatusTip('Save Visual States') saveAction.triggered.connect(self.saveAction) saveAsAction = QAction('&Save As', self) saveAsAction.setShortcut('Ctrl+Shift+S') saveAsAction.setStatusTip('Save Visual States as New One') saveAsAction.triggered.connect(self.saveAsAction) quitAction = QAction('&Quit', self) quitAction.setShortcut('Ctrl+Q') quitAction.setStatusTip('Quit Visual States') quitAction.triggered.connect(self.quitAction) # figures menu stateAction = QAction('&State', self) stateAction.setStatusTip('Create a state') stateAction.triggered.connect(self.stateAction) transitionAction = QAction('&Transition', self) transitionAction.setStatusTip('Create a transition') transitionAction.triggered.connect(self.transitionAction) # data menu timerAction = QAction('&Timer', self) timerAction.setShortcut('Ctrl+M') timerAction.setStatusTip('Set timing of states') timerAction.triggered.connect(self.timerAction) globalNamespaceAction = QAction('&Global Namespace', self) globalNamespaceAction.setShortcut('Ctrl+G') globalNamespaceAction.setStatusTip('Open Global Namespace') globalNamespaceAction.triggered.connect(self.globalNamespaceAction) stateNamespaceAction = QAction('&State Namespace', self) stateNamespaceAction.setShortcut('Ctrl+T') stateNamespaceAction.setStatusTip('Open State Namespace') stateNamespaceAction.triggered.connect(self.localNamespaceAction) # actions menu librariesAction = QAction('&Libraries', self) librariesAction.setShortcut('Ctrl+L') librariesAction.setStatusTip('Add additional libraries') librariesAction.triggered.connect(self.librariesAction) configFileAction = QAction('&ROS Config', self) configFileAction.setShortcut('Ctrl+R') configFileAction.setStatusTip('Edit ROS configuration') configFileAction.triggered.connect(self.configFileAction) generateCppAction = QAction('&Generate C++', self) generateCppAction.setShortcut('Ctrl+U') generateCppAction.setStatusTip('Generate C++ code') generateCppAction.triggered.connect(self.generateCppAction) generatePythonAction = QAction('&Generate Python', self) generatePythonAction.setShortcut('Ctrl+Y') generatePythonAction.setStatusTip('Generate Python code') generatePythonAction.triggered.connect(self.generatePythonAction) # help menu aboutAction = QAction('&About', self) aboutAction.setShortcut('F1') aboutAction.setStatusTip('Information about VisualStates') aboutAction.triggered.connect(self.aboutAction) # create main menu menubar = self.menuBar() archieveMenu = menubar.addMenu('&File') archieveMenu.addAction(newAction) archieveMenu.addAction(openAction) archieveMenu.addAction(importAction) archieveMenu.addAction(saveAction) archieveMenu.addAction(saveAsAction) archieveMenu.addAction(quitAction) figuresMenu = menubar.addMenu('&Figures') figuresMenu.addAction(stateAction) figuresMenu.addAction(transitionAction) dataMenu = menubar.addMenu('&Data') dataMenu.addAction(timerAction) dataMenu.addAction(globalNamespaceAction) dataMenu.addAction(stateNamespaceAction) actionsMenu = menubar.addMenu('&Actions') actionsMenu.addAction(librariesAction) actionsMenu.addAction(configFileAction) actionsMenu.addAction(generateCppAction) # actionsMenu.addAction(compileCppAction) actionsMenu.addAction(generatePythonAction) helpMenu = menubar.addMenu('&Help') helpMenu.addAction(aboutAction) def newAction(self): self.automataScene.clearScene() self.treeModel.removeAll() self.fileManager.setPath("") # create new root state self.globalNamespace = Namespace('', '') self.localNamespace = Namespace('', '') self.rootState = State(0, 'root', True, self.localNamespace) self.automataScene.setActiveState(self.rootState) self.automataScene.resetIndexes() self.libraries = [] self.config = None def openAction(self): fileDialog = QFileDialog(self) fileDialog.setWindowTitle("Open VisualStates File") fileDialog.setViewMode(QFileDialog.Detail) fileDialog.setNameFilters(['VisualStates File (*.xml)']) fileDialog.setDefaultSuffix('.xml') fileDialog.setAcceptMode(QFileDialog.AcceptOpen) if fileDialog.exec_(): self.openFile(fileDialog.selectedFiles()[0]) def openFile(self, fileName): (rootState, config, libraries, globalNamespace) = self.fileManager.open(fileName) if rootState is not None: (self.rootState, self.config, self.libraries, self.globalNamespace) = (rootState, config, libraries, globalNamespace) self.automataPath = self.fileManager.fullPath self.treeModel.removeAll() self.treeModel.loadFromRoot(self.rootState) # set the active state as the loaded state self.automataScene.setActiveState(self.rootState) self.automataScene.setLastIndexes(self.rootState) else: self.showWarning("Wrong file selected", "The selected file is not a valid VisualStates file") def saveAction(self): if len(self.fileManager.getFileName()) == 0: self.saveAsAction() else: self.fileManager.save(self.rootState, self.config, self.libraries, self.globalNamespace) def saveAsAction(self): fileDialog = QFileDialog(self) fileDialog.setWindowTitle("Save VisualStates Project") fileDialog.setViewMode(QFileDialog.Detail) fileDialog.setNameFilters(['VisualStates File (*.xml)']) fileDialog.setAcceptMode(QFileDialog.AcceptSave) if fileDialog.exec_(): self.fileManager.setFullPath(fileDialog.selectedFiles()[0]) self.fileManager.save(self.rootState, self.config, self.libraries, self.globalNamespace) def quitAction(self): # print('Quit') self.close() def stateAction(self): self.automataScene.setOperationType(OpType.ADDSTATE) def transitionAction(self): self.automataScene.setOperationType(OpType.ADDTRANSITION) def importAction(self): fileDialog = QFileDialog(self) fileDialog.setWindowTitle("Import VisualStates File") fileDialog.setViewMode(QFileDialog.Detail) fileDialog.setNameFilters(['VisualStates File (*.xml)']) fileDialog.setDefaultSuffix('.xml') fileDialog.setAcceptMode(QFileDialog.AcceptOpen) if fileDialog.exec_(): tempPath = self.fileManager.getFullPath() file = self.fileManager.open(fileDialog.selectedFiles()[0]) if file[0] is not None: self.fileManager.setPath(tempPath) # if the current active state already has an initial state make sure that # there will not be any initial state in the imported state if self.activeState.getInitialChild() is not None: for childState in file[0].getChildren(): childState.setInitial(False) # Update importing Namespaces importedState, self.config, self.libraries, self.globalNamespace = self.importManager.updateAuxiliaryData(file, self) self.treeModel.loadFromRoot(importedState, self.activeState) self.automataScene.displayState(self.activeState) self.automataScene.setLastIndexes(self.rootState) else: self.showWarning("Wrong file selected", "The selected file is not a valid VisualStates file") def timerAction(self): if self.activeState is not None: timerDialog = TimerDialog('Time Step Duration', str(self.activeState.getTimeStep())) timerDialog.timeChanged.connect(self.timeStepDurationChanged) timerDialog.exec_() def globalNamespaceAction(self): self.globalNamespaceDialog = NamespaceDialog('Global Namespace', self.globalNamespace) self.globalNamespaceDialog.namespaceChanged.connect(self.globalNamespaceChanged) self.globalNamespaceDialog.exec_() def localNamespaceAction(self): self.localNamespaceDialog = NamespaceDialog('Local Namespace', self.activeNamespace) self.localNamespaceDialog.namespaceChanged.connect(self.localNamespaceChanged) self.localNamespaceDialog.exec_() def librariesAction(self): librariesDialog = LibrariesDialog('Libraries', self.libraries) librariesDialog.librariesChanged.connect(self.librariesChanged) librariesDialog.exec_() def configFileAction(self): if self.config is None: self.config = RosConfig() self.configDialog = RosConfigDialog('Config', self.config) self.configDialog.exec_() def showWarning(self, title, msg): QMessageBox.warning(self, title, msg) def showInfo(self, title, msg): QMessageBox.information(self, title, msg) def generateCppAction(self): stateList = [] if self.fileManager.hasFile(): self.getStateList(self.rootState, stateList) if self.config is None: self.config = RosConfig() generator = CppRosGenerator(self.libraries, self.config, stateList, self.globalNamespace) generator.generate(self.fileManager.getPath(), self.fileManager.getFileName()) self.showInfo('C++ Code Generation', 'C++ code generation is successful.') else: self.showWarning('C++ Generation', 'Please save the project before code generation.') # def compileCppAction(self): # # print('compile cpp action') # pass def generatePythonAction(self): stateList = [] if self.fileManager.hasFile(): self.getStateList(self.rootState, stateList) if self.config is None: self.config = RosConfig() generator = PythonRosGenerator(self.libraries, self.config, stateList, self.globalNamespace) generator.generate(self.fileManager.getPath(), self.fileManager.getFileName()) self.showInfo('Python Code Generation', 'Python code generation is successful.') else: self.showWarning('Python Generation', 'Please save the project before code generation.') def aboutAction(self): aboutDialog = AboutDialog() aboutDialog.exec_() def createTreeView(self): dockWidget = QDockWidget() dockWidget.setAllowedAreas(Qt.LeftDockWidgetArea) dockWidget.setFeatures(QDockWidget.NoDockWidgetFeatures) dockWidget.setTitleBarWidget(QWidget()) self.treeView = QTreeView() self.treeView.clicked.connect(self.treeItemClicked) self.treeModel = TreeModel() self.treeView.setModel(self.treeModel) self.upButton = QPushButton() self.upButton.setText('Up') self.upButton.clicked.connect(self.upButtonClicked) leftContainer = QWidget() leftLayout = QVBoxLayout() leftLayout.addWidget(self.treeView) leftLayout.addWidget(self.upButton) leftContainer.setLayout(leftLayout) dockWidget.setWidget(leftContainer) self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget) def createStateCanvas(self): self.stateCanvas = QGraphicsView() self.automataScene = AutomataScene() self.automataScene.setSceneRect(0, 0, 2000, 2000) self.automataScene.activeStateChanged.connect(self.activeStateChanged) self.automataScene.activeNamespaceChanged.connect(self.activeNamespaceChanged) self.automataScene.stateInserted.connect(self.stateInserted) self.automataScene.stateRemoved.connect(self.stateRemoved) self.automataScene.stateImported.connect(self.stateImported) self.automataScene.transitionInserted.connect(self.transitionInserted) self.automataScene.stateNameChangedSignal.connect(self.stateNameChanged) self.automataScene.setActiveState(self.rootState) self.setCentralWidget(self.stateCanvas) self.stateCanvas.setScene(self.automataScene) self.stateCanvas.setRenderHint(QPainter.Antialiasing) self.stateCanvas.setAcceptDrops(True) def stateInserted(self, state): if self.activeState != self.rootState: parent = self.treeModel.getByDataId(self.activeState.id) self.treeModel.insertState(state, QColor(Qt.white), parent) else: self.treeModel.insertState(state, QColor(Qt.white)) def stateRemoved(self, state): if self.activeState != self.rootState: parent = self.treeModel.getByDataId(self.activeState.id) self.treeModel.removeState(state.stateData, parent) else: self.treeModel.removeState(state.stateData) def stateImported(self): self.importAction() def transitionInserted(self, tran): # print('transition inserted:' + tran.transitionData.name) pass def stateNameChanged(self, state): dataItem = self.treeModel.getByDataId(state.stateData.id) if dataItem != None: dataItem.name = state.stateData.name self.treeModel.layoutChanged.emit() def activeStateChanged(self): if self.automataScene.activeState != self.activeState: # print('visual states active state changed:' + self.automataScene.activeState.name) self.activeState = self.automataScene.activeState if self.activeState == self.rootState: self.treeView.selectionModel().clearSelection() else: self.treeView.setCurrentIndex(self.treeModel.indexOf(self.treeModel.getByDataId(self.activeState.id))) def activeNamespaceChanged(self): if self.automataScene.activeNamespace != self.activeNamespace: self.activeNamespace = self.automataScene.activeNamespace def upButtonClicked(self): if self.activeState != None: if self.activeState.parent != None: #print(self.activeState.parent.id) self.automataScene.setActiveState(self.activeState.parent) def getStateById(self, state, id): if state.id == id: return state else: result = None for child in state.getChildren(): result = self.getStateById(child, id) if result is not None: return result return result def treeItemClicked(self, index): # print('clicked item.id:' + str(index.internalPointer().id)) state = self.getStateById(self.rootState, index.internalPointer().id) if state is not None: # set the active state as the loaded state self.automataScene.setActiveState(state) def timeStepDurationChanged(self, duration): if self.activeState is not None: self.activeState.setTimeStep(duration) def librariesChanged(self, libraries): self.libraries = libraries def globalNamespaceChanged(self): if self.globalNamespaceDialog: self.globalNamespace = self.globalNamespaceDialog.getNamespace() def localNamespaceChanged(self): if self.localNamespaceDialog: self.activeNamespace = self.localNamespaceDialog.getNamespace() def getStateList(self, state, stateList): if len(state.getChildren()) > 0: stateList.append(state) for s in state.getChildren(): self.getStateList(s, stateList)
class _LocatorDialog(QDialog): """Locator widget and implementation """ def __init__(self, parent, commandClasses): QDialog.__init__(self, parent) self._terminated = False self._commandClasses = commandClasses self._createUi() self._loadingTimer = QTimer(self) self._loadingTimer.setSingleShot(True) self._loadingTimer.setInterval(200) self._loadingTimer.timeout.connect(self._applyLoadingCompleter) self._completerLoaderThread = _CompleterLoaderThread(self) self.finished.connect(self._terminate) self._command = None self._updateCurrentCommand() def _createUi(self): self.setWindowTitle(core.project().path() or 'Locator') self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(1) biggerFont = self.font() biggerFont.setPointSizeF(biggerFont.pointSizeF() * 2) self.setFont(biggerFont) self._edit = _CompletableLineEdit(self) self._edit.updateCurrentCommand.connect(self._updateCurrentCommand) self._edit.enterPressed.connect(self._onEnterPressed) self._edit.installEventFilter(self) # catch Up, Down self.layout().addWidget(self._edit) self.setFocusProxy(self._edit) self._table = QTreeView(self) self._table.setFont(biggerFont) self._model = _CompleterModel() self._table.setModel(self._model) self._table.setItemDelegate(HTMLDelegate(self._table)) self._table.setRootIsDecorated(False) self._table.setHeaderHidden(True) self._table.clicked.connect(self._onItemClicked) self._table.setAlternatingRowColors(True) self._table.installEventFilter( self) # catch focus and give to the edit self.layout().addWidget(self._table) width = QFontMetrics(self.font()).width('x' * 64) # width of 64 'x' letters self.resize(width, width * 0.62) def _terminate(self): if not self._terminated: if self._command is not None: self._command.terminate() self._command = None self._edit.terminate() self._completerLoaderThread.terminate() core.workspace().focusCurrentDocument() self._terminated = True def _updateCurrentCommand(self): """Try to parse line edit text and set current command """ if self._terminated: return newCommand = self._parseCurrentCommand() if newCommand is not self._command: if self._command is not None: self._command.updateCompleter.disconnect( self._updateCompletion) self._command.terminate() self._command = newCommand if self._command is not None: self._command.updateCompleter.connect(self._updateCompletion) self._updateCompletion() def _updateCompletion(self): """User edited text or moved cursor. Update inline and TreeView completion """ if self._command is not None: completer = self._command.completer() if completer is not None and completer.mustBeLoaded: self._loadingTimer.start() self._completerLoaderThread.loadCompleter( self._command, completer) else: self._applyCompleter(self._command, completer) else: self._applyCompleter(None, _HelpCompleter(self._commandClasses)) def _applyLoadingCompleter(self): """Set 'Loading...' message """ self._applyCompleter(None, StatusCompleter('<i>Loading...</i>')) def onCompleterLoaded(self, command, completer): """The method called from _CompleterLoaderThread when the completer is ready This code works in the GUI thread """ self._applyCompleter(command, completer) def _applyCompleter(self, command, completer): """Apply completer. Called by _updateCompletion or by thread function when Completer is constructed """ self._loadingTimer.stop() if command is not None: command.onCompleterLoaded(completer) if completer is None: completer = _HelpCompleter([command]) if self._edit.cursorPosition() == len( self._edit.text()): # if cursor at the end of text self._edit.setInlineCompletion(completer.inline()) self._model.setCompleter(completer) if completer.columnCount() > 1: self._table.resizeColumnToContents(0) self._table.setColumnWidth(0, self._table.columnWidth(0) + 20) # 20 px spacing between columns selItem = completer.autoSelectItem() if selItem: index = self._model.createIndex(selItem[0], selItem[1]) self._table.setCurrentIndex(index) def _onItemClicked(self, index): """Item in the TreeView has been clicked. Open file, if user selected it """ if self._command is not None: fullText = self._model.completer.getFullText(index.row()) if fullText is not None: self._command.onItemClicked(fullText) if self._tryExecCurrentCommand(): self.accept() return else: self._edit.setText(self._command.lineEditText()) self._updateCurrentCommand() self._edit.setFocus() def _onEnterPressed(self): """User pressed Enter or clicked item. Execute command, if possible """ if self._table.currentIndex().isValid(): self._onItemClicked(self._table.currentIndex()) else: self._tryExecCurrentCommand() def _tryExecCurrentCommand(self): if self._command is not None and self._command.isReadyToExecute(): self._command.execute() self.accept() return True else: return False def _chooseCommand(self, words): for cmd in self._commandClasses: if cmd.command == words[0]: return cmd, words[1:] isPath = words and (words[0].startswith('/') or words[0].startswith('./') or words[0].startswith('../') or words[0].startswith('~/') or words[0][1:3] == ':\\' or words[0][1:3] == ':/') isNumber = len(words) == 1 and all([c.isdigit() for c in words[0]]) def matches(cmd): if isPath: return cmd.isDefaultPathCommand elif isNumber: return cmd.isDefaultNumericCommand else: return cmd.isDefaultCommand for cmd in self._commandClasses: if matches(cmd): return cmd, words def _parseCurrentCommand(self): """ Parse text and try to get (command, completable word index) Return None if failed to parse """ # Split line text = self._edit.commandText() words = splitLine(text) if not words: return None # Find command cmdClass, args = self._chooseCommand(words) if isinstance(self._command, cmdClass): command = self._command else: command = cmdClass() # Try to make command object try: command.setArgs(args) except InvalidCmdArgs: return None else: return command def eventFilter(self, obj, event): if obj is self._edit: if event.type() == QEvent.KeyPress and \ event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown): return self._table.event(event) elif obj is self._table: if event.type() == QEvent.FocusIn: self._edit.setFocus() return True return False
class NavigatorDock(DockWidget): def __init__(self): DockWidget.__init__(self, core.mainWindow(), '&Navigator', QIcon(':/enkiicons/goto.png'), "Alt+N") self._tags = [] self._tree = QTreeView(self) self._tree.installEventFilter(self) self._tree.setHeaderHidden(True) self.setFocusProxy(self._tree) self._filterEdit = LineEdit(self) self._filterEdit.setClearButtonVisible(True) self._filterEdit.textEdited.connect(self._applyFilter) self._filterEdit.clearButtonClicked.connect(self._applyFilter) self._filterEdit.clearButtonClicked.connect(self._tree.setFocus) self._filterEdit.clearButtonClicked.connect(self._hideFilter) self._filterEdit.installEventFilter(self) self._displayWidget = QWidget(self) layout = QVBoxLayout(self._displayWidget) layout.addWidget(self._tree) layout.addWidget(self._filterEdit) layout.setContentsMargins(0, 0, 0, 0) self.setWidget(self._displayWidget) self._tagModel = _TagModel(self._tree) self._tagModel.jumpToTagDone.connect(self._hideFilter) self._tree.setModel(self._tagModel) self._tree.activated.connect(self._tagModel.onActivated) self._tree.clicked.connect(self._tagModel.onActivated) self._tagModel.modelAboutToBeReset.connect(self._onModelAboutToBeReset) self._tagModel.modelReset.connect(self._onModelReset) self._currentTagPath = None self._errorLabel = None self._installed = False def term(self): self._tagModel.term() def install(self): if not self._installed: core.mainWindow().addDockWidget(Qt.RightDockWidgetArea, self) core.actionManager().addAction("mView/aNavigator", self.showAction()) self._installed = True def remove(self): if self._installed: core.mainWindow().removeDockWidget(self) core.actionManager().removeAction("mView/aNavigator") self.hide() self._installed = False def setTags(self, tags): self._tags = tags self._setFilteredTags(tags) self._hideFilter() if self.widget() is not self._displayWidget: self.setWidget(self._displayWidget) self._displayWidget.show() if self._errorLabel is not None: self._errorLabel.hide() def _setFilteredTags(self, tags): self._tagModel.setTags(tags) def onError(self, error): self._displayWidget.hide() if self._errorLabel is None: self._errorLabel = QLabel(self) self._errorLabel.setWordWrap(True) self._errorLabel.setText(error) if not self.widget() is self._errorLabel: self.setWidget(self._errorLabel) self._errorLabel.show() self._displayWidget.hide() def _onModelAboutToBeReset(self): currIndex = self._tree.currentIndex() self._currentTagPath = self._tagModel.tagPathForIndex(currIndex) if currIndex.isValid() else None def _onModelReset(self): self._tree.expandAll() # restore current item if self._currentTagPath is not None: index = self._tagModel.indexForTagPath(self._currentTagPath) if index.isValid(): self._tree.setCurrentIndex(index) def eventFilter(self, object_, event): if object_ is self._tree: if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Backspace: if event.modifiers() == Qt.ControlModifier: self._onTreeCtrlBackspace() else: self._onTreeBackspace() return True elif event.text() and \ (event.text().isalnum() or event.text() == '_'): self._onTreeTextTyped(event.text()) return True elif object_ is self._filterEdit: if event.type() == QEvent.KeyPress: if event.key() in (Qt.Key_Up, Qt.Key_Down): self._tree.setFocus() self._tree.event(event) return True elif event.key() in (Qt.Key_Enter, Qt.Key_Return): currIndex = self._tree.currentIndex() if currIndex.isValid(): self._tagModel.onActivated(currIndex) return DockWidget.eventFilter(self, object_, event) def _hideFilter(self): hadText = self._filterEdit.text() != '' self._filterEdit.clear() self._filterEdit.hide() if hadText: self._applyFilter() def _applyFilter(self): text = self._filterEdit.text() if text: if not text.startswith('*'): text = '*' + text if not text.endswith('*'): text = text + '*' wildcard = text.lower() filteredTags = _filterTags(wildcard, self._tags) self._setFilteredTags(filteredTags) self._tree.expandAll() if filteredTags: firstMatchingTag = _findFirstMatching(wildcard, filteredTags) path = _tagPath(firstMatchingTag) index = self._tagModel.indexForTagPath(path) self._tree.setCurrentIndex(index) else: self._setFilteredTags(self._tags) if text: self._filterEdit.show() elif not self._filterEdit.hasFocus(): self._hideFilter() def _onTreeTextTyped(self, text): self._filterEdit.setText(self._filterEdit.text() + text) self._applyFilter() def _onTreeBackspace(self): text = self._filterEdit.text() if text: self._filterEdit.setText(text[:-1]) self._applyFilter() def _onTreeCtrlBackspace(self): self._hideFilter() self._applyFilter()
class Dialog_ImageFolder(): def __init__(self, parent, title, init_path): self.w = QDialog(parent) self.parent = parent self.left = 300 self.top = 300 self.width = 600 self.height = 400 self.title = title self.dirModel = QFileSystemModel() self.dirModel.setRootPath(init_path) self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs) self.treeview = QTreeView() self.treeview.setModel(self.dirModel) self.treeview.setRootIndex(self.dirModel.index("")) self.treeview.clicked.connect(self.on_clicked) #--- Hide All Header Sections Except First ---- header = self.treeview.header() for sec in range(1, header.count()): header.setSectionHidden(sec, True) #--- ---- ---- ---- ---- ---- ---- ---- ---- -- focus_index = self.dirModel.index(init_path) self.treeview.setCurrentIndex(focus_index) self.current_row_changed() self.listview = QListView() self.listview.setViewMode(QListView.IconMode) self.listview.setIconSize(QSize(192, 192)) targetfiles1 = glob.glob(os.path.join(init_path, '*.png')) targetfiles2 = glob.glob(os.path.join(init_path, '*.tif')) targetfiles3 = glob.glob(os.path.join(init_path, '*.tiff')) targetfiles = targetfiles1 + targetfiles2 + targetfiles3 lm = _MyListModel(targetfiles, self.parent) self.listview.setModel(lm) self.sub_layout = QHBoxLayout() self.sub_layout.addWidget(self.treeview) self.sub_layout.addWidget(self.listview) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.main_layout = QVBoxLayout() self.main_layout.addLayout(self.sub_layout) self.main_layout.addWidget(self.buttonBox) self.w.setGeometry(self.left, self.top, self.width, self.height) self.w.setWindowTitle(self.title) self.w.setWindowIcon(QIcon(os.path.join(icon_dir, 'Mojo2_16.png'))) self.w.setLayout(self.main_layout) def current_row_changed(self): index = self.treeview.currentIndex() self.treeview.scrollTo(index, QAbstractItemView.EnsureVisible) self.treeview.resizeColumnToContents(0) def on_clicked(self, index): path = self.dirModel.fileInfo(index).absoluteFilePath() targetfiles1 = glob.glob(os.path.join(path, '*.png')) targetfiles2 = glob.glob(os.path.join(path, '*.tif')) targetfiles3 = glob.glob(os.path.join(path, '*.tiff')) targetfiles = targetfiles1 + targetfiles2 + targetfiles3 lm = _MyListModel(targetfiles, self.parent) self.listview.setModel(lm) def accept(self): index = self.treeview.currentIndex() self.newdir = self.dirModel.filePath(index) self.w.done(1) def reject(self): self.w.done(0) def GetValue(self): index = self.treeview.currentIndex() self.newdir = self.dirModel.filePath(index) return self.newdir
class MainWindow(QWidget): Id, Password = range(2) CONFIG_FILE = 'config' def __init__(self): super().__init__() with open(self.CONFIG_FILE, 'a'): pass self.init() def init(self): # ------ initUI self.resize(555, 245) self.setFixedSize(555, 245) self.center() self.setWindowTitle('Portal Connector') self.setWindowIcon(QIcon('gao.ico')) self.backgroundRole() palette1 = QPalette() palette1.setColor(self.backgroundRole(), QColor(250, 250, 250)) # 设置背景颜色 self.setPalette(palette1) # ------setLeftWidget self.dataGroupBox = QGroupBox("Saved", self) self.dataGroupBox.setGeometry(10, 10, 60, 20) self.dataGroupBox.setStyleSheet(MyGroupBox) self.model = QStandardItemModel(0, 2, self) self.model.setHeaderData(self.Id, Qt.Horizontal, "Id") self.model.setHeaderData(self.Password, Qt.Horizontal, "Pw") self.dataView = QTreeView(self) self.dataView.setGeometry(10, 32, 255, 150) self.dataView.setRootIsDecorated(False) self.dataView.setAlternatingRowColors(True) self.dataView.setModel(self.model) self.dataView.setStyleSheet(MyTreeView) save_btn = QPushButton('Save', self) save_btn.setGeometry(15, 195, 100, 35) save_btn.setStyleSheet(MyPushButton) delete_btn = QPushButton('Delete', self) delete_btn.setGeometry(135, 195, 100, 35) delete_btn.setStyleSheet(MyPushButton) # ------ setRightWidget username = QLabel('Id:', self) username.setGeometry(300, 45, 50, 30) username.setStyleSheet(MyLabel) self.username_edit = QLineEdit(self) self.username_edit.setGeometry(350, 40, 190, 35) self.username_edit.setStyleSheet(MyLineEdit) password = QLabel('Pw:', self) password.setGeometry(300, 100, 50, 30) password.setStyleSheet(MyLabel) self.password_edit = QLineEdit(self) self.password_edit.setGeometry(350, 95, 190, 35) self.password_edit.setStyleSheet(MyLineEdit) status_label = QLabel('Result:', self) status_label.setGeometry(295, 150, 70, 30) status_label.setStyleSheet(UnderLabel) self.status = QLabel('Disconnect', self) self.status.setGeometry(360, 150, 190, 30) self.status.setStyleSheet(UnderLabel) connect_btn = QPushButton('Connect', self) connect_btn.setGeometry(320, 195, 100, 35) connect_btn.setStyleSheet(MyPushButton) test_btn = QPushButton('Test', self) test_btn.setGeometry(440, 195, 100, 35) test_btn.setStyleSheet(MyPushButton) # ------setTabOrder self.setTabOrder(self.username_edit, self.password_edit) self.setTabOrder(self.password_edit, connect_btn) self.setTabOrder(connect_btn, test_btn) # ------setEvent self.dataView.mouseDoubleClickEvent = self.set_text self.dataView.mousePressEvent = self.set_focus delete_btn.clicked.connect(self.removeItem) connect_btn.clicked.connect(self.connect_clicked) save_btn.clicked.connect(self.save_infomation) test_btn.clicked.connect(self.test_network) self.readItem(self.CONFIG_FILE) self.connect_clicked() self.show() def connect_clicked(self): result = connect_portal(self.username_edit.text(), self.password_edit.text()) self.status.setText(result) def save_infomation(self): if self.username_edit.text() and self.password_edit.text(): try: selected = self.dataView.selectedIndexes()[0].row() self.modifyItem(selected) except IndexError: self.addItem(self.username_edit.text(), self.password_edit.text()) def test_network(self): result = test_public() self.status.setText(result) def set_text(self, event=None): try: self.username_edit.setText( self.dataView.selectedIndexes()[0].data()) self.password_edit.setText( self.dataView.selectedIndexes()[1].data()) except IndexError: pass def set_focus(self, event): index = self.dataView.indexAt(event.pos()) if not index.isValid(): self.dataView.clearSelection() else: self.dataView.setCurrentIndex(index) def readItem(self, filename): with open(filename, 'r') as f: for line in f.readlines(): self.addItem(*(line.split())) self.dataView.setCurrentIndex(self.dataView.indexAt(QPoint(1, 1))) self.set_text() def addItem(self, username, password): self.model.insertRow(0) self.model.setData(self.model.index(0, self.Id), username) self.model.setData(self.model.index(0, self.Password), password) self.save_to_file() def modifyItem(self, row): self.model.setData(self.model.index(row, self.Id), self.username_edit.text()) self.model.setData(self.model.index(row, self.Password), self.password_edit.text()) self.save_to_file() def removeItem(self): try: self.model.removeRow(self.dataView.selectedIndexes()[0].row()) self.save_to_file() except IndexError: pass def save_to_file(self): with open(self.CONFIG_FILE, 'w') as f: for x in range(self.model.rowCount()): for y in range(self.model.columnCount()): f.write(self.model.data(self.model.index(x, y)) + " ") f.write("\n") def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft())
class AddBookmarkDialog(QDialog, Ui_AddBookmarkDialog): """ Class implementing a dialog to add a bookmark or a bookmark folder. """ def __init__(self, parent=None, bookmarksManager=None): """ Constructor @param parent reference to the parent widget (QWidget) @param bookmarksManager reference to the bookmarks manager object (BookmarksManager) """ super(AddBookmarkDialog, self).__init__(parent) self.setupUi(self) self.__bookmarksManager = bookmarksManager self.__addedNode = None self.__addFolder = False if self.__bookmarksManager is None: import Helpviewer.HelpWindow self.__bookmarksManager = \ Helpviewer.HelpWindow.HelpWindow.bookmarksManager() self.__proxyModel = AddBookmarkProxyModel(self) model = self.__bookmarksManager.bookmarksModel() self.__proxyModel.setSourceModel(model) self.__treeView = QTreeView(self) self.__treeView.setModel(self.__proxyModel) self.__treeView.expandAll() self.__treeView.header().setStretchLastSection(True) self.__treeView.header().hide() self.__treeView.setItemsExpandable(False) self.__treeView.setRootIsDecorated(False) self.__treeView.setIndentation(10) self.__treeView.show() self.locationCombo.setModel(self.__proxyModel) self.locationCombo.setView(self.__treeView) self.addressEdit.setInactiveText(self.tr("Url")) self.nameEdit.setInactiveText(self.tr("Title")) self.resize(self.sizeHint()) def setUrl(self, url): """ Public slot to set the URL of the new bookmark. @param url URL of the bookmark (string) """ self.addressEdit.setText(url) self.resize(self.sizeHint()) def url(self): """ Public method to get the URL of the bookmark. @return URL of the bookmark (string) """ return self.addressEdit.text() def setTitle(self, title): """ Public method to set the title of the new bookmark. @param title title of the bookmark (string) """ self.nameEdit.setText(title) def title(self): """ Public method to get the title of the bookmark. @return title of the bookmark (string) """ return self.nameEdit.text() def setDescription(self, description): """ Public method to set the description of the new bookmark. @param description description of the bookamrk (string) """ self.descriptionEdit.setPlainText(description) def description(self): """ Public method to get the description of the bookmark. @return description of the bookamrk (string) """ return self.descriptionEdit.toPlainText() def setCurrentIndex(self, idx): """ Public method to set the current index. @param idx current index to be set (QModelIndex) """ proxyIndex = self.__proxyModel.mapFromSource(idx) self.__treeView.setCurrentIndex(proxyIndex) self.locationCombo.setCurrentIndex(proxyIndex.row()) def currentIndex(self): """ Public method to get the current index. @return current index (QModelIndex) """ idx = self.locationCombo.view().currentIndex() idx = self.__proxyModel.mapToSource(idx) return idx def setFolder(self, folder): """ Public method to set the dialog to "Add Folder" mode. @param folder flag indicating "Add Folder" mode (boolean) """ self.__addFolder = folder if folder: self.setWindowTitle(self.tr("Add Folder")) self.addressEdit.setVisible(False) else: self.setWindowTitle(self.tr("Add Bookmark")) self.addressEdit.setVisible(True) self.resize(self.sizeHint()) def isFolder(self): """ Public method to test, if the dialog is in "Add Folder" mode. @return flag indicating "Add Folder" mode (boolean) """ return self.__addFolder def addedNode(self): """ Public method to get a reference to the added bookmark node. @return reference to the added bookmark node (BookmarkNode) """ return self.__addedNode def accept(self): """ Public slot handling the acceptance of the dialog. """ if (not self.__addFolder and not self.addressEdit.text()) or \ not self.nameEdit.text(): super(AddBookmarkDialog, self).accept() return from .BookmarkNode import BookmarkNode idx = self.currentIndex() if not idx.isValid(): idx = self.__bookmarksManager.bookmarksModel().index(0, 0) parent = self.__bookmarksManager.bookmarksModel().node(idx) if self.__addFolder: type_ = BookmarkNode.Folder else: type_ = BookmarkNode.Bookmark bookmark = BookmarkNode(type_) bookmark.title = self.nameEdit.text() if not self.__addFolder: bookmark.url = self.addressEdit.text() bookmark.desc = self.descriptionEdit.toPlainText() self.__bookmarksManager.addBookmark(parent, bookmark) self.__addedNode = bookmark super(AddBookmarkDialog, self).accept()
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel(self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) # Add auto-hide capability. self._waitForCtrlRelease = False core.actionManager().action("mNavigation/aNext").triggered.connect( self._setWaitForCtrlRelease) core.actionManager().action("mNavigation/aPrevious").triggered.connect( self._setWaitForCtrlRelease) QApplication.instance().installEventFilter(self) def terminate(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") QApplication.instance().removeEventFilter(self) def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) @pyqtSlot(Document, Document) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() @pyqtSlot(QItemSelection, QItemSelection) def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() @pyqtSlot(QPoint) def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos)) def _setWaitForCtrlRelease(self): # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten # by the QAction and don't even show up in the event filter below. We # want to avoid waiting for a Ctrl release if the menu item brought us # here. As a workaround, check that Ctrl is pressed. If so, it's # unlikely to be the menu item. if QApplication.instance().keyboardModifiers() & Qt.ControlModifier: self._waitForCtrlRelease = True self.show() else: # If this was a menu selection, then update the MRU list. We can't # do this now, since the current document hasn't been changed yet. QTimer.singleShot(0, self.model.sortDocuments) def eventFilter(self, obj, event): """An event filter that looks for ctrl key releases and focus out events.""" # Wait for the user to release the Ctrl key. if ( self._waitForCtrlRelease and event.type() == QEvent.KeyRelease and event.key() == Qt.Key_Control and event.modifiers() == Qt.NoModifier): self.model.sortDocuments() self._waitForCtrlRelease = False if not self.isPinned(): self.hide() # Look for a focus out event sent by the containing widget's focus # proxy. if event.type() == QEvent.FocusOut and obj == self.focusProxy(): self.model.sortDocuments() return QObject.eventFilter(self, obj, event)
class Widget(QWidget): def __init__(self, panel): super(Widget, self).__init__(panel) layout = QVBoxLayout() self.setLayout(layout) layout.setSpacing(0) self.searchEntry = SearchLineEdit() self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu) self.textView = QTextBrowser() applyButton = QToolButton(autoRaise=True) editButton = QToolButton(autoRaise=True) addButton = QToolButton(autoRaise=True) self.menuButton = QPushButton(flat=True) menu = QMenu(self.menuButton) self.menuButton.setMenu(menu) splitter = QSplitter(Qt.Vertical) top = QHBoxLayout() layout.addLayout(top) splitter.addWidget(self.treeView) splitter.addWidget(self.textView) layout.addWidget(splitter) splitter.setSizes([200, 100]) splitter.setCollapsible(0, False) top.addWidget(self.searchEntry) top.addWidget(applyButton) top.addSpacing(10) top.addWidget(addButton) top.addWidget(editButton) top.addWidget(self.menuButton) # action generator for actions added to search entry def act(slot, icon=None): a = QAction(self, triggered=slot) self.addAction(a) a.setShortcutContext(Qt.WidgetWithChildrenShortcut) icon and a.setIcon(icons.get(icon)) return a # hide if ESC pressed in lineedit a = act(self.slotEscapePressed) a.setShortcut(QKeySequence(Qt.Key_Escape)) # import action a = self.importAction = act(self.slotImport, 'document-open') menu.addAction(a) # export action a = self.exportAction = act(self.slotExport, 'document-save-as') menu.addAction(a) # apply button a = self.applyAction = act(self.slotApply, 'edit-paste') applyButton.setDefaultAction(a) menu.addSeparator() menu.addAction(a) # add button a = self.addAction_ = act(self.slotAdd, 'list-add') a.setShortcut(QKeySequence(Qt.Key_Insert)) addButton.setDefaultAction(a) menu.addSeparator() menu.addAction(a) # edit button a = self.editAction = act(self.slotEdit, 'document-edit') a.setShortcut(QKeySequence(Qt.Key_F2)) editButton.setDefaultAction(a) menu.addAction(a) # set shortcut action a = self.shortcutAction = act( self.slotShortcut, 'preferences-desktop-keyboard-shortcuts') menu.addAction(a) # delete action a = self.deleteAction = act(self.slotDelete, 'list-remove') a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete)) menu.addAction(a) # restore action a = self.restoreAction = act(self.slotRestore) menu.addSeparator() menu.addAction(a) # help button a = self.helpAction = act(self.slotHelp, 'help-contents') menu.addSeparator() menu.addAction(a) self.treeView.setSelectionBehavior(QTreeView.SelectRows) self.treeView.setSelectionMode(QTreeView.ExtendedSelection) self.treeView.setRootIsDecorated(False) self.treeView.setAllColumnsShowFocus(True) self.treeView.setModel(model.model()) self.treeView.setCurrentIndex(QModelIndex()) # signals self.searchEntry.returnPressed.connect(self.slotReturnPressed) self.searchEntry.textChanged.connect(self.updateFilter) self.treeView.doubleClicked.connect(self.slotDoubleClicked) self.treeView.customContextMenuRequested.connect(self.showContextMenu) self.treeView.selectionModel().currentChanged.connect(self.updateText) self.treeView.model().dataChanged.connect(self.updateFilter) # highlight text self.highlighter = highlight.Highlighter(self.textView.document()) # complete on snippet variables self.searchEntry.setCompleter( QCompleter([ ':icon', ':indent', ':menu', ':name', ':python', ':selection', ':set', ':symbol', ':template', ':template-run' ], self.searchEntry)) self.readSettings() app.settingsChanged.connect(self.readSettings) app.translateUI(self) self.updateColumnSizes() self.setAcceptDrops(True) def dropEvent(self, ev): if not ev.source() and ev.mimeData().hasUrls(): filename = ev.mimeData().urls()[0].toLocalFile() if filename: ev.accept() from . import import_export import_export.load(filename, self) def dragEnterEvent(self, ev): if not ev.source() and ev.mimeData().hasUrls(): ev.accept() def translateUI(self): try: self.searchEntry.setPlaceholderText(_("Search...")) except AttributeError: pass # not in Qt 4.6 shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText) self.menuButton.setText(_("&Menu")) self.addAction_.setText(_("&Add...")) self.addAction_.setToolTip( _("Add a new snippet. ({key})").format( key=shortcut(self.addAction_))) self.editAction.setText(_("&Edit...")) self.editAction.setToolTip( _("Edit the current snippet. ({key})").format( key=shortcut(self.editAction))) self.shortcutAction.setText(_("Configure Keyboard &Shortcut...")) self.deleteAction.setText(_("&Remove")) self.deleteAction.setToolTip(_("Remove the selected snippets.")) self.applyAction.setText(_("A&pply")) self.applyAction.setToolTip(_("Apply the current snippet.")) self.importAction.setText(_("&Import...")) self.importAction.setToolTip(_("Import snippets from a file.")) self.exportAction.setText(_("E&xport...")) self.exportAction.setToolTip(_("Export snippets to a file.")) self.restoreAction.setText(_("Restore &Built-in Snippets...")) self.restoreAction.setToolTip( _("Restore deleted or changed built-in snippets.")) self.helpAction.setText(_("&Help")) self.searchEntry.setToolTip( _("Enter text to search in the snippets list.\n" "See \"What's This\" for more information.")) self.searchEntry.setWhatsThis(''.join( map("<p>{0}</p>\n".format, ( _("Enter text to search in the snippets list, and " "press Enter to apply the currently selected snippet."), _("If the search text fully matches the value of the '{name}' variable " "of a snippet, that snippet is selected.").format( name="name"), _("If the search text starts with a colon ':', the rest of the " "search text filters snippets that define the given variable. " "After a space a value can also be entered, snippets will then " "match if the value of the given variable contains the text after " "the space."), _("E.g. entering {menu} will show all snippets that are displayed " "in the insert menu.").format(menu="<code>:menu</code>"), )))) def sizeHint(self): return self.parent().mainwindow().size() / 4 def readSettings(self): data = textformats.formatData('editor') self.textView.setFont(data.font) self.textView.setPalette(data.palette()) def showContextMenu(self, pos): """Called when the user right-clicks the tree view.""" self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos)) def slotReturnPressed(self): """Called when the user presses Return in the search entry. Applies current snippet.""" name = self.currentSnippet() if name: view = self.parent().mainwindow().currentView() insert.insert(name, view) self.parent().hide() # make configurable? view.setFocus() def slotEscapePressed(self): """Called when the user presses ESC in the search entry. Hides the panel.""" self.parent().hide() self.parent().mainwindow().currentView().setFocus() def slotDoubleClicked(self, index): name = self.treeView.model().name(index) view = self.parent().mainwindow().currentView() insert.insert(name, view) def slotAdd(self): """Called when the user wants to add a new snippet.""" edit.Edit(self, None) def slotEdit(self): """Called when the user wants to edit a snippet.""" name = self.currentSnippet() if name: edit.Edit(self, name) def slotShortcut(self): """Called when the user selects the Configure Shortcut action.""" from widgets import shortcuteditdialog name = self.currentSnippet() if name: collection = self.parent().snippetActions action = actions.action(name, None, collection) default = collection.defaults().get(name) mgr = actioncollectionmanager.manager(self.parent().mainwindow()) cb = mgr.findShortcutConflict dlg = shortcuteditdialog.ShortcutEditDialog( self, cb, (collection, name)) if dlg.editAction(action, default): mgr.removeShortcuts(action.shortcuts()) collection.setShortcuts(name, action.shortcuts()) self.treeView.update() def slotDelete(self): """Called when the user wants to delete the selected rows.""" rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()), reverse=True) if rows: for row in rows: name = self.treeView.model().names()[row] self.parent().snippetActions.setShortcuts(name, []) self.treeView.model().removeRow(row) self.updateFilter() def slotApply(self): """Called when the user clicks the apply button. Applies current snippet.""" name = self.currentSnippet() if name: view = self.parent().mainwindow().currentView() insert.insert(name, view) def slotImport(self): """Called when the user activates the import action.""" filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files")) caption = app.caption(_("dialog title", "Import Snippets")) filename = None filename = QFileDialog.getOpenFileName(self, caption, filename, filetypes)[0] if filename: from . import import_export import_export.load(filename, self) def slotExport(self): """Called when the user activates the export action.""" allrows = [ row for row in range(model.model().rowCount()) if not self.treeView.isRowHidden(row, QModelIndex()) ] selectedrows = [ i.row() for i in self.treeView.selectedIndexes() if i.column() == 0 and i.row() in allrows ] names = self.treeView.model().names() names = [names[row] for row in selectedrows or allrows] filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files")) n = len(names) caption = app.caption( _("dialog title", "Export {num} Snippet", "Export {num} Snippets", n).format(num=n)) filename = QFileDialog.getSaveFileName(self, caption, None, filetypes)[0] if filename: from . import import_export try: import_export.save(names, filename) except (IOError, OSError) as e: QMessageBox.critical( self, _("Error"), _("Can't write to destination:\n\n{url}\n\n{error}"). format(url=filename, error=e.strerror)) def slotRestore(self): """Called when the user activates the Restore action.""" from . import restore dlg = restore.RestoreDialog(self) dlg.setWindowModality(Qt.WindowModal) dlg.populate() dlg.show() dlg.finished.connect(dlg.deleteLater) def slotHelp(self): """Called when the user clicks the small help button.""" userguide.show("snippets") def currentSnippet(self): """Returns the name of the current snippet if it is visible.""" row = self.treeView.currentIndex().row() if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()): return self.treeView.model().names()[row] def updateFilter(self): """Called when the text in the entry changes, updates search results.""" text = self.searchEntry.text() ltext = text.lower() filterVars = text.startswith(':') if filterVars: try: fvar, fval = text[1:].split(None, 1) fhide = lambda v: v.get(fvar) in (True, None ) or fval not in v.get(fvar) except ValueError: fvar = text[1:].strip() fhide = lambda v: not v.get(fvar) for row in range(self.treeView.model().rowCount()): name = self.treeView.model().names()[row] nameid = snippets.get(name).variables.get('name', '') if filterVars: hide = fhide(snippets.get(name).variables) elif nameid == text: i = self.treeView.model().createIndex(row, 0) self.treeView.selectionModel().setCurrentIndex( i, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) hide = False elif nameid.lower().startswith(ltext): hide = False elif ltext in snippets.title(name).lower(): hide = False else: hide = True self.treeView.setRowHidden(row, QModelIndex(), hide) self.updateText() def updateText(self): """Called when the current snippet changes.""" name = self.currentSnippet() self.textView.clear() if name: s = snippets.get(name) self.highlighter.setPython('python' in s.variables) self.textView.setPlainText(s.text) def updateColumnSizes(self): self.treeView.resizeColumnToContents(0) self.treeView.resizeColumnToContents(1)
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setMinimumSize(QSize(480, 80)) self.setWindowTitle("PyQtSample") central_widget = QWidget(self) self.setCentralWidget(central_widget) hbox_layout = QHBoxLayout(self) central_widget.setLayout(hbox_layout) buttons_background = QWidget(self) buttons_layout = QHBoxLayout() buttons_background.setLayout(buttons_layout) self.add_button = QPushButton("Add") buttons_layout.addWidget(self.add_button) self.remove_button = QPushButton("Remove") buttons_layout.addWidget(self.remove_button) tree_layout = QVBoxLayout(self) self.tree_view = QTreeView() self.tree_view.header().hide() self.tree_view.setMaximumWidth(300) self.tree_model = ObjectsModel.TreeModel() self.tree_view.setModel(self.tree_model) tree_layout.addWidget(buttons_background) tree_layout.addWidget(self.tree_view) hbox_layout.addLayout(tree_layout) self.graphics_view = QGraphicsView() self.scene = QGraphicsScene() self.graphics_view.setScene(self.scene) hbox_layout.addWidget(self.graphics_view) self.properties_view = QTableView() self.properties_view.setMaximumWidth(300) self.properties_model = PropertiesModel.TableModel() self.properties_view.setModel(self.properties_model) hbox_layout.addWidget(self.properties_view) self.init_menu() self.test() self.connectSignals() def connectSignals(self): self.add_button.clicked.connect(self.onAddClicked) self.remove_button.clicked.connect(self.onRemoveClicked) self.tree_view.clicked.connect(self.onClicked) self.properties_model.dataChanged.connect(self.onPropertyChanged) def onPropertyChanged(self): self.rebuildModel() def rebuildModel(self): #save index hierarhy indexes = [] tmp_index = self.tree_view.currentIndex() indexes.append(tmp_index) while tmp_index.parent().isValid(): indexes.append(tmp_index.parent()) tmp_index = tmp_index.parent() self.tree_model.initRoot(self.items) self.tree_view.expandAll() if len(indexes) == 0: self.onClicked(self.tree_model.index(0, 0)) else: last_index = indexes.pop(-1) index = self.tree_model.index(last_index.row(), last_index.column()) #restore index hierarchy while len(indexes) > 0: last_index = indexes.pop(-1) index = self.tree_model.index(last_index.row(), last_index.column(), index) if index.isValid(): self.onClicked(index) else: self.onClicked(self.tree_model.index(0, 0)) def init_menu(self): exit_action = QAction("&Exit", self) exit_action.setShortcut('Ctrl+Q') exit_action.triggered.connect(qApp.quit) file_menu = self.menuBar().addMenu("&File") file_menu.addAction(QAction("Open", self)) file_menu.addAction(QAction("Save", self)) file_menu.addAction(QAction("SaveAs", self)) file_menu.addSeparator() file_menu.addAction(exit_action) def appendObjectOnScene(self, object=DataStructures.Object): if object.rect.isValid(): self.scene.addRect(object.rect, object.color) for child in object.childrens: self.appendObjectOnScene(child) def onClicked(self, index): object = index.data(Qt.UserRole + 1) self.properties_model.initProperties(object) self.scene.clear() self.appendObjectOnScene(object) self.tree_view.setCurrentIndex(index) def onAddClicked(self): index = self.tree_view.currentIndex() print(index) object = index.data(Qt.UserRole + 1) print(object.description()) object.add_children(DataStructures.Object("New item")) self.rebuildModel() def onRemoveClicked(self): index = self.tree_view.currentIndex() object = index.data(Qt.UserRole + 1) if not object.parent: self.items.remove(object) else: object.parent.childrens.remove(object) self.rebuildModel() def test(self): self.items = [] static = DataStructures.Object( "Static", DataStructures.createRect(0, 0, 800, 200)) static.add_children(DataStructures.Object("child_1")) static.add_children(DataStructures.Object("child_2")) static.add_children(DataStructures.Object("child_3")) static.color = QColor(200, 0, 0).name() static.childrens[0].add_children( DataStructures.Object("child_1.1", DataStructures.createRect(40, 40, 80, 40))) self.items.append(static) dynamic = DataStructures.Object( "Dynamic", DataStructures.createRect(0, 0, 200, 800)) dynamic.add_children(DataStructures.Object("child_1")) dynamic.add_children(DataStructures.Object("child_2")) dynamic.add_children(DataStructures.Object("child_3")) dynamic.childrens[2].add_children(DataStructures.Object("child_2.1")) dynamic.color = QColor(0, 0, 200).name() self.items.append(dynamic) self.rebuildModel()
class FilenamePrompt(_BasePrompt): """A prompt for a filename.""" def __init__(self, question, parent=None): super().__init__(question, parent) self._init_texts(question) self._init_key_label() self._lineedit = LineEdit(self) if question.default: self._lineedit.setText(question.default) self._lineedit.textEdited.connect(self._set_fileview_root) self._vbox.addWidget(self._lineedit) self.setFocusProxy(self._lineedit) self._init_fileview() self._set_fileview_root(question.default) if config.val.prompt.filebrowser: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self._to_complete = '' @pyqtSlot(str) def _set_fileview_root(self, path, *, tabbed=False): """Set the root path for the file display.""" separators = os.sep if os.altsep is not None: separators += os.altsep dirname = os.path.dirname(path) basename = os.path.basename(path) if not tabbed: self._to_complete = '' try: if not path: pass elif path in separators and os.path.isdir(path): # Input "/" -> don't strip anything pass elif path[-1] in separators and os.path.isdir(path): # Input like /foo/bar/ -> show /foo/bar/ contents path = path.rstrip(separators) elif os.path.isdir(dirname) and not tabbed: # Input like /foo/ba -> show /foo contents path = dirname self._to_complete = basename else: return except OSError: log.prompt.exception("Failed to get directory information") return root = self._file_model.setRootPath(path) self._file_view.setRootIndex(root) @pyqtSlot(QModelIndex) def _insert_path(self, index, *, clicked=True): """Handle an element selection. Args: index: The QModelIndex of the selected element. clicked: Whether the element was clicked. """ if index == QModelIndex(): path = os.path.join(self._file_model.rootPath(), self._to_complete) else: path = os.path.normpath(self._file_model.filePath(index)) if clicked: path += os.sep else: # On Windows, when we have C:\foo and tab over .., we get C:\ path = path.rstrip(os.sep) log.prompt.debug('Inserting path {}'.format(path)) self._lineedit.setText(path) self._lineedit.setFocus() self._set_fileview_root(path, tabbed=True) if clicked: # Avoid having a ..-subtree highlighted self._file_view.setCurrentIndex(QModelIndex()) def _init_fileview(self): self._file_view = QTreeView(self) self._file_model = QFileSystemModel(self) self._file_view.setModel(self._file_model) self._file_view.clicked.connect(self._insert_path) if config.val.prompt.filebrowser: self._vbox.addWidget(self._file_view) else: self._file_view.hide() # Only show name self._file_view.setHeaderHidden(True) for col in range(1, 4): self._file_view.setColumnHidden(col, True) # Nothing selected initially self._file_view.setCurrentIndex(QModelIndex()) # The model needs to be sorted so we get the correct first/last index self._file_model.directoryLoaded.connect( lambda: self._file_model.sort(0)) def accept(self, value=None, save=False): self._check_save_support(save) text = value if value is not None else self._lineedit.text() text = downloads.transform_path(text) if text is None: message.error("Invalid filename") return False self.question.answer = text return True def item_focus(self, which): # This duplicates some completion code, but I don't see a nicer way... assert which in ['prev', 'next'], which selmodel = self._file_view.selectionModel() parent = self._file_view.rootIndex() first_index = self._file_model.index(0, 0, parent) row = self._file_model.rowCount(parent) - 1 last_index = self._file_model.index(row, 0, parent) if not first_index.isValid(): # No entries return assert last_index.isValid() idx = selmodel.currentIndex() if not idx.isValid(): # No item selected yet idx = last_index if which == 'prev' else first_index elif which == 'prev': idx = self._file_view.indexAbove(idx) else: assert which == 'next', which idx = self._file_view.indexBelow(idx) # wrap around if we arrived at beginning/end if not idx.isValid(): idx = last_index if which == 'prev' else first_index idx = self._do_completion(idx, which) selmodel.setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect | # type: ignore[arg-type] QItemSelectionModel.Rows) self._insert_path(idx, clicked=False) def _do_completion(self, idx, which): filename = self._file_model.fileName(idx) while not filename.startswith(self._to_complete) and idx.isValid(): if which == 'prev': idx = self._file_view.indexAbove(idx) else: assert which == 'next', which idx = self._file_view.indexBelow(idx) filename = self._file_model.fileName(idx) return idx def _allowed_commands(self): return [('prompt-accept', 'Accept'), ('leave-mode', 'Abort')]
class FileBrowserWidget(QWidget): on_open = pyqtSignal(str) def __init__(self): super().__init__() self.initUI() def initUI(self): self.model = QFileSystemModel() self.rootFolder = '' self.model.setRootPath(self.rootFolder) self.tree = QTreeView() self.tree.setModel(self.model) self.tree.setAnimated(False) self.tree.setIndentation(20) self.tree.setSortingEnabled(True) self.tree.sortByColumn(0, 0) self.tree.setColumnWidth(0, 200) self.tree.setDragEnabled(True) self.tree.setWindowTitle("Dir View") self.tree.resize(640, 480) self.tree.doubleClicked.connect(self.onDblClick) self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect( self.onCustomContextMenuRequested) windowLayout = QVBoxLayout() windowLayout.addWidget(self.tree) windowLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(windowLayout) def onCustomContextMenuRequested(self, point): index = self.tree.indexAt(point) selectedFile = None selectedFolder = None ctx = QMenu("Context menu", self) if index.isValid(): file = self.model.fileInfo(index) selectedFile = file.absoluteFilePath() selectedFolder = selectedFile if file.isDir( ) else file.absolutePath() if file.isDir(): ctx.addAction( "Open in file manager", lambda: QDesktopServices.openUrl( QUrl.fromLocalFile(selectedFile))) if not file.isDir(): for wndTyp, meta in WindowTypes.types: text = 'Open with ' + meta.get('displayName', meta['name']) print(wndTyp, meta) ctx.addAction( QAction(text, self, statusTip=text, triggered=lambda dummy, meta=meta: navigate( "WINDOW", "Type=" + meta['name'], "FileName=" + selectedFile))) ctx.addSeparator() ctx.addAction("Set root folder ...", lambda: self.selectRootFolder(preselect=selectedFolder)) ctx.exec(self.tree.viewport().mapToGlobal(point)) def selectRootFolder(self, preselect=None): if preselect == None: preselect = self.rootFolder dir = QFileDialog.getExistingDirectory(self, "Set root folder", preselect) if dir != None: self.setRoot(dir) def setRoot(self, dir): self.rootFolder = dir self.model.setRootPath(dir) self.tree.setRootIndex(self.model.index(dir)) def onDblClick(self, index): if index.isValid(): file = self.model.fileInfo(index) if not file.isDir(): navigate("OPEN", "FileName=" + file.absoluteFilePath()) def saveState(self): if self.tree.currentIndex().isValid(): info = self.model.fileInfo(self.tree.currentIndex()) return {"sel": info.absoluteFilePath(), "root": self.rootFolder} def restoreState(self, state): try: self.setRoot(state["root"]) except: pass try: idx = self.model.index(state["sel"]) if idx.isValid(): self.tree.expand(idx) self.tree.setCurrentIndex(idx) self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter) except: pass
class VisualStates(QMainWindow): def __init__(self, parent=None): super(QMainWindow, self).__init__() self.setWindowTitle("VisualStates") self.configDialog = None # root state self.rootState = State(0, "root", True) self.activeState = self.rootState # create status bar self.statusBar() self.createMenu() self.createTreeView() self.createStateCanvas() self.setGeometry(0, 0, 800, 600) self.show() self.fileManager = FileManager() self.libraries = [] self.config = None self.interfaceHeaderMap = Interfaces.getInterfaces() def createMenu(self): # create actions newAction = QAction('&New', self) newAction.setShortcut('Ctrl+N') newAction.setStatusTip('Create New Visual States') newAction.triggered.connect(self.newAction) openAction = QAction('&Open', self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Open Visual States') openAction.triggered.connect(self.openAction) saveAction = QAction('&Save', self) saveAction.setShortcut('Ctrl+S') saveAction.setStatusTip('Save Visual States') saveAction.triggered.connect(self.saveAction) saveAsAction = QAction('&Save As', self) saveAsAction.setShortcut('Ctrl+S') saveAsAction.setStatusTip('Save Visual States as New One') saveAsAction.triggered.connect(self.saveAsAction) quitAction = QAction('&Quit', self) quitAction.setShortcut('Ctrl+Q') quitAction.setStatusTip('Quit Visual States') quitAction.triggered.connect(self.quitAction) # figures menu stateAction = QAction('&State', self) stateAction.setStatusTip('Create a state') stateAction.triggered.connect(self.stateAction) transitionAction = QAction('&Transition', self) transitionAction.setStatusTip('Create a transition') transitionAction.triggered.connect(self.transitionAction) # data menu timerAction = QAction('&Timer', self) timerAction.setShortcut('Ctrl+M') timerAction.setStatusTip('Set timing of states') timerAction.triggered.connect(self.timerAction) variablesAction = QAction('&Variables', self) variablesAction.setShortcut('Ctrl+V') variablesAction.setStatusTip('Define state variables') variablesAction.triggered.connect(self.variablesAction) functionsAction = QAction('&Functions', self) functionsAction.setShortcut('Ctrl+F') functionsAction.setStatusTip('Define functions') functionsAction.triggered.connect(self.functionsAction) # actions menu librariesAction = QAction('&Libraries', self) librariesAction.setShortcut('Ctrl+L') librariesAction.setStatusTip('Add additional libraries') librariesAction.triggered.connect(self.librariesAction) configFileAction = QAction('&Config File', self) configFileAction.setShortcut('Ctrl+C') configFileAction.setStatusTip('Edit configuration file') configFileAction.triggered.connect(self.configFileAction) generateCppAction = QAction('&Generate C++', self) generateCppAction.setShortcut('Ctrl+G') generateCppAction.setStatusTip('Generate C++ code') generateCppAction.triggered.connect(self.generateCppAction) compileCppAction = QAction('&Compile C++', self) compileCppAction.setShortcut('Ctrl+P') compileCppAction.setStatusTip('Compile generated C++ code') compileCppAction.triggered.connect(self.compileCppAction) generatePythonAction = QAction('&Generate Python', self) generatePythonAction.setShortcut('Ctrl+Y') generatePythonAction.setStatusTip('Generate Python code') generatePythonAction.triggered.connect(self.generatePythonAction) # help menu aboutAction = QAction('&About', self) aboutAction.setShortcut('F1') aboutAction.setStatusTip('Information about VisualStates') aboutAction.triggered.connect(self.aboutAction) # create main menu menubar = self.menuBar() archieveMenu = menubar.addMenu('&File') archieveMenu.addAction(newAction) archieveMenu.addAction(openAction) archieveMenu.addAction(saveAction) archieveMenu.addAction(saveAsAction) archieveMenu.addAction(quitAction) figuresMenu = menubar.addMenu('&Figures') figuresMenu.addAction(stateAction) figuresMenu.addAction(transitionAction) dataMenu = menubar.addMenu('&Data') dataMenu.addAction(timerAction) dataMenu.addAction(variablesAction) dataMenu.addAction(functionsAction) actionsMenu = menubar.addMenu('&Actions') actionsMenu.addAction(librariesAction) actionsMenu.addAction(configFileAction) actionsMenu.addAction(generateCppAction) actionsMenu.addAction(compileCppAction) actionsMenu.addAction(generatePythonAction) helpMenu = menubar.addMenu('&Help') helpMenu.addAction(aboutAction) def newAction(self): self.automataScene.clearScene() # create new root state self.rootState = State(0, 'root', True) self.automataScene.setActiveState(self.rootState) self.automataScene.resetIndexes() def openAction(self): fileDialog = QFileDialog(self) fileDialog.setWindowTitle("Open VisualStates File") fileDialog.setViewMode(QFileDialog.Detail) fileDialog.setNameFilters(['VisualStates File (*.xml)']) fileDialog.setDefaultSuffix('.xml') fileDialog.setAcceptMode(QFileDialog.AcceptOpen) if fileDialog.exec_(): (self.rootState, self.config, self.libraries) = self.fileManager.open(fileDialog.selectedFiles()[0]) self.treeModel.removeAll() self.treeModel.loadFromRoot(self.rootState) # set the active state as the loaded state self.automataScene.setActiveState(self.rootState) self.automataScene.setLastIndexes(self.rootState) # print(str(self.config)) # else: # print('open is canceled') def saveAction(self): if len(self.fileManager.getFileName()) == 0: self.saveAsAction() else: self.fileManager.save(self.rootState, self.config, self.libraries) def saveAsAction(self): fileDialog = QFileDialog(self) fileDialog.setWindowTitle("Save VisualStates Project") fileDialog.setViewMode(QFileDialog.Detail) fileDialog.setNameFilters(['VisualStates File (*.xml)']) fileDialog.setAcceptMode(QFileDialog.AcceptSave) if fileDialog.exec_(): self.fileManager.setFullPath(fileDialog.selectedFiles()[0]) self.fileManager.save(self.rootState, self.config, self.libraries) # else: # print('file dialog canceled') def quitAction(self): # print('Quit') self.close() def stateAction(self): self.automataScene.setOperationType(OpType.ADDSTATE) def transitionAction(self): self.automataScene.setOperationType(OpType.ADDTRANSITION) def timerAction(self): if self.activeState is not None: timerDialog = TimerDialog('Time Step Duration', str(self.activeState.getTimeStep())) timerDialog.timeChanged.connect(self.timeStepDurationChanged) timerDialog.exec_() def variablesAction(self): if self.activeState is not None: variablesDialog = CodeDialog('Variables', self.activeState.getVariables()) variablesDialog.codeChanged.connect(self.variablesChanged) variablesDialog.exec_() else: self.showWarning('Choose a state', 'You can create variables only for a selected state') def functionsAction(self): if self.activeState is not None: functionsDialog = CodeDialog('Functions', self.activeState.getFunctions()) functionsDialog.codeChanged.connect(self.functionsChanged) functionsDialog.exec_() else: self.showWarning('Choose a state', 'You can create functions only for a selected state') def librariesAction(self): librariesDialog = LibrariesDialog('Libraries', self.libraries) librariesDialog.librariesChanged.connect(self.librariesChanged) librariesDialog.exec_() def configFileAction(self): self.configDialog = ConfigDialog('Config', self.config) self.configDialog.configChanged.connect(self.configChanged) self.configDialog.exec_() def showWarning(self, title, msg): QMessageBox.warning(self, title, msg) def showInfo(self, title, msg): QMessageBox.information(self, title, msg) def generateCppAction(self): stateList = [] if self.fileManager.hasFile(): self.getStateList(self.rootState, stateList) if self.config.type == ROS: generator = CppRosGenerator(self.libraries, self.config, self.interfaceHeaderMap, stateList) elif self.config.type == JDEROBOTCOMM: generator = CppGenerator(self.libraries, self.config, self.interfaceHeaderMap, stateList) generator.generate(self.fileManager.getPath(), self.fileManager.getFileName()) self.showInfo('C++ Code Generation', 'C++ code generation is successful.') else: self.showWarning('C++ Generation', 'Please save the project before code generation.') def compileCppAction(self): # print('compile cpp action') pass def generatePythonAction(self): stateList = [] if self.fileManager.hasFile(): self.getStateList(self.rootState, stateList) if self.config.type == ROS: generator = PythonRosGenerator(self.libraries, self.config, stateList) elif self.config.type == JDEROBOTCOMM: generator = PythonGenerator(self.libraries, self.config, self.interfaceHeaderMap, stateList) generator.generate(self.fileManager.getPath(), self.fileManager.getFileName()) self.showInfo('Python Code Generation', 'Python code generation is successful.') else: self.showWarning('Python Generation', 'Please save the project before code generation.') def aboutAction(self): pass # print('about action') def createTreeView(self): dockWidget = QDockWidget() dockWidget.setAllowedAreas(Qt.LeftDockWidgetArea) dockWidget.setFeatures(QDockWidget.NoDockWidgetFeatures) dockWidget.setTitleBarWidget(QWidget()) self.treeView = QTreeView() self.treeView.clicked.connect(self.treeItemClicked) self.treeModel = TreeModel() self.treeView.setModel(self.treeModel) self.logo = QLabel() logoPixmap = QPixmap(CMAKE_INSTALL_PREFIX + '/share/jderobot/resources/jderobot.png') self.logo.setPixmap(logoPixmap) self.upButton = QPushButton() self.upButton.setText('Up') self.upButton.clicked.connect(self.upButtonClicked) leftContainer = QWidget() leftLayout = QVBoxLayout() leftLayout.addWidget(self.treeView) leftLayout.addWidget(self.upButton) leftLayout.addWidget(self.logo) leftContainer.setLayout(leftLayout) dockWidget.setWidget(leftContainer) self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget) def createStateCanvas(self): self.stateCanvas = QGraphicsView() self.automataScene = AutomataScene() self.automataScene.setSceneRect(0, 0, 2000, 2000) self.automataScene.activeStateChanged.connect(self.activeStateChanged) self.automataScene.stateInserted.connect(self.stateInserted) self.automataScene.stateRemoved.connect(self.stateRemoved) self.automataScene.transitionInserted.connect(self.transitionInserted) self.automataScene.stateNameChangedSignal.connect(self.stateNameChanged) self.automataScene.setActiveState(self.rootState) self.setCentralWidget(self.stateCanvas) self.stateCanvas.setScene(self.automataScene) self.stateCanvas.setRenderHint(QPainter.Antialiasing) self.stateCanvas.setAcceptDrops(True) def stateInserted(self, state): if self.activeState != self.rootState: parent = self.treeModel.getByDataId(self.activeState.id) self.treeModel.insertState(state, QColor(Qt.white), parent) else: self.treeModel.insertState(state, QColor(Qt.white)) def stateRemoved(self, state): if self.activeState != self.rootState: self.treeModel.removeState(state.stateData, self.activeState) else: self.treeModel.removeState(state.stateData) def transitionInserted(self, tran): # print('transition inserted:' + tran.transitionData.name) pass def stateNameChanged(self, state): dataItem = self.treeModel.getByDataId(state.stateData.id) if dataItem != None: dataItem.name = state.stateData.name self.treeModel.layoutChanged.emit() def activeStateChanged(self): if self.automataScene.activeState != self.activeState: # print('visual states active state changed:' + self.automataScene.activeState.name) self.activeState = self.automataScene.activeState if self.activeState == self.rootState: self.treeView.selectionModel().clearSelection() else: self.treeView.setCurrentIndex(self.treeModel.indexOf(self.treeModel.getByDataId(self.activeState.id))) def upButtonClicked(self): if self.activeState != None: if self.activeState.parent != None: # print('parent name:' + self.activeState.parent.name) self.automataScene.setActiveState(self.activeState.parent) def getStateById(self,state, id): if state.id == id: return state else: result = None for child in state.getChildren(): result = self.getStateById(child, id) if result is not None: return result return result def treeItemClicked(self, index): # print('clicked item.id:' + str(index.internalPointer().id)) state = self.getStateById(self.rootState, index.internalPointer().id) if state is not None: # set the active state as the loaded state self.automataScene.setActiveState(state) def timeStepDurationChanged(self, duration): if self.activeState is not None: self.activeState.setTimeStep(duration) def variablesChanged(self, variables): if self.activeState is not None: self.activeState.setVariables(variables) def functionsChanged(self, functions): if self.activeState is not None: self.activeState.setFunctions(functions) def librariesChanged(self, libraries): self.libraries = libraries def configChanged(self): if self.configDialog is not None: self.config = self.configDialog.getConfig() def getStateList(self, state, stateList): if len(state.getChildren()) > 0: stateList.append(state) for s in state.getChildren(): self.getStateList(s, stateList)