1 '''
2 Defines an abstract base class for all persistent and/or configurable setting
3 classes.
4
5 @author: Peter Parente
6 @organization: IBM Corporation
7 @copyright: Copyright (c) 2005, 2007 IBM Corporation
8
9 @license: I{The BSD License}
10 All rights reserved. This program and the accompanying materials are made
11 available under the terms of the BSD license which accompanies
12 this distribution, and is available at
13 U{http://www.opensource.org/licenses/bsd-license.php}
14 '''
15
16 from Setting import *
17
19 '''
20 Generate a new unique identifier to help in comparison of L{AEState} copies.
21
22 @return: Unique ID
23 @rtype: integer
24 '''
25 i = 0
26 while 1:
27 yield i
28 i += 1
29 uuid = _uuid()
30
32 '''
33 Abstract base class for objects containing data that will have their data
34 serialized to disk or configured by the user. All new* methods construct
35 L{Setting} objects which contain metadata describing setting values for the
36 purpose of checking bounds, generating configuration dialogs, etc.
37
38 All subclasses of L{AEState} should override L{init} and L{getGroups}. The
39 init method should be used to create new settings and initialize non-setting
40 instance variables. The overriden init does not need to invoke any base class
41 init methods to create inherited settings. All baser class settings are
42 created in the __init__ constructor instead. The getGroups method should
43 call L{newGroup} to create group objects containing the names of related
44 settings to aid the generation of configuration dialogs. Group objects
45 themselves also have newGroup methods to support nested groupings.
46
47 Two attribute names are reserved by L{AEState} objects. L{settings} is the
48 dictionary containing all L{Setting} instances. L{dirty} is a set that
49 stores the names of settings that have been modified in the current instance.
50 The list is automatically reset when the instance is persisted. The
51 L{isDirty}, L{makeClean}, L{iterDirty} methods may be used to check if any
52 part of the state is dirty, clear the list of dirty attributes, and iterate
53 over the names and values of the dirty attributes to enable optimization at
54 higher levels.
55
56 Note that the L{AEState} object itself is not designed to be persisted to
57 disk, but rather to act as a delegate for settings to be persisted. The
58 reason for this is that not all persistence mechanisms can support the
59 serialization of Python objects.
60
61 @ivar dirty: Set of dirty attribute names in this state object
62 @type dirty: set
63 @ivar settings: Dictionary of named L{Setting} objects
64 @type settings: dictionary
65 @ivar ident: Unique ID for this state object which is given to all copies
66 @type ident: integer
67 '''
69 '''
70 Initialize the dirty set and settings dict.
71 '''
72
73 self.__dict__['settings'] = {}
74 self.__dict__['dirty'] = set()
75 self.__dict__['ident'] = uuid.next()
76
78 '''
79 Does nothing by default. Use to create settings after instantiation.
80 '''
81 pass
82
84 '''
85 Gets the root L{Setting.Group} object and all of its child L{Setting} names
86 and nested groups.
87
88 @raise NotImplementedError: When not overridden in a subclass, indicating
89 configuration is not supported
90 '''
91 raise NotImplementedError
92
94 '''
95 Compares two state objects for equality based on their attributes.
96
97 @param other: Another state object
98 @type other: L{AEState}
99 '''
100 try:
101 return self.ident == other.ident
102 except Exception:
103 return False
104
106 '''
107 Compares two state objects for inequality based on their attributes.
108
109 @param other: Another state object
110 @type other: L{AEState}
111 '''
112 return not self.__eq__(other)
113
115 '''
116 Makes a copy of this state object and all of its L{Setting}s.
117 '''
118 c = self.__class__()
119 c.dirty = self.dirty
120 c.ident = self.ident
121 for name, val in self.settings.items():
122 c.settings[name] = val.copy()
123 return c
124
126 '''
127 Adds a copy of an existing setting to this state object under the same
128 name.
129
130 @param setting: Existing setting to copy and store
131 @type setting: L{Setting}
132 '''
133 self.settings[setting.name] = setting.copy()
134
136 '''
137 Builds a new L{Group} object relating settings. The group is not stored
138 internally, just returned for temporary use by another object.
139
140 @param label: Label for the new L{Group}
141 @type label: string
142 '''
143 return Group(self, label)
144
145 - def newString(self, name, default, label, description='', persist=True):
146 '''
147 Adds a new L{StringSetting} to this group.
148
149 @param name: New name of the setting
150 @type name: string
151 @param default: Default value of the setting
152 @type default: string
153 @param label: Label for the new L{Setting}
154 @type label: string
155 @param description: Extended description of the L{Setting}
156 @type description: string
157 @param persist: Persist the setting value to disk?
158 @type persist: boolean
159 @return: New setting object
160 @rtype: L{StringSetting}
161 '''
162 s = StringSetting(self, name, default, label, description, persist)
163 self.settings[name] = s
164 return s
165
166 - def newFilename(self, name, default, label, relative_path, description='',
167 persist=True):
168 '''
169 Adds a new L{FilenameSetting} to this group.
170
171 @param name: New name of the setting
172 @type name: string
173 @param default: Default value of the setting
174 @type default: string
175 @param label: Label for the new L{Setting}
176 @type label: string
177 @param relative_path: Absolute path to which '.' or a filename with no
178 path refers
179 @type relative_path: string
180 @param description: Extended description of the L{Setting}
181 @type description: string
182 @param persist: Persist the setting value to disk?
183 @type persist: boolean
184 @return: New setting object
185 @rtype: L{FilenameSetting}
186 '''
187 relative_path = os.path.realpath(relative_path)
188 if os.path.isfile(relative_path):
189 relative_path = os.path.dirname(relative_path)
190 s = FilenameSetting(self, name, default, label, description, persist,
191 relative_path)
192 self.settings[name] = s
193 return s
194
195 - def newBool(self, name, default, label, description='', persist=True):
196 '''
197 Adds a new L{BoolSetting} to this group.
198
199 @param name: New name of the setting
200 @type name: string
201 @param default: Default value of the setting
202 @type default: boolean
203 @param label: Label for the new L{Setting}
204 @type label: string
205 @param description: Extended description of the L{Setting}
206 @type description: string
207 @param persist: Persist the setting value to disk?
208 @type persist: boolean
209 @return: New setting object
210 @rtype: L{BoolSetting}
211 '''
212 s = BoolSetting(self, name, default, label, description, persist)
213 self.settings[name] = s
214 return s
215
216 - def newNumeric(self, name, default, label, min, max, precision,
217 description='', persist=True):
218 '''
219 Adds a new L{NumericSetting} to this group.
220
221 @param name: New name of the setting
222 @type name: string
223 @param default: Default value of the setting
224 @type default: float
225 @param label: Label for the new L{Setting}
226 @type label: string
227 @param min: Minimum value in the range
228 @type min: number
229 @param max: Maximum value in the range
230 @type max: number
231 @param precision: Number of decimal places
232 @type precision: integer
233 @param description: Extended description of the L{Setting}
234 @type description: string
235 @param persist: Persist the setting value to disk?
236 @type persist: boolean
237 @return: New setting object
238 @rtype: L{NumericSetting}
239 '''
240 s = NumericSetting(self, name, default, label, description, persist, min,
241 max, precision)
242 self.settings[name] = s
243 return s
244
245 - def newRange(self, name, default, label, min, max, precision, description='',
246 persist=True):
247 '''
248 Adds a new L{RangeSetting} to this group.
249
250 @param name: New name of the setting
251 @type name: string
252 @param default: Default value of the setting
253 @type default: float
254 @param label: Label for the new L{Setting}
255 @type label: string
256 @param min: Minimum value in the range
257 @type min: number
258 @param max: Maximum value in the range
259 @type max: number
260 @param precision: Number of decimal places
261 @type precision: integer
262 @param description: Extended description of the L{Setting}
263 @type description: string
264 @param persist: Persist the setting value to disk?
265 @type persist: boolean
266 @return: New setting object
267 @rtype: L{RangeSetting}
268 '''
269 s = RangeSetting(self, name, default, label, description, persist, min,
270 max, precision)
271 self.settings[name] = s
272 return s
273
274 - def newPercent(self, name, default, label, min, max, precision,
275 description='', persist=True):
276 '''
277 Adds a new L{PercentRangeSetting} to this group.
278
279 @param name: New name of the setting
280 @type name: string
281 @param default: Default value of the setting
282 @type default: float
283 @param label: Label for the new L{Setting}
284 @type label: string
285 @param min: Minimum value in the range to be shown as 0%
286 @type min: number
287 @param max: Maximum value in the range to be shown as 100%
288 @type max: number
289 @param precision: Number of decimal places
290 @type precision: integer
291 @param description: Extended description of the L{Setting}
292 @type description: string
293 @param persist: Persist the setting value to disk?
294 @type persist: boolean
295 @return: New setting object
296 @rtype: L{PercentRangeSetting}
297 '''
298 s = PercentRangeSetting(self, name, default, label, description, persist,
299 min, max, precision)
300 self.settings[name] = s
301 return s
302
303 - def newChoice(self, name, default, label, choices, description='',
304 persist=True):
305 '''
306 Adds a new L{ChoiceSetting} to this group.
307
308 @param name: New name of the setting
309 @type name: string
310 @param default: Default value of the setting
311 @type default: string
312 @param label: Label for the new L{Setting}
313 @type label: string
314 @param choices: Collection of choices
315 @type choices: list
316 @param description: Extended description of the L{Setting}
317 @type description: string
318 @param persist: Persist the setting value to disk?
319 @type persist: boolean
320 @return: New setting object
321 @rtype: L{ChoiceSetting}
322 '''
323 s = ChoiceSetting(self, name, default, label, description, persist,
324 choices)
325 self.settings[name] = s
326 return s
327
328 - def newColor(self, name, default, label, description='', persist=True):
329 '''
330 Adds a new L{ColorSetting} to this group.
331
332 @param name: New name of the setting
333 @type name: string
334 @param default: Default value of the setting
335 @type default: 3 or 4-tuple of integer
336 @param label: Label for the new L{Setting}
337 @type label: string
338 @param description: Extended description of the L{Setting}
339 @type description: string
340 @param persist: Persist the setting value to disk?
341 @type persist: boolean
342 @return: New setting object
343 @rtype: L{ColorSetting}
344 '''
345 s = ColorSetting(self, name, default, label, description, persist)
346 self.settings[name] = s
347 return s
348
349 - def newEnum(self, name, default, label, choices, description='',
350 persist=True):
351 '''
352 Adds a new L{EnumSetting} to this group.
353
354 @param name: New name of the setting
355 @type name: string
356 @param default: Default value of the setting
357 @type default: integer
358 @param label: Label for the new L{Setting}
359 @type label: string
360 @param choices: Collection of choices mapped to the values they represent
361 @type choices: dictionary
362 @param description: Extended description of the L{Setting}
363 @type description: string
364 @param persist: Persist the setting value to disk?
365 @type persist: boolean
366 @return: New setting object
367 @rtype: L{EnumSetting}
368 '''
369 s = EnumSetting(self, name, default, label, description, persist, choices)
370 self.settings[name] = s
371 return s
372
374 '''
375 Stores a setting value if the setting is already defined by the give name
376 or an instance attribute if the name doesn't not define a setting.
377 Provided for convenience over using L{setSettingVal}.
378
379 @param name: Name of the setting or instance attribute
380 @type name: string
381 @param val: Arbitrary value to store
382 @type val: object
383 '''
384 try:
385 self.__dict__['settings'][name].value = val
386 except KeyError:
387 self.__dict__[name] = val
388
390 '''
391 Gets a setting value if the setting is already defined by the give name
392 or an instance attribute if the name doesn't not define a setting.
393 Provided for convenience over using L{getSettingVal}.
394
395 @param name: Name of the setting or instance attribute
396 @type name: string
397 @return: Arbitrary value to get
398 @rtype: object
399 @raise AttributeError: When the name does not define a setting or instance
400 attribute
401 '''
402 try:
403
404 return self.__dict__['settings'][name].value
405 except KeyError:
406 pass
407 try:
408
409 return self.__dict__[name]
410 except KeyError:
411 raise AttributeError("'%s' object has no attribute '%s'" %
412 (self.__class__.__name__, name))
413
415 '''Invokes L{Setting.Setting.save} on all settings in the state.'''
416 for sett in self.settings.values():
417 sett.save()
418
420 '''Invokes L{Setting.Setting.restore} on all settings in the state.'''
421 for sett in self.settings.values():
422 sett.restore()
423
425 '''
426 Gets if this object has a setting with the given name.
427
428 @param name: Name of the setting
429 @type name: string
430 @return: Has the setting or not?
431 @rtype: boolean
432 '''
433 return self.settings.has_key(name)
434
436 '''
437 Retrieves the L{Setting} object with the given name.
438
439 @param name: Name of the setting
440 @type name: string
441 @return: The named setting object
442 @rtype: L{Setting}
443 @raise KeyError: When a L{Setting} with the given name is not defined
444 '''
445 return self.settings[name]
446
448 '''
449 Convenience method for getting the value of a L{Setting} object.
450
451 @param name: Name of the setting
452 @type name: string
453 @return: Current value of a setting
454 @rtype: object
455 @raise KeyError: When a L{Setting} with the given name is not defined
456 '''
457 return self.settings[name].value
458
460 '''
461 Convenience method for setting the value of a L{Setting} object.
462
463 @param name: Name of the setting
464 @type name: string
465 @param val: Value to store
466 @type val: object
467 @raise KeyError: When a L{Setting} with the given name is not defined
468 '''
469 self.settings[name].value = val
470
472 '''
473 Grabs all L{Setting} values and stores them in a dictionary keyed by
474 setting name. Invokes L{restore} first to ensure only user accepted values
475 are persisted.
476
477 @return: Mapping from setting names to values
478 @rtype: dictionary
479 '''
480 self.restore()
481 return dict([(name, sett.serialize()) for name, sett in
482 self.settings.items() if sett.persist])
483
485 '''
486 Stores all L{Setting} values given a dictionary mapping setting names to
487 values. Ignores names in the dictionary that do not exist in L{settings}.
488
489 @param data: Mapping from setting names to values
490 @type data: dictionary
491 '''
492 for name, value in data.items():
493 try:
494 self.settings[name].unserialize(value)
495 except KeyError:
496
497 pass
498
500 '''
501 Gets if values in L{settings} have changed since the last call to
502 L{makeClean}.
503
504 @return: Dirty or not?
505 @rtype: boolean
506 '''
507 return len(self.dirty) > 0
508
510 '''
511 Resets the dirty set to empty.
512 '''
513 self.dirty = set()
514
516 '''
517 Adds the given setting name to the dirty set.
518
519 @param state: State object to which the setting is attached
520 @type state: weakref.proxy to L{AEState}
521 @param setting: Setting that changed value
522 @type setting: L{Setting}
523 '''
524 self.dirty.add(setting.name)
525
527 '''
528 Iterates over all of the names in L{dirty} in no particular order.
529
530 @return: Name of a dirty setting
531 @rtype: string
532 '''
533 for name in self.dirty:
534 yield name
535
537 '''
538 Iterates over all of the setting objects in L{settings}
539 in no particular order.
540
541 @return: A setting
542 @rtype: L{Setting}
543 '''
544 for setting in self.settings.values():
545 yield setting
546