def draw_icon(self, painter): painter.drawEllipse(QPointF(0.5, 0.5), 0.5, 0.5) painter.drawChord(QRectF(0.0, 0.0, 1.0, 1.0), 45 * 16, -120 * 16) painter.drawChord(QRectF(0.0, 0.0, 1.0, 1.0), 135 * 16, 120 * 16) bottom_arrow_point = QPointF(0.5, 0.8) painter.drawLine(bottom_arrow_point, QPointF(0.5, 0.7)) curve_start = QPointF(0.5, 0.7) bend_angle = 25 curve_end_l = QPointF( 0.4 * math.cos(math.radians(90 + bend_angle)) + 0.5, -0.4 * math.sin(math.radians(90 + bend_angle)) + 0.5) c1 = QPointF(0.5, 0.4) path = QPainterPath(curve_start) path.quadTo(c1, curve_end_l) painter.drawPath(path) curve_end_r = QPointF( 0.4 * math.cos(math.radians(90 - bend_angle)) + 0.5, -0.4 * math.sin(math.radians(90 - bend_angle)) + 0.5) path = QPainterPath(curve_start) path.quadTo(c1, curve_end_r) painter.drawPath(path) # Draw the arrow end-caps painter.setBrush(QBrush(QColor(0, 0, 0))) arrow = QPolygonF( [QPointF(-0.025, 0.0), QPointF(0.025, 0.0), QPointF(0.0, 0.025)]) painter.drawPolygon(arrow.translated(bottom_arrow_point)) t = QTransform() t.rotate(180.0 - 25.0) arrow_l = t.map(arrow) arrow_l = arrow_l.translated(curve_end_l) painter.drawPolygon(arrow_l) t = QTransform() t.rotate(180.0 + 25.0) arrow_r = t.map(arrow) arrow_r = arrow_r.translated(curve_end_r) painter.drawPolygon(arrow_r)
class Connection_Item(QGraphicsPathItem): def __init__(self, connection, parent): super().__init__() self._connection = connection self._source_ui = self._find_ui(parent, connection.source.connector) self._sink_ui = self._find_ui(parent, connection.sink.connector) self.avoid_conn = None self._duplicate_is = None self._duplicate_of = None self._switch_direction_count = 1 self.path = QPainterPath() self.stroker_path = QPainterPath() self.stroker = QPainterPathStroker() self.stroker.setWidth(8) self.update_endpoints() self.setZValue(-10) self.arrow = QGraphicsPolygonItem() self._set_default_appearance() self.setFlag(self.ItemIsFocusable) self._ports_item = Connection_Ports_Item() self._ports_item.setParentItem(self) self._timer = QTimer(self.scene()) self._timer.setSingleShot(True) self._timer.setSingleShot(True) self._timer.timeout.connect(self._start_hover) self._hover_pos = 0, 0 self.set_show_connection_ports_on_hover(False) def shape(self): return self.stroker_path def setPath(self, path): super().setPath(path) self.stroker_path = self.stroker.createStroke(path) def _set_default_appearance(self): self.setPen(QPen(Qt.black, 2)) self.arrow.setPen(QPen(Qt.black)) self.arrow.setBrush(QBrush(Qt.black)) self._arrow_height = 8 self.update_from_avoid_router() # trigger arrow size change def focusInEvent(self, event): self.setPen(QPen(highlight_color, 4)) self.arrow.setPen(QPen(highlight_color)) self.arrow.setBrush(QBrush(highlight_color)) self._arrow_height = 12 self.update_from_avoid_router() # trigger arrow size change super().focusInEvent(event) def focusOutEvent(self, event): self._set_default_appearance() super().focusOutEvent(event) def keyPressEvent(self, event): key = event.key() if (key == Qt.Key_Delete): self.parentItem().remove_connection(self) return elif (key == Qt.Key_D): self._switch_direction() return super().keyPressEvent(event) def _switch_direction(self): if self._duplicate_is: self.parentItem().remove_connection(self._duplicate_is) self._switch_direction_count = 0 elif self._switch_direction_count < 2: sink = self._connection.sink self._connection.sink = self._connection.source self._connection.source = sink self._source_ui = self._find_ui(self.parentItem(), self._connection.source.connector) self._sink_ui = self._find_ui(self.parentItem(), self._connection.sink.connector) self._switch_direction_count += 1 else: conn = diagram.Connection( source=self._connection.sink, sink=self._connection.source, ) self.parentItem().add_connection(conn) self._switch_direction_count = 3 self.update_endpoints() self.parentItem()._hide_duplicate_connections() if self._switch_direction_count == 0: self._switch_direction() def mouseDoubleClickEvent(self, event): if event.button() == Qt.LeftButton: self._switch_direction() return # Do not call mousePressEvent, will pass on to diagram super().mouseDoubleClickEvent(event) def set_show_connection_ports_on_hover(self, show): if not show: self._ports_item.hide() self.setAcceptHoverEvents(show) def hoverEnterEvent(self, event): w = self._ports_item.rect().width() h = self._ports_item.rect().height() x = event.pos().x() - w / 2.0 y = event.pos().y() - h / 2.0 self._hover_pos = x, y self._timer.start(200) def hoverLeaveEvent(self, event): self._stop_hover() def _start_hover(self): if self.hasFocus(): return #print("start hover", [a.name for a in self._connection.source_ports]) x, y = self._hover_pos self._ports_item.setX(x) self._ports_item.setY(y) self.setZValue(10) self._ports_item.show() def _stop_hover(self): self._timer.stop() self._ports_item.hide() self.setZValue(-10) def _find_ui(self, parent, c): result = None for b_ui in parent._block_items: for c_ui in b_ui._connectors: if c_ui._connector == c: return c_ui return result def _get_endpoint(self, c_ui): x, y = c_ui.get_connection_point() if x is None: return None, None p = self.mapToParent( self.mapFromScene(c_ui.parentItem().mapToScene(x, y))) return p.x(), p.y() def update_endpoints(self): self.x1, self.y1 = self._get_endpoint(self._source_ui) self.x2, self.y2 = self._get_endpoint(self._sink_ui) if self.x1 is None or self.x2 is None: return False self.path = QPainterPath() self.path.moveTo(self.x1, self.y1) self.path.lineTo(self.x2, self.y2) self.setPath(self.path) self._update_avoid() return True def _update_avoid(self): if self.parentItem(): avoid_router = self.parentItem().avoid_router src = avoid.ConnEnd(avoid.Point(self.x1, self.y1)) if self.isVisible(): dest = avoid.ConnEnd(avoid.Point(self.x2, self.y2)) else: dest = src # Don't route duplicate connection if self.avoid_conn is None: self.avoid_conn = avoid.ConnRef(avoid_router, src, dest) else: self.avoid_conn.setEndpoints(src, dest) def update_from_avoid_router(self): if self.avoid_conn is not None and self.avoid_conn.needsRepaint(): radius = self.parentItem().route_radius route = self.avoid_conn.displayRoute() self.path = QPainterPath() last_i = route.size() - 1 for i in range(0, route.size()): point = route.at(i) if i > 0: last_point = route.at(i - 1) last_path_point = self.path.currentPosition() if point.y == last_point.y: # horizontal line if point.x > last_point.x: # right sign = 1 else: # left sign = -1 self.path.quadTo(last_point.x, last_point.y, last_point.x + sign * radius, point.y) if i == last_i: my_x = point.x else: my_x = point.x - sign * radius self.path.lineTo(my_x, point.y) elif point.x == last_point.x: # vertical line if point.y > last_point.y: # down sign = 1 else: # up sign = -1 self.path.quadTo(last_point.x, last_point.y, point.x, last_point.y + sign * radius) if i == last_i: my_y = point.y else: my_y = point.y - sign * radius self.path.lineTo(point.x, my_y) else: self.path.lineTo(point.x, point.y) else: self.path.moveTo(point.x, point.y) self.setPath(self.path) sink = self._sink_ui entry_from = "L" x = 0 if route.at(route.size() - 1).x < route.at(route.size() - 2).x: entry_from = "R" x = sink.parentItem().rect().width() if self._duplicate_of: route = self._duplicate_of.avoid_conn.displayRoute() if route.at(0).x < route.at(1).x: entry_from = "R" x = sink.parentItem().rect().width() y = sink.y() + sink.rect().height() / 2.0 p = self.mapToParent( self.mapFromScene(sink.parentItem().mapToScene(x, y))) xc, yc = p.x(), p.y() arrow_h = self._arrow_height arrow_y = arrow_h / 2.0 arrow_x = sqrt(arrow_h * arrow_h - arrow_y * arrow_y) poly = QPolygonF() if entry_from == "L": poly << QPointF(xc - arrow_x, yc) poly << QPointF(xc - arrow_x, yc + arrow_y) poly << QPointF(xc, yc) poly << QPointF(xc - arrow_x, yc - arrow_y) poly << QPointF(xc - arrow_x, yc) else: poly << QPointF(xc + arrow_x, yc) poly << QPointF(xc + arrow_x, yc + arrow_y) poly << QPointF(xc, yc) poly << QPointF(xc + arrow_x, yc - arrow_y) poly << QPointF(xc + arrow_x, yc) self.arrow.setPolygon(poly) def setParentItem(self, parent_item): super().setParentItem(parent_item) self._update_avoid()