source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11502:66682f793dbd

Revision 11502:66682f793dbd, 62.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Fixed placement of popup category menu widgets.

They now popup to the right or left of the toolbar.

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