source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11617:108c380bf5c0

Revision 11617:108c380bf5c0, 48.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 10 months ago (diff)

Changed the default settings filename.

Now using fully qualified import name as the base instead of the
widget caption.

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