source: orange/Orange/OrangeCanvas/utils/settings.py @ 11300:306fd82d04e3

Revision 11300:306fd82d04e3, 10.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Removed PyQt < v4.8.3 compatibility code from settings module.

Relying instead on utils.qtcompat to export a compatible interface.

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