source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11272:0bfd3a5b6d28

Revision 11272:0bfd3a5b6d28, 55.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Added scheme document interaction help tips.

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