source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11206:49f2a7576167

Revision 11206:49f2a7576167, 51.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Added Tutorial dialog.

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