def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool): """ Create a QGraphicsPathItem for a network path Args: brushColor (str) : The color for filling the rectangles painterPath (QPainterPath) : The path to be inserted to the item isOccupyPathItem (bool) : Whether the path is occupied or unoccupied path """ # Generate the path item if not created if isOccupyPathItem: if self.occupiedPathItem is None: self.occupiedPathItem = QGraphicsPathItem(self) pathItem = self.occupiedPathItem else: if self.unoccupiedPathItem is None: self.unoccupiedPathItem = QGraphicsPathItem(self) pathItem = self.unoccupiedPathItem if pathItem is None: pathItem = QGraphicsPathItem(self) # Set the item parameters pathItem.setPath(painterPath) pathItem.setPen(QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine)) pathItem.setBrush(QBrush(QColor(brushColor))) pathItem.setZValue(0)
def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool): """ Create a QGraphicsPathItem for a network path Args: brushColor (str) : The color for filling the rectangles painterPath (QPainterPath) : The path to be inserted to the item isOccupyPathItem (bool) : Whether the path is occupied or unoccupied path """ # Generate the path item if not created if isOccupyPathItem: if self.occupiedPathItem is None: self.occupiedPathItem = QGraphicsPathItem(self) pathItem = self.occupiedPathItem else: if self.unoccupiedPathItem is None: self.unoccupiedPathItem = QGraphicsPathItem(self) pathItem = self.unoccupiedPathItem if pathItem is None: pathItem = QGraphicsPathItem(self) # Set the item parameters pathItem.setPath(painterPath) pathItem.setPen( QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine)) pathItem.setBrush(QBrush(QColor(brushColor))) pathItem.setZValue(0)
def drawGlyph(self, scene, glyph, offsetX=0, offsetY=0, color=(255, 255, 255)): path = QPainterPath() path.setFillRule(Qt.WindingFill) for c in self.decomposedPaths(glyph): segs = c.segments path.moveTo(segs[-1].points[-1].x, segs[-1].points[-1].y) for seg in segs: tuples = [(a.x, a.y) for a in seg.points] flattuples = list(sum(tuples, ())) if len(tuples) == 2: path.quadTo(*flattuples) elif len(tuples) == 3: path.cubicTo(*flattuples) else: path.lineTo(*flattuples) line = QGraphicsPathItem() line.setBrush(QColor(*color)) p = QPen() p.setStyle(Qt.NoPen) line.setPen(p) line.setPath(path) reflect = QTransform(1, 0, 0, -1, 0, 0) reflect.translate(offsetX, offsetY) # print(f"Drawing {glyph} at offset {offsetX} {offsetY}") line.setTransform(reflect) scene.addItem(line)
class PathWorkplaneOutline(QGraphicsRectItem): def __init__(self, parent=None): super(PathWorkplaneOutline, self).__init__(parent) self.setPen(getNoPen()) self._path = QGraphicsPathItem(self) self._path.setBrush(getNoBrush()) self._path.setPen(newPenObj(styles.BLUE_STROKE, 0)) # end def def updateAppearance(self): tl = self.rect().topLeft() tl1 = tl + QPointF(0, -BASE_WIDTH/2) tl2 = tl + QPointF(BASE_WIDTH/2, -BASE_WIDTH/2) bl = self.rect().bottomLeft() bl1 = bl + QPointF(0, BASE_WIDTH/2) bl2 = bl + QPointF(BASE_WIDTH/2, BASE_WIDTH/2) tr = self.rect().topRight() tr1 = tr + QPointF(0, -BASE_WIDTH/2) tr2 = tr + QPointF(-BASE_WIDTH/2, -BASE_WIDTH/2) br = self.rect().bottomRight() br1 = br + QPointF(0, BASE_WIDTH/2) br2 = br + QPointF(-BASE_WIDTH/2, BASE_WIDTH/2) pp = QPainterPath() pp.moveTo(tl2) pp.lineTo(tl1) pp.lineTo(bl1) pp.lineTo(bl2) pp.moveTo(tr2) pp.lineTo(tr1) pp.lineTo(br1) pp.lineTo(br2) self._path.setPath(pp)
def get_elements(self, cell, height): result = [] current = [] rectangles = self.doc.elementsByTagName('rect') for i in range(rectangles.size()): rect = rectangles.item(i).toElement() fill = '#aaaaaa' item = QGraphicsRectItem(float(rect.attribute('x')), float(rect.attribute('y')), float(rect.attribute('width')), float(rect.attribute('height'))) item.setBrush(QBrush(QColor(fill))) result.append(item) if rect.attribute('id') == str(cell): side_fill = fill size = item.rect().getRect() for j in range(1, height+1): x = size[0] - 12 * j y = size[1] - 20 * j if j == height: side_fill = '#ff0000' top = QGraphicsRectItem(x, y, size[2], size[3]) top.setBrush(QBrush(QColor(fill).darker(200))) current.append(top) path = QPainterPath() path.moveTo(x + size[2], y + size[3]) path.lineTo(x + size[2] + 12, y + size[3] + 20) path.lineTo(x + 12, y + size[3] + 20) path.lineTo(x, y + size[3]) front = QGraphicsPathItem(path) front.setBrush(QBrush(QColor(side_fill))) current.append(front) path = QPainterPath() path.moveTo(x + size[2], y + size[3]) path.lineTo(x + size[2] + 12, y + size[3] + 20) path.lineTo(x + size[2] + 12, y + 20) path.lineTo(x + size[2], y) right = QGraphicsPathItem(path) right.setBrush(QBrush(QColor(side_fill).lighter())) current.append(right) return result + current
class PathWorkplaneOutline(QGraphicsRectItem): """ """ def __init__(self, parent: QGraphicsItem = None): """ Args: parent: default is ``None`` """ super(PathWorkplaneOutline, self).__init__(parent) self.setPen(getNoPen()) self._path = QGraphicsPathItem(self) self._path.setBrush(getNoBrush()) self._path.setPen(newPenObj(styles.BLUE_STROKE, 0)) # end def def updateAppearance(self): tl = self.rect().topLeft() tl1 = tl + QPointF(0, -BASE_WIDTH/2) tl2 = tl + QPointF(BASE_WIDTH/2, -BASE_WIDTH/2) bl = self.rect().bottomLeft() bl1 = bl + QPointF(0, BASE_WIDTH/2) bl2 = bl + QPointF(BASE_WIDTH/2, BASE_WIDTH/2) tr = self.rect().topRight() tr1 = tr + QPointF(0, -BASE_WIDTH/2) tr2 = tr + QPointF(-BASE_WIDTH/2, -BASE_WIDTH/2) br = self.rect().bottomRight() br1 = br + QPointF(0, BASE_WIDTH/2) br2 = br + QPointF(-BASE_WIDTH/2, BASE_WIDTH/2) pp = QPainterPath() pp.moveTo(tl2) pp.lineTo(tl1) pp.lineTo(bl1) pp.lineTo(bl2) pp.moveTo(tr2) pp.lineTo(tr1) pp.lineTo(br1) pp.lineTo(br2) self._path.setPath(pp)
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn Attributes: is_forward (TYPE): Description """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): """Summary Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): from vhi xover_item (TYPE): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self.is_forward = strand3p.strandSet().isForward() self._is_on_top = self.is_forward self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, is_forward, idx_x, idx_y): """ Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_forward (TYPE): Description idx_x (TYPE): Description idx_y (TYPE): Description """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._idx = idx_x self._is_on_top = self.is_forward = True if is_forward else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = self.is_forward = strand3p.strandSet().isForward() self.updatePositionAndAppearance() # end def def configurePath(self): """Summary Returns: TYPE: Description """ self._path_thing.setBrush(getBrushObj(_PENCIL_COLOR)) path = PPR3 if self.is_forward else PPL3 offset = -_BASE_WIDTH if self.is_forward else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self.is_forward else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): """Summary Returns: TYPE: Description """ self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.gui.views.pathview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ self._partner_virtual_helix = virtual_helix_item # end def def idx(self): """Summary Returns: TYPE: Description """ return self._idx # end def def virtualHelixItem(self): """Summary Returns: TYPE: Description """ return self._vhi # end def def point(self): """Summary Returns: TYPE: Description """ return self._vhi.upperLeftCornerOfBaseType(self._idx, self.is_forward) # end def def floatPoint(self): """Summary Returns: TYPE: Description """ pt = self.pos() return pt.x(), pt.y() # end def def isForward(self): """Summary Returns: TYPE: Description """ return self.is_forward # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. Args: is_from_strand (bool, optional): Description """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self.is_forward else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def remove(self): """Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """Called by updatePositionAndAppearance during init. Updates drawing and position of the label. Args: is_left (TYPE): Description """ lbl = self._label if self._idx is not None: bw = _BASE_WIDTH num = self._partner_virtual_helix.idNum() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl is None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.idNum())) lbl.show() # end if # end def def hideItems(self): """Summary Returns: TYPE: Description """ if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn Attributes: is_forward (TYPE): Description """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): """Summary Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): from vhi xover_item (TYPE): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self.is_forward = strand3p.strandSet().isForward() self._is_on_top = self.is_forward self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, is_forward, idx_x, idx_y): """ Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description is_forward (TYPE): Description idx_x (TYPE): Description idx_y (TYPE): Description """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._idx = idx_x self._is_on_top = self.is_forward = True if is_forward else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description strand3p (Strand): reference to the 3' strand idx (int): the base index within the virtual helix """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = self.is_forward = strand3p.strandSet().isForward() self.updatePositionAndAppearance() # end def def configurePath(self): """Summary Returns: TYPE: Description """ self._path_thing.setBrush(getBrushObj(_PENCIL_COLOR)) path = PPR3 if self.is_forward else PPL3 offset = -_BASE_WIDTH if self.is_forward else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self.is_forward else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): """Summary Returns: TYPE: Description """ self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): """Summary Args: virtual_helix_item (cadnano.views.pathview.virtualhelixitem.VirtualHelixItem): Description Returns: TYPE: Description """ self._partner_virtual_helix = virtual_helix_item # end def def idx(self): """Summary Returns: TYPE: Description """ return self._idx # end def def virtualHelixItem(self): """Summary Returns: TYPE: Description """ return self._vhi # end def def point(self): """Summary Returns: TYPE: Description """ return self._vhi.upperLeftCornerOfBaseType(self._idx, self.is_forward) # end def def floatPoint(self): """Summary Returns: TYPE: Description """ pt = self.pos() return pt.x(), pt.y() # end def def isForward(self): """Summary Returns: TYPE: Description """ return self.is_forward # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. Args: is_from_strand (bool, optional): Description """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self.is_forward else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def remove(self): """Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """Called by updatePositionAndAppearance during init. Updates drawing and position of the label. Args: is_left (TYPE): Description """ lbl = self._label if self._idx is not None: bw = _BASE_WIDTH num = self._partner_virtual_helix.idNum() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl is None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.idNum())) lbl.show() # end if # end def def hideItems(self): """Summary Returns: TYPE: Description """ if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
class TextAnnotation(Annotation): """Text annotation item for the canvas scheme. """ editingFinished = Signal() """Emitted when the editing is finished (i.e. the item loses focus).""" textEdited = Signal() """Emitted when the edited text changes.""" def __init__(self, parent=None, **kwargs): Annotation.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) self.__textMargins = (2, 2, 2, 2) rect = self.geometry().translated(-self.pos()) ################################ # PyQt 5.10 crashes because of this call (3) #self.__framePathItem = QGraphicsPathItem(self) self.__framePathItem = QGraphicsPathItem(None) self.__framePathItem.setPen(QPen(Qt.NoPen)) self.__textItem = GraphicsTextEdit(self) self.__textItem.setPlaceholderText(self.tr("Enter text here")) self.__textItem.setPos(2, 2) self.__textItem.setTextWidth(rect.width() - 4) self.__textItem.setTabChangesFocus(True) self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction) self.__textItem.setFont(self.font()) self.__textInteractionFlags = Qt.NoTextInteraction layout = self.__textItem.document().documentLayout() layout.documentSizeChanged.connect(self.__onDocumentSizeChanged) self.__updateFrame() def adjustSize(self): """Resize to a reasonable size. """ self.__textItem.setTextWidth(-1) self.__textItem.adjustSize() size = self.__textItem.boundingRect().size() left, top, right, bottom = self.textMargins() geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom)) self.setGeometry(geom) def setFramePen(self, pen): """Set the frame pen. By default Qt.NoPen is used (i.e. the frame is not shown). """ self.__framePathItem.setPen(pen) def framePen(self): """Return the frame pen. """ return self.__framePathItem.pen() def setFrameBrush(self, brush): """Set the frame brush. """ self.__framePathItem.setBrush(brush) def frameBrush(self): """Return the frame brush. """ return self.__framePathItem.brush() def setPlainText(self, text): """Set the annotation plain text. """ self.__textItem.setPlainText(text) def toPlainText(self): return self.__textItem.toPlainText() def setHtml(self, text): """Set the annotation rich text. """ self.__textItem.setHtml(text) def toHtml(self): return self.__textItem.toHtml() def setDefaultTextColor(self, color): """Set the default text color. """ self.__textItem.setDefaultTextColor(color) def defaultTextColor(self): return self.__textItem.defaultTextColor() def setTextMargins(self, left, top, right, bottom): """Set the text margins. """ margins = (left, top, right, bottom) if self.__textMargins != margins: self.__textMargins = margins self.__textItem.setPos(left, top) self.__textItem.setTextWidth( max(self.geometry().width() - left - right, 0)) def textMargins(self): """Return the text margins. """ return self.__textMargins def document(self): """Return the QTextDocument instance used internally. """ return self.__textItem.document() def setTextCursor(self, cursor): self.__textItem.setTextCursor(cursor) def textCursor(self): return self.__textItem.textCursor() def setTextInteractionFlags(self, flags): self.__textInteractionFlags = flags def textInteractionFlags(self): return self.__textInteractionFlags def setDefaultStyleSheet(self, stylesheet): self.document().setDefaultStyleSheet(stylesheet) def mouseDoubleClickEvent(self, event): Annotation.mouseDoubleClickEvent(self, event) if event.buttons() == Qt.LeftButton and \ self.__textInteractionFlags & Qt.TextEditable: self.startEdit() def startEdit(self): """Start the annotation text edit process. """ self.__textItem.setTextInteractionFlags(self.__textInteractionFlags) self.__textItem.setFocus(Qt.MouseFocusReason) # Install event filter to find out when the text item loses focus. self.__textItem.installSceneEventFilter(self) self.__textItem.document().contentsChanged.connect(self.textEdited) def endEdit(self): """End the annotation edit. """ self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction) self.__textItem.removeSceneEventFilter(self) self.__textItem.document().contentsChanged.disconnect(self.textEdited) self.editingFinished.emit() def __onDocumentSizeChanged(self, size): # The size of the text document has changed. Expand the text # control rect's height if the text no longer fits inside. try: rect = self.geometry() _, top, _, bottom = self.textMargins() if rect.height() < (size.height() + bottom + top): rect.setHeight(size.height() + bottom + top) self.setGeometry(rect) except Exception: log.error("error in __onDocumentSizeChanged", exc_info=True) def __updateFrame(self): rect = self.geometry() rect.moveTo(0, 0) path = QPainterPath() path.addRect(rect) self.__framePathItem.setPath(path) def resizeEvent(self, event): width = event.newSize().width() left, _, right, _ = self.textMargins() self.__textItem.setTextWidth(max(width - left - right, 0)) self.__updateFrame() QGraphicsWidget.resizeEvent(self, event) def sceneEventFilter(self, obj, event): if obj is self.__textItem and event.type() == QEvent.FocusOut: self.__textItem.focusOutEvent(event) self.endEdit() return True return Annotation.sceneEventFilter(self, obj, event) def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedHasChanged: if self.isSelected(): self.setFramePen(QPen(Qt.DashDotLine)) else: self.setFramePen(QPen(Qt.NoPen)) return Annotation.itemChange(self, change, value) def changeEvent(self, event): if event.type() == QEvent.FontChange: self.__textItem.setFont(self.font()) Annotation.changeEvent(self, event)
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self.setPen(_NO_PEN) self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, strand_type, idx_x, idx_y): """ """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._strand_type = strand_type self._idx = idx_x self._is_on_top = self._is_drawn_5_to_3 = True if idx_y == 0 else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self.updatePositionAndAppearance() # end def def strandType(self): return self._strand_type # end def def configurePath(self): self._path_thing.setBrush(QBrush(styles.RED_STROKE)) path = PPR3 if self._is_drawn_5_to_3 else PPL3 offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): self._partner_virtual_helix = virtual_helix_item # end def def idx(self): return self._idx # end def def virtualHelixItem(self): return self._vhi # end def def point(self): return self._vhi.upperLeftCornerOfBaseType(self._idx, self._strand_type) # end def def floatPoint(self): pt = self.pos() return pt.x(), pt.y() # end def def isOnTop(self): return self._is_on_top # end def def isDrawn5to3(self): return self._is_drawn_5_to_3 # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def updateConnectivity(self): is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) # end def def remove(self): """ Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl == None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText( str(self._partner_virtual_helix.number()) ) lbl.show() # end if # end def def hideItems(self): if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
def createGraphics(self): """ Create the graphical representation of the FMU's inputs and outputs """ def variableColor(variable): if variable.type == 'Real': return QColor.fromRgb(0, 0, 127) elif variable.type in ['Integer', 'Enumeration']: return QColor.fromRgb(255, 127, 0) elif variable.type == 'Boolean': return QColor.fromRgb(255, 0, 255) elif variable.type == 'String': return QColor.fromRgb(0, 128, 0) else: return QColor.fromRgb(0, 0, 0) inputVariables = [] outputVariables = [] maxInputLabelWidth = 0 maxOutputLabelWidth = 0 textItem = QGraphicsTextItem() fontMetrics = QFontMetricsF(textItem.font()) for variable in self.modelDescription.modelVariables: if variable.causality == 'input': inputVariables.append(variable) elif variable.causality == 'output': outputVariables.append(variable) for variable in inputVariables: maxInputLabelWidth = max(maxInputLabelWidth, fontMetrics.width(variable.name)) for variable in outputVariables: maxOutputLabelWidth = max(maxOutputLabelWidth, fontMetrics.width(variable.name)) from math import floor scene = QGraphicsScene() self.ui.graphicsView.setScene(scene) group = QGraphicsItemGroup() scene.addItem(group) group.setPos(200.5, -50.5) lh = 15 # line height w = max(150., maxInputLabelWidth + maxOutputLabelWidth + 20) h = max(50., 10 + lh * max(len(inputVariables), len(outputVariables))) block = QGraphicsRectItem(0, 0, w, h, group) block.setPen(QColor.fromRgb(0, 0, 255)) pen = QPen() pen.setWidthF(1) font = QFont() font.setPixelSize(10) # inputs y = floor((h - len(inputVariables) * lh) / 2 - 2) for variable in inputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(3) text.setY(y) polygon = QPolygonF([ QPointF(-13.5, y + 4), QPointF(1, y + 11), QPointF(-13.5, y + 18) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) contour.setPen(QPen(Qt.NoPen)) contour.setBrush(variableColor(variable)) y += lh # outputs y = floor((h - len(outputVariables) * lh) / 2 - 2) for variable in outputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(w - 3 - text.boundingRect().width()) text.setY(y) polygon = QPolygonF([ QPointF(w, y + 0 + 7.5), QPointF(w + 7, y + 3.5 + 7.5), QPointF(w, y + 7 + 7.5) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) pen = QPen() pen.setColor(variableColor(variable)) pen.setJoinStyle(Qt.MiterJoin) contour.setPen(pen) y += lh
class ForcedXoverNode3(QGraphicsRectItem): """ This is a QGraphicsRectItem to allow actions and also a QGraphicsSimpleTextItem to allow a label to be drawn """ def __init__(self, virtual_helix_item, xover_item, strand3p, idx): super(ForcedXoverNode3, self).__init__(virtual_helix_item) self._vhi = virtual_helix_item self._xover_item = xover_item self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self._partner_virtual_helix = virtual_helix_item self._blank_thing = QGraphicsRectItem(_blankRect, self) self._blank_thing.setBrush(QBrush(Qt.white)) self._path_thing = QGraphicsPathItem(self) self.configurePath() self.setPen(_NO_PEN) self._label = None self.setPen(_NO_PEN) self.setBrush(_NO_BRUSH) self.setRect(_rect) self.setZValue(styles.ZENDPOINTITEM + 1) # end def def updateForFloatFromVHI(self, virtual_helix_item, strand_type, idx_x, idx_y): """ """ self._vhi = virtual_helix_item self.setParentItem(virtual_helix_item) self._strand_type = strand_type self._idx = idx_x self._is_on_top = self._is_drawn_5_to_3 = True if idx_y == 0 else False self.updatePositionAndAppearance(is_from_strand=False) # end def def updateForFloatFromStrand(self, virtual_helix_item, strand3p, idx): """ """ self._vhi = virtual_helix_item self._strand = strand3p self.setParentItem(virtual_helix_item) self._idx = idx self._is_on_top = virtual_helix_item.isStrandOnTop(strand3p) self._is_drawn_5_to_3 = strand3p.strandSet().isDrawn5to3() self._strand_type = strand3p.strandSet().strandType() self.updatePositionAndAppearance() # end def def strandType(self): return self._strand_type # end def def configurePath(self): self._path_thing.setBrush(QBrush(styles.RED_STROKE)) path = PPR3 if self._is_drawn_5_to_3 else PPL3 offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else _BASE_WIDTH self._path_thing.setPath(path) self._path_thing.setPos(offset, 0) offset = -_BASE_WIDTH if self._is_drawn_5_to_3 else 0 self._blank_thing.setPos(offset, 0) self._blank_thing.show() self._path_thing.show() # end def def refreshXover(self): self._xover_item.refreshXover() # end def def setPartnerVirtualHelix(self, virtual_helix_item): self._partner_virtual_helix = virtual_helix_item # end def def idx(self): return self._idx # end def def virtualHelixItem(self): return self._vhi # end def def point(self): return self._vhi.upperLeftCornerOfBaseType(self._idx, self._strand_type) # end def def floatPoint(self): pt = self.pos() return pt.x(), pt.y() # end def def isOnTop(self): return self._is_on_top # end def def isDrawn5to3(self): return self._is_drawn_5_to_3 # end def def updatePositionAndAppearance(self, is_from_strand=True): """ Sets position by asking the VirtualHelixItem Sets appearance by choosing among pre-defined painterpaths (from normalstrandgraphicsitem) depending on drawing direction. """ self.setPos(*self.point()) n5 = self._xover_item._node5 if is_from_strand: from_strand, from_idx = (n5._strand, n5._idx) if n5 != self else (None, None) if self._strand.canInstallXoverAt(self._idx, from_strand, from_idx): self.configurePath() # We can only expose a 5' end. But on which side? is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) else: self.hideItems() else: self.hideItems() # end def def updateConnectivity(self): is_left = True if self._is_drawn_5_to_3 else False self._updateLabel(is_left) # end def def remove(self): """ Clean up this joint """ scene = self.scene() scene.removeItem(self._label) self._label = None scene.removeItem(self._path_thing) self._path_thing = None scene.removeItem(self._blank_thing) self._blank_thing = None scene.removeItem(self) # end def def _updateLabel(self, is_left): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height() / 2.0 half_label_w = tBR.width() / 2.0 # determine x and y positions label_x = bw / 2.0 - half_label_w if self._is_on_top: label_y = -0.25 * half_label_h - 0.5 - 0.5 * bw else: label_y = 2 * half_label_h + 0.5 + 0.5 * bw # adjust x for left vs right label_x_offset = 0.25 * bw if is_left else -0.25 * bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w / 2.0 # create text item if lbl == None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.number())) lbl.show() # end if # end def def hideItems(self): if self._label: self._label.hide() if self._blank_thing: self._path_thing.hide() if self._blank_thing: self._blank_thing.hide()
class RCircleSegment(QObject): def __init__(self, radius: float, center_x: float, center_y: float, start_angle: float, end_angle: float, clockwise, line_width: float, line_color=Qt.black, fill_color=Qt.transparent): super().__init__() # The supporting rectangle if end_angle < start_angle: end_angle += 2 * math.pi start_angle = -start_angle end_angle = -end_angle shift = end_angle - start_angle if clockwise: shift = -shift - 2 * math.pi x, y = center_x - radius, center_y - radius self.rect = QRectF(x, y, 2 * radius, 2 * radius) # The underlying QGraphicsPathItem self.painter_path = QPainterPath( QPointF(center_x + math.cos(start_angle) * radius, center_y - math.sin(start_angle) * radius)) self.painter_path.arcTo(self.rect, math.degrees(start_angle), math.degrees(shift)) self.path = QGraphicsPathItem(self.painter_path) self.path.setBrush(QtGui.QBrush(fill_color)) pen = QPen() pen.setWidthF(line_width) pen.setColor(line_color) self.path.setPen(pen) self._visible = 1 # def x(self): # return self._pos.x() # # def y(self): # return self._pos.y() # # # The following functions are for animation support # # @pyqtProperty(QPointF) # def pos(self): # return self._pos # # @pos.setter # def pos(self, value): # self.rect = QRectF(value.x() - self._radius, value.y() - self._radius, 2 * self._radius, 2 * self._radius) # self.path.setRect(self.rect) # self._pos = value # @pyqtProperty(int) def visible(self): return self._visible @visible.setter def visible(self, value): if (value > 0): self.path.show() else: self.path.hide() self._visible = value
class PenArrowItem(QGraphicsItemGroup): def __init__(self, path, parent=None): super(PenArrowItem, self).__init__() self.saveable = True self.pi = QGraphicsPathItem() self.path = path self.animator = [parent] self.animateFlag = False # set arrowhead initial e0 = self.path.elementAt(0) e1 = self.path.elementAt(1) pti = QPointF(e1.x, e1.y) ptf = QPointF(e0.x, e0.y) delta = ptf - pti el = sqrt(delta.x()**2 + delta.y()**2) ndelta = delta / el northog = QPointF(-ndelta.y(), ndelta.x()) arBase = ptf - 16 * ndelta arTip = ptf + 8 * ndelta arLeft = arBase - 10 * northog - 4 * ndelta arRight = arBase + 10 * northog - 4 * ndelta self.ari = QPainterPath() self.ari.moveTo(ptf) self.ari.lineTo(arLeft) self.ari.lineTo(arBase) self.ari.lineTo(arRight) self.ari.lineTo(ptf) self.endi = QGraphicsPathItem() self.endi.setPath(self.ari) # set arrowhead final e2 = self.path.elementAt(self.path.elementCount() - 2) e3 = self.path.elementAt(self.path.elementCount() - 1) pti = QPointF(e2.x, e2.y) ptf = QPointF(e3.x, e3.y) delta = ptf - pti el = sqrt(delta.x()**2 + delta.y()**2) ndelta = delta / el northog = QPointF(-ndelta.y(), ndelta.x()) arBase = ptf - 16 * ndelta arTip = ptf + 8 * ndelta arLeft = arBase - 10 * northog - 4 * ndelta arRight = arBase + 10 * northog - 4 * ndelta self.arf = QPainterPath() self.arf.moveTo(ptf) self.arf.lineTo(arLeft) self.arf.lineTo(arBase) self.arf.lineTo(arRight) self.arf.lineTo(ptf) self.endf = QGraphicsPathItem() self.endf.setPath(self.arf) # put everything together self.pi.setPath(self.path) self.pi.setPen(QPen(Qt.red, 2)) self.endi.setPen(QPen(Qt.red, 2)) self.endi.setBrush(QBrush(Qt.red)) self.endf.setPen(QPen(Qt.red, 2)) self.endf.setBrush(QBrush(Qt.red)) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.addToGroup(self.pi) self.addToGroup(self.endi) self.addToGroup(self.endf) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange and self.scene(): command = CommandMoveItem(self, value) self.scene().undoStack.push(command) return QGraphicsItemGroup.itemChange(self, change, value) def pickle(self): pth = [] for k in range(self.path.elementCount()): # e should be either a moveTo or a lineTo e = self.path.elementAt(k) if e.isMoveTo(): pth.append(["m", e.x + self.x(), e.y + self.y()]) else: if e.isLineTo(): pth.append(["l", e.x + self.x(), e.y + self.y()]) else: log.error("Problem pickling penarrowitem path {}".format( self.path)) return ["PenArrow", pth] def paint(self, painter, option, widget): if not self.collidesWithItem(self.scene().underImage, mode=Qt.ContainsItemShape): # paint a bounding rectangle out-of-bounds warning painter.setPen(QPen(QColor(255, 165, 0), 8)) painter.setBrush(QBrush(QColor(255, 165, 0, 128))) painter.drawRoundedRect(option.rect, 10, 10) # paint the normal item with the default 'paint' method super(PenArrowItem, self).paint(painter, option, widget)
class NodeItem(QGraphicsObject): """ An widget node item in the canvas. """ #: Signal emitted when the scene position of the node has changed. positionChanged = Signal() #: Signal emitted when the geometry of the channel anchors changes. anchorGeometryChanged = Signal() #: Signal emitted when the item has been activated (by a mouse double #: click or a keyboard) activated = Signal() #: The item is under the mouse. hovered = Signal() #: Span of the anchor in degrees ANCHOR_SPAN_ANGLE = 90 #: Z value of the item Z_VALUE = 100 def __init__(self, widget_description=None, parent=None, **kwargs): self.__boundingRect = None QGraphicsObject.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) self.setFlag(QGraphicsItem.ItemHasNoContents, True) self.setFlag(QGraphicsItem.ItemIsSelectable, True) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setFlag(QGraphicsItem.ItemIsFocusable, True) # central body shape item self.shapeItem = None # in/output anchor items self.inputAnchorItem = None self.outputAnchorItem = None # title text item self.captionTextItem = None # error, warning, info items self.errorItem = None self.warningItem = None self.infoItem = None # background when selected self.backgroundItem = None self.__title = "" self.__processingState = 0 self.__progress = -1 self.__statusMessage = "" self.__error = None self.__warning = None self.__info = None self.__anchorLayout = None self.__animationEnabled = False self.setZValue(self.Z_VALUE) self.setupGraphics() self.setWidgetDescription(widget_description) @classmethod def from_node(cls, node): """ Create an :class:`NodeItem` instance and initialize it from a :class:`SchemeNode` instance. """ self = cls() self.setWidgetDescription(node.description) # self.setCategoryDescription(node.category) return self @classmethod def from_node_meta(cls, meta_description): """ Create an `NodeItem` instance from a node meta description. """ self = cls() self.setWidgetDescription(meta_description) return self def setupGraphics(self): """ Set up the graphics. """ shape_rect = QRectF(-24, -24, 48, 48) self.shapeItem = NodeBodyItem(self) self.shapeItem.setShapeRect(shape_rect) self.shapeItem.setAnimationEnabled(self.__animationEnabled) # Rect for widget's 'ears'. anchor_rect = QRectF(-31, -31, 62, 62) self.inputAnchorItem = SinkAnchorItem(self) input_path = QPainterPath() start_angle = 180 - self.ANCHOR_SPAN_ANGLE / 2 input_path.arcMoveTo(anchor_rect, start_angle) input_path.arcTo(anchor_rect, start_angle, self.ANCHOR_SPAN_ANGLE) self.inputAnchorItem.setAnchorPath(input_path) self.outputAnchorItem = SourceAnchorItem(self) output_path = QPainterPath() start_angle = self.ANCHOR_SPAN_ANGLE / 2 output_path.arcMoveTo(anchor_rect, start_angle) output_path.arcTo(anchor_rect, start_angle, - self.ANCHOR_SPAN_ANGLE) self.outputAnchorItem.setAnchorPath(output_path) self.inputAnchorItem.hide() self.outputAnchorItem.hide() # Title caption item self.captionTextItem = NameTextItem(self) self.captionTextItem.setPlainText("") self.captionTextItem.setPos(0, 33) def iconItem(standard_pixmap): item = GraphicsIconItem(self, icon=standard_icon(standard_pixmap), iconSize=QSize(16, 16)) item.hide() return item self.errorItem = iconItem(QStyle.SP_MessageBoxCritical) self.warningItem = iconItem(QStyle.SP_MessageBoxWarning) self.infoItem = iconItem(QStyle.SP_MessageBoxInformation) ################################ # PyQt 5.10 crashes because of this call (2) #self.backgroundItem = QGraphicsPathItem(self) self.backgroundItem = QGraphicsPathItem(None) ################################ backgroundrect = QPainterPath() backgroundrect.addRoundedRect(anchor_rect.adjusted(-4, -2, 4, 2), 5, 5, mode=Qt.AbsoluteSize) self.backgroundItem.setPen(QPen(Qt.NoPen)) self.backgroundItem.setBrush(QPalette().brush(QPalette.Highlight)) self.backgroundItem.setOpacity(0.5) self.backgroundItem.setPath(backgroundrect) self.backgroundItem.setZValue(-10) self.backgroundItem.setVisible(self.isSelected()) self.prepareGeometryChange() self.__boundingRect = None # TODO: Remove the set[Widget|Category]Description. The user should # handle setting of icons, title, ... def setWidgetDescription(self, desc): """ Set widget description. """ self.widget_description = desc if desc is None: return icon = icon_loader.from_description(desc).get(desc.icon) if icon: self.setIcon(icon) if not self.title(): self.setTitle(desc.name) if desc.inputs: self.inputAnchorItem.show() if desc.outputs: self.outputAnchorItem.show() tooltip = NodeItem_toolTipHelper(self) self.setToolTip(tooltip) def setWidgetCategory(self, desc): """ Set the widget category. """ self.category_description = desc if desc and desc.background: background = NAMED_COLORS.get(desc.background, desc.background) color = QColor(background) if color.isValid(): self.setColor(color) def setIcon(self, icon): """ Set the node item's icon (:class:`QIcon`). """ if isinstance(icon, QIcon): self.icon_item = GraphicsIconItem(self.shapeItem, icon=icon, iconSize=QSize(36, 36)) self.icon_item.setPos(-18, -18) else: raise TypeError def setColor(self, color, selectedColor=None): """ Set the widget color. """ if selectedColor is None: selectedColor = saturated(color, 150) palette = create_palette(color, selectedColor) self.shapeItem.setPalette(palette) def setPalette(self, palette): # TODO: The palette should override the `setColor` raise NotImplementedError def setTitle(self, title): """ Set the node title. The title text is displayed at the bottom of the node. """ self.__title = title self.__updateTitleText() def title(self): """ Return the node title. """ return self.__title title_ = Property(six.text_type, fget=title, fset=setTitle, doc="Node title text.") def setFont(self, font): """ Set the title text font (:class:`QFont`). """ if font != self.font(): self.prepareGeometryChange() self.captionTextItem.setFont(font) self.__updateTitleText() def font(self): """ Return the title text font. """ return self.captionTextItem.font() def setAnimationEnabled(self, enabled): """ Set the node animation enabled state. """ if self.__animationEnabled != enabled: self.__animationEnabled = enabled self.shapeItem.setAnimationEnabled(enabled) def animationEnabled(self): """ Are node animations enabled. """ return self.__animationEnabled def setProcessingState(self, state): """ Set the node processing state i.e. the node is processing (is busy) or is idle. """ if self.__processingState != state: self.__processingState = state self.shapeItem.setProcessingState(state) if not state: # Clear the progress meter. self.setProgress(-1) if self.__animationEnabled: self.shapeItem.ping() def processingState(self): """ The node processing state. """ return self.__processingState processingState_ = Property(int, fget=processingState, fset=setProcessingState) def setProgress(self, progress): """ Set the node work progress state (number between 0 and 100). """ if progress is None or progress < 0 or not self.__processingState: progress = -1 progress = max(min(progress, 100), -1) if self.__progress != progress: self.__progress = progress self.shapeItem.setProgress(progress) self.__updateTitleText() def progress(self): """ Return the node work progress state. """ return self.__progress progress_ = Property(float, fget=progress, fset=setProgress, doc="Node progress state.") def setStatusMessage(self, message): """ Set the node status message text. This text is displayed below the node's title. """ if self.__statusMessage != message: self.__statusMessage = six.text_type(message) self.__updateTitleText() def statusMessage(self): return self.__statusMessage def setStateMessage(self, message): """ Set a state message to display over the item. Parameters ---------- message : UserMessage Message to display. `message.severity` is used to determine the icon and `message.contents` is used as a tool tip. """ # TODO: Group messages by message_id not by severity # and deprecate set[Error|Warning|Error]Message if message.severity == UserMessage.Info: self.setInfoMessage(message.contents) elif message.severity == UserMessage.Warning: self.setWarningMessage(message.contents) elif message.severity == UserMessage.Error: self.setErrorMessage(message.contents) def setErrorMessage(self, message): if self.__error != message: self.__error = message self.__updateMessages() def setWarningMessage(self, message): if self.__warning != message: self.__warning = message self.__updateMessages() def setInfoMessage(self, message): if self.__info != message: self.__info = message self.__updateMessages() def newInputAnchor(self): """ Create and return a new input :class:`AnchorPoint`. """ if not (self.widget_description and self.widget_description.inputs): raise ValueError("Widget has no inputs.") anchor = AnchorPoint() self.inputAnchorItem.addAnchor(anchor, position=1.0) positions = self.inputAnchorItem.anchorPositions() positions = uniform_linear_layout(positions) self.inputAnchorItem.setAnchorPositions(positions) return anchor def removeInputAnchor(self, anchor): """ Remove input anchor. """ self.inputAnchorItem.removeAnchor(anchor) positions = self.inputAnchorItem.anchorPositions() positions = uniform_linear_layout(positions) self.inputAnchorItem.setAnchorPositions(positions) def newOutputAnchor(self): """ Create and return a new output :class:`AnchorPoint`. """ if not (self.widget_description and self.widget_description.outputs): raise ValueError("Widget has no outputs.") anchor = AnchorPoint(self) self.outputAnchorItem.addAnchor(anchor, position=1.0) positions = self.outputAnchorItem.anchorPositions() positions = uniform_linear_layout(positions) self.outputAnchorItem.setAnchorPositions(positions) return anchor def removeOutputAnchor(self, anchor): """ Remove output anchor. """ self.outputAnchorItem.removeAnchor(anchor) positions = self.outputAnchorItem.anchorPositions() positions = uniform_linear_layout(positions) self.outputAnchorItem.setAnchorPositions(positions) def inputAnchors(self): """ Return a list of all input anchor points. """ return self.inputAnchorItem.anchorPoints() def outputAnchors(self): """ Return a list of all output anchor points. """ return self.outputAnchorItem.anchorPoints() def setAnchorRotation(self, angle): """ Set the anchor rotation. """ self.inputAnchorItem.setRotation(angle) self.outputAnchorItem.setRotation(angle) self.anchorGeometryChanged.emit() def anchorRotation(self): """ Return the anchor rotation. """ return self.inputAnchorItem.rotation() def boundingRect(self): # TODO: Important because of this any time the child # items change geometry the self.prepareGeometryChange() # needs to be called. if self.__boundingRect is None: self.__boundingRect = self.childrenBoundingRect() return self.__boundingRect def shape(self): # Shape for mouse hit detection. # TODO: Should this return the union of all child items? return self.shapeItem.shape() def __updateTitleText(self): """ Update the title text item. """ text = ['<div align="center">%s' % escape(self.title())] status_text = [] progress_included = False if self.__statusMessage: msg = escape(self.__statusMessage) format_fields = dict(parse_format_fields(msg)) if "progress" in format_fields and len(format_fields) == 1: # Insert progress into the status text format string. spec, _ = format_fields["progress"] if spec != None: progress_included = True progress_str = "{0:.0f}%".format(self.progress()) status_text.append(msg.format(progress=progress_str)) else: status_text.append(msg) if self.progress() >= 0 and not progress_included: status_text.append("%i%%" % int(self.progress())) if status_text: text += ["<br/>", '<span style="font-style: italic">', "<br/>".join(status_text), "</span>"] text += ["</div>"] text = "".join(text) # The NodeItems boundingRect could change. self.prepareGeometryChange() self.__boundingRect = None self.captionTextItem.setHtml(text) self.captionTextItem.document().adjustSize() width = self.captionTextItem.textWidth() self.captionTextItem.setPos(-width / 2.0, 33) def __updateMessages(self): """ Update message items (position, visibility and tool tips). """ items = [self.errorItem, self.warningItem, self.infoItem] messages = [self.__error, self.__warning, self.__info] for message, item in zip(messages, items): item.setVisible(bool(message)) item.setToolTip(message or "") shown = [item for item in items if item.isVisible()] count = len(shown) if count: spacing = 3 rects = [item.boundingRect() for item in shown] width = sum(rect.width() for rect in rects) width += spacing * max(0, count - 1) height = max(rect.height() for rect in rects) origin = self.shapeItem.boundingRect().top() - spacing - height origin = QPointF(-width / 2, origin) for item, rect in zip(shown, rects): item.setPos(origin) origin = origin + QPointF(rect.width() + spacing, 0) def mousePressEvent(self, event): if self.shapeItem.path().contains(event.pos()): return QGraphicsObject.mousePressEvent(self, event) else: event.ignore() def mouseDoubleClickEvent(self, event): if self.shapeItem.path().contains(event.pos()): QGraphicsObject.mouseDoubleClickEvent(self, event) QTimer.singleShot(0, self.activated.emit) else: event.ignore() def contextMenuEvent(self, event): if self.shapeItem.path().contains(event.pos()): return QGraphicsObject.contextMenuEvent(self, event) else: event.ignore() def focusInEvent(self, event): self.shapeItem.setHasFocus(True) return QGraphicsObject.focusInEvent(self, event) def focusOutEvent(self, event): self.shapeItem.setHasFocus(False) return QGraphicsObject.focusOutEvent(self, event) def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: selected = bool(qtcompat.qunwrap(value)) self.shapeItem.setSelected(selected) self.captionTextItem.setSelectionState(selected) self.backgroundItem.setVisible(selected) elif change == QGraphicsItem.ItemPositionHasChanged: self.positionChanged.emit() return QGraphicsObject.itemChange(self, change, value)