source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11413:98d8cc8906c4

Revision 11413:98d8cc8906c4, 59.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Replaced QTimer.singleShot with a QTimer instance to schedule preview item updates.

QTimer.singleShot event can outlive the PreviewModel causing a RuntimeError.

Line 
1"""
2Orange Canvas Main Window
3
4"""
5import os
6import sys
7import logging
8import operator
9from functools import partial
10
11import pkg_resources
12
13from PyQt4.QtGui import (
14    QMainWindow, QWidget, QAction, QActionGroup, QMenu, QMenuBar, QDialog,
15    QFileDialog, QMessageBox, QVBoxLayout, QSizePolicy, QColor, QKeySequence,
16    QIcon, QToolBar, QToolButton, QDockWidget, QDesktopServices, QApplication
17)
18
19from PyQt4.QtCore import (
20    Qt, QEvent, QSize, QUrl, QTimer, QFile, QByteArray
21)
22
23from PyQt4.QtNetwork import QNetworkDiskCache
24
25from PyQt4.QtWebKit import QWebView
26
27from PyQt4.QtCore import pyqtProperty as Property
28
29# Compatibility with PyQt < v4.8.3
30from ..utils.qtcompat import QSettings
31
32from ..gui.dropshadow import DropShadowFrame
33from ..gui.dock import CollapsibleDockWidget
34from ..gui.quickhelp import QuickHelpTipEvent
35from ..gui.utils import message_critical, message_question, \
36                        message_warning, message_information
37
38from ..help import HelpManager
39
40from .canvastooldock import CanvasToolDock, QuickCategoryToolbar
41from .aboutdialog import AboutDialog
42from .schemeinfo import SchemeInfoDialog
43from .outputview import OutputView
44from .settings import UserSettingsDialog
45from ..document.schemeedit import SchemeEditWidget
46
47from ..scheme import widgetsscheme
48from ..scheme.readwrite import parse_scheme, sniff_version
49
50from . import welcomedialog
51from ..preview import previewdialog, previewmodel
52
53from .. import config
54
55from . import tutorials
56
57log = logging.getLogger(__name__)
58
59# TODO: Orange Version in the base link
60
61BASE_LINK = "http://orange.biolab.si/"
62
63LINKS = \
64    {"start-using": BASE_LINK + "start-using/",
65     "tutorial": BASE_LINK + "tutorial/",
66     "reference": BASE_LINK + "doc/"
67     }
68
69
70def style_icons(widget, standard_pixmap):
71    """Return the Qt standard pixmap icon.
72    """
73    return QIcon(widget.style().standardPixmap(standard_pixmap))
74
75
76def canvas_icons(name):
77    """Return the named canvas icon.
78    """
79    icon_file = QFile("canvas_icons:" + name)
80    if icon_file.exists():
81        return QIcon("canvas_icons:" + name)
82    else:
83        return QIcon(pkg_resources.resource_filename(
84                      config.__name__,
85                      os.path.join("icons", name))
86                     )
87
88
89class FakeToolBar(QToolBar):
90    """A Toolbar with no contents (used to reserve top and bottom margins
91    on the main window).
92
93    """
94    def __init__(self, *args, **kwargs):
95        QToolBar.__init__(self, *args, **kwargs)
96        self.setFloatable(False)
97        self.setMovable(False)
98
99        # Don't show the tool bar action in the main window's
100        # context menu.
101        self.toggleViewAction().setVisible(False)
102
103    def paintEvent(self, event):
104        # Do nothing.
105        pass
106
107
108class DockableWindow(QDockWidget):
109    def __init__(self, *args, **kwargs):
110        QDockWidget.__init__(self, *args, **kwargs)
111
112        # Fist show after floating
113        self.__firstShow = True
114        # Flags to use while floating
115        self.__windowFlags = Qt.Window
116        self.setWindowFlags(self.__windowFlags)
117        self.topLevelChanged.connect(self.__on_topLevelChanged)
118        self.visibilityChanged.connect(self.__on_visbilityChanged)
119
120        self.__closeAction = QAction(self.tr("Close"), self,
121                                     shortcut=QKeySequence.Close,
122                                     triggered=self.close,
123                                     enabled=self.isFloating())
124        self.topLevelChanged.connect(self.__closeAction.setEnabled)
125        self.addAction(self.__closeAction)
126
127    def setFloatingWindowFlags(self, flags):
128        """
129        Set `windowFlags` to use while the widget is floating (undocked).
130        """
131        if self.__windowFlags != flags:
132            self.__windowFlags = flags
133            if self.isFloating():
134                self.__fixWindowFlags()
135
136    def floatingWindowFlags(self):
137        """
138        Return the `windowFlags` used when the widget is floating.
139        """
140        return self.__windowFlags
141
142    def __fixWindowFlags(self):
143        if self.isFloating():
144            update_window_flags(self, self.__windowFlags)
145
146    def __on_topLevelChanged(self, floating):
147        if floating:
148            self.__firstShow = True
149            self.__fixWindowFlags()
150
151    def __on_visbilityChanged(self, visible):
152        if visible and self.isFloating() and self.__firstShow:
153            self.__firstShow = False
154            self.__fixWindowFlags()
155
156
157def update_window_flags(widget, flags):
158    currflags = widget.windowFlags()
159    if int(flags) != int(currflags):
160        hidden = widget.isHidden()
161        widget.setWindowFlags(flags)
162        # setting the flags hides the widget
163        if not hidden:
164            widget.show()
165
166
167class CanvasMainWindow(QMainWindow):
168    SETTINGS_VERSION = 2
169
170    def __init__(self, *args):
171        QMainWindow.__init__(self, *args)
172
173        self.__scheme_margins_enabled = True
174        self.__document_title = "untitled"
175        self.__first_show = True
176
177        self.widget_registry = None
178
179        self.last_scheme_dir = QDesktopServices.StandardLocation(
180            QDesktopServices.DocumentsLocation
181        )
182
183        self.recent_schemes = config.recent_schemes()
184
185        self.open_in_external_browser = False
186        self.help = HelpManager(self)
187
188        self.setup_actions()
189        self.setup_ui()
190        self.setup_menu()
191
192        self.restore()
193
194        self.resize(800, 600)
195
196    def setup_ui(self):
197        """Setup main canvas ui
198        """
199
200        log.info("Setting up Canvas main window.")
201
202        # Two dummy tool bars to reserve space
203        self.__dummy_top_toolbar = FakeToolBar(
204                            objectName="__dummy_top_toolbar")
205        self.__dummy_bottom_toolbar = FakeToolBar(
206                            objectName="__dummy_bottom_toolbar")
207
208        self.__dummy_top_toolbar.setFixedHeight(20)
209        self.__dummy_bottom_toolbar.setFixedHeight(20)
210
211        self.addToolBar(Qt.TopToolBarArea, self.__dummy_top_toolbar)
212        self.addToolBar(Qt.BottomToolBarArea, self.__dummy_bottom_toolbar)
213
214        self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea)
215        self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea)
216
217        self.setDockOptions(QMainWindow.AnimatedDocks)
218        # Create an empty initial scheme inside a container with fixed
219        # margins.
220        w = QWidget()
221        w.setLayout(QVBoxLayout())
222        w.layout().setContentsMargins(20, 0, 10, 0)
223
224        self.scheme_widget = SchemeEditWidget()
225        self.scheme_widget.setScheme(widgetsscheme.WidgetsScheme())
226
227        w.layout().addWidget(self.scheme_widget)
228
229        self.setCentralWidget(w)
230
231        # Drop shadow around the scheme document
232        frame = DropShadowFrame(radius=15)
233        frame.setColor(QColor(0, 0, 0, 100))
234        frame.setWidget(self.scheme_widget)
235
236        # Main window title and title icon.
237        self.set_document_title(self.scheme_widget.scheme().title)
238        self.scheme_widget.titleChanged.connect(self.set_document_title)
239        self.scheme_widget.modificationChanged.connect(self.setWindowModified)
240
241        self.setWindowIcon(canvas_icons("Get Started.svg"))
242
243        # QMainWindow's Dock widget
244        self.dock_widget = CollapsibleDockWidget(objectName="main-area-dock")
245        self.dock_widget.setFeatures(QDockWidget.DockWidgetMovable | \
246                                     QDockWidget.DockWidgetClosable)
247
248        self.dock_widget.setAllowedAreas(Qt.LeftDockWidgetArea | \
249                                         Qt.RightDockWidgetArea)
250
251        # Main canvas tool dock (with widget toolbox, common actions.
252        # This is the widget that is shown when the dock is expanded.
253        canvas_tool_dock = CanvasToolDock(objectName="canvas-tool-dock")
254        canvas_tool_dock.setSizePolicy(QSizePolicy.Fixed,
255                                       QSizePolicy.MinimumExpanding)
256
257        # Bottom tool bar
258        self.canvas_toolbar = canvas_tool_dock.toolbar
259        self.canvas_toolbar.setIconSize(QSize(25, 25))
260        self.canvas_toolbar.setFixedHeight(28)
261        self.canvas_toolbar.layout().setSpacing(1)
262
263        # Widgets tool box
264        self.widgets_tool_box = canvas_tool_dock.toolbox
265        self.widgets_tool_box.setObjectName("canvas-toolbox")
266        self.widgets_tool_box.setTabButtonHeight(30)
267        self.widgets_tool_box.setTabIconSize(QSize(26, 26))
268        self.widgets_tool_box.setButtonSize(QSize(64, 84))
269        self.widgets_tool_box.setIconSize(QSize(48, 48))
270
271        self.widgets_tool_box.triggered.connect(
272            self.on_tool_box_widget_activated
273        )
274
275        self.dock_help = canvas_tool_dock.help
276        self.dock_help.setMaximumHeight(150)
277        self.dock_help.document().setDefaultStyleSheet("h3, a {color: orange;}")
278
279        self.dock_help_action = canvas_tool_dock.toogleQuickHelpAction()
280        self.dock_help_action.setText(self.tr("Show Help"))
281        self.dock_help_action.setIcon(canvas_icons("Info.svg"))
282
283        self.canvas_tool_dock = canvas_tool_dock
284
285        # Dock contents when collapsed (a quick category tool bar, ...)
286        dock2 = QWidget(objectName="canvas-quick-dock")
287        dock2.setLayout(QVBoxLayout())
288        dock2.layout().setContentsMargins(0, 0, 0, 0)
289        dock2.layout().setSpacing(0)
290        dock2.layout().setSizeConstraint(QVBoxLayout.SetFixedSize)
291
292        self.quick_category = QuickCategoryToolbar()
293        self.quick_category.setButtonSize(QSize(38, 30))
294        self.quick_category.actionTriggered.connect(
295            self.on_quick_category_action
296        )
297
298        tool_actions = self.current_document().toolbarActions()
299
300        (self.canvas_zoom_action, self.canvas_align_to_grid_action,
301         self.canvas_text_action, self.canvas_arrow_action,) = tool_actions
302
303        self.canvas_zoom_action.setIcon(canvas_icons("Search.svg"))
304        self.canvas_align_to_grid_action.setIcon(canvas_icons("Grid.svg"))
305        self.canvas_text_action.setIcon(canvas_icons("Text Size.svg"))
306        self.canvas_arrow_action.setIcon(canvas_icons("Arrow.svg"))
307
308        dock_actions = [self.show_properties_action] + \
309                       tool_actions + \
310                       [self.freeze_action,
311                        self.dock_help_action]
312
313        # Tool bar in the collapsed dock state (has the same actions as
314        # the tool bar in the CanvasToolDock
315        actions_toolbar = QToolBar(orientation=Qt.Vertical)
316        actions_toolbar.setFixedWidth(38)
317        actions_toolbar.layout().setSpacing(0)
318
319        actions_toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly)
320
321        for action in dock_actions:
322            self.canvas_toolbar.addAction(action)
323            button = self.canvas_toolbar.widgetForAction(action)
324            button.setPopupMode(QToolButton.DelayedPopup)
325
326            actions_toolbar.addAction(action)
327            button = actions_toolbar.widgetForAction(action)
328            button.setFixedSize(38, 30)
329            button.setPopupMode(QToolButton.DelayedPopup)
330
331        dock2.layout().addWidget(self.quick_category)
332        dock2.layout().addWidget(actions_toolbar)
333
334        self.dock_widget.setAnimationEnabled(False)
335        self.dock_widget.setExpandedWidget(self.canvas_tool_dock)
336        self.dock_widget.setCollapsedWidget(dock2)
337        self.dock_widget.setExpanded(True)
338
339        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_widget)
340        self.dock_widget.dockLocationChanged.connect(
341            self._on_dock_location_changed
342        )
343
344        self.output_dock = DockableWindow(self.tr("Output"), self,
345                                          objectName="output-dock")
346        self.output_dock.setAllowedAreas(Qt.BottomDockWidgetArea)
347        output_view = OutputView()
348        # Set widget before calling addDockWidget, otherwise the dock
349        # does not resize properly on first undock
350        self.output_dock.setWidget(output_view)
351        self.output_dock.hide()
352
353        self.help_dock = DockableWindow(self.tr("Help"), self,
354                                        objectName="help-dock")
355        self.help_dock.setAllowedAreas(Qt.NoDockWidgetArea)
356        self.help_view = QWebView()
357        manager = self.help_view.page().networkAccessManager()
358        cache = QNetworkDiskCache()
359        cache.setCacheDirectory(
360            os.path.join(config.cache_dir(), "help", "help-view-cache")
361        )
362        manager.setCache(cache)
363        self.help_dock.setWidget(self.help_view)
364        self.help_dock.hide()
365
366        self.setMinimumSize(600, 500)
367
368    def setup_actions(self):
369        """Initialize main window actions.
370        """
371
372        self.new_action = \
373            QAction(self.tr("New"), self,
374                    objectName="action-new",
375                    toolTip=self.tr("Open a new scheme."),
376                    triggered=self.new_scheme,
377                    shortcut=QKeySequence.New,
378                    icon=canvas_icons("New.svg")
379                    )
380
381        self.open_action = \
382            QAction(self.tr("Open"), self,
383                    objectName="action-open",
384                    toolTip=self.tr("Open a scheme."),
385                    triggered=self.open_scheme,
386                    shortcut=QKeySequence.Open,
387                    icon=canvas_icons("Open.svg")
388                    )
389
390        self.save_action = \
391            QAction(self.tr("Save"), self,
392                    objectName="action-save",
393                    toolTip=self.tr("Save current scheme."),
394                    triggered=self.save_scheme,
395                    shortcut=QKeySequence.Save,
396                    )
397
398        self.save_as_action = \
399            QAction(self.tr("Save As ..."), self,
400                    objectName="action-save-as",
401                    toolTip=self.tr("Save current scheme as."),
402                    triggered=self.save_scheme_as,
403                    shortcut=QKeySequence.SaveAs,
404                    )
405
406        self.quit_action = \
407            QAction(self.tr("Quit"), self,
408                    objectName="quit-action",
409                    toolTip=self.tr("Quit Orange Canvas."),
410                    triggered=self.quit,
411                    menuRole=QAction.QuitRole,
412                    shortcut=QKeySequence.Quit,
413                    )
414
415        self.welcome_action = \
416            QAction(self.tr("Welcome"), self,
417                    objectName="welcome-action",
418                    toolTip=self.tr("Show welcome screen."),
419                    triggered=self.welcome_dialog,
420                    )
421
422        self.get_started_action = \
423            QAction(self.tr("Get Started"), self,
424                    objectName="get-started-action",
425                    toolTip=self.tr("View a 'Getting Started' video."),
426                    triggered=self.get_started,
427                    icon=canvas_icons("Get Started.svg")
428                    )
429
430        self.tutorials_action = \
431            QAction(self.tr("Tutorials"), self,
432                    objectName="tutorial-action",
433                    toolTip=self.tr("Browse tutorials."),
434                    triggered=self.tutorial_scheme,
435                    icon=canvas_icons("Tutorials.svg")
436                    )
437
438        self.documentation_action = \
439            QAction(self.tr("Documentation"), self,
440                    objectName="documentation-action",
441                    toolTip=self.tr("View reference documentation."),
442                    triggered=self.documentation,
443                    icon=canvas_icons("Documentation.svg")
444                    )
445
446        self.about_action = \
447            QAction(self.tr("About"), self,
448                    objectName="about-action",
449                    toolTip=self.tr("Show about dialog."),
450                    triggered=self.open_about,
451                    menuRole=QAction.AboutRole,
452                    )
453
454        # Action group for for recent scheme actions
455        self.recent_scheme_action_group = \
456            QActionGroup(self, exclusive=False,
457                         objectName="recent-action-group",
458                         triggered=self._on_recent_scheme_action)
459
460        self.recent_action = \
461            QAction(self.tr("Browse Recent"), self,
462                    objectName="recent-action",
463                    toolTip=self.tr("Browse and open a recent scheme."),
464                    triggered=self.recent_scheme,
465                    shortcut=QKeySequence(Qt.ControlModifier | \
466                                          (Qt.ShiftModifier | Qt.Key_R)),
467                    icon=canvas_icons("Recent.svg")
468                    )
469
470        self.reload_last_action = \
471            QAction(self.tr("Reload Last Scheme"), self,
472                    objectName="reload-last-action",
473                    toolTip=self.tr("Reload last open scheme."),
474                    triggered=self.reload_last,
475                    shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_R)
476                    )
477
478        self.clear_recent_action = \
479            QAction(self.tr("Clear Menu"), self,
480                    objectName="clear-recent-menu-action",
481                    toolTip=self.tr("Clear recent menu."),
482                    triggered=self.clear_recent_schemes
483                    )
484
485        self.show_properties_action = \
486            QAction(self.tr("Show Properties"), self,
487                    objectName="show-properties-action",
488                    toolTip=self.tr("Show scheme properties."),
489                    triggered=self.show_scheme_properties,
490                    shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_I),
491                    icon=canvas_icons("Document Info.svg")
492                    )
493
494        self.canvas_settings_action = \
495            QAction(self.tr("Settings"), self,
496                    objectName="canvas-settings-action",
497                    toolTip=self.tr("Set application settings."),
498                    triggered=self.open_canvas_settings,
499                    menuRole=QAction.PreferencesRole,
500                    shortcut=QKeySequence.Preferences
501                    )
502
503        self.show_output_action = \
504            QAction(self.tr("Show Output View"), self,
505                    toolTip=self.tr("Show application output."),
506                    triggered=self.show_output_view,
507                    )
508
509        if sys.platform == "darwin":
510            # Actions for native Mac OSX look and feel.
511            self.minimize_action = \
512                QAction(self.tr("Minimize"), self,
513                        triggered=self.showMinimized,
514                        shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_M)
515                        )
516
517            self.zoom_action = \
518                QAction(self.tr("Zoom"), self,
519                        objectName="application-zoom",
520                        triggered=self.toggleMaximized,
521                        )
522
523        self.freeze_action = \
524            QAction(self.tr("Freeze"), self,
525                    objectName="signal-freeze-action",
526                    checkable=True,
527                    toolTip=self.tr("Freeze signal propagation."),
528                    triggered=self.set_signal_freeze,
529                    icon=canvas_icons("Pause.svg")
530                    )
531
532        # Gets assigned in setup_ui (the action is defined in CanvasToolDock)
533        # TODO: This is bad (should be moved here).
534        self.dock_help_action = None
535
536        self.toogle_margins_action = \
537            QAction(self.tr("Show Scheme Margins"), self,
538                    checkable=True,
539                    checked=True,
540                    toolTip=self.tr("Show margins around the scheme view."),
541                    toggled=self.set_scheme_margins_enabled
542                    )
543
544    def setup_menu(self):
545        menu_bar = QMenuBar()
546
547        # File menu
548        file_menu = QMenu(self.tr("&File"), menu_bar)
549        file_menu.addAction(self.new_action)
550        file_menu.addAction(self.open_action)
551        file_menu.addAction(self.reload_last_action)
552
553        # File -> Open Recent submenu
554        self.recent_menu = QMenu(self.tr("Open Recent"), file_menu)
555        file_menu.addMenu(self.recent_menu)
556        file_menu.addSeparator()
557        file_menu.addAction(self.save_action)
558        file_menu.addAction(self.save_as_action)
559        file_menu.addSeparator()
560        file_menu.addAction(self.show_properties_action)
561        file_menu.addAction(self.quit_action)
562
563        self.recent_menu.addAction(self.recent_action)
564
565        # Store the reference to separator for inserting recent
566        # schemes into the menu in `add_recent_scheme`.
567        self.recent_menu_begin = self.recent_menu.addSeparator()
568
569        # Add recent items.
570        for title, filename in self.recent_schemes:
571            action = QAction(title or self.tr("untitled"), self,
572                             toolTip=filename)
573
574            action.setData(filename)
575            self.recent_menu.addAction(action)
576            self.recent_scheme_action_group.addAction(action)
577
578        self.recent_menu.addSeparator()
579        self.recent_menu.addAction(self.clear_recent_action)
580        menu_bar.addMenu(file_menu)
581
582        editor_menus = self.scheme_widget.menuBarActions()
583
584        # WARNING: Hard coded order, should lookup the action text
585        # and determine the proper order
586        self.edit_menu = editor_menus[0].menu()
587        self.widget_menu = editor_menus[1].menu()
588
589        # Edit menu
590        menu_bar.addMenu(self.edit_menu)
591
592        # View menu
593        self.view_menu = QMenu(self.tr("&View"), self)
594        self.toolbox_menu = QMenu(self.tr("Widget Toolbox Style"),
595                                  self.view_menu)
596        self.toolbox_menu_group = \
597            QActionGroup(self, objectName="toolbox-menu-group")
598
599        a1 = self.toolbox_menu.addAction(self.tr("Tool Box"))
600        a2 = self.toolbox_menu.addAction(self.tr("Tool List"))
601        self.toolbox_menu_group.addAction(a1)
602        self.toolbox_menu_group.addAction(a2)
603
604        self.view_menu.addMenu(self.toolbox_menu)
605        self.view_menu.addSeparator()
606        self.view_menu.addAction(self.toogle_margins_action)
607        menu_bar.addMenu(self.view_menu)
608
609        # Options menu
610        self.options_menu = QMenu(self.tr("&Options"), self)
611        self.options_menu.addAction(self.show_output_action)
612#        self.options_menu.addAction("Add-ons")
613#        self.options_menu.addAction("Developers")
614#        self.options_menu.addAction("Run Discovery")
615#        self.options_menu.addAction("Show Canvas Log")
616#        self.options_menu.addAction("Attach Python Console")
617        self.options_menu.addSeparator()
618        self.options_menu.addAction(self.canvas_settings_action)
619
620        # Widget menu
621        menu_bar.addMenu(self.widget_menu)
622
623        if sys.platform == "darwin":
624            # Mac OS X native look and feel.
625            self.window_menu = QMenu(self.tr("Window"), self)
626            self.window_menu.addAction(self.minimize_action)
627            self.window_menu.addAction(self.zoom_action)
628            menu_bar.addMenu(self.window_menu)
629
630        menu_bar.addMenu(self.options_menu)
631
632        # Help menu.
633        self.help_menu = QMenu(self.tr("&Help"), self)
634        self.help_menu.addAction(self.about_action)
635        self.help_menu.addAction(self.welcome_action)
636        self.help_menu.addAction(self.tutorials_action)
637        self.help_menu.addAction(self.documentation_action)
638        menu_bar.addMenu(self.help_menu)
639
640        self.setMenuBar(menu_bar)
641
642    def restore(self):
643        """Restore the main window state from saved settings.
644        """
645        QSettings.setDefaultFormat(QSettings.IniFormat)
646        settings = QSettings()
647        settings.beginGroup("mainwindow")
648
649        self.dock_widget.setExpanded(
650            settings.value("canvasdock/expanded", True, type=bool)
651        )
652
653        floatable = settings.value("toolbox-dock-floatable", False, type=bool)
654        if floatable:
655            self.dock_widget.setFeatures(self.dock_widget.features() | \
656                                         QDockWidget.DockWidgetFloatable)
657
658        self.widgets_tool_box.setExclusive(
659            settings.value("toolbox-dock-exclusive", False, type=bool)
660        )
661
662        self.toogle_margins_action.setChecked(
663            settings.value("scheme-margins-enabled", True, type=bool)
664        )
665
666        default_dir = QDesktopServices.storageLocation(
667            QDesktopServices.DocumentsLocation
668        )
669
670        self.last_scheme_dir = settings.value("last-scheme-dir", default_dir,
671                                              type=unicode)
672
673        if not os.path.exists(self.last_scheme_dir):
674            # if directory no longer exists reset the saved location.
675            self.last_scheme_dir = default_dir
676
677        self.canvas_tool_dock.setQuickHelpVisible(
678            settings.value("quick-help/visible", True, type=bool)
679        )
680
681        self.__update_from_settings()
682
683    def set_document_title(self, title):
684        """Set the document title (and the main window title). If `title`
685        is an empty string a default 'untitled' placeholder will be used.
686
687        """
688        if self.__document_title != title:
689            self.__document_title = title
690
691            if not title:
692                # TODO: should the default name be platform specific
693                title = self.tr("untitled")
694
695            self.setWindowTitle(title + "[*]")
696
697    def document_title(self):
698        """Return the document title.
699        """
700        return self.__document_title
701
702    def set_widget_registry(self, widget_registry):
703        """Set widget registry.
704        """
705        if self.widget_registry is not None:
706            # Clear the dock widget and popup.
707            pass
708
709        self.widget_registry = widget_registry
710        self.widgets_tool_box.setModel(widget_registry.model())
711        self.quick_category.setModel(widget_registry.model())
712
713        self.scheme_widget.setRegistry(widget_registry)
714
715        self.help.set_registry(widget_registry)
716
717        # Restore possibly saved widget toolbox tab states
718        settings = QSettings()
719
720        state = settings.value("mainwindow/widgettoolbox/state",
721                                defaultValue=QByteArray(),
722                                type=QByteArray)
723        if state:
724            self.widgets_tool_box.restoreState(state)
725
726    def set_quick_help_text(self, text):
727        self.canvas_tool_dock.help.setText(text)
728
729    def current_document(self):
730        return self.scheme_widget
731
732    def on_tool_box_widget_activated(self, action):
733        """A widget action in the widget toolbox has been activated.
734        """
735        widget_desc = action.data().toPyObject()
736        if widget_desc:
737            scheme_widget = self.current_document()
738            if scheme_widget:
739                scheme_widget.createNewNode(widget_desc)
740
741    def on_quick_category_action(self, action):
742        """The quick category menu action triggered.
743        """
744        category = action.text()
745        for i in range(self.widgets_tool_box.count()):
746            cat_act = self.widgets_tool_box.tabAction(i)
747            if cat_act.text() == category:
748                if not cat_act.isChecked():
749                    # Trigger the action to expand the tool grid contained
750                    # within.
751                    cat_act.trigger()
752
753            else:
754                if cat_act.isChecked():
755                    # Trigger the action to hide the tool grid contained
756                    # within.
757                    cat_act.trigger()
758
759        self.dock_widget.expand()
760
761    def set_scheme_margins_enabled(self, enabled):
762        """Enable/disable the margins around the scheme document.
763        """
764        if self.__scheme_margins_enabled != enabled:
765            self.__scheme_margins_enabled = enabled
766            self.__update_scheme_margins()
767
768    def scheme_margins_enabled(self):
769        return self.__scheme_margins_enabled
770
771    scheme_margins_enabled = Property(bool,
772                                      fget=scheme_margins_enabled,
773                                      fset=set_scheme_margins_enabled)
774
775    def __update_scheme_margins(self):
776        """Update the margins around the scheme document.
777        """
778        enabled = self.__scheme_margins_enabled
779        self.__dummy_top_toolbar.setVisible(enabled)
780        self.__dummy_bottom_toolbar.setVisible(enabled)
781        central = self.centralWidget()
782
783        margin = 20 if enabled else 0
784
785        if self.dockWidgetArea(self.dock_widget) == Qt.LeftDockWidgetArea:
786            margins = (margin / 2, 0, margin, 0)
787        else:
788            margins = (margin, 0, margin / 2, 0)
789
790        central.layout().setContentsMargins(*margins)
791
792    #################
793    # Action handlers
794    #################
795    def new_scheme(self):
796        """New scheme. Return QDialog.Rejected if the user canceled
797        the operation and QDialog.Accepted otherwise.
798
799        """
800        document = self.current_document()
801        if document.isModifiedStrict():
802            # Ask for save changes
803            if self.ask_save_changes() == QDialog.Rejected:
804                return QDialog.Rejected
805
806        new_scheme = widgetsscheme.WidgetsScheme()
807
808        settings = QSettings()
809        show = settings.value("schemeinfo/show-at-new-scheme", True,
810                              type=bool)
811
812        if show:
813            status = self.show_scheme_properties_for(
814                new_scheme, self.tr("New Scheme")
815            )
816
817            if status == QDialog.Rejected:
818                return QDialog.Rejected
819
820        self.set_new_scheme(new_scheme)
821
822        return QDialog.Accepted
823
824    def open_scheme(self):
825        """Open a new scheme. Return QDialog.Rejected if the user canceled
826        the operation and QDialog.Accepted otherwise.
827
828        """
829        document = self.current_document()
830        if document.isModifiedStrict():
831            if self.ask_save_changes() == QDialog.Rejected:
832                return QDialog.Rejected
833
834        if self.last_scheme_dir is None:
835            # Get user 'Documents' folder
836            start_dir = QDesktopServices.storageLocation(
837                            QDesktopServices.DocumentsLocation)
838        else:
839            start_dir = self.last_scheme_dir
840
841        # TODO: Use a dialog instance and use 'addSidebarUrls' to
842        # set one or more extra sidebar locations where Schemes are stored.
843        # Also use setHistory
844        filename = QFileDialog.getOpenFileName(
845            self, self.tr("Open Orange Scheme File"),
846            start_dir, self.tr("Orange Scheme (*.ows)"),
847        )
848
849        if filename:
850            self.load_scheme(filename)
851            return QDialog.Accepted
852        else:
853            return QDialog.Rejected
854
855    def load_scheme(self, filename):
856        """Load a scheme from a file (`filename`) into the current
857        document updates the recent scheme list and the loaded scheme path
858        property.
859
860        """
861        filename = unicode(filename)
862        dirname = os.path.dirname(filename)
863
864        self.last_scheme_dir = dirname
865
866        new_scheme = self.new_scheme_from(filename)
867        if new_scheme is not None:
868            self.set_new_scheme(new_scheme)
869
870            scheme_doc_widget = self.current_document()
871            scheme_doc_widget.setPath(filename)
872
873            self.add_recent_scheme(new_scheme.title, filename)
874
875    def new_scheme_from(self, filename):
876        """Create and return a new :class:`widgetsscheme.WidgetsScheme`
877        from a saved `filename`. Return `None` if an error occurs.
878
879        """
880        new_scheme = widgetsscheme.WidgetsScheme()
881        errors = []
882        try:
883            parse_scheme(new_scheme, open(filename, "rb"),
884                         error_handler=errors.append,
885                         allow_pickle_data=True)
886        except Exception:
887            message_critical(
888                 self.tr("Could not load an Orange Scheme file"),
889                 title=self.tr("Error"),
890                 informative_text=self.tr("An unexpected error occurred "
891                                          "while loading '%s'.") % filename,
892                 exc_info=True,
893                 parent=self)
894            return None
895        if errors:
896            message_warning(
897                self.tr("Errors occurred while loading the scheme."),
898                title=self.tr("Problem"),
899                informative_text=self.tr(
900                     "There were problems loading some "
901                     "of the widgets/links in the "
902                     "scheme."
903                ),
904                details="\n".join(map(repr, errors))
905            )
906        return new_scheme
907
908    def reload_last(self):
909        """Reload last opened scheme. Return QDialog.Rejected if the
910        user canceled the operation and QDialog.Accepted otherwise.
911
912        """
913        document = self.current_document()
914        if document.isModifiedStrict():
915            if self.ask_save_changes() == QDialog.Rejected:
916                return QDialog.Rejected
917
918        # TODO: Search for a temp backup scheme with per process
919        # locking.
920        if self.recent_schemes:
921            self.load_scheme(self.recent_schemes[0][1])
922
923        return QDialog.Accepted
924
925    def set_new_scheme(self, new_scheme):
926        """
927        Set new_scheme as the current shown scheme. The old scheme
928        will be deleted.
929
930        """
931        scheme_doc = self.current_document()
932        old_scheme = scheme_doc.scheme()
933
934        manager = new_scheme.signal_manager
935        if self.freeze_action.isChecked():
936            manager.freeze().push()
937
938        scheme_doc.setScheme(new_scheme)
939
940        old_scheme.save_widget_settings()
941        old_scheme.close_all_open_widgets()
942
943        old_scheme.deleteLater()
944
945    def ask_save_changes(self):
946        """Ask the user to save the changes to the current scheme.
947        Return QDialog.Accepted if the scheme was successfully saved
948        or the user selected to discard the changes. Otherwise return
949        QDialog.Rejected.
950
951        """
952        document = self.current_document()
953
954        selected = message_question(
955            self.tr("Do you want to save the changes you made to scheme %r?") \
956                    % document.scheme().title,
957            self.tr("Save Changes?"),
958            self.tr("If you do not save your changes will be lost"),
959            buttons=QMessageBox.Save | QMessageBox.Cancel | \
960                    QMessageBox.Discard,
961            default_button=QMessageBox.Save,
962            parent=self)
963
964        if selected == QMessageBox.Save:
965            return self.save_scheme()
966        elif selected == QMessageBox.Discard:
967            return QDialog.Accepted
968        elif selected == QMessageBox.Cancel:
969            return QDialog.Rejected
970
971    def check_can_save(self, document, path):
972        """
973        Check if saving the document to `path` would prevent it from
974        being read by the version 1.0 of scheme parser.
975
976        """
977        if path and os.path.exists(path):
978            version = sniff_version(open(path, "rb"))
979            if version == "1.0":
980                message_information(
981                    self.tr("Can not overwrite a version 1.0 ows file. "
982                            "Please save your work to a new file"),
983                    title="Info",
984                    parent=self)
985                return False
986        return True
987
988    def save_scheme(self):
989        """Save the current scheme. If the scheme does not have an associated
990        path then prompt the user to select a scheme file. Return
991        QDialog.Accepted if the scheme was successfully saved and
992        QDialog.Rejected if the user canceled the file selection.
993
994        """
995        document = self.current_document()
996        curr_scheme = document.scheme()
997
998        if document.path() and self.check_can_save(document, document.path()):
999            curr_scheme.save_to(open(document.path(), "wb"),
1000                                pretty=True, pickle_fallback=True)
1001
1002            document.setModified(False)
1003            self.add_recent_scheme(curr_scheme.title, document.path())
1004            return QDialog.Accepted
1005        else:
1006            return self.save_scheme_as()
1007
1008    def save_scheme_as(self):
1009        """Save the current scheme by asking the user for a filename.
1010        Return QFileDialog.Accepted if the scheme was saved successfully
1011        and QFileDialog.Rejected if not.
1012
1013        """
1014        document = self.current_document()
1015        curr_scheme = document.scheme()
1016
1017        if document.path():
1018            start_dir = document.path()
1019        else:
1020            if self.last_scheme_dir is not None:
1021                start_dir = self.last_scheme_dir
1022            else:
1023                start_dir = QDesktopServices.storageLocation(
1024                    QDesktopServices.DocumentsLocation
1025                )
1026
1027            title = curr_scheme.title or "untitled"
1028            start_dir = os.path.join(unicode(start_dir), title + ".ows")
1029
1030        filename = QFileDialog.getSaveFileName(
1031            self, self.tr("Save Orange Scheme File"),
1032            start_dir, self.tr("Orange Scheme (*.ows)")
1033        )
1034
1035        if filename:
1036            filename = unicode(filename)
1037            if not self.check_can_save(document, filename):
1038                return QDialog.Rejected
1039
1040            dirname, basename = os.path.split(filename)
1041            self.last_scheme_dir = dirname
1042
1043            try:
1044                curr_scheme.save_to(open(filename, "wb"),
1045                                    pretty=True, pickle_fallback=True)
1046            except Exception:
1047                log.error("Error saving %r to %r", curr_scheme, filename,
1048                          exc_info=True)
1049                # Also show a message box
1050                # TODO: should handle permission errors with a
1051                # specialized messages.
1052                message_critical(
1053                     self.tr("An error occurred while trying to save the %r "
1054                             "scheme to %r" % \
1055                             (curr_scheme.title, basename)),
1056                     title=self.tr("Error saving %r") % basename,
1057                     exc_info=True,
1058                     parent=self)
1059                return QFileDialog.Rejected
1060
1061            document.setPath(filename)
1062
1063            document.setModified(False)
1064            self.add_recent_scheme(curr_scheme.title, document.path())
1065            return QFileDialog.Accepted
1066        else:
1067            return QFileDialog.Rejected
1068
1069    def get_started(self, *args):
1070        """Show getting started video
1071        """
1072        url = QUrl(LINKS["start-using"])
1073        QDesktopServices.openUrl(url)
1074
1075    def tutorial(self, *args):
1076        """Show tutorial.
1077        """
1078        url = QUrl(LINKS["tutorial"])
1079        QDesktopServices.openUrl(url)
1080
1081    def documentation(self, *args):
1082        """Show reference documentation.
1083        """
1084        url = QUrl(LINKS["tutorial"])
1085        QDesktopServices.openUrl(url)
1086
1087    def recent_scheme(self, *args):
1088        """Browse recent schemes. Return QDialog.Rejected if the user
1089        canceled the operation and QDialog.Accepted otherwise.
1090
1091        """
1092        items = [previewmodel.PreviewItem(name=title, path=path)
1093                 for title, path in self.recent_schemes]
1094        model = previewmodel.PreviewModel(items=items)
1095
1096        dialog = previewdialog.PreviewDialog(self)
1097        title = self.tr("Recent Schemes")
1098        dialog.setWindowTitle(title)
1099        template = ('<h3 style="font-size: 26px">\n'
1100                    #'<img height="26" src="canvas_icons:Recent.svg">\n'
1101                    '{0}\n'
1102                    '</h3>')
1103        dialog.setHeading(template.format(title))
1104        dialog.setModel(model)
1105
1106        model.delayedScanUpdate()
1107
1108        status = dialog.exec_()
1109
1110        index = dialog.currentIndex()
1111
1112        dialog.deleteLater()
1113        model.deleteLater()
1114
1115        if status == QDialog.Accepted:
1116            doc = self.current_document()
1117            if doc.isModifiedStrict():
1118                if self.ask_save_changes() == QDialog.Rejected:
1119                    return QDialog.Rejected
1120
1121            selected = model.item(index)
1122
1123            self.load_scheme(unicode(selected.path()))
1124
1125        return status
1126
1127    def tutorial_scheme(self, *args):
1128        """Browse a collection of tutorial schemes. Returns QDialog.Rejected
1129        if the user canceled the dialog else loads the selected scheme into
1130        the canvas and returns QDialog.Accepted.
1131
1132        """
1133        tutors = tutorials.tutorials()
1134        items = [previewmodel.PreviewItem(path=t.abspath()) for t in tutors]
1135        model = previewmodel.PreviewModel(items=items)
1136        dialog = previewdialog.PreviewDialog(self)
1137        title = self.tr("Tutorials")
1138        dialog.setWindowTitle(title)
1139        template = ('<h3 style="font-size: 26px">\n'
1140                    #'<img height="26" src="canvas_icons:Tutorials.svg">\n'
1141                    '{0}\n'
1142                    '</h3>')
1143
1144        dialog.setHeading(template.format(title))
1145        dialog.setModel(model)
1146
1147        model.delayedScanUpdate()
1148        status = dialog.exec_()
1149        index = dialog.currentIndex()
1150
1151        dialog.deleteLater()
1152
1153        if status == QDialog.Accepted:
1154            doc = self.current_document()
1155            if doc.isModifiedStrict():
1156                if self.ask_save_changes() == QDialog.Rejected:
1157                    return QDialog.Rejected
1158
1159            selected = model.item(index)
1160
1161            new_scheme = self.new_scheme_from(unicode(selected.path()))
1162            if new_scheme is not None:
1163                self.set_new_scheme(new_scheme)
1164
1165        return status
1166
1167    def welcome_dialog(self):
1168        """Show a modal welcome dialog for Orange Canvas.
1169        """
1170
1171        dialog = welcomedialog.WelcomeDialog(self)
1172        dialog.setWindowTitle(self.tr("Welcome to Orange Data Mining"))
1173
1174        def new_scheme():
1175            if self.new_scheme() == QDialog.Accepted:
1176                dialog.accept()
1177
1178        def open_scheme():
1179            if self.open_scheme() == QDialog.Accepted:
1180                dialog.accept()
1181
1182        def open_recent():
1183            if self.recent_scheme() == QDialog.Accepted:
1184                dialog.accept()
1185
1186        def tutorial():
1187            if self.tutorial_scheme() == QDialog.Accepted:
1188                dialog.accept()
1189
1190        new_action = \
1191            QAction(self.tr("New"), dialog,
1192                    toolTip=self.tr("Open a new scheme."),
1193                    triggered=new_scheme,
1194                    shortcut=QKeySequence.New,
1195                    icon=canvas_icons("New.svg")
1196                    )
1197
1198        open_action = \
1199            QAction(self.tr("Open"), dialog,
1200                    objectName="welcome-action-open",
1201                    toolTip=self.tr("Open a scheme."),
1202                    triggered=open_scheme,
1203                    shortcut=QKeySequence.Open,
1204                    icon=canvas_icons("Open.svg")
1205                    )
1206
1207        recent_action = \
1208            QAction(self.tr("Recent"), dialog,
1209                    objectName="welcome-recent-action",
1210                    toolTip=self.tr("Browse and open a recent scheme."),
1211                    triggered=open_recent,
1212                    shortcut=QKeySequence(Qt.ControlModifier | \
1213                                          (Qt.ShiftModifier | Qt.Key_R)),
1214                    icon=canvas_icons("Recent.svg")
1215                    )
1216
1217        tutorials_action = \
1218            QAction(self.tr("Tutorial"), dialog,
1219                    objectName="welcome-tutorial-action",
1220                    toolTip=self.tr("Browse tutorial schemes."),
1221                    triggered=tutorial,
1222                    icon=canvas_icons("Tutorials.svg")
1223                    )
1224
1225        bottom_row = [self.get_started_action, tutorials_action,
1226                      self.documentation_action]
1227
1228        self.new_action.triggered.connect(dialog.accept)
1229        top_row = [new_action, open_action, recent_action]
1230
1231        dialog.addRow(top_row, background="light-grass")
1232        dialog.addRow(bottom_row, background="light-orange")
1233
1234        settings = QSettings()
1235
1236        dialog.setShowAtStartup(
1237            settings.value("startup/show-welcome-screen", True, type=bool)
1238        )
1239
1240        status = dialog.exec_()
1241
1242        settings.setValue("startup/show-welcome-screen",
1243                          dialog.showAtStartup())
1244
1245        dialog.deleteLater()
1246
1247        return status
1248
1249    def scheme_properties_dialog(self):
1250        """Return an empty `SchemeInfo` dialog instance.
1251        """
1252        settings = QSettings()
1253        value_key = "schemeinfo/show-at-new-scheme"
1254
1255        dialog = SchemeInfoDialog(self)
1256
1257        dialog.setWindowTitle(self.tr("Scheme Info"))
1258        dialog.setFixedSize(725, 450)
1259
1260        dialog.setDontShowAtNewScheme(
1261            not settings.value(value_key, True, type=bool)
1262        )
1263
1264        return dialog
1265
1266    def show_scheme_properties(self):
1267        """Show current scheme properties.
1268        """
1269        settings = QSettings()
1270        value_key = "schemeinfo/show-at-new-scheme"
1271
1272        current_doc = self.current_document()
1273        scheme = current_doc.scheme()
1274        dlg = self.scheme_properties_dialog()
1275        dlg.setAutoCommit(False)
1276        dlg.setScheme(scheme)
1277        status = dlg.exec_()
1278
1279        if status == QDialog.Accepted:
1280            editor = dlg.editor
1281            stack = current_doc.undoStack()
1282            stack.beginMacro(self.tr("Change Info"))
1283            current_doc.setTitle(editor.title())
1284            current_doc.setDescription(editor.description())
1285            stack.endMacro()
1286
1287            # Store the check state.
1288            settings.setValue(value_key, not dlg.dontShowAtNewScheme())
1289        return status
1290
1291    def show_scheme_properties_for(self, scheme, window_title=None):
1292        """Show scheme properties for `scheme` with `window_title (if None
1293        a default 'Scheme Info' title will be used.
1294
1295        """
1296        settings = QSettings()
1297        value_key = "schemeinfo/show-at-new-scheme"
1298
1299        dialog = self.scheme_properties_dialog()
1300
1301        if window_title is not None:
1302            dialog.setWindowTitle(window_title)
1303
1304        dialog.setScheme(scheme)
1305
1306        status = dialog.exec_()
1307        if status == QDialog.Accepted:
1308            # Store the check state.
1309            settings.setValue(value_key, not dialog.dontShowAtNewScheme())
1310
1311        dialog.deleteLater()
1312
1313        return status
1314
1315    def set_signal_freeze(self, freeze):
1316        scheme = self.current_document().scheme()
1317        if freeze:
1318            scheme.signal_manager.freeze().push()
1319        else:
1320            scheme.signal_manager.freeze().pop()
1321
1322    def remove_selected(self):
1323        """Remove current scheme selection.
1324        """
1325        self.current_document().removeSelected()
1326
1327    def quit(self):
1328        """Quit the application.
1329        """
1330        self.close()
1331
1332    def select_all(self):
1333        self.current_document().selectAll()
1334
1335    def open_widget(self):
1336        """Open/raise selected widget's GUI.
1337        """
1338        self.current_document().openSelected()
1339
1340    def rename_widget(self):
1341        """Rename the current focused widget.
1342        """
1343        doc = self.current_document()
1344        nodes = doc.selectedNodes()
1345        if len(nodes) == 1:
1346            doc.editNodeTitle(nodes[0])
1347
1348    def open_canvas_settings(self):
1349        """Open canvas settings/preferences dialog
1350        """
1351        dlg = UserSettingsDialog(self)
1352        dlg.show()
1353        status = dlg.exec_()
1354        if status == 0:
1355            self.__update_from_settings()
1356
1357    def show_output_view(self):
1358        """Show a window with application output.
1359        """
1360        self.output_dock.show()
1361
1362    def output_view(self):
1363        """Return the output text widget.
1364        """
1365        return self.output_dock.widget()
1366
1367    def open_about(self):
1368        """Open the about dialog.
1369        """
1370        dlg = AboutDialog(self)
1371        dlg.setAttribute(Qt.WA_DeleteOnClose)
1372        dlg.exec_()
1373
1374    def add_recent_scheme(self, title, path):
1375        """Add an entry (`title`, `path`) to the list of recent schemes.
1376        """
1377        if not path:
1378            # No associated persistent path so we can't do anything.
1379            return
1380
1381        if title is None:
1382            title = os.path.basename(path)
1383            title, _ = os.path.splitext(title)
1384
1385        filename = os.path.abspath(os.path.realpath(path))
1386        filename = os.path.normpath(filename)
1387
1388        actions_by_filename = {}
1389        for action in self.recent_scheme_action_group.actions():
1390            path = unicode(action.data().toString())
1391            actions_by_filename[path] = action
1392
1393        if filename in actions_by_filename:
1394            # Remove the title/filename (so it can be reinserted)
1395            recent_index = index(self.recent_schemes, filename,
1396                                 key=operator.itemgetter(1))
1397            self.recent_schemes.pop(recent_index)
1398
1399            action = actions_by_filename[filename]
1400            self.recent_menu.removeAction(action)
1401            action.setText(title or self.tr("untitled"))
1402        else:
1403            action = QAction(title or self.tr("untitled"), self,
1404                             toolTip=filename)
1405            action.setData(filename)
1406
1407        # Find the separator action in the menu (after 'Browse Recent')
1408        recent_actions = self.recent_menu.actions()
1409        begin_index = index(recent_actions, self.recent_menu_begin)
1410        action_before = recent_actions[begin_index + 1]
1411
1412        self.recent_menu.insertAction(action_before, action)
1413        self.recent_scheme_action_group.addAction(action)
1414        self.recent_schemes.insert(0, (title, filename))
1415
1416        config.save_recent_scheme_list(self.recent_schemes)
1417
1418    def clear_recent_schemes(self):
1419        """Clear list of recent schemes
1420        """
1421        actions = list(self.recent_menu.actions())
1422
1423        # Exclude permanent actions (Browse Recent, separators, Clear List)
1424        actions_to_remove = [action for action in actions \
1425                             if unicode(action.data().toString())]
1426
1427        for action in actions_to_remove:
1428            self.recent_menu.removeAction(action)
1429
1430        self.recent_schemes = []
1431        config.save_recent_scheme_list([])
1432
1433    def _on_recent_scheme_action(self, action):
1434        """A recent scheme action was triggered by the user
1435        """
1436        document = self.current_document()
1437        if document.isModifiedStrict():
1438            if self.ask_save_changes() == QDialog.Rejected:
1439                return
1440
1441        filename = unicode(action.data().toString())
1442        self.load_scheme(filename)
1443
1444    def _on_dock_location_changed(self, location):
1445        """Location of the dock_widget has changed, fix the margins
1446        if necessary.
1447
1448        """
1449        self.__update_scheme_margins()
1450
1451    def createPopupMenu(self):
1452        # Override the default context menu popup (we don't want the user to
1453        # be able to hide the tool dock widget).
1454        return None
1455
1456    def closeEvent(self, event):
1457        """Close the main window.
1458        """
1459        document = self.current_document()
1460        if document.isModifiedStrict():
1461            if self.ask_save_changes() == QDialog.Rejected:
1462                # Reject the event
1463                event.ignore()
1464                return
1465
1466        scheme = document.scheme()
1467        scheme.save_widget_settings()
1468        scheme.close_all_open_widgets()
1469
1470        # Set an empty scheme to clear the document
1471        document.setScheme(widgetsscheme.WidgetsScheme())
1472        document.deleteLater()
1473
1474        config.save_config()
1475
1476        geometry = self.saveGeometry()
1477        state = self.saveState(version=self.SETTINGS_VERSION)
1478        settings = QSettings()
1479        settings.beginGroup("mainwindow")
1480        settings.setValue("geometry", geometry)
1481        settings.setValue("state", state)
1482        settings.setValue("canvasdock/expanded",
1483                          self.dock_widget.expanded())
1484        settings.setValue("scheme-margins-enabled",
1485                          self.scheme_margins_enabled)
1486
1487        settings.setValue("last-scheme-dir", self.last_scheme_dir)
1488        settings.setValue("widgettoolbox/state",
1489                          self.widgets_tool_box.saveState())
1490
1491        settings.setValue("quick-help/visible",
1492                          self.canvas_tool_dock.quickHelpVisible())
1493
1494        settings.endGroup()
1495
1496        event.accept()
1497
1498        # Close any windows left.
1499        application = QApplication.instance()
1500        QTimer.singleShot(0, application.closeAllWindows)
1501
1502    def showEvent(self, event):
1503        if self.__first_show:
1504            settings = QSettings()
1505            settings.beginGroup("mainwindow")
1506
1507            # Restore geometry and dock/toolbar state
1508            state = settings.value("state", QByteArray(), type=QByteArray)
1509            if state:
1510                self.restoreState(state, version=self.SETTINGS_VERSION)
1511
1512            geom_data = settings.value("geometry", QByteArray(),
1513                                       type=QByteArray)
1514            if geom_data:
1515                self.restoreGeometry(geom_data)
1516
1517            self.__first_show = False
1518
1519        return QMainWindow.showEvent(self, event)
1520
1521    def event(self, event):
1522        if event.type() == QEvent.StatusTip and \
1523                isinstance(event, QuickHelpTipEvent):
1524            # Using singleShot to update the text browser.
1525            # If updating directly the application experiences strange random
1526            # segfaults (in ~StatusTipEvent in QTextLayout or event just normal
1527            # event loop), but only when the contents are larger then the
1528            # QTextBrowser's viewport.
1529            if event.priority() == QuickHelpTipEvent.Normal:
1530                QTimer.singleShot(0, partial(self.dock_help.showHelp,
1531                                             event.html()))
1532            elif event.priority() == QuickHelpTipEvent.Temporary:
1533                QTimer.singleShot(0, partial(self.dock_help.showHelp,
1534                                             event.html(), event.timeout()))
1535            elif event.priority() == QuickHelpTipEvent.Permanent:
1536                QTimer.singleShot(0, partial(self.dock_help.showPermanentHelp,
1537                                             event.html()))
1538
1539            return True
1540
1541        elif event.type() == QEvent.WhatsThisClicked:
1542            ref = event.href()
1543            url = QUrl(ref)
1544
1545            if url.scheme() == "help" and url.authority() == "search":
1546                try:
1547                    url = self.help.search(url)
1548                except KeyError:
1549                    url = None
1550                    log.info("No help topic found for %r", url)
1551
1552            if url:
1553                self.show_help(url)
1554            else:
1555                message_information(
1556                    self.tr("Sorry there is no documentation available for "
1557                            "this widget."),
1558                    parent=self)
1559
1560            return True
1561
1562        return QMainWindow.event(self, event)
1563
1564    def show_help(self, url):
1565        """
1566        Show `url` in a help window.
1567        """
1568        log.info("Setting help to url: %r", url)
1569        if self.open_in_external_browser:
1570            url = QUrl(url)
1571            if not QDesktopServices.openUrl(url):
1572                # Try fixing some common problems.
1573                url = QUrl.fromUserInput(url.toString())
1574                # 'fromUserInput' includes possible fragment into the path
1575                # (which prevents it to open local files) so we reparse it
1576                # again.
1577                url = QUrl(url.toString())
1578                QDesktopServices.openUrl(url)
1579        else:
1580            self.help_view.load(QUrl(url))
1581            self.help_dock.show()
1582            self.help_dock.raise_()
1583
1584    # Mac OS X
1585    if sys.platform == "darwin":
1586        def toggleMaximized(self):
1587            """Toggle normal/maximized window state.
1588            """
1589            if self.isMinimized():
1590                # Do nothing if window is minimized
1591                return
1592
1593            if self.isMaximized():
1594                self.showNormal()
1595            else:
1596                self.showMaximized()
1597
1598        def changeEvent(self, event):
1599            if event.type() == QEvent.WindowStateChange:
1600                # Can get 'Qt.WindowNoState' before the widget is fully
1601                # initialized
1602                if hasattr(self, "window_state"):
1603                    # Enable/disable window menu based on minimized state
1604                    self.window_menu.setEnabled(not self.isMinimized())
1605
1606            QMainWindow.changeEvent(self, event)
1607
1608    def tr(self, sourceText, disambiguation=None, n=-1):
1609        """Translate the string.
1610        """
1611        return unicode(QMainWindow.tr(self, sourceText, disambiguation, n))
1612
1613    def __update_from_settings(self):
1614        settings = QSettings()
1615        settings.beginGroup("mainwindow")
1616        toolbox_floatable = settings.value("toolbox-dock-floatable",
1617                                           defaultValue=False,
1618                                           type=bool)
1619
1620        features = self.dock_widget.features()
1621        features = updated_flags(features, QDockWidget.DockWidgetFloatable,
1622                                 toolbox_floatable)
1623        self.dock_widget.setFeatures(features)
1624
1625        toolbox_exclusive = settings.value("toolbox-dock-exclusive",
1626                                           defaultValue=False,
1627                                           type=bool)
1628        self.widgets_tool_box.setExclusive(toolbox_exclusive)
1629
1630        settings.endGroup()
1631        settings.beginGroup("quickmenu")
1632
1633        triggers = 0
1634        dbl_click = settings.value("trigger-on-double-click",
1635                                   defaultValue=True,
1636                                   type=bool)
1637        if dbl_click:
1638            triggers |= SchemeEditWidget.DoubleClicked
1639
1640        left_click = settings.value("trigger-on-left-click",
1641                                    defaultValue=False,
1642                                    type=bool)
1643        if left_click:
1644            triggers |= SchemeEditWidget.Clicked
1645
1646        space_press = settings.value("trigger-on-space-key",
1647                                     defaultValue=True,
1648                                     type=bool)
1649        if space_press:
1650            triggers |= SchemeEditWidget.SpaceKey
1651
1652        any_press = settings.value("trigger-on-any-key",
1653                                   defaultValue=False,
1654                                   type=bool)
1655        if any_press:
1656            triggers |= SchemeEditWidget.AnyKey
1657
1658        self.scheme_widget.setQuickMenuTriggers(triggers)
1659
1660        settings.endGroup()
1661        settings.beginGroup("schemeedit")
1662        show_channel_names = settings.value("show-channel-names",
1663                                            defaultValue=True,
1664                                            type=bool)
1665        self.scheme_widget.setChannelNamesVisible(show_channel_names)
1666
1667        node_animations = settings.value("enable-node-animations",
1668                                         defaultValue=False,
1669                                         type=bool)
1670        self.scheme_widget.setNodeAnimationEnabled(node_animations)
1671        settings.endGroup()
1672
1673        settings.beginGroup("output")
1674        stay_on_top = settings.value("stay-on-top", defaultValue=True,
1675                                     type=bool)
1676        if stay_on_top:
1677            self.output_dock.setFloatingWindowFlags(Qt.Tool)
1678        else:
1679            self.output_dock.setFloatingWindowFlags(Qt.Window)
1680
1681        dockable = settings.value("dockable", defaultValue=True,
1682                                  type=bool)
1683        if dockable:
1684            self.output_dock.setAllowedAreas(Qt.BottomDockWidgetArea)
1685        else:
1686            self.output_dock.setAllowedAreas(Qt.NoDockWidgetArea)
1687
1688        settings.endGroup()
1689
1690        settings.beginGroup("help")
1691        stay_on_top = settings.value("stay-on-top", defaultValue=True,
1692                                     type=bool)
1693        if stay_on_top:
1694            self.help_dock.setFloatingWindowFlags(Qt.Tool)
1695        else:
1696            self.help_dock.setFloatingWindowFlags(Qt.Window)
1697
1698        dockable = settings.value("dockable", defaultValue=False,
1699                                  type=bool)
1700        if dockable:
1701            self.help_dock.setAllowedAreas(Qt.LeftDockWidgetArea | \
1702                                           Qt.RightDockWidgetArea)
1703        else:
1704            self.help_dock.setAllowedAreas(Qt.NoDockWidgetArea)
1705
1706        self.open_in_external_browser = \
1707            settings.value("open-in-external-browser", defaultValue=False,
1708                           type=bool)
1709
1710
1711def updated_flags(flags, mask, state):
1712    if state:
1713        flags |= mask
1714    else:
1715        flags &= ~mask
1716    return flags
1717
1718
1719def identity(item):
1720    return item
1721
1722
1723def index(sequence, *what, **kwargs):
1724    """index(sequence, what, [key=None, [predicate=None]])
1725
1726    Return index of `what` in `sequence`.
1727
1728    """
1729    what = what[0]
1730    key = kwargs.get("key", identity)
1731    predicate = kwargs.get("predicate", operator.eq)
1732    for i, item in enumerate(sequence):
1733        item_key = key(item)
1734        if predicate(what, item_key):
1735            return i
1736    raise ValueError("%r not in sequence" % what)
Note: See TracBrowser for help on using the repository browser.