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 select(self, uuids, lbox: QTreeView = None, scroll_to_show_single=True): lbox = self.current_set_listbox if lbox is None else lbox lbox.clearSelection() if not uuids: return # FUTURE: this is quick and dirty rowdict = dict( (u, i) for i, u in enumerate(self.doc.current_layer_uuid_order)) items = QItemSelection() q = None for uuid in uuids: row = rowdict.get(uuid, None) if row is None: LOG.error( 'UUID {} cannot be selected in list view'.format(uuid)) continue q = self.createIndex(row, 0) items.select(q, q) lbox.selectionModel().select(items, QItemSelectionModel.Select) # lbox.setCurrentIndex(q) if scroll_to_show_single and len(uuids) == 1 and q is not None: lbox.scrollTo(q)
def _init_widget(self, listbox: QTreeView): listbox.setModel(self) listbox.setItemDelegate(self.item_delegate) listbox.setContextMenuPolicy(Qt.CustomContextMenu) # listbox.customContextMenuRequested.connect(self.context_menu) listbox.customContextMenuRequested.connect(self.menu) listbox.setDragEnabled(True) listbox.setAcceptDrops(True) listbox.setDropIndicatorShown(True) listbox.setSelectionMode(listbox.ExtendedSelection) # listbox.setMovement(QTreeView.Snap) # listbox.setDragDropMode(QTreeView.InternalMove) listbox.setDragDropMode(QAbstractItemView.DragDrop) # listbox.setAlternatingRowColors(True) # listbox.setDefaultDropAction(Qt.MoveAction) # listbox.setDragDropOverwriteMode(True) # listbox.entered.connect(self.layer_entered) # listbox.setFont(QFont('Andale Mono', 13)) # the various signals that may result from the user changing the selections # listbox.activated.connect(self.changedSelection) # listbox.clicked.connect(self.changedSelection) # listbox.doubleClicked.connect(self.changedSelection) # listbox.pressed.connect(self.changedSelection) listbox.selectionModel().selectionChanged.connect( self.changedSelection) self.widgets.append(listbox)
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)
def main(): import sys app = QApplication(sys.argv) v = QTreeView() model = TreeModel(utils.find_data_file('disease_num2name.p'), 'Disease') v.setModel(model) v.selectionModel().select( QItemSelection(model.index(0, 0), model.index(5, 0)), QItemSelectionModel.Select) v.setSelectionMode(QAbstractItemView.NoSelection) v.setEditTriggers(QAbstractItemView.NoEditTriggers) v.show() app.exec()
class ItemsView(QDockWidget): def __init__(self, title, parent, items): super().__init__(title, parent) self.setFloating(False) self.set_items(items) def set_items(self, items): self.items = items self.itemsTree = QTreeView() self.itemsTree.setHeaderHidden(True) treeModel = QStandardItemModel() rootNode = treeModel.invisibleRootItem() groups = {} for item in items: category = item['data']['category'] node = self.__get_node(item['name']) if (category not in groups): groups[category] = self.__get_node(category) groups[category].appendRow(node) for key in groups: rootNode.appendRow(groups[key]) self.itemsTree.setModel(treeModel) self.itemsTree.expandAll() self.setWidget(self.itemsTree) def set_on_item_selected(self, method): self.itemsTree.selectionModel().selectionChanged.connect( lambda: method(self.__get_selected_item())) def __get_selected_item(self): current = self.itemsTree.currentIndex() found_item = None if (current.parent() != None): for item in self.items: if (item['name'] == current.data()): found_item = item return found_item def __get_node(self, title): node = QStandardItem() node.setEditable(False) node.setText(title) return node
class FileSystemView(QWidget): def __init__(self, dir_path): super().__init__() appWidth = 800 appHeight = 300 self.setWindowTitle('File System Viewer') self.setGeometry(300, 300, appWidth, appHeight) self.model = QFileSystemModel() self.model.setRootPath(dir_path) self.tree = QTreeView() self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(dirPath)) self.tree.setColumnWidth(0, 250) self.tree.setAlternatingRowColors(True) layout = QVBoxLayout() self.photo = QLabel('Hola' + chr(10) + 'Caracola') layout.addWidget(self.tree) layout.addWidget(self.photo) self.setLayout(layout) self.tree.selectionModel().selectionChanged.connect(self.select) def keyPressEvent(self, event): if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return: index = self.tree.selectedIndexes()[0] crawler = index.model().filePath(index) print(crawler) #self.tree.keyPressEvent(self, event) def select(self, index1): index = self.tree.selectedIndexes()[0] file_sel = index.model().filePath(index) if os.path.isfile(file_sel) and file_sel[-3:].upper() in 'JPG PNG': pixmap = QPixmap(file_sel) self.photo.setPixmap(pixmap) print(file_sel)
class Example(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.folderLayout = QWidget() self.pathRoot = QDir.rootPath() self.dirmodel = QFileSystemModel(self) self.dirmodel.setRootPath(QDir.currentPath()) self.indexRoot = self.dirmodel.index(self.dirmodel.rootPath()) self.folder_view = QTreeView() self.folder_view.setDragEnabled(True) self.folder_view.setModel(self.dirmodel) self.folder_view.setRootIndex(self.indexRoot) self.selectionModel = self.folder_view.selectionModel() self.left_layout = QVBoxLayout() self.left_layout.addWidget(self.folder_view) self.folderLayout.setLayout(self.left_layout) splitter_filebrowser = QSplitter(Qt.Horizontal) splitter_filebrowser.addWidget(self.folderLayout) splitter_filebrowser.addWidget(Figure_Canvas(self)) splitter_filebrowser.setStretchFactor(1, 1) hbox = QHBoxLayout(self) hbox.addWidget(splitter_filebrowser) self.centralWidget().setLayout(hbox) self.setWindowTitle('Simple drag & drop') self.setGeometry(0, 0, 1000, 500)
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 MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self._app = QApplication.instance() self._lineEdit = None self.create_menu() self._completer = TreeModelCompleter(None, self) self._completer.setModel(self.model_from_file("./treemodel.txt")) self._completer.set_separator(".") self._completer.highlighted[QModelIndex].connect(self.highlight) central_widget = QWidget() model_label = QLabel() model_label.setText("Tree Model<br>(Double click items to edit)") mode_label = QLabel() mode_label.setText("Completion Mode") self._mode_combo = QComboBox() self._mode_combo.addItem(tr("Inline")) self._mode_combo.addItem(tr("Filtered Popup")) self._mode_combo.addItem(tr("Unfiltered Popup")) self._mode_combo.setCurrentIndex(1) case_label = QLabel() case_label.setText(tr("Case Sensitivity")) self._case_combo = QComboBox() self._case_combo.addItem(tr("Case Insensitive")) self._case_combo.addItem(tr("Case Sensitive")) self._case_combo.setCurrentIndex(0) separator_label = QLabel() separator_label.setText(tr("Tree Separator")) separator_line_edit = QLineEdit() separator_line_edit.setText(self._completer.separator()) separator_line_edit.textChanged.connect(self._completer.set_separator) wrap_check_box = QCheckBox() wrap_check_box.setText(tr("Wrap around completions")) wrap_check_box.setChecked(self._completer.wrapAround()) wrap_check_box.clicked.connect(self._completer.setWrapAround) self._contents_label = QLabel() self._contents_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) separator_line_edit.textChanged.connect(self.update_contents_label) self._tree_view = QTreeView() self._tree_view.setModel(self._completer.model()) self._tree_view.header().hide() self._tree_view.expandAll() self._mode_combo.activated.connect(self.change_mode) self._case_combo.activated.connect(self.change_case) self._line_edit = QLineEdit() self._line_edit.setCompleter(self._completer) layout = QGridLayout() layout.addWidget(model_label, 0, 0), layout.addWidget(self._tree_view, 0, 1) layout.addWidget(mode_label, 1, 0), layout.addWidget(self._mode_combo, 1, 1) layout.addWidget(case_label, 2, 0), layout.addWidget(self._case_combo, 2, 1) layout.addWidget(separator_label, 3, 0), layout.addWidget(separator_line_edit, 3, 1) layout.addWidget(wrap_check_box, 4, 0) layout.addWidget(self._contents_label, 5, 0, 1, 2) layout.addWidget(self._line_edit, 6, 0, 1, 2) central_widget.setLayout(layout) self.setCentralWidget(central_widget) self.change_case(self._case_combo.currentIndex()) self.change_mode(self._mode_combo.currentIndex()) self.setWindowTitle(tr("Tree Model Completer")) self._line_edit.setFocus() def create_menu(self): exit_action = QAction(tr("Exit"), self) about_act = QAction(tr("About"), self) about_qt_act = QAction(tr("About Qt"), self) exit_action.triggered.connect(QApplication.instance().quit) about_act.triggered.connect(self.about) about_qt_act.triggered.connect(QApplication.instance().aboutQt) file_menu = self.menuBar().addMenu(tr("File")) file_menu.addAction(exit_action) help_menu = self.menuBar().addMenu(tr("About")) help_menu.addAction(about_act) help_menu.addAction(about_qt_act) def change_mode(self, index): modes = (QCompleter.InlineCompletion, QCompleter.PopupCompletion, QCompleter.UnfilteredPopupCompletion) self._completer.setCompletionMode(modes[index]) def model_from_file(self, file_name): # file = QFile(file_name) # if not file.open(QFile.ReadOnly): # return QStringListModel(self._completer) QApplication.instance().setOverrideCursor(QCursor(Qt.WaitCursor)) model = QStandardItemModel(self._completer) parents = [model.invisibleRootItem()] with open(file_name) as file: pat = re.compile("^\\s+") for line in file: if not line: continue trimmed_line = line.strip() if not trimmed_line: continue match = pat.match(line) if not match: level = 0 else: length = match.end() - match.start() if line.startswith("\t"): level = length else: level = length // 4 while len(parents) < level + 2: parents.append(None) item = QStandardItem() item.setText(trimmed_line) parents[level].appendRow(item) parents[level + 1] = item QApplication.instance().restoreOverrideCursor() return model @pyqtSlot(QModelIndex) def highlight(self, index): proxy = self._completer.completionModel() source_index = proxy.mapToSource(index) self._tree_view.selectionModel().select( source_index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self._tree_view.scrollTo(source_index) def about(self): QMessageBox.about( self, tr("About"), tr("This example demonstrates how to use a QCompleter with a custom tree datamodel." )) def change_case(self, cs): self._completer.setCaseSensitivity( Qt.CaseSensitive if cs else Qt.CaseInsensitive) def update_contents_label(self, sep): self._contents_label.setText( "Type path from datamodel above with items at each level separated by a '%s'" % sep)
class Installed(preferences.Group): """Overview of installed extensions. A QTreeView lists the metadata of all installed extensions. If the currently selected extension provides a configuration widget it is displayed in the bottom group of the page. With a checkbox individual extensions can be deactivated. Metadata is listed for all *installed* extensions, regardless of manual deactivation or load failure. """ def __init__(self, page): super(Installed, self).__init__(page) layout = QVBoxLayout() self.setLayout(layout) # This must be called before self.populate() because # the data model relies on the labels app.translateUI(self) self.tree = QTreeView() self.name_items = {} self._selected_extension = '' self.tree.setModel(QStandardItemModel()) self.tree.model().setColumnCount(2) self.tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.tree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tree.setHeaderHidden(True) self.tree.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.tree.selectionModel().selectionChanged.connect( self.selection_changed) self.populate() layout.addWidget(self.tree) def translateUI(self): self.setTitle(_("Installed Extensions")) self.config_labels = { 'extension-name': _("Name"), 'maintainers': _("Maintainer(s)"), 'version': _("Version"), 'api-version': _("API version"), 'license': _("License"), 'short-description': _("Short Description"), 'description': _("Description"), 'repository': _("Repository"), 'website': _("Website"), 'dependencies': _("Dependencies") } def loadSettings(self): s = QSettings() self.setEnabled(self.page().active()) s.beginGroup("extension-settings/installed") inactive = s.value("inactive", [], list) for ext in self.name_items.keys(): self.name_items[ext].setCheckState( Qt.Checked if ext not in inactive else Qt.Unchecked) self.tree.model().dataChanged.connect(self.page().changed) def saveSettings(self): s = QSettings() s.beginGroup("extension-settings/installed") inactive = [ext for ext in self.name_items.keys() if self.name_items[ext].checkState() == Qt.Unchecked] s.setValue("inactive", inactive) def populate(self): """Populate the tree view with data from the installed extensions. """ # TODO/Question: # Would it make sense to move this to a dedicated model class # complementing the FailedModel? root = self.tree.model().invisibleRootItem() extensions = app.extensions() for ext in extensions.installed_extensions(): ext_infos = extensions.infos(ext) display_name = ext_infos.get(ext, ext) if ext_infos else ext.name() loaded_extension = extensions.get(ext) if loaded_extension: display_name += ' ({})'.format(loaded_extension.load_time()) name_item = QStandardItem(display_name) name_item.extension_name = ext name_item.setCheckable(True) self.name_items[ext] = name_item icon = extensions.icon(ext) if icon: name_item.setIcon(icon) root.appendRow([name_item]) for entry in [ 'extension-name', 'short-description', 'description', 'version', 'api-version', 'dependencies', 'maintainers', 'repository', 'website', 'license' ]: label_item = QStandardItem('{}:'.format( self.config_labels[entry])) label_item.setTextAlignment(Qt.AlignTop) bold = QFont() bold.setWeight(QFont.Bold) label_item.setFont(bold) details = ext_infos.get(entry, "") if ext_infos else "" if type(details) == list: details = '\n'.join(details) details_item = QStandardItem(details) details_item.setTextAlignment(Qt.AlignTop) if entry == 'api-version': # Check for correct(ly formatted) api-version entry # and highlight it in case of mismatch api_version = appinfo.extension_api if not details: details_item.setFont(bold) details_item.setText( _("Misformat: {api}").format(details)) elif not details == api_version: details_item.setFont(bold) details_item.setText('{} ({}: {})'.format( details, appinfo.appname, api_version)) name_item.appendRow([label_item, details_item]) def selected_extension(self): """Return the (directory) name of the extension that is currently selected.""" return self._selected_extension def selection_changed(self, new, old): """Show the configuration widget for the selected extension, if available.""" config = self.siblingGroup(Config) if new.indexes(): ext_item = self.tree.model().itemFromIndex(new.indexes()[0]) # NOTE: This may have to be changed if there should be # more complexity in the tree model than now (a selected # row is either a top-level row or its immediate child) if not hasattr(ext_item, 'extension_name'): ext_item = ext_item.parent() name = ext_item.extension_name if name == self.selected_extension(): return else: # If nothing is selected, show the "empty" widget name = "" config.hide_extension() self._selected_extension = name config.show_extension(name)
class ImageViewer(QWidget): def __init__(self): super(ImageViewer, self).__init__() pal = QPalette() pal.setColor(QPalette.Background, Qt.lightGray) self.factor = 3.0 self.config = Config() self.currentRep = "" self.createActions() self.createToolbarMenus() #self.createMenus() self.browserFile() self.imgqLabel() self.boxSliders() self.verticalLayout = QVBoxLayout(self) self.horizontalLayout = QHBoxLayout(self) self.textInfo = QTextEdit() self.textInfoTop = QTextEdit() self.textInfoTop.setEnabled(True) self.textInfoTop.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) self.textInfoTop.setFontPointSize(11) self.textInfoTop.setStyleSheet("background-color: lightgray") #self.textInfoTop.adjustSize() self.textInfoTop.setText('Welcome to IRMaGe') self.tableJson = QTableWidget() self.tableJson.setColumnCount(2) self.tableJson.setColumnWidth(0, 150) self.tableJson.setColumnWidth(1, 400) self.tableJson.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.tableJson.setHorizontalHeaderLabels(['Keys', 'Values']) #self.tableJson.setBackgroundRole(QPalette.Light) self.scrollText = QScrollArea() self.scrollText.setBackgroundRole(QPalette.Dark) self.scrollText.setWidget(self.textInfoTop) self.scrollText.setWidgetResizable(True) #======================================================================= # self.adjustScrollBar(self.scrollText.horizontalScrollBar(), 1.0) # self.adjustScrollBar(self.scrollText.verticalScrollBar(), 2.0) #======================================================================= self.scrollTable = QScrollArea() self.scrollTable.setBackgroundRole(QPalette.Dark) self.scrollTable.setWidget(self.tableJson) self.scrollTable.setWidgetResizable(True) #======================================================================= # self.adjustScrollBar(self.scrollTable.horizontalScrollBar(), 2.0) # self.adjustScrollBar(self.scrollTable.verticalScrollBar(), 2.0) #======================================================================= self.headerTabData = [ 'Data', 'PatientName', 'StudyName', 'DateCreation', 'PatientSex', 'PatientWeight', 'ProtocolName', 'SequenceName' ] self.tableData = TableDataBrower(self) self.tableData.setColumnCount(8) self.tableData.setRowCount(10) self.tableData.setColumnWidth(0, 200) self.tableData.setHorizontalHeaderLabels(self.headerTabData) self.tableData.setBackgroundRole(QPalette.Light) self.tableData.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.tableData.verticalHeader().hide() self.scrollBrowser = QScrollArea() self.scrollBrowser.setBackgroundRole(QPalette.Dark) self.scrollBrowser.setWidget(self.tableData) self.scrollBrowser.setWidgetResizable(True) self.splitter0 = QSplitter(Qt.Vertical) self.splitter0.addWidget(self.scrollText) self.splitter0.addWidget(self.scrollTable) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.scrollArea.setWidgetResizable(False) self.scrollArea.setAlignment(Qt.AlignCenter) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), 0.8) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), 1.0) self.splitter1 = QSplitter(Qt.Horizontal) self.splitter1.addWidget(self.splitter0) self.splitter1.addWidget(self.scrollArea) self.splitter1.addWidget(self.layoutSlide) self.splitter3 = QSplitter(Qt.Horizontal) self.splitter3.addWidget(self.browser) self.splitter3.addWidget(self.scrollBrowser) self.splitter2 = QSplitter(Qt.Vertical) self.splitter2.addWidget(self.splitter1) self.splitter2.addWidget(self.splitter3) self.splitter2.setHandleWidth(15) #======================================================================= # self.splitter2. #======================================================================= self.verticalLayout.addWidget(self.menuToolBar) self.verticalLayout.addWidget(self.splitter2) self.setWindowTitle("MRImage Viewer (IRMaGe)") self.resize(800, 600) self.setAutoFillBackground(True) self.setPalette(pal) def changeSel(self): print('Tab changed') def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue( int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep() / 2))) def imgqLabel(self): QLabel.__init__(self) image = QImage('sources_images/LogoIRMaGe.png') self.scaleFactor = 1.0 self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor *= self.factor self.imageLabel.adjustSize() self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) def open(self, filePath): self.img = nib.load(filePath) self.textInfoTop.setText('File : ' + filePath + '\n') self.textInfoTop.append('Dim : ' + str(self.img.shape) + '\n') self.enableSliders() self.a1.setValue(0) self.a2.setValue(0) self.a3.setValue(0) self.c2.setMaximum(self.img.shape[0]) self.c2.setMinimum(-self.img.shape[0]) self.c3.setMaximum(self.img.shape[1]) self.c3.setMinimum(-self.img.shape[1]) self.navigImage() self.fitToWindowAct.setEnabled(True) self.fitToWindow() def openJson(self, pathJson, fileName): with open(pathJson, 'r') as stream: try: json_object = json.load(stream) data = json.dumps(json_object, indent=0, sort_keys=True) data = json.loads(data) rowPosition = 0 self.tableJson.setRowCount(0) i = 0 for keyd in self.headerTabData: try: val = str(data[keyd]) val = val.replace('[', '') val = val.replace(']', '') except: val = '' #=========================================================== # self.tableData.insertRow(i) # self.tableData.setItem(0,i,QTableWidgetItem(val)) # i+=1 #=========================================================== #=============================================================== # self.tableData.setItem(0,0,QTableWidgetItem(fileName)) # self.tableData.selectRow(0) #=============================================================== for keys in data: stringValue = str(data[keys]) stringValue = stringValue.replace('[', '') stringValue = stringValue.replace(']', '') self.tableJson.insertRow(rowPosition) self.tableJson.setItem(rowPosition, 0, QTableWidgetItem(keys)) self.tableJson.setItem(rowPosition, 1, QTableWidgetItem(stringValue)) rowPosition += 1 self.tableJson.resizeColumnsToContents() except json.JSONDecodeError as exc: itemError = 'Error Json format' self.tableJson.setRowCount(0) self.tableJson.insertRow(0) self.tableJson.setItem(0, 0, QTableWidgetItem(itemError)) print(exc) def jsonParser(self, pathJson): with open(pathJson, 'r') as stream: try: json_object = json.load(stream) listTag = json.dumps(json_object, indent=0, sort_keys=True) listTag = json.loads(listTag) except json.JSONDecodeError as exc: itemError = 'Error Json format' return listTag def tableDataFill(self, pathRepertory): files = [f for f in fnmatch.filter(os.listdir(pathRepertory), '*.nii')] self.tableData.setRowCount(0) j = 0 for f in files: base = os.path.splitext(f)[0] g = os.path.join(pathRepertory, base + ".json") self.tableData.insertRow(j) if os.path.isfile(g): data = self.jsonParser(g) i = 0 for keyw in self.headerTabData: try: val = str(data[keyw]) val = val.replace('[', '') val = val.replace(']', '') except: val = '' self.tableData.setItem(j, i, QTableWidgetItem(val)) i += 1 else: self.tableData.setItem(j, 1, QTableWidgetItem('No json file found')) self.tableData.setItem(j, 0, QTableWidgetItem(f)) self.tableData.resizeColumnsToContents() j += 1 def indexImage(self): sl1 = self.a1.value() sl2 = self.a2.value() sl3 = self.a3.value() if len(self.img.shape) == 3: x = self.img.get_data()[:, :, sl1].copy() self.a1.setMaximum(self.img.shape[2] - 1) self.a2.setMaximum(0) self.a3.setMaximum(0) if len(self.img.shape) == 4: x = self.img.get_data()[:, :, sl1, sl2].copy() self.a1.setMaximum(self.img.shape[2] - 1) self.a2.setMaximum(self.img.shape[3] - 1) self.a3.setMaximum(0) if len(self.img.shape) == 5: x = self.img.get_data()[:, :, sl1, sl2, sl3].copy() self.a1.setMaximum(self.img.shape[2] - 1) self.a2.setMaximum(self.img.shape[3] - 1) self.a3.setMaximum(self.img.shape[4] - 1) x = rotate(x, -90, reshape=False) x = np.uint8((x - x.min()) / x.ptp() * 255.0) self.x = x ############################ Slice controls ######################################### def boxSliders(self): self.k1 = QLabel('Slider 1 ') self.k2 = QLabel('Slider 2') self.k3 = QLabel('Slider 3') self.a1 = self.createSlider(0, 0, 0) self.a2 = self.createSlider(0, 0, 0) self.a3 = self.createSlider(0, 0, 0) self.a1.valueChanged.connect(self.changePosValue) self.a2.valueChanged.connect(self.changePosValue) self.a3.valueChanged.connect(self.changePosValue) self.txta1 = self.createFieldValue() self.txta2 = self.createFieldValue() self.txta3 = self.createFieldValue() self.controlsGroup = QGroupBox('Slice Controls') gridCtrl = QGridLayout() gridCtrl.addWidget(self.k1, 0, 0) gridCtrl.addWidget(self.a1, 0, 1) gridCtrl.addWidget(self.txta1, 0, 2) gridCtrl.addWidget(self.k2, 1, 0) gridCtrl.addWidget(self.a2, 1, 1) gridCtrl.addWidget(self.txta2, 1, 2) gridCtrl.addWidget(self.k3, 2, 0) gridCtrl.addWidget(self.a3, 2, 1) gridCtrl.addWidget(self.txta3, 2, 2) self.controlsGroup.setLayout(gridCtrl) ############################ brightness and contrast ################################ self.txtb1 = self.createFieldValue() self.txtb2 = self.createFieldValue() self.txtb3 = self.createFieldValue() self.txtb4 = self.createFieldValue() self.l1 = QLabel('Brightness ') self.b1 = self.createSlider(101, 0, 50) self.l2 = QLabel('Contrast') self.b2 = self.createSlider(101, 0, 50) self.l3 = QLabel('Sharpness') self.b3 = self.createSlider(101, 0, 50) self.l4 = QLabel('Color') self.b4 = self.createSlider(101, 0, 50) self.b1.valueChanged.connect(self.changeContValue) self.b2.valueChanged.connect(self.changeContValue) self.b3.valueChanged.connect(self.changeContValue) self.b4.valueChanged.connect(self.changeContValue) self.txtb1.setText(str(0)) self.txtb2.setText(str(0)) self.txtb3.setText(str(0)) self.txtb4.setText(str(0)) self.buttonResetContrast = QPushButton('reset', self) self.buttonResetContrast.setToolTip('Reset all values') self.buttonResetContrast.setEnabled(False) self.buttonResetContrast.clicked.connect(self.resetValuesContrast) self.contrastGroup = QGroupBox('Brightness and Contrast') gridCont = QGridLayout() gridCont.addWidget(self.l1, 0, 0) gridCont.addWidget(self.b1, 0, 1) gridCont.addWidget(self.txtb1, 0, 2) gridCont.addWidget(self.l2, 1, 0) gridCont.addWidget(self.b2, 1, 1) gridCont.addWidget(self.txtb2, 1, 2) gridCont.addWidget(self.l3, 2, 0) gridCont.addWidget(self.b3, 2, 1) gridCont.addWidget(self.txtb3, 2, 2) gridCont.addWidget(self.l4, 3, 0) gridCont.addWidget(self.b4, 3, 1) gridCont.addWidget(self.txtb4, 3, 2) gridCont.addWidget(self.buttonResetContrast, 4, 2) self.contrastGroup.setLayout(gridCont) ############################ Transformation ######################################### self.txtc1 = self.createFieldValue() self.txtc2 = self.createFieldValue() self.txtc3 = self.createFieldValue() self.txtc4 = self.createFieldValue() self.m1 = QLabel('Rotation') self.c1 = self.createSlider(180, -180, 0) self.m2 = QLabel('Translate X ') self.c2 = self.createSlider(1, -1, 0) self.m3 = QLabel('Translate Y ') self.c3 = self.createSlider(1, -1, 0) self.m4 = QLabel('Resize') self.c4 = self.createSlider(10, 0, 0) self.c1.valueChanged.connect(self.changeTransValue) self.c2.valueChanged.connect(self.changeTransValue) self.c3.valueChanged.connect(self.changeTransValue) self.c4.valueChanged.connect(self.changeTransValue) self.txtc1.setText(str(0)) self.txtc2.setText(str(0)) self.txtc3.setText(str(0)) self.txtc4.setText(str(0)) self.buttonResetTransform = QPushButton('reset', self) self.buttonResetTransform.setToolTip('Reset all values') self.buttonResetTransform.setEnabled(False) self.buttonResetTransform.clicked.connect(self.resetValuesTransform) self.transformationGroup = QGroupBox('Transformations') gridTransf = QGridLayout() gridTransf.addWidget(self.m1, 0, 0) gridTransf.addWidget(self.c1, 0, 1) gridTransf.addWidget(self.txtc1, 0, 2) gridTransf.addWidget(self.m2, 1, 0) gridTransf.addWidget(self.c2, 1, 1) gridTransf.addWidget(self.txtc2, 1, 2) gridTransf.addWidget(self.m3, 2, 0) gridTransf.addWidget(self.c3, 2, 1) gridTransf.addWidget(self.txtc3, 2, 2) gridTransf.addWidget(self.m4, 3, 0) gridTransf.addWidget(self.c4, 3, 1) gridTransf.addWidget(self.txtc4, 3, 2) gridTransf.addWidget(self.buttonResetTransform, 4, 2) self.transformationGroup.setLayout(gridTransf) #################################################################################### self.layoutSliders = QVBoxLayout() self.layoutSliders.addWidget(self.controlsGroup) self.layoutSliders.addWidget(self.contrastGroup) self.layoutSliders.addWidget(self.transformationGroup) self.layoutSlide = QWidget() self.layoutSlide.setLayout(self.layoutSliders) def createSlider(self, maxm=0, minm=0, pos=0): slider = QSlider(Qt.Horizontal) slider.setFocusPolicy(Qt.StrongFocus) #slider.setTickPosition(QSlider.TicksBothSides) slider.setTickInterval(1) #slider.setSingleStep(1) slider.setMaximum(maxm) slider.setMinimum(minm) slider.setValue(pos) slider.setEnabled(False) return slider def createFieldValue(self): fieldValue = QLineEdit() fieldValue.setEnabled(False) fieldValue.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) return fieldValue def displayPosValue(self): self.txta1.setText( str(self.a1.value() + 1) + ' / ' + str(self.a1.maximum() + 1)) self.txta2.setText( str(self.a2.value() + 1) + ' / ' + str(self.a2.maximum() + 1)) self.txta3.setText( str(self.a3.value() + 1) + ' / ' + str(self.a3.maximum() + 1)) def changePosValue(self): self.navigImage() def navigImage(self): self.indexImage() self.displayPosValue() w, h = self.x.shape image = QImage(self.x.data, w, h, QImage.Format_Indexed8) self.pixm = QPixmap.fromImage(image) self.imageLabel.setPixmap(self.pixm) self.imageLabel.adjustSize() self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.filter() def changeContValue(self): self.txtb1.setText(str(self.b1.value() - 50)) self.txtb2.setText(str(self.b2.value() - 50)) self.txtb3.setText(str(self.b3.value() - 50)) self.txtb4.setText(str(self.b4.value() - 50)) self.filter() def changeTransValue(self): self.txtc1.setText(str(self.c1.value())) self.txtc2.setText(str(self.c2.value())) self.txtc3.setText(str(self.c3.value())) self.txtc4.setText(str(self.c4.value())) self.filter() def filter(self): img = Image.fromarray(self.x, 'L') brightness = ImageEnhance.Brightness(img) newImg = brightness.enhance(1.2 * (self.b1.value() + 1) / 50.0) contrast = ImageEnhance.Contrast(newImg) newImg = contrast.enhance((self.b2.value() + 1) / 50.0) sharpness = ImageEnhance.Sharpness(newImg) newImg = sharpness.enhance(2.0 * (self.b3.value() + 1) / 50.0) color = ImageEnhance.Color(newImg) newImg = color.enhance((self.b4.value() + 1) / 50.0) newImg = newImg.rotate(self.c1.value()) newImg = newImg.transform( img.size, Image.AFFINE, (1, 0, self.c2.value(), 0, 1, self.c3.value())) size1 = int(img.size[0] * (self.c4.value() + 1)) size2 = int(img.size[1] * (self.c4.value() + 1)) newImg = newImg.resize((size1, size2), Image.ANTIALIAS) self.pixm = QPixmap.fromImage(newImg.toqimage()) self.imageLabel.setPixmap(self.pixm) self.imageLabel.adjustSize() self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) def resetValuesContrast(self): self.b1.setSliderPosition(50) self.b2.setSliderPosition(50) self.b3.setSliderPosition(50) self.b4.setSliderPosition(50) self.changeContValue() def resetValuesTransform(self): self.c1.setSliderPosition(0) self.c2.setSliderPosition(0) self.c3.setSliderPosition(0) self.c4.setSliderPosition(0) self.changeTransValue() def enableSliders(self): self.a1.setEnabled(True) self.a2.setEnabled(True) self.a3.setEnabled(True) self.b1.setEnabled(True) self.b2.setEnabled(True) self.b3.setEnabled(True) self.b4.setEnabled(True) self.c1.setEnabled(True) self.c2.setEnabled(True) self.c3.setEnabled(True) self.c4.setEnabled(True) self.buttonResetContrast.setEnabled(True) self.buttonResetTransform.setEnabled(True) #################################################################################### def browserFile(self): global Browser, Model self.browser = QTreeView() model = QFileSystemModel() model.setNameFilters(['*.nii']) model.setNameFilterDisables(False) model.setReadOnly(True) self.browser.setModel(model) self.browser.expandAll() self.browser.setColumnWidth(0, 400) self.browser.selectionModel().selectionChanged.connect(self.select) Browser = self.browser Model = model #======================================================================= # self.browser.doubleClicked.connect(self.selection) #self.browser.clicked.connect(self.selection) #======================================================================= def select(self, signal): file_path = self.browser.model().filePath(signal.indexes()[0]) shortName, fileExt = os.path.splitext(file_path) filePath, fileName = os.path.split(file_path) self.textInfo.setText(filePath) blackColor = QColor(0, 0, 0) if os.path.isfile(file_path): if fileExt == ".nii": if self.currentRep != filePath: self.tableDataFill(filePath) self.currentRep = filePath self.open(file_path) self.tableData.selectRow( self.tableData.findItems(fileName, Qt.MatchExactly)[0].row()) if os.path.isfile(shortName + '.json'): greenColor = QColor(50, 150, 100) self.textInfoTop.setTextColor(greenColor) self.textInfoTop.append('Json file exists ' + '\n') self.openJson(shortName + '.json', fileName) else: redColor = QColor(255, 0, 0) self.textInfoTop.setTextColor(redColor) self.textInfoTop.append('Json file doesn\'t exist' + '\n') self.tableJson.setRowCount(0) else: self.tableData.setRowCount(0) self.currentRep = filePath self.textInfoTop.setTextColor(blackColor) self.scrollText.setWidgetResizable(True) #################################################################################### def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.viewMenu.addSeparator() self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.menuBar = QMenuBar() self.menuBar.addMenu(self.fileMenu) self.menuBar.addMenu(self.viewMenu) self.menuBar.addMenu(self.helpMenu) def createToolbarMenus(self): self.menuToolBar = QToolBar() viewMenu = QToolButton() viewMenu.setText('View') viewMenu.setPopupMode(QToolButton.MenuButtonPopup) aMenu = QMenu() aMenu.addAction(self.zoomInAct) aMenu.addAction(self.zoomOutAct) aMenu.addAction(self.normalSizeAct) aMenu.addSeparator() aMenu.addAction(self.fitToWindowAct) viewMenu.setMenu(aMenu) helpMenu = QToolButton() helpMenu.setText('Help') helpMenu.setPopupMode(QToolButton.MenuButtonPopup) bMenu = QMenu() helpMenu.setMenu(bMenu) self.menuToolBar.addWidget(viewMenu) self.menuToolBar.addWidget(helpMenu) def createActions(self): self.exitAct = QAction("Exit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) def zoomIn(self): self.factor = 1.25 self.scaleImage(self.factor) def zoomOut(self): self.factor = 0.8 self.scaleImage(self.factor) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) self.scrollText.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 5.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def close(self): self.close()
class MangaControlsWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) self.setLayout(QHBoxLayout()) self.__init_done = False self.__model = None model = get_default_manga() if model is None: model = manga_selection_dialog('~', self) self.__contents = QTreeView() self.__contents.setMaximumWidth(200) self.__contents.setMinimumWidth(150) if not global_data.get('show_contents'): self.__contents.hide() self.__display = Display() self.__contents.setFocusProxy(self.__display) self.layout().addWidget(self.__contents) self.layout().addWidget(self.__display) self.set_model(model) def set_model(self, model): self.__model = model self.__contents.setModel(model) self.__model.position_changed.connect(self.on_position_change) self.__contents.selectionModel().currentChanged.connect( self.selection_changed) self.sync_selection() self.__display.display_image(self.__model.current()) def selection_changed(self, index): if self.__init_done: self.__model.select(index) else: self.__init_done = True def on_position_change(self, path): self.__display.display_image(path) self.sync_selection() save_manga_state(self.__model) def sync_selection(self): index = self.__model._current_index() self.__contents.selectionModel().setCurrentIndex( index, QItemSelectionModel.SelectCurrent) def open_dialog(self): path, _ = os.path.split(self.__model.path()) model = manga_selection_dialog(path, self) if model is not None: self.set_model(model) def keyPressEvent(self, event): if event.key() == Qt.Key_C: self.toggle_contents() elif event.key() == Qt.Key_Plus: self.__display.zoom(1.05) elif event.key() == Qt.Key_Minus: self.__display.zoom(1 / 1.05) elif event.key() == Qt.Key_J: self.__display.scroll(100) elif event.key() == Qt.Key_K: self.__display.scroll(-100) elif event.key() == Qt.Key_L: self.__model.next() elif event.key() == Qt.Key_H: self.__model.prev() else: event.ignore() def toggle_contents(self): showed = global_data.get('show_contents') if showed: self.__contents.hide() else: self.__contents.show() global_data.set('show_contents', not showed)
class OWGeneSets(OWWidget): name = "Gene Sets" description = "" icon = "icons/OWGeneSets.svg" priority = 9 want_main_area = True # settings selected_organism = Setting(0) auto_commit = Setting(True) auto_apply = Setting(True) gene_col_index = ContextSetting(0) use_attr_names = ContextSetting(False) class Inputs: genes = Input("Genes", Table) class Outputs: matched_genes = Output("Matched Genes", Table) class Information(OWWidget.Information): pass class Error(OWWidget.Error): cant_reach_host = Msg("Host orange.biolab.si is unreachable.") cant_load_organisms = Msg( "No available organisms, please check your connection.") def __init__(self): super().__init__() # commit self.commit_button = None # progress bar self.progress_bar = None self.progress_bar_iterations = None # data self.input_data = None self.tax_id = None self.input_genes = None self.mapped_genes = None self.organisms = list() self.input_info = None self.column_candidates = [] # filter self.lineEdit_filter = None self.search_pattern = '' self.organism_select_combobox = None # data model view self.data_view = None self.data_model = None # gene matcher NCBI self.gene_matcher = None # filter proxy model self.filter_proxy_model = None # hierarchy widget self.hierarchy_widget = None self.hierarchy_state = None # input options self.gene_columns = None self.gene_column_combobox = None self.attr_names_checkbox = None # threads self.threadpool = QThreadPool(self) self.workers = None # gui self.setup_gui() self._get_available_organisms() # self.handle_input(self.input_genes) # self.on_organism_change() def _progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here if self.progress_bar: self.progress_bar.advance() def _get_selected_organism(self): return self.organisms[self.selected_organism] def _get_available_organisms(self): available_organism = sorted([(tax_id, taxonomy.name(tax_id)) for tax_id in taxonomy.common_taxids()], key=lambda x: x[1]) self.organisms = [tax_id[0] for tax_id in available_organism] self.organism_select_combobox.addItems( [tax_id[1] for tax_id in available_organism]) def _gene_names_from_table(self): """ Extract and return gene names from `Orange.data.Table`. """ self.input_genes = [] if self.input_data: if self.use_attr_names: self.input_genes = [ str(attr.name).strip() for attr in self.input_data.domain.attributes ] elif self.gene_columns: column = self.gene_columns[self.gene_col_index] self.input_genes = [ str(e[column]) for e in self.input_data if not np.isnan(e[column]) ] def _update_gene_matcher(self): self._gene_names_from_table() if self.gene_matcher: self.gene_matcher.genes = self.input_genes self.gene_matcher.organism = self._get_selected_organism() def on_input_option_change(self): self._update_gene_matcher() self.match_genes() @Inputs.genes def handle_input(self, data): if data: self.input_data = data self.gene_matcher = gene.GeneMatcher(self._get_selected_organism()) self.gene_column_combobox.clear() self.column_candidates = [ attr for attr in data.domain.variables + data.domain.metas if isinstance(attr, (StringVariable, DiscreteVariable)) ] for var in self.column_candidates: self.gene_column_combobox.addItem(*attributeItem(var)) self.tax_id = str(data_hints.get_hint(self.input_data, TAX_ID)) self.use_attr_names = data_hints.get_hint( self.input_data, GENE_NAME, default=self.use_attr_names) self.gene_col_index = min(self.gene_col_index, len(self.column_candidates) - 1) if self.tax_id in self.organisms: self.selected_organism = self.organisms.index(self.tax_id) self.on_input_option_change() def update_info_box(self): info_string = '' if self.input_genes: info_string += '{} unique gene names on input.\n'.format( len(self.input_genes)) mapped = self.gene_matcher.get_known_genes() if mapped: ratio = (len(mapped) / len(self.input_genes)) * 100 info_string += '{} ({:.2f}%) gene names matched.\n'.format( len(mapped), ratio) else: info_string += 'No genes on input.\n' self.input_info.setText(info_string) def match_genes(self): if self.gene_matcher: # init progress bar self.progress_bar = ProgressBar(self, iterations=len( self.gene_matcher.genes)) # status message self.setStatusMessage('gene matcher running') worker = Worker(self.gene_matcher.run_matcher, progress_callback=True) worker.signals.progress.connect(self._progress_advance) worker.signals.finished.connect(self.handle_matcher_results) # move download process to worker thread self.threadpool.start(worker) def handle_matcher_results(self): assert threading.current_thread() == threading.main_thread() if self.progress_bar: self.progress_bar.finish() self.setStatusMessage('') if self.gene_matcher.map_input_to_ncbi(): self.download_gene_sets() self.update_info_box() else: # reset gene sets self.init_item_model() self.update_info_box() def on_gene_sets_download(self, result): # make sure this happens in the main thread. # Qt insists that widgets be created within the GUI(main) thread. assert threading.current_thread() == threading.main_thread() self.progress_bar.finish() self.setStatusMessage('') tax_id, sets = result self.set_hierarchy_model(self.hierarchy_widget, *hierarchy_tree(tax_id, sets)) self.organism_select_combobox.setEnabled(True) # re-enable combobox self.update_info_box() self.mapped_genes = self.gene_matcher.map_input_to_ncbi() self.workers = defaultdict(list) self.progress_bar_iterations = dict() for selected_hierarchy in [*self.get_hierarchies()]: gene_sets = geneset.load_gene_sets(selected_hierarchy) worker = Worker(get_collections, gene_sets, self.mapped_genes, progress_callback=True, partial_result=True) worker.signals.error.connect(self.handle_error) worker.signals.finished.connect(self.handle_worker_finished) worker.signals.progress.connect(self._progress_advance) worker.signals.partial_result.connect(self.populate_data_model) worker.setAutoDelete(False) self.workers[selected_hierarchy] = worker self.progress_bar_iterations[selected_hierarchy] = len(gene_sets) def handle_worker_finished(self): # We check if all workers have completed. If not, continue # dirty hax, is this ok? if self.progress_bar and self.progress_bar.widget.progressBarValue == 100: self.progress_bar.finish() self.setStatusMessage('') self.hierarchy_widget.setDisabled(False) # adjust column width for i in range(len(DATA_HEADER_LABELS) - 1): self.data_view.resizeColumnToContents(i) self.filter_proxy_model.setSourceModel(self.data_model) def populate_data_model(self, partial_result): assert threading.current_thread() == threading.main_thread() if partial_result: self.data_model.appendRow(partial_result) def set_hierarchy_model(self, model, tax_id, sets): # TODO: maybe optimize this code? for key, value in sets.items(): item = QTreeWidgetItem(model, [key]) item.setFlags(item.flags() & (Qt.ItemIsUserCheckable | ~Qt.ItemIsSelectable | Qt.ItemIsEnabled)) # item.setDisabled(True) item.setData(0, Qt.CheckStateRole, Qt.Unchecked) item.setExpanded(True) item.tax_id = tax_id item.hierarchy = key if value: item.setFlags(item.flags() | Qt.ItemIsTristate) self.set_hierarchy_model(item, tax_id, value) else: if item.parent(): item.hierarchy = ((item.parent().hierarchy, key), tax_id) if not item.childCount() and not item.parent(): item.hierarchy = ((key, ), tax_id) def download_gene_sets(self): tax_id = self._get_selected_organism() self.Error.clear() # do not allow user to change organism when download task is running self.organism_select_combobox.setEnabled(False) # reset hierarchy widget state self.hierarchy_widget.clear() # clear data view self.init_item_model() # get all gene sets for selected organism gene_sets = geneset.list_all(organism=tax_id) # init progress bar self.progress_bar = ProgressBar(self, iterations=len(gene_sets) * 100) # status message self.setStatusMessage('downloading sets') worker = Worker(download_gene_sets, gene_sets, progress_callback=True) worker.signals.progress.connect(self._progress_advance) worker.signals.result.connect(self.on_gene_sets_download) worker.signals.error.connect(self.handle_error) # move download process to worker thread self.threadpool.start(worker) def display_gene_sets(self): self.init_item_model() self.hierarchy_widget.setDisabled(True) only_selected_hier = [*self.get_hierarchies(only_selected=True)] # init progress bar iterations = sum([ self.progress_bar_iterations[hier] for hier in only_selected_hier ]) self.progress_bar = ProgressBar(self, iterations=iterations) self.setStatusMessage('displaying gene sets') if not only_selected_hier: self.progress_bar.finish() self.setStatusMessage('') self.hierarchy_widget.setDisabled(False) return for selected_hierarchy in only_selected_hier: self.threadpool.start(self.workers[selected_hierarchy]) def handle_error(self, ex): self.progress_bar.finish() self.setStatusMessage('') if isinstance(ex, ConnectionError): self.organism_select_combobox.setEnabled( True) # re-enable combobox self.Error.cant_reach_host() print(ex) def get_hierarchies(self, **kwargs): """ return selected hierarchy """ only_selected = kwargs.get('only_selected', None) sets_to_display = list() if only_selected: iterator = QTreeWidgetItemIterator(self.hierarchy_widget, QTreeWidgetItemIterator.Checked) else: iterator = QTreeWidgetItemIterator(self.hierarchy_widget) while iterator.value(): # note: if hierarchy value is not a tuple, then this is just top level qTreeWidgetItem that # holds subcategories. We don't want to display all sets from category if type(iterator.value().hierarchy) is not str: if not only_selected: sets_to_display.append(iterator.value().hierarchy) else: if not iterator.value().isDisabled(): sets_to_display.append(iterator.value().hierarchy) iterator += 1 return sets_to_display def commit(self): selection_model = self.data_view.selectionModel() if selection_model: # genes_from_set = selection_model.selectedRows(GENES) matched_genes = selection_model.selectedRows(MATCHED) if matched_genes and self.input_genes: genes = [ model_index.data(Qt.UserRole) for model_index in matched_genes ] output_genes = [ gene_name for gene_name in list(set.union(*genes)) ] input_to_ncbi = self.gene_matcher.map_input_to_ncbi() ncbi_to_input = { ncbi_id: input_name for input_name, ncbi_id in self.gene_matcher.map_input_to_ncbi().items() } if self.use_attr_names: selected = [ self.input_data.domain[ncbi_to_input[output_gene]] for output_gene in output_genes ] domain = Domain(selected, self.input_data.domain.class_vars, self.input_data.domain.metas) new_data = self.input_data.from_table( domain, self.input_data) self.Outputs.matched_genes.send(new_data) elif self.column_candidates: column = self.column_candidates[self.gene_col_index] selected_rows = [] for row_index, row in enumerate(self.input_data): if str(row[column]) in input_to_ncbi.keys( ) and input_to_ncbi[str(row[column])] in output_genes: selected_rows.append(row_index) if selected_rows: selected = self.input_data[selected_rows] else: selected = None self.Outputs.matched_genes.send(selected) def setup_gui(self): # control area info_box = vBox(self.controlArea, 'Input info') self.input_info = widgetLabel(info_box) organism_box = vBox(self.controlArea, 'Organisms') self.organism_select_combobox = comboBox( organism_box, self, 'selected_organism', callback=self.on_input_option_change) # Selection of genes attribute box = widgetBox(self.controlArea, 'Gene attribute') self.gene_columns = itemmodels.VariableListModel(parent=self) self.gene_column_combobox = comboBox( box, self, 'gene_col_index', callback=self.on_input_option_change) self.gene_column_combobox.setModel(self.gene_columns) self.attr_names_checkbox = checkBox( box, self, 'use_attr_names', 'Use attribute names', disables=[(-1, self.gene_column_combobox)], callback=self.on_input_option_change) self.gene_column_combobox.setDisabled(bool(self.use_attr_names)) hierarchy_box = widgetBox(self.controlArea, "Entity Sets") self.hierarchy_widget = QTreeWidget(self) self.hierarchy_widget.setEditTriggers(QTreeView.NoEditTriggers) self.hierarchy_widget.setHeaderLabels(HIERARCHY_HEADER_LABELS) self.hierarchy_widget.itemClicked.connect(self.display_gene_sets) hierarchy_box.layout().addWidget(self.hierarchy_widget) self.commit_button = auto_commit(self.controlArea, self, "auto_commit", "&Commit", box=False) # rubber(self.controlArea) # main area self.filter_proxy_model = QSortFilterProxyModel(self.data_view) self.filter_proxy_model.setFilterKeyColumn(3) self.data_view = QTreeView() self.data_view.setModel(self.filter_proxy_model) self.data_view.setAlternatingRowColors(True) self.data_view.setSortingEnabled(True) self.data_view.setSelectionMode(QTreeView.ExtendedSelection) self.data_view.setEditTriggers(QTreeView.NoEditTriggers) self.data_view.viewport().setMouseTracking(True) self.data_view.setItemDelegateForColumn( TERM, LinkStyledItemDelegate(self.data_view)) self.data_view.selectionModel().selectionChanged.connect(self.commit) self.lineEdit_filter = lineEdit(self.mainArea, self, 'search_pattern', 'Filter gene sets:') self.lineEdit_filter.setPlaceholderText('search pattern ...') self.lineEdit_filter.textChanged.connect( self.filter_proxy_model.setFilterRegExp) self.mainArea.layout().addWidget(self.data_view) def init_item_model(self): if self.data_model: self.data_model.clear() self.filter_proxy_model.setSourceModel(None) else: self.data_model = QStandardItemModel() self.data_model.setSortRole(Qt.UserRole) self.data_model.setHorizontalHeaderLabels(DATA_HEADER_LABELS) def sizeHint(self): return QSize(1280, 960)
# Defining a couple of items americaItem = QStandardItem("America") canadaItem = QStandardItem("Canada") europeItem = QStandardItem("Europe") franceItem = QStandardItem("France") brittanyItem = QStandardItem("Brittany") # Building up the hierarchy rootItem.appendRow(americaItem) rootItem.appendRow(europeItem) americaItem.appendRow(canadaItem) europeItem.appendRow(franceItem) franceItem.appendRow(brittanyItem) tree_view.setModel(model) # Bind selection to the print_selection function (must be after the model setup) selection_model = tree_view.selectionModel() selection_model.selectionChanged.connect(print_selection) tree_view.expandAll() # expand all (this is not the case by default) tree_view.show() # The mainloop of the application. The event handling starts from this point. # The exec_() method has an underscore. It is because the exec is a Python keyword. And thus, exec_() was used instead. exit_code = app.exec_() # The sys.exit() method ensures a clean exit. # The environment will be informed, how the application ended. sys.exit(exit_code)
class ZeroConfExplorer(QWidget): """ create a zeroconf qgroubbox with a qlist view """ def __init__(self, name): super(ZeroConfExplorer, self).__init__() # init explorer to None self.oscquery_device = None if not name: name = 'OSCJSON thru TCP Explorer' # create the view self.explorer = QTreeView() # Hide Useless Header self.explorer.header().hide() self.panel = Panel() # create right-click menu self.explorer.setContextMenuPolicy(Qt.CustomContextMenu) self.explorer.customContextMenuRequested.connect(self.contextual_menu) # create the model self.devices_model = QStandardItemModel() # link model to the view self.explorer.setModel(self.devices_model) self.explorer.selectionModel().selectionChanged.connect( self.selection_updated) # set selection self.device_selection_model = self.explorer.selectionModel() # set layout and group Layout = QGridLayout() # add the view to the layout Layout.addWidget(self.explorer, 0, 0) Layout.addWidget(self.panel, 0, 1) # add the layout to the GroupBox self.setLayout(Layout) #self.setMinimumSize(300, 300) self.explorer.setFixedSize(300, 300) # start zeroconf services zeroconf = Zeroconf() # start the callback, it will create items listener = ZeroConfListener(self.devices_model) listener.add_device.connect(self.connect_device) browser = ServiceBrowser(zeroconf, "_oscjson._tcp.local.", listener) self.current_remote = None def connect_device(self, device): self.panel.device = device def contextual_menu(self, position): indexes = self.explorer.selectedIndexes() if len(indexes) > 0: level = 0 index = indexes[0] while index.parent().isValid(): index = index.parent() level += 1 node = self.devices_model.itemFromIndex(index) menu = QMenu() if level == 0: menu.addAction("Refresh Device Namespace", node.update) elif level > 0: menu.addAction("Refresh Node", node.update) menu.exec_(self.explorer.viewport().mapToGlobal(position)) def selection_updated(self, *args, **kwargs): """ called when device selection is updated we will disconnect our ossia.OSCQueryDevice from the previous device if there was one and then we will reconnect it to the current instance of the Device model """ index = self.device_selection_model.selectedIndexes() # we consider unique selection modelIndex = index[0] if modelIndex: if self.current_remote: self.panel.layout.removeWidget(self.current_remote) self.current_remote.deleteLater() del self.current_remote self.current_remote = None node = self.devices_model.itemFromIndex(modelIndex).node if node.__class__.__name__ == 'OSCQueryDevice': print('Device') else: if node.parameter: self.current_remote = self.panel.add_remote(node.parameter) else: self.current_remote = self.panel.add_inspector(node) else: print('no node selected')
class RegisterViewWidget(WidgetBase): def __init__(self, parent: QWidget): super(RegisterViewWidget, self).__init__(parent) self._registers = [] self._running_task: asyncio.Task = None self._visibility_selector = QComboBox(self) self._visibility_selector.addItem("Show all registers", lambda _: True) self._visibility_selector.addItem("Only configuration parameters", lambda r: r.mutable and r.persistent) # noinspection PyUnresolvedReferences self._visibility_selector.currentIndexChanged.connect( lambda _: self._on_visibility_changed()) self._reset_selected_button = make_button( self, "Reset selected", icon_name="clear-symbol", tool_tip=f"Reset the currently selected registers to their default " f"values. The restored values will be committed " f"immediately. This function is available only if a " f"default value is defined. [{RESET_SELECTED_SHORTCUT}]", on_clicked=self._do_reset_selected, ) self._reset_all_button = make_button( self, "Reset all", icon_name="skull-crossbones", tool_tip=f"Reset the all registers to their default " f"values. The restored values will be committed " f"immediately.", on_clicked=self._do_reset_all, ) self._read_selected_button = make_button( self, "Read selected", icon_name="process", tool_tip=f"Read the currently selected registers only " f"[{READ_SELECTED_SHORTCUT}]", on_clicked=self._do_read_selected, ) self._read_all_button = make_button( self, "Read all", icon_name="process-plus", tool_tip="Read all registers from the device", on_clicked=self._do_read_all, ) self._export_button = make_button( self, "Export", icon_name="export", tool_tip="Export configuration parameters", on_clicked=self._do_export, ) self._import_button = make_button( self, "Import", icon_name="import", tool_tip="Import configuration parameters", on_clicked=self._do_import, ) self._expand_all_button = make_button( self, "", icon_name="expand-arrow", tool_tip="Expand all namespaces", on_clicked=lambda: self._tree.expandAll(), ) self._collapse_all_button = make_button( self, "", icon_name="collapse-arrow", tool_tip="Collapse all namespaces", on_clicked=lambda: self._tree.collapseAll(), ) self._status_display = QLabel(self) self._status_display.setWordWrap(True) self._reset_selected_button.setEnabled(False) self._reset_all_button.setEnabled(False) self._read_selected_button.setEnabled(False) self._read_all_button.setEnabled(False) self._export_button.setEnabled(False) self._import_button.setEnabled(False) self._tree = QTreeView(self) self._tree.setVerticalScrollMode(QTreeView.ScrollPerPixel) self._tree.setHorizontalScrollMode(QTreeView.ScrollPerPixel) self._tree.setAnimated(True) self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection) self._tree.setAlternatingRowColors(True) self._tree.setContextMenuPolicy(Qt.ActionsContextMenu) # Not sure about this one. This hardcoded value may look bad on some platforms. self._tree.setIndentation(20) def add_action( callback: typing.Callable[[], None], icon_name: str, name: str, shortcut: typing.Optional[str] = None, ): action = QAction(get_icon(icon_name), name, self) # noinspection PyUnresolvedReferences action.triggered.connect(callback) if shortcut: action.setShortcut(shortcut) action.setAutoRepeat(False) try: action.setShortcutVisibleInContextMenu(True) except AttributeError: pass # This feature is not available in PyQt before 5.10 self._tree.addAction(action) add_action(self._do_read_all, "process-plus", "Read all registers") add_action( self._do_read_selected, "process", "Read selected registers", READ_SELECTED_SHORTCUT, ) add_action( self._do_reset_selected, "clear-symbol", "Reset selected to default", RESET_SELECTED_SHORTCUT, ) self._tree.setItemDelegateForColumn( int(Model.ColumnIndices.VALUE), EditorDelegate(self._tree, self._display_status), ) # It doesn't seem to be explicitly documented, but it seems to be necessary to select either top or bottom # decoration position in order to be able to use center alignment. Left or right positions do not work here. self._tree.setItemDelegateForColumn( int(Model.ColumnIndices.FLAGS), StyleOptionModifyingDelegate( self._tree, decoration_position=QStyleOptionViewItem.Top, # Important decoration_alignment=Qt.AlignCenter, ), ) header: QHeaderView = self._tree.header() header.setSectionResizeMode(QHeaderView.ResizeToContents) header.setStretchLastSection( False) # Horizontal scroll bar doesn't work if this is enabled buttons_layout = QGridLayout() buttons_layout.addWidget(self._read_selected_button, 0, 0) buttons_layout.addWidget(self._reset_selected_button, 0, 2) buttons_layout.addWidget(self._read_all_button, 1, 0) buttons_layout.addWidget(self._reset_all_button, 1, 2) buttons_layout.addWidget(self._import_button, 2, 0) buttons_layout.addWidget(self._export_button, 2, 2) for col in range(3): buttons_layout.setColumnStretch(col, 1) layout = lay_out_vertically( (self._tree, 1), buttons_layout, lay_out_horizontally( self._visibility_selector, (None, 1), self._expand_all_button, self._collapse_all_button, ), self._status_display, ) self.setLayout(layout) def reset(self): self.setup([]) def setup(self, registers: typing.Iterable[Register]): self._registers = list(registers) self._on_visibility_changed() def _replace_model( self, register_visibility_predicate: typing.Callable[[Register], bool]): # Cancel all operations that might be pending on the old model self._cancel_task() old_model = self._tree.model() # Configure the new model filtered_registers = list( filter(register_visibility_predicate, self._registers)) # It is important to set the Tree widget as the parent in order to let the widget take ownership new_model = Model(self._tree, filtered_registers) _logger.info("New model %r", new_model) self._tree.setModel(new_model) # The selection model is implicitly replaced when we replace the model, so it has to be reconfigured self._tree.selectionModel().selectionChanged.connect( lambda *_: self._on_selection_changed()) # TODO: Something fishy is going on. Something keeps the old model alive when we're replacing it. # We could call deleteLater() on it, but it seems dangerous, because if that something ever decided # to refer to that dead model later for any reason, we'll get a rougue dangling pointer access on # our hands. The horror! if old_model is not None: import gc model_referrers = gc.get_referrers(old_model) if len(model_referrers) > 1: _logger.warning( "Extra references to the old model %r: %r", old_model, model_referrers, ) # Update the widget - all root items are expanded by default for row in itertools.count(): index = self._tree.model().index(row, 0) if not index.isValid(): break self._tree.expand(index) self._reset_selected_button.setEnabled(False) self._read_selected_button.setEnabled(False) self._read_all_button.setEnabled(len(filtered_registers) > 0) self._reset_all_button.setEnabled(len(filtered_registers) > 0) self._export_button.setEnabled(len(filtered_registers) > 0) self._import_button.setEnabled(len(filtered_registers) > 0) self._display_status(f"{len(filtered_registers)} registers loaded") def _on_visibility_changed(self): self._replace_model(self._visibility_selector.currentData()) def _on_selection_changed(self): selected = self._get_selected_registers() self._reset_selected_button.setEnabled( any(map(lambda r: r.has_default_value, selected))) self._read_selected_button.setEnabled(len(selected) > 0) def _do_read_selected(self): selected = self._get_selected_registers() if selected: self._read_specific(selected) else: self._display_status("No registers are selected, nothing to read") def _do_reset_selected(self): rv = {} for r in self._get_selected_registers(): if r.has_default_value: rv[r] = r.default_value self._write_specific(rv) def _do_reset_all(self): rv = {} for r in self._registers: if r.has_default_value: rv[r] = r.default_value self._write_specific(rv) def _do_read_all(self): self._read_specific(self._tree.model().registers) def _do_import(self): import_registers(parent=self, registers=self._registers) def _do_export(self): export_registers(parent=self, registers=self._registers) def _read_specific(self, registers: typing.List[Register]): total_registers_read = None def progress_callback(register: Register, current_register_index: int, total_registers: int): nonlocal total_registers_read total_registers_read = total_registers self._display_status( f"Reading {register.name!r} " f"({current_register_index + 1} of {total_registers})") async def executor(): try: _logger.info("Reading registers: %r", [r.name for r in registers]) mod: Model = self._tree.model() await mod.read(registers=registers, progress_callback=progress_callback) except asyncio.CancelledError: self._display_status(f"Read has been cancelled") raise except Exception as ex: _logger.exception("Register read failed") show_error("Read failed", "Could not read registers", repr(ex), self) self._display_status(f"Could not read registers: {ex!r}") else: self._display_status( f"{total_registers_read} registers have been read") self._cancel_task() self._running_task = asyncio.get_event_loop().create_task(executor()) def _write_specific(self, register_value_mapping: typing.Dict[Register, typing.Any]): total_registers_assigned = None def progress_callback(register: Register, current_register_index: int, total_registers: int): nonlocal total_registers_assigned total_registers_assigned = total_registers self._display_status( f"Writing {register.name!r} " f"({current_register_index + 1} of {total_registers})") async def executor(): try: _logger.info( "Writing registers: %r", [r.name for r in register_value_mapping.keys()], ) mod: Model = self._tree.model() await mod.write( register_value_mapping=register_value_mapping, progress_callback=progress_callback, ) except asyncio.CancelledError: self._display_status(f"Write has been cancelled") raise except Exception as ex: _logger.exception("Register write failed") show_error("Write failed", "Could not read registers", repr(ex), self) self._display_status(f"Could not write registers: {ex!r}") else: self._display_status( f"{total_registers_assigned} registers have been written") self._cancel_task() self._running_task = asyncio.get_event_loop().create_task(executor()) def _get_selected_registers(self) -> typing.List[Register]: selected_indexes: typing.List[ QModelIndex] = self._tree.selectedIndexes() selected_registers = set() for si in selected_indexes: r = Model.get_register_from_index(si) if r is not None: selected_registers.add(r) # Beware that sets are not sorted, this may lead to weird user experience when watching the registers # read in a funny order. return list(sorted(selected_registers, key=lambda x: x.name)) def _cancel_task(self): # noinspection PyBroadException try: self._running_task.cancel() except Exception: pass else: _logger.info("A running task had to be cancelled: %r", self._running_task) finally: self._running_task = None def _display_status(self, text=None): self._status_display.setText(text)
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 PyCommonist(QWidget): tool = None def __init__(self): super(PyCommonist, self).__init__() self.initUI() self.threads = [] self.workers = [] def initUI(self): self.currentDirectoryPath = "" self.generateSplitter() self.generateLeftTopFrame() self.generateLeftBottomFrame() self.showMaximized() self.setWindowTitle("PyCommonist - Wikimedia Commons") self.show() """ onSelectFolder """ def onSelectFolder(self, selected, deselected): try: currentIndex = selected.indexes()[0] self.currentDirectoryPath = self.modelTree.filePath(currentIndex) print(self.currentDirectoryPath) self.statusBar.setText("") self.exifImageCollection = [] list_dir = os.listdir(self.currentDirectoryPath) files = [ f for f in sorted(list_dir) if isfile(join(self.currentDirectoryPath, f)) ] for file in files: fullFilePath = os.path.join(self.currentDirectoryPath, file) if fullFilePath.endswith(".jpeg") or fullFilePath.endswith( ".jpg") or fullFilePath.endswith(".png"): currentExifImage = EXIFImage() currentExifImage.fullFilePath = fullFilePath currentExifImage.filename = file tags = None try: """ EXIF """ f_exif = open(fullFilePath, "rb") tags = exifread.process_file(f_exif) # print(tags) except: print("A problem with EXIF data reading") """ Location""" # 'GPS GPSLatitude', 'GPS GPSLongitude'] # [45, 49, 339/25] [4, 55, 716/25] # 'GPS GPSImgDirection' 'GPS GPSLatitudeRef' lat = "" long = "" heading = "" try: currentExifImage.lat, currentExifImage.long, currentExifImage.heading = get_exif_location( tags) except: print("A problem with EXIF data reading") dt = None try: """ Date Time """ dt = tags[ "EXIF DateTimeOriginal"] # 2021:01:13 14:48:44 except: print("A problem with EXIF data reading") print(dt) dt = str(dt) indexSpace = dt.find(" ") currentExifImage.date = dt[0:indexSpace].replace(":", "-") currentExifImage.time = dt[indexSpace + 1:] self.exifImageCollection.append(currentExifImage) print(currentExifImage) self.generateRightFrame() except: print("Something bad happened inside onSelectFolder function") traceback.print_exc() """ cbImportNoneStateChanged """ def cbImportNoneStateChanged(self): print(self.cbImportNone.isChecked()) print(len(self._currentUpload)) if self.cbImportNone.isChecked() and len(self._currentUpload) > 0: for element in self._currentUpload: element.cbImport.setCheckState(False) """ cbImportAllStateChanged """ def cbImportAllStateChanged(self): print(self.cbImportAll.isChecked()) print(len(self._currentUpload)) if self.cbImportAll.isChecked() and len(self._currentUpload) > 0: for element in self._currentUpload: element.cbImport.setCheckState(True) """ onClickImport """ def onClickImport(self): if self.tool == None: self.tool = UploadTool() ret = self.tool.uploadImages(self) """ cleanThreads """ def cleanThreads(self): try: print("Clean properly threads") for thread in self.threads: print("Current thread proper deletion") thread.quit() thread.wait() except: print("A problem with cleanThreads") """ generateSplitter """ def generateSplitter(self): vbox = QVBoxLayout() hbox = QHBoxLayout() self.leftTopFrame = QFrame() self.leftTopFrame.setFrameShape(QFrame.StyledPanel) self.rightWidget = QWidget() self.rightWidget.resize(300, 300) self.layoutRight = QVBoxLayout() self.rightWidget.setLayout(self.layoutRight) self.scroll = QScrollArea() self.layoutRight.addWidget(self.scroll) self.scroll.setWidgetResizable(True) self.scrollContent = QWidget(self.scroll) self.scrollLayout = QVBoxLayout(self.scrollContent) self.scrollContent.setLayout(self.scrollLayout) self.scroll.setWidget(self.scrollContent) self.splitterLeft = QSplitter(Qt.Vertical) self.leftBottonFrame = QFrame() self.leftBottonFrame.setFrameShape(QFrame.StyledPanel) self.splitterLeft.addWidget(self.leftTopFrame) self.splitterLeft.addWidget(self.leftBottonFrame) self.splitterLeft.setSizes([VERTICAL_TOP_SIZE, VERTICAL_BOTTOM_SIZE]) """ Horizontal Splitter """ self.splitterCentral = QSplitter(Qt.Horizontal) self.splitterCentral.addWidget(self.splitterLeft) self.splitterCentral.addWidget(self.rightWidget) self.splitterCentral.setSizes( [HORIZONTAL_LEFT_SIZE, HORIZONTAL_RIGHT_SIZE]) hbox.addWidget(self.splitterCentral) vbox.addLayout(hbox) """ Status Bar """ self.statusBar = QLabel() self.statusBar.setStyleSheet(STYLE_STATUSBAR) vbox.addWidget(self.statusBar) self.setLayout(vbox) """ generateLeftTopFrame """ def generateLeftTopFrame(self): self.layoutLeftTop = QFormLayout() self.layoutLeftTop.setFormAlignment(Qt.AlignTop) self.lblUserName = QLabel("Username: "******"Password: "******"Source: ") self.lblSource.setAlignment(Qt.AlignLeft) self.lineEditSource = QLineEdit() self.lineEditSource.setFixedWidth(WIDTH_WIDGET) self.lineEditSource.setText(LeftFrameConfig.source) self.lineEditSource.setAlignment(Qt.AlignLeft) self.layoutLeftTop.addRow(self.lblSource, self.lineEditSource) self.lblAuthor = QLabel("Author: ") self.lblAuthor.setAlignment(Qt.AlignLeft) self.lineEditAuthor = QLineEdit() self.lineEditAuthor.setFixedWidth(WIDTH_WIDGET) self.lineEditAuthor.setText(LeftFrameConfig.author) self.lineEditAuthor.setAlignment(Qt.AlignLeft) self.layoutLeftTop.addRow(self.lblAuthor, self.lineEditAuthor) self.lblCategories = QLabel("Categories: ") self.lblCategories.setAlignment(Qt.AlignLeft) self.lineEditCategories = QLineEdit() self.lineEditCategories.setText(LeftFrameConfig.categories) self.lineEditCategories.setFixedWidth(WIDTH_WIDGET) self.lineEditCategories.setAlignment(Qt.AlignLeft) self.layoutLeftTop.addRow(self.lblCategories, self.lineEditCategories) self.lblLicense = QLabel("License: ") self.lblLicense.setAlignment(Qt.AlignLeft) self.lineEditLicense = QLineEdit() self.lineEditLicense.setFixedWidth(WIDTH_WIDGET) self.lineEditLicense.setText(LeftFrameConfig.licence) self.lineEditLicense.setAlignment(Qt.AlignLeft) self.layoutLeftTop.addRow(self.lblLicense, self.lineEditLicense) self.lblDescription = QLabel("Description: ") self.lblDescription.setAlignment(Qt.AlignLeft) self.lineEditDescription = QPlainTextEdit() self.lineEditDescription.setFixedWidth(WIDTH_WIDGET) self.layoutLeftTop.addRow(self.lblDescription, self.lineEditDescription) separationLeftTopFrame = QLabel() self.layoutLeftTop.addWidget(separationLeftTopFrame) """ Button Import & None/All checkboxes""" importWidget = QWidget() importLayout = QHBoxLayout() importWidget.setLayout(importLayout) self.cbImportNone = QCheckBox("None") self.cbImportNone.stateChanged.connect(self.cbImportNoneStateChanged) self.cbImportAll = QCheckBox("All") self.cbImportAll.stateChanged.connect(self.cbImportAllStateChanged) self.btnImport = QPushButton("Import!") self.btnImport.clicked.connect(self.onClickImport) importLayout.addWidget(self.cbImportNone) importLayout.addWidget(self.cbImportAll) importLayout.addWidget(self.btnImport) self.layoutLeftTop.addWidget(importWidget) importWidget.setStyleSheet("border:1px solid #808080;") self.cbImportNone.setStyleSheet("border:0px;") self.cbImportAll.setStyleSheet("border:0px;") self.btnImport.setStyleSheet(STYLE_IMPORT_BUTTON) """ Layout Association of the Left Top Frame""" self.leftTopFrame.setLayout(self.layoutLeftTop) """ generateLeftBottomFrame """ def generateLeftBottomFrame(self): self.layoutLeftBottom = QVBoxLayout() """Model for QTreeView""" self.modelTree = QFileSystemModel() self.modelTree.setRootPath(QDir.currentPath()) self.modelTree.setFilter(QDir.Dirs) # Only directories """ QTreeView """ self.treeLeftBottom = QTreeView() self.treeLeftBottom.setModel(self.modelTree) self.treeLeftBottom.setAnimated(False) self.treeLeftBottom.setIndentation(10) self.treeLeftBottom.setColumnWidth(0, 300) self.treeLeftBottom.expandAll() self.treeLeftBottom.selectionModel().selectionChanged.connect( self.onSelectFolder) self.layoutLeftBottom.addWidget(self.treeLeftBottom) self.leftBottonFrame.setLayout(self.layoutLeftBottom) """ generateRightFrame """ def generateRightFrame(self): self._currentUpload = [] layout = self.scrollLayout print(layout) print(layout.count()) while layout.count(): print("destroy") child = layout.takeAt(0) if child.widget(): child.widget().deleteLater() for currentExifImage in self.exifImageCollection: """ Current image """ localWidget = ImageUpload() localLayout = QHBoxLayout() localLayout.setAlignment(Qt.AlignRight) localWidget.setLayout(localLayout) self.scrollLayout.addWidget(localWidget) self._currentUpload.append(localWidget) """Local Left Widget""" localLeftWidget = QWidget() localLeftLayout = QFormLayout() localLeftLayout.setAlignment(Qt.AlignRight) localLeftWidget.setLayout(localLeftLayout) localLayout.addWidget(localLeftWidget) """ import? + Import Status """ cbImport = QCheckBox("Import") lblUploadResult = QLabel() lblUploadResult.setStyleSheet(STYLE_IMPORT_STATUS) localLeftLayout.addRow(cbImport, lblUploadResult) localWidget.cbImport = cbImport localWidget.lblUploadResult = lblUploadResult """ File Name of picture """ lblFileName = QLabel("Name: ") lblFileName.setAlignment(Qt.AlignLeft) lineEditFileName = QLineEdit() lineEditFileName.setFixedWidth(WIDTH_WIDGET_RIGHT) lineEditFileName.setText(currentExifImage.filename) lineEditFileName.setAlignment(Qt.AlignLeft) localLeftLayout.addRow(lblFileName, lineEditFileName) localWidget.lineEditFileName = lineEditFileName """ Shadow Real FileName """ lblRealFileName = QLineEdit() lblRealFileName.setText(currentExifImage.filename) localWidget.lblRealFileName = lblRealFileName localWidget.lblRealFileName.isVisible = False """ Description """ lblDescription = QLabel("Description: ") lblDescription.setAlignment(Qt.AlignLeft) lineEditDescription = QPlainTextEdit() lineEditDescription.setFixedWidth(WIDTH_WIDGET_RIGHT) localLeftLayout.addRow(lblDescription, lineEditDescription) localWidget.lineEditDescription = lineEditDescription """ Categories """ lblCategories = QLabel("Categories: ") lblCategories.setAlignment(Qt.AlignLeft) lineEditCategories = QLineEdit() lineEditCategories.setFixedWidth(WIDTH_WIDGET_RIGHT) lineEditCategories.setAlignment(Qt.AlignLeft) localLeftLayout.addRow(lblCategories, lineEditCategories) localWidget.lineEditCategories = lineEditCategories lblLocation = QLabel("Location: ") lblLocation.setAlignment(Qt.AlignLeft) lineEditLocation = QLineEdit() lineEditLocation.setFixedWidth(WIDTH_WIDGET_RIGHT) if currentExifImage.lat == None or currentExifImage.long == None: lineEditLocation.setText("") else: lineEditLocation.setText( str(currentExifImage.lat) + "|" + str(currentExifImage.long) + "|heading:" + str(currentExifImage.heading)) lineEditLocation.setAlignment(Qt.AlignLeft) localLeftLayout.addRow(lblLocation, lineEditLocation) localWidget.lineEditLocation = lineEditLocation lblDateTime = QLabel("Date Time: ") lblDateTime.setAlignment(Qt.AlignLeft) lineEditDateTime = QLineEdit() lineEditDateTime.setFixedWidth(WIDTH_WIDGET_RIGHT) lineEditDateTime.setText(currentExifImage.date + " " + currentExifImage.time) lineEditDateTime.setAlignment(Qt.AlignLeft) localLeftLayout.addRow(lblDateTime, lineEditDateTime) localWidget.lineEditDateTime = lineEditDateTime """ Image itself """ label = QLabel() pixmap = QPixmap(currentExifImage.fullFilePath) pixmapResize = pixmap.scaledToWidth(IMAGE_DIMENSION, Qt.FastTransformation) label.setPixmap(pixmapResize) localLayout.addWidget(label) localWidget.fullFilePath = currentExifImage.fullFilePath self.update()
class MyMainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.config_window() self.create_widgets() self.config_widgets() self.create_menubar() self.bind_widgets() self.show_widgets() def config_window(self): self.setWindowTitle('DirectoPy') self.setMinimumHeight(600) self.setMinimumWidth(1000) def create_widgets(self): self.central_widget = QWidget() self.main_layout = QGridLayout() self.moveup_button = QPushButton('Collapse all', self) self.goto_lineedit = QLineEdit('', self) self.goto_button = QPushButton('Go', self) self.folder_view = QTreeView(self) self.file_view = QTreeView(self) self.folder_model = QFileSystemModel(self) self.file_model = QFileSystemModel(self) def config_widgets(self): self.main_layout.addWidget(self.moveup_button, 0, 0) self.main_layout.addWidget(self.goto_lineedit, 0, 1, 1, 2) self.main_layout.addWidget(self.goto_button, 0, 3) self.main_layout.addWidget(self.folder_view, 1, 0, 1, 2) self.main_layout.addWidget(self.file_view, 1, 2, 1, 2) self.central_widget.setLayout(self.main_layout) # Кнопка "вверх" self.moveup_button.setMaximumWidth(100) # Кнопка "перейти" self.goto_button.setMaximumWidth(70) self.setCentralWidget(self.central_widget) # панели self.folder_model.setRootPath(None) self.folder_model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot) self.folder_view.setModel(self.folder_model) self.folder_view.setRootIndex(self.folder_model.index(None)) self.folder_view.clicked[QModelIndex].connect(self.clicked_onfolder) self.folder_view.hideColumn(1) self.folder_view.hideColumn(2) self.folder_view.hideColumn(3) self.file_model.setFilter(QDir.Files) self.file_view.setModel(self.file_model) self.file_model.setReadOnly(False) self.file_view.setColumnWidth(0, 200) self.file_view.setSelectionMode(QAbstractItemView.ExtendedSelection) # открытие папки при нажати на неё в окне папок def clicked_onfolder(self, index): selection_model = self.folder_view.selectionModel() index = selection_model.currentIndex() dir_path = self.folder_model.filePath(index) self.file_model.setRootPath(dir_path) self.file_view.setRootIndex(self.file_model.index(dir_path)) # ф-я открытия нового файла def open_file(self): index = self.file_view.selectedIndexes() if not index: return else: index = index[0] file_path = self.file_model.filePath(index).replace('/', '\\') print(file_path) self.file_view.update() # ф-я создания нового файла def new_file(self): global file_name index = self.folder_view.selectedIndexes() if len(index) > 0: path = self.folder_model.filePath(index[0]) for i in range(1, 9999999999999999): if not os.path.isfile(os.path.join(path, "newfile{}.txt".format(i))): file_name = os.path.join(path, "newfile{}.txt".format(i)) break file_name = os.path.abspath(file_name) open(file_name, 'w').close() else: print("Please, select folder") # ф-я удаления файла def delete_file(self): indexes = self.file_view.selectedIndexes() for i in indexes: self.file_model.remove(i) # ф-я переименования файла def rename_file(self): index = self.file_view.selectedIndexes() if not index: return else: index = index[0] self.file_view.edit(index) # ф-я копирования файла def copy_file(self): print("COPY") ask = QFileDialog.getExistingDirectory(self, "Open Directory", "C:\\", QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) new_path = ask.replace('\\', '/') indexes = self.file_view.selectedIndexes()[::4] for i in indexes: new_filename = new_path + '/' + self.file_model.fileName(i) copy2(self.file_model.filePath(i), new_filename) # ф-я возвращения к корню пути def colapse(self): self.folder_view.collapseAll() # ф-я перемещения в заданную директорию def go_to(self): dir_path = self.goto_lineedit.text().replace('\\', '/') print(dir_path) self.file_model.setRootPath(dir_path) self.file_view.setRootIndex(self.file_model.index(dir_path)) #self.file_model.setRootPath() # ф-я перемещения файла def move_file(self): print("MOVE") ask = QFileDialog.getExistingDirectory(self, "Open Directory", "C:\\", QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) if ask == '': return new_path = ask.replace('\\', '/') indexes = self.file_view.selectedIndexes()[::4] for i in indexes: new_filename = new_path + '/' + self.file_model.fileName(i) move(self.file_model.filePath(i), new_filename) # ф-я создания новой папки def new_folder(self): global file_name index = self.folder_view.selectedIndexes() if len(index) > 0: path = self.folder_model.filePath(index[0]) for i in range(1, 9999999999999999): if not os.path.isdir(os.path.join(path, "newfolder{}".format(i))): file_name = os.path.join(path, "newfolder{}".format(i)) break file_name = os.path.abspath(file_name) os.mkdir(file_name) else: print("Please, select folder") # ф-я удаления папки def delete_folder(self): indexes = self.folder_view.selectedIndexes() for i in indexes: self.folder_model.remove(i) # ф-я переименования папки def rename_folder(self): index = self.folder_view.selectedIndexes() if not index: return else: index = index[0] self.folder_view.edit(index) # ф-я закрытия окна файлового менеджера def exit_application(self): print("EXIT") self.close() # задавание функции каждой кнопке def bind_widgets(self): self.open_file_action.triggered.connect(self.open_file) self.new_file_action.triggered.connect(self.new_file) self.delete_file_action.triggered.connect(self.delete_file) self.rename_file_action.triggered.connect(self.rename_file) self.copy_file_action.triggered.connect(self.copy_file) self.move_file_action.triggered.connect(self.move_file) self.exit_action.triggered.connect(self.exit_application) self.new_folder_action.triggered.connect(self.new_folder) self.delete_folder_action.triggered.connect(self.delete_folder) self.rename_folder_action.triggered.connect(self.rename_folder) self.goto_button.clicked.connect(partial(self.go_to)) self.moveup_button.clicked.connect(partial(self.colapse)) # создание меню def create_menubar(self): self.exit_action = QAction('Exit', self) self.exit_action.setShortcut('Ctrl+Q') self.new_file_action = QAction('New file', self) self.new_file_action.setShortcut('F4') self.open_file_action = QAction('Open file', self) self.open_file_action.setShortcut('F3') self.rename_file_action = QAction('Rename file', self) self.rename_file_action.setShortcut('F2') self.delete_file_action = QAction('Remove file', self) self.delete_file_action.setShortcut(QKeySequence.Delete) self.copy_file_action = QAction('Copy folder...', self) self.copy_file_action.setShortcut(QKeySequence.Copy) self.move_file_action = QAction('Move folder...', self) self.move_file_action.setShortcut(QKeySequence.Cut) self.new_folder_action = QAction('New folder', self) self.new_folder_action.setShortcut('Ctrl+Shift+N') self.delete_folder_action = QAction('Delete folder', self) self.delete_folder_action.setShortcut('Ctrl+Shift+Del') self.rename_folder_action = QAction('Rename folder', self) self.rename_folder_action.setShortcut('Ctrl+Shift+R') self.menubar = self.menuBar() self.file_menu = self.menubar.addMenu('File') self.file_menu.addAction(self.new_file_action) self.file_menu.addAction(self.open_file_action) self.file_menu.addAction(self.rename_file_action) self.file_menu.addAction(self.delete_file_action) self.file_menu.addAction(self.copy_file_action) self.file_menu.addAction(self.move_file_action) self.file_menu.addSeparator() self.file_menu.addAction(self.exit_action) self.folder_menu = self.menubar.addMenu('Folder') self.folder_menu.addAction(self.new_folder_action) self.folder_menu.addAction(self.delete_folder_action) self.folder_menu.addAction(self.rename_folder_action) def show_widgets(self): self.setLayout(self.main_layout)
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 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 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 EncoderSelector(QComboBox): __PATH_SEPARATOR__ = " → " _PATH_ROLE = 1 _DETAILS_ROLE = 2 class EncoderModel(QStandardItemModel): def __init__(self, json_file, disable_selections=False): super().__init__() self.disable_selections = disable_selections self.json_file = json_file self.json = None self.reload() def reload(self): with open(self.json_file) as config: self.json = json.load(config) self.beginResetModel() self.clear() self._build_descendents(self.invisibleRootItem(), self.json, self.disable_selections) self.endResetModel() def get_item(self, model_index): item = self.itemFromIndex(model_index) if item: return item.text() return None def del_item(self, media_type, encoder_group, name): del self.json[media_type][encoder_group][name] # Clean up ancestors if they dont have any children if len(self.json[media_type][encoder_group].keys()) == 0: del self.json[media_type][encoder_group] if len(self.json[media_type].keys()) == 0: del self.json[media_type] def add_item(self, media_type, group, name, extension, executable, command): if media_type not in self.json: self.json[media_type] = {} if group not in self.json[media_type]: self.json[media_type][group] = {} self.json[media_type][group][name] = { "extension": extension, "executable": executable, "command": command } def backup_and_save(self): # Backup current config backup_config_file() # Save new config with open(self.json_file, "w") as config: json.dump(self.json, config, indent=4) self.reload() @staticmethod def _build_descendents(parent, _json, disable_selections): encoder_keys = {"extension", "executable", "command"} intersection = set(_json.keys()) & encoder_keys if not intersection: # add node and build further for key in _json: section_root = QStandardItem(key) parent.appendRow(section_root) if disable_selections: parent.setSelectable(False) EncoderSelector.EncoderModel._build_descendents( section_root, _json[key], disable_selections) else: if len(intersection) < 3: raise SyntaxError( f"Missing value(s) {encoder_keys - intersection} in node {parent.text()}" ) else: path = "" item = parent while item.parent() is not None: path = f"{EncoderSelector.__PATH_SEPARATOR__}{item.text()}" + path item = item.parent() path = f"{item.text()}" + path parent.setData(path, Qt.UserRole + EncoderSelector._PATH_ROLE) parent.setData(_json, Qt.UserRole + EncoderSelector._DETAILS_ROLE) encoder_changed = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject') def __init__(self): super().__init__() self.tree_view = QTreeView(self) self.tree_view.setEditTriggers(QTreeView.NoEditTriggers) self.tree_view.setSelectionBehavior(QTreeView.SelectRows) self.tree_view.setWordWrap(True) self.tree_view.setHeaderHidden(True) self.setView(self.tree_view) self.encoder_model = EncoderSelector.EncoderModel( get_config_file(), disable_selections=True) self.setModel(self.encoder_model) self.refresh_encoders() self._selected_encoder = "" def paintEvent(self, event): style = QApplication.style() opt = QStyleOptionComboBox() opt.rect = self.rect() self.initStyleOption(opt) painter = QPainter(self) painter.save() style.drawComplexControl(QStyle.CC_ComboBox, opt, painter) opt.currentText = self._encoder_path() style.drawControl(QStyle.CE_ComboBoxLabel, opt, painter) painter.restore() def hidePopup(self): super(EncoderSelector, self).hidePopup() selected_index = self.tree_view.selectionModel().currentIndex() if selected_index: encoder = self.tree_view.model().get_item(selected_index) if encoder: self._selected_encoder = encoder self.encoder_changed.emit(self._encoder_path(), self._encoder_details()) def get_encoder(self): return self._encoder_path(), self._encoder_details() def add_encoder(self, media_type, encoder_group, name, command, executable, extension): self.encoder_model.add_item(media_type, encoder_group, name, extension, executable, command) self.encoder_model.backup_and_save() self.refresh_encoders() def del_encoder(self, media_type, encoder_group, name): self.encoder_model.del_item(media_type, encoder_group, name) self.encoder_model.backup_and_save() self.refresh_encoders() def update_encoder(self, media_type, encoder_group, name, command, executable, extension): self.encoder_model.del_item(media_type, encoder_group, name) self.encoder_model.add_item(media_type, encoder_group, name, extension, executable, command) self.encoder_model.backup_and_save() self.refresh_encoders() def refresh_encoders(self): self.encoder_model.reload() self.tree_view.expandAll() def select_encoder(self, encoder_name): match = self._find_encoder(encoder_name) if match: self._selected_encoder = encoder_name self.encoder_changed.emit(self._encoder_path(), self._encoder_details()) def _encoder_path(self): if self._selected_encoder: match = self._find_encoder(self._selected_encoder) if match: return match[0].data(Qt.UserRole + self._PATH_ROLE) return None def _encoder_details(self): if self._selected_encoder: match = self._find_encoder(self._selected_encoder) if match: return match[0].data(Qt.UserRole + self._DETAILS_ROLE) return None def _find_encoder(self, encoder_name): return self.model().match(self.model().index(0, 0), Qt.UserRole + self._PATH_ROLE, encoder_name, 1, Qt.MatchEndsWith | Qt.MatchRecursive)
class Explorer(QDialog): def __init__( self, parent, window_title=_("Select resources"), subtitle=_("Select files and/or folders to include") ): super().__init__(parent) self.logger = logging.getLogger(__name__) self.setModal(True) self.setSizeGripEnabled(True) self.setWindowTitle(window_title) self.subtitle = subtitle self.file_set = set() self.config = Configuration() self.__init_ui__() self.filename_filter = FilenameFilter() # self.show() def __init_ui__(self): # layout vert = QVBoxLayout(self) vert.setContentsMargins(0, 0, 0, 0) resource_dir = self.config.cfg_resource_dir() p_top = QVBoxLayout() lb_subtitle = QLabel(self.subtitle) lb_subtitle.setContentsMargins(10, 10, 10, 0) p_top.addWidget(lb_subtitle) self.model = QFileSystemModel() self.model.setRootPath(resource_dir) self.view = QTreeView() self.view.setModel(self.model) self.view.setRootIndex(self.model.index(self.model.rootPath())) self.view.setAlternatingRowColors(True) self.view.setSelectionMode(QAbstractItemView.MultiSelection) self.view.selectionModel().selectionChanged.connect(self.selection_changed) self.view.collapsed.connect(self.item_collapsed) self.view.expanded.connect(self.item_expanded) p_top.addWidget(self.view) p_info = QHBoxLayout() lb_resource = QLabel(_("resource dir") + ": " + resource_dir) lb_resource.setContentsMargins(10, 0, 10, 0) self.lb_selection_count = QLabel(str(self.selected_file_count()) + " " + _("resources selected")) self.lb_selection_count.setContentsMargins(10, 0, 10, 0) p_info.addWidget(self.lb_selection_count) p_info.addStretch(1) p_info.addWidget(lb_resource) p_top.addLayout(p_info) p_bottom = QHBoxLayout() self.pb_deselect = QPushButton(_("Deselect all")) self.pb_deselect.clicked.connect(self.pb_deselect_clicked) self.pb_deselect.setEnabled(self.selected_file_count() > 0) p_bottom.addWidget(self.pb_deselect) p_bottom.addStretch(1) self.pb_ok = QPushButton(_("OK")) self.pb_ok.setAutoDefault(True) self.pb_ok.clicked.connect(self.accept) p_bottom.addWidget(self.pb_ok) pb_cancel = QPushButton(_("Cancel")) pb_cancel.clicked.connect(self.reject) p_bottom.addWidget(pb_cancel) vert.addLayout(p_top) vert.addLayout(p_bottom) self.setLayout(vert) self.resize(self.config.explorer_width(), self.config.explorer_height()) width = self.view.width() - 50 self.view.setColumnWidth(0, width / 2) self.view.setColumnWidth(1, width / 6) self.view.setColumnWidth(2, width / 6) self.view.setColumnWidth(3, width / 6) def __persist__(self): # persist properties of the explorer self.config.set_explorer_width(self.width()) self.config.set_explorer_height(self.height()) self.config.persist() def __compute_filenames__(self, item_selection): # item_selection: a QItemSelection # return corresponding absolute filenames as a set, including filenames in underlying folders s = set() for index in item_selection.indexes(): # we have an index for each column in the model if index.column() == 0: path = index.model().filePath(index) if os.path.isdir(path): for root, directories, filenames in os.walk(path): for filename in filenames: if self.filename_filter.accept(filename): s.add(os.path.join(root, filename)) elif os.path.isfile(path): s.add(path) else: self.logger.warn("isUnknownThing", path) return s def showEvent(self, QShowEvent): # self.pb_ok.setFocus() pass def set_filename_filter(self, filename_filter): # set the FilenameFilter self.filename_filter = filename_filter def selected_file_count(self): return len(self.file_set) def selected_file_set(self): return frozenset(self.file_set) def selection_changed(self, selected, deselected): # selected, deselected: PyQt5.QtCore.QItemSelection selected_filenames = self.__compute_filenames__(selected) self.file_set.update(selected_filenames) deselected_filenames = self.__compute_filenames__(deselected) self.file_set.difference_update(deselected_filenames) self.pb_deselect.setEnabled(self.selected_file_count() > 0) self.lb_selection_count.setText(str(self.selected_file_count()) + " " + _("resources selected")) def item_expanded(self, index): # index: a QModelIndex # show all child items selected/deselected in accordance with state of parent folder pass def item_collapsed(self, index): pass def pb_deselect_clicked(self): self.view.selectionModel().clear() def hideEvent(self, QHideEvent): self.__persist__()
class EditScreen(QWidget): def __init__(self, rester: Rester): super(EditScreen, self).__init__() self.last_item_type = '' self.rester = rester self.tree = QTreeView() layout = QVBoxLayout() layout.addWidget(self.tree) self.model = QStandardItemModel() self.model.setHorizontalHeaderLabels(['Name']) self.tree.header().setDefaultSectionSize(200) self.tree.setModel(self.model) self.data = self.rester.list_request(list(APIEnum)) self.import_data(self.data) self.tree.setSortingEnabled(False) self.tree.expandAll() self.tree.setSelectionMode(QAbstractItemView.SingleSelection) self.tree.selectionModel().selectionChanged.connect(self.item_selected) self.init_ui() def init_ui(self): hbox = QHBoxLayout() vbox = QVBoxLayout() left_layout = QFormLayout() tree_group_box = QGroupBox() self.search_field = QLineEdit() # self.search_field.editingFinished.connect(self.search_field_changed) self.search_field.textEdited.connect(self.search_field_changed) left_layout.addRow(QLabel('Search:'), self.search_field) tree_group_box.setLayout(left_layout) vbox.addWidget(tree_group_box) vbox.addWidget(self.tree) gb = QGroupBox() gb.setLayout(vbox) self.splitter = QSplitter() self.splitter.addWidget(gb) self.ew = EquipmentEdit() self.splitter.addWidget(self.ew) hbox.addWidget(self.splitter) self.setLayout(hbox) def import_data(self, data, root=None): self.model.setRowCount(0) if root is None: root = self.model.invisibleRootItem() for key, value_list in data.items(): parent = TreeItem({'name': key}, '', parent=True) parent.setEditable(False) root.appendRow([parent]) for value in value_list: parent.appendRow([TreeItem(value, key)]) def search_field_changed(self): term = self.search_field.text() data = {} for key, value_list in self.data.items(): li = [] for value in value_list: for k in value.keys(): if term in k: li.append(value) data[key] = li self.import_data(data) def item_selected(self): indexes = self.tree.selectedIndexes() selected = indexes[0] item = self.model.itemFromIndex(selected) if item.is_parent: return #if item.typ != self.last_item_type: self.ew.hide() self.ew.destroy() self.ew = get_edit_widget(item.typ, self.data) self.splitter.addWidget(self.ew) self.last_item_type = item.typ data_dict = item.get_data_dict() self.ew.load_data(data_dict)
class StatWidget(QWidget): def __init__(self, parent=None, domain=None, headers=None): super().__init__(parent=parent) self._root = None self._domain = domain self._button = QPushButton('Открыть...') self._edit = QLineEdit() self._tree = QTreeView() self._model = StatTreeModel(parent=self, headers=headers) self._layoutControl = QHBoxLayout() self._layoutControl.addWidget(self._button) self._layoutControl.addWidget(self._edit) self._layoutMain = QVBoxLayout() self._layoutMain.addLayout(self._layoutControl) self._layoutMain.addWidget(self._tree) self.setLayout(self._layoutMain) self._edit.setPlaceholderText('Папка...') self._edit.setReadOnly(True) self._button.clicked.connect(self._on_button_clicked) self._tree.setModel(self._model) self._tree.setItemDelegateForColumn(3, ProgressBarDelegate()) self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection) self._tree.setSelectionBehavior(QAbstractItemView.SelectRows) def init(self): self._domain.workDir = os.path.normpath(os.getcwd()) + '\\xlsx' self._edit.setText(self._domain.workDir) self._model.init(self._domain._root) @pyqtSlot() def _on_button_clicked(self): dialog = QFileDialog(self, 'Выбрать папку...', self._domain.workDir) dialog.setFileMode(QFileDialog.DirectoryOnly) if dialog.exec() != QFileDialog.Accepted: return self._domain.workDir = dialog.selectedFiles()[0] self._model.init(self._domain._root) @property def hasSelection(self): return self._tree.selectionModel().hasSelection() @property def rows(self): top_level_rows = [ index for index in self._tree.selectionModel().selectedIndexes() if index.data(StatTreeModel.RoleTier) == StatTreeModel.TIER_1 and index.column() == 0 ] sorted_row_numbers = list( sorted([index.row() for index in top_level_rows])) return sorted_row_numbers def getEmailData(self, rows): # TODO handle selected rows # TODO handle different domains for email generation, works only for batch stats now email_data = dict() for batch in self._model._rootNode.child_nodes: specs_for_dev = defaultdict(list) for spec in batch.child_nodes: if not spec['received']: specs_for_dev[spec['developer']].append(spec) email_data[batch] = specs_for_dev return email_data def resizeTable(self, width, columns): for index, column in enumerate(columns): self._tree.setColumnWidth(index, width * column)
class UIFilterManager(object): def __init__(self): self.mainDialog = filtermanagerdialog.FilterManagerDialog() self.mainLayout = QVBoxLayout(self.mainDialog) self.formLayout = QFormLayout() self.buttonBox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.kritaInstance = krita.Krita.instance() self._filters = sorted(self.kritaInstance.filters()) self._documents = self.kritaInstance.documents() self.treeModel = filtermanagertreemodel.FilterManagerTreeModel(self) self.documentsTreeView = QTreeView() self.filterComboBox = filtercombobox.FilterComboBox(self) self.buttonBox.accepted.connect(self.confirmButton) self.buttonBox.rejected.connect(self.mainDialog.close) self.documentsTreeView.setSelectionMode( QAbstractItemView.SingleSelection) self.mainDialog.setWindowModality(Qt.NonModal) def initialize(self): self.documentsTreeView.setModel(self.treeModel) self.documentsTreeView.setWindowTitle(i18n("Document Tree Model")) self.documentsTreeView.resizeColumnToContents(0) self.documentsTreeView.resizeColumnToContents(1) self.documentsTreeView.resizeColumnToContents(2) self.formLayout.addRow(i18nc("Python filters", "Filters:"), self.filterComboBox) self.line = QFrame() self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.mainLayout.addWidget(self.documentsTreeView) self.mainLayout.addLayout(self.formLayout) self.mainLayout.addWidget(self.line) self.mainLayout.addWidget(self.buttonBox) self.mainDialog.resize(500, 300) self.mainDialog.setWindowTitle(i18n("Filter Manager")) self.mainDialog.setSizeGripEnabled(True) self.mainDialog.show() self.mainDialog.activateWindow() def confirmButton(self): documentsIndexes = [] selectionModel = self.documentsTreeView.selectionModel() for index in selectionModel.selectedRows(): node = self.treeModel.data(index, Qt.UserRole + 1) documentIndex = self.treeModel.data(index, Qt.UserRole + 2) _type = self.treeModel.data(index, Qt.UserRole + 3) if _type == 'Document': self.applyFilterOverDocument(self.documents[documentIndex]) else: self.applyFilterOverNode(node, self.documents[documentIndex]) documentsIndexes.append(documentIndex) self.refreshDocumentsProjections(set(documentsIndexes)) def refreshDocumentsProjections(self, indexes): for index in indexes: document = self.documents[index] document.refreshProjection() def applyFilterOverNode(self, node, document): _filter = self.kritaInstance.filter(self.filterComboBox.currentText()) _filter.apply(node, 0, 0, document.width(), document.height()) def applyFilterOverDocument(self, document): """This method applies the selected filter just to topLevelNodes, then if topLevelNodes are GroupLayers, that filter will not be applied.""" for node in document.topLevelNodes(): self.applyFilterOverNode(node, document) @property def filters(self): return self._filters @property def documents(self): return self._documents
class LeftSideBar(QWidget): treeViewSelectionChanged = pyqtSignal(QModelIndex, QModelIndex) treeViewDoubleClicked = pyqtSignal(QModelIndex) addPlaylistRequested = pyqtSignal() removePlaylistRequested = pyqtSignal(UUID) addToPlaylistRequested = pyqtSignal(UUID) playlistAdded = pyqtSignal(UUID) playlistRenamed = pyqtSignal(UUID, str) def __init__(self, tree_items, parent=None): super(LeftSideBar, self).__init__(parent) self._tree_items = tree_items self._restoreSettings() self._setTreeView() self._setAlbumCoverBox() self._renderUI() self.setMinimumWidth(FRONT_COVER_MIN_WIDTH) self.setMaximumWidth(FRONT_COVER_MAX_WIDTH) def _restoreSettings(self): pass def _setTreeView(self): self.treeModel = TreeModel() self.treeModel.addTopLevelItems(self._tree_items.keys()) self.leftBarView = QTreeView() self.leftBarView.setModel(self.treeModel) self.leftBarView.setHeaderHidden(True) self.leftBarView.setRootIsDecorated(False) self.leftBarView.setItemsExpandable(False) self.leftBarView.setMouseTracking(True) self.leftBarView.expandAll() self.leftBarView.setFocusPolicy(Qt.NoFocus) self.leftBarView.setSelectionBehavior(QAbstractItemView.SelectRows) self.leftBarView.setSelectionMode(QAbstractItemView.SingleSelection) self.leftBarView.setEditTriggers(QAbstractItemView.SelectedClicked) self.leftBarView.selectionModel().currentRowChanged.connect( lambda c, p: self.treeViewSelectionChanged.emit(c, p)) self.leftBarView.selectionModel().setCurrentIndex( self.leftBarView.model().index( 0, 0, self.leftBarView.model().index(0, 0)), QItemSelectionModel.Select) self.leftBarView.doubleClicked.connect( lambda i: self.treeViewDoubleClicked.emit(i)) delegate = LeftSideBarDelegate(self.leftBarView) self.leftBarView.setItemDelegate(delegate) delegate.addPlaylistRequested.connect( lambda: self.addPlaylistRequested.emit()) delegate.removePlaylistRequested.connect( lambda i: self.removePlaylistRequested.emit( self.__getUuidFromIndex(i))) delegate.addToPlaylistRequested.connect( lambda i: self.addToPlaylistRequested.emit(self.__getUuidFromIndex(i))) delegate.editingFinished.connect(self._onRenamed) def _onRenamed(self, index, text): self.playlistRenamed.emit( self.__getUuidFromIndex(index), text) @property def model(self): return self.treeModel def _setAlbumCoverBox(self): self.albumCoverBox = CoverArtBox() def _renderUI(self): self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.layout.addWidget(self.leftBarView) self.layout.addWidget(self.albumCoverBox) self.setLayout(self.layout) @QtCore.pyqtSlot(str, str, bytes) def changeCoverArtBoxInformation(self, title, artist, cover): self.albumCoverBox.setCoverArtBox(title, artist, cover) @QtCore.pyqtSlot(UUID, str, int, int) def addPlaylistEntry(self, uuid, name, row=0, column=0): model = self.model parent = model.getTopLevelIndex('PLAYLISTS') if model.insertPlaylistEntry(row, name, uuid, parent): self.playlistAdded.emit(uuid) child = model.index(row, column, parent) self.leftBarView.selectionModel().setCurrentIndex( child, QItemSelectionModel.SelectCurrent) self.leftBarView.edit(child) @QtCore.pyqtSlot(UUID, int, int) def createDefaults(self, uuid, row=0, column=0): model = self.model parent = model.getTopLevelIndex('LIBRARY') model.insertPlaylistEntry(row, 'Songs', uuid, parent) def __getUuidFromIndex(self, index): model = self.model uuid = model.getItemUuid(index) return uuid def __getIndexFromUuid(self, uuid): model = self.model row = model.getIndexFromUuid(uuid) return row @QtCore.pyqtSlot(UUID) def removePlaylistEntry(self, uuid): model = self.model row = self.__getIndexFromUuid(uuid) parent = model.getTopLevelIndex('PLAYLISTS') if not model.removeRow(row, parent): return None
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 RedditScraperWindow(QWidget): """The main window of the program.""" ########### Setup ################################## def __init__(self): super().__init__() self.read_user_config() self.load_assets() self.initialize_ui() def load_assets(self): script_folder = os.path.dirname( os.path.dirname(os.path.abspath(__file__))) asset_folder = os.path.join(script_folder, "assets") if os.path.isdir(asset_folder): self.download_icon = QIcon( os.path.join(asset_folder, 'download_icon.png')) self.stop_icon = QIcon(os.path.join(asset_folder, 'stop_icon.png')) self.reddit_icon = QIcon( os.path.join(asset_folder, 'reddit_icon.png')) else: self.download_icon = QIcon( os.path.join("assets", 'download_icon.png')) self.stop_icon = QIcon(os.path.join("assets", 'stop_icon.png')) self.reddit_icon = QIcon(os.path.join("assets", 'reddit_icon.png')) def initialize_ui(self): """sets up the user interface, connects all the signals and shows the window. """ self.init_components() self.read_settings_config() self.connect_signals() self.setGeometry(100, 100, 1000, 800) self.setWindowTitle('Reddit Image Scraper') self.setWindowIcon(self.reddit_icon) self.show() def init_components(self): """initializes all components and sets up the layout""" internalWidgetInput = QWidget() internalWidgetTree = QWidget() ############ define components #################### self.subredditInput = QComboBox() self.subredditInput.setEditable(True) self.subredditInput.addItems(self.get_downloaded_subreddits()) self.numInput = QLineEdit() self.onlyInt = QIntValidator() self.numInput.setValidator(self.onlyInt) subredditLabel = QLabel('subreddit') numLabel = QLabel('number of images') self.dirLabel = QLabel('choose a directory') scale_label = QLabel("Scale images?") self.imgView = QLabel() self.outputText = QTextEdit('') self.outputText.setReadOnly(True) self.scale_cb = QCheckBox() self.sortingCb = QComboBox() self.sortingCb.addItems([ "Hot", "Top all time", "Top this month", "Top past year", "New", "Controversial" ]) sortingLabel = QLabel('sorting method') self.runButton = QPushButton('Download') self.runButton.setIcon(self.download_icon) self.chooseDirButton = QPushButton('Save dir') self.stopButton = QPushButton('Stop') self.stopButton.setIcon(self.stop_icon) self.fileModel = QFileSystemModel() self.tree = QTreeView() self.tree.setModel(self.fileModel) self.tree.setColumnHidden(1, True) self.tree.setColumnHidden(2, True) self.tree.setColumnHidden(3, True) ############## Menu stuff ################### menu_bar = QMenuBar() file_menu = menu_bar.addMenu('File') help_menu = menu_bar.addMenu('Help') self.exit_action = QAction('Exit', self) file_menu.addAction(self.exit_action) self.help_action = QAction('Help', self) help_menu.addAction(self.help_action) menu_bar.setFixedHeight(30) ############# Setup the grid layout############################### grid = QGridLayout() # grid.addWidget(menu_bar, 1, 0, 1, 4) grid.setSpacing(4) grid.addWidget(subredditLabel, 1, 0) grid.addWidget(self.subredditInput, 1, 1) grid.addWidget(numLabel, 2, 0) grid.addWidget(self.numInput, 2, 1) grid.addWidget(sortingLabel, 3, 0) grid.addWidget(self.sortingCb, 3, 1) grid.addWidget(self.chooseDirButton, 4, 0) grid.addWidget(self.dirLabel, 4, 1) grid.addWidget(self.stopButton, 5, 0) grid.addWidget(self.runButton, 5, 1) grid.addWidget(self.outputText, 7, 0, 7, 2) # grid.addWidget(self.tree,1,2, 11,7) grid.addWidget(scale_label, 6, 0) grid.addWidget(self.scale_cb, 6, 1) hboxTree = QVBoxLayout() hboxTree.addWidget(self.tree) #the image viewer, setting how it behaves under resizing. self.imgView.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.imgView.setMaximumHeight(MAX_IMAGE_HEIGHT) self.imgView.setAlignment(Qt.AlignmentFlag.AlignCenter) img_scroll_area = QScrollArea() img_scroll_area.setMinimumHeight(MAX_IMAGE_HEIGHT) img_scroll_area.setMinimumWidth(MAX_IMAGE_HEIGHT) img_scroll_area.setWidget(self.imgView) internalWidgetInput.setLayout(grid) # internalWidgetInput.setMinimumWidth(300) internalWidgetInput.setFixedWidth(300) internalWidgetInput.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) internalWidgetTree.setLayout(hboxTree) internalWidgetTree.setFixedWidth(360) internalWidgetTree.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) #construct layout of main window. hbox = QHBoxLayout() hbox.setSpacing(0) hbox.setContentsMargins(0, 0, 0, 0) hbox.setMenuBar(menu_bar) hbox.addWidget(internalWidgetInput) hbox.addWidget(internalWidgetTree) hbox.addWidget(img_scroll_area) self.setLayout(hbox) def connect_signals(self): """connects all the signals to the right functions""" self.chooseDirButton.clicked.connect(self.show_dir_dialog) self.runButton.clicked.connect(self.run_download_threaded) self.tree.clicked.connect(self.on_treeView_clicked) self.stopButton.clicked.connect(self.stop_download) self.scale_cb.clicked.connect(self.refresh_image) self.exit_action.triggered.connect(exit) #self.edit_login_action.triggered.connect(self.edit_login_info) self.help_action.triggered.connect(self.show_help) self.tree.selectionModel().selectionChanged.connect( self.on_selection_change) def read_user_config(self): """reads in the users username and password from the config file, or if there is no config file, shows an input dialog. Also tests so that only valid login information gets saved to the config file. """ config = configparser.ConfigParser() self.redditScraper = redditScraper() if os.path.exists('redditScraper.ini'): config.read('redditScraper.ini') if 'DIR' in config: self.folder = config['DIR']['root_folder'] else: config['REDDIT'] = { 'subreddit': "wallpapers", 'num': 10, 'sorting': "Hot", 'downloaded_subreddits': "" } with open('redditScraper.ini', 'w') as configfile: config.write(configfile) self.config = config def read_settings_config(self): """reads the saved settings from the config file, if they're there.""" if 'DIR' in self.config: self.folder = self.config['DIR']['root_folder'] idx = self.fileModel.setRootPath(str(self.folder)) self.tree.setRootIndex(idx) self.dirLabel.setText(self.folder) if 'REDDIT' in self.config: self.subredditInput.setCurrentText( self.config['REDDIT']['subreddit']) self.numInput.setText(self.config['REDDIT']['num']) self.sortingCb.setCurrentText(self.config['REDDIT']['sorting']) ################### Actions: ################## @pyqtSlot(QModelIndex) def on_treeView_clicked(self, index): """triggers when the user clicks on a file item shown in the treeView, and shows that file in the picture viewer.""" index = self.fileModel.index(index.row(), 0, index.parent()) self.show_image(index) def on_selection_change(self, selected: QItemSelection, deselected: QItemSelection): """ Triggers when the selected item in the treeview changes, and updates the shown picture. """ self.refresh_image() def refresh_image(self): selected_image_index = self.tree.selectedIndexes()[0] self.show_image(selected_image_index) def show_image(self, index: QModelIndex): filePath = self.fileModel.filePath(index) if os.path.isfile(filePath) and filePath.split(".")[-1] in [ "jpg", "gif", "png", "jpeg" ]: pixmap = QPixmap(filePath) if self.scale_cb.isChecked(): self.imgView.setFixedHeight(MAX_IMAGE_HEIGHT) scaled_img = pixmap.scaledToHeight(MAX_IMAGE_HEIGHT) self.imgView.setFixedWidth(scaled_img.width()) self.imgView.setPixmap(scaled_img) else: self.imgView.setFixedHeight(pixmap.height()) self.imgView.setFixedWidth(pixmap.width()) self.imgView.setPixmap(pixmap) def show_dir_dialog(self): """lets the user select the root folder, and saves the choice to the config file.""" self.folder = QFileDialog.getExistingDirectory( self, 'Choose base directory', '/home') self.dirLabel.setText(self.folder) self.config['DIR'] = {'root_folder': self.folder} with open('redditScraper.ini', 'w') as configfile: self.config.write(configfile) idx = self.fileModel.setRootPath(self.folder) self.tree.setRootIndex(idx) return self.folder @pyqtSlot(str) def update_output_text(self, message: str): """updates the output text area, to show progress on downloads.""" self.outputText.setText(message + self.outputText.toPlainText()) def save_subreddit(self, subreddit: str, num: int, sorting: str): """helper function to save the current settings to the config file.""" downloaded_subreddits = self.get_downloaded_subreddits() if subreddit not in downloaded_subreddits: downloaded_subreddits.append(subreddit) self.subredditInput.addItem(subreddit) downloaded_subreddits = ','.join(downloaded_subreddits) self.config['REDDIT'] = { 'subreddit': subreddit, 'num': str(num), 'sorting': sorting, 'downloaded_subreddits': downloaded_subreddits } with open('redditScraper.ini', 'w') as configfile: self.config.write(configfile) def get_downloaded_subreddits(self) -> list: if 'downloaded_subreddits' in self.config['REDDIT'].keys(): subreddits = self.config['REDDIT']['downloaded_subreddits'].split( ',') return subreddits return [] def run_download_threaded(self): """downloads the pictures. Runs in a QThread, so that the program does not freeze. Also checks whether the specified subreddit exists. """ subreddit = self.subredditInput.currentText() num = int(self.numInput.text()) sorting = self.sortingCb.currentText() if self.redditScraper.sub_exists(subreddit): if not hasattr(self, "folder"): msgBox = QMessageBox() msgBox.setText('You need to set a download folder!') msgBox.setWindowTitle("Pick a download folder") msgBox.exec_() return self.save_subreddit(subreddit, num, sorting) self.get_thread = RedditDownloadThread( self.redditScraper, subreddit, num, num * LOOKUP_LIMIT_MULTIPLIER, sorting, self.folder) self.get_thread.changeText.connect(self.update_output_text) self.get_thread.start() else: msgBox = QMessageBox() msgBox.setText('That subreddit does not exist, please try again') msgBox.setWindowTitle("Invalid subreddit") msgBox.exec_() pass def stop_download(self): """Stops the download thread and prints a message to the output.""" try: if self.get_thread.isRunning(): self.get_thread.terminate() self.outputText.setText(self.outputText.toPlainText() + ' Aborted!\n') except Exception: pass ############### Menu actions: ############### def show_help(self): msgBox = QMessageBox() msgBox.setWindowIcon(self.reddit_icon) msgBox.setText( 'This program downloads images posted to reddit.com, or more specifically, to subreddits' + '(i.e. sub-forums). To use it, one needs a valid reddit account, which one can sign up for ' + 'on reddit.com. One also needs to know some names of subreddits. \n Some suggestions for subreddits: ' + 'wallpapers , earthporn , nature , and pics . \n \n ' + "This program will download up to the specified number of images. It can handle png, jpg and gif," + "so links to gyf-files, videos or anything else will be ignored. Therefore the number of images actually " + "downloaded will usually be less than the specified number. So if you want a lot of images, just put" + " a large limit.\n The images will be placed in a subfolder of the chosen base folder, named after the subreddit. This folder will be created if it does not exist.\n \n " + "To view the images, click on them in the tree-view, and they will appear on the right" ) msgBox.setWindowTitle("Help") msgBox.exec_()
class TapeWidget(QWidget): def __init__(self, parent = None): super().__init__(parent) self._main_layout = QVBoxLayout(self) self._search_box = QLineEdit(self) self._button_layout = QHBoxLayout() self._view = QTreeView() self._view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self._view.setSelectionMode(QAbstractItemView.ExtendedSelection) self._view.setHeaderHidden(True) self._add_note_button = QPushButton(self) self._add_note_button.setText("New note") self._add_child_button = QPushButton(self) self._add_child_button.setText("New child") self._add_sibling_button = QPushButton(self) self._add_sibling_button.setText("New sibling") self._delete_note_button = QPushButton(self) self._delete_note_button.setText("Delete note") self._button_layout.addWidget(self._add_note_button) self._button_layout.addWidget(self._add_sibling_button) self._button_layout.addWidget(self._add_child_button) self._button_layout.addWidget(self._delete_note_button) self._button_layout.addStretch() self._main_layout.addWidget(self._search_box) self._main_layout.addLayout(self._button_layout) self._main_layout.addWidget(self._view) self._tape_filter_proxy_model = TapeFilterProxyModel() self._note_delegate = NoteDelegate() self._tape_model = QStandardItemModel() self.set_model(self._tape_model) self._view.setItemDelegate(self._note_delegate) self._view.setModel(self._tape_filter_proxy_model) self._add_note_button.clicked.connect(lambda checked: self.add_and_focus_note()) self._add_sibling_button.clicked.connect(self._new_sibling_handler) self._add_child_button.clicked.connect(self._new_child_handler) self._delete_note_button.clicked.connect(self.delete_selected_notes) self._search_box.textChanged.connect(self._tape_filter_proxy_model.setFilterFixedString) def model(self): """ Returns the model that contains all notes managed by the tape. The model should be treated as read-only. You can only modify it indirectly through the methods provided by TapeWidget. """ return self._tape_model def proxy_model(self): """ Returns the model that contains notes matching current filter. The model should be treated as read-only. You can only modify it indirectly through the methods provided by TapeWidget. """ return self._tape_filter_proxy_model def set_model(self, model): assert ( len(set([item_to_id(item) for item in all_items(model) if item_to_id(item) != None])) == len( [item_to_id(item) for item in all_items(model) if item_to_id(item) != None]) ) # NOTE: If there's an exception in setSourceModel(), we can hope that the source model # remains unchanged. That's why we assing to _tape_model only if that instruction succeeds. self._tape_filter_proxy_model.setSourceModel(model) self._tape_model = model def notes(self): return all_notes(self._tape_model) def assign_ids(self): assign_note_ids(self._tape_model) def create_empty_note(self): return Note( body = "", tags = [], created_at = datetime.utcnow() ) def add_note(self, note = None, parent_index = None): # NOTE: Remember to use indexes from _tape_model, not _tape_filter_proxy_model here assert parent_index == None or self._tape_model.itemFromIndex(parent_index) != None and parent_index.isValid() root_item = self._tape_model.invisibleRootItem() if parent_index == None: parent_item = root_item else: parent_item = self._tape_model.itemFromIndex(parent_index) if note != None: assert note not in self.notes() else: note = self.create_empty_note() item = QStandardItem() set_item_note(item, note) parent_item.appendRow(item) def add_and_focus_note(self, parent_proxy_index = None): if parent_proxy_index != None: parent_index = self._tape_filter_proxy_model.mapToSource(parent_proxy_index) else: parent_index = None self.add_note(parent_index = parent_index) if parent_proxy_index != None: self._view.expand(parent_proxy_index) parent_item = self._tape_model.itemFromIndex(parent_index) else: parent_item = self._tape_model.invisibleRootItem() # NOTE: It's likely that the new note does not match the filter and won't not be present # in the proxy model. We want to select it and focus on it so the filter must be cleared. # And it must be cleared before taking the index in the proxy because changing the filter # may change the set of notes present in the proxy and invalidate the index. self.set_filter('') new_note_index = parent_item.child(parent_item.rowCount() - 1).index() new_note_proxy_index = self._tape_filter_proxy_model.mapFromSource(new_note_index) self.clear_selection() self.set_note_selection(new_note_proxy_index, True) self._view.scrollTo(new_note_proxy_index) def remove_notes(self, indexes): remove_items(self._tape_model, indexes) def clear(self): self._tape_model.clear() def set_filter(self, text): # NOTE: This triggers textChanged() signal which applies the filter self._search_box.setText(text) def get_filter(self): return self._search_box.text() def selected_proxy_indexes(self): return self._view.selectedIndexes() def selected_indexes(self): return [self._tape_filter_proxy_model.mapToSource(proxy_index) for proxy_index in self.selected_proxy_indexes()] def set_note_selection(self, proxy_index, select): assert proxy_index != None and proxy_index.isValid() assert self._tape_model.itemFromIndex(self._tape_filter_proxy_model.mapToSource(proxy_index)) != None self._view.selectionModel().select( QItemSelection(proxy_index, proxy_index), QItemSelectionModel.Select if select else QItemSelectionModel.Deselect ) def clear_selection(self): self._view.selectionModel().clear() def delete_selected_notes(self): self.remove_notes(self.selected_indexes()) def _new_sibling_handler(self): selected_proxy_indexes = self._view.selectedIndexes() if len(selected_proxy_indexes) > 1: self.clear_selection() selected_proxy_indexes = [] if len(selected_proxy_indexes) == 0 or selected_proxy_indexes[0].parent() == QModelIndex(): self.add_and_focus_note() else: self.add_and_focus_note(selected_proxy_indexes[0].parent()) def add_child_to_selected_element(self): selected_proxy_indexes = self._view.selectedIndexes() if len(selected_proxy_indexes) != 1: return False else: self.add_and_focus_note(selected_proxy_indexes[0]) return True def _new_child_handler(self): added = self.add_child_to_selected_element() if not added: QMessageBox.warning(self, "Can't add note", "To be able to add a new child note select exactly one parent")
class HttpMessagesTreeView(QWidget): selected = pyqtSignal(object) class ModelItem: def __init__(self, model, branch): self.branch = branch self.model = model def __init__(self, plugin_registry, parent=None): super().__init__(parent) self.plugin_registry = plugin_registry self.tree_view = QTreeView() self.label = QLabel() self.clear() self.column_definitions = self.plugin_registry.get_columns() layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.tree_view) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) def getAllMessagePairs(self): return (item.model for item in self.__index.values()) def clear(self): self.model = QStandardItemModel() self.filteredModel = FilteredModel(self.plugin_registry) self.filteredModel.setSourceModel(self.model) self.rootNode = self.model.invisibleRootItem() self.__index = OrderedDict() self.tree_view.setModel(self.filteredModel) self.label.setText(self.__getLabelText()) self.tree_view.selectionModel().selectionChanged.connect( self.onSelectionChanged) def refresh(self): self.filteredModel.invalidateFilter() self.label.setText(self.__getLabelText()) def applyModel(self): self.tree_view.setModel(self.filteredModel) for i, column in enumerate(self.column_definitions): self.model.setHeaderData(i, Qt.Horizontal, column[1]) column_count = self.model.columnCount() column_width = self.tree_view.width() / column_count for col in range(0, column_count): self.tree_view.setColumnWidth(col, column_width) self.label.setText(self.__getLabelText()) def onSelectionChanged(self, selection: QItemSelection): if selection.isEmpty(): return item = selection.indexes()[0] data = item.data(ROLE_HTTP_MESSAGE) if data: self.selected.emit(data) def onRequestResponse(self, request_response): new_row = request_response.guid not in self.__index if not new_row: model_item = self.__index[request_response.guid] branch = model_item.branch model_item.model = request_response else: branch = [QStandardItem() for x in self.column_definitions] self.__index[ request_response.guid] = HttpMessagesTreeView.ModelItem( request_response, branch) branch[0].setData(request_response, ROLE_HTTP_MESSAGE) for i, column in enumerate(self.column_definitions): text = self.plugin_registry.get_cell_content( request_response, column[0]) if text: branch[i].setText(text) if new_row: self.rootNode.appendRow(branch) self.applyModel() def __getLabelText(self): return "Displaying <b>{}</b> out of <b>{}</b>.".format( self.filteredModel.rowCount(), self.model.rowCount())
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 Explorer(QDialog): def __init__(self, parent, window_title=_("Select resources"), subtitle=_("Select files and/or folders to include")): super().__init__(parent) self.logger = logging.getLogger(__name__) self.setModal(True) self.setSizeGripEnabled(True) self.setWindowTitle(window_title) self.subtitle = subtitle self.file_set = set() self.config = Configuration() self.__init_ui__() self.filename_filter = FilenameFilter() #self.show() def __init_ui__(self): # layout vert = QVBoxLayout(self) vert.setContentsMargins(0, 0, 0, 0) resource_dir = self.config.cfg_resource_dir() p_top = QVBoxLayout() lb_subtitle = QLabel(self.subtitle) lb_subtitle.setContentsMargins(10, 10, 10, 0) p_top.addWidget(lb_subtitle) self.model = QFileSystemModel() self.model.setRootPath(resource_dir) self.view = QTreeView() self.view.setModel(self.model) self.view.setRootIndex(self.model.index(self.model.rootPath())) self.view.setAlternatingRowColors(True) self.view.setSelectionMode(QAbstractItemView.MultiSelection) self.view.selectionModel().selectionChanged.connect( self.selection_changed) self.view.collapsed.connect(self.item_collapsed) self.view.expanded.connect(self.item_expanded) p_top.addWidget(self.view) p_info = QHBoxLayout() lb_resource = QLabel(_("resource dir") + ": " + resource_dir) lb_resource.setContentsMargins(10, 0, 10, 0) self.lb_selection_count = QLabel( str(self.selected_file_count()) + " " + _("resources selected")) self.lb_selection_count.setContentsMargins(10, 0, 10, 0) p_info.addWidget(self.lb_selection_count) p_info.addStretch(1) p_info.addWidget(lb_resource) p_top.addLayout(p_info) p_bottom = QHBoxLayout() self.pb_deselect = QPushButton(_("Deselect all")) self.pb_deselect.clicked.connect(self.pb_deselect_clicked) self.pb_deselect.setEnabled(self.selected_file_count() > 0) p_bottom.addWidget(self.pb_deselect) p_bottom.addStretch(1) self.pb_ok = QPushButton(_("OK")) self.pb_ok.setAutoDefault(True) self.pb_ok.clicked.connect(self.accept) p_bottom.addWidget(self.pb_ok) pb_cancel = QPushButton(_("Cancel")) pb_cancel.clicked.connect(self.reject) p_bottom.addWidget(pb_cancel) vert.addLayout(p_top) vert.addLayout(p_bottom) self.setLayout(vert) self.resize(self.config.explorer_width(), self.config.explorer_height()) width = self.view.width() - 50 self.view.setColumnWidth(0, width / 2) self.view.setColumnWidth(1, width / 6) self.view.setColumnWidth(2, width / 6) self.view.setColumnWidth(3, width / 6) def __persist__(self): # persist properties of the explorer self.config.set_explorer_width(self.width()) self.config.set_explorer_height(self.height()) self.config.persist() def __compute_filenames__(self, item_selection): # item_selection: a QItemSelection # return corresponding absolute filenames as a set, including filenames in underlying folders s = set() for index in item_selection.indexes(): # we have an index for each column in the model if index.column() == 0: path = index.model().filePath(index) if os.path.isdir(path): for root, directories, filenames in os.walk(path): for filename in filenames: if self.filename_filter.accept(filename): s.add(os.path.join(root, filename)) elif os.path.isfile(path): s.add(path) else: self.logger.warn("isUnknownThing", path) return s def showEvent(self, QShowEvent): #self.pb_ok.setFocus() pass def set_filename_filter(self, filename_filter): # set the FilenameFilter self.filename_filter = filename_filter def selected_file_count(self): return len(self.file_set) def selected_file_set(self): return frozenset(self.file_set) def selection_changed(self, selected, deselected): # selected, deselected: PyQt5.QtCore.QItemSelection selected_filenames = self.__compute_filenames__(selected) self.file_set.update(selected_filenames) deselected_filenames = self.__compute_filenames__(deselected) self.file_set.difference_update(deselected_filenames) self.pb_deselect.setEnabled(self.selected_file_count() > 0) self.lb_selection_count.setText( str(self.selected_file_count()) + " " + _("resources selected")) def item_expanded(self, index): # index: a QModelIndex # show all child items selected/deselected in accordance with state of parent folder pass def item_collapsed(self, index): pass def pb_deselect_clicked(self): self.view.selectionModel().clear() def hideEvent(self, QHideEvent): self.__persist__()
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 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)