source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11482:30188643fe5b

Revision 11482:30188643fe5b, 47.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Added 'progressEventsFlags' argument to 'progressBarSet' method.

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