source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 10651:4f6fcf57db06

Revision 10651:4f6fcf57db06, 46.9 KB checked in by markotoplak, 2 years ago (diff)

Moved caching, collections, debugging, fileutil, r, testing from misc to utils.

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