source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11296:207c96a59fe3

Revision 11296:207c96a59fe3, 47.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Capturing help requests ("F1" key press) from OWBaseWidget.

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__ and \
804                hasattr(qApp, "canvasDlg"):
805            # This widget is on a canvas.
806            qApp.canvasDlg.helpWindow.showHelpFor(self.widgetInfo, True)
807
808    def keyPressEvent(self, e):
809        if e.key() in (Qt.Key_Help, Qt.Key_F1):
810            self.openWidgetHelp()
811        elif (int(e.modifiers()), e.key()) in OWBaseWidget.defaultKeyActions:
812            OWBaseWidget.defaultKeyActions[int(e.modifiers()), e.key()](self)
813        else:
814            QDialog.keyPressEvent(self, e)
815
816    def information(self, id=0, text=None):
817        self.setState("Info", id, text)
818
819    def warning(self, id=0, text=""):
820        self.setState("Warning", id, text)
821
822    def error(self, id=0, text=""):
823        self.setState("Error", id, text)
824
825    def setState(self, stateType, id, text):
826        changed = 0
827        if type(id) == list:
828            for val in id:
829                if self.widgetState[stateType].has_key(val):
830                    self.widgetState[stateType].pop(val)
831                    changed = 1
832        else:
833            if isinstance(id, basestring):
834                # if we call information(), warning(), or error() function
835                # with only one parameter - a string - then set id = 0
836                text = id
837                id = 0
838            if not text:
839                if self.widgetState[stateType].has_key(id):
840                    self.widgetState[stateType].pop(id)
841                    changed = 1
842            else:
843                self.widgetState[stateType][id] = text
844                changed = 1
845
846        if changed:
847            if self.widgetStateHandler:
848                self.widgetStateHandler()
849            elif text: # and stateType != "Info":
850                self.printEvent(stateType + " - " + text)
851           
852            if type(id) == list:
853                for i in id:
854                    self.emit(SIGNAL("widgetStateChanged(QString, int, QString)"),
855                              QString(stateType), i,QString(""))
856            else:
857                self.emit(SIGNAL("widgetStateChanged(QString, int, QString)"),
858                             QString(stateType), id, QString(text or ""))
859            #qApp.processEvents()
860        return changed
861
862    widgetStateChanged = pyqtSignal(QString, int, QString)
863    """Widget state has changed first arg is the state type
864    ('Info', 'Warning' or 'Error') the second is the message id
865    and finally the message string."""
866
867    def widgetStateToHtml(self, info=True, warning=True, error=True):
868        pixmaps = self.getWidgetStateIcons()
869        items = [] 
870        iconPath = {"Info": "canvasIcons:information.png",
871                    "Warning": "canvasIcons:warning.png",
872                    "Error": "canvasIcons:error.png"}
873        for show, what in [(info, "Info"), (warning, "Warning"),(error, "Error")]:
874            if show and self.widgetState[what]:
875                items.append('<img src="%s" style="float: left;"> %s' % (iconPath[what], "\n".join(self.widgetState[what].values())))
876        return "<br>".join(items)
877       
878    @classmethod
879    def getWidgetStateIcons(cls):
880        if not hasattr(cls, "_cached__widget_state_icons"):
881            iconsDir = os.path.join(environ.canvas_install_dir, "icons")
882            QDir.addSearchPath("canvasIcons",os.path.join(environ.canvas_install_dir,
883                "icons/"))
884            info = QPixmap("canvasIcons:information.png")
885            warning = QPixmap("canvasIcons:warning.png")
886            error = QPixmap("canvasIcons:error.png")
887            cls._cached__widget_state_icons = \
888                    {"Info": info, "Warning": warning, "Error": error}
889        return cls._cached__widget_state_icons
890
891    def synchronizeContexts(self):
892        if hasattr(self, "contextHandlers"):
893            for contextName, handler in self.contextHandlers.items():
894                context = self.currentContexts.get(contextName, None)
895                if context:
896                    handler.settingsFromWidget(self, context)
897
898    def openContext(self, contextName="", *arg):
899        if not self._useContexts:
900            return
901        handler = self.contextHandlers[contextName]
902        context = handler.openContext(self, *arg)
903        if context:
904            self.currentContexts[contextName] = context
905
906
907    def closeContext(self, contextName=""):
908        if not self._useContexts:
909            return
910        curcontext = self.currentContexts.get(contextName)
911        if curcontext:
912            self.contextHandlers[contextName].closeContext(self, curcontext)
913            del self.currentContexts[contextName]
914
915    def settingsToWidgetCallback(self, handler, context):
916        pass
917
918    def settingsFromWidgetCallback(self, handler, context):
919        pass
920
921    def setControllers(self, obj, controlledName, controller, prefix):
922        while obj:
923            if prefix:
924#                print "SET CONTROLLERS: %s %s + %s" % (obj.__class__.__name__, prefix, controlledName)
925                if obj.__dict__.has_key("attributeController"):
926                    obj.__dict__["__attributeControllers"][(controller, prefix)] = True
927                else:
928                    obj.__dict__["__attributeControllers"] = {(controller, prefix): True}
929
930            parts = controlledName.split(".", 1)
931            if len(parts) < 2:
932                break
933            obj = getattr(obj, parts[0], None)
934            prefix += parts[0]
935            controlledName = parts[1]
936
937    def __setattr__(self, name, value):
938        return unisetattr(self, name, value, QDialog)
939   
940    defaultKeyActions = {}
941   
942    if sys.platform == "darwin":
943        defaultKeyActions = {
944            (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(),
945            (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible())}
946
947    def scheduleSignalProcessing(self):
948        """
949        Schedule signal processing by the signal manager.
950
951        ..note:: The processing is already scheduled at the most appropriate
952                 time so you should have few uses for this method.
953        """
954        if self.signalManager is not None:
955            self.signalManager.scheduleSignalProcessing(self)
956
957    def setBlocking(self, state=True):
958        """ Set blocking flag for this widget. While this flag is set this
959        widget and all its descendants will not receive any new signals from
960        the signal manager
961        """
962        self.asyncBlock = state
963        self.emit(SIGNAL("blockingStateChanged(bool)"), self.asyncBlock)
964        if not self.isBlocking():
965            self.scheduleSignalProcessing()
966       
967       
968    def isBlocking(self):
969        """ Is this widget blocking signal processing. Widget is blocking if
970        asyncBlock value is True or any AsyncCall objects in asyncCalls list
971        has blocking flag set
972        """
973        return self.asyncBlock or any(a.blocking for a in self.asyncCalls)
974   
975    def asyncExceptionHandler(self, (etype, value, tb)):
976        import traceback
977        sys.excepthook(etype, value, tb)
978       
979    def asyncFinished(self, async, string):
980        """ Remove async from asyncCalls, update blocking state
981        """
982       
983        index = self.asyncCalls.index(async)
984        async = self.asyncCalls.pop(index)
985       
986        if async.blocking and not self.isBlocking():
987            # if we are responsible for unblocking
988            self.emit(SIGNAL("blockingStateChanged(bool)"), False)
989            self.scheduleSignalProcessing()
990           
991        async.disconnect(async, SIGNAL("finished(PyQt_PyObject, QString)"), self.asyncFinished)
992        self.emit(SIGNAL("asyncCallsStateChange()"))
993               
994           
995   
996    def asyncCall(self, func, args=(), kwargs={}, name=None, onResult=None, onStarted=None, onFinished=None, onError=None, blocking=True, thread=None, threadPool=None):
997        """ Return an OWConcurent.AsyncCall object func, args and kwargs
998        set and signals connected.
999        """
1000        from functools import partial
1001        from OWConcurrent import AsyncCall
1002       
1003        asList = lambda slot: slot if isinstance(slot, list) else ([slot] if slot else [])
1004       
1005        onResult = asList(onResult)
1006        onStarted = asList(onStarted) #+ [partial(self.setBlocking, True)]
1007        onFinished = asList(onFinished) #+ [partial(self.blockSignals, False)]
1008        onError = asList(onError) or [self.asyncExceptionHandler]
1009       
1010        async = AsyncCall(func, args, kwargs, thread=thread, threadPool=threadPool)
1011        async.name = name if name is not None else ""
1012           
1013        for slot in  onResult:
1014            async.connect(async, SIGNAL("resultReady(PyQt_PyObject)"), slot, Qt.QueuedConnection)
1015        for slot in onStarted:
1016            async.connect(async, SIGNAL("starting()"), slot, Qt.QueuedConnection)
1017        for slot in onFinished:
1018            async.connect(async, SIGNAL("finished(QString)"), slot, Qt.QueuedConnection)
1019        for slot in onError:
1020            async.connect(async, SIGNAL("unhandledException(PyQt_PyObject)"), slot, Qt.QueuedConnection)
1021       
1022        self.addAsyncCall(async, blocking)
1023           
1024        return async
1025   
1026    def addAsyncCall(self, async, blocking=True):
1027        """ Add AsyncCall object to asyncCalls list (will be removed
1028        once it finishes processing).
1029       
1030        """
1031        ## TODO: make this thread safe
1032       
1033        async.connect(async, SIGNAL("finished(PyQt_PyObject, QString)"), self.asyncFinished)
1034       
1035        async.blocking = blocking
1036       
1037        if blocking:
1038            # if we are responsible for blocking
1039            state = any(a.blocking for a in self.asyncCalls)
1040            self.asyncCalls.append(async)
1041            if not state:
1042                self.emit(SIGNAL("blockingStateChanged(bool)"), True)
1043        else:
1044            self.asyncCalls.append(async)
1045           
1046        self.emit(SIGNAL("asyncCallsStateChange()"))
1047       
1048   
1049   
1050def blocking(method):
1051    """ Return method that sets blocking flag while executing
1052    """
1053    from functools import wraps
1054    @wraps(method)
1055    def wrapper(self, *args, **kwargs):
1056        old = self._blocking
1057        self.setBlocking(True)
1058        try:
1059            return method(self, *args, **kwargs)
1060        finally:
1061            self.setBlocking(old)
1062   
1063
1064if __name__ == "__main__":
1065    a=QApplication(sys.argv)
1066    oww=OWBaseWidget(adfaf=1)
1067    oww.show()
1068    a.exec_()
1069    oww.saveSettings()
Note: See TracBrowser for help on using the repository browser.