source: orange/orange/OrangeWidgets/OWBaseWidget.py @ 7117:b587613c6e01

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