source: orange/orange/OrangeWidgets/OWBaseWidget.py @ 7727:37a05f384ca4

Revision 7727:37a05f384ca4, 46.3 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Fixed a problem with sip 4.12.1 (any attribute lookup fails (even getattr(_,_,_) with "underlying C/C++ object has been deleted" before the QDialog init is called).

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