/** Example 025 Xml Handling Demonstrates loading and saving of configurations via XML @author Y.M. Bosman \ This demo features a fully usable system for configuration handling. The code can easily be integrated into own apps. */ #include using namespace irr; using namespace core; using namespace scene; using namespace video; using namespace io; using namespace gui; #ifdef _IRR_WINDOWS_ #pragma comment(lib, "Irrlicht.lib") #endif /* SettingManager class. This class loads and writes the settings and manages the options. The class makes use of irrMap which is a an associative arrays using a red-black tree it allows easy mapping of a key to a value, along the way there is some information on how to use it. */ class SettingManager { public: // Construct setting managers and set default settings SettingManager(const stringw& settings_file): SettingsFile(settings_file), NullDevice(0) { // Irrlicht null device, we want to load settings before we actually created our device, therefore, nulldevice NullDevice = irr::createDevice(irr::video::EDT_NULL); //DriverOptions is an irrlicht map, //we can insert values in the map in two ways by calling insert(key,value) or by using the [key] operator //the [] operator overrides values if they already exist DriverOptions.insert(L"Software", EDT_SOFTWARE); DriverOptions.insert(L"OpenGL", EDT_OPENGL); DriverOptions.insert(L"Direct3D9", EDT_DIRECT3D9); //some resolution options ResolutionOptions.insert(L"640x480", dimension2du(640,480)); ResolutionOptions.insert(L"800x600", dimension2du(800,600)); ResolutionOptions.insert(L"1024x768", dimension2du(1024,768)); //our preferred defaults SettingMap.insert(L"driver", L"Direct3D9"); SettingMap.insert(L"resolution", L"640x480"); SettingMap.insert(L"fullscreen", L"0"); //0 is false } // Destructor, you could store settings automatically on exit of your // application if you wanted to in our case we simply drop the // nulldevice ~SettingManager() { if (NullDevice) { NullDevice->closeDevice(); NullDevice->drop(); } }; /* Load xml from disk, overwrite default settings The xml we are trying to load has the following structure settings nested in sections nested in the root node, like so
		
		
			
		
	
*/ bool load() { //if not able to create device don't attempt to load if (!NullDevice) return false; irr::io::IXMLReader* xml = NullDevice->getFileSystem()->createXMLReader(SettingsFile); //create xml reader if (!xml) return false; const stringw settingTag(L"setting"); //we'll be looking for this tag in the xml stringw currentSection; //keep track of our current section const stringw videoTag(L"video"); //constant for videotag //while there is more to read while (xml->read()) { //check the node type switch (xml->getNodeType()) { //we found a new element case irr::io::EXN_ELEMENT: { //we currently are in the empty or mygame section and find the video tag so we set our current section to video if (currentSection.empty() && videoTag.equals_ignore_case(xml->getNodeName())) { currentSection = videoTag; } //we are in the video section and we find a setting to parse else if (currentSection.equals_ignore_case(videoTag) && settingTag.equals_ignore_case(xml->getNodeName() )) { //read in the key stringw key = xml->getAttributeValueSafe(L"name"); //if there actually is a key to set if (!key.empty()) { //set the setting in the map to the value, //the [] operator overrides values if they already exist or inserts a new key value //pair into the settings map if it was not defined yet SettingMap[key] = xml->getAttributeValueSafe(L"value"); } } //.. // You can add your own sections and tags to read in here //.. } break; //we found the end of an element case irr::io::EXN_ELEMENT_END: //we were at the end of the video section so we reset our tag currentSection=L""; break; } } // don't forget to delete the xml reader xml->drop(); return true; } // Save the xml to disk. We use the nulldevice. bool save() { //if not able to create device don't attempt to save if (!NullDevice) return false; //create xml writer irr::io::IXMLWriter* xwriter = NullDevice->getFileSystem()->createXMLWriter( SettingsFile ); if (!xwriter) return false; //write out the obligatory xml header. Each xml-file needs to have exactly one of those. xwriter->writeXMLHeader(); //start element mygame, you replace the label "mygame" with anything you want xwriter->writeElement(L"mygame"); xwriter->writeLineBreak(); //new line //start section with video settings xwriter->writeElement(L"video"); xwriter->writeLineBreak(); //new line // getIterator gets us a pointer to the first node of the settings map // every iteration we increase the iterator which gives us the next map node // until we reach the end we write settings one by one by using the nodes key and value functions map::Iterator i = SettingMap.getIterator(); for(; !i.atEnd(); i++) { //write element as //the second parameter indicates this is an empty element with no children, just attributes xwriter->writeElement(L"setting",true, L"name", i->getKey().c_str(), L"value",i->getValue().c_str() ); xwriter->writeLineBreak(); } xwriter->writeLineBreak(); //close video section xwriter->writeClosingTag(L"video"); xwriter->writeLineBreak(); //.. // You can add writing sound settings, savegame information etc //.. //close mygame section xwriter->writeClosingTag(L"mygame"); //delete xml writer xwriter->drop(); return true; } // Set setting in our manager void setSetting(const stringw& name, const stringw& value) { SettingMap[name]=value; } // set setting overload to quickly assign integers to our setting map void setSetting(const stringw& name, s32 value) { SettingMap[name]=stringw(value); } // Get setting as string stringw getSetting(const stringw& key) const { //the find function or irrmap returns a pointer to a map Node //if the key can be found, otherwise it returns null //the map node has the function getValue and getKey, as we already know the key, we return node->getValue() map::Node* n = SettingMap.find(key); if (n) return n->getValue(); else return L""; } // bool getSettingAsBoolean(const stringw& key ) const { stringw s = getSetting(key); if (s.empty()) return false; return s.equals_ignore_case(L"1"); } // s32 getSettingAsInteger(const stringw& key) const { //we implicitly cast to string instead of stringw because strtol10 does not accept wide strings const stringc s = getSetting(key); if (s.empty()) return 0; return strtol10(s.c_str()); } public: map DriverOptions; //available options for driver config map ResolutionOptions; //available options for resolution config private: SettingManager(const SettingManager& other); // defined but not implemented SettingManager& operator=(const SettingManager& other); // defined but not implemented map SettingMap; //current config stringw SettingsFile; // location of the xml, usually the irr::IrrlichtDevice* NullDevice; }; /* Application context for global variables */ struct SAppContext { SAppContext() : Device(0),Gui(0), Driver(0), Settings(0), ShouldQuit(false), ButtonSave(0), ButtonExit(0), ListboxDriver(0), ListboxResolution(0), CheckboxFullscreen(0) { } ~SAppContext() { if (Settings) delete Settings; if (Device) { Device->closeDevice(); Device->drop(); } } IrrlichtDevice* Device; IGUIEnvironment* Gui; IVideoDriver* Driver; SettingManager* Settings; bool ShouldQuit; //settings dialog IGUIButton* ButtonSave; IGUIButton* ButtonExit; IGUIListBox* ListboxDriver; IGUIListBox* ListboxResolution; IGUICheckBox* CheckboxFullscreen; }; /* A typical event receiver. */ class MyEventReceiver : public IEventReceiver { public: MyEventReceiver(SAppContext & a) : App(a) { } virtual bool OnEvent(const SEvent& event) { if (event.EventType == EET_GUI_EVENT ) { switch ( event.GUIEvent.EventType ) { //handle button click events case EGET_BUTTON_CLICKED: { //Our save button was called so we obtain the settings from our dialog and save them if ( event.GUIEvent.Caller == App.ButtonSave ) { //if there is a selection write it if ( App.ListboxDriver->getSelected() != -1) App.Settings->setSetting(L"driver", App.ListboxDriver->getListItem(App.ListboxDriver->getSelected())); //if there is a selection write it if ( App.ListboxResolution->getSelected() != -1) App.Settings->setSetting(L"resolution", App.ListboxResolution->getListItem(App.ListboxResolution->getSelected())); App.Settings->setSetting(L"fullscreen", App.CheckboxFullscreen->isChecked()); if (App.Settings->save()) { App.Gui->addMessageBox(L"settings save",L"settings saved, please restart for settings to change effect","",true); } } // cancel/exit button clicked, tell the application to exit else if ( event.GUIEvent.Caller == App.ButtonExit) { App.ShouldQuit = true; } } break; } } return false; } private: SAppContext & App; }; /* Function to create a video settings dialog This dialog shows the current settings from the configuration xml and allows them to be changed */ void createSettingsDialog(SAppContext& app) { // first get rid of alpha in gui for (irr::s32 i=0; igetSkin()->getColor((irr::gui::EGUI_DEFAULT_COLOR)i); col.setAlpha(255); app.Gui->getSkin()->setColor((irr::gui::EGUI_DEFAULT_COLOR)i, col); } //create video settings windows gui::IGUIWindow* windowSettings = app.Gui->addWindow(rect(10,10,400,400),true,L"Videosettings"); app.Gui->addStaticText (L"Select your desired video settings", rect< s32 >(10,20, 200, 40), false, true, windowSettings); // add listbox for driver choice app.Gui->addStaticText (L"Driver", rect< s32 >(10,50, 200, 60), false, true, windowSettings); app.ListboxDriver = app.Gui->addListBox(rect(10,60,220,120), windowSettings, 1,true); //add all available options to the driver choice listbox map::Iterator i = app.Settings->DriverOptions.getIterator(); for(; !i.atEnd(); i++) app.ListboxDriver->addItem(i->getKey().c_str()); //set currently selected driver app.ListboxDriver->setSelected(app.Settings->getSetting("driver").c_str()); // add listbox for resolution choice app.Gui->addStaticText (L"Resolution", rect< s32 >(10,130, 200, 140), false, true, windowSettings); app.ListboxResolution = app.Gui->addListBox(rect(10,140,220,200), windowSettings, 1,true); //add all available options to the resolution listbox map::Iterator ri = app.Settings->ResolutionOptions.getIterator(); for(; !ri.atEnd(); ri++) app.ListboxResolution->addItem(ri->getKey().c_str()); //set currently selected resolution app.ListboxResolution->setSelected(app.Settings->getSetting("resolution").c_str()); //add checkbox to toggle fullscreen, initially set to loaded setting app.CheckboxFullscreen = app.Gui->addCheckBox( app.Settings->getSettingAsBoolean("fullscreen"), rect(10,220,220,240), windowSettings, -1, L"Fullscreen"); //last but not least add save button app.ButtonSave = app.Gui->addButton( rect(80,250,150,270), windowSettings, 2, L"Save video settings"); //exit/cancel button app.ButtonExit = app.Gui->addButton( rect(160,250,240,270), windowSettings, 2, L"Cancel and exit"); } /* The main function. Creates all objects and does the XML handling. */ int main() { //create new application context SAppContext app; //create device creation parameters that can get overwritten by our settings file SIrrlichtCreationParameters param; param.DriverType = EDT_SOFTWARE; param.WindowSize.set(640,480); // Try to load config. // I leave it as an exercise of the reader to store the configuration in the local application data folder, // the only logical place to store config data for games. For all other operating systems I redirect to your manuals app.Settings = new SettingManager("../../media/settings.xml"); if ( !app.Settings->load() ) { // ... // Here add your own exception handling, for now we continue because there are defaults set in SettingManager constructor // ... } else { //settings xml loaded from disk, //map driversetting to driver type and test if the setting is valid //the DriverOptions map contains string representations mapped to to irrlicht E_DRIVER_TYPE enum //e.g "direct3d9" will become 4 //see DriverOptions in the settingmanager class for details map::Node* driver = app.Settings->DriverOptions.find( app.Settings->getSetting("driver") ); if (driver) { if ( irr::IrrlichtDevice::isDriverSupported( static_cast( driver->getValue() ))) { // selected driver is supported, so we use it. param.DriverType = static_cast( driver->getValue()); } } //map resolution setting to dimension in a similar way as demonstrated above map::Node* res = app.Settings->ResolutionOptions.find( app.Settings->getSetting("resolution") ); if (res) { param.WindowSize = res->getValue(); } //get fullscreen setting from config param.Fullscreen = app.Settings->getSettingAsBoolean("fullscreen"); } //create the irrlicht device using the settings app.Device = createDeviceEx(param); if (app.Device == 0) { // You can add your own exception handling on driver failure exit(0); } app.Device->setWindowCaption(L"Xmlhandling - Irrlicht engine tutorial"); app.Driver = app.Device->getVideoDriver(); app.Gui = app.Device->getGUIEnvironment(); createSettingsDialog(app); //set event receiver so we can respond to gui events MyEventReceiver receiver(app); app.Device->setEventReceiver(&receiver); //enter main loop while (!app.ShouldQuit && app.Device->run()) { if (app.Device->isWindowActive()) { app.Driver->beginScene(true, true, SColor(0,200,200,200)); app.Gui->drawAll(); app.Driver->endScene(); } app.Device->sleep(10); } //app destroys device in destructor return 0; } /* **/