source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11481:08b42fee6cf6

Revision 11481:08b42fee6cf6, 47.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Extended the OWConcurrent module with 'concurrent.futures' like interface.

The old interface is now deprecated.

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