source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11399:ad4d1c89c266

Revision 11399:ad4d1c89c266, 59.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Test for a change in floating window flags before updating.

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