source: orange/Orange/OrangeCanvas/application/canvasmain.py @ 11189:6e8b14ab7b9a

Revision 11189:6e8b14ab7b9a, 50.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Preserve empty scheme title, display 'untitled' placeholders where appropriate.

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