source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11469:35ca66b6b2d0

Revision 11469:35ca66b6b2d0, 61.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Use pause/resume interface of base SignalManager.

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