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):