source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11882:ab8e80815d54

Revision 11882:ab8e80815d54, 48.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 3 weeks ago (diff)

Added settings data versioning to OWBaseWidget.

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