source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11269:b9a89af169b2

Revision 11269:b9a89af169b2, 47.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Replaced old orngSignalManager.SignalManager class.

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