class RightPanel(FuturaFrame): side = "right" def __init__(self, *args): super(RightPanel, self).__init__(*args) self.connect_signals() self.label.setText('Recipe') self.tree_view = QTreeView() self.model = RecipeModel() self.tree_view.setModel(self.model) self.tree_view.setWordWrap(True) self.tree_view.setColumnWidth(0, 50) self.widget_layout.addWidget(self.tree_view) signals.update_recipe.emit() def connect_signals(self): signals.update_recipe.connect(self.update_recipe) def update_recipe(self): self.model.parse_recipe(findMainWindow().loader.recipe) if self.model.rowCount() == 0: self.tree_view.header().hide() else: self.tree_view.header().show()
class VfsDirWidget(QWidget): def __init__(self, vfs_view, *args, **kwargs): QWidget.__init__(self, *args, **kwargs) self.vnode_2click_selected = None # Getting the Model self.source_model = VfsDirModel() # Creating a QTableView self.view = QTreeView() self.view.doubleClicked.connect(self.double_clicked) self.view.clicked.connect(self.clicked) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.ExtendedSelection) font = self.view.font() font.setPointSize(8) self.view.setFont(font) self.view.setModel(self.source_model) # # QTableView Headers self.header = self.view.header() self.header.setSectionResizeMode(QHeaderView.ResizeToContents) self.header.setStretchLastSection(True) size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size.setHorizontalStretch(1) self.view.setSizePolicy(size) self.main_layout = QHBoxLayout() self.main_layout.addWidget(self.view) self.setLayout(self.main_layout) self.source_model.vfs_view_set(vfs_view) def vfs_view_get(self): return self.source_model.vfs_view_get() def clicked(self, index): if index.isValid(): if self.source_model.vfs_view is not None: items = self.view.selectedIndexes() items = list(set([idx.internalPointer() for idx in items])) items = [ idx.v_path() for idx in items if isinstance(idx, VfsDirLeaf) or isinstance(idx, VfsDirBranch) ] self.source_model.vfs_view.paths_set(items) def double_clicked(self, index): if index.isValid(): tnode = index.internalPointer() if isinstance( tnode, VfsDirLeaf) and self.vnode_2click_selected is not None: self.vnode_2click_selected(tnode.uids_hard)
class Snippets(QDialog): def __init__(self, parent=None): super(Snippets, self).__init__(parent) # Create widgets self.setWindowModality(Qt.NonModal) self.title = QLabel(self.tr("Snippet Editor")) self.saveButton = QPushButton(self.tr("Save")) self.revertButton = QPushButton(self.tr("Revert")) self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) self.setWindowTitle(self.title.text()) self.newFolderButton = QPushButton("New Folder") self.deleteSnippetButton = QPushButton("Delete") self.newSnippetButton = QPushButton("New Snippet") self.edit = QPlainTextEdit() self.resetting = False self.columns = 3 self.keySequenceEdit = QKeySequenceEdit(self) self.currentHotkey = QKeySequence() self.currentHotkeyLabel = QLabel("") self.currentFileLabel = QLabel() self.currentFile = "" self.snippetDescription = QLineEdit() self.snippetEditsPending = False self.clearSelection() #Set Editbox Size font = getMonospaceFont(self) self.edit.setFont(font) font = QFontMetrics(font) self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API #Files self.files = QFileSystemModel() self.files.setRootPath(snippetPath) self.files.setNameFilters(["*.py"]) #Tree self.tree = QTreeView() self.tree.setModel(self.files) self.tree.setSortingEnabled(True) self.tree.hideColumn(2) self.tree.sortByColumn(0, Qt.AscendingOrder) self.tree.setRootIndex(self.files.index(snippetPath)) for x in range(self.columns): #self.tree.resizeColumnToContents(x) self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) treeLayout = QVBoxLayout() treeLayout.addWidget(self.tree) treeButtons = QHBoxLayout() treeButtons.addWidget(self.newFolderButton) treeButtons.addWidget(self.newSnippetButton) treeButtons.addWidget(self.deleteSnippetButton) treeLayout.addLayout(treeButtons) treeWidget = QWidget() treeWidget.setLayout(treeLayout) # Create layout and add widgets buttons = QHBoxLayout() buttons.addWidget(self.clearHotkeyButton) buttons.addWidget(self.keySequenceEdit) buttons.addWidget(self.currentHotkeyLabel) buttons.addWidget(self.revertButton) buttons.addWidget(self.saveButton) description = QHBoxLayout() description.addWidget(QLabel(self.tr("Description: "))) description.addWidget(self.snippetDescription) vlayoutWidget = QWidget() vlayout = QVBoxLayout() vlayout.addWidget(self.currentFileLabel) vlayout.addWidget(self.edit) vlayout.addLayout(description) vlayout.addLayout(buttons) vlayoutWidget.setLayout(vlayout) hsplitter = QSplitter() hsplitter.addWidget(treeWidget) hsplitter.addWidget(vlayoutWidget) hlayout = QHBoxLayout() hlayout.addWidget(hsplitter) self.showNormal() #Fixes bug that maximized windows are "stuck" self.settings = QSettings("Vector 35", "Snippet Editor") if self.settings.contains("ui/snippeteditor/geometry"): self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry")) else: self.edit.setMinimumWidth(80 * font.averageCharWidth()) self.edit.setMinimumHeight(30 * font.lineSpacing()) # Set dialog layout self.setLayout(hlayout) # Add signals self.saveButton.clicked.connect(self.save) self.revertButton.clicked.connect(self.loadSnippet) self.clearHotkeyButton.clicked.connect(self.clearHotkey) self.tree.selectionModel().selectionChanged.connect(self.selectFile) self.newSnippetButton.clicked.connect(self.newFileDialog) self.deleteSnippetButton.clicked.connect(self.deleteSnippet) self.newFolderButton.clicked.connect(self.newFolder) def registerAllSnippets(self): for action in list(filter(lambda x: x.startswith("Snippet\\"), UIAction.getAllRegisteredActions())): UIActionHandler.globalActions().unbindAction(action) UIAction.unregisterAction(action) for snippet in includeWalk(snippetPath, ".py"): (snippetDescription, snippetKey, snippetCode) = loadSnippetFromFile(snippet) if not snippetDescription: actionText = "Snippet\\" + snippet else: actionText = "Snippet\\" + snippetDescription UIAction.registerAction(actionText, snippetKey) UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode))) def clearSelection(self): self.keySequenceEdit.clear() self.currentHotkey = QKeySequence() self.currentHotkeyLabel.setText("") self.currentFileLabel.setText("") self.snippetDescription.setText("") self.edit.setPlainText("") def reject(self): self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry()) if self.snippetChanged(): question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?")) if question != QMessageBox.StandardButton.Yes: return self.accept() def newFolder(self): (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: ")) if ok and folderName: index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): QDir(selection).mkdir(folderName) else: QDir(snippetPath).mkdir(folderName) def selectFile(self, new, old): if (self.resetting): self.resetting = False return newSelection = self.files.filePath(new.indexes()[0]) if QFileInfo(newSelection).isDir(): self.clearSelection() return if old.length() > 0: oldSelection = self.files.filePath(old.indexes()[0]) if not QFileInfo(oldSelection).isDir() and self.snippetChanged(): question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?")) if question != QMessageBox.StandardButton.Yes: self.resetting = True self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) return False self.currentFile = newSelection self.loadSnippet() def loadSnippet(self): self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName()) log_debug("Loading %s as a snippet." % self.currentFile) (snippetDescription, snippetKey, snippetCode) = loadSnippetFromFile(self.currentFile) self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("") self.keySequenceEdit.setKeySequence(snippetKey[0]) if len(snippetKey) != 0 else self.keySequenceEdit.setKeySequence(QKeySequence("")) self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("") def newFileDialog(self): (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: ")) if ok and snippetName: if not snippetName.endswith(".py"): snippetName += ".py" index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): open(os.path.join(selection, snippetName), "w").close() else: open(os.path.join(snippetPath, snippetName), "w").close() log_debug("Snippet %s created." % snippetName) def deleteSnippet(self): selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row snippetName = self.files.fileName(selection) question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName) if (question == QMessageBox.StandardButton.Yes): log_debug("Deleting snippet %s." % snippetName) self.clearSelection() self.files.remove(selection) def snippetChanged(self): if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()): return False (snippetDescription, snippetKey, snippetCode) = loadSnippetFromFile(self.currentFile) if (not snippetCode): return False if len(snippetKey) == 0 and not self.keySequenceEdit.keySequence().isEmpty(): return True if len(snippetKey) != 0 and snippetKey[0] != self.keySequenceEdit.keySequence(): return True return self.edit.toPlainText() != snippetCode or \ self.snippetDescription.text() != snippetDescription def save(self): log_debug("Saving snippet %s" % self.currentFile) outputSnippet = open(self.currentFile, "w") outputSnippet.write("#" + self.snippetDescription.text() + "\n") outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") outputSnippet.write(self.edit.toPlainText()) outputSnippet.close() self.registerAllSnippets() def clearHotkey(self): self.keySequenceEdit.clear()
class NavigationWidget(QWidget): def __init__(self, index_filename): QWidget.__init__(self) self.data_index = pd.DataFrame() self.data_info = aecg.tools.indexer.StudyInfo() self.data_index_stats = aecg.tools.indexer.StudyStats( self.data_info, self.data_index) # Getting the Models self.project_loaded = '' self.projectmodel = ProjectTreeModel() # Creating a QTreeView for displaying the selected project index self.project_treeview = QTreeView() self.project_treeview.setModel(self.projectmodel) self.phorizontal_header = self.project_treeview.header() self.phorizontal_header.setSectionResizeMode( QHeaderView.ResizeToContents) self.phorizontal_header.setStretchLastSection(True) self.sp_right = QSplitter(Qt.Orientation.Horizontal) self.sp_left = QSplitter(Qt.Orientation.Vertical, self.sp_right) # NavigationWidget Layout self.main_layout = QVBoxLayout() size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) # Left side # Left side - Top layout # Left side - Bottom Layout size.setVerticalStretch(4) self.project_treeview.setSizePolicy(size) self.sp_left.addWidget(self.project_treeview) # Right side size = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) size.setHorizontalStretch(2) size.setHeightForWidth(False) self.tabdisplays = TabDisplays(self) self.sp_right.addWidget(self.tabdisplays) self.tabdisplays.validator_data_ready.connect( self.load_projectindex_after_validation) self.sp_right.setSizePolicy(size) # Set the layout to the QWidget self.main_layout.addWidget(self.sp_right) self.setLayout(self.main_layout) self.tabdisplays.setCurrentWidget(self.tabdisplays.validator) # Load study index if index_filename and (index_filename != ""): if os.path.exists(index_filename): self.load_projectindex(os.path.normpath(index_filename)) else: QMessageBox.warning(self, f"Study index file not found", f"{index_filename} not found") projectindex_loaded = Signal() projectindexstats_loaded = Signal() def load_projectindex_after_validation(self): self.load_projectindex( os.path.normpath(self.tabdisplays.study_info_file.text())) def load_projectindex(self, project_idx_file): index_loaded = False stats_loaded = False if project_idx_file != "": if os.path.exists(project_idx_file): try: self.parent().status.showMessage( f"Loading {project_idx_file}") progress = QProgressDialog("Loading index file...", "Cancel", 0, 3, self) progress.setWindowTitle("Loading study index") progress.setWindowModality(Qt.WindowModal) progress.setMinimumWidth(300) progress.setMinimumDuration(0) progress.forceShow() progress.setValue(0) wb = load_workbook(project_idx_file, read_only=True) progress.setValue(1) ws = wb['Index'] ws.reset_dimensions() data = ws.values cols = next(data) data = list(data) progress.setValue(2) self.data_index = pd.DataFrame(data, columns=cols).fillna("") progress.setValue(3) progress.close() # Parse index to the tree num_ecgs = 0 if "EGREFID" in self.data_index.columns: num_ecgs = self.data_index[[ "ZIPFILE", "AECGXML", "EGREFID", "WFTYPE" ]].drop_duplicates().shape[0] progress = QProgressDialog("Parsing index ...", "Cancel", 0, num_ecgs, self) progress.setWindowTitle("Loading study index") progress.setLabelText("Parsing index...") progress.setWindowModality(Qt.WindowModal) progress.setMinimumWidth(300) progress.setMinimumDuration(0) progress.forceShow() progress.setValue(0) self.projectmodel = ProjectTreeModel( self.data_index, progress_dialog=progress) self.project_treeview.setModel(self.projectmodel) self.project_treeview.selectionModel().\ selectionChanged.connect(self.load_data) progress.close() else: QMessageBox.warning( self, "EGREFID missing", f"EGREFID column missing Index sheet of " f"{project_idx_file}") self.project_loaded = project_idx_file # Reset aECG display self.tabdisplays.aecg_display.aecg_data = None self.tabdisplays.aecg_display.plot_aecg() # Populate study information/validator tab self.tabdisplays.load_study_info(project_idx_file) self.data_index_info = self.tabdisplays.studyindex_info index_loaded = True try: progress = QProgressDialog( "Loading study index stats...", "Cancel", 0, 3, self) progress.setWindowTitle("Loading study index stats") progress.setWindowModality(Qt.WindowModal) progress.setMinimumWidth(300) progress.setMinimumDuration(0) progress.forceShow() progress.setValue(0) ws = wb['Stats'] ws.reset_dimensions() data = ws.values cols = next(data) data = list(data) progress.setValue(1) progress.forceShow() statsdf = pd.DataFrame(data, columns=cols).fillna("") progress.setValue(2) progress.forceShow() self.data_index_stats = aecg.tools.indexer.StudyStats() self.data_index_stats.__dict__.update( statsdf.set_index("Property").transpose(). reset_index(drop=True).to_dict('index')[0]) progress.setValue(3) progress.forceShow() progress.close() stats_loaded = True except Exception as ex: QMessageBox.warning( self, f"Error loading study index stats", f"Error loading study index stats from" f"{project_idx_file}\n{str(ex)}") except Exception as ex: QMessageBox.warning( self, f"Error loading study index", f"Error loading study index from {project_idx_file}" f"\n{str(ex)}") else: QMessageBox.warning(self, f"Study index file not found", f"{project_idx_file} not found") if index_loaded: self.projectindex_loaded.emit() if stats_loaded: self.projectindexstats_loaded.emit() self.parentWidget().status.clearMessage() def load_data(self, selected, deselected): self.tabdisplays.setCurrentWidget(self.tabdisplays.waveforms) rhythm = self.projectmodel.itemData(selected.indexes()[2])[0] derived = self.projectmodel.itemData(selected.indexes()[3])[0] # Get study directory provided in the GUI studydir = self.tabdisplays.effective_aecgs_dir(self, silent=True) # Calculate effective study dir aecg_xml_file = self.projectmodel.itemData(selected.indexes()[5])[0] if aecg_xml_file != "": zipfile = self.projectmodel.itemData(selected.indexes()[4])[0] if zipfile != "": zipfile = os.path.join(studydir, zipfile) else: aecg_xml_file = os.path.join(studydir, aecg_xml_file) # Load aECG file aecg_data = aecg.io.read_aecg(aecg_xml_file, zipfile, include_digits=True, in_memory_xml=True, log_validation=False) if aecg_data.xmlfound: # Plot aECG self.tabdisplays.aecg_display.set_aecg(aecg_data) self.tabdisplays.aecg_display.plot_aecg( rhythm, derived, ecg_layout=aecg.utils.ECG_plot_layout( self.tabdisplays.cbECGLayout.currentIndex() + 1)) # Populate XML viewer self.tabdisplays.xml_display.setText(aecg_data.xmlstring()) else: QMessageBox.warning(self, "aECG XML file not found", f"aECG XML {aecg_xml_file} not found") self.parentWidget().update_status_bar()
def testHeader(self): tree = QTreeView() tree.setHeader(QHeaderView(Qt.Horizontal)) self.assertIsNotNone(tree.header())
class ProjectWidget(QSplitter): treeCurrentItemChanged = Signal(Container) def __init__(self, parent, f=...): super().__init__(parent, f) self.project = Project() self.current_tree_item: Optional[Container] = None self.main_splitter = self # inheritance from QSplitter may not be preserved self.main_splitter.setOrientation(Qt.Horizontal) self.left_splitter = QSplitter(Qt.Vertical) self.right_splitter = QSplitter(Qt.Vertical) self.main_splitter.addWidget(self.left_splitter) self.main_splitter.addWidget(self.right_splitter) # self.w_parameters = ParametersWidget(self.project.bundle) self.w_parameters_tree = QTreeView() self.w_parameters_tree.setModel(self.project.parameters_model) delegate = ParametersTableDelegate() self.w_parameters_tree.setItemDelegate(delegate) self.selection_model_parameters = self.w_parameters_tree.selectionModel( ) self.selection_model_parameters.currentRowChanged.connect( self._on_tree_row_changed) self.w_curves_tree = QTreeView() self.w_curves_tree.setModel(self.project.curves_model) delegate = CurvesTableDelegate() self.w_curves_tree.setItemDelegate(delegate) self.selection_model_curves = self.w_curves_tree.selectionModel() self.selection_model_curves.currentRowChanged.connect( self._on_tree_row_changed) self.w_light_plot = LightPlotWidget(self.project.curves_model) self.w_rv_plot = RvPlotWidget(self.project.curves_model) self.left_splitter.addWidget(self.w_parameters_tree) self.left_splitter.addWidget(self.w_curves_tree) self.right_splitter.addWidget(self.w_light_plot) self.right_splitter.addWidget(self.w_rv_plot) def resizeEvent(self, arg__1: PySide2.QtGui.QResizeEvent): super().resizeEvent(arg__1) def save_session(self, settings: QSettings): settings.beginGroup('project_splitter') settings.setValue('horiz', self.main_splitter.saveState()) settings.setValue('vert_trees', self.left_splitter.saveState()) settings.setValue('vert_plots', self.right_splitter.saveState()) settings.endGroup() settings.beginGroup('tree_columns') settings.setValue('params', self.w_parameters_tree.header().saveState()) settings.setValue('curves', self.w_curves_tree.header().saveState()) settings.endGroup() def restore_session(self, settings: QSettings): settings.beginGroup('project_splitter') try: self.main_splitter.restoreState(settings.value("horiz")) self.left_splitter.restoreState(settings.value("vert_trees")) self.right_splitter.restoreState(settings.value("vert_plots")) except AttributeError: pass settings.endGroup() settings.beginGroup('tree_columns') try: self.w_parameters_tree.header().restoreState( settings.value("params")) self.w_curves_tree.header().restoreState(settings.value("curves")) except AttributeError: pass settings.endGroup() @Slot(QModelIndex, QModelIndex) def _on_tree_row_changed(self, current: QModelIndex, previous: QModelIndex): model = current.model() prev_item, prev_column = model.item_and_column(previous) curr_item, curr_column = model.item_and_column(current) logger().info(f'Selection changed {prev_item} -> {curr_item}') self.current_tree_item = curr_item self.treeCurrentItemChanged.emit(curr_item) @Slot() def add_lc(self): self.project.add_lc() @Slot() def add_rv(self): self.project.add_rv() @Slot() def del_curve(self): to_del = self.current_tree_item while to_del is not None and not isinstance(to_del, CurveContainer): to_del = to_del.parent() if QMessageBox.question(self, 'Deleting Curve Confirmation', f'Delete the curve "{to_del.objectName()}"?') \ != QMessageBox.Yes: return self.project.delete_curve(to_del)
class MVCConfigurationGUI(MVCConfigurationBase): """ GUI implementation of MVCConfigurationBase """ def __init__(self, configuration): super().__init__(configuration) assertMainThread() srv = Services.getService("MainWindow") srv.aboutToClose.connect(self._aboutToClose) confMenu = srv.menuBar().addMenu("&Configuration") toolBar = srv.getToolBar() configuration.configNameChanged.connect(self._configNameChanged) configuration.dirtyChanged.connect(self._dirtyChanged) style = QApplication.style() self.actLoad = QAction( QIcon.fromTheme("document-open", style.standardIcon(QStyle.SP_DialogOpenButton)), "Open config", self) self.actLoad.triggered.connect(self._execLoad) self.actSave = QAction( QIcon.fromTheme("document-save", style.standardIcon(QStyle.SP_DialogSaveButton)), "Save config", self) self.actSave.triggered.connect(self._execSaveConfig) self.actSaveWithGuiState = QAction( QIcon.fromTheme("document-save", style.standardIcon(QStyle.SP_DialogSaveButton)), "Save config sync gui state", self) self.actSaveWithGuiState.triggered.connect( self._execSaveConfigWithGuiState) self.actNew = QAction( QIcon.fromTheme("document-new", style.standardIcon(QStyle.SP_FileIcon)), "New config", self) self.actNew.triggered.connect(self._execNew) self.actActivate = QAction( QIcon.fromTheme("arrow-up", style.standardIcon(QStyle.SP_ArrowUp)), "Initialize", self) self.actActivate.triggered.connect(self.activate) self.actDeactivate = QAction( QIcon.fromTheme("arrow-down", style.standardIcon(QStyle.SP_ArrowDown)), "Deinitialize", self) self.actDeactivate.triggered.connect(self.deactivate) confMenu.addAction(self.actLoad) confMenu.addAction(self.actSave) confMenu.addAction(self.actSaveWithGuiState) confMenu.addAction(self.actNew) confMenu.addAction(self.actActivate) confMenu.addAction(self.actDeactivate) toolBar.addAction(self.actLoad) toolBar.addAction(self.actSave) toolBar.addAction(self.actNew) toolBar.addAction(self.actActivate) toolBar.addAction(self.actDeactivate) self.recentConfigs = [QAction() for i in range(10)] self.recentConfigs[0].setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) confMenu.addSeparator() recentMenu = confMenu.addMenu("Recent") for a in self.recentConfigs: a.setVisible(False) a.triggered.connect(self._openRecent) recentMenu.addAction(a) self.mainWidget = srv.newDockWidget("Configuration", None, Qt.LeftDockWidgetArea) self.treeView = QTreeView(self.mainWidget) self.treeView.setHeaderHidden(False) self.treeView.setSelectionMode(QAbstractItemView.NoSelection) self.treeView.setEditTriggers(self.treeView.EditKeyPressed | self.treeView.AnyKeyPressed) self.treeView.setAllColumnsShowFocus(True) self.treeView.setExpandsOnDoubleClick(False) self.treeView.setDragEnabled(True) self.treeView.setDropIndicatorShown(True) self.treeView.setDragDropMode(QAbstractItemView.DragOnly) self.mainWidget.setWidget(self.treeView) self.treeView.setModel(self.model) self.treeView.header().setStretchLastSection(True) self.treeView.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.treeView.doubleClicked.connect(self._onItemDoubleClicked) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.customContextMenuRequested.connect( self._execTreeViewContextMenu) # expand applications by default self.treeView.setExpanded(self.model.index(1, 0), True) self.delegate = PropertyDelegate(self.model, ITEM_ROLE, ConfigurationModel.PropertyContent, self) self.treeView.setItemDelegate(self.delegate) self.restoreState() srv.aboutToClose.connect(self.saveState) # a list of dock widgets displaying subgraphs self._graphViews = [] # make sure that the graph views are closed when the config is closed self._configuration.subConfigRemoved.connect(self._subConfigRemoved) self._waitForActivated = None self._waitForOpenState = None def _execLoad(self): assertMainThread() if self._checkDirty(): return fn, _ = QFileDialog.getOpenFileName(self.mainWidget, "Load configuration", self.cfgfile, filter="*.json") if fn is not None and fn != "": logger.debug("Loading config file %s", fn) try: self.loadConfig(fn) except Exception as e: # pylint: disable=broad-except logger.exception("Error while loading configuration %s: %s", fn, str(e)) QMessageBox.warning(self.mainWidget, "Error while loading configuration", str(e)) def _openRecent(self): """ Called when the user clicks on a recent config. :return: """ if self._checkDirty(): return action = self.sender() fn = action.data() try: self.loadConfig(fn) except Exception as e: # pylint: disable=broad-except # catching general exception is wanted here. logger.exception("Error while loading configuration %s: %s", fn, str(e)) QMessageBox.warning(self.mainWidget, "Error while loading configuration", str(e)) def _checkDirty(self): if self._configuration.dirty(): ans = QMessageBox.question( None, "Save changes?", "There are unsaved changes. Do you want to save them?", buttons=QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, defaultButton=QMessageBox.Save) if ans == QMessageBox.Save: self.saveConfig() return False if ans == QMessageBox.Cancel: return True return False def _aboutToClose(self, mainWindow): if self._checkDirty(): mainWindow.ignoreCloseEvent() def _execNew(self): assertMainThread() if self._checkDirty(): return fn, _ = QFileDialog.getSaveFileName(self.mainWidget, "New configuration", ".", filter="*.json") if fn is not None and fn != "": logger.debug("Creating config file %s", fn) self.newConfig(fn) def _execSaveConfig(self): if self.configuration().filename() is None: self._execSaveConfigAs() else: self.saveConfig() def _execSaveConfigWithGuiState(self): if self.configuration().filename() is None: self._execSaveConfigAs() else: self.saveConfigWithGuiState() def _execSaveConfigAs(self): """ Opens a file dialog to get the save file name and calls saveConfig. :return: """ assertMainThread() fn, _ = QFileDialog.getSaveFileName(self.mainWidget, "Save configuration as", ".", "*.json") if fn is not None and fn != "": self.saveConfigAs(fn) def _addGraphView(self, subConfig): g = subConfig.getGraph() # remove already deleted graph views from internal list valid_graphViews = [] for gv in self._graphViews: if shiboken2.isValid(gv): # pylint: disable=no-member valid_graphViews.append(gv) self._graphViews = valid_graphViews # check if graph view is already there for gv in self._graphViews: if gv.widget().scene().graph == g: logger.info("Graph view already exists.") return # create new graph view srv = Services.getService("MainWindow") graphDw = srv.newDockWidget("Graph (%s)" % (subConfig.getName()), parent=None, defaultArea=Qt.RightDockWidgetArea, allowedArea=Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea) graphDw.setAttribute(Qt.WA_DeleteOnClose, True) assert isinstance(graphDw, QDockWidget) graphView = GraphEditorView(graphDw) graphView.setScene(GraphScene(subConfig.getGraph(), graphDw)) graphDw.setWidget(graphView) self._graphViews.append(graphDw) graphDw.visibleChanged.connect(self._removeGraphViewFromList) def _subConfigRemoved(self, subConfigName, configType): g = self._configuration.subConfigByNameAndTye(subConfigName, configType).getGraph() for gv in self._graphViews: if gv.widget().scene().graph == g: logger.debug("deleting graph view for subconfig %s", subConfigName) gv.deleteLater() def _removeGraphViewFromList(self, visible): if visible: return gv = self.sender() try: self._graphViews.remove(gv) logger.debug("removed graphview from list") except ValueError: logger.debug("graphview not in list, ignored") def _execTreeViewContextMenu(self, point): index = self.treeView.indexAt(point) item = self.model.data(index, ITEM_ROLE) if isinstance(item, ConfigurationModel.SubConfigContent): m = QMenu() a1 = QAction("Edit graph") m.addAction(a1) a1.triggered.connect(lambda: self._addGraphView(item.subConfig)) if self.model.isApplication(index): a2 = QAction("Select Application") a2.triggered.connect(lambda: self.changeActiveApp( self.model.data(index, Qt.DisplayRole))) a3 = QAction("Init Application") a3.triggered.connect(lambda: self._changeActiveAppAndInit( self.model.data(index, Qt.DisplayRole))) m.addActions([a2, a3]) pbsrv = Services.getService("PlaybackControl") m2 = m.addMenu("Init and load sequence") m3 = m.addMenu("Init, load and play") s1 = [] s2 = [] for a in pbsrv.recentSeqs: assert isinstance(a, QAction) if a.isVisible(): # pylint: disable=cell-var-from-loop # the below statements are tested and work aseq = QAction(a.text()) aseq.triggered.connect(lambda arg1=a.data( ), seq=a.data(): self._changeActiveAppInitAndLoad( self.model.data(index, Qt.DisplayRole), seq, False) ) s1.append(aseq) aseq = QAction(a.text()) aseq.triggered.connect(lambda arg1=a.data( ), seq=a.data(): self._changeActiveAppInitAndLoad( self.model.data(index, Qt.DisplayRole), seq, True)) # pylint: enable=cell-var-from-loop s2.append(aseq) m2.addActions(s1) m3.addActions(s2) m.exec_(self.treeView.mapToGlobal(point)) return if self.model.isSubConfigParent( index) == Configuration.CONFIG_TYPE_APPLICATION: m = QMenu() a = QAction("Add application") m.addAction(a) a = m.exec_(self.treeView.mapToGlobal(point)) if a is not None: self._configuration.addNewApplication() return if self.model.isSubConfigParent( index) == Configuration.CONFIG_TYPE_COMPOSITE: m = QMenu() a = QAction("Add composite filter") m.addAction(a) a = m.exec_(self.treeView.mapToGlobal(point)) if a is not None: self._configuration.addNewCompositeFilter() return def _configNameChanged(self, cfgfile): logger.debug("_configNameChanged: %s", cfgfile) assertMainThread() self.cfgfile = cfgfile self._dirtyChanged(self._configuration.dirty()) foundIdx = None for i, a in enumerate(self.recentConfigs): if a.data() == cfgfile: foundIdx = i if foundIdx is None: foundIdx = len(self.recentConfigs) - 1 for i in range(foundIdx, 0, -1): self.recentConfigs[i].setText(self.recentConfigs[i - 1].text()) self.recentConfigs[i].setData(self.recentConfigs[i - 1].data()) self.recentConfigs[i].setVisible( self.recentConfigs[i - 1].data() is not None) self.recentConfigs[0].setText(cfgfile) self.recentConfigs[0].setData(cfgfile) self.recentConfigs[0].setVisible(True) def _dirtyChanged(self, dirty): srv = Services.getService("MainWindow") if self.cfgfile is None: title = "nexxT: <unnamed>" else: title = "nexxT: " + self.cfgfile if dirty: title += " *" srv.setWindowTitle(title) def _onItemDoubleClicked(self, index): assertMainThread() if self.model.isApplication(index): app = self.model.data(index, Qt.DisplayRole) self.changeActiveApp(app) else: self.treeView.edit(index) def _changeActiveAppAndInit(self, app): """ Call this slot to activate and init an application :param app: can be either an Application instance or the name of an application :return: """ assertMainThread() if isinstance(app, str): app = self.configuration().applicationByName(app) currentApp = Application.activeApplication if currentApp is not None: currentApp = currentApp.getApplication() self._waitForActivated = app self.changeActiveApp(app.getName()) def _changeActiveAppInitAndLoad(self, app, sequence, startPlay): self._waitForOpenState = (app, sequence, startPlay) self._changeActiveAppAndInit(app) def appActivated(self, name, app): # pylint: disable=unused-argument """ Called when the application is activated. :param name: the application name :param app: An ActiveApplication instance. :return: """ assertMainThread() if app is not None: self.activeAppStateChange(app.getState()) app.stateChanged.connect(self.activeAppStateChange) if self._waitForActivated == app.getApplication(): MethodInvoker(self.activate, Qt.QueuedConnection) else: self.actActivate.setEnabled(False) self.actDeactivate.setEnabled(False) self._waitForActivated = None def _disconnectSingleShotPlay(self): assertMainThread() pbsrv = Services.getService("PlaybackControl") try: pbsrv.playbackPaused.disconnect(self._singleShotPlay) except RuntimeError: # we are already disconnected. pass def _singleShotPlay(self): assertMainThread() pbsrv = Services.getService("PlaybackControl") MethodInvoker(pbsrv.startPlayback, Qt.QueuedConnection) self._disconnectSingleShotPlay() def activeAppStateChange(self, newState): """ Called when the active application changes its state. :param newState: the new application's state (see FilterState) :return: """ assertMainThread() if newState == FilterState.CONSTRUCTED: self.actActivate.setEnabled(True) else: self.actActivate.setEnabled(False) if newState == FilterState.ACTIVE: if self._waitForOpenState is not None: app, pbfile, startPlay = self._waitForOpenState self._waitForOpenState = None if app == Application.activeApplication.getApplication( ).getName(): pbsrv = Services.getService("PlaybackControl") if startPlay: pbsrv.playbackPaused.connect(self._singleShotPlay) QTimer.singleShot(2000, self._disconnectSingleShotPlay) MethodInvoker(pbsrv.browser.setActive, Qt.QueuedConnection, pbfile) self.actDeactivate.setEnabled(True) self.actSaveWithGuiState.setEnabled(False) else: self.actDeactivate.setEnabled(False) self.actSaveWithGuiState.setEnabled(True) def restoreState(self): """ Restore the state of the configuration gui service (namely the recently open config files). This is saved in QSettings because it is used across config files. :return: """ logger.debug("restoring config state ...") settings = QSettings() v = settings.value("ConfigurationRecentFiles") if v is not None and isinstance(v, QByteArray): ds = QDataStream(v) recentFiles = ds.readQStringList() idx = 0 for f in recentFiles: if f != "" and f is not None: self.recentConfigs[idx].setData(f) self.recentConfigs[idx].setText(f) self.recentConfigs[idx].setVisible(True) idx += 1 if idx >= len(self.recentConfigs): break logger.debug("restoring config state done") def saveState(self): """ Save the state of the configuration gui service (namely the recently open config files). This is saved in QSettings because it is used across config files. :return: """ logger.debug("saving config state ...") settings = QSettings() b = QByteArray() ds = QDataStream(b, QIODevice.WriteOnly) l = [ rc.data() for rc in self.recentConfigs if rc.isVisible() and rc.data() is not None and rc.data() != "" ] ds.writeQStringList(l) settings.setValue("ConfigurationRecentFiles", b) logger.debug("saving config state done (%s)", l)
class ComponentTreeViewTab(QWidget): def __init__(self, parent=None): super().__init__() self.setLayout(QVBoxLayout()) self.setParent(parent) self.componentsTabLayout = QVBoxLayout() self.component_tree_view = QTreeView() self.componentsTabLayout.addWidget(self.component_tree_view) self.layout().addLayout(self.componentsTabLayout) self.component_tree_view.setDragEnabled(True) self.component_tree_view.setAcceptDrops(True) self.component_tree_view.setDropIndicatorShown(True) self.component_tree_view.header().hide() self.component_tree_view.updateEditorGeometries() self.component_tree_view.updateGeometries() self.component_tree_view.updateGeometry() self.component_tree_view.clicked.connect(self._set_button_state) self.component_tree_view.setSelectionMode(QAbstractItemView.SingleSelection) self.component_tool_bar = QToolBar("Actions", self) self.new_component_action = create_and_add_toolbar_action( "new_component.png", "New Component", self.parent().show_add_component_window, self.component_tool_bar, self, True, ) self.new_translation_action = create_and_add_toolbar_action( "new_translation.png", "New Translation", lambda: self._add_transformation(TransformationType.TRANSLATION), self.component_tool_bar, self, ) self.new_rotation_action = create_and_add_toolbar_action( "new_rotation.png", "New Rotation", lambda: self._add_transformation(TransformationType.ROTATION), self.component_tool_bar, self, ) self.create_link_action = create_and_add_toolbar_action( "create_link.png", "Create Link", self.on_create_link, self.component_tool_bar, self, ) self.duplicate_action = create_and_add_toolbar_action( "duplicate.png", "Duplicate", self.on_duplicate_node, self.component_tool_bar, self, ) self.edit_component_action = create_and_add_toolbar_action( "edit_component.png", "Edit Component", self.parent().show_edit_component_dialog, self.component_tool_bar, self, ) self.delete_action = create_and_add_toolbar_action( "delete.png", "Delete", self.on_delete_item, self.component_tool_bar, self ) self.zoom_action = create_and_add_toolbar_action( "zoom.svg", "Zoom To Component", self.on_zoom_item, self.component_tool_bar, self, ) self.component_tool_bar.insertSeparator(self.zoom_action) self.componentsTabLayout.insertWidget(0, self.component_tool_bar) def set_up_model(self, instrument): self.component_model = ComponentTreeModel(instrument) self.component_delegate = ComponentEditorDelegate( self.component_tree_view, instrument ) self.component_tree_view.setItemDelegate(self.component_delegate) self.component_tree_view.setModel(self.component_model) def _set_button_state(self): set_button_states( self.component_tree_view, self.delete_action, self.duplicate_action, self.new_rotation_action, self.new_translation_action, self.create_link_action, self.zoom_action, self.edit_component_action, ) def on_create_link(self): selected = self.component_tree_view.selectedIndexes() if len(selected) > 0: self.component_model.add_link(selected[0]) self._expand_transformation_list(selected[0]) self._set_button_state() def on_duplicate_node(self): selected = self.component_tree_view.selectedIndexes() if len(selected) > 0: self.component_model.duplicate_node(selected[0]) self._expand_transformation_list(selected[0]) def _expand_transformation_list(self, node: QModelIndex): expand_transformation_list(node, self.component_tree_view, self.component_model) def _add_transformation(self, transformation_type: TransformationType): add_transformation( transformation_type, self.component_tree_view, self.component_model ) def on_delete_item(self): selected = self.component_tree_view.selectedIndexes() for item in selected: self.component_model.remove_node(item) self._set_button_state() def on_zoom_item(self): selected = self.component_tree_view.selectedIndexes()[0] component = selected.internalPointer() self.sceneWidget.zoom_to_component( self.sceneWidget.get_entity(component.name), self.sceneWidget.view.camera() )
class Snippets(QDialog): def __init__(self, context, parent=None): super(Snippets, self).__init__(parent) # Create widgets self.setWindowModality(Qt.ApplicationModal) self.title = QLabel(self.tr("Snippet Editor")) self.saveButton = QPushButton(self.tr("&Save")) self.saveButton.setShortcut(QKeySequence(self.tr("Ctrl+S"))) self.runButton = QPushButton(self.tr("&Run")) self.runButton.setShortcut(QKeySequence(self.tr("Ctrl+R"))) self.closeButton = QPushButton(self.tr("Close")) self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) self.setWindowTitle(self.title.text()) #self.newFolderButton = QPushButton("New Folder") self.browseButton = QPushButton("Browse Snippets") self.browseButton.setIcon(QIcon.fromTheme("edit-undo")) self.deleteSnippetButton = QPushButton("Delete") self.newSnippetButton = QPushButton("New Snippet") self.edit = QCodeEditor(HIGHLIGHT_CURRENT_LINE=False, SyntaxHighlighter=PythonHighlighter) self.edit.setPlaceholderText("python code") self.resetting = False self.columns = 3 self.context = context self.keySequenceEdit = QKeySequenceEdit(self) self.currentHotkey = QKeySequence() self.currentHotkeyLabel = QLabel("") self.currentFileLabel = QLabel() self.currentFile = "" self.snippetDescription = QLineEdit() self.snippetDescription.setPlaceholderText("optional description") #Set Editbox Size font = getMonospaceFont(self) self.edit.setFont(font) font = QFontMetrics(font) self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API #Files self.files = QFileSystemModel() self.files.setRootPath(snippetPath) self.files.setNameFilters(["*.py"]) #Tree self.tree = QTreeView() self.tree.setModel(self.files) self.tree.setSortingEnabled(True) self.tree.hideColumn(2) self.tree.sortByColumn(0, Qt.AscendingOrder) self.tree.setRootIndex(self.files.index(snippetPath)) for x in range(self.columns): #self.tree.resizeColumnToContents(x) self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) treeLayout = QVBoxLayout() treeLayout.addWidget(self.tree) treeButtons = QHBoxLayout() #treeButtons.addWidget(self.newFolderButton) treeButtons.addWidget(self.browseButton) treeButtons.addWidget(self.newSnippetButton) treeButtons.addWidget(self.deleteSnippetButton) treeLayout.addLayout(treeButtons) treeWidget = QWidget() treeWidget.setLayout(treeLayout) # Create layout and add widgets buttons = QHBoxLayout() buttons.addWidget(self.clearHotkeyButton) buttons.addWidget(self.keySequenceEdit) buttons.addWidget(self.currentHotkeyLabel) buttons.addWidget(self.closeButton) buttons.addWidget(self.runButton) buttons.addWidget(self.saveButton) description = QHBoxLayout() description.addWidget(QLabel(self.tr("Description: "))) description.addWidget(self.snippetDescription) vlayoutWidget = QWidget() vlayout = QVBoxLayout() vlayout.addLayout(description) vlayout.addWidget(self.edit) vlayout.addLayout(buttons) vlayoutWidget.setLayout(vlayout) hsplitter = QSplitter() hsplitter.addWidget(treeWidget) hsplitter.addWidget(vlayoutWidget) hlayout = QHBoxLayout() hlayout.addWidget(hsplitter) self.showNormal() #Fixes bug that maximized windows are "stuck" #Because you can't trust QT to do the right thing here if (sys.platform == "darwin"): self.settings = QSettings("Vector35", "Snippet Editor") else: self.settings = QSettings("Vector 35", "Snippet Editor") if self.settings.contains("ui/snippeteditor/geometry"): self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry")) else: self.edit.setMinimumWidth(80 * font.averageCharWidth()) self.edit.setMinimumHeight(30 * font.lineSpacing()) # Set dialog layout self.setLayout(hlayout) # Add signals self.saveButton.clicked.connect(self.save) self.closeButton.clicked.connect(self.close) self.runButton.clicked.connect(self.run) self.clearHotkeyButton.clicked.connect(self.clearHotkey) self.tree.selectionModel().selectionChanged.connect(self.selectFile) self.newSnippetButton.clicked.connect(self.newFileDialog) self.deleteSnippetButton.clicked.connect(self.deleteSnippet) #self.newFolderButton.clicked.connect(self.newFolder) self.browseButton.clicked.connect(self.browseSnippets) if self.settings.contains("ui/snippeteditor/selected"): selectedName = self.settings.value("ui/snippeteditor/selected") self.tree.selectionModel().select(self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) if self.tree.selectionModel().hasSelection(): self.selectFile(self.tree.selectionModel().selection(), None) self.edit.setFocus() cursor = self.edit.textCursor() cursor.setPosition(self.edit.document().characterCount()-1) self.edit.setTextCursor(cursor) else: self.readOnly(True) else: self.readOnly(True) @staticmethod def registerAllSnippets(): for action in list(filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())): if action == "Snippets\\Snippet Editor...": continue UIActionHandler.globalActions().unbindAction(action) Menu.mainMenu("Tools").removeAction(action) UIAction.unregisterAction(action) for snippet in includeWalk(snippetPath, ".py"): snippetKeys = None (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet) actionText = actionFromSnippet(snippet, snippetDescription) if snippetCode: if snippetKeys == None: UIAction.registerAction(actionText) else: UIAction.registerAction(actionText, snippetKeys) UIActionHandler.globalActions().bindAction(actionText, UIAction(makeSnippetFunction(snippetCode))) Menu.mainMenu("Tools").addAction(actionText, "Snippets") def clearSelection(self): self.keySequenceEdit.clear() self.currentHotkey = QKeySequence() self.currentHotkeyLabel.setText("") self.currentFileLabel.setText("") self.snippetDescription.setText("") self.edit.clear() self.tree.clearSelection() self.currentFile = "" def reject(self): self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry()) if self.snippetChanged(): question = QMessageBox.question(self, self.tr("Discard"), self.tr("You have unsaved changes, quit anyway?")) if question != QMessageBox.StandardButton.Yes: return self.accept() def browseSnippets(self): url = QUrl.fromLocalFile(snippetPath) QDesktopServices.openUrl(url); def newFolder(self): (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: ")) if ok and folderName: index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): QDir(selection).mkdir(folderName) else: QDir(snippetPath).mkdir(folderName) def selectFile(self, new, old): if (self.resetting): self.resetting = False return if len(new.indexes()) == 0: self.clearSelection() self.currentFile = "" self.readOnly(True) return newSelection = self.files.filePath(new.indexes()[0]) self.settings.setValue("ui/snippeteditor/selected", newSelection) if QFileInfo(newSelection).isDir(): self.readOnly(True) self.clearSelection() self.currentFile = "" return if old and old.length() > 0: oldSelection = self.files.filePath(old.indexes()[0]) if not QFileInfo(oldSelection).isDir() and self.snippetChanged(): question = QMessageBox.question(self, self.tr("Discard"), self.tr("Snippet changed. Discard changes?")) if question != QMessageBox.StandardButton.Yes: self.resetting = True self.tree.selectionModel().select(old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) return False self.currentFile = newSelection self.loadSnippet() def loadSnippet(self): self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName()) (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("") self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence("")) self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("") self.readOnly(False) def newFileDialog(self): (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: ")) if ok and snippetName: if not snippetName.endswith(".py"): snippetName += ".py" index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): path = os.path.join(selection, snippetName) else: path = os.path.join(snippetPath, snippetName) self.readOnly(False) open(path, "w").close() self.tree.setCurrentIndex(self.files.index(path)) log_debug("Snippet %s created." % snippetName) def readOnly(self, flag): self.keySequenceEdit.setEnabled(not flag) self.snippetDescription.setReadOnly(flag) self.edit.setReadOnly(flag) if flag: self.snippetDescription.setDisabled(True) self.edit.setDisabled(True) else: self.snippetDescription.setEnabled(True) self.edit.setEnabled(True) def deleteSnippet(self): selection = self.tree.selectedIndexes()[::self.columns][0] #treeview returns each selected element in the row snippetName = self.files.fileName(selection) question = QMessageBox.question(self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName) if (question == QMessageBox.StandardButton.Yes): log_debug("Deleting snippet %s." % snippetName) self.clearSelection() self.files.remove(selection) self.registerAllSnippets() def snippetChanged(self): if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()): return False (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) if snippetKeys == None and not self.keySequenceEdit.keySequence().isEmpty(): return True if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString(): return True return self.edit.toPlainText() != snippetCode or \ self.snippetDescription.text() != snippetDescription def save(self): log_debug("Saving snippet %s" % self.currentFile) outputSnippet = codecs.open(self.currentFile, "w", "utf-8") outputSnippet.write("#" + self.snippetDescription.text() + "\n") outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") outputSnippet.write(self.edit.toPlainText()) outputSnippet.close() self.registerAllSnippets() def run(self): if self.context == None: log_warn("Cannot run snippets outside of the UI at this time.") return if self.snippetChanged(): question = QMessageBox.question(self, self.tr("Confirm"), self.tr("You have unsaved changes, must save first. Save?")) if (question == QMessageBox.StandardButton.No): return else: self.save() actionText = actionFromSnippet(self.currentFile, self.snippetDescription.text()) UIActionHandler.globalActions().executeAction(actionText, self.context) log_debug("Saving snippet %s" % self.currentFile) outputSnippet = codecs.open(self.currentFile, "w", "utf-8") outputSnippet.write("#" + self.snippetDescription.text() + "\n") outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") outputSnippet.write(self.edit.toPlainText()) outputSnippet.close() self.registerAllSnippets() def clearHotkey(self): self.keySequenceEdit.clear()
class BrowserWidget(QWidget): """ This class puts together a TabCompletionLineEdit and a list view of teh FolderListModel in one single widget. """ activated = Signal(str) # emitted when the user selects a file def __init__(self, parent=None): super().__init__(parent) self._recursiveActivated = False self._model = FolderListModel(self) self._completer = QCompleter(self._model, self) self._completer.setCompletionColumn(3) self._lineedit = TabCompletionLineEdit(self._completer, self) self._view = QTreeView(self) self._view.setModel(self._model) self._model.folderChanged.connect(self._lineedit.setText) self._model.setFolder(".") self._view.header().setSectionResizeMode(0, QHeaderView.Interactive) self._view.header().setSectionResizeMode(1, QHeaderView.ResizeToContents) self._view.header().setSectionResizeMode(2, QHeaderView.ResizeToContents) self._view.header().resizeSection(0, 500) self._view.header().setSectionHidden(3, True) layout = QVBoxLayout(self) layout.addWidget(self._lineedit) layout.addWidget(self._view) self.setLayout(layout) self._view.activated.connect(self._activated) self.activated.connect(self._lineedit.setText) self._lineedit.returnPressed.connect(self._leActivated, Qt.QueuedConnection) self._lineedit.textEdited.connect(self._leTextEdited) def setActive(self, activeFile): """ set the activated file :param activeFile: a string or Path instance :return: """ activeFile = Path(activeFile) assert activeFile.is_file() self._model.setFolder(activeFile.parent) idx = self._model.fileToIndex(activeFile) self._view.setCurrentIndex(idx) self._view.scrollTo(idx) self.activated.emit(str(activeFile)) def active(self): """ The currently activated file :return: a string instance """ cidx = self._view.currentIndex() c = self._model.data(cidx, Qt.UserRole) return str(c) if c is not None else None def current(self): """ A synonym for active() :return: a string instance """ return self.active() def setFilter(self, flt): """ Set the name filter of the file browser :param flt: a string instance or a list of strings :return: """ self._model.setFilter(flt) def scrollTo(self, item): """ Scrolls to the given item. :param item: a string instance :return: """ cidx = self._model.fileToIndex(item) self._view.scrollTo(cidx) def folder(self): """ Returns the current folder :return: a Path instance """ return self._model.folder() def setFolder(self, folder): """ Sets the current folder :param folder: a string or a Path instance :return: """ self._model.setFolder(folder) def _leActivated(self): idx = self._model.fileToIndex(self._lineedit.text()) self._activated(idx) def _leTextEdited(self, text): p = Path(text) if p.is_dir() and len(text) > 0 and text[-1] in ["/", "\\"]: self.setFolder(p) def _activated(self, idx): c = self._model.data(idx, Qt.UserRole) if c is None: return logger.debug("activate %s", c) if c.is_file(): self.activated.emit(str(c)) else: self._model.setFolder(c)
class TelemetryViewerGUI(QMainWindow): def __init__(self): super().__init__() title = "Telemetry Viewer" self.setWindowTitle(title) if sys.platform == "win32": import ctypes try: ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( title.replace(" ", "_")) except BaseException: pass self.icon = QIcon() iconPath = Path(__file__).parent / "icon.ico" self.icon.addFile(str(iconPath), QSize(64, 64)) self.setWindowIcon(self.icon) self.setAcceptDrops(True) self.fileDlg = QFileDialog(self, Qt.WindowFlags(0)) try: parentDir = str( Path(os.environ["ProgramData"]) / "Microsoft" / "Diagnosis") except BaseException: parentDir = "." self.fileDlg.setDirectory(parentDir) def callback(_, rowNo): #self.ar.setText(json.dumps(self.indexed[rowNo], indent="\t")) self.model.loadDict(self.indexed[rowNo]) self.table = TableWidget( ("#", "subIdx", "idx", "unkn3", "unkn4", "name", "time"), self, callback) #self.ar = QScrollArea(self) #self.ar = QTextEdit(self) self.ar = QTreeView() self.header = self.ar.header() self.model = QJsonModel() self.ar.setModel(self.model) for i in range(3): self.header.setSectionResizeMode(QHeaderView.ResizeToContents) #self.ar.setFontFamily("monospace") #self.ar.setReadOnly(True) #self.ar.acceptRichText #.dropEvent #wordWrapMode dockableWindowsFeatures = QDockWidget.DockWidgetFeatures( QDockWidget.AllDockWidgetFeatures & ~QDockWidget.DockWidgetClosable) self.JSONWindow = QDockWidget("JSON", self) self.JSONWindow.setFeatures(dockableWindowsFeatures) self.addDockWidget(Qt.RightDockWidgetArea, self.JSONWindow) # l self.JSONWindow.setWidget(self.ar) self.tableWindow = QDockWidget("Table", self) self.tableWindow.setFeatures(dockableWindowsFeatures) self.addDockWidget(Qt.LeftDockWidgetArea, self.tableWindow) self.tableWindow.setWidget(self.table) #self.openTelemetryBtn = QPushButton("Open telemetry", self) #self.openTelemetryBtn.clicked.connect(self.openTelemetry) self.indexed = {} telemetryFileMask = "Windows Diagnostics Framework (*.rbs)" def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() else: event.ignore() def dropEvent(self, evt): urls = evt.mimeData().urls() if len(urls) != 1: errMsg = QErrorMessage() errMsg.showMessage("Drop exactly single rbs file!") else: self.loadTelemetry(urls[0].toLocalFile()) def openTelemetry(self) -> None: fns = self.fileDlg.getOpenFileName(self, "Load telemetry data file", None, self.__class__.telemetryFileMask) if fns[0]: self.loadTelemetry(fns[0]) def loadTelemetry(self, f: str) -> None: self.table.clear() parsed = SelectedRBS(f) self.indexed = {} i = 0 for j, item in enumerate(parsed): #repr(item.header.unkn3) # bluec0re suspects it is the type for subIndex, subItemText in enumerate(item.json): jso = parseJSON(subItemText) self.indexed[i] = jso self.table.appendRow( i - 1, (j, subIndex, item.index, item.unkn3, item.unkn4, jso["name"], transformTimeIntoLocal(jso["time"]))) i += 1 del parsed self.table.table.resizeColumnsToContents() if i: self.table.table.setCurrentIndex(self.table.table.model().index( 0, 0))
class SVDBrowser(QDialog): def __init__(self, context, parent=None): super(SVDBrowser, self).__init__(parent) QWebEngineProfile.defaultProfile().downloadRequested.connect( self.on_downloadRequested) # Create widgets #self.setWindowModality(Qt.ApplicationModal) self.title = QLabel(self.tr("SVD Browser")) self.closeButton = QPushButton(self.tr("Close")) self.setWindowTitle(self.title.text()) self.browseButton = QPushButton("Browse SVD Folder") self.deleteSvdButton = QPushButton("Delete") self.applySvdButton = QPushButton("Apply SVD") self.view = QWebEngineView() url = "https://developer.arm.com/tools-and-software/embedded/cmsis/cmsis-search" self.view.load(QUrl(url)) self.columns = 3 self.context = context self.currentFileLabel = QLabel() self.currentFile = "" #Files self.files = QFileSystemModel() self.files.setRootPath(svdPath) self.files.setNameFilters(["*.svd", "*.patched"]) #Tree self.tree = QTreeView() self.tree.setModel(self.files) self.tree.setSortingEnabled(True) self.tree.hideColumn(2) self.tree.sortByColumn(0, Qt.AscendingOrder) self.tree.setRootIndex(self.files.index(svdPath)) for x in range(self.columns): #self.tree.resizeColumnToContents(x) self.tree.header().setSectionResizeMode( x, QHeaderView.ResizeToContents) treeLayout = QVBoxLayout() treeLayout.addWidget(self.tree) treeButtons = QHBoxLayout() #treeButtons.addWidget(self.newFolderButton) treeButtons.addWidget(self.browseButton) treeButtons.addWidget(self.applySvdButton) treeButtons.addWidget(self.deleteSvdButton) treeLayout.addLayout(treeButtons) treeWidget = QWidget() treeWidget.setLayout(treeLayout) # Create layout and add widgets buttons = QHBoxLayout() buttons.addWidget(self.closeButton) vlayoutWidget = QWidget() vlayout = QVBoxLayout() vlayout.addWidget(self.view) vlayout.addLayout(buttons) vlayoutWidget.setLayout(vlayout) hsplitter = QSplitter() hsplitter.addWidget(treeWidget) hsplitter.addWidget(vlayoutWidget) hlayout = QHBoxLayout() hlayout.addWidget(hsplitter) self.showMaximized() #Fixes bug that maximized windows are "stuck" #Because you can't trust QT to do the right thing here # Set dialog layout self.setLayout(hlayout) # Add signals self.closeButton.clicked.connect(self.close) self.tree.selectionModel().selectionChanged.connect(self.selectFile) self.applySvdButton.clicked.connect(self.applySvd) self.deleteSvdButton.clicked.connect(self.deleteSvd) self.browseButton.clicked.connect(self.browseSvd) def browseSvd(self): url = QUrl.fromLocalFile(svdPath) QDesktopServices.openUrl(url) def selectFile(self, new, old): if len(new.indexes()) == 0: self.tree.clearSelection() self.currentFile = "" return newSelection = self.files.filePath(new.indexes()[0]) if QFileInfo(newSelection).isDir(): self.tree.clearSelection() self.currentFile = "" return self.currentFile = newSelection def applySvd(self): selection = self.tree.selectedIndexes()[::self.columns][ 0] #treeview returns each selected element in the row svdName = self.files.fileName(selection) if (svdName != ""): question = QMessageBox.question( self, self.tr("Confirm"), self. tr(f"Confirm applying {svdName} to {os.path.basename(self.context.file.filename)} : " )) if (question == QMessageBox.StandardButton.Yes): log_debug("SVD Browser: Applying SVD %s." % svdName) load_svd(self.context, self.currentFile) self.close() def deleteSvd(self): selection = self.tree.selectedIndexes()[::self.columns][ 0] #treeview returns each selected element in the row svdName = self.files.fileName(selection) question = QMessageBox.question( self, self.tr("Confirm"), self.tr("Confirm deletion: ") + svdName) if (question == QMessageBox.StandardButton.Yes): log_debug("SVD Browser: Deleting SVD %s." % svdName) self.files.remove(selection) self.tree.clearSelection() def on_downloadRequested(self, download): old_path = download.url().path() # download.path() suffix = QFileInfo(old_path).suffix() if (suffix.lower() in ["zip", "svd", "pack", "patched"]): log_debug(f"SVD Browser: Downloading {str(download.url())}") if suffix.lower() == "svd" or suffix.lower() == "patched": download.setDownloadDirectory(svdPath) download.accept() else: with TemporaryDirectory() as tempfolder: log_debug( f"SVD Browser: Downloading pack/zip to {tempfolder}") fname = download.url().fileName() r = requests.get(download.url().toString(), allow_redirects=True) dlfile = os.path.join(tempfolder, fname) open(dlfile, "wb").write(r.content) ''' # TODO: See if the original QT Downloader can be fixed since it would # help with situations where the user entered credentials in the browser. download.setDownloadDirectory(tempfolder) download.accept() while not download.finished: import time time.sleep(100) ''' if fname.endswith(".zip") or fname.endswith(".pack"): destFolder = os.path.join(svdPath, os.path.splitext(fname)[0]) log_debug(f"SVD Browser: Creating {destFolder}") if not os.path.exists(destFolder): os.mkdir(destFolder) with ZipFile(dlfile, 'r') as zipp: for ifname in zipp.namelist(): if ifname.endswith(".svd"): info = zipp.getinfo(ifname) info.filename = os.path.basename( info.filename) log_debug( f"SVD Browser: Extracting {info.filename} from {ifname}" ) zipp.extract(info, path=destFolder) else: #Move file into place shutil.move(dlfile, svdPath) else: show_message_box( "Invalid file", "That download does not appear to be a valid SVD/ZIP/PACK file." ) download.cancel()