source: orange/Orange/OrangeCanvas/canvas/items/linkitem.py @ 11181:a60586c66457

Revision 11181:a60586c66457, 10.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Added channel names to the LinkItems.

Line 
1"""
2Link Item
3
4"""
5
6from PyQt4.QtGui import (
7    QGraphicsItem, QGraphicsEllipseItem, QGraphicsPathItem, QGraphicsObject,
8    QGraphicsTextItem, QGraphicsDropShadowEffect, QPen, QBrush, QColor,
9    QPainterPath, QFont, QTransform
10)
11
12from PyQt4.QtCore import Qt, QPointF
13
14from .nodeitem import SHADOW_COLOR
15
16
17class LinkCurveItem(QGraphicsPathItem):
18    """Link curve item. The main component of `LinkItem`.
19    """
20    def __init__(self, parent):
21        QGraphicsPathItem.__init__(self, parent)
22        assert(isinstance(parent, LinkItem))
23        self.__canvasLink = parent
24        self.setAcceptHoverEvents(True)
25        self.setAcceptedMouseButtons(Qt.RightButton)
26        self.setAcceptedMouseButtons(Qt.LeftButton)
27
28        self.shadow = QGraphicsDropShadowEffect(
29            blurRadius=5, color=QColor(SHADOW_COLOR),
30            offset=QPointF(0, 0)
31        )
32
33        self.normalPen = QPen(QBrush(QColor("#9CACB4")), 2.0)
34        self.hoverPen = QPen(QBrush(QColor("#7D7D7D")), 2.1)
35        self.setPen(self.normalPen)
36        self.setGraphicsEffect(self.shadow)
37        self.shadow.setEnabled(False)
38
39        self.__hover = False
40
41    def linkItem(self):
42        """Return the :class:`LinkItem` instance this curve belongs to.
43
44        """
45        return self.__canvasLink
46
47    def setHoverState(self, state):
48        self.__hover = state
49        self.__update()
50
51    def itemChange(self, change, value):
52        if change == QGraphicsItem.ItemEnabledHasChanged:
53            # Update the pen style
54            self.__update()
55
56        return QGraphicsPathItem.itemChange(self, change, value)
57
58    def __update(self):
59        shadow_enabled = self.__hover
60        if self.shadow.isEnabled() != shadow_enabled:
61            self.shadow.setEnabled(shadow_enabled)
62
63        link_enabled = self.isEnabled()
64        if link_enabled:
65            pen_style = Qt.SolidLine
66        else:
67            pen_style = Qt.DashLine
68
69        if self.__hover:
70            pen = self.hoverPen
71        else:
72            pen = self.normalPen
73
74        pen.setStyle(pen_style)
75        self.setPen(pen)
76
77
78class LinkAnchorIndicator(QGraphicsEllipseItem):
79    """A visual indicator of the link anchor point at both ends
80    of the `LinkItem`.
81
82    """
83    def __init__(self, *args):
84        QGraphicsEllipseItem.__init__(self, *args)
85        self.setRect(-3, -3, 6, 6)
86        self.setPen(QPen(Qt.NoPen))
87        self.normalBrush = QBrush(QColor("#9CACB4"))
88        self.hoverBrush = QBrush(QColor("#7D7D7D"))
89        self.setBrush(self.normalBrush)
90        self.__hover = False
91
92    def setHoverState(self, state):
93        """The hover state is set by the LinkItem.
94        """
95        self.__hover = state
96        if state:
97            self.setBrush(self.hoverBrush)
98        else:
99            self.setBrush(self.normalBrush)
100
101
102class LinkItem(QGraphicsObject):
103    """A Link in the canvas.
104    """
105
106    Z_VALUE = 0
107    """Z value of the item"""
108
109    def __init__(self, *args):
110        QGraphicsObject.__init__(self, *args)
111        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
112        self.setAcceptedMouseButtons(Qt.RightButton)
113        self.setAcceptHoverEvents(True)
114
115        self.setZValue(self.Z_VALUE)
116
117        self.sourceItem = None
118        self.sourceAnchor = None
119        self.sinkItem = None
120        self.sinkAnchor = None
121        self.curveItem = LinkCurveItem(self)
122        self.sourceIndicator = LinkAnchorIndicator(self)
123        self.sinkIndicator = LinkAnchorIndicator(self)
124        self.sourceIndicator.hide()
125        self.sinkIndicator.hide()
126
127        self.linkTextItem = QGraphicsTextItem(self)
128        self.linkTextItem.setFont(QFont("Helvetica", 11))
129
130        self.__sourceName = ""
131        self.__sinkName = ""
132
133        self.hover = False
134
135    def setSourceItem(self, item, anchor=None):
136        """Set the source `item` (:class:`CanvasNodeItem`). Use `anchor`
137        (:class:`AnchorPoint) as the curve start point (if `None` a new
138        output anchor will be created).
139
140        Setting item to `None` and a valid anchor is a valid operation
141        (for instance while mouse dragging one and of the link).
142
143        """
144        if item is not None and anchor is not None:
145            if anchor not in item.outputAnchors():
146                raise ValueError("Anchor must be belong to the item")
147
148        if self.sourceItem != item:
149            if self.sourceAnchor:
150                # Remove a previous source item and the corresponding anchor
151                self.sourceAnchor.scenePositionChanged.disconnect(
152                    self._sourcePosChanged
153                )
154
155                if self.sourceItem is not None:
156                    self.sourceItem.removeOutputAnchor(self.sourceAnchor)
157
158                self.sourceItem = self.sourceAnchor = None
159
160            self.sourceItem = item
161
162            if item is not None and anchor is None:
163                # Create a new output anchor for the item if none is provided.
164                anchor = item.newOutputAnchor()
165
166            # Update the visibility of the start point indicator.
167            self.sourceIndicator.setVisible(bool(item))
168
169        if anchor != self.sourceAnchor:
170            if self.sourceAnchor is not None:
171                self.sourceAnchor.scenePositionChanged.disconnect(
172                    self._sourcePosChanged
173                )
174
175            self.sourceAnchor = anchor
176
177            if self.sourceAnchor is not None:
178                self.sourceAnchor.scenePositionChanged.connect(
179                    self._sourcePosChanged
180                )
181
182        self.__updateCurve()
183
184    def setSinkItem(self, item, anchor=None):
185        """Set the sink `item` (:class:`CanvasNodeItem`). Use `anchor`
186        (:class:`AnchorPoint) as the curve end point (if `None` a new
187        input anchor will be created).
188
189        Setting item to `None` and a valid anchor is a valid operation
190        (for instance while mouse dragging one and of the link).
191        """
192        if item is not None and anchor is not None:
193            if anchor not in item.inputAnchors():
194                raise ValueError("Anchor must be belong to the item")
195
196        if self.sinkItem != item:
197            if self.sinkAnchor:
198                # Remove a previous source item and the corresponding anchor
199                self.sinkAnchor.scenePositionChanged.disconnect(
200                    self._sinkPosChanged
201                )
202
203                if self.sinkItem is not None:
204                    self.sinkItem.removeInputAnchor(self.sinkAnchor)
205
206                self.sinkItem = self.sinkAnchor = None
207
208            self.sinkItem = item
209
210            if item is not None and anchor is None:
211                # Create a new input anchor for the item if none is provided.
212                anchor = item.newInputAnchor()
213
214            # Update the visibility of the end point indicator.
215            self.sinkIndicator.setVisible(bool(item))
216
217        if self.sinkAnchor != anchor:
218            if self.sinkAnchor is not None:
219                self.sinkAnchor.scenePositionChanged.disconnect(
220                    self._sinkPosChanged
221                )
222
223            self.sinkAnchor = anchor
224
225            if self.sinkAnchor is not None:
226                self.sinkAnchor.scenePositionChanged.connect(
227                    self._sinkPosChanged
228                )
229
230        self.__updateCurve()
231
232    def setChannelNamesVisible(self, visible):
233        self.linkTextItem.setVisible(visible)
234
235    def setSourceName(self, name):
236        if self.__sourceName != name:
237            self.__sourceName = name
238            self.__updateText()
239
240    def sourceName(self):
241        return self.__sourceName
242
243    def setSinkName(self, name):
244        if self.__sinkName != name:
245            self.__sinkName = name
246            self.__updateText()
247
248    def sinkName(self):
249        return self.__sinkName
250
251    def _sinkPosChanged(self, *arg):
252        self.__updateCurve()
253
254    def _sourcePosChanged(self, *arg):
255        self.__updateCurve()
256
257    def __updateCurve(self):
258        self.prepareGeometryChange()
259        if self.sourceAnchor and self.sinkAnchor:
260            source_pos = self.sourceAnchor.anchorScenePos()
261            sink_pos = self.sinkAnchor.anchorScenePos()
262            source_pos = self.curveItem.mapFromScene(source_pos)
263            sink_pos = self.curveItem.mapFromScene(sink_pos)
264            # TODO: get the orthogonal angle to the anchors path.
265            path = QPainterPath()
266            path.moveTo(source_pos)
267            path.cubicTo(source_pos + QPointF(60, 0),
268                         sink_pos - QPointF(60, 0),
269                         sink_pos)
270
271            self.curveItem.setPath(path)
272            self.sourceIndicator.setPos(source_pos)
273            self.sinkIndicator.setPos(sink_pos)
274            self.__updateText()
275        else:
276            self.setHoverState(False)
277            self.curveItem.setPath(QPainterPath())
278
279    def __updateText(self):
280        if self.__sourceName or self.__sinkName:
281            text = "{0} --> {1}".format(self.__sourceName, self.__sinkName)
282        else:
283            text = ""
284        self.linkTextItem.setPlainText(text)
285
286        path = self.curveItem.path()
287        if not path.isEmpty():
288            center = path.pointAtPercent(0.5)
289            angle = path.angleAtPercent(0.5)
290
291            brect = self.linkTextItem.boundingRect()
292
293            transform = QTransform()
294            transform.translate(center.x(), center.y())
295            transform.rotate(-angle)
296
297            # Center and move above the curve path.
298            transform.translate(-brect.width() / 2, -brect.height())
299
300            self.linkTextItem.setTransform(transform)
301
302    def removeLink(self):
303        self.setSinkItem(None)
304        self.setSourceItem(None)
305        self.__updateCurve()
306
307    def setHoverState(self, state):
308        if self.hover != state:
309            self.hover = state
310            self.sinkIndicator.setHoverState(state)
311            self.sourceIndicator.setHoverState(state)
312            self.curveItem.setHoverState(state)
313
314    def hoverEnterEvent(self, event):
315        self.setHoverState(True)
316        return QGraphicsObject.hoverEnterEvent(self, event)
317
318    def hoverLeaveEvent(self, event):
319        self.setHoverState(False)
320        return QGraphicsObject.hoverLeaveEvent(self, event)
321
322    def boundingRect(self):
323        return self.childrenBoundingRect()
324
325    def shape(self):
326        return self.curveItem.shape()
327
328    def setEnabled(self, enabled):
329        QGraphicsObject.setEnabled(self, enabled)
Note: See TracBrowser for help on using the repository browser.