def testQGraphicsProxyWidget(self): scene = QGraphicsScene() proxy = QGraphicsProxyWidget(None, Qt.Window) widget = QLabel('Widget') proxy.setWidget(widget) proxy.setCacheMode(QGraphicsItem.DeviceCoordinateCache) scene.addItem(proxy) scene.setSceneRect(scene.itemsBoundingRect()) view = QGraphicsView(scene) view.setRenderHints(QPainter.Antialiasing|QPainter.SmoothPixmapTransform) view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) view.show() timer = QTimer.singleShot(100, self.app.quit) self.app.exec_()
def relationship_pixmap(self, str_object_class_name_list): """A pixmap for the given object_class name list, created by rendering several object pixmaps next to each other.""" if not str_object_class_name_list: engine = CharIconEngine("\uf1b3", 0) return engine.pixmap(self.ICON_SIZE) object_class_name_list = tuple(str_object_class_name_list.split(",")) if object_class_name_list in self.rel_cls_pixmap_cache: return self.rel_cls_pixmap_cache[object_class_name_list] scene = QGraphicsScene() x = 0 for j, object_class_name in enumerate(object_class_name_list): pixmap = self.object_pixmap(object_class_name) pixmap_item = scene.addPixmap(pixmap) if j % 2 == 0: y = 0 else: y = -0.875 * 0.75 * pixmap_item.boundingRect().height() pixmap_item.setZValue(-1) pixmap_item.setPos(x, y) x += 0.875 * 0.5 * pixmap_item.boundingRect().width() pixmap = QPixmap(scene.itemsBoundingRect().toRect().size()) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) painter.setRenderHint(QPainter.Antialiasing, True) scene.render(painter) painter.end() self.rel_cls_pixmap_cache[object_class_name_list] = pixmap return pixmap
class UiVScreenOverview: def __init__(self, view, vscreen): """ :type view: PySide2.QtWidgets.QGraphicsView.QGraphicsView :param vscreen: hwmonitor.vscreen.vscreen.VScreen """ self.view = view self.vscreen = vscreen self.view_margin = 10 self.background_color = QColor(100, 100, 100) view.setInteractive(False) view.setStyleSheet('border: 0px;') view.setRenderHint(QPainter.Antialiasing) view.setCacheMode(QGraphicsView.CacheBackground) view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scene = QGraphicsScene(view) self.scene.setBackgroundBrush(self.background_color) for monitor in self.vscreen.monitors: item = MonitorRepresentation(index_from_device_name(monitor.device_name), monitor) item.update_position() self.scene.addItem(item) view.setScene(self.scene) def update_position(self): for item in self.scene.items(): item.update_position() def resize_scene(self): """Resize the scene to fit inside the available space of the viewport with an given margin. Update the scene rect, since it probably changes when changing the layout. """ rect = self.scene.itemsBoundingRect() self.view.setSceneRect(rect) view_rect = self.view.viewport().rect().adjusted(self.view_margin, self.view_margin, -self.view_margin, -self.view_margin) scene_rect = self.view.matrix().mapRect(rect) x_factor = view_rect.width() / scene_rect.width() y_factor = view_rect.height() / scene_rect.height() factor = min(x_factor, y_factor) self.view.scale(factor, factor) self.view.centerOn(rect.center())
def testQGraphicsProxyWidget(self): scene = QGraphicsScene() proxy = QGraphicsProxyWidget(None, Qt.Window) widget = QLabel('Widget') proxy.setWidget(widget) proxy.setCacheMode(QGraphicsItem.DeviceCoordinateCache) scene.addItem(proxy) scene.setSceneRect(scene.itemsBoundingRect()) view = QGraphicsView(scene) view.setRenderHints(QPainter.Antialiasing|QPainter.SmoothPixmapTransform) view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) view.show() timer = QTimer.singleShot(100, self.app.quit) self.app.exec_()
def _create_obj_group_renderer(self, object_class_name): display_icon = self.display_icons.get(object_class_name, -1) icon_code, color_code = interpret_icon_id(display_icon) font = QFont('Font Awesome 5 Free Solid') scene = QGraphicsScene() x = 0 for _ in range(2): y = 0 for _ in range(2): text_item = scene.addText(chr(icon_code), font) text_item.setDefaultTextColor(color_code) text_item.setPos(x, y) y += 0.875 * text_item.boundingRect().height() x += 0.875 * text_item.boundingRect().width() scene.addRect(scene.itemsBoundingRect()) self.obj_group_renderers[ object_class_name] = _SceneSvgRenderer.from_scene(scene)
class DisplayWidget(QStackedWidget): imageClicked = Signal(int, QEvent) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.max_columns = None # Set when presenter starts # Max cols for multimages self.cached_images = {} # Cache read of image from disk self.start() def emitter(self, index, event): self.imageClicked.emit(index, event) def start(self): # Layout for single images self.single_image = QWidget() self.single_image_layout = QGridLayout() self.single_image_view = QGraphicsView() self.single_image_scene = QGraphicsScene() self.single_image_view.setScene(self.single_image_scene) self.single_image_layout.addWidget(self.single_image_view) self.single_image.setLayout(self.single_image_layout) # Layout for multiple images self.multiple_image = QWidget() self.multiple_image_view_layout = QVBoxLayout() self.multiple_image_layout = QGraphicsGridLayout() self.multiple_image_view = QGraphicsView() self.multiple_image_scene = QGraphicsScene() self.multiple_image_view.setScene(self.multiple_image_scene) self.panel = QGraphicsWidget() self.multiple_image_scene.addItem(self.panel) self.multiple_image_view_layout.addWidget(self.multiple_image_view) self.panel.setLayout(self.multiple_image_layout) self.multiple_image.setLayout(self.multiple_image_view_layout) self.addWidget(self.single_image) self.addWidget(self.multiple_image) def setMaxColumns(self, max_cols): self.max_columns = max_cols def images_load(self, images): """ Take list of images and display in main window """ num = len(images) if num == 0: return width = self.width() maxcol = self.max_columns if num < maxcol: maxcol = num colwidth = width / maxcol # Set proper widget for display of multiple or single images if num > 1: self.setCurrentWidget(self.multiple_image) # Clear the layout while self.multiple_image_layout.count(): self.multiple_image_layout.removeAt(0) # Clear the scene for child in self.panel.childItems(): child.setParent(None) else: self.setCurrentWidget(self.single_image) self.single_image_scene.clear() self.single_image_scene.setSceneRect( self.single_image_scene.itemsBoundingRect()) # Display images or image row = 0 col = -1 for index, image in images.items(): col += 1 if col >= maxcol: col = 0 row += 1 # Used any cached reads if index not in self.cached_images.keys(): image_reader = QImageReader() image_reader.setDecideFormatFromContent(True) image_reader.setFileName(image.getFilename()) self.cached_images[index] = image_reader.read() cached_image = self.cached_images[index] pixmap = QPixmap(cached_image) if num > 1: pixmap = pixmap.scaledToWidth(colwidth - 20) rec = MultiImageWidget(pixmap, basename(image.getFilename()), index) rec.imageClicked.connect(self.emitter) self.multiple_image_layout.addItem(rec, row, col) else: self.single_image_scene.addPixmap(pixmap) adjusted = self.multiple_image_scene.itemsBoundingRect() adjusted.adjust(0, 0, 0, 8 * row) self.multiple_image_scene.setSceneRect(adjusted)
class HumanVisualizationWidget(QGraphicsView): def __init__(self, parent=None): super(HumanVisualizationWidget, self).__init__(parent) self._scene = QGraphicsScene(self) self.setScene(self._scene) # circle = QGraphicsEllipseItem( 10, 10, 10 ,10) # self._scene.addItem(circle) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) self._boxes = [] self._humans = {} def load_inner_model(self, file): import xml.etree.cElementTree as ET tree = ET.ElementTree(file=file) root = tree.getroot() transforms = tree.findall(".//transform[plane]") walls = {} for trans in transforms: if 'id' in trans.attrib and 'pared' in trans.attrib['id']: print("Pared:", trans.attrib['id']) current_wall = [0] * 7 # "wall5": [x, y, width, height, posx, posy, 0] if 'tx' in trans.attrib: # print trans.attrib['tx'] current_wall[4] = int(float(trans.attrib['tx'])) if 'ty' in trans.attrib: # print trans.attrib['ty'] current_wall[5] = int(float(trans.attrib['tz'])) # current_wall = planes = trans.findall('plane') for plane in planes: if 'id' in plane.attrib and 'muro' in plane.attrib['id']: # if 'nx' in plane.attrib: # print plane.attrib['nx'] # if 'nz' in plane.attrib: # print plane.attrib['nz'] if 'size' in plane.attrib: # print int(float(plane.attrib['size'].split(',')[0]) current_wall[2] = int( float(plane.attrib['size'].split(',')[0])) / 2. # print int(float(plane.attrib['size'].split(',')[1]) current_wall[3] = int( float(plane.attrib['size'].split(',')[1])) / 2. if current_wall[2] < current_wall[3]: current_wall[2] = 200 else: current_wall[3] = 200 walls[trans.attrib['id']] = current_wall for id in sorted(walls.keys()): object = walls[id] # rect = QRectF(-float(object[2]) / 2, -float(object[3]) / 2, float(object[2]), float(object[3])) rect = QRectF(0, 0, float(object[2]), float(object[3])) border = QPen(QColor("black")) fill = QBrush(QColor("black")) box = self._scene.addRect(rect, border, fill) self._scene.addEllipse( QRectF(float(object[4]), float(object[5]), 10, 10), QPen(QColor("green")), QBrush(QColor("green"))) box.setPos(float(object[4]), float(object[5])) box.setRotation(float(object[6])) self._boxes.append(box) self._scene.update() QApplication.processEvents() sleep(1) def load_custom_json_world(self, file): if not os.path.isfile(file): print("Error reading world file, check config params:", file) return False with open(file, "r") as read_file: json_data = json.load(read_file) types_colors = { "tables": "SandyBrown", "roundTables": "Khaki", "walls": "Brown", "points": "Blue" } self.clear() for type, color in types_colors.items(): if type in json_data: tables = json_data[type] for object in tables.values(): rect = QRectF(-float(object[2]) / 2, -float(object[3]) / 2, float(object[2]), float(object[3])) border = QPen(QColor(color)) fill = QBrush(QColor(color)) if type == "roundTables": box = self._scene.addEllipse(rect, border, fill) else: box = self._scene.addRect(rect, border, fill) box.setPos(float(object[4]), float(object[5])) box.setRotation(float(object[6])) self._boxes.append(box) def load_json_world(self, file): if not os.path.isfile(file): print("Error reading world file, check config params:", file) return False with open(file, "r") as read_file: json_data = json.load(read_file) polygon_points = [] paths_count = 0 for item in json_data: if 'json_geometry' in item: geometry = item['json_geometry'] if geometry['type'] == 'Polygon': for coord in geometry['coordinates'][0]: if isinstance(coord, list) and ( (isinstance(coord, list) and len(coord) == 2) or (len(coord) == 3 and coord[3] == 0)): current_point = QPointF(coord[0], coord[1]) polygon_points.append(current_point) else: print("Unknown coord", geometry["coordinates"][0]) polygon = QPolygonF(polygon_points) path = QPainterPath() path.addPolygon(polygon) contour = QGraphicsPathItem(path) # r = lambda: random.randint(0, 255) # next_color = '#%02X%02X%02X' % (r(), r(), r()) contour.setPen(QPen(QColor("red"), 0.1)) contour.setBrush(QBrush(Qt.transparent)) # if paths_count == 4: print(item['json_featuretype']) self._scene.addItem(contour) paths_count += 1 self.update() def clear(self): for human in self._humans.values(): self._scene.removeItem(human) self._humans = {} # self._scene.setSceneRect(QRectF(0,0,400,400)) def resizeEvent(self, event): # skip initial entry self.own_resize() super(HumanVisualizationWidget, self).resizeEvent(event) def own_resize(self): self.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio) self._scene.setSceneRect(self._scene.itemsBoundingRect()) def add_human_by_pos(self, id, pos): x, y = pos human = QGraphicsEllipseItem(0, 0, 200, 200) self._scene.addItem(human) human.setBrush(QBrush(Qt.black, style=Qt.SolidPattern)) human_text = QGraphicsTextItem(str(pos)) font = QFont("Helvetica [Cronyx]", 40, QFont.Bold) human_text.setFont(font) human_text.setParentItem(human) human.setPos(pos[0], pos[1]) self._humans[id] = human human.setZValue(30) def move_human(self, id, pos): x, y = pos human = self._humans[id] human.setPos(x, y) def set_human_color(self, id, color): if id in self._humans: self._humans[id].setBrush(color)
class QTraceViewer(QWidget): TAG_SPACING = 50 LEGEND_X = -50 LEGEND_Y = 0 LEGEND_WIDTH = 10 TRACE_FUNC_X = 0 TRACE_FUNC_Y = 0 TRACE_FUNC_WIDTH = 50 TRACE_FUNC_MINHEIGHT = 1000 MARK_X = LEGEND_X MARK_WIDTH = TRACE_FUNC_X - LEGEND_X + TRACE_FUNC_WIDTH MARK_HEIGHT = 1 def __init__(self, workspace, disasm_view, parent=None): super().__init__(parent=parent) self.workspace = workspace self.disasm_view = disasm_view self.view = None self.scene = None self.mark = None self.curr_position = 0 self._use_precise_position = False self._init_widgets() self.trace.am_subscribe(self._on_set_trace) self.selected_ins.am_subscribe(self._on_select_ins) self.view.installEventFilter(self) # # Forwarding properties # @property def trace(self): return self.workspace.instance.trace @property def selected_ins(self): return self.disasm_view.infodock.selected_insns def _init_widgets(self): self.view = QGraphicsView() self.scene = QGraphicsScene() self.view.setScene(self.scene) self._reset() layout = QHBoxLayout() layout.addWidget(self.view) layout.setContentsMargins(0, 0, 0, 0) layout.setAlignment(self.view, Qt.AlignLeft) self.setLayout(layout) def _reset(self): self.scene.clear() #clear items self.mark = None self.legend = None self.legend_height = 0 self.trace_func = QGraphicsItemGroup() self.scene.addItem(self.trace_func) self.hide() def _on_set_trace(self, **kwargs): self._reset() if self.trace.am_obj is not None: l.debug('minheight: %d, count: %d', self.TRACE_FUNC_MINHEIGHT, self.trace.count) if self.trace.count <= 0: l.warning( "No valid addresses found in trace to show. Check base address offsets?" ) self.trace.am_obj = None self.trace.am_event() return if self.TRACE_FUNC_MINHEIGHT < self.trace.count * 15: self.trace_func_unit_height = 15 show_func_tag = True else: self.trace_func_unit_height = self.TRACE_FUNC_MINHEIGHT / self.trace.count show_func_tag = True self.legend_height = int(self.trace.count * self.trace_func_unit_height) self._show_trace_func(show_func_tag) self._show_legend() self._set_mark_color() self.scene.setSceneRect(self.scene.itemsBoundingRect()) #resize self.setFixedWidth(self.scene.itemsBoundingRect().width()) self.view.setFixedWidth(self.scene.itemsBoundingRect().width()) self.show() def _on_select_ins(self, **kwargs): if self.trace == None: return if self.mark is not None: for i in self.mark.childItems(): self.mark.removeFromGroup(i) self.scene.removeItem(i) self.scene.removeItem(self.mark) self.mark = QGraphicsItemGroup() self.scene.addItem(self.mark) if self.selected_ins: addr = next(iter(self.selected_ins)) positions = self.trace.get_positions(addr) if positions: #if addr is in list of positions if not self._use_precise_position: #handle case where insn was selected from disas view self.curr_position = positions[0] - self.trace.count for p in positions: color = self._get_mark_color(p, self.trace.count) y = self._get_mark_y(p, self.trace.count) if p == self.trace.count + self.curr_position: #add thicker line for 'current' mark self.mark.addToGroup( self.scene.addRect(self.MARK_X, y, self.MARK_WIDTH, self.MARK_HEIGHT * 4, QPen(QColor('black')), QBrush(color))) else: self.mark.addToGroup( self.scene.addRect(self.MARK_X, y, self.MARK_WIDTH, self.MARK_HEIGHT, QPen(color), QBrush(color))) #y = self._get_mark_y(positions[0], self.trace.count) #self.view.verticalScrollBar().setValue(y - 0.5 * self.view.size().height()) self.scene.update() #force redraw of the scene self.scroll_to_position(self.curr_position) def scroll_to_position(self, position): relative_pos = self.trace.count + position y_offset = self._get_mark_y(relative_pos, self.trace.count) scrollValue = 0 if y_offset > 0.5 * self.view.size().height(): scrollValue = y_offset - 0.5 * self.view.size().height() scrollValue = min(scrollValue, self.view.verticalScrollBar().maximum()) self.view.verticalScrollBar().setValue(scrollValue) self._use_precise_position = False def jump_next_insn(self): if self.curr_position + self.trace.count < self.trace.count - 1: #for some reason indexing is done backwards self.curr_position += 1 self._use_precise_position = True func_name = self.trace.trace_func[self.curr_position].func_name func = self._get_func_from_func_name(func_name) bbl_addr = self.trace.trace_func[self.curr_position].bbl_addr self._jump_bbl(func, bbl_addr) def jump_prev_insn(self): if self.curr_position + self.trace.count > 0: self.curr_position -= 1 self._use_precise_position = True func_name = self.trace.trace_func[self.curr_position].func_name func = self._get_func_from_func_name(func_name) bbl_addr = self.trace.trace_func[self.curr_position].bbl_addr self._jump_bbl(func, bbl_addr) def eventFilter(self, object, event): #specifically to catch arrow keys # more elegant solution to link w/ self.view's scroll bar keypressevent? if event.type() == QEvent.Type.KeyPress: if not (event.modifiers() & Qt.ShiftModifier): #shift + arrowkeys return False key = event.key() if key == Qt.Key_Up or key == Qt.Key_Left: self.jump_prev_insn() elif key == Qt.Key_Down or key == Qt.Key_Right: self.jump_next_insn() return True return False # pass through all other events def mousePressEvent(self, event): button = event.button() pos = self._to_logical_pos(event.pos()) if button == Qt.LeftButton and self._at_legend(pos): func = self._get_func_from_y(pos.y()) bbl_addr = self._get_bbl_from_y(pos.y()) self._use_precise_position = True self.curr_position = self._get_position(pos.y()) self._jump_bbl(func, bbl_addr) def _jump_bbl(self, func, bbl_addr): all_insn_addrs = self.workspace.instance.project.factory.block( bbl_addr).instruction_addrs # TODO: replace this with am_events perhaps? self.workspace.on_function_selected(func) self.selected_ins.clear() self.selected_ins.update(all_insn_addrs) self.selected_ins.am_event() # TODO: this ought to happen automatically as a result of the am_event self.disasm_view.current_graph.show_instruction(bbl_addr) def _get_mark_color(self, i, total): relative_gradient_pos = i * 1000 // total return self.legend_img.pixelColor(self.LEGEND_WIDTH // 2, relative_gradient_pos) def _get_mark_y(self, i, total): return self.TRACE_FUNC_Y + self.trace_func_unit_height * i def _show_trace_func(self, show_func_tag): x = self.TRACE_FUNC_X y = self.TRACE_FUNC_Y prev_name = None for position in self.trace.trace_func: bbl_addr = position.bbl_addr func_name = position.func_name l.debug('Draw function %x, %s', bbl_addr, func_name) color = self.trace.get_func_color(func_name) self.trace_func.addToGroup( self.scene.addRect(x, y, self.TRACE_FUNC_WIDTH, self.trace_func_unit_height, QPen(color), QBrush(color))) if show_func_tag is True and func_name != prev_name: tag = self.scene.addText(func_name, QFont("Source Code Pro", 7)) tag.setPos(x + self.TRACE_FUNC_WIDTH + self.TAG_SPACING, y - tag.boundingRect().height() // 2) self.trace_func.addToGroup(tag) anchor = self.scene.addLine( self.TRACE_FUNC_X + self.TRACE_FUNC_WIDTH, y, x + self.TRACE_FUNC_WIDTH + self.TAG_SPACING, y) self.trace_func.addToGroup(anchor) prev_name = func_name y += self.trace_func_unit_height def _make_legend_gradient(self, x1, y1, x2, y2): gradient = QLinearGradient(x1, y1, x2, y2) gradient.setColorAt(0.0, Qt.red) gradient.setColorAt(0.4, Qt.yellow) gradient.setColorAt(0.6, Qt.green) gradient.setColorAt(0.8, Qt.blue) gradient.setColorAt(1.0, Qt.darkBlue) return gradient def _show_legend(self): pen = QPen(Qt.transparent) gradient = self._make_legend_gradient( self.LEGEND_X, self.LEGEND_Y, self.LEGEND_X, self.LEGEND_Y + self.legend_height) brush = QBrush(gradient) self.legend = self.scene.addRect(self.LEGEND_X, self.LEGEND_Y, self.LEGEND_WIDTH, self.legend_height, pen, brush) reference_gradient = self._make_legend_gradient( 0, 0, self.LEGEND_WIDTH, 1000) base_img = QImage(self.LEGEND_WIDTH, 1000, QImage.Format.Format_ARGB32) p = QPainter(base_img) p.fillRect(base_img.rect(), reference_gradient) self.legend_img = base_img #reference shade def _set_mark_color(self): for p in range(self.trace.count): color = self._get_mark_color(p, self.trace.count) self.trace.set_mark_color(p, color) def _at_legend(self, pos): x = pos.x() y = pos.y() if self.TRACE_FUNC_X + self.LEGEND_X < x < self.view.width() and \ self.TRACE_FUNC_Y < y < self.TRACE_FUNC_Y + self.legend_height: return True else: return False def _to_logical_pos(self, pos): x_offset = self.view.horizontalScrollBar().value() y_offset = self.view.verticalScrollBar().value() return QPoint(pos.x() + x_offset, pos.y() + y_offset) def _get_position(self, y): y_relative = y - self.legend_height return int(y_relative // self.trace_func_unit_height) def _get_bbl_from_y(self, y): position = self._get_position(y) return self.trace.get_bbl_from_position(position) def _get_func_from_func_name(self, func_name): return self.workspace.instance.kb.functions.function(name=func_name) def _get_func_from_y(self, y): position = self._get_position(y) func_name = self.trace.get_func_name_from_position(position) return self._get_func_from_func_name(func_name)
class AbstractGraphicViewer(QGraphicsView, TcWidget): def __init__(self, parent=None): super(AbstractGraphicViewer, self).__init__(parent) self.m_scaleX = 0 self.m_scaleY = 0 self._pan = False self._panStartX = 0 self._panStartY = 0 self.scene = QGraphicsScene() self.scene.setItemIndexMethod(QGraphicsScene.NoIndex) self.scene.setSceneRect(-2000, -2000, 4000, 4000) self.setScene(self.scene) self.setCacheMode(QGraphicsView.CacheBackground) self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) self.setRenderHint(QPainter.Antialiasing) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setMinimumSize(400, 400) self.adjustSize() self.setMouseTracking(True) self.viewport().setMouseTracking(True) def wheelEvent(self, event): if event.angleDelta().y() > 0: factor = 1.1 else: factor = 0.9 view_pos = event.pos() scene_pos = self.mapToScene(view_pos) self.centerOn(scene_pos) self.scale(factor, factor) delta = self.mapToScene(view_pos) - self.mapToScene(self.viewport().rect().center()) self.centerOn(scene_pos - delta) def resizeEvent(self, event): super(AbstractGraphicViewer, self).resizeEvent(event) def mouseMoveEvent(self, event): if self._pan: self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - (event.x() - self._panStartX)) self.verticalScrollBar().setValue(self.verticalScrollBar().value() - (event.y() - self._panStartY)) self._panStartX = event.x() self._panStartY = event.y() event.accept() super(AbstractGraphicViewer, self).mouseMoveEvent(event) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._pan = True self._panStartX = event.x() self._panStartY = event.y() self.setCursor(QCursor.ClosedHandCursor) event.accept() return super(AbstractGraphicViewer, self).mousePressEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self._pan = False self.setCursor(QCursor.ArrowCursor) event.accept() super(AbstractGraphicViewer, self).mouseReleaseEvent(event) def showEvent(self, event): super(AbstractGraphicViewer, self).showEvent(event) adjusted = self.scene.itemsBoundingRect().adjusted(-100, -100, 100, 100) self.scene.setSceneRect(adjusted) # FitInView is called two times because of this bug: https://bugreports.qt.io/browse/QTBUG-1047 update_state = self.updatesEnabled() self.setUpdatesEnabled(False) self.fitInView(adjusted, Qt.KeepAspectRatio) QApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.fitInView(adjusted, Qt.KeepAspectRatio) self.setUpdatesEnabled(update_state)
class QTraceViewer(QWidget): """ Load a basic block trace through json and visualize it in the disassembly Ref: https://github.com/angr/angr-management/pull/122 """ TAG_SPACING = 50 LEGEND_X = -50 LEGEND_Y = 0 LEGEND_WIDTH = 10 TRACE_FUNC_X = 0 TRACE_FUNC_Y = 0 TRACE_FUNC_WIDTH = 50 TRACE_FUNC_MINHEIGHT = 1000 TAB_HEADER_SIZE = 40 MAX_WINDOW_SIZE = 500 MARK_X = LEGEND_X MARK_WIDTH = TRACE_FUNC_X - LEGEND_X + TRACE_FUNC_WIDTH MARK_HEIGHT = 1 def __init__(self, workspace, disasm_view, parent=None): super().__init__(parent=parent) self.workspace = workspace self.disasm_view = disasm_view self.mark = None self.legend = None self.legend_height = 0 self.legend_img = None self.trace_func_unit_height = 0 self.trace_func = None self.trace_id = None self.view = None self.traceView = None self.traceTab = None self.traceScene = None self.multiView = None self.listView = None self.mark = None self.curr_position = 0 self._use_precise_position = False self._selected_traces = [] self._init_widgets() self.trace.am_subscribe(self._on_set_trace) self.selected_ins.am_subscribe(self._on_select_ins) self.traceTab.installEventFilter(self) # # Forwarding properties # @property def trace(self): return self.workspace.instance.trace @property def multi_trace(self): return self.workspace.instance.multi_trace @property def selected_ins(self): return self.disasm_view.infodock.selected_insns def _init_widgets(self): self.view = QTabWidget() # QGraphicsView() self.traceTab = QWidget() tracelayout = QVBoxLayout() self.traceView = QGraphicsView() self.traceScene = QGraphicsScene() self.traceView.setScene(self.traceScene) self.listView = QTableWidget(0, 2) # row, col self.listView.setHorizontalHeaderItem(0, QTableWidgetItem("Trace ID")) self.listView.setHorizontalHeaderItem(1, QTableWidgetItem("Input ID")) self.listView.setSelectionMode(QAbstractItemView.SingleSelection) self.listView.setSelectionBehavior(QAbstractItemView.SelectRows) # self.listView.horizontalHeader().setStretchLastSection(True) # self.listView.horizontalHeader().setSectionResizeModel(0, QHeaderView.Stretch) self.listView.cellClicked.connect(self._switch_current_trace) self.traceSeedButton = QPushButton("View Input Seed") self.traceSeedButton.clicked.connect(self._view_input_seed) tracelayout.addWidget(self.traceView) tracelayout.addWidget(self.listView) tracelayout.addWidget(self.traceSeedButton) self.traceTab.setLayout(tracelayout) self.multiView = QWidget() multiLayout = QVBoxLayout() self.multiTraceList = QTableWidget(0, 2) # row, col self.multiTraceList.setSelectionMode(QAbstractItemView.MultiSelection) self.multiTraceList.setSelectionBehavior(QAbstractItemView.SelectRows) self.multiTraceList.setHorizontalScrollMode( self.multiTraceList.ScrollPerPixel) self.multiTraceList.setHorizontalHeaderItem( 0, QTableWidgetItem("Trace ID")) self.multiTraceList.setHorizontalHeaderItem( 1, QTableWidgetItem("Input ID")) self.selectMultiTrace = QPushButton("Refresh Heatmap") self.selectMultiTrace.clicked.connect(self._refresh_heatmap) multiLayout.addWidget(self.multiTraceList) multiLayout.addWidget(self.selectMultiTrace) self.multiView.setLayout(multiLayout) self.view.addTab(self.traceTab, "SingleTrace") self.view.addTab(self.multiView, "MultiTrace HeatMap") self.SINGLE_TRACE = 0 self.MULTI_TRACE = 1 self.view.currentChanged.connect(self._on_tab_change) self._reset() layout = QVBoxLayout() layout.addWidget(self.view) layout.setContentsMargins(0, 0, 0, 0) layout.setAlignment(self.view, Qt.AlignLeft) self.setLayout(layout) def _reset(self): self.traceScene.clear() #clear items self.listView.clearContents() self.multiTraceList.clearContents() self.mark = None self.legend = None self.legend_height = 0 self.trace_func = QGraphicsItemGroup() self.trace_id = QGraphicsItemGroup() self.traceScene.addItem(self.trace_func) self.hide() def _view_input_seed(self): current_trace_stats = self.trace.am_obj input_id = current_trace_stats.input_id inputSeed = self.multi_trace.am_obj.get_input_seed_for_id(input_id) msgText = "%s" % inputSeed msgDetails = "Input for [%s]" % current_trace_stats.id msgbox = QMessageBox() msgbox.setWindowTitle("Seed Input") msgbox.setDetailedText(msgDetails) msgbox.setText(msgText) msgbox.setStandardButtons(QMessageBox.Ok) msgbox.exec() def _switch_current_trace(self, row): if self.listView.rowCount() <= 0: return current_trace = self.trace.am_obj.id new_trace = self.multiTraceList.item(row, 0).text() if current_trace == new_trace: return trace_stats = self.multi_trace.am_obj.get_trace_with_id(new_trace) if trace_stats: self.trace.am_obj = trace_stats self._on_set_trace() def _on_set_trace(self): self._reset() if self.trace.am_none or self.trace.count is None: return l.debug('minheight: %d, count: %d', self.TRACE_FUNC_MINHEIGHT, self.trace.count) if self.trace.count <= 0: l.warning( "No valid addresses found in trace to show. Check base address offsets?" ) self.trace.am_obj = None self.trace.am_event() return if self.TRACE_FUNC_MINHEIGHT < self.trace.count * 15: self.trace_func_unit_height = 15 show_func_tag = True else: self.trace_func_unit_height = self.TRACE_FUNC_MINHEIGHT / self.trace.count show_func_tag = True self.legend_height = int(self.trace.count * self.trace_func_unit_height) self._show_trace_func(show_func_tag) self._show_legend() self._show_trace_ids() self._set_mark_color() self._refresh_multi_list() boundingSize = self.traceScene.itemsBoundingRect().width() windowSize = boundingSize if boundingSize > self.MAX_WINDOW_SIZE: windowSize = self.MAX_WINDOW_SIZE self.traceScene.setSceneRect( self.traceScene.itemsBoundingRect()) #resize self.setFixedWidth(windowSize) # self.listScene.setSceneRect(self.listScene.itemsBoundingRect()) #resize self.multiView.setFixedWidth(windowSize) cellWidth = windowSize // 2 self.listView.setColumnWidth(0, cellWidth) self.listView.setColumnWidth(1, cellWidth) self.listView.setFixedHeight(self.multiView.height() // 4) self.multiTraceList.setColumnWidth(0, cellWidth) self.multiTraceList.setColumnWidth(1, cellWidth) self.view.setFixedWidth(windowSize) self.show() def _populate_trace_table(self, view, trace_ids): numIDs = len(trace_ids) view.clearContents() view.setRowCount(numIDs) row = 0 #start after label row for traceID in trace_ids: inputID = self.multi_trace.am_obj.get_input_id_for_trace_id( traceID) if inputID is None: self.workspace.log("No inputID found for trace %s" % traceID) view.setItem(row, 0, QTableWidgetItem(traceID)) view.setItem(row, 1, QTableWidgetItem(inputID)) row += 1 def _refresh_heatmap(self): multiTrace = self.multi_trace.am_obj multiTrace.clear_heatmap() multiTrace.is_active_tab = True selected_items = self.multiTraceList.selectedItems() self._selected_traces.clear() for row in range(self.multiTraceList.rowCount()): item = self.multiTraceList.item(row, 0) if item in selected_items: self._selected_traces.append(item.text()) multiTrace.reload_heatmap(self._selected_traces) self.multi_trace.am_event() def _refresh_multi_list(self): multiTrace = self.multi_trace.am_obj trace_ids = multiTrace.get_all_trace_ids() self.multiTraceList.clearContents() self._populate_trace_table(self.multiTraceList, trace_ids) if self._selected_traces and self.multiTraceList.rowCount() > 0: self.multiTraceList.item(0, 0).setSelected(True) self.multiTraceList.item(0, 1).setSelected(True) else: for row in range(self.multiTraceList.rowCount()): item = self.multiTraceList.item(row, 0) inputItem = self.multiTraceList.item(row, 1) if item.text() in self._selected_traces: item.setSelected(True) inputItem.setSelected(True) self.multi_trace.am_event() def _on_tab_change(self): # self._reset() multiTrace = self.multi_trace.am_obj if self.view.currentIndex() == self.MULTI_TRACE: multiTrace.is_active_tab = True self._refresh_multi_list() elif self.view.currentIndex() == self.SINGLE_TRACE: multiTrace = self.multi_trace.am_obj multiTrace.is_active_tab = False self._show_trace_ids() def _on_select_ins(self, **kwargs): # pylint: disable=unused-argument if self.trace.am_none: return if self.mark is not None: for i in self.mark.childItems(): self.mark.removeFromGroup(i) self.traceScene.removeItem(i) self.traceScene.removeItem(self.mark) self.mark = QGraphicsItemGroup() self.traceScene.addItem(self.mark) if self.selected_ins: addr = next(iter(self.selected_ins)) positions = self.trace.get_positions(addr) if positions: #if addr is in list of positions if not self._use_precise_position: #handle case where insn was selected from disas view self.curr_position = positions[0] - self.trace.count for p in positions: color = self._get_mark_color(p, self.trace.count) y = self._get_mark_y(p) if p == self.trace.count + self.curr_position: #add thicker line for 'current' mark self.mark.addToGroup( self.traceScene.addRect(self.MARK_X, y, self.MARK_WIDTH, self.MARK_HEIGHT * 4, QPen(QColor('black')), QBrush(color))) else: self.mark.addToGroup( self.traceScene.addRect(self.MARK_X, y, self.MARK_WIDTH, self.MARK_HEIGHT, QPen(color), QBrush(color))) self.traceScene.update() #force redraw of the traceScene self.scroll_to_position(self.curr_position) def scroll_to_position(self, position): relative_pos = self.trace.count + position y_offset = self._get_mark_y(relative_pos) scrollValue = 0 if y_offset > 0.5 * self.traceView.size().height(): scrollValue = y_offset - 0.5 * self.traceView.size().height() scrollValue = min(scrollValue, self.traceView.verticalScrollBar().maximum()) self.traceView.verticalScrollBar().setValue(scrollValue) self._use_precise_position = False def jump_next_insn(self): if self.curr_position + self.trace.count < self.trace.count - 1: #for some reason indexing is done backwards self.curr_position += 1 self._use_precise_position = True bbl_addr = self.trace.get_bbl_from_position(self.curr_position) func = self.trace.get_func_from_position(self.curr_position) self._jump_bbl(func, bbl_addr) def jump_prev_insn(self): if self.curr_position + self.trace.count > 0: self.curr_position -= 1 self._use_precise_position = True bbl_addr = self.trace.get_bbl_from_position(self.curr_position) func = self.trace.get_func_from_position(self.curr_position) self._jump_bbl(func, bbl_addr) def eventFilter(self, obj, event): #specifically to catch arrow keys #pylint: disable=unused-argument # more elegant solution to link w/ self.view's scroll bar keypressevent? if event.type() == QEvent.Type.KeyPress: if not event.modifiers() & Qt.ShiftModifier: #shift + arrowkeys return False key = event.key() if key in [Qt.Key_Up, Qt.Key_Left]: self.jump_prev_insn() elif key in [Qt.Key_Down, Qt.Key_Right]: self.jump_next_insn() return True return False # pass through all other events def mousePressEvent(self, event): button = event.button() pos = self._to_logical_pos(event.pos()) if button == Qt.LeftButton and self.view.currentIndex( ) == self.SINGLE_TRACE and self._at_legend(pos): func = self._get_func_from_y(pos.y()) bbl_addr = self._get_bbl_from_y(pos.y()) self._use_precise_position = True self.curr_position = self._get_position(pos.y()) self._jump_bbl(func, bbl_addr) def _jump_bbl(self, func, bbl_addr): all_insn_addrs = self.workspace.instance.project.factory.block( bbl_addr).instruction_addrs # TODO: replace this with am_events perhaps? if func is None: return self.workspace.on_function_selected(func) self.selected_ins.clear() self.selected_ins.update(all_insn_addrs) self.selected_ins.am_event() # TODO: this ought to happen automatically as a result of the am_event self.disasm_view.current_graph.show_instruction(bbl_addr) def _get_mark_color(self, i, total): relative_gradient_pos = i * 1000 // total return self.legend_img.pixelColor(self.LEGEND_WIDTH // 2, relative_gradient_pos) def _get_mark_y(self, i): return self.TRACE_FUNC_Y + self.trace_func_unit_height * i def _show_trace_ids(self): trace_ids = self.multi_trace.get_all_trace_ids() # traceID = self.listScene.addText(id_txt, QFont("Source Code Pro", 7)) # traceID.setPos(5,5) self.listView.clearContents() self._populate_trace_table(self.listView, trace_ids) if len(self.listView.selectedItems()) <= 0 and not self.trace.am_none: for row in range(self.listView.rowCount()): item = self.listView.item(row, 0) inputItem = self.listView.item(row, 1) if self.trace.id in item.text(): item.setSelected(True) inputItem.setSelected(True) break def _show_trace_func(self, show_func_tag): x = self.TRACE_FUNC_X y = self.TRACE_FUNC_Y prev_name = None for position in self.trace.trace_func: bbl_addr = position.bbl_addr func_name = position.func_name l.debug('Draw function %x, %s', bbl_addr, func_name) color = self.trace.get_func_color(func_name) self.trace_func.addToGroup( self.traceScene.addRect(x, y, self.TRACE_FUNC_WIDTH, self.trace_func_unit_height, QPen(color), QBrush(color))) if show_func_tag is True and func_name != prev_name: tag = self.traceScene.addText(func_name, QFont("Source Code Pro", 7)) tag.setPos(x + self.TRACE_FUNC_WIDTH + self.TAG_SPACING, y - tag.boundingRect().height() // 2) self.trace_func.addToGroup(tag) anchor = self.traceScene.addLine( self.TRACE_FUNC_X + self.TRACE_FUNC_WIDTH, y, x + self.TRACE_FUNC_WIDTH + self.TAG_SPACING, y) self.trace_func.addToGroup(anchor) prev_name = func_name y += self.trace_func_unit_height @staticmethod def _make_legend_gradient(x1, y1, x2, y2): gradient = QLinearGradient(x1, y1, x2, y2) gradient.setColorAt(0.0, Qt.red) gradient.setColorAt(0.4, Qt.yellow) gradient.setColorAt(0.6, Qt.green) gradient.setColorAt(0.8, Qt.blue) gradient.setColorAt(1.0, Qt.darkBlue) return gradient def _show_legend(self): pen = QPen(Qt.transparent) gradient = self._make_legend_gradient( self.LEGEND_X, self.LEGEND_Y, self.LEGEND_X, self.LEGEND_Y + self.legend_height) brush = QBrush(gradient) self.legend = self.traceScene.addRect(self.LEGEND_X, self.LEGEND_Y, self.LEGEND_WIDTH, self.legend_height, pen, brush) reference_gradient = self._make_legend_gradient( 0, 0, self.LEGEND_WIDTH, 1000) base_img = QImage(self.LEGEND_WIDTH, 1000, QImage.Format.Format_ARGB32) p = QPainter(base_img) p.fillRect(base_img.rect(), reference_gradient) self.legend_img = base_img #reference shade def _set_mark_color(self): for p in range(self.trace.count): color = self._get_mark_color(p, self.trace.count) self.trace.set_mark_color(p, color) def _at_legend(self, pos): x = pos.x() y = pos.y() return self.TRACE_FUNC_X + self.LEGEND_X < x < self.traceView.width() and \ self.TRACE_FUNC_Y < y < self.TRACE_FUNC_Y + self.legend_height def _to_logical_pos(self, pos): x_offset = self.traceView.horizontalScrollBar().value() y_offset = self.traceView.verticalScrollBar().value() return QPoint(pos.x() + x_offset, pos.y() + y_offset) def _get_position(self, y): y_relative = y - self.legend_height - self.TAB_HEADER_SIZE return int(y_relative // self.trace_func_unit_height) def _get_bbl_from_y(self, y): position = self._get_position(y) return self.trace.get_bbl_from_position(position) def _get_func_from_y(self, y): position = self._get_position(y) func = self.trace.get_func_from_position(position) return func
class QFeatureMapView(QGraphicsView): """ Main view for feature map scene. """ def __init__(self, disasm_view, parent=None): super().__init__(parent) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setMouseTracking(True) self._scene = QGraphicsScene(parent=self) self.setScene(self._scene) self.fm: FeatureMapItem = FeatureMapItem(disasm_view) self._scale: float = 1.0 self._scene.addItem(self.fm) self.setBackgroundBrush(Conf.palette_base) self.setResizeAnchor(QGraphicsView.NoAnchor) self.setTransformationAnchor(QGraphicsView.NoAnchor) self.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.update_size() self._base_width: int = 0 def mouseMoveEvent(self, event): """ Handle mouse move events. Mouse move events whilst not holding a mouse button will not be propagated down to QGraphicsItems, so we catch the movement events here in the view and forward them to the feature map item. """ scene_pt = self.mapToScene(event.pos().x(), event.pos().y()) item_pt = self.fm.mapFromScene(scene_pt) self.fm.on_mouse_move_event_from_view(item_pt) super().mouseMoveEvent(event) def wheelEvent(self, event): """ Handle wheel events to scale and translate the feature map. """ if event.modifiers() & Qt.ControlModifier == Qt.ControlModifier: self.adjust_viewport_scale( 1.25 if event.delta() > 0 else 1 / 1.25, QPoint(event.pos().x(), event.pos().y())) else: self.translate(100 * (-1 if event.delta() < 0 else 1), 0) super().wheelEvent(event) def resizeEvent(self, event): # pylint: disable=unused-argument """ Handle view resize events, updating the feature map size accordingly. """ self.update_size() def adjust_viewport_scale(self, scale: Optional[float] = None, point: Optional[QPoint] = None): """ Adjust viewport scale factor. """ if point is None: point = QPoint(0, 0) point_rel = self.mapToScene(point).x() / self.fm.width if scale is None: self._scale = 1.0 else: self._scale *= scale if self._scale < 1.0: self._scale = 1.0 self.update_size() self.translate( self.mapToScene(point).x() - point_rel * self.fm.width, 0) def keyPressEvent(self, event): """ Handle key events. """ if event.modifiers() & Qt.ControlModifier == Qt.ControlModifier: if event.key() == Qt.Key_0: self.adjust_viewport_scale() event.accept() return elif event.key() == Qt.Key_Equal: self.adjust_viewport_scale(1.25) event.accept() return elif event.key() == Qt.Key_Minus: self.adjust_viewport_scale(1 / 1.25) event.accept() return super().keyPressEvent(event) def changeEvent(self, event: QEvent): """ Redraw on color scheme update. """ if event.type() == QEvent.StyleChange: self.setBackgroundBrush(Conf.palette_base) self.fm.refresh() def update_size(self): """ Resize feature map. """ vg = self.viewport().geometry() if self._scale <= 1.0: # Only resize to feature map to viewport width if scale is at base level to not disturb preferred size self._base_width = vg.width() self.fm.set_width(self._base_width * self._scale) self.fm.set_height(vg.height()) self.fm.refresh() self.setSceneRect(self._scene.itemsBoundingRect())