source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11205:60e3cdc7c041

Revision 11205:60e3cdc7c041, 47.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Removed obsolete unused widget geometry settings (were replaced by savedWidgetGeometry).

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", []) + ["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 = ["widgetShown"]
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.savedWidgetGeometry = str(self.saveGeometry())
337
338
339    # when widget is moved, save new x and y position into widgetXPosition and widgetYPosition. some widgets can put this two
340    # variables into settings and last widget position is restored after restart
341    # Commented out because of Ubuntu (on call to restoreGeometry calls move event saving pos (0, 0)
342#    def moveEvent(self, ev):
343#        QDialog.moveEvent(self, ev)
344#        if self.savePosition:
345#            self.widgetXPosition = self.frameGeometry().x()
346#            self.widgetYPosition = self.frameGeometry().y()
347#            self.savedWidgetGeometry = str(self.saveGeometry())
348
349    # set widget state to hidden
350    def hideEvent(self, ev):
351        if self.savePosition:
352            self.widgetShown = 0
353            self.savedWidgetGeometry = str(self.saveGeometry())
354        QDialog.hideEvent(self, ev)
355
356    # override the default show function.
357    # after show() we must call processEvents because show puts some LayoutRequests in queue
358    # and we must process them immediately otherwise the width(), height(), ... of elements in the widget will be wrong
359#    def show(self):
360#        QDialog.show(self)
361#        qApp.processEvents()
362
363    # set widget state to shown
364    def showEvent(self, ev):   
365        QDialog.showEvent(self, ev)
366        if self.savePosition:
367            self.widgetShown = 1
368           
369        self.restoreWidgetPosition()
370       
371    def closeEvent(self, ev):
372        if self.savePosition:
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 isinstance(file, basestring):
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 isinstance(file, basestring):
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    # PROGRESS BAR FUNCTIONS
719
720    progressBarValueChanged = pyqtSignal(float)
721    """Progress bar value has changed"""
722
723    processingStateChanged = pyqtSignal(int)
724    """Processing state has changed"""
725
726    def progressBarInit(self):
727        self.progressBarValue = 0
728        self.startTime = time.time()
729        self.setWindowTitle(self.captionTitle + " (0% complete)")
730        if self.progressBarHandler:
731            self.progressBarHandler(self, 0)
732        self.processingStateChanged.emit(1)
733
734    def progressBarSet(self, value):
735        if value > 0:
736            self.__progressBarValue = value
737            usedTime = max(1, time.time() - self.startTime)
738            totalTime = (100.0 * usedTime) / float(value)
739            remainingTime = max(0, totalTime - usedTime)
740            h = int(remainingTime / 3600)
741            min = int((remainingTime - h * 3600) / 60)
742            sec = int(remainingTime - h * 3600 - min * 60)
743            if h > 0:
744                text = "%(h)d:%(min)02d:%(sec)02d" % vars()
745            else:
746                text = "%(min)d:%(sec)02d" % vars()
747            self.setWindowTitle(self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars())
748        else:
749            self.setWindowTitle(self.captionTitle + " (0% complete)")
750        if self.progressBarHandler:
751            self.progressBarHandler(self, value)
752
753        self.progressBarValueChanged.emit(value)
754
755        qApp.processEvents()
756
757    def progressBarValue(self):
758        return self.__progressBarValue
759
760    progressBarValue = pyqtProperty(float, fset=progressBarSet,
761                                    fget=progressBarValue)
762
763    def progressBarAdvance(self, value):
764        self.progressBarSet(self.progressBarValue + value)
765
766    def progressBarFinished(self):
767        self.setWindowTitle(self.captionTitle)
768        if self.progressBarHandler:
769            self.progressBarHandler(self, 101)
770        self.processingStateChanged.emit(0)
771
772    # handler must be a function, that receives 2 arguments. First is the widget instance, the second is the value between -1 and 101
773    def setProgressBarHandler(self, handler):
774        self.progressBarHandler = handler
775
776    def setProcessingHandler(self, handler):
777        self.processingHandler = handler
778
779    def setEventHandler(self, handler):
780        self.eventHandler = handler
781
782    def setWidgetStateHandler(self, handler):
783        self.widgetStateHandler = handler
784
785
786    # if we are in debug mode print the event into the file
787    def printEvent(self, text, eventVerbosity = 1):
788        self.signalManager.addEvent(self.captionTitle + ": " + text, eventVerbosity = eventVerbosity)
789        if self.eventHandler:
790            self.eventHandler(self.captionTitle + ": " + text, eventVerbosity)
791
792    def openWidgetHelp(self):
793        if "widgetInfo" in self.__dict__:  # This widget is on a canvas.
794            qApp.canvasDlg.helpWindow.showHelpFor(self.widgetInfo, True)
795       
796    def focusInEvent(self, *ev):
797        #print "focus in"
798        #if qApp.canvasDlg.settings["synchronizeHelp"]:  on ubuntu: pops up help window on first widget focus for every widget   
799        #    qApp.canvasDlg.helpWindow.showHelpFor(self, True)
800        QDialog.focusInEvent(self, *ev)
801       
802   
803    def keyPressEvent(self, e):
804        if e.key() in (Qt.Key_Help, Qt.Key_F1):
805            self.openWidgetHelp()
806#            e.ignore()
807        elif (int(e.modifiers()), e.key()) in OWBaseWidget.defaultKeyActions:
808            OWBaseWidget.defaultKeyActions[int(e.modifiers()), e.key()](self)
809        else:
810            QDialog.keyPressEvent(self, e)
811
812    def information(self, id = 0, text = None):
813        self.setState("Info", id, text)
814        #self.setState("Warning", id, text)
815
816    def warning(self, id = 0, text = ""):
817        self.setState("Warning", id, text)
818        #self.setState("Info", id, text)        # if we want warning just set information
819
820    def error(self, id = 0, text = ""):
821        self.setState("Error", id, text)
822
823    def setState(self, stateType, id, text):
824        changed = 0
825        if type(id) == list:
826            for val in id:
827                if self.widgetState[stateType].has_key(val):
828                    self.widgetState[stateType].pop(val)
829                    changed = 1
830        else:
831            if type(id) == str:
832                text = id; id = 0       # if we call information(), warning(), or error() function with only one parameter - a string - then set id = 0
833            if not text:
834                if self.widgetState[stateType].has_key(id):
835                    self.widgetState[stateType].pop(id)
836                    changed = 1
837            else:
838                self.widgetState[stateType][id] = text
839                changed = 1
840
841        if changed:
842            if self.widgetStateHandler:
843                self.widgetStateHandler()
844            elif text: # and stateType != "Info":
845                self.printEvent(stateType + " - " + text)
846           
847            if type(id) == list:
848                for i in id:
849                    self.emit(SIGNAL("widgetStateChanged(QString, int, QString)"),
850                              QString(stateType), i,QString(""))
851            else:
852                self.emit(SIGNAL("widgetStateChanged(QString, int, QString)"),
853                             QString(stateType), id, QString(text or ""))
854            #qApp.processEvents()
855        return changed
856
857    widgetStateChanged = pyqtSignal(QString, int, QString)
858    """Widget state has changed first arg is the state type
859    ('Info', 'Warning' or 'Error') the second is the message id
860    and finally the message string."""
861
862    def widgetStateToHtml(self, info=True, warning=True, error=True):
863        pixmaps = self.getWidgetStateIcons()
864        items = [] 
865        iconPath = {"Info": "canvasIcons:information.png",
866                    "Warning": "canvasIcons:warning.png",
867                    "Error": "canvasIcons:error.png"}
868        for show, what in [(info, "Info"), (warning, "Warning"),(error, "Error")]:
869            if show and self.widgetState[what]:
870                items.append('<img src="%s" style="float: left;"> %s' % (iconPath[what], "\n".join(self.widgetState[what].values())))
871        return "<br>".join(items)
872       
873    @classmethod
874    def getWidgetStateIcons(cls):
875        if not hasattr(cls, "_cached__widget_state_icons"):
876            iconsDir = os.path.join(environ.canvas_install_dir, "icons")
877            QDir.addSearchPath("canvasIcons",os.path.join(environ.canvas_install_dir,
878                "icons/"))
879            info = QPixmap("canvasIcons:information.png")
880            warning = QPixmap("canvasIcons:warning.png")
881            error = QPixmap("canvasIcons:error.png")
882            cls._cached__widget_state_icons = \
883                    {"Info": info, "Warning": warning, "Error": error}
884        return cls._cached__widget_state_icons
885
886    def synchronizeContexts(self):
887        if hasattr(self, "contextHandlers"):
888            for contextName, handler in self.contextHandlers.items():
889                context = self.currentContexts.get(contextName, None)
890                if context:
891                    handler.settingsFromWidget(self, context)
892
893    def openContext(self, contextName="", *arg):
894        if not self._useContexts:
895            return
896        handler = self.contextHandlers[contextName]
897        context = handler.openContext(self, *arg)
898        if context:
899            self.currentContexts[contextName] = context
900
901
902    def closeContext(self, contextName=""):
903        if not self._useContexts:
904            return
905        curcontext = self.currentContexts.get(contextName)
906        if curcontext:
907            self.contextHandlers[contextName].closeContext(self, curcontext)
908            del self.currentContexts[contextName]
909
910    def settingsToWidgetCallback(self, handler, context):
911        pass
912
913    def settingsFromWidgetCallback(self, handler, context):
914        pass
915
916    def setControllers(self, obj, controlledName, controller, prefix):
917        while obj:
918            if prefix:
919#                print "SET CONTROLLERS: %s %s + %s" % (obj.__class__.__name__, prefix, controlledName)
920                if obj.__dict__.has_key("attributeController"):
921                    obj.__dict__["__attributeControllers"][(controller, prefix)] = True
922                else:
923                    obj.__dict__["__attributeControllers"] = {(controller, prefix): True}
924
925            parts = controlledName.split(".", 1)
926            if len(parts) < 2:
927                break
928            obj = getattr(obj, parts[0], None)
929            prefix += parts[0]
930            controlledName = parts[1]
931
932    def __setattr__(self, name, value):
933        return unisetattr(self, name, value, QDialog)
934   
935    defaultKeyActions = {}
936   
937    if sys.platform == "darwin":
938        defaultKeyActions = {
939            (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(),
940            (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible())}
941
942
943    def scheduleSignalProcessing(self):
944        self.signalManager.scheduleSignalProcessing(self)
945
946    def setBlocking(self, state=True):
947        """ Set blocking flag for this widget. While this flag is set this
948        widget and all its descendants will not receive any new signals from
949        the signal manager
950        """
951        self.asyncBlock = state
952        self.emit(SIGNAL("blockingStateChanged(bool)"), self.asyncBlock)
953        if not self.isBlocking():
954            self.scheduleSignalProcessing()
955       
956       
957    def isBlocking(self):
958        """ Is this widget blocking signal processing. Widget is blocking if
959        asyncBlock value is True or any AsyncCall objects in asyncCalls list
960        has blocking flag set
961        """
962        return self.asyncBlock or any(a.blocking for a in self.asyncCalls)
963   
964    def asyncExceptionHandler(self, (etype, value, tb)):
965        import traceback
966        sys.excepthook(etype, value, tb)
967       
968    def asyncFinished(self, async, string):
969        """ Remove async from asyncCalls, update blocking state
970        """
971       
972        index = self.asyncCalls.index(async)
973        async = self.asyncCalls.pop(index)
974       
975        if async.blocking and not self.isBlocking():
976            # if we are responsible for unblocking
977            self.emit(SIGNAL("blockingStateChanged(bool)"), False)
978            self.scheduleSignalProcessing()
979           
980        async.disconnect(async, SIGNAL("finished(PyQt_PyObject, QString)"), self.asyncFinished)
981        self.emit(SIGNAL("asyncCallsStateChange()"))
982               
983           
984   
985    def asyncCall(self, func, args=(), kwargs={}, name=None, onResult=None, onStarted=None, onFinished=None, onError=None, blocking=True, thread=None, threadPool=None):
986        """ Return an OWConcurent.AsyncCall object func, args and kwargs
987        set and signals connected.
988        """
989        from functools import partial
990        from OWConcurrent import AsyncCall
991       
992        asList = lambda slot: slot if isinstance(slot, list) else ([slot] if slot else [])
993       
994        onResult = asList(onResult)
995        onStarted = asList(onStarted) #+ [partial(self.setBlocking, True)]
996        onFinished = asList(onFinished) #+ [partial(self.blockSignals, False)]
997        onError = asList(onError) or [self.asyncExceptionHandler]
998       
999        async = AsyncCall(func, args, kwargs, thread=thread, threadPool=threadPool)
1000        async.name = name if name is not None else ""
1001           
1002        for slot in  onResult:
1003            async.connect(async, SIGNAL("resultReady(PyQt_PyObject)"), slot, Qt.QueuedConnection)
1004        for slot in onStarted:
1005            async.connect(async, SIGNAL("starting()"), slot, Qt.QueuedConnection)
1006        for slot in onFinished:
1007            async.connect(async, SIGNAL("finished(QString)"), slot, Qt.QueuedConnection)
1008        for slot in onError:
1009            async.connect(async, SIGNAL("unhandledException(PyQt_PyObject)"), slot, Qt.QueuedConnection)
1010       
1011        self.addAsyncCall(async, blocking)
1012           
1013        return async
1014   
1015    def addAsyncCall(self, async, blocking=True):
1016        """ Add AsyncCall object to asyncCalls list (will be removed
1017        once it finishes processing).
1018       
1019        """
1020        ## TODO: make this thread safe
1021       
1022        async.connect(async, SIGNAL("finished(PyQt_PyObject, QString)"), self.asyncFinished)
1023       
1024        async.blocking = blocking
1025       
1026        if blocking:
1027            # if we are responsible for blocking
1028            state = any(a.blocking for a in self.asyncCalls)
1029            self.asyncCalls.append(async)
1030            if not state:
1031                self.emit(SIGNAL("blockingStateChanged(bool)"), True)
1032        else:
1033            self.asyncCalls.append(async)
1034           
1035        self.emit(SIGNAL("asyncCallsStateChange()"))
1036       
1037   
1038   
1039def blocking(method):
1040    """ Return method that sets blocking flag while executing
1041    """
1042    from functools import wraps
1043    @wraps(method)
1044    def wrapper(self, *args, **kwargs):
1045        old = self._blocking
1046        self.setBlocking(True)
1047        try:
1048            return method(self, *args, **kwargs)
1049        finally:
1050            self.setBlocking(old)
1051   
1052
1053if __name__ == "__main__":
1054    a=QApplication(sys.argv)
1055    oww=OWBaseWidget(adfaf=1)
1056    oww.show()
1057    a.exec_()
1058    oww.saveSettings()
Note: See TracBrowser for help on using the repository browser.