source: orange/Orange/OrangeCanvas/orngCanvas.pyw @ 11024:e7cba04312ff

Revision 11024:e7cba04312ff, 58.8 KB checked in by Matija Polajnar <matija.polajnar@…>, 18 months ago (diff)

Add-ons: more robust handling of the add-ons database.

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        dlg = orngDlgs.AddOnManagerDialog(self, self)
849        if t - lastRefresh > 7*24*3600 or Orange.utils.addons.addons_corrupted:
850            dlg.show()
851            if Orange.utils.addons.addons_corrupted or \
852               QMessageBox.question(self, "Refresh",
853                                    "List of add-ons in repository has not been refreshed for more than a week. Do you want to download the list now?",
854                                     QMessageBox.Yes | QMessageBox.Default,
855                                     QMessageBox.No | QMessageBox.Escape) == QMessageBox.Yes:
856
857                try:
858                    dlg.reloadRepo()
859                    self.settings["lastAddonsRefresh"] = time.time()
860                except Exception, e:
861                    import traceback
862                    traceback.print_exc()
863                    QMessageBox.warning(self,'Download Failed', "Download of add-on list has failed.")
864
865        dlg.exec_()
866
867    def menuItemShowStatusBar(self):
868        state = self.showStatusBarAction.isChecked()
869        self.statusBar().setVisible(state)
870        self.sizeGrip.setVisible(not state)
871        self.sizeGrip.raise_()
872        self.settings["showStatusBar"] = state
873
874    def updateStyle(self):
875        QApplication.setStyle(QStyleFactory.create(self.settings["style"]))
876#        qApp.setStyleSheet(" QDialogButtonBox { button-layout: 0; }")       # we want buttons to go in the "windows" direction (Yes, No, Cancel)
877        if self.settings["useDefaultPalette"]:
878            QApplication.setPalette(qApp.style().standardPalette())
879        else:
880            QApplication.setPalette(self.originalPalette)
881
882
883    def setStatusBarEvent(self, text):
884        if text == "" or text == None:
885            self.statusBar().showMessage("")
886            return
887        elif text == "\n": return
888        text = str(text)
889        text = text.replace("<nobr>", ""); text = text.replace("</nobr>", "")
890        text = text.replace("<b>", ""); text = text.replace("</b>", "")
891        text = text.replace("<i>", ""); text = text.replace("</i>", "")
892        text = text.replace("<br>", ""); text = text.replace("&nbsp", "")
893        self.statusBar().showMessage("Last event: " + str(text), 5000)
894
895    # Loads settings from the widget's .ini file
896    def loadSettings(self):
897        self.settings = {"widgetListType": 4, "iconSize": "40 x 40", "toolbarIconSize": 2, "toolboxWidth": 200, 'schemeIconSize': 1,
898                       "snapToGrid": 1, "writeLogFile": 1, "dontAskBeforeClose": 0, "saveWidgetsPosition": 1,
899#                       "widgetSelectedColor": (0, 255, 0), "widgetActiveColor": (0, 0, 255), "lineColor": (0, 255, 0),
900                       "reportsDir": self.defaultReportsDir, "saveSchemaDir": self.canvasSettingsDir, "saveApplicationDir": self.canvasSettingsDir,
901                       "showSignalNames": 1, "useContexts": 1, "enableCanvasDropShadows": 0,
902                       "canvasWidth": 700, "canvasHeight": 600, "useDefaultPalette": 0,
903                       "focusOnCatchException": 1, "focusOnCatchOutput": 0, "printOutputInStatusBar": 1, "printExceptionInStatusBar": 1,
904                       "outputVerbosity": 0, "synchronizeHelp": 1,
905                       "ocShow": 1, "owShow": 0, "ocInfo": 1, "owInfo": 1, "ocWarning": 1, "owWarning": 1, "ocError": 1, "owError": 1,
906                       "lastAddonsRefresh": 0}
907        if RedR:
908            self.setting.update({"svnSettings": None, "versionNumber": "Version0"})
909        try:
910            filename = os.path.join(self.canvasSettingsDir, "orngCanvas.ini")
911            self.settings.update(cPickle.load(open(filename, "rb")))
912        except:
913            pass
914
915        if not self.settings.has_key("style"):
916            items = [str(n) for n in QStyleFactory.keys()]
917            lowerItems = [str(n).lower() for n in QStyleFactory.keys()]
918            if sys.platform == "darwin" and qVersion() < "4.6": #On Mac OSX full aqua style isn't supported until Qt 4.6
919                currStyle = "cleanlooks"
920            else:
921                currStyle = str(qApp.style().objectName()).lower()
922            self.settings.setdefault("style", items[lowerItems.index(currStyle)])
923
924
925    # Saves settings to this widget's .ini file
926    def saveSettings(self):
927        filename = os.path.join(self.canvasSettingsDir, "orngCanvas.ini")
928        file = open(filename, "wb")
929        if self.settings["widgetListType"] == 1:        # tree view
930            self.settings["treeItemsOpenness"] = dict([(key, self.tabs.tabDict[key].isExpanded()) for key in self.tabs.tabDict.keys()])
931        cPickle.dump(self.settings, file)
932        file.close()
933
934    def closeEvent(self, ce):
935        # save the current width of the toolbox, if we are using it
936        if isinstance(self.widgetsToolBar, orngTabs.WidgetToolBox):
937            self.settings["toolboxWidth"] = self.widgetsToolBar.toolbox.width()
938        self.settings["showWidgetToolbar"] = self.widgetsToolBar.isVisible()
939        self.settings["showToolbar"] = self.toolbar.isVisible()
940        self.settings["reportsDir"] = self.reportWindow.saveDir
941
942        closed = self.schema.close()
943        if closed:
944            self.canvasIsClosing = 1        # output window (and possibly report window also) will check this variable before it will close the window
945           
946            self.helpWindow.close()
947            self.reportWindow.close()
948           
949            self.output.catchOutput(False)
950            self.output.catchException(False)
951            self.output.hide()
952            self.output.logFile.close()
953           
954            ce.accept()
955           
956        else:
957            ce.ignore()
958       
959        self.reportWindow.removeTemp()
960       
961        size = self.geometry().size()
962        self.settings["canvasWidth"] = size.width()
963        self.settings["canvasHeight"] = size.height()
964        self.settings["CanvasMainWindowGeometry"] = str(self.saveGeometry())
965       
966        self.saveSettings()
967       
968    def wheelEvent(self, event):
969        """ Silently accept the wheel event. This is to ensure combo boxes
970        and other controls that have focus don't receive this event unless
971        the cursor is over them.
972       
973        """
974        event.accept()
975       
976
977    def setCaption(self, caption=""):
978        if caption:
979            caption = caption.split(".")[0]
980            self.setWindowTitle(caption + " - %s Canvas" % product)
981        else:
982            self.setWindowTitle("%s Canvas" % product)
983   
984    def getWidgetIcon(self, widgetInfo):
985        if self.iconNameToIcon.has_key(widgetInfo.icon):
986            return self.iconNameToIcon[widgetInfo.icon]
987       
988        iconNames = self.getFullWidgetIconName(widgetInfo)
989        iconBackgrounds = self.getFullIconBackgroundName(widgetInfo)
990        icon = QIcon()
991        if len(iconNames) == 1:
992            iconSize = QPixmap(iconNames[0]).width()
993            iconBackgrounds = [name for name in iconBackgrounds if QPixmap(name).width() == iconSize]
994        for name, back in zip(iconNames, iconBackgrounds):
995            image = QPixmap(back).toImage()
996            painter = QPainter(image)
997            painter.drawPixmap(0, 0, QPixmap(name))
998            painter.end()
999            icon.addPixmap(QPixmap.fromImage(image))
1000        if iconNames != [self.defaultPic]:
1001            self.iconNameToIcon[widgetInfo.icon] = icon
1002        return icon
1003           
1004   
1005    def getFullWidgetIconName(self, widgetInfo):
1006        iconName = widgetInfo.icon
1007        names = []
1008        name, ext = os.path.splitext(iconName)
1009        for num in [16, 32, 40, 48, 60]:
1010            names.append("%s_%d%s" % (name, num, ext))
1011       
1012        if widgetInfo.module:
1013            widgetDir = ''
1014        else:
1015            widgetDir = str(widgetInfo.directory)  #os.path.split(self.getFileName())[0]
1016        fullPaths = []
1017        for paths in [(self.widgetDir, widgetInfo.category), (self.widgetDir,), (self.picsDir,), tuple(), (widgetDir,), (widgetDir, "icons")]:
1018            for name in names + [iconName]:
1019                fname = os.path.join(*paths + (name,))
1020                if widgetInfo.module:
1021                    if pkg_resources.resource_exists(widgetInfo.module, fname):
1022                        # TODO: Optimize, we should not be required to extract the icon
1023                        fullPaths.append(pkg_resources.resource_filename(widgetInfo.module, fname))
1024                elif os.path.exists(fname):
1025                    fullPaths.append(fname)
1026            if len(fullPaths) > 1 and fullPaths[-1].endswith(iconName):
1027                fullPaths.pop()     # if we have the new icons we can remove the default icon
1028            if fullPaths != []:
1029                return fullPaths
1030        return [self.defaultPic]
1031   
1032    def getFullIconBackgroundName(self, widgetInfo):
1033        if widgetInfo.module:
1034            widgetDir = ''
1035        else:
1036            widgetDir = str(widgetInfo.directory)
1037        fullPaths = []
1038        for paths in [(widgetDir, "icons"), (self.widgetDir, widgetInfo.category, "icons"), (self.widgetDir, "icons"), (self.picsDir,), tuple(), (widgetDir,), (widgetDir, "icons")]:
1039            for name in ["background_%d.png" % num for num in [16, 32, 40, 48, 60]]:
1040                fname = os.path.join(*paths + (name,))
1041                if widgetInfo.module:
1042                    if pkg_resources.resource_exists(widgetInfo.module, fname):
1043                        # TODO: Optimize, we should not be required to extract the icon
1044                        fullPaths.append(pkg_resources.resource_filename(widgetInfo.module, fname))
1045                elif os.path.exists(fname):
1046                    fullPaths.append(fname)
1047            if fullPaths != []:
1048                return fullPaths   
1049        return [self.defaultBackground]
1050   
1051class MyStatusBar(QStatusBar):
1052    def __init__(self, parent):
1053        QStatusBar.__init__(self, parent)
1054        self.parentWidget = parent
1055
1056    def mouseDoubleClickEvent(self, ev):
1057        self.parentWidget.menuItemShowOutputWindow()
1058       
1059class SizeGrip(QSizeGrip):
1060    def __init__(self, mainwindow):
1061        QSizeGrip.__init__(self, mainwindow)
1062        mainwindow.installEventFilter(self)
1063        self.updateMyPos(mainwindow)
1064       
1065    def eventFilter(self, obj, event):
1066        if obj is self.parent() and isinstance(event, QResizeEvent):
1067            self.updateMyPos(obj)
1068           
1069        return QSizeGrip.eventFilter(self, obj, event)
1070   
1071    def updateMyPos(self, mainwindow):
1072        window_size = mainwindow.size()
1073        mysize = self.size()
1074        self.move(window_size.width() - mysize.width(),
1075                  window_size.height() - mysize.height())
1076           
1077from collections import defaultdict
1078class EventNotifier(QObject):
1079    """ An Qt event notifier.
1080    """
1081    def __init__(self, parent=None):
1082        QObject.__init__(self, parent)
1083        self._filtering = set()
1084        self._attached = defaultdict(list)
1085       
1086    def attach(self, obj, event, slot):
1087        if hasattr(event, "__iter__"):
1088            events = list(event)
1089        else:
1090            events = [event]
1091        for e in events:
1092            self._attached[obj, e].append(slot)
1093       
1094        if obj not in self._filtering:
1095            self._filtering.add(obj)
1096            obj.installEventFilter(self)
1097           
1098    def detach(self, obj, event, slot=None):
1099        if hasattr(event, "__iter__"):
1100            events = list(event)
1101        else:
1102            events = [event]
1103        for e in events:
1104            slots = self._attached.get((obj, e), [])
1105            if slot is None:
1106                slots_to_remove = slots
1107            else:
1108                slots_to_remove = [slot]
1109            for s in slots_to_remove:
1110                if s in slots:
1111                    slots.remove(s)
1112            if not slots:
1113                del self._attached[obj, e]
1114       
1115        if not any([s for (obj_, _), s in self._attached.items() \
1116                    if obj_ == obj]):
1117            # If obj has no more slots
1118            self._filtering.remove(obj)
1119            obj.removeEventFilter(self)
1120           
1121    def eventFilter(self, obj, event):
1122        if obj in self._filtering:
1123            if (obj, type(event)) in self._attached or (obj, event.type()) in self._attached:
1124                slots = self._attached.get((obj, type(event)), []) + \
1125                        self._attached.get((obj, event.type()), [])
1126                for slot in slots:
1127                    slot()
1128        return False
1129   
1130       
1131class OrangeQApplication(QApplication):
1132    def __init__(self, *args):
1133        QApplication.__init__(self, *args)
1134       
1135    #QFileOpenEvent are Mac OSX only
1136    if sys.platform == "darwin":
1137        def event(self, event):
1138            if event.type() == QEvent.FileOpen:
1139                file = str(event.file())
1140                def send():
1141                    if hasattr(qApp, "canvasDlg"):
1142                        qApp.canvasDlg.openSchema(file)
1143                    else:
1144                        QTimer.singleShot(100, send)
1145                send()
1146            return QApplication.event(self, event)
1147       
1148#    def notify(self, receiver, event):
1149#        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'}
1150#        import time
1151#        if isinstance(receiver, QWidget):
1152#            if str(receiver.windowTitle()) != "":
1153#                print time.strftime("%H:%M:%S"), "%15s %15s" % (str(receiver.windowTitle()) ,type(receiver).__name__)  + ": ",      # print only events for QWidget classes and up
1154#                if eventDict.has_key(event.type()):
1155#                    print eventDict[event.type()]
1156#                else:
1157#                    print "unknown event name (" + str(event.type()) + ")"
1158#        else:
1159#            print time.strftime("%H:%M:%S"), type(receiver).__name__, str(receiver.objectName()),
1160#            if eventDict.has_key(event.type()):
1161#                print eventDict[event.type()]
1162#            else:
1163#                print "unknown event name (" + str(event.type()) + ")"
1164#               
1165#        return QApplication.notify(self, receiver, event)
1166
1167
1168def main(argv=None):
1169    if argv == None:
1170        argv = sys.argv
1171
1172    app = OrangeQApplication(sys.argv)
1173    dlg = OrangeCanvasDlg(app)
1174    qApp.canvasDlg = dlg
1175    dlg.show()
1176    for arg in sys.argv[1:]:
1177        if arg == "-reload":
1178            dlg.menuItemOpenLastSchema()
1179    r = app.exec_()
1180    app.closeAllWindows()
1181   
1182    del qApp.canvasDlg
1183    del dlg
1184   
1185    app.processEvents()
1186   
1187    # Call gc.collect before the app is destroyed to colect any remainig
1188    # cycles. Some sip objects are stored in it's global state (for instance
1189    # QGraphicsScene) and can cause a crash in Py_Finalize when sip trys to
1190    # deallocate it (a bug in sip?).
1191   
1192    gc.collect()
1193    del app
1194   
1195    return r
1196
1197if __name__ == "__main__":
1198    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.