source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11292:136a860daa67

Revision 11292:136a860daa67, 46.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

OWBaseWidget cleanup/compatibility.

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