source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11387:1f8c6eab7b36

Revision 11387:1f8c6eab7b36, 59.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Added caching to help view browser.

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
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`.
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        except Exception:
885            message_critical(
886                 self.tr("Could not load Orange Scheme file"),
887                 title=self.tr("Error"),
888                 informative_text=self.tr("An unexpected error occurred "
889                                          "while loading %r.") % filename,
890                 exc_info=True,
891                 parent=self)
892            return None
893        if errors:
894            message_warning(
895                self.tr("Errors occured while loading the scheme."),
896                title=self.tr("Problem"),
897                informative_text=self.tr(
898                     "There were problems loading some "
899                     "of the widgets/links in the "
900                     "scheme."
901                ),
902                details="\n".join(map(repr, errors))
903            )
904        return new_scheme
905
906    def reload_last(self):
907        """Reload last opened scheme. Return QDialog.Rejected if the
908        user canceled the operation and QDialog.Accepted otherwise.
909
910        """
911        document = self.current_document()
912        if document.isModifiedStrict():
913            if self.ask_save_changes() == QDialog.Rejected:
914                return QDialog.Rejected
915
916        # TODO: Search for a temp backup scheme with per process
917        # locking.
918        if self.recent_schemes:
919            self.load_scheme(self.recent_schemes[0][1])
920
921        return QDialog.Accepted
922
923    def set_new_scheme(self, new_scheme):
924        """
925        Set new_scheme as the current shown scheme. The old scheme
926        will be deleted.
927
928        """
929        scheme_doc = self.current_document()
930        old_scheme = scheme_doc.scheme()
931
932        manager = new_scheme.signal_manager
933        if self.freeze_action.isChecked():
934            manager.freeze().push()
935
936        scheme_doc.setScheme(new_scheme)
937
938        old_scheme.save_widget_settings()
939        old_scheme.close_all_open_widgets()
940
941        old_scheme.deleteLater()
942
943    def ask_save_changes(self):
944        """Ask the user to save the changes to the current scheme.
945        Return QDialog.Accepted if the scheme was successfully saved
946        or the user selected to discard the changes. Otherwise return
947        QDialog.Rejected.
948
949        """
950        document = self.current_document()
951
952        selected = message_question(
953            self.tr("Do you want to save the changes you made to scheme %r?") \
954                    % document.scheme().title,
955            self.tr("Save Changes?"),
956            self.tr("If you do not save your changes will be lost"),
957            buttons=QMessageBox.Save | QMessageBox.Cancel | \
958                    QMessageBox.Discard,
959            default_button=QMessageBox.Save,
960            parent=self)
961
962        if selected == QMessageBox.Save:
963            return self.save_scheme()
964        elif selected == QMessageBox.Discard:
965            return QDialog.Accepted
966        elif selected == QMessageBox.Cancel:
967            return QDialog.Rejected
968
969    def check_can_save(self, document, path):
970        """
971        Check if saving the document to `path` would prevent it from
972        being read by the version 1.0 of scheme parser.
973
974        """
975        if path and os.path.exists(path):
976            version = sniff_version(open(path, "rb"))
977            if version == "1.0":
978                message_information(
979                    self.tr("Can not overwrite a version 1.0 ows file. "
980                            "Please save your work to a new file"),
981                    title="Info",
982                    parent=self)
983                return False
984        return True
985
986    def save_scheme(self):
987        """Save the current scheme. If the scheme does not have an associated
988        path then prompt the user to select a scheme file. Return
989        QDialog.Accepted if the scheme was successfully saved and
990        QDialog.Rejected if the user canceled the file selection.
991
992        """
993        document = self.current_document()
994        curr_scheme = document.scheme()
995
996        if document.path() and self.check_can_save(document, document.path()):
997            curr_scheme.save_to(open(document.path(), "wb"))
998            document.setModified(False)
999            self.add_recent_scheme(curr_scheme.title, document.path())
1000            return QDialog.Accepted
1001        else:
1002            return self.save_scheme_as()
1003
1004    def save_scheme_as(self):
1005        """Save the current scheme by asking the user for a filename.
1006        Return QFileDialog.Accepted if the scheme was saved successfully
1007        and QFileDialog.Rejected if not.
1008
1009        """
1010        document = self.current_document()
1011        curr_scheme = document.scheme()
1012
1013        if document.path():
1014            start_dir = document.path()
1015        else:
1016            if self.last_scheme_dir is not None:
1017                start_dir = self.last_scheme_dir
1018            else:
1019                start_dir = QDesktopServices.storageLocation(
1020                    QDesktopServices.DocumentsLocation
1021                )
1022
1023            title = curr_scheme.title or "untitled"
1024            start_dir = os.path.join(unicode(start_dir), title + ".ows")
1025
1026        filename = QFileDialog.getSaveFileName(
1027            self, self.tr("Save Orange Scheme File"),
1028            start_dir, self.tr("Orange Scheme (*.ows)")
1029        )
1030
1031        if filename:
1032            filename = unicode(filename)
1033            if not self.check_can_save(document, filename):
1034                return QDialog.Rejected
1035
1036            dirname, basename = os.path.split(filename)
1037            self.last_scheme_dir = dirname
1038
1039            try:
1040                curr_scheme.save_to(open(filename, "wb"))
1041            except Exception:
1042                log.error("Error saving %r to %r", curr_scheme, filename,
1043                          exc_info=True)
1044                # Also show a message box
1045                # TODO: should handle permission errors with a
1046                # specialized messages.
1047                message_critical(
1048                     self.tr("An error occurred while trying to save the %r "
1049                             "scheme to %r" % \
1050                             (curr_scheme.title, basename)),
1051                     title=self.tr("Error saving %r") % basename,
1052                     exc_info=True,
1053                     parent=self)
1054                return QFileDialog.Rejected
1055
1056            document.setPath(filename)
1057
1058            document.setModified(False)
1059            self.add_recent_scheme(curr_scheme.title, document.path())
1060            return QFileDialog.Accepted
1061        else:
1062            return QFileDialog.Rejected
1063
1064    def get_started(self, *args):
1065        """Show getting started video
1066        """
1067        url = QUrl(LINKS["start-using"])
1068        QDesktopServices.openUrl(url)
1069
1070    def tutorial(self, *args):
1071        """Show tutorial.
1072        """
1073        url = QUrl(LINKS["tutorial"])
1074        QDesktopServices.openUrl(url)
1075
1076    def documentation(self, *args):
1077        """Show reference documentation.
1078        """
1079        url = QUrl(LINKS["tutorial"])
1080        QDesktopServices.openUrl(url)
1081
1082    def recent_scheme(self, *args):
1083        """Browse recent schemes. Return QDialog.Rejected if the user
1084        canceled the operation and QDialog.Accepted otherwise.
1085
1086        """
1087        items = [previewmodel.PreviewItem(name=title, path=path)
1088                 for title, path in self.recent_schemes]
1089        model = previewmodel.PreviewModel(items=items)
1090
1091        dialog = previewdialog.PreviewDialog(self)
1092        title = self.tr("Recent Schemes")
1093        dialog.setWindowTitle(title)
1094        template = ('<h3 style="font-size: 26px">\n'
1095                    #'<img height="26" src="canvas_icons:Recent.svg">\n'
1096                    '{0}\n'
1097                    '</h3>')
1098        dialog.setHeading(template.format(title))
1099        dialog.setModel(model)
1100
1101        model.delayedScanUpdate()
1102
1103        status = dialog.exec_()
1104
1105        index = dialog.currentIndex()
1106
1107        dialog.deleteLater()
1108
1109        if status == QDialog.Accepted:
1110            doc = self.current_document()
1111            if doc.isModifiedStrict():
1112                if self.ask_save_changes() == QDialog.Rejected:
1113                    return QDialog.Rejected
1114
1115            selected = model.item(index)
1116
1117            self.load_scheme(unicode(selected.path()))
1118
1119        return status
1120
1121    def tutorial_scheme(self, *args):
1122        """Browse a collection of tutorial schemes. Returns QDialog.Rejected
1123        if the user canceled the dialog else loads the selected scheme into
1124        the canvas and returns QDialog.Accepted.
1125
1126        """
1127        tutors = tutorials.tutorials()
1128        items = [previewmodel.PreviewItem(path=t.abspath()) for t in tutors]
1129        model = previewmodel.PreviewModel(items=items)
1130        dialog = previewdialog.PreviewDialog(self)
1131        title = self.tr("Tutorials")
1132        dialog.setWindowTitle(title)
1133        template = ('<h3 style="font-size: 26px">\n'
1134                    #'<img height="26" src="canvas_icons:Tutorials.svg">\n'
1135                    '{0}\n'
1136                    '</h3>')
1137
1138        dialog.setHeading(template.format(title))
1139        dialog.setModel(model)
1140
1141        model.delayedScanUpdate()
1142        status = dialog.exec_()
1143        index = dialog.currentIndex()
1144
1145        dialog.deleteLater()
1146
1147        if status == QDialog.Accepted:
1148            doc = self.current_document()
1149            if doc.isModifiedStrict():
1150                if self.ask_save_changes() == QDialog.Rejected:
1151                    return QDialog.Rejected
1152
1153            selected = model.item(index)
1154
1155            new_scheme = self.new_scheme_from(unicode(selected.path()))
1156
1157            self.set_new_scheme(new_scheme)
1158
1159        return status
1160
1161    def welcome_dialog(self):
1162        """Show a modal welcome dialog for Orange Canvas.
1163        """
1164
1165        dialog = welcomedialog.WelcomeDialog(self)
1166        dialog.setWindowTitle(self.tr("Welcome to Orange Data Mining"))
1167
1168        def new_scheme():
1169            if self.new_scheme() == QDialog.Accepted:
1170                dialog.accept()
1171
1172        def open_scheme():
1173            if self.open_scheme() == QDialog.Accepted:
1174                dialog.accept()
1175
1176        def open_recent():
1177            if self.recent_scheme() == QDialog.Accepted:
1178                dialog.accept()
1179
1180        def tutorial():
1181            if self.tutorial_scheme() == QDialog.Accepted:
1182                dialog.accept()
1183
1184        new_action = \
1185            QAction(self.tr("New"), dialog,
1186                    toolTip=self.tr("Open a new scheme."),
1187                    triggered=new_scheme,
1188                    shortcut=QKeySequence.New,
1189                    icon=canvas_icons("New.svg")
1190                    )
1191
1192        open_action = \
1193            QAction(self.tr("Open"), dialog,
1194                    objectName="welcome-action-open",
1195                    toolTip=self.tr("Open a scheme."),
1196                    triggered=open_scheme,
1197                    shortcut=QKeySequence.Open,
1198                    icon=canvas_icons("Open.svg")
1199                    )
1200
1201        recent_action = \
1202            QAction(self.tr("Recent"), dialog,
1203                    objectName="welcome-recent-action",
1204                    toolTip=self.tr("Browse and open a recent scheme."),
1205                    triggered=open_recent,
1206                    shortcut=QKeySequence(Qt.ControlModifier | \
1207                                          (Qt.ShiftModifier | Qt.Key_R)),
1208                    icon=canvas_icons("Recent.svg")
1209                    )
1210
1211        tutorials_action = \
1212            QAction(self.tr("Tutorial"), dialog,
1213                    objectName="welcome-tutorial-action",
1214                    toolTip=self.tr("Browse tutorial schemes."),
1215                    triggered=tutorial,
1216                    icon=canvas_icons("Tutorials.svg")
1217                    )
1218
1219        bottom_row = [self.get_started_action, tutorials_action,
1220                   self.documentation_action]
1221
1222        self.new_action.triggered.connect(dialog.accept)
1223        top_row = [new_action, open_action, recent_action]
1224
1225        dialog.addRow(top_row, background="light-grass")
1226        dialog.addRow(bottom_row, background="light-orange")
1227
1228        settings = QSettings()
1229
1230        dialog.setShowAtStartup(
1231            settings.value("startup/show-welcome-screen", True, type=bool)
1232        )
1233
1234        status = dialog.exec_()
1235
1236        settings.setValue("startup/show-welcome-screen",
1237                          dialog.showAtStartup())
1238
1239        dialog.deleteLater()
1240
1241        return status
1242
1243    def scheme_properties_dialog(self):
1244        """Return an empty `SchemeInfo` dialog instance.
1245        """
1246        settings = QSettings()
1247        value_key = "schemeinfo/show-at-new-scheme"
1248
1249        dialog = SchemeInfoDialog(self)
1250
1251        dialog.setWindowTitle(self.tr("Scheme Info"))
1252        dialog.setFixedSize(725, 450)
1253
1254        dialog.setDontShowAtNewScheme(
1255            not settings.value(value_key, True, type=bool)
1256        )
1257
1258        return dialog
1259
1260    def show_scheme_properties(self):
1261        """Show current scheme properties.
1262        """
1263        settings = QSettings()
1264        value_key = "schemeinfo/show-at-new-scheme"
1265
1266        current_doc = self.current_document()
1267        scheme = current_doc.scheme()
1268        dlg = self.scheme_properties_dialog()
1269        dlg.setAutoCommit(False)
1270        dlg.setScheme(scheme)
1271        status = dlg.exec_()
1272
1273        if status == QDialog.Accepted:
1274            editor = dlg.editor
1275            stack = current_doc.undoStack()
1276            stack.beginMacro(self.tr("Change Info"))
1277            current_doc.setTitle(editor.title())
1278            current_doc.setDescription(editor.description())
1279            stack.endMacro()
1280
1281            # Store the check state.
1282            settings.setValue(value_key, not dlg.dontShowAtNewScheme())
1283        return status
1284
1285    def show_scheme_properties_for(self, scheme, window_title=None):
1286        """Show scheme properties for `scheme` with `window_title (if None
1287        a default 'Scheme Info' title will be used.
1288
1289        """
1290        settings = QSettings()
1291        value_key = "schemeinfo/show-at-new-scheme"
1292
1293        dialog = self.scheme_properties_dialog()
1294
1295        if window_title is not None:
1296            dialog.setWindowTitle(window_title)
1297
1298        dialog.setScheme(scheme)
1299
1300        status = dialog.exec_()
1301        if status == QDialog.Accepted:
1302            # Store the check state.
1303            settings.setValue(value_key, not dialog.dontShowAtNewScheme())
1304
1305        dialog.deleteLater()
1306
1307        return status
1308
1309    def set_signal_freeze(self, freeze):
1310        scheme = self.current_document().scheme()
1311        if freeze:
1312            scheme.signal_manager.freeze().push()
1313        else:
1314            scheme.signal_manager.freeze().pop()
1315
1316    def remove_selected(self):
1317        """Remove current scheme selection.
1318        """
1319        self.current_document().removeSelected()
1320
1321    def quit(self):
1322        """Quit the application.
1323        """
1324        self.close()
1325
1326    def select_all(self):
1327        self.current_document().selectAll()
1328
1329    def open_widget(self):
1330        """Open/raise selected widget's GUI.
1331        """
1332        self.current_document().openSelected()
1333
1334    def rename_widget(self):
1335        """Rename the current focused widget.
1336        """
1337        doc = self.current_document()
1338        nodes = doc.selectedNodes()
1339        if len(nodes) == 1:
1340            doc.editNodeTitle(nodes[0])
1341
1342    def open_canvas_settings(self):
1343        """Open canvas settings/preferences dialog
1344        """
1345        dlg = UserSettingsDialog(self)
1346        dlg.show()
1347        status = dlg.exec_()
1348        if status == 0:
1349            self.__update_from_settings()
1350
1351    def show_output_view(self):
1352        """Show a window with application output.
1353        """
1354        self.output_dock.show()
1355
1356    def output_view(self):
1357        """Return the output text widget.
1358        """
1359        return self.output_dock.widget()
1360
1361    def open_about(self):
1362        """Open the about dialog.
1363        """
1364        dlg = AboutDialog(self)
1365        dlg.setAttribute(Qt.WA_DeleteOnClose)
1366        dlg.exec_()
1367
1368    def add_recent_scheme(self, title, path):
1369        """Add an entry (`title`, `path`) to the list of recent schemes.
1370        """
1371        if not path:
1372            # No associated persistent path so we can't do anything.
1373            return
1374
1375        if title is None:
1376            title = os.path.basename(path)
1377            title, _ = os.path.splitext(title)
1378
1379        filename = os.path.abspath(os.path.realpath(path))
1380        filename = os.path.normpath(filename)
1381
1382        actions_by_filename = {}
1383        for action in self.recent_scheme_action_group.actions():
1384            path = unicode(action.data().toString())
1385            actions_by_filename[path] = action
1386
1387        if filename in actions_by_filename:
1388            # Remove the title/filename (so it can be reinserted)
1389            recent_index = index(self.recent_schemes, filename,
1390                                 key=operator.itemgetter(1))
1391            self.recent_schemes.pop(recent_index)
1392
1393            action = actions_by_filename[filename]
1394            self.recent_menu.removeAction(action)
1395            action.setText(title or self.tr("untitled"))
1396        else:
1397            action = QAction(title or self.tr("untitled"), self,
1398                             toolTip=filename)
1399            action.setData(filename)
1400
1401        # Find the separator action in the menu (after 'Browse Recent')
1402        recent_actions = self.recent_menu.actions()
1403        begin_index = index(recent_actions, self.recent_menu_begin)
1404        action_before = recent_actions[begin_index + 1]
1405
1406        self.recent_menu.insertAction(action_before, action)
1407        self.recent_scheme_action_group.addAction(action)
1408        self.recent_schemes.insert(0, (title, filename))
1409
1410        config.save_recent_scheme_list(self.recent_schemes)
1411
1412    def clear_recent_schemes(self):
1413        """Clear list of recent schemes
1414        """
1415        actions = list(self.recent_menu.actions())
1416
1417        # Exclude permanent actions (Browse Recent, separators, Clear List)
1418        actions_to_remove = [action for action in actions \
1419                             if unicode(action.data().toString())]
1420
1421        for action in actions_to_remove:
1422            self.recent_menu.removeAction(action)
1423
1424        self.recent_schemes = []
1425        config.save_recent_scheme_list([])
1426
1427    def _on_recent_scheme_action(self, action):
1428        """A recent scheme action was triggered by the user
1429        """
1430        document = self.current_document()
1431        if document.isModifiedStrict():
1432            if self.ask_save_changes() == QDialog.Rejected:
1433                return
1434
1435        filename = unicode(action.data().toString())
1436        self.load_scheme(filename)
1437
1438    def _on_dock_location_changed(self, location):
1439        """Location of the dock_widget has changed, fix the margins
1440        if necessary.
1441
1442        """
1443        self.__update_scheme_margins()
1444
1445    def createPopupMenu(self):
1446        # Override the default context menu popup (we don't want the user to
1447        # be able to hide the tool dock widget).
1448        return None
1449
1450    def closeEvent(self, event):
1451        """Close the main window.
1452        """
1453        document = self.current_document()
1454        if document.isModifiedStrict():
1455            if self.ask_save_changes() == QDialog.Rejected:
1456                # Reject the event
1457                event.ignore()
1458                return
1459
1460        scheme = document.scheme()
1461        scheme.save_widget_settings()
1462        scheme.close_all_open_widgets()
1463
1464        # Set an empty scheme to clear the document
1465        document.setScheme(widgetsscheme.WidgetsScheme())
1466        document.deleteLater()
1467
1468        config.save_config()
1469
1470        geometry = self.saveGeometry()
1471        state = self.saveState(version=self.SETTINGS_VERSION)
1472        settings = QSettings()
1473        settings.beginGroup("mainwindow")
1474        settings.setValue("geometry", geometry)
1475        settings.setValue("state", state)
1476        settings.setValue("canvasdock/expanded",
1477                          self.dock_widget.expanded())
1478        settings.setValue("scheme-margins-enabled",
1479                          self.scheme_margins_enabled)
1480
1481        settings.setValue("last-scheme-dir", self.last_scheme_dir)
1482        settings.setValue("widgettoolbox/state",
1483                          self.widgets_tool_box.saveState())
1484
1485        settings.setValue("quick-help/visible",
1486                          self.canvas_tool_dock.quickHelpVisible())
1487
1488        settings.endGroup()
1489
1490        event.accept()
1491
1492        # Close any windows left.
1493        application = QApplication.instance()
1494        QTimer.singleShot(0, application.closeAllWindows)
1495
1496    def showEvent(self, event):
1497        if self.__first_show:
1498            settings = QSettings()
1499            settings.beginGroup("mainwindow")
1500
1501            # Restore geometry and dock/toolbar state
1502            state = settings.value("state", QByteArray(), type=QByteArray)
1503            if state:
1504                self.restoreState(state, version=self.SETTINGS_VERSION)
1505
1506            geom_data = settings.value("geometry", QByteArray(),
1507                                       type=QByteArray)
1508            if geom_data:
1509                self.restoreGeometry(geom_data)
1510
1511            self.__first_show = False
1512
1513        return QMainWindow.showEvent(self, event)
1514
1515    def event(self, event):
1516        if event.type() == QEvent.StatusTip and \
1517                isinstance(event, QuickHelpTipEvent):
1518            # Using singleShot to update the text browser.
1519            # If updating directly the application experiences strange random
1520            # segfaults (in ~StatusTipEvent in QTextLayout or event just normal
1521            # event loop), but only when the contents are larger then the
1522            # QTextBrowser's viewport.
1523            if event.priority() == QuickHelpTipEvent.Normal:
1524                QTimer.singleShot(0, partial(self.dock_help.showHelp,
1525                                             event.html()))
1526            elif event.priority() == QuickHelpTipEvent.Temporary:
1527                QTimer.singleShot(0, partial(self.dock_help.showHelp,
1528                                             event.html(), event.timeout()))
1529            elif event.priority() == QuickHelpTipEvent.Permanent:
1530                QTimer.singleShot(0, partial(self.dock_help.showPermanentHelp,
1531                                             event.html()))
1532
1533            return True
1534
1535        elif event.type() == QEvent.WhatsThisClicked:
1536            ref = event.href()
1537            url = QUrl(ref)
1538
1539            if url.scheme() == "help" and url.authority() == "search":
1540                try:
1541                    url = self.help.search(url)
1542                except KeyError:
1543                    url = None
1544                    log.info("No help topic found for %r", url)
1545
1546            if url:
1547                self.show_help(url)
1548            else:
1549                message_information(
1550                    self.tr("Sorry there is no documentation available for "
1551                            "this widget."),
1552                    parent=self)
1553
1554            return True
1555
1556        return QMainWindow.event(self, event)
1557
1558    def show_help(self, url):
1559        """
1560        Show `url` in a help window.
1561        """
1562        log.info("Setting help to url: %r", url)
1563        if self.open_in_external_browser:
1564            url = QUrl(url)
1565            if not QDesktopServices.openUrl(url):
1566                # Try fixing some common problems.
1567                url = QUrl.fromUserInput(url.toString())
1568                # 'fromUserInput' includes possible fragment into the path
1569                # (which prevents it to open local files) so we reparse it
1570                # again.
1571                url = QUrl(url.toString())
1572                QDesktopServices.openUrl(url)
1573        else:
1574            self.help_view.load(QUrl(url))
1575            self.help_dock.show()
1576            self.help_dock.raise_()
1577
1578    # Mac OS X
1579    if sys.platform == "darwin":
1580        def toggleMaximized(self):
1581            """Toggle normal/maximized window state.
1582            """
1583            if self.isMinimized():
1584                # Do nothing if window is minimized
1585                return
1586
1587            if self.isMaximized():
1588                self.showNormal()
1589            else:
1590                self.showMaximized()
1591
1592        def changeEvent(self, event):
1593            if event.type() == QEvent.WindowStateChange:
1594                # Can get 'Qt.WindowNoState' before the widget is fully
1595                # initialized
1596                if hasattr(self, "window_state"):
1597                    # Enable/disable window menu based on minimized state
1598                    self.window_menu.setEnabled(not self.isMinimized())
1599
1600            QMainWindow.changeEvent(self, event)
1601
1602    def tr(self, sourceText, disambiguation=None, n=-1):
1603        """Translate the string.
1604        """
1605        return unicode(QMainWindow.tr(self, sourceText, disambiguation, n))
1606
1607    def __update_from_settings(self):
1608        settings = QSettings()
1609        settings.beginGroup("mainwindow")
1610        toolbox_floatable = settings.value("toolbox-dock-floatable",
1611                                           defaultValue=False,
1612                                           type=bool)
1613
1614        features = self.dock_widget.features()
1615        features = updated_flags(features, QDockWidget.DockWidgetFloatable,
1616                                 toolbox_floatable)
1617        self.dock_widget.setFeatures(features)
1618
1619        toolbox_exclusive = settings.value("toolbox-dock-exclusive",
1620                                           defaultValue=False,
1621                                           type=bool)
1622        self.widgets_tool_box.setExclusive(toolbox_exclusive)
1623
1624        settings.endGroup()
1625        settings.beginGroup("quickmenu")
1626
1627        triggers = 0
1628        dbl_click = settings.value("trigger-on-double-click",
1629                                   defaultValue=True,
1630                                   type=bool)
1631        if dbl_click:
1632            triggers |= SchemeEditWidget.DoubleClicked
1633
1634        left_click = settings.value("trigger-on-left-click",
1635                                    defaultValue=False,
1636                                    type=bool)
1637        if left_click:
1638            triggers |= SchemeEditWidget.Clicked
1639
1640        space_press = settings.value("trigger-on-space-key",
1641                                     defaultValue=True,
1642                                     type=bool)
1643        if space_press:
1644            triggers |= SchemeEditWidget.SpaceKey
1645
1646        any_press = settings.value("trigger-on-any-key",
1647                                   defaultValue=False,
1648                                   type=bool)
1649        if any_press:
1650            triggers |= SchemeEditWidget.AnyKey
1651
1652        self.scheme_widget.setQuickMenuTriggers(triggers)
1653
1654        settings.endGroup()
1655        settings.beginGroup("schemeedit")
1656        show_channel_names = settings.value("show-channel-names",
1657                                            defaultValue=True,
1658                                            type=bool)
1659        self.scheme_widget.setChannelNamesVisible(show_channel_names)
1660
1661        settings.endGroup()
1662
1663        settings.beginGroup("output")
1664        stay_on_top = settings.value("stay-on-top", defaultValue=True,
1665                                     type=bool)
1666        if stay_on_top:
1667            self.output_dock.setFloatingWindowFlags(Qt.Tool)
1668        else:
1669            self.output_dock.setFloatingWindowFlags(Qt.Window)
1670
1671        dockable = settings.value("dockable", defaultValue=True,
1672                                  type=bool)
1673        if dockable:
1674            self.output_dock.setAllowedAreas(Qt.BottomDockWidgetArea)
1675        else:
1676            self.output_dock.setAllowedAreas(Qt.NoDockWidgetArea)
1677
1678        settings.endGroup()
1679
1680        settings.beginGroup("help")
1681        stay_on_top = settings.value("stay-on-top", defaultValue=True,
1682                                     type=bool)
1683        if stay_on_top:
1684            self.help_dock.setFloatingWindowFlags(Qt.Tool)
1685        else:
1686            self.help_dock.setFloatingWindowFlags(Qt.Window)
1687
1688        dockable = settings.value("dockable", defaultValue=False,
1689                                  type=bool)
1690        if dockable:
1691            self.help_dock.setAllowedAreas(Qt.LeftDockWidgetArea | \
1692                                           Qt.RightDockWidgetArea)
1693        else:
1694            self.help_dock.setAllowedAreas(Qt.NoDockWidgetArea)
1695
1696        self.open_in_external_browser = \
1697            settings.value("open-in-external-browser", defaultValue=False,
1698                           type=bool)
1699
1700
1701def updated_flags(flags, mask, state):
1702    if state:
1703        flags |= mask
1704    else:
1705        flags &= ~mask
1706    return flags
1707
1708
1709def identity(item):
1710    return item
1711
1712
1713def index(sequence, *what, **kwargs):
1714    """index(sequence, what, [key=None, [predicate=None]])
1715
1716    Return index of `what` in `sequence`.
1717
1718    """
1719    what = what[0]
1720    key = kwargs.get("key", identity)
1721    predicate = kwargs.get("predicate", operator.eq)
1722    for i, item in enumerate(sequence):
1723        item_key = key(item)
1724        if predicate(what, item_key):
1725            return i
1726    raise ValueError("%r not in sequence" % what)
Note: See TracBrowser for help on using the repository browser.