changeset 121:5b37d0400732

Widgets now receive and store their parent (IParentWidget). Infinite widget recursion checks. WidgetManager code redistributed. WidgetManager code redistributed between classes; WMScreen class moved to WMScreen.d. addContent function now calls makeWidget with another id.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 02 Jan 2009 18:07:10 +0000
parents 46c63cb1c74f
children f96e8d18c00a
files data/conf/guiDemo.mtt examples/guiDemo.d mde/gui/WidgetManager.d mde/gui/widget/Floating.d mde/gui/widget/Ifaces.d mde/gui/widget/PopupMenu.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/gui/widget/miscContent.d mde/gui/widget/miscWidgets.d
diffstat 12 files changed, 421 insertions(+), 633 deletions(-) [+]
line wrap: on
line diff
--- a/data/conf/guiDemo.mtt	Thu Jan 01 15:16:00 2009 +0000
+++ b/data/conf/guiDemo.mtt	Fri Jan 02 18:07:10 2009 +0000
@@ -2,17 +2,19 @@
 <char[]|Renderer="Simple">
 <char[]|Design="Working">
 {Working}
-<WidgetData|root={0:[0x4100,0,3,1],1:["bar","opts","bar"]}>
+<WidgetData|root={0:[0x4100,0,3,1],1:["bar","optaC","bar"]}>
 <WidgetData|bar={0:[0x4100,0,1,3],1:["menu","blank","menu"]}>
-<WidgetData|menu={0:[0x2031,0x4011,0],1:["imde.menu","menu0"]}>
-<WidgetData|menu0={0:[0xE032,0],1:["menu1"]}>
-<WidgetData|menu1={0:[0x6033],1:["menu2"]}>
-<WidgetData|menu2={0:[0xE032,0],1:["menu2"]}>
+<WidgetData|menu={0:[0x2031],1:["imde.menu","menu0"]}>
+<WidgetData|menu0={0:[0x4011,0],1:["menu1"]}>
+<WidgetData|menu1={0:[0xE032,0],1:["menu2"]}>
+<WidgetData|menu2={0:[0x6033],1:["menu3"]}>
+<WidgetData|menu3={0:[0xE032,0],1:["menu3"]}>
 <WidgetData|blank={0:[0x2]}>
-<WidgetData|opts={0:[0x2031,0x4100,4,2,1],1:["Options","optName","optSecs"]}>
-<WidgetData|optSecs={0:[0xE030,4],1:["optSec"]}>
+<WidgetData|optaC={0:[0x2031],1:["Options","opts"]}>
+<WidgetData|opts={0:[0x4100,4,2,1],1:["optName","optSecs"]}>
+<WidgetData|optSecs={0:[0xC110,4],1:["optSec"]}>
 <WidgetData|optSec={0:[0x4100,0,2,1],1:["optName","optVars"]}>
-<WidgetData|optVars={0:[0xE030,0],1:["optDBox"]}>
+<WidgetData|optVars={0:[0xC110,0],1:["optDBox"]}>
 <WidgetData|optDBox={0:[0x4100,1,2,1],1:["optBox","optDesc"]}>
 <WidgetData|optBox={0:[0x4100,1,1,3],1:["optName","optSep","optVal"]}>
 <WidgetData|optName={0:[0x4020, 1, 0xffffff]}>
--- a/examples/guiDemo.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/examples/guiDemo.d	Fri Jan 02 18:07:10 2009 +0000
@@ -23,7 +23,7 @@
 import mde.scheduler.Scheduler;         // mainSchedule
 import mde.setup.Screen;                // Screen.draw()
 import mde.setup.InitStage;             // StageState
-import mde.gui.WidgetManager;
+import mde.gui.WMScreen;
 
 import tango.core.Thread : Thread;	// Thread.sleep()
 import tango.time.Clock;                // Clock.now()
@@ -43,7 +43,7 @@
     }
     
     // Set up the gui
-    scope WidgetManager gui = new WidgetManager ("guiDemo");
+    scope WMScreen gui = new WMScreen ("guiDemo");
     StageState guiLoad () {   // init func
         gui.init;
         gui.loadDesign();
--- a/mde/gui/WidgetManager.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/WidgetManager.d	Fri Jan 02 18:07:10 2009 +0000
@@ -14,27 +14,33 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 /*************************************************************************************************
- * The gui manager class.
+ * The gui manager class base.
  *
- * This is the module to use externally to create a graphical user interface (likely also with
- * content modules).
+ * This contains most of the code required by a window manager, but does not interact with a screen
+ * or get user input. Rendering is handled separately by the renderer anyway.
  *************************************************************************************************/
 module mde.gui.WidgetManager;
 
 import mde.gui.WidgetDataSet;
 import mde.gui.widget.Ifaces;
-import mde.gui.renderer.createRenderer;
+import mde.gui.exception;
+import mde.content.Content;
 
 import imde = mde.imde;
-import mde.input.Input;
-import mde.scheduler.Scheduler;
-import mde.setup.Screen;
-import Items = mde.content.Items;	// loadTranslation
-import mde.lookup.Options;	// miscOpts.L10n callback
+import mde.file.mergetag.Reader;
+import mde.file.mergetag.Writer;
+import mde.setup.paths;
+
+// Widgets to create:
+import mde.gui.widget.layout;
+import mde.gui.widget.miscWidgets;
+import mde.gui.widget.TextWidget;
+import mde.gui.widget.miscContent;
+import mde.gui.widget.Floating;
+import mde.gui.widget.PopupMenu;
 
 import tango.core.sync.Mutex;
 import tango.util.log.Log : Log, Logger;
-import tango.util.container.CircularList;	// pop-up draw callbacks
 import tango.util.container.SortedMap;
 
 private Logger logger;
@@ -43,283 +49,12 @@
 }
 
 /*************************************************************************************************
- * The widget manager.
- * 
- * This provides a layer on top of WidgetLoader, handling input and rendering. Other functionality
- * is contained in the super class, to simplify supporting new input/graphics libraries.
- * 
- * Currently mouse coordinates are passed to widgets untranslated. It may make sense to translate
- * them and possibly drop events for some uses, such as if the gui is drawn to a texture.
- * 
- * Aside from the IWidgetManager methods, this class should be thread-safe.
- *************************************************************************************************/
-class WidgetManager : WidgetLoader, Screen.IDrawable {
-    /** Construct a new widget manager.
-     * 
-     * params:
-     *  fileName = Name of file specifying the gui, excluding path and extension.
-     */
-    this (char[] file) {
-        super(file);
-        
-        Screen.addDrawable (this);
-	clickCallbacks = new typeof(clickCallbacks);
-	motionCallbacks = new typeof(motionCallbacks);
-    }
-    
-    // this() runs during static this(), when imde.input doesn't exist. init() runs later.
-    void init () {
-        // Doesn't need a lock - cannot conflict with other class functions.
-        // Events we want to know about:
-        imde.input.addMouseClickCallback(&clickEvent);
-        imde.input.addMouseMotionCallback(&motionEvent);
-	
-	Items.loadTranslation ();
-	miscOpts.L10n.addCallback (&reloadStrings);
-    }
-    
-    
-    /** Draw the gui. */
-    void draw() {
-        synchronized(mutex) {
-            if (child)
-                child.draw;
-	    foreach (popup; popups)
-		popup.widget.draw();
-	}
-    }
-    
-    
-    /** For mouse click events.
-     *
-     * Sends the event on to the relevant windows and all click callbacks. */
-    void clickEvent (ushort usx, ushort usy, ubyte b, bool state) {
-        debug scope (failure)
-            logger.warn ("clickEvent: failed!");
-        mutex.lock;
-        scope(exit) mutex.unlock;
-        if (child is null) return;
-        
-        wdabs cx = cast(wdabs) usx, cy = cast(wdabs) usy;
-        
-        // 1. Callbacks have the highest priority recieving events (e.g. a button release)
-        foreach (dg; clickCallbacks)
-            if (dg (cx, cy, b, state)) return;
-        
-        // 2. Then pop-ups: close from top, depending on click pos
-        // Note: assumes each evaluated popup's parent is not under another still open popup.
-        // Also assumes popup's parent doesn't have other children in its box.
-        size_t removeTo = popups.length;
-        bool eventDone;		// don't pass clickEvent
-        IChildWidget widg;	// widget clicked on
-        foreach_reverse (i,popup; popups) with (popup) {
-            if (cx < x || cx >= x + w ||
-                cy < y || cy >= y + h) {	// on popup
-                if (parent.onSelf (cx, cy)) {
-                    if (parent.popupParentClick()) removeTo = i;
-                    eventDone = true;
-                    break;
-                } else {
-                    removeTo = i;
-                    parent.popupClose;
-                }
-            } else {
-                widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy);
-                break;
-            }
-        }
-        if (removeTo < popups.length) {
-            requestRedraw;
-            popups = popups[0..removeTo];
-        }
-        if (eventDone)
-            return;
-        
-        // 3. Then the main widget tree
-        debug assert (cx < child.width && cy < child.height, "WidgetManager: child doesn't cover whole area (code error)");
-        if (widg is null)
-            widg = child.getWidget (cast(wdabs)cx,cast(wdabs)cy);
-	if (keyFocus && keyFocus !is widg) {
-	    keyFocus.keyFocusLost;
-	    keyFocus = null;
-	    imde.input.setLetterCallback (null);
-	}
-        if (widg !is null) {
-	    if (widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state) & 1) {
-		keyFocus = widg;
-		imde.input.setLetterCallback (&widg.keyEvent);
-	    }
-	}
-    }
-    
-    /** For mouse motion events.
-     *
-     * Sends the event on to all motion callbacks. */
-    void motionEvent (ushort scx, ushort scy) {
-        debug scope (failure)
-                logger.warn ("motionEvent: failed!");
-        mutex.lock;
-        scope(exit) mutex.unlock;
-	wdabs cx = cast(wdabs) scx, cy = cast(wdabs) scy;
-        foreach (dg; motionCallbacks)
-            dg (cx, cy);
-	
-	IChildWidget ohighlighted = highlighted;
-	foreach_reverse (popup; popups) with (popup) {
-	    if (cx >= x && cx < x+w && cy >= y && cy < y+h) {
-		highlighted = widget.getWidget (cx,cy);
-		goto foundPopup;
-	    }
-	}
-	highlighted = null;	// not over a popup
-	foundPopup:
-	if (ohighlighted != highlighted) {
-	    if (ohighlighted)
-		ohighlighted.highlight (false);
-	    if (highlighted)
-		highlighted.highlight (true);
-	    requestRedraw;
-	}
-    }
-    
-    
-    void sizeEvent (int nw, int nh) {   // IDrawable function
-        mutex.lock;
-        scope(exit) mutex.unlock;
-        
-        w = cast(wdim) nw;
-        h = cast(wdim) nh;
-        
-        if (w < mw || h < mh)
-            logger.warn ("Minimal dimensions ({},{}) not met: ({},{}), but I cannot resize myself!",mw,mh,w,h);
-        
-        if (!child) return;     // if not created yet.
-        child.setWidth  (w, -1);
-        child.setHeight (h, -1);
-        child.setPosition (0,0);
-    }
-    
-    //BEGIN IWidgetManager methods
-    // These methods are only intended for use within the gui package. They are not necessarily
-    // thread-safe.
-    IRenderer renderer () {
-        assert (rend !is null, "WidgetManager.renderer: rend is null");
-        return rend;
-    }
-    
-    void addPopup (IChildWidget parnt, IChildWidget widg, int flags = 0) {
-	debug assert (parnt && widg, "addPopup: null widget");
-        if (popups.length >= popupsMem.length)
-            popupsMem.length = popupsMem.length * 2 + 2;
-        with (popupsMem[popups.length]) {
-            parent = parnt;
-            widget = widg;
-	    w = widg.width;
-	    h = widg.height;
-            if (flags & 1) {
-	    	y = parent.yPos;
-            	if (y+h > this.h) y += parent.height - h;
-	    	x = parent.xPos + parent.width;
-	    	if (x+w > this.w) x = parent.xPos - w;
-            } else {
-                x = parent.xPos;				// align on left edge
-                if (x+w > this.w) x += parent.width - w;	// align on right edge
-                y = parent.yPos + parent.height;		// place below
-                if (y+h > this.h) y = parent.yPos - h;		// place above
-            }
-	    widget.setPosition (x, y);
-	}
-        popups = popupsMem[0..popups.length+1];
-	requestRedraw;
-    }
-    void removePopup (IChildWidget parnt) {
-	foreach_reverse (i,popup; popups) {
-	    if (popup.parent is parnt)
-		popups = popups[0..i];
-	}
-	requestRedraw;
-    }
-
-    void requestRedraw () {
-        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
-    }
-    
-    void addClickCallback (bool delegate(wdabs, wdabs, ubyte, bool) dg) {
-        clickCallbacks[dg.ptr] = dg;
-    }
-    void addMotionCallback (void delegate(wdabs, wdabs) dg) {
-        motionCallbacks[dg.ptr] = dg;
-    }
-    void removeCallbacks (void* frame) {
-        clickCallbacks.removeKey(frame);
-        motionCallbacks.removeKey(frame);
-    }
-    //END IWidgetManager methods
-    
-protected:
-    /* Second stage of widget loading.
-     * Note: sizeEvent should be called with window size before this. */
-    final override void createRootWidget () {
-        // The renderer needs to be created on the first load, but not after this.
-        if (rend is null)
-            rend = createRenderer (rendName);
-	popups = null;
-        
-        child = makeWidget ("root");
-        child.setup (0, 3);
-        
-        mw = child.minWidth;
-        mh = child.minHeight;
-        
-        if (w < mw || h < mh)
-            logger.warn ("Minimal dimensions ({},{}) not met: ({},{}), but I cannot resize myself!",mw,mh,w,h);
-        
-        child.setWidth  (w, -1);
-        child.setHeight (h, -1);
-        child.setPosition (0,0);
-    }
-    
-    final override void preSave () {
-	if (keyFocus) {
-	    keyFocus.keyFocusLost;
-	    keyFocus = null;
-	    imde.input.setLetterCallback (null);
-	}
-    }
-    
-private:
-    struct ActivePopup {
-        IChildWidget widget;
-	IChildWidget parent;
-        wdabs x,y;
-        wdsize w,h;
-    }
-    IRenderer rend;
-    ActivePopup[] popups;	// Pop-up [menus] to draw. Last element is top popup.
-    ActivePopup[] popupsMem;	// allocated memory for popups
-    // callbacks indexed by their frame pointers. Must support removal of elements in foreach:
-    SortedMap!(void*,bool delegate(wdabs cx, wdabs cy, ubyte b, bool state)) clickCallbacks;
-    SortedMap!(void*,void delegate(wdabs cx, wdabs cy)) motionCallbacks;
-    IChildWidget keyFocus;	// widget receiving keyboard input when non-null
-    IChildWidget highlighted;	// NOTE: in some ways should be same as keyFocus
-}
-
-
-import mde.gui.exception;
-import mde.content.Content;	// Content passed to a callback
-import mde.gui.widget.createWidget;
-
-import mde.file.mergetag.Reader;
-import mde.file.mergetag.Writer;
-import mde.setup.paths;
-
-/*************************************************************************************************
  * 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 WidgetLoader : IWidgetManager
+abstract scope class AWidgetManager : IWidgetManager
 {
     /** Construct a new widget loader.
     * 
@@ -503,6 +238,110 @@
 	requestRedraw;
     }
     
+    //BEGIN IParentWidget methods
+    // If call reaches the widget manager there isn't any recursion.
+    //NOTE: should be override
+    final void recursionCheck (widgetID) {}
+    //END IParentWidget methods
+    
+    //BEGIN IWidgetManager methods
+    override IChildWidget makeWidget (IParentWidget parent, widgetID id, IContent content = null)
+    {
+        debug assert (parent, "makeWidget: parent is null (code error)");
+        debug scope (failure)
+                logger.warn ("Creating widget \""~id~"\" failed.");
+        
+        WidgetData data = curData[id];
+        if (data.ints.length < 1) {
+            logger.error ("No int data; creating a debug widget");
+            data.ints = [WIDGET_TYPE.Debug];
+        }
+        int type = data.ints[0];    // type is first element of data
+    
+        try {
+            // Statically programmed binary search on type, returning a new widget or calling a
+            // function:
+            //pragma (msg, binarySearch ("type", WIDGETS));
+            mixin (binarySearch ("type", WIDGETS));
+            // Not returned a new widget:
+            logger.error ("Bad widget type: {}; creating a debug widget instead",type);
+        } catch (Exception e) {
+            logger.error ("Error creating widget: {}; creating a debug widget instead.", e.msg);
+        }
+    
+        return new DebugWidget (this, this, id, data);
+    }
+    
+    override WidgetData widgetData (widgetID id) {
+        return curData[id];
+    }
+    override void widgetData (widgetID id, WidgetData d) {
+        changes[id] = d;		// also updates WidgetDataSet in data.
+    }
+    
+    override wdims dimData (widgetID id) {
+        return curData.dims (id);
+    }
+    override void dimData (widgetID id, wdims d) {
+        changes.setDims(id, d);		// also updates WidgetDataSet in data.
+    }
+    
+    // These methods are only intended for use within the gui package. They are not necessarily
+    // thread-safe.
+    IRenderer renderer () {
+        assert (rend !is null, "WidgetManager.renderer: rend is null");
+        return rend;
+    }
+    
+    void addPopup (IChildWidget parnt, IChildWidget widg, int flags = 0) {
+	debug assert (parnt && widg, "addPopup: null widget");
+        if (popups.length >= popupsMem.length)
+            popupsMem.length = popupsMem.length * 2 + 2;
+        with (popupsMem[popups.length]) {
+            parent = parnt;
+            widget = widg;
+	    w = widg.width;
+	    h = widg.height;
+            if (flags & 1) {
+	    	y = parent.yPos;
+            	if (y+h > this.h) y += parent.height - h;
+	    	x = parent.xPos + parent.width;
+	    	if (x+w > this.w) x = parent.xPos - w;
+            } else {
+                x = parent.xPos;				// align on left edge
+                if (x+w > this.w) x += parent.width - w;	// align on right edge
+                y = parent.yPos + parent.height;		// place below
+                if (y+h > this.h) y = parent.yPos - h;		// place above
+            }
+	    widget.setPosition (x, y);
+	}
+        popups = popupsMem[0..popups.length+1];
+	requestRedraw;
+    }
+    void removePopup (IChildWidget parnt) {
+	foreach_reverse (i,popup; popups) {
+	    if (popup.parent is parnt)
+		popups = popups[0..i];
+	}
+	requestRedraw;
+    }
+
+    void requestRedraw () {
+        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
+    }
+    
+    void addClickCallback (bool delegate(wdabs, wdabs, ubyte, bool) dg) {
+        clickCallbacks[dg.ptr] = dg;
+    }
+    void addMotionCallback (void delegate(wdabs, wdabs) dg) {
+        motionCallbacks[dg.ptr] = dg;
+    }
+    void removeCallbacks (void* frame) {
+        clickCallbacks.removeKey(frame);
+        motionCallbacks.removeKey(frame);
+    }
+    //END IWidgetManager methods
+    
 protected:
     /** Second stage of loading the widgets.
     * 
@@ -525,24 +364,116 @@
     void preSave ();
     
 public:
-    //BEGIN IWidgetManager methods
-    override IChildWidget makeWidget (widgetID id, IContent content = null) {
-        debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"');
-        return createWidget (this, id, curData[id], content);
-    }
+    //BEGIN makeWidget metacode
+private static {
+/// Widget types. Items match widget names without the "Widget" suffix.
+enum WIDGET_TYPE : int {
+    FUNCTION		= 0x2000,   // Function called instead of widget created (no "Widget" appended to fct name)
+    TAKES_CONTENT	= 0x4000,   // Flag indicates widget's this should be passed an IContent reference.
+    SAFE_RECURSION	= 0x8000,   // Safe to instantiate recursively without infinite looping.
+    
+    // Use widget names rather than usual capitals convention
+    Unnamed		= 0x0,      // Only for use by widgets not created with createWidget
+    
+    // blank: 0x1
+    FixedBlank		= 0x1,
+    SizableBlank	= 0x2,
+    Debug		= 0xF,
+    
+    // popup widgets: 0x10
+    PopupMenu		= TAKES_CONTENT | 0x11,
+    SubMenu		= TAKES_CONTENT | 0x12,
+    
+    // labels: 0x20
+    ContentLabel	= TAKES_CONTENT | 0x20,
+    TextLabel		= 0x21,
+    
+    // content functions: 0x30
+    editContent		= FUNCTION | TAKES_CONTENT | SAFE_RECURSION | 0x30,
+    addContent		= FUNCTION | 0x31,
+    flatMenuContent	= FUNCTION | TAKES_CONTENT | SAFE_RECURSION | 0x32,
+    subMenuContent	= FUNCTION | TAKES_CONTENT | 0x33,
+    
+    // content widgets: 0x40
+    DisplayContent	= TAKES_CONTENT | 0x40,
+    BoolContent		= TAKES_CONTENT | 0x41,
+    AStringContent	= TAKES_CONTENT | 0x42,
+    ButtonContent	= TAKES_CONTENT | 0x43,
+    MenuButtonContent	= TAKES_CONTENT | 0x44,
+    
+    GridLayout		= TAKES_CONTENT | 0x100,
+    ContentList		= TAKES_CONTENT | SAFE_RECURSION | 0x110,
     
-    override wdims dimData (widgetID id) {
-        return curData.dims (id);
+    FloatingArea	= 0x200,
+}
+
+// Only used for binarySearch algorithm generation; must be ordered by numerical values.
+const char[][] WIDGETS = [
+        "FixedBlank",
+        "SizableBlank",
+        "Debug",
+	"TextLabel",
+	"FloatingArea",
+	"addContent",
+	"PopupMenu",
+	"SubMenu",
+	"ContentLabel",
+        "DisplayContent",
+        "BoolContent",
+	"AStringContent",
+	"ButtonContent",
+	"MenuButtonContent",
+	"GridLayout",
+	"subMenuContent",
+	"ContentList",
+	"editContent",
+	"flatMenuContent"];
+
+/* Generates a binary search algorithm for makeWidget. */
+char[] binarySearch (char[] var, char[][] consts) {
+    if (consts.length > 3) {
+        return `if (`~var~` <= WIDGET_TYPE.`~consts[$/2 - 1]~`) {` ~
+                binarySearch (var, consts[0 .. $/2]) ~
+                `} else {` ~
+                binarySearch (var, consts[$/2 .. $]) ~
+                `}`;
+    } else {
+        char[] ret;
+        foreach (c; consts) {
+            ret ~= `if (` ~ var ~ ` == WIDGET_TYPE.` ~ c ~ `) {
+                        debug (mdeWidgets) logger.trace ("Creating new `~c~`.");
+                        if (!(WIDGET_TYPE.`~c~` & WIDGET_TYPE.SAFE_RECURSION))
+                    	  parent.recursionCheck (id);
+                        static if (WIDGET_TYPE.`~c~` & WIDGET_TYPE.FUNCTION)
+                          return `~c~` (this, parent, id, data, content);
+                        else static if (WIDGET_TYPE.`~c~` & WIDGET_TYPE.TAKES_CONTENT)
+                          return new `~c~`Widget (this, parent, id, data, content);
+                        else
+                          return new `~c~`Widget (this, parent, id, data);
+                    } else `;
+        }
+        ret = ret[0..$-6];	// remove last else
+        return ret;
     }
-    override void setData (widgetID id, WidgetData d) {
-        changes[id] = d;        // also updates WidgetDataSet in data.
+}
+
+debug { // check items in WIDGETS are listed in order
+    char[] WIDGETS_check () {
+        char[] ret;
+        for (int i = WIDGETS.length-2; i > 0; --i) {
+            ret ~= "WIDGET_TYPE."~WIDGETS[i] ~" >= WIDGET_TYPE."~ WIDGETS[i+1];
+            if (i>1) ret ~= " || ";
+        }
+        return ret;
     }
-    override void setDimData (widgetID id, wdims d) {
-        changes.setDims(id, d);    // also updates WidgetDataSet in data.
-    }
-    //END IWidgetManager methods
+    mixin ("static if ("~WIDGETS_check~")
+        static assert (false, \"WIDGETS is not in order!\");");
+}
+}
+    //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
@@ -555,10 +486,27 @@
     scope mt.DataSet changesDS;		// changes and sections from user file (used for saving)
     bool loadUserFile = true;		// still need to load user file for saving?
     
+    IRenderer rend;
+    
+    // Widgets:
     wdim w,h;				// area available to the widgets
     wdim mw,mh;				// minimal area required by widgets (ideally for limiting w,h)
     scope IChildWidget child;		// The primary widget.
     uint setupN;			// n to pass to IChildWidget.setup
     
+    struct ActivePopup {
+        IChildWidget widget;
+	IChildWidget parent;
+        wdabs x,y;
+        wdsize w,h;
+    }
+    ActivePopup[] popups;	// Pop-up [menus] to draw. Last element is top popup.
+    ActivePopup[] popupsMem;	// allocated memory for popups
+    // callbacks indexed by their frame pointers. Must support removal of elements in foreach:
+    SortedMap!(void*,bool delegate(wdabs cx, wdabs cy, ubyte b, bool state)) clickCallbacks;
+    SortedMap!(void*,void delegate(wdabs cx, wdabs cy)) motionCallbacks;
+    IChildWidget keyFocus;	// widget receiving keyboard input when non-null
+    IChildWidget highlighted;	// NOTE: in some ways should be same as keyFocus
+    
     Mutex mutex;			// lock on methods for use outside the package.
 }
--- a/mde/gui/widget/Floating.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/Floating.d	Fri Jan 02 18:07:10 2009 +0000
@@ -38,15 +38,16 @@
  */
 class FloatingAreaWidget : AParentWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data) {
         if (data.ints.length != 1 + data.strings.length)
             throw new WidgetDataException (this);
+        super (mgr, parent, id);
         
         subWidgets.length = data.strings.length;        // widgets created from string data
         sWOrder.length = subWidgets.length;
         sWData.length = subWidgets.length;
         foreach (i,s; data.strings) {
-            subWidgets[i] = mgr.makeWidget (s);
+            subWidgets[i] = mgr.makeWidget (this, s);
             sWOrder[i] = i;
             sWData[i].borderType = cast(BTYPE) data.ints[i+1];
         }
@@ -56,11 +57,10 @@
             foreach (i, ref d; sWData)
                 (&d.x)[0..4] = dd[i*4..i*4+4];
         }
-        
-        super (mgr, id, data);
     }
     
     override bool setup (uint n, uint flags) {
+        debug (mdeWidgets) logger.trace ("FloatingAreaWidget.setup");
         foreach (i, ref d; sWData) with (d) {
             auto widg = subWidgets[i];
 	    if (!widg.setup (n, flags) && n != 0 && !(flags & 1))
@@ -84,7 +84,7 @@
             dd[4*i..4*i+4] = (&d.x)[0..4];
         }
         
-        mgr.setDimData (id, dd);  // save positions
+        mgr.dimData (id, dd);	// save positions
         return true;
     }
     
--- a/mde/gui/widget/Ifaces.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/Ifaces.d	Fri Jan 02 18:07:10 2009 +0000
@@ -29,16 +29,17 @@
 
 
 /*************************************************************************************************
- * Common interface for all widgets.
+ * Interface for parent widgets, including IWidgetManager.
  *
  * Notation:
  *  Positive/negative direction: along the x/y axis in this direction.
  *  Layout widget: a widget containing multiple sub-widges (which hence controls how they are
  *  laid out).
  *************************************************************************************************/
-//NOTE: keep this?
-interface IWidget
+interface IParentWidget
 {
+    /** Checks for recursion of unsafe widgets to prevent infinite recursion. */
+    void recursionCheck (widgetID);
 }
 
 
@@ -47,29 +48,33 @@
  * 
  * This class handles widget rendering, input, loading and saving.
  *************************************************************************************************/
-interface IWidgetManager : IWidget
+interface IWidgetManager : IParentWidget
 {
     // Loading/saving:
-    /** Create a widget by ID.
+    /** Create a widget by looking up the data for id then looking up data.ints[0] in WIDGET_TYPES.
      *
      * Params:
      *  id      = Identifier, within data files, of the data for the widget.
      *  data    = Pass this data to the widget, not data looked up via id.
      *  content = An IContent may be passed to some widgets on creation.
      *
+     * When used in a this(), super() should be called before any calls to makeWidget (or at least
+     * parent and id set) due to recursionCheck being called on the widget.
+     *
      * Creates a widget, using the widget data with index id. Widget data is loaded from files,
      * and per design (multiple gui layouts, called designs, may exist; data is per design). */
-    IChildWidget makeWidget (widgetID id, IContent content = null);
-    
-    /** Get dimension data for a widget. */
-    wdims dimData (widgetID id);
+    IChildWidget makeWidget (IParentWidget parent, widgetID id, IContent content = null);
     
-    /** Record some changes, for saving. Should only be called from IWidget.saveChanges() to avoid
-     * multiple calls for instanced widgets of same id.
-     * 
-     * WidgetData is for most data, dimensional data (wdims) is for dimensions. */
-    void setData (widgetID id, WidgetData);
-    void setDimData (widgetID id, wdims d);     /// ditto
+    /** Get or set widget id's WidgetData or dimension data.
+     *
+     * WidgetData is for most data, dimensional data (wdims) is for dimensions.
+     *
+     * Data should only be set from IChildWidget.saveChanges() to
+     * avoid setting multiple times when a widget id has several instances. */
+    WidgetData widgetData (widgetID id);
+    void widgetData (widgetID id, WidgetData data);	/// ditto
+    wdims dimData (widgetID id);			/// ditto
+    void dimData (widgetID id, wdims d);		/// ditto
     
     // Rendering:
     /** For when a widget needs redrawing.
@@ -151,7 +156,7 @@
  * although some parents may set child-widgets' size during their creation.
  *************************************************************************************************/
 //NOTE: add another this() without the data for default initialization, for the GUI editor?
-interface IChildWidget : IWidget
+interface IChildWidget : IParentWidget
 {
 //BEGIN Load and save
     /** 2nd stage of initialization for widgets; also called on some changes.
--- a/mde/gui/widget/PopupMenu.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/PopupMenu.d	Fri Jan 02 18:07:10 2009 +0000
@@ -38,17 +38,18 @@
  *************************************************************************************************/
 class PopupMenuWidget : AParentSingleWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
 	content = c;
 	WDCMinCheck (data, 1,1, content);
-	subWidget = mgr.makeWidget (data.strings[0], content);
+        super (mgr, parent, id);
+        
+        subWidget = mgr.makeWidget (this, data.strings[0], content);
 	
 	adapter = mgr.renderer.getAdapter;
 	adapter.text = content.toString (1);
 	adapter.getDimensions (mw, mh);
 	w = mw;
 	h = mh;
-	super (mgr, id, data);
     }
     
     override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
@@ -95,8 +96,8 @@
  *************************************************************************************************/
 class SubMenuWidget : PopupMenuWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
-        super (mgr, id, data, c);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
+        super (mgr, parent, id, data, c);
     }
     
     override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
@@ -114,27 +115,27 @@
 /*************************************************************************************************
  * A function which returns a ContentListWidget or MenuButtonContentWidget.
  *************************************************************************************************/
-IChildWidget flatMenuContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+IChildWidget flatMenuContent (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
     if (c is null) throw new ContentException;
     if (cast(IContentList) c)
-	return new ContentListWidget(mgr,id,data,c);
-    else if (cast(EventContent) c)
-	return new MenuButtonContentWidget(mgr,id,data,c);
-    else // generic uneditable option
-        return new DisplayContentWidget(mgr,id,data,c);
+        return new ContentListWidget(mgr,parent,id,data,c);
+    if (cast(EventContent) c)
+        return new MenuButtonContentWidget(mgr,parent,id,data,c);
+    // generic uneditable option
+    return new DisplayContentWidget(mgr,parent,id,data,c);
 }
 
 /*************************************************************************************************
  * A function which returns a SubMenuWidget or MenuButtonContentWidget.
  *************************************************************************************************/
-IChildWidget subMenuContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+IChildWidget subMenuContent (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
     if (c is null) throw new ContentException;
     if (cast(IContentList) c)
-	return new SubMenuWidget(mgr,id,data,c);
-    else if (cast(EventContent) c)
-	return new MenuButtonContentWidget(mgr,id,data,c);
-    else // generic uneditable option
-        return new DisplayContentWidget(mgr,id,data,c);
+        return new SubMenuWidget(mgr,parent,id,data,c);
+    if (cast(EventContent) c)
+        return new MenuButtonContentWidget(mgr,parent,id,data,c);
+    // generic uneditable option
+    return new DisplayContentWidget(mgr,parent,id,data,c);
 }
 
 /*************************************************************************************************
@@ -142,11 +143,11 @@
  *************************************************************************************************/
 class MenuButtonContentWidget : ATextWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
-	content = cast(EventContent) c;
-	WDCMinCheck (data, 1,0, content);
-	adapter = mgr.renderer.getAdapter ();
-	super (mgr, id, data);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData, IContent c) {
+        content = cast(EventContent) c;
+        if (content is null) throw new ContentException (this);
+        adapter = mgr.renderer.getAdapter ();
+        super (mgr, parent, id);
     }
     
     override bool setup (uint n, uint flags) {
--- a/mde/gui/widget/TextWidget.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/TextWidget.d	Fri Jan 02 18:07:10 2009 +0000
@@ -32,8 +32,8 @@
 {
     /** Set the adapter first:
      * adapter = mgr.renderer.getAdapter (...); */
-    protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        super (mgr, id, data);
+    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
     }
     
     /** Recalculates dims if the renderer changed. */
@@ -70,11 +70,11 @@
      * [widgetID, contentID, colour]
      * where contentID is an ID for the string ID of the contained content
      * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data) {
         WDCheck (data, 2, 1);
+        super (mgr, parent, id);
         adapter = mgr.renderer.getAdapter (data.ints[1]);
 	adapter.text = data.strings[0];
-        super (mgr, id, data);
     }
 }
 
@@ -84,12 +84,12 @@
  * data.ints[1]. */
 class ContentLabelWidget : ATextWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
 	content = c;
 	WDCCheck (data, 3, 0, content);
+        super (mgr, parent, id);
         index = data.ints[1];
         adapter = mgr.renderer.getAdapter (data.ints[2]);
-        super (mgr, id,data);
     }
     
     override bool setup (uint n, uint flags) {
@@ -106,12 +106,13 @@
 /// Just displays the value of a content. Generic − any IContent.
 class DisplayContentWidget : ATextWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData, IContent c) {
 	content = c;
-	WDCMinCheck(data, 1,0, content);
-	adapter = mgr.renderer.getAdapter ();
+        if (content is null) throw new ContentException (this);
+        super (mgr, parent, id);
+        
+        adapter = mgr.renderer.getAdapter ();
 	adapter.text = content.toString(0);
-	super (mgr, id, data);
     }
     
 protected:
@@ -121,12 +122,13 @@
 /// Text-box for editing a content's value. Generic − any AStringContent.
 class AStringContentWidget : ATextWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData, IContent c) {
 	content = cast(AStringContent) c;
-	WDCMinCheck(data, 1,0, content);
+        if (content is null) throw new ContentException (this);
+        super (mgr, parent, id);
+        
         adapter = mgr.renderer.getAdapter ();
 	adapter.text = content.toString(0);
-	super (mgr, id, data);
     }
     
     override bool isWSizable () {    return true;    }
--- a/mde/gui/widget/Widget.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/Widget.d	Fri Jan 02 18:07:10 2009 +0000
@@ -47,11 +47,23 @@
  *************************************************************************************************/
 abstract class AWidget : IChildWidget
 {
+//BEGIN IParentWidget methods
+    // Don't override; use the WIDGET_TYPE.SAFE_RECURSION flag for safe widgets.
+    //NOTE: should be override (compiler bug)
+    final void recursionCheck (widgetID a) {
+        debug assert (id !is null && parent !is null, "recursionCheck called before parent and id set");
+        if (a is id)
+            throw new GuiException ("Infite recursion of "~a);
+        parent.recursionCheck (a);
+    }
+//END IParentWidget methods
+    
 //BEGIN Load and save
     // Base this() for child Widgets.
-    protected this (IWidgetManager mgr, widgetID id, WidgetData) {
-        this.mgr = mgr;
-        this.id = id;
+    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        this.mgr	= mgr;
+        this.parent	= parent;
+        this.id		= id;
     }
     
     // Widgets need to do their initialization either in this() or setup().
@@ -183,9 +195,10 @@
 	    throw new ContentException (this);
     }
     
+    IWidgetManager mgr;		// the enclosing window
+    IParentWidget parent;	// the parent widget
+    wdim x, y;			// position
     widgetID id;                // The widget's ID, used for saving data
-    IWidgetManager mgr;		// the enclosing window
-    wdim x, y;			// position
     wdim w, h;			// size
     wdim mw = 0, mh = 0;	// minimal or fixed size, depending on whether the widget is
     				// resizible; both types of widgets should actually be expandable.
@@ -199,12 +212,13 @@
  *************************************************************************************************/
 abstract class AParentWidget : AWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        super (mgr, id, data);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
     }
     
     override bool setup (uint n, uint flags) {
-	bool c = false;
+        debug (mdeWidgets) logger.trace ("AParentWidget.setup");
+        bool c = false;
 	foreach (w; subWidgets) {
 	    debug assert (w);
 	    c |= w.setup (n,flags);
@@ -225,12 +239,13 @@
 /** ditto */
 abstract class AParentSingleWidget : AWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
-	super (mgr, id, data);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
     }
     
     override bool setup (uint n, uint flags) {
-	debug assert (subWidget);
+        debug (mdeWidgets) logger.trace ("AParentSingleWidget.setup");
+        debug assert (subWidget);
 	return subWidget.setup (n,flags);
     }
     
@@ -250,8 +265,8 @@
      * Widget uses the initialisation data:
      * [widgetID, w, h]
      * where w, h is the fixed size. */
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        super (mgr, id, data);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data) {
+        super (mgr, parent, id);
         w = mw = cast(wdim) data.ints[1];
         h = mh = cast(wdim) data.ints[2];
     }
@@ -261,8 +276,8 @@
 class SizableWidget : AWidget {
     // Check data.length is at least 1 before calling!
     /// Constructor for a completely resizable [blank] widget.
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        super (mgr, id, data);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
     }
     
     override bool isWSizable () {    return true;    }
@@ -274,8 +289,8 @@
  * Overriding classes should implement this() (setting the size), draw() and activated(). */
 abstract class AButtonWidget : AWidget
 {
-    protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        super (mgr, id, data);
+    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
     }
     
     /// May be over-ridden. Pushed is true if the button has been pushed and not released.
--- a/mde/gui/widget/createWidget.d	Thu Jan 01 15:16:00 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +0,0 @@
-/* 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/>. */
-
-/** This module contains code to create a widget based on an enumeration value passed at runtime.
- *
- * It could be a part of the WidgetLoader.makeWidget function, but having it here makes things
- * tidier. */
-module mde.gui.widget.createWidget;
-
-import mde.gui.widget.Ifaces;
-import mde.gui.exception;
-import mde.content.Content;
-import Items = mde.content.Items;
-
-// Widgets to create:
-import mde.gui.widget.layout;
-import mde.gui.widget.miscWidgets;
-import mde.gui.widget.TextWidget;
-import mde.gui.widget.miscContent;
-import mde.gui.widget.Floating;
-import mde.gui.widget.PopupMenu;
-
-import tango.util.log.Log : Log, Logger;
-
-private Logger logger;
-static this () {
-    logger = Log.getLogger ("mde.gui.widget.createWidget");
-}
-
-/** Create a widget.
- *
- * Usually called by the widget manager's makeWidget function.
- *
- * Widget created of type data.ints[0] (see enum WIDGET_TYPES), with one of the following CTORs:
- * ---
- * this (IWidgetManager mgr, WidgetData data);
- * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_CONTENT):
- * this (IWidgetManager mgr, WidgetData data, IContent content);
- * ---
- *************************************************************************************************/
-IChildWidget createWidget (IWidgetManager mgr, widgetID id, WidgetData data, IContent content)
-in {
-    assert (mgr !is null, "createWidget: mgr is null");
-} body {
-    if (data.ints.length < 1) {
-        logger.error ("No int data; creating a debug widget");
-        data.ints = [WIDGET_TYPE.Debug];
-    }
-    int type = data.ints[0];    // type is first element of data
-    
-    try {
-        //pragma (msg, binarySearch ("type", WIDGETS));
-        mixin (binarySearch ("type", WIDGETS)); // creates widget by type: new XWidget (mgr, data [, parent]);
-        // Not returned a new widget or thrown:
-        logger.error ("Bad widget type: {}; creating a debug widget instead",type);
-    } catch (Exception e) {
-        logger.error ("Error creating widget: {}; creating a debug widget instead.", e.msg);
-    }
-    
-    return new DebugWidget (mgr, id, data);
-}
-
-/*************************************************************************************************
- * A function which uses Items.get (data.strings[0]) to get a content and creates a widget from
- * data.ints[1]. The first item in each ints and strings is removed before passing data to the new
- * widget.
- * 
- * The function only takes an IContent parameter to satisfy createWidget; it's value is ignored.
- * 
- * Circularly depends on createWidget, so should be in this module.
- *************************************************************************************************/
-IChildWidget addContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent) {
-    if (data.ints.length < 2 || data.strings.length < 1) throw new WidgetDataException;
-    char[] cItem = data.strings[0];
-    data.strings = data.strings[1..$];
-    data.ints    = data.ints   [1..$];
-    return createWidget (mgr, id, data, Items.get (cItem));
-}
-
-private:
-/// Widget types.
-enum WIDGET_TYPE : int {
-    FUNCTION		= 0x2000,   // Function called instead of widget created (no "Widget" appended to fct name)
-    TAKES_CONTENT	= 0x4000,   // Flag indicates widget's this should be passed an IContent reference.
-    SAFE_RECURSION	= 0x8000,   // Safe to instantiate recursively without infinite looping.
-    
-    // Use widget names rather than usual capitals convention
-    Unnamed		= 0x0,      // Only for use by widgets not created with createWidget
-    
-    // blank: 0x1
-    FixedBlank		= 0x1,
-    SizableBlank	= 0x2,
-    Debug		= 0xF,
-    
-    // popup widgets: 0x10
-    PopupMenu		= TAKES_CONTENT | 0x11,
-    SubMenu		= TAKES_CONTENT | 0x12,
-    
-    // labels: 0x20
-    ContentLabel	= TAKES_CONTENT | 0x20,
-    TextLabel		= 0x21,
-    
-    // content functions: 0x30
-    editContent		= FUNCTION | TAKES_CONTENT | SAFE_RECURSION | 0x30,
-    addContent		= FUNCTION | 0x31,
-    flatMenuContent	= FUNCTION | TAKES_CONTENT | SAFE_RECURSION | 0x32,
-    subMenuContent	= FUNCTION | TAKES_CONTENT | 0x33,
-    
-    // content widgets: 0x40
-    DisplayContent	= TAKES_CONTENT | 0x40,
-    BoolContent		= TAKES_CONTENT | 0x41,
-    AStringContent	= TAKES_CONTENT | 0x42,
-    ButtonContent	= TAKES_CONTENT | 0x43,
-    MenuButtonContent	= TAKES_CONTENT | 0x44,
-    
-    GridLayout		= TAKES_CONTENT | 0x100,
-    ContentList		= TAKES_CONTENT | SAFE_RECURSION | 0x110,
-    
-    FloatingArea	= 0x200,
-}
-
-//const char[][int] WIDGET_NAMES;
-
-// Only used for binarySearch algorithm generation; must be ordered by numerical values.
-const char[][] WIDGETS = [
-        "FixedBlank",
-        "SizableBlank",
-        "Debug",
-	"TextLabel",
-	"FloatingArea",
-	"addContent",
-	"PopupMenu",
-	"SubMenu",
-	"ContentLabel",
-        "DisplayContent",
-        "BoolContent",
-	"AStringContent",
-	"ButtonContent",
-	"MenuButtonContent",
-	"GridLayout",
-	"subMenuContent",
-	"ContentList",
-	"editContent",
-	"flatMenuContent"];
-
-/* Generates a binary search algorithm. */
-char[] binarySearch (char[] var, char[][] consts) {
-    if (consts.length > 3) {
-        return "if (" ~ var ~ " <= WIDGET_TYPE." ~ consts[$/2 - 1] ~ ") {\n" ~
-                binarySearch (var, consts[0 .. $/2]) ~
-                "} else {\n" ~
-                binarySearch (var, consts[$/2 .. $]) ~
-                "}\n";
-    } else {
-        char[] ret;
-        foreach (c; consts) {
-            ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n"~
-                      /+if ((WIDGET_TYPE."~c~" & WIDGET_TYPE.SAFE_RECURSION) ||
-			  "not being recursed (no parent with same id)") {\n
-                    	For recursion detection; would probably work with above check and some modification to widgets. +/
-                       "debug (mdeWidgets) logger.trace (\"Creating new "~c~"Widget.\");\n
-                        static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.FUNCTION)\n
-                          return " ~ c ~ " (mgr, id, data, content);\n
-                        else static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n
-                          return new " ~ c ~ "Widget (mgr, id, data, content);\n
-                        else\n
-                          return new " ~ c ~ "Widget (mgr, id, data);\n"~
-                      /+} else
-                    	throw new GuiException (\"Widget not safe to be recursed: "~c~"\");+/
-                   "} else ";
-        }
-        ret = ret[0..$-6] ~ '\n';  // remove last else
-        return ret;
-    }
-}
-
-debug { // check items in WIDGETS are listed in order
-    char[] WIDGETS_check () {
-        char[] ret;
-        for (int i = WIDGETS.length-2; i > 0; --i) {
-            ret ~= "WIDGET_TYPE."~WIDGETS[i] ~" >= WIDGET_TYPE."~ WIDGETS[i+1];
-            if (i>1) ret ~= " || ";
-        }
-        return ret;
-    }
-    mixin ("static if ("~WIDGETS_check~")
-        static assert (false, \"WIDGETS is not in order!\");");
-}
\ No newline at end of file
--- a/mde/gui/widget/layout.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/layout.d	Fri Jan 02 18:07:10 2009 +0000
@@ -56,26 +56,25 @@
      * list) for the widget in row i and column j. The number of parameters must be r*c + 3.
      * 
      * The content parameter is passed on to all children accepting an IContent. */
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent content) {
         // Get grid size and check data
         // Check sufficient data for type, align-flags, rows, cols, and possibly row/col widths.
         if (data.ints.length != 4) throw new WidgetDataException (this);
         
         rows = data.ints[2];
         cols = data.ints[3];
-        // Check: at least one sub-widget, ints length == 3, strings' length is correct:
-        if (rows < 1 || cols < 1 || data.ints.length != 4 || data.strings.length != rows * cols)
+        // Check: at least one sub-widget and strings's length is correct:
+        if (rows < 1 || cols < 1 || data.strings.length != rows * cols)
             throw new WidgetDataException (this);
+        super (mgr, parent, id, data);
         
         // Get all sub-widgets
         subWidgets.length = rows*cols;
         foreach (i, ref subWidget; subWidgets) {
-            subWidget = mgr.makeWidget (data.strings[i], content);
+            subWidget = mgr.makeWidget (this, data.strings[i], content);
         }
         
         initWidths = mgr.dimData (id);  // may be null, tested later
-        
-        super (mgr, id, data);
     }
     
     // Save column/row sizes. Currently always do so.
@@ -83,7 +82,7 @@
         foreach (widget; subWidgets) // recurse on subwidgets
             widget.saveChanges ();
         
-        mgr.setDimData (id, col.width ~ row.width);
+        mgr.dimData (id, col.width ~ row.width);
         return true;
     }
 protected:
@@ -98,25 +97,27 @@
  *************************************************************************************************/
 class ContentListWidget : GridWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent content) {
 	cList = cast(IContentList) content;
 	WDCCheck (data, 2, 1, cList);
-	
+        logger.trace ("Got data: {} and {}", data.ints, data.strings);
         cols = 1;
-        if ((rows = cList.list.length) > 0) {
-            subWidgets.length = rows;
-            foreach (i, c; cList.list) {
-                subWidgets[i] = mgr.makeWidget (data.strings[0], c);
-            }
-        } else {
-            rows = 1;
-            subWidgets = [mgr.makeWidget (data.strings[0], new ErrorContent ("<empty list>"))];
-        }
+        rows = cList.list.length;
+        subWidgets.length = rows;
         if (data.ints[1] & 8) {	// orient horizontally
             cols = rows;
             rows = 1;
         }
-        super (mgr, id, data);
+        super (mgr, parent, id, data);
+	
+        if (subWidgets) {	// i.e. rows*cols > 0
+            foreach (i, c; cList.list) {
+                subWidgets[i] = mgr.makeWidget (this, data.strings[0], c);
+            }
+        } else {
+            rows = cols = 1;
+            subWidgets = [mgr.makeWidget (this, data.strings[0], new ErrorContent ("<empty list>"))];
+        }
     }
     
     override bool saveChanges () {
@@ -146,14 +147,13 @@
     //BEGIN Creation & saving
     /** Partial constructor for a grid layout widget.
      *
-     * Deriving classes should check data lengths, and set rows, cols, and the subWidgets array,
-     * before calling this super constructor. (If it's necessary to call super(...) first,
-     * the call to genCachedConstructionData can be moved to the derived this() methods.)
+     * Deriving classes should check data lengths, and set rows and cols
+     * before calling this super constructor.
      * 
      * Derived constructors may also set initWidths to the array of column widths followed by
      * row heights used to initially set the row/column dimensions. */
-    protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        super (mgr, id, data);
+    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data) {
+        super (mgr, parent, id);
         
         // Create cell aligners with appropriate col/row adjustment function
         if (data.ints[1] & 1)
@@ -173,7 +173,8 @@
      *
      * As such, this must be the first function called after this(). */
     override bool setup (uint n, uint flags) {
-	// Run all internal calculations regardless of changes, then check dimensions for changes.
+       debug (mdeWidgets) logger.trace ("GridWidget.setup");
+ 	// Run all internal calculations regardless of changes, then check dimensions for changes.
 	// Don't try shortcutting internal calculations when there are no changes - I've tried, and
 	// doing so adds enough overhead to make doing so almost(?) worthless (or at least large
 	// increases in complexity).
@@ -202,7 +203,7 @@
 	    widget.setWidth  (col.width[i % cols], -1);
 	    widget.setHeight (row.width[i / cols], -1);
 	}
-	return (ow != w || oh != h);
+        return (ow != w || oh != h);
     }
     //END Creation & saving
     
@@ -293,14 +294,16 @@
 	if (sADD_n == n) return;	// cached data is current
 	sADD_n = n;
 	
+        debug (mdeWidgets) logger.trace ("GridWidget: setup on subWidgets...");
 	foreach (widg; subWidgets) {	// make sure all subwidgets have been set up
 	    debug assert (widg);
 	    widg.setup (n,flags);
 	}
+        debug (mdeWidgets) logger.trace ("GridWidget: setup on subWidgets...done");
 	// make sure both AlignColumns are set up (since first call to setup(n) calls reset):
 	col.setup (n, flags);
 	row.setup (n, flags);
-	
+        
 	// Note: shared AlignColumns get this set by all sharing GridWidgets
         col.spacing = row.spacing = useSpacing ? mgr.renderer.layoutSpacing : 0;
         
--- a/mde/gui/widget/miscContent.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/miscContent.d	Fri Jan 02 18:07:10 2009 +0000
@@ -27,6 +27,7 @@
 import mde.gui.renderer.IRenderer;
 import mde.content.AStringContent;
 import mde.content.miscContent;
+import Items = mde.content.Items;
 
 debug {
     import tango.util.log.Log : Log, Logger;
@@ -37,39 +38,51 @@
 }
 
 /*************************************************************************************************
+ * A function which uses Items.get (data.strings[0]) to get a content and creates a widget from
+ * data.ints[1]. The first item in each ints and strings is removed before passing data to the new
+ * widget.
+ * 
+ * The function only takes an IContent parameter to satisfy createWidget; it's value is ignored.
+ ************************************************************************************************/
+IChildWidget addContent (IWidgetManager mgr, IParentWidget parent, widgetID, WidgetData data, IContent) {
+    if (data.strings.length != 2) throw new WidgetDataException;
+    return mgr.makeWidget (parent, data.strings[1], Items.get (data.strings[0]));
+}
+
+/*************************************************************************************************
  * A function which returns the most appropriate content editing widget.
  *
  * Widgets which can be returned: BoolContentWidget (toggle button), ValueContentWidget (generic
  * text-box editor), DisplayContentWidget (generic text label).
  *************************************************************************************************/
-IChildWidget editContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+IChildWidget editContent (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
     // Note: SAFE_RECURSION enabled
     if (c is null) throw new ContentException;
-    else if (cast(AStringContent) c) {
+    if (cast(AStringContent) c) {
         if (cast(EnumContent) c)	// can be PopupMenuWidget or ContentListWidget
-            return new ContentListWidget(mgr,id,data,c);
-        else if (cast(BoolContent) c)
-            return new BoolContentWidget(mgr,id,data,c);
-        else
-	    return new AStringContentWidget(mgr,id,data,c);
-    } else if (cast(IContentList) c)
-	return new ContentListWidget(mgr,id,data,c);
-    else if (cast(EventContent) c)
-	return new ButtonContentWidget(mgr,id,data,c);
-    else	// generic uneditable option
-        return new DisplayContentWidget(mgr,id,data,c);
+            return new ContentListWidget(mgr,parent,id,data,c);
+        if (cast(BoolContent) c)
+            return new BoolContentWidget(mgr,parent,id,data,c);
+        return new AStringContentWidget(mgr,parent,id,data,c);
+    }
+    if (cast(IContentList) c)
+        return new ContentListWidget(mgr,parent,id,data,c);
+    if (cast(EventContent) c)
+        return new ButtonContentWidget(mgr,parent,id,data,c);
+    // generic uneditable option
+    return new DisplayContentWidget(mgr,parent,id,data,c);
 }
 
 /// Editable boolean widget
 class BoolContentWidget : AButtonWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData, IContent c) {
 	content = cast(BoolContent) c;
-	WDCMinCheck(data, 1,0, content);
+        if (content is null) throw new ContentException (this);
+        super (mgr, parent, id);
         wdimPair s = mgr.renderer.getToggleSize;
         w = mw = s.x;
         h = mh = s.y;
-        super (mgr, id, data);
     }
     
     override void draw () {
@@ -87,11 +100,11 @@
 /// A button connected to an EventContent
 class ButtonContentWidget : AButtonWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData, IContent c) {
 	content = cast(EventContent) c;
-	WDCMinCheck (data, 1,0, content);
-	adapter = mgr.renderer.getAdapter ();
-	super (mgr, id, data);
+        if (content is null) throw new ContentException (this);
+        adapter = mgr.renderer.getAdapter ();
+        super (mgr, parent, id);
     }
     
     override bool setup (uint n, uint flags) {
--- a/mde/gui/widget/miscWidgets.d	Thu Jan 01 15:16:00 2009 +0000
+++ b/mde/gui/widget/miscWidgets.d	Fri Jan 02 18:07:10 2009 +0000
@@ -31,9 +31,9 @@
 /// A fixed-size blank widget.
 class FixedBlankWidget : FixedWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data) {
         WDCheck (data, 3);
-        super (mgr, id, data);
+        super (mgr, parent, id, data);
     }
     
     override void draw () {
@@ -46,9 +46,8 @@
 /// A completely resizable blank widget (initial size zero).
 class SizableBlankWidget : SizableWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        WDCheck (data, 1);
-        super (mgr, id, data);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData) {
+        super (mgr, parent, id);
     }
     
     override void draw () {
@@ -61,9 +60,9 @@
 /// A debug widget. Essentially as SizableBlankWidget but doesn't mind any amount of data and prints it.
 class DebugWidget : SizableWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        super (mgr, id, data);
-        logger.warn ("Debug widget - parameters: ints = {}, strings = {}", data.ints, data.strings);
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data) {
+        super (mgr, parent, id);
+        logger.warn ("Debug widget ({}); parameters: ints = {}, strings = {}", id, data.ints, data.strings);
     }
     
     override void draw () {