Mercurial > fife-parpg
comparison engine/python/fife/extensions/fife_settings.py @ 567:9152ed2b5bb8
Created SimpleXMLSerializer which makes loading and saving variables to an XML file simple. In the process I removed the the XML code from the Settings class. It now uses SimpleXMLSerializer to load and save settings.
I have also updated the RPG demo to use the SimpleXMLSerializer for loading and saving game specific data.
author | prock@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Mon, 28 Jun 2010 18:41:23 +0000 |
parents | 4cb5d0ed33a9 |
children | 466d76db9701 |
comparison
equal
deleted
inserted
replaced
566:90d369c788c0 | 567:9152ed2b5bb8 |
---|---|
36 import os | 36 import os |
37 from StringIO import StringIO | 37 from StringIO import StringIO |
38 | 38 |
39 from fife.extensions import pychan | 39 from fife.extensions import pychan |
40 from fife.extensions.fife_utils import getUserDataDirectory | 40 from fife.extensions.fife_utils import getUserDataDirectory |
41 from fife.extensions.serializers.simplexml import SimpleXMLSerializer | |
42 | |
41 try: | 43 try: |
42 import xml.etree.cElementTree as ET | 44 import xml.etree.cElementTree as ET |
43 except: | 45 except: |
44 import xml.etree.ElementTree as ET | 46 import xml.etree.ElementTree as ET |
45 | |
46 | 47 |
47 SETTINGS_GUI_XML="""\ | 48 SETTINGS_GUI_XML="""\ |
48 <Window name="Settings" title="Settings"> | 49 <Window name="Settings" title="Settings"> |
49 <Label text="Settings menu!" /> | 50 <Label text="Settings menu!" /> |
50 <HBox> | 51 <HBox> |
76 <Button name="closeButton" text="Ok" /> | 77 <Button name="closeButton" text="Ok" /> |
77 </HBox> | 78 </HBox> |
78 </Window> | 79 </Window> |
79 """ | 80 """ |
80 | 81 |
81 EMPTY_SETTINGS="""\ | |
82 <?xml version='1.0' encoding='UTF-8'?> | |
83 <Settings> | |
84 | |
85 </Settings> | |
86 """ | |
87 | |
88 FIFE_MODULE = "FIFE" | 82 FIFE_MODULE = "FIFE" |
89 | 83 |
90 class Setting(object): | 84 class Setting(object): |
91 """ | 85 """ |
92 This class manages loading and saving of game settings. | 86 This class manages loading and saving of game settings. |
121 self._settings_gui_xml = settings_gui_xml | 115 self._settings_gui_xml = settings_gui_xml |
122 self._changes_gui_xml = changes_gui_xml | 116 self._changes_gui_xml = changes_gui_xml |
123 | 117 |
124 # Holds SettingEntries | 118 # Holds SettingEntries |
125 self._entries = {} | 119 self._entries = {} |
120 | |
121 self._xmlserializer = None | |
126 | 122 |
127 if self._settings_file == "": | 123 if self._settings_file == "": |
128 self._settings_file = "settings.xml" | 124 self._settings_file = "settings.xml" |
129 self._appdata = getUserDataDirectory("fife", self._app_name) | 125 self._appdata = getUserDataDirectory("fife", self._app_name) |
130 else: | 126 else: |
140 | 136 |
141 | 137 |
142 if not os.path.exists(os.path.join(self._appdata, self._settings_file)): | 138 if not os.path.exists(os.path.join(self._appdata, self._settings_file)): |
143 if os.path.exists('settings-dist.xml') and copy_dist: | 139 if os.path.exists('settings-dist.xml') and copy_dist: |
144 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) | 140 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) |
145 else: | |
146 #no settings file found | |
147 tree = ET.parse(StringIO(EMPTY_SETTINGS)) | |
148 tree.write(os.path.join(self._appdata, self._settings_file), 'UTF-8') | |
149 | 141 |
150 #default settings | 142 #default settings |
151 self._resolutions = ['640x480', '800x600', '1024x768', '1280x800', '1440x900'] | 143 self._resolutions = ['640x480', '800x600', '1024x768', '1280x800', '1440x900'] |
152 self._renderbackends = ['OpenGL', 'SDL'] | 144 self._renderbackends = ['OpenGL', 'SDL'] |
153 | 145 |
154 #Used to stylize the options gui | 146 #Used to stylize the options gui |
155 self._gui_style = "default" | 147 self._gui_style = "default" |
156 | 148 |
149 #Initialize the XML serializer | |
157 self.loadSettings() | 150 self.loadSettings() |
158 | |
159 | 151 |
160 self._initDefaultSettingEntries() | 152 self._initDefaultSettingEntries() |
161 | 153 |
162 def _initDefaultSettingEntries(self): | 154 def _initDefaultSettingEntries(self): |
163 """Initializes the default fife setting entries. Not to be called from | 155 """Initializes the default fife setting entries. Not to be called from |
210 if self.get(entry.module, entry.name) is None: | 202 if self.get(entry.module, entry.name) is None: |
211 print "WARNING:", entry.module, ":", entry.name, "still not found!" | 203 print "WARNING:", entry.module, ":", entry.name, "still not found!" |
212 print "It's probably missing in settings-dist.xml as well!" | 204 print "It's probably missing in settings-dist.xml as well!" |
213 | 205 |
214 def loadSettings(self): | 206 def loadSettings(self): |
215 self._tree = ET.parse(os.path.join(self._appdata, self._settings_file)) | 207 self._xmlserializer = SimpleXMLSerializer(os.path.join(self._appdata, self._settings_file)) |
216 | 208 |
217 self._root_element = self._tree.getroot() | 209 def saveSettings(self): |
218 self.validateTree() | 210 """ Writes the settings to the settings file """ |
219 | 211 if self._xmlserializer: |
220 def setGuiStyle(self, style): | 212 self._xmlserializer.save() |
221 """ Set a custom gui style used for the option dialog. | |
222 @param style: Pychan style to be used | |
223 @type style: C{string} | |
224 """ | |
225 self._gui_style = style | |
226 | |
227 def validateTree(self): | |
228 """ Iterates the settings tree and prints warning when an invalid tag is found """ | |
229 for c in self._root_element.getchildren(): | |
230 if c.tag != "Module": | |
231 print "Invalid tag in settings.xml. Expected Module, got: ", c.tag | |
232 elif c.get("name", "") == "": | |
233 print "Invalid tag in settings.xml. Module name is empty." | |
234 else: | |
235 for e in c.getchildren(): | |
236 if e.tag != "Setting": | |
237 print "Invalid tag in settings.xml in module: ",c.tag, | |
238 print ". Expected Setting, got: ", e.tag | |
239 elif c.get("name", "") == "": | |
240 print "Invalid tag in settings.xml in module: ",c.tag, | |
241 print ". Setting name is empty", e.tag | |
242 | |
243 def getModuleTree(self, module): | |
244 """ | |
245 Returns a module element from the settings tree. If no module with the specified | |
246 name exists, a new element will be created. | |
247 | |
248 @param module: The module to get from the settings tree | |
249 @type module: C{string} | |
250 """ | |
251 if not isinstance(module, str) and not isinstance(module, unicode): | |
252 raise AttributeError("Settings:getModuleTree: Invalid type for module argument.") | |
253 | |
254 for c in self._root_element.getchildren(): | |
255 if c.tag == "Module" and c.get("name", "") == module: | |
256 return c | |
257 | |
258 # Create module | |
259 return ET.SubElement(self._root_element, "Module", {"name":module}) | |
260 | 213 |
261 def get(self, module, name, defaultValue=None): | 214 def get(self, module, name, defaultValue=None): |
262 """ Gets the value of a specified setting | 215 """ Gets the value of a specified setting |
263 | 216 |
264 @param module: Name of the module to get the setting from | 217 @param module: Name of the module to get the setting from |
265 @param name: Setting name | 218 @param name: Setting name |
266 @param defaultValue: Specifies the default value to return if the setting is not found | 219 @param defaultValue: Specifies the default value to return if the setting is not found |
267 @type defaultValue: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} | 220 @type defaultValue: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} |
268 """ | 221 """ |
269 if not isinstance(name, str) and not isinstance(name, unicode): | 222 if self._xmlserializer: |
270 raise AttributeError("Settings:get: Invalid type for name argument.") | 223 return self._xmlserializer.get(module, name, defaultValue) |
271 | |
272 moduleTree = self.getModuleTree(module) | |
273 element = None | |
274 for e in moduleTree.getchildren(): | |
275 if e.tag == "Setting" and e.get("name", "") == name: | |
276 element = e | |
277 break | |
278 else: | 224 else: |
279 return defaultValue | 225 return None |
280 | 226 |
281 e_value = element.text | |
282 e_strip = element.get("strip", "1").strip().lower() | |
283 e_type = str(element.get("type", "str")).strip() | |
284 | |
285 if e_value is None: | |
286 return defaultValue | |
287 | |
288 # Strip value | |
289 if e_strip == "" or e_strip == "false" or e_strip == "no" or e_strip == "0": | |
290 e_strip = False | |
291 else: e_strip = True | |
292 | |
293 if e_type == "str" or e_type == "unicode": | |
294 if e_strip: e_value = e_value.strip() | |
295 else: | |
296 e_value = e_value.strip() | |
297 | |
298 # Return value | |
299 if e_type == 'int': | |
300 return int(e_value) | |
301 elif e_type == 'float': | |
302 return float(e_value) | |
303 elif e_type == 'bool': | |
304 e_value = e_value.lower() | |
305 if e_value == "" or e_value == "false" or e_value == "no" or e_value == "0": | |
306 return False | |
307 else: | |
308 return True | |
309 elif e_type == 'str': | |
310 return str(e_value) | |
311 elif e_type == 'unicode': | |
312 return unicode(e_value) | |
313 elif e_type == 'list': | |
314 return self._deserializeList(e_value) | |
315 elif e_type == 'dict': | |
316 return self._deserializeDict(e_value) | |
317 | |
318 def set(self, module, name, value, extra_attrs={}): | 227 def set(self, module, name, value, extra_attrs={}): |
319 """ | 228 """ |
320 Sets a setting to specified value. | 229 Sets a setting to specified value. |
321 | 230 |
322 @param module: Module where the setting should be set | 231 @param module: Module where the setting should be set |
324 @param value: Value to assign to setting | 233 @param value: Value to assign to setting |
325 @type value: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} | 234 @type value: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} |
326 @param extra_attrs: Extra attributes to be stored in the XML-file | 235 @param extra_attrs: Extra attributes to be stored in the XML-file |
327 @type extra_attrs: C{dict} | 236 @type extra_attrs: C{dict} |
328 """ | 237 """ |
329 if not isinstance(name, str) and not isinstance(name, unicode): | 238 if self._xmlserializer: |
330 raise AttributeError("Settings:set: Invalid type for name argument.") | 239 self._xmlserializer.set(module, name, value, extra_attrs) |
331 | 240 |
332 moduleTree = self.getModuleTree(module) | 241 def setGuiStyle(self, style): |
333 e_type = "str" | 242 """ Set a custom gui style used for the option dialog. |
334 | 243 @param style: Pychan style to be used |
335 if isinstance(value, bool): # This must be before int | 244 @type style: C{string} |
336 e_type = "bool" | 245 """ |
337 value = str(value) | 246 self._gui_style = style |
338 elif isinstance(value, int): | 247 |
339 e_type = "int" | |
340 value = str(value) | |
341 elif isinstance(value, float): | |
342 e_type = "float" | |
343 value = str(value) | |
344 elif isinstance(value, unicode): | |
345 e_type = "unicode" | |
346 value = unicode(value) | |
347 elif isinstance(value, list): | |
348 e_type = "list" | |
349 value = self._serializeList(value) | |
350 elif isinstance(value, dict): | |
351 e_type = "dict" | |
352 value = self._serializeDict(value) | |
353 else: | |
354 e_type = "str" | |
355 value = str(value) | |
356 | |
357 for e in moduleTree.getchildren(): | |
358 if e.tag != "Setting": continue | |
359 if e.get("name", "") == name: | |
360 e.text = value | |
361 break | |
362 else: | |
363 attrs = {"name":name, "type":e_type} | |
364 for k in extra_attrs: | |
365 if k not in attrs: | |
366 attrs[k] = extra_args[k] | |
367 elm = ET.SubElement(moduleTree, "Setting", attrs) | |
368 elm.text = value | |
369 | |
370 def saveSettings(self): | |
371 """ Writes the settings to the settings file """ | |
372 self._indent(self._root_element) | |
373 self._tree.write(os.path.join(self._appdata, self._settings_file), 'UTF-8') | |
374 | |
375 def _indent(self, elem, level=0): | |
376 """ | |
377 Adds whitespace, so the resulting XML-file is properly indented. | |
378 Shamelessly stolen from http://effbot.org/zone/element-lib.htm | |
379 """ | |
380 i = "\n" + level*" " | |
381 if len(elem): | |
382 if not elem.text or not elem.text.strip(): | |
383 elem.text = i + " " | |
384 if not elem.tail or not elem.tail.strip(): | |
385 elem.tail = i | |
386 for elem in elem: | |
387 self._indent(elem, level+1) | |
388 if not elem.tail or not elem.tail.strip(): | |
389 elem.tail = i | |
390 else: | |
391 if level and (not elem.tail or not elem.tail.strip()): | |
392 elem.tail = i | |
393 | |
394 # FIXME: | |
395 # These serialization functions are not reliable at all | |
396 # This will only serialize the first level of a dict or list | |
397 # It will not check the types nor the content for conflicts. | |
398 # Perhaps we should add a small serialization library? | |
399 def _serializeList(self, list): | |
400 """ Serializes a list, so it can be stored in a text file """ | |
401 return " ; ".join(list) | |
402 | |
403 def _deserializeList(self, string): | |
404 """ Deserializes a list back into a list object """ | |
405 return string.split(" ; ") | |
406 | |
407 def _serializeDict(self, dict): | |
408 """ Serializes a list, so it can be stored in a text file """ | |
409 serial = "" | |
410 for key in dict: | |
411 value = dict[key] | |
412 if serial != "": serial += " ; " | |
413 serial += str(key)+" : "+str(value) | |
414 | |
415 return serial | |
416 | |
417 def _deserializeDict(self, serial): | |
418 """ Deserializes a list back into a dict object """ | |
419 dict = {} | |
420 items = serial.split(" ; ") | |
421 for i in items: | |
422 kv_pair = i.split(" : ") | |
423 dict[kv_pair[0]] = kv_pair[1] | |
424 return dict | |
425 | |
426 def onOptionsPress(self): | 248 def onOptionsPress(self): |
427 """ | 249 """ |
428 Opens the options dialog box. Usually you would bind this to a button. | 250 Opens the options dialog box. Usually you would bind this to a button. |
429 """ | 251 """ |
430 self.changesRequireRestart = False | 252 self.changesRequireRestart = False |
444 """Loads a widget. Can load both files and pure xml strings""" | 266 """Loads a widget. Can load both files and pure xml strings""" |
445 if os.path.isfile(self._settings_gui_xml): | 267 if os.path.isfile(self._settings_gui_xml): |
446 return pychan.loadXML(dialog) | 268 return pychan.loadXML(dialog) |
447 else: | 269 else: |
448 return pychan.loadXML(StringIO(dialog)) | 270 return pychan.loadXML(StringIO(dialog)) |
449 | |
450 | 271 |
451 def fillWidgets(self): | 272 def fillWidgets(self): |
452 for module in self._entries.itervalues(): | 273 for module in self._entries.itervalues(): |
453 for entry in module.itervalues(): | 274 for entry in module.itervalues(): |
454 widget = self.OptionsDlg.findChildByName(entry.settingwidgetname) | 275 widget = self.OptionsDlg.findChildByName(entry.settingwidgetname) |
516 Overwrites the setting file with the default settings-dist.xml file. | 337 Overwrites the setting file with the default settings-dist.xml file. |
517 """ | 338 """ |
518 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) | 339 shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) |
519 self.changesRequireRestart = True | 340 self.changesRequireRestart = True |
520 self.loadSettings() | 341 self.loadSettings() |
521 self._showChangeRequireRestartDialog() | 342 #self._showChangeRequireRestartDialog() |
522 | 343 |
523 if self.OptionsDlg: | 344 #if self.OptionsDlg: |
524 self.OptionsDlg.hide() | 345 # self.OptionsDlg.hide() |
525 | 346 |
526 def _getEntries(self): | 347 def _getEntries(self): |
527 return self._entries | 348 return self._entries |
528 | 349 |
529 def _setEntries(self, entries): | 350 def _setEntries(self, entries): |