class Window(QWidget): def __init__(self): super(Window, self).__init__() self.proxyModel = SortFilterProxyModel() self.proxyModel.setDynamicSortFilter(True) self.sourceGroupBox = QGroupBox("Original Model") self.proxyGroupBox = QGroupBox("Sorted/Filtered Model") self.sourceView = QTreeView() self.sourceView.setRootIsDecorated(False) self.sourceView.setAlternatingRowColors(True) self.proxyView = QTreeView() self.proxyView.setRootIsDecorated(False) self.proxyView.setAlternatingRowColors(True) self.proxyView.setModel(self.proxyModel) self.proxyView.setSortingEnabled(True) self.sortCaseSensitivityCheckBox = QCheckBox("Case sensitive sorting") self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter") self.filterPatternLineEdit = QLineEdit() self.filterPatternLabel = QLabel("&Filter pattern:") self.filterPatternLabel.setBuddy(self.filterPatternLineEdit) self.filterSyntaxComboBox = QComboBox() self.filterSyntaxComboBox.addItem("Regular expression", QRegExp.RegExp) self.filterSyntaxComboBox.addItem("Wildcard", QRegExp.Wildcard) self.filterSyntaxComboBox.addItem("Fixed string", QRegExp.FixedString) self.filterSyntaxLabel = QLabel("Filter &syntax:") self.filterSyntaxLabel.setBuddy(self.filterSyntaxComboBox) self.filterColumnComboBox = QComboBox() self.filterColumnComboBox.addItem("Subject") self.filterColumnComboBox.addItem("Sender") self.filterColumnComboBox.addItem("Date") self.filterColumnLabel = QLabel("Filter &column:") self.filterColumnLabel.setBuddy(self.filterColumnComboBox) self.filterPatternLineEdit.textChanged.connect( self.filterRegExpChanged) self.filterSyntaxComboBox.currentIndexChanged.connect( self.filterRegExpChanged) self.filterColumnComboBox.currentIndexChanged.connect( self.filterColumnChanged) self.filterCaseSensitivityCheckBox.toggled.connect( self.filterRegExpChanged) self.sortCaseSensitivityCheckBox.toggled.connect(self.sortChanged) sourceLayout = QHBoxLayout() sourceLayout.addWidget(self.sourceView) self.sourceGroupBox.setLayout(sourceLayout) proxyLayout = QGridLayout() proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3) proxyLayout.addWidget(self.filterPatternLabel, 1, 0) proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1, 1, 2) proxyLayout.addWidget(self.filterSyntaxLabel, 2, 0) proxyLayout.addWidget(self.filterSyntaxComboBox, 2, 1, 1, 2) proxyLayout.addWidget(self.filterColumnLabel, 3, 0) proxyLayout.addWidget(self.filterColumnComboBox, 3, 1, 1, 2) proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 4, 0, 1, 2) proxyLayout.addWidget(self.sortCaseSensitivityCheckBox, 4, 2) self.proxyGroupBox.setLayout(proxyLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(self.sourceGroupBox) mainLayout.addWidget(self.proxyGroupBox) self.setLayout(mainLayout) self.setWindowTitle("Basic Sort/Filter Model") self.resize(500, 450) self.proxyView.sortByColumn(SENDER, Qt.AscendingOrder) self.filterColumnComboBox.setCurrentIndex(SENDER) self.filterPatternLineEdit.setText("Andy|Grace") self.filterCaseSensitivityCheckBox.setChecked(True) self.sortCaseSensitivityCheckBox.setChecked(True) def setSourceModel(self, model): self.proxyModel.setSourceModel(model) self.sourceView.setModel(model) def filterRegExpChanged(self): syntax_nr = self.filterSyntaxComboBox.itemData( self.filterSyntaxComboBox.currentIndex()) syntax = QRegExp.PatternSyntax(syntax_nr) if self.filterCaseSensitivityCheckBox.isChecked(): caseSensitivity = Qt.CaseSensitive else: caseSensitivity = Qt.CaseInsensitive regExp = QRegExp(self.filterPatternLineEdit.text(), caseSensitivity, syntax) self.proxyModel.setFilterRegExp(regExp) def filterColumnChanged(self): self.proxyModel.setFilterKeyColumn( self.filterColumnComboBox.currentIndex()) def sortChanged(self): if self.sortCaseSensitivityCheckBox.isChecked(): caseSensitivity = Qt.CaseSensitive else: caseSensitivity = Qt.CaseInsensitive self.proxyModel.setSortCaseSensitivity(caseSensitivity)
def create_ui(self): """Setup main UI elements, dock widgets, UI-related elements, etc. """ log.debug('Loading UI') # Undo Stack self.undo_stack = QUndoStack(self) self.undo_stack.setUndoLimit(100) # Object navigation history self.obj_history = deque([], config.MAX_OBJ_HISTORY) app = QApplication.instance() base_font = QFont() base_font.fromString(self.prefs['base_font']) app.setFont(base_font) # Object class table widget # classTable = QTableView(self) classTable = classtable.TableView(self) classTable.setObjectName("classTable") classTable.setAlternatingRowColors(True) classTable.setFrameShape(QFrame.StyledPanel) classTable_font = QFont() classTable_font.fromString(self.prefs['class_table_font']) classTable.setFont(classTable_font) fm = classTable.fontMetrics() classTable.setWordWrap(True) classTable.setEditTriggers(QAbstractItemView.EditKeyPressed | QAbstractItemView.DoubleClicked | QAbstractItemView.AnyKeyPressed | QAbstractItemView.SelectedClicked) # classTable.horizontalHeader().setMovable(True) # classTable.verticalHeader().setMovable(False) classTable.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) classTable.verticalHeader().setSectionResizeMode(QHeaderView.Interactive) classTable.horizontalHeader().setDefaultSectionSize(self.prefs['default_column_width']) classTable.verticalHeader().setDefaultSectionSize(fm.height() + 0) classTable.setSelectionMode(QAbstractItemView.ExtendedSelection) classTable.setContextMenuPolicy(Qt.CustomContextMenu) classTable.customContextMenuRequested.connect(self.custom_table_context_menu) # Create table model and proxy layers for transposing and filtering self.classTableModel = classtable.IDFObjectTableModel(classTable) self.transposeableModel = classtable.TransposeProxyModel(self.classTableModel) self.transposeableModel.setSourceModel(self.classTableModel) self.sortableModel = classtable.SortFilterProxyModel(self.transposeableModel) self.sortableModel.setSourceModel(self.transposeableModel) # Assign model to table (enable sorting FIRST) # table.setSortingEnabled(True) # Disable for now, CRUD actions won't work! classTable.setModel(self.sortableModel) # Connect some signals selection_model = classTable.selectionModel() selection_model.selectionChanged.connect(self.table_selection_changed) scroll_bar = classTable.verticalScrollBar() scroll_bar.valueChanged.connect(self.scroll_changed) # These are currently broken # classTable.horizontalHeader().sectionMoved.connect(self.moveObject) # classTable.verticalHeader().sectionMoved.connect(self.moveObject) # Object class tree widget classTreeDockWidget = QDockWidget("Object Classes and Counts", self) classTreeDockWidget.setObjectName("classTreeDockWidget") classTreeDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) classTree = QTreeView(classTreeDockWidget) classTree.setUniformRowHeights(True) classTree.setAllColumnsShowFocus(True) classTree.setRootIsDecorated(False) classTree.setExpandsOnDoubleClick(True) classTree.setIndentation(15) classTree.setAnimated(True) classTree_font = QFont() classTree_font.fromString(self.prefs['class_tree_font']) classTree.setFont(classTree_font) classTree.setAlternatingRowColors(True) classTree.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) palette = classTree.palette() palette.setColor(QPalette.Highlight, Qt.darkCyan) classTree.setPalette(palette) class_tree_window = QWidget(classTreeDockWidget) class_tree_dock_layout_v = QVBoxLayout() class_tree_dock_layout_h = QHBoxLayout() class_tree_dock_layout_v.setContentsMargins(0, 8, 0, 0) class_tree_dock_layout_h.setContentsMargins(0, 0, 0, 0) class_tree_filter_edit = QLineEdit(classTreeDockWidget) class_tree_filter_edit.setPlaceholderText("Filter Classes") class_tree_filter_edit.textChanged.connect(self.treeFilterRegExpChanged) class_tree_filter_cancel = QPushButton("Clear", classTreeDockWidget) class_tree_filter_cancel.setMaximumWidth(45) class_tree_filter_cancel.clicked.connect(self.clearTreeFilterClicked) class_tree_dock_layout_h.addWidget(class_tree_filter_edit) class_tree_dock_layout_h.addWidget(class_tree_filter_cancel) class_tree_dock_layout_v.addLayout(class_tree_dock_layout_h) class_tree_dock_layout_v.addWidget(classTree) class_tree_window.setLayout(class_tree_dock_layout_v) classTreeDockWidget.setWidget(class_tree_window) classTreeDockWidget.setContentsMargins(0,0,0,0) # Comments widget commentDockWidget = QDockWidget("Comments", self) commentDockWidget.setObjectName("commentDockWidget") commentDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) commentView = UndoRedoTextEdit(commentDockWidget, self) commentView.setLineWrapMode(QTextEdit.FixedColumnWidth) commentView.setLineWrapColumnOrWidth(499) commentView.setFrameShape(QFrame.StyledPanel) commentView_font = QFont() commentView_font.fromString(self.prefs['comments_font']) commentView.setFont(commentView_font) commentDockWidget.setWidget(commentView) # Info and help widget infoDockWidget = QDockWidget("Info", self) infoDockWidget.setObjectName("infoDockWidget") infoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) infoView = QTextEdit(infoDockWidget) infoView.setFrameShape(QFrame.StyledPanel) infoView.setReadOnly(True) infoDockWidget.setWidget(infoView) # Node list and jump menu widget refDockWidget = QDockWidget("Field References", self) refDockWidget.setObjectName("refDockWidget") refDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) ref_model = reftree.ReferenceTreeModel(None, refDockWidget) refView = QTreeView(refDockWidget) refView.setModel(ref_model) refView.setUniformRowHeights(True) refView.setRootIsDecorated(False) refView.setIndentation(15) refView.setColumnWidth(0, 160) refView.setFrameShape(QFrame.StyledPanel) refDockWidget.setWidget(refView) refView.doubleClicked.connect(self.ref_tree_double_clicked) # Logging and debugging widget logDockWidget = QDockWidget("Log Viewer", self) logDockWidget.setObjectName("logDockWidget") logDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) logView = QPlainTextEdit(logDockWidget) logView.setLineWrapMode(QPlainTextEdit.NoWrap) logView.setReadOnly(True) logView_font = QFont() logView_font.fromString(self.prefs['base_font']) logView.setFont(logView_font) logView.ensureCursorVisible() logDockWidget.setWidget(logView) # Undo view widget undoDockWidget = QDockWidget("Undo History", self) undoDockWidget.setObjectName("undoDockWidget") undoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) undoView = QUndoView(self.undo_stack) undoDockWidget.setWidget(undoView) # Define corner docking behaviour self.setDockNestingEnabled(True) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # Assign main widget and dock widgets to QMainWindow self.setCentralWidget(classTable) self.addDockWidget(Qt.LeftDockWidgetArea, classTreeDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, commentDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, infoDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, refDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, logDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, undoDockWidget) # Store widgets for access by other objects self.classTable = classTable self.commentView = commentView self.infoView = infoView self.classTree = classTree self.logView = logView self.undoView = undoView self.refView = refView self.filterTreeBox = class_tree_filter_edit # Store docks for access by other objects self.commentDockWidget = commentDockWidget self.infoDockWidget = infoDockWidget self.classTreeDockWidget = classTreeDockWidget self.logDockWidget = logDockWidget self.undoDockWidget = undoDockWidget self.refDockWidget = refDockWidget # Perform other UI-related initialization tasks self.center() self.setUnifiedTitleAndToolBarOnMac(True) self.setWindowIcon(QIcon(':/images/logo.png')) # Status bar setup self.statusBar().showMessage('Status: Ready') self.unitsLabel = QLabel() self.unitsLabel.setAlignment(Qt.AlignCenter) self.unitsLabel.setMinimumSize(self.unitsLabel.sizeHint()) self.unitsLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.unitsLabel) self.pathLabel = QLabel() self.pathLabel.setAlignment(Qt.AlignCenter) self.pathLabel.setMinimumSize(self.pathLabel.sizeHint()) self.pathLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.pathLabel) self.versionLabel = QLabel() self.versionLabel.setAlignment(Qt.AlignCenter) self.versionLabel.setMinimumSize(self.versionLabel.sizeHint()) self.versionLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.versionLabel) self.progressBarIDF = QProgressBar() self.progressBarIDF.setAlignment(Qt.AlignCenter) self.progressBarIDF.setMaximumWidth(200) self.statusBar().addPermanentWidget(self.progressBarIDF) self.clipboard = QApplication.instance().clipboard() self.obj_clipboard = [] self.setStyleSheet(""" QToolTip { background-color: gray; color: white; border: black solid 1px } # QMenu { # background-color: rgbf(0.949020, 0.945098, 0.941176); # color: rgb(255,255,255); # } # QMenu::item::selected { # background-color: rgbf(0.949020, 0.945098, 0.941176); # } """)
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 Window(QWidget): def __init__(self): super(Window, self).__init__() self.proxyModel = QSortFilterProxyModel() self.proxyModel.setDynamicSortFilter(True) self.sourceGroupBox = QGroupBox("Original Model") self.proxyGroupBox = QGroupBox("Sorted/Filtered Model") self.sourceView = QTreeView() self.sourceView.setRootIsDecorated(False) self.sourceView.setAlternatingRowColors(True) self.proxyView = QTreeView() self.proxyView.setRootIsDecorated(False) self.proxyView.setAlternatingRowColors(True) self.proxyView.setModel(self.proxyModel) self.proxyView.setSortingEnabled(True) self.sortCaseSensitivityCheckBox = QCheckBox("Case sensitive sorting") self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter") self.filterPatternLineEdit = QLineEdit() self.filterPatternLineEdit.setClearButtonEnabled(True) self.filterPatternLabel = QLabel("&Filter pattern:") self.filterPatternLabel.setBuddy(self.filterPatternLineEdit) self.filterSyntaxComboBox = QComboBox() self.filterSyntaxComboBox.addItem("Regular expression", REGULAR_EXPRESSION) self.filterSyntaxComboBox.addItem("Wildcard", WILDCARD) self.filterSyntaxComboBox.addItem("Fixed string", FIXED_STRING) self.filterSyntaxLabel = QLabel("Filter &syntax:") self.filterSyntaxLabel.setBuddy(self.filterSyntaxComboBox) self.filterColumnComboBox = QComboBox() self.filterColumnComboBox.addItem("Subject") self.filterColumnComboBox.addItem("Sender") self.filterColumnComboBox.addItem("Date") self.filterColumnLabel = QLabel("Filter &column:") self.filterColumnLabel.setBuddy(self.filterColumnComboBox) self.filterPatternLineEdit.textChanged.connect(self.filterRegExpChanged) self.filterSyntaxComboBox.currentIndexChanged.connect(self.filterRegExpChanged) self.filterColumnComboBox.currentIndexChanged.connect(self.filterColumnChanged) self.filterCaseSensitivityCheckBox.toggled.connect(self.filterRegExpChanged) self.sortCaseSensitivityCheckBox.toggled.connect(self.sortChanged) sourceLayout = QHBoxLayout() sourceLayout.addWidget(self.sourceView) self.sourceGroupBox.setLayout(sourceLayout) proxyLayout = QGridLayout() proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3) proxyLayout.addWidget(self.filterPatternLabel, 1, 0) proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1, 1, 2) proxyLayout.addWidget(self.filterSyntaxLabel, 2, 0) proxyLayout.addWidget(self.filterSyntaxComboBox, 2, 1, 1, 2) proxyLayout.addWidget(self.filterColumnLabel, 3, 0) proxyLayout.addWidget(self.filterColumnComboBox, 3, 1, 1, 2) proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 4, 0, 1, 2) proxyLayout.addWidget(self.sortCaseSensitivityCheckBox, 4, 2) self.proxyGroupBox.setLayout(proxyLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(self.sourceGroupBox) mainLayout.addWidget(self.proxyGroupBox) self.setLayout(mainLayout) self.setWindowTitle("Basic Sort/Filter Model") self.resize(500, 450) self.proxyView.sortByColumn(1, Qt.AscendingOrder) self.filterColumnComboBox.setCurrentIndex(1) self.filterPatternLineEdit.setText("Andy|Grace") self.filterCaseSensitivityCheckBox.setChecked(True) self.sortCaseSensitivityCheckBox.setChecked(True) def setSourceModel(self, model): self.proxyModel.setSourceModel(model) self.sourceView.setModel(model) def filterRegExpChanged(self): syntax_nr = self.filterSyntaxComboBox.currentData() pattern = self.filterPatternLineEdit.text() if syntax_nr == WILDCARD: pattern = QRegularExpression.wildcardToRegularExpression(pattern) elif syntax_nr == FIXED_STRING: pattern = QRegularExpression.escape(pattern) regExp = QRegularExpression(pattern) if not self.filterCaseSensitivityCheckBox.isChecked(): options = regExp.patternOptions() options |= QRegularExpression.CaseInsensitiveOption regExp.setPatternOptions(options) self.proxyModel.setFilterRegularExpression(regExp) def filterColumnChanged(self): self.proxyModel.setFilterKeyColumn(self.filterColumnComboBox.currentIndex()) def sortChanged(self): if self.sortCaseSensitivityCheckBox.isChecked(): caseSensitivity = Qt.CaseSensitive else: caseSensitivity = Qt.CaseInsensitive self.proxyModel.setSortCaseSensitivity(caseSensitivity)
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)