Mercurial > projects > mde
view mde/gui/WidgetData.d @ 75:25cb7420dc91
A massive overhaul/rewrite for the gui's data management and setup code. Currently much that was working is broken.
imde's classes are created in a static this instead of mde's main.
gl setup code moved from gl/basic.d to gl/draw.d
mergetag.DefaultData: now HIGH_LOW priority instead of LOW_HIGH. Reduced type list to only used types; small fix for indent function.
setup.paths: new NoFileException thrown instead of MTFileIOException
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Mon, 28 Jul 2008 18:17:48 +0100 |
parents | |
children | 65780e0e48e6 |
line wrap: on
line source
/* LICENSE BLOCK Part of mde: a Modular D game-oriented Engine Copyright © 2007-2008 Diggory Hardy This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /************************************************************************************************* * Code to manage the data used to create widgets and save changes to it. * * When loading, a WidgetDataSet instance is loaded from file and its data used to create the * widgets. An empty WidgetDataChanges instance is also created. * * If any data requires changing, it is added to the WidgetDataChanges instance, which also * updates the the WidgetDataSet instance used to load the widgets (in case of a re-load from this * data). When the data should be saved, if the WidgetDataChanges instance is not empty, data from * the highest priority (i.e. the user) file is merged into it, preserving both the current * changes and previous changes saved to the use file, before saving to the user file. *************************************************************************************************/ module mde.gui.WidgetData; import mde.gui.exception; import mde.gui.widget.Ifaces; import mde.gui.widget.createWidget; // For loading from file: import mt = mde.mergetag.DataSet; import mt = mde.mergetag.DefaultData; import mt = mde.mergetag.exception; import mde.mergetag.Reader; import mde.mergetag.Writer; import mde.setup.paths; import mde.mergetag.parse.parseTo; import mde.mergetag.parse.parseFrom : parseFrom; import tango.core.sync.Mutex; import tango.util.log.Log : Log, Logger; private Logger logger; static this () { logger = Log.getLogger ("mde.gui.WidgetData"); } /************************************************************************************************* * Contains the code for loading and saving the gui, but not the code for drawing it or handling * user input. * * This abstract class exists solely for separating out some of the functionality. *************************************************************************************************/ abstract scope class WidgetLoader : IWidgetManager { /** Construct a new widget loader. * * params: * fileName = Name of file specifying the gui, excluding path and extension. */ protected this (char[] file) { mutex = new Mutex; // Used on functions intended to be called from outside the gui package. fileName = file; } ~this () { save; } /* Load the widgets' data from the file specified to the CTOR. * * params: * allDesigns = Load all sections */ private void loadData (bool allDesigns = false) { if (allLoaded || (defaultDesign !is null && allDesigns == false)) return; // test if already loaded if (!confDir.exists (fileName)) { logger.error ("Unable to load GUI: no config file!"); return; // not a fatal error (so long as the game can run without a GUI!) } // Set up a reader scope IReader reader; try { reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true); // Read from the HEADER: // Get the renderer char[]* p = "Renderer" in reader.dataset.header.Arg!(char[]); if (p is null || *p is null) { logger.warn ("No renderer specified: using \"Simple\""); rendName = "Simple"; } else rendName = *p; // Get which section to use p = "Design" in reader.dataset.header.Arg!(char[]); if (p is null || *p is null) { logger.warn ("No gui design specified: trying \"Default\""); defaultDesign = "Default"; } else defaultDesign = *p; // Read the body: // Load the chosen design reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) { WidgetDataSet* p = id in data; if (p is null) { data[id] = new WidgetDataSet; return *(id in data); } return *p; }; if (allDesigns) { reader.read; allLoaded = true; } else reader.read([defaultDesign]); } catch (Exception e) { logger.error ("Unable to load GUI: errors parsing config file ("~confDir.getFileName(fileName,PRIORITY.HIGH_LOW)~"):"); logger.error (e.msg); throw new GuiException ("Failure parsing config file"); } } /** Load the gui from some design. * * If a design was previously loaded, its changes are saved first. * * Params: * name = Design to load. If null, the default will be loaded. */ void loadDesign (char[] name = null) { if (changes !is null) save; // own lock mutex.lock; scope(exit) mutex.unlock; // Load data (loadData tests if it's already loaded first): if (name is null) { loadData (false); name = defaultDesign; } else loadData (true); // Get data: curData = data[name]; // NOTE: may throw // Get/create a changes section: if (changesDS is null) changesDS = new mt.DataSet; mt.IDataSection* p = name in changesDS.sec; if (p && ((changes = cast(WidgetDataChanges) *p) !is null)) {} else { changes = new WidgetDataChanges (curData); changesDS.sec[name] = changes; } // Create the widgets: createRootWidget; } /** Save changes, if any exist. * * Is run when the manager is destroyed, but could be run at other times too. */ void save () { mutex.lock; scope(exit) mutex.unlock; if (noChanges) return; if (loadUserFile) { // merge entries from user file into current changes try { scope IReader reader = confDir.makeMTReader ( fileName, PRIORITY.HIGH_ONLY, changesDS, true); // Create if necessary, only corresponding to existing designs read: reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) { WidgetDataSet* p = id in data; if (p is null) throw new Exception ("File has changed since it was loaded!"); return new WidgetDataChanges (*p); }; reader.read; } catch (NoFileException) { // No user file exists; not an error. } catch (Exception e) { logger.error ("Error reading "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" prior to saving:"); logger.error (e.msg); logger.error ("Overwriting the file."); // Continue... } loadUserFile = false; // don't need to do it again } try { // Save IWriter writer; writer = confDir.makeMTWriter (fileName, changesDS); writer.write; } catch (Exception e) { logger.error ("Saving to "~confDir.getFileName(fileName,PRIORITY.HIGH_ONLY)~" failed:"); logger.error (e.msg); // No point in throwing since it doesn't affect anything else. } } /** Get the names of all designs available. */ char[][] designs() { synchronized(mutex) { loadData (true); return data.keys; } } /** Create a widget by ID. */ IChildWidget makeWidget (widgetID id) { return createWidget (this, curData[id]); } /** For making changes. */ void setData (widgetID id, WidgetData d) { noChanges = false; changes[id] = d; // also updates WidgetDataSet in data. } /** Second stage of loading the widgets. * * loadDesign handles the data; this method needs to: * --- * // 1. Create the root widget: * child = makeWidget ("root"); * // 2. Set the setSize, e.g.: * child.setWidth (child.minWidth, 1); * child.setHeight (child.minHeight, 1); * --- */ // FIXME: abstract? void createRootWidget(); protected: final char[] fileName; char[] defaultDesign; // The design specified in the file header. char[] rendName; // Name of renderer; for saving and creating renderers // Loaded data, indexed by design name. May not be loaded for all gui designs: scope WidgetDataSet[char[]] data; private bool allLoaded = false; // applies to data WidgetDataSet curData; // Current data WidgetDataChanges changes; // Changes for the current design. scope mt.DataSet changesDS; // changes and sections from user file (used for saving) bool loadUserFile = true; // still need to load user file for saving? bool noChanges = true; // do we have any changes to save? scope IChildWidget child; // The primary widget. Mutex mutex; // lock on methods for use outside the package. } package: /************************************************************************************************* * Contains data for all widgets in a GUI. *************************************************************************************************/ class WidgetDataSet : mt.IDataSection { //BEGIN Mergetag code void addTag (char[] tp, mt.ID id, char[] dt) { // Priority is HIGH_LOW. Only load tag if it doesn't already exist. if (tp == "WidgetData" && (id in widgetData) is null) { // Note: is a very simple form of struct deserialization WidgetData data; with(data) { char[][] strs = split (dt); if (strs.length != 2) throw new ParseException ("Not two components"); ints = parseTo!(int[]) (strs[0]); str = parseTo!(char[]) (strs[1]); } widgetData[id] = data; } } // Only WidgetDataChanges is used for writing. void writeAll (ItemDelg dlg) {} //END Mergetag code /** Get the widget data for widget i. */ WidgetData opIndex (widgetID i) { return widgetData[i]; } // Per-widget data: protected WidgetData[widgetID] widgetData; } /************************************************************************************************* * Contains changes to widget data. * * Can be read as normal and written. *************************************************************************************************/ class WidgetDataChanges : WidgetDataSet { /** * Params: * wds = The base WidgetDataSet these changes are applied against. */ this (WidgetDataSet wds) { base = wds; } //BEGIN Mergetag code // HIGH_LOW priority of addTag allows existing entries (i.e. the changes) to be preserved while // other entries are read from files. void writeAll (ItemDelg dlg) { foreach (id,data; widgetData) { // Note: is a very simple form of struct serialization with(data) { dlg ("WidgetData", id, parseFrom!(int[]) (ints) ~ ',' ~ parseFrom!(char[]) (str) ); } } } //END Mergetag code /** Set the widget data for widget i. */ void opIndexAssign (WidgetData d, widgetID i) { widgetData[i] = d; base.widgetData[i] = d; } /** Do any changes exist? True if no changes have been stored. */ bool none () { return widgetData.length == 0; } protected WidgetDataSet base; }