source: orange/Orange/OrangeCanvas/canvas/items/linkitem.py @ 11102:1ae099099c23

Revision 11102:1ae099099c23, 8.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 19 months ago (diff)

Added GraphicsItems representing the items in the workflow scheme.

Line 
1"""
2Link Item
3
4"""
5
6from PyQt4.QtGui import (
7    QGraphicsItem, QGraphicsEllipseItem, QGraphicsPathItem, QGraphicsObject,
8    QGraphicsDropShadowEffect,
9    QPen, QBrush, QColor, QPainterPath
10)
11
12from PyQt4.QtCore import (
13    Qt, QPointF, QRectF
14)
15
16from .nodeitem import SHADOW_COLOR
17
18
19class LinkCurveItem(QGraphicsPathItem):
20    """Link curve item. The main component of `LinkItem`.
21    """
22    def __init__(self, parent):
23        QGraphicsPathItem.__init__(self, parent)
24        assert(isinstance(parent, LinkItem))
25        self.__canvasLink = parent
26        self.setAcceptHoverEvents(True)
27        self.setAcceptedMouseButtons(Qt.RightButton)
28        self.setAcceptedMouseButtons(Qt.LeftButton)
29
30        self.shadow = QGraphicsDropShadowEffect(
31            blurRadius=5, color=QColor(SHADOW_COLOR),
32            offset=QPointF(0, 0)
33        )
34
35        self.normalPen = QPen(QBrush(QColor("#9CACB4")), 2.0)
36        self.hoverPen = QPen(QBrush(QColor("#7D7D7D")), 2.1)
37        self.setPen(self.normalPen)
38        self.setGraphicsEffect(self.shadow)
39        self.shadow.setEnabled(False)
40
41        self.__hover = False
42
43    def hoverEnterEvent(self, event):
44        self.setHoverState(True)
45        return QGraphicsPathItem.hoverEnterEvent(self, event)
46
47    def hoverLeaveEvent(self, event):
48        self.setHoverState(False)
49        return QGraphicsPathItem.hoverLeaveEvent(self, event)
50
51    def setHoverState(self, state):
52        self.__hover = state
53        self._update()
54        self.__canvasLink.setHoverState(state)
55
56    def _update(self):
57        shadow_enabled = self.__hover
58        if self.shadow.isEnabled() != shadow_enabled:
59            self.shadow.setEnabled(shadow_enabled)
60
61        link_enabled = self.isEnabled()
62        if link_enabled:
63            pen_style = Qt.SolidLine
64        else:
65            pen_style = Qt.DashLine
66
67        if self.__hover:
68            pen = self.hoverPen
69        else:
70            pen = self.normalPen
71
72        pen.setStyle(pen_style)
73        self.setPen(pen)
74
75
76class LinkAnchorIndicator(QGraphicsEllipseItem):
77    """A visual indicator of the link anchor point at both ends
78    of the `LinkItem`.
79
80    """
81    def __init__(self, *args):
82        QGraphicsEllipseItem.__init__(self, *args)
83        self.setRect(-3, -3, 6, 6)
84        self.setPen(QPen(Qt.NoPen))
85        self.normalBrush = QBrush(QColor("#9CACB4"))
86        self.hoverBrush = QBrush(QColor("#7D7D7D"))
87        self.setBrush(self.normalBrush)
88        self.__hover = False
89
90    def setHoverState(self, state):
91        """The hover state is set by the LinkItem.
92        """
93        self.__hover = state
94        if state:
95            self.setBrush(self.hoverBrush)
96        else:
97            self.setBrush(self.normalBrush)
98
99
100class LinkItem(QGraphicsObject):
101    """A Link in the canvas.
102    """
103
104    Z_VALUE = 0
105    """Z value of the item"""
106
107    def __init__(self, *args):
108        QGraphicsObject.__init__(self, *args)
109        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
110        self.setAcceptedMouseButtons(Qt.RightButton)
111
112        self.setZValue(self.Z_VALUE)
113
114        self.sourceItem = None
115        self.sourceAnchor = None
116        self.sinkItem = None
117        self.sinkAnchor = None
118        self.curveItem = LinkCurveItem(self)
119        self.sourceIndicator = LinkAnchorIndicator(self)
120        self.sinkIndicator = LinkAnchorIndicator(self)
121        self.sourceIndicator.hide()
122        self.sinkIndicator.hide()
123
124        self.hover = False
125
126    def setSourceItem(self, item, anchor=None):
127        """Set the source `item` (:class:`CanvasNodeItem`). Use `anchor`
128        (:class:`AnchorPoint) as the curve start point (if `None` a new
129        output anchor will be created).
130
131        Setting item to `None` and a valid anchor is a valid operation
132        (for instance while mouse dragging one and of the link).
133
134        """
135        if item is not None and anchor is not None:
136            if anchor not in item.outputAnchors:
137                raise ValueError("Anchor must be belong to the item")
138
139        if self.sourceItem != item:
140            if self.sourceAnchor:
141                # Remove a previous source item and the corresponding anchor
142                self.sourceAnchor.scenePositionChanged.disconnect(
143                    self._sourcePosChanged
144                )
145
146                if self.sourceItem is not None:
147                    self.sourceItem.removeOutputAnchor(self.sourceAnchor)
148
149                self.sourceItem = self.sourceAnchor = None
150
151            self.sourceItem = item
152
153            if item is not None and anchor is None:
154                # Create a new output anchor for the item if none is provided.
155                anchor = item.newOutputAnchor()
156
157            # Update the visibility of the start point indicator.
158            self.sourceIndicator.setVisible(bool(item))
159
160        if anchor != self.sourceAnchor:
161            if self.sourceAnchor is not None:
162                self.sourceAnchor.scenePositionChanged.disconnect(
163                    self._sourcePosChanged
164                )
165
166            self.sourceAnchor = anchor
167
168            if self.sourceAnchor is not None:
169                self.sourceAnchor.scenePositionChanged.connect(
170                    self._sourcePosChanged
171                )
172
173        self.__updateCurve()
174
175    def setSinkItem(self, item, anchor=None):
176        """Set the sink `item` (:class:`CanvasNodeItem`). Use `anchor`
177        (:class:`AnchorPoint) as the curve end point (if `None` a new
178        input anchor will be created).
179
180        Setting item to `None` and a valid anchor is a valid operation
181        (for instance while mouse dragging one and of the link).
182        """
183        if item is not None and anchor is not None:
184            if anchor not in item.inputAnchors:
185                raise ValueError("Anchor must be belong to the item")
186
187        if self.sinkItem != item:
188            if self.sinkAnchor:
189                # Remove a previous source item and the corresponding anchor
190                self.sinkAnchor.scenePositionChanged.disconnect(
191                    self._sinkPosChanged
192                )
193
194                if self.sinkItem is not None:
195                    self.sinkItem.removeInputAnchor(self.sinkAnchor)
196
197                self.sinkItem = self.sinkAnchor = None
198
199            self.sinkItem = item
200
201            if item is not None and anchor is None:
202                # Create a new input anchor for the item if none is provided.
203                anchor = item.newInputAnchor()
204
205            # Update the visibility of the end point indicator.
206            self.sinkIndicator.setVisible(bool(item))
207
208        if self.sinkAnchor != anchor:
209            if self.sinkAnchor is not None:
210                self.sinkAnchor.scenePositionChanged.disconnect(
211                    self._sinkPosChanged
212                )
213
214            self.sinkAnchor = anchor
215
216            if self.sinkAnchor is not None:
217                self.sinkAnchor.scenePositionChanged.connect(
218                    self._sinkPosChanged
219                )
220
221        self.__updateCurve()
222
223    def _sinkPosChanged(self, *arg):
224        self.__updateCurve()
225
226    def _sourcePosChanged(self, *arg):
227        self.__updateCurve()
228
229    def __updateCurve(self):
230        if self.sourceAnchor and self.sinkAnchor:
231            source_pos = self.sourceAnchor.anchorScenePos()
232            sink_pos = self.sinkAnchor.anchorScenePos()
233            source_pos = self.curveItem.mapFromScene(source_pos)
234            sink_pos = self.curveItem.mapFromScene(sink_pos)
235            # TODO: get the orthogonal angle to the anchors path.
236            path = QPainterPath()
237            path.moveTo(source_pos)
238            path.cubicTo(source_pos + QPointF(60, 0),
239                         sink_pos - QPointF(60, 0),
240                         sink_pos)
241
242            self.curveItem.setPath(path)
243            self.sourceIndicator.setPos(source_pos)
244            self.sinkIndicator.setPos(sink_pos)
245        else:
246            self.setHoverState(False)
247            self.curveItem.setPath(QPainterPath())
248
249    def removeLink(self):
250        self.setSinkItem(None)
251        self.setSourceItem(None)
252        self.__updateCurve()
253
254    # TODO: This item should control the hover state not its child.
255    #       (use sceneEventFilter on curveItem??).
256    def setHoverState(self, state):
257        if self.hover != state:
258            self.hover = state
259            self.sinkIndicator.setHoverState(state)
260            self.sourceIndicator.setHoverState(state)
261
262    def boundingRect(self):
263        return QRectF()
264
265    def setEnabled(self, enabled):
266        QGraphicsObject.setEnabled(self, enabled)
267        self.curveItem._update()
Note: See TracBrowser for help on using the repository browser.