source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11134:306625da2200

Revision 11134:306625da2200, 47.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 18 months ago (diff)

Added information, warning and error indicators to items in the canvas.

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