source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11384:5a17ac856f3a

Revision 11384:5a17ac856f3a, 59.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Close all open widgets when setting a new scheme.

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