Skip to content

Commit 502b7d6

Browse files
committed
feat: Add Filter, Collapse and Expand to "Classes" window
1 parent b0a5412 commit 502b7d6

File tree

5 files changed

+54
-2
lines changed

5 files changed

+54
-2
lines changed

HexRaysPyTools/Core/Classes.py

+27
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,12 @@ def set_first_argument_type(self, class_name):
327327
if 0 in self.vtables:
328328
self.vtables[0].set_first_argument_type(class_name)
329329

330+
def has_function(self, regexp):
331+
for vtable in self.vtables.values():
332+
if filter(lambda func: regexp.indexIn(func.name) >= 0, vtable.virtual_functions):
333+
return True
334+
return False
335+
330336
def data(self, column):
331337
if column == 0:
332338
return self.name
@@ -504,3 +510,24 @@ def commit(self):
504510
def open_function(self, index):
505511
if index.column() == 2:
506512
index.internalPointer().item.open_function()
513+
514+
515+
class ProxyModel(QtGui.QSortFilterProxyModel):
516+
def __init__(self):
517+
super(ProxyModel, self).__init__()
518+
self.filter_by_function = False
519+
520+
def set_regexp_filter(self, regexp):
521+
if regexp and regexp[0] == '!':
522+
self.filter_by_function = True
523+
self.setFilterRegExp(regexp[1:])
524+
else:
525+
self.filter_by_function = False
526+
self.setFilterRegExp(regexp)
527+
528+
def filterAcceptsRow(self, row, parent):
529+
if not parent.isValid() and self.filterRegExp():
530+
if self.filter_by_function:
531+
return self.sourceModel().tree_data[row].item.has_function(self.filterRegExp())
532+
return self.filterRegExp().indexIn(self.sourceModel().tree_data[row].item.class_name) >= 0
533+
return True

HexRaysPyTools/Cute.py

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
QtGui.QTableView = QtWidgets.QTableView
4444
QtGui.QAction = QtWidgets.QAction
4545
QtGui.QMenu = QtWidgets.QMenu
46+
QtGui.QLabel = QtWidgets.QLabel
4647

4748
QtGui.QHeaderView = QtWidgets.QHeaderView
4849
QtGui.QAbstractItemView = QtWidgets.QAbstractItemView
@@ -52,6 +53,8 @@
5253
QtGui.QPushButton = QtWidgets.QPushButton
5354
QtGui.QSpacerItem = QtWidgets.QSpacerItem
5455
QtGui.QSizePolicy = QtWidgets.QSizePolicy
56+
QtGui.QSortFilterProxyModel = QtCore.QSortFilterProxyModel
57+
QtGui.QLineEdit = QtWidgets.QLineEdit
5558

5659
use_qt5 = True
5760
else:

HexRaysPyTools/Forms.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ def __init__(self):
140140
super(ClassViewer, self).__init__()
141141
self.parent = None
142142
self.class_tree = QtGui.QTreeView()
143+
self.line_edit_filter = QtGui.QLineEdit()
143144

145+
self.action_collapse = QtGui.QAction("Collapse all", self.class_tree)
146+
self.action_expand = QtGui.QAction("Expand all", self.class_tree)
144147
self.action_set_arg = QtGui.QAction("Set First Argument Type", self.class_tree)
145148
self.action_rollback = QtGui.QAction("Rollback", self.class_tree)
146149
self.action_refresh = QtGui.QAction("Refresh", self.class_tree)
@@ -163,14 +166,25 @@ def init_ui(self):
163166
"QHeaderView::section {background-color: transparent; border: 1px solid;}"
164167
)
165168

169+
hbox_layout = QtGui.QHBoxLayout()
170+
label_filter = QtGui.QLabel("&Filter:")
171+
label_filter.setBuddy(self.line_edit_filter)
172+
hbox_layout.addWidget(label_filter)
173+
hbox_layout.addWidget(self.line_edit_filter)
174+
166175
class_model = Core.Classes.TreeModel()
167-
self.class_tree.setModel(class_model)
176+
proxy_model = Core.Classes.ProxyModel()
177+
proxy_model.setSourceModel(class_model)
178+
proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
179+
self.class_tree.setModel(proxy_model)
168180
self.class_tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
169181
self.class_tree.expandAll()
170182
self.class_tree.header().setStretchLastSection(True)
171183
self.class_tree.header().setSectionResizeMode(QtGui.QHeaderView.ResizeToContents)
172184
self.class_tree.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
173185

186+
self.action_collapse.triggered.connect(self.class_tree.collapseAll)
187+
self.action_expand.triggered.connect(self.class_tree.expandAll)
174188
self.action_set_arg.triggered.connect(
175189
lambda: class_model.set_first_argument_type(self.class_tree.selectedIndexes())
176190
)
@@ -179,17 +193,22 @@ def init_ui(self):
179193
self.action_commit.triggered.connect(lambda: class_model.commit())
180194
class_model.refreshed.connect(self.class_tree.expandAll)
181195

196+
self.menu.addAction(self.action_collapse)
197+
self.menu.addAction(self.action_expand)
182198
self.menu.addAction(self.action_refresh)
183199
self.menu.addAction(self.action_set_arg)
184200
self.menu.addAction(self.action_rollback)
185201
self.menu.addAction(self.action_commit)
186202

187203
vertical_box = QtGui.QVBoxLayout()
188204
vertical_box.addWidget(self.class_tree)
205+
vertical_box.addLayout(hbox_layout)
189206
self.parent.setLayout(vertical_box)
190207

191208
self.class_tree.activated[QtCore.QModelIndex].connect(class_model.open_function)
192209
self.class_tree.customContextMenuRequested[QtCore.QPoint].connect(self.show_menu)
210+
self.line_edit_filter.textChanged[str].connect(proxy_model.set_regexp_filter)
211+
# proxy_model.rowsInserted[object].connect(lambda: self.class_tree.setExpanded(object, True))
193212

194213
def OnClose(self, form):
195214
pass

Img/classes.JPG

2.99 KB
Loading

readme.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,14 @@ Also can be found at _View->Open Subview->Classes_. Helps to manage classes (str
150150
##### !! Better to rename all functions before debugging because Ida can mess up with default names and information in virtual tables will be inconsistent
151151
Class, virtual table and functions names are editable. Also function's declaration can be edited. After edit, altered items change font to _italic_. Right click opens following menu options:
152152

153+
* Expand All / Collapse All
153154
* Refresh - clear all and rescan local types for information again
154155
* Rollback - undo changes
155-
* Commit - apply changes. Functions will be renamed and recasted both in virtual tables in Local Types and dissasmbly code.
156+
* Commit - apply changes. Functions will be renamed and recasted both in virtual tables in Local Types and dissasembly code.
156157
* Set First Argument type - allows to select first argument for function among all classes. If right click was used on class name, than it's type will be automatically applied to virtual table at offset 0
157158

159+
You can also filter classes using Regexp either by class_name or by existence of specific functions. Just input expression in line edit for filtering by class_name or prepend it with "!" to filter by function name.
160+
158161

159162

160163
### 7) Function signature manipulation

0 commit comments

Comments
 (0)