source: orange/Orange/OrangeWidgets/OWBaseWidget.py @ 11539:2723d9b63fb0

Revision 11539:2723d9b63fb0, 47.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 11 months ago (diff)

Save widget geometry only if the widget was explicitly shown.

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