class MainWidget(QWidget): def __init__(self, parent=None): super(MainWidget, self).__init__(parent) # define tree view self.treeView = QTreeView(self) # insert line edit self.lineEdit1 = QLineEdit(self) self.lineEdit2 = QLineEdit(self) # insert a button group widget self.buttonGroupWidget1 = ButtonGroupWidget(self) self.buttonGroupWidget1.setTitle("Select option") self.buttonGroupWidget1.addRadioButton("Option 1", 1) self.buttonGroupWidget1.addRadioButton("Option 2", 2) self.buttonGroupWidget1.addRadioButton("Option 3", 3) layoutMain = QVBoxLayout(self) layoutMain.addWidget(self.treeView) layoutMain.addWidget(self.lineEdit1) layoutMain.addWidget(self.lineEdit2) layoutMain.addWidget(self.buttonGroupWidget1) # Create the data model and map the model to the widgets. self._model = DataModel() self.treeView.setModel(self._model) self._dataMapper = QDataWidgetMapper() self._dataMapper.setModel(self._model) # the mapping works fine for line edits and combo boxes self._dataMapper.addMapping(self.lineEdit1, 0) self._dataMapper.addMapping(self.lineEdit2, 1) # mapping to custom property self._dataMapper.addMapping(self.buttonGroupWidget1, 1, "selectedOption") self.treeView.selectionModel().currentChanged.connect( self.setSelection) def setSelection(self, current): parent = current.parent() # self._dataMapper.setRootIndex(parent) self._dataMapper.setCurrentModelIndex(current)
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()
class TriageFilePicker(QWidget): def __init__(self, context): super(TriageFilePicker, self).__init__() self.context = context self.actionHandler = UIActionHandler() self.actionHandler.setupActionHandler(self) self.contextMenu = Menu() self.contextMenuManager = ContextMenuManager(self) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.model = QFileSystemModel() self.model.setRootPath("") self.model.setFilter(QDir.AllEntries | QDir.Hidden | QDir.System) self.tree = QTreeView(self) self.tree.setModel(self.model) self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tree.setColumnWidth(0, 500) layout.addWidget(self.tree, 1) self.setLayout(layout) self.tree.doubleClicked.connect(self.onDoubleClick) recentFile = QSettings().value("triage/recentFile", os.path.expanduser("~")) while len(recentFile) > 0: f = self.model.index(recentFile) if f.isValid(): self.tree.scrollTo(f) self.tree.setExpanded(f, True) break parentDir = os.path.dirname(recentFile) if parentDir == recentFile: break recentFile = parentDir self.actionHandler.bindAction("Open Selected Files", UIAction( lambda context: self.openSelectedFiles(), lambda context: self.areFilesSelected())) self.contextMenu.addAction("Open Selected Files", "Open") def contextMenuEvent(self, event): self.contextMenuManager.show(self.contextMenu, self.actionHandler) def onDoubleClick(self, index): self.openSelectedFiles() def openSelectedFiles(self): failedToOpen = [] files = set() for index in self.tree.selectionModel().selectedIndexes(): if self.model.fileInfo(index).isFile(): files.add(self.model.fileInfo(index).absoluteFilePath()) for filename in files: QSettings().setValue("triage/recentFile", filename) f = FileContext.openFilename(filename) if not f: failedToOpen.append(filename) continue f.createBinaryViews() for data in f.getAllDataViews(): Settings().set_string("analysis.mode", Settings().get_string("triage.analysisMode"), data) Settings().set_bool("triage.preferSummaryView", True, data) if data.view_type != "Raw": linearSweepMode = Settings().get_string("triage.linearSweep") if linearSweepMode == "none": Settings().set_bool("analysis.linearSweep.autorun", False, data) elif linearSweepMode == "partial": Settings().set_bool("analysis.linearSweep.autorun", True, data) Settings().set_bool("analysis.linearSweep.controlFlowGraph", False, data) elif linearSweepMode == "full": Settings().set_bool("analysis.linearSweep.autorun", True, data) Settings().set_bool("analysis.linearSweep.controlFlowGraph", True, data) self.context.openFileContext(f) if len(failedToOpen) > 0: QMessageBox.critical(self, "Error", "Unable to open:\n" + "\n".join(failedToOpen)) def areFilesSelected(self): return self.tree.selectionModel().hasSelection()
class MainWindow(QMainWindow): def __init__(self, app): super(MainWindow, self).__init__() self._app = app self._selectedIndex = None # model nodeFactory = NodeFactory() rootNode = nodeFactory.create(NodeType.General, 'Root') # for i in range(10000): # for testing childNode0 = nodeFactory.create(NodeType.General, 'RightPirateLeg', rootNode) childNode1 = nodeFactory.create(NodeType.General, 'RightPirateLeg_END', childNode0) childNode2 = nodeFactory.create(NodeType.General, 'LeftFemur', rootNode) childNode3 = nodeFactory.create(NodeType.Sphere, 'LeftTibia', childNode2) childNode4 = nodeFactory.create(NodeType.Sphere, 'LeftFoot', childNode3) transform = childNode4.component(ComponentType.Transform) qTransform = transform.component() translation = qTransform.translation() translation.setX(5) qTransform.setTranslation(translation) # childNode5 = nodeFactory.create(NodeType.Box, 'LeftFoot_END', childNode4) self._model = SceneGraphModel(rootNode) self._sceneView = SceneView(rootNode.entity()) self._container = self.createWindowContainer(self._sceneView) # scene graph view self._treeView = QTreeView() self._treeView.setModel(self._model) self._treeView.setHeaderHidden(True) self._treeView.setAlternatingRowColors(True) dockWidget = QDockWidget() dockWidget.setWidget(self._treeView) dockWidget.setWindowTitle('Scene Graph') dockWidget.setObjectName('sceneGraph') sceneGraphToggleAction = dockWidget.toggleViewAction() self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget) # property editor propertyEditor = PropertyEditor(self._model, nodeFactory, FieldFactory()) dockWidget = QDockWidget() dockWidget.setWidget(propertyEditor) dockWidget.setWindowTitle('Property Editor') dockWidget.setObjectName('propertyEditor') propertyEditorToggleAction = dockWidget.toggleViewAction() self.addDockWidget(Qt.RightDockWidgetArea, dockWidget) # menu menuBar = self.menuBar() menu = menuBar.addMenu('&File') exitAction = menu.addAction('E&xit') exitAction.triggered.connect(self.close) menu.addAction(exitAction) menu = menuBar.addMenu('&Windows') menu.addAction(sceneGraphToggleAction) menu.addAction(propertyEditorToggleAction) menuBar.addMenu(menu) # central widget #button = QPushButton() self.setCentralWidget(self._container) # selection change event selectionModel = self._treeView.selectionModel() selectionModel.currentChanged.connect(propertyEditor.changeSelection) # click event #button.clicked.connect(self.buttonClicked) def selectedNode(self): indices = self._treeView.selectedIndexes() result = None if len(indices) == 1: self._selectedIndex = indices[0] result = self._selectedIndex.internalPointer() return result def commitChange(self): self._model.dataChanged.emit(self._selectedIndex, self._selectedIndex) def closeEvent(self, event): self._app.exit() # data change example def buttonClicked(self): node = self.selectedNode() if not node: return general = node.component(ComponentType.General) general.setName('CLICKED') self.commitChange()
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 BindiffViewerDialog(QDialog): def __init__(self, bv, match_db, role, primary_be, secondary_be): super(BindiffViewerDialog, self).__init__() self.bv = bv self.primary_be = primary_be self.secondary_be = secondary_be self.role = role # UI self.match_model = BindiffMatchModel(bv, match_db, role, primary_be, secondary_be) self.match_view = QTreeView() self.match_view.setModel(self.match_model) self.match_view.setSelectionMode(QTreeView.ExtendedSelection) self.match_view.setContextMenuPolicy(Qt.CustomContextMenu) self.match_view.customContextMenuRequested.connect(self.match_view_context_menu_requested) self.match_view.doubleClicked.connect(self.match_view_double_clicked) self.match_view.setRootIsDecorated(False) self.match_view.setFont(binaryninjaui.getMonospaceFont(self)) for i in range(len(self.match_model.column_infos)): self.match_view.resizeColumnToContents(i) self.match_view.setSortingEnabled(True) self.match_view.sortByColumn(0, Qt.AscendingOrder) layout = QVBoxLayout() layout.addWidget(self.match_view) self.setLayout(layout) self.setWindowTitle("BinDiff Viewer") self.resize(1000, 640) flags = self.windowFlags() flags |= Qt.WindowMaximizeButtonHint flags &= ~Qt.WindowContextHelpButtonHint self.setWindowFlags(flags) def match_view_double_clicked(self, index): if not index.isValid(): assert(False) return if self.role == None: return entry = self.match_model.entries[index.row()] if self.role == 0: address = entry["address1"] elif self.role == 1: address = entry["address2"] else: assert(False) self.bv.navigate(self.bv.file.view, address) def match_view_context_menu_requested(self, pos): if self.role == None: return selected_indices = self.match_view.selectionModel().selectedIndexes() # This may return each row multiple times, so we uniquify selected = set([i.row() for i in selected_indices]) def action_port_symbols(): for i in selected: self.port_symbols(i) menu = QMenu(self.match_view) menu.addAction("Port symbols", action_port_symbols) menu.exec_(self.match_view.mapToGlobal(pos)) def port_symbols(self, i): if self.role == None: return entry = self.match_model.entries[i] target_index = self.role source_index = 1 if target_index == 0 else 0 source_name = entry["name{}".format(source_index + 1)] target_address = entry["address{}".format(target_index + 1)] old_sym = self.bv.get_symbol_at(target_address) target_name = None if old_sym: target_name = old_sym.name target_text = target_name if target_name else "<unnamed>" if not source_name: bn.log_warn("Port symbols: {} @ {:x} has no source name, skipping".format(target_text, target_address)) return if old_sym and not old_sym.auto: bn.log_warn("Port symbols: {} @ {:x} is already named, skipping".format(target_text, target_address)) return bn.log_info("Port symbols: {} @ {:x} -> {}".format(target_text, target_address, source_name)) new_sym = bn.Symbol(bn.SymbolType.FunctionSymbol, target_address, source_name) self.bv.define_user_symbol(new_sym)
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 WTreeEdit(QWidget): """TreeEdit widget is to show and edit all of the pyleecan objects data.""" # Signals dataChanged = Signal() def __init__(self, obj, *args, **kwargs): QWidget.__init__(self, *args, **kwargs) self.class_dict = ClassInfo().get_dict() self.treeDict = None # helper to track changes self.obj = obj # the object self.is_save_needed = False self.model = TreeEditModel(obj) self.setupUi() # === Signals === self.selectionModel.selectionChanged.connect(self.onSelectionChanged) self.treeView.collapsed.connect(self.onItemCollapse) self.treeView.expanded.connect(self.onItemExpand) self.treeView.customContextMenuRequested.connect(self.openContextMenu) self.model.dataChanged.connect(self.onDataChanged) self.dataChanged.connect(self.setSaveNeeded) # === Finalize === # set 'root' the selected item and resize columns self.treeView.setCurrentIndex(self.treeView.model().index(0, 0)) self.treeView.resizeColumnToContents(0) def setupUi(self): """Setup the UI""" # === Widgets === # TreeView self.treeView = QTreeView() # self.treeView.rootNode = model.invisibleRootItem() self.treeView.setModel(self.model) self.treeView.setAlternatingRowColors(False) # self.treeView.setColumnWidth(0, 150) self.treeView.setMinimumWidth(100) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.selectionModel = self.treeView.selectionModel() self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.statusBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.statusBar.setStyleSheet( "QStatusBar {border: 1px solid rgb(200, 200, 200)}") self.saveLabel = QLabel("unsaved") self.saveLabel.setVisible(False) self.statusBar.addPermanentWidget(self.saveLabel) # Splitters self.leftSplitter = QSplitter() self.leftSplitter.setStretchFactor(0, 0) self.leftSplitter.setStretchFactor(1, 1) # === Layout === # Horizontal Div. self.hLayout = QVBoxLayout() self.hLayout.setContentsMargins(0, 0, 0, 0) self.hLayout.setSpacing(0) # add widgets to layout self.hLayout.addWidget(self.leftSplitter) self.hLayout.addWidget(self.statusBar) # add widgets self.leftSplitter.addWidget(self.treeView) self.setLayout(self.hLayout) def update(self, obj): """Check if object has changed and update tree in case.""" if not obj is self.obj: self.obj = obj self.model = TreeEditModel(obj) self.treeView.setModel(self.model) self.model.dataChanged.connect(self.onDataChanged) self.selectionModel = self.treeView.selectionModel() self.selectionModel.selectionChanged.connect( self.onSelectionChanged) self.treeView.setCurrentIndex(self.treeView.model().index(0, 0)) self.setSaveNeeded(True) def setSaveNeeded(self, state=True): self.is_save_needed = state self.saveLabel.setVisible(state) def openContextMenu(self, point): """Generate and open context the menu at the given point position.""" index = self.treeView.indexAt(point) pos = QtGui.QCursor.pos() if not index.isValid(): return # get the data item = self.model.item(index) obj_info = self.model.get_obj_info(item) # init the menu menu = TreeEditContextMenu(obj_dict=obj_info, parent=self) menu.exec_(pos) self.onSelectionChanged(self.selectionModel.selection()) def onItemCollapse(self, index): """Slot for item collapsed""" # dynamic resize for ii in range(3): self.treeView.resizeColumnToContents(ii) def onItemExpand(self, index): """Slot for item expand""" # dynamic resize for ii in range(3): self.treeView.resizeColumnToContents(ii) def onDataChanged(self, first=None, last=None): """Slot for changed data""" self.dataChanged.emit() self.onSelectionChanged(self.selectionModel.selection()) def onSelectionChanged(self, itemSelection): """Slot for changed item selection""" # get the index if itemSelection.indexes(): index = itemSelection.indexes()[0] else: index = self.treeView.model().index(0, 0) self.treeView.setCurrentIndex(index) return # get the data item = self.model.item(index) obj = item.object() typ = type(obj).__name__ obj_info = self.model.get_obj_info(item) ref_typ = obj_info["ref_typ"] if obj_info else None # set statusbar information on class typ msg = f"{typ} (Ref: {ref_typ})" if ref_typ else f"{typ}" self.statusBar.showMessage(msg) # --- choose the respective widget by class type --- # numpy array -> table editor if typ == "ndarray": widget = WTableData(obj, editable=True) widget.dataChanged.connect(self.dataChanged.emit) elif typ == "MeshSolution": widget = WMeshSolution(obj) # only a view (not editable) # list (no pyleecan type, non empty) -> table editor # TODO add another widget for lists of non 'primitive' types (e.g. DataND) elif isinstance(obj, list) and not self.isListType(ref_typ) and obj: widget = WTableData(obj, editable=True) widget.dataChanged.connect(self.dataChanged.emit) # generic editor else: # widget = SimpleInputWidget().generate(obj) widget = WTableParameterEdit(obj) widget.dataChanged.connect(self.dataChanged.emit) # show the widget if self.leftSplitter.widget(1) is None: self.leftSplitter.addWidget(widget) else: self.leftSplitter.replaceWidget(1, widget) widget.setParent( self.leftSplitter) # workaround for PySide2 replace bug widget.show() pass def isListType(self, typ): if not typ: return False return typ[0] == "[" and typ[-1] == "]" and typ[1:-1] in self.class_dict def isDictType(self, typ): if not typ: return False return typ[0] == "{" and typ[-1] == "}" and typ[1:-1] in self.class_dict
def import_accounts_dialog(self): self.import_accounts_window.setWindowTitle(_("Import accounts")) self.import_accounts_window.setWindowIcon(self.switcher_logo) self.import_accounts_window.setMinimumWidth(400) layout = QVBoxLayout() self.import_accounts_window.setLayout(layout) text_label = QLabel(_("Select accounts to import")) import_accounts_list = QTreeView() import_button = QPushButton() model = QStandardItemModel() model.setHorizontalHeaderLabels( [_('Login name'), _('Steam name'), _('Steam UID')]) import_accounts_list.setModel(model) import_accounts_list.setUniformRowHeights(True) import_accounts_list.setEditTriggers(QAbstractItemView.NoEditTriggers) import_accounts_list.setSelectionMode(QTreeView.MultiSelection) layout.addWidget(text_label) layout.addWidget(import_accounts_list) layout.addWidget(import_button) installed_accounts = self.switcher.settings.get("users").keys() disabled = [] for uid, steam_user in self.switcher.load_loginusers().items(): account_row = [ QStandardItem(steam_user.get("AccountName")), QStandardItem(steam_user.get("PersonaName")), QStandardItem(uid) ] # account_row[0].setCheckable(True) account_row[2].setEnabled(False) if steam_user.get("AccountName") in installed_accounts: # account_row = [ x.setEnabled(False) for x in account_row] disabled.append(account_row) else: model.appendRow(account_row) # model.appendRows(disabled) #Existing accounts grayed out import_accounts_list.resizeColumnToContents(0) def import_accounts(): selected_accounts = import_accounts_list.selectionModel( ).selectedRows() for account in selected_accounts: self.switcher.add_account(account.data(0)) self.steamapi_refresh() self.import_accounts_window.hide() def button_enabled(): num_selected = len( import_accounts_list.selectionModel().selectedRows()) import_button.setText( _("Import {0} accounts").format(num_selected)) if num_selected: import_button.setEnabled(True) else: import_button.setEnabled(False) button_enabled() import_accounts_list.selectionModel().selectionChanged.connect( button_enabled) import_button.clicked.connect(import_accounts) self.import_accounts_window.show()
class FE14ChapterSpawnsTab(QWidget): def __init__(self): super().__init__() self.chapter_data = None self.dispos_model = None self.dispos = None self.terrain = None self.tiles_model = None self.terrain_mode = False self.initialized_selection_signal = False self.selected_faction = None left_panel_container = QWidget() left_panel_layout = QVBoxLayout() self.toggle_editor_type_checkbox = QCheckBox() self.toggle_editor_type_checkbox.setText("Spawns/Terrain") self.toggle_editor_type_checkbox.setChecked(True) self.toggle_editor_type_checkbox.stateChanged.connect( self._on_mode_change_requested) self.toggle_coordinate_type_checkbox = QCheckBox() self.toggle_coordinate_type_checkbox.setText( "Coordinate (1)/Coordinate (2)") self.toggle_coordinate_type_checkbox.setChecked(True) self.toggle_coordinate_type_checkbox.stateChanged.connect( self._on_coordinate_change_requested) self.tree_view = QTreeView() left_panel_layout.addWidget(self.toggle_editor_type_checkbox) left_panel_layout.addWidget(self.toggle_coordinate_type_checkbox) left_panel_layout.addWidget(self.tree_view) left_panel_container.setLayout(left_panel_layout) self.grid = FE14MapGrid() self.dispos_scroll, self.dispos_form = PropertyForm.create_with_scroll( dispo.SPAWN_TEMPLATE) self.terrain_form, self.terrain_persistent_editors, self.tile_form = _create_terrain_form( ) self.organizer = QSplitter() self.organizer.addWidget(left_panel_container) self.organizer.addWidget(self.grid) self.organizer.addWidget(self.dispos_scroll) main_layout = QVBoxLayout(self) main_layout.addWidget(self.organizer) self.setLayout(main_layout) self.add_faction_shortcut = QShortcut(QKeySequence("Ctrl+F"), self) self.add_item_shortcut = QShortcut(QKeySequence("Ctrl+N"), self) self.grid.focused_spawn_changed.connect(self._on_focused_spawn_changed) self.add_faction_shortcut.activated.connect( self._on_add_faction_requested) self.add_item_shortcut.activated.connect(self._on_add_item_requested) self.dispos_form.editors["PID"].editingFinished.connect( self._on_pid_field_changed) self.dispos_form.editors["Team"].currentIndexChanged.connect( self._on_team_field_changed) self.dispos_form.editors["Coordinate (1)"].textChanged.connect( self._on_coordinate_1_field_changed) self.dispos_form.editors["Coordinate (2)"].textChanged.connect( self._on_coordinate_2_field_changed) def update_chapter_data(self, chapter_data): self.chapter_data = chapter_data if self.chapter_data and self.chapter_data.dispos and self.chapter_data.terrain: self.setEnabled(True) self.dispos = self.chapter_data.dispos self.dispos_model = DisposModel(self.dispos) self.terrain = self.chapter_data.terrain self.tiles_model = TilesModel(self.terrain.tiles) if self.terrain_mode: self.tree_view.setModel(self.tiles_model) else: self.tree_view.setModel(self.dispos_model) self.tree_view.selectionModel().currentChanged.connect( self._on_tree_selection_changed) self.grid.set_chapter_data(chapter_data) self._update_forms(self.terrain, None, None) else: self.setEnabled(False) self.grid.clear() def _update_forms(self, terrain_target, tile_target, dispos_target): self.dispos_form.update_target(dispos_target) self.tile_form.update_target(tile_target) for editor in self.terrain_persistent_editors: editor.update_target(terrain_target) def _on_focused_spawn_changed(self, spawn): self.dispos_form.update_target(spawn) def _on_mode_change_requested(self, state): self.organizer.widget(2).setParent(None) self.terrain_mode = state != QtGui.Qt.Checked if not self.terrain_mode: self.grid.transition_to_dispos_mode() self.organizer.addWidget(self.dispos_scroll) self.tree_view.setModel(self.dispos_model) else: self.grid.transition_to_terrain_mode() self.organizer.addWidget(self.terrain_form) self.tree_view.setModel(self.tiles_model) self.tree_view.selectionModel().currentChanged.connect( self._on_tree_selection_changed) def _on_coordinate_change_requested(self, _state): self.grid.toggle_coordinate_key() def _on_tree_selection_changed(self, index: QModelIndex, _previous): data = index.data(QtCore.Qt.UserRole) if self.terrain_mode: self.grid.selected_tile = data self.tile_form.update_target(data) else: if type(data) == PropertyContainer: self.grid.select_spawn(data) self.selected_faction = None else: self.selected_faction = data def _on_add_faction_requested(self): if self.dispos_model: (faction_name, ok) = QInputDialog.getText(self, "Enter a faction name.", "Name:") if ok: self.dispos_model.add_faction(faction_name) def _on_add_item_requested(self): if not self.chapter_data or (not self.terrain_mode and not self.selected_faction): return if self.terrain_mode: self.tiles_model.add_tile() else: self.dispos_model.add_spawn_to_faction(self.selected_faction) spawn = self.selected_faction.spawns[-1] self.grid.add_spawn_to_map(spawn) def _on_coordinate_1_field_changed(self, _text): new_position = self._parse_coordinate_field( self.dispos_form.editors["Coordinate (1)"]) self.grid.update_focused_spawn_position(new_position, "Coordinate (1)") def _on_coordinate_2_field_changed(self, _text): new_position = self._parse_coordinate_field( self.dispos_form.editors["Coordinate (2)"]) self.grid.update_focused_spawn_position(new_position, "Coordinate (2)") @staticmethod def _parse_coordinate_field(field): split_text = field.displayText().split() result = [] for entry in split_text: result.append(int(entry, 16)) return result def _on_team_field_changed(self): if self.chapter_data and self.chapter_data.dispos: self.grid.update_team_for_focused_spawn() def _on_pid_field_changed(self): self.dispos_model.refresh_spawn(self.grid.selected_spawns[-1])
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()