Mercurial > projects > mde
diff mde/gui/WidgetLoader.d @ 159:b06b04c75e86
Finished last commit, rearranged code for the WidgetManager class.
There is now a GUI options section.
Created a third WidgetManager class called WidgetLoader to handle file loading/saving.
Moved most of the code in WMScreen's draw/clickEvent/motionEvent functions to WidgetManager.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 21 May 2009 20:55:10 +0200 |
parents | |
children | 7f7b2011b759 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/gui/WidgetLoader.d Thu May 21 20:55:10 2009 +0200 @@ -0,0 +1,257 @@ +/* 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/>. */ + +/****************************************************************************** + * A gui manager extension to load widgets from files. + * + * Public methods in this class should be thread safe (by locking on a mutex). + *****************************************************************************/ +module mde.gui.WidgetLoader; + +import mde.gui.WidgetManager; +import mde.gui.WidgetDataSet; +import mde.gui.exception; + +import mt = mde.file.mergetag.DataSet; +import mde.file.mergetag.Reader; +import mde.file.mergetag.Writer; +import mde.file.paths; + +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this () { + logger = Log.getLogger ("mde.gui.WidgetLoader"); +} + +/****************************************************************************** + * Contains the code for loading and saving an entire gui (more than one may + * exist), 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 AWidgetLoader : AWidgetManager +{ + /** 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; + super (file); + } + + /* 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 + + // 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._charA; + 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._charA; + 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 (NoFileException) { + logger.error ("Unable to load GUI: no config file: "~fileName); + // just return: not a fatal error (so long as the game can run without a GUI!) + } 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) // A design was previously loaded + 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: + auto p = name in data; + while (p is null) { + if (name == defaultDesign) + throw new GuiException ("Unable to load [specified or] default design"); + name = defaultDesign; // try again with the default + p = name in data; + } + curData = *p; + + // Get/create a changes section: + if (changesDS is null) + changesDS = new mt.DataSet; + + mt.IDataSection* q = name in changesDS.sec; + if (!q || ((changes = cast(WidgetDataChanges) *q) is null)) { + changes = new WidgetDataChanges (curData); + changesDS.sec[name] = changes; + } + + // Create the widgets: + createRootWidget; + underMouse = child; // must be something + } + + /** Save changes, if any exist. + * + * Is run when the manager is destroyed, but could be run at other times too. */ + void save () { + preSave; + + mutex.lock; + scope(exit) mutex.unlock; + + // Make all widgets save any changed data: + child.saveChanges; + + if (changes.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; + } + } + +protected: + // Called by derived classes, not thread safe for the same instance + //BEGIN WidgetManagement methods + /** Second stage of loading the widgets. + * + * loadDesign handles the data; this method needs to: + * --- + * // 1. Create the root widget: + * child = makeWidget ("root"); + * child.setup (0, 3); + * // 2. Set the size: + * child.setWidth (child.minWidth, 1); + * child.setHeight (child.minHeight, 1); + * // 3. Set the position (necessary part of initialization): + * child.setPosition (0,0); + * --- + */ + void createRootWidget(); + + /** Called before saving (usually when the GUI is about to be destroyed, although not + * necessarily). */ + void preSave (); + //END WidgetManagement methods + + + // Dataset/design data: + final char[] fileName; + char[] defaultDesign; // The design specified in the file header. + + // 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 + scope mt.DataSet changesDS; // changes and sections from user file (used for saving) + bool loadUserFile = true; // still need to load user file for saving? +}