Mercurial > projects > mde
diff mde/gui/WidgetManager.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 | c67d074a7111 |
children | ccd01fde535e |
line wrap: on
line diff
--- a/mde/gui/WidgetManager.d Fri Apr 24 17:35:53 2009 +0100 +++ b/mde/gui/WidgetManager.d Thu May 21 20:55:10 2009 +0200 @@ -26,17 +26,11 @@ import mde.gui.WidgetDataSet; import mde.gui.widget.Ifaces; -import mde.gui.exception; import imde = mde.imde; import mde.content.Content; debug import mde.content.miscContent; // Debug menu -import mt = mde.file.mergetag.DataSet; -import mde.file.mergetag.Reader; -import mde.file.mergetag.Writer; -import mde.file.paths; - // Widgets to create: import mde.gui.widget.layout; import mde.gui.widget.miscWidgets; @@ -46,7 +40,7 @@ import mde.gui.widget.Floating; import mde.gui.widget.ParentContent; -import tango.core.sync.Mutex; +public import tango.core.sync.Mutex; import tango.util.log.Log : Log, Logger; import tango.util.container.SortedMap; @@ -58,20 +52,23 @@ /****************************************************************************** * 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. + * + * Methods in this class are only intended for use within the gui package, + * either by widgets (the IXXXWidget methods implementing from an interface in + * widgets.Ifaces.d) or by a derived class (back-end methods doing widget + * work). None of these methods are intended to be thread-safe when called + * concurrently on the same WidgetManager instance, but they should be thread- + * safe for calling on separate instances. * * This abstract class exists solely for separating out some of the functionality. *****************************************************************************/ abstract scope class AWidgetManager : 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; - + /** Construct a new widget manager. + * + * Params: + * name = The file name of the config for this GUI (to identify multiple GUIs). */ + protected this (char[] name) { clickCallbacks = new typeof(clickCallbacks); motionCallbacks = new typeof(motionCallbacks); @@ -79,193 +76,12 @@ assert (p, "MiscOptions.l10n not created!"); p.addCallback (&reloadStrings); debug { // add a debug-mode menu - auto lWS = new EventContent ("menus.debug."~file~".logWidgetSize"); + auto lWS = new EventContent ("menus.debug."~name~".logWidgetSize"); lWS.addCallback (&logWidgetSize); } } - /* 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; - } - } - - /** A change callback on MiscOptions.l10n content to update widgets. - * - * Relies on another callback reloading translations to content first! */ - protected void reloadStrings (Content) { - synchronized(mutex) { - if (child is null) return; - child.setup (++setupN, 2); - child.setWidth (w, -1); - child.setHeight (h, -1); - child.setPosition (0,0); - requestRedraw; - } - } - - // These methods are only intended for use within the gui package. - // They are not necessarily thread-safe: - +public: //BEGIN IParentWidget methods // If call reaches the widget manager there isn't any recursion. //NOTE: should be override @@ -471,6 +287,79 @@ } protected: + // These methods are called by derived classes to do the widget-management work + //BEGIN WidgetManagement methods + /** Draw all widgets */ + void wmDrawWidgets() { + if (child) + child.draw; + if (childIPPW) + childIPPW.drawPopup; + drawPopup; + } + + /** For mouse click events. + * + * Sends the event on to the relevant windows and all click callbacks. */ + void wmMouseClick (wdabs cx, wdabs cy, ubyte b, bool state) { + if (child is null) return; + + // Callbacks have the highest priority receiving events (e.g. a button release) + foreach (dg; clickCallbacks) + if (dg (cx, cy, b, state)) return; + + // Update underMouse to get the widget clicked on + updateUnderMouse (cx, cy, state); + + // Disable keyboard input if on another widget: + if (keyFocus && keyFocus !is underMouse) { + keyFocus.keyFocusLost; + keyFocus = null; + setLetterCallback (null); + } + + // Finally, post the actual event: + if (b == 3 && state) { // right click - open context menu + IContent contextContent = underMouse.content; + if (contextContent is null) return; + // NOTE: Creates new widgets every time; not optimal + popupContext = makeWidget (this, "context", contextContent); + popupContext.setup (0, 3); + positionPopup (underMouse, popupContext); + requestRedraw; + } else // post other button presses to clickEvent + if (underMouse.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state) & 1) { + // keyboard input requested + keyFocus = underMouse; + setLetterCallback (&underMouse.keyEvent); + } + } + + /** For mouse motion events. + * + * Lock on mutex before calling. Pass new mouse coordinates. */ + void wmMouseMotion (wdabs cx, wdabs cy) { + foreach (dg; motionCallbacks) + dg (cx, cy); + + updateUnderMouse (cx, cy, false); + } + + + /** A change callback on MiscOptions.l10n content to update widgets. + * + * Relies on another callback reloading translations to content first! */ + void reloadStrings (Content) { + synchronized(mutex) { + if (child is null) return; + child.setup (++setupN, 2); + child.setWidth (w, -1); + child.setHeight (h, -1); + child.setPosition (0,0); + requestRedraw; + } + } + // for internal use void updateUnderMouse (wdabs cx, wdabs cy, bool closePopup) { auto oUM = underMouse; underMouse = getPopupWidget (cx, cy, closePopup); @@ -485,25 +374,9 @@ } } - /** 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 (); + /// This should be overloaded to set a callback receiving keyboard input. + abstract void setLetterCallback(void delegate(ushort, char[])); + //END WidgetManagement methods public: //BEGIN makeWidget metacode @@ -613,19 +486,10 @@ //END makeWidget metacode protected: - // Dataset/design data: - 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? + char[] rendName; // Name of renderer; for saving and creating renderers IRenderer rend; // Widgets: