source: orange/Orange/OrangeCanvas/canvas/editlinksdialog.py @ 11283:babe4e92dae3

Revision 11283:babe4e92dae3, 24.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Fixed an error which allowed the dialog to connect more than one connection to a single input channel.

Line 
1"""
2An Dialog to edit links between two nodes in the scheme.
3
4"""
5
6from collections import namedtuple
7
8from xml.sax.saxutils import escape
9
10from PyQt4.QtGui import (
11    QApplication, QDialog, QVBoxLayout, QDialogButtonBox, QGraphicsScene,
12    QGraphicsView, QGraphicsWidget, QGraphicsRectItem,
13    QGraphicsLineItem, QGraphicsTextItem, QGraphicsLayoutItem,
14    QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsPixmapItem,
15    QGraphicsDropShadowEffect, QSizePolicy, QPalette, QPen,
16    QPainter
17)
18
19from PyQt4.QtCore import (
20    Qt, QObject, QSize, QSizeF, QPointF, QRectF, qVersion
21)
22
23from PyQt4.QtCore import pyqtSignal as Signal
24
25from ..scheme import SchemeNode, SchemeLink, compatible_channels
26from ..registry import InputSignal, OutputSignal
27
28from ..resources import icon_loader
29
30
31QWIDGETSIZE_MAX = ((1 << 24) - 1)
32
33
34class EditLinksDialog(QDialog):
35    """
36    A dialog for editing links.
37
38    >>> dlg = EditLinksDialog()
39    >>> dlg.setNodes(file_node, test_learners_node)
40    >>> dlg.setLinks([(file_node.output_channel("Data"),
41    ...               (test_learners_node.input_channel("Data")])
42    >>> if dlg.exec_() == EditLinksDialog.Accpeted:
43    ...     new_links = dlg.links()
44    ...
45
46    """
47    def __init__(self, *args, **kwargs):
48        QDialog.__init__(self, *args, **kwargs)
49
50        self.setModal(True)
51
52        self.__setupUi()
53
54    def __setupUi(self):
55        layout = QVBoxLayout()
56
57        # Scene with the link editor.
58        self.scene = LinksEditScene()
59        self.view = QGraphicsView(self.scene)
60        self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
61        self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
62        self.view.setRenderHint(QPainter.Antialiasing)
63
64        self.scene.editWidget.geometryChanged.connect(self.__onGeometryChanged)
65
66        # Ok/Cancel/Clear All buttons.
67        buttons = QDialogButtonBox(QDialogButtonBox.Ok |
68                                   QDialogButtonBox.Cancel |
69                                   QDialogButtonBox.Reset,
70                                   Qt.Horizontal)
71
72        clear_button = buttons.button(QDialogButtonBox.Reset)
73        clear_button.setText(self.tr("Clear All"))
74
75        buttons.accepted.connect(self.accept)
76        buttons.rejected.connect(self.reject)
77        clear_button.clicked.connect(self.scene.editWidget.clearLinks)
78
79        layout.addWidget(self.view)
80        layout.addWidget(buttons)
81
82        self.setLayout(layout)
83        layout.setSizeConstraint(QVBoxLayout.SetFixedSize)
84
85        self.setSizeGripEnabled(False)
86
87    def setNodes(self, source_node, sink_node):
88        """Set the source/sink nodes (`SchemeNode` instances)
89        between which to edit the links.
90
91        """
92        self.scene.editWidget.setNodes(source_node, sink_node)
93
94    def setLinks(self, links):
95        """Set a list of links to display between the source and sink
96        nodes. The `links` is a list of (`OutputSignal`, `InputSignal`)
97        instances where the first element refers to the source node
98        and the second to the sink node.
99
100        """
101        self.scene.editWidget.setLinks(links)
102
103    def links(self):
104        """Return the links between the source and sink node.
105        """
106        return self.scene.editWidget.links()
107
108    def __onGeometryChanged(self):
109        size = self.scene.editWidget.size()
110        left, top, right, bottom = self.getContentsMargins()
111        self.view.setFixedSize(size.toSize() + \
112                               QSize(left + right + 4, top + bottom + 4))
113
114
115def find_item_at(scene, pos, order=Qt.DescendingOrder, type=None,
116                 name=None):
117    """Find an object in a :class:`QGraphicsScene` `scene` at `pos`.
118    If `type` is not `None` the it must specify  the type of the item.
119    I `name` is not `None` it must be a name of the object
120    (`QObject.objectName()`).
121
122    """
123    items = scene.items(pos, Qt.IntersectsItemShape, order)
124    for item in items:
125        if type is not None and \
126                not isinstance(item, type):
127            continue
128
129        if name is not None and isinstance(item, QObject) and \
130                item.objectName() != name:
131            continue
132        return item
133    else:
134        return None
135
136
137class LinksEditScene(QGraphicsScene):
138    """A :class:`QGraphicsScene` used by the :class:`LinkEditWidget`.
139
140    """
141    def __init__(self, *args, **kwargs):
142        QGraphicsScene.__init__(self, *args, **kwargs)
143
144        self.editWidget = LinksEditWidget()
145        self.addItem(self.editWidget)
146
147    findItemAt = find_item_at
148
149
150_Link = namedtuple(
151    "_Link",
152    ["output",    # OutputSignal
153     "input",     # InputSignal
154     "lineItem",  # QGraphicsLineItem connecting the input to output
155     ])
156
157
158class LinksEditWidget(QGraphicsWidget):
159    """
160    A Graphics Widget for editing the links between two nodes.
161    """
162    def __init__(self, *args, **kwargs):
163        QGraphicsWidget.__init__(self, *args, **kwargs)
164        self.setAcceptedMouseButtons(Qt.LeftButton | Qt.RightButton)
165
166        self.source = None
167        self.sink = None
168
169        # QGraphicsWidget/Items in the scene.
170        self.sourceNodeWidget = None
171        self.sourceNodeTitle = None
172        self.sinkNodeWidget = None
173        self.sinkNodeTitle = None
174
175        self.__links = []
176
177        self.__textItems = []
178        self.__iconItems = []
179        self.__tmpLine = None
180        self.__dragStartItem = None
181
182        self.setLayout(QGraphicsLinearLayout(Qt.Vertical))
183        self.layout().setContentsMargins(0, 0, 0, 0)
184
185    def removeItems(self, items):
186        """
187        Remove child items form the widget and scene.
188        """
189        scene = self.scene()
190        for item in items:
191            item.setParentItem(None)
192            if scene is not None:
193                scene.removeItem(item)
194
195    def clear(self):
196        """
197        Clear the editor state (source and sink nodes, channels ...).
198        """
199        if self.layout().count():
200            widget = self.layout().takeAt(0).graphicsItem()
201            self.removeItems([widget])
202
203        self.source = None
204        self.sink = None
205
206    def setNodes(self, source, sink):
207        """
208        Set the source/sink nodes (:class:`SchemeNode` instances) between
209        which to edit the links.
210
211        .. note:: Call this before `setLinks`.
212
213        """
214        self.clear()
215
216        self.source = source
217        self.sink = sink
218
219        self.__updateState()
220
221    def setLinks(self, links):
222        """
223        Set a list of links to display between the source and sink
224        nodes. `links` must be a list of (`OutputSignal`, `InputSignal`)
225        tuples where the first element refers to the source node
226        and the second to the sink node (as set by `setNodes`).
227
228        """
229        self.clearLinks()
230        for output, input in links:
231            self.addLink(output, input)
232
233    def links(self):
234        """
235        Return the links between the source and sink node.
236        """
237        return [(link.output, link.input) for link in self.__links]
238
239    def mousePressEvent(self, event):
240        if event.button() == Qt.LeftButton:
241            startItem = find_item_at(self.scene(), event.pos(),
242                                     type=ChannelAnchor)
243            if startItem is not None:
244                # Start a connection line drag.
245                self.__dragStartItem = startItem
246                self.__tmpLine = None
247                event.accept()
248                return
249
250            lineItem = find_item_at(self.scene(), event.scenePos(),
251                                    type=QGraphicsLineItem)
252            if lineItem is not None:
253                # Remove a connection under the mouse
254                for link in self.__links:
255                    if link.lineItem == lineItem:
256                        self.removeLink(link.output, link.input)
257                event.accept()
258                return
259
260        QGraphicsWidget.mousePressEvent(self, event)
261
262    def mouseMoveEvent(self, event):
263        if event.buttons() & Qt.LeftButton:
264
265            downPos = event.buttonDownPos(Qt.LeftButton)
266            if not self.__tmpLine and self.__dragStartItem and \
267                    (downPos - event.pos()).manhattanLength() > \
268                        QApplication.instance().startDragDistance():
269                # Start a line drag
270                line = QGraphicsLineItem(self)
271                start = self.__dragStartItem.boundingRect().center()
272                start = self.mapFromItem(self.__dragStartItem, start)
273                line.setLine(start.x(), start.y(),
274                             event.pos().x(), event.pos().y())
275
276                pen = QPen(Qt.green, 4)
277                pen.setCapStyle(Qt.RoundCap)
278                line.setPen(pen)
279                line.show()
280
281                self.__tmpLine = line
282
283            if self.__tmpLine:
284                # Update the temp line
285                line = self.__tmpLine.line()
286                line.setP2(event.pos())
287                self.__tmpLine.setLine(line)
288
289        QGraphicsWidget.mouseMoveEvent(self, event)
290
291    def mouseReleaseEvent(self, event):
292        if event.button() == Qt.LeftButton and self.__tmpLine:
293            endItem = find_item_at(self.scene(), event.scenePos(),
294                                     type=ChannelAnchor)
295
296            if endItem is not None:
297                startItem = self.__dragStartItem
298                startChannel = startItem.channel()
299                endChannel = endItem.channel()
300                possible = False
301
302                # Make sure the drag was from input to output (or reversed) and
303                # not between input -> input or output -> output
304                if type(startChannel) != type(endChannel):
305                    if isinstance(startChannel, InputSignal):
306                        startChannel, endChannel = endChannel, startChannel
307
308                    possible = compatible_channels(startChannel, endChannel)
309
310                if possible:
311                    self.addLink(startChannel, endChannel)
312
313            self.scene().removeItem(self.__tmpLine)
314            self.__tmpLine = None
315            self.__dragStartItem = None
316
317        QGraphicsWidget.mouseReleaseEvent(self, event)
318
319    def addLink(self, output, input):
320        """
321        Add a link between `output` (:class:`OutputSignal`) and `input`
322        (:class:`InputSignal`).
323
324        """
325        if not compatible_channels(output, input):
326            return
327
328        if output not in self.source.output_channels():
329            raise ValueError("%r is not an output channel of %r" % \
330                             (output, self.source))
331
332        if input not in self.sink.input_channels():
333            raise ValueError("%r is not an input channel of %r" % \
334                             (input, self.sink))
335
336        if input.single:
337            # Remove existing link if it exists.
338            for s1, s2, _ in self.__links:
339                if s2 == input:
340                    self.removeLink(s1, s2)
341
342        line = QGraphicsLineItem(self)
343
344        source_anchor = self.sourceNodeWidget.anchor(output)
345        sink_anchor = self.sinkNodeWidget.anchor(input)
346
347        source_pos = source_anchor.boundingRect().center()
348        source_pos = self.mapFromItem(source_anchor, source_pos)
349
350        sink_pos = sink_anchor.boundingRect().center()
351        sink_pos = self.mapFromItem(sink_anchor, sink_pos)
352        line.setLine(source_pos.x(), source_pos.y(),
353                     sink_pos.x(), sink_pos.y())
354        pen = QPen(Qt.green, 4)
355        pen.setCapStyle(Qt.RoundCap)
356        line.setPen(pen)
357
358        self.__links.append(_Link(output, input, line))
359
360    def removeLink(self, output, input):
361        """
362        Remove a link between the `output` and `input` channels.
363        """
364        for link in list(self.__links):
365            if link.output == output and link.input == input:
366                self.scene().removeItem(link.lineItem)
367                self.__links.remove(link)
368                break
369        else:
370            raise ValueError("No such link {0.name!r} -> {1.name!r}." \
371                             .format(output, input))
372
373    def clearLinks(self):
374        """
375        Clear (remove) all the links.
376        """
377        for output, input, _ in list(self.__links):
378            self.removeLink(output, input)
379
380    def __updateState(self):
381        """
382        Update the widget with the new source/sink node signal descriptions.
383        """
384        widget = QGraphicsWidget()
385        widget.setLayout(QGraphicsGridLayout())
386
387        # Space between left and right anchors
388        widget.layout().setHorizontalSpacing(50)
389
390        left_node = EditLinksNode(self, direction=Qt.LeftToRight,
391                                  node=self.source)
392
393        left_node.setSizePolicy(QSizePolicy.MinimumExpanding,
394                                QSizePolicy.MinimumExpanding)
395
396        right_node = EditLinksNode(self, direction=Qt.RightToLeft,
397                                   node=self.sink)
398
399        right_node.setSizePolicy(QSizePolicy.MinimumExpanding,
400                                 QSizePolicy.MinimumExpanding)
401
402        left_node.setMinimumWidth(150)
403        right_node.setMinimumWidth(150)
404
405        widget.layout().addItem(left_node, 0, 0,)
406        widget.layout().addItem(right_node, 0, 1,)
407
408        title_template = "<center><b>{0}<b></center>"
409
410        left_title = GraphicsTextWidget(self)
411        left_title.setHtml(title_template.format(escape(self.source.title)))
412        left_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
413
414        right_title = GraphicsTextWidget(self)
415        right_title.setHtml(title_template.format(escape(self.sink.title)))
416        right_title.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
417
418        widget.layout().addItem(left_title, 1, 0,
419                                alignment=Qt.AlignHCenter | Qt.AlignTop)
420        widget.layout().addItem(right_title, 1, 1,
421                                alignment=Qt.AlignHCenter | Qt.AlignTop)
422
423        widget.setParentItem(self)
424
425        max_w = max(left_node.sizeHint(Qt.PreferredSize).width(),
426                    right_node.sizeHint(Qt.PreferredSize).width())
427
428        # fix same size
429        left_node.setMinimumWidth(max_w)
430        right_node.setMinimumWidth(max_w)
431        left_title.setMinimumWidth(max_w)
432        right_title.setMinimumWidth(max_w)
433
434        self.layout().addItem(widget)
435        self.layout().activate()
436
437        self.sourceNodeWidget = left_node
438        self.sinkNodeWidget = right_node
439        self.sourceNodeTitle = left_title
440        self.sinkNodeTitle = right_title
441
442    if qVersion() < "4.7":
443        geometryChanged = Signal()
444
445        def setGeometry(self, rect):
446            QGraphicsWidget.setGeometry(self, rect)
447            self.geometryChanged.emit()
448
449
450class EditLinksNode(QGraphicsWidget):
451    """
452    A Node with channel anchors.
453
454    `direction` specifies the layout (default `Qt.LeftToRight` will
455    have icon on the left and channels on the right).
456
457    """
458
459    def __init__(self, parent=None, direction=Qt.LeftToRight,
460                 node=None, icon=None, iconSize=None, **args):
461        QGraphicsWidget.__init__(self, parent, **args)
462        self.setAcceptedMouseButtons(Qt.NoButton)
463        self.__direction = direction
464
465        self.setLayout(QGraphicsLinearLayout(Qt.Horizontal))
466
467        # Set the maximum size, otherwise the layout can't grow beyond its
468        # sizeHint (and we need it to grow so the widget can grow and keep the
469        # contents centered vertically.
470        self.layout().setMaximumSize(QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX))
471
472        self.setSizePolicy(QSizePolicy.MinimumExpanding,
473                           QSizePolicy.MinimumExpanding)
474
475        self.__iconSize = iconSize or QSize(64, 64)
476        self.__icon = icon
477
478        self.__iconItem = QGraphicsPixmapItem(self)
479        self.__iconLayoutItem = GraphicsItemLayoutItem(item=self.__iconItem)
480
481        self.__channelLayout = QGraphicsGridLayout()
482        self.__channelAnchors = []
483
484        if self.__direction == Qt.LeftToRight:
485            self.layout().addItem(self.__iconLayoutItem)
486            self.layout().addItem(self.__channelLayout)
487            channel_alignemnt = Qt.AlignRight
488
489        else:
490            self.layout().addItem(self.__channelLayout)
491            self.layout().addItem(self.__iconLayoutItem)
492            channel_alignemnt = Qt.AlignLeft
493
494        self.layout().setAlignment(self.__iconLayoutItem, Qt.AlignCenter)
495        self.layout().setAlignment(self.__channelLayout,
496                                   Qt.AlignVCenter | channel_alignemnt)
497
498        if node is not None:
499            self.setSchemeNode(node)
500
501    def setIconSize(self, size):
502        """
503        Set the icon size for the node.
504        """
505        if size != self.__iconSize:
506            self.__iconSize = size
507            if self.__icon:
508                self.__iconItem.setPixmap(self.__icon.pixmap(size))
509                self.__iconLayoutItem.updateGeometry()
510
511    def iconSize(self):
512        return self.__iconSize
513
514    def setIcon(self, icon):
515        """
516        Set the icon to display.
517        """
518        if icon != self.__icon:
519            self.__icon = icon
520            self.__iconItem.setPixmap(icon.pixmap(self.iconSize()))
521            self.__iconLayoutItem.updateGeometry()
522
523    def icon(self):
524        return self.__icon
525
526    def setSchemeNode(self, node):
527        """
528        Set an instance of `SchemeNode`. The widget will be
529        initialized with its icon and channels.
530
531        """
532        self.node = node
533
534        if self.__direction == Qt.LeftToRight:
535            channels = node.output_channels()
536        else:
537            channels = node.input_channels()
538        self.channels = channels
539
540        loader = icon_loader.from_description(node.description)
541        icon = loader.get(node.description.icon)
542
543        self.setIcon(icon)
544
545        label_template = ('<div align="{align}">'
546                          '<b class="channelname">{name}</b><br/>'
547                          '<span class="typename">{typename}</span>'
548                          '</div>')
549
550        if self.__direction == Qt.LeftToRight:
551            align = "right"
552            label_alignment = Qt.AlignVCenter | Qt.AlignRight
553            anchor_alignment = Qt.AlignVCenter | Qt.AlignLeft
554            label_row = 0
555            anchor_row = 1
556        else:
557            align = "left"
558            label_alignment = Qt.AlignVCenter | Qt.AlignLeft
559            anchor_alignment = Qt.AlignVCenter | Qt.AlignLeft
560            label_row = 1
561            anchor_row = 0
562
563        self.__channelAnchors = []
564        grid = self.__channelLayout
565
566        for i, channel in enumerate(channels):
567            text = label_template.format(align=align,
568                                         name=escape(channel.name),
569                                         typename=escape(channel.type))
570
571            text_item = GraphicsTextWidget(self)
572            text_item.setHtml(text)
573            text_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
574
575            grid.addItem(text_item, i, label_row,
576                         alignment=label_alignment)
577
578            anchor = ChannelAnchor(self, channel=channel,
579                                   rect=QRectF(0, 0, 20, 20))
580
581            anchor.setBrush(self.palette().brush(QPalette.Mid))
582
583            layout_item = GraphicsItemLayoutItem(grid, item=anchor)
584            grid.addItem(layout_item, i, anchor_row,
585                         alignment=anchor_alignment)
586
587            if hasattr(channel, "description"):
588                text_item.setToolTip((channel.description))
589
590            self.__channelAnchors.append(anchor)
591
592    def anchor(self, channel):
593        """
594        Return the anchor item for the `channel` name.
595        """
596        for anchor in self.__channelAnchors:
597            if anchor.channel() == channel:
598                return anchor
599
600        raise ValueError(channel.name)
601
602    def paint(self, painter, option, widget=None):
603        painter.save()
604        palette = self.palette()
605        border = palette.brush(QPalette.Mid)
606        pen = QPen(border, 1)
607        pen.setCosmetic(True)
608        painter.setPen(pen)
609        painter.setBrush(palette.brush(QPalette.Window))
610        brect = self.boundingRect()
611        painter.drawRoundedRect(brect, 4, 4)
612        painter.restore()
613
614
615class GraphicsItemLayoutItem(QGraphicsLayoutItem):
616    """
617    A graphics layout that handles the position of a general QGraphicsItem
618    in a QGraphicsLayout. The items boundingRect is used as this items fixed
619    sizeHint and the item is positioned at the top left corner of the this
620    items geometry.
621
622    """
623
624    def __init__(self, parent=None, item=None, ):
625        self.__item = None
626
627        QGraphicsLayoutItem.__init__(self, parent, isLayout=False)
628
629        self.setOwnedByLayout(True)
630        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
631
632        if item is not None:
633            self.setItem(item)
634
635    def setItem(self, item):
636        self.__item = item
637        self.setGraphicsItem(item)
638
639    def setGeometry(self, rect):
640        # TODO: specifiy if the geometry should be set relative to the
641        # bounding rect top left corner
642        if self.__item:
643            self.__item.setPos(rect.topLeft())
644
645        QGraphicsLayoutItem.setGeometry(self, rect)
646
647    def sizeHint(self, which, constraint):
648        if self.__item:
649            return self.__item.boundingRect().size()
650        else:
651            return QGraphicsLayoutItem.sizeHint(self, which, constraint)
652
653
654class ChannelAnchor(QGraphicsRectItem):
655    def __init__(self, parent=None, channel=None, rect=None, **kwargs):
656        QGraphicsRectItem.__init__(self, **kwargs)
657        self.setAcceptHoverEvents(True)
658        self.setAcceptedMouseButtons(Qt.NoButton)
659        self.__channel = None
660
661        if rect is None:
662            rect = QRectF(0, 0, 20, 20)
663
664        self.setRect(rect)
665
666        if channel:
667            self.setChannel(channel)
668
669        self.__shadow = QGraphicsDropShadowEffect(blurRadius=5,
670                                                  offset=QPointF(0, 0))
671        self.setGraphicsEffect(self.__shadow)
672        self.__shadow.setEnabled(False)
673
674    def setChannel(self, channel):
675        if channel != self.__channel:
676            self.__channel = channel
677            if hasattr(channel, "description"):
678                self.setToolTip(channel.description)
679            # TODO: Should also include name, type, flags, dynamic in the
680            #       tool tip as well as add visual clues to the anchor
681
682    def channel(self):
683        return self.__channel
684
685    def hoverEnterEvent(self, event):
686        self.__shadow.setEnabled(True)
687        QGraphicsRectItem.hoverEnterEvent(self, event)
688
689    def hoverLeaveEvent(self, event):
690        self.__shadow.setEnabled(False)
691        QGraphicsRectItem.hoverLeaveEvent(self, event)
692
693
694class GraphicsTextWidget(QGraphicsWidget):
695    """A QGraphicsWidget subclass that manages a QGraphicsTextItem
696
697    """
698
699    def __init__(self, parent=None, textItem=None):
700        QGraphicsLayoutItem.__init__(self, parent)
701        if textItem is None:
702            textItem = QGraphicsTextItem()
703
704        self.__textItem = textItem
705        self.__textItem.setParentItem(self)
706        self.__textItem.setPos(0, 0)
707
708        doc_layout = self.document().documentLayout()
709        doc_layout.documentSizeChanged.connect(self._onDocumentSizeChanged)
710
711    def sizeHint(self, which, constraint=QSizeF()):
712        # TODO: More sensible size hints.
713        # If the text is a plain text or html
714        # Check how QLabel.sizeHint works.
715
716        if which == Qt.PreferredSize:
717            return self.__textItem.boundingRect().size()
718        else:
719            return QGraphicsWidget.sizeHint(self, which, constraint)
720
721    def setGeometry(self, rect):
722        QGraphicsWidget.setGeometry(self, rect)
723        self.__textItem.setTextWidth(rect.width())
724
725    def setPlainText(self, text):
726        self.__textItem.setPlainText(text)
727        self.updateGeometry()
728
729    def setHtml(self, text):
730        self.__textItem.setHtml(text)
731
732    def adjustSize(self):
733        self.__textItem.adjustSize()
734        self.updateGeometry()
735
736    def setDefaultTextColor(self, color):
737        self.__textItem.setDefaultTextColor(color)
738
739    def document(self):
740        return self.__textItem.document()
741
742    def setDocument(self, doc):
743        doc_layout = self.document().documentLayout()
744        doc_layout.documentSizeChanged.disconnect(self._onDocumentSizeChanged)
745
746        self.__textItem.setDocument(doc)
747
748        doc_layout = self.document().documentLayout()
749        doc_layout.documentSizeChanged.connect(self._onDocumentSizeChanged)
750
751        self.updateGeometry()
752
753    def _onDocumentSizeChanged(self, size):
754        """The doc size has changed"""
755        self.updateGeometry()
Note: See TracBrowser for help on using the repository browser.