class FilterIcon(QGraphicsEllipseItem): """An icon to show that a Link has filters.""" def __init__(self, x, y, w, h, parent): super().__init__(x, y, w, h, parent) self._parent = parent color = QColor("slateblue") self.setBrush(qApp.palette().window()) # pylint: disable=undefined-variable self._text_item = QGraphicsTextItem(self) font = QFont('Font Awesome 5 Free Solid') self._text_item.setFont(font) self._text_item.setPos(0, 0) self._text_item.setPlainText("\uf0b0") self._text_item.setDefaultTextColor(color) self._text_item.setPos(self.sceneBoundingRect().center() - self._text_item.sceneBoundingRect().center()) self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True) self.setCursor(Qt.PointingHandCursor) def itemChange(self, change, value): """Selects the parent item instead of this.""" if change == QGraphicsItem.GraphicsItemChange.ItemSelectedChange and value == 1: if not self._parent.isSelected(): self._parent.setSelected(True) return not value return super().itemChange(change, value)
def overlay_text( self, message: str, color: int, size: int, x: int, y: int, timeout: int, font_name: str, centered: bool, shadow: bool, ): gfx = QGraphicsTextItem(message) gfx.setDefaultTextColor(decode_color(color)) font = QFont(font_name, min(50, size)) font.setStyleHint(QFont.SansSerif) gfx.setFont(font) if shadow: effect = QGraphicsDropShadowEffect(gfx) effect.setBlurRadius(0) effect.setColor(Qt.GlobalColor.black) effect.setOffset(1, 1) gfx.setGraphicsEffect(effect) if centered: # The provided x, y is at the center of the text bound = gfx.boundingRect() gfx.setPos(x - (bound.width() / 2), y - (bound.height() / 2)) else: gfx.setPos(x, y) self._finalize_gfx(gfx, timeout)
def draw_message_text(item: QGraphicsTextItem, font: QFont, text: str, x: int, y: int, max_width: int): item.setDefaultTextColor(QColor.fromRgba(0xFF440400)) item.setFont(font) item.setPos(x, y) # Calculate how many character to trim *without* making copies of the string. font_metrics = QFontMetrics(font) cur_width = font_metrics.width(text) cur_end = len(text) - 1 while cur_width > max_width: cur_width -= font_metrics.charWidth(text, cur_end) cur_end -= 1 item.setPlainText(text[:cur_end + 1])
class FilterIcon(QGraphicsEllipseItem): """An icon to show that a Link has filters.""" def __init__(self, x, y, w, h, parent): super().__init__(x, y, w, h, parent) self._parent = parent color = QColor("slateblue") self.setBrush(qApp.palette().window()) # pylint: disable=undefined-variable self._text_item = QGraphicsTextItem(self) font = QFont('Font Awesome 5 Free Solid') self._text_item.setFont(font) self._text_item.setPos(0, 0) self._text_item.setPlainText("\uf0b0") self._text_item.setDefaultTextColor(color) self._text_item.setPos(self.sceneBoundingRect().center() - self._text_item.sceneBoundingRect().center()) self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=False)
class _LinkIcon(QGraphicsEllipseItem): """An icon to show over a Link.""" def __init__(self, x, y, w, h, parent): super().__init__(x, y, w, h, parent) self._parent = parent color = QColor("slateblue") self.setBrush(qApp.palette().window()) # pylint: disable=undefined-variable self._text_item = QGraphicsTextItem(self) font = QFont('Font Awesome 5 Free Solid') self._text_item.setFont(font) self._text_item.setDefaultTextColor(color) self._svg_item = QGraphicsSvgItem(self) self._datapkg_renderer = QSvgRenderer() self._datapkg_renderer.load(":/icons/datapkg.svg") self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=False) self._block_updates = False def update_icon(self): """Sets the icon (filter, datapkg, or none), depending on Connection state.""" connection = self._parent.connection if connection.use_datapackage: self.setVisible(True) self._svg_item.setVisible(True) self._svg_item.setSharedRenderer(self._datapkg_renderer) scale = 0.8 * self.rect().width( ) / self._datapkg_renderer.defaultSize().width() self._svg_item.setScale(scale) self._svg_item.setPos(0, 0) self._svg_item.setPos(self.sceneBoundingRect().center() - self._svg_item.sceneBoundingRect().center()) self._text_item.setVisible(False) return if connection.has_filters(): self.setVisible(True) self._text_item.setVisible(True) self._text_item.setPlainText("\uf0b0") self._svg_item.setPos(0, 0) self._text_item.setPos( self.sceneBoundingRect().center() - self._text_item.sceneBoundingRect().center()) self._svg_item.setVisible(False) return self.setVisible(False) self._text_item.setVisible(False) self._svg_item.setVisible(False)
def dibujar(self): pen = QPen() brush = QBrush() pen.setWidth(3) for i in self.organizador: color = QColor(i.red, i.green, i.blue) brush.setStyle(Qt.SolidPattern) brush.setColor(color) pen.setColor(color) self.scene.addEllipse(i.or_x, i.or_y, 7, 7, pen, brush) self.scene.addEllipse(i.de_x, i.de_y, 7, 7, pen, brush) self.scene.addLine((i.or_x) + 3.5, (i.or_y) + 3.5, (i.de_x) + 3.5, (i.de_y) + 3.5, pen) for keys in self.organizador.grafo_dic: text = QGraphicsTextItem(str(keys)) text.setFlag(QGraphicsItem.ItemIsMovable) text.setFont(QFont("TimesNewRoman", 12, QFont.ExtraBold)) self.scene.addItem(text) text.setPos(keys[0], keys[1])
class NodeItem(AbstractNodeItem): """ Base Node item. Args: name (str): name displayed on the node. parent (QtWidgets.QGraphicsItem): parent item. """ def __init__(self, name='node', parent=None): super(NodeItem, self).__init__(name, parent) pixmap = QtGui.QPixmap(ICON_NODE_BASE) if pixmap.size().height() > NODE_ICON_SIZE: pixmap = pixmap.scaledToHeight(NODE_ICON_SIZE, QtCore.Qt.SmoothTransformation) self._properties['icon'] = ICON_NODE_BASE self._icon_item = QGraphicsPixmapItem(pixmap, self) self._icon_item.setTransformationMode(QtCore.Qt.SmoothTransformation) self._text_item = QGraphicsTextItem(self.name, self) self._x_item = XDisabledItem(self, 'DISABLED') self._input_items = {} self._output_items = {} self._widgets = {} def paint(self, painter, option, widget): """ Draws the node base not the ports. Args: painter (QtGui.QPainter): painter used for drawing the item. option (QtGui.QStyleOptionGraphicsItem): used to describe the parameters needed to draw. widget (QtWidgets.QWidget): not used. """ painter.save() bg_border = 1.0 rect = QtCore.QRectF(0.5 - (bg_border / 2), 0.5 - (bg_border / 2), self._width + bg_border, self._height + bg_border) radius = 2 border_color = QtGui.QColor(*self.border_color) path = QtGui.QPainterPath() path.addRoundedRect(rect, radius, radius) painter.setPen(QtGui.QPen(border_color.darker(200), 1.5)) painter.drawPath(path) rect = self.boundingRect() bg_color = QtGui.QColor(*self.color) painter.setBrush(bg_color) painter.setPen(QtCore.Qt.NoPen) painter.drawRoundRect(rect, radius, radius) if self.selected and NODE_SEL_COLOR: painter.setBrush(QtGui.QColor(*NODE_SEL_COLOR)) painter.drawRoundRect(rect, radius, radius) label_rect = QtCore.QRectF(rect.left() + (radius / 2), rect.top() + (radius / 2), self._width - (radius / 1.25), 28) path = QtGui.QPainterPath() path.addRoundedRect(label_rect, radius / 1.5, radius / 1.5) painter.setBrush(QtGui.QColor(0, 0, 0, 50)) painter.fillPath(path, painter.brush()) border_width = 0.8 if self.selected and NODE_SEL_BORDER_COLOR: border_width = 1.2 border_color = QtGui.QColor(*NODE_SEL_BORDER_COLOR) border_rect = QtCore.QRectF(rect.left() - (border_width / 2), rect.top() - (border_width / 2), rect.width() + border_width, rect.height() + border_width) pen = QtGui.QPen(border_color, border_width) pen.setCosmetic(self.viewer().get_zoom() < 0.0) path = QtGui.QPainterPath() path.addRoundedRect(border_rect, radius, radius) painter.setBrush(QtCore.Qt.NoBrush) painter.setPen(pen) painter.drawPath(path) painter.restore() def mousePressEvent(self, event): if event.button() == QtCore.Qt.MouseButton.LeftButton: start = PortItem().boundingRect().width() end = self.boundingRect().width() - start x_pos = event.pos().x() if not start <= x_pos <= end: event.ignore() super(NodeItem, self).mousePressEvent(event) def mouseReleaseEvent(self, event): if event.modifiers() == QtCore.Qt.AltModifier: event.ignore() return super(NodeItem, self).mouseReleaseEvent(event) def itemChange(self, change, value): if change == self.ItemSelectedChange and self.scene(): self.reset_pipes() if value: self.hightlight_pipes() self.setZValue(Z_VAL_NODE) if not self.selected: self.setZValue(Z_VAL_NODE + 1) return super(NodeItem, self).itemChange(change, value) def _tooltip_disable(self, state): tooltip = '<b>{}</b>'.format(self._properties['name']) if state: tooltip += ' <font color="red"><b>(DISABLED)</b></font>' tooltip += '<br/>{}<br/>'.format(self._properties['type']) self.setToolTip(tooltip) def _set_base_size(self): """ setup initial base size. """ self._width = NODE_WIDTH self._height = NODE_HEIGHT width, height = self.calc_size() if width > self._width: self._width = width if height > self._height: self._height = height def _set_text_color(self, color): """ set text color. Args: color (tuple): color value in (r, g, b, a). """ text_color = QtGui.QColor(*color) for port, text in self._input_items.items(): text.setDefaultTextColor(text_color) for port, text in self._output_items.items(): text.setDefaultTextColor(text_color) self._text_item.setDefaultTextColor(text_color) def activate_pipes(self): """ active pipe color. """ ports = self.inputs + self.outputs for port in ports: for pipe in port.connected_pipes: pipe.activate() def hightlight_pipes(self): """ highlight pipe color. """ ports = self.inputs + self.outputs for port in ports: for pipe in port.connected_pipes: pipe.highlight() def reset_pipes(self): """ reset the pipe color. """ ports = self.inputs + self.outputs for port in ports: for pipe in port.connected_pipes: pipe.reset() def calc_size(self): """ calculate minimum node size. """ width = 0.0 if self._widgets: widget_widths = [ w.boundingRect().width() for w in self._widgets.values() ] width = max(widget_widths) if self._text_item.boundingRect().width() > width: width = self._text_item.boundingRect().width() port_height = 0.0 if self._input_items: input_widths = [] for port, text in self._input_items.items(): input_width = port.boundingRect().width() * 2 if text.isVisible(): input_width += text.boundingRect().width() input_widths.append(input_width) width += max(input_widths) port = list(self._input_items.keys())[0] port_height = port.boundingRect().height() * 2 if self._output_items: output_widths = [] for port, text in self._output_items.items(): output_width = port.boundingRect().width() * 2 if text.isVisible(): output_width += text.boundingRect().width() output_widths.append(output_width) width += max(output_widths) port = list(self._output_items.keys())[0] port_height = port.boundingRect().height() * 2 in_count = len([p for p in self.inputs if p.isVisible()]) out_count = len([p for p in self.outputs if p.isVisible()]) height = port_height * (max([in_count, out_count]) + 2) if self._widgets: wid_height = sum( [w.boundingRect().height() for w in self._widgets.values()]) if wid_height > height: height = wid_height + (wid_height / len(self._widgets)) height += 10 return width, height def arrange_icon(self): """ Arrange node icon to the default top left of the node. """ self._icon_item.setPos(2.0, 2.0) def arrange_label(self): """ Arrange node label to the default top center of the node. """ text_rect = self._text_item.boundingRect() text_x = (self._width / 2) - (text_rect.width() / 2) self._text_item.setPos(text_x, 1.0) def arrange_widgets(self): """ Arrange node widgets to the default center of the node. """ if not self._widgets: return wid_heights = sum( [w.boundingRect().height() for w in self._widgets.values()]) pos_y = self._height / 2 pos_y -= wid_heights / 2 for name, widget in self._widgets.items(): rect = widget.boundingRect() pos_x = (self._width / 2) - (rect.width() / 2) widget.setPos(pos_x, pos_y) pos_y += rect.height() def arrange_ports(self, padding_x=0.0, padding_y=0.0): """ Arrange input, output ports in the node layout. Args: padding_x (float): horizontal padding. padding_y: (float): vertical padding. """ width = self._width - padding_x height = self._height - padding_y # adjust input position inputs = [p for p in self.inputs if p.isVisible()] if inputs: port_width = inputs[0].boundingRect().width() port_height = inputs[0].boundingRect().height() chunk = (height / len(inputs)) port_x = (port_width / 2) * -1 port_y = (chunk / 2) - (port_height / 2) for port in inputs: port.setPos(port_x + padding_x, port_y + (padding_y / 2)) port_y += chunk # adjust input text position for port, text in self._input_items.items(): if not port.isVisible(): continue txt_height = text.boundingRect().height() - 8.0 txt_x = port.x() + port.boundingRect().width() txt_y = port.y() - (txt_height / 2) text.setPos(txt_x + 3.0, txt_y) # adjust output position outputs = [p for p in self.outputs if p.isVisible()] if outputs: port_width = outputs[0].boundingRect().width() port_height = outputs[0].boundingRect().height() chunk = height / len(outputs) port_x = width - (port_width / 2) port_y = (chunk / 2) - (port_height / 2) for port in outputs: port.setPos(port_x, port_y + (padding_y / 2)) port_y += chunk # adjust output text position for port, text in self._output_items.items(): if not port.isVisible(): continue txt_width = text.boundingRect().width() txt_height = text.boundingRect().height() - 8.0 txt_x = width - txt_width - (port.boundingRect().width() / 2) txt_y = port.y() - (txt_height / 2) text.setPos(txt_x - 1.0, txt_y) def offset_icon(self, x=0.0, y=0.0): """ offset the icon in the node layout. Args: x (float): horizontal x offset y (float): vertical y offset """ if self._icon_item: icon_x = self._icon_item.pos().x() + x icon_y = self._icon_item.pos().y() + y self._icon_item.setPos(icon_x, icon_y) def offset_label(self, x=0.0, y=0.0): """ offset the label in the node layout. Args: x (float): horizontal x offset y (float): vertical y offset """ icon_x = self._text_item.pos().x() + x icon_y = self._text_item.pos().y() + y self._text_item.setPos(icon_x, icon_y) def offset_widgets(self, x=0.0, y=0.0): """ offset the node widgets in the node layout. Args: x (float): horizontal x offset y (float): vertical y offset """ for name, widget in self._widgets.items(): pos_x = widget.pos().x() pos_y = widget.pos().y() widget.setPos(pos_x + x, pos_y + y) def offset_ports(self, x=0.0, y=0.0): """ offset the ports in the node layout. Args: x (float): horizontal x offset y (float): vertical y offset """ for port, text in self._input_items.items(): port_x, port_y = port.pos().x(), port.pos().y() text_x, text_y = text.pos().x(), text.pos().y() port.setPos(port_x + x, port_y + y) text.setPos(text_x + x, text_y + y) for port, text in self._output_items.items(): port_x, port_y = port.pos().x(), port.pos().y() text_x, text_y = text.pos().x(), text.pos().y() port.setPos(port_x + x, port_y + y) text.setPos(text_x + x, text_y + y) def post_init(self, viewer=None, pos=None): """ Called after node has been added into the scene. Adjust the node layout and form after the node has been added. Args: viewer (NodeGraphQt.widgets.viewer.NodeViewer): not used pos (tuple): cursor position. """ # setup initial base size. self._set_base_size() # set text color when node is initialized. self._set_text_color(self.text_color) # set the tooltip self._tooltip_disable(self.disabled) # --- setup node layout --- # arrange label text self.arrange_label() self.offset_label(0.0, 5.0) # arrange icon self.arrange_icon() self.offset_icon(5.0, 2.0) # arrange node widgets self.arrange_widgets() self.offset_widgets(0.0, 10.0) # arrange input and output ports. self.arrange_ports(padding_y=35.0) self.offset_ports(0.0, 15.0) # set initial node position. if pos: self.xy_pos = pos @property def icon(self): return self._properties['icon'] @icon.setter def icon(self, path=None): self._properties['icon'] = path path = path or ICON_NODE_BASE pixmap = QtGui.QPixmap(path) if pixmap.size().height() > NODE_ICON_SIZE: pixmap = pixmap.scaledToHeight(NODE_ICON_SIZE, QtCore.Qt.SmoothTransformation) self._icon_item.setPixmap(pixmap) if self.scene(): self.post_init() @AbstractNodeItem.width.setter def width(self, width=0.0): w, h = self.calc_size() width = width if width > w else w AbstractNodeItem.width.fset(self, width) @AbstractNodeItem.height.setter def height(self, height=0.0): w, h = self.calc_size() h = 70 if h < 70 else h height = height if height > h else h AbstractNodeItem.height.fset(self, height) @AbstractNodeItem.disabled.setter def disabled(self, state=False): AbstractNodeItem.disabled.fset(self, state) for n, w in self._widgets.items(): w.widget.setDisabled(state) self._tooltip_disable(state) self._x_item.setVisible(state) @AbstractNodeItem.selected.setter def selected(self, selected=False): AbstractNodeItem.selected.fset(self, selected) if selected: self.hightlight_pipes() @AbstractNodeItem.name.setter def name(self, name=''): AbstractNodeItem.name.fset(self, name) self._text_item.setPlainText(name) if self.scene(): self.post_init() @property def inputs(self): """ Returns: list[PortItem]: input port graphic items. """ return list(self._input_items.keys()) @property def outputs(self): """ Returns: list[PortItem]: output port graphic items. """ return list(self._output_items.keys()) def add_input(self, name='input', multi_port=False, display_name=True): """ Args: name (str): name for the port. multi_port (bool): allow multiple connections. display_name (bool): display the port name. Returns: PortItem: input item widget """ port = PortItem(self) port.name = name port.port_type = IN_PORT port.multi_connection = multi_port port.display_name = display_name text = QGraphicsTextItem(port.name, self) text.font().setPointSize(8) text.setFont(text.font()) text.setVisible(display_name) self._input_items[port] = text if self.scene(): self.post_init() return port def add_output(self, name='output', multi_port=False, display_name=True): """ Args: name (str): name for the port. multi_port (bool): allow multiple connections. display_name (bool): display the port name. Returns: PortItem: output item widget """ port = PortItem(self) port.name = name port.port_type = OUT_PORT port.multi_connection = multi_port port.display_name = display_name text = QGraphicsTextItem(port.name, self) text.font().setPointSize(8) text.setFont(text.font()) text.setVisible(display_name) self._output_items[port] = text if self.scene(): self.post_init() return port def get_input_text_item(self, port_item): """ Args: port_item (PortItem): port item. Returns: QGraphicsTextItem: graphic item used for the port text. """ return self._input_items[port_item] def get_output_text_item(self, port_item): """ Args: port_item (PortItem): port item. Returns: QGraphicsTextItem: graphic item used for the port text. """ return self._output_items[port_item] @property def widgets(self): return dict(self._widgets) def add_combo_menu(self, name='', label='', items=None, tooltip=''): items = items or [] widget = NodeComboBox(self, name, label, items) widget.setToolTip(tooltip) self.add_widget(widget) return widget def add_text_input(self, name='', label='', text='', tooltip=''): widget = NodeLineEdit(self, name, label, text) widget.setToolTip(tooltip) self.add_widget(widget) return widget def add_checkbox(self, name='', label='', text='', state=False, tooltip=''): widget = NodeCheckBox(self, name, label, text, state) widget.setToolTip(tooltip) self.add_widget(widget) return widget def add_widget(self, widget): if isinstance(widget, NodeBaseWidget): self._widgets[widget.name] = widget else: raise TypeError('{} is not an instance of a node widget.') def get_widget(self, name): widget = self._widgets.get(name) if widget: return widget raise KeyError('node has no widget "{}"'.format(name)) def delete(self): for port, text in self._input_items.items(): port.delete() for port, text in self._output_items.items(): port.delete() super(NodeItem, self).delete() def from_dict(self, node_dict): super(NodeItem, self).from_dict(node_dict) widgets = node_dict.pop('widgets', {}) for name, value in widgets.items(): if self._widgets.get(name): self._widgets[name].value = value
class ArrowItem(QGraphicsLineItem): MOUSE_MARGIN = 20 ENDS_TEXT_SHIFT_X = 20 ENDS_TEXT_SHIFT_Y = 0 TEXT_SHIFT = QPoint(ENDS_TEXT_SHIFT_X, ENDS_TEXT_SHIFT_Y) def __init__(self, line: QLineF): super().__init__(line) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) pen = QPen() pen.setWidth(5) self.setPen(pen) self.__p1Ends = QGraphicsTextItem('1', self) self.__p2Ends = QGraphicsTextItem('2', self) self.__updateTextLinePos() self.__currentEndsMove = MoveLineEnds.NONE self.__currentEndsHover = MoveLineEnds.NONE self.__endsCapture = [None, None] self.__tryCapture = None self.__blockMove = False def __updateTextLinePos(self): if self.line().p1().x() < self.line().p2().x(): self.__p1Ends.setPos(self.line().p1()-self.TEXT_SHIFT) self.__p2Ends.setPos(self.line().p2()+self.TEXT_SHIFT) else: self.__p1Ends.setPos(self.line().p1()+self.TEXT_SHIFT) self.__p2Ends.setPos(self.line().p2()-self.TEXT_SHIFT) def setEndsText(self, moveLineEnds: MoveLineEnds, bold: bool=False, color: QColor=Qt.black): if moveLineEnds == MoveLineEnds.NONE: return if moveLineEnds == MoveLineEnds.BOTH: self.setEndsText(MoveLineEnds.P1, bold, color) self.setEndsText(MoveLineEnds.P2, bold, color) return color = 'red' if color == Qt.red else 'black' style = f'color="{color}"' index = int(moveLineEnds) text = f'<font {style}><b>{index}</b></font>' if bold else f'<font {style}><p>{index}</p></font>' if moveLineEnds == MoveLineEnds.P1: self.__p1Ends.setHtml(text) return if moveLineEnds == MoveLineEnds.P2: self.__p2Ends.setHtml(text) return @classmethod def isInBox(cls, mousePosition: QPoint, objectPosition: QPoint): x_ok = mousePosition.x() - cls.MOUSE_MARGIN <= objectPosition.x() <= mousePosition.x() + cls.MOUSE_MARGIN y_ok = mousePosition.y() - cls.MOUSE_MARGIN <= objectPosition.y() <= mousePosition.y() + cls.MOUSE_MARGIN return x_ok and y_ok def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): if self.__blockMove: return if self.__currentEndsMove != MoveLineEnds.NONE: return p1_ok = self.isInBox(event.pos(), self.line().p1()) p2_ok = self.isInBox(event.pos(), self.line().p2()) if p1_ok: self.setEndsText(MoveLineEnds.P1, True) self.setCursor(Qt.OpenHandCursor) self.setSelected(False) self.__currentEndsHover = MoveLineEnds.P1 return if p2_ok: self.setEndsText(MoveLineEnds.P2, True) self.setCursor(Qt.OpenHandCursor) self.setSelected(False) self.__currentEndsHover = MoveLineEnds.P2 return if not self.isSelected(): self.setCursor(Qt.OpenHandCursor) self.__currentEndsHover = MoveLineEnds.BOTH self.setEndsText(MoveLineEnds.BOTH, True) super().hoverMoveEvent(event) def hoverLeaveEvent(self, event: QHoverEvent): if self.__blockMove: return if self.__currentEndsMove != MoveLineEnds.NONE: return self.setEndsText(MoveLineEnds.BOTH) self.setCursor(Qt.ArrowCursor) self.setSelected(False) super().hoverLeaveEvent(event) def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if self.__blockMove: return if self.__currentEndsMove != MoveLineEnds.NONE: return if self.isInBox(event.pos(), self.line().p1()) and self.__endsCapture[0] is None: self.__currentEndsMove = MoveLineEnds.P1 self.setEndsText(MoveLineEnds.P1, True, Qt.red) self.setCursor(Qt.ClosedHandCursor) return if self.isInBox(event.pos(), self.line().p2()) and self.__endsCapture[1] is None: self.__currentEndsMove = MoveLineEnds.P2 self.setEndsText(MoveLineEnds.P2, True, Qt.red) self.setCursor(Qt.ClosedHandCursor) return if self.__endsCapture[0] is None and self.__endsCapture[1] is None: self.__currentEndsMove = MoveLineEnds.BOTH self.setEndsText(MoveLineEnds.BOTH, True, Qt.red) self.setCursor(Qt.ClosedHandCursor) super().mousePressEvent(event) def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): if self.__blockMove: return self.setCursor(Qt.OpenHandCursor) self.setEndsText(self.__currentEndsMove, True) if self.__tryCapture is not None: print(type(self.__tryCapture)) self.__tryCapture.capture(self) if self.__currentEndsMove == MoveLineEnds.P1: self.__endsCapture[0] = self.__tryCapture else: self.__endsCapture[1] = self.__tryCapture self.__tryCapture = None self.__currentEndsMove = MoveLineEnds.NONE def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): if self.__blockMove: return if self.__currentEndsMove == MoveLineEnds.NONE: return if self.__tryCapture is not None: self.__tryCapture.unCapture(self) if self.__currentEndsMove == MoveLineEnds.P1: line = QLineF(event.pos(), self.line().p2()) self.setLine(line) self.__updateTextLinePos() rect = self.__filteredCapturedObject() print(rect) if 1 == len(rect): rect = rect[0] self.__tryCapture = rect self.__tryCapture.tryCapture(self) self.update() return if self.__currentEndsMove == MoveLineEnds.P2: line = QLineF(self.line().p1(), event.pos()) self.setLine(line) self.__updateTextLinePos() rect = self.__filteredCapturedObject() print(rect) if 1 == len(rect): rect = rect[0] self.__tryCapture = rect self.__tryCapture.tryCapture(self) self.update() return if self.__currentEndsMove == MoveLineEnds.BOTH: super().mouseMoveEvent(event) self.__updateTextLinePos() self.update() return def moveObject(self, object, pos): if object is self.__endsCapture[0]: line = QLineF(pos, self.line().p2()) self.__blockMove = True self.setLine(line) self.__updateTextLinePos() self.__blockMove = False return if object is self.__endsCapture[1]: line = QLineF(self.line().p1(), pos) self.__blockMove = True self.setLine(line) self.__updateTextLinePos() rect = self.collidingItems() self.__blockMove = False return def __filteredCapturedObject(self): rect = self.collidingItems() try: rect.remove(self.__endsCapture[0]) for ch in self.__endsCapture[0].children(): try: rect.remove(ch) except: ... except: ... try: rect.remove(self.__endsCapture[1]) for ch in self.__endsCapture[1].children(): try: rect.remove(ch) except: ... except: ... return rect
class ExecutionIcon(QGraphicsEllipseItem): """An icon to show information about the item's execution.""" _CHECK = "\uf00c" _CROSS = "\uf00d" _CLOCK = "\uf017" def __init__(self, parent): """ Args: parent (ProjectItemIcon): the parent item """ super().__init__(parent) self._parent = parent self._execution_state = "not started" self._text_item = QGraphicsTextItem(self) font = QFont('Font Awesome 5 Free Solid') self._text_item.setFont(font) parent_rect = parent.rect() self.setRect(0, 0, 0.5 * parent_rect.width(), 0.5 * parent_rect.height()) self.setPen(Qt.NoPen) # pylint: disable=undefined-variable self.normal_brush = qApp.palette().window() self.selected_brush = qApp.palette().highlight() self.setBrush(self.normal_brush) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=False) self.hide() def item_name(self): return self._parent.name() def _repaint(self, text, color): self._text_item.prepareGeometryChange() self._text_item.setPos(0, 0) self._text_item.setPlainText(text) self._text_item.setDefaultTextColor(color) size = self._text_item.boundingRect().size() dim_max = max(size.width(), size.height()) rect_w = self.rect().width() self._text_item.setScale(rect_w / dim_max) self._text_item.setPos(self.sceneBoundingRect().center() - self._text_item.sceneBoundingRect().center()) self.show() def mark_execution_wating(self): self._execution_state = "waiting for dependencies" self._repaint(self._CLOCK, QColor("orange")) def mark_execution_started(self): self._execution_state = "in progress" self._repaint(self._CHECK, QColor("orange")) def mark_execution_finished(self, success, skipped): if success: self._execution_state = "skipped" if skipped else "completed" colorname = "orange" if skipped else "green" self._repaint(self._CHECK, QColor(colorname)) else: self._execution_state = "failed" self._repaint(self._CROSS, QColor("red")) def hoverEnterEvent(self, event): tip = f"<p><b>Execution {self._execution_state}</b>. Select this item to see Console and Log messages.</p>" QToolTip.showText(event.screenPos(), tip) def hoverLeaveEvent(self, event): QToolTip.hideText()
class ScaleLine: def __init__(self, parent, color, text_color, text_start="", margins=(0, 0, 0, 0), dp=1, is_upload=False): self._parent = parent self._color = color self._text_color = text_color self._text_start = text_start self._left, self._top, self._right, self._bottom = margins self._dp = dp self._is_upload = is_upload self._line = QGraphicsLineItem(self._parent) self._line.setZValue(12) pen = QPen(self._color) pen.setWidth(1) pen.setStyle(Qt.DashLine) self._line.setPen(pen) if not self._is_upload: self._text_item = QGraphicsTextItem(self._parent) self._text_item.setZValue(11) self._text_item.setDefaultTextColor(self._text_color) font = self._text_item.font() font_size = 10 * self._dp if font_size > 0: self._text_item.setFont(QFont(font.family(), font_size)) def set_line(self, max_value=0, resize=False): height = self._parent.size().height() + self._top + self._bottom shift = int(height - height / 1.1) y = -self._top + shift if not self._is_upload: value = 0 max_value = int(max_value / 1.1) megabyte = 1024 * 1024 if max_value > megabyte: value = "{} MB".format(round(max_value / megabyte, 1)) elif max_value > 1024: max_value //= 1024 if max_value >= 10: max_value = max_value // 10 * 10 value = "{} KB".format(max_value) elif max_value > 0: if max_value >= 10: max_value = max_value // 10 * 10 value = "{} B".format(max_value) scale_text = self._text_start if not value \ else "{}{}/s".format(self._text_start, value) font_height = QFontMetrics(self._text_item.font())\ .boundingRect(scale_text).height() x = 10 self._text_item.setPos(x, y - font_height - 10) self._text_item.setPlainText(scale_text) if not resize: return self._line.setLine(QLineF(0, y, self._parent.size().width() + 30, y))
def draw_message(self, text: str, name: str, window_type="standard", mode=0, left=True) -> QGraphicsItemGroup: # Create the message box talk_window: QPixmap = self.view.talk_windows[window_type][mode] message_box = self.scene.addPixmap(talk_window) talk_window_x = _TALK_WINDOW_MODE_0_X if not mode else -3 talk_window_y = _TALK_WINDOW_Y if not mode else _TALK_WINDOW_Y - 5 message_box.setPos(talk_window_x, talk_window_y) # Create the name plate name_plate_texture: QPixmap = self.view.talk_windows["name_plate"][ "plate"] name_plate = self.scene.addPixmap(name_plate_texture) name_plate_x = _NAME_PLATE_LEFT_X if left else _NAME_PLATE_RIGHT_X name_plate.setPos(name_plate_x, _NAME_PLATE_Y) # Create the name plate text name_plate_text = QGraphicsTextItem() name_plate_text.setPlainText(name) name_plate_text.setDefaultTextColor(QColor.fromRgba(0xFFFFFFB3)) name_plate_text.setFont(self.view.name_plate_font) name_plate_text.setTextWidth(name_plate_texture.width()) name_plate_text.setPos(name_plate_x, _NAME_PLATE_Y) # Center the name plate text block_format = QTextBlockFormat() block_format.setAlignment(QtGui.Qt.AlignCenter) cursor = name_plate_text.textCursor() cursor.select(QTextCursor.Document) cursor.mergeBlockFormat(block_format) cursor.clearSelection() name_plate_text.setTextCursor(cursor) self.scene.addItem(name_plate_text) # Create the message box text. Draw two lines if required. # Truncate lines with width > 312 split_text = text.split("\n") message_box_text = QGraphicsTextItem() message_box_text_2 = QGraphicsTextItem() if split_text and split_text[0]: text_utils.draw_message_text(message_box_text, self.view.name_plate_font, split_text[0], _TALK_WINDOW_MODE_0_X + 20, _TALK_WINDOW_Y + 5, 312) if len(split_text) > 1 and split_text[1]: text_utils.draw_message_text(message_box_text_2, self.view.name_plate_font, split_text[1], _TALK_WINDOW_MODE_0_X + 20, _TALK_WINDOW_Y + 21, 312) group = self.scene.createItemGroup([ message_box, message_box_text, message_box_text_2, name_plate, name_plate_text ]) group.setZValue(2.0) return group
class GraphicsPortPainter: """The GraphicsPortPainter class provides a set of functions and configurations for painting a GraphicsPort object. """ class DrawingState(Enum): Normal = 0 Target = 1 class PortNamePosition(Enum): Left = 0 Right = 1 drawing_state = DrawingState.Normal target_port_type = None def __init__(self, graphics_port: "GraphicsPort", port_name_position: "PortNamePosition"): self.__graphics_port = graphics_port self.__port_name = QGraphicsTextItem(parent=graphics_port) self.__port_name.setPlainText(graphics_port._port.name) self.__port_name.setDefaultTextColor("#FFFFFF") self.__port_name.setFlag(QGraphicsItem.ItemStacksBehindParent) self.port_name_position = port_name_position # Colors/Pens/Brushes self.__color = TypeColor.get_color_for(graphics_port._port.port_type) self.__outline_pen = QPen(self.__color.darker()) self.__outline_pen.setWidthF(2) self.__background_brush = QBrush(self.__color) self.__dashed_outline_pen = QPen(self.__color) self.__dashed_outline_pen.setStyle(Qt.DashLine) self.__dashed_outline_pen.setWidth(2) @property def port_name_position(self) -> "PortNamePosition": return self.__port_name_position @port_name_position.setter def port_name_position(self, position: "PortNamePosition"): if position == self.PortNamePosition.Left: self.__port_name.setPos( -self.__port_name.boundingRect().width() - 3, 1) elif position == self.PortNamePosition.Right: self.__port_name.setPos(3, 1) self.__port_name_position = position @property def color(self): return self.__color def paint_area(self): return QRectF( -self.__graphics_port.radius, -self.__graphics_port.radius, 2 * self.__graphics_port.radius, 2 * self.__graphics_port.radius, ) def paint( self, painter: "QPainter", option: "QStyleOptionGraphicsItem", widget: "QWidget" = None, ): if (GraphicsPortPainter.drawing_state == GraphicsPortPainter.DrawingState.Target and self.__graphics_port.port_type == GraphicsPortPainter.target_port_type): painter.setPen(self.__dashed_outline_pen) painter.setBrush(Qt.NoBrush) painter.drawEllipse(self.__graphics_port.boundingRect()) painter.setPen(self.__outline_pen) painter.setBrush(self.__background_brush) painter.drawEllipse(self.paint_area())
class PortGraphics(QGraphicsItem): """ This class defines the graphics for displaying a port in the APIM View. A port is shaped somewhat like an inverted, elongated pentagon. """ REQUIRED_PEN_WIDTH = 5.0 OPTIONAL_PEN_WIDTH = 1.0 WIDTH = 50 SIDE_HEIGHT = 1.5 * WIDTH TAPER_HEIGHT = 0.5 * SIDE_HEIGHT TOTAL_HEIGHT = SIDE_HEIGHT + TAPER_HEIGHT # Center the shape about the origin of its coordinate system. X_POS = -0.5 * WIDTH Y_POS = -0.5 * TOTAL_HEIGHT PEN_COLOR = QColor(Qt.black) INNER_COLOR = QColor(100, 200, 0) OUTER_COLOR = QColor(252, 140, 3) NAME_FONT = QFont("Times", 15) TYPE_FONT = QFont("Times", 15) def __init__(self, port: 'Port', parent: QGraphicsItem = None, menuEnabled: bool = True): """ Constructs a portGraphics object for the given Port object. :param port: The port for which this graphics item represents. :type port: Port :param parent: A QGraphicsItem (probably an actionGraphics object) :type parent: QGraphicsItem :param menuEnabled: If true, a context menu will be shown on right-click. :type menuEnabled: bool """ QGraphicsItem.__init__(self, parent) self.setAcceptDrops(True) self.setFlag(QGraphicsItem.ItemIsSelectable) self._port = port self._menuEnabled = menuEnabled self.menu = PortMenu() # If port is required and it's an input, make the border thicker if not self._port.isOptional() and self._port in self._port.getAction( ).getInputPorts(): self.borderWidth = PortGraphics.REQUIRED_PEN_WIDTH else: self.borderWidth = PortGraphics.OPTIONAL_PEN_WIDTH # show port name and type if self._menuEnabled: fm = QFontMetricsF(PortGraphics.NAME_FONT) name = fm.elidedText(self._port.getName(), Qt.ElideRight, PortGraphics.SIDE_HEIGHT) self.nameItem = QGraphicsTextItem(name, self) self.nameItem.setFont(PortGraphics.NAME_FONT) self.nameItem.setRotation(90) self.nameItem.setPos(PortGraphics.WIDTH / 2 + 5, -PortGraphics.TOTAL_HEIGHT / 2) fm = QFontMetricsF(PortGraphics.TYPE_FONT) t = fm.elidedText(self._port.getDataType().__name__, Qt.ElideRight, PortGraphics.SIDE_HEIGHT) self.typeItem = QGraphicsTextItem(t, self) self.typeItem.setFont(PortGraphics.TYPE_FONT) self.typeItem.setRotation(90) self.typeItem.setPos(5, -PortGraphics.TOTAL_HEIGHT / 2) def getPort(self): """ Returns the PortGraphics' Port. :return: """ return self._port def boundingRect(self) -> QRectF: """ This pure virtual function defines the outer bounds of the item as a rectangle. :return: create the bounding of the item :rtype: QRectF """ halfPenWidth = self.borderWidth / 2 x = PortGraphics.X_POS - halfPenWidth y = PortGraphics.Y_POS - halfPenWidth actualWidth = PortGraphics.WIDTH + self.borderWidth actualHeight = PortGraphics.TOTAL_HEIGHT + self.borderWidth return QRectF(x, y, actualWidth, actualHeight) def shape(self) -> QPainterPath: """ Returns the shape of the Port as a QPainterPath. Ports are shaped like an inverted, elongated pentagon. :return: The shape of the Port as a QPainterPath. :rtype: QPainterPath """ # Create the polygon (pentagon-like shape) poly = QPolygon() poly << QPoint(PortGraphics.X_POS, PortGraphics.Y_POS) poly << QPoint(PortGraphics.X_POS + PortGraphics.WIDTH, PortGraphics.Y_POS) poly << QPoint(PortGraphics.X_POS + PortGraphics.WIDTH, PortGraphics.Y_POS + PortGraphics.SIDE_HEIGHT) poly << QPoint(0, PortGraphics.Y_POS + PortGraphics.TOTAL_HEIGHT) poly << QPoint(PortGraphics.X_POS, PortGraphics.Y_POS + PortGraphics.SIDE_HEIGHT) poly << QPoint(PortGraphics.X_POS, PortGraphics.Y_POS) path = QPainterPath() path.addPolygon(poly) return path def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget) -> None: """ Paints a port. This function is used implicitly by the QGraphicsView to render a port. :param painter: The painter to paint with. :type painter: QPainter :param option: provides style options for the item. :type option: QStyleOptionGraphicsItem :param widget: QWidget :type widget: It points to the widget that is being painted on; or make it = None. :return: None :rtype: NoneType """ pen = QPen(PortGraphics.PEN_COLOR) pen.setWidth(self.borderWidth) painter.setPen(pen) if type(self._port.getAction()) == ActionWrapper: painter.setBrush(PortGraphics.INNER_COLOR) else: painter.setBrush(PortGraphics.OUTER_COLOR) painter.drawPath(self.shape()) def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: """ Opens a context menu (right click menu) for the component. :param event: The event that was generated when the user right-clicked on this item. :type event: QGraphicsSceneContextMenuEvent :return: None :rtype: NoneType """ return QGraphicsItem.contextMenuEvent(event) # if not self._menuEnabled: # return QGraphicsItem.contextMenuEvent(self, event) # # self.setSelected(True) # self.menu.exec_(event.screenPos()) def mousePressEvent(self, event): event.ignore()
class AbstractNode(AbstractGeneralGraphicsItemClass): __metaclass__ = AbstractGeneralGraphicsItem_Meta def __init__(self, graphWidget, state): super(AbstractNode, self).__init__() self.graph = weakref.ref(graphWidget) self.isAcceptable = state.isAcceptable self.isCurrent = state.isCurrent self.newPos = QPointF() self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setCacheMode(self.DeviceCoordinateCache) self.setZValue(-1) self.setAcceptHoverEvents(True) self._edges = [] self.currentFill = self.fillQColor self.border = QGraphicsEllipseItem(-self.radius, -self.radius, self.radius * 2, self.radius * 2) self.finalNodeBorder = QGraphicsEllipseItem(-self.radius + 5, -self.radius + 5, self.radius * 2 - 10, self.radius * 2 - 10) self.finalNodeBorder.mapToParent(self.border.pos()) if self.isAcceptable == False: pen = self.finalNodeBorder.pen() pen.setColor(QColor(25, 255, 5)) self.finalNodeBorder.update() if self.isCurrent == True: self.resolveFillColor() self.labelBox = QGraphicsTextItem(self) self.labelBox.setPos(-self.radius / 2, -self.radius / 2) self.labelBox.setDefaultTextColor(QColor(0, 0, 0)) @property @abstractmethod def borderQColor(self): pass @property @abstractmethod def fillQColor(self): pass @property @abstractmethod def currentStateFillQColor(self): pass @property @abstractmethod def radius(self): pass @abstractmethod def setLabel(self, value): pass def toggleCurrentState(self): self.isCurrent = not self.isCurrent self.resolveFillColor() def resolveFillColor(self): brush = QBrush() if self.isCurrent == True: brush.setColor(self.currentStateFillQColor) brush.setStyle(Qt.SolidPattern) self.currentFill = self.currentStateFillQColor else: brush.setColor(self.fillQColor) brush.setStyle(Qt.SolidPattern) self.currentFill = self.fillQColor self.border.setBrush(brush) self.update() def toggleFinalNodeBorder(self): self.isAcceptable = not self.isAcceptable self.resolveFinalNodeBorder() def resolveFinalNodeBorder(self): pen = QPen() if self.isAcceptable == True: pen.setColor(self.borderQColor) pen.setWidth(2) else: pen.setColor(self.currentFill) pen.setWidth(1) self.finalNodeBorder.setPen(pen) self.update() def addEdge(self, edge): self._edges.append(weakref.ref(edge)) edge.adjust() def getEdges(self): return self._edges def getEdgesForInput(self, input): edges = [] for edge in self._edges: if edge.onInput == input: edges.append(edge) return edges def getEdgesFromSelf(self, input): edges = [] for edge in self._edges: if edge.onInput == input and edge.fromNode is self: edges.append(edge) return edges def getEdgeToNode(self, toNode): toReturn = None for edge in self._edges: if edge.toNode() is toNode: toReturn = edge return toReturn def boundingRect(self): adjust = 2.0 return QRectF(-self.radius - adjust, -self.radius - adjust, (self.radius + adjust) * 2, (self.radius + adjust) * 2) def shape(self): path = QtGui.QPainterPath() path.addEllipse(-self.radius, -self.radius, self.radius * 2, self.radius * 2) return path def paint(self, painter, option, widget): pen = QPen() pen.setWidth(2) pen.setColor(self.borderQColor) brush = QBrush() self.border.setPen(pen) self.border.setBrush(brush) self.resolveFillColor() self.resolveFinalNodeBorder() self.border.paint(painter, option, widget) self.finalNodeBorder.paint(painter, option, widget) self.labelBox.setTextWidth(35) self.labelBox.setDefaultTextColor(QColor(0, 0, 0)) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange: for edge in self._edges: edge().adjust() return QGraphicsItem.itemChange(self, change, value) def mousePressEvent(self, event): self.update() QGraphicsItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): QGraphicsItem.mouseReleaseEvent(self, event) self.update()
class GraphicsNodePainter: def __init__(self, graphics_node: "GraphicsNode"): self.__graphics_node = graphics_node # Dimensions self.padding = 12 self.clickable_margin = 15 self.round_edge_size = 10 self.window_outline_width = 4 # Colors/Pens/Brushes self.__title_color = Qt.white self.__outline_selected_color = QColor("#FFA637") self.__outline_default_color = QColor("#000000") self.__outline_pen = QPen(self.__outline_default_color) self.__title_background_brush = QBrush(QColor("#FF313131")) self.__background_brush = QBrush(QColor("#E3212121")) self.__window_markers_pen = QPen(self.__outline_default_color) self.__window_markers_brush = QBrush(self.__outline_default_color) # Graphics Components self.__graphics_title = QGraphicsTextItem(parent=graphics_node) self.__graphics_title.setDefaultTextColor(self.__title_color) self.__graphics_title.setPlainText(self.__graphics_node.title) self.__graphics_title.setPos(self.padding, 0) # Position ProxyWidget self.repositionWidget() # position GraphicsPort objects def position_graphics_ports(x_offset, graphics_ports_dict): for i, graphics_port in enumerate(graphics_ports_dict.values()): graphics_port.setPos( x_offset, self.title_height() + (graphics_port.radius * 4) * (i + 1)) position_graphics_ports(0, self.__graphics_node.inputs) position_graphics_ports(self.boundingRect().width(), self.__graphics_node.outputs) @property def outline_selected_color(self) -> "QColor": return self.__outline_selected_color @outline_selected_color.setter def outline_selected_color(self, color: "QColor"): self.__outline_selected_color = color @property def outline_default_color(self) -> "QColor": return self.__outline_default_color @outline_default_color.setter def outline_default_color(self, color: "QColor"): self.__outline_default_color = color def boundingRect(self) -> "QRectF": return self.background_paint_area() def background_paint_area(self) -> "QRectF": proxy_rect = self.__graphics_node._proxy_widget.boundingRect() return proxy_rect.adjusted( 0, 0, self.padding * 2, self.title_height() + self.padding * 2, ).normalized() def repositionWidget(self): self.__graphics_node._proxy_widget.setPos( self.padding, self.title_height() + self.padding) def recalculateGeometry(self): for graphics_port in self.__graphics_node.outputs.values(): graphics_port.setX(self.boundingRect().width()) def itemChange(self, change: "QGraphicsItem.GraphicsItemChange", value: Any) -> Any: if change == QGraphicsItem.ItemSelectedChange: self.__outline_pen.setColor(self.__outline_selected_color if value else self.__outline_default_color) return value def title_height(self) -> int: """Returns the height of the title graphics item.""" return self.__graphics_title.boundingRect().height() def paint(self, painter: "QPainter", option: "QStyleOptionGraphicsItem", widget: "QWidget"): """Paints the GraphicsNode item.""" self.__paint_background(painter) self.__paint_title_background(painter) self.__paint_outline(painter) self.__paint_window_markers(painter) def __paint_background(self, painter: "QPainter"): """Paints the background of the node. Plain color, no lines.""" path_background = QPainterPath() path_background.addRoundedRect( self.background_paint_area(), self.round_edge_size, self.round_edge_size, ) painter.setPen(Qt.NoPen) painter.setBrush(self.__background_brush) painter.drawPath(path_background.simplified()) def __paint_title_background(self, painter: "QPainter"): """Paints a little background behind the title text, at the top of the node.""" title_rect = self.__graphics_title.boundingRect() path_title_background = QPainterPath() path_title_background.setFillRule(Qt.WindingFill) path_title_background.addRoundedRect( 0, 0, self.background_paint_area().width(), title_rect.height(), self.round_edge_size, self.round_edge_size, ) # (Drawing rects to hide the two botton round edges) path_title_background.addRect( 0, title_rect.height() - self.round_edge_size, self.round_edge_size, self.round_edge_size, ) path_title_background.addRect( self.background_paint_area().width() - self.round_edge_size, title_rect.height() - self.round_edge_size, self.round_edge_size, self.round_edge_size, ) painter.setPen(Qt.NoPen) painter.setBrush(self.__title_background_brush) painter.drawPath(path_title_background.simplified()) def __paint_window_markers(self, painter: "QPainter"): title_width = self.__graphics_title.boundingRect().width() for i, parent_window in reversed( list(enumerate(self.__graphics_node.parent_node_windows))): self.__window_markers_brush.setColor( parent_window.color_identifier) self.__window_markers_pen.setColor( parent_window.color_identifier.darker()) painter.setPen(self.__window_markers_pen) painter.setBrush(self.__window_markers_brush) painter.drawRect(title_width + 20 + i * 25, 8, 15, 15) def __paint_outline(self, painter: "QPainter"): """Paints the outline of the node. Depending on if its selected or not, the color of the outline changes.""" path_outline = QPainterPath() path_outline.addRoundedRect( self.boundingRect(), self.round_edge_size, self.round_edge_size, ) painter.setPen(self.__outline_pen) painter.setBrush(Qt.NoBrush) painter.drawPath(path_outline.simplified())
def eventFilter(self, obj, event): if event.type() == QEvent.GraphicsSceneMousePress: if event.button() == Qt.MouseButton.LeftButton: self.mouse_pressed = True self.mouse_pressed_x, self.mouse_pressed_y = event.pos().x( ), event.pos().y() if self.draw_ellipse: ellipsis = QGraphicsEllipseItem(self.chart) ellipsis.setZValue(12) ellipsis.setBrush(QBrush(QColor(244, 67, 54, 50))) ellipsis.setPen(QPen(Qt.transparent)) self.ellipses.append(ellipsis) elif self.write_text: for t in self.texts: r = QRectF() r.setTopLeft(t.pos()) r.setWidth(t.boundingRect().width()) r.setHeight(t.boundingRect().height()) if r.contains(self.mouse_pressed_x, self.mouse_pressed_y): return True """ The user clicked over an area where there is no text. So we create one. """ text = QGraphicsTextItem(self.chart) text.setZValue(12) text.setPos( QPointF(self.mouse_pressed_x, self.mouse_pressed_y)) text.setPlainText("label") text.setAcceptHoverEvents(True) text.setTabChangesFocus(True) text.setFlags(QGraphicsTextItem.ItemIsMovable) text.installEventFilter(self.text_event_filter) self.texts.append(text) return True elif event.button() == Qt.MouseButton.RightButton: x, y = event.pos().x(), event.pos().y() for e in self.ellipses: if e.rect().contains(x, y): e.hide() self.ellipses.remove(e) for t in self.texts: r = QRectF() r.setTopLeft(t.pos()) r.setWidth(t.boundingRect().width()) r.setHeight(t.boundingRect().height()) if r.contains(x, y): t.hide() self.texts.remove(t) return True return QObject.eventFilter(self, obj, event) elif event.type() == QEvent.GraphicsSceneMouseRelease: self.mouse_pressed = False return True elif event.type() == QEvent.GraphicsSceneMouseMove: if self.mouse_pressed: if self.draw_ellipse: x, y = event.pos().x(), event.pos().y() width = x - self.mouse_pressed_x height = y - self.mouse_pressed_y self.ellipses[-1].setRect(self.mouse_pressed_x, self.mouse_pressed_y, width, height) return True return QObject.eventFilter(self, obj, event) return QObject.eventFilter(self, obj, event)