source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11260:d59d0a632922

Revision 11260:d59d0a632922, 47.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Make sure the OWBaseWidget's caption is a unicode string.

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