main.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. /** Example 025 Xml Handling
  2. Demonstrates loading and saving of configurations via XML
  3. @author Y.M. Bosman \<yoran.bosman@gmail.com\>
  4. This demo features a fully usable system for configuration handling. The code
  5. can easily be integrated into own apps.
  6. */
  7. #include <irrlicht.h>
  8. #include "exampleHelper.h"
  9. using namespace irr;
  10. using namespace core;
  11. using namespace scene;
  12. using namespace video;
  13. using namespace io;
  14. using namespace gui;
  15. #ifdef _MSC_VER
  16. #pragma comment(lib, "Irrlicht.lib")
  17. #endif
  18. /* SettingManager class.
  19. This class loads and writes the settings and manages the options.
  20. The class makes use of irrMap which is a an associative arrays using a
  21. red-black tree it allows easy mapping of a key to a value, along the way there
  22. is some information on how to use it.
  23. */
  24. class SettingManager
  25. {
  26. public:
  27. // Construct setting managers and set default settings
  28. SettingManager(const stringw& settings_file): SettingsFile(settings_file), NullDevice(0)
  29. {
  30. // Irrlicht null device, we want to load settings before we actually created our device, therefore, nulldevice
  31. NullDevice = irr::createDevice(irr::video::EDT_NULL);
  32. //DriverOptions is an irrlicht map,
  33. //we can insert values in the map in two ways by calling insert(key,value) or by using the [key] operator
  34. //the [] operator overrides values if they already exist
  35. DriverOptions.insert(L"Software", EDT_SOFTWARE);
  36. DriverOptions.insert(L"OpenGL", EDT_OPENGL);
  37. DriverOptions.insert(L"Direct3D9", EDT_DIRECT3D9);
  38. //some resolution options
  39. ResolutionOptions.insert(L"640x480", dimension2du(640,480));
  40. ResolutionOptions.insert(L"800x600", dimension2du(800,600));
  41. ResolutionOptions.insert(L"1024x768", dimension2du(1024,768));
  42. //our preferred defaults
  43. SettingMap.insert(L"driver", L"Direct3D9");
  44. SettingMap.insert(L"resolution", L"640x480");
  45. SettingMap.insert(L"fullscreen", L"0"); //0 is false
  46. }
  47. // Destructor, you could store settings automatically on exit of your
  48. // application if you wanted to in our case we simply drop the
  49. // nulldevice
  50. ~SettingManager()
  51. {
  52. if (NullDevice)
  53. {
  54. NullDevice->closeDevice();
  55. NullDevice->drop();
  56. }
  57. };
  58. /*
  59. Load xml from disk, overwrite default settings
  60. The xml we are trying to load has the following structure
  61. settings nested in sections nested in the root node, like so
  62. <pre>
  63. <?xml version="1.0"?>
  64. <mygame>
  65. <video>
  66. <setting name="driver" value="Direct3D9" />
  67. <setting name="fullscreen" value="0" />
  68. <setting name="resolution" value="1024x768" />
  69. </video>
  70. </mygame>
  71. </pre>
  72. */
  73. bool load()
  74. {
  75. //if not able to create device don't attempt to load
  76. if (!NullDevice)
  77. return false;
  78. irr::io::IXMLReader* xml = NullDevice->getFileSystem()->createXMLReader(SettingsFile); //create xml reader
  79. if (!xml)
  80. return false;
  81. const stringw settingTag(L"setting"); //we'll be looking for this tag in the xml
  82. stringw currentSection; //keep track of our current section
  83. const stringw videoTag(L"video"); //constant for videotag
  84. //while there is more to read
  85. while (xml->read())
  86. {
  87. //check the node type
  88. switch (xml->getNodeType())
  89. {
  90. //we found a new element
  91. case irr::io::EXN_ELEMENT:
  92. {
  93. //we currently are in the empty or mygame section and find the video tag so we set our current section to video
  94. if (currentSection.empty() && videoTag.equals_ignore_case(xml->getNodeName()))
  95. {
  96. currentSection = videoTag;
  97. }
  98. //we are in the video section and we find a setting to parse
  99. else if (currentSection.equals_ignore_case(videoTag) && settingTag.equals_ignore_case(xml->getNodeName() ))
  100. {
  101. //read in the key
  102. stringw key = xml->getAttributeValueSafe(L"name");
  103. //if there actually is a key to set
  104. if (!key.empty())
  105. {
  106. //set the setting in the map to the value,
  107. //the [] operator overrides values if they already exist or inserts a new key value
  108. //pair into the settings map if it was not defined yet
  109. SettingMap[key] = xml->getAttributeValueSafe(L"value");
  110. }
  111. }
  112. //..
  113. // You can add your own sections and tags to read in here
  114. //..
  115. }
  116. break;
  117. //we found the end of an element
  118. case irr::io::EXN_ELEMENT_END:
  119. //we were at the end of the video section so we reset our tag
  120. currentSection=L"";
  121. break;
  122. default:
  123. break;
  124. }
  125. }
  126. // don't forget to delete the xml reader
  127. xml->drop();
  128. return true;
  129. }
  130. // Save the xml to disk. We use the nulldevice.
  131. bool save()
  132. {
  133. //if not able to create device don't attempt to save
  134. if (!NullDevice)
  135. return false;
  136. //create xml writer
  137. irr::io::IXMLWriter* xwriter = NullDevice->getFileSystem()->createXMLWriter( SettingsFile );
  138. if (!xwriter)
  139. return false;
  140. //write out the obligatory xml header. Each xml-file needs to have exactly one of those.
  141. xwriter->writeXMLHeader();
  142. //start element mygame, you replace the label "mygame" with anything you want
  143. xwriter->writeElement(L"mygame");
  144. xwriter->writeLineBreak(); //new line
  145. //start section with video settings
  146. xwriter->writeElement(L"video");
  147. xwriter->writeLineBreak(); //new line
  148. // getIterator gets us a pointer to the first node of the settings map
  149. // every iteration we increase the iterator which gives us the next map node
  150. // until we reach the end we write settings one by one by using the nodes key and value functions
  151. map<stringw, stringw>::Iterator i = SettingMap.getIterator();
  152. for(; !i.atEnd(); i++)
  153. {
  154. //write element as <setting name="key" value="x" />
  155. //the second parameter indicates this is an empty element with no children, just attributes
  156. xwriter->writeElement(L"setting",true, L"name", i->getKey().c_str(), L"value",i->getValue().c_str() );
  157. xwriter->writeLineBreak();
  158. }
  159. xwriter->writeLineBreak();
  160. //close video section
  161. xwriter->writeClosingTag(L"video");
  162. xwriter->writeLineBreak();
  163. //..
  164. // You can add writing sound settings, savegame information etc
  165. //..
  166. //close mygame section
  167. xwriter->writeClosingTag(L"mygame");
  168. //delete xml writer
  169. xwriter->drop();
  170. return true;
  171. }
  172. // Set setting in our manager
  173. void setSetting(const stringw& name, const stringw& value)
  174. {
  175. SettingMap[name]=value;
  176. }
  177. // set setting overload to quickly assign integers to our setting map
  178. void setSetting(const stringw& name, s32 value)
  179. {
  180. SettingMap[name]=stringw(value);
  181. }
  182. // Get setting as string
  183. stringw getSetting(const stringw& key) const
  184. {
  185. //the find function or irrmap returns a pointer to a map Node
  186. //if the key can be found, otherwise it returns null
  187. //the map node has the function getValue and getKey, as we already know the key, we return node->getValue()
  188. map<stringw, stringw>::Node* n = SettingMap.find(key);
  189. if (n)
  190. return n->getValue();
  191. else
  192. return L"";
  193. }
  194. //
  195. bool getSettingAsBoolean(const stringw& key ) const
  196. {
  197. stringw s = getSetting(key);
  198. if (s.empty())
  199. return false;
  200. return s.equals_ignore_case(L"1");
  201. }
  202. //
  203. s32 getSettingAsInteger(const stringw& key) const
  204. {
  205. //we implicitly cast to string instead of stringw because strtol10 does not accept wide strings
  206. const stringc s = getSetting(key);
  207. if (s.empty())
  208. return 0;
  209. return strtol10(s.c_str());
  210. }
  211. public:
  212. map<stringw, s32> DriverOptions; //available options for driver config
  213. map<stringw, dimension2du> ResolutionOptions; //available options for resolution config
  214. private:
  215. SettingManager(const SettingManager& other); // defined but not implemented
  216. SettingManager& operator=(const SettingManager& other); // defined but not implemented
  217. map<stringw, stringw> SettingMap; //current config
  218. stringw SettingsFile; // location of the xml, usually the
  219. irr::IrrlichtDevice* NullDevice;
  220. };
  221. /*
  222. Application context for global variables
  223. */
  224. struct SAppContext
  225. {
  226. SAppContext()
  227. : Device(0),Gui(0), Driver(0), Settings(0), ShouldQuit(false),
  228. ButtonSave(0), ButtonExit(0), ListboxDriver(0),
  229. ListboxResolution(0), CheckboxFullscreen(0)
  230. {
  231. }
  232. ~SAppContext()
  233. {
  234. if (Settings)
  235. delete Settings;
  236. if (Device)
  237. {
  238. Device->closeDevice();
  239. Device->drop();
  240. }
  241. }
  242. IrrlichtDevice* Device;
  243. IGUIEnvironment* Gui;
  244. IVideoDriver* Driver;
  245. SettingManager* Settings;
  246. bool ShouldQuit;
  247. //settings dialog
  248. IGUIButton* ButtonSave;
  249. IGUIButton* ButtonExit;
  250. IGUIListBox* ListboxDriver;
  251. IGUIListBox* ListboxResolution;
  252. IGUICheckBox* CheckboxFullscreen;
  253. };
  254. /*
  255. A typical event receiver.
  256. */
  257. class MyEventReceiver : public IEventReceiver
  258. {
  259. public:
  260. MyEventReceiver(SAppContext & a) : App(a) { }
  261. virtual bool OnEvent(const SEvent& event)
  262. {
  263. if (event.EventType == EET_GUI_EVENT )
  264. {
  265. switch ( event.GUIEvent.EventType )
  266. {
  267. //handle button click events
  268. case EGET_BUTTON_CLICKED:
  269. {
  270. //Our save button was called so we obtain the settings from our dialog and save them
  271. if ( event.GUIEvent.Caller == App.ButtonSave )
  272. {
  273. //if there is a selection write it
  274. if ( App.ListboxDriver->getSelected() != -1)
  275. App.Settings->setSetting(L"driver", App.ListboxDriver->getListItem(App.ListboxDriver->getSelected()));
  276. //if there is a selection write it
  277. if ( App.ListboxResolution->getSelected() != -1)
  278. App.Settings->setSetting(L"resolution", App.ListboxResolution->getListItem(App.ListboxResolution->getSelected()));
  279. App.Settings->setSetting(L"fullscreen", App.CheckboxFullscreen->isChecked());
  280. if (App.Settings->save())
  281. {
  282. App.Gui->addMessageBox(L"settings save",L"settings saved, please restart for settings to change effect","",true);
  283. }
  284. }
  285. // cancel/exit button clicked, tell the application to exit
  286. else if ( event.GUIEvent.Caller == App.ButtonExit)
  287. {
  288. App.ShouldQuit = true;
  289. }
  290. }
  291. break;
  292. default:
  293. break;
  294. }
  295. }
  296. return false;
  297. }
  298. private:
  299. SAppContext & App;
  300. };
  301. /*
  302. Function to create a video settings dialog
  303. This dialog shows the current settings from the configuration xml and allows them to be changed
  304. */
  305. void createSettingsDialog(SAppContext& app)
  306. {
  307. // first get rid of alpha in gui
  308. for (irr::s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
  309. {
  310. irr::video::SColor col = app.Gui->getSkin()->getColor((irr::gui::EGUI_DEFAULT_COLOR)i);
  311. col.setAlpha(255);
  312. app.Gui->getSkin()->setColor((irr::gui::EGUI_DEFAULT_COLOR)i, col);
  313. }
  314. //create video settings windows
  315. gui::IGUIWindow* windowSettings = app.Gui->addWindow(rect<s32>(10,10,400,400),true,L"Videosettings");
  316. app.Gui->addStaticText (L"Select your desired video settings", rect< s32 >(10,20, 200, 40), false, true, windowSettings);
  317. // add listbox for driver choice
  318. app.Gui->addStaticText (L"Driver", rect< s32 >(10,50, 200, 60), false, true, windowSettings);
  319. app.ListboxDriver = app.Gui->addListBox(rect<s32>(10,60,220,120), windowSettings, 1,true);
  320. //add all available options to the driver choice listbox
  321. map<stringw, s32>::Iterator i = app.Settings->DriverOptions.getIterator();
  322. for(; !i.atEnd(); i++)
  323. app.ListboxDriver->addItem(i->getKey().c_str());
  324. //set currently selected driver
  325. app.ListboxDriver->setSelected(app.Settings->getSetting("driver").c_str());
  326. // add listbox for resolution choice
  327. app.Gui->addStaticText (L"Resolution", rect< s32 >(10,130, 200, 140), false, true, windowSettings);
  328. app.ListboxResolution = app.Gui->addListBox(rect<s32>(10,140,220,200), windowSettings, 1,true);
  329. //add all available options to the resolution listbox
  330. map<stringw, dimension2du>::Iterator ri = app.Settings->ResolutionOptions.getIterator();
  331. for(; !ri.atEnd(); ri++)
  332. app.ListboxResolution->addItem(ri->getKey().c_str());
  333. //set currently selected resolution
  334. app.ListboxResolution->setSelected(app.Settings->getSetting("resolution").c_str());
  335. //add checkbox to toggle fullscreen, initially set to loaded setting
  336. app.CheckboxFullscreen = app.Gui->addCheckBox(
  337. app.Settings->getSettingAsBoolean("fullscreen"),
  338. rect<s32>(10,220,220,240), windowSettings, -1,
  339. L"Fullscreen");
  340. //last but not least add save button
  341. app.ButtonSave = app.Gui->addButton(
  342. rect<s32>(80,250,150,270), windowSettings, 2,
  343. L"Save video settings");
  344. //exit/cancel button
  345. app.ButtonExit = app.Gui->addButton(
  346. rect<s32>(160,250,240,270), windowSettings, 2,
  347. L"Cancel and exit");
  348. }
  349. /*
  350. The main function. Creates all objects and does the XML handling.
  351. */
  352. int main()
  353. {
  354. //create new application context
  355. SAppContext app;
  356. //create device creation parameters that can get overwritten by our settings file
  357. SIrrlichtCreationParameters param;
  358. param.DriverType = EDT_SOFTWARE;
  359. param.WindowSize.set(640,480);
  360. // Try to load config.
  361. // I leave it as an exercise of the reader to store the configuration in the local application data folder,
  362. // the only logical place to store config data for games. For all other operating systems I redirect to your manuals
  363. app.Settings = new SettingManager(getExampleMediaPath() + "settings.xml");
  364. if ( !app.Settings->load() )
  365. {
  366. // ...
  367. // Here add your own exception handling, for now we continue because there are defaults set in SettingManager constructor
  368. // ...
  369. }
  370. else
  371. {
  372. //settings xml loaded from disk,
  373. //map driversetting to driver type and test if the setting is valid
  374. //the DriverOptions map contains string representations mapped to to irrlicht E_DRIVER_TYPE enum
  375. //e.g "direct3d9" will become 4
  376. //see DriverOptions in the settingmanager class for details
  377. map<stringw, s32>::Node* driver = app.Settings->DriverOptions.find( app.Settings->getSetting("driver") );
  378. if (driver)
  379. {
  380. if ( irr::IrrlichtDevice::isDriverSupported( static_cast<E_DRIVER_TYPE>( driver->getValue() )))
  381. {
  382. // selected driver is supported, so we use it.
  383. param.DriverType = static_cast<E_DRIVER_TYPE>( driver->getValue());
  384. }
  385. }
  386. //map resolution setting to dimension in a similar way as demonstrated above
  387. map<stringw, dimension2du>::Node* res = app.Settings->ResolutionOptions.find( app.Settings->getSetting("resolution") );
  388. if (res)
  389. {
  390. param.WindowSize = res->getValue();
  391. }
  392. //get fullscreen setting from config
  393. param.Fullscreen = app.Settings->getSettingAsBoolean("fullscreen");
  394. }
  395. //create the irrlicht device using the settings
  396. app.Device = createDeviceEx(param);
  397. if (app.Device == 0)
  398. {
  399. // You can add your own exception handling on driver failure
  400. exit(0);
  401. }
  402. app.Device->setWindowCaption(L"Xmlhandling - Irrlicht engine tutorial");
  403. app.Driver = app.Device->getVideoDriver();
  404. app.Gui = app.Device->getGUIEnvironment();
  405. createSettingsDialog(app);
  406. //set event receiver so we can respond to gui events
  407. MyEventReceiver receiver(app);
  408. app.Device->setEventReceiver(&receiver);
  409. //enter main loop
  410. while (!app.ShouldQuit && app.Device->run())
  411. {
  412. if (app.Device->isWindowActive())
  413. {
  414. app.Driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(0,200,200,200));
  415. app.Gui->drawAll();
  416. app.Driver->endScene();
  417. }
  418. app.Device->sleep(10);
  419. }
  420. //app destroys device in destructor
  421. return 0;
  422. }
  423. /*
  424. **/