source: orange/Orange/OrangeCanvas/orngCanvas.pyw @ 10689:1bb34376f08d

Revision 10689:1bb34376f08d, 60.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Canvas improvements as suggested by romzee, fixes #1154.

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