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

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

Removed control points from the annotation graphics items.

Line 
1
2import logging
3
4from PyQt4.QtGui import (
5    QGraphicsItem, QGraphicsPathItem, QGraphicsWidget,
6    QGraphicsTextItem, QPainterPath, QPainterPathStroker,
7    QPen, QPolygonF
8)
9
10from PyQt4.QtCore import (
11    Qt, QSizeF, QRectF, QLineF, QEvent
12)
13
14from PyQt4.QtCore import pyqtSignal as Signal
15
16log = logging.getLogger(__name__)
17
18from .graphicspathobject import GraphicsPathObject
19
20
21class Annotation(QGraphicsWidget):
22    """Base class for annotations in the canvas scheme.
23    """
24    def __init__(self, parent=None, **kwargs):
25        QGraphicsWidget.__init__(self, parent, **kwargs)
26
27
28class TextAnnotation(Annotation):
29    """Text annotation item for the canvas scheme.
30
31    """
32    editingFinished = Signal()
33    """Emitted when the editing is finished (i.e. the item loses focus)."""
34
35    textEdited = Signal()
36    """Emitted when the edited text changes."""
37
38    def __init__(self, parent=None, **kwargs):
39        Annotation.__init__(self, parent, **kwargs)
40        self.setFlag(QGraphicsItem.ItemIsMovable)
41
42        self.setFocusPolicy(Qt.ClickFocus)
43
44        self.__textMargins = (2, 2, 2, 2)
45
46        rect = self.geometry().translated(-self.pos())
47        self.__framePathItem = QGraphicsPathItem(self)
48        self.__framePathItem.setPen(QPen(Qt.NoPen))
49
50        self.__textItem = QGraphicsTextItem(self)
51        self.__textItem.setPos(2, 2)
52        self.__textItem.setTextWidth(rect.width() - 4)
53        self.__textItem.setTabChangesFocus(True)
54        self.__textInteractionFlags = Qt.NoTextInteraction
55
56        layout = self.__textItem.document().documentLayout()
57        layout.documentSizeChanged.connect(self.__onDocumentSizeChanged)
58
59        self.setFocusProxy(self.__textItem)
60
61        self.__updateFrame()
62
63    def adjustSize(self):
64        """Resize to a reasonable size.
65        """
66        self.__textItem.setTextWidth(-1)
67        self.__textItem.adjustSize()
68        size = self.__textItem.boundingRect().size()
69        left, top, right, bottom = self.textMargins()
70        geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom))
71        self.setGeometry(geom)
72
73    def setFramePen(self, pen):
74        """Set the frame pen. By default Qt.NoPen is used (i.e. the frame
75        is not shown).
76
77        """
78        self.__framePathItem.setPen(pen)
79
80    def framePen(self):
81        """Return the frame pen.
82        """
83        return self.__framePathItem.pen()
84
85    def setPlainText(self, text):
86        """Set the annotation plain text.
87        """
88        self.__textItem.setPlainText(text)
89
90    def toPlainText(self):
91        return self.__textItem.toPlainText()
92
93    def setHtml(self, text):
94        """Set the annotation rich text.
95        """
96        self.__textItem.setHtml(text)
97
98    def toHtml(self):
99        return self.__textItem.toHtml()
100
101    def setDefaultTextColor(self, color):
102        """Set the default text color.
103        """
104        self.__textItem.setDefaultTextColor(color)
105
106    def defaultTextColor(self):
107        return self.__textItem.defaultTextColor()
108
109    def setTextMargins(self, left, top, right, bottom):
110        """Set the text margins.
111        """
112        margins = (left, top, right, bottom)
113        if self.__textMargins != margins:
114            self.__textMargins = margins
115            self.__textItem.setPos(left, top)
116            self.__textItem.setTextWidth(
117                max(self.geometry().width() - left - right, 0)
118            )
119
120    def textMargins(self):
121        """Return the text margins.
122        """
123        return self.__textMargins
124
125    def document(self):
126        """Return the QTextDocument instance used internally.
127        """
128        return self.__textItem.document()
129
130    def setTextCursor(self, cursor):
131        self.__textItem.setTextCursor(cursor)
132
133    def textCursor(self):
134        return self.__textItem.textCursor()
135
136    def setTextInteractionFlags(self, flags):
137        self.__textInteractionFlags = flags
138        if self.__textItem.hasFocus():
139            self.__textItem.setTextInteractionFlags(flags)
140
141    def textInteractionFlags(self):
142        return self.__textInteractionFlags
143
144    def setDefaultStyleSheet(self, stylesheet):
145        self.document().setDefaultStyleSheet(stylesheet)
146
147    def mouseDoubleClickEvent(self, event):
148        Annotation.mouseDoubleClickEvent(self, event)
149
150        if event.buttons() == Qt.LeftButton and \
151                self.__textInteractionFlags & Qt.TextEditable:
152            self.startEdit()
153
154    def startEdit(self):
155        """Start the annotation text edit process.
156        """
157        self.__textItem.setTextInteractionFlags(
158                            self.__textInteractionFlags)
159        self.__textItem.setFocus(Qt.MouseFocusReason)
160
161        # Install event filter to find out when the text item loses focus.
162        self.__textItem.installSceneEventFilter(self)
163        self.__textItem.document().contentsChanged.connect(
164            self.textEdited
165        )
166
167    def endEdit(self):
168        """End the annotation edit.
169        """
170        if self.__textItem.hasFocus():
171            self.__textItem.clearFocus()
172
173        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction)
174        self.__textItem.removeSceneEventFilter(self)
175        self.__textItem.document().contentsChanged.disconnect(
176            self.textEdited
177        )
178        self.editingFinished.emit()
179
180    def __onDocumentSizeChanged(self, size):
181        # The size of the text document has changed. Expand the text
182        # control rect's height if the text no longer fits inside.
183        try:
184            rect = self.geometry()
185            _, top, _, bottom = self.textMargins()
186            if rect.height() < (size.height() + bottom + top):
187                rect.setHeight(size.height() + bottom + top)
188                self.setGeometry(rect)
189        except Exception:
190            log.error("error in __onDocumentSizeChanged",
191                      exc_info=True)
192
193    def __updateFrame(self):
194        rect = self.geometry()
195        rect.moveTo(0, 0)
196        path = QPainterPath()
197        path.addRect(rect)
198        self.__framePathItem.setPath(path)
199
200    def resizeEvent(self, event):
201        width = event.newSize().width()
202        left, _, right, _ = self.textMargins()
203        self.__textItem.setTextWidth(max(width - left - right, 0))
204        self.__updateFrame()
205        QGraphicsWidget.resizeEvent(self, event)
206
207    def sceneEventFilter(self, obj, event):
208        if obj is self.__textItem and event.type() == QEvent.FocusOut:
209            self.__textItem.focusOutEvent(event)
210            self.endEdit()
211            return True
212
213        return Annotation.sceneEventFilter(self, obj, event)
214
215
216class ArrowItem(GraphicsPathObject):
217    def __init__(self, parent=None, line=None, lineWidth=4, **kwargs):
218        GraphicsPathObject.__init__(self, parent, **kwargs)
219
220        if line is None:
221            line = QLineF(0, 0, 10, 0)
222
223        self.__line = line
224
225        self.__lineWidth = lineWidth
226
227        self.__updateArrowPath()
228
229    def setLine(self, line):
230        if self.__line != line:
231            self.__line = QLineF(line)
232            self.__updateArrowPath()
233
234    def line(self):
235        return QLineF(self.__line)
236
237    def setLineWidth(self, lineWidth):
238        if self.__lineWidth != lineWidth:
239            self.__lineWidth = lineWidth
240            self.__updateArrowPath()
241
242    def lineWidth(self):
243        return self.__lineWidth
244
245    def __updateArrowPath(self):
246        line = self.__line
247        width = self.__lineWidth
248        path = QPainterPath()
249        p1, p2 = line.p1(), line.p2()
250        if p1 == p2:
251            self.setPath(path)
252            return
253
254        baseline = QLineF(line)
255        baseline.setLength(max(line.length() - width * 3, width * 3))
256        path.moveTo(baseline.p1())
257        path.lineTo(baseline.p2())
258
259        stroker = QPainterPathStroker()
260        stroker.setWidth(width)
261        path = stroker.createStroke(path)
262
263        arrow_head_len = width * 4
264        arrow_head_angle = 60
265        line_angle = line.angle() - 180
266
267        angle_1 = line_angle - arrow_head_angle / 2.0
268        angle_2 = line_angle + arrow_head_angle / 2.0
269
270        points = [p2,
271                  p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(),
272                  p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(),
273                  p2]
274        poly = QPolygonF(points)
275        path_head = QPainterPath()
276        path_head.addPolygon(poly)
277        path = path.united(path_head)
278        self.setPath(path)
279
280
281class ArrowAnnotation(Annotation):
282    def __init__(self, parent=None, line=None, **kwargs):
283        Annotation.__init__(self, parent, **kwargs)
284        self.setFlag(QGraphicsItem.ItemIsMovable)
285        self.setFocusPolicy(Qt.ClickFocus)
286
287        if line is None:
288            line = QLineF(0, 0, 20, 0)
289
290        self.__line = line
291        self.__arrowItem = ArrowItem(self)
292        self.__arrowItem.setLine(line)
293        self.__arrowItem.setBrush(Qt.red)
294        self.__arrowItem.setPen(QPen(Qt.NoPen))
295
296    def setLine(self, line):
297        """Set the arrow base line (a `QLineF` in object coordinates).
298        """
299        if self.__line != line:
300            self.__line = line
301
302            geom = self.geometry().translated(-self.pos())
303
304            if geom.isNull() and not line.isNull():
305                geom = QRectF(0, 0, 1, 1)
306            line_rect = QRectF(line.p1(), line.p2()).normalized()
307
308            if not (geom.contains(line_rect)):
309                geom = geom.united(line_rect)
310
311            diff = geom.topLeft()
312            line = QLineF(line.p1() - diff, line.p2() - diff)
313            self.__arrowItem.setLine(line)
314            self.__line = line
315
316            geom.translate(self.pos())
317            self.setGeometry(geom)
318
319    def adjustGeometry(self):
320        """Adjust the widget geometry to exactly fit the arrow inside
321        while preserving the arrow path scene geometry.
322
323        """
324        geom = self.geometry().translated(-self.pos())
325        line = self.__line
326        line_rect = QRectF(line.p1(), line.p2()).normalized()
327        if geom.isNull() and not line.isNull():
328            geom = QRectF(0, 0, 1, 1)
329        if not (geom.contains(line_rect)):
330            geom = geom.united(line_rect)
331        geom = geom.intersected(line_rect)
332        diff = geom.topLeft()
333        line = QLineF(line.p1() - diff, line.p2() - diff)
334        geom.translate(self.pos())
335        self.setGeometry(geom)
336        self.setLine(line)
337
338    def line(self):
339        return QLineF(self.__line)
340
341    def setLineWidth(self, lineWidth):
342        self.__arrowItem.setLineWidth(lineWidth)
343
344    def lineWidth(self):
345        return self.__arrowItem.lineWidth()
346
347    def shape(self):
348        arrow_shape = self.__arrowItem.shape()
349        return self.mapFromItem(self.__arrowItem, arrow_shape)
350
351#    def paint(self, painter, option, widget=None):
352#        painter.drawRect(self.geometry().translated(-self.pos()))
353#        return Annotation.paint(self, painter, option, widget)
Note: See TracBrowser for help on using the repository browser.