Changed 'Dismiss' to 'Close' (Chris Shaffer).
[rox-lib.git] / python / rox / options.py
blob736c68fd19aaeb1593a47eee88167ceaad8c69e9
1 """
2 To use the Options system:
4 1. Create an OptionGroup:
5 options = OptionGroup('MyProg', 'Options')
6 You can also use the handy rox.setup_app_options() in most applications.
8 2. Create the options:
9 colour = Option('colour', 'red', options)
10 size = Option('size', 3, options)
12 3. Register any callbacks (notification of options changing):
13 def my_callback():
14 if colour.has_changed:
15 print "The colour is now", colour.value
16 options.add_notify(my_callback)
18 4. Notify any changes from defaults:
19 options.notify()
21 See OptionsBox for editing options. Do not change the value of options
22 yourself.
23 """
25 from __future__ import generators
27 import choices
28 import rox
30 class Option:
31 """An Option stores a single value. Every option is part of exactly one OptionGroup.
33 The read-only attributes value and int_value can be used to get the current setting
34 for the Option. int_value will be -1 if the value is not a valid integer.
36 The has_changed attribute is used during notify() calls to indicate whether this
37 Option's value has changed since the last notify (or option creation).
38 You may set has_changed = 1 right after creating an option if you want to force
39 notification the first time even if the default is used.
40 """
41 def __init__(self, name, value, group = None):
42 """Create a new option with this name and default value.
43 Add to 'group', or to rox.app_options if no group is given.
44 The value cannot be used until the first notify() call to
45 the group."""
46 if not group:
47 assert rox.app_options
48 group = rox.app_options
49 self.name = name
50 self.has_changed = 0 # ... since last notify/default
51 self.default_value = str(value)
52 self.group = group
53 self.value = None
54 self.int_value = None
56 self.group._register(self)
58 def _set(self, value):
59 if self.value != value:
60 self.value = str(value)
61 self.has_changed = 1
62 try:
63 if self.value == 'True':
64 self.int_value = 1
65 elif self.value == 'False':
66 self.int_value = 0
67 else:
68 self.int_value = int(float(self.value))
69 except:
70 self.int_value = -1
72 def _to_xml(self, parent):
73 doc = parent.ownerDocument
74 node = doc.createElement('Option')
75 node.setAttribute('name', self.name)
76 node.appendChild(doc.createTextNode(self.value))
77 parent.appendChild(node)
79 def __str__(self):
80 return "<Option %s=%s>" % (self.name, self.value)
82 class OptionGroup:
83 def __init__(self, program, leaf):
84 "program/leaf is a Choices pair for the saved options."
85 self.program = program
86 self.leaf = leaf
87 self.pending = {} # Loaded, but not registered
88 self.options = {} # Name -> Option
89 self.callbacks = []
90 self.too_late_for_registrations = 0
92 path = choices.load(program, leaf)
93 if not path:
94 return
96 try:
97 from xml.dom import Node, minidom
99 doc = minidom.parse(path)
101 root = doc.documentElement
102 assert root.localName == 'Options'
103 for o in root.childNodes:
104 if o.nodeType != Node.ELEMENT_NODE:
105 continue
106 if o.localName != 'Option':
107 print "Warning: Non Option element", o
108 continue
109 name = o.getAttribute('name')
110 if o.childNodes:
111 value = o.childNodes[0].nodeValue
112 else:
113 value = ''
114 self.pending[name] = value
115 except:
116 rox.report_exception()
118 def _register(self, option):
119 """Called by Option.__init__."""
120 assert option.name not in self.options
121 assert not self.too_late_for_registrations
123 name = option.name
125 self.options[name] = option
127 if name in self.pending:
128 option._set(self.pending[name])
129 del self.pending[name]
131 def save(self):
132 """Save all option values. Usually called by OptionsBox()."""
133 assert self.too_late_for_registrations
135 path = choices.save(self.program, self.leaf)
136 if not path:
137 return # Saving is disabled
139 from xml.dom.minidom import Document
140 doc = Document()
141 root = doc.createElement('Options')
142 doc.appendChild(root)
144 for option in self:
145 option._to_xml(root)
147 file = open(path, 'w')
148 doc.writexml(file)
149 file.close()
151 def add_notify(self, callback):
152 "Call callback() after one or more options have changed value."
153 assert callback not in self.callbacks
155 self.callbacks.append(callback)
157 def remove_notify(self, callback):
158 """Remove a callback added with add_notify()."""
159 self.callbacks.remove(callback)
161 def notify(self):
162 """Call this after creating any new options or changing their values."""
163 if not self.too_late_for_registrations:
164 self.too_late_for_registrations = 1
165 if self.pending:
166 print "Warning: Some options loaded but unused:"
167 for (key, value) in self.pending.iteritems():
168 print "%s=%s" % (key, value)
169 for o in self:
170 if o.value is None:
171 o._set(o.default_value)
172 map(apply, self.callbacks)
173 for option in self:
174 option.has_changed = 0
176 def __iter__(self):
177 return self.options.itervalues()