source: orange/Orange/OrangeCanvas/orngCanvas.pyw @ 10463:431347363442

Revision 10463:431347363442, 59.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Added unicode filename support for save/load dialogs.

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