source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 9671:a7b056375472

Revision 9671:a7b056375472, 46.7 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Moved orange to Orange (part 2)

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