source: orange/Orange/OrangeCanvas/canvas/items/controlpoints.py @ 11172:b9add0151621

Revision 11172:b9add0151621, 13.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Compatibility fixes for PyQt4 < 4.9

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