source: orange/Orange/OrangeCanvas/canvas/items/controlpoints.py @ 11161:981b2bbe3262

Revision 11161:981b2bbe3262, 13.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Removed control points from the annotation graphics items.

Line 
1import logging
2
3from PyQt4.QtGui import QGraphicsItem, QGraphicsObject, QBrush, QPainterPath
4from PyQt4.QtCore import Qt, QPointF, QLineF, QRectF, QMargins, QVariant, \
5                         QEvent
6
7from PyQt4.QtCore import pyqtSignal as Signal, pyqtProperty as Property
8
9from .graphicspathobject import GraphicsPathObject
10
11log = logging.getLogger(__name__)
12
13
14class ControlPoint(GraphicsPathObject):
15    """A control point for annotations in the canvas.
16    """
17    Free = 0
18
19    Left, Top, Right, Bottom, Center = 1, 2, 4, 8, 16
20
21    TopLeft = Top | Left
22    TopRight = Top | Right
23    BottomRight = Bottom | Right
24    BottomLeft = Bottom | Left
25
26    def __init__(self, parent=None, anchor=0, **kwargs):
27        GraphicsPathObject.__init__(self, parent, **kwargs)
28        self.setFlag(QGraphicsItem.ItemIsMovable)
29        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, False)
30        self.setAcceptedMouseButtons(Qt.LeftButton)
31
32        self.__constraint = 0
33        self.__constraintFunc = None
34        self.__anchor = 0
35        self.setAnchor(anchor)
36
37        path = QPainterPath()
38        path.addEllipse(QRectF(-4, -4, 8, 8))
39        self.setPath(path)
40
41        self.setBrush(QBrush(Qt.lightGray, Qt.SolidPattern))
42
43    def setAnchor(self, anchor):
44        """Set anchor position
45        """
46        self.__anchor = anchor
47
48    def anchor(self):
49        return self.__anchor
50
51    def mousePressEvent(self, event):
52        if event.button() == Qt.LeftButton:
53            # Enable ItemPositionChange (and pos constraint) only when
54            # this is the mouse grabber item
55            self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
56        return GraphicsPathObject.mousePressEvent(self, event)
57
58    def mouseReleaseEvent(self, event):
59        if event.button() == Qt.LeftButton:
60            self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, False)
61        return GraphicsPathObject.mouseReleaseEvent(self, event)
62
63    def itemChange(self, change, value):
64
65        if change == QGraphicsItem.ItemPositionChange:
66            pos = value.toPointF()
67            newpos = self.constrain(pos)
68            return QVariant(newpos)
69
70        return GraphicsPathObject.itemChange(self, change, value)
71
72    def hasConstraint(self):
73        return self.__constraintFunc is not None or self.__constraint != 0
74
75    def setConstraint(self, constraint):
76        """Set the constraint for the point (Qt.Vertical Qt.Horizontal or 0)
77
78        .. note:: Clears the constraintFunc if it was previously set
79
80        """
81        if self.__constraint != constraint:
82            self.__constraint = constraint
83
84        self.__constraintFunc = None
85
86    def constrain(self, pos):
87        """Constrain the pos.
88        """
89        if self.__constraintFunc:
90            return self.__constraintFunc(pos)
91        elif self.__constraint == Qt.Vertical:
92            return QPointF(self.pos().x(), pos.y())
93        elif self.__constraint == Qt.Horizontal:
94            return QPointF(pos.x(), self.pos().y())
95        else:
96            return pos
97
98    def setConstraintFunc(self, func):
99        if self.__constraintFunc != func:
100            self.__constraintFunc = func
101
102
103class ControlPointRect(QGraphicsObject):
104    Free = 0
105    KeepAspectRatio = 1
106    KeepCenter = 2
107
108    rectChanged = Signal(QRectF)
109    rectEdited = Signal(QRectF)
110
111    editingStarted = Signal()
112    editingFinished = Signal()
113
114    def __init__(self, parent=None, rect=None, constraints=0, **kwargs):
115        QGraphicsObject.__init__(self, parent, **kwargs)
116        self.setFlag(QGraphicsItem.ItemHasNoContents)
117        self.setFlag(QGraphicsItem.ItemIsFocusable)
118
119        self.__rect = rect if rect is not None else QRectF()
120        self.__margins = QMargins()
121        points = \
122            [ControlPoint(self, ControlPoint.Left),
123             ControlPoint(self, ControlPoint.Top),
124             ControlPoint(self, ControlPoint.TopLeft),
125             ControlPoint(self, ControlPoint.Right),
126             ControlPoint(self, ControlPoint.TopRight),
127             ControlPoint(self, ControlPoint.Bottom),
128             ControlPoint(self, ControlPoint.BottomLeft),
129             ControlPoint(self, ControlPoint.BottomRight)
130             ]
131        assert(points == sorted(points, key=lambda p: p.anchor()))
132
133        self.__points = dict((p.anchor(), p) for p in points)
134
135        if self.scene():
136            self.__installFilter()
137
138        for p in points:
139            p.setFlag(QGraphicsItem.ItemIsFocusable)
140            p.setFocusProxy(self)
141
142        self.controlPoint(ControlPoint.Top).setConstraint(Qt.Vertical)
143        self.controlPoint(ControlPoint.Bottom).setConstraint(Qt.Vertical)
144        self.controlPoint(ControlPoint.Left).setConstraint(Qt.Horizontal)
145        self.controlPoint(ControlPoint.Right).setConstraint(Qt.Horizontal)
146
147        self.__constraints = constraints
148        self.__activeControl = None
149
150        self.__pointsLayout()
151
152    def controlPoint(self, anchor):
153        """Return the anchor point at anchor position if not set.
154        """
155        return self.__points.get(anchor)
156
157    def setRect(self, rect):
158        if self.__rect != rect:
159            self.__rect = QRectF(rect)
160            self.__pointsLayout()
161            self.prepareGeometryChange()
162            self.rectChanged.emit(rect)
163
164    def rect(self):
165        """Return the control rect
166        """
167        # Return the rect normalized. During the control point move the
168        # rect can change to an invalid size, but the layout must still
169        # know to which point does an unnormalized rect side belong.
170        return self.__rect.normalized()
171
172    rect_ = Property(QRectF, fget=rect, fset=setRect, user=True)
173
174    def setControlMargins(self, *margins):
175        """Set the controls points on the margins around `rect`
176        """
177        if len(margins) > 1:
178            margins = QMargins(*margins)
179        else:
180            margins = margins[0]
181            if isinstance(margins, int):
182                margins = QMargins(margins, margins, margins, margins)
183
184        if self.__margins != margins:
185            self.__margins = margins
186            self.__pointsLayout()
187
188    def controlMargins(self):
189        return self.__margins
190
191    def setConstraints(self, constraints):
192        pass
193
194    def focusInEvent(self, event):
195        QGraphicsObject.focusInEvent(self, event)
196        if event.isAccepted():
197            self.editingStarted.emit()
198
199    def focusOutEvent(self, event):
200        QGraphicsObject.focusOutEvent(self, event)
201        if event.isAccepted():
202            self.editingFinished.emit()
203
204    def itemChange(self, change, value):
205        if change == QGraphicsItem.ItemSceneHasChanged and self.scene():
206            self.__installFilter()
207
208        return QGraphicsObject.itemChange(self, change, value)
209
210    def sceneEventFilter(self, obj, event):
211        try:
212            if isinstance(obj, ControlPoint):
213                etype = event.type()
214                if etype == QEvent.GraphicsSceneMousePress and \
215                        event.button() == Qt.LeftButton:
216                    self.__setActiveControl(obj)
217
218                elif etype == QEvent.GraphicsSceneMouseRelease and \
219                        event.button() == Qt.LeftButton:
220                    self.__setActiveControl(None)
221
222        except Exception:
223            log.error("Error in 'ControlPointRect.sceneEventFilter'",
224                      exc_info=True)
225
226        return QGraphicsObject.sceneEventFilter(self, obj, event)
227
228    def __installFilter(self):
229        # Install filters on the control points.
230        try:
231            for p in self.__points.values():
232                p.installSceneEventFilter(self)
233        except Exception:
234            log.error("Error in ControlPointRect.__installFilter",
235                      exc_info=True)
236
237    def __pointsLayout(self):
238        """Layout the control points
239        """
240        rect = self.__rect
241        margins = self.__margins
242        rect = rect.adjusted(-margins.left(), -margins.top(),
243                             margins.right(), margins.bottom())
244        center = rect.center()
245        cx, cy = center.x(), center.y()
246        left, top, right, bottom = \
247                rect.left(), rect.top(), rect.right(), rect.bottom()
248
249        self.controlPoint(ControlPoint.Left).setPos(left, cy)
250        self.controlPoint(ControlPoint.Right).setPos(right, cy)
251        self.controlPoint(ControlPoint.Top).setPos(cx, top)
252        self.controlPoint(ControlPoint.Bottom).setPos(cx, bottom)
253
254        self.controlPoint(ControlPoint.TopLeft).setPos(left, top)
255        self.controlPoint(ControlPoint.TopRight).setPos(right, top)
256        self.controlPoint(ControlPoint.BottomLeft).setPos(left, bottom)
257        self.controlPoint(ControlPoint.BottomRight).setPos(right, bottom)
258
259    def __setActiveControl(self, control):
260        if self.__activeControl != control:
261            if self.__activeControl is not None:
262                self.__activeControl.positionChanged[QPointF].disconnect(
263                    self.__activeControlMoved
264                )
265
266            self.__activeControl = control
267
268            if control is not None:
269                control.positionChanged[QPointF].connect(
270                    self.__activeControlMoved
271                )
272
273    def __activeControlMoved(self, pos):
274        # The active control point has moved, update the control
275        # rectangle
276        control = self.__activeControl
277        pos = control.pos()
278        rect = QRectF(self.__rect)
279        margins = self.__margins
280
281        # TODO: keyboard modifiers and constraints.
282
283        anchor = control.anchor()
284        if anchor & ControlPoint.Top:
285            rect.setTop(pos.y() + margins.top())
286        elif anchor & ControlPoint.Bottom:
287            rect.setBottom(pos.y() - margins.bottom())
288
289        if anchor & ControlPoint.Left:
290            rect.setLeft(pos.x() + margins.left())
291        elif anchor & ControlPoint.Right:
292            rect.setRight(pos.x() - margins.right())
293
294        changed = self.__rect != rect
295
296        self.blockSignals(True)
297        self.setRect(rect)
298        self.blockSignals(False)
299
300        if changed:
301            self.rectEdited.emit(rect)
302
303    def boundingRect(self):
304        return QRectF()
305
306
307class ControlPointLine(QGraphicsObject):
308
309    lineChanged = Signal(QLineF)
310    lineEdited = Signal(QLineF)
311
312    editingStarted = Signal()
313    editingFinished = Signal()
314
315    def __init__(self, parent=None, **kwargs):
316        QGraphicsObject.__init__(self, parent, **kwargs)
317        self.setFlag(QGraphicsItem.ItemHasNoContents)
318        self.setFlag(QGraphicsItem.ItemIsFocusable)
319
320        self.__line = QLineF()
321        self.__points = \
322            [ControlPoint(self, ControlPoint.TopLeft),  # TopLeft is line start
323             ControlPoint(self, ControlPoint.BottomRight)  # line end
324             ]
325
326        self.__activeControl = None
327
328        if self.scene():
329            self.__installFilter()
330        for p in self.__points:
331            p.setFlag(QGraphicsItem.ItemIsFocusable)
332            p.setFocusProxy(self)
333
334    def setLine(self, line):
335        if not isinstance(line, QLineF):
336            raise TypeError()
337
338        if line != self.__line:
339            self.__line = line
340            self.__pointsLayout()
341            self.lineChanged.emit(line)
342
343    def line(self):
344        return self.__line
345
346    def focusInEvent(self, event):
347        QGraphicsObject.focusInEvent(self, event)
348        if event.isAccepted():
349            self.editingStarted.emit()
350
351    def focusOutEvent(self, event):
352        QGraphicsObject.focusOutEvent(self, event)
353        if event.isAccepted():
354            self.editingFinished.emit()
355
356    def __installFilter(self):
357        for p in self.__points:
358            p.installSceneEventFilter(self)
359
360    def itemChange(self, change, value):
361        if change == QGraphicsItem.ItemSceneHasChanged:
362            if self.scene():
363                self.__installFilter()
364        return QGraphicsObject.itemChange(self, change, value)
365
366    def sceneEventFilter(self, obj, event):
367        try:
368            if isinstance(obj, ControlPoint):
369                etype = event.type()
370                if etype == QEvent.GraphicsSceneMousePress:
371                    self.__setActiveControl(obj)
372                elif etype == QEvent.GraphicsSceneMouseRelease:
373                    self.__setActiveControl(None)
374
375            return QGraphicsObject.sceneEventFilter(self, obj, event)
376        except Exception:
377            log.error("", exc_info=True)
378
379    def __pointsLayout(self):
380        self.__points[0].setPos(self.__line.p1())
381        self.__points[1].setPos(self.__line.p2())
382
383    def __setActiveControl(self, control):
384        if self.__activeControl != control:
385            if self.__activeControl is not None:
386                self.__activeControl.positionChanged[QPointF].disconnect(
387                    self.__activeControlMoved
388                )
389
390            self.__activeControl = control
391
392            if control is not None:
393                control.positionChanged[QPointF].connect(
394                    self.__activeControlMoved
395                )
396
397    def __activeControlMoved(self, pos):
398        line = QLineF(self.__line)
399        control = self.__activeControl
400        if control.anchor() == ControlPoint.TopLeft:
401            line.setP1(pos)
402        elif control.anchor() == ControlPoint.BottomRight:
403            line.setP2(pos)
404
405        if self.__line != line:
406            self.blockSignals(True)
407            self.setLine(line)
408            self.blockSignals(False)
409            self.lineEdited.emit(line)
410
411    def boundingRect(self):
412        return QRectF()
Note: See TracBrowser for help on using the repository browser.