1 /** Example 025 Xml Handling
\r
3 Demonstrates loading and saving of configurations via XML
\r
5 @author Y.M. Bosman \<yoran.bosman@gmail.com\>
\r
7 This demo features a fully usable system for configuration handling. The code
\r
8 can easily be integrated into own apps.
\r
12 #include <irrlicht.h>
\r
13 #include "exampleHelper.h"
\r
15 using namespace irr;
\r
16 using namespace core;
\r
17 using namespace scene;
\r
18 using namespace video;
\r
20 using namespace gui;
\r
23 #pragma comment(lib, "Irrlicht.lib")
\r
27 /* SettingManager class.
\r
29 This class loads and writes the settings and manages the options.
\r
31 The class makes use of irrMap which is a an associative arrays using a
\r
32 red-black tree it allows easy mapping of a key to a value, along the way there
\r
33 is some information on how to use it.
\r
36 class SettingManager
\r
40 // Construct setting managers and set default settings
\r
41 SettingManager(const stringw& settings_file): SettingsFile(settings_file), NullDevice(0)
\r
43 // Irrlicht null device, we want to load settings before we actually created our device, therefore, nulldevice
\r
44 NullDevice = irr::createDevice(irr::video::EDT_NULL);
\r
46 //DriverOptions is an irrlicht map,
\r
47 //we can insert values in the map in two ways by calling insert(key,value) or by using the [key] operator
\r
48 //the [] operator overrides values if they already exist
\r
49 DriverOptions.insert(L"Software", EDT_SOFTWARE);
\r
50 DriverOptions.insert(L"OpenGL", EDT_OPENGL);
\r
51 DriverOptions.insert(L"Direct3D9", EDT_DIRECT3D9);
\r
53 //some resolution options
\r
54 ResolutionOptions.insert(L"640x480", dimension2du(640,480));
\r
55 ResolutionOptions.insert(L"800x600", dimension2du(800,600));
\r
56 ResolutionOptions.insert(L"1024x768", dimension2du(1024,768));
\r
58 //our preferred defaults
\r
59 SettingMap.insert(L"driver", L"Direct3D9");
\r
60 SettingMap.insert(L"resolution", L"640x480");
\r
61 SettingMap.insert(L"fullscreen", L"0"); //0 is false
\r
64 // Destructor, you could store settings automatically on exit of your
\r
65 // application if you wanted to in our case we simply drop the
\r
71 NullDevice->closeDevice();
\r
77 Load xml from disk, overwrite default settings
\r
78 The xml we are trying to load has the following structure
\r
79 settings nested in sections nested in the root node, like so
\r
81 <?xml version="1.0"?>
\r
84 <setting name="driver" value="Direct3D9" />
\r
85 <setting name="fullscreen" value="0" />
\r
86 <setting name="resolution" value="1024x768" />
\r
93 //if not able to create device don't attempt to load
\r
97 irr::io::IXMLReader* xml = NullDevice->getFileSystem()->createXMLReader(SettingsFile); //create xml reader
\r
101 const stringw settingTag(L"setting"); //we'll be looking for this tag in the xml
\r
102 stringw currentSection; //keep track of our current section
\r
103 const stringw videoTag(L"video"); //constant for videotag
\r
105 //while there is more to read
\r
106 while (xml->read())
\r
108 //check the node type
\r
109 switch (xml->getNodeType())
\r
111 //we found a new element
\r
112 case irr::io::EXN_ELEMENT:
\r
114 //we currently are in the empty or mygame section and find the video tag so we set our current section to video
\r
115 if (currentSection.empty() && videoTag.equals_ignore_case(xml->getNodeName()))
\r
117 currentSection = videoTag;
\r
119 //we are in the video section and we find a setting to parse
\r
120 else if (currentSection.equals_ignore_case(videoTag) && settingTag.equals_ignore_case(xml->getNodeName() ))
\r
123 stringw key = xml->getAttributeValueSafe(L"name");
\r
124 //if there actually is a key to set
\r
127 //set the setting in the map to the value,
\r
128 //the [] operator overrides values if they already exist or inserts a new key value
\r
129 //pair into the settings map if it was not defined yet
\r
130 SettingMap[key] = xml->getAttributeValueSafe(L"value");
\r
135 // You can add your own sections and tags to read in here
\r
140 //we found the end of an element
\r
141 case irr::io::EXN_ELEMENT_END:
\r
142 //we were at the end of the video section so we reset our tag
\r
143 currentSection=L"";
\r
150 // don't forget to delete the xml reader
\r
156 // Save the xml to disk. We use the nulldevice.
\r
160 //if not able to create device don't attempt to save
\r
164 //create xml writer
\r
165 irr::io::IXMLWriter* xwriter = NullDevice->getFileSystem()->createXMLWriter( SettingsFile );
\r
169 //write out the obligatory xml header. Each xml-file needs to have exactly one of those.
\r
170 xwriter->writeXMLHeader();
\r
172 //start element mygame, you replace the label "mygame" with anything you want
\r
173 xwriter->writeElement(L"mygame");
\r
174 xwriter->writeLineBreak(); //new line
\r
176 //start section with video settings
\r
177 xwriter->writeElement(L"video");
\r
178 xwriter->writeLineBreak(); //new line
\r
180 // getIterator gets us a pointer to the first node of the settings map
\r
181 // every iteration we increase the iterator which gives us the next map node
\r
182 // until we reach the end we write settings one by one by using the nodes key and value functions
\r
183 map<stringw, stringw>::Iterator i = SettingMap.getIterator();
\r
184 for(; !i.atEnd(); i++)
\r
186 //write element as <setting name="key" value="x" />
\r
187 //the second parameter indicates this is an empty element with no children, just attributes
\r
188 xwriter->writeElement(L"setting",true, L"name", i->getKey().c_str(), L"value",i->getValue().c_str() );
\r
189 xwriter->writeLineBreak();
\r
191 xwriter->writeLineBreak();
\r
193 //close video section
\r
194 xwriter->writeClosingTag(L"video");
\r
195 xwriter->writeLineBreak();
\r
198 // You can add writing sound settings, savegame information etc
\r
201 //close mygame section
\r
202 xwriter->writeClosingTag(L"mygame");
\r
204 //delete xml writer
\r
210 // Set setting in our manager
\r
211 void setSetting(const stringw& name, const stringw& value)
\r
213 SettingMap[name]=value;
\r
216 // set setting overload to quickly assign integers to our setting map
\r
217 void setSetting(const stringw& name, s32 value)
\r
219 SettingMap[name]=stringw(value);
\r
222 // Get setting as string
\r
223 stringw getSetting(const stringw& key) const
\r
225 //the find function or irrmap returns a pointer to a map Node
\r
226 //if the key can be found, otherwise it returns null
\r
227 //the map node has the function getValue and getKey, as we already know the key, we return node->getValue()
\r
228 map<stringw, stringw>::Node* n = SettingMap.find(key);
\r
230 return n->getValue();
\r
236 bool getSettingAsBoolean(const stringw& key ) const
\r
238 stringw s = getSetting(key);
\r
241 return s.equals_ignore_case(L"1");
\r
245 s32 getSettingAsInteger(const stringw& key) const
\r
247 //we implicitly cast to string instead of stringw because strtol10 does not accept wide strings
\r
248 const stringc s = getSetting(key);
\r
252 return strtol10(s.c_str());
\r
256 map<stringw, s32> DriverOptions; //available options for driver config
\r
257 map<stringw, dimension2du> ResolutionOptions; //available options for resolution config
\r
259 SettingManager(const SettingManager& other); // defined but not implemented
\r
260 SettingManager& operator=(const SettingManager& other); // defined but not implemented
\r
262 map<stringw, stringw> SettingMap; //current config
\r
264 stringw SettingsFile; // location of the xml, usually the
\r
265 irr::IrrlichtDevice* NullDevice;
\r
269 Application context for global variables
\r
274 : Device(0),Gui(0), Driver(0), Settings(0), ShouldQuit(false),
\r
275 ButtonSave(0), ButtonExit(0), ListboxDriver(0),
\r
276 ListboxResolution(0), CheckboxFullscreen(0)
\r
287 Device->closeDevice();
\r
292 IrrlichtDevice* Device;
\r
293 IGUIEnvironment* Gui;
\r
294 IVideoDriver* Driver;
\r
295 SettingManager* Settings;
\r
299 IGUIButton* ButtonSave;
\r
300 IGUIButton* ButtonExit;
\r
301 IGUIListBox* ListboxDriver;
\r
302 IGUIListBox* ListboxResolution;
\r
303 IGUICheckBox* CheckboxFullscreen;
\r
307 A typical event receiver.
\r
309 class MyEventReceiver : public IEventReceiver
\r
312 MyEventReceiver(SAppContext & a) : App(a) { }
\r
314 virtual bool OnEvent(const SEvent& event)
\r
316 if (event.EventType == EET_GUI_EVENT )
\r
318 switch ( event.GUIEvent.EventType )
\r
320 //handle button click events
\r
321 case EGET_BUTTON_CLICKED:
\r
323 //Our save button was called so we obtain the settings from our dialog and save them
\r
324 if ( event.GUIEvent.Caller == App.ButtonSave )
\r
326 //if there is a selection write it
\r
327 if ( App.ListboxDriver->getSelected() != -1)
\r
328 App.Settings->setSetting(L"driver", App.ListboxDriver->getListItem(App.ListboxDriver->getSelected()));
\r
330 //if there is a selection write it
\r
331 if ( App.ListboxResolution->getSelected() != -1)
\r
332 App.Settings->setSetting(L"resolution", App.ListboxResolution->getListItem(App.ListboxResolution->getSelected()));
\r
334 App.Settings->setSetting(L"fullscreen", App.CheckboxFullscreen->isChecked());
\r
337 if (App.Settings->save())
\r
339 App.Gui->addMessageBox(L"settings save",L"settings saved, please restart for settings to change effect","",true);
\r
342 // cancel/exit button clicked, tell the application to exit
\r
343 else if ( event.GUIEvent.Caller == App.ButtonExit)
\r
345 App.ShouldQuit = true;
\r
363 Function to create a video settings dialog
\r
364 This dialog shows the current settings from the configuration xml and allows them to be changed
\r
366 void createSettingsDialog(SAppContext& app)
\r
368 // first get rid of alpha in gui
\r
369 for (irr::s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
\r
371 irr::video::SColor col = app.Gui->getSkin()->getColor((irr::gui::EGUI_DEFAULT_COLOR)i);
\r
373 app.Gui->getSkin()->setColor((irr::gui::EGUI_DEFAULT_COLOR)i, col);
\r
376 //create video settings windows
\r
377 gui::IGUIWindow* windowSettings = app.Gui->addWindow(rect<s32>(10,10,400,400),true,L"Videosettings");
\r
378 app.Gui->addStaticText (L"Select your desired video settings", rect< s32 >(10,20, 200, 40), false, true, windowSettings);
\r
380 // add listbox for driver choice
\r
381 app.Gui->addStaticText (L"Driver", rect< s32 >(10,50, 200, 60), false, true, windowSettings);
\r
382 app.ListboxDriver = app.Gui->addListBox(rect<s32>(10,60,220,120), windowSettings, 1,true);
\r
384 //add all available options to the driver choice listbox
\r
385 map<stringw, s32>::Iterator i = app.Settings->DriverOptions.getIterator();
\r
386 for(; !i.atEnd(); i++)
\r
387 app.ListboxDriver->addItem(i->getKey().c_str());
\r
389 //set currently selected driver
\r
390 app.ListboxDriver->setSelected(app.Settings->getSetting("driver").c_str());
\r
392 // add listbox for resolution choice
\r
393 app.Gui->addStaticText (L"Resolution", rect< s32 >(10,130, 200, 140), false, true, windowSettings);
\r
394 app.ListboxResolution = app.Gui->addListBox(rect<s32>(10,140,220,200), windowSettings, 1,true);
\r
396 //add all available options to the resolution listbox
\r
397 map<stringw, dimension2du>::Iterator ri = app.Settings->ResolutionOptions.getIterator();
\r
398 for(; !ri.atEnd(); ri++)
\r
399 app.ListboxResolution->addItem(ri->getKey().c_str());
\r
401 //set currently selected resolution
\r
402 app.ListboxResolution->setSelected(app.Settings->getSetting("resolution").c_str());
\r
404 //add checkbox to toggle fullscreen, initially set to loaded setting
\r
405 app.CheckboxFullscreen = app.Gui->addCheckBox(
\r
406 app.Settings->getSettingAsBoolean("fullscreen"),
\r
407 rect<s32>(10,220,220,240), windowSettings, -1,
\r
410 //last but not least add save button
\r
411 app.ButtonSave = app.Gui->addButton(
\r
412 rect<s32>(80,250,150,270), windowSettings, 2,
\r
413 L"Save video settings");
\r
415 //exit/cancel button
\r
416 app.ButtonExit = app.Gui->addButton(
\r
417 rect<s32>(160,250,240,270), windowSettings, 2,
\r
418 L"Cancel and exit");
\r
422 The main function. Creates all objects and does the XML handling.
\r
426 //create new application context
\r
429 //create device creation parameters that can get overwritten by our settings file
\r
430 SIrrlichtCreationParameters param;
\r
431 param.DriverType = EDT_SOFTWARE;
\r
432 param.WindowSize.set(640,480);
\r
434 // Try to load config.
\r
435 // I leave it as an exercise of the reader to store the configuration in the local application data folder,
\r
436 // the only logical place to store config data for games. For all other operating systems I redirect to your manuals
\r
437 app.Settings = new SettingManager(getExampleMediaPath() + "settings.xml");
\r
438 if ( !app.Settings->load() )
\r
441 // Here add your own exception handling, for now we continue because there are defaults set in SettingManager constructor
\r
446 //settings xml loaded from disk,
\r
448 //map driversetting to driver type and test if the setting is valid
\r
449 //the DriverOptions map contains string representations mapped to to irrlicht E_DRIVER_TYPE enum
\r
450 //e.g "direct3d9" will become 4
\r
451 //see DriverOptions in the settingmanager class for details
\r
452 map<stringw, s32>::Node* driver = app.Settings->DriverOptions.find( app.Settings->getSetting("driver") );
\r
456 if ( irr::IrrlichtDevice::isDriverSupported( static_cast<E_DRIVER_TYPE>( driver->getValue() )))
\r
458 // selected driver is supported, so we use it.
\r
459 param.DriverType = static_cast<E_DRIVER_TYPE>( driver->getValue());
\r
463 //map resolution setting to dimension in a similar way as demonstrated above
\r
464 map<stringw, dimension2du>::Node* res = app.Settings->ResolutionOptions.find( app.Settings->getSetting("resolution") );
\r
467 param.WindowSize = res->getValue();
\r
470 //get fullscreen setting from config
\r
471 param.Fullscreen = app.Settings->getSettingAsBoolean("fullscreen");
\r
474 //create the irrlicht device using the settings
\r
475 app.Device = createDeviceEx(param);
\r
476 if (app.Device == 0)
\r
478 // You can add your own exception handling on driver failure
\r
482 app.Device->setWindowCaption(L"Xmlhandling - Irrlicht engine tutorial");
\r
483 app.Driver = app.Device->getVideoDriver();
\r
484 app.Gui = app.Device->getGUIEnvironment();
\r
486 createSettingsDialog(app);
\r
488 //set event receiver so we can respond to gui events
\r
489 MyEventReceiver receiver(app);
\r
490 app.Device->setEventReceiver(&receiver);
\r
493 while (!app.ShouldQuit && app.Device->run())
\r
495 if (app.Device->isWindowActive())
\r
497 app.Driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,200,200,200));
\r
498 app.Gui->drawAll();
\r
499 app.Driver->endScene();
\r
501 app.Device->sleep(10);
\r
504 //app destroys device in destructor
\r