source: orange/Orange/OrangeCanvas/orngCanvas.pyw @ 11018:b7cbf2b86522

Revision 11018:b7cbf2b86522, 59.7 KB checked in by Matija Polajnar <matija.polajnar@…>, 17 months ago (diff)

Rewrite the add-on support modules and GUI to support the new properly packed add-ons, published on PyPI.

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