source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11522:425d83b20b96

Revision 11522:425d83b20b96, 63.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 11 months ago (diff)

Handle QFileOpenEvent request events.

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