def __init__(self, v_from: Vertex, v_to: Vertex, node: Vertex, oriented: bool, weight: int = 1, weighted: bool = True): super().__init__() self.v_from = v_from self.v_to = v_to self.node = node self.weighted = weighted self.weight = weight # self.pen = QPen(QBrush(QColor(0, 0, 0)), 3) if weighted: self.ellipse = QGraphicsEllipseItem(self.node.x - 8, self.node.y - 8, 16, 16) self.ellipse.setBrush(QBrush(QColor(0, 0, 0))) self.addToGroup(self.ellipse) text = QGraphicsSimpleTextItem(str(self.weight)) font = QFont() font.setPixelSize(8) text.setPen(QColor(255, 255, 255)) text.setFont(font) # настраиваем расположение текста if self.weight < 10: text.setPos(self.node.x - 16 / 6, self.node.y - 16 / 3) else: text.setPos(self.node.x - 4, self.node.y - 16 / 3) self.addToGroup(text) else: self.ellipse = QGraphicsEllipseItem(self.node.x - 4, self.node.y - 4, 8, 8) self.ellipse.setBrush(QBrush(QColor(0, 0, 0))) self.addToGroup(self.ellipse)
class EdgeItem(GraphItem): _qt_pen_styles = { 'dashed': Qt.DashLine, 'dotted': Qt.DotLine, 'solid': Qt.SolidLine, } def __init__(self, highlight_level, spline, label_center, label, from_node, to_node, parent=None, penwidth=1, edge_color=None, style='solid'): super(EdgeItem, self).__init__(highlight_level, parent) self.from_node = from_node self.from_node.add_outgoing_edge(self) self.to_node = to_node self.to_node.add_incoming_edge(self) self._default_edge_color = self._COLOR_BLACK if edge_color is not None: self._default_edge_color = edge_color self._default_text_color = self._COLOR_BLACK self._default_color = self._COLOR_BLACK self._text_brush = QBrush(self._default_color) self._shape_brush = QBrush(self._default_color) if style in ['dashed', 'dotted']: self._shape_brush = QBrush(Qt.transparent) self._label_pen = QPen() self._label_pen.setColor(self._default_text_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._edge_pen = QPen(self._label_pen) self._edge_pen.setWidth(penwidth) self._edge_pen.setColor(self._default_edge_color) self._edge_pen.setStyle(self._qt_pen_styles.get(style, Qt.SolidLine)) self._sibling_edges = set() self._label = None if label is not None: self._label = QGraphicsSimpleTextItem(label) self._label.setFont(GraphItem._LABEL_FONT) label_rect = self._label.boundingRect() label_rect.moveCenter(label_center) self._label.setPos(label_rect.x(), label_rect.y()) self._label.hoverEnterEvent = self._handle_hoverEnterEvent self._label.hoverLeaveEvent = self._handle_hoverLeaveEvent self._label.setAcceptHoverEvents(True) # spline specification according to # http://www.graphviz.org/doc/info/attrs.html#k:splineType coordinates = spline.split(' ') # extract optional end_point end_point = None if (coordinates[0].startswith('e,')): parts = coordinates.pop(0)[2:].split(',') end_point = QPointF(float(parts[0]), -float(parts[1])) # extract optional start_point if (coordinates[0].startswith('s,')): parts = coordinates.pop(0).split(',') # first point parts = coordinates.pop(0).split(',') point = QPointF(float(parts[0]), -float(parts[1])) path = QPainterPath(point) while len(coordinates) > 2: # extract triple of points for a cubic spline parts = coordinates.pop(0).split(',') point1 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point2 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point3 = QPointF(float(parts[0]), -float(parts[1])) path.cubicTo(point1, point2, point3) self._arrow = None if end_point is not None: # draw arrow self._arrow = QGraphicsPolygonItem() polygon = QPolygonF() polygon.append(point3) offset = QPointF(end_point - point3) corner1 = QPointF(-offset.y(), offset.x()) * 0.35 corner2 = QPointF(offset.y(), -offset.x()) * 0.35 polygon.append(point3 + corner1) polygon.append(end_point) polygon.append(point3 + corner2) self._arrow.setPolygon(polygon) self._arrow.hoverEnterEvent = self._handle_hoverEnterEvent self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent self._arrow.setAcceptHoverEvents(True) self._path = QGraphicsPathItem(parent) self._path.setPath(path) self.addToGroup(self._path) self.set_node_color() self.set_label_color() def add_to_scene(self, scene): scene.addItem(self) if self._label is not None: scene.addItem(self._label) if self._arrow is not None: scene.addItem(self._arrow) def setToolTip(self, tool_tip): super(EdgeItem, self).setToolTip(tool_tip) if self._label is not None: self._label.setToolTip(tool_tip) if self._arrow is not None: self._arrow.setToolTip(tool_tip) def add_sibling_edge(self, edge): self._sibling_edges.add(edge) def set_node_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) self._text_brush.setColor(self._default_color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(self._default_edge_color) self._edge_pen.setColor(self._default_edge_color) else: self._label_pen.setColor(color) self._text_brush.setColor(color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(color) self._edge_pen.setColor(color) self._path.setPen(self._edge_pen) if self._arrow is not None: self._arrow.setBrush(self._shape_brush) self._arrow.setPen(self._edge_pen) def set_label_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) else: self._label_pen.setColor(color) if self._label is not None: self._label.setBrush(self._text_brush) self._label.setPen(self._label_pen) def _handle_hoverEnterEvent(self, event): # hovered edge item in red self.set_node_color(self._COLOR_RED) self.set_label_color(self._COLOR_RED) if self._highlight_level > 1: if self.from_node != self.to_node: # from-node in blue self.from_node.set_node_color(self._COLOR_BLUE) # to-node in green self.to_node.set_node_color(self._COLOR_GREEN) else: # from-node/in-node in teal self.from_node.set_node_color(self._COLOR_TEAL) self.to_node.set_node_color(self._COLOR_TEAL) if self._highlight_level > 2: # sibling edges in orange for sibling_edge in self._sibling_edges: sibling_edge.set_node_color(self._COLOR_ORANGE) def _handle_hoverLeaveEvent(self, event): self.set_node_color() self.set_label_color() if self._highlight_level > 1: self.from_node.set_node_color() self.to_node.set_node_color() if self._highlight_level > 2: for sibling_edge in self._sibling_edges: sibling_edge.set_node_color()
class EdgeItem(QGraphicsItem): LINE_WIDTH = 1 OFFSET = 8 # 方向线偏离中心线的距离 MIN_ARROW_WIDTH, MAX_ARROW_WIDTH = 1, 8 double_click_callback = EMPTY_FUNC def __init__(self, edge_id): super().__init__() self.setZValue(1) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setAcceptHoverEvents(True) self.edge_id = edge_id self.text_item = QGraphicsSimpleTextItem('', self) self.text_item.setZValue(4) self.style = { 'name': f'Edge{edge_id}', 'color': Qt.black, 'width': 0.5, # 0~1 的中间值 'line': Qt.SolidLine, 'show_arrow': False, 'text': '', 'text_color': Qt.black, 'show_text': False, } self.hover = False def type(self): return QGraphicsItem.UserType + abs(hash(EdgeItem)) def boundingRect(self): return self.bounding_rect def shape(self): path = QPainterPath() path.addPolygon(self.shape_polygon) path.closeSubpath() return path # ------------------------------------------------------------------------- def adjust(self, src_p: QPointF, dst_p: QPointF): self.angle = getAngle(src_p, dst_p) self.src_p = src_p self.arrow_p = (src_p + 2 * dst_p) / 3 # 箭头开始位置, 前端2/3处 self.dst_p = dst_p W1 = 1 * self.OFFSET W2 = 2 * self.OFFSET W3 = 3 * self.OFFSET vec = getRightOffsetVector(self.angle) self.arrow_polygon = QPolygonF([ src_p + vec * W1, dst_p + vec * W1, self.arrow_p + vec * W2, self.arrow_p + vec * W1 ]) self.shape_polygon = QPolygonF( [src_p, src_p + vec * W2, dst_p + vec * W2, dst_p]) self.bounding_rect = QRectF(src_p, dst_p).normalized() # normalized 正方向 self.bounding_rect.adjust(-W3, -W3, W3, W3) self.text_p = ((src_p + dst_p) / 2) + vec * W1 self.text_item.setPos(self.text_p) self.prepareGeometryChange() # ------------------------------------------------------------------------- def paint(self, painter, option, widget=None): if self.style['show_arrow'] or self.hover: width = threshold(0.0, self.style['width'], 1.0) width = width * (self.MAX_ARROW_WIDTH - self.MIN_ARROW_WIDTH) + self.MIN_ARROW_WIDTH painter.setPen(QPen(self.style['color'], width, self.style['line'])) painter.setBrush(self.style['color']) painter.drawPolygon(self.arrow_polygon) else: # TODO 定制线类型 虚线或实线 painter.setPen(QPen(Qt.black, self.LINE_WIDTH)) painter.drawLine(self.src_p, self.dst_p) if (self.style['show_arrow'] and self.style['show_text']) or self.hover: self.text_item.setPen(self.style['text_color']) self.text_item.setText( f"{self.style['name']}\n{self.style['text']}") self.text_item.show() else: self.text_item.hide() # ------------------------------------------------------------------------- def mouseDoubleClickEvent(self, event): self.double_click_callback(self.edge_id) super().mouseDoubleClickEvent(event) def hoverEnterEvent(self, event): self.hover = True self.update() super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.hover = False self.update() super().hoverLeaveEvent(event) # ------------------------------------------------------------------------- def setStyle(self, style) -> None: for key in self.style: try: self.style[key] = style[key] except KeyError: pass self.update()
class NodeItem(QGraphicsItem): # 面向图形界面, 负责控制显示效果 MIN_SIZE, MAX_SIZE = 10, 100 press_callback = EMPTY_FUNC release_callback = EMPTY_FUNC double_click_callback = EMPTY_FUNC move_callback = EMPTY_FUNC def __init__(self, node_id): super().__init__() self.setZValue(2) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsMovable) # 可以移动 self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) # 面向UINet 负责增添逻辑操作 self.node_id = node_id self.hover = False self.cached_size = None self.bounding_rect = QRectF() # XXX 在重绘时会被更新,重绘前可能会节点可能会被覆盖显示 # self.call_backs = CallTable() self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setZValue(3) # 面向图形界面, 负责控制显示效果 self.style = { 'name': f' {node_id}', 'color': Qt.white, 'shape': 'Pie', # ('Pie', 'Rect', QPixmap) 'size': 0.5, # 0~1 的中间值 'text': '', 'text_color': Qt.black, 'show_text': False, } def type(self) -> int: return QGraphicsItem.UserType + abs(hash(NodeItem)) def boundingRect(self): return self.bounding_rect def shape(self): path = QPainterPath() path.addRect(self.bounding_rect) return path def paint(self, painter, option, widget=None) -> None: # 绘制尺寸 size = threshold(0.0, self.style['size'], 1.0) size = size * (self.MAX_SIZE - self.MIN_SIZE) + self.MIN_SIZE if size != self.cached_size: self.bounding_rect = QRectF(-size / 2, -size / 2, size, size) self.cached_size = size # 绘制图标或颜色和形状 if isinstance(self.style['shape'], QPixmap): pixmap = self.style['shape'] painter.drawPixmap(self.bounding_rect, pixmap, QRectF(pixmap.rect())) elif self.style['shape'] == 'Pie': painter.setBrush(self.style['color']) painter.drawEllipse(self.bounding_rect) # or drawRect elif self.style['shape'] == 'Rect': painter.setBrush(self.style['color']) painter.drawRect(self.bounding_rect) # or drawRect else: raise ValueError('未知shape类型', self.style['shape']) # 绘制说明 text = f"{self.style['name']}\n" if self.style['show_text'] or self.hover: text += str(self.style['text']) self.text_item.setPen(self.style['text_color']) self.text_item.setText(text) self.text_item.show() # ------------------------------------------------------------------------------------------------------------------ def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionHasChanged: self.style['pos'] = self.pos() # 更新位置变化 self.move_callback(self.node_id) return super().itemChange(change, value) def mousePressEvent(self, event): self.press_callback(self.node_id) return super().mousePressEvent(event) def mouseReleaseEvent(self, event): self.release_callback(self.node_id) return super().mouseReleaseEvent(event) def mouseDoubleClickEvent(self, event): self.double_click_callback(self.node_id) return super().mouseDoubleClickEvent(event) def hoverEnterEvent(self, event): self.hover = True return super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.hover = False return super().hoverLeaveEvent(event) # ------------------------------------------------------------------------------------------------------------------ def setStyle(self, style) -> None: for key in self.style: if key in style: self.style[key] = style[key] self.update() def checkPos(self, x, y): pos = QPointF(x, y) if self.pos() != pos: self.setPos(pos)
class CameraView(QGraphicsObject): font: QFont = QFont("monospace", 16) stick_link_requested = pyqtSignal(StickWidget) stick_context_menu = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject') stick_widgets_out_of_sync = pyqtSignal('PyQt_PyObject') visibility_toggled = pyqtSignal() synchronize_clicked = pyqtSignal('PyQt_PyObject') previous_photo_clicked = pyqtSignal('PyQt_PyObject') next_photo_clicked = pyqtSignal('PyQt_PyObject') sync_confirm_clicked = pyqtSignal('PyQt_PyObject') sync_cancel_clicked = pyqtSignal('PyQt_PyObject') first_photo_clicked = pyqtSignal('PyQt_PyObject') enter_pressed = pyqtSignal() def __init__(self, scale: float, parent: Optional[QGraphicsItem] = None): QGraphicsObject.__init__(self, parent) self.current_highlight_color = QColor(0, 0, 0, 0) self.current_timer = -1 self.scaling = scale self.pixmap = QGraphicsPixmapItem(self) self.stick_widgets: List[StickWidget] = [] self.link_cam_text = QGraphicsSimpleTextItem("Link camera...", self) self.link_cam_text.setZValue(40) self.link_cam_text.setVisible(False) self.link_cam_text.setFont(CameraView.font) self.link_cam_text.setPos(0, 0) self.link_cam_text.setPen(QPen(QColor(255, 255, 255, 255))) self.link_cam_text.setBrush(QBrush(QColor(255, 255, 255, 255))) self.show_add_buttons = False self.camera = None self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.show_stick_widgets = False self.setAcceptHoverEvents(True) self.stick_edit_mode = False self.original_pixmap = self.pixmap.pixmap() self.hovered = False self.mode = 0 # TODO make enum Mode self.click_handler = None self.double_click_handler: Callable[[int, int], None] = None self.stick_widget_mode = StickMode.Display self.highlight_animation = QPropertyAnimation(self, b"highlight_color") self.highlight_animation.setEasingCurve(QEasingCurve.Linear) self.highlight_animation.valueChanged.connect( self.handle_highlight_color_changed) self.highlight_rect = QGraphicsRectItem(self) self.highlight_rect.setZValue(4) self.highlight_rect.setPen(QPen(QColor(0, 0, 0, 0))) self.title_btn = Button('btn_title', '', parent=self) self.title_btn.setFlag(QGraphicsItem.ItemIgnoresTransformations, False) self.title_btn.setZValue(5) self.title_btn.setVisible(False) self.sticks_without_width: List[Stick] = [] self.current_image_name: str = '' self.control_widget = ControlWidget(parent=self) self.control_widget.setFlag(QGraphicsItem.ItemIgnoresTransformations, False) self.control_widget.setVisible(True) self._connect_control_buttons() self.image_available = True self.blur_eff = QGraphicsBlurEffect() self.blur_eff.setBlurRadius(5.0) self.blur_eff.setEnabled(False) self.pixmap.setGraphicsEffect(self.blur_eff) self.overlay_message = QGraphicsSimpleTextItem('not available', parent=self) font = self.title_btn.font font.setPointSize(48) self.overlay_message.setFont(font) self.overlay_message.setBrush(QBrush(QColor(200, 200, 200, 200))) self.overlay_message.setPen(QPen(QColor(0, 0, 0, 200), 2.0)) self.overlay_message.setVisible(False) self.overlay_message.setZValue(6) self.stick_box = QGraphicsRectItem(parent=self) self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True) self.stick_box.setVisible(False) self.stick_box_start_pos = QPoint() def _connect_control_buttons(self): self.control_widget.synchronize_btn.clicked.connect( lambda: self.synchronize_clicked.emit(self)) self.control_widget.prev_photo_btn.clicked.connect( lambda: self.previous_photo_clicked.emit(self)) self.control_widget.next_photo_btn.clicked.connect( lambda: self.next_photo_clicked.emit(self)) self.control_widget.accept_btn.clicked.connect( lambda: self.sync_confirm_clicked.emit(self)) self.control_widget.cancel_btn.clicked.connect( lambda: self.sync_cancel_clicked.emit(self)) self.control_widget.first_photo_btn.clicked.connect( lambda: self.first_photo_clicked.emit(self)) def paint(self, painter: QPainter, option: PyQt5.QtWidgets.QStyleOptionGraphicsItem, widget: QWidget): if self.pixmap.pixmap().isNull(): return painter.setRenderHint(QPainter.Antialiasing, True) if self.show_stick_widgets: brush = QBrush(QColor(255, 255, 255, 100)) painter.fillRect(self.boundingRect(), brush) if self.mode and self.hovered: pen = QPen(QColor(0, 125, 200, 255)) pen.setWidth(4) painter.setPen(pen) def boundingRect(self) -> PyQt5.QtCore.QRectF: return self.pixmap.boundingRect().united( self.title_btn.boundingRect().translated(self.title_btn.pos())) def initialise_with(self, camera: Camera): if self.camera is not None: self.camera.stick_added.disconnect(self.handle_stick_created) self.camera.sticks_added.disconnect(self.handle_sticks_added) self.camera.stick_removed.disconnect(self.handle_stick_removed) self.camera.sticks_removed.disconnect(self.handle_sticks_removed) self.camera.stick_changed.disconnect(self.handle_stick_changed) self.camera = camera self.prepareGeometryChange() self.set_image(camera.rep_image, Path(camera.rep_image_path).name) self.title_btn.set_label(self.camera.folder.name) self.title_btn.set_height(46) self.title_btn.fit_to_contents() self.title_btn.set_width(int(self.boundingRect().width())) self.title_btn.setPos(0, self.boundingRect().height()) self.control_widget.title_btn.set_label(self.camera.folder.name) self.camera.stick_added.connect(self.handle_stick_created) self.camera.sticks_added.connect(self.handle_sticks_added) self.camera.stick_removed.connect(self.handle_stick_removed) self.camera.sticks_removed.connect(self.handle_sticks_removed) self.camera.stick_changed.connect(self.handle_stick_changed) self.control_widget.set_font_height(32) self.control_widget.set_widget_height( self.title_btn.boundingRect().height()) self.control_widget.set_widget_width(int(self.boundingRect().width())) self.control_widget.setPos(0, self.pixmap.boundingRect().height() ) #self.boundingRect().height()) self.control_widget.set_mode('view') self.update_stick_widgets() def set_image(self, img: Optional[np.ndarray] = None, image_name: Optional[str] = None): if img is None: self.show_overlay_message('not available') return self.show_overlay_message(None) self.prepareGeometryChange() barray = QByteArray(img.tobytes()) image = QImage(barray, img.shape[1], img.shape[0], QImage.Format_BGR888) self.original_pixmap = QPixmap.fromImage(image) self.pixmap.setPixmap(self.original_pixmap) self.highlight_rect.setRect(self.boundingRect()) self.current_image_name = image_name def update_stick_widgets(self): stick_length = 60 for stick in self.camera.sticks: sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) stick_length = stick.length_cm self.update_stick_box() self.scene().update() def scale_item(self, factor: float): self.prepareGeometryChange() pixmap = self.original_pixmap.scaledToHeight( int(self.original_pixmap.height() * factor)) self.pixmap.setPixmap(pixmap) self.__update_title() def set_show_stick_widgets(self, value: bool): for sw in self.stick_widgets: sw.setVisible(value) self.scene().update() def hoverEnterEvent(self, e: QGraphicsSceneHoverEvent): self.hovered = True self.scene().update(self.sceneBoundingRect()) def hoverLeaveEvent(self, e: QGraphicsSceneHoverEvent): self.hovered = False self.scene().update(self.sceneBoundingRect()) def mousePressEvent(self, e: QGraphicsSceneMouseEvent): super().mousePressEvent(e) def mouseReleaseEvent(self, e: QGraphicsSceneMouseEvent): if self.mode == 1: self.click_handler(self.camera) def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent): if self.stick_widget_mode == StickMode.EditDelete: x = event.pos().toPoint().x() y = event.pos().toPoint().y() stick = self.camera.create_new_sticks( [(np.array([[x, y - 50], [x, y + 50]]), 3)], self.current_image_name)[ 0] #self.dataset.create_new_stick(self.camera) self.sticks_without_width.append(stick) def set_button_mode(self, click_handler: Callable[[Camera], None], data: str): self.mode = 1 # TODO make a proper ENUM self.click_handler = lambda c: click_handler(c, data) def set_display_mode(self): self.mode = 0 # TODO make a proper ENUM self.click_handler = None def _remove_stick_widgets(self): for sw in self.stick_widgets: sw.setParentItem(None) self.scene().removeItem(sw) sw.deleteLater() self.stick_widgets.clear() def handle_stick_created(self, stick: Stick): if stick.camera_id != self.camera.id: return sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) self.stick_widgets_out_of_sync.emit(self) self.update() def handle_stick_removed(self, stick: Stick): if stick.camera_id != self.camera.id: return stick_widget = next( filter(lambda sw: sw.stick.id == stick.id, self.stick_widgets)) self.disconnect_stick_widget_signals(stick_widget) self.stick_widgets.remove(stick_widget) stick_widget.setParentItem(None) self.scene().removeItem(stick_widget) stick_widget.deleteLater() self.update() def handle_sticks_removed(self, sticks: List[Stick]): if sticks[0].camera_id != self.camera.id: return for stick in sticks: to_remove: StickWidget = None for sw in self.stick_widgets: if sw.stick.id == stick.id: to_remove = sw break self.stick_widgets.remove(to_remove) to_remove.setParentItem(None) if self.scene() is not None: self.scene().removeItem(to_remove) to_remove.deleteLater() self.update() def handle_sticks_added(self, sticks: List[Stick]): if len(sticks) == 0: return if sticks[0].camera_id != self.camera.id: return for stick in sticks: sw = StickWidget(stick, self.camera, self) sw.set_mode(self.stick_widget_mode) self.connect_stick_widget_signals(sw) self.stick_widgets.append(sw) self.update_stick_box() self.stick_widgets_out_of_sync.emit(self) self.update() def connect_stick_widget_signals(self, stick_widget: StickWidget): stick_widget.delete_clicked.connect( self.handle_stick_widget_delete_clicked) stick_widget.stick_changed.connect(self.handle_stick_widget_changed) stick_widget.link_initiated.connect(self.handle_stick_link_initiated) stick_widget.right_clicked.connect( self.handle_stick_widget_context_menu) def disconnect_stick_widget_signals(self, stick_widget: StickWidget): stick_widget.delete_clicked.disconnect( self.handle_stick_widget_delete_clicked) stick_widget.stick_changed.disconnect(self.handle_stick_widget_changed) stick_widget.link_initiated.disconnect( self.handle_stick_link_initiated) stick_widget.right_clicked.disconnect( self.handle_stick_widget_context_menu) def handle_stick_widget_delete_clicked(self, stick: Stick): self.camera.remove_stick(stick) def set_stick_widgets_mode(self, mode: StickMode): self.stick_widget_mode = mode for sw in self.stick_widgets: sw.set_mode(mode) self.set_stick_edit_mode(mode == StickMode.Edit) def handle_stick_widget_changed(self, stick_widget: StickWidget): self.camera.stick_changed.emit(stick_widget.stick) def handle_stick_changed(self, stick: Stick): if stick.camera_id != self.camera.id: return sw = next( filter(lambda _sw: _sw.stick.id == stick.id, self.stick_widgets)) sw.adjust_line() sw.update_tooltip() def handle_stick_link_initiated(self, stick_widget: StickWidget): self.stick_link_requested.emit(stick_widget) def get_top_left(self) -> QPointF: return self.sceneBoundingRect().topLeft() def get_top_right(self) -> QPointF: return self.sceneBoundingRect().topRight() def highlight(self, color: Optional[QColor]): if color is None: self.highlight_animation.stop() self.highlight_rect.setVisible(False) return alpha = color.alpha() color.setAlpha(0) self.highlight_animation.setStartValue(color) self.highlight_animation.setEndValue(color) color.setAlpha(alpha) self.highlight_animation.setKeyValueAt(0.5, color) self.highlight_animation.setDuration(2000) self.highlight_animation.setLoopCount(-1) self.highlight_rect.setPen(QPen(color)) self.highlight_rect.setVisible(True) self.highlight_animation.start() @pyqtProperty(QColor) def highlight_color(self) -> QColor: return self.current_highlight_color @highlight_color.setter def highlight_color(self, color: QColor): self.current_highlight_color = color def handle_highlight_color_changed(self, color: QColor): self.highlight_rect.setBrush(QBrush(color)) self.update() def handle_stick_widget_context_menu(self, sender: Dict[str, StickWidget]): self.stick_context_menu.emit(sender['stick_widget'], self) def show_overlay_message(self, msg: Optional[str]): if msg is None: self.overlay_message.setVisible(False) self.blur_eff.setEnabled(False) return self.overlay_message.setText(msg) self.overlay_message.setPos( self.pixmap.boundingRect().center() - QPointF(0.5 * self.overlay_message.boundingRect().width(), 0.5 * self.overlay_message.boundingRect().height())) self.overlay_message.setVisible(True) self.blur_eff.setEnabled(True) def show_status_message(self, msg: Optional[str]): if msg is None: self.control_widget.set_title_text(self.camera.folder.name) else: self.control_widget.set_title_text(msg) def update_stick_box(self): left = 9000 right = 0 top = 9000 bottom = -1 for stick in self.camera.sticks: left = min(left, min(stick.top[0], stick.bottom[0])) right = max(right, max(stick.top[0], stick.bottom[0])) top = min(top, min(stick.top[1], stick.bottom[1])) bottom = max(bottom, max(stick.top[1], stick.bottom[1])) left -= 100 right += 100 top -= 100 bottom += 100 self.stick_box.setRect(left, top, right - left, bottom - top) pen = QPen(QColor(0, 100, 200, 200)) pen.setWidth(2) pen.setStyle(Qt.DashLine) self.stick_box.setPen(pen) def set_stick_edit_mode(self, is_edit: bool): if is_edit: self.update_stick_box() self.stick_box_start_pos = self.stick_box.pos() for sw in self.stick_widgets: sw.setParentItem(self.stick_box) else: offset = self.stick_box.pos() - self.stick_box_start_pos for sw in self.stick_widgets: stick = sw.stick stick.translate(np.array([int(offset.x()), int(offset.y())])) sw.setParentItem(self) sw.set_stick(stick) self.stick_box.setParentItem(None) self.stick_box = QGraphicsRectItem(self) self.stick_box.setFlag(QGraphicsItem.ItemIsMovable, True) self.stick_box.setVisible(False) self.stick_box.setVisible(is_edit) def keyPressEvent(self, event: QKeyEvent) -> None: pass def keyReleaseEvent(self, event: QKeyEvent) -> None: if event.key() in [Qt.Key_Right, Qt.Key_Tab, Qt.Key_Space]: self.control_widget.next_photo_btn.click_button(True) elif event.key() in [Qt.Key_Left]: self.control_widget.prev_photo_btn.click_button(True) elif event.key() == Qt.Key_S: self.enter_pressed.emit()
class NodeItem(GraphItem): def __init__(self, highlight_level, bounding_box, label, shape, color=None, parent=None, label_pos=None, tooltip=None): super(NodeItem, self).__init__(highlight_level, parent) self._default_color = self._COLOR_BLACK if color is None else color self._brush = QBrush(self._default_color) self._label_pen = QPen() self._label_pen.setColor(self._default_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._ellipse_pen = QPen(self._label_pen) self._ellipse_pen.setWidth(1) self._incoming_edges = set() self._outgoing_edges = set() self.parse_shape(shape, bounding_box) self.addToGroup(self._graphics_item) self._label = QGraphicsSimpleTextItem(label) self._label.setFont(GraphItem._LABEL_FONT) label_rect = self._label.boundingRect() if label_pos is None: label_rect.moveCenter(bounding_box.center()) else: label_rect.moveCenter(label_pos) self._label.setPos(label_rect.x(), label_rect.y()) self.addToGroup(self._label) if tooltip is not None: self.setToolTip(tooltip) self.set_node_color() self.setAcceptHoverEvents(True) self.hovershape = None def parse_shape(self, shape, bounding_box): if shape in ('box', 'rect', 'rectangle'): self._graphics_item = QGraphicsRectItem(bounding_box) elif shape in ('ellipse', 'oval', 'circle'): self._graphics_item = QGraphicsEllipseItem(bounding_box) elif shape in ('box3d', ): self._graphics_item = QGraphicsBox3dItem(bounding_box) else: print("Invalid shape '%s', defaulting to ellipse" % shape, file=sys.stderr) self._graphics_item = QGraphicsEllipseItem(bounding_box) def set_hovershape(self, newhovershape): self.hovershape = newhovershape def shape(self): if self.hovershape is not None: path = QPainterPath() path.addRect(self.hovershape) return path else: return super(self.__class__, self).shape() def add_incoming_edge(self, edge): self._incoming_edges.add(edge) def add_outgoing_edge(self, edge): self._outgoing_edges.add(edge) def set_node_color(self, color=None): if color is None: color = self._default_color self._brush.setColor(color) self._ellipse_pen.setColor(color) self._label_pen.setColor(color) self._graphics_item.setPen(self._ellipse_pen) self._label.setBrush(self._brush) self._label.setPen(self._label_pen) def hoverEnterEvent(self, event): # hovered node item in red self.set_node_color(self._COLOR_RED) if self._highlight_level > 1: cyclic_edges = self._incoming_edges.intersection( self._outgoing_edges) # incoming edges in blue incoming_nodes = set() for incoming_edge in self._incoming_edges.difference(cyclic_edges): incoming_edge.set_node_color(self._COLOR_BLUE) incoming_edge.set_label_color(self._COLOR_BLUE) if incoming_edge.from_node != self: incoming_nodes.add(incoming_edge.from_node) # outgoing edges in green outgoing_nodes = set() for outgoing_edge in self._outgoing_edges.difference(cyclic_edges): outgoing_edge.set_node_color(self._COLOR_GREEN) outgoing_edge.set_label_color(self._COLOR_GREEN) if outgoing_edge.to_node != self: outgoing_nodes.add(outgoing_edge.to_node) # incoming/outgoing edges in teal for edge in cyclic_edges: edge.set_node_color(self._COLOR_TEAL) if self._highlight_level > 2: cyclic_nodes = incoming_nodes.intersection(outgoing_nodes) # incoming nodes in blue for incoming_node in incoming_nodes.difference(cyclic_nodes): incoming_node.set_node_color(self._COLOR_BLUE) # outgoing nodes in green for outgoing_node in outgoing_nodes.difference(cyclic_nodes): outgoing_node.set_node_color(self._COLOR_GREEN) # incoming/outgoing nodes in teal for node in cyclic_nodes: node.set_node_color(self._COLOR_TEAL) def hoverLeaveEvent(self, event): self.set_node_color() if self._highlight_level > 1: for incoming_edge in self._incoming_edges: incoming_edge.set_node_color() incoming_edge.set_label_color() if self._highlight_level > 2 and incoming_edge.from_node != self: incoming_edge.from_node.set_node_color() for outgoing_edge in self._outgoing_edges: outgoing_edge.set_node_color() outgoing_edge.set_label_color() if self._highlight_level > 2 and outgoing_edge.to_node != self: outgoing_edge.to_node.set_node_color()