class ObjectTreeDialog(QDialog): def __init__(self, parent=None, root_object=None): super(ObjectTreeDialog, self).__init__(parent) self.setWindowTitle("Object Tree") layout = QtWidgets.QVBoxLayout() # Tree widget for displaying our object hierarchy self.tree_widget = QTreeWidget() self.tree_widget_columns = [ "TYPE", "OBJECT NAME", "TEXT", "ICONTEXT", "TITLE", "WINDOW_TITLE", "CLASSES", "POINTER_ADDRESS", "GEOMETRY" ] self.tree_widget.setColumnCount(len(self.tree_widget_columns)) self.tree_widget.setHeaderLabels(self.tree_widget_columns) # Only show our type and object name columns. The others we only use to store data so that # we can use the built-in QTreeWidget.findItems to query. for column_name in self.tree_widget_columns: if column_name == "TYPE" or column_name == "OBJECT NAME": continue column_index = self.tree_widget_columns.index(column_name) self.tree_widget.setColumnHidden(column_index, True) header = self.tree_widget.header() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # Populate our object tree widget # If a root object wasn't specified, then use the Editor main window if not root_object: params = azlmbr.qt.QtForPythonRequestBus( azlmbr.bus.Broadcast, "GetQtBootstrapParameters") editor_id = QtWidgets.QWidget.find(params.mainWindowId) editor_main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow) root_object = editor_main_window self.build_tree(root_object, self.tree_widget) # Listen for when the tree widget selection changes so we can update # selected item properties self.tree_widget.itemSelectionChanged.connect( self.on_tree_widget_selection_changed) # Split our tree widget with a properties view for showing more information about # a selected item. We also use a stacked layout for the properties view so that # when nothing has been selected yet, we can show a message informing the user # that something needs to be selected. splitter = QSplitter() splitter.addWidget(self.tree_widget) self.widget_properties = QWidget(self) self.stacked_layout = QtWidgets.QStackedLayout() self.widget_info = QWidget() form_layout = QtWidgets.QFormLayout() self.name_value = QLineEdit("") self.name_value.setReadOnly(True) self.type_value = QLabel("") self.geometry_value = QLabel("") self.text_value = QLabel("") self.icon_text_value = QLabel("") self.title_value = QLabel("") self.window_title_value = QLabel("") self.classes_value = QLabel("") form_layout.addRow("Name:", self.name_value) form_layout.addRow("Type:", self.type_value) form_layout.addRow("Geometry:", self.geometry_value) form_layout.addRow("Text:", self.text_value) form_layout.addRow("Icon Text:", self.icon_text_value) form_layout.addRow("Title:", self.title_value) form_layout.addRow("Window Title:", self.window_title_value) form_layout.addRow("Classes:", self.classes_value) self.widget_info.setLayout(form_layout) self.widget_properties.setLayout(self.stacked_layout) self.stacked_layout.addWidget( QLabel("Select an object to view its properties")) self.stacked_layout.addWidget(self.widget_info) splitter.addWidget(self.widget_properties) # Give our splitter stretch factor of 1 so it will expand to take more room over # the footer layout.addWidget(splitter, 1) # Create our popup widget for showing information when hovering over widgets self.hovered_widget = None self.inspect_mode = False self.inspect_popup = InspectPopup() self.inspect_popup.resize(100, 50) self.inspect_popup.hide() # Add a footer with a button to switch to widget inspect mode self.footer = QWidget() footer_layout = QtWidgets.QHBoxLayout() self.inspect_button = QPushButton("Pick widget to inspect") self.inspect_button.clicked.connect(self.on_inspect_clicked) footer_layout.addStretch(1) footer_layout.addWidget(self.inspect_button) self.footer.setLayout(footer_layout) layout.addWidget(self.footer) self.setLayout(layout) # Delete ourselves when the dialog is closed, so that we don't stay living in the background # since we install an event filter on the application self.setAttribute(Qt.WA_DeleteOnClose, True) # Listen to events at the application level so we can know when the mouse is moving app = QtWidgets.QApplication.instance() app.installEventFilter(self) def eventFilter(self, obj, event): # Look for mouse movement events so we can see what widget the mouse is hovered over event_type = event.type() if event_type == QEvent.MouseMove: global_pos = event.globalPos() # Make our popup follow the mouse, but we need to offset it by 1, 1 otherwise # the QApplication.widgetAt will always return our popup instead of the Editor # widget since it is on top self.inspect_popup.move(global_pos + QtCore.QPoint(1, 1)) # Find out which widget is under our current mouse position hovered_widget = QtWidgets.QApplication.widgetAt(global_pos) if self.hovered_widget: # Bail out, this is the same widget we are already hovered on if self.hovered_widget is hovered_widget: return False # Update our hovered widget and label self.hovered_widget = hovered_widget self.update_hovered_widget_popup() elif event_type == QEvent.KeyRelease: if event.key() == Qt.Key_Escape: # Cancel the inspect mode if the Escape key is pressed # We don't need to actually hide the inspect popup here because # it will be hidden already by the Escape action self.inspect_mode = False elif event_type == QEvent.MouseButtonPress or event_type == QEvent.MouseButtonRelease: # Trigger inspecting the currently hovered widget when the left mouse button is clicked # Don't continue processing this event if self.inspect_mode and event.button() == Qt.LeftButton: # Only trigger the inspect on the click release, but we want to also eat the press # event so that the widget we clicked on isn't stuck in a weird state (e.g. thinks its being dragged) # Also hide the inspect popup since it won't be hidden automatically by the mouse click since we are # consuming the event if event_type == event_type == QEvent.MouseButtonRelease: self.inspect_popup.hide() self.inspect_widget() return True # Pass every event through return False def build_tree(self, obj, parent_tree): if len(obj.children()) == 0: return for child in obj.children(): object_type = type(child).__name__ object_name = child.objectName() text = icon_text = title = window_title = geometry_str = classes = "(N/A)" if isinstance(child, QtGui.QWindow): title = child.title() if isinstance(child, QAction): text = child.text() icon_text = child.iconText() if isinstance(child, QWidget): window_title = child.windowTitle() if not (child.property("class") == ""): classes = child.property("class") if isinstance(child, QAbstractButton): text = child.text() # Keep track of the pointer address for this object so we can search for it later pointer_address = str(int(getCppPointer(child)[0])) # Some objects might not have a geometry (e.g. actions, generic qobjects) if hasattr(child, 'geometry'): geometry_rect = child.geometry() geometry_str = "x: {x}, y: {y}, width: {width}, height: {height}".format( x=geometry_rect.x(), y=geometry_rect.y(), width=geometry_rect.width(), height=geometry_rect.height()) child_tree = QTreeWidgetItem([ object_type, object_name, text, icon_text, title, window_title, classes, pointer_address, geometry_str ]) if isinstance(parent_tree, QTreeWidget): parent_tree.addTopLevelItem(child_tree) else: parent_tree.addChild(child_tree) self.build_tree(child, child_tree) def update_hovered_widget_popup(self): if self.inspect_mode and self.hovered_widget: if not self.inspect_popup.isVisible(): self.inspect_popup.show() self.inspect_popup.update_widget(self.hovered_widget) else: self.inspect_popup.hide() def on_inspect_clicked(self): self.inspect_mode = True self.update_hovered_widget_popup() def on_tree_widget_selection_changed(self): selected_items = self.tree_widget.selectedItems() # If nothing is selected, then switch the stacked layout back to 0 # to show the message if not selected_items: self.stacked_layout.setCurrentIndex(0) return # Update the selected widget properties and switch to the 1 index in # the stacked layout so that all the rows will be visible item = selected_items[0] self.name_value.setText( item.text(self.tree_widget_columns.index("OBJECT NAME"))) self.type_value.setText( item.text(self.tree_widget_columns.index("TYPE"))) self.geometry_value.setText( item.text(self.tree_widget_columns.index("GEOMETRY"))) self.text_value.setText( item.text(self.tree_widget_columns.index("TEXT"))) self.icon_text_value.setText( item.text(self.tree_widget_columns.index("ICONTEXT"))) self.title_value.setText( item.text(self.tree_widget_columns.index("TITLE"))) self.window_title_value.setText( item.text(self.tree_widget_columns.index("WINDOW_TITLE"))) self.classes_value.setText( item.text(self.tree_widget_columns.index("CLASSES"))) self.stacked_layout.setCurrentIndex(1) def inspect_widget(self): self.inspect_mode = False # Find the tree widget item that matches our hovered widget, and then set it as the current item # so that the tree widget will scroll to it, expand it, and select it widget_pointer_address = str(int( getCppPointer(self.hovered_widget)[0])) pointer_address_column = self.tree_widget_columns.index( "POINTER_ADDRESS") items = self.tree_widget.findItems( widget_pointer_address, Qt.MatchFixedString | Qt.MatchRecursive, pointer_address_column) if items: item = items[0] self.tree_widget.clearSelection() self.tree_widget.setCurrentItem(item) else: print("Unable to find widget")
class MemTree(QWidget): def __init__(self, qmp, parent): super().__init__() self.qmp = qmp self.qmp.memoryMap.connect(self.update_tree) self.parent = parent self.tree_sem = QSemaphore(1) self.sending_sem = QSemaphore( 1) # used to prevent sending too many requests at once self.init_ui() self.get_map() def init_ui(self): self.vbox = QVBoxLayout() self.refresh = QPushButton('Refresh') self.refresh.clicked.connect(lambda: self.get_map()) self.vbox.addWidget(self.refresh) self.tree = QTreeWidget() self.tree.itemDoubleClicked.connect(self.open_region) self.tree.setColumnCount(3) self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.tree.header().setStretchLastSection(False) self.tree.setHeaderLabels( ['Memory Region', 'Start Address', 'End Address']) self.vbox.addWidget(self.tree) self.setLayout(self.vbox) self.setGeometry(100, 100, 500, 300) self.setWindowTitle("Memory Tree") self.show() def get_map(self): self.tree.clear() self.qmp.command('mtree') # finds item with name 'name' in self.tree # self.tree_sem must be acquired before use def find(self, name, node): if node.text(0) == name: return node else: for i in range(node.childCount()): result = self.find(name, node.child(i)) if result: return result return None def update_tree(self, value): if value != None: self.tree_sem.acquire() current_addr_space = '' for region in value: parent_node = self.tree parent = region['parent'] if parent != '': root = self.tree.invisibleRootItem() for i in range(root.childCount()): if root.child(i).text(0) == current_addr_space: root = root.child(i) break parent_node = self.find(parent, root) else: current_addr_space = region['name'] node = QTreeWidgetItem(parent_node) node.setText(0, region['name']) start = region['start'] end = region['end'] if start < 0: start = start + (1 << constants['bits']) if end < 0: end = end + (1 << constants['bits']) node.setText(1, f'{start:016x}') node.setText(2, f'{end:016x}') node.setFont(0, QFont('Courier New')) node.setFont(1, QFont('Courier New')) node.setFont(2, QFont('Courier New')) self.tree_sem.release() def open_region(self, node, col): self.parent.open_new_window( MemDumpWindow(self.qmp, base=int(node.text(1), 16), max=int(node.text(2), 16)))