source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11392:2bddec091c91

Revision 11392:2bddec091c91, 59.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Test the return value from 'new_scheme_from'.

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