source: orange/Orange/OrangeCanvas/document/schemeedit.py @ 11194:12d76d869aba

Revision 11194:12d76d869aba, 27.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Moved tool actions from main window to SchemeEditWidget.

Line 
1"""
2Scheme Edit widget.
3
4"""
5import logging
6from operator import attrgetter
7
8from PyQt4.QtGui import (
9    QWidget, QVBoxLayout, QInputDialog, QMenu, QAction, QKeySequence,
10    QUndoStack, QGraphicsItem, QGraphicsObject, QPainter
11)
12
13from PyQt4.QtCore import Qt, QObject, QEvent, QSignalMapper, QRectF
14from PyQt4.QtCore import pyqtProperty as Property, pyqtSignal as Signal
15
16from ..scheme import scheme
17from ..canvas.scene import CanvasScene
18from ..canvas.view import CanvasView
19from ..canvas import items
20from . import interactions
21from . import commands
22from . import quickmenu
23
24
25log = logging.getLogger(__name__)
26
27
28# TODO: Should this be moved to CanvasScene?
29class GraphicsSceneFocusEventListener(QGraphicsObject):
30
31    itemFocusedIn = Signal(QGraphicsItem)
32    itemFocusedOut = Signal(QGraphicsItem)
33
34    def __init__(self, parent=None):
35        QGraphicsObject.__init__(self, parent)
36        self.setFlag(QGraphicsItem.ItemHasNoContents)
37
38    def sceneEventFilter(self, obj, event):
39        print obj, event, event.type()
40        if event.type() == QEvent.FocusIn and \
41                obj.flags() & QGraphicsItem.ItemIsFocusable:
42            obj.focusInEvent(event)
43            if obj.hasFocus():
44                self.itemFocusedIn.emit(obj)
45            return True
46        elif event.type() == QEvent.FocusOut:
47            obj.focusOutEvent(event)
48            if not obj.hasFocus():
49                self.itemFocusedOut.emit(obj)
50            return True
51
52        return QGraphicsObject.sceneEventFilter(self, obj, event)
53
54    def boundingRect(self):
55        return QRectF()
56
57
58class SchemeEditWidget(QWidget):
59    undoAvailable = Signal(bool)
60    redoAvailable = Signal(bool)
61    modificationChanged = Signal(bool)
62    undoCommandAdded = Signal()
63    selectionChanged = Signal()
64
65    titleChanged = Signal(unicode)
66
67    def __init__(self, parent=None, ):
68        QWidget.__init__(self, parent)
69
70        self.__modified = False
71        self.__registry = None
72        self.__scheme = None
73        self.__undoStack = QUndoStack(self)
74        self.__undoStack.cleanChanged[bool].connect(self.__onCleanChanged)
75        self.__possibleMouseItemsMove = False
76        self.__itemsMoving = {}
77        self.__contextMenuTarget = None
78        self.__quickMenu = None
79
80        self.__editFinishedMapper = QSignalMapper(self)
81        self.__editFinishedMapper.mapped[QObject].connect(
82            self.__onEditingFinished
83        )
84
85        self.__annotationGeomChanged = QSignalMapper(self)
86
87        self.__setupActions()
88        self.__setupUi()
89
90        self.__linkMenu = QMenu(self)
91        self.__linkMenu.addAction(self.__linkEnableAction)
92        self.__linkMenu.addSeparator()
93        self.__linkMenu.addAction(self.__linkRemoveAction)
94        self.__linkMenu.addAction(self.__linkResetAction)
95
96    def __setupActions(self):
97
98        self.__zoomAction = \
99            QAction(self.tr("Zoom"), self,
100                    objectName="zoom",
101                    checkable=True,
102                    shortcut=QKeySequence.ZoomIn,
103                    toolTip=self.tr("Zoom in the scheme."),
104                    toggled=self.toogleZoom,
105                    )
106
107        self.__cleanUpAction = \
108            QAction(self.tr("Clean Up"), self,
109                    objectName="cleanup",
110                    toolTip=self.tr("Align widget to a grid."),
111                    triggered=self.alignToGrid,
112                    )
113
114        self.__newTextAnnotationAction = \
115            QAction(self.tr("Text"), self,
116                    objectName="new-text-annotation",
117                    toolTip=self.tr("Add a text annotation to the scheme."),
118                    checkable=True,
119                    toggled=self.__toggleNewTextAnnotation,
120                    )
121
122        self.__newArrowAnnotationAction = \
123            QAction(self.tr("Arrow"), self,
124                    objectName="new-arrow-annotation",
125                    toolTip=self.tr("Add a arrow annotation to the scheme."),
126                    checkable=True,
127                    toggled=self.__toggleNewArrowAnnotation,
128                    )
129
130        self.__linkEnableAction = \
131            QAction(self.tr("Enabled"), self,
132                    objectName="link-enable-action",
133                    triggered=self.__toggleLinkEnabled,
134                    checkable=True,
135                    )
136
137        self.__linkRemoveAction = \
138            QAction(self.tr("Remove"), self,
139                    objectName="link-remove-action",
140                    triggered=self.__linkRemove,
141                    toolTip=self.tr("Remove link."),
142                    )
143
144        self.__linkResetAction = \
145            QAction(self.tr("Reset Signals"), self,
146                    objectName="link-reset-action",
147                    triggered=self.__linkReset,
148                    )
149
150        self.addActions([self.__newTextAnnotationAction,
151                         self.__newArrowAnnotationAction,
152                         self.__linkEnableAction,
153                         self.__linkRemoveAction,
154                         self.__linkResetAction])
155
156    def __setupUi(self):
157        layout = QVBoxLayout()
158        layout.setContentsMargins(0, 0, 0, 0)
159        layout.setSpacing(0)
160
161        scene = CanvasScene()
162        view = CanvasView(scene)
163        view.setFrameStyle(CanvasView.NoFrame)
164        view.setRenderHint(QPainter.Antialiasing)
165        view.setContextMenuPolicy(Qt.CustomContextMenu)
166        view.customContextMenuRequested.connect(
167            self.__onCustomContextMenuRequested
168        )
169
170        self.__view = view
171        self.__scene = scene
172
173        self.__focusListener = GraphicsSceneFocusEventListener()
174        self.__focusListener.itemFocusedIn.connect(self.__onItemFocusedIn)
175        self.__focusListener.itemFocusedOut.connect(self.__onItemFocusedOut)
176        self.__scene.addItem(self.__focusListener)
177
178        self.__scene.selectionChanged.connect(
179            self.__onSelectionChanged
180        )
181
182        layout.addWidget(view)
183        self.setLayout(layout)
184
185    def toolbarActions(self):
186        """Return a list of actions that can be inserted into a toolbar.
187        """
188        return [self.__zoomAction,
189                self.__cleanUpAction,
190                self.__newTextAnnotationAction,
191                self.__newArrowAnnotationAction]
192
193    def isModified(self):
194        return not self.__undoStack.isClean()
195
196    def setModified(self, modified):
197        if modified and not self.isModified():
198            raise NotImplementedError
199        else:
200            self.__undoStack.setClean()
201
202    modified = Property(bool, fget=isModified, fset=setModified)
203
204    def undoStack(self):
205        """Return the undo stack.
206        """
207        return self.__undoStack
208
209    def setScheme(self, scheme):
210        if self.__scheme is not scheme:
211            if self.__scheme:
212                self.__scheme.title_changed.disconnect(self.titleChanged)
213                self.__scheme.node_added.disconnect(self.__onNodeAdded)
214                self.__scheme.node_removed.disconnect(self.__onNodeRemoved)
215
216            self.__scheme = scheme
217
218            if self.__scheme:
219                self.__scheme.title_changed.connect(self.titleChanged)
220                self.__scheme.node_added.connect(self.__onNodeAdded)
221                self.__scheme.node_removed.connect(self.__onNodeRemoved)
222                self.titleChanged.emit(scheme.title)
223
224            self.__annotationGeomChanged.deleteLater()
225            self.__annotationGeomChanged = QSignalMapper(self)
226
227            self.__undoStack.clear()
228
229            self.__focusListener.itemFocusedIn.disconnect(
230                self.__onItemFocusedIn
231            )
232            self.__focusListener.itemFocusedOut.disconnect(
233                self.__onItemFocusedOut
234            )
235
236            self.__scene.selectionChanged.disconnect(
237                self.__onSelectionChanged
238            )
239
240            self.__scene.clear()
241            self.__scene.removeEventFilter(self)
242            self.__scene.deleteLater()
243
244            self.__scene = CanvasScene()
245            self.__view.setScene(self.__scene)
246            self.__scene.installEventFilter(self)
247
248            self.__scene.set_registry(self.__registry)
249
250            # Focus listener
251            self.__focusListener = GraphicsSceneFocusEventListener()
252            self.__focusListener.itemFocusedIn.connect(
253                self.__onItemFocusedIn
254            )
255            self.__focusListener.itemFocusedOut.connect(
256                self.__onItemFocusedOut
257            )
258            self.__scene.addItem(self.__focusListener)
259
260            self.__scene.selectionChanged.connect(
261                self.__onSelectionChanged
262            )
263
264            self.__scene.node_item_activated.connect(
265                self.__onNodeActivate
266            )
267
268            self.__scene.annotation_added.connect(
269                self.__onAnnotationAdded
270            )
271
272            self.__scene.annotation_removed.connect(
273                self.__onAnnotationRemoved
274            )
275
276            self.__scene.set_scheme(scheme)
277
278    def scheme(self):
279        return self.__scheme
280
281    def scene(self):
282        return self.__scene
283
284    def view(self):
285        return self.__view
286
287    def setRegistry(self, registry):
288        # Is this method necessary
289        self.__registry = registry
290        if self.__scene:
291            self.__scene.set_registry(registry)
292            self.__quickMenu = None
293
294    def quickMenu(self):
295        """Return a quick menu instance for quick new node creation.
296        """
297        if self.__quickMenu is None:
298            menu = quickmenu.QuickMenu(self)
299            if self.__registry is not None:
300                menu.setModel(self.__registry.model())
301            self.__quickMenu = menu
302        return self.__quickMenu
303
304    def addNode(self, node):
305        """Add a new node to the scheme.
306        """
307        command = commands.AddNodeCommand(self.__scheme, node)
308        self.__undoStack.push(command)
309
310    def createNewNode(self, description):
311        """Create a new SchemeNode add at it to the document at left of the
312        last added node.
313
314        """
315        node = scheme.SchemeNode(description)
316
317        if self.scheme().nodes:
318            x, y = self.scheme().nodes[-1].position
319            node.position = (x + 150, y)
320        else:
321            node.position = (150, 150)
322
323        self.addNode(node)
324
325    def removeNode(self, node):
326        command = commands.RemoveNodeCommand(self.__scheme, node)
327        self.__undoStack.push(command)
328
329    def renameNode(self, node, title):
330        command = commands.RenameNodeCommand(self.__scheme, node, title)
331        self.__undoStack.push(command)
332
333    def addLink(self, link):
334        command = commands.AddLinkCommand(self.__scheme, link)
335        self.__undoStack.push(command)
336
337    def removeLink(self, link):
338        command = commands.RemoveLinkCommand(self.__scheme, link)
339        self.__undoStack.push(command)
340
341    def addAnnotation(self, annotation):
342        command = commands.AddAnnotationCommand(self.__scheme, annotation)
343        self.__undoStack.push(command)
344
345    def removeAnnotation(self, annotation):
346        command = commands.RemoveAnnotationCommand(self.__scheme, annotation)
347        self.__undoStack.push(command)
348
349    def removeSelected(self):
350        selected = self.scene().selectedItems()
351        if not selected:
352            return
353
354        self.__undoStack.beginMacro(self.tr("Remove"))
355        for item in selected:
356            print item
357            if isinstance(item, items.NodeItem):
358                node = self.scene().node_for_item(item)
359                self.__undoStack.push(
360                    commands.RemoveNodeCommand(self.__scheme, node)
361                )
362            elif isinstance(item, items.annotationitem.Annotation):
363                annot = self.scene().annotation_for_item(item)
364                self.__undoStack.push(
365                    commands.RemoveAnnotationCommand(self.__scheme, annot)
366                )
367        self.__undoStack.endMacro()
368
369    def selectAll(self):
370        for item in self.__scene.items():
371            if item.flags() & QGraphicsItem.ItemIsSelectable:
372                item.setSelected(True)
373
374    def toogleZoom(self, zoom):
375        view = self.view()
376        if zoom:
377            view.scale(1.5, 1.5)
378        else:
379            view.resetTransform()
380
381    def newArrowAnnotation(self):
382        handler = interactions.NewArrowAnnotation(self)
383        self.__scene.set_user_interaction_handler(handler)
384
385    def newTextAnnotation(self):
386        handler = interactions.NewTextAnnotation(self)
387        self.__scene.set_user_interaction_handler(handler)
388
389    def alignToGrid(self):
390        """Align nodes to a grid.
391        """
392        tile_size = 150
393        tiles = {}
394
395        nodes = sorted(self.scheme().nodes, key=attrgetter("position"))
396
397        if nodes:
398            self.__undoStack.beginMacro(self.tr("Align To Grid"))
399
400            for node in nodes:
401                x, y = node.position
402                x = int(round(float(x) / tile_size) * tile_size)
403                y = int(round(float(y) / tile_size) * tile_size)
404                while (x, y) in tiles:
405                    x += tile_size
406
407                self.__undoStack.push(
408                    commands.MoveNodeCommand(self.scheme(), node,
409                                             node.position, (x, y))
410                )
411
412                tiles[x, y] = node
413                self.__scene.item_for_node(node).setPos(x, y)
414
415            self.__undoStack.endMacro()
416
417    def selectedNodes(self):
418        return map(self.scene().node_for_item,
419                   self.scene().selected_node_items())
420
421    def openSelected(self):
422        selected = self.scene().selected_node_items()
423        for item in selected:
424            self.__onNodeActivate(item)
425
426    def editNodeTitle(self, node):
427        name, ok = QInputDialog.getText(
428                    self, self.tr("Rename"),
429                    unicode(self.tr("Enter a new name for the %r widget")) \
430                    % node.title,
431                    text=node.title
432                    )
433
434        if ok:
435            self.__undoStack.push(
436                commands.RenameNodeCommand(self.__scheme, node, node.title,
437                                           unicode(name))
438            )
439
440    def __onCleanChanged(self, clean):
441        if self.isWindowModified() != (not clean):
442            self.setWindowModified(not clean)
443            self.modificationChanged.emit(not clean)
444
445    def eventFilter(self, obj, event):
446        # Filter the scene's drag/drop events.
447        if obj is self.scene():
448            etype = event.type()
449            if  etype == QEvent.GraphicsSceneDragEnter or \
450                    etype == QEvent.GraphicsSceneDragMove:
451                mime_data = event.mimeData()
452                if mime_data.hasFormat(
453                        "application/vnv.orange-canvas.registry.qualified-name"
454                        ):
455                    event.acceptProposedAction()
456                return True
457            elif etype == QEvent.GraphicsSceneDrop:
458                data = event.mimeData()
459                qname = data.data(
460                    "application/vnv.orange-canvas.registry.qualified-name"
461                )
462                desc = self.__registry.widget(unicode(qname))
463                pos = event.scenePos()
464                node = scheme.SchemeNode(desc, position=(pos.x(), pos.y()))
465                self.addNode(node)
466                return True
467
468            elif etype == QEvent.GraphicsSceneMousePress:
469                return self.sceneMousePressEvent(event)
470            elif etype == QEvent.GraphicsSceneMouseMove:
471                return self.sceneMouseMoveEvent(event)
472            elif etype == QEvent.GraphicsSceneMouseRelease:
473                return self.sceneMouseReleaseEvent(event)
474            elif etype == QEvent.GraphicsSceneMouseDoubleClick:
475                return self.sceneMouseDoubleClickEvent(event)
476            elif etype == QEvent.KeyRelease:
477                return self.sceneKeyPressEvent(event)
478            elif etype == QEvent.KeyRelease:
479                return self.sceneKeyReleaseEvent(event)
480            elif etype == QEvent.GraphicsSceneContextMenu:
481                return self.sceneContextMenuEvent(event)
482
483        return QWidget.eventFilter(self, obj, event)
484
485    def sceneMousePressEvent(self, event):
486        scene = self.__scene
487        if scene.user_interaction_handler:
488            return False
489
490        pos = event.scenePos()
491
492        anchor_item = scene.item_at(pos, items.NodeAnchorItem)
493        if anchor_item and event.button() == Qt.LeftButton:
494            # Start a new link starting at item
495            handler = interactions.NewLinkAction(self)
496            scene.set_user_interaction_handler(handler)
497
498            return handler.mousePressEvent(event)
499
500        any_item = scene.item_at(pos)
501        if not any_item and event.button() == Qt.LeftButton:
502            # Start rect selection
503            handler = interactions.RectangleSelectionAction(self)
504            scene.set_user_interaction_handler(handler)
505            return handler.mousePressEvent(event)
506
507        if any_item and event.button() == Qt.LeftButton:
508            self.__possibleMouseItemsMove = True
509            self.__itemsMoving.clear()
510            self.__scene.node_item_position_changed.connect(
511                self.__onNodePositionChanged
512            )
513            self.__annotationGeomChanged.mapped[QObject].connect(
514                self.__onAnnotationGeometryChanged
515            )
516
517        return False
518
519    def sceneMouseMoveEvent(self, event):
520        scene = self.__scene
521        if scene.user_interaction_handler:
522            return False
523
524        return False
525
526    def sceneMouseReleaseEvent(self, event):
527        if event.button() == Qt.LeftButton and self.__possibleMouseItemsMove:
528            self.__possibleMouseItemsMove = False
529            self.__scene.node_item_position_changed.disconnect(
530                self.__onNodePositionChanged
531            )
532            self.__annotationGeomChanged.mapped[QObject].disconnect(
533                self.__onAnnotationGeometryChanged
534            )
535
536            if self.__itemsMoving:
537                self.__scene.mouseReleaseEvent(event)
538                stack = self.undoStack()
539                stack.beginMacro(self.tr("Move"))
540                for scheme_item, (old, new) in self.__itemsMoving.items():
541                    if isinstance(scheme_item, scheme.SchemeNode):
542                        command = commands.MoveNodeCommand(
543                            self.scheme(), scheme_item, old, new
544                        )
545                    elif isinstance(scheme_item, scheme.BaseSchemeAnnotation):
546                        command = commands.AnnotationGeometryChange(
547                            self.scheme(), scheme_item, old, new
548                        )
549                    else:
550                        continue
551
552                    stack.push(command)
553                stack.endMacro()
554
555                self.__itemsMoving.clear()
556                return True
557        return False
558
559    def sceneMouseDoubleClickEvent(self, event):
560        scene = self.__scene
561        if scene.user_interaction_handler:
562            return False
563
564        item = scene.item_at(event.scenePos())
565        if not item:
566            # Double click on an empty spot
567            # Create a new node quick
568            action = interactions.NewNodeAction(self)
569            action.create_new(event)
570            event.accept()
571            return True
572
573        item = scene.item_at(event.scenePos(), items.LinkItem)
574        if item is not None:
575            link = self.scene().link_for_item(item)
576            action = interactions.EditNodeLinksAction(self, link.source_node,
577                                                      link.sink_node)
578            action.edit_links()
579            event.accept()
580            return True
581
582        return False
583
584    def sceneKeyPressEvent(self, event):
585        return False
586
587    def sceneKeyReleaseEvent(self, event):
588        return False
589
590    def sceneContextMenuEvent(self, event):
591        return False
592
593    def __onSelectionChanged(self):
594        pass
595
596    def __onNodeAdded(self, node):
597        widget = self.__scheme.widget_for_node[node]
598        widget.widgetStateChanged.connect(self.__onWidgetStateChanged)
599
600    def __onNodeRemoved(self, node):
601        widget = self.__scheme.widget_for_node[node]
602        widget.widgetStateChanged.disconnect(self.__onWidgetStateChanged)
603
604    def __onWidgetStateChanged(self, *args):
605        widget = self.sender()
606        self.scheme()
607        widget_to_node = dict(reversed(item) for item in \
608                              self.__scheme.widget_for_node.items())
609        node = widget_to_node[widget]
610        item = self.__scene.item_for_node(node)
611
612        info = widget.widgetStateToHtml(True, False, False)
613        warning = widget.widgetStateToHtml(False, True, False)
614        error = widget.widgetStateToHtml(False, False, True)
615
616        item.setInfoMessage(info or None)
617        item.setWarningMessage(warning or None)
618        item.setErrorMessage(error or None)
619
620    def __onNodeActivate(self, item):
621        node = self.__scene.node_for_item(item)
622        widget = self.scheme().widget_for_node[node]
623        widget.show()
624        widget.raise_()
625
626    def __onNodePositionChanged(self, item, pos):
627        node = self.__scene.node_for_item(item)
628        new = (pos.x(), pos.y())
629        if node not in self.__itemsMoving:
630            self.__itemsMoving[node] = (node.position, new)
631        else:
632            old, _ = self.__itemsMoving[node]
633            self.__itemsMoving[node] = (old, new)
634
635    def __onAnnotationGeometryChanged(self, item):
636        annot = self.scene().annotation_for_item(item)
637        if annot not in self.__itemsMoving:
638            self.__itemsMoving[annot] = (annot.geometry,
639                                         geometry_from_annotation_item(item))
640        else:
641            old, _ = self.__itemsMoving[annot]
642            self.__itemsMoving[annot] = (old,
643                                         geometry_from_annotation_item(item))
644
645    def __onAnnotationAdded(self, item):
646        log.debug("Annotation added (%r)", item)
647        item.setFlag(QGraphicsItem.ItemIsSelectable)
648        item.setFlag(QGraphicsItem.ItemIsMovable)
649        item.setFlag(QGraphicsItem.ItemIsFocusable)
650
651        item.installSceneEventFilter(self.__focusListener)
652
653        if isinstance(item, items.ArrowAnnotation):
654            pass
655        elif isinstance(item, items.TextAnnotation):
656            # Make the annotation editable.
657            item.setTextInteractionFlags(Qt.TextEditorInteraction)
658
659            self.__editFinishedMapper.setMapping(item, item)
660            item.editingFinished.connect(
661                self.__editFinishedMapper.map
662            )
663
664        self.__annotationGeomChanged.setMapping(item, item)
665        item.geometryChanged.connect(
666            self.__annotationGeomChanged.map
667        )
668
669    def __onAnnotationRemoved(self, item):
670        log.debug("Annotation removed (%r)", item)
671        if isinstance(item, items.ArrowAnnotation):
672            pass
673        elif isinstance(item, items.TextAnnotation):
674            item.editingFinished.disconnect(
675                self.__editFinishedMapper.map
676            )
677
678        item.removeSceneEventFilter(self.__focusListener)
679
680        self.__annotationGeomChanged.removeMappings(item)
681        item.geometryChanged.disconnect(
682            self.__annotationGeomChanged.map
683        )
684
685    def __onItemFocusedIn(self, item):
686        """Annotation item has gained focus.
687        """
688        if not self.__scene.user_interaction_handler:
689            self.__startControlPointEdit(item)
690
691    def __onItemFocusedOut(self, item):
692        """Annotation item lost focus.
693        """
694        self.__endControlPointEdit()
695
696    def __onEditingFinished(self, item):
697        """Text annotation editing has finished.
698        """
699        annot = self.__scene.annotation_for_item(item)
700        text = unicode(item.toPlainText())
701        if annot.text != text:
702            self.__undoStack.push(
703                commands.TextChangeCommand(self.scheme(), annot,
704                                           annot.text, text)
705            )
706
707    def __toggleNewArrowAnnotation(self, checked):
708        if self.__newTextAnnotationAction.isChecked():
709            self.__newTextAnnotationAction.setChecked(not checked)
710        if not checked:
711            handler = self.__scene.user_interaction_handler
712            if isinstance(handler, interactions.NewArrowAnnotation):
713                # Cancel the interaction and restore the state
714                handler.cancel()
715                log.info("Canceled new arrow annotation")
716
717        else:
718            handler = interactions.NewArrowAnnotation(self)
719            # TODO: when does the interaction complete.
720            self.__scene.set_user_interaction_handler(handler)
721
722    def __toggleNewTextAnnotation(self, checked):
723        if self.__newArrowAnnotationAction.isChecked():
724            self.__newArrowAnnotationAction.setChecked(not checked)
725
726        if not checked:
727            handler = self.__scene.user_interaction_handler
728            if isinstance(handler, interactions.NewTextAnnotation):
729                # cancel the interaction and restore the state
730                handler.cancel()
731                log.info("Canceled new text annotation")
732
733        else:
734            handler = interactions.NewTextAnnotation(self)
735            self.__scene.set_user_interaction_handler(handler)
736
737    def __onCustomContextMenuRequested(self, pos):
738        scenePos = self.view().mapToScene(pos)
739        globalPos = self.view().mapToGlobal(pos)
740
741        item = self.scene().item_at(scenePos, items.NodeItem)
742        if item is not None:
743            self.window().widget_menu.popup(globalPos)
744            return
745
746        item = self.scene().item_at(scenePos, items.LinkItem)
747        if item is not None:
748            link = self.scene().link_for_item(item)
749            self.__linkEnableAction.setChecked(link.enabled)
750            self.__contextMenuTarget = link
751            self.__linkMenu.popup(globalPos)
752            return
753
754    def __toggleLinkEnabled(self, enabled):
755        """Link enabled state was toggled in the context menu.
756        """
757        if self.__contextMenuTarget:
758            link = self.__contextMenuTarget
759            command = commands.SetAttrCommand(
760                link, "enabled", enabled, name=self.tr("Set enabled"),
761            )
762            self.__undoStack.push(command)
763
764    def __linkRemove(self):
765        """Remove link was requested from the context menu.
766        """
767        if self.__contextMenuTarget:
768            self.removeLink(self.__contextMenuTarget)
769
770    def __linkReset(self):
771        """Link reset from the context menu was requested.
772        """
773        if self.__contextMenuTarget:
774            link = self.__contextMenuTarget
775            action = interactions.EditNodeLinksAction(
776                self, link.source_node, link.sink_node
777            )
778            action.edit_links()
779
780    def __startControlPointEdit(self, item):
781        """Start a control point edit interaction for item.
782        """
783        if isinstance(item, items.ArrowAnnotation):
784            handler = interactions.ResizeArrowAnnotation(self)
785        elif isinstance(item, items.TextAnnotation):
786            handler = interactions.ResizeTextAnnotation(self)
787        else:
788            log.warning("Unknown annotation item type %r" % item)
789            return
790
791        handler.editItem(item)
792        self.__scene.set_user_interaction_handler(handler)
793
794        log.info("Control point editing started (%r)." % item)
795
796    def __endControlPointEdit(self):
797        """End the current control point edit interaction.
798        """
799        handler = self.__scene.user_interaction_handler
800        if isinstance(handler, (interactions.ResizeArrowAnnotation,
801                                interactions.ResizeTextAnnotation)):
802            handler.commit()
803            handler.end()
804
805            log.info("Control point editing finished.")
806
807
808def geometry_from_annotation_item(item):
809    if isinstance(item, items.ArrowAnnotation):
810        line = item.line()
811        p1 = item.mapToScene(line.p1())
812        p2 = item.mapToScene(line.p2())
813        return ((p1.x(), p1.y()), (p2.x(), p2.y()))
814    elif isinstance(item, items.TextAnnotation):
815        geom = item.geometry()
816        return (geom.x(), geom.y(), geom.width(), geom.height())
Note: See TracBrowser for help on using the repository browser.