Mercurial > fife-parpg
diff 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 |
line wrap: on
line diff
--- a/engine/python/fife/extensions/fife_settings.py Fri May 14 16:23:52 2010 +0000 +++ b/engine/python/fife/extensions/fife_settings.py Fri May 14 17:37:42 2010 +0000 @@ -87,6 +87,7 @@ settings = Setting(app_name="myapp") screen_width = settings.readSetting("ScreenWidth") """ + def __init__(self, app_name="", settings_file="", settings_gui_xml=""): """ Initializes the Setting object. @@ -124,10 +125,212 @@ if not os.path.exists(os.path.join(self._appdata, self._settings_file)): shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) + self._tree = ET.parse(os.path.join(self._appdata, self._settings_file)) + self._root_element = self._tree.getroot() + self.validateTree() + + def validateTree(self): + """ Iterates the settings tree and prints warning when an invalid tag is found """ + for c in self._root_element.getchildren(): + if c.tag != "Module": + print "Invalid tag in settings.xml. Expected Module, got: ", c.tag + elif c.get("name", "") == "": + print "Invalid tag in settings.xml. Module name is empty." + else: + for e in c.getchildren(): + if e.tag != "Setting": + print "Invalid tag in settings.xml in module: ",c.tag, + print ". Expected Setting, got: ", e.tag + elif c.get("name", "") == "": + print "Invalid tag in settings.xml in module: ",c.tag, + print ". Setting name is empty", e.tag + + def getModuleTree(self, module): + """ + Returns a module element from the settings tree. If no module with the specified + name exists, a new element will be created. + + @param module: The module to get from the settings tree + @type module: C{string} + """ + if not isinstance(module, str) and not isinstance(module, unicode): + raise AttributeError("Settings:getModuleTree: Invalid type for module argument.") + + for c in self._root_element.getchildren(): + if c.tag == "Module" and c.get("name", "") == module: + return c + + # Create module + return ET.SubElement(self._root_element, "Module", {"name":module}) + + def get(self, module, name, defaultValue=None): + """ Gets the value of a specified setting + + @param module: Name of the module to get the setting from + @param name: Setting name + @param defaultValue: Specifies the default value to return if the setting is not found + @type defaultValue: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} + """ + if not isinstance(name, str) and not isinstance(name, unicode): + raise AttributeError("Settings:get: Invalid type for name argument.") + + moduleTree = self.getModuleTree(module) + element = None + for e in moduleTree.getchildren(): + if e.tag == "Setting" and e.get("name", "") == name: + element = e + break + else: + return defaultValue + + e_value = element.text + e_strip = element.get("strip", "1").strip().lower() + e_type = str(element.get("type", "str")).strip() + + if e_value is None: + return defaultValue + + # Strip value + if e_strip == "" or e_strip == "false" or e_strip == "no" or e_strip == "0": + e_strip = False + else: e_strip = True + + if e_type == "str" or e_type == "unicode": + if e_strip: e_value = e_value.strip() + else: + e_value = e_value.strip() + + # Return value + if e_type == 'int': + return int(e_value) + elif e_type == 'float': + return float(e_value) + elif e_type == 'bool': + e_value = e_value.lower() + if e_value == "" or e_value == "false" or e_value == "no" or e_value == "0": + return False + else: + return True + elif e_type == 'str': + return str(e_value) + elif e_type == 'unicode': + return unicode(e_value) + elif e_type == 'list': + return self._deserializeList(e_value) + elif e_type == 'dict': + return self._deserializeDict(e_value) + + def set(self, module, name, value, extra_attrs={}): + """ + Sets a setting to specified value. + + @param module: Module where the setting should be set + @param name: Name of setting + @param value: Value to assign to setting + @type value: C{str} or C{unicode} or C{int} or C{float} or C{bool} or C{list} or C{dict} + @param extra_attrs: Extra attributes to be stored in the XML-file + @type extra_attrs: C{dict} + """ + if not isinstance(name, str) and not isinstance(name, unicode): + raise AttributeError("Settings:set: Invalid type for name argument.") + + moduleTree = self.getModuleTree(module) + e_type = "str" + + if isinstance(value, bool): # This must be before int + e_type = "bool" + value = str(value) + elif isinstance(value, int): + e_type = "int" + value = str(value) + elif isinstance(value, float): + e_type = "float" + value = str(value) + elif isinstance(value, unicode): + e_type = "unicode" + value = unicode(value) + elif isinstance(value, list): + e_type = "list" + value = self._serializeList(value) + elif isinstance(value, dict): + e_type = "dict" + value = self._serializeDict(value) + else: + e_type = "str" + value = str(value) + + for e in moduleTree.getchildren(): + if e.tag != "Setting": continue + if e.get("name", "") == name: + e.text = value + break + else: + attrs = {"name":name, "type":e_type} + for k in extra_attrs: + if k not in attrs: + attrs[k] = extra_args[k] + elm = ET.SubElement(moduleTree, "Setting", attrs) + elm.text = value + + def saveSettings(self): + """ Writes the settings to the settings file """ + self._indent(self._root_element) + self._tree.write(os.path.join(self._appdata, self._settings_file), 'UTF-8') + + def _indent(self, elem, level=0): + """ + Adds whitespace, so the resulting XML-file is properly indented. + Shamelessly stolen from http://effbot.org/zone/element-lib.htm + """ + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + self._indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + # FIXME: + # These serialization functions are not reliable at all + # This will only serialize the first level of a dict or list + # It will not check the types nor the content for conflicts. + # Perhaps we should add a small serialization library? + def _serializeList(self, list): + """ Serializes a list, so it can be stored in a text file """ + return " ; ".join(list) + + def _deserializeList(self, string): + """ Deserializes a list back into a list object """ + return string.split(" ; ") + + def _serializeDict(self, dict): + """ Serializes a list, so it can be stored in a text file """ + serial = "" + for key in dict: + value = dict[key] + if serial != "": serial += " ; " + serial += str(key)+" : "+str(value) + + return serial + + def _deserializeDict(self, serial): + """ Deserializes a list back into a dict object """ + dict = {} + items = serial.split(" ; ") + for i in items: + kv_pair = i.split(" : ") + dict[kv_pair[0]] = kv_pair[1] + return dict def onOptionsPress(self): """ - Opends the options dialog box. Usually you would bind this to a button. + Opens the options dialog box. Usually you would bind this to a button. """ self.changesRequireRestart = False self.isSetToDefault = False @@ -141,18 +344,48 @@ 'render_backend' : ['OpenGL', 'SDL'] }) self.OptionsDlg.distributeData({ - 'screen_resolution' : self.Resolutions.index(str(self.readSetting("ScreenWidth")) + 'x' + str(self.readSetting("ScreenHeight"))), - 'render_backend' : 0 if str(self.readSetting("RenderBackend")) == "OpenGL" else 1, - 'enable_fullscreen' : int(self.readSetting("FullScreen")), - 'enable_sound' : int(self.readSetting("PlaySounds")) + 'screen_resolution' : self.Resolutions.index(str(self.get("FIFE", "ScreenWidth")) + 'x' + str(self.get("FIFE", "ScreenHeight"))), + 'render_backend' : 0 if self.get("FIFE", "RenderBackend") == "OpenGL" else 1, + 'enable_fullscreen' : self.get("FIFE", "FullScreen"), + 'enable_sound' : self.get("FIFE", "PlaySounds") }) self.OptionsDlg.mapEvents({ - 'okButton' : self.saveSettings, + 'okButton' : self.applySettings, 'cancelButton' : self.OptionsDlg.hide, 'defaultButton' : self.setDefaults }) self.OptionsDlg.show() + def applySettings(self): + """ + Writes the settings file. If a change requires a restart of the engine + it notifies you with a small dialog box. + """ + screen_resolution, render_backend, enable_fullscreen, enable_sound = self.OptionsDlg.collectData('screen_resolution', 'render_backend', 'enable_fullscreen', 'enable_sound') + render_backend = 'OpenGL' if render_backend is 0 else 'SDL' + if render_backend != self.get("FIFE", "RenderBackend"): + self.set("FIFE", 'RenderBackend', render_backend) + self.changesRequireRestart = True + if int(enable_fullscreen) != int(self.get("FIFE", "FullScreen")): + self.set("FIFE", 'FullScreen', int(enable_fullscreen)) + self.changesRequireRestart = True + if int(enable_sound) != int(self.get("FIFE", "PlaySounds")): + self.set("FIFE", 'PlaySounds', int(enable_sound)) + self.changesRequireRestart = True + if screen_resolution != self.Resolutions.index(str(self.get("FIFE", "ScreenWidth")) + 'x' + str(self.get("FIFE", "ScreenHeight"))): + self.set("FIFE", 'ScreenWidth', int(self.Resolutions[screen_resolution].partition('x')[0])) + self.set("FIFE", 'ScreenHeight', int(self.Resolutions[screen_resolution].partition('x')[2])) + self.changesRequireRestart = True + + if not self.isSetToDefault: + self.saveSettings() + + self.OptionsDlg.hide() + if self.changesRequireRestart: + RestartDlg = pychan.loadXML(StringIO(CHANGES_REQUIRE_RESTART)) + RestartDlg.mapEvents({ 'closeButton' : RestartDlg.hide }) + RestartDlg.show() + def setDefaults(self): """ Overwrites the setting file with the default settings-dist.xml file. @@ -160,71 +393,3 @@ shutil.copyfile('settings-dist.xml', os.path.join(self._appdata, self._settings_file)) self.isSetToDefault = True self.changesRequireRestart = True - - def readSetting(self, name, type='int', strip=True, text=False): - if not hasattr(self, 'tree'): - self.tree = ET.parse(os.path.join(self._appdata, self._settings_file)) - self.root_element = self.tree.getroot() - element = self.root_element.find(name) - if element is not None: - element_value = element.text - if element_value is None: - if type == 'int': - return 0 - elif type == 'list': - list = [] - return list - else: - if type == 'int': - return element_value.strip() if strip else element_value - elif type == 'list': - list = [] - list_s = [] - list = str(element_value.strip()).split(";") - for item in list: - item = item.strip() - if text: - item = item.replace('\\n', '\n') - list_s.append(item) - return list_s - elif type == 'bool': - return False if element_value.strip() == 'False' else True - else: - print 'Setting,', name, 'does not exist!' - - def setSetting(self, name, value): - element = self.root_element.find(name) - if element is not None: - if value is not element.text: - element.text = str(value) - else: - print 'Setting,', name, 'does not exist!' - - def saveSettings(self): - """ - Writes the settings file. If a change requires a restart of the engine - it notifies you with a small dialog box. - """ - screen_resolution, render_backend, enable_fullscreen, enable_sound = self.OptionsDlg.collectData('screen_resolution', 'render_backend', 'enable_fullscreen', 'enable_sound') - render_backend = 'OpenGL' if render_backend is 0 else 'SDL' - if render_backend != str(self.readSetting("RenderBackend")): - self.setSetting('RenderBackend', render_backend) - self.changesRequireRestart = True - if int(enable_fullscreen) != int(self.readSetting("FullScreen")): - self.setSetting('FullScreen', int(enable_fullscreen)) - self.changesRequireRestart = True - if int(enable_sound) != int(self.readSetting("PlaySounds")): - self.setSetting('PlaySounds', int(enable_sound)) - self.changesRequireRestart = True - if screen_resolution != self.Resolutions.index(str(self.readSetting("ScreenWidth")) + 'x' + str(self.readSetting("ScreenHeight"))): - self.setSetting('ScreenWidth', int(self.Resolutions[screen_resolution].partition('x')[0])) - self.setSetting('ScreenHeight', int(self.Resolutions[screen_resolution].partition('x')[2])) - self.changesRequireRestart = True - - if not self.isSetToDefault: - self.tree.write(os.path.join(self._appdata, self._settings_file)) - self.OptionsDlg.hide() - if self.changesRequireRestart: - RestartDlg = pychan.loadXML(StringIO(CHANGES_REQUIRE_RESTART)) - RestartDlg.mapEvents({ 'closeButton' : RestartDlg.hide }) - RestartDlg.show()