source: orange/Orange/OrangeCanvas/canvas/items/linkitem.py @ 11121:59ef4caed303

Revision 11121:59ef4caed303, 8.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Fixed LinkItem hover events handling.

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