source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11305:b85791927494

Revision 11305:b85791927494, 57.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Prevent a overwrite of the old version 1.0 ows scheme format.

Old schemes should not be allowed to be overwritten in a way that would
prevent the old interface from reading them.

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