source: orange/Orange/OrangeCanvas/orngCanvas.pyw @ 10489:1175d1de82b7

Revision 10489:1175d1de82b7, 59.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Fixed a random Orange Canvas crash when exiting the application.

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.misc.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.misc.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        if not self.settings.has_key("RecentFiles"): return
608        recentDocs = self.settings["RecentFiles"]
609
610        # remove missing recent files
611        for i in range(len(recentDocs) - 1, -1, -1):
612            if not os.path.exists(recentDocs[i]):
613                recentDocs.remove(recentDocs[i])
614
615        recentDocs = recentDocs[:9]
616        self.settings["RecentFiles"] = recentDocs
617
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 + 1))
621
622    def openRecentFile(self, index):
623        if len(self.settings["RecentFiles"]) >= index:
624            self.schema.clear()
625            name = self.settings["RecentFiles"][index - 1]
626            dirname = os.path.dirname(name)
627            os.chdir(dirname)
628            self.schema.loadDocument(name)
629            self.addToRecentMenu(name)
630
631    def addToRecentMenu(self, name):
632        recentDocs = []
633        if self.settings.has_key("RecentFiles"):
634            recentDocs = self.settings["RecentFiles"]
635
636        # convert to a valid file name
637        name = os.path.realpath(name)
638
639        if name in recentDocs:
640            recentDocs.remove(name)
641        recentDocs.insert(0, name)
642
643        if len(recentDocs) > 5:
644            recentDocs.remove(recentDocs[5])
645        self.settings["RecentFiles"] = recentDocs
646        self.readRecentFiles()
647
648    def menuItemSelectAll(self):
649        return
650
651    def updateSnapToGrid(self):
652        if self.settings["snapToGrid"]:
653            for widget in self.schema.widgets:
654                widget.setCoords(widget.x(), widget.y())
655            self.schema.canvas.update()
656
657    def menuItemEnableAll(self):
658        self.schema.enableAllLines()
659
660    def menuItemDisableAll(self):
661        self.schema.disableAllLines()
662
663    def menuItemSaveSettings(self):
664        self.menuSaveSettings = not self.menuSaveSettings
665        self.menuOptions.setItemChecked(self.menuSaveSettingsID, self.menuSaveSettings)
666
667    def menuItemNewScheme(self):
668        if self.schema.saveBeforeClose():
669            self.schema.clear()
670            self.schema.removeTempDoc()
671
672    def dumpVariables(self):
673        self.schema.dumpWidgetVariables()
674
675    def menuItemShowOutputWindow(self):
676        self.output.show()
677        self.output.raise_()
678#        self.output.activateWindow()
679
680    def menuItemClearOutputWindow(self):
681        self.output.textOutput.clear()
682        self.statusBar().showMessage("")
683
684    def menuItemSaveOutputWindow(self):
685        qname = QFileDialog.getSaveFileName(self, "Save Output To File", self.canvasSettingsDir + "/Output.html", "HTML Document (*.html)")
686        if qname.isEmpty(): return
687       
688        text = str(self.output.textOutput.toHtml())
689        #text = text.replace("</nobr>", "</nobr><br>")
690
691        file = open(unicode(name), "wt")
692        file.write(text)
693        file.close()
694
695    def menuItemShowToolbar(self, show=True):
696        self.toolbar.setVisible(show)
697        self.settings["showToolbar"] = show
698        self.showMainToolbarAction.setChecked(show)
699
700    def menuItemShowWidgetToolbar(self, show=True):
701        self.widgetsToolBar.setVisible(show)
702        self.settings["showWidgetToolbar"] = show
703        self.showWidgetToolbarAction.setChecked(show)
704       
705    def createPopupMenu(self):
706        """ Create a menu with show toolbar entries.
707        """
708        toolbars = QMenu("Show Toolbars", self)
709        toolbars.addAction(self.showMainToolbarAction)
710        toolbars.addAction(self.showWidgetToolbarAction)
711        return toolbars
712       
713    def toogleToolbarState(self):
714        """ Toogle the toolbar state (Mac OSX specific). This gets called when
715        the toolbar button in the unified title bar was clicked.
716        """
717        state = not self.settings["showToolbar"]
718        self.settings["showToolbar"] = state
719        self.showMainToolbarAction.setChecked(state)
720       
721
722    def menuItemEditWidgetShortcuts(self):
723        dlg = orngDlgs.WidgetShortcutDlg(self, self)
724        if dlg.exec_() == QDialog.Accepted:
725            self.widgetShortcuts = dict([(y, x) for x, y in dlg.invDict.items()])
726            shf = file(os.path.join(self.canvasSettingsDir, "shortcuts.txt"), "wt")
727            for k, widgetInfo in self.widgetShortcuts.items():
728                shf.write("%s: %s\n" % (k, (widgetInfo.category, widgetInfo.name)))
729
730    def menuItemDeleteWidgetSettings(self):
731        if QMessageBox.warning(self, 'Orange Canvas', 'Delete all settings?\nNote that for a complete reset there should be no open schema with any widgets.', QMessageBox.Ok | QMessageBox.Default, QMessageBox.Cancel | QMessageBox.Escape) == QMessageBox.Ok:
732            if os.path.exists(self.widgetSettingsDir):
733                for f in os.listdir(self.widgetSettingsDir):
734                    if os.path.splitext(f)[1].lower() == ".ini":
735                        os.remove(os.path.join(self.widgetSettingsDir, f))
736
737    def menuOpenLocalOrangeHelp(self):
738        import webbrowser
739        webbrowser.open("file:///" + os.path.join(self.orangeDir, "doc/reference/default.htm"))
740
741    def menuOpenLocalWidgetCatalog(self):
742        import webbrowser
743        webbrowser.open("file:///" + os.path.join(self.orangeDir, "doc/catalog/index.html"))
744
745    def menuOpenLocalCanvasHelp(self):
746        import webbrowser
747        webbrowser.open(os.path.join(self.orangeDir, "doc/canvas/default.htm"))
748
749    def menuOpenOnlineOrangeHelp(self):
750        import webbrowser
751        webbrowser.open("http://orange.biolab.si/doc/catalog")
752
753    def menuOpenOnlineCanvasHelp(self):
754        import webbrowser
755        #webbrowser.open("http://orange.biolab.si/orangeCanvas") # to be added on the web
756        webbrowser.open("http://orange.biolab.si")
757
758    def menuCheckForUpdates(self):
759        import updateOrange
760        self.updateDlg = updateOrange.updateOrangeDlg(None)#, Qt.WA_DeleteOnClose)
761
762    def menuItemAboutOrange(self):
763        dlg = orngDlgs.AboutDlg(self)
764        dlg.exec_()
765
766
767## to see the signals you have to call: self.output.catchException(0); self.output.catchOutput(0)
768## and run orngCanvas.pyw from command line using "python.exe orngCanvas.pyw"
769#    def event(self, e):
770#        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")])
771#        if eventDict.has_key(e.type()):
772#            print str(self.windowTitle()), eventDict[e.type()]
773#        return QMainWindow.event(self, e)
774
775
776    def menuItemCanvasOptions(self):
777        dlg = orngDlgs.CanvasOptionsDlg(self, self)
778
779        if dlg.exec_() == QDialog.Accepted:
780            if self.settings["snapToGrid"] != dlg.settings["snapToGrid"]:
781                self.updateSnapToGrid()
782
783            if self.settings["widgetListType"] != dlg.settings["widgetListType"]:
784                self.settings["widgetListType"] = dlg.settings["widgetListType"]
785                self.createWidgetsToolbar()
786                self.widgetListTypeCB.setCurrentIndex(self.settings["widgetListType"])
787            self.settings.update(dlg.settings)
788            self.updateStyle()
789           
790#            self.widgetSelectedColor = dlg.selectedWidgetIcon.color
791#            self.widgetActiveColor   = dlg.activeWidgetIcon.color
792#            self.lineColor           = dlg.lineIcon.color
793           
794            # update settings in widgets in current documents
795            for widget in self.schema.widgets:
796                widget.instance._useContexts = self.settings["useContexts"]
797                widget.instance._owInfo = self.settings["owInfo"]
798                widget.instance._owWarning = self.settings["owWarning"]
799                widget.instance._owError = self.settings["owError"]
800                widget.instance._owShowStatus = self.settings["owShow"]
801                widget.instance.updateStatusBarState()
802                widget.resetWidgetSize()
803                widget.updateWidgetState()
804               
805            # update tooltips for lines in all documents
806            for line in self.schema.lines:
807                line.showSignalNames = self.settings["showSignalNames"]
808                line.updateTooltip()
809           
810            self.schema.canvasView.repaint()
811       
812#            import orngEnviron, orngRegistry
813#            if dlg.toAdd != []:
814#                for (name, dir) in dlg.toAdd:
815#                    orngEnviron.registerAddOn(name, dir)
816           
817#            if dlg.toRemove != []:
818#                for (catName, cat) in dlg.toRemove:
819#                    addonsToRemove = set()
820#                    for widget in cat.values():
821#                        addonDir = widget.directory
822#                        while os.path.split(addonDir)[1] in ["prototypes", "widgets"]:
823#                            addonDir = os.path.split(addonDir)[0]
824#                        addonName = os.path.split(addonDir)[1]
825#                        addonsToRemove.add( (addonName, addonDir) )
826#                    for addonToRemove in addonsToRemove:
827#                        orngEnviron.registerAddOn(add=False, *addonToRemove)
828#           
829#            if dlg.toAdd != [] or dlg.toRemove != []:
830#                self.widgetRegistry = orngRegistry.readCategories()
831
832            # save tab order settings
833            newTabList = [(str(dlg.tabOrderList.item(i).text()), int(dlg.tabOrderList.item(i).checkState())) for i in range(dlg.tabOrderList.count())]
834            if newTabList != self.settings["WidgetTabs"]:
835                self.settings["WidgetTabs"] = newTabList
836                self.createWidgetsToolbar()
837                orngTabs.constructCategoriesPopup(self)
838
839    def menuItemAddOns(self):
840        import time
841        t = time.time()
842        lastRefresh = self.settings["lastAddonsRefresh"]
843        if t - lastRefresh > 7*24*3600:
844            if QMessageBox.question(self, "Refresh",
845                                    "List of add-ons in repositories has %s. Do you want to %s the lists now?" %
846                                    (("not yet been loaded" if lastRefresh==0 else "not been refreshed for more than a week"),
847                                     ("download" if lastRefresh==0 else "reload")),
848                                     QMessageBox.Yes | QMessageBox.Default,
849                                     QMessageBox.No | QMessageBox.Escape) == QMessageBox.Yes:
850               
851                anyFailed = False
852                anyDone = False
853                for r in Orange.misc.addons.available_repositories:
854                    #TODO: # Should show some progress (and enable cancellation)
855                    try:
856                        if r.refreshdata(force=True):
857                            anyDone = True
858                        else:
859                            anyFailed = True
860                    except Exception, e:
861                        anyFailed = True
862                        print "Unable to refresh repository %s! Error: %s" % (r.name, e)
863               
864                if anyDone:
865                    self.settings["lastAddonsRefresh"] = t
866                if anyFailed:
867                    QMessageBox.warning(self,'Download Failed', "Download of add-on list has failed for at least one repostitory.")
868       
869        dlg = orngDlgs.AddOnManagerDialog(self, self)
870        if dlg.exec_() == QDialog.Accepted:
871            for (id, addOn) in dlg.addOnsToRemove.items():
872                try:
873                    addOn.uninstall(refresh=False)
874                    if id in dlg.addOnsToAdd.items():
875                        Orange.misc.addons.install_addon_from_repo(dlg.addOnsToAdd[id], global_install=False, refresh=False)
876                        del dlg.addOnsToAdd[id]
877                except Exception, e:
878                    print "Problem %s add-on %s: %s" % ("upgrading" if id in dlg.addOnsToAdd else "removing", addOn.name, e)
879            for (id, addOn) in dlg.addOnsToAdd.items():
880                if id.startswith("registered:"):
881                    try:
882                        Orange.misc.addons.register_addon(addOn.name, addOn.directory, refresh=False, systemwide=False)
883                    except Exception, e:
884                        print "Problem registering add-on %s: %s" % (addOn.name, e)
885                else:
886                    try:
887                        Orange.misc.addons.install_addon_from_repo(dlg.addOnsToAdd[id], global_install=False, refresh=False)
888                    except Exception, e:
889                        print "Problem installing add-on %s: %s" % (addOn.name, e)
890            if len(dlg.addOnsToAdd)+len(dlg.addOnsToRemove)>0:
891                Orange.misc.addons.refresh_addons(reload_path=True)
892               
893    def menuItemShowStatusBar(self):
894        state = self.showStatusBarAction.isChecked()
895        self.statusBar().setVisible(state)
896        self.sizeGrip.setVisible(not state)
897        self.sizeGrip.raise_()
898        self.settings["showStatusBar"] = state
899
900    def updateStyle(self):
901        QApplication.setStyle(QStyleFactory.create(self.settings["style"]))
902#        qApp.setStyleSheet(" QDialogButtonBox { button-layout: 0; }")       # we want buttons to go in the "windows" direction (Yes, No, Cancel)
903        if self.settings["useDefaultPalette"]:
904            QApplication.setPalette(qApp.style().standardPalette())
905        else:
906            QApplication.setPalette(self.originalPalette)
907
908
909    def setStatusBarEvent(self, text):
910        if text == "" or text == None:
911            self.statusBar().showMessage("")
912            return
913        elif text == "\n": return
914        text = str(text)
915        text = text.replace("<nobr>", ""); text = text.replace("</nobr>", "")
916        text = text.replace("<b>", ""); text = text.replace("</b>", "")
917        text = text.replace("<i>", ""); text = text.replace("</i>", "")
918        text = text.replace("<br>", ""); text = text.replace("&nbsp", "")
919        self.statusBar().showMessage("Last event: " + str(text), 5000)
920
921    # Loads settings from the widget's .ini file
922    def loadSettings(self):
923        self.settings = {"widgetListType": 4, "iconSize": "40 x 40", "toolbarIconSize": 2, "toolboxWidth": 200, 'schemeIconSize': 1,
924                       "snapToGrid": 1, "writeLogFile": 1, "dontAskBeforeClose": 0, "saveWidgetsPosition": 1,
925#                       "widgetSelectedColor": (0, 255, 0), "widgetActiveColor": (0, 0, 255), "lineColor": (0, 255, 0),
926                       "reportsDir": self.defaultReportsDir, "saveSchemaDir": self.canvasSettingsDir, "saveApplicationDir": self.canvasSettingsDir,
927                       "showSignalNames": 1, "useContexts": 1, "enableCanvasDropShadows": 0,
928                       "canvasWidth": 700, "canvasHeight": 600, "useDefaultPalette": 0,
929                       "focusOnCatchException": 1, "focusOnCatchOutput": 0, "printOutputInStatusBar": 1, "printExceptionInStatusBar": 1,
930                       "outputVerbosity": 0, "synchronizeHelp": 1,
931                       "ocShow": 1, "owShow": 0, "ocInfo": 1, "owInfo": 1, "ocWarning": 1, "owWarning": 1, "ocError": 1, "owError": 1,
932                       "lastAddonsRefresh": 0}
933        if RedR:
934            self.setting.update({"svnSettings": None, "versionNumber": "Version0"})
935        try:
936            filename = os.path.join(self.canvasSettingsDir, "orngCanvas.ini")
937            self.settings.update(cPickle.load(open(filename, "rb")))
938        except:
939            pass
940
941        if not self.settings.has_key("style"):
942            items = [str(n) for n in QStyleFactory.keys()]
943            lowerItems = [str(n).lower() for n in QStyleFactory.keys()]
944            if sys.platform == "darwin" and qVersion() < "4.6": #On Mac OSX full aqua style isn't supported until Qt 4.6
945                currStyle = "cleanlooks"
946            else:
947                currStyle = str(qApp.style().objectName()).lower()
948            self.settings.setdefault("style", items[lowerItems.index(currStyle)])
949
950
951    # Saves settings to this widget's .ini file
952    def saveSettings(self):
953        filename = os.path.join(self.canvasSettingsDir, "orngCanvas.ini")
954        file = open(filename, "wb")
955        if self.settings["widgetListType"] == 1:        # tree view
956            self.settings["treeItemsOpenness"] = dict([(key, self.tabs.tabDict[key].isExpanded()) for key in self.tabs.tabDict.keys()])
957        cPickle.dump(self.settings, file)
958        file.close()
959
960    def closeEvent(self, ce):
961        # save the current width of the toolbox, if we are using it
962        if isinstance(self.widgetsToolBar, orngTabs.WidgetToolBox):
963            self.settings["toolboxWidth"] = self.widgetsToolBar.toolbox.width()
964        self.settings["showWidgetToolbar"] = self.widgetsToolBar.isVisible()
965        self.settings["showToolbar"] = self.toolbar.isVisible()
966        self.settings["reportsDir"] = self.reportWindow.saveDir
967
968        closed = self.schema.close()
969        if closed:
970            self.canvasIsClosing = 1        # output window (and possibly report window also) will check this variable before it will close the window
971           
972            self.helpWindow.close()
973            self.reportWindow.close()
974           
975            self.output.catchOutput(False)
976            self.output.catchException(False)
977            self.output.hide()
978            self.output.logFile.close()
979           
980            ce.accept()
981           
982        else:
983            ce.ignore()
984       
985        self.reportWindow.removeTemp()
986       
987        size = self.geometry().size()
988        self.settings["canvasWidth"] = size.width()
989        self.settings["canvasHeight"] = size.height()
990        self.settings["CanvasMainWindowGeometry"] = str(self.saveGeometry())
991       
992        self.saveSettings()
993       
994    def wheelEvent(self, event):
995        """ Silently accept the wheel event. This is to ensure combo boxes
996        and other controls that have focus don't receive this event unless
997        the cursor is over them.
998       
999        """
1000        event.accept()
1001       
1002
1003    def setCaption(self, caption=""):
1004        if caption:
1005            caption = caption.split(".")[0]
1006            self.setWindowTitle(caption + " - %s Canvas" % product)
1007        else:
1008            self.setWindowTitle("%s Canvas" % product)
1009   
1010    def getWidgetIcon(self, widgetInfo):
1011        if self.iconNameToIcon.has_key(widgetInfo.icon):
1012            return self.iconNameToIcon[widgetInfo.icon]
1013       
1014        iconNames = self.getFullWidgetIconName(widgetInfo)
1015        iconBackgrounds = self.getFullIconBackgroundName(widgetInfo)
1016        icon = QIcon()
1017        if len(iconNames) == 1:
1018            iconSize = QPixmap(iconNames[0]).width()
1019            iconBackgrounds = [name for name in iconBackgrounds if QPixmap(name).width() == iconSize]
1020        for name, back in zip(iconNames, iconBackgrounds):
1021            image = QPixmap(back).toImage()
1022            painter = QPainter(image)
1023            painter.drawPixmap(0, 0, QPixmap(name))
1024            painter.end()
1025            icon.addPixmap(QPixmap.fromImage(image))
1026        if iconNames != [self.defaultPic]:
1027            self.iconNameToIcon[widgetInfo.icon] = icon
1028        return icon
1029           
1030   
1031    def getFullWidgetIconName(self, widgetInfo):
1032        iconName = widgetInfo.icon
1033        names = []
1034        name, ext = os.path.splitext(iconName)
1035        for num in [16, 32, 40, 48, 60]:
1036            names.append("%s_%d%s" % (name, num, ext))
1037           
1038        widgetDir = str(widgetInfo.directory)  #os.path.split(self.getFileName())[0]
1039        fullPaths = []
1040        for paths in [(self.widgetDir, widgetInfo.category), (self.widgetDir,), (self.picsDir,), tuple(), (widgetDir,), (widgetDir, "icons")]:
1041            for name in names + [iconName]:
1042                fname = os.path.join(*paths + (name,))
1043                if os.path.exists(fname):
1044                    fullPaths.append(fname)
1045            if len(fullPaths) > 1 and fullPaths[-1].endswith(iconName):
1046                fullPaths.pop()     # if we have the new icons we can remove the default icon
1047            if fullPaths != []:
1048                return fullPaths
1049        return [self.defaultPic]
1050   
1051    def getFullIconBackgroundName(self, widgetInfo):
1052        widgetDir = str(widgetInfo.directory)
1053        fullPaths = []
1054        for paths in [(widgetDir, "icons"), (self.widgetDir, widgetInfo.category, "icons"), (self.widgetDir, "icons"), (self.picsDir,), tuple(), (widgetDir,), (widgetDir, "icons")]:
1055            for name in ["background_%d.png" % num for num in [16, 32, 40, 48, 60]]:
1056                fname = os.path.join(*paths + (name,))
1057#                print fname
1058                if os.path.exists(fname):
1059                    fullPaths.append(fname)
1060            if fullPaths != []:
1061                return fullPaths   
1062        return [self.defaultBackground]
1063   
1064class MyStatusBar(QStatusBar):
1065    def __init__(self, parent):
1066        QStatusBar.__init__(self, parent)
1067        self.parentWidget = parent
1068
1069    def mouseDoubleClickEvent(self, ev):
1070        self.parentWidget.menuItemShowOutputWindow()
1071       
1072class SizeGrip(QSizeGrip):
1073    def __init__(self, mainwindow):
1074        QSizeGrip.__init__(self, mainwindow)
1075        mainwindow.installEventFilter(self)
1076        self.updateMyPos(mainwindow)
1077       
1078    def eventFilter(self, obj, event):
1079        if obj is self.parent() and isinstance(event, QResizeEvent):
1080            self.updateMyPos(obj)
1081           
1082        return QSizeGrip.eventFilter(self, obj, event)
1083   
1084    def updateMyPos(self, mainwindow):
1085        window_size = mainwindow.size()
1086        mysize = self.size()
1087        self.move(window_size.width() - mysize.width(),
1088                  window_size.height() - mysize.height())
1089           
1090from collections import defaultdict
1091class EventNotifier(QObject):
1092    """ An Qt event notifier.
1093    """
1094    def __init__(self, parent=None):
1095        QObject.__init__(self, parent)
1096        self._filtering = set()
1097        self._attached = defaultdict(list)
1098       
1099    def attach(self, obj, event, slot):
1100        if hasattr(event, "__iter__"):
1101            events = list(event)
1102        else:
1103            events = [event]
1104        for e in events:
1105            self._attached[obj, e].append(slot)
1106       
1107        if obj not in self._filtering:
1108            self._filtering.add(obj)
1109            obj.installEventFilter(self)
1110           
1111    def detach(self, obj, event, slot=None):
1112        if hasattr(event, "__iter__"):
1113            events = list(event)
1114        else:
1115            events = [event]
1116        for e in events:
1117            slots = self._attached.get((obj, e), [])
1118            if slot is None:
1119                slots_to_remove = slots
1120            else:
1121                slots_to_remove = [slot]
1122            for s in slots_to_remove:
1123                if s in slots:
1124                    slots.remove(s)
1125            if not slots:
1126                del self._attached[obj, e]
1127       
1128        if not any([s for (obj_, _), s in self._attached.items() \
1129                    if obj_ == obj]):
1130            # If obj has no more slots
1131            self._filtering.remove(obj)
1132            obj.removeEventFilter(self)
1133           
1134    def eventFilter(self, obj, event):
1135        if obj in self._filtering:
1136            if (obj, type(event)) in self._attached or (obj, event.type()) in self._attached:
1137                slots = self._attached.get((obj, type(event)), []) + \
1138                        self._attached.get((obj, event.type()), [])
1139                for slot in slots:
1140                    slot()
1141        return False
1142   
1143       
1144class OrangeQApplication(QApplication):
1145    def __init__(self, *args):
1146        QApplication.__init__(self, *args)
1147       
1148    #QFileOpenEvent are Mac OSX only
1149    if sys.platform == "darwin":
1150        def event(self, event):
1151            if event.type() == QEvent.FileOpen:
1152                file = str(event.file())
1153                def send():
1154                    if hasattr(qApp, "canvasDlg"):
1155                        qApp.canvasDlg.openSchema(file)
1156                    else:
1157                        QTimer.singleShot(100, send)
1158                send()
1159            return QApplication.event(self, event)
1160       
1161#    def notify(self, receiver, event):
1162#        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'}
1163#        import time
1164#        if isinstance(receiver, QWidget):
1165#            if str(receiver.windowTitle()) != "":
1166#                print time.strftime("%H:%M:%S"), "%15s %15s" % (str(receiver.windowTitle()) ,type(receiver).__name__)  + ": ",      # print only events for QWidget classes and up
1167#                if eventDict.has_key(event.type()):
1168#                    print eventDict[event.type()]
1169#                else:
1170#                    print "unknown event name (" + str(event.type()) + ")"
1171#        else:
1172#            print time.strftime("%H:%M:%S"), type(receiver).__name__, str(receiver.objectName()),
1173#            if eventDict.has_key(event.type()):
1174#                print eventDict[event.type()]
1175#            else:
1176#                print "unknown event name (" + str(event.type()) + ")"
1177#               
1178#        return QApplication.notify(self, receiver, event)
1179
1180
1181def main(argv=None):
1182    if argv == None:
1183        argv = sys.argv
1184
1185    app = OrangeQApplication(sys.argv)
1186    dlg = OrangeCanvasDlg(app)
1187    qApp.canvasDlg = dlg
1188    dlg.show()
1189    for arg in sys.argv[1:]:
1190        if arg == "-reload":
1191            dlg.menuItemOpenLastSchema()
1192    r = app.exec_()
1193    app.closeAllWindows()
1194   
1195    del qApp.canvasDlg
1196    del dlg
1197   
1198    app.processEvents()
1199   
1200    # Call gc.collect before the app is destroyed to colect any remainig
1201    # cycles. Some sip objects are stored in it's global state (for instance
1202    # QGraphicsScene) and can cause a crash in Py_Finalize when sip trys to
1203    # deallocate it (a bug in sip?).
1204   
1205    gc.collect()
1206    del app
1207   
1208    return r
1209
1210if __name__ == "__main__":
1211    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.