def set_functions_list(self, functionsList, prefix="", sufix=""): self.funtionsList = functionsList self.funtionsListItems = [] self.maxHeight = 0 tempHeight = 0 for element in functionsList: tempElement = QGraphicsTextItem(self) tempElement.setPlainText(prefix + element + sufix) tempElement.setPos(0, tempHeight) tempHeight += tempElement.document().size().height() if self.maxWidth < tempElement.document().size().width(): self.maxWidth = tempElement.document().size().width() self.funtionsListItems.append(tempElement) self.maxHeight = tempHeight
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): 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 self.__title = "" self.__processingState = 0 self.__progress = -1 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 = QGraphicsTextItem(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) # 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(unicode, 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: 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 setProgressMessage(self, message): """ Set the node work progress message. .. note:: Not yet implemented """ pass 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. return self.childrenBoundingRect() 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. """ title_safe = escape(self.title()) if self.progress() > 0: text = '<div align="center">%s<br/>%i%%</div>' % \ (title_safe, int(self.progress())) else: text = '<div align="center">%s</div>' % \ (title_safe) # The NodeItems boundingRect could change. self.prepareGeometryChange() 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: self.shapeItem.setSelected(value.toBool()) elif change == QGraphicsItem.ItemPositionHasChanged: self.positionChanged.emit() return QGraphicsObject.itemChange(self, change, value)
class EFrameLayout(QGraphicsPolygonItem): def __init__(self, parent=None, scene=None): super(EFrameLayout, self).__init__(parent, scene) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setAcceptsHoverEvents(True) self.__name = QGraphicsTextItem() self.__name.setPlainText('Frame Layout') self.__handleWidth = 500 self.__handleHeight = 20 self.__separator = 5 self.__boundExtra = 3 + self.pen().width() self.__handleRect = QRectF(0.0, 0.0, self.__handleWidth, self.__handleHeight) self.__controlsBound = 0.0 self.__controls = [] self.__isDefaultPen = False self.__isCollapsed = True self.__pens = { 0: EDraw.EColor.DefaultLeaveHoverPen, 1: EDraw.EColor.DefaultEnterHoverPen } self.setPen(self.__pens[self.__isDefaultPen]) @property def Label(self): return self.__name.toPlainText() @Label.setter def Label(self, label): self.__name.setPlainText(str(label)) @property def Width(self): return self.__handleWidth @Width.setter def Width(self, width): self.__handleWidth = width self.updateGeometry() def toggleContentVisibility(self): if not len(self.__controls): return if self.__controls[0].isVisible(): [control.hide() for control in self.__controls] self.__isCollapsed = True return [control.show() for control in self.__controls] self.__isCollapsed = False def updateGeometry(self): self.__handleRect = QRectF(0.0, 0.0, self.__handleWidth, self.__handleHeight) if not len(self.__controls) or self.__isCollapsed: self.__controlsBound = 0.0 return self.__controlsBound = 0.0 self.__controlsBound = self.__separator self.__controlsBound += sum([ control.boundingRect().height() + self.__separator for control in self.__controls ]) step = self.__handleHeight + self.__separator * 2 for control in self.__controls: control.Name.setTextWidth(self.__controlNameWidth) control.Width = self.__handleRect.normalized().adjusted( 5.0, 0.0, -5.0, 0.0).width() control.setPos(5.0, step) step += control.boundingRect().height() + self.__separator def addControl(self, control): if not isinstance(control, EControlsGroup): raise AttributeError self.__controls.append(control) control.setParentItem(self) control.setFlag(QGraphicsItem.ItemIsMovable, False) self.__controlNameWidth = max([ control.Name.boundingRect().width() for control in self.__controls ]) + 5 self.__isCollapsed = False self.updateGeometry() def clear(self): [control.setParentItem(None) for control in self.__controls] self.__controls = [] self.updateGeometry() def mouseDoubleClickEvent(self, mouseEvent): QGraphicsPolygonItem.mouseDoubleClickEvent(self, mouseEvent) self.toggleContentVisibility() self.updateGeometry() def shape(self): return QGraphicsItem.shape(self) def boundingRect(self): return self.__handleRect.normalized().adjusted(0.0, 0.0, 0.0, self.__controlsBound) def paint(self, painter, option, widget=None): if not self.__isCollapsed: painter.setPen(QPen(QColor(0, 0, 0, 50), 2, Qt.SolidLine)) painter.setBrush(QColor(0, 0, 0, 50)) painter.drawRect(self.boundingRect().adjusted( self.pen().width(), self.__handleHeight, -self.pen().width(), 0.0)) painter.setPen(self.pen()) painter.setBrush( EDraw.EColor.LinearGradient(self.__handleRect, Qt.darkGray)) #painter.drawPolygon( QPolygonF( self.__handleRect.normalized().adjusted(-self.__boundExtra, 0.0, self.__boundExtra, 0.0 ) ) ) painter.drawPolygon(QPolygonF(self.__handleRect)) painter.setPen(Qt.lightGray) r = QRectF(0.0, 0.0, self.__name.boundingRect().width(), self.__handleRect.height()) painter.drawText(r, Qt.AlignCenter, self.__name.toPlainText())
class EControlsGroup(QGraphicsPolygonItem): def __init__(self, parent=None, scene=None): super(EControlsGroup, self).__init__(parent, scene) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setAcceptsHoverEvents(True) self.__kId = str(uuid.uuid1()) self.__name = QGraphicsTextItem() self.__name.setPlainText('EddGroup') self.__sWidth = 500 self.__sHeight = 20 self.__sExtra = 8 self.__controlsOffset = 5 self.__isDefaultPen = False self.__pens = { 0: EDraw.EColor.DefaultLeaveHoverPen, 1: EDraw.EColor.DefaultEnterHoverPen } self.setPen(self.__pens[self.__isDefaultPen]) self.__shapeRect = QRectF(0, 0, self.__sWidth, self.__sHeight) self.__controls = [] def updateGeometry(self): adjustableControls = [ control for control in self.__controls if control.Adjustable ] if len(adjustableControls): fixedControlsBounds = self.__name.boundingRect().width() fixedControlsBounds += sum( map(int, [ control.boundingRect().width() for control in self.__controls if not control.Adjustable ])) adjustableControlsWidth = int( (self.__sWidth - fixedControlsBounds)) / len(adjustableControls) step = self.__name.boundingRect().width() for control in self.__controls: if control.Adjustable: control.Width = adjustableControlsWidth - self.__controlsOffset control.setPos(step, 0.0) step += control.boundingRect().width() + self.__controlsOffset self.__shapeRect = QRectF( 0, -(self.__sExtra / 2), self.__sWidth, max([control.Height for control in self.__controls]) + self.__sExtra) @property def kId(self): return self.__kId @property def Name(self): return self.__name @Name.setter def Name(self, name): " %s" % self.__name.setPlainText(name) self.updateGeometry() @property def Width(self): return self.__sWidth @Width.setter def Width(self, width): self.__sWidth = width #- self.__controlsOffset * len( self.__controls ) self.updateGeometry() @property def Height(self): return self.__sHeight @property def Controls(self): return self.__controls @Controls.setter def Controls(self, control): control.setParentItem(self) self.__controls.append(control) self.updateGeometry() def toggleHighlight(self): if self.__isDefaultPen: self.__isDefaultPen = False self.setPen(self.__pens[self.__isDefaultPen]) return self.__isDefaultPen = True self.setPen(self.__pens[self.__isDefaultPen]) def hoverEnterEvent(self, mouseEvent): QGraphicsPolygonItem.hoverEnterEvent(self, mouseEvent) self.toggleHighlight() def hoverLeaveEvent(self, mouseEvent): QGraphicsPolygonItem.hoverLeaveEvent(self, mouseEvent) self.toggleHighlight() def shape(self): return QGraphicsItem.shape(self) def boundingRect(self): return self.__shapeRect def paint(self, painter, option, widget=None): painter.setPen(self.pen()) painter.setBrush( EDraw.EColor.LinearGradient(self.boundingRect(), Qt.darkGray)) painter.drawPolygon(QPolygonF(self.boundingRect())) painter.setPen(Qt.lightGray) r = QRectF(0.0, -(self.__sExtra / 2), self.__name.boundingRect().width(), self.boundingRect().height()) painter.drawText(r, Qt.AlignRight | Qt.AlignCenter, "%s: " % self.__name.toPlainText())
class LinkItem(QGraphicsObject): """ A Link item in the canvas that connects two :class:`.NodeItem`\s in the canvas. The link curve connects two `Anchor` items (see :func:`setSourceItem` and :func:`setSinkItem`). Once the anchors are set the curve automatically adjusts its end points whenever the anchors move. An optional source/sink text item can be displayed above the curve's central point (:func:`setSourceName`, :func:`setSinkName`) """ #: Z value of the item Z_VALUE = 0 def __init__(self, *args): QGraphicsObject.__init__(self, *args) self.setFlag(QGraphicsItem.ItemHasNoContents, True) self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton) self.setAcceptHoverEvents(True) self.setZValue(self.Z_VALUE) self.sourceItem = None self.sourceAnchor = None self.sinkItem = None self.sinkAnchor = None self.curveItem = LinkCurveItem(self) self.sourceIndicator = LinkAnchorIndicator(self) self.sinkIndicator = LinkAnchorIndicator(self) self.sourceIndicator.hide() self.sinkIndicator.hide() self.linkTextItem = QGraphicsTextItem(self) self.__sourceName = "" self.__sinkName = "" self.__dynamic = False self.__dynamicEnabled = False self.hover = False def setSourceItem(self, item, anchor=None): """ Set the source `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve start point (if ``None`` a new output anchor will be created using ``item.newOutputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one end of the link). """ if item is not None and anchor is not None: if anchor not in item.outputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sourceItem != item: if self.sourceAnchor: # Remove a previous source item and the corresponding anchor self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged ) if self.sourceItem is not None: self.sourceItem.removeOutputAnchor(self.sourceAnchor) self.sourceItem = self.sourceAnchor = None self.sourceItem = item if item is not None and anchor is None: # Create a new output anchor for the item if none is provided. anchor = item.newOutputAnchor() # Update the visibility of the start point indicator. self.sourceIndicator.setVisible(bool(item)) if anchor != self.sourceAnchor: if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged ) self.sourceAnchor = anchor if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.connect( self._sourcePosChanged ) self.__updateCurve() def setSinkItem(self, item, anchor=None): """ Set the sink `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve end point (if ``None`` a new input anchor will be created using ``item.newInputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one and of the link). """ if item is not None and anchor is not None: if anchor not in item.inputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sinkItem != item: if self.sinkAnchor: # Remove a previous source item and the corresponding anchor self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged ) if self.sinkItem is not None: self.sinkItem.removeInputAnchor(self.sinkAnchor) self.sinkItem = self.sinkAnchor = None self.sinkItem = item if item is not None and anchor is None: # Create a new input anchor for the item if none is provided. anchor = item.newInputAnchor() # Update the visibility of the end point indicator. self.sinkIndicator.setVisible(bool(item)) if self.sinkAnchor != anchor: if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged ) self.sinkAnchor = anchor if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.connect( self._sinkPosChanged ) self.__updateCurve() def setFont(self, font): """ Set the font for the channel names text item. """ if font != self.font(): self.linkTextItem.setFont(font) self.__updateText() def font(self): """ Return the font for the channel names text. """ return self.linkTextItem.font() def setChannelNamesVisible(self, visible): """ Set the visibility of the channel name text. """ self.linkTextItem.setVisible(visible) def setSourceName(self, name): """ Set the name of the source (used in channel name text). """ if self.__sourceName != name: self.__sourceName = name self.__updateText() def sourceName(self): """ Return the source name. """ return self.__sourceName def setSinkName(self, name): """ Set the name of the sink (used in channel name text). """ if self.__sinkName != name: self.__sinkName = name self.__updateText() def sinkName(self): """ Return the sink name. """ return self.__sinkName def _sinkPosChanged(self, *arg): self.__updateCurve() def _sourcePosChanged(self, *arg): self.__updateCurve() def __updateCurve(self): self.prepareGeometryChange() if self.sourceAnchor and self.sinkAnchor: source_pos = self.sourceAnchor.anchorScenePos() sink_pos = self.sinkAnchor.anchorScenePos() source_pos = self.curveItem.mapFromScene(source_pos) sink_pos = self.curveItem.mapFromScene(sink_pos) # Adaptive offset for the curve control points to avoid a # cusp when the two points have the same y coordinate # and are close together delta = source_pos - sink_pos dist = math.sqrt(delta.x() ** 2 + delta.y() ** 2) cp_offset = min(dist / 2.0, 60.0) # TODO: make the curve tangent orthogonal to the anchors path. path = QPainterPath() path.moveTo(source_pos) path.cubicTo(source_pos + QPointF(cp_offset, 0), sink_pos - QPointF(cp_offset, 0), sink_pos) self.curveItem.setPath(path) self.sourceIndicator.setPos(source_pos) self.sinkIndicator.setPos(sink_pos) self.__updateText() else: self.setHoverState(False) self.curveItem.setPath(QPainterPath()) def __updateText(self): self.prepareGeometryChange() if self.__sourceName or self.__sinkName: if self.__sourceName != self.__sinkName: text = u"{0} \u2192 {1}".format(self.__sourceName, self.__sinkName) else: # If the names are the same show only one. # Is this right? If the sink has two input channels of the # same type having the name on the link help elucidate # the scheme. text = self.__sourceName else: text = "" self.linkTextItem.setPlainText(text) path = self.curveItem.path() if not path.isEmpty(): center = path.pointAtPercent(0.5) angle = path.angleAtPercent(0.5) brect = self.linkTextItem.boundingRect() transform = QTransform() transform.translate(center.x(), center.y()) transform.rotate(-angle) # Center and move above the curve path. transform.translate(-brect.width() / 2, -brect.height()) self.linkTextItem.setTransform(transform) def removeLink(self): self.setSinkItem(None) self.setSourceItem(None) self.__updateCurve() def setHoverState(self, state): if self.hover != state: self.prepareGeometryChange() self.hover = state self.sinkIndicator.setHoverState(state) self.sourceIndicator.setHoverState(state) self.curveItem.setHoverState(state) def hoverEnterEvent(self, event): # Hover enter event happens when the mouse enters any child object # but we only want to show the 'hovered' shadow when the mouse # is over the 'curveItem', so we install self as an event filter # on the LinkCurveItem and listen to its hover events. self.curveItem.installSceneEventFilter(self) return QGraphicsObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): # Remove the event filter to prevent unnecessary work in # scene event filter when not needed self.curveItem.removeSceneEventFilter(self) return QGraphicsObject.hoverLeaveEvent(self, event) def sceneEventFilter(self, obj, event): if obj is self.curveItem: if event.type() == QEvent.GraphicsSceneHoverEnter: self.setHoverState(True) elif event.type() == QEvent.GraphicsSceneHoverLeave: self.setHoverState(False) return QGraphicsObject.sceneEventFilter(self, obj, event) def boundingRect(self): return self.childrenBoundingRect() def shape(self): return self.curveItem.shape() def setEnabled(self, enabled): """ Reimplemented from :class:`QGraphicsObject` Set link enabled state. When disabled the link is rendered with a dashed line. """ # This getter/setter pair override a property from the base class. # They should be renamed to e.g. setLinkEnabled/linkEnabled self.curveItem.setLinkEnabled(enabled) def isEnabled(self): return self.curveItem.isLinkEnabled() def setDynamicEnabled(self, enabled): """ Set the link's dynamic enabled state. If the link is `dynamic` it will be rendered in red/green color respectively depending on the state of the dynamic enabled state. """ if self.__dynamicEnabled != enabled: self.__dynamicEnabled = enabled if self.__dynamic: self.__updatePen() def isDynamicEnabled(self): """ Is the link dynamic enabled. """ return self.__dynamicEnabled def setDynamic(self, dynamic): """ Mark the link as dynamic (i.e. it responds to :func:`setDynamicEnabled`). """ if self.__dynamic != dynamic: self.__dynamic = dynamic self.__updatePen() def isDynamic(self): """ Is the link dynamic. """ return self.__dynamic def __updatePen(self): self.prepareGeometryChange() if self.__dynamic: if self.__dynamicEnabled: color = QColor(0, 150, 0, 150) else: color = QColor(150, 0, 0, 150) normal = QPen(QBrush(color), 2.0) hover = QPen(QBrush(color.darker(120)), 2.1) else: normal = QPen(QBrush(QColor("#9CACB4")), 2.0) hover = QPen(QBrush(QColor("#7D7D7D")), 2.1) self.curveItem.setCurvePenSet(normal, hover)
class TaskGraphicsItem (QGraphicsEllipseItem): def __init__(self, rect=None): super(TaskGraphicsItem, self).__init__() if rect is not None: self.setRect(rect) self.setPen(QPen(Qt.NoPen)) # Setup the text item self.textItem = QGraphicsTextItem() self.textItem.setParentItem(self) self.textItem.rotate(-90) self.textItem.setDefaultTextColor(QColor(255, 255, 255)) # The dimensions to reach via a LERP. self.startPos = QPointF(0, 0) self.endPos = QPointF(0, 0) self.startDiameter = 1 self.endDiameter = 1 self.centerMark = QGraphicsEllipseItem() self.centerMark.setBrush(QBrush(Qt.white)) self.centerMark.setPen(QPen(Qt.NoPen)) self.centerMark.setParentItem(self) self.pid = -1 # To determine if it is associated with an active process. self.used = False def mousePressEvent(self, event): print "Clicked On Ellipse at: ", self.rect().topLeft() def set_pid(self, pid): self.pid = pid def set_name(self, str_name): self.textItem.setPlainText(str_name) def update_name_pos(self): rect = self.boundingRect() text_rect = self.textItem.boundingRect() # Center text (on the x-axis) and offset (on the y-axis) so it doesn't overlap the ellipse item. x_text = rect.x() + rect.width()/2 - text_rect.height()/2 y_text = rect.y() + 100 + text_rect.width() + rect.height() self.textItem.setPos(x_text, y_text) # Time step is in seconds. def update(self, time_step): diameter = self.rect().width() + self.lerp_rate(self.startDiameter, self.endDiameter, time_step) if diameter <= 1: diameter = 1 pos = self.rect().topLeft() x = pos.x() + self.lerp_rate(self.startPos.x(), self.endPos.x(), time_step) y = pos.y() + self.lerp_rate(self.startPos.y(), self.endPos.y(), time_step) self.setRect(QRectF(x, y, diameter, diameter)) self.update_name_pos() self.update_center_mark() def update_center_mark(self): scale = self.scene().views()[0].currentScale hwidth = self.rect().width() / 2.0 diam = 2.0 / scale # Only mark center for large enough items. if hwidth * 0.2 > diam: self.centerMark.setVisible(True) hdiam = diam / 2.0 pos = self.rect().topLeft() x = pos.x() - hdiam + hwidth y = pos.y() - hdiam + hwidth self.centerMark.setRect(QRectF(x, y, diam, diam)) else: self.centerMark.setVisible(False) # Return the linear interpolation rate. Reach start to end at a rate of 'growth rate' @staticmethod def lerp_rate(start, end, time_step): return (end - start) * time_step
class VennIntersectionArea(QGraphicsPathItem): def __init__(self, parent=None, text=""): super(QGraphicsPathItem, self).__init__(parent) self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.text = QGraphicsTextItem(self) layout = self.text.document().documentLayout() layout.documentSizeChanged.connect(self._onLayoutChanged) self._text = "" self._anchor = QPointF() def setText(self, text): if self._text != text: self._text = text self.text.setPlainText(text) def text(self): return self._text def setTextAnchor(self, pos): if self._anchor != pos: self._anchor = pos self._updateTextAnchor() def hoverEnterEvent(self, event): self.setZValue(self.zValue() + 1) return QGraphicsPathItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.setZValue(self.zValue() - 1) return QGraphicsPathItem.hoverLeaveEvent(self, event) def paint(self, painter, option, widget=None): painter.save() path = self.path() brush = QBrush(self.brush()) pen = QPen(self.pen()) if option.state & QStyle.State_Selected: pen.setColor(Qt.red) brush.setStyle(Qt.DiagCrossPattern) brush.setColor(QColor(40, 40, 40, 100)) elif option.state & QStyle.State_MouseOver: pen.setColor(Qt.blue) if option.state & QStyle.State_MouseOver: brush.setColor(QColor(100, 100, 100, 100)) if brush.style() == Qt.NoBrush: # Make sure the highlight is actually visible. brush.setStyle(Qt.SolidPattern) painter.setPen(pen) painter.setBrush(brush) painter.drawPath(path) painter.restore() def itemChange(self, change, value): if change == QGraphicsPathItem.ItemSelectedHasChanged: self.setZValue(self.zValue() + (1 if value else -1)) return QGraphicsPathItem.itemChange(self, change, value) def _updateTextAnchor(self): rect = self.text.boundingRect() pos = anchor_rect(rect, self._anchor) self.text.setPos(pos) def _onLayoutChanged(self): self._updateTextAnchor()
class ClassModel(QGraphicsItem): def __init__(self, parent=None, graphicView=None, graphicScene=None): QGraphicsItem.__init__(self) self.set_default_data() self.className = QGraphicsTextItem(self) self.functionsItem = FunctionsContainerModel(self) self.className.setPlainText(self.defaultClassName) self.setFlag(self.ItemIsMovable) self.setFlag(self.ItemSendsGeometryChanges) self.functionsItem.setPos(0, self.__get_title_height()) self.attributesItem = FunctionsContainerModel(self) self.attributesItem.setPos(0, self.functionsItem.get_height()) def set_default_data(self): self.maxWidth = 100 self.defaultClassNameHeight = 30 self.defaultClassName = "No name" def set_functions_list(self, functionsList): self.functionsItem.set_functions_list(functionsList, "*", "()") self.update_positions() def set_attributes_list(self, attributesList): self.attributesItem.set_functions_list(attributesList) self.update_positions() def set_class_name(self, className): self.className.setPlainText(className) def _get_width(self): self.__calc_max_width() return self.maxWidth def __get_title_height(self): titleHeight = self.defaultClassNameHeight if titleHeight == self.className.document().size().height(): titleHeight = self.className.document().size().height() return titleHeight def get_height(self): summary = self.defaultClassNameHeight summary += self.functionsItem.get_height() summary += self.attributesItem.get_height() return summary def __calc_max_width(self): if self.maxWidth < self.className.document().size().width(): self.maxWidth = self.className.document().size().width() if hasattr(self, "functionsItem"): if self.maxWidth < self.functionsItem.get_width(): self.maxWidth = self.functionsItem.get_width() if hasattr(self, "attributesItem"): if self.maxWidth < self.attributesItem.get_width(): self.maxWidth = self.attributesItem.get_width() def set_bg_color(self, qColor): self.backgroundColor = qColor def set_method_list(self, itemList): self.methodList = itemList def update_positions(self): self.functionsItem.setPos(0, self.__get_title_height()) self.attributesItem.setPos( 0, self.functionsItem.y() + self.functionsItem.get_height()) def paint(self, painter, option, widget): gradient = QRadialGradient(-3, -3, 10) if option.state & QStyle.State_Sunken: gradient.setCenter(3, 3) gradient.setFocalPoint(3, 3) gradient.setColorAt(0, QColor(Qt.yellow).light(120)) else: gradient.setColorAt(0, QColor(Qt.yellow).light(120)) painter.setBrush(gradient) painter.setPen(QPen(Qt.black, 0)) painter.drawRoundedRect(self.boundingRect(), 3, 3) def boundingRect(self): return QRectF(0, 0, self._get_width(), self.get_height()) def add_edge(self, edge): self.myEdge = edge edge.adjust()
class pageView(QGraphicsView): def __init__(self, wpage): QGraphicsView.__init__(self) self.init(wpage) self.initShape() # self.selection = pageSelection(self.heditor) def init(self, wpage): self.wpage = wpage self.heditor = wpage.heditor self.filesize = self.heditor.filesize self.start = True self.pagew = 20 self.pageh = 20 self.pageItems = [] self.offsetItems = [] self.displayLines = 0 def initShape(self): #Scene self.scene = QGraphicsScene() self.setScene(self.scene) #Font self.initFont() #Headers self.setHeads(self.heditor.pagesPerBlock) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setAlignment(Qt.AlignLeft) def initFont(self): self.font = QFont("Gothic") self.font.setFixedPitch(1) self.font.setBold(False) self.font.setPixelSize(14) def setHeads(self, pagesPerBlock): if self.heditor.pageOffView: self.setOffsetHead() else: self.setBlockHead() self.setPageHead(self.heditor.pagesPerBlock) linesize = 95 + (self.heditor.pagesPerBlock * (self.pagew + 2)) #Decoration headLine = QGraphicsLineItem(QLineF(0, 20, linesize, 20)) self.scene.addItem(headLine) headOffLine = QGraphicsLineItem(QLineF(90, 0, 90, 700)) self.scene.addItem(headOffLine) def setOffsetHead(self): self.offHead = QGraphicsTextItem() self.offHead.setDefaultTextColor(QColor(Qt.red)) self.offHead.setFont(self.font) self.offHead.setPlainText("Offset(Kb)") self.offHead.setPos(5, 0) self.scene.addItem(self.offHead) def setPageHead(self, len): count = 0 x = 95 while count < len: item = QGraphicsSimpleTextItem() item.setFont(self.font) item.setText("%.2x" % count) item.setPos(x, 3) self.scene.addItem(item) x += self.pagew + 2 count += 1 def setBlockHead(self): self.blockHead = QGraphicsTextItem() self.blockHead.setDefaultTextColor(QColor(Qt.red)) self.blockHead.setFont(self.font) self.blockHead.setPlainText("Block") self.blockHead.setPos(15, 0) self.scene.addItem(self.blockHead) def initOffsetItems(self): count = 0 x = 0 y = 25 while count <= self.displayLines: item = QGraphicsTextItem() item.setDefaultTextColor(QColor(Qt.red)) item.setFont(self.font) item.setPos(x, y) self.offsetItems.append(item) y += self.pageh + 2 count += 1 #Add Items in scene for item in self.offsetItems: self.scene.addItem(item) def initPageItems(self): id = 0 countpage = 0 startx = 95 starty = 25 x = startx y = starty line = 0 while line <= self.displayLines: while countpage < self.heditor.pagesPerBlock: p = page(x, y, id, self) self.pageItems.append(p) id += 1 # self.scene.addItem(sec) x += self.pagew + 2 countpage += 1 x = startx y += self.pageh + 2 countpage = 0 line += 1 #Add items in scene for item in self.pageItems: self.scene.addItem(item) def hidePageItems(self, startid): end = len(self.pageItems) for item in self.pageItems[startid:end]: item.setVisible(False) def setAllPageItemsVisible(self): for item in self.pageItems: item.setVisible(True) def hideOffsetItems(self, startid): end = len(self.offsetItems) for item in self.offsetItems[startid:end]: item.setVisible(False) def setAllOffsetItemsVisible(self): for item in self.offsetItems: item.setVisible(True) def appendOffsetItems(self, linesToAppend): count = 0 x = 0 y = 25 + (len(self.offsetItems) * (self.pageh + 2)) # print "Y append offset ", y while count <= linesToAppend: item = QGraphicsTextItem() item.setDefaultTextColor(QColor(Qt.red)) item.setFont(self.font) item.setPos(x, y) self.offsetItems.append(item) self.scene.addItem(item) y += self.pageh + 2 count += 1 def appendPageItems(self, linesToAppend): count = 0 cp = 0 x = 95 y = 25 + ((len(self.pageItems) / self.heditor.pagesPerBlock) * (self.pageh + 2)) id = len(self.pageItems) while count <= linesToAppend: while cp < self.heditor.pagesPerBlock: item = page(x, y, id, self) self.pageItems.append(item) self.scene.addItem(item) id += 1 x += self.pagew + 2 cp += 1 count += 1 x = 95 y += self.pageh + 2 def refreshOffsetItems(self, offset): #Check if end of pages or if number of pages < display pages self.setAllOffsetItemsVisible() block = (offset / self.heditor.pageSize) / self.heditor.pagesPerBlock startBlockOffset = block * (self.heditor.pagesPerBlock * self.heditor.pageSize) lineToDisplay = ((self.filesize - offset) / self.heditor.pageSize) / self.heditor.pagesPerBlock if ((self.filesize - offset) / self.heditor.pageSize) % self.heditor.pagesPerBlock > 0: lineToDisplay += 1 if lineToDisplay >= self.displayLines: offset = startBlockOffset for item in self.offsetItems[0:self.displayLines]: if self.heditor.decimalview: if self.heditor.pageOffView: offlabel = QString("%.10d" % (offset / 1024)) else: offlabel = QString("%.10d" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock))) else: if self.heditor.pageOffView: offlabel = QString("%.10x" % (offset / 1024)) else: offlabel = QString("%.10x" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock))) item.setPlainText(offlabel) offset = offset + (self.heditor.pagesPerBlock * self.heditor.pageSize) self.heditor.startBlockOffset = startBlockOffset else: if lineToDisplay == 0: lineToDisplay = 5 offset = startBlockOffset - (lineToDisplay * self.heditor.pagesPerBlock * self.heditor.pageSize) if ((self.filesize - offset) / self.heditor.pageSize) % self.heditor.pagesPerBlock > 0: lineToDisplay += 1 self.heditor.startBlockOffset = offset for item in self.offsetItems[0:lineToDisplay]: if self.heditor.decimalview: if self.heditor.pageOffView: offlabel = QString("%.10d" % (offset / 1024)) else: offlabel = QString("%.10d" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock))) else: if self.heditor.pageOffView: offlabel = QString("%.10x" % (offset / 1024)) else: offlabel = QString("%.10x" % (offset / (self.heditor.pageSize * self.heditor.pagesPerBlock))) item.setPlainText(offlabel) offset = offset + (self.heditor.pagesPerBlock * self.heditor.pageSize) self.hideOffsetItems(lineToDisplay) def refreshPageItems(self, offset): self.setAllPageItemsVisible() maxpages = self.displayLines * self.heditor.pagesPerBlock displaypages = (self.filesize - offset) / self.heditor.pageSize if displaypages <= maxpages: if displaypages == 0: startline = self.lines - 5 startOffset = startline * (self.heditor.pageSize * self.heditor.pagesPerBlock) rangeOffset = self.filesize - startOffset newdisplaypages = rangeOffset / self.heditor.pageSize if rangeOffset % self.heditor.pageSize > 0: newdisplaypages += 1 self.hidePageItems(newdisplaypages) self.heditor.startBlockOffset = startOffset else: rangeOffset = self.filesize - offset rangePages = rangeOffset / self.heditor.pageSize rangeLines = rangePages / self.heditor.pagesPerBlock newdisplaypages = rangeOffset / self.heditor.pageSize if rangeOffset % self.heditor.pageSize > 0: newdisplaypages += 1 self.hidePageItems(newdisplaypages) self.heditor.startBlockOffset = offset else: self.heditor.startBlockOffset = offset self.heditor.pageselection.update() def refreshAllContent(self): self.scene.clear() del self.offsetItems[:] del self.pageItems[:] self.setHeads(self.heditor.pagesPerBlock) self.initOffsetItems() self.initPageItems() self.refreshOffsetItems(self.heditor.startBlockOffset) self.refreshPageItems(self.heditor.startBlockOffset) def lineToOffset(self, line): offset = (line * (self.heditor.pagesPerBlock * self.heditor.pageSize)) return offset def lineToOffsetKb(self, line): offset = (line * (self.heditor.pagesPerBlock * self.heditor.pageSize)) / 1024 return offset def lineToOffsetMb(self, line): offset = ((line * (self.heditor.pagesPerBlock * self.heditor.pageSize)) / 1024) / 1024 return offset def lineToOffsetGb(self, line): offset = (((line * (self.heditor.pagesPerBlock * self.heditor.pageSize)) / 1024) / 1024) / 1024 return offset ############################ # Colorize Pages # ############################ def refreshPagesColor(self, id): # print "Refresh: ", id self.cleanSelection() self.pageItems[id].setBrush(QBrush(Qt.green, Qt.SolidPattern)) def cleanSelection(self): item = self.pageItems[self.selectedPage] item.setBrush(item.brush) ############################ # Resize Event # ############################ def resizeEvent(self, sizEvent): y = sizEvent.size().height() disline = (y / self.pageh) #Start if self.start == True: if self.height() < self.heditor.mainWindow.height(): if disline > self.displayLines: self.displayLines = (y / self.pageh) #Lines self.lines = self.filesize / (self.heditor.pagesPerBlock * self.heditor.pageSize) #Init self.initOffsetItems() self.initPageItems() #Refresh self.refreshOffsetItems(0) self.refreshPageItems(0) if self.lines > self.displayLines: self.wpage.setScrollBar() else: self.wpage.scroll = False self.start = False else: range = disline - self.displayLines if range > 0: self.displayLines = (y / self.pageh) #Append self.appendOffsetItems(range) self.appendPageItems(range) #Refresh self.refreshOffsetItems(self.heditor.startBlockOffset) self.refreshPageItems(self.heditor.startBlockOffset) # self.heditor.footer.pixel.view.resizeEvent(sizEvent) ############################ # CallBacks # ############################ def wheelEvent(self, event): offset = self.heditor.startBlockOffset # up = False if self.wpage.scroll: if event.delta() > 0: #UP subOffset = 3 * self.heditor.pagesPerBlock * self.heditor.pageSize if offset - subOffset > 0: offset = offset - subOffset else: offset = 0 else: #DOWN addOffset = 3 * self.heditor.pagesPerBlock * self.heditor.pageSize if offset + addOffset < self.filesize: offset = offset + addOffset else: offset = self.filesize - (5 * self.heditor.pagesPerBlock * self.heditor.pageSize) #Set ScrollBar value: if offset < self.filesize - (5 * self.heditor.pagesPerBlock * self.heditor.pageSize): value = self.wpage.scroll.offsetToValue(offset) self.wpage.scroll.setValue(value) self.refreshOffsetItems(offset) self.refreshPageItems(offset) def keyPressEvent(self, keyEvent): pass #if keyEvent.matches(QKeySequence.MoveToNextPage): #print "Next block" #elif keyEvent.matches(QKeySequence.MoveToPreviousPage): #print "Previous block" #elif keyEvent.matches(QKeySequence.MoveToNextLine): #print "Next Line" #elif keyEvent.matches(QKeySequence.MoveToPreviousLine): #print "Previous Line" #else: #pass def resetSelection(self): for item in self.pageItems: item.setBrush(item.brush) def getPageID(self, x, y): startx = 95 starty = 25 #Set scrollbar seek hscroll = self.horizontalScrollBar() if hscroll.value() > 0: startx -= hscroll.value() xrange = x - startx if xrange > 0: xcut = (xrange / (self.pagew + 2)) # print xcut else: xcut = 0 yrange = y - starty if yrange > 0: ycut = yrange / (self.pageh + 2) else: ycut = 0 id = (ycut * self.heditor.pagesPerBlock) + xcut return id def mousePressEvent(self, event): button = event.button() pos = event.pos() if event.button() == 1: #Get CLicked coordonates x = pos.x() y = pos.y() id = self.getPageID(x, y) self.resetSelection() self.heditor.pageselection.select(id, self.heditor.startBlockOffset, 0) def mouseMoveEvent(self, event): pos = event.pos() x = pos.x() y = pos.y() if self.heditor.pageselection.mode == 1: id = self.getPageID(x, y) self.resetSelection() self.heditor.pageselection.select(id, self.heditor.startBlockOffset, 1) # else: # id = self.getPageID(x, y) # self.pageItems[id].setBrush(self) def mouseReleaseEvent(self, event): self.heditor.pageselection.mode = 0
class EControlsGroup(QGraphicsPolygonItem): def __init__(self, parent=None, scene=None): super(EControlsGroup, self).__init__(parent, scene) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setAcceptsHoverEvents(True) self.__kId = str( uuid.uuid1() ) self.__name = QGraphicsTextItem() self.__name.setPlainText('EddGroup') self.__sWidth = 500 self.__sHeight = 20 self.__sExtra = 8 self.__controlsOffset = 5 self.__isDefaultPen = False self.__pens = { 0: EDraw.EColor.DefaultLeaveHoverPen, 1: EDraw.EColor.DefaultEnterHoverPen } self.setPen(self.__pens[self.__isDefaultPen]) self.__shapeRect = QRectF( 0 , 0, self.__sWidth, self.__sHeight ) self.__controls = [] def updateGeometry(self): adjustableControls = [ control for control in self.__controls if control.Adjustable ] if len(adjustableControls): fixedControlsBounds = self.__name.boundingRect().width() fixedControlsBounds += sum( map( int, [ control.boundingRect().width() for control in self.__controls if not control.Adjustable ] )) adjustableControlsWidth = int(( self.__sWidth - fixedControlsBounds )) / len(adjustableControls) step = self.__name.boundingRect().width() for control in self.__controls: if control.Adjustable: control.Width = adjustableControlsWidth - self.__controlsOffset control.setPos( step, 0.0 ) step += control.boundingRect().width() + self.__controlsOffset self.__shapeRect = QRectF( 0 , -(self.__sExtra / 2), self.__sWidth, max([ control.Height for control in self.__controls ]) + self.__sExtra ) @property def kId(self): return self.__kId @property def Name(self): return self.__name @Name.setter def Name(self, name ): " %s" % self.__name.setPlainText(name) self.updateGeometry() @property def Width(self): return self.__sWidth @Width.setter def Width(self, width): self.__sWidth = width #- self.__controlsOffset * len( self.__controls ) self.updateGeometry() @property def Height(self): return self.__sHeight @property def Controls(self): return self.__controls @Controls.setter def Controls(self, control): control.setParentItem(self) self.__controls.append(control) self.updateGeometry() def toggleHighlight(self): if self.__isDefaultPen: self.__isDefaultPen = False self.setPen(self.__pens[self.__isDefaultPen]) return self.__isDefaultPen = True self.setPen(self.__pens[self.__isDefaultPen]) def hoverEnterEvent(self, mouseEvent): QGraphicsPolygonItem.hoverEnterEvent(self, mouseEvent) self.toggleHighlight() def hoverLeaveEvent(self, mouseEvent): QGraphicsPolygonItem.hoverLeaveEvent(self, mouseEvent) self.toggleHighlight() def shape(self): return QGraphicsItem.shape(self) def boundingRect(self): return self.__shapeRect def paint(self, painter, option, widget=None): painter.setPen(self.pen()) painter.setBrush( EDraw.EColor.LinearGradient( self.boundingRect(), Qt.darkGray ) ) painter.drawPolygon( QPolygonF( self.boundingRect() ) ) painter.setPen(Qt.lightGray) r = QRectF( 0.0, -(self.__sExtra / 2), self.__name.boundingRect().width(), self.boundingRect().height() ) painter.drawText( r , Qt.AlignRight | Qt.AlignCenter , "%s: " % self.__name.toPlainText() )
class MyView(QGraphicsView): ''' Näkymä. ''' def __init__(self, kappalelista): ''' Constructor ''' super(MyView, self).__init__() self.kappalelista = kappalelista self.setMouseTracking(True) self.liitosOk = False self.tempLiitos = [] self.tempVastin = [] self.textItem = None def mousePressEvent(self, event): selected_items = self.scene().selectedItems() if not selected_items: currentItem = self.itemAt(event.pos()) if currentItem is not None: if currentItem.brush().color() == QColor(170,0,0) or currentItem.brush().color() == QColor(150,210,230): currentItem = currentItem.parentItem() currentItem.setSelected(True) currentItem.parentItem().setZValue(1) for liitoskohta in currentItem.parentItem().liitoskohdat: if not liitoskohta.isFree(): liitoskohta.setFree() else: currentItem = selected_items[0] collision = False for anotherItem in self.kappalelista: if anotherItem.item != currentItem: if currentItem.collidesWithItem(anotherItem.item,0x1): collision = True if collision == False: currentItem.setSelected(False) currentItem.parentItem().setZValue(0) if self.liitosOk: n = len(self.tempLiitokset) for i in range(0,n): self.tempLiitokset[i].liita(self.tempVastineet[i],0) def keyPressEvent(self, event): selected_items = self.scene().selectedItems() if selected_items: key = event.key() currentItem = selected_items[0] if key == 0x01000014: #Key_Right currentItem.parentItem().pyorita(5) if key == 0x01000012: #Key_Left currentItem.parentItem().pyorita(-5) if key == 0x01000007: #Delete currentItem.parentItem().poista() def rakenna(self): for currentItem in self.kappalelista: for anotherItem in self.kappalelista: if anotherItem != currentItem: for liitoskohta in currentItem.liitoskohdat: if liitoskohta.vapaa: for vastakohta in anotherItem.liitoskohdat: if vastakohta.vapaa: if liitoskohta.collidesWithItem(vastakohta,0x1): liitoskohta.liita(vastakohta,1) def LoopText(self): self.textItem = QGraphicsTextItem() self.textItem.setPlainText('Muodostui silmukka') self.scene().addItem(self.textItem) self.textItem.setZValue(2) self.textItem.setPos(200,0) fontti = QFont() fontti.setPointSize(40) self.textItem.setFont(fontti) timer = QTimer() timer.singleShot(3000,self.removeLoopText) def removeLoopText(self): self.scene().removeItem(self.textItem)
class hexView(QGraphicsView): def __init__(self, parent): QGraphicsView.__init__(self, None, parent) self.init(parent) self.initShape() def init(self, parent): self.whex = parent self.heditor = self.whex.heditor #Init scene self.__scene = QGraphicsScene(self) self.setScene(self.__scene) #Get heditor stuff self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setAlignment(Qt.AlignLeft) def setItems(self): self.__scene.addItem(self.whex.offsetitem) self.__scene.addItem(self.whex.hexitem) self.__scene.addItem(self.whex.asciitem) def initShape(self): self.initHeads() #Line decoration offsetLine = QGraphicsLineItem(QLineF(90, 0, 90, 700)) asciiLine = QGraphicsLineItem(QLineF(480, 0, 480, 700)) #Add to scene self.__scene.addItem(offsetLine) self.__scene.addItem(asciiLine) def setCursors(self): self.__scene.addItem(self.whex.hexcursor) self.__scene.addItem(self.whex.asciicursor) def initHeads(self): self.offHead = QGraphicsTextItem() self.hexHead = QGraphicsTextItem() self.asciiHead = QGraphicsTextItem() #Set Color self.offHead.setDefaultTextColor(QColor(Qt.red)) self.hexHead.setDefaultTextColor(QColor(Qt.black)) self.asciiHead.setDefaultTextColor(QColor(Qt.darkCyan)) #Create Font self.font = QFont("Gothic") self.font.setFixedPitch(1) self.font.setBold(False) self.font.setPixelSize(14) #Set Font self.offHead.setFont(self.font) self.hexHead.setFont(self.font) self.asciiHead.setFont(self.font) #Set Text self.offHead.setPlainText("Offset") self.hexHead.setPlainText( "0 1 2 3 4 5 6 7 8 9 A B C D E F") self.asciiHead.setPlainText("Ascii") #Position self.offHead.setPos(20, 0) self.hexHead.setPos(95, 0) self.asciiHead.setPos(520, 0) #Add to scene self.__scene.addItem(self.offHead) self.__scene.addItem(self.hexHead) self.__scene.addItem(self.asciiHead) headLine = QGraphicsLineItem(QLineF(0, 20, 615, 20)) self.__scene.addItem(headLine) def move(self, step, way): #step: line = 1 * bytesPerLine, page = pagesize, wheel = 3 * bytesPerLine offset = self.heditor.currentOffset if way == 0: #UP if (offset - (step * self.heditor.bytesPerLine)) >= 0: self.heditor.readOffset(offset - (step * self.heditor.bytesPerLine)) if self.whex.isLFMOD(): self.whex.scroll.setValue( self.whex.offsetToValue(offset - step * (self.heditor.bytesPerLine))) else: self.whex.scroll.setValue(self.whex.scroll.value() - step) else: self.heditor.readOffset(0) self.whex.scroll.setValue(0) elif way == 1: #Down if (offset + (step * self.heditor.bytesPerLine)) <= ( self.heditor.filesize - (step * self.heditor.bytesPerLine)): self.heditor.readOffset(offset + (step * self.heditor.bytesPerLine)) if self.whex.isLFMOD(): self.whex.scroll.setValue( self.whex.offsetToValue(offset + step * (self.heditor.bytesPerLine))) else: self.whex.scroll.setValue(self.whex.scroll.value() + step) else: self.heditor.readOffset(self.heditor.filesize - 5 * (self.heditor.bytesPerLine)) self.whex.scroll.setValue(self.whex.scroll.max) #################################### # Navigation Operations # #################################### def wheelEvent(self, event): offset = self.heditor.currentOffset if event.delta() > 0: self.move(3, 0) else: self.move(3, 1) def keyPressEvent(self, keyEvent): # off = self.heditor.currentOffset if keyEvent.matches(QKeySequence.MoveToNextPage): self.move(self.heditor.pageSize / self.heditor.bytesPerLine, 1) elif keyEvent.matches(QKeySequence.MoveToPreviousPage): self.move(self.heditor.pageSize / self.heditor.bytesPerLine, 0) elif keyEvent.matches(QKeySequence.MoveToNextWord): print "Next Word" elif keyEvent.matches(QKeySequence.MoveToPreviousWord): print "Previous word" elif keyEvent.matches(QKeySequence.MoveToNextLine): print "Next Line" elif keyEvent.matches(QKeySequence.MoveToPreviousLine): print "Previous Line"
class VennIntersectionArea(QGraphicsPathItem): def __init__(self, parent=None, text=""): super(QGraphicsPathItem, self).__init__(parent) self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.text = QGraphicsTextItem(self) layout = self.text.document().documentLayout() layout.documentSizeChanged.connect(self._onLayoutChanged) self._text = "" self._anchor = QPointF() def setText(self, text): if self._text != text: self._text = text self.text.setPlainText(text) def text(self): return self._text def setTextAnchor(self, pos): if self._anchor != pos: self._anchor = pos self._updateTextAnchor() def hoverEnterEvent(self, event): self.setZValue(self.zValue() + 1) return QGraphicsPathItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.setZValue(self.zValue() - 1) return QGraphicsPathItem.hoverLeaveEvent(self, event) # def mousePressEvent(self, event): # pos = event.pos() # parent = self.parentItem() # pbrect = parent.boundingRect() # w, h = pbrect.width(), pbrect.height() # print "(%.3f, %.3f)" % (pos.x() / w, pos.y() / h) # super(VennIntersectionArea, self).mousePressEvent(event) def paint(self, painter, option, widget=None): painter.save() path = self.path() brush = QBrush(self.brush()) pen = QPen(self.pen()) if option.state & QStyle.State_Selected: pen.setColor(Qt.red) brush.setStyle(Qt.DiagCrossPattern) brush.setColor(QColor(40, 40, 40, 100)) elif option.state & QStyle.State_MouseOver: pen.setColor(Qt.blue) if option.state & QStyle.State_MouseOver: brush.setColor(QColor(100, 100, 100, 100)) if brush.style() == Qt.NoBrush: # Make sure the highlight is actually visible. brush.setStyle(Qt.SolidPattern) painter.setPen(pen) painter.setBrush(brush) painter.drawPath(path) painter.restore() def itemChange(self, change, value): if change == QGraphicsPathItem.ItemSelectedHasChanged: if value.toBool(): self.setZValue(self.zValue() + 1) else: self.setZValue(self.zValue() - 1) return QGraphicsPathItem.itemChange(self, change, value) def _updateTextAnchor(self): rect = self.text.boundingRect() pos = anchor_rect(rect, self._anchor) self.text.setPos(pos) def _onLayoutChanged(self): self._updateTextAnchor()
class VennIntersectionArea(QGraphicsPathItem): def __init__(self, parent=None, text=""): super(QGraphicsPathItem, self).__init__(parent) self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.text = QGraphicsTextItem(self) layout = self.text.document().documentLayout() layout.documentSizeChanged.connect(self._onLayoutChanged) self._text = "" self._anchor = QPointF() def setText(self, text): if self._text != text: self._text = text self.text.setPlainText(text) def text(self): return self._text def setTextAnchor(self, pos): if self._anchor != pos: self._anchor = pos self._updateTextAnchor() def hoverEnterEvent(self, event): self.setZValue(self.zValue() + 1) return QGraphicsPathItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.setZValue(self.zValue() - 1) return QGraphicsPathItem.hoverLeaveEvent(self, event) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: if event.modifiers() & Qt.AltModifier: self.setSelected(False) elif event.modifiers() & Qt.ControlModifier: self.setSelected(not self.isSelected()) elif event.modifiers() & Qt.ShiftModifier: self.setSelected(True) else: for area in self.parentWidget().vennareas(): area.setSelected(False) self.setSelected(True) def mouseReleaseEvent(self, event): pass def paint(self, painter, option, widget=None): painter.save() path = self.path() brush = QBrush(self.brush()) pen = QPen(self.pen()) if option.state & QStyle.State_Selected: pen.setColor(Qt.red) brush.setStyle(Qt.DiagCrossPattern) brush.setColor(QColor(40, 40, 40, 100)) elif option.state & QStyle.State_MouseOver: pen.setColor(Qt.blue) if option.state & QStyle.State_MouseOver: brush.setColor(QColor(100, 100, 100, 100)) if brush.style() == Qt.NoBrush: # Make sure the highlight is actually visible. brush.setStyle(Qt.SolidPattern) painter.setPen(pen) painter.setBrush(brush) painter.drawPath(path) painter.restore() def itemChange(self, change, value): if change == QGraphicsPathItem.ItemSelectedHasChanged: self.setZValue(self.zValue() + (1 if value else -1)) return QGraphicsPathItem.itemChange(self, change, value) def _updateTextAnchor(self): rect = self.text.boundingRect() pos = anchor_rect(rect, self._anchor) self.text.setPos(pos) def _onLayoutChanged(self): self._updateTextAnchor()
class LinkItem(QGraphicsObject): """ A Link item in the canvas that connects two :class:`.NodeItem`\s in the canvas. The link curve connects two `Anchor` items (see :func:`setSourceItem` and :func:`setSinkItem`). Once the anchors are set the curve automatically adjusts its end points whenever the anchors move. An optional source/sink text item can be displayed above the curve's central point (:func:`setSourceName`, :func:`setSinkName`) """ #: Z value of the item Z_VALUE = 0 def __init__(self, *args): QGraphicsObject.__init__(self, *args) self.setFlag(QGraphicsItem.ItemHasNoContents, True) self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton) self.setAcceptHoverEvents(True) self.setZValue(self.Z_VALUE) self.sourceItem = None self.sourceAnchor = None self.sinkItem = None self.sinkAnchor = None self.curveItem = LinkCurveItem(self) self.sourceIndicator = LinkAnchorIndicator(self) self.sinkIndicator = LinkAnchorIndicator(self) self.sourceIndicator.hide() self.sinkIndicator.hide() self.linkTextItem = QGraphicsTextItem(self) self.__sourceName = "" self.__sinkName = "" self.__dynamic = False self.__dynamicEnabled = False self.hover = False def setSourceItem(self, item, anchor=None): """ Set the source `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve start point (if ``None`` a new output anchor will be created using ``item.newOutputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one end of the link). """ if item is not None and anchor is not None: if anchor not in item.outputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sourceItem != item: if self.sourceAnchor: # Remove a previous source item and the corresponding anchor self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) if self.sourceItem is not None: self.sourceItem.removeOutputAnchor(self.sourceAnchor) self.sourceItem = self.sourceAnchor = None self.sourceItem = item if item is not None and anchor is None: # Create a new output anchor for the item if none is provided. anchor = item.newOutputAnchor() # Update the visibility of the start point indicator. self.sourceIndicator.setVisible(bool(item)) if anchor != self.sourceAnchor: if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) self.sourceAnchor = anchor if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.connect( self._sourcePosChanged) self.__updateCurve() def setSinkItem(self, item, anchor=None): """ Set the sink `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve end point (if ``None`` a new input anchor will be created using ``item.newInputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one and of the link). """ if item is not None and anchor is not None: if anchor not in item.inputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sinkItem != item: if self.sinkAnchor: # Remove a previous source item and the corresponding anchor self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) if self.sinkItem is not None: self.sinkItem.removeInputAnchor(self.sinkAnchor) self.sinkItem = self.sinkAnchor = None self.sinkItem = item if item is not None and anchor is None: # Create a new input anchor for the item if none is provided. anchor = item.newInputAnchor() # Update the visibility of the end point indicator. self.sinkIndicator.setVisible(bool(item)) if self.sinkAnchor != anchor: if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) self.sinkAnchor = anchor if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.connect( self._sinkPosChanged) self.__updateCurve() def setFont(self, font): """ Set the font for the channel names text item. """ if font != self.font(): self.linkTextItem.setFont(font) self.__updateText() def font(self): """ Return the font for the channel names text. """ return self.linkTextItem.font() def setChannelNamesVisible(self, visible): """ Set the visibility of the channel name text. """ self.linkTextItem.setVisible(visible) def setSourceName(self, name): """ Set the name of the source (used in channel name text). """ if self.__sourceName != name: self.__sourceName = name self.__updateText() def sourceName(self): """ Return the source name. """ return self.__sourceName def setSinkName(self, name): """ Set the name of the sink (used in channel name text). """ if self.__sinkName != name: self.__sinkName = name self.__updateText() def sinkName(self): """ Return the sink name. """ return self.__sinkName def _sinkPosChanged(self, *arg): self.__updateCurve() def _sourcePosChanged(self, *arg): self.__updateCurve() def __updateCurve(self): self.prepareGeometryChange() if self.sourceAnchor and self.sinkAnchor: source_pos = self.sourceAnchor.anchorScenePos() sink_pos = self.sinkAnchor.anchorScenePos() source_pos = self.curveItem.mapFromScene(source_pos) sink_pos = self.curveItem.mapFromScene(sink_pos) # Adaptive offset for the curve control points to avoid a # cusp when the two points have the same y coordinate # and are close together delta = source_pos - sink_pos dist = math.sqrt(delta.x()**2 + delta.y()**2) cp_offset = min(dist / 2.0, 60.0) # TODO: make the curve tangent orthogonal to the anchors path. path = QPainterPath() path.moveTo(source_pos) path.cubicTo(source_pos + QPointF(cp_offset, 0), sink_pos - QPointF(cp_offset, 0), sink_pos) self.curveItem.setPath(path) self.sourceIndicator.setPos(source_pos) self.sinkIndicator.setPos(sink_pos) self.__updateText() else: self.setHoverState(False) self.curveItem.setPath(QPainterPath()) def __updateText(self): self.prepareGeometryChange() if self.__sourceName or self.__sinkName: if self.__sourceName != self.__sinkName: text = u"{0} \u2192 {1}".format(self.__sourceName, self.__sinkName) else: # If the names are the same show only one. # Is this right? If the sink has two input channels of the # same type having the name on the link help elucidate # the scheme. text = self.__sourceName else: text = "" self.linkTextItem.setPlainText(text) path = self.curveItem.path() if not path.isEmpty(): center = path.pointAtPercent(0.5) angle = path.angleAtPercent(0.5) brect = self.linkTextItem.boundingRect() transform = QTransform() transform.translate(center.x(), center.y()) transform.rotate(-angle) # Center and move above the curve path. transform.translate(-brect.width() / 2, -brect.height()) self.linkTextItem.setTransform(transform) def removeLink(self): self.setSinkItem(None) self.setSourceItem(None) self.__updateCurve() def setHoverState(self, state): if self.hover != state: self.prepareGeometryChange() self.hover = state self.sinkIndicator.setHoverState(state) self.sourceIndicator.setHoverState(state) self.curveItem.setHoverState(state) def hoverEnterEvent(self, event): # Hover enter event happens when the mouse enters any child object # but we only want to show the 'hovered' shadow when the mouse # is over the 'curveItem', so we install self as an event filter # on the LinkCurveItem and listen to its hover events. self.curveItem.installSceneEventFilter(self) return QGraphicsObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): # Remove the event filter to prevent unnecessary work in # scene event filter when not needed self.curveItem.removeSceneEventFilter(self) return QGraphicsObject.hoverLeaveEvent(self, event) def sceneEventFilter(self, obj, event): if obj is self.curveItem: if event.type() == QEvent.GraphicsSceneHoverEnter: self.setHoverState(True) elif event.type() == QEvent.GraphicsSceneHoverLeave: self.setHoverState(False) return QGraphicsObject.sceneEventFilter(self, obj, event) def boundingRect(self): return self.childrenBoundingRect() def shape(self): return self.curveItem.shape() def setEnabled(self, enabled): """ Reimplemented from :class:`QGraphicsObject` Set link enabled state. When disabled the link is rendered with a dashed line. """ # This getter/setter pair override a property from the base class. # They should be renamed to e.g. setLinkEnabled/linkEnabled self.curveItem.setLinkEnabled(enabled) def isEnabled(self): return self.curveItem.isLinkEnabled() def setDynamicEnabled(self, enabled): """ Set the link's dynamic enabled state. If the link is `dynamic` it will be rendered in red/green color respectively depending on the state of the dynamic enabled state. """ if self.__dynamicEnabled != enabled: self.__dynamicEnabled = enabled if self.__dynamic: self.__updatePen() def isDynamicEnabled(self): """ Is the link dynamic enabled. """ return self.__dynamicEnabled def setDynamic(self, dynamic): """ Mark the link as dynamic (i.e. it responds to :func:`setDynamicEnabled`). """ if self.__dynamic != dynamic: self.__dynamic = dynamic self.__updatePen() def isDynamic(self): """ Is the link dynamic. """ return self.__dynamic def __updatePen(self): self.prepareGeometryChange() if self.__dynamic: if self.__dynamicEnabled: color = QColor(0, 150, 0, 150) else: color = QColor(150, 0, 0, 150) normal = QPen(QBrush(color), 2.0) hover = QPen(QBrush(color.darker(120)), 2.1) else: normal = QPen(QBrush(QColor("#9CACB4")), 2.0) hover = QPen(QBrush(QColor("#7D7D7D")), 2.1) self.curveItem.setCurvePenSet(normal, hover)
class EFrameLayout(QGraphicsPolygonItem): def __init__(self, parent=None, scene=None): super(EFrameLayout, self).__init__(parent, scene) self.setFlag(QGraphicsItem.ItemIsMovable, True) self.setAcceptsHoverEvents(True) self.__name = QGraphicsTextItem() self.__name.setPlainText('Frame Layout') self.__handleWidth = 500 self.__handleHeight = 20 self.__separator = 5 self.__boundExtra = 3 + self.pen().width() self.__handleRect = QRectF( 0.0 , 0.0 , self.__handleWidth, self.__handleHeight ) self.__controlsBound = 0.0 self.__controls = [] self.__isDefaultPen = False self.__isCollapsed = True self.__pens = { 0: EDraw.EColor.DefaultLeaveHoverPen, 1: EDraw.EColor.DefaultEnterHoverPen } self.setPen(self.__pens[self.__isDefaultPen]) @property def Label(self): return self.__name.toPlainText() @Label.setter def Label(self, label): self.__name.setPlainText( str( label ) ) @property def Width(self): return self.__handleWidth @Width.setter def Width(self, width): self.__handleWidth = width self.updateGeometry() def toggleContentVisibility(self): if not len(self.__controls): return if self.__controls[0].isVisible(): [ control.hide() for control in self.__controls ] self.__isCollapsed = True return [ control.show() for control in self.__controls ] self.__isCollapsed = False def updateGeometry(self): self.__handleRect = QRectF( 0.0 , 0.0 , self.__handleWidth, self.__handleHeight ) if not len(self.__controls) or self.__isCollapsed: self.__controlsBound = 0.0 return self.__controlsBound = 0.0 self.__controlsBound = self.__separator self.__controlsBound += sum( [ control.boundingRect().height() + self.__separator for control in self.__controls ] ) step = self.__handleHeight + self.__separator * 2 for control in self.__controls: control.Name.setTextWidth( self.__controlNameWidth ) control.Width = self.__handleRect.normalized().adjusted( 5.0, 0.0, -5.0, 0.0 ).width() control.setPos( 5.0 , step ) step += control.boundingRect().height() + self.__separator def addControl(self, control): if not isinstance(control, EControlsGroup): raise AttributeError self.__controls.append(control) control.setParentItem(self) control.setFlag(QGraphicsItem.ItemIsMovable, False) self.__controlNameWidth = max([ control.Name.boundingRect().width() for control in self.__controls ]) + 5 self.__isCollapsed = False self.updateGeometry() def clear(self): [ control.setParentItem(None) for control in self.__controls ] self.__controls = [] self.updateGeometry() def mouseDoubleClickEvent(self, mouseEvent): QGraphicsPolygonItem.mouseDoubleClickEvent(self, mouseEvent) self.toggleContentVisibility() self.updateGeometry() def shape(self): return QGraphicsItem.shape(self) def boundingRect(self): return self.__handleRect.normalized().adjusted( 0.0, 0.0, 0.0, self.__controlsBound ) def paint(self, painter, option, widget=None): if not self.__isCollapsed: painter.setPen( QPen( QColor( 0, 0, 0, 50 ), 2 , Qt.SolidLine ) ) painter.setBrush( QColor( 0, 0, 0, 50 ) ) painter.drawRect( self.boundingRect().adjusted( self.pen().width(), self.__handleHeight , -self.pen().width(), 0.0 ) ) painter.setPen(self.pen()) painter.setBrush( EDraw.EColor.LinearGradient( self.__handleRect, Qt.darkGray ) ) #painter.drawPolygon( QPolygonF( self.__handleRect.normalized().adjusted(-self.__boundExtra, 0.0, self.__boundExtra, 0.0 ) ) ) painter.drawPolygon( QPolygonF( self.__handleRect ) ) painter.setPen(Qt.lightGray) r = QRectF( 0.0, 0.0, self.__name.boundingRect().width(), self.__handleRect.height() ) painter.drawText( r, Qt.AlignCenter, self.__name.toPlainText() )
class hexView(QGraphicsView): def __init__(self, parent): QGraphicsView.__init__(self) self.init(parent) self.initShape() def init(self, parent): self.whex = parent self.bdiff = self.whex.bdiff #Init scene self.scene = QGraphicsScene() self.setScene(self.scene) #Get bdiff stuff self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setAlignment(Qt.AlignLeft) def setItems(self): self.scene.addItem(self.whex.offsetitem) self.scene.addItem(self.whex.hexitem) self.scene.addItem(self.whex.asciitem) def initShape(self): self.initHeads() #Line decoration offsetLine = QGraphicsLineItem(QLineF(90, 0, 90, 700)) asciiLine = QGraphicsLineItem(QLineF(480, 0, 480, 700)) #Add to scene self.scene.addItem(offsetLine) self.scene.addItem(asciiLine) def initHeads(self): self.offHead = QGraphicsTextItem() self.hexHead = QGraphicsTextItem() self.asciiHead = QGraphicsTextItem() #Set Color self.offHead.setDefaultTextColor(QColor(Qt.red)) self.hexHead.setDefaultTextColor(QColor(Qt.black)) self.asciiHead.setDefaultTextColor(QColor(Qt.darkCyan)) #Create Font self.font = QFont("Gothic") self.font.setFixedPitch(1) self.font.setBold(False) self.font.setPixelSize(14) #Set Font self.offHead.setFont(self.font) self.hexHead.setFont(self.font) self.asciiHead.setFont(self.font) #Set Text self.offHead.setPlainText("Offset") self.hexHead.setPlainText("0 1 2 3 4 5 6 7 8 9 A B C D E F") self.asciiHead.setPlainText("Ascii") #Position self.offHead.setPos(20, 0) self.hexHead.setPos(95, 0) self.asciiHead.setPos(520, 0) #Add to scene self.scene.addItem(self.offHead) self.scene.addItem(self.hexHead) self.scene.addItem(self.asciiHead) headLine = QGraphicsLineItem(QLineF(0, 20, 615, 20)) self.scene.addItem(headLine) def setSyncView(self, whexview): self.whexviewsync = whexview def move(self, step, way): #step: line = 1 * bytesPerLine, page = pagesize, wheel = 3 * bytesPerLine offset = self.bdiff.currentOffset # print offset if way == 0: #UP if (offset - (step * self.bdiff.bytesPerLine)) >= 0: self.bdiff.readOffset(offset - (step * self.bdiff.bytesPerLine)) if self.bdiff.scrollbar.isLFMOD(): self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.offsetToValue(offset - (step * self.bdiff.bytesPerLine))) else: self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.value() - step) else: self.bdiff.readOffset(0) self.bdiff.scrollbar.setValue(0) elif way == 1: #Down if (offset + (step * self.bdiff.bytesPerLine)) <= (self.bdiff.masterFileSize - (step * self.bdiff.bytesPerLine)): self.bdiff.readOffset(offset + (step * self.bdiff.bytesPerLine)) if self.bdiff.scrollbar.isLFMOD(): self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.offsetToValue(offset + (step * self.bdiff.bytesPerLine))) else: self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.value() + step) else: self.bdiff.readOffset(self.bdiff.masterFileSize - 5 * (self.bdiff.bytesPerLine)) self.bdiff.scrollbar.setValue(self.bdiff.scrollbar.max) #################################### # Navigation Operations # #################################### def wheelEvent(self, event): offset = self.bdiff.currentOffset if event.delta() > 0: self.move(3, 0) self.whexviewsync.move(3, 0) else: self.move(3, 1) self.whexviewsync.move(3, 1) def keyPressEvent(self, keyEvent): off = self.bdiff.currentOffset if keyEvent.matches(QKeySequence.MoveToNextPage): self.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 1) self.whexviewsync.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 1) elif keyEvent.matches(QKeySequence.MoveToPreviousPage): self.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 0) self.whexviewsync.move(self.bdiff.pageSize / self.bdiff.bytesPerLine, 0) elif keyEvent.matches(QKeySequence.MoveToNextLine): self.move(1, 1) self.whexviewsync.move(1, 1) elif keyEvent.matches(QKeySequence.MoveToPreviousLine): self.move(1, 0) self.whexviewsync.move(1, 0)
class CanvasProps (QGraphicsItem): def __init__(self, parent=None, scene=None): QGraphicsItem.__init__ (self) self.parent = parent self.helper = self.parent.getHelper() #self.setFlags (QGraphicsItem.ItemIsSelectable) self.setAcceptsHoverEvents (True) self.pen_color = QPen (Qt.black, 2) self.color = QColor (Qt.white).dark (150) # init Canvas Animation Tweening self.timeline = QTimeLine (200) self.timeline.setFrameRange (0, 100) self.anim = QGraphicsItemAnimation () self.anim.setItem (self) self.anim.setTimeLine (self.timeline) self.helper.connect (self.timeline, SIGNAL("finished()"), self.moveFurtherUp) self.anim_active = False #self._nodename = QGraphicsTextItem ('text '+str(self.node_id), self) self._nodename = QGraphicsTextItem ('', self) self._nodename.setPos (QPointF (18, 20)) self._nodename.setDefaultTextColor (QColor (Qt.white).light (255)) self._nodename.setFont (QFont ("Helvetica", 11, QFont.Bold, False)) self._nodename.setTextWidth(120) self._nodename.setToolTip (self._nodename.toPlainText ()) #self._nodename.setHtml("<h2 align=\"center\">hello</h2><h2 align=\"center\">world 1234345345</h2>123"); self.props_list = [] self.props_textItem_value_list = [] self.FACTOR = 4.0 self._canvas_height = 0 def boundingRect (self): return QRectF (0, 0, 122, 150) def shape (self): path = QPainterPath () path.addRect (0, 0, 122, 20) return path def paint (self, painter, option, unused_widget): if option.state & QStyle.State_Selected: fillColor = self.color.dark (100) else: fillColor = self.color if option.state & QStyle.State_MouseOver: fillColor = fillColor.light (120) if option.levelOfDetail < 0.2: if option.levelOfDetail < 0.125: painter.fillRect (QRectF (0, 0, 110, 70), fillColor) return painter.setPen (QPen (Qt.black, 0)) painter.setBrush (fillColor) painter.drawRect (0, 0, 120, 20) return oldPen = painter.pen () pen = oldPen width = 0 if option.state & QStyle.State_Selected: width += 2 pen.setWidth (width) if option.state & QStyle.State_Sunken: level = 120 else: level = 100 painter.setBrush (QBrush (fillColor.dark (level))) #painter.drawRoundRect (QRect (0, 0, 80, 34+self.height), 20) painter.drawRect (QRect (0, 20, 120, 30+9*self._canvas_height)) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def addProp (self, prop_name, prop_value): i = len (self.props_list) self.props_list.append (QGraphicsTextItem (prop_name + ' : ', self)) self.props_textItem_value_list.append (CustomFloatingText (prop_value, self)) # (1) adding the prop's name. self.props_list[i].setPos (QPointF (7, 35+i*10)) self.props_list[i].setDefaultTextColor (QColor (Qt.white).light (255)) self.props_list[i].setFont (QFont ("Helvetica", 9, QFont.StyleItalic, False)) self.props_list[i].setTextWidth (55) self.props_list[i].setToolTip (self.props_list[i].toPlainText ()) # (2) adding the prop's value. self.props_textItem_value_list[i].setTextInteractionFlags (Qt.TextEditable) self.props_textItem_value_list[i].setPos (QPointF (55, 35+i*10)) self.props_textItem_value_list[i].setDefaultTextColor (QColor (Qt.white).light (255)) self.props_textItem_value_list[i].setFont (QFont ("Helvetica", 9, QFont.StyleNormal, False)) self.props_textItem_value_list[i].setTextWidth (55) receiver = lambda value: self.parent.listenToChangedPropsValues (prop_name, value) self.helper.connect (self.props_textItem_value_list[i], SIGNAL ("textChanged(QString)"), receiver) def getProps (self): tmp_list = [] l = len (self.props_list) for i in range (0, l): tmp_list[i] = [self.props_list[i].toPlainText(), self.props_textItem_value_list[i].toPlainText()] return tmp_list def setTitle (self, title): self._nodename.setPlainText (title) def setCanvasHeightInUnits (self, ch): self._canvas_height = ch # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def moveDown (self, canvas_height_in_units): self.anim_active = True self.upwards_flag = False self.canvas_height = canvas_height_in_units self.timeline.stop () self.anim.setPosAt (0, QPointF(0, 1+(self.canvas_height+1)*self.FACTOR)) self.anim.setPosAt (1, QPointF(0, 1+(self.canvas_height+2)*self.FACTOR)) self.timeline.start () self.update () def moveUp (self, canvas_height_in_units): if self.anim_active == False: self.anim_active = True self.upwards_flag = True self.canvas_height = canvas_height_in_units self.timeline.stop () self.anim.setPosAt (0, QPointF(0, 1+(self.canvas_height+1)*self.FACTOR)) self.anim.setPosAt (1, QPointF(0, 1+(self.canvas_height) *self.FACTOR)) self.timeline.start () self.update () # this method double-checks whether the canvas needs to be further up as a # result of receiving other asynchronous "delete link" SIGNALs while moving up. def moveFurtherUp (self): self.anim_active = False if self.upwards_flag==True: if self.parent.getMaxLen() < self.canvas_height: self.moveUp (self.canvas_height-1) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def hoverEnterEvent (self, e): self._nodename.setToolTip (self._nodename.toPlainText ()) QGraphicsItem.hoverEnterEvent (self, e) '''
class XNodeConnection( QGraphicsPathItem ): """ Defines the base graphics item class that is used to draw a connection between two nodes. """ def __init__( self, scene ): self._visible = True super(XNodeConnection, self).__init__() # define custom properties self._textItem = None self._polygons = [] self._style = XConnectionStyle.Linear self._padding = 20 self._squashThreshold = 2 * scene.cellWidth() self._showDirectionArrow = False self._highlightPen = QPen(QColor('yellow')) self._disabledPen = QPen(QColor(100, 100, 100)) self._disableWithLayer = False self._enabled = True self._dirty = True self._customData = {} self._layer = None self._font = QApplication.instance().font() self._text = '' self._inputNode = None self._inputFixedY = None self._inputFixedX = None self._inputPoint = QPointF() self._inputLocation = XConnectionLocation.Left self._autoCalculateInputLocation = False self._showInputArrow = False self._outputNode = None self._outputFixedX = None self._outputFixedY = None self._outputPoint = QPointF() self._outputLocation = XConnectionLocation.Right self._autoCalculateOutputLocation = False self._showOutputArrow = False # set standard properties self.setFlags( self.ItemIsSelectable ) self.setZValue(-1) self.setPen( QColor('white') ) self.setLayer( scene.currentLayer() ) def autoCalculateInputLocation( self ): """ :remarks Returns whether or not to auto calculate the input location based on the proximity to the output node or point. :return <bool> """ return self._autoCalculateInputLocation def autoCalculateOutputLocation( self ): """ :remarks Returns whether or not to auto calculate the input location based on the proximity to the output node or point. :return <bool> """ return self._autoCalculateOutputLocation def connectSignals( self, node ): """ :remarks Connects to signals of the inputed node, if the node is a valid XNode type. :param node <XNode> || None :return <bool> success """ from projexui.widgets.xnodewidget.xnode import XNode # make sure we're connecting to a valid node if ( not isinstance(node, XNode) ): return False node.dispatch.geometryChanged.connect( self.setDirty ) node.dispatch.visibilityChanged.connect(self.setDirty) node.dispatch.removed.connect( self.forceRemove ) return True def controlPoints( self ): """ :remarks Generates the control points for this path :return <list> [ <tuple> ( <float> x, <float> y), .. ] """ # calculate the positions outputPoint = self.outputPoint() inputPoint = self.inputPoint() points = [] x0 = outputPoint.x() y0 = outputPoint.y() xN = inputPoint.x() yN = inputPoint.y() xC = (x0 + xN) / 2.0 yC = (y0 + yN) / 2.0 points.append((x0, y0)) oloc = self.outputLocation() iloc = self.inputLocation() left = XConnectionLocation.Left right = XConnectionLocation.Right bot = XConnectionLocation.Bottom top = XConnectionLocation.Top # create a right-to-left if ( (oloc & right) and (iloc & left) ): if ( xN < (x0 + self.squashThreshold()) ): points.append((x0+self.padding(), y0)) points.append((x0+self.padding(), yC)) points.append((xN-self.padding(), yC)) points.append((xN-self.padding(), yN)) else: points.append((xC, y0)) points.append((xC, yN)) # create a left-to-right elif ( (oloc & left) and (iloc & right) ): if ( (x0 - self.squashThreshold()) < xN ): points.append((x0-self.padding(), y0)) points.append((x0-self.padding(), yC)) points.append((xN+self.padding(), yC)) points.append((xN+self.padding(), yN)) else: points.append((xC, y0)) points.append((xC, yN)) # create a bottom-to-top elif ( (oloc & bot) and (iloc & top) ): if ( yN < (y0 + self.squashThreshold()) ): points.append((x0, y0+self.padding())) points.append((xC, y0+self.padding())) points.append((xC, yN-self.padding())) points.append((xN, yN-self.padding())) else: points.append((x0, yC)) points.append((xN, yC)) # create a top-to-bottom elif ( (oloc & top) and (iloc & bot) ): if ( (y0 - self.squashThreshold()) < yN ): points.append((x0, y0-self.padding())) points.append((xC, y0-self.padding())) points.append((xC, yN+self.padding())) points.append((xN, yN+self.padding())) else: points.append((x0, yC)) points.append((xN, yC)) # create a left-to-left elif ( (oloc & left) and (iloc & left) ): xMin = min(x0-self.padding(), xN-self.padding()) points.append((xMin, y0)) points.append((xMin, yN)) # create a right-to-right elif ( (oloc & right) and (iloc & right) ): xMax = max(x0+self.padding(), xN+self.padding()) points.append((xMax, y0)) points.append((xMax, yN)) # create a bottom-to-bottom elif ( (oloc & top) and (iloc & top) ): yMin = min(y0-self.padding(), yN-self.padding()) points.append((x0, yMin)) points.append((xN, yMin)) # create a bottom-to-bottom elif ( (oloc & bot) and (iloc & bot) ): yMax = max(y0+self.padding(), yN+self.padding()) points.append((x0, yMax)) points.append((xN, yMax)) # create a bottom-to-left or left-to-bottom elif ( ((oloc & bot) and (iloc & left)) or ((oloc & left) and (iloc & bot)) ): points.append((x0, yN)) # create a bottom-to-right or right-to-bottom elif ( ((oloc & bot) and (iloc & right)) or ((oloc & right) and (iloc & bot)) ): points.append((x0, y0)) # create a top-to-left or left-to-top elif ( ((oloc & top) and (iloc & left)) or ((oloc & left) and (iloc & top)) ): points.append((xN, yN)) # create a top-to-right or right-to-top elif ( ((oloc & top) and (iloc & right)) or ((oloc & right) and (iloc & top)) ): points.append((xN, y0)) points.append((xN, yN)) return points def customData( self, key, default = None ): """ Returns custom defined data that can be tracked per connection. :param key <str> :param default <variant> :return <variant> """ return self._customData.get(str(key), default) def direction( self ): """ Returns the output-to-input direction as a tuple of the output \ and input locations. :return (<XConnectionLocation> output, <XConnectionLocation> input) """ return (self.outputLocation(), self.inputLocation()) def disabledPen( self ): """ Returns the pen that should be used when rendering a disabled \ connection. :return <QPen> """ return self._disabledPen def disableWithLayer( self ): """ Returns whether or not this connection's enabled state should be \ affected by its layer. :return <bool> """ return self._disableWithLayer def disconnectSignals( self, node ): """ Disconnects from signals of the inputed node, if the node is a \ valid XNode type. :param node <XNode> || None :return <bool> success """ from projexui.widgets.xnodewidget.xnode import XNode # make sure we're disconnecting from a valid node if ( not isinstance(node, XNode) ): return False node.dispatch.geometryChanged.disconnect( self.setDirty ) node.dispatch.removed.disconnect( self.forceRemove ) return True def forceRemove( self ): """ Removes the object from the scene by queuing it up for removal. """ scene = self.scene() if ( scene ): scene.forceRemove(self) def font( self ): """ Returns the font for this connection. :return <QFont> """ return self._font def hasCustomData( self, key ): """ Returns whether or not there is the given key in the custom data. :param key | <str> :return <bool> """ return str(key) in self._customData def highlightPen( self ): """ Return the highlight pen for this connection. :return <QPen> """ return self._highlightPen def inputLocation( self ): """ Returns the input location for this connection. :return <XConnectionLocation> """ if ( not self.autoCalculateInputLocation() ): return self._inputLocation # auto calculate directions based on the scene if ( self._outputNode ): outputRect = self._outputNode.sceneRect() else: y = self._outputPoint.y() outputRect = QRectF( self._outputPoint.x(), y, 0, 0 ) if ( self._inputNode ): inputRect = self._inputNode.sceneRect() else: y = self._inputPoint.y() inputRect = QRectF( self._inputPoint.x(), y, 0, 0 ) # use the input location as potential places where it can be iloc = self._inputLocation left = XConnectionLocation.Left right = XConnectionLocation.Right top = XConnectionLocation.Top bot = XConnectionLocation.Bottom if ( self._inputNode == self._outputNode ): if ( iloc & right ): return right elif ( iloc & left ): return left elif ( iloc & top ): return top else: return bot elif ( (iloc & left) and outputRect.right() < inputRect.left() ): return left elif ( (iloc & right) and inputRect.right() < outputRect.left() ): return right elif ( (iloc & top) and outputRect.bottom() < inputRect.top() ): return top elif ( (iloc & bot) ): return bot elif ( (iloc & left) ): return left elif ( (iloc & right) ): return right elif ( (iloc & top) ): return top else: return left def inputNode( self ): """ Returns the input node that is connected to this connection. :return <XNode> """ return self._inputNode def inputFixedX( self ): """ Returns the fixed X value for the input option :return <float> || None """ return self._inputFixedX def inputFixedY( self ): """ Returns the fixed Y value for the input option. :return <float> || None """ return self._inputFixedY def inputPoint( self ): """ Returns a scene space point that the connection \ will draw to as its input target. If the connection \ has a node defined, then it will calculate the input \ point based on the position of the node, factoring in \ preference for input location and fixed information. \ If there is no node connected, then the point defined \ using the setInputPoint method will be used. :return <QPointF> """ node = self.inputNode() # return the set point if ( not node ): return self._inputPoint # otherwise, calculate the point based on location and fixed info ilocation = self.inputLocation() ifixedx = self.inputFixedX() ifixedy = self.inputFixedY() return node.positionAt( ilocation, ifixedx, ifixedy ) def isDirection( self, outputLocation, inputLocation ): """ Checks to see if the output and input locations match the settings \ for this item. :param outputLocation | <XConnectionLocation> :param inputLocation | <XConnectionLocation> :return <bool> """ return (self.isOutputLocation(outputLocation) and self.isInputLocation(inputLocation)) def isDirty( self ): """ Returns whether or not this path object is dirty and needs to \ be rebuilt. :return <bool> """ return self._dirty def isEnabled( self ): """ Returns whether or not this connection is enabled. :sa disableWithLayer :return <bool> """ if self._disableWithLayer and self._layer: lenabled = self._layer.isEnabled() elif self._inputNode and self._outputNode: lenabled = self._inputNode.isEnabled() and self._outputNode.isEnabled() else: lenabled = True return self._enabled and lenabled def isInputLocation( self, location ): """ Returns whether or not the inputed location value matches the \ given input location. :param location | <XConnectionLocation> :return <bool> """ return (self.inputLocation() & location) != 0 def isOutputLocation( self, location ): """ Returns whether or not the inputed location value matches the \ given output location. :param location | <XConnectionLocation> :return <bool> """ return (self.outputLocation() & location) != 0 def isStyle( self, style ): """ Return whether or not the connection is set to a particular style. :param style | <XConnectionStyle> :return <bool> """ return (self._style & style) != 0 def isVisible( self ): """ Returns whether or not this connection is visible. If either node it is connected to is hidden, then it should be as well. :return <bool> """ in_node = self.inputNode() out_node = self.outputNode() if ( in_node and not in_node.isVisible() ): return False if ( out_node and not out_node.isVisible() ): return False return self._visible def layer( self ): """ Returns the layer that this node is assigned to. :return <XNodeLayer> || None """ return self._layer def mappedPolygon( self, polygon, path = None, percent = 0.5 ): """ Maps the inputed polygon to the inputed path \ used when drawing items along the path. If no \ specific path is supplied, then this object's own \ path will be used. It will rotate and move the \ polygon according to the inputed percentage. :param polygon <QPolygonF> :param path <QPainterPath> :param percent <float> :return <QPolygonF> mapped_poly """ translatePerc = percent anglePerc = percent # we don't want to allow the angle percentage greater than 0.85 # or less than 0.05 or we won't get a good rotation angle if ( 0.95 <= anglePerc ): anglePerc = 0.98 elif ( anglePerc <= 0.05 ): anglePerc = 0.05 if ( not path ): path = self.path() if ( not (path and path.length()) ): return QPolygonF() # transform the polygon to the path point = path.pointAtPercent(translatePerc) angle = path.angleAtPercent(anglePerc) # rotate about the 0 axis transform = QTransform().rotate(-angle) polygon = transform.map(polygon) # move to the translation point transform = QTransform().translate(point.x(), point.y()) # create the rotated polygon mapped_poly = transform.map(polygon) self._polygons.append(mapped_poly) return mapped_poly def mousePressEvent( self, event ): """ Overloads the mouse press event to handle special cases and \ bypass when the scene is in view mode. :param event <QMousePressEvent> """ # ignore events when the scene is in view mode scene = self.scene() if ( scene and scene.inViewMode() ): event.ignore() return # block the selection signals if ( scene ): scene.blockSelectionSignals(True) # clear the selection if ( not (self.isSelected() or event.modifiers() == Qt.ControlModifier) ): for item in scene.selectedItems(): if ( item != self ): item.setSelected(False) # try to start the connection super(XNodeConnection, self).mousePressEvent(event) def mouseMoveEvent( self, event ): """ Overloads the mouse move event to ignore the event when \ the scene is in view mode. :param event <QMouseMoveEvent> """ # ignore events when the scene is in view mode scene = self.scene() if ( scene and (scene.inViewMode() or scene.isConnecting()) ): event.ignore() return # call the base method super(XNodeConnection, self).mouseMoveEvent(event) def mouseReleaseEvent( self, event ): """ Overloads the mouse release event to ignore the event when the \ scene is in view mode, and release the selection block signal. :param event <QMouseReleaseEvent> """ # ignore events when the scene is in view mode scene = self.scene() if ( scene and (scene.inViewMode() or scene.isConnecting()) ): event.ignore() return # emit the scene's connection menu requested signal if # the button was a right mouse button if ( event.button() == Qt.RightButton and scene ): scene.emitConnectionMenuRequested(self) event.accept() else: super(XNodeConnection, self).mouseReleaseEvent(event) # unblock the selection signals if ( scene ): scene.blockSelectionSignals(False) def opacity( self ): """ Returns the opacity amount for this connection. :return <float> """ in_node = self.inputNode() out_node = self.outputNode() if ( in_node and out_node and \ (in_node.isIsolateHidden() or out_node.isIsolateHidden()) ): return 0.1 opacity = super(XNodeConnection, self).opacity() layer = self.layer() if ( layer ): return layer.opacity() * opacity return opacity def outputLocation( self ): """ Returns the location for the output source position. :return <XConnectionLocation> """ if ( not self.autoCalculateOutputLocation() ): return self._outputLocation # auto calculate directions based on the scene if ( self._outputNode ): outputRect = self._outputNode.sceneRect() else: y = self._outputPoint.y() outputRect = QRectF( self._outputPoint.x(), y, 0, 0 ) if ( self._inputNode ): inputRect = self._inputNode.sceneRect() else: y = self._inputPoint.y() inputRect = QRectF( self._inputPoint.x(), y, 0, 0 ) oloc = self._outputLocation left = XConnectionLocation.Left right = XConnectionLocation.Right top = XConnectionLocation.Top bot = XConnectionLocation.Bottom if ( self._inputNode == self._outputNode ): if ( oloc & right ): return right elif ( oloc & left ): return left elif ( oloc & top ): return top else: return bot elif ( (oloc & right) and outputRect.right() < inputRect.left() ): return right elif ( (oloc & left) and inputRect.right() < outputRect.left() ): return left elif ( (oloc & bot) and outputRect.bottom() < inputRect.top() ): return bot elif ( (oloc & top) ): return top elif ( (oloc & right) ): return right elif ( (oloc & left) ): return left elif ( (oloc & bot) ): return bot else: return right def outputNode( self ): """ Returns the output source node that this connection is currently \ connected to. :return <XNode> || None """ return self._outputNode def outputFixedX( self ): """ Returns the fixed X position for the output component of this \ connection. :return <float> || None """ return self._outputFixedX def outputFixedY( self ): """ Returns the fixed Y position for the output component of this \ connection. :return <float> || None """ return self._outputFixedY def outputPoint( self ): """ Returns a scene space point that the connection \ will draw to as its output source. If the connection \ has a node defined, then it will calculate the output \ point based on the position of the node, factoring in \ preference for output location and fixed positions. If \ there is no node connected, then the point defined using \ the setOutputPoint method will be used. :return <QPointF> """ node = self.outputNode() # return the set point if ( not node ): return self._outputPoint # otherwise, calculate the point based on location and fixed positions olocation = self.outputLocation() ofixedx = self.outputFixedX() ofixedy = self.outputFixedY() return node.positionAt( olocation, ofixedx, ofixedy ) def padding( self ): """ Returns the amount of padding to be used when drawing a connection \ that will be drawn backwards. :return <float> """ return self._padding def paint( self, painter, option, widget ): """ Overloads the paint method from QGraphicsPathItem to \ handle custom drawing of the path using this items \ pens and polygons. :param painter <QPainter> :param option <QGraphicsItemStyleOption> :param widget <QWidget> """ # following the arguments required by Qt # pylint: disable-msg=W0613 painter.setOpacity(self.opacity()) # show the connection selected if ( not self.isEnabled() ): pen = QPen(self.disabledPen()) elif ( self.isSelected() ): pen = QPen(self.highlightPen()) else: pen = QPen(self.pen()) if ( self._textItem ): self._textItem.setOpacity(self.opacity()) self._textItem.setDefaultTextColor(pen.color().darker(110)) # rebuild first if necessary if ( self.isDirty() ): self.setPath(self.rebuild()) # store the initial hint hint = painter.renderHints() painter.setRenderHint( painter.Antialiasing ) pen.setWidthF(1.25) painter.setPen(pen) painter.drawPath(self.path()) # redraw the polys to force-fill them for poly in self._polygons: if ( not poly.isClosed() ): continue painter.setBrush(pen.color()) painter.drawPolygon(poly) # restore the render hints painter.setRenderHints(hint) def prepareToRemove( self ): """ Handles any code that needs to run to cleanup the connection \ before it gets removed from the scene. :return <bool> success """ # disconnect the signals from the input and output nodes for node in (self._outputNode, self._inputNode): self.disconnectSignals(node) # clear the pointers to the nodes self._inputNode = None self._outputNode = None return True def rebuild( self ): """ Rebuilds the path for this connection based on the given connection \ style parameters that have been set. :return <QPainterPath> """ # create the path path = self.rebuildPath() self._polygons = self.rebuildPolygons(path) if ( self._textItem ): point = path.pointAtPercent(0.5) metrics = QFontMetrics(self._textItem.font()) point.setY(point.y() - metrics.height() / 2.0) self._textItem.setPos(point) # create the path for the item for poly in self._polygons: path.addPolygon(poly) # unmark as dirty self.setDirty(False) return path def rebuildPath( self ): """ Rebuilds the path for the given style options based on the currently \ set parameters. :return <QPainterPath> """ # rebuild linear style if ( self.isStyle( XConnectionStyle.Linear ) ): return self.rebuildLinear() # rebuild block style elif ( self.isStyle( XConnectionStyle.Block ) ): return self.rebuildBlock() # rebuild smooth style elif ( self.isStyle( XConnectionStyle.Smooth ) ): return self.rebuildSmooth() # otherwise, we have an invalid style, or a style # defined by a subclass else: return QPainterPath() def rebuildPolygons( self, path ): """ Rebuilds the polygons that will be used on this path. :param path | <QPainterPath> :return <list> [ <QPolygonF>, .. ] """ output = [] # create the input arrow if ( self.showInputArrow() ): a = QPointF(-10, -3) b = QPointF(0, 0) c = QPointF(-10, 3) mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 1.0 ) output.append( mpoly ) # create the direction arrow if ( self.showDirectionArrow() ): a = QPointF(-5, -3) b = QPointF(5, 0) c = QPointF(-5, 3) mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0.5 ) output.append( mpoly ) # create the output arrow if ( self.showOutputArrow() ): a = QPointF(10, -3) b = QPointF(0, 0) c = QPointF(10, 3) mpoly = self.mappedPolygon(QPolygonF([a, b, c, a]), path, 0 ) output.append( mpoly ) return output def rebuildLinear( self ): """ Rebuilds a linear path from the output to input points. :return <QPainterPath> """ points = self.controlPoints() x0, y0 = points[0] xN, yN = points[-1] # create a simple line between the output and # input points path = QPainterPath() path.moveTo(x0, y0) path.lineTo(xN, yN) return path def rebuildBlock( self ): """ Rebuilds a blocked path from the output to input points. :return <QPainterPath> """ # collect the control points points = self.controlPoints() # create the path path = QPainterPath() for i, point in enumerate(points): if ( not i ): path.moveTo( point[0], point[1] ) else: path.lineTo( point[0], point[1] ) return path def rebuildSmooth( self ): """ Rebuilds a smooth path based on the inputed points and set \ parameters for this item. :return <QPainterPath> """ # collect the control points points = self.controlPoints() # create the path path = QPainterPath() if ( len(points) == 3 ): x0, y0 = points[0] x1, y1 = points[1] xN, yN = points[2] path.moveTo(x0, y0) path.quadTo(x1, y1, xN, yN) elif ( len(points) == 4 ): x0, y0 = points[0] x1, y1 = points[1] x2, y2 = points[2] xN, yN = points[3] path.moveTo(x0, y0) path.cubicTo(x1, y1, x2, y2, xN, yN) elif ( len(points) == 6 ): x0, y0 = points[0] x1, y1 = points[1] x2, y2 = points[2] x3, y3 = points[3] x4, y4 = points[4] xN, yN = points[5] xC = (x2+x3) / 2.0 yC = (y2+y3) / 2.0 path.moveTo(x0, y0) path.cubicTo(x1, y1, x2, y2, xC, yC) path.cubicTo(x3, y3, x4, y4, xN, yN) else: x0, y0 = points[0] xN, yN = points[-1] path.moveTo(x0, y0) path.lineTo(xN, yN) return path def refreshVisible( self ): """ Refreshes whether or not this node should be visible based on its current visible state. """ super(XNodeConnection, self).setVisible(self.isVisible()) def setAutoCalculateInputLocation( self, state = True ): """ Sets whether or not to auto calculate the input location based on \ the proximity to the output node or point. :param state | <bool> """ self._autoCalculateInputLocation = state self.setDirty() def setAutoCalculateOutputLocation( self, state = True ): """ Sets whether or not to auto calculate the input location based on \ the proximity to the output node or point. :param state | <bool> """ self._autoCalculateOutputLocation = state self.setDirty() def setCustomData( self, key, value ): """ Stores the inputed value as custom data on this connection for \ the given key. :param key | <str> :param value | <variant> """ self._customData[str(key)] = value def setDirection( self, outputLocation, inputLocation ): """ Sets the output-to-input direction by setting both the locations \ at the same time. :param outputLocation | <XConnectionLocation> :param inputLocation | <XConnectionLocation> """ self.setOutputLocation(outputLocation) self.setInputLocation(inputLocation) def setDirty( self, state = True ): """ Flags the connection as being dirty and needing a rebuild. :param state | <bool> """ self._dirty = state # set if this connection should be visible if ( self._inputNode and self._outputNode ): vis = self._inputNode.isVisible() and self._outputNode.isVisible() self.setVisible(vis) def setDisabledPen( self, pen ): """ Sets the disabled pen that will be used when rendering a connection \ in a disabled state. :param pen | <QPen> """ self._disabledPen = QPen(pen) def setDisableWithLayer( self, state ): """ Sets whether or not this connection's layer's current state should \ affect its enabled state. :param state | <bool> """ self._disableWithLayer = state self.setDirty() def setEnabled( self, state ): """ Sets whether or not this connection is enabled or not. :param state | <bool> """ self._enabled = state def setFont( self, font ): """ Sets the font for this connection to the inputed font. :param font | <QFont> """ self._font = font def setHighlightPen( self, pen ): """ Sets the pen to be used when highlighting a selected connection. :param pen | <QPen> || <QColor> """ self._highlightPen = QPen(pen) def setInputLocation( self, location ): """ Sets the input location for where this connection should point to. :param location | <XConnectionLocation> """ self._inputLocation = location self.setDirty() def setInputNode( self, node ): """ Sets the node that will be recieving this connection as an input. :param node | <XNode> """ # if the node already matches the current input node, ignore if ( self._inputNode == node ): return # disconnect from the existing node self.disconnectSignals( self._inputNode ) # store the node self._inputNode = node # connect to the new node self.connectSignals( self._inputNode ) # force the rebuilding of the path self.setPath(self.rebuild()) def setInputFixedX( self, x ): """ Sets the fixed x position for the input component of this connection. :param x | <float> || None """ self._inputFixedX = x self.setDirty() def setInputFixedY( self, y ): """ Sets the fixed y position for the input component of this connection. :param y | <float> || None """ self._inputFixedY = y self.setDirty() def setInputPoint( self, point ): """ Sets the scene level input point position to draw the connection to. \ This is used mainly by the scene when drawing a user connection - \ it will only be used when there is no connected input node. :param point | <QPointF> """ self._inputPoint = point self.setPath(self.rebuild()) def setLayer( self, layer ): """ Sets the layer that this node is associated with to the given layer. :param layer | <XNodeLayer> || None :return <bool> changed """ if ( layer == self._layer ): return False self._layer = layer self.syncLayerData() return True def setOutputLocation( self, location ): """ Sets the location for the output part of the connection to generate \ from. :param location | <XConnectionLocation> """ self._outputLocation = location self.setDirty() def setOutputNode( self, node ): """ Sets the node that will be generating the output information for \ this connection. :param node | <XNode> """ # if the output node matches the current, ignore if ( node == self._outputNode ): return # disconnect from an existing node self.disconnectSignals( self._outputNode ) # set the current node self._outputNode = node self.connectSignals( self._outputNode ) # force the rebuilding of the path self.setPath( self.rebuild() ) def setOutputFixedX( self, x ): """ Sets the fixed x position for the output component of this connection. :param x | <float> || None """ self._outputFixedX = x self.setDirty() def setOutputFixedY( self, y ): """ Sets the fixed y position for the output component of this connection. :param y | <float> || None """ self._outputFixedY = y self.setDirty() def setOutputPoint( self, point ): """ Sets the scene space point for where this connection should draw \ its output from. This value will only be used if no output \ node is defined. :param point | <QPointF> """ self._outputPoint = point self.setPath( self.rebuild() ) def setPadding( self, padding ): """ Sets the padding amount that will be used when drawing a connection \ whose points will overlap. :param padding | <float> """ self._padding = padding self.setDirty() def setShowDirectionArrow( self, state = True ): """ Marks whether or not an arrow in the center of the path should be \ drawn, showing the direction that the connection is flowing in. :param state | <bool> """ self._showDirectionArrow = state self.setDirty() def setShowInputArrow( self, state = True ): """ :remarks Marks whether or not an arrow should be shown pointing at the input node. :param state <bool> """ self._showInputArrow = state self.setDirty() def setShowOutputArrow( self, state = True ): """ :remarks Marks whether or not an arrow should be shown pointing at the output node. :param state <bool> """ self._showOutputArrow = state self.setDirty() def setSquashThreshold( self, amount ): """ :remarks Sets the threshold limit of when the connection should start 'squashing', calculated based on the distance between the input and output points when rebuilding. :param amount <float> """ self._squashThreshold = amount self.setDirty() def setStyle( self, style ): """ :remarks Sets the style of the connection that will be used. :param style <XConnectionStyle> """ self._style = style self.setDirty() self.update() def setVisible( self, state ): """ Sets whether or not this connection's local visibility should be on. :param state | ,bool> """ self._visible = state super(XNodeConnection, self).setVisible(self.isVisible()) def setZValue( self, value ): """ Sets the z value for this connection, also updating the text item to match the value if one is defined. :param value | <int> """ super(XNodeConnection, self).setZValue(value) if ( self._textItem ): self._textItem.setZValue(value) def showDirectionArrow( self ): """ :remarks Return whether or not the direction arrow is visible for this connection. :return <bool> """ return self._showDirectionArrow def showInputArrow( self ): """ :remarks Return whether or not the input arrow is visible for this connection. :return <bool> """ return self._showInputArrow def showOutputArrow( self ): """ :remarks Return whether or not the output arrow is visible for this connection. :return <bool> """ return self._showOutputArrow def squashThreshold( self ): """ :remarks Returns the sqash threshold for when the line should be squashed based on the input and output points becoming too close together. :return <float> """ return self._squashThreshold def setText( self, text ): """ Sets the text for this connection to the inputed text. :param text | <str> """ self._text = text if ( text ): if ( self._textItem is None ): self._textItem = QGraphicsTextItem() self._textItem.setParentItem(self) self._textItem.setPlainText(text) elif ( self._textItem ): self.scene().removeItem(self._textItem) self._textItem = None def style( self ): """ :remarks Returns the style of the connection that is being drawn. :return style <XConnectionStyle> """ return self._style def syncLayerData( self, layerData = None ): """ Syncs the layer information for this item from the given layer data. :param layerData | <dict> """ if ( not self._layer ): return if ( not layerData ): layerData = self._layer.layerData() self.setVisible( layerData.get('visible', True) ) if ( layerData.get('current') ): # set the default parameters self.setFlags( self.ItemIsSelectable ) self.setAcceptHoverEvents(True) self.setZValue(99) else: # set the default parameters self.setFlags( self.GraphicsItemFlags(0) ) self.setAcceptHoverEvents(True) self.setZValue(layerData.get('zValue', 0)-1) def text( self ): """ Returns the text for this connection. :return <str> """ return self._text
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): 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 self.__title = "" self.__processingState = 0 self.__progress = -1 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 = QGraphicsTextItem(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) # 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(unicode, 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: 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 setProgressMessage(self, message): """ Set the node work progress message. .. note:: Not yet implemented """ pass 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. return self.childrenBoundingRect() 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. """ title_safe = escape(self.title()) if self.progress() > 0: text = '<div align="center">%s<br/>%i%%</div>' % \ (title_safe, int(self.progress())) else: text = '<div align="center">%s</div>' % \ (title_safe) # The NodeItems boundingRect could change. self.prepareGeometryChange() 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: self.shapeItem.setSelected(value.toBool()) elif change == QGraphicsItem.ItemPositionHasChanged: self.positionChanged.emit() return QGraphicsObject.itemChange(self, change, value)