source: orange/Orange/OrangeCanvas/orngCanvas.pyw @ 10903:04accc6a6f21

Revision 10903:04accc6a6f21, 60.7 KB checked in by mstajdohar, 23 months ago (diff)

Import Orange first to set the orng paths.

Line 
1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2# Description:
3#    main file, that creates the MDI environment
4
5# This module has to be imported first because it takes care of the system PATH variable
6# Namely, it throws out the MikTeX directories which contain an incompatible Qt .dll's
7import Orange
8import orngEnviron, Orange.utils.addons
9
10from PyQt4.QtCore import *
11from PyQt4.QtGui import *
12   
13import sys, os, cPickle, orngRegistry, OWGUI
14import pkg_resources
15import orngTabs, orngDoc, orngDlgs, orngOutput, orngHelp, OWReport
16import user, orngMisc
17
18import gc
19
20RedR = False
21product = "Red R" if RedR else "Orange"
22
23class OrangeCanvasDlg(QMainWindow):
24    def __init__(self, app, parent=None, flags=0):
25        QMainWindow.__init__(self, parent)
26        self.debugMode = 1        # print extra output for debuging
27        self.setWindowTitle("%s Canvas" % product)
28        self.windows = []    # list of id for windows in Window menu
29        self.recentDocs = []
30        self.iconNameToIcon = {}
31        self.toolbarIconSizeList = [16, 32, 40, 48, 60]
32        self.schemeIconSizeList = [32, 40, 48]
33        self.widgetsToolBar = None
34        self.originalPalette = QApplication.palette()
35
36        self.__dict__.update(orngEnviron.directoryNames)
37           
38        self.defaultPic = os.path.join(self.picsDir, "Unknown.png")
39        self.defaultBackground = os.path.join(self.picsDir, "frame.png")
40        canvasPicsDir = os.path.join(self.canvasDir, "icons")
41        self.file_new = os.path.join(canvasPicsDir, "doc.png")
42        self.outputPix = os.path.join(canvasPicsDir, "output.png")
43        self.file_open = os.path.join(canvasPicsDir, "open.png")
44        self.file_save = os.path.join(canvasPicsDir, "save.png")
45        if RedR:
46            self.reload_pic = os.path.join(canvasPicsDir, "update1.png")
47        self.text_icon = os.path.join(canvasPicsDir, "text.png")
48        self.file_print = os.path.join(canvasPicsDir, "print.png")
49        self.file_exit = os.path.join(canvasPicsDir, "exit.png")
50        canvasIconName = os.path.join(canvasPicsDir, "CanvasIcon.png")
51        if os.path.exists(canvasIconName):
52            self.setWindowIcon(QIcon(canvasIconName))
53           
54        self.settings = {}
55        if RedR:
56            self.settings['svnSettings'] = {}
57            self.settings['versionNumber'] = 'Version1.0'
58        self.menuSaveSettingsID = -1
59        self.menuSaveSettings = 1
60
61        self.loadSettings()
62        if RedR:
63            import updateRedR
64            self.settings['svnSettings'], self.settings['versionNumber'] = updateRedR.start(self.settings['svnSettings'], self.settings['versionNumber'])
65           
66#        self.widgetSelectedColor = QColor(*self.settings["widgetSelectedColor"])
67#        self.widgetActiveColor = QColor(*self.settings["widgetActiveColor"])
68#        self.lineColor = QColor(*self.settings["lineColor"])
69
70        # output window
71        self.output = orngOutput.OutputWindow(self)
72#        self.output.catchException(1)
73        self.output.catchOutput(1)
74       
75        self.updateWidgetRegistry()
76
77        # create error and warning icons
78        informationIconName = os.path.join(canvasPicsDir, "information.png")
79        warningIconName = os.path.join(canvasPicsDir, "warning.png")
80        errorIconName = os.path.join(canvasPicsDir, "error.png")
81        if os.path.exists(errorIconName) and os.path.exists(warningIconName) and os.path.exists(informationIconName):
82            self.errorIcon = QPixmap(errorIconName)
83            self.warningIcon = QPixmap(warningIconName)
84            self.informationIcon = QPixmap(informationIconName)
85            self.widgetIcons = {"Info": self.informationIcon, "Warning": self.warningIcon, "Error": self.errorIcon}
86        else:
87            self.errorIcon = None
88            self.warningIcon = None
89            self.informationIcon = None
90            self.widgetIcons = None
91            print "Unable to load all necessary icons. Please reinstall Orange."
92       
93        #########
94        # Actions
95        #########
96        self.showMainToolbarAction = QAction("Main Toolbar", self)
97        self.showMainToolbarAction.setCheckable(True)
98        self.showMainToolbarAction.setChecked(self.settings.get("showToolbar", True))
99        self.connect(self.showMainToolbarAction,
100                     SIGNAL("triggered(bool)"),
101                     self.menuItemShowToolbar)
102       
103        self.showWidgetToolbarAction = QAction("Widget Toolbar", self)
104        self.showWidgetToolbarAction.setCheckable(True)
105        self.showWidgetToolbarAction.setChecked(self.settings.get("showWidgetToolbar", True))
106        self.connect(self.showWidgetToolbarAction,
107                     SIGNAL("triggered(bool)"),
108                     self.menuItemShowWidgetToolbar)
109       
110        # TODO: Move other actions currently defined elsewhere
111       
112        self.setStatusBar(MyStatusBar(self))
113        self.statusBar().setVisible(self.settings.get("showStatusBar", True))
114        self.sizeGrip = SizeGrip(self)
115        self.sizeGrip.setVisible(not self.settings.get("showStatusBar", True))
116       
117        self.updateStyle()
118       
119        self.eventNotifier = EventNotifier(parent=self)
120        # create toolbar
121        self.toolbar = self.addToolBar("Toolbar")
122        self.toolbar.setOrientation(Qt.Horizontal)
123#        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
124        if not self.settings.get("showToolbar", True):
125            self.toolbar.hide()
126       
127        # create a schema
128        self.schema = orngDoc.SchemaDoc(self)
129        # A corner widget to reserve space for the size grip when
130        # the status bar is hidden
131        self.schema.canvasView.setCornerWidget(QWidget())
132       
133        self.setCentralWidget(self.schema)
134        self.schema.setFocus()
135
136        self.toolbar.addAction(QIcon(self.file_open), "Open schema", self.menuItemOpen)
137#        self.toolbar.addAction(QIcon(self.style().standardIcon(QStyle.SP_FileIcon)), "Open schema", self.menuItemOpen)
138        self.toolSave = self.toolbar.addAction(QIcon(self.file_save), "Save schema", self.menuItemSave)
139#        self.toolSave = self.toolbar.addAction(QIcon(self.style().standardIcon(QStyle.SP_DialogSaveButton)), "Save schema", self.menuItemSave)
140        if RedR:
141            self.toolReloadWidgets = self.toolbar.addAction(QIcon(self.reload_pic), "Reload Widgets", self.reloadWidgets)
142        self.toolbar.addSeparator()
143        self.toolbar.addAction(QIcon(self.file_print), "Print", self.menuItemPrinter)
144        self.toolbar.addSeparator()
145       
146        # Create the controls for widget list type in the main toolbar
147        # Commented out because this duplicates the entries in the View menu.
148#        def updateWidgetListType():
149#            self.createWidgetsToolbar()
150#            ind = min(len(self.widgetListTypeActions) - 1, self.settings["widgetListType"])
151#            self.widgetListTypeActions[ind].setChecked(True)
152#           
153#        def updateToolbarIconSize():
154#            self.createWidgetsToolbar()
155#            ind = min(len(self.iconSizeActions) - 1, self.settings["toolbarIconSize"])
156#            self.iconSizeActions[ind].setChecked(True)
157#           
158#        w = QWidget()
159#        w.setLayout(QHBoxLayout())
160#       
161#        items = ["Tool box", "Tree view", "Tree view (no icons)", "Tabs without labels", "Tabs with labels"]
162#        ind = max(len(items) - 1, self.settings["widgetListType"])
163#        self.widgetListTypeCB = OWGUI.comboBox(w, self.settings, "widgetListType",
164#                                    label="Style:", orientation="horizontal",
165#                                    items=items,
166#                                    callback=updateWidgetListType,
167#                                    debuggingEnabled=0)
168#       
169#        self.widgetListTypeCB.setFocusPolicy(Qt.TabFocus)
170#        self.toolbar.addWidget(w)
171#       
172#        self.toolbar.addSeparator()
173#
174#        w = QWidget()
175#        w.setLayout(QHBoxLayout())
176#        items = ["%d x %d" % (v, v) for v in self.toolbarIconSizeList]
177#        self.settings["toolbarIconSize"] = min(len(items) - 1, self.settings["toolbarIconSize"])
178#        cb = OWGUI.comboBoxWithCaption(w, self.settings, "toolbarIconSize", "Icon size:",
179#                                       items=items,
180#                                       tooltip="Set the size of the widget icons in the toolbar, tool box, and tree view area",
181#                                       callback=updateToolbarIconSize,
182#                                       debuggingEnabled=0)
183#        cb.setFocusPolicy(Qt.TabFocus)
184#       
185#        self.toolbar.addWidget(w)
186       
187        self.freezeAction = self.toolbar.addAction("Freeze signals")
188        self.freezeAction.setCheckable(True)
189        self.freezeAction.setIcon(QIcon(self.style().standardIcon(QStyle.SP_MediaPause))) #self.schema_pause))
190       
191        def toogleSchemaFreeze(freeze):
192            self.freezeAction.setIcon(QIcon(self.style().standardIcon(QStyle.SP_MediaPlay if freeze else QStyle.SP_MediaPause))) #self.schema_pause))
193            self.schema.setFreeze(freeze)
194           
195        self.connect(self.freezeAction, SIGNAL("toggled(bool)"), toogleSchemaFreeze)
196       
197        # Restore geometry before calling createWidgetsToolbar.
198        # On Mac OSX with unified title bar the canvas can move up on restarts
199        state = self.settings.get("CanvasMainWindowGeometry", None)
200        if state is not None:
201            state = self.restoreGeometry(QByteArray(state))
202            width, height = self.width(), self.height()
203       
204        if not state:
205            width, height = self.settings.get("canvasWidth", 700), self.settings.get("canvasHeight", 600)
206
207        # center window in the desktop
208        # on multiheaded desktops if it does not fit
209       
210        desktop = qApp.desktop()
211        space = desktop.availableGeometry(self)
212        geometry, frame = self.geometry(), self.frameGeometry()
213       
214        #Fit the frame size to fit in space
215        width = min(space.width() - (frame.width() - geometry.width()), geometry.width())
216        height = min(space.height() - (frame.height() - geometry.height()), geometry.height())
217       
218        self.resize(width, height)
219       
220        self.addToolBarBreak()
221        orngTabs.constructCategoriesPopup(self)
222        self.createWidgetsToolbar()
223        self.readShortcuts()
224       
225        def addOnRefreshCallback():
226            self.updateWidgetRegistry()
227            orngTabs.constructCategoriesPopup(self)
228            self.createWidgetsToolbar()
229        Orange.utils.addons.addon_refresh_callback.append(addOnRefreshCallback)
230
231        # create menu
232        self.initMenu()
233        self.readRecentFiles()
234       
235        #move to center if frame not fully contained in space
236        if not space.contains(self.frameGeometry()):
237            x = max(0, space.width() / 2 - width / 2)
238            y = max(0, space.height() / 2 - height / 2)
239           
240            self.move(x, y)
241
242        self.helpWindow = orngHelp.HelpWindow(self)
243        self.reportWindow = OWReport.ReportWindow()
244        self.reportWindow.widgets = self.schema.widgets
245        self.reportWindow.saveDir = self.settings["reportsDir"]
246       
247
248        # did Orange crash the last time we used it? If yes, you will find a tempSchema.tmp file
249        if not RedR:
250            if os.path.exists(os.path.join(self.canvasSettingsDir, "tempSchema.tmp")):
251                mb = QMessageBox('%s Canvas' % product, "Your previous %s Canvas session was not closed successfully.\nYou can choose to reload your unsaved work or start a new session.\n\nIf you choose 'Reload', the links will be disabled to prevent reoccurence of the crash.\nYou can enable them by clicking Options/Enable all links." % product, QMessageBox.Information, QMessageBox.Ok | QMessageBox.Default, QMessageBox.Cancel | QMessageBox.Escape, QMessageBox.NoButton)
252                mb.setButtonText(QMessageBox.Ok, "Reload")
253                mb.setButtonText(QMessageBox.Cancel, "New schema")
254                if mb.exec_() == QMessageBox.Ok:
255                    self.schema.loadDocument(os.path.join(self.canvasSettingsDir, "tempSchema.tmp"), freeze=1)
256       
257        if self.schema.widgets == [] and len(sys.argv) > 1 and os.path.exists(sys.argv[-1]) and os.path.splitext(sys.argv[-1])[1].lower() == ".ows":
258            self.schema.loadDocument(sys.argv[-1])
259       
260        # Raise the size grip so it is above all other widgets
261        self.sizeGrip.raise_()
262       
263        # show message box if no numpy
264        qApp.processEvents()
265        try:
266            import numpy
267        except ImportError:
268            if QMessageBox.warning(self, '%s Canvas' % product, 'Several widgets now use numpy module, \nthat is not yet installed on this computer. \nDo you wish to download it?', QMessageBox.Ok | QMessageBox.Default, QMessageBox.Cancel | QMessageBox.Escape) == QMessageBox.Ok:
269                import webbrowser
270                webbrowser.open("http://sourceforge.net/projects/numpy/")
271               
272        # On Mac if the user clicks the Toolbar button in the title bar
273        if sys.platform == "darwin":
274            self.eventNotifier.attach(self, QEvent.ToolBarChange, self.toogleToolbarState)
275           
276        self.output.catchException(1)
277
278    def updateWidgetRegistry(self):
279        """ Update the widget registry and add new category tabs to the
280        settings dict. 
281        """
282        # The default Widget tabs order
283        if not self.settings.has_key("WidgetTabs") or self.settings["WidgetTabs"] == []:
284            f = open(os.path.join(self.canvasDir, "WidgetTabs.txt"), "r")
285            defaultTabs = [c for c in [line.split("#")[0].strip() for line in f.readlines()] if c!=""]
286            for i in xrange(len(defaultTabs)-1,0,-1):
287                if defaultTabs[i] in defaultTabs[0:i]:
288                    del defaultTabs[i]
289            self.settings["WidgetTabs"] = [(name, Qt.Checked) for name in defaultTabs] + [("Visualize Qt", Qt.Unchecked), ("Prototypes", Qt.Unchecked)]
290           
291        widgetTabList = self.settings["WidgetTabs"]
292        self.widgetRegistry = orngRegistry.readCategories()
293       
294        extraTabs = [(name, 1) for name in self.widgetRegistry.keys() if name not in [tab for (tab, s) in widgetTabList]]
295        extraTabs = sorted(extraTabs)
296       
297        # Keep Prototypes as last in list
298        if widgetTabList[-1][0] == "Prototypes" and widgetTabList[-2][0] == "Visualize Qt":
299            widgetTabList = widgetTabList[: -2] + extraTabs + widgetTabList[-2 :]
300        elif widgetTabList[-1][0] == "Prototypes":
301            widgetTabList = widgetTabList[: -1] + extraTabs + widgetTabList[-1 :]
302        else:
303            widgetTabList = widgetTabList + extraTabs
304        self.settings["WidgetTabs"] = widgetTabList
305           
306    def createWidgetsToolbar(self):
307        barstate, treestate = None, None
308        if self.widgetsToolBar:
309            self.settings["showWidgetToolbar"] = self.widgetsToolBar.isVisible()
310            if isinstance(self.widgetsToolBar, QToolBar):
311                self.removeToolBar(self.widgetsToolBar)
312                barstate = (self.tabs.currentIndex(), )
313            elif isinstance(self.widgetsToolBar, orngTabs.WidgetToolBox):
314                self.settings["toolboxWidth"] = self.widgetsToolBar.toolbox.width()
315                self.removeDockWidget(self.widgetsToolBar)
316                barstate = (self.tabs.toolbox.currentIndex(), )
317            elif isinstance(self.widgetsToolBar, orngTabs.WidgetTree):
318                self.settings["toolboxWidth"] = self.widgetsToolBar.treeWidget.width()
319                self.removeDockWidget(self.widgetsToolBar)
320                treestate = ( [self.tabs.treeWidget.topLevelItem(i).isExpanded()
321                               for i in range(self.tabs.treeWidget.topLevelItemCount())], )
322           
323        if self.settings["widgetListType"] == 0:
324            self.tabs = self.widgetsToolBar = orngTabs.WidgetToolBox(self, self.widgetRegistry)
325            self.addDockWidget(Qt.LeftDockWidgetArea, self.widgetsToolBar)
326        elif self.settings["widgetListType"] in [1, 2]:
327            self.tabs = self.widgetsToolBar = orngTabs.WidgetTree(self, self.widgetRegistry)
328            self.addDockWidget(Qt.LeftDockWidgetArea, self.widgetsToolBar)
329        else:
330            if sys.platform == "darwin":
331                self.setUnifiedTitleAndToolBarOnMac(False)
332            self.widgetsToolBar = self.addToolBar("Widgets")
333            self.insertToolBarBreak(self.widgetsToolBar)
334            self.tabs = orngTabs.WidgetTabs(self, self.widgetRegistry, self.widgetsToolBar)
335            self.widgetsToolBar.addWidget(self.tabs)
336           
337        if sys.platform == "darwin":
338            self.setUnifiedTitleAndToolBarOnMac(self.settings["widgetListType"] in [0, 1, 2] and self.settings["style"].lower() == "macintosh (aqua)")
339
340        # find widgets and create tab with buttons
341        self.tabs.createWidgetTabs(self.settings["WidgetTabs"], self.widgetRegistry, self.widgetDir, self.picsDir, self.defaultPic)
342        if not self.settings.get("showWidgetToolbar", True):
343            self.widgetsToolBar.hide()
344        if barstate:
345            if self.settings["widgetListType"] == 0:
346                self.tabs.toolbox.setCurrentIndex(barstate[0])
347            elif self.settings["widgetListType"] in [1, 2]:
348                widget = self.tabs.treeWidget
349                widget.scrollToItem(widget.topLevelItem(barstate[0]),
350                                    QAbstractItemView.PositionAtTop)
351            else:
352                self.tabs.setCurrentIndex(barstate[0])
353        if treestate and self.settings["widgetListType"] in [1, 2]:
354            for i, e in enumerate(treestate[0]):
355                self.tabs.treeWidget.topLevelItem(i).setExpanded(e)
356
357
358    def readShortcuts(self):
359        self.widgetShortcuts = {}
360        shfn = os.path.join(self.canvasSettingsDir, "shortcuts.txt")
361        if os.path.exists(shfn):
362            for t in file(shfn).readlines():
363                key, info = [x.strip() for x in t.split(":")]
364                if len(info) == 0: continue
365                if info[0] == "(" and info[-1] == ")":
366                    cat, widgetName = eval(info)            # new style of shortcuts are of form F: ("Data", "File")
367                else:
368                    cat, widgetName = info.split(" - ")   # old style of shortcuts are of form F: Data - File
369                if self.widgetRegistry.has_key(cat) and self.widgetRegistry[cat].has_key(widgetName):
370                    self.widgetShortcuts[key] = self.widgetRegistry[cat][widgetName]
371
372    def initMenu(self):
373        self.menuRecent = QMenu("Recent Schemas", self)
374
375        self.menuFile = QMenu("&File", self)
376        self.menuFile.addAction("New Scheme", self.menuItemNewScheme, QKeySequence.New)
377        self.menuFile.addAction(QIcon(self.file_open), "&Open...", self.menuItemOpen, QKeySequence.Open)
378        self.menuFile.addAction(QIcon(self.file_open), "&Open and Freeze...", self.menuItemOpenFreeze)
379        if RedR:
380            self.menuFile.addAction("Import Schema", self.importSchema)
381        if os.path.exists(os.path.join(self.canvasSettingsDir, "lastSchema.tmp")):
382            self.menuFile.addAction("Reload Last Schema", self.menuItemOpenLastSchema, Qt.CTRL + Qt.Key_R)
383        #self.menuFile.addAction( "&Clear", self.menuItemClear)
384        self.menuFile.addSeparator()
385        self.menuReportID = self.menuFile.addAction("&Report", self.menuItemReport, Qt.CTRL + Qt.ALT + Qt.Key_R)
386        self.menuFile.addSeparator()
387        self.menuSaveID = self.menuFile.addAction(QIcon(self.file_save), "&Save", self.menuItemSave, QKeySequence.Save)
388        self.menuSaveAsID = self.menuFile.addAction("Save &as...", self.menuItemSaveAs)
389        if not RedR:
390            self.menuFile.addAction("&Save as Application (Tabs)...", self.menuItemSaveAsAppTabs)
391            self.menuFile.addAction("&Save as Application (Buttons)...", self.menuItemSaveAsAppButtons)
392        self.menuFile.addSeparator()
393        self.menuFile.addAction(QIcon(self.file_print), "&Print Schema / Save image", self.menuItemPrinter, QKeySequence.Print)
394        self.menuFile.addSeparator()
395        self.menuFile.addMenu(self.menuRecent)
396        self.menuFile.addSeparator()
397        self.menuFile.addAction("E&xit", self.close, Qt.CTRL + Qt.Key_Q)
398       
399        self.menuView = QMenu("&View", self)
400        # Show Toolbars menu
401        toolbars = self.createPopupMenu()
402        self.menuView.addMenu(toolbars)
403        self.menuView.addSeparator()
404       
405       
406        # Widget list type menu
407        actions = ["Toolbox", "Tree View", "Tree View (no icons)",
408                   "Tabs Without Labels", "Tabs With Labels"]
409        style = QMenu("Widget Toolbar Style", self)
410        styleGroup = QActionGroup(style)
411        styleGroup.setExclusive(True)
412       
413        def setListType(id):
414            self.settings["widgetListType"] = id
415            self.createWidgetsToolbar()
416           
417        for id, action in enumerate(actions):
418            action = style.addAction(action, lambda id=id: setListType(id))
419            action.setCheckable(True)
420            styleGroup.addAction(action)
421            style.addAction(action)
422           
423        styleActions = list(styleGroup.actions())
424        ind = min(len(styleActions) -1 , self.settings.get("widgetListType", -1))
425        styleActions[ind].setChecked(True)
426        self.widgetListTypeActions = styleActions
427       
428        self.menuView.addMenu(style)
429       
430        # Widget toobox icon size menu
431        iconSize = QMenu("Widget Icon Size", self)
432        sizes = ["%i x %i" % (s, s) for s in self.toolbarIconSizeList]
433        sizeGroup = QActionGroup(iconSize)
434       
435        def setIconSize(id):
436            self.settings["toolbarIconSize"] = id
437            self.createWidgetsToolbar()
438           
439        for id, size in enumerate(sizes):
440            action = iconSize.addAction(size, lambda id=id: setIconSize(id))
441            action.setCheckable(True)
442            sizeGroup.addAction(action)
443            iconSize.addAction(action)
444           
445        sizeActions = list(sizeGroup.actions())
446        ind = min(len(sizeActions) - 1, self.settings.get("toolbarIconSize", -1))
447        sizeActions[ind].setChecked(True)
448        self.iconSizeActions = sizeActions
449       
450        self.menuView.addMenu(iconSize)
451       
452        self.menuView.addSeparator()
453        self.showStatusBarAction = self.menuView.addAction("Show Status Bar",
454                                                self.menuItemShowStatusBar)
455        self.showStatusBarAction.setCheckable(True)
456        self.showStatusBarAction.setChecked(self.settings.get("showStatusBar", True))
457
458        self.menuOptions = QMenu("&Options", self)
459        self.menuOptions.addAction("Enable All Links", self.menuItemEnableAll, Qt.CTRL + Qt.Key_E)
460        self.menuOptions.addAction("Disable All Links", self.menuItemDisableAll, Qt.CTRL + Qt.Key_D)
461        self.menuOptions.addSeparator()
462        self.menuOptions.addAction("Show Output Window", self.menuItemShowOutputWindow)
463        self.menuOptions.addAction("Clear Output Window", self.menuItemClearOutputWindow)
464        self.menuOptions.addAction("Save Output Text...", self.menuItemSaveOutputWindow)
465        if RedR:
466            self.menuOptions.addAction("Set to debug mode", self.setDebugMode)
467       
468        # uncomment this only for debugging
469#        self.menuOptions.addSeparator()
470#        self.menuOptions.addAction("Dump widget variables", self.dumpVariables)
471
472        self.menuOptions.addSeparator()
473#        self.menuOptions.addAction( "Channel preferences",  self.menuItemPreferences)
474        #self.menuOptions.addSeparator()
475        self.menuOptions.addAction("&Customize Shortcuts", self.menuItemEditWidgetShortcuts)
476        self.menuOptions.addAction("&Delete Widget Settings", self.menuItemDeleteWidgetSettings)
477        self.menuOptions.addSeparator()
478        self.menuOptions.addAction(sys.platform == "darwin" and "&Preferences..." or "Canvas &Options...", self.menuItemCanvasOptions)
479        self.menuOptions.addAction("&Add-ons...", self.menuItemAddOns)
480
481        localHelp = 0
482        self.menuHelp = QMenu("&Help", self)
483        self.menuHelp.addAction("Orange Online Reference", self.menuOpenOnlineOrangeReference)
484#        if os.path.exists(os.path.join(self.orangeDir, r"doc/catalog/index.html")):
485#            self.menuHelp.addAction("Orange Widget Catalog", self.menuOpenLocalWidgetCatalog)
486#        if os.path.exists(os.path.join(self.orangeDir, r"doc/canvas/default.htm")):
487#            self.menuHelp.addAction("Orange Canvas Help", self.menuOpenLocalCanvasHelp)
488        self.menuHelp.addAction("Orange Online Widget Catalog", self.menuOpenOnlineOrangeHelp)
489        #self.menuHelp.addAction("Orange Canvas Online Help", self.menuOpenOnlineCanvasHelp)           
490        self.menuHelp.addSeparator()
491        self.menuHelp.addAction("About Orange", self.menuItemAboutOrange)
492
493        # widget popup menu
494        self.widgetPopup = QMenu("Widget", self)
495        self.openActiveWidgetAction = self.widgetPopup.addAction("Open", self.schema.canvasView.openActiveWidget)
496        self.widgetPopup.addSeparator()
497        self.renameActiveWidgetAction = rename = self.widgetPopup.addAction("&Rename", self.schema.canvasView.renameActiveWidget, Qt.Key_F2)
498        self.removeActiveWidgetAction = delete = self.widgetPopup.addAction("Remove", self.schema.canvasView.removeActiveWidget, Qt.Key_Delete)
499        if sys.platform != "darwin":
500            delete.setShortcuts([Qt.Key_Delete, Qt.CTRL + Qt.Key_Backspace, Qt.CTRL + Qt.Key_Delete])
501        else:
502            delete.setShortcuts([Qt.CTRL + Qt.Key_Backspace, Qt.Key_Delete, Qt.CTRL + Qt.Key_Delete])
503        self.widgetPopup.addSeparator()
504        self.helpActiveWidgetAction = self.widgetPopup.addAction("Help", self.schema.canvasView.helpOnActiveWidget, Qt.Key_F1)
505        self.widgetPopup.setEnabled(0)
506       
507        if sys.platform == "darwin":
508            self.windowPopup = QMenu("Window", self)
509            self.windowPopup.addAction("Minimize", self.showMinimized, Qt.CTRL + Qt.Key_M)
510            self.windowPopup.addAction("Zoom", self.showMaximized, 0)
511
512        self.menuBar = QMenuBar(self)
513        self.menuBar.addMenu(self.menuFile)
514        self.menuBar.addMenu(self.menuView)
515        self.menuBar.addMenu(self.menuOptions)
516        self.menuBar.addMenu(self.widgetPopup)
517       
518        if hasattr(self, "windowPopup"):
519            self.menuBar.addMenu(self.windowPopup)
520           
521        self.menuBar.addMenu(self.menuHelp)
522       
523        self.setMenuBar(self.menuBar)
524       
525    def setDebugMode(self):   # RedR specific
526        if self.output.debugMode:
527            self.output.debugMode = 0
528        else:
529            self.output.debugMode = 1
530    def importSchema(self):   # RedR specific
531        name = QFileDialog.getOpenFileName(self, "Import File", self.settings["saveSchemaDir"], "Orange Widget Scripts (*.ows)")
532        if name.isEmpty():
533            return
534        name = unicode(name)
535        self.schema.clear()
536        self.schema.loadDocument(name, freeze = 0, importBlank = 1)
537        self.addToRecentMenu(name)
538   
539    def openSchema(self, filename):
540        if self.schema.isSchemaChanged() and self.schema.widgets:
541            ret = QMessageBox.warning(self, "Orange Canvas", "Changes to your present schema are not saved.\nSave them?",
542                                      QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, QMessageBox.Save)
543            if ret == QMessageBox.Save:
544                self.schema.saveDocument()
545            elif ret == QMessageBox.Cancel:
546                return
547        self.schema.clear()
548        dirname = os.path.dirname(filename)
549        os.chdir(dirname)
550        self.schema.loadDocument(filename)
551       
552    def menuItemOpen(self):
553        name = QFileDialog.getOpenFileName(self, "Open Orange Schema", self.settings["saveSchemaDir"], "Orange Widget Scripts (*.ows)")
554        if name.isEmpty():
555            return
556        name = unicode(name)
557        self.schema.clear()
558        dirname = os.path.dirname(name)
559        os.chdir(dirname)
560        self.schema.loadDocument(name, freeze=0)
561        self.addToRecentMenu(name)
562
563    def menuItemOpenFreeze(self):
564        name = QFileDialog.getOpenFileName(self, "Open Orange Schema", self.settings["saveSchemaDir"], "Orange Widget Scripts (*.ows)")
565        if name.isEmpty():
566            return
567        name = unicode(name)
568        self.schema.clear()
569        dirname = os.path.dirname(name)
570        os.chdir(dirname)
571        self.schema.loadDocument(name, freeze=1)
572        self.addToRecentMenu(name)
573
574    def menuItemOpenLastSchema(self):
575        fullName = os.path.join(self.canvasSettingsDir, "lastSchema.tmp")
576        if os.path.exists(fullName):
577            self.schema.loadDocument(fullName)
578
579    def menuItemReport(self):
580        self.schema.reportAll()
581       
582    def menuItemSave(self):
583        self.schema.saveDocument()
584       
585    def menuItemSaveAs(self):
586        self.schema.saveDocumentAs()
587
588    def menuItemSaveAsAppButtons(self):
589        self.schema.saveDocumentAsApp(asTabs=0)
590
591    def menuItemSaveAsAppTabs(self):
592        self.schema.saveDocumentAsApp(asTabs=1)
593
594    def menuItemPrinter(self):
595        try:
596            import OWDlgs
597            sizeDlg = OWDlgs.OWChooseImageSizeDlg(self.schema.canvas, defaultName=self.schema.schemaName or "schema", parent=self)
598            sizeDlg.exec_()
599        except:
600            print "Missing file 'OWDlgs.py'. This file should be in OrangeWidgets folder. Unable to print/save image."
601       
602
603    def readRecentFiles(self):
604        self.menuRecent.clear()
605        recentDocs = self.settings.get("RecentFiles", [])
606
607        # remove missing recent files
608        for i in range(len(recentDocs) - 1, -1, -1):
609            if not os.path.exists(recentDocs[i]):
610                recentDocs.remove(recentDocs[i])
611
612        recentDocs = recentDocs[:9]
613        self.settings["RecentFiles"] = recentDocs
614       
615        if len(recentDocs) == 0 :
616            self.menuRecent.addAction("None").setEnabled(False)
617        else :           
618            for i in range(len(recentDocs)):
619                shortName = "&" + str(i + 1) + " " + os.path.basename(recentDocs[i])
620                self.menuRecent.addAction(shortName, lambda ind=i: self.openRecentFile(ind),)
621
622    def openRecentFile(self, index):
623        if index < len(self.settings["RecentFiles"]):
624            name = self.settings["RecentFiles"][index]
625            if self.schema.saveBeforeClose():
626                self.schema.clear()
627                dirname = os.path.dirname(name)
628                os.chdir(dirname)
629                self.schema.loadDocument(name)
630                self.addToRecentMenu(name)
631
632    def addToRecentMenu(self, name):
633        recentDocs = []
634        if self.settings.has_key("RecentFiles"):
635            recentDocs = self.settings["RecentFiles"]
636
637        # convert to a valid file name
638        name = os.path.realpath(name)
639
640        if name in recentDocs:
641            recentDocs.remove(name)
642        recentDocs.insert(0, name)
643
644        if len(recentDocs) > 5:
645            recentDocs.remove(recentDocs[5])
646        self.settings["RecentFiles"] = recentDocs
647        self.readRecentFiles()
648
649    def menuItemSelectAll(self):
650        return
651
652    def updateSnapToGrid(self):
653        if self.settings["snapToGrid"]:
654            for widget in self.schema.widgets:
655                widget.setCoords(widget.x(), widget.y())
656            self.schema.canvas.update()
657
658    def menuItemEnableAll(self):
659        self.schema.enableAllLines()
660
661    def menuItemDisableAll(self):
662        self.schema.disableAllLines()
663
664    def menuItemSaveSettings(self):
665        self.menuSaveSettings = not self.menuSaveSettings
666        self.menuOptions.setItemChecked(self.menuSaveSettingsID, self.menuSaveSettings)
667
668    def menuItemNewScheme(self):
669        if self.schema.saveBeforeClose():
670            self.schema.clear()
671            self.schema.removeTempDoc()
672
673    def dumpVariables(self):
674        self.schema.dumpWidgetVariables()
675
676    def menuItemShowOutputWindow(self):
677        self.output.show()
678        self.output.raise_()
679#        self.output.activateWindow()
680
681    def menuItemClearOutputWindow(self):
682        self.output.textOutput.clear()
683        self.statusBar().showMessage("")
684
685    def menuItemSaveOutputWindow(self):
686        qname = QFileDialog.getSaveFileName(self, "Save Output To File", self.canvasSettingsDir + "/Output.html", "HTML Document (*.html)")
687        if qname.isEmpty(): return
688       
689        text = str(self.output.textOutput.toHtml())
690        #text = text.replace("</nobr>", "</nobr><br>")
691
692        file = open(unicode(name), "wt")
693        file.write(text)
694        file.close()
695
696    def menuItemShowToolbar(self, show=True):
697        self.toolbar.setVisible(show)
698        self.settings["showToolbar"] = show
699        self.showMainToolbarAction.setChecked(show)
700
701    def menuItemShowWidgetToolbar(self, show=True):
702        self.widgetsToolBar.setVisible(show)
703        self.settings["showWidgetToolbar"] = show
704        self.showWidgetToolbarAction.setChecked(show)
705       
706    def createPopupMenu(self):
707        """ Create a menu with show toolbar entries.
708        """
709        toolbars = QMenu("Show Toolbars", self)
710        toolbars.addAction(self.showMainToolbarAction)
711        toolbars.addAction(self.showWidgetToolbarAction)
712        return toolbars
713       
714    def toogleToolbarState(self):
715        """ Toogle the toolbar state (Mac OSX specific). This gets called when
716        the toolbar button in the unified title bar was clicked.
717        """
718        state = not self.settings["showToolbar"]
719        self.settings["showToolbar"] = state
720        self.showMainToolbarAction.setChecked(state)
721       
722
723    def menuItemEditWidgetShortcuts(self):
724        dlg = orngDlgs.WidgetShortcutDlg(self, self)
725        if dlg.exec_() == QDialog.Accepted:
726            self.widgetShortcuts = dict([(y, x) for x, y in dlg.invDict.items()])
727            shf = file(os.path.join(self.canvasSettingsDir, "shortcuts.txt"), "wt")
728            for k, widgetInfo in self.widgetShortcuts.items():
729                shf.write("%s: %s\n" % (k, (widgetInfo.category, widgetInfo.name)))
730
731    def menuItemDeleteWidgetSettings(self):
732        if QMessageBox.warning(self, 'Orange Canvas', 'Delete all settings?\nNote that for a complete reset there should be no open schema with any widgets.', QMessageBox.Ok | QMessageBox.Default, QMessageBox.Cancel | QMessageBox.Escape) == QMessageBox.Ok:
733            if os.path.exists(self.widgetSettingsDir):
734                for f in os.listdir(self.widgetSettingsDir):
735                    if os.path.splitext(f)[1].lower() == ".ini":
736                        os.remove(os.path.join(self.widgetSettingsDir, f))
737
738    def menuOpenLocalOrangeHelp(self):
739        import webbrowser
740        webbrowser.open("file:///" + os.path.join(self.orangeDir, "doc/reference/default.htm"))
741
742    def menuOpenLocalWidgetCatalog(self):
743        import webbrowser
744        webbrowser.open("file:///" + os.path.join(self.orangeDir, "doc/catalog/index.html"))
745
746    def menuOpenLocalCanvasHelp(self):
747        import webbrowser
748        webbrowser.open(os.path.join(self.orangeDir, "doc/canvas/default.htm"))
749
750    def menuOpenOnlineOrangeReference(self):
751        import webbrowser
752        webbrowser.open("http://orange.biolab.si/doc/reference/")
753       
754    def menuOpenOnlineOrangeHelp(self):
755        import webbrowser
756        webbrowser.open("http://orange.biolab.si/doc/widgets/")
757
758    def menuOpenOnlineCanvasHelp(self):
759        import webbrowser
760        #webbrowser.open("http://orange.biolab.si/orangeCanvas") # to be added on the web
761        webbrowser.open("http://orange.biolab.si")
762
763    def menuCheckForUpdates(self):
764        import updateOrange
765        self.updateDlg = updateOrange.updateOrangeDlg(None)#, Qt.WA_DeleteOnClose)
766
767    def menuItemAboutOrange(self):
768        dlg = orngDlgs.AboutDlg(self)
769        dlg.exec_()
770
771
772## to see the signals you have to call: self.output.catchException(0); self.output.catchOutput(0)
773## and run orngCanvas.pyw from command line using "python.exe orngCanvas.pyw"
774#    def event(self, e):
775#        eventDict = dict([(0, "None"), (130, "AccessibilityDescription"), (119, "AccessibilityHelp"), (86, "AccessibilityPrepare"), (114, "ActionAdded"), (113, "ActionChanged"), (115, "ActionRemoved"), (99, "ActivationChange"), (121, "ApplicationActivated"), (122, "ApplicationDeactivated"), (36, "ApplicationFontChange"), (37, "ApplicationLayoutDirectionChange"), (38, "ApplicationPaletteChange"), (35, "ApplicationWindowIconChange"), (68, "ChildAdded"), (69, "ChildPolished"), (71, "ChildRemoved"), (40, "Clipboard"), (19, "Close"), (82, "ContextMenu"), (52, "DeferredDelete"), (60, "DragEnter"), (62, "DragLeave"), (61, "DragMove"), (63, "Drop"), (98, "EnabledChange"), (10, "Enter"), (150, "EnterEditFocus"), (124, "EnterWhatsThisMode"), (116, "FileOpen"), (8, "FocusIn"), (9, "FocusOut"), (97, "FontChange"), (159, "GraphicsSceneContextMenu"), (164, "GraphicsSceneDragEnter"), (166, "GraphicsSceneDragLeave"), (165, "GraphicsSceneDragMove"), (167, "GraphicsSceneDrop"), (163, "GraphicsSceneHelp"), (160, "GraphicsSceneHoverEnter"), (162, "GraphicsSceneHoverLeave"), (161, "GraphicsSceneHoverMove"), (158, "GraphicsSceneMouseDoubleClick"), (155, "GraphicsSceneMouseMove"), (156, "GraphicsSceneMousePress"), (157, "GraphicsSceneMouseRelease"), (168, "GraphicsSceneWheel"), (18, "Hide"), (27, "HideToParent"), (127, "HoverEnter"), (128, "HoverLeave"), (129, "HoverMove"), (96, "IconDrag"), (101, "IconTextChange"), (83, "InputMethod"), (6, "KeyPress"), (7, "KeyRelease"), (89, "LanguageChange"), (90, "LayoutDirectionChange"), (76, "LayoutRequest"), (11, "Leave"), (151, "LeaveEditFocus"), (125, "LeaveWhatsThisMode"), (88, "LocaleChange"), (153, "MenubarUpdated"), (43, "MetaCall"), (102, "ModifiedChange"), (4, "MouseButtonDblClick"), (2, "MouseButtonPress"), (3, "MouseButtonRelease"), (5, "MouseMove"), (109, "MouseTrackingChange"), (13, "Move"), (12, "Paint"), (39, "PaletteChange"), (131, "ParentAboutToChange"), (21, "ParentChange"), (75, "Polish"), (74, "PolishRequest"), (123, "QueryWhatsThis"), (14, "Resize"), (117, "Shortcut"), (51, "ShortcutOverride"), (17, "Show"), (26, "ShowToParent"), (50, "SockAct"), (112, "StatusTip"), (100, "StyleChange"), (87, "TabletMove"), (92, "TabletPress"), (93, "TabletRelease"), (171, "TabletEnterProximity"), (172, "TabletLeaveProximity"), (1, "Timer"), (120, "ToolBarChange"), (110, "ToolTip"), (78, "UpdateLater"), (77, "UpdateRequest"), (111, "WhatsThis"), (118, "WhatsThisClicked"), (31, "Wheel"), (132, "WinEventAct"), (24, "WindowActivate"), (103, "WindowBlocked"), (25, "WindowDeactivate"), (34, "WindowIconChange"), (105, "WindowStateChange"), (33, "WindowTitleChange"), (104, "WindowUnblocked"), (126, "ZOrderChange"), (169, "KeyboardLayoutChange"), (170, "DynamicPropertyChange")])
776#        if eventDict.has_key(e.type()):
777#            print str(self.windowTitle()), eventDict[e.type()]
778#        return QMainWindow.event(self, e)
779
780
781    def menuItemCanvasOptions(self):
782        dlg = orngDlgs.CanvasOptionsDlg(self, self)
783
784        if dlg.exec_() == QDialog.Accepted:
785            if self.settings["snapToGrid"] != dlg.settings["snapToGrid"]:
786                self.updateSnapToGrid()
787
788            if self.settings["widgetListType"] != dlg.settings["widgetListType"]:
789                self.settings["widgetListType"] = dlg.settings["widgetListType"]
790                self.createWidgetsToolbar()
791                self.widgetListTypeCB.setCurrentIndex(self.settings["widgetListType"])
792            self.settings.update(dlg.settings)
793            self.updateStyle()
794           
795#            self.widgetSelectedColor = dlg.selectedWidgetIcon.color
796#            self.widgetActiveColor   = dlg.activeWidgetIcon.color
797#            self.lineColor           = dlg.lineIcon.color
798           
799            # update settings in widgets in current documents
800            for widget in self.schema.widgets:
801                widget.instance._useContexts = self.settings["useContexts"]
802                widget.instance._owInfo = self.settings["owInfo"]
803                widget.instance._owWarning = self.settings["owWarning"]
804                widget.instance._owError = self.settings["owError"]
805                widget.instance._owShowStatus = self.settings["owShow"]
806                widget.instance.updateStatusBarState()
807                widget.resetWidgetSize()
808                widget.updateWidgetState()
809               
810            # update tooltips for lines in all documents
811            for line in self.schema.lines:
812                line.showSignalNames = self.settings["showSignalNames"]
813                line.updateTooltip()
814           
815            self.schema.canvasView.repaint()
816       
817#            import orngEnviron, orngRegistry
818#            if dlg.toAdd != []:
819#                for (name, dir) in dlg.toAdd:
820#                    orngEnviron.registerAddOn(name, dir)
821           
822#            if dlg.toRemove != []:
823#                for (catName, cat) in dlg.toRemove:
824#                    addonsToRemove = set()
825#                    for widget in cat.values():
826#                        addonDir = widget.directory
827#                        while os.path.split(addonDir)[1] in ["prototypes", "widgets"]:
828#                            addonDir = os.path.split(addonDir)[0]
829#                        addonName = os.path.split(addonDir)[1]
830#                        addonsToRemove.add( (addonName, addonDir) )
831#                    for addonToRemove in addonsToRemove:
832#                        orngEnviron.registerAddOn(add=False, *addonToRemove)
833#           
834#            if dlg.toAdd != [] or dlg.toRemove != []:
835#                self.widgetRegistry = orngRegistry.readCategories()
836
837            # save tab order settings
838            newTabList = [(str(dlg.tabOrderList.item(i).text()), int(dlg.tabOrderList.item(i).checkState())) for i in range(dlg.tabOrderList.count())]
839            if newTabList != self.settings["WidgetTabs"]:
840                self.settings["WidgetTabs"] = newTabList
841                self.createWidgetsToolbar()
842                orngTabs.constructCategoriesPopup(self)
843
844    def menuItemAddOns(self):
845        import time
846        t = time.time()
847        lastRefresh = self.settings["lastAddonsRefresh"]
848        if t - lastRefresh > 7*24*3600:
849            if QMessageBox.question(self, "Refresh",
850                                    "List of add-ons in repositories has %s. Do you want to %s the lists now?" %
851                                    (("not yet been loaded" if lastRefresh==0 else "not been refreshed for more than a week"),
852                                     ("download" if lastRefresh==0 else "reload")),
853                                     QMessageBox.Yes | QMessageBox.Default,
854                                     QMessageBox.No | QMessageBox.Escape) == QMessageBox.Yes:
855               
856                anyFailed = False
857                anyDone = False
858                for r in Orange.utils.addons.available_repositories:
859                    #TODO: # Should show some progress (and enable cancellation)
860                    try:
861                        if r.refreshdata(force=True):
862                            anyDone = True
863                        else:
864                            anyFailed = True
865                    except Exception, e:
866                        anyFailed = True
867                        print "Unable to refresh repository %s! Error: %s" % (r.name, e)
868               
869                if anyDone:
870                    self.settings["lastAddonsRefresh"] = t
871                if anyFailed:
872                    QMessageBox.warning(self,'Download Failed', "Download of add-on list has failed for at least one repostitory.")
873       
874        dlg = orngDlgs.AddOnManagerDialog(self, self)
875        if dlg.exec_() == QDialog.Accepted:
876            for (id, addOn) in dlg.addOnsToRemove.items():
877                try:
878                    addOn.uninstall(refresh=False)
879                    if id in dlg.addOnsToAdd.items():
880                        Orange.utils.addons.install_addon_from_repo(dlg.addOnsToAdd[id], global_install=False, refresh=False)
881                        del dlg.addOnsToAdd[id]
882                except Exception, e:
883                    print "Problem %s add-on %s: %s" % ("upgrading" if id in dlg.addOnsToAdd else "removing", addOn.name, e)
884            for (id, addOn) in dlg.addOnsToAdd.items():
885                if id.startswith("registered:"):
886                    try:
887                        Orange.utils.addons.register_addon(addOn.name, addOn.directory, refresh=False, systemwide=False)
888                    except Exception, e:
889                        print "Problem registering add-on %s: %s" % (addOn.name, e)
890                else:
891                    try:
892                        Orange.utils.addons.install_addon_from_repo(dlg.addOnsToAdd[id], global_install=False, refresh=False)
893                    except Exception, e:
894                        print "Problem installing add-on %s: %s" % (addOn.name, e)
895            if len(dlg.addOnsToAdd)+len(dlg.addOnsToRemove)>0:
896                Orange.utils.addons.refresh_addons(reload_path=True)
897               
898    def menuItemShowStatusBar(self):
899        state = self.showStatusBarAction.isChecked()
900        self.statusBar().setVisible(state)
901        self.sizeGrip.setVisible(not state)
902        self.sizeGrip.raise_()
903        self.settings["showStatusBar"] = state
904
905    def updateStyle(self):
906        QApplication.setStyle(QStyleFactory.create(self.settings["style"]))
907#        qApp.setStyleSheet(" QDialogButtonBox { button-layout: 0; }")       # we want buttons to go in the "windows" direction (Yes, No, Cancel)
908        if self.settings["useDefaultPalette"]:
909            QApplication.setPalette(qApp.style().standardPalette())
910        else:
911            QApplication.setPalette(self.originalPalette)
912
913
914    def setStatusBarEvent(self, text):
915        if text == "" or text == None:
916            self.statusBar().showMessage("")
917            return
918        elif text == "\n": return
919        text = str(text)
920        text = text.replace("<nobr>", ""); text = text.replace("</nobr>", "")
921        text = text.replace("<b>", ""); text = text.replace("</b>", "")
922        text = text.replace("<i>", ""); text = text.replace("</i>", "")
923        text = text.replace("<br>", ""); text = text.replace("&nbsp", "")
924        self.statusBar().showMessage("Last event: " + str(text), 5000)
925
926    # Loads settings from the widget's .ini file
927    def loadSettings(self):
928        self.settings = {"widgetListType": 4, "iconSize": "40 x 40", "toolbarIconSize": 2, "toolboxWidth": 200, 'schemeIconSize': 1,
929                       "snapToGrid": 1, "writeLogFile": 1, "dontAskBeforeClose": 0, "saveWidgetsPosition": 1,
930#                       "widgetSelectedColor": (0, 255, 0), "widgetActiveColor": (0, 0, 255), "lineColor": (0, 255, 0),
931                       "reportsDir": self.defaultReportsDir, "saveSchemaDir": self.canvasSettingsDir, "saveApplicationDir": self.canvasSettingsDir,
932                       "showSignalNames": 1, "useContexts": 1, "enableCanvasDropShadows": 0,
933                       "canvasWidth": 700, "canvasHeight": 600, "useDefaultPalette": 0,
934                       "focusOnCatchException": 1, "focusOnCatchOutput": 0, "printOutputInStatusBar": 1, "printExceptionInStatusBar": 1,
935                       "outputVerbosity": 0, "synchronizeHelp": 1,
936                       "ocShow": 1, "owShow": 0, "ocInfo": 1, "owInfo": 1, "ocWarning": 1, "owWarning": 1, "ocError": 1, "owError": 1,
937                       "lastAddonsRefresh": 0}
938        if RedR:
939            self.setting.update({"svnSettings": None, "versionNumber": "Version0"})
940        try:
941            filename = os.path.join(self.canvasSettingsDir, "orngCanvas.ini")
942            self.settings.update(cPickle.load(open(filename, "rb")))
943        except:
944            pass
945
946        if not self.settings.has_key("style"):
947            items = [str(n) for n in QStyleFactory.keys()]
948            lowerItems = [str(n).lower() for n in QStyleFactory.keys()]
949            if sys.platform == "darwin" and qVersion() < "4.6": #On Mac OSX full aqua style isn't supported until Qt 4.6
950                currStyle = "cleanlooks"
951            else:
952                currStyle = str(qApp.style().objectName()).lower()
953            self.settings.setdefault("style", items[lowerItems.index(currStyle)])
954
955
956    # Saves settings to this widget's .ini file
957    def saveSettings(self):
958        filename = os.path.join(self.canvasSettingsDir, "orngCanvas.ini")
959        file = open(filename, "wb")
960        if self.settings["widgetListType"] == 1:        # tree view
961            self.settings["treeItemsOpenness"] = dict([(key, self.tabs.tabDict[key].isExpanded()) for key in self.tabs.tabDict.keys()])
962        cPickle.dump(self.settings, file)
963        file.close()
964
965    def closeEvent(self, ce):
966        # save the current width of the toolbox, if we are using it
967        if isinstance(self.widgetsToolBar, orngTabs.WidgetToolBox):
968            self.settings["toolboxWidth"] = self.widgetsToolBar.toolbox.width()
969        self.settings["showWidgetToolbar"] = self.widgetsToolBar.isVisible()
970        self.settings["showToolbar"] = self.toolbar.isVisible()
971        self.settings["reportsDir"] = self.reportWindow.saveDir
972
973        closed = self.schema.close()
974        if closed:
975            self.canvasIsClosing = 1        # output window (and possibly report window also) will check this variable before it will close the window
976           
977            self.helpWindow.close()
978            self.reportWindow.close()
979           
980            self.output.catchOutput(False)
981            self.output.catchException(False)
982            self.output.hide()
983            self.output.logFile.close()
984           
985            ce.accept()
986           
987        else:
988            ce.ignore()
989       
990        self.reportWindow.removeTemp()
991       
992        size = self.geometry().size()
993        self.settings["canvasWidth"] = size.width()
994        self.settings["canvasHeight"] = size.height()
995        self.settings["CanvasMainWindowGeometry"] = str(self.saveGeometry())
996       
997        self.saveSettings()
998       
999    def wheelEvent(self, event):
1000        """ Silently accept the wheel event. This is to ensure combo boxes
1001        and other controls that have focus don't receive this event unless
1002        the cursor is over them.
1003       
1004        """
1005        event.accept()
1006       
1007
1008    def setCaption(self, caption=""):
1009        if caption:
1010            caption = caption.split(".")[0]
1011            self.setWindowTitle(caption + " - %s Canvas" % product)
1012        else:
1013            self.setWindowTitle("%s Canvas" % product)
1014   
1015    def getWidgetIcon(self, widgetInfo):
1016        if self.iconNameToIcon.has_key(widgetInfo.icon):
1017            return self.iconNameToIcon[widgetInfo.icon]
1018       
1019        iconNames = self.getFullWidgetIconName(widgetInfo)
1020        iconBackgrounds = self.getFullIconBackgroundName(widgetInfo)
1021        icon = QIcon()
1022        if len(iconNames) == 1:
1023            iconSize = QPixmap(iconNames[0]).width()
1024            iconBackgrounds = [name for name in iconBackgrounds if QPixmap(name).width() == iconSize]
1025        for name, back in zip(iconNames, iconBackgrounds):
1026            image = QPixmap(back).toImage()
1027            painter = QPainter(image)
1028            painter.drawPixmap(0, 0, QPixmap(name))
1029            painter.end()
1030            icon.addPixmap(QPixmap.fromImage(image))
1031        if iconNames != [self.defaultPic]:
1032            self.iconNameToIcon[widgetInfo.icon] = icon
1033        return icon
1034           
1035   
1036    def getFullWidgetIconName(self, widgetInfo):
1037        iconName = widgetInfo.icon
1038        names = []
1039        name, ext = os.path.splitext(iconName)
1040        for num in [16, 32, 40, 48, 60]:
1041            names.append("%s_%d%s" % (name, num, ext))
1042       
1043        if widgetInfo.module:
1044            widgetDir = ''
1045        else:
1046            widgetDir = str(widgetInfo.directory)  #os.path.split(self.getFileName())[0]
1047        fullPaths = []
1048        for paths in [(self.widgetDir, widgetInfo.category), (self.widgetDir,), (self.picsDir,), tuple(), (widgetDir,), (widgetDir, "icons")]:
1049            for name in names + [iconName]:
1050                fname = os.path.join(*paths + (name,))
1051                if widgetInfo.module:
1052                    if pkg_resources.resource_exists(widgetInfo.module, fname):
1053                        # TODO: Optimize, we should not be required to extract the icon
1054                        fullPaths.append(pkg_resources.resource_filename(widgetInfo.module, fname))
1055                elif os.path.exists(fname):
1056                    fullPaths.append(fname)
1057            if len(fullPaths) > 1 and fullPaths[-1].endswith(iconName):
1058                fullPaths.pop()     # if we have the new icons we can remove the default icon
1059            if fullPaths != []:
1060                return fullPaths
1061        return [self.defaultPic]
1062   
1063    def getFullIconBackgroundName(self, widgetInfo):
1064        if widgetInfo.module:
1065            widgetDir = ''
1066        else:
1067            widgetDir = str(widgetInfo.directory)
1068        fullPaths = []
1069        for paths in [(widgetDir, "icons"), (self.widgetDir, widgetInfo.category, "icons"), (self.widgetDir, "icons"), (self.picsDir,), tuple(), (widgetDir,), (widgetDir, "icons")]:
1070            for name in ["background_%d.png" % num for num in [16, 32, 40, 48, 60]]:
1071                fname = os.path.join(*paths + (name,))
1072                if widgetInfo.module:
1073                    if pkg_resources.resource_exists(widgetInfo.module, fname):
1074                        # TODO: Optimize, we should not be required to extract the icon
1075                        fullPaths.append(pkg_resources.resource_filename(widgetInfo.module, fname))
1076                elif os.path.exists(fname):
1077                    fullPaths.append(fname)
1078            if fullPaths != []:
1079                return fullPaths   
1080        return [self.defaultBackground]
1081   
1082class MyStatusBar(QStatusBar):
1083    def __init__(self, parent):
1084        QStatusBar.__init__(self, parent)
1085        self.parentWidget = parent
1086
1087    def mouseDoubleClickEvent(self, ev):
1088        self.parentWidget.menuItemShowOutputWindow()
1089       
1090class SizeGrip(QSizeGrip):
1091    def __init__(self, mainwindow):
1092        QSizeGrip.__init__(self, mainwindow)
1093        mainwindow.installEventFilter(self)
1094        self.updateMyPos(mainwindow)
1095       
1096    def eventFilter(self, obj, event):
1097        if obj is self.parent() and isinstance(event, QResizeEvent):
1098            self.updateMyPos(obj)
1099           
1100        return QSizeGrip.eventFilter(self, obj, event)
1101   
1102    def updateMyPos(self, mainwindow):
1103        window_size = mainwindow.size()
1104        mysize = self.size()
1105        self.move(window_size.width() - mysize.width(),
1106                  window_size.height() - mysize.height())
1107           
1108from collections import defaultdict
1109class EventNotifier(QObject):
1110    """ An Qt event notifier.
1111    """
1112    def __init__(self, parent=None):
1113        QObject.__init__(self, parent)
1114        self._filtering = set()
1115        self._attached = defaultdict(list)
1116       
1117    def attach(self, obj, event, slot):
1118        if hasattr(event, "__iter__"):
1119            events = list(event)
1120        else:
1121            events = [event]
1122        for e in events:
1123            self._attached[obj, e].append(slot)
1124       
1125        if obj not in self._filtering:
1126            self._filtering.add(obj)
1127            obj.installEventFilter(self)
1128           
1129    def detach(self, obj, event, slot=None):
1130        if hasattr(event, "__iter__"):
1131            events = list(event)
1132        else:
1133            events = [event]
1134        for e in events:
1135            slots = self._attached.get((obj, e), [])
1136            if slot is None:
1137                slots_to_remove = slots
1138            else:
1139                slots_to_remove = [slot]
1140            for s in slots_to_remove:
1141                if s in slots:
1142                    slots.remove(s)
1143            if not slots:
1144                del self._attached[obj, e]
1145       
1146        if not any([s for (obj_, _), s in self._attached.items() \
1147                    if obj_ == obj]):
1148            # If obj has no more slots
1149            self._filtering.remove(obj)
1150            obj.removeEventFilter(self)
1151           
1152    def eventFilter(self, obj, event):
1153        if obj in self._filtering:
1154            if (obj, type(event)) in self._attached or (obj, event.type()) in self._attached:
1155                slots = self._attached.get((obj, type(event)), []) + \
1156                        self._attached.get((obj, event.type()), [])
1157                for slot in slots:
1158                    slot()
1159        return False
1160   
1161       
1162class OrangeQApplication(QApplication):
1163    def __init__(self, *args):
1164        QApplication.__init__(self, *args)
1165       
1166    #QFileOpenEvent are Mac OSX only
1167    if sys.platform == "darwin":
1168        def event(self, event):
1169            if event.type() == QEvent.FileOpen:
1170                file = str(event.file())
1171                def send():
1172                    if hasattr(qApp, "canvasDlg"):
1173                        qApp.canvasDlg.openSchema(file)
1174                    else:
1175                        QTimer.singleShot(100, send)
1176                send()
1177            return QApplication.event(self, event)
1178       
1179#    def notify(self, receiver, event):
1180#        eventDict = {0: 'None', 1: 'Timer', 2: 'MouseButtonPress', 3: 'MouseButtonRelease', 4: 'MouseButtonDblClick', 5: 'MouseMove', 6: 'KeyPress', 7: 'KeyRelease', 8: 'FocusIn', 9: 'FocusOut', 10: 'Enter', 11: 'Leave', 12: 'Paint', 13: 'Move', 14: 'Resize', 17: 'Show', 18: 'Hide', 19: 'Close', 21: 'ParentChange', 24: 'WindowActivate', 25: 'WindowDeactivate', 26: 'ShowToParent', 27: 'HideToParent', 31: 'Wheel', 33: 'WindowTitleChange', 34: 'WindowIconChange', 35: 'ApplicationWindowIconChange', 36: 'ApplicationFontChange', 37: 'ApplicationLayoutDirectionChange', 38: 'ApplicationPaletteChange', 39: 'PaletteChange', 40: 'Clipboard', 43: 'MetaCall', 50: 'SockAct', 51: 'ShortcutOverride', 52: 'DeferredDelete', 60: 'DragEnter', 61: 'DragMove', 62: 'DragLeave', 63: 'Drop', 68: 'ChildAdded', 69: 'ChildPolished', 70: 'ChildInserted', 71: 'ChildRemoved', 74: 'PolishRequest', 75: 'Polish', 76: 'LayoutRequest', 77: 'UpdateRequest', 78: 'UpdateLater', 82: 'ContextMenu', 83: 'InputMethod', 86: 'AccessibilityPrepare', 87: 'TabletMove', 88: 'LocaleChange', 89: 'LanguageChange', 90: 'LayoutDirectionChange', 92: 'TabletPress', 93: 'TabletRelease', 94: 'OkRequest', 96: 'IconDrag', 97: 'FontChange', 98: 'EnabledChange', 99: 'ActivationChange', 100: 'StyleChange', 101: 'IconTextChange', 102: 'ModifiedChange', 103: 'WindowBlocked', 104: 'WindowUnblocked', 105: 'WindowStateChange', 109: 'MouseTrackingChange', 110: 'ToolTip', 111: 'WhatsThis', 112: 'StatusTip', 113: 'ActionChanged', 114: 'ActionAdded', 115: 'ActionRemoved', 116: 'FileOpen', 117: 'Shortcut', 118: 'WhatsThisClicked', 119: 'AccessibilityHelp', 120: 'ToolBarChange', 121: 'ApplicationActivate', 122: 'ApplicationDeactivate', 123: 'QueryWhatsThis', 124: 'EnterWhatsThisMode', 125: 'LeaveWhatsThisMode', 126: 'ZOrderChange', 127: 'HoverEnter', 128: 'HoverLeave', 129: 'HoverMove', 130: 'AccessibilityDescription', 131: 'ParentAboutToChange', 132: 'WinEventAct', 150: 'EnterEditFocus', 151: 'LeaveEditFocus', 153: 'MenubarUpdated', 155: 'GraphicsSceneMouseMove', 156: 'GraphicsSceneMousePress', 157: 'GraphicsSceneMouseRelease', 158: 'GraphicsSceneMouseDoubleClick', 159: 'GraphicsSceneContextMenu', 160: 'GraphicsSceneHoverEnter', 161: 'GraphicsSceneHoverMove', 162: 'GraphicsSceneHoverLeave', 163: 'GraphicsSceneHelp', 164: 'GraphicsSceneDragEnter', 165: 'GraphicsSceneDragMove', 166: 'GraphicsSceneDragLeave', 167: 'GraphicsSceneDrop', 168: 'GraphicsSceneWheel', 169: 'KeyboardLayoutChange', 170: 'DynamicPropertyChange', 171: 'TabletEnterProximity', 172: 'TabletLeaveProximity', 173: 'NonClientAreaMouseMove', 174: 'NonClientAreaMouseButtonPress', 175: 'NonClientAreaMouseButtonRelease', 176: 'NonClientAreaMouseButtonDblClick', 177: 'MacSizeChange', 178: 'ContentsRectChange', 181: 'GraphicsSceneResize', 182: 'GraphicsSceneMove', 183: 'CursorChange', 184: 'ToolTipChange', 186: 'GrabMouse', 187: 'UngrabMouse', 188: 'GrabKeyboard', 189: 'UngrabKeyboard'}
1181#        import time
1182#        if isinstance(receiver, QWidget):
1183#            if str(receiver.windowTitle()) != "":
1184#                print time.strftime("%H:%M:%S"), "%15s %15s" % (str(receiver.windowTitle()) ,type(receiver).__name__)  + ": ",      # print only events for QWidget classes and up
1185#                if eventDict.has_key(event.type()):
1186#                    print eventDict[event.type()]
1187#                else:
1188#                    print "unknown event name (" + str(event.type()) + ")"
1189#        else:
1190#            print time.strftime("%H:%M:%S"), type(receiver).__name__, str(receiver.objectName()),
1191#            if eventDict.has_key(event.type()):
1192#                print eventDict[event.type()]
1193#            else:
1194#                print "unknown event name (" + str(event.type()) + ")"
1195#               
1196#        return QApplication.notify(self, receiver, event)
1197
1198
1199def main(argv=None):
1200    if argv == None:
1201        argv = sys.argv
1202
1203    app = OrangeQApplication(sys.argv)
1204    dlg = OrangeCanvasDlg(app)
1205    qApp.canvasDlg = dlg
1206    dlg.show()
1207    for arg in sys.argv[1:]:
1208        if arg == "-reload":
1209            dlg.menuItemOpenLastSchema()
1210    r = app.exec_()
1211    app.closeAllWindows()
1212   
1213    del qApp.canvasDlg
1214    del dlg
1215   
1216    app.processEvents()
1217   
1218    # Call gc.collect before the app is destroyed to colect any remainig
1219    # cycles. Some sip objects are stored in it's global state (for instance
1220    # QGraphicsScene) and can cause a crash in Py_Finalize when sip trys to
1221    # deallocate it (a bug in sip?).
1222   
1223    gc.collect()
1224    del app
1225   
1226    return r
1227
1228if __name__ == "__main__":
1229    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.