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 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 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 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)
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 SearchReplaceDialog(QDialog): """Search & Replace window """ def __init__(self, parent, initial_query=None): """Initialize the search window """ super(SearchReplaceDialog, self).__init__(parent) self.parent = parent self.prefs = parent.prefs self.items_checked = 0 self.search_button = QPushButton('Search') self.search_text = MySearchField(self.search_button) self.search_text.setPlaceholderText("Enter search query here") self.search_button.clicked.connect(self.submit_search) input_layout = QHBoxLayout() input_layout.addWidget(self.search_text) input_layout.addWidget(self.search_button) self.results_tree = QTreeView() self.results_tree.setRootIsDecorated(False) self.results_tree.setAlternatingRowColors(True) self.results_tree.setAllColumnsShowFocus(True) self.results_tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.results_tree.setSelectionMode(QAbstractItemView.SingleSelection) self.results_tree.doubleClicked.connect(self.go_to_object) self.whole_field_checkbox = QCheckBox("Whole Field Only", self) self.advanced_search_checkbox = QCheckBox("Advanced Search", self) self.advanced_search_checkbox.stateChanged.connect( self.advanced_search_checked) self.ignore_geometry_checkbox = QCheckBox("Ignore Geometry", self) self.go_button = QPushButton('Go') self.go_button.clicked.connect(self.go_to_object) checks_layout = QHBoxLayout() checks_layout.addWidget(self.whole_field_checkbox) checks_layout.addWidget(self.advanced_search_checkbox) checks_layout.addWidget(self.ignore_geometry_checkbox) checks_layout.addStretch() checks_layout.addWidget(self.go_button) self.query_label = QLabel("Query:") self.query_label.setEnabled(False) self.query_text = QLineEdit() self.query_text.setEnabled(True) self.query_text.setReadOnly(True) self.query_text.setFrame(False) self.query_text.setStyleSheet("""QLineEdit { background-color: LightGray; color: white; } """) query_layout = QHBoxLayout() query_layout.addWidget(self.query_label) query_layout.addWidget(self.query_text) self.select_label = QLabel("Select:") self.select_all_button = QPushButton("All") self.select_none_button = QPushButton("None") self.select_invert_button = QPushButton("Invert") self.delete_button = QPushButton("Delete Objects") self.select_all_button.clicked.connect(self.select_all_clicked) self.select_none_button.clicked.connect(self.select_none_clicked) self.select_invert_button.clicked.connect(self.select_invert_clicked) self.delete_button.clicked.connect(self.delete_button_clicked) self.delete_button.setEnabled(False) selection_layout = QHBoxLayout() selection_layout.addWidget(self.select_label) selection_layout.addWidget(self.select_all_button) selection_layout.addWidget(self.select_none_button) selection_layout.addWidget(self.select_invert_button) selection_layout.addStretch() selection_layout.addWidget(self.delete_button) self.replace_with_text = QLineEdit() self.replace_with_label = QLabel("Replace With:") self.replace_button = QPushButton("Replace") self.replace_button.clicked.connect(self.replace_button_clicked) self.replace_button.setEnabled(False) replace_layout = QHBoxLayout() replace_layout.addWidget(self.replace_with_label) replace_layout.addWidget(self.replace_with_text) replace_layout.addWidget(self.replace_button) layout = QVBoxLayout() layout.addLayout(input_layout) layout.addLayout(checks_layout) layout.addLayout(query_layout) layout.addWidget(self.results_tree) layout.addLayout(selection_layout) layout.addLayout(replace_layout) self.resize(650, 450) self.setLayout(layout) self.setWindowTitle("IDF+ Search & Replace") self.search_text.setFocus() self.setTabOrder(self.search_text, self.search_button) self.setTabOrder(self.search_button, self.whole_field_checkbox) self.setTabOrder(self.whole_field_checkbox, self.advanced_search_checkbox) self.setTabOrder(self.advanced_search_checkbox, self.select_all_button) self.setTabOrder(self.select_all_button, self.select_none_button) self.setTabOrder(self.select_none_button, self.select_invert_button) self.setTabOrder(self.select_invert_button, self.replace_with_text) self.setTabOrder(self.replace_with_text, self.replace_button) self.setTabOrder(self.replace_button, self.search_text) self.results_tree.setModel(self.create_results_model([])) self.results_tree.setColumnHidden(2, True) if initial_query is not None: self.search_text.setText(initial_query) self.search_button.click() def create_results_model(self, results): def add_result_row(row_model, value, obj_class, uuid): row_model.insertRow(0) row_model.setData(model.index(0, 0), value) row_model.setData(model.index(0, 1), obj_class) row_model.setData(model.index(0, 2), uuid) item_0 = model.itemFromIndex(model.index(0, 0)) item_0.setCheckState(Qt.Unchecked) item_0.setFlags(item_0.flags() | Qt.ItemIsUserCheckable) item_0.setEditable(False) item_1 = model.itemFromIndex(model.index(0, 1)) item_1.setEditable(False) model = QStandardItemModel(0, 3, self) model.setHeaderData(0, Qt.Horizontal, "Value") model.setHeaderData(1, Qt.Horizontal, "Class") model.setHeaderData(2, Qt.Horizontal, "UUID") for hit in results: add_result_row(model, hit['value'], hit['obj_class_display'], hit['uuid']) return model def submit_search(self): """Submits a search based on the current query """ user_query = self.search_text.text() idf = self.parent.idf if not user_query or len(user_query) < 2 or not idf: return [], "" results, my_query = idf.search( user_query, self.whole_field_checkbox.isChecked(), self.advanced_search_checkbox.isChecked(), self.ignore_geometry_checkbox.isChecked()) self.query_text.setText(str(my_query)) self.results_tree.setModel(self.create_results_model(results)) self.results_tree.model().itemChanged.connect(self.item_checked) # self.results_tree.setColumnHidden(2, True) self.results_tree.resizeColumnToContents(0) self.results_tree.resizeColumnToContents(1) self.results_tree.setSortingEnabled(True) def item_checked(self, item): if item.checkState() == Qt.Checked: self.items_checked += 1 else: self.items_checked -= 1 if self.items_checked > 0: self.delete_button.setEnabled(True) self.replace_button.setEnabled(True) else: self.delete_button.setEnabled(False) self.replace_button.setEnabled(False) def select_all_clicked(self): model = self.results_tree.model() result_count = model.rowCount() for i in range(result_count): model.itemFromIndex(model.index(i, 0)).setCheckState(Qt.Checked) def select_none_clicked(self): model = self.results_tree.model() result_count = model.rowCount() for i in range(result_count): model.itemFromIndex(model.index(i, 0)).setCheckState(Qt.Unchecked) def select_invert_clicked(self): model = self.results_tree.model() result_count = model.rowCount() for i in range(result_count): item = model.itemFromIndex(model.index(i, 0)) if item.checkState() == Qt.Checked: new_state = Qt.Unchecked else: new_state = Qt.Checked item.setCheckState(new_state) def delete_button_clicked(self): model = self.results_tree.model() result_count = model.rowCount() if result_count <= 0 or self.items_checked <= 0: return question = "Are you sure you want to perform this deletion?\n" \ "Undo is currently NOT supported for this operation." response = self.confirm_action(question) if response is not True: return for i in range(result_count): item_0 = model.itemFromIndex(model.index(i, 0)) item_2 = model.itemFromIndex(model.index(i, 2)) if item_0.checkState() != Qt.Checked: continue field = self.parent.idf.field_by_uuid(item_2.text()) obj = field._outer obj_class = self.parent.idf.idf_objects(obj.obj_class) try: index = obj_class.index(obj) self.parent.idf.remove_objects(obj.obj_class, index, index + 1) except ValueError: pass # already deleted self.parent.set_dirty(True) self.submit_search() self.parent.load_table_view(self.parent.current_obj_class) QMessageBox.information(self, "Delete Action", "Deletion Complete!") def advanced_search_checked(self): if self.advanced_search_checkbox.isChecked(): self.whole_field_checkbox.setEnabled(False) self.whole_field_checkbox.setChecked(True) self.ignore_geometry_checkbox.setEnabled(False) self.ignore_geometry_checkbox.setChecked(False) else: self.whole_field_checkbox.setEnabled(True) self.whole_field_checkbox.setChecked(False) self.ignore_geometry_checkbox.setEnabled(True) self.ignore_geometry_checkbox.setChecked(False) def replace_button_clicked(self): search_text = self.search_text.text() replace_with_text = self.replace_with_text.text() if not search_text: return model = self.results_tree.model() result_count = model.rowCount() if result_count <= 0 or self.items_checked <= 0: return question = "Are you sure you want to perform this replacement?\n" \ "Undo is currently NOT supported for this operation." response = self.confirm_action(question) if response is not True: return for i in range(result_count): item_0 = model.itemFromIndex(model.index(i, 0)) item_2 = model.itemFromIndex(model.index(i, 2)) if item_0.checkState() != Qt.Checked: continue field = self.parent.idf.field_by_uuid(item_2.text()) if self.whole_field_checkbox.isChecked( ) or self.advanced_search_checkbox.isChecked(): field.value = replace_with_text else: regex = re.compile(re.escape(search_text), re.IGNORECASE) field.value = regex.sub(replace_with_text, field.value) self.parent.set_dirty(True) self.submit_search() self.parent.load_table_view(self.parent.current_obj_class) QMessageBox.information(self, "Replacement", "Replacement Complete!") def confirm_action(self, question): """Confirm user wants to perform action """ flags = QMessageBox.StandardButton.Yes flags |= QMessageBox.StandardButton.No response = QMessageBox.question(self, "Are you sure?", question, flags) if response == QMessageBox.Yes: return True elif QMessageBox.No: return False else: return False def go_to_object(self, index=None): if index is None: selected = self.results_tree.selectedIndexes() if not selected: return index = selected[0] model = self.results_tree.model() item = model.itemFromIndex(model.index(index.row(), 2)) field = self.parent.idf.field_by_uuid(item.text()) self.parent.activateWindow() self.parent.jump_to_field(field)