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