source: orange/Orange/OrangeCanvas/orngCanvas.pyw @ 10838:3e65e0fec526

Revision 10838:3e65e0fec526, 60.6 KB checked in by mitar, 2 years ago (diff)

Support also add-ons in Python eggs.

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