1 '''
2 Defines a class representing a configurable setting in a L{AEState} object.
3
4 @author: Peter Parente
5 @author: Eitan Isaacson
6 @organization: IBM Corporation
7 @copyright: Copyright (c) 2005, 2007 IBM Corporation
8 @license: The BSD License
9
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 import weakref, new, copy
17 import os.path
18
20 '''
21 Our own proxy object which enables weak references to bound and unbound
22 methods and arbitrary callables. Pulls information about the function,
23 class, and instance out of a bound method. Stores a weak reference to the
24 instance to support garbage collection.
25 '''
27 try:
28 try:
29 self.inst = weakref.ref(cb.im_self)
30 except TypeError:
31 self.inst = None
32 self.func = cb.im_func
33 self.klass = cb.im_class
34 except AttributeError:
35 self.inst = None
36 self.func = cb.im_func
37 self.klass = None
38
40 '''
41 Proxy for a call to the weak referenced object. Take arbitrary params to
42 pass to the callable.
43
44 @raise ReferenceError: When the weak reference refers to a dead object
45 '''
46 if self.inst is not None and self.inst() is None:
47 raise ReferenceError
48 elif self.inst is not None:
49
50 mtd = new.instancemethod(self.func, self.inst(), self.klass)
51 else:
52
53 mtd = self.func
54
55 return mtd(*args, **kwargs)
56
58 '''
59 Compare the held function and instance with that held by another proxy.
60
61 @param other: Another proxy object
62 @type other: L{Proxy}
63 @return: Whether this func/inst pair is equal to the one in the other proxy
64 object or not
65 @rtype: boolean
66 '''
67 try:
68 return self.func == other.func and self.inst() == other.inst()
69 except Exception:
70 return False
71
73 '''
74 Inverse of __eq__.
75 '''
76 return not self.__eq__(other)
77
79 '''
80 Exposes the raw value, min, and max properties of a L{NumericSetting}.
81 '''
83 self.setting = setting
84
86 return self.setting.value
87
89 self.setting.value = val
90
91 min = property(lambda self: self.setting.min)
92 max = property(lambda self: self.setting.max)
93 precision = property(lambda self: self.setting.precision)
94 value = property(_getValue, _setValue)
95
97 '''
98 Exposes the percent value, min, and max of a L{PercentRangeSetting}
99 '''
101 '''
102 Converts the given percentage to a raw value and stores it.
103
104 @param val: New percent to store
105 @type val: integer
106 @raise TypeError: When the value cannot be converted to a integer
107 '''
108
109 val = (val/100.0 * (self.setting.max - self.setting.min))+self.setting.min
110 self.setting.value = val
111
113 '''
114 Gets the current value as a percentage.
115
116 @return: Current value as %
117 @rtype: integer
118 '''
119
120 val = self.setting.value
121 return int(round(val/float(self.setting.max - self.setting.min)*100, 0))
122
123 precision = property(lambda self: 0)
124 min = property(lambda self: 0)
125 max = property(lambda self: 100)
126 value = property(_getPercent, _setPercent)
127
129 '''
130 Exposes the raw value, min, and max properties of a L{RelNumericSetting}.
131 '''
133 if self.setting.parent is None:
134 return self.setting.min
135 else:
136 return self.setting.rel_min
137
139 if self.setting.parent is None:
140 return self.setting.max
141 else:
142 return self.setting.rel_max
143
144 min = property(_getMin)
145 max = property(_getMax)
146
148 '''
149 Exposes the percent value, min, and max of a L{RelPercentRangeSetting}.
150 '''
152 if self.setting.parent is None:
153 return 0
154 else:
155 return self.setting.rel_min
156
158 if self.setting.parent is None:
159 return 100
160 else:
161 return self.setting.rel_max
162
163 min = property(_getMin)
164 max = property(_getMax)
165
167 '''
168 Named collection of related L{Setting}s.
169
170 Both the name and the state of the group should be considered public
171 readable.
172
173 @ivar state: Object whose properties are contained within this group
174 @type state: L{AEState.AEState}
175 @ivar name: Name of the group
176 @type name: string
177 '''
179 '''
180 Stores the L{Group} state and its name.
181
182 @param state: Object whose properties are contained within this group
183 @type state: L{AEState.AEState}
184 @param name: Name of the group
185 @type name: string
186 '''
187 self.state = state
188 self.name = name
189
191 return 'Group %s: %s' % (self.name, list.__str__(self))
192
194 '''
195 Adds a subgroup to this group with the given name.
196
197 @param name: Name of the new group
198 @type name: string
199 @return: The newly created group
200 @rtype: L{Group}
201 '''
202 grp = Group(self.state, name)
203 self.append(grp)
204 return grp
205
207 '''
208 Iterates over the L{Setting}s and L{Group}s stored in this L{Group}. Yield
209 both the index of the item and the item itself.
210
211 @param reverse: Iterate in reverse direction?
212 @type reverse: boolean
213 @return: Next index and setting object on each iteration
214 @rtype: 2-tuple of (integer, L{Setting} or L{Group})
215 '''
216 if reverse: self.reverse()
217 for i, obj in enumerate(self):
218 if isinstance(obj, Group):
219 yield (i, obj)
220 else:
221 yield (i, self.state.getSettingObj(obj))
222 if reverse: self.reverse()
223
225 '''
226 A wrapper around a value with a strict type in a L{AEState.AEState} object.
227 Provides a label and longer description of the value. Has a flag indicating
228 whether the value should be persisted or not. Defines methods for caching the
229 current value and restoring it later. Supports the observer pattern to notify
230 listeners of changes to the value.
231
232 All instance variables should be considered public readable except for those
233 prefixed with an underscore. The L{value} property should also be considered
234 public writable. L{value} is a property which will correctly notify observers
235 when changed.
236
237 Subclasses may define new value properties that perform conversions or do
238 other tasks. The data retrieved via the value property must always be the
239 data that should be persisted to disk for a setting.
240
241 @ivar state: Object in which this setting resides
242 @type state: weakref.proxy to L{AEState.AEState}
243 @ivar name: Name of the setting
244 @type name: string
245 @ivar default: Default value of the setting
246 @type default: object
247 @ivar label: Label of the setting
248 @type label: string
249 @ivar description: Extended description of the setting
250 @type description: string
251 @ivar persist: Should this setting value be persisted to disk?
252 @type persist: boolean
253 @ivar _value: Arbitrary protected value of this setting.
254 @type _value: object
255 @ivar _cached: Cached value from the last L{save} invocation
256 @type _cached: object
257 @ivar _observers: Callables to notify on a value change mapped to their
258 arguments
259 @type _observers: dictionary
260 '''
261 - def __init__(self, state, name, default, label, description, persist):
262 '''
263 Initializes all instance variables.
264 '''
265 self.state = weakref.proxy(state)
266 self.name = name
267 self.default = default
268 self.label = label
269 self.description = description
270 self.persist = persist
271 self._value = default
272 self._cached = None
273 self._observers = {}
274
275
276
277 self.addObserver(self.state._makeDirty)
278
280 '''
281 Updates this setting with the default value and current value of another
282 setting of the same kind.
283
284 @param setting: Setting of the same kind as this one
285 @type setting: L{Setting}
286 '''
287 self.default = setting.default
288 self.value = setting.value
289
291 '''
292 Makes a shallow copy of this object.
293
294 @return: Shallow copy of this setting
295 @rtype: L{Setting}
296 '''
297 return copy.copy(self)
298
300 '''
301 Gets the value.
302
303 @return: Current value of the setting
304 @rtype: object
305 '''
306 return self._value
307
309 '''
310 Sets the value and notifies all observers only if the new value is
311 different from the old.
312
313 @param val: New value to store in the setting
314 @type val: object
315 '''
316 if val != self._value:
317 self._value = val
318 self._notify()
319
320 value = property(_getValue, _setValue)
321
323 '''
324 Notifies all observers of a change in value. If an observer is dead, as
325 indicated by a ReferenceError, removes it from the list of observers.
326 '''
327 dead = []
328 for ob, args in self._observers.items():
329 try:
330 ob(self.state, self, *args)
331 except ReferenceError:
332 dead.append(ob)
333
334 map(self._observers.pop, dead)
335
337 '''
338 Stores a weak reference to the observer to be notified when the value
339 of this setting changes. Bound methods B{will} work as observers.
340
341 Additional arguments will be passed to the observer on notification. Weak
342 reference proxies to arguments will be used whenever possible to avoid
343 inhibiting garbage collection.
344
345 @note: Because weak references are used, a reference may be dead when it
346 is later delivered to an observer. The observer may safely ignore this
347 error as it will be caught by the L{_notify} method. Notification will
348 cease immediately as soon as the first ReferenceError is encountered.
349 @param ob: The observer
350 @type ob: callable
351 '''
352 weak_args = []
353 for a in args:
354 try:
355 weak_args.append(weakref.proxy(a))
356 except TypeError:
357 weak_args.append(a)
358 self._observers[Proxy(ob)] = weak_args
359
361 '''
362 Removes a weak reference to the observer. Pass a strong reference to the
363 observer to remove, not a weak reference.
364
365 @param ob: The observer
366 @type ob: callable
367 @raise KeyError: When the given callable is not an observer of this
368 setting
369 '''
370 del self._observers[Proxy(ob)]
371
373 '''
374 Remove all observers.
375 '''
376 self._observers = []
377
379 '''
380 Caches the current value for later restoration. Only one value may be
381 cached at a time. It's a box, not a stack.
382 '''
383 self._cached = self._value
384
386 '''
387 Restores the cached value. Sends notification only if the value in the
388 cache is different from the current value.
389 '''
390 if self._cached is not None:
391 if self._value != self._cached:
392
393
394 self.value = self._cached
395 self.cached = None
396
398 '''
399 Gets the current L{_value} as the data to serialize. This default
400 implementation bypasses the property fget method.
401
402 @return: Data value to serialize
403 @rtype: object
404 '''
405 return self._value
406
408 '''
409 Sets the current L{_value} from data that was unserialized. This default
410 implementation bypasses the property fset method.
411
412 @param val: Data value to store
413 @type val: object
414 '''
415 self._value = val
416
418 '''Represents a boolean setting.'''
420 '''
421 Stores the given value as a boolean.
422
423 @param val: New value
424 @type val: boolean
425 @raise TypeError: When the value cannot be converted to a boolean
426 '''
427 Setting._setValue(self, bool(val))
428
429 value = property(Setting._getValue, _setValue)
430
432 '''Represents a string setting.'''
434 '''
435 Stores the given value as a string.
436
437 @param val: New value
438 @type val: string
439 @raise TypeError: When the value cannot be converted to a string
440 '''
441 Setting._setValue(self, str(val))
442
443 value = property(Setting._getValue, _setValue)
444
446 '''
447 Represents a string filename setting.
448
449 @ivar relative_path: Absolute path to which '.' or a filename with no path
450 refers (i.e. the root of the starting filename path)
451 @type relative_path: string
452 '''
453 - def __init__(self, state, name, default, label, description, persist,
454 relative_path):
457
459 '''
460 Updates the relative path in addition to everything done in the parent
461 class method.
462
463 @param setting: Setting of the same kind as this one
464 @type setting: L{FilenameSetting}
465 '''
466 super(FilenameSetting, self).update(setting)
467 self.relative_path = setting.relative_path
468
470 '''
471 Gets the filename as a string.
472
473 @return: Current filename value
474 @rtype: string
475 '''
476 val = Setting._getValue(self)
477 if not os.path.isfile(val):
478 val = os.path.join(self.relative_path, os.path.basename(val))
479 return val
480
482 '''
483 Stores the given value as a string.
484
485 @param val: New value
486 @type val: string
487 @raise TypeError: When the value cannot be converted to a string
488 '''
489 val = str(val)
490 if os.path.dirname(val) == self.relative_path:
491 new = os.path.basename(val)
492 StringSetting._setValue(self, val)
493
494 value = property(_getValue, _setValue)
495
497 '''
498 Represents a numeric setting where the bounds are arbitrarily chosen.
499
500 @ivar min: Minimum value in the range
501 @type min: number
502 @ivar max: Maximum value in the range
503 @type max: number
504 @ivar precision: Number of decimal places
505 @type precision: integer
506 '''
507 - def __init__(self, state, name, default, label, description, persist, min,
508 max, precision):
513
515 '''
516 Updates the min, max, and precision in addition to everything done in the
517 parent class method.
518
519 @param setting: Setting of the same kind as this one
520 @type setting: L{NumericSetting}
521 '''
522 super(NumericSetting, self).update(setting)
523 self.min = setting.min
524 self.max = setting.max
525 self.precision = setting.precision
526
528 '''
529 Snaps values to the specific range and precision.
530
531 @param val: Value to bound
532 @type val: numeric
533 @param min: Minimum of range
534 @type min: numeric
535 @param max: Maximum of range
536 @type max: numeric
537 @param prec: Number of decimal places of precision
538 @type prec: integer
539 @return: Number snapped to the appropriate range and rounded or truncated
540 to the proper precision
541 @rtype: numeric
542 @raise TypeError: When the value is no a numeric
543 '''
544
545 if val > max:
546 val = max
547 elif val < min:
548 val = min
549
550 if prec == 0:
551 val = int(val)
552 else:
553 val = round(val, prec)
554 return val
555
557 '''
558 Stores the given value as an integer or float with the given precision.
559 Uses round to store floats and int to store integers.
560
561 @param val: New value to store
562 @type val: numeric
563 @raise TypeError: When the value is not a numeric
564 '''
565 Setting._setValue(self,self._fixBounds(val, self.min, self.max,
566 self.precision))
567
568 value = property(Setting._getValue, _setValue)
569
571 '''
572 Returns an object that proxies as a view for the value, min, max, and
573 precision for this object. Having this separate view permits other ranges
574 to override those values with their own conversions (e.g. percent, relative
575 ranges).
576
577 @return: A raw view of this range
578 @rtype: L{RawRangeView}
579 '''
580 return RawRangeView(self)
581
583 '''
584 Represents a numeric setting where the bounds are critical.
585 '''
586 pass
587
589 '''
590 Represents a numeric setting that should be scaled for presentation to a
591 user as a percentage between 0% and 100%. The value property of this setting
592 returns the raw value while the percent property returns the raw value
593 converted to a percentage.
594 '''
596 '''
597 Returns a view that converts raw values to percentages.
598
599 @return: A percent range view of this range
600 @rtype: L{PercentRangeView}
601 '''
602 return PercentRangeView(self)
603
605 '''
606 Represents a one of many choice setting where the labels to be displayed to
607 a user and the values are the same.
608
609 @ivar values: Collection of choice values
610 @type values: list
611 '''
612 - def __init__(self, state, name, default, label, description, persist,
613 choices):
616
618 '''
619 Add a new choice possibility.
620
621 @param choice: New option
622 @type choice: string
623 '''
624 self.values.append(choice)
625
627 '''
628 Updates the choices in addition to everything done in the parent class
629 method.
630
631 @param setting: Setting of the same kind as this one
632 @type setting: L{ChoiceSetting}
633 '''
634 super(ChoiceSetting, self).update(setting)
635 self.values = setting.values
636
638 '''
639 Stores the given choice value as the active selection. If the value is not
640 one of the available choices, defaults to the first choice.
641
642 @param val: New value
643 @type val: object
644 '''
645 if val not in self.values:
646 val = self.default
647 Setting._setValue(self, val)
648
649 value = property(Setting._getValue, _setValue)
650
652 '''
653 Does not bypass the fset property method so that when data is restored, all
654 invalid values are set back to the default.
655
656 @param val: Data value to store
657 @type val: object
658 '''
659 self.value = val
660
662 '''
663 Represents a one of many choice setting where text labels describe an
664 arbitrary value. The labels collection is intended to be presented to the
665 user while the values collection is intended to stored values to be used
666 internally only.
667
668 @ivar labels: Collection of choice labels
669 @type labels: list
670 '''
671 - def __init__(self, state, name, default, label, description, persist,
672 choices):
673
674 self.labels = choices.keys()
675 self.labels.sort()
676 ChoiceSetting.__init__(self, state, name, default, label, description,
677 persist, [choices[key] for key in self.labels])
678
680 '''
681 Gets the label for a given choice.
682
683 @param choice: Value for which to retrieve the label
684 @type choice: object
685 @raise ValueError: When L{choice} is not a valid value
686 '''
687 return self.labels[self.values.index(choice)]
688
690 '''
691 Add a new choice possibility with the given label.
692
693 @param label: Human readable name
694 @type label: string
695 @param choice: New option
696 @type choice: object
697 '''
698 super(EnumSetting, self).addChoice(choice)
699 self.labels.append(label)
700
702 '''
703 Updates the labels in addition to everything done in the parent class
704 method.
705
706 @param setting: Setting of the same kind as this one
707 @type setting: L{EnumSetting}
708 '''
709 super(EnumSetting, self).update(setting)
710 self.labels = setting.labels
711
713 '''
714 Represents a color choice setting, with our without an alpha channel. The
715 value must be a collection with 3 or 4 values in it representing RGBA.
716 '''
718 '''
719 Stores the given color and (optionally) alpha information.
720
721 @param val: New value
722 @type val: collection
723 @raise TypeError: When the value is not a 3 or 4 element collection
724 '''
725 if len(val) != 3 and len(val) != 4:
726 raise TypeError
727 Setting._setValue(self, val)
728
729 value = property(Setting._getValue, _setValue)
730
732 '''
733 Defines a numeric setting that specifies a value relative to another one. If
734 the parent style is None, acts just like a regular
735 L{AEState.Setting.NumericSetting}.
736
737 The relative value is persisted to disk for settings that have an equivalent
738 in the parent style. The absolute value is persisted otherwise.
739
740 @ivar parent: Parent style of the stored style, None if no parent
741 @type parent: L{AEOutput.Style}
742 @ivar rel_min: Relative minimum based off of the true -(max - min)
743 @type rel_min: numeric
744 @ivar rel_max: Relative maximum bassed off of the true max - min
745 @type rel_max: numeric
746 '''
747 - def __init__(self, style, name, default, label, description,
748 persist, min, max, precision):
758
760 '''
761 Updates the relative min and max in addition to everything done in the
762 parent class method.
763
764 @param setting: Setting of the same kind as this one
765 @type setting: L{RelNumericSetting}
766 '''
767 super(RelNumericSetting, self).update(setting)
768 self.rel_min = setting.rel_min
769 self.rel_max = setting.rel_max
770
772 '''
773 Gets the current absolute value.
774
775 @return: Relative value
776 @rtype: numeric
777 '''
778
779 val = NumericSetting._getValue(self)
780 if self.parent is None:
781
782 return val
783 else:
784
785 val += self.parent.getSettingVal(self.name)
786 return self._fixBounds(val, self.min, self.max, self.precision)
787
789 '''
790 Stores the given absolute value as an integer or float with the given
791 precision. Uses round to store floats and int to store integers.
792
793 @param val: New absolute value to store
794 @type val: numeric
795 @raise TypeError: When the value is not a numeric
796 '''
797 if self.parent is None:
798
799 val = NumericSetting._setValue(self, val)
800 else:
801
802 val -= self.parent.getSettingVal(self.name)
803 self._value = self._fixBounds(val, self.rel_min, self.rel_max,
804 self.precision)
805
806 value = property(_getValue, _setValue)
807
809 '''
810 Gets a relative range view that returns the proper min and max values
811 depending on if the value is relative or not.
812
813 @return: A relative view of this range
814 @rtype: L{RelRawRangeView}
815 '''
816 return RelRawRangeView(self)
817
819 '''
820 Defines a range setting that specifies a value relative to another one. If
821 the parent style is None, acts just like a regular
822 L{RangeSetting}.
823 '''
824 pass
825
827 '''
828 Defines a percent setting that specifies a value relative to another one. If
829 the parent style is None, acts just like a regular
830 L{PercentRangeSetting}.
831
832 The relative percentage of this setting is additive,
833 '''
835 '''
836 Gets a relative range view that returns the proper min and max values
837 depending on if the value is relative or not.
838
839 @return: A relative percent view of this range
840 @rtype: L{RelPercentRangeView}
841 '''
842 return RelPercentRangeView(self)
843