source: orange/Orange/OrangeCanvas/canvas/items/linkitem.py @ 11343:84f579a58442

Revision 11343:84f579a58442, 12.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Fixed font size in the canvas scene.

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, QEvent
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.setAcceptedMouseButtons(Qt.NoButton)
24        self.__canvasLink = parent
25        self.setAcceptHoverEvents(True)
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 setCurvePenSet(self, pen, hoverPen):
51        if pen is not None:
52            self.normalPen = pen
53        if hoverPen is not None:
54            self.hoverPen = hoverPen
55        self.__update()
56
57    def itemChange(self, change, value):
58        if change == QGraphicsItem.ItemEnabledHasChanged:
59            # Update the pen style
60            self.__update()
61
62        return QGraphicsPathItem.itemChange(self, change, value)
63
64    def __update(self):
65        shadow_enabled = self.__hover
66        if self.shadow.isEnabled() != shadow_enabled:
67            self.shadow.setEnabled(shadow_enabled)
68
69        link_enabled = self.isEnabled()
70        if link_enabled:
71            pen_style = Qt.SolidLine
72        else:
73            pen_style = Qt.DashLine
74
75        if self.__hover:
76            pen = self.hoverPen
77        else:
78            pen = self.normalPen
79
80        pen.setStyle(pen_style)
81        self.setPen(pen)
82
83
84class LinkAnchorIndicator(QGraphicsEllipseItem):
85    """A visual indicator of the link anchor point at both ends
86    of the `LinkItem`.
87
88    """
89    def __init__(self, *args):
90        QGraphicsEllipseItem.__init__(self, *args)
91        self.setRect(-3, -3, 6, 6)
92        self.setPen(QPen(Qt.NoPen))
93        self.normalBrush = QBrush(QColor("#9CACB4"))
94        self.hoverBrush = QBrush(QColor("#7D7D7D"))
95        self.setBrush(self.normalBrush)
96        self.__hover = False
97
98    def setHoverState(self, state):
99        """The hover state is set by the LinkItem.
100        """
101        self.__hover = state
102        if state:
103            self.setBrush(self.hoverBrush)
104        else:
105            self.setBrush(self.normalBrush)
106
107
108class LinkItem(QGraphicsObject):
109    """A Link in the canvas.
110    """
111
112    Z_VALUE = 0
113    """Z value of the item"""
114
115    def __init__(self, *args):
116        QGraphicsObject.__init__(self, *args)
117        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
118        self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton)
119        self.setAcceptHoverEvents(True)
120
121        self.setZValue(self.Z_VALUE)
122
123        self.sourceItem = None
124        self.sourceAnchor = None
125        self.sinkItem = None
126        self.sinkAnchor = None
127
128        self.curveItem = LinkCurveItem(self)
129
130        self.sourceIndicator = LinkAnchorIndicator(self)
131        self.sinkIndicator = LinkAnchorIndicator(self)
132        self.sourceIndicator.hide()
133        self.sinkIndicator.hide()
134
135        self.linkTextItem = QGraphicsTextItem(self)
136
137        self.__sourceName = ""
138        self.__sinkName = ""
139
140        self.__dynamic = False
141        self.__dynamicEnabled = False
142
143        self.hover = False
144
145    def setSourceItem(self, item, anchor=None):
146        """Set the source `item` (:class:`CanvasNodeItem`). Use `anchor`
147        (:class:`AnchorPoint) as the curve start point (if `None` a new
148        output anchor will be created).
149
150        Setting item to `None` and a valid anchor is a valid operation
151        (for instance while mouse dragging one and of the link).
152
153        """
154        if item is not None and anchor is not None:
155            if anchor not in item.outputAnchors():
156                raise ValueError("Anchor must be belong to the item")
157
158        if self.sourceItem != item:
159            if self.sourceAnchor:
160                # Remove a previous source item and the corresponding anchor
161                self.sourceAnchor.scenePositionChanged.disconnect(
162                    self._sourcePosChanged
163                )
164
165                if self.sourceItem is not None:
166                    self.sourceItem.removeOutputAnchor(self.sourceAnchor)
167
168                self.sourceItem = self.sourceAnchor = None
169
170            self.sourceItem = item
171
172            if item is not None and anchor is None:
173                # Create a new output anchor for the item if none is provided.
174                anchor = item.newOutputAnchor()
175
176            # Update the visibility of the start point indicator.
177            self.sourceIndicator.setVisible(bool(item))
178
179        if anchor != self.sourceAnchor:
180            if self.sourceAnchor is not None:
181                self.sourceAnchor.scenePositionChanged.disconnect(
182                    self._sourcePosChanged
183                )
184
185            self.sourceAnchor = anchor
186
187            if self.sourceAnchor is not None:
188                self.sourceAnchor.scenePositionChanged.connect(
189                    self._sourcePosChanged
190                )
191
192        self.__updateCurve()
193
194    def setSinkItem(self, item, anchor=None):
195        """Set the sink `item` (:class:`CanvasNodeItem`). Use `anchor`
196        (:class:`AnchorPoint) as the curve end point (if `None` a new
197        input anchor will be created).
198
199        Setting item to `None` and a valid anchor is a valid operation
200        (for instance while mouse dragging one and of the link).
201        """
202        if item is not None and anchor is not None:
203            if anchor not in item.inputAnchors():
204                raise ValueError("Anchor must be belong to the item")
205
206        if self.sinkItem != item:
207            if self.sinkAnchor:
208                # Remove a previous source item and the corresponding anchor
209                self.sinkAnchor.scenePositionChanged.disconnect(
210                    self._sinkPosChanged
211                )
212
213                if self.sinkItem is not None:
214                    self.sinkItem.removeInputAnchor(self.sinkAnchor)
215
216                self.sinkItem = self.sinkAnchor = None
217
218            self.sinkItem = item
219
220            if item is not None and anchor is None:
221                # Create a new input anchor for the item if none is provided.
222                anchor = item.newInputAnchor()
223
224            # Update the visibility of the end point indicator.
225            self.sinkIndicator.setVisible(bool(item))
226
227        if self.sinkAnchor != anchor:
228            if self.sinkAnchor is not None:
229                self.sinkAnchor.scenePositionChanged.disconnect(
230                    self._sinkPosChanged
231                )
232
233            self.sinkAnchor = anchor
234
235            if self.sinkAnchor is not None:
236                self.sinkAnchor.scenePositionChanged.connect(
237                    self._sinkPosChanged
238                )
239
240        self.__updateCurve()
241
242    def setFont(self, font):
243        """
244        Set the channel names font.
245        """
246        if font != self.font():
247            self.linkTextItem.setFont(font)
248            self.__updateText()
249
250    def font(self):
251        """
252        Return the channel names font.
253        """
254        return self.linkTextItem.font()
255
256    def setChannelNamesVisible(self, visible):
257        self.linkTextItem.setVisible(visible)
258
259    def setSourceName(self, name):
260        if self.__sourceName != name:
261            self.__sourceName = name
262            self.__updateText()
263
264    def sourceName(self):
265        return self.__sourceName
266
267    def setSinkName(self, name):
268        if self.__sinkName != name:
269            self.__sinkName = name
270            self.__updateText()
271
272    def sinkName(self):
273        return self.__sinkName
274
275    def _sinkPosChanged(self, *arg):
276        self.__updateCurve()
277
278    def _sourcePosChanged(self, *arg):
279        self.__updateCurve()
280
281    def __updateCurve(self):
282        self.prepareGeometryChange()
283        if self.sourceAnchor and self.sinkAnchor:
284            source_pos = self.sourceAnchor.anchorScenePos()
285            sink_pos = self.sinkAnchor.anchorScenePos()
286            source_pos = self.curveItem.mapFromScene(source_pos)
287            sink_pos = self.curveItem.mapFromScene(sink_pos)
288            # TODO: get the orthogonal angle to the anchors path.
289            path = QPainterPath()
290            path.moveTo(source_pos)
291            path.cubicTo(source_pos + QPointF(60, 0),
292                         sink_pos - QPointF(60, 0),
293                         sink_pos)
294
295            self.curveItem.setPath(path)
296            self.sourceIndicator.setPos(source_pos)
297            self.sinkIndicator.setPos(sink_pos)
298            self.__updateText()
299        else:
300            self.setHoverState(False)
301            self.curveItem.setPath(QPainterPath())
302
303    def __updateText(self):
304        self.prepareGeometryChange()
305
306        if self.__sourceName or self.__sinkName:
307            if self.__sourceName != self.__sinkName:
308                text = u"{0} \u2192 {1}".format(self.__sourceName,
309                                                self.__sinkName)
310            else:
311                # If the names are the same show only one.
312                # Is this right? If the sink has two input channels of the
313                # same type having the name on the link help elucidate
314                # the scheme.
315                text = self.__sourceName
316        else:
317            text = ""
318
319        self.linkTextItem.setPlainText(text)
320
321        path = self.curveItem.path()
322        if not path.isEmpty():
323            center = path.pointAtPercent(0.5)
324            angle = path.angleAtPercent(0.5)
325
326            brect = self.linkTextItem.boundingRect()
327
328            transform = QTransform()
329            transform.translate(center.x(), center.y())
330            transform.rotate(-angle)
331
332            # Center and move above the curve path.
333            transform.translate(-brect.width() / 2, -brect.height())
334
335            self.linkTextItem.setTransform(transform)
336
337    def removeLink(self):
338        self.setSinkItem(None)
339        self.setSourceItem(None)
340        self.__updateCurve()
341
342    def setHoverState(self, state):
343        if self.hover != state:
344            self.prepareGeometryChange()
345            self.hover = state
346            self.sinkIndicator.setHoverState(state)
347            self.sourceIndicator.setHoverState(state)
348            self.curveItem.setHoverState(state)
349
350    def hoverEnterEvent(self, event):
351        # Hover enter event happens when the mouse enters any child object
352        # but we only want to show the 'hovered' shadow when the mouse
353        # is over the 'curveItem', so we install self as an event filter
354        # on the LinkCurveItem and listen to its hover events.
355        self.curveItem.installSceneEventFilter(self)
356        return QGraphicsObject.hoverEnterEvent(self, event)
357
358    def hoverLeaveEvent(self, event):
359        # Remove the event filter to prevent unnecessary work in
360        # scene event filter when not needed
361        self.curveItem.removeSceneEventFilter(self)
362        return QGraphicsObject.hoverLeaveEvent(self, event)
363
364    def sceneEventFilter(self, obj, event):
365        if obj is self.curveItem:
366            if event.type() == QEvent.GraphicsSceneHoverEnter:
367                self.setHoverState(True)
368            elif event.type() == QEvent.GraphicsSceneHoverLeave:
369                self.setHoverState(False)
370
371        return QGraphicsObject.sceneEventFilter(self, obj, event)
372
373    def boundingRect(self):
374        return self.childrenBoundingRect()
375
376    def shape(self):
377        return self.curveItem.shape()
378
379    def setEnabled(self, enabled):
380        QGraphicsObject.setEnabled(self, enabled)
381
382    def setDynamicEnabled(self, enabled):
383        if self.__dynamicEnabled != enabled:
384            self.__dynamicEnabled = enabled
385            if self.__dynamic:
386                self.__updatePen()
387
388    def isDynamicEnabled(self):
389        return self.__dynamicEnabled
390
391    def setDynamic(self, dynamic):
392        if self.__dynamic != dynamic:
393            self.__dynamic = dynamic
394            self.__updatePen()
395
396    def isDynamic(self):
397        return self.__dynamic
398
399    def __updatePen(self):
400        self.prepareGeometryChange()
401        if self.__dynamic:
402            if self.__dynamicEnabled:
403                color = QColor(0, 150, 0, 150)
404            else:
405                color = QColor(150, 0, 0, 150)
406
407            normal = QPen(QBrush(color), 2.0)
408            hover = QPen(QBrush(color.darker(120)), 2.1)
409        else:
410            normal = QPen(QBrush(QColor("#9CACB4")), 2.0)
411            hover = QPen(QBrush(QColor("#7D7D7D")), 2.1)
412
413        self.curveItem.setCurvePenSet(normal, hover)
Note: See TracBrowser for help on using the repository browser.