changeset 39:5132301e9ed7

Implemented widget saving. Widget creation data saving (sub-widgets, etc:) code there but not used. Widget mutable data saving & loading: window size/position, row/column dimensions saved (still needs a fix in GridWidget.setSize()). committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 07 May 2008 13:07:03 +0100
parents 8c4c96f04e7f
children b28d7adc786b
files mde/gui/Gui.d mde/gui/exception.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Ifaces.d mde/gui/widget/Widget.d mde/gui/widget/Window.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/scheduler/Scheduler.d mde/scheduler/init2.d mde/scheduler/initFunctions.d
diffstat 11 files changed, 374 insertions(+), 147 deletions(-) [+]
line wrap: on
line diff
--- a/mde/gui/Gui.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/Gui.d	Wed May 07 13:07:03 2008 +0100
@@ -37,6 +37,7 @@
 import mt = mde.mergetag.DefaultData;
 import mt = mde.mergetag.exception;
 import mde.mergetag.Reader;
+import mde.mergetag.Writer;
 import mde.resource.paths;
 
 import tango.util.log.Log : Log, Logger;
@@ -56,7 +57,7 @@
     //BEGIN Methods for external use
     //BEGIN Loading code
     /** Load all windows from the file gui. */
-    void load(char[] fileName) {
+    void load (char[] fileName) {
         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!)
@@ -64,7 +65,7 @@
         
         IReader reader;
         try {
-            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_ONLY, null, true);
+            reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true);
             reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) {
                 return new Window (id);
             };
@@ -77,12 +78,13 @@
         }
         
         // Get the renderer
-        char[]* p = "Renderer" in reader.dataset.header.Arg!(char[]).Arg;
+        char[]* p = RENDERER in reader.dataset.header.Arg!(char[]).Arg;
         if (p is null || *p is null) {
             logger.error ("Loading GUI aborted: no renderer specified");
             return;
         }
-        rend = createRenderer (*p);
+        rendName = *p;
+        rend = createRenderer (rendName);
         
         // get list
         windows.length = reader.dataset.sec.length; // pre-allocate
@@ -104,6 +106,32 @@
         imde.input.addMouseClickCallback(&clickEvent);
         imde.input.addMouseMotionCallback(&motionEvent);
     }
+    
+    void save (char[] fileName) {
+        mt.DataSet ds = new mt.DataSet;
+        
+        // Add header:
+        ds.header = new mt.DefaultData;
+        ds.header.Arg!(char[]).Arg[RENDERER] = rendName;
+        
+        // Add windows to be saved:
+        foreach (window; windows)
+            ds.sec [window.name] = window;
+        
+        try {   // Save
+            IWriter writer;
+            writer = confDir.makeMTWriter (fileName, ds);
+            writer.write;
+        } catch (mt.MTException e) {
+            logger.error ("Saving GUI failed:");
+            logger.error (e.msg);
+            
+            return;
+        }
+    }
+    private static const {
+        auto RENDERER = "Renderer";
+    }
     //END Loading code
     
     /** Draw each window.
@@ -171,7 +199,10 @@
     
 private:
     Window[] windows;   // Windows. First window is "on top", others may be obscured.
-    IRenderer rend;
+    
+    IRenderer rend;     // Renderer (synonymous with theme)
+    char[] rendName;    // Name of renderer; for saving
+    
     // callbacks indexed by their frame pointers:
     void delegate(ushort cx, ushort cy, ubyte b, bool state) [void*] clickCallbacks;
     void delegate(ushort cx, ushort cy) [void*] motionCallbacks;
--- a/mde/gui/exception.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/exception.d	Wed May 07 13:07:03 2008 +0100
@@ -37,7 +37,7 @@
     }
 }
 
-/// Thrown when or createWidget a Widget class's this() is called with invalid data.
+/// Thrown when createWidget or a Widget class's this() is called with invalid data.
 class WidgetDataException : WindowLoadException
 {
     this () {   // Default, by Widget class's this
@@ -47,3 +47,14 @@
         super (msg);
     }
 }
+
+/// Thrown when a Widget class's adjust() is called with invalid data.
+class MutableDataException : WindowLoadException
+{
+    this () {   // Default, by Widget class's this
+        super ("Bad widget mutable data");
+    }
+    this (char[] msg) { // From createWidget
+        super (msg);
+    }
+}
--- a/mde/gui/renderer/SimpleRenderer.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/renderer/SimpleRenderer.d	Wed May 07 13:07:03 2008 +0100
@@ -29,8 +29,6 @@
 * The renderer is intended to be per-GUI. */
 class SimpleRenderer : IRenderer
 {
-    
-    
     BorderDimensions getBorder (BORDER_TYPES type) {
         BorderDimensions dims;
         with (BORDER_TYPES) with (dims) {
--- a/mde/gui/widget/Ifaces.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/widget/Ifaces.d	Wed May 07 13:07:03 2008 +0100
@@ -20,27 +20,26 @@
 import mde.gui.IGui;
 
 /** Interface for Window, allowing widgets to call some of Window's methods.
-*
-* Contains the methods in Window available for widgets to call on their root. */
+ *
+ * Contains the methods in Window available for widgets to call on their root. */
 interface IWindow : IWidget
 {
     /** Widget ID type. Each ID is unique under this window.
-    *
-    * Type is int since this is the widget data type. */
+     *
+     * Type is int since this is the widget data type. */
     alias int widgetID;
     
     /** Get a widget by ID.
-    *
-    * Returns the widget with the given ID from the Window's widget list. If the widget hasn't yet
-    * been created, creates it using the Window's widget creation data.
-    *
-    * Widgets should never be used more than once (must have a unique parent and position!), and
-    * this function is only intended for widgets to get child-widgets, hence a warning is logged
-    * if a widget is asked for more than once. */
-    //NOTE: possibly revise: parent isn't actually used any more
-    IWidget makeWidget (widgetID i, IWidget parent);
+     *
+     * Returns the widget with the given ID from the Window's widget list. If the widget hasn't yet
+     * been created, creates it using the Window's widget creation data. */
+    IWidget makeWidget (widgetID i);
+    
+    /** Add widget's saveData to the data to be saved, returning it's widgetID. */
+    widgetID addCreationData (IWidget widget);
     
     /** Get the managing Gui. */
+    //FIXME: remove and add requestRedraw to allow for only redrawing the window
     IGui gui ();
     
     /** Get the window's renderer.
@@ -51,29 +50,47 @@
 }
 
 /** Interface for widgets.
-*
-* Note that Window also implements this interface so that widgets can interact with their parent in
-* a uniform way.
-*
-* A widget is a region of a GUI window which handles rendering and user-interaction for itself
-* and is able to communicate with it's window and parent/child widgets as necessary.
-*
-* A widget's constructor should have this prototype:
-* ----------------------------------
-* this (IWindow window, IWidget parent, int[] data);
-* ----------------------------------
-* Where window is the root window (the window to which the widget belongs), parent is the parent
-* widget, and data is an array of initialisation data. The method should throw a
-* WidgetDataException (created without parameters) if the data has wrong length or is otherwise
-* invalid.
-*
-* The widget's size should be set either by it's this() method or by the first call to
-* setSize(). setSize() is called on all widgets immediately after their creation, and throwing an
-* exception at this point (but not on later calls to setSize) is an acceptible method of failure.
-*/
+ *
+ * Note that Window also implements this interface so that widgets can interact with their parent
+ * in a uniform way.
+ *
+ * A widget is a region of a GUI window which handles rendering and user-interaction for itself
+ * and is able to communicate with it's window and parent/child widgets as necessary.
+ *
+ * A widget's constructor should have this prototype:
+ * ----------------------------------
+ * this (IWindow window, int[] data);
+ * ----------------------------------
+ * Where window is the root window (the window to which the widget belongs) and data is an array of
+ * initialisation data. The method should throw a WidgetDataException (created without parameters)
+ * if the data has wrong length or is otherwise invalid.
+ *
+ * The widget's size should be set either by it's this() method or by the first call to
+ * setSize(). setSize() is called on all widgets immediately after their creation, and throwing an
+ * exception at this point (but not on later calls to setSize) is an acceptible method of failure.
+ */
 //NOTE: add another this() without the data for default initialization, for the GUI editor?
 interface IWidget
 {
+//BEGIN Load and save
+    /** Called after creating widgets to adjust size & other mutable attributes from saved data.
+     *
+     * Each widget should call adjust on each of its sub-widgets in turn with data, each time
+     * replacing data by the return value of the call. It should then take its own mutable data
+     * from the beginning of the array and return the remainder of the array.
+     *
+     * Throws: on error, throw a MutableDataException. */
+    int[] adjust (int[] data);
+    
+    /** Output data suitible for recreating the widget (data to be passed to this()). */
+    int[] getCreationData ();
+    
+    /** Output data containing the widget's current adjustments (data to be passed to adjust()).
+     * Should be a concatenation of each sub-widget's mutable data and the widget's own. */
+    int[] getMutableData ();
+//END Load and save
+    
+//BEGIN Size and position
     /** is the width / height resizable?
      *
      * If not, the widget has fixed dimensions equal the output of getMinimalSize. */
@@ -89,6 +106,10 @@
     
     /** Used to adjust the size.
      *
+     * setPosition should always be called after setSize (for layout widgets). Adding this
+     * restriction appears to be the most efficient approach without a lot more tests.
+     *
+     * Implementation:
      * The size should be clamped to the widget's minimal size, i.e. the size set may be larger
      * than that given by the parameters. Conversely, the size should not be reduced to the
      * widget's maximal size (if any) but expanded as necessary (alignment to be implemented).
@@ -98,8 +119,9 @@
     
     /** Set the current position (i.e. called on init and move). */
     void setPosition (int x, int y);
+//END Size and position
     
-    
+//BEGIN Events
     /** Recursively scan the widget tree to find the widget under (x,y).
      *
      * If called on a widget, that widget should assume the location is over itself, and so should
@@ -118,7 +140,7 @@
      *
      * Widget may assume coordinates are on the widget (caller must check). */
     void clickEvent (ushort cx, ushort cy, ubyte b, bool state);
-    
+//END Events
     
     /** Draw, using the stored values of x and y.
      *
--- a/mde/gui/widget/Widget.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/widget/Widget.d	Wed May 07 13:07:03 2008 +0100
@@ -29,6 +29,28 @@
 * IWidget); they are simply provided for convenience and to promote code reuse. */
 abstract class Widget : IWidget
 {
+    // Base this(); all widgets must at least check data.length is correct.
+    this (IWindow wind, int[] data) {
+        window = wind;
+        widgetType = data[0];
+    }
+    
+    // Most widgets don't need to do adjustments based on mutable data, however they usually do
+    // still need to set their size.
+    int[] adjust (int[] data) {
+        setSize (0,0);
+        return data;
+    }
+    
+    // Widget type should always be the first value.
+    int[] getCreationData () {
+        return [widgetType];
+    }
+    // Most widgets don't use mutable data.
+    int[] getMutableData () {
+        return [];
+    }
+    
     void getCurrentSize (out int cw, out int ch) {
         cw = w;
         ch = h;
@@ -54,12 +76,23 @@
     }
     
 protected:
+    final int widgetType;   // the type (stored for saving)
     IWindow window;         // the enclosing window
     int x, y;               // position
     int w, h;               // size
 }
 /** A base for fixed-size widgets. */
 class FixedWidget : Widget {
+    this (IWindow wind, int[] data) {
+        super (wind, data);
+        w = wF = data[1];
+        h = hF = data[2];
+    }
+    
+    int[] getCreationData () {
+        return [widgetType, wF, hF];
+    }
+    
     bool isWSizable () {    return false;   }
     bool isHSizable () {    return false;   }
     
@@ -80,6 +113,10 @@
 }
 /** A base for resizable widgets. */
 class SizableWidget : Widget {
+    this (IWindow wind, int[] data) {
+        super (wind, data);
+    }
+    
     bool isWSizable () {    return true;    }
     bool isHSizable () {    return true;    }
     
@@ -97,23 +134,18 @@
 /// A fixed-size blank widget.
 class FixedBlankWidget : FixedWidget
 {
-    this (IWindow wind, IWidget, int[] data) {
-        if (data.length != 2) throw new WidgetDataException;
-        
-        window = wind;
-        
-        w = wF = data[0];
-        h = hF = data[1];
+    this (IWindow wind, int[] data) {
+        if (data.length != 3) throw new WidgetDataException;
+        super (wind, data);
     }
 }
 
 /// A completely resizable blank widget (initial size zero).
 class SizableBlankWidget : SizableWidget
 {
-    this (IWindow wind, IWidget, int[] data) {
-        if (data.length != 0) throw new WidgetDataException;
-        
-        window = wind;
+    this (IWindow wind, int[] data) {
+        if (data.length != 1) throw new WidgetDataException;
+        super (wind, data);
     }
 }
 
@@ -124,13 +156,9 @@
     // pushed is not the same as the button being clicked but not yet released.
     // it is whether the mouse is over the button after being clicked.
     
-    this (IWindow wind, IWidget, int[] data) {
-        if (data.length != 2) throw new WidgetDataException;
-        
-        window = wind;
-        
-        w = wF = data[0];
-        h = hF = data[1];
+    this (IWindow wind, int[] data) {
+        if (data.length != 3) throw new WidgetDataException;
+        super (wind, data);
     }
     
     void draw () {
--- a/mde/gui/widget/Window.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/widget/Window.d	Wed May 07 13:07:03 2008 +0100
@@ -25,8 +25,7 @@
 
 import mt = mde.mergetag.DataSet;
 import tango.scrapple.text.convert.parseTo : parseTo;
-// not yet implemented:
-//import tango.scrapple.text.convert.parseFrom : parseFrom;
+import tango.scrapple.text.convert.parseFrom : parseFrom;
 
 import tango.util.log.Log : Log, Logger;
 
@@ -59,20 +58,23 @@
         assert (gui !is null, "Window.finalise ("~name~"): gui is null");
     } body {
         // Check data was loaded:
-        if (widgetData is null) throw new WindowLoadException ("No widget data");
+        if (widgetData is null || mutableData is null)
+            throw new WindowLoadException ("No widget/mutable data");
         
         gui_ = gui;
         rend = gui.renderer;
         
-        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
-        widget = makeWidget (0, this);  // primary widget always has ID 0.
-        widgetData = null;  // data is no longer needed: allow GC to collect (cannot safely delete)
-        
         // Get border sizes
         border = rend.getBorder (BORDER_TYPES.WINDOW_TOTAL);
         resize = rend.getBorder (BORDER_TYPES.WINDOW_RESIZE);
         
-        widget.setSize (0,0);           // set the minimal size
+        // Create the primary widget (and indirectly all sub-widgets), throwing on error:
+        widget = makeWidget (0);    // primary widget always has ID 0.
+        widgetData = null;  // data is no longer needed: allow GC to collect (cannot safely delete)
+        
+        widget.adjust (mutableData);    // adjust/set size, etc.
+        mutableData = null;             // no longer needed
+        
         widget.getCurrentSize (w,h);    // and get this size
         w += border.l + border.r;       // Adjust for border
         h += border.t + border.b;
@@ -86,19 +88,46 @@
     }
     //BEGIN Mergetag code
     void addTag (char[] tp, mt.ID id, char[] dt) {
-        if (tp == "int[][int]") {
-            if (id == "widgetData") {
+        // Priority is HIGH_LOW, so don't overwrite data which has already been loaded.
+        if (tp == INTAA) {
+            if (id == WDGD && widgetData == null) {
                 widgetData = cast(int[][widgetID]) parseTo!(int[][int]) (dt);
             }
-        } else if (tp == "int") {
-            if (id == "x") {
+        } else if (tp == INTA) {
+            if (id == MD && mutableData == null) {
+                mutableData = parseTo!(int[]) (dt);
+            }
+        } else if (tp == INT) {
+            if (id == X && x == -1) {
                 x = parseTo!(int) (dt);
-            } else if (id == "y") {
+            } else if (id == Y && y == -1) {
                 y = parseTo!(int) (dt);
             }
         }
     }
-    void writeAll (ItemDelg dlg) {
+    void writeAll (ItemDelg dlg)
+    in {
+        assert (widgetData is null, "Window.writeAll: widgetData !is null");
+    } body {
+        /+ NOTE: currently editing is impossible...
+        if (edited) {   // only save the widget creation data if it's been adjusted:
+            addSaveData (widget);       // generate widget save data
+            dlg (INTAA, WDGD, parseFrom!(int[][int]) (widgetData));
+        }+/
+        // Save mutable data:
+        dlg (INTA, MD, parseFrom!(int[]) (widget.getMutableData));
+        // Save the window position:
+        dlg (INT, X, parseFrom!(int) (x));
+        dlg (INT, Y, parseFrom!(int) (y));
+    }
+    private static const {
+        auto INTAA = "int[][int]";
+        auto INTA = "int[]";
+        auto INT = "int";
+        auto WDGD = "widgetData";
+        auto MD = "mutableData";
+        auto X = "x";
+        auto Y = "y";
     }
     //END Mergetag code
     //END Methods for GUI
@@ -107,7 +136,7 @@
     /** Get/create a widget by ID.
      *
      * Should $(I only) be called internally and by sub-widgets! */
-    IWidget makeWidget (widgetID i, IWidget parent)
+    IWidget makeWidget (widgetID i)
     in {
         // widgetData is normally left to be garbage collected after widgets have been created:
         assert (widgetData !is null, "Window.makeWidget ("~name~"): widgetData is null");
@@ -120,11 +149,24 @@
             throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found");
         
         // Throws WidgetDataException (a WindowLoadException) if bad data:
-        IWidget widg = createWidget (this, parent, *d);
-        widgets ~= widg;
-        return widg;
+        return createWidget (this, *d);
     }
     
+    /** Add this widget's data to that to be saved, returning it's widgetID. */
+    widgetID addCreationData (IWidget widget)
+    {
+        widgetID i;
+        if (widgetData is null)
+            i = 0;
+        else
+            i = widgetData.keys[$-1] + 1;
+        
+        widgetData[i] = null;   // Make sure the same ID doesn't get used by a recursive call
+        widgetData[i] = widget.getCreationData;
+        
+        return i;
+    }
+
     IGui gui () {
         return gui_;
     }
@@ -135,6 +177,17 @@
     //END IWindow methods
     
     //BEGIN IWidget methods
+    //FIXME: how many of these methods are actually needed/used?
+    int[] adjust (int[]) {          // simply not relevant (never used)
+        return [];
+    }
+    int[] getCreationData () {      // simply not relevant (never used)
+        return [];
+    }
+    int[] getMutableData () {       // simply not relevant (never used)
+        return [];
+    }
+    
     bool isWSizable () {
         return widget.isWSizable;
     }
@@ -200,25 +253,32 @@
                 /* check for resizes (different to above; use whole border giving larger area for
                  * diagonal resizes). */
                 resizeType = RESIZE.NONE;
-                if (cx < x + border.l) {
-                    xDrag = w + cx;
-                    resizeType = RESIZE.L;
-                }
-                else if (cx >= xw - border.r) {
-                    xDrag = w - cx;
-                    resizeType = RESIZE.R;
+                
+                if (isWSizable) {
+                    if (cx < x + border.l) {
+                        xDrag = w + cx;
+                        resizeType = RESIZE.L;
+                    }
+                    else if (cx >= xw - border.r) {
+                        xDrag = w - cx;
+                        resizeType = RESIZE.R;
+                    }
                 }
-                if (cy < y + border.t) {
-                    yDrag = h + cy;
-                    resizeType |= RESIZE.T;
-                }
-                else if (cy >= yh - border.b) {
-                    yDrag = h - cy;
-                    resizeType |= RESIZE.B;
+                if (isHSizable) {
+                    if (cy < y + border.t) {
+                        yDrag = h + cy;
+                        resizeType |= RESIZE.T;
+                    }
+                    else if (cy >= yh - border.b) {
+                        yDrag = h - cy;
+                        resizeType |= RESIZE.B;
+                    }
                 }
                 
-                gui_.addClickCallback (&endCallback);
-                gui_.addMotionCallback (&resizeCallback);
+                if (resizeType != RESIZE.NONE) {    // only if some valid size is being done
+                    gui_.addClickCallback (&endCallback);
+                    gui_.addMotionCallback (&resizeCallback);
+                }
             } else {                // window is being moved
                 xDrag = cx - x;
                 yDrag = cy - y;
@@ -252,22 +312,26 @@
             getMinimalSize (mw, nw);    // (only want mw)
             nw = xDrag - cx;
             if (nw < mw) nw = mw;       // clamp
-            setPosition (x + w - nw, y);
+            mw = x + w - nw;            // reuse
             setSize (nw, h);
+            setPosition (mw, y);
         }
         else if (resizeType & RESIZE.R) {
             setSize (xDrag + cx, h);
+            setPosition (x, y);
         }
         if (resizeType & RESIZE.T) {
             int mh, nh;
-            getMinimalSize (nh, mh);    // (only want mh)
+            getMinimalSize (nh, mh);
             nh = yDrag - cy;
-            if (nh < mh) nh = mh;       // clamp
-            setPosition (x, y + h - nh);
+            if (nh < mh) nh = mh;
+            mh = y + h - nh;
             setSize (w, nh);
+            setPosition (x, mh);
         }
         else if (resizeType & RESIZE.B) {
             setSize (w, yDrag + cy);
+            setPosition (x, y);
         }
     }
     void endCallback (ushort cx, ushort cy, ubyte b, bool state) {
@@ -284,16 +348,19 @@
     RESIZE resizeType;              // Type of current resize
     //END Window moving and resizing
     
-    char[] name;                    // The window's name (id from config file)
-    IGui gui_;                      // The gui managing this window
+    // Load/save data:
+    public char[] name;             // The window's name (id from config file)
+    //bool edited = false;            // True if any widgets have been edited (excluding scaling)
+    // Data used for saving and loading (null in between):
+    int[][widgetID] widgetData = null;// Data for all widgets under this window
+    int[] mutableData = null;       // Widget's mutable data (adjusted sizes, etc.)
     
-    int[][widgetID] widgetData;     // Data for all widgets under this window (deleted after loading)
-    IWidget[] widgets;              // List of all widgets under this window (created on demand). Use for saving?
+    IGui gui_;                      // The gui managing this window
+    IRenderer rend;                 // The window's renderer
+    
     IWidget widget;                 // The primary widget in this window.
     
-    IRenderer rend;                 // The window's renderer
-    // FIXME: revise which parameters are stored once Gui knows window position
-    int x,y;                        // Window position
+    int x = -1, y = -1;             // Window position
     int w,h;                        // Window size (calculated from Widgets)
     int xw, yh;                     // x+w, y+h (frequent use by clickEvent)
     int widgetX, widgetY;           // Widget position (= window position plus BORDER_WIDTH)
--- a/mde/gui/widget/createWidget.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/widget/createWidget.d	Wed May 07 13:07:03 2008 +0100
@@ -24,14 +24,13 @@
 
 /** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation
 * data [1..$]. */
-IWidget createWidget (IWindow window, IWidget parent, int[] data)
+IWidget createWidget (IWindow window, int[] data)
 in {
     assert (window !is null, "createWidget: window is null");
-    assert (parent !is null, "createWidget: parent is null");
 } body {
     if (data.length < 1) throw new WidgetDataException ("No widget data");
     int type = data[0];     // type is first element of data
-    data = data[1..$];      // the rest is passed to the Widget
+    // the whole of data is passed to the Widget
     
     mixin (binarySearch ("type", WIDGETS));
     throw new WidgetDataException ("Bad widget type");
@@ -100,7 +99,7 @@
         ret ~= indent(indents);
         foreach (c; consts) {
             ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~
-                    indent(indents+1) ~ "return new " ~ c ~ "Widget (window, parent, data);\n" ~
+                    indent(indents+1) ~ "return new " ~ c ~ "Widget (window, data);\n" ~
                     indent(indents) ~ "} else ";
         }
         ret = ret[0..$-6] ~ '\n';  // remove last else
--- a/mde/gui/widget/layout.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/gui/widget/layout.d	Wed May 07 13:07:03 2008 +0100
@@ -17,34 +17,74 @@
 module mde.gui.widget.layout;
 
 import mde.gui.widget.Widget;
-import mde.gui.exception : WidgetDataException;
+import mde.gui.exception;
 
 /** Encapsulates a grid of Widgets.
 *
 * Since a grid with either dimension zero is not useful, there must be at least one sub-widget. */
 class GridLayoutWidget : Widget
 {
-    this (IWindow wind, IWidget, int[] data) {
+    this (IWindow wind, int[] data) {
         // Get grid size and check data
         // Check sufficient data for rows, cols, and at least one widget:
-        if (data.length < 3) throw new WidgetDataException;
-        rows = data[0];
-        cols = data[1];
-        if (data.length != 2 + rows * cols) throw new WidgetDataException;
+        if (data.length < 4) throw new WidgetDataException;
+        super (wind, data);
+        
+        rows = data[1];
+        cols = data[2];
+        if (data.length != 3 + rows * cols) throw new WidgetDataException;
         /* data.length >= 3 so besides checking the length is correct, this tells us:
-         *      rows * cols >= 3 - 2 = 1            a free check!
+         *      rows * cols >= 4 - 3 = 1            a free check!
          * The only thing not checked is whether both rows and cols are negative, which would
          * cause an exception when dynamic arrays are allocated later (and is unlikely). */
         
-        window = wind;
-        
         // Get all sub-widgets
         subWidgets.length = rows*cols;
         foreach (i, inout subWidget; subWidgets) {
-            subWidget = window.makeWidget (data[i+2], this);
+            subWidget = window.makeWidget (data[i+3]);
         }
     }
     
+    int[] adjust (int[] data) {
+        // Give all sub-widgets their data:
+        foreach (widget; subWidgets)
+            data = widget.adjust (data);
+        if (data.length < rows + cols) throw new MutableDataException;
+        
+        /* We basically short-cut setSize by loading previous col/row sizes and doing the final
+         * calculations. There isn't checks that the data is valid/up-to-date... worst case is too
+         * small overlapping widgets or huge ones?
+         * Note: if setSize gets called afterwards, it should have same dimensions and so not do
+         * anything. */
+        colW = data[0..cols];
+        rowH = data[cols..rows+cols];
+        setColRowSizes;
+        w = colW[$-1] + colX[$-1];
+        h = rowY[$-1] + rowH[$-1];
+        
+        return data[rows+cols..$];
+    }
+    
+    int[] getCreationData () {
+        int[] ret;
+        ret.length = 3 + subWidgets.length;
+        
+        ret [0..3] = [widgetType, rows, cols];  // first data
+        
+        foreach (i,widget; subWidgets)          // sub widgets
+            ret[i+3] = window.addCreationData (widget);
+        
+        return ret;
+    }
+    int[] getMutableData () {
+        int[] ret;
+        foreach (widget; subWidgets)
+            ret ~= widget.getMutableData;
+        
+        ret ~= colW ~ rowH;
+        return ret;
+    }
+    
     bool isWSizable () {
         if (colSizable == -2) {     // check whether any columns are resizable
             for1:
@@ -108,6 +148,19 @@
     }
     
     void setSize (int nw, int nh) {
+        /* For each of width and height, there are several cases:
+         *  [new value is] more than old value
+         *  ->  enlarge any row/column
+         *  same as old value
+         *  ->  do nothing
+         *  more than min but less than current value
+         *  ->  find an enlarged row/col and reduce size
+         *  ->  repeat if necessary
+         *  minimal value or less
+         *  -> clamp to min and use min col/row sizes
+         */
+        // FIXME: implement!
+        
         // Step 1: calculate the minimal row/column sizes.
         alias w mw;             // no need for extra vars, just use these
         alias h mh;
@@ -132,34 +185,15 @@
                 rowH[$-1] += nh - mh;
         }
         
-        // Step 3: set each sub-widget's size.
-        foreach (i,widget; subWidgets)
-            widget.setSize (colW[i % cols], rowH[i / cols]);
-        
         w = nw;
         h = nh;
         
+        // Step 3: set each sub-widget's size.
         // Step 4: calculate the column and row positions
-        colX.length = cols;
-        rowY.length = rows;
-        int spacing = window.renderer.layoutSpacing;
-        
-        int cum = 0;
-        foreach (i, x; rowH) {
-            rowY[i] = cum;
-            cum += x + spacing;
-        }
-        
-        cum = 0;
-        foreach (i, x; colW) {
-            colX[i] = cum;
-            cum += x + spacing;
-        }
+        setColRowSizes;
         
         // Step 5: position needs resetting
-        // FIXME: find a more efficient method of doing this?
-        // maybe setPosition should ALWAYS be called after setSize?
-        setPosition (x,y);
+        // Currently this happens by specifying that setPosition should be run after setSize.
     }
     
     void setPosition (int x, int y) {
@@ -228,6 +262,29 @@
         }
     }
     
+    void setColRowSizes () {
+        // Calculate column and row locations:
+        colX.length = cols;
+        rowY.length = rows;
+        int spacing = window.renderer.layoutSpacing;
+        
+        int cum = 0;
+        foreach (i, x; rowH) {
+            rowY[i] = cum;
+            cum += x + spacing;
+        }
+        
+        cum = 0;
+        foreach (i, x; colW) {
+            colX[i] = cum;
+            cum += x + spacing;
+        }
+        
+        // Tell subwidgets their new sizes:
+        foreach (i,widget; subWidgets)
+            widget.setSize (colW[i % cols], rowH[i / cols]);
+    }
+    
 protected:
     int cols, rows;     // number of cells in grid
     
--- a/mde/scheduler/Scheduler.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/scheduler/Scheduler.d	Wed May 07 13:07:03 2008 +0100
@@ -119,10 +119,12 @@
     /** Generate an ID. All generated IDs are >= 0xF000_0000 to provide plenty of room for other
     * IDs. */
     ID getNewID () {
-        // For now use a very simple method to find a vacant ID: iterate
-        for (ID i = 0xF000_0000; i < ID.max; ++i)
-            if ((i in funcs) is null)
-                return i;
+        if (funcs.length == 0) return 0xF000_0000;  // otherwise would get an out-of-bounds error
+        // Take the last used ID and add one, making sure it's at least 0xF000_0000.
+        // Don't bother checking if it's out of bounds since there's 2^28 available IDs.
+        ID i = funcs.keys[$-1] + 1;
+        if (i < 0xF000_0000) i = 0xF000_0000;
+        return i;
     }
     
     /** This function should get called by the main loop, once per frame.
--- a/mde/scheduler/init2.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/scheduler/init2.d	Wed May 07 13:07:03 2008 +0100
@@ -50,12 +50,22 @@
 
 void guiLoad () {   // init func
     try {
-        gui.load ("gui");
+        gui.load (GUI);
+        cleanup.addFunc (&guiSave, "guiSave");
     } catch (Exception e) {
         logger.fatal ("guiLoad failed: " ~ e.msg);
         setInitFailure;
     }
 }
+void guiSave () {   // cleanup func
+    try {
+        gui.save (GUI);
+    } catch (Exception e) {
+        logger.fatal ("guiSave failed: " ~ e.msg);
+        setInitFailure;
+    }
+}
+private const GUI = "gui";
 
 void initInput () { // init func
     try {
--- a/mde/scheduler/initFunctions.d	Mon May 05 17:02:21 2008 +0100
+++ b/mde/scheduler/initFunctions.d	Wed May 07 13:07:03 2008 +0100
@@ -65,6 +65,8 @@
 }
 
 InitStage init;     // all functions called during init (all should be thread-safe)
+//FIXME: implement:
+InitStage save;     // all functions to be called to save data (possible to run more than once)
 InitStage cleanup;  // all functions called during cleanup (all should be thread-safe)
 
 package: