source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11543:2938d744f3cd

Revision 11543:2938d744f3cd, 63.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 11 months ago (diff)

Fixed user error messages formating.

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