Mercurial > fife-parpg
comparison engine/python/fife/extensions/fife_settings.py @ 499:3dff106b945b
Combined the settings extension with the editor settings module. It is now a little more robust. Note that the settings file format has changed.
All demos and tools now use the new settings extension.
author | prock@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Fri, 14 May 2010 17:37:42 +0000 |
parents | 559a26347730 |
children | ee65aa323457 |
comparison
equal
deleted
inserted
replaced
498:5ff83f209333 | 499:3dff106b945b |
---|---|
85 Usage:: | 85 Usage:: |
86 from fife.extensions.fife_settings import Setting | 86 from fife.extensions.fife_settings import Setting |
87 settings = Setting(app_name="myapp") | 87 settings = Setting(app_name="myapp") |
88 screen_width = settings.readSetting("ScreenWidth") | 88 screen_width = settings.readSetting("ScreenWidth") |
89 """ | 89 """ |
90 | |
90 def __init__(self, app_name="", settings_file="", settings_gui_xml=""): | 91 def __init__(self, app_name="", settings_file="", settings_gui_xml=""): |
91 """ | 92 """ |
92 Initializes the Setting object. | 93 Initializes the Setting object. |
93 | 94 |
94 @param app_name: The applications name. If this parameter is provided | 95 @param app_name: The applications name. If this parameter is provided |
122 | 123 |
123 | 124 |
124 if not os.path.exists(os.path.join(self._appdata, self._settings_file)): | 125 if not os.path.exists(os.path.join(self._appdata, self._settings_file)): |
125 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) | 126 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) |
126 | 127 |
128 self._tree = ET.parse(os.path.join(self._appdata, self._settings_file)) | |
129 self._root_element = self._tree.getroot() | |
130 self.validateTree() | |
131 | |
132 def validateTree(self): | |
133 """ Iterates the settings tree and prints warning when an invalid tag is found """ | |
134 for c in self._root_element.getchildren(): | |
135 if c.tag != "Module": | |
136 print "Invalid tag in settings.xml. Expected Module, got: ", c.tag | |
137 elif c.get("name", "") == "": | |
138 print "Invalid tag in settings.xml. Module name is empty." | |
139 else: | |
140 for e in c.getchildren(): | |
141 if e.tag != "Setting": | |
142 print "Invalid tag in settings.xml in module: ",c.tag, | |
143 print ". Expected Setting, got: ", e.tag | |
144 elif c.get("name", "") == "": | |
145 print "Invalid tag in settings.xml in module: ",c.tag, | |
146 print ". Setting name is empty", e.tag | |
147 | |
148 def getModuleTree(self, module): | |
149 """ | |
150 Returns a module element from the settings tree. If no module with the specified | |
151 name exists, a new element will be created. | |
152 | |
153 @param module: The module to get from the settings tree | |
154 @type module: C{string} | |
155 """ | |
156 if not isinstance(module, str) and not isinstance(module, unicode): | |
157 raise AttributeError("Settings:getModuleTree: Invalid type for module argument.") | |
158 | |
159 for c in self._root_element.getchildren(): | |
160 if c.tag == "Module" and c.get("name", "") == module: | |
161 return c | |
162 | |
163 # Create module | |
164 return ET.SubElement(self._root_element, "Module", {"name":module}) | |
165 | |
166 def get(self, module, name, defaultValue=None): | |
167 """ Gets the value of a specified setting | |
168 | |
169 @param module: Name of the module to get the setting from | |
170 @param name: Setting name | |
171 @param defaultValue: Specifies the default value to return if the setting is not found | |
172 @type defaultValue: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} | |
173 """ | |
174 if not isinstance(name, str) and not isinstance(name, unicode): | |
175 raise AttributeError("Settings:get: Invalid type for name argument.") | |
176 | |
177 moduleTree = self.getModuleTree(module) | |
178 element = None | |
179 for e in moduleTree.getchildren(): | |
180 if e.tag == "Setting" and e.get("name", "") == name: | |
181 element = e | |
182 break | |
183 else: | |
184 return defaultValue | |
185 | |
186 e_value = element.text | |
187 e_strip = element.get("strip", "1").strip().lower() | |
188 e_type = str(element.get("type", "str")).strip() | |
189 | |
190 if e_value is None: | |
191 return defaultValue | |
192 | |
193 # Strip value | |
194 if e_strip == "" or e_strip == "false" or e_strip == "no" or e_strip == "0": | |
195 e_strip = False | |
196 else: e_strip = True | |
197 | |
198 if e_type == "str" or e_type == "unicode": | |
199 if e_strip: e_value = e_value.strip() | |
200 else: | |
201 e_value = e_value.strip() | |
202 | |
203 # Return value | |
204 if e_type == 'int': | |
205 return int(e_value) | |
206 elif e_type == 'float': | |
207 return float(e_value) | |
208 elif e_type == 'bool': | |
209 e_value = e_value.lower() | |
210 if e_value == "" or e_value == "false" or e_value == "no" or e_value == "0": | |
211 return False | |
212 else: | |
213 return True | |
214 elif e_type == 'str': | |
215 return str(e_value) | |
216 elif e_type == 'unicode': | |
217 return unicode(e_value) | |
218 elif e_type == 'list': | |
219 return self._deserializeList(e_value) | |
220 elif e_type == 'dict': | |
221 return self._deserializeDict(e_value) | |
222 | |
223 def set(self, module, name, value, extra_attrs={}): | |
224 """ | |
225 Sets a setting to specified value. | |
226 | |
227 @param module: Module where the setting should be set | |
228 @param name: Name of setting | |
229 @param value: Value to assign to setting | |
230 @type value: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} | |
231 @param extra_attrs: Extra attributes to be stored in the XML-file | |
232 @type extra_attrs: C{dict} | |
233 """ | |
234 if not isinstance(name, str) and not isinstance(name, unicode): | |
235 raise AttributeError("Settings:set: Invalid type for name argument.") | |
236 | |
237 moduleTree = self.getModuleTree(module) | |
238 e_type = "str" | |
239 | |
240 if isinstance(value, bool): # This must be before int | |
241 e_type = "bool" | |
242 value = str(value) | |
243 elif isinstance(value, int): | |
244 e_type = "int" | |
245 value = str(value) | |
246 elif isinstance(value, float): | |
247 e_type = "float" | |
248 value = str(value) | |
249 elif isinstance(value, unicode): | |
250 e_type = "unicode" | |
251 value = unicode(value) | |
252 elif isinstance(value, list): | |
253 e_type = "list" | |
254 value = self._serializeList(value) | |
255 elif isinstance(value, dict): | |
256 e_type = "dict" | |
257 value = self._serializeDict(value) | |
258 else: | |
259 e_type = "str" | |
260 value = str(value) | |
261 | |
262 for e in moduleTree.getchildren(): | |
263 if e.tag != "Setting": continue | |
264 if e.get("name", "") == name: | |
265 e.text = value | |
266 break | |
267 else: | |
268 attrs = {"name":name, "type":e_type} | |
269 for k in extra_attrs: | |
270 if k not in attrs: | |
271 attrs[k] = extra_args[k] | |
272 elm = ET.SubElement(moduleTree, "Setting", attrs) | |
273 elm.text = value | |
274 | |
275 def saveSettings(self): | |
276 """ Writes the settings to the settings file """ | |
277 self._indent(self._root_element) | |
278 self._tree.write(os.path.join(self._appdata, self._settings_file), 'UTF-8') | |
279 | |
280 def _indent(self, elem, level=0): | |
281 """ | |
282 Adds whitespace, so the resulting XML-file is properly indented. | |
283 Shamelessly stolen from http://effbot.org/zone/element-lib.htm | |
284 """ | |
285 i = "\n" + level*" " | |
286 if len(elem): | |
287 if not elem.text or not elem.text.strip(): | |
288 elem.text = i + " " | |
289 if not elem.tail or not elem.tail.strip(): | |
290 elem.tail = i | |
291 for elem in elem: | |
292 self._indent(elem, level+1) | |
293 if not elem.tail or not elem.tail.strip(): | |
294 elem.tail = i | |
295 else: | |
296 if level and (not elem.tail or not elem.tail.strip()): | |
297 elem.tail = i | |
298 | |
299 # FIXME: | |
300 # These serialization functions are not reliable at all | |
301 # This will only serialize the first level of a dict or list | |
302 # It will not check the types nor the content for conflicts. | |
303 # Perhaps we should add a small serialization library? | |
304 def _serializeList(self, list): | |
305 """ Serializes a list, so it can be stored in a text file """ | |
306 return " ; ".join(list) | |
307 | |
308 def _deserializeList(self, string): | |
309 """ Deserializes a list back into a list object """ | |
310 return string.split(" ; ") | |
311 | |
312 def _serializeDict(self, dict): | |
313 """ Serializes a list, so it can be stored in a text file """ | |
314 serial = "" | |
315 for key in dict: | |
316 value = dict[key] | |
317 if serial != "": serial += " ; " | |
318 serial += str(key)+" : "+str(value) | |
319 | |
320 return serial | |
321 | |
322 def _deserializeDict(self, serial): | |
323 """ Deserializes a list back into a dict object """ | |
324 dict = {} | |
325 items = serial.split(" ; ") | |
326 for i in items: | |
327 kv_pair = i.split(" : ") | |
328 dict[kv_pair[0]] = kv_pair[1] | |
329 return dict | |
127 | 330 |
128 def onOptionsPress(self): | 331 def onOptionsPress(self): |
129 """ | 332 """ |
130 Opends the options dialog box. Usually you would bind this to a button. | 333 Opens the options dialog box. Usually you would bind this to a button. |
131 """ | 334 """ |
132 self.changesRequireRestart = False | 335 self.changesRequireRestart = False |
133 self.isSetToDefault = False | 336 self.isSetToDefault = False |
134 self.Resolutions = ['640x480', '800x600', '1024x768', '1280x800', '1440x900'] | 337 self.Resolutions = ['640x480', '800x600', '1024x768', '1280x800', '1440x900'] |
135 if not hasattr(self, 'OptionsDlg'): | 338 if not hasattr(self, 'OptionsDlg'): |
139 self.OptionsDlg.distributeInitialData({ | 342 self.OptionsDlg.distributeInitialData({ |
140 'screen_resolution' : self.Resolutions, | 343 'screen_resolution' : self.Resolutions, |
141 'render_backend' : ['OpenGL', 'SDL'] | 344 'render_backend' : ['OpenGL', 'SDL'] |
142 }) | 345 }) |
143 self.OptionsDlg.distributeData({ | 346 self.OptionsDlg.distributeData({ |
144 'screen_resolution' : self.Resolutions.index(str(self.readSetting("ScreenWidth")) + 'x' + str(self.readSetting("ScreenHeight"))), | 347 'screen_resolution' : self.Resolutions.index(str(self.get("FIFE", "ScreenWidth")) + 'x' + str(self.get("FIFE", "ScreenHeight"))), |
145 'render_backend' : 0 if str(self.readSetting("RenderBackend")) == "OpenGL" else 1, | 348 'render_backend' : 0 if self.get("FIFE", "RenderBackend") == "OpenGL" else 1, |
146 'enable_fullscreen' : int(self.readSetting("FullScreen")), | 349 'enable_fullscreen' : self.get("FIFE", "FullScreen"), |
147 'enable_sound' : int(self.readSetting("PlaySounds")) | 350 'enable_sound' : self.get("FIFE", "PlaySounds") |
148 }) | 351 }) |
149 self.OptionsDlg.mapEvents({ | 352 self.OptionsDlg.mapEvents({ |
150 'okButton' : self.saveSettings, | 353 'okButton' : self.applySettings, |
151 'cancelButton' : self.OptionsDlg.hide, | 354 'cancelButton' : self.OptionsDlg.hide, |
152 'defaultButton' : self.setDefaults | 355 'defaultButton' : self.setDefaults |
153 }) | 356 }) |
154 self.OptionsDlg.show() | 357 self.OptionsDlg.show() |
155 | 358 |
156 def setDefaults(self): | 359 def applySettings(self): |
157 """ | |
158 Overwrites the setting file with the default settings-dist.xml file. | |
159 """ | |
160 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) | |
161 self.isSetToDefault = True | |
162 self.changesRequireRestart = True | |
163 | |
164 def readSetting(self, name, type='int', strip=True, text=False): | |
165 if not hasattr(self, 'tree'): | |
166 self.tree = ET.parse(os.path.join(self._appdata, self._settings_file)) | |
167 self.root_element = self.tree.getroot() | |
168 element = self.root_element.find(name) | |
169 if element is not None: | |
170 element_value = element.text | |
171 if element_value is None: | |
172 if type == 'int': | |
173 return 0 | |
174 elif type == 'list': | |
175 list = [] | |
176 return list | |
177 else: | |
178 if type == 'int': | |
179 return element_value.strip() if strip else element_value | |
180 elif type == 'list': | |
181 list = [] | |
182 list_s = [] | |
183 list = str(element_value.strip()).split(";") | |
184 for item in list: | |
185 item = item.strip() | |
186 if text: | |
187 item = item.replace('\\n', '\n') | |
188 list_s.append(item) | |
189 return list_s | |
190 elif type == 'bool': | |
191 return False if element_value.strip() == 'False' else True | |
192 else: | |
193 print 'Setting,', name, 'does not exist!' | |
194 | |
195 def setSetting(self, name, value): | |
196 element = self.root_element.find(name) | |
197 if element is not None: | |
198 if value is not element.text: | |
199 element.text = str(value) | |
200 else: | |
201 print 'Setting,', name, 'does not exist!' | |
202 | |
203 def saveSettings(self): | |
204 """ | 360 """ |
205 Writes the settings file. If a change requires a restart of the engine | 361 Writes the settings file. If a change requires a restart of the engine |
206 it notifies you with a small dialog box. | 362 it notifies you with a small dialog box. |
207 """ | 363 """ |
208 screen_resolution, render_backend, enable_fullscreen, enable_sound = self.OptionsDlg.collectData('screen_resolution', 'render_backend', 'enable_fullscreen', 'enable_sound') | 364 screen_resolution, render_backend, enable_fullscreen, enable_sound = self.OptionsDlg.collectData('screen_resolution', 'render_backend', 'enable_fullscreen', 'enable_sound') |
209 render_backend = 'OpenGL' if render_backend is 0 else 'SDL' | 365 render_backend = 'OpenGL' if render_backend is 0 else 'SDL' |
210 if render_backend != str(self.readSetting("RenderBackend")): | 366 if render_backend != self.get("FIFE", "RenderBackend"): |
211 self.setSetting('RenderBackend', render_backend) | 367 self.set("FIFE", 'RenderBackend', render_backend) |
212 self.changesRequireRestart = True | 368 self.changesRequireRestart = True |
213 if int(enable_fullscreen) != int(self.readSetting("FullScreen")): | 369 if int(enable_fullscreen) != int(self.get("FIFE", "FullScreen")): |
214 self.setSetting('FullScreen', int(enable_fullscreen)) | 370 self.set("FIFE", 'FullScreen', int(enable_fullscreen)) |
215 self.changesRequireRestart = True | 371 self.changesRequireRestart = True |
216 if int(enable_sound) != int(self.readSetting("PlaySounds")): | 372 if int(enable_sound) != int(self.get("FIFE", "PlaySounds")): |
217 self.setSetting('PlaySounds', int(enable_sound)) | 373 self.set("FIFE", 'PlaySounds', int(enable_sound)) |
218 self.changesRequireRestart = True | 374 self.changesRequireRestart = True |
219 if screen_resolution != self.Resolutions.index(str(self.readSetting("ScreenWidth")) + 'x' + str(self.readSetting("ScreenHeight"))): | 375 if screen_resolution != self.Resolutions.index(str(self.get("FIFE", "ScreenWidth")) + 'x' + str(self.get("FIFE", "ScreenHeight"))): |
220 self.setSetting('ScreenWidth', int(self.Resolutions[screen_resolution].partition('x')[0])) | 376 self.set("FIFE", 'ScreenWidth', int(self.Resolutions[screen_resolution].partition('x')[0])) |
221 self.setSetting('ScreenHeight', int(self.Resolutions[screen_resolution].partition('x')[2])) | 377 self.set("FIFE", 'ScreenHeight', int(self.Resolutions[screen_resolution].partition('x')[2])) |
222 self.changesRequireRestart = True | 378 self.changesRequireRestart = True |
223 | 379 |
224 if not self.isSetToDefault: | 380 if not self.isSetToDefault: |
225 self.tree.write(os.path.join(self._appdata, self._settings_file)) | 381 self.saveSettings() |
382 | |
226 self.OptionsDlg.hide() | 383 self.OptionsDlg.hide() |
227 if self.changesRequireRestart: | 384 if self.changesRequireRestart: |
228 RestartDlg = pychan.loadXML(StringIO(CHANGES_REQUIRE_RESTART)) | 385 RestartDlg = pychan.loadXML(StringIO(CHANGES_REQUIRE_RESTART)) |
229 RestartDlg.mapEvents({ 'closeButton' : RestartDlg.hide }) | 386 RestartDlg.mapEvents({ 'closeButton' : RestartDlg.hide }) |
230 RestartDlg.show() | 387 RestartDlg.show() |
388 | |
389 def setDefaults(self): | |
390 """ | |
391 Overwrites the setting file with the default settings-dist.xml file. | |
392 """ | |
393 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) | |
394 self.isSetToDefault = True | |
395 self.changesRequireRestart = True |