]> git.lizzy.rs Git - irrlicht.git/blob - examples/25.XmlHandling/main.cpp
Merging r6173 through r6179 from trunk to ogl-es branch
[irrlicht.git] / examples / 25.XmlHandling / main.cpp
1 /** Example 025 Xml Handling\r
2 \r
3 Demonstrates loading and saving of configurations via XML\r
4 \r
5 @author Y.M. Bosman     \<yoran.bosman@gmail.com\>\r
6 \r
7 This demo features a fully usable system for configuration handling. The code\r
8 can easily be integrated into own apps.\r
9 \r
10 */\r
11 \r
12 #include <irrlicht.h>\r
13 #include "exampleHelper.h"\r
14 \r
15 using namespace irr;\r
16 using namespace core;\r
17 using namespace scene;\r
18 using namespace video;\r
19 using namespace io;\r
20 using namespace gui;\r
21 \r
22 #ifdef _MSC_VER\r
23 #pragma comment(lib, "Irrlicht.lib")\r
24 #endif\r
25 \r
26 \r
27 /* SettingManager class.\r
28 \r
29 This class loads and writes the settings and manages the options.\r
30 \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
34 */\r
35 \r
36 class SettingManager\r
37 {\r
38 public:\r
39 \r
40         // Construct setting managers and set default settings\r
41         SettingManager(const stringw& settings_file): SettingsFile(settings_file), NullDevice(0)\r
42         {\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
45 \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
52 \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
57 \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
62         }\r
63 \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
66         // nulldevice\r
67         ~SettingManager()\r
68         {\r
69                 if (NullDevice)\r
70                 {\r
71                         NullDevice->closeDevice();\r
72                         NullDevice->drop();\r
73                 }\r
74         };\r
75 \r
76         /*\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
80         <pre>\r
81                 <?xml version="1.0"?>\r
82                 <mygame>\r
83                         <video>\r
84                                 <setting name="driver" value="Direct3D9" />\r
85                                 <setting name="fullscreen" value="0" />\r
86                                 <setting name="resolution" value="1024x768" />\r
87                         </video>\r
88                 </mygame>\r
89         </pre>\r
90         */\r
91         bool load()\r
92         {\r
93                 //if not able to create device don't attempt to load\r
94                 if (!NullDevice)\r
95                         return false;\r
96 \r
97                 irr::io::IXMLReader* xml = NullDevice->getFileSystem()->createXMLReader(SettingsFile);  //create xml reader\r
98                 if (!xml)\r
99                         return false;\r
100 \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
104 \r
105                 //while there is more to read\r
106                 while (xml->read())\r
107                 {\r
108                         //check the node type\r
109                         switch (xml->getNodeType())\r
110                         {\r
111                                 //we found a new element\r
112                                 case irr::io::EXN_ELEMENT:\r
113                                 {\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
116                                         {\r
117                                                 currentSection = videoTag;\r
118                                         }\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
121                                         {\r
122                                                 //read in the key\r
123                                                 stringw key = xml->getAttributeValueSafe(L"name");\r
124                                                 //if there actually is a key to set\r
125                                                 if (!key.empty())\r
126                                                 {\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
131                                                 }\r
132                                         }\r
133 \r
134                                         //..\r
135                                         // You can add your own sections and tags to read in here\r
136                                         //..\r
137                                 }\r
138                                 break;\r
139 \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
144                                 break;\r
145                                 default:\r
146                                         break;\r
147                         }\r
148                 }\r
149 \r
150                 // don't forget to delete the xml reader\r
151                 xml->drop();\r
152 \r
153                 return true;\r
154         }\r
155 \r
156         // Save the xml to disk. We use the nulldevice.\r
157         bool save()\r
158         {\r
159 \r
160                 //if not able to create device don't attempt to save\r
161                 if (!NullDevice)\r
162                         return false;\r
163 \r
164                 //create xml writer\r
165                 irr::io::IXMLWriter* xwriter = NullDevice->getFileSystem()->createXMLWriter( SettingsFile );\r
166                 if (!xwriter)\r
167                         return false;\r
168 \r
169                 //write out the obligatory xml header. Each xml-file needs to have exactly one of those.\r
170                 xwriter->writeXMLHeader();\r
171 \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
175 \r
176                 //start section with video settings\r
177                 xwriter->writeElement(L"video");\r
178                 xwriter->writeLineBreak();                                      //new line\r
179 \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
185                 {\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
190                 }\r
191                 xwriter->writeLineBreak();\r
192 \r
193                 //close video section\r
194                 xwriter->writeClosingTag(L"video");\r
195                 xwriter->writeLineBreak();\r
196 \r
197                 //..\r
198                 // You can add writing sound settings, savegame information etc\r
199                 //..\r
200 \r
201                 //close mygame section\r
202                 xwriter->writeClosingTag(L"mygame");\r
203 \r
204                 //delete xml writer\r
205                 xwriter->drop();\r
206 \r
207                 return true;\r
208         }\r
209 \r
210         // Set setting in our manager\r
211         void setSetting(const stringw& name, const stringw& value)\r
212         {\r
213                 SettingMap[name]=value;\r
214         }\r
215 \r
216         // set setting overload to quickly assign integers to our setting map\r
217         void setSetting(const stringw& name, s32 value)\r
218         {\r
219                 SettingMap[name]=stringw(value);\r
220         }\r
221 \r
222         // Get setting as string\r
223         stringw getSetting(const stringw& key) const\r
224         {\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
229                 if (n)\r
230                         return n->getValue();\r
231                 else\r
232                         return L"";\r
233         }\r
234 \r
235         //\r
236         bool getSettingAsBoolean(const stringw& key ) const\r
237         {\r
238                 stringw s = getSetting(key);\r
239                 if (s.empty())\r
240                         return false;\r
241                 return s.equals_ignore_case(L"1");\r
242         }\r
243 \r
244         //\r
245         s32 getSettingAsInteger(const stringw& key) const\r
246         {\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
249                 if (s.empty())\r
250                         return 0;\r
251 \r
252                 return strtol10(s.c_str());\r
253         }\r
254 \r
255 public:\r
256         map<stringw, s32> DriverOptions; //available options for driver config\r
257         map<stringw, dimension2du> ResolutionOptions; //available options for resolution config\r
258 private:\r
259         SettingManager(const SettingManager& other); // defined but not implemented\r
260         SettingManager& operator=(const SettingManager& other); // defined but not implemented\r
261 \r
262         map<stringw, stringw> SettingMap; //current config\r
263 \r
264         stringw SettingsFile; // location of the xml, usually the\r
265         irr::IrrlichtDevice* NullDevice;\r
266 };\r
267 \r
268 /*\r
269 Application context for global variables\r
270 */\r
271 struct SAppContext\r
272 {\r
273         SAppContext()\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
277         {\r
278         }\r
279 \r
280         ~SAppContext()\r
281         {\r
282                 if (Settings)\r
283                         delete Settings;\r
284 \r
285                 if (Device)\r
286                 {\r
287                         Device->closeDevice();\r
288                         Device->drop();\r
289                 }\r
290         }\r
291 \r
292         IrrlichtDevice* Device;\r
293         IGUIEnvironment* Gui;\r
294         IVideoDriver* Driver;\r
295         SettingManager* Settings;\r
296         bool ShouldQuit;\r
297 \r
298         //settings dialog\r
299         IGUIButton* ButtonSave;\r
300         IGUIButton* ButtonExit;\r
301         IGUIListBox* ListboxDriver;\r
302         IGUIListBox* ListboxResolution;\r
303         IGUICheckBox* CheckboxFullscreen;\r
304 };\r
305 \r
306 /*\r
307         A typical event receiver.\r
308 */\r
309 class MyEventReceiver : public IEventReceiver\r
310 {\r
311 public:\r
312         MyEventReceiver(SAppContext & a) : App(a) { }\r
313 \r
314         virtual bool OnEvent(const SEvent& event)\r
315         {\r
316                 if (event.EventType == EET_GUI_EVENT )\r
317                 {\r
318                         switch ( event.GUIEvent.EventType )\r
319                         {\r
320                                 //handle button click events\r
321                                 case EGET_BUTTON_CLICKED:\r
322                                 {\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
325                                         {\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
329 \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
333 \r
334                                                 App.Settings->setSetting(L"fullscreen", App.CheckboxFullscreen->isChecked());\r
335 \r
336 \r
337                                                 if (App.Settings->save())\r
338                                                 {\r
339                                                         App.Gui->addMessageBox(L"settings save",L"settings saved, please restart for settings to change effect","",true);\r
340                                                 }\r
341                                         }\r
342                                         // cancel/exit button clicked, tell the application to exit\r
343                                         else if ( event.GUIEvent.Caller == App.ButtonExit)\r
344                                         {\r
345                                                 App.ShouldQuit = true;\r
346                                         }\r
347                                 }\r
348                                 break;\r
349                                 default:\r
350                                         break;\r
351                         }\r
352                 }\r
353 \r
354                 return false;\r
355         }\r
356 \r
357 private:\r
358         SAppContext & App;\r
359 };\r
360 \r
361 \r
362 /*\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
365 */\r
366 void createSettingsDialog(SAppContext& app)\r
367 {\r
368         // first get rid of alpha in gui\r
369         for (irr::s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)\r
370         {\r
371                 irr::video::SColor col = app.Gui->getSkin()->getColor((irr::gui::EGUI_DEFAULT_COLOR)i);\r
372                 col.setAlpha(255);\r
373                 app.Gui->getSkin()->setColor((irr::gui::EGUI_DEFAULT_COLOR)i, col);\r
374         }\r
375 \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
379 \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
383 \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
388 \r
389         //set currently selected driver\r
390         app.ListboxDriver->setSelected(app.Settings->getSetting("driver").c_str());\r
391 \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
395 \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
400 \r
401         //set currently selected resolution\r
402         app.ListboxResolution->setSelected(app.Settings->getSetting("resolution").c_str());\r
403 \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
408                         L"Fullscreen");\r
409 \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
414 \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
419 }\r
420 \r
421 /*\r
422 The main function. Creates all objects and does the XML handling.\r
423 */\r
424 int main()\r
425 {\r
426         //create new application context\r
427         SAppContext app;\r
428 \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
433 \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
439         {\r
440                 // ...\r
441                 // Here add your own exception handling, for now we continue because there are defaults set in SettingManager constructor\r
442                 // ...\r
443         }\r
444         else\r
445         {\r
446                 //settings xml loaded from disk,\r
447 \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
453 \r
454                 if (driver)\r
455                 {\r
456                         if ( irr::IrrlichtDevice::isDriverSupported( static_cast<E_DRIVER_TYPE>( driver->getValue() )))\r
457                         {\r
458                                 // selected driver is supported, so we use it.\r
459                                 param.DriverType = static_cast<E_DRIVER_TYPE>( driver->getValue());\r
460                         }\r
461                 }\r
462 \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
465                 if (res)\r
466                 {\r
467                         param.WindowSize = res->getValue();\r
468                 }\r
469 \r
470                 //get fullscreen setting from config\r
471                 param.Fullscreen = app.Settings->getSettingAsBoolean("fullscreen");\r
472         }\r
473 \r
474         //create the irrlicht device using the settings\r
475         app.Device = createDeviceEx(param);\r
476         if (app.Device == 0)\r
477         {\r
478                 // You can add your own exception handling on driver failure\r
479                 exit(0);\r
480         }\r
481 \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
485 \r
486         createSettingsDialog(app);\r
487 \r
488         //set event receiver so we can respond to gui events\r
489         MyEventReceiver receiver(app);\r
490         app.Device->setEventReceiver(&receiver);\r
491 \r
492         //enter main loop\r
493         while (!app.ShouldQuit && app.Device->run())\r
494         {\r
495                 if (app.Device->isWindowActive())\r
496                 {\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
500                 }\r
501                 app.Device->sleep(10);\r
502         }\r
503 \r
504         //app destroys device in destructor\r
505 \r
506         return 0;\r
507 }\r
508 \r
509 /*\r
510 **/\r