source: orange/Orange/OrangeCanvas/utils/settings.py @ 11342:6b6c71fa3af1

Revision 11342:6b6c71fa3af1, 10.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Small fixes for PyQt compatibility

Line 
1"""
2Settings (`settings`)
3=====================
4
5A more `dict` like interface for QSettings
6
7"""
8
9import abc
10import logging
11
12from collections import namedtuple, MutableMapping
13
14from PyQt4.QtCore import QObject, QString, QChar, QEvent, QCoreApplication
15
16from PyQt4.QtCore import pyqtSignal as Signal
17
18pyqtWrapperType = type(QObject)
19
20from . import toPyObject
21
22# Import QSettings from qtcompat module (compatibility with PyQt < 4.8.3
23from .qtcompat import QSettings
24
25log = logging.getLogger(__name__)
26
27
28config_slot = namedtuple(
29    "config_slot",
30    ["key",
31     "value_type",
32     "default_value",
33     "doc"]
34)
35
36
37class SettingChangedEvent(QEvent):
38    """
39    A settings has changed.
40
41    This event is sent by Settings instance to itself when a setting for
42    a key has changed.
43
44    """
45    SettingChanged = QEvent.registerEventType()
46    """Setting was changed"""
47
48    SettingAdded = QEvent.registerEventType()
49    """A setting was added"""
50
51    SettingRemoved = QEvent.registerEventType()
52    """A setting was removed"""
53
54    def __init__(self, etype, key, value=None, oldValue=None):
55        """
56        Initialize the event instance
57        """
58        QEvent.__init__(self, etype)
59        self.__key = key
60        self.__value = value
61        self.__oldValue = oldValue
62
63    def key(self):
64        return self.__key
65
66    def value(self):
67        return self.__value
68
69    def oldValue(self):
70        return self.__oldValue
71
72
73def qt_to_mapped_type(value):
74    """
75    Try to convert a Qt value to the corresponding python mapped type
76    (i.e. QString to unicode, etc.).
77
78    """
79    if isinstance(value, QString):
80        return unicode(value)
81    elif isinstance(value, QChar):
82        return str(value)
83    else:
84        return value
85
86
87class QABCMeta(pyqtWrapperType, abc.ABCMeta):
88    def __init__(self, name, bases, attr_dict):
89        pyqtWrapperType.__init__(self, name, bases, attr_dict)
90        abc.ABCMeta.__init__(self, name, bases, attr_dict)
91
92
93class _pickledvalue(object):
94    def __init__(self, value):
95        self.value = value
96
97
98class Settings(QObject, MutableMapping):
99    """
100    A `dict` like interface to a QSettings store.
101    """
102    __metaclass__ = QABCMeta
103
104    valueChanged = Signal(unicode, object)
105    valueAdded = Signal(unicode, object)
106    keyRemoved = Signal(unicode)
107
108    def __init__(self, parent=None, defaults=(), path=None, store=None):
109        QObject.__init__(self, parent)
110
111        if store is None:
112            store = QSettings()
113
114        path = path = (path or "").rstrip("/")
115
116        self.__path = path
117        self.__defaults = dict([(slot.key, slot) for slot in defaults])
118        self.__store = store
119
120    def __key(self, key):
121        """
122        Return the full key (including group path).
123        """
124        if self.__path:
125            return "/".join([self.__path, key])
126        else:
127            return key
128
129    def __delitem__(self, key):
130        """
131        Delete the setting for key. If key is a group remove the
132        whole group.
133
134        .. note:: defaults cannot be deleted they are instead reverted
135                  to their original state.
136
137        """
138        if key not in self:
139            raise KeyError(key)
140
141        if self.isgroup(key):
142            group = self.group(key)
143            for key in group:
144                del group[key]
145
146        else:
147            fullkey = self.__key(key)
148
149            oldValue = self.get(key)
150
151            if self.__store.contains(fullkey):
152                self.__store.remove(fullkey)
153
154            newValue = None
155            if fullkey in self.__defaults:
156                newValue = self.__defaults[fullkey].default_value
157                etype = SettingChangedEvent.SettingChanged
158            else:
159                etype = SettingChangedEvent.SettingRemoved
160
161            QCoreApplication.sendEvent(
162                self, SettingChangedEvent(etype, key, newValue, oldValue)
163            )
164
165    def __value(self, fullkey, value_type):
166        typesafe = value_type is not None
167
168        if value_type is None:
169            value = toPyObject(self.__store.value(fullkey))
170        else:
171            try:
172                value = self.__store.value(fullkey, type=value_type)
173            except TypeError:
174                # In case the value was pickled in a type unsafe mode
175                value = toPyObject(self.__store.value(fullkey))
176                typesafe = False
177
178        if not typesafe:
179            if isinstance(value, _pickledvalue):
180                value = value.value
181            else:
182                log.warning("value for key %r is not a '_pickledvalue' (%r),"
183                            "possible loss of type information.",
184                            fullkey,
185                            type(value))
186
187        return value
188
189    def __setValue(self, fullkey, value, value_type=None):
190        typesafe = value_type is not None
191        if not typesafe:
192            # value is stored in a _pickledvalue wrapper to force PyQt
193            # to store it in a pickled format so we don't lose the type
194            # TODO: Could check if QSettings.Format stores type info.
195            value = _pickledvalue(value)
196
197        self.__store.setValue(fullkey, value)
198
199    def __getitem__(self, key):
200        """
201        Get the setting for key.
202        """
203        if key not in self:
204            raise KeyError(key)
205
206        if self.isgroup(key):
207            raise KeyError("{0!r} is a group".format(key))
208
209        fullkey = self.__key(key)
210        slot = self.__defaults.get(fullkey, None)
211
212        if self.__store.contains(fullkey):
213            value = self.__value(fullkey, slot.value_type if slot else None)
214        else:
215            value = slot.default_value
216
217        return value
218
219    def __setitem__(self, key, value):
220        """
221        Set the setting for key.
222        """
223        if not isinstance(key, basestring):
224            raise TypeError(key)
225
226        fullkey = self.__key(key)
227        value_type = None
228
229        if fullkey in self.__defaults:
230            value_type = self.__defaults[fullkey].value_type
231            if not isinstance(value, value_type):
232                value = qt_to_mapped_type(value)
233                if not isinstance(value, value_type):
234                    raise TypeError("Expected {0!r} got {1!r}".format(
235                                        value_type.__name__,
236                                        type(value).__name__)
237                                    )
238
239        if key in self:
240            oldValue = self.get(key)
241            etype = SettingChangedEvent.SettingChanged
242        else:
243            oldValue = None
244            etype = SettingChangedEvent.SettingAdded
245
246        self.__setValue(fullkey, value, value_type)
247
248        QCoreApplication.sendEvent(
249            self, SettingChangedEvent(etype, key, value, oldValue)
250        )
251
252    def __contains__(self, key):
253        """
254        Return `True` if settings contain the `key`, False otherwise.
255        """
256        fullkey = self.__key(key)
257        return self.__store.contains(fullkey) or (fullkey in self.__defaults)
258
259    def __iter__(self):
260        """Return an iterator over over all keys.
261        """
262        keys = map(unicode, self.__store.allKeys()) + \
263               self.__defaults.keys()
264
265        if self.__path:
266            path = self.__path + "/"
267            keys = filter(lambda key: key.startswith(path), keys)
268            keys = [key[len(path):] for key in keys]
269
270        return iter(sorted(set(keys)))
271
272    def __len__(self):
273        return len(list(iter(self)))
274
275    def group(self, path):
276        if self.__path:
277            path = "/".join([self.__path, path])
278
279        return Settings(self, self.__defaults.values(), path, self.__store)
280
281    def isgroup(self, key):
282        """
283        Is the `key` a settings group i.e. does it have subkeys.
284        """
285        if key not in self:
286            raise KeyError("{0!r} is not a valid key".format(key))
287
288        return len(self.group(key)) > 0
289
290    def isdefault(self, key):
291        """
292        Is the value for key the default.
293        """
294        if key not in self:
295            raise KeyError(key)
296        return not self.__store.contains(self.__key(key))
297
298    def clear(self):
299        """
300        Clear the settings and restore the defaults.
301        """
302        self.__store.clear()
303
304    def add_default_slot(self, default):
305        """
306        Add a default slot to the settings This also replaces any
307        previously set value for the key.
308
309        """
310        value = default.default_value
311        oldValue = None
312        etype = SettingChangedEvent.SettingAdded
313        key = default.key
314
315        if key in self:
316            oldValue = self.get(key)
317            etype = SettingChangedEvent.SettingChanged
318            if not self.isdefault(key):
319                # Replacing a default value.
320                self.__store.remove(self.__key(key))
321
322        self.__defaults[key] = default
323        event = SettingChangedEvent(etype, key, value, oldValue)
324        QCoreApplication.sendEvent(self, event)
325
326    def get_default_slot(self, key):
327        return self.__defaults[self.__key(key)]
328
329    def values(self):
330        """
331        Return a list over of all values in the settings.
332        """
333        return MutableMapping.values(self)
334
335    def customEvent(self, event):
336        QObject.customEvent(self, event)
337
338        if isinstance(event, SettingChangedEvent):
339            if event.type() == SettingChangedEvent.SettingChanged:
340                self.valueChanged.emit(event.key(), event.value())
341            elif event.type() == SettingChangedEvent.SettingAdded:
342                self.valueAdded.emit(event.key(), event.value())
343            elif event.type() == SettingChangedEvent.SettingRemoved:
344                self.keyRemoved.emit(event.key())
345
346            parent = self.parent()
347            if isinstance(parent, Settings):
348                # Assumption that the parent is a parent setting group.
349                parent.customEvent(
350                    SettingChangedEvent(event.type(),
351                                        "/".join([self.__path, event.key()]),
352                                        event.value(),
353                                        event.oldValue())
354                )
Note: See TracBrowser for help on using the repository browser.