source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11320:4a8d643fe709

Revision 11320:4a8d643fe709, 58.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Added 'Open in external browser option' for widget help.

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