source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11458:2c6a670c84c3

Revision 11458:2c6a670c84c3, 47.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Fixed saving/loading of widget settings when the directory path contains non ascii characters.

(fixes #1294)

RevLine 
[8042]1#
2# OWWidget.py
3# Orange Widget
4# A General Orange Widget, from which all the Orange Widgets are derived
5#
[10580]6from Orange.utils import environ
[9873]7from Orange.orng.orngEnviron import directoryNames as old_directory_names
[8042]8from PyQt4.QtCore import *
9from PyQt4.QtGui import *
10
11# Define  pyqtConfigure not available in PyQt4 versions prior to 4.6
12if not hasattr(QObject, "pyqtConfigure"):
13    def pyqtConfigure(obj, **kwargs):
14        meta = obj.metaObject()
15        for name, val in kwargs.items():
16            if meta.indexOfProperty(name) >= 0:
17                obj.setProperty(name, QVariant(val))
18            elif meta.indexOfSignal(meta.normalizedSignature(name)) >= 0:
19                obj.connect(obj, SIGNAL(name), val)
20    QObject.pyqtConfigure = pyqtConfigure
21
22from OWContexts import *
[9873]23import sys, time, random, user, os, os.path, cPickle, copy
[11292]24import logging
25
[8042]26import orange
[9873]27from Orange import misc
[10651]28import Orange.utils
29from Orange.utils import debugging as orngDebugging
[8042]30from string import *
[11269]31
32from Orange.OrangeCanvas.registry.description import (
33    Default, NonDefault, Single, Multiple, Explicit, Dynamic,
34    InputSignal, OutputSignal
35)
36
37from Orange.OrangeCanvas.scheme.widgetsscheme import (
38    SignalLink, WidgetsSignalManager, SignalWrapper
39)
40
[8042]41import OWGUI
42
[11269]43
[11292]44_log = logging.getLogger(__name__)
45
[8042]46ERROR = 0
47WARNING = 1
48
[11292]49TRUE = 1
50FALSE = 0
[8042]51
[11269]52
[8042]53def unisetattr(self, name, value, grandparent):
54    if "." in name:
55        names = name.split(".")
56        lastname = names.pop()
57        obj = reduce(lambda o, n: getattr(o, n, None),  names, self)
58    else:
59        lastname, obj = name, self
60
61    if not obj:
62        print "unable to set setting ", name, " to value ", value
63    else:
64        if hasattr(grandparent, "__setattr__") and isinstance(obj, grandparent):
65            grandparent.__setattr__(obj, lastname,  value)
66        else:
67            setattr(obj, lastname, value)
68#            obj.__dict__[lastname] = value
69
70    controlledAttributes = hasattr(self, "controlledAttributes") and getattr(self, "controlledAttributes", None)
71    controlCallback = controlledAttributes and controlledAttributes.get(name, None)
72    if controlCallback:
73        for callback in controlCallback:
74            callback(value)
75#        controlCallback(value)
76
77    # controlled things (checkboxes...) never have __attributeControllers
78    else:
79        if hasattr(self, "__attributeControllers"):
80            for controller, myself in self.__attributeControllers.keys():
81                if getattr(controller, myself, None) != self:
82                    del self.__attributeControllers[(controller, myself)]
83                    continue
84
85                controlledAttributes = hasattr(controller, "controlledAttributes") and getattr(controller, "controlledAttributes", None)
86                if controlledAttributes:
87                    fullName = myself + "." + name
88
89                    controlCallback = controlledAttributes.get(fullName, None)
90                    if controlCallback:
91                        for callback in controlCallback:
92                            callback(value)
93
94                    else:
95                        lname = fullName + "."
96                        dlen = len(lname)
97                        for controlled in controlledAttributes.keys():
98                            if controlled[:dlen] == lname:
99                                self.setControllers(value, controlled[dlen:], controller, fullName)
100                                # no break -- can have a.b.c.d and a.e.f.g; needs to set controller for all!
101
102
103    # if there are any context handlers, call the fastsave to write the value into the context
104    if hasattr(self, "contextHandlers") and hasattr(self, "currentContexts"):
105        for contextName, contextHandler in self.contextHandlers.items():
106            contextHandler.fastSave(self.currentContexts.get(contextName), self, name, value)
107
108
109
110class ControlledAttributesDict(dict):
111    def __init__(self, master):
112        self.master = master
113
114    def __setitem__(self, key, value):
115        if not self.has_key(key):
116            dict.__setitem__(self, key, [value])
117        else:
118            dict.__getitem__(self, key).append(value)
119        self.master.setControllers(self.master, key, self.master, "")
120
121
122from orange import ExampleTable
123
124class AttributeList(list):
125    pass
126
127class ExampleList(list):
128    pass
129
130widgetId = 0
131
132class OWBaseWidget(QDialog):
133    def __new__(cls, *arg, **args):
134        self = QDialog.__new__(cls)
[11292]135
[8042]136        self.currentContexts = {}   # the "currentContexts" MUST be the first thing assigned to a widget
137        self._useContexts = 1       # do you want to use contexts
138        self._owInfo = 1            # currently disabled !!!
139        self._owWarning = 1         # do we want to see warnings
140        self._owError = 1           # do we want to see errors
141        self._owShowStatus = 0      # do we want to see warnings and errors in status bar area of the widget
142        self._guiElements = []      # used for automatic widget debugging
143        for key in args:
144            if key in ["_owInfo", "_owWarning", "_owError", "_owShowStatus", "_useContexts", "_category", "_settingsFromSchema"]:
145                self.__dict__[key] = args[key]        # we cannot use __dict__.update(args) since we can have many other
146
147        return self
148
149
150    def __init__(self, parent = None, signalManager = None, title="Orange BaseWidget", modal=FALSE, savePosition = False, resizingEnabled = 1, **args):
151        if resizingEnabled:
152            QDialog.__init__(self, parent, Qt.Window)
153        else:
154            QDialog.__init__(self, parent, Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint)# | Qt.WindowMinimizeButtonHint)
155           
156        # do we want to save widget position and restore it on next load
157        self.savePosition = savePosition
158        if savePosition:
[11205]159            self.settingsList = getattr(self, "settingsList", []) + ["widgetShown", "savedWidgetGeometry"]
[8042]160
161        # directories are better defined this way, otherwise .ini files get written in many places
[9873]162        self.__dict__.update(old_directory_names)
[8042]163        try:
164            self.__dict__["thisWidgetDir"] = os.path.dirname(sys.modules[self.__class__.__module__].__file__)
165        except:
166            pass
167
168        self.setCaption(title.replace("&","")) # used for widget caption
[9177]169        self.setFocusPolicy(Qt.StrongFocus)
[8042]170
171        # number of control signals, that are currently being processed
172        # needed by signalWrapper to know when everything was sent
173        self.parent = parent
174        self.needProcessing = 0     # used by signalManager
[11269]175
176        self.signalManager = signalManager
[8042]177
178        self.inputs = []     # signalName:(dataType, handler, onlySingleConnection)
179        self.outputs = []    # signalName: dataType
180        self.wrappers = []    # stored wrappers for widget events
181        self.linksIn = {}      # signalName : (dirty, widgetFrom, handler, signalData)
182        self.linksOut = {}       # signalName: (signalData, id)
183        self.connections = {}   # dictionary where keys are (control, signal) and values are wrapper instances. Used in connect/disconnect
184        self.controlledAttributes = ControlledAttributesDict(self)
185        self.progressBarHandler = None  # handler for progress bar events
186        self.processingHandler = None   # handler for processing events
187        self.eventHandler = None
188        self.callbackDeposit = []
189        self.startTime = time.time()    # used in progressbar
190
191        self.widgetStateHandler = None
192        self.widgetState = {"Info":{}, "Warning":{}, "Error":{}}
193
194        if hasattr(self, "contextHandlers"):
195            for contextHandler in self.contextHandlers.values():
196                contextHandler.initLocalContext(self)
197               
198        global widgetId
199        widgetId += 1
200        self.widgetId = widgetId
201       
202        self._private_thread_pools = {}
203        self.asyncCalls = []
204        self.asyncBlock = False
205
206    # uncomment this when you need to see which events occured
207    """
208    def event(self, e):
209        #eventDict = dict([(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'), (15, 'Create'), (16, 'Destroy'), (17, 'Show'), (18, 'Hide'), (19, 'Close'), (20, 'Quit'), (21, 'Reparent'), (22, 'ShowMinimized'), (23, 'ShowNormal'), (24, 'WindowActivate'), (25, 'WindowDeactivate'), (26, 'ShowToParent'), (27, 'HideToParent'), (28, 'ShowMaximized'), (30, 'Accel'), (31, 'Wheel'), (32, 'AccelAvailable'), (33, 'CaptionChange'), (34, 'IconChange'), (35, 'ParentFontChange'), (36, 'ApplicationFontChange'), (37, 'ParentPaletteChange'), (38, 'ApplicationPaletteChange'), (40, 'Clipboard'), (42, 'Speech'), (50, 'SockAct'), (51, 'AccelOverride'), (60, 'DragEnter'), (61, 'DragMove'), (62, 'DragLeave'), (63, 'Drop'), (64, 'DragResponse'), (70, 'ChildInserted'), (71, 'ChildRemoved'), (72, 'LayoutHint'), (73, 'ShowWindowRequest'), (80, 'ActivateControl'), (81, 'DeactivateControl'), (1000, 'User')])
210        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")])
211        if eventDict.has_key(e.type()):
212            print str(self.windowTitle()), eventDict[e.type()]
213        return QDialog.event(self, e)
214    """
215
216    def getIconNames(self, iconName):
217        if type(iconName) == list:      # if canvas sent us a prepared list of valid names, just return those
218            return iconName
219       
220        names = []
221        name, ext = os.path.splitext(iconName)
222        for num in [16, 32, 42, 60]:
223            names.append("%s_%d%s" % (name, num, ext))
224        fullPaths = []
225        for paths in [(self.widgetDir, name), (self.widgetDir, "icons", name), (os.path.dirname(sys.modules[self.__module__].__file__), "icons", name)]:
226            for name in names + [iconName]:
227                fname = os.path.join(*paths)
228                if os.path.exists(fname):
229                    fullPaths.append(fname)
230            if fullPaths != []:
231                break
232
233        if len(fullPaths) > 1 and fullPaths[-1].endswith(iconName):
234            fullPaths.pop()     # if we have the new icons we can remove the default icon
235        return fullPaths
236   
237
238    def setWidgetIcon(self, iconName):
239        iconNames = self.getIconNames(iconName)
240           
241        icon = QIcon()
242        for name in iconNames:
243            pix = QPixmap(name)
244            icon.addPixmap(pix)
245#            frame = QPixmap(os.path.join(self.widgetDir, "icons/frame.png"))
246#            icon = QPixmap(iconName)
247#            result = QPixmap(icon.size())
248#            painter = QPainter()
249#            painter.begin(result)
250#            painter.drawPixmap(0,0, frame)
251#            painter.drawPixmap(0,0, icon)
252#            painter.end()
253
254        self.setWindowIcon(icon)
255       
256
257    # ##############################################
258    def createAttributeIconDict(self):
259        return OWGUI.getAttributeIcons()
260
261    def isDataWithClass(self, data, wantedVarType = None, checkMissing=False):
262        self.error([1234, 1235, 1236])
263        if not data:
264            return 0
265        if not data.domain.classVar:
266            self.error(1234, "A data set with a class attribute is required.")
267            return 0
268        if wantedVarType and data.domain.classVar.varType != wantedVarType:
269            self.error(1235, "Unable to handle %s class." % (data.domain.classVar.varType == orange.VarTypes.Discrete and "discrete" or "continuous"))
270            return 0
271        if checkMissing and not orange.Preprocessor_dropMissingClasses(data):
272            self.error(1236, "Unable to handle data set with no known classes")
273            return 0
274        return 1
275
276    # call processEvents(), but first remember position and size of widget in case one of the events would be move or resize
277    # call this function if needed in __init__ of the widget
278    def safeProcessEvents(self):
[11205]279        keys = ["widgetShown"]
[8042]280        vals = [(key, getattr(self, key, None)) for key in keys]
281        qApp.processEvents()
282        for (key, val) in vals:
283            if val != None:
284                setattr(self, key, val)
285
286
287    # this function is called at the end of the widget's __init__ when the widgets is saving its position and size parameters
288    def restoreWidgetPosition(self):
289        if self.savePosition:
290            geometry = getattr(self, "savedWidgetGeometry", None)
291            restored = False
292            if geometry is not None:
293               restored =  self.restoreGeometry(QByteArray(geometry))
294               
295            if restored:
296                space = qApp.desktop().availableGeometry(self)
297                frame, geometry = self.frameGeometry(), self.geometry()
298               
299                #Fix the widget size to fit inside the available space
300                width = min(space.width() - (frame.width() - geometry.width()), geometry.width())
301                height = min(space.height() - (frame.height() - geometry.height()), geometry.height())
302                self.resize(width, height)
303               
304                #Move the widget to the center of available space if it is currently outside it
305                if not space.contains(self.frameGeometry()):
306                    x = max(0, space.width() / 2 - width / 2)
307                    y = max(0, space.height() / 2 - height / 2)
308           
309                    self.move(x, y)
310           
311#            geometry.move(frameOffset) #Make sure the title bar is shown
312#            self.setGeometry(geometry.intersected(space.adjusted(-frameOffset.x(), -frameOffset.y(), 0, 0)))
313           
314           
315#            if self.isWindow():
316#                frame = self.frameGeometry()
317#                if space.topLeft() != QPoint(0, 0):
318#                    self.move(self.geometry().topLeft() - frame.topLeft())
319#            if getattr(self, "widgetXPosition", None) != None and getattr(self, "widgetYPosition", None) != None:
320##                print self.captionTitle, "restoring position", self.widgetXPosition, self.widgetYPosition, "to", max(self.widgetXPosition, 0), max(self.widgetYPosition, 0)
321#                self.move(max(self.widgetXPosition, space.x()), max(self.widgetYPosition, space.y()))
322#            if getattr(self,"widgetWidth", None) != None and getattr(self,"widgetHeight", None) != None:
323#                self.resize(min(self.widgetWidth, space.width()), min(self.widgetHeight, space.height()))
324#            frame = self.frameGeometry()
325#            area = lambda rect: rect.width() * rect.height()
326#            if area(frame.intersected(space)) < area(frame):
327#                self.move(max(min(space.right() - frame.width(), frame.x()), space.x()),
328#                          max(min(space.height() - frame.height(), frame.y()), space.y()))
329
330    # this is called in canvas when loading a schema. it opens the widgets that were shown when saving the schema
331    def restoreWidgetStatus(self):
332        if self.savePosition and getattr(self, "widgetShown", None):
333            self.show()
334
335    # when widget is resized, save new width and height into widgetWidth and widgetHeight. some widgets can put this two
336    # variables into settings and last widget shape is restored after restart
337    def resizeEvent(self, ev):
338        QDialog.resizeEvent(self, ev)
[11101]339        # Don't store geometry if the widget is not visible
340        # (the widget receives the resizeEvent before showEvent and we must not
341        # overwrite the the savedGeometry before then)
342        if self.savePosition and self.isVisible():
[8042]343            self.savedWidgetGeometry = str(self.saveGeometry())
344
345
346    # when widget is moved, save new x and y position into widgetXPosition and widgetYPosition. some widgets can put this two
347    # variables into settings and last widget position is restored after restart
348    # Commented out because of Ubuntu (on call to restoreGeometry calls move event saving pos (0, 0)
349#    def moveEvent(self, ev):
350#        QDialog.moveEvent(self, ev)
351#        if self.savePosition:
352#            self.widgetXPosition = self.frameGeometry().x()
353#            self.widgetYPosition = self.frameGeometry().y()
354#            self.savedWidgetGeometry = str(self.saveGeometry())
355
356    # set widget state to hidden
357    def hideEvent(self, ev):
358        if self.savePosition:
359            self.widgetShown = 0
360            self.savedWidgetGeometry = str(self.saveGeometry())
361        QDialog.hideEvent(self, ev)
362
363    # override the default show function.
364    # after show() we must call processEvents because show puts some LayoutRequests in queue
365    # and we must process them immediately otherwise the width(), height(), ... of elements in the widget will be wrong
366#    def show(self):
367#        QDialog.show(self)
368#        qApp.processEvents()
369
370    # set widget state to shown
371    def showEvent(self, ev):   
372        QDialog.showEvent(self, ev)
373        if self.savePosition:
374            self.widgetShown = 1
375           
376        self.restoreWidgetPosition()
377       
378    def closeEvent(self, ev):
379        if self.savePosition:
380            self.savedWidgetGeometry = str(self.saveGeometry())
381        QDialog.closeEvent(self, ev)
382       
383    def wheelEvent(self, event):
384        """ Silently accept the wheel event. This is to ensure combo boxes
385        and other controls that have focus don't receive this event unless
386        the cursor is over them.
387       
388        """
389        event.accept()
390
391    def setCaption(self, caption):
392        if self.parent != None and isinstance(self.parent, QTabWidget):
393            self.parent.setTabText(self.parent.indexOf(self), caption)
394        else:
[11260]395            # we have to save caption title in case progressbar will change it
396            self.captionTitle = unicode(caption)
[8042]397            self.setWindowTitle(caption)
398
399    # put this widget on top of all windows
400    def reshow(self):
401        self.show()
402        self.raise_()
403        self.activateWindow()
404
405
406    def send(self, signalName, value, id = None):
407        if self.linksOut.has_key(signalName):
408            self.linksOut[signalName][id] = value
409        else:
410            self.linksOut[signalName] = {id:value}
411
[11292]412        if self.signalManager is not None:
413            self.signalManager.send(self, signalName, value, id)
[8042]414
415    def getdeepattr(self, attr, **argkw):
416        try:
417            return reduce(lambda o, n: getattr(o, n, None),  attr.split("."), self)
418        except:
419            if argkw.has_key("default"):
420                return argkw[default]
421            else:
422                raise AttributeError, "'%s' has no attribute '%s'" % (self, attr)
423
424
425    # Set all settings
426    # settings - the map with the settings
427    def setSettings(self,settings):
428        for key in settings:
429            self.__setattr__(key, settings[key])
430        #self.__dict__.update(settings)
431
432    # Get all settings
433    # returns map with all settings
434    def getSettings(self, alsoContexts = True, globalContexts=False):
435        settings = {}
436        if hasattr(self, "settingsList"):
437            for name in self.settingsList:
438                try:
439                    settings[name] =  self.getdeepattr(name)
440                except:
441                    #print "Attribute %s not found in %s widget. Remove it from the settings list." % (name, self.captionTitle)
442                    pass
443       
444        if alsoContexts:
[8890]445            self.synchronizeContexts()
[8042]446            contextHandlers = getattr(self, "contextHandlers", {})
447            for contextHandler in contextHandlers.values():
448                contextHandler.mergeBack(self)
449#                settings[contextHandler.localContextName] = contextHandler.globalContexts
450# Instead of the above line, I found this; as far as I recall this was a fix
451# for some bugs related to, say, Select Attributes not handling the context
452# attributes properly, but I dare not add it without understanding what it does.
453# Here it is, if these contexts give us any further trouble.
454                if (contextHandler.syncWithGlobal and contextHandler.globalContexts is getattr(self, contextHandler.localContextName)) or globalContexts:
455                    settings[contextHandler.localContextName] = contextHandler.globalContexts
456                else:
457                    contexts = getattr(self, contextHandler.localContextName, None)
458                    if contexts:
459                        settings[contextHandler.localContextName] = contexts
460###
461                settings[contextHandler.localContextName+"Version"] = (contextStructureVersion, contextHandler.contextDataVersion)
[11458]462
[8042]463        return settings
464
[11458]465    def getDefaultSettingsFilename(self):
466        fs_encoding = sys.getfilesystemencoding()
467        basename = self.captionTitle + ".ini"
468        filename = os.path.join(
469            self.widgetSettingsDir,  # is assumed to be a str in FS encoding
470            basename.encode(fs_encoding))
471        return filename
[8042]472
473    def getSettingsFile(self, file):
[11458]474        if file is None:
475            file = self.getDefaultSettingsFilename()
476
[8042]477            if not os.path.exists(file):
478                try:
479                    f = open(file, "wb")
480                    cPickle.dump({}, f)
481                    f.close()
482                except IOError:
[11458]483                    return
[10466]484        if isinstance(file, basestring):
[8042]485            if os.path.exists(file):
486                return open(file, "r")
487        else:
488            return file
489
490
491    # Loads settings from the widget's .ini file
492    def loadSettings(self, file = None):
493        file = self.getSettingsFile(file)
494        if file:
495            try:
496                settings = cPickle.load(file)
497            except Exception, ex:
498                print >> sys.stderr, "Failed to load settings!", repr(ex)
499                settings = None
500           
501            if hasattr(self, "_settingsFromSchema"):
502                if settings: settings.update(self._settingsFromSchema)
503                else:        settings = self._settingsFromSchema
504
505            # can't close everything into one big try-except since this would mask all errors in the below code
506            if settings:
507                if hasattr(self, "settingsList"):
508                    self.setSettings(settings)
509
510                contextHandlers = getattr(self, "contextHandlers", {})
511                for contextHandler in contextHandlers.values():
512                    localName = contextHandler.localContextName
513
514                    structureVersion, dataVersion = settings.get(localName+"Version", (0, 0))
515                    if (structureVersion < contextStructureVersion or dataVersion < contextHandler.contextDataVersion) \
516                       and settings.has_key(localName):
517                        del settings[localName]
518                        delattr(self, localName)
519                        contextHandler.initLocalContext(self)
520                       
521                    if not hasattr(self, "_settingsFromSchema"): #When running stand alone widgets
522                        if contextHandler.syncWithGlobal:
523                            contexts = settings.get(localName, None)
524                            if contexts is not None:
525                                contextHandler.globalContexts = contexts
526                        else:
527                            setattr(self, localName, contextHandler.globalContexts)
528
529
530    def saveSettings(self, file = None):
531        settings = self.getSettings(globalContexts=True)
532        if settings:
[11458]533            if file is None:
534                file = self.getDefaultSettingsFilename()
535
[10466]536            if isinstance(file, basestring):
[8042]537                file = open(file, "w")
538            cPickle.dump(settings, file)
539
540    # Loads settings from string str which is compatible with cPickle
541    def loadSettingsStr(self, str):
542        if str == None or str == "":
543            return
544
545        settings = cPickle.loads(str)
546        self.setSettings(settings)
547
548        contextHandlers = getattr(self, "contextHandlers", {})
549        for contextHandler in contextHandlers.values():
550            localName = contextHandler.localContextName
551            if settings.has_key(localName):
552                structureVersion, dataVersion = settings.get(localName+"Version", (0, 0))
553                if structureVersion < contextStructureVersion or dataVersion < contextHandler.contextDataVersion:
554                    del settings[localName]
555                    delattr(self, localName)
556                    contextHandler.initLocalContext(self)
557                else:
558                    setattr(self, localName, settings[localName])
559
560    # return settings in string format compatible with cPickle
561    def saveSettingsStr(self):
562        settings = self.getSettings()
563        return cPickle.dumps(settings)
564
565    def onDeleteWidget(self):
566        pass
567
568    # this function is only intended for derived classes to send appropriate signals when all settings are loaded
569    def activateLoadedSettings(self):
570        pass
571
572    # reimplemented in other widgets
573    def setOptions(self):
574        pass
575
576    # does widget have a signal with name in inputs
577    def hasInputName(self, name):
578        for input in self.inputs:
579            if name == input[0]: return 1
580        return 0
581
582    # does widget have a signal with name in outputs
583    def hasOutputName(self, name):
584        for output in self.outputs:
585            if name == output[0]: return 1
586        return 0
587
588    def getInputType(self, signalName):
589        for input in self.inputs:
590            if input[0] == signalName: return input[1]
591        return None
592
593    def getOutputType(self, signalName):
594        for output in self.outputs:
595            if output[0] == signalName: return output[1]
596        return None
597
598    # ########################################################################
599    def connect(self, control, signal, method, type=Qt.AutoConnection):
600        wrapper = SignalWrapper(self, method)
601        self.connections[(control, signal)] = wrapper   # save for possible disconnect
602        self.wrappers.append(wrapper)
603        QDialog.connect(control, signal, wrapper, type)
604        #QWidget.connect(control, signal, method)        # ordinary connection useful for dialogs and windows that don't send signals to other widgets
605
606
607    def disconnect(self, control, signal, method=None):
608        wrapper = self.connections[(control, signal)]
609        QDialog.disconnect(control, signal, wrapper)
610
611
612    def getConnectionMethod(self, control, signal):
613        if (control, signal) in self.connections:
614            wrapper = self.connections[(control, signal)]
615            return wrapper.method
616        else:
617            return None
618
619
620    def signalIsOnlySingleConnection(self, signalName):
621        for i in self.inputs:
622            input = InputSignal(*i)
623            if input.name == signalName: return input.single
624
625    def addInputConnection(self, widgetFrom, signalName):
626        for i in range(len(self.inputs)):
627            if self.inputs[i][0] == signalName:
628                handler = self.inputs[i][2]
629                break
630
631        existing = []
632        if self.linksIn.has_key(signalName):
633            existing = self.linksIn[signalName]
634            for (dirty, widget, handler, data) in existing:
635                if widget == widgetFrom: return             # no need to add new tuple, since one from the same widget already exists
636        self.linksIn[signalName] = existing + [(0, widgetFrom, handler, [])]    # (dirty, handler, signalData)
637        #if not self.linksIn.has_key(signalName): self.linksIn[signalName] = [(0, widgetFrom, handler, [])]    # (dirty, handler, signalData)
638
639    # delete a link from widgetFrom and this widget with name signalName
640    def removeInputConnection(self, widgetFrom, signalName):
641        if self.linksIn.has_key(signalName):
642            links = self.linksIn[signalName]
643            for i in range(len(self.linksIn[signalName])):
644                if widgetFrom == self.linksIn[signalName][i][1]:
645                    self.linksIn[signalName].remove(self.linksIn[signalName][i])
646                    if self.linksIn[signalName] == []:  # if key is empty, delete key value
647                        del self.linksIn[signalName]
648                    return
649
650    # return widget, that is already connected to this singlelink signal. If this widget exists, the connection will be deleted (since this is only single connection link)
651    def removeExistingSingleLink(self, signal):
652        for i in self.inputs:
653            input = InputSignal(*i)
654            if input.name == signal and not input.single: return None
655
656        for signalName in self.linksIn.keys():
657            if signalName == signal:
658                widget = self.linksIn[signalName][0][1]
659                del self.linksIn[signalName]
660                return widget
661
662        return None
663
664
665    def handleNewSignals(self):
666        # this is called after all new signals have been handled
667        # implement this in your widget if you want to process something only after you received multiple signals
668        pass
669
670    # signal manager calls this function when all input signals have updated the data
671    def processSignals(self):
672        if self.processingHandler:
673            self.processingHandler(self, 1)    # focus on active widget
674        newSignal = 0        # did we get any new signals
675
676        # we define only a way to handle signals that have defined a handler function
677        for signal in self.inputs:        # we go from the first to the last defined input
678            key = signal[0]
679            if self.linksIn.has_key(key):
680                for i in range(len(self.linksIn[key])):
681                    (dirty, widgetFrom, handler, signalData) = self.linksIn[key][i]
682                    if not (handler and dirty): continue
683                    newSignal = 1
684
685                    qApp.setOverrideCursor(Qt.WaitCursor)
686                    try:
687                        for (value, id, nameFrom) in signalData:
688                            if self.signalIsOnlySingleConnection(key):
689                                self.printEvent("ProcessSignals: Calling %s with %s" % (handler, value), eventVerbosity = 2)
690                                handler(value)
691                            else:
692                                self.printEvent("ProcessSignals: Calling %s with %s (%s, %s)" % (handler, value, nameFrom, id), eventVerbosity = 2)
693                                handler(value, (widgetFrom, nameFrom, id))
694                    except:
695                        type, val, traceback = sys.exc_info()
696                        sys.excepthook(type, val, traceback)  # we pretend that we handled the exception, so that we don't crash other widgets
697                    qApp.restoreOverrideCursor()
698
699                    self.linksIn[key][i] = (0, widgetFrom, handler, []) # clear the dirty flag
700
701        if newSignal == 1:
702            self.handleNewSignals()
703       
704        while self.isBlocking():
705            self.thread().msleep(50)
706            qApp.processEvents()
707
708        if self.processingHandler:
709            self.processingHandler(self, 0)    # remove focus from this widget
710        self.needProcessing = 0
711
712    # set new data from widget widgetFrom for a signal with name signalName
713    def updateNewSignalData(self, widgetFrom, signalName, value, id, signalNameFrom):
714        if not self.linksIn.has_key(signalName): return
715        for i in range(len(self.linksIn[signalName])):
716            (dirty, widget, handler, signalData) = self.linksIn[signalName][i]
717            if widget == widgetFrom:
718                if self.linksIn[signalName][i][3] == []:
719                    self.linksIn[signalName][i] = (1, widget, handler, [(value, id, signalNameFrom)])
720                else:
721                    found = 0
722                    for j in range(len(self.linksIn[signalName][i][3])):
723                        (val, ID, nameFrom) = self.linksIn[signalName][i][3][j]
724                        if ID == id and nameFrom == signalNameFrom:
725                            self.linksIn[signalName][i][3][j] = (value, id, signalNameFrom)
726                            found = 1
727                    if not found:
728                        self.linksIn[signalName][i] = (1, widget, handler, self.linksIn[signalName][i][3] + [(value, id, signalNameFrom)])
729        self.needProcessing = 1
730
731    # ############################################
732    # PROGRESS BAR FUNCTIONS
[11101]733
734    progressBarValueChanged = pyqtSignal(float)
735    """Progress bar value has changed"""
736
737    processingStateChanged = pyqtSignal(int)
738    """Processing state has changed"""
739
[8042]740    def progressBarInit(self):
741        self.progressBarValue = 0
742        self.startTime = time.time()
743        self.setWindowTitle(self.captionTitle + " (0% complete)")
744        if self.progressBarHandler:
745            self.progressBarHandler(self, 0)
[11101]746        self.processingStateChanged.emit(1)
[8042]747
748    def progressBarSet(self, value):
749        if value > 0:
[11101]750            self.__progressBarValue = value
[8042]751            usedTime = max(1, time.time() - self.startTime)
[11101]752            totalTime = (100.0 * usedTime) / float(value)
[8042]753            remainingTime = max(0, totalTime - usedTime)
[11101]754            h = int(remainingTime / 3600)
755            min = int((remainingTime - h * 3600) / 60)
756            sec = int(remainingTime - h * 3600 - min * 60)
757            if h > 0:
758                text = "%(h)d:%(min)02d:%(sec)02d" % vars()
759            else:
760                text = "%(min)d:%(sec)02d" % vars()
[8042]761            self.setWindowTitle(self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars())
762        else:
[11101]763            self.setWindowTitle(self.captionTitle + " (0% complete)")
764        if self.progressBarHandler:
765            self.progressBarHandler(self, value)
766
767        self.progressBarValueChanged.emit(value)
768
[8042]769        qApp.processEvents()
770
[11101]771    def progressBarValue(self):
772        return self.__progressBarValue
773
774    progressBarValue = pyqtProperty(float, fset=progressBarSet,
775                                    fget=progressBarValue)
776
[8042]777    def progressBarAdvance(self, value):
[11101]778        self.progressBarSet(self.progressBarValue + value)
[8042]779
780    def progressBarFinished(self):
781        self.setWindowTitle(self.captionTitle)
[11101]782        if self.progressBarHandler:
783            self.progressBarHandler(self, 101)
784        self.processingStateChanged.emit(0)
[8042]785
786    # handler must be a function, that receives 2 arguments. First is the widget instance, the second is the value between -1 and 101
787    def setProgressBarHandler(self, handler):
788        self.progressBarHandler = handler
789
790    def setProcessingHandler(self, handler):
791        self.processingHandler = handler
792
793    def setEventHandler(self, handler):
794        self.eventHandler = handler
795
796    def setWidgetStateHandler(self, handler):
797        self.widgetStateHandler = handler
798
[11292]799    # if we are in debug mode print the event into the file
800    def printEvent(self, text, eventVerbosity=1):
801        text = self.captionTitle + ": " + text
[8042]802
[11292]803        if eventVerbosity > 0:
804            _log.debug(text)
805        else:
806            _log.info(text)
807
[8042]808        if self.eventHandler:
[11292]809            self.eventHandler(text, eventVerbosity)
[8042]810
811    def openWidgetHelp(self):
[11296]812        if "widgetInfo" in self.__dict__ and \
813                hasattr(qApp, "canvasDlg"):
814            # This widget is on a canvas.
[8042]815            qApp.canvasDlg.helpWindow.showHelpFor(self.widgetInfo, True)
[11292]816
[8042]817    def keyPressEvent(self, e):
818        if e.key() in (Qt.Key_Help, Qt.Key_F1):
819            self.openWidgetHelp()
820        elif (int(e.modifiers()), e.key()) in OWBaseWidget.defaultKeyActions:
821            OWBaseWidget.defaultKeyActions[int(e.modifiers()), e.key()](self)
822        else:
823            QDialog.keyPressEvent(self, e)
824
[11292]825    def information(self, id=0, text=None):
[8042]826        self.setState("Info", id, text)
827
[11292]828    def warning(self, id=0, text=""):
[8042]829        self.setState("Warning", id, text)
830
[11292]831    def error(self, id=0, text=""):
[8042]832        self.setState("Error", id, text)
833
834    def setState(self, stateType, id, text):
835        changed = 0
836        if type(id) == list:
837            for val in id:
838                if self.widgetState[stateType].has_key(val):
839                    self.widgetState[stateType].pop(val)
840                    changed = 1
841        else:
[11292]842            if isinstance(id, basestring):
843                # if we call information(), warning(), or error() function
844                # with only one parameter - a string - then set id = 0
845                text = id
846                id = 0
[8042]847            if not text:
848                if self.widgetState[stateType].has_key(id):
849                    self.widgetState[stateType].pop(id)
850                    changed = 1
851            else:
852                self.widgetState[stateType][id] = text
853                changed = 1
854
855        if changed:
856            if self.widgetStateHandler:
857                self.widgetStateHandler()
858            elif text: # and stateType != "Info":
859                self.printEvent(stateType + " - " + text)
860           
861            if type(id) == list:
862                for i in id:
863                    self.emit(SIGNAL("widgetStateChanged(QString, int, QString)"),
864                              QString(stateType), i,QString(""))
865            else:
866                self.emit(SIGNAL("widgetStateChanged(QString, int, QString)"),
867                             QString(stateType), id, QString(text or ""))
868            #qApp.processEvents()
869        return changed
[11134]870
871    widgetStateChanged = pyqtSignal(QString, int, QString)
872    """Widget state has changed first arg is the state type
873    ('Info', 'Warning' or 'Error') the second is the message id
874    and finally the message string."""
875
[8042]876    def widgetStateToHtml(self, info=True, warning=True, error=True):
877        pixmaps = self.getWidgetStateIcons()
878        items = [] 
879        iconPath = {"Info": "canvasIcons:information.png",
880                    "Warning": "canvasIcons:warning.png",
881                    "Error": "canvasIcons:error.png"}
882        for show, what in [(info, "Info"), (warning, "Warning"),(error, "Error")]:
[11134]883            if show and self.widgetState[what]:
[8042]884                items.append('<img src="%s" style="float: left;"> %s' % (iconPath[what], "\n".join(self.widgetState[what].values())))
885        return "<br>".join(items)
886       
887    @classmethod
888    def getWidgetStateIcons(cls):
889        if not hasattr(cls, "_cached__widget_state_icons"):
[9873]890            iconsDir = os.path.join(environ.canvas_install_dir, "icons")
891            QDir.addSearchPath("canvasIcons",os.path.join(environ.canvas_install_dir,
892                "icons/"))
[8042]893            info = QPixmap("canvasIcons:information.png")
894            warning = QPixmap("canvasIcons:warning.png")
895            error = QPixmap("canvasIcons:error.png")
896            cls._cached__widget_state_icons = \
897                    {"Info": info, "Warning": warning, "Error": error}
898        return cls._cached__widget_state_icons
899
900    def synchronizeContexts(self):
901        if hasattr(self, "contextHandlers"):
902            for contextName, handler in self.contextHandlers.items():
903                context = self.currentContexts.get(contextName, None)
904                if context:
905                    handler.settingsFromWidget(self, context)
906
907    def openContext(self, contextName="", *arg):
908        if not self._useContexts:
909            return
910        handler = self.contextHandlers[contextName]
911        context = handler.openContext(self, *arg)
912        if context:
913            self.currentContexts[contextName] = context
914
915
916    def closeContext(self, contextName=""):
917        if not self._useContexts:
918            return
919        curcontext = self.currentContexts.get(contextName)
920        if curcontext:
921            self.contextHandlers[contextName].closeContext(self, curcontext)
922            del self.currentContexts[contextName]
923
924    def settingsToWidgetCallback(self, handler, context):
925        pass
926
927    def settingsFromWidgetCallback(self, handler, context):
928        pass
929
930    def setControllers(self, obj, controlledName, controller, prefix):
931        while obj:
932            if prefix:
933#                print "SET CONTROLLERS: %s %s + %s" % (obj.__class__.__name__, prefix, controlledName)
934                if obj.__dict__.has_key("attributeController"):
935                    obj.__dict__["__attributeControllers"][(controller, prefix)] = True
936                else:
937                    obj.__dict__["__attributeControllers"] = {(controller, prefix): True}
938
939            parts = controlledName.split(".", 1)
940            if len(parts) < 2:
941                break
942            obj = getattr(obj, parts[0], None)
943            prefix += parts[0]
944            controlledName = parts[1]
945
946    def __setattr__(self, name, value):
947        return unisetattr(self, name, value, QDialog)
948   
949    defaultKeyActions = {}
950   
951    if sys.platform == "darwin":
952        defaultKeyActions = {
953            (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(),
954            (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible())}
955
[11292]956    def scheduleSignalProcessing(self):
957        """
958        Schedule signal processing by the signal manager.
[8042]959
[11292]960        ..note:: The processing is already scheduled at the most appropriate
961                 time so you should have few uses for this method.
962        """
963        if self.signalManager is not None:
964            self.signalManager.scheduleSignalProcessing(self)
[8042]965
966    def setBlocking(self, state=True):
967        """ Set blocking flag for this widget. While this flag is set this
968        widget and all its descendants will not receive any new signals from
969        the signal manager
970        """
971        self.asyncBlock = state
972        self.emit(SIGNAL("blockingStateChanged(bool)"), self.asyncBlock)
973        if not self.isBlocking():
974            self.scheduleSignalProcessing()
975       
976       
977    def isBlocking(self):
978        """ Is this widget blocking signal processing. Widget is blocking if
979        asyncBlock value is True or any AsyncCall objects in asyncCalls list
980        has blocking flag set
981        """
982        return self.asyncBlock or any(a.blocking for a in self.asyncCalls)
983   
984    def asyncExceptionHandler(self, (etype, value, tb)):
985        import traceback
986        sys.excepthook(etype, value, tb)
987       
988    def asyncFinished(self, async, string):
989        """ Remove async from asyncCalls, update blocking state
990        """
991       
992        index = self.asyncCalls.index(async)
993        async = self.asyncCalls.pop(index)
994       
995        if async.blocking and not self.isBlocking():
996            # if we are responsible for unblocking
997            self.emit(SIGNAL("blockingStateChanged(bool)"), False)
998            self.scheduleSignalProcessing()
999           
1000        async.disconnect(async, SIGNAL("finished(PyQt_PyObject, QString)"), self.asyncFinished)
1001        self.emit(SIGNAL("asyncCallsStateChange()"))
1002               
1003           
1004   
1005    def asyncCall(self, func, args=(), kwargs={}, name=None, onResult=None, onStarted=None, onFinished=None, onError=None, blocking=True, thread=None, threadPool=None):
1006        """ Return an OWConcurent.AsyncCall object func, args and kwargs
1007        set and signals connected.
1008        """
1009        from functools import partial
1010        from OWConcurrent import AsyncCall
1011       
1012        asList = lambda slot: slot if isinstance(slot, list) else ([slot] if slot else [])
1013       
1014        onResult = asList(onResult)
1015        onStarted = asList(onStarted) #+ [partial(self.setBlocking, True)]
1016        onFinished = asList(onFinished) #+ [partial(self.blockSignals, False)]
1017        onError = asList(onError) or [self.asyncExceptionHandler]
1018       
1019        async = AsyncCall(func, args, kwargs, thread=thread, threadPool=threadPool)
1020        async.name = name if name is not None else ""
1021           
1022        for slot in  onResult:
1023            async.connect(async, SIGNAL("resultReady(PyQt_PyObject)"), slot, Qt.QueuedConnection)
1024        for slot in onStarted:
1025            async.connect(async, SIGNAL("starting()"), slot, Qt.QueuedConnection)
1026        for slot in onFinished:
1027            async.connect(async, SIGNAL("finished(QString)"), slot, Qt.QueuedConnection)
1028        for slot in onError:
1029            async.connect(async, SIGNAL("unhandledException(PyQt_PyObject)"), slot, Qt.QueuedConnection)
1030       
1031        self.addAsyncCall(async, blocking)
1032           
1033        return async
1034   
1035    def addAsyncCall(self, async, blocking=True):
1036        """ Add AsyncCall object to asyncCalls list (will be removed
1037        once it finishes processing).
1038       
1039        """
1040        ## TODO: make this thread safe
1041       
1042        async.connect(async, SIGNAL("finished(PyQt_PyObject, QString)"), self.asyncFinished)
1043       
1044        async.blocking = blocking
1045       
1046        if blocking:
1047            # if we are responsible for blocking
1048            state = any(a.blocking for a in self.asyncCalls)
1049            self.asyncCalls.append(async)
1050            if not state:
1051                self.emit(SIGNAL("blockingStateChanged(bool)"), True)
1052        else:
1053            self.asyncCalls.append(async)
1054           
1055        self.emit(SIGNAL("asyncCallsStateChange()"))
1056       
1057   
1058   
1059def blocking(method):
1060    """ Return method that sets blocking flag while executing
1061    """
1062    from functools import wraps
1063    @wraps(method)
1064    def wrapper(self, *args, **kwargs):
1065        old = self._blocking
1066        self.setBlocking(True)
1067        try:
1068            return method(self, *args, **kwargs)
1069        finally:
1070            self.setBlocking(old)
1071   
1072
1073if __name__ == "__main__":
1074    a=QApplication(sys.argv)
1075    oww=OWBaseWidget(adfaf=1)
1076    oww.show()
1077    a.exec_()
[10580]1078    oww.saveSettings()
Note: See TracBrowser for help on using the repository browser.