source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11303:9401b375eba0

Revision 11303:9401b375eba0, 57.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Error handling/recovery in scheme parsing code.

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