source: orange/Orange/OrangeCanvas/application/schemedocument.py @ 11124:90ef76dcd594

Revision 11124:90ef76dcd594, 9.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Added SchemeDocumentWidget class.

Line 
1"""
2Scheme Document widget.
3
4"""
5
6import os
7import StringIO
8from operator import attrgetter
9
10from PyQt4.QtGui import (
11    QWidget, QFrame, QMenu,  QInputDialog, QVBoxLayout, QSizePolicy,
12    QPainter, QAction
13)
14
15from PyQt4.QtCore import Qt, QEvent, pyqtSignal as Signal
16
17from .. import scheme
18from ..canvas.scene import CanvasScene
19from ..canvas.view import CanvasView
20from ..canvas import items
21from ..canvas import interactions
22
23
24class SchemeDocumentWidget(QWidget):
25    """A container widget for an open scheme.
26    """
27
28    node_hovered = Signal(scheme.SchemeNode)
29    link_hovered = Signal(scheme.SchemeLink)
30    title_changed = Signal(unicode)
31
32    def __init__(self, parent=None, scheme=None):
33        QWidget.__init__(self, parent)
34        self.registry = None
35        self.scheme = None
36        self.scene = None
37        self.view = None
38
39        self.setup_ui()
40
41        if scheme:
42            self.set_scheme(scheme)
43
44    def setup_ui(self):
45        layout = QVBoxLayout()
46        layout.setContentsMargins(0, 0, 0, 0)
47        self.setLayout(layout)
48        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
49
50        self.scene = CanvasScene(self)
51        self.scene.setStickyFocus(False)
52        self.scene.node_item_activated.connect(self._on_node_activate)
53        self.scene.node_item_position_changed.connect(
54            self._on_node_position_changed
55        )
56        self.scene.installEventFilter(self)
57
58        self.view = CanvasView(self.scene, self)
59        self.view.setFrameShape(QFrame.NoFrame)
60        self.view.setRenderHints(QPainter.Antialiasing | \
61                                 QPainter.TextAntialiasing)
62        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
63        self.view.customContextMenuRequested.connect(self._on_context_event)
64
65        self.layout().addWidget(self.view)
66
67        self.link_enable_action = \
68            QAction(self.tr("Enabled"), self,
69                    objectName="link-enable-action",
70                    triggered=self.toggle_link_enabled,
71                    checkable=True,
72                    )
73
74        self.link_remove_action = \
75            QAction(self.tr("Remove"), self,
76                    objectName="link-remove-action",
77                    triggered=self.link_remove,
78                    toolTip=self.tr("Remove link."),
79                    )
80
81        self.link_reset_action = \
82            QAction(self.tr("Reset Signals"), self,
83                    objectName="link-reset-action",
84                    triggered=self.link_reset,
85                    )
86
87        self.link_menu = QMenu(self)
88        self.link_menu.addAction(self.link_enable_action)
89        self.link_menu.addSeparator()
90        self.link_menu.addAction(self.link_remove_action)
91        self.link_menu.addAction(self.link_reset_action)
92
93    def set_registry(self, registry):
94        self.registry = registry
95        if self.scene is not None:
96            self.scene.set_registry(registry)
97
98    def set_scheme(self, scheme):
99        """Set the scheme for the document.
100
101        .. note:: new CanvasScene is created for the new scheme.
102
103        """
104        # Schedule delete for the old scene.
105        self.scene.clear()
106        self.scene.removeEventFilter(self)
107        self.scene.deleteLater()
108        del self.scene
109
110        self.scene = CanvasScene(self)
111        self.scene.setStickyFocus(False)
112        self.scene.node_item_activated.connect(self._on_node_activate)
113        self.scene.node_item_position_changed.connect(
114            self._on_node_position_changed
115        )
116        self.scene.set_registry(self.registry)
117        self.scene.set_scheme(scheme)
118        self.view.setScene(self.scene)
119        self.scene.installEventFilter(self)
120
121        if self.scheme is not None:
122            self.scheme.close_all_open_widgets()
123            self.scheme.title_changed.disconnect(self.title_changed)
124
125        self.scheme = scheme
126
127        if self.scheme:
128            self.scheme.title_changed.connect(self.title_changed)
129            self.title_changed.emit(self.scheme.title)
130
131    def create_new_node(self, desc, position=None):
132        scheme = self.scheme
133        node = scheme.new_node(desc, position=position)
134        if position is None:
135            item = self.scene.item_for_node(node)
136            node.position = (item.pos().x(), item.pos().y())
137
138    def _on_node_activate(self, item):
139        node = self.scene.node_for_item(item)
140        widget = self.scheme.widget_for_node[node]
141        widget.show()
142        widget.raise_()
143
144    def _on_context_event(self, pos):
145        """A Context menu has been requested in the scene/view at
146        position `pos`.
147
148        """
149        scene_pos = self.view.mapToScene(pos)
150        global_pos = self.view.mapToGlobal(pos)
151        item = self.scene.item_at(scene_pos, items.NodeBodyItem)
152        if item:
153            # Reuse the main window's widget menu.
154            window = self.window()
155            window.widget_menu.popup(global_pos)
156            return
157
158        item = self.scene.item_at(scene_pos, items.LinkCurveItem)
159        item = self.scene.item_at(scene_pos, items.LinkItem)
160        if item:
161            self._hovered_link = self.scene.link_for_item(item)
162            self.link_enable_action.setChecked(self._hovered_link.enabled)
163            self.link_menu.popup(global_pos)
164
165    def toggle_link_enabled(self, state):
166        self._hovered_link.enabled = state
167
168    def link_remove(self):
169        self.scheme.remove_link(self._hovered_link)
170
171    def link_reset(self):
172        link = self._hovered_link
173        action = interactions.EditNodeLinksAction(
174                    self.scene, link.source_node, link.sink_node
175                )
176
177        action.edit_links()
178
179    def _on_node_position_changed(self, item, pos):
180        node = self.scene.node_for_item(item)
181        node.position = (pos.x(), pos.y())
182
183    def selected_nodes(self):
184        """Return all current selected nodes.
185        """
186        selected = self.scene.selected_node_items()
187        return map(self.scene.node_for_item, selected)
188
189    def remove_selected(self):
190        selected = self.scene.selected_node_items()
191        nodes = map(self.scene.node_for_item, selected)
192        for node in nodes:
193            self.scheme.remove_node(node)
194
195    def select_all(self):
196        for item in self.scene.node_items:
197            item.setSelected(True)
198
199    def open_selected(self):
200        for item in self.scene.selected_node_items():
201            self._on_node_activate(item)
202
203    def is_modified(self):
204        if self.scheme.path and os.path.exists(self.scheme.path):
205            saved_scheme_str = open(self.scheme.path, "rb")
206            curr_scheme_str = scheme_to_string(self.scheme)
207            return curr_scheme_str != saved_scheme_str
208        else:
209            return len(self.scheme.nodes) != 0
210
211    def edit_node_title(self, node):
212        """Edit a `SchemeNode` title.
213        """
214        name, ok = QInputDialog.getText(
215                    self, self.tr("Rename"),
216                    self.tr("Enter a new name for the %r widget") % node.title,
217                    text=node.title
218                    )
219        if ok:
220            node.title = unicode(name)
221
222    def align_to_grid(self):
223        """Align nodes to a grid.
224        """
225        tile_size = 150
226        tiles = {}
227
228        nodes = sorted(self.scheme.nodes, key=attrgetter("position"))
229        for node in nodes:
230            x, y = node.position
231            x = int(round(float(x) / tile_size) * tile_size)
232            y = int(round(float(y) / tile_size) * tile_size)
233            while (x, y) in tiles:
234                x += tile_size
235            node.position = (x, y)
236
237            tiles[x, y] = node
238            self.scene.item_for_node(node).setPos(x, y)
239
240    def new_arrow_annotation(self):
241        """Enter a new arrow annotation edit mode.
242        """
243        handler = interactions.NewArrowAnnotation(self.scene)
244        self.scene.set_user_interaction_handler(handler)
245
246    def new_text_annotation(self):
247        """Enter a new text annotation edit mode.
248        """
249        handler = interactions.NewTextAnnotation(self.scene)
250        self.scene.set_user_interaction_handler(handler)
251
252    def eventFilter(self, obj, event):
253        # Filter the scene's drag/drop events.
254        if obj is self.scene and \
255                event.type() == QEvent.GraphicsSceneDragEnter or \
256                event.type() == QEvent.GraphicsSceneDragMove:
257            mime_data = event.mimeData()
258            if mime_data.hasFormat(
259                    "application/vnv.orange-canvas.registry.qualified-name"):
260                event.acceptProposedAction()
261            return True
262        elif obj is self.scene and \
263                event.type() == QEvent.GraphicsSceneDrop:
264            data = event.mimeData()
265            qname = data.data(
266                    "application/vnv.orange-canvas.registry.qualified-name")
267            desc = self.registry.widget(unicode(qname))
268            pos = event.scenePos()
269            self.create_new_node(desc, position=(pos.x(), pos.y()))
270            return True
271
272        return QWidget.eventFilter(self, obj, event)
273
274
275def scheme_to_string(scheme):
276    stream = StringIO.StringIO()
277    scheme.save_to(stream)
278    return stream.getvalue()
Note: See TracBrowser for help on using the repository browser.