def down_list(tree: QTreeWidget): selected = tree.currentItem() if selected: index = tree.indexOfTopLevelItem(selected) name = selected.text(0) desc = selected.text(1) data = selected.data(2, 2) new = QTreeWidgetItem([name, desc]) new.setData(2, 2, data) tree.takeTopLevelItem(index) tree.insertTopLevelItem(index + 1, new) tree.setCurrentItem(new)
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 TraceWindow(QMainWindow): def __init__(self, qmp): QMainWindow.__init__(self) self.qmp = qmp os.system('rm /tmp/errors.log 2>/dev/null') self.trace_events = self.qmp.hmp_command('info trace-events') self.qmp.hmp_command('logfile /tmp/errors.log') self.trace_events = sorted( self.trace_events['return'].split('\r\n'))[1:] self.activated = [] self.length = 100 self.timer = QTimer(self) self.timer.timeout.connect(self.disp_output) self.timer.start(100) self.init_ui() def init_ui(self): self.setWindowTitle('Trace Event Window') self.setGeometry(100, 100, 800, 600) bar = self.menuBar() file_ = bar.addMenu('File') export_log = QAction('Save to File', self, triggered=lambda: self.save_log()) options = bar.addMenu('Options') auto_refresh = QAction( 'Auto Refresh', self, checkable=True, triggered=lambda: self.timer.start(100) if auto_refresh.isChecked() else self.timer.stop()) auto_refresh.setChecked(True) options.addAction(auto_refresh) file_.addAction(export_log) vgrid = QVBoxLayout() grid = QHBoxLayout() self.tree = QTreeWidget() self.tree.setHeaderLabels(['Name']) self.top = [] self.lst = [] for n, event in enumerate(self.trace_events): word = event.split('_')[0] if word not in self.top: self.top.append(word) item = QTreeWidgetItem(self.tree) self.lst.append(item) item.setText(0, word) subitem = QTreeWidgetItem(item) subitem.setText(0, ' ' + event.split(' : ')[0]) # subitem.setCheckState(0, Qt.Unchecked) cbox = QCheckBox() cbox.stateChanged.connect(lambda state, text=subitem.text(0): self. handle_checked(state, text)) self.tree.setItemWidget(subitem, 0, cbox) # self.tree.setColumnWidth(0, 25) self.tracelist = QLabel() self.disp_output() self.traceview = QScrollArea() self.traceview.setWidget(self.tracelist) self.traceview.setWidgetResizable(True) search = QHBoxLayout() self.search_bar = QLineEdit(self) self.completer = QCompleter(self.top, self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.search_bar.setCompleter(self.completer) search_button = QPushButton('Search') search_button.clicked.connect(lambda: self.tree.setCurrentItem( self.lst[self.top.index(self.search_bar.text())])) expand = QPushButton('▼') expand.setFixedSize(QSize(25, 25)) expand.clicked.connect(lambda: self.tree.expandAll()) collapse = QPushButton('▲') collapse.setFixedSize(QSize(25, 25)) collapse.clicked.connect(lambda: self.tree.collapseAll()) self.search_bar.returnPressed.connect(lambda: search_button.click()) search.addWidget(self.search_bar) search.addWidget(search_button) search.addWidget(expand) search.addWidget(collapse) self.digest = QLabel() vgrid.addLayout(search) vgrid.addWidget(self.tree) vgridwid = QWidget() vgridwid.setLayout(vgrid) split = QSplitter(Qt.Horizontal) split.addWidget(vgridwid) split.addWidget(self.traceview) split.setStretchFactor(1, 1) # grid.addLayout(vgrid) grid.addWidget(split) # grid.addWidget(self.tracelist) self.disp_output() center = QWidget() center.setLayout(grid) self.setCentralWidget(center) self.show() def disp_output(self): self.shorten_file() with open('/tmp/errors.log', 'r') as errors: self.digest = [] lines = 0 for line in errors: if re.match(r"\d+@\d+\.\d+:.*", line): self.digest.append(line) lines += 1 if not self.digest: self.digest = ['<font color="grey">Empty...</font>'] self.digest = ''.join(self.digest[-self.length:]) self.tracelist.setText(self.digest) self.tracelist.setFont(QFont('Monospace', 10)) self.tracelist.setTextInteractionFlags(Qt.TextSelectableByMouse) def shorten_file(self): with open('/tmp/errors.log', 'r+') as tracefile: content = ''.join(tracefile.readlines()[-(self.length * 3):]) tracefile.seek(0) tracefile.truncate() tracefile.write(content) def save_log(self): name = QFileDialog.getSaveFileName(self, 'Save File', '', 'Text files (*.txt)') log_file = open(name[0], 'w') log_file.write(self.digest) log_file.close() def handle_checked(self, state, text): if state: self.qmp.hmp_command('trace-event %s on' % text.strip()) self.activated.append(text) else: self.qmp.hmp_command('trace-event %s off' % text.strip()) self.activated.remove(text) def closeEvent(self, event): self.timer.stop() for e in self.activated: self.qmp.hmp_command('trace-event %s off' % e.strip()) os.system('rm /tmp/errors.log') event.accept()