changeset 131:9cff74f68b84

Major revisions to popup handling. Buttons can close menus now, plus some smaller impovements. Removed Widget module. Moved Widget.AWidget to AChildWidget.AChildWidget and Widget.AParentWidget to AParentWidget.AParentWidget. Removed ASingleParentWidget to improve code sharing. AChildWidget doesn't implement IParentWidget like AWidget did. New IPopupParentWidget extending IParentWidget for the WM and some widgets to handle popups. Cut old popup management code. New underMouse() function replacing highlight(); called on all widgets. Separate menu-popup and button widgets aren't needed for menus now. Functions returning content widgets have been moved to their own module. Cleaned up jobs.txt. Switched to 80 line length for Ddoc.
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 21 Jan 2009 13:01:40 +0000
parents c5c38eaadb64
children 264028f4115a
files codeDoc/jobs.txt codeDoc/policies.txt mde/content/miscContent.d mde/gui/WMScreen.d mde/gui/WidgetManager.d mde/gui/widget/AChildWidget.d mde/gui/widget/AParentWidget.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/contentFunctions.d mde/gui/widget/layout.d mde/gui/widget/miscContent.d mde/gui/widget/miscWidgets.d
diffstat 16 files changed, 901 insertions(+), 737 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Sat Jan 17 16:11:26 2009 +0000
+++ b/codeDoc/jobs.txt	Wed Jan 21 13:01:40 2009 +0000
@@ -7,24 +7,16 @@
 
 
 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent):
-Also see todo.txt and FIXME/NOTE comment marks.
-4   Close popup menu on button activation/click.
-3   Synchronization of IContent with gui (e.g. multiple edit widgets): worth adding (temporary) callbacks?
+Also search for FIXME/NOTE/BUG/WARNING comment marks.
+4   Revise widgets/functions available
+3   Glyph 's' drawn incorrectly in release mode - ??
+3   Improvements for non-button popup widgets.
 3   Widget saving: how to deal with modifier functions, esp. when they discard parameters? Remove feature except for dimdata and handle gui editing separately?
-3   Use of dtors - don't rely on them? Or what happens when init throws during creation - relying on undefined behaviour.
 3   glBindTexture not working with non-0 index - perhaps use a higher level graphics library at some point.
-3   Windows building/compatibility (currently partial) - tango/sys/win32/SpecialPath.d
+3   Windows compatibility - no registry support (useful to find path).
 2   Popup help boxes on hover/right click to display content description.
-2   clickEvent: don't evaluate b directly but pass to a WidgetManager function to allow button configuration. Alternately, evaluate in WidgetManager's clickEvent and pass flags on to IChildWidget's clickEvent.
-2   Reduce variable passing (e.g. for some Content widgets data and id aren't even relevant). Wait for gui editor.
-2   Possibility: Could have a content method "update (T*)" to add a synchronized variable. Or could store both a content and a syncronized variable for each option within Options.
-2   Add callback to miscOpts.L10n reverting the option to it's last value if no file is found and reloading translations.
-2   Remove ability to scan, then load, mergetag sections. Not so necessary with section creator callback and allows "sliding window" type partial buffering. Also remove dataset and force use of section creator callback?
 2   Options need a "level": simple options, for advanced users, for debugging only, etc.
 2   Command-line options for paths to by-pass normal path finding functionality.
-2   Consider replacing byte/short types with int type
-2   File loading from compressed archives
-2   gdc compatibility - now due to SDL?
 2   Sensitivity adjustments. From es_a_out:
         /+ FIXME: revise.
         + I can't see any point using HALF_RANGE here, since it should really be used dependant on
@@ -52,7 +44,8 @@
         y = sign(y) * pow(abs(y), a);		// sensitivity adjustment by a +/
         myThis.axis[cast(inputID) s.pop()] = y;
         +/
-1   Mergetag binary support / other file formats.
+1   Remove ability to scan, then load, mergetag sections. Not so necessary with section creator callback and allows "sliding window" type partial buffering. Also remove dataset and force use of section creator callback?
+1   File loading from compressed archives and/or binary archives.
 
 
 Done (since last commit):
--- a/codeDoc/policies.txt	Sat Jan 17 16:11:26 2009 +0000
+++ b/codeDoc/policies.txt	Wed Jan 21 13:01:40 2009 +0000
@@ -18,7 +18,7 @@
 --- Introduction ---
 This is a collection of all coding policies for the mde engine as a whole. Policies for individual packages should be put in the individual package directory or elsewhere.
 
-These are principles, not cast-iron rules, which I (Diggory Hardy) generally try to adhere to. If any other programmers have better principles to apply over these rules, they may do so for their own coding providing they have a good reason (i.e. not simply wanting to be a little different).
+These are principles, not cast-iron rules, which I (Diggory aka Cyborg16) generally try to adhere to. If any other programmers have better principles to apply over these rules, they can use them.
 
 A warning: I have several times changed my mind about items I've written here. So don't expect all existing code to conform, and if you feel that a principle listed is not a good idea don't think you have to conform to it.
 
@@ -54,7 +54,7 @@
 
 Indentation: Preferably indent with four spaces and align comments either with tabs or spaces. If you do use tabs, use a tab-width of 8.
 
-Long lines: Aim to break long lines at around 100 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code so just try to keep it neat.
+Long lines: Aim to break long lines at around 80 chars (particularly with documentation); this isn't essential but provides a good guide and keeps text looking reasonable. With code, however, breaking lines doesn't always produce better-looking code so just try to keep it neat. It used to be 100 chars.
 
 Identifiers: as for the D spec, use descriptive words for identifiers, although try to keep them from being overlong. Use capital letters to show separate words, not underscores. E.g.: file, readFile; not: rdFile, read_file, readFileUsingMyMethodNow. Use CAPS for consts (with underscores as word separators), Capitalisation for class/interface/struct/union names, smallLetters for variables and class instances. Don't capitalise acronyms like SDL; instead use capitalisation like outlined above (personally I hate doing so but it does make things clearer).
 
--- a/mde/content/miscContent.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/content/miscContent.d	Wed Jan 21 13:01:40 2009 +0000
@@ -13,20 +13,18 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/*************************************************************************************************
+/******************************************************************************
  * The content system − a few content classes.
- *************************************************************************************************/
+ *****************************************************************************/
 module mde.content.miscContent;
 
 public import mde.content.Content;
 
-debug {
     import tango.util.log.Log : Log, Logger;
     private Logger logger;
     static this () {
         logger = Log.getLogger ("mde.content.miscContent");
     }
-}
 
 /** A generic way to handle a list of type IContent. */
 class ContentList : Content, IContentList
--- a/mde/gui/WMScreen.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/WMScreen.d	Wed Jan 21 13:01:40 2009 +0000
@@ -13,12 +13,12 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/*************************************************************************************************
+/******************************************************************************
  * A gui manager class using mde.setup.Screen and mde.input.Input.
  *
- * This is the module to use externally to create a graphical user interface (likely also with
- * content modules).
- *************************************************************************************************/
+ * This is the module to use externally to create a graphical user interface
+ * (likely also with content modules).
+ *****************************************************************************/
 module mde.gui.WMScreen;
 
 import mde.gui.WidgetManager;
@@ -34,17 +34,19 @@
     logger = Log.getLogger ("mde.gui.WMScreen");
 }
 
-/*************************************************************************************************
+/******************************************************************************
  * 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.
+ * 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.
+ * 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.
- *************************************************************************************************/
+ * Public non IWidget* methods should be thread-safe.
+ *****************************************************************************/
 scope class WMScreen : AWidgetManager, Screen.IDrawable {
     /** Construct a new widget manager.
      * 
@@ -68,8 +70,8 @@
         synchronized(mutex) {
             if (child)
                 child.draw;
-	    foreach (popup; popups)
-		popup.widget.draw();
+	    if (childIPPW)
+                childIPPW.drawPopup;
 	}
     }
     
@@ -85,54 +87,25 @@
         
         wdabs cx = cast(wdabs) usx, cy = cast(wdabs) usy;
         
-        // 1. Callbacks have the highest priority recieving events (e.g. a button release)
+        // Callbacks have the highest priority receiving 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;
+        // Update underMouse to get the widget clicked on
+        updateUnderMouse (cx, cy, state);
         
-        // 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) {
+        // Disable keyboard input if on another widget:
+	if (keyFocus && keyFocus !is underMouse) {
 	    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);
-	    }
-	}
+        // Finally, post the actual event:
+        if (underMouse.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state) & 1) {
+            // keyboard input requested
+            keyFocus = underMouse;
+            imde.input.setLetterCallback (&underMouse.keyEvent);
+        }
     }
     
     /** For mouse motion events.
@@ -146,23 +119,8 @@
 	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;
-	}
+        
+        updateUnderMouse (cx, cy, false);
     }
     
     
@@ -195,7 +153,6 @@
         // The renderer needs to be created on the first load, but not after this.
         if (rend is null)
             rend = createRenderer (rendName);
-	popups = null;
         
         debug (mdeWidgets) logger.trace ("Creating root widget...");
         child = makeWidget (this, "root");
--- a/mde/gui/WidgetManager.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/WidgetManager.d	Wed Jan 21 13:01:40 2009 +0000
@@ -13,12 +13,15 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/*************************************************************************************************
+/******************************************************************************
  * The gui manager class base.
  *
- * 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.
- *************************************************************************************************/
+ * 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.
+ * 
+ * Public non IWidget* methods should be thread-safe.
+ *****************************************************************************/
 module mde.gui.WidgetManager;
 
 import mde.gui.WidgetDataSet;
@@ -39,6 +42,7 @@
 import mde.gui.widget.layout;
 import mde.gui.widget.miscWidgets;
 import mde.gui.widget.TextWidget;
+import mde.gui.widget.contentFunctions;
 import mde.gui.widget.miscContent;
 import mde.gui.widget.Floating;
 import mde.gui.widget.PopupMenu;
@@ -52,12 +56,12 @@
     logger = Log.getLogger ("mde.gui.WidgetManager");
 }
 
-/*************************************************************************************************
- * 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.
+/******************************************************************************
+ * 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 AWidgetManager : IWidgetManager
 {
     /** Construct a new widget loader.
@@ -185,6 +189,7 @@
         
         // Create the widgets:
         createRootWidget;
+        underMouse = child;	// must be something
     }
     
     /** Save changes, if any exist.
@@ -247,7 +252,7 @@
     }
     
     /** Called when translation strings have been reloaded. */
-    void reloadStrings (Content) {
+    protected void reloadStrings (Content) {
 	Items.loadTranslation;
 	child.setup (++setupN, 2);
 	child.setWidth  (w, -1);
@@ -256,6 +261,9 @@
 	requestRedraw;
     }
     
+    // These methods are only intended for use within the gui package.
+    // They are not necessarily thread-safe:
+    
     //BEGIN IParentWidget methods
     // If call reaches the widget manager there isn't any recursion.
     //NOTE: should be override
@@ -283,6 +291,58 @@
     }
     //END IParentWidget methods
     
+    //BEGIN IPopupParentWidget methods
+    override IPopupParentWidget getParentIPPW () {
+        return this;
+    }
+    
+    override void addChildIPPW (IPopupParentWidget ippw) {
+        if (childIPPW)
+            childIPPW.removedIPPW;
+        childIPPW = ippw;
+        requestRedraw;
+    }
+    override bool removeChildIPPW (IPopupParentWidget ippw) {
+        if (childIPPW !is ippw) return false;
+        childIPPW.removedIPPW;
+        childIPPW = null;
+        mAIPPW = false;
+        requestRedraw;
+        return false;
+    }
+    
+    override void menuActive (bool mA) {
+        mAIPPW = mA;
+        if (childIPPW)
+            childIPPW.menuActive = mA;
+    }
+    override bool menuActive () {
+        return mAIPPW;
+    }
+    
+    // Don't do anything. E.g. can get called by non-popup buttons.
+    override void menuDone () {}
+    
+    override IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup) {
+        IChildWidget ret;
+        if (childIPPW) {
+            ret = childIPPW.getPopupWidget (cx, cy, closePopup);
+            if (closePopup && ret is null) {
+                menuActive = false;
+                removeChildIPPW (childIPPW);
+            }
+        }
+        return ret;
+    }
+    
+    debug protected override bool isChild (IPopupParentWidget ippw) {
+        return ippw is childIPPW;
+    }
+    
+    override void removedIPPW () {}	// irrelevant
+    override void drawPopup () {}
+    //END IPopupParentWidget methods
+    
     //BEGIN IWidgetManager methods
     override IChildWidget makeWidget (IParentWidget parent, widgetID id, IContent content = null)
     {
@@ -325,44 +385,28 @@
         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 positionPopup (IChildWidget parent, IChildWidget popup, int flags = 0) {
+	debug assert (parent && popup, "positionPopup: null widget");
+        wdim w = popup.width,
+             h = popup.height,
+             x, y;
+        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
+        }
+        popup.setPosition (x, y);
     }
 
     void requestRedraw () {
@@ -382,11 +426,25 @@
     //END IWidgetManager methods
     
     debug void logWidgetSize (Content) {
-        logger.trace ("Current size: {,4},{,4}; minimal: {,4},{,4} - WidgetManager", w,h, mw,mh);
+        logger.trace ("size: {,4},{,4}; minimal: {,4},{,4} - WidgetManager", w,h, mw,mh);
         child.logWidgetSize;
     }
     
 protected:
+    void updateUnderMouse (wdabs cx, wdabs cy, bool closePopup) {
+        auto oUM = underMouse;
+        underMouse = getPopupWidget (cx, cy, closePopup);
+        if (underMouse is null) {
+            debug assert (child.onSelf (cx, cy), "WidgetManager: child doesn't cover whole area");
+            underMouse = child.getWidget (cx, cy);
+        }
+        if (underMouse !is oUM) {
+            debug assert (oUM && underMouse, "no widget under mouse: error");
+            oUM.underMouse (false);
+            underMouse.underMouse (true);
+        }
+    }
+    
     /** Second stage of loading the widgets.
     * 
     * loadDesign handles the data; this method needs to:
@@ -426,7 +484,6 @@
     
     // popup widgets: 0x10
     PopupMenu		= TAKES_CONTENT | 0x11,
-    SubMenu		= TAKES_CONTENT | 0x12,
     
     // labels: 0x20
     ContentLabel	= TAKES_CONTENT | 0x20,
@@ -443,7 +500,6 @@
     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,
@@ -460,13 +516,11 @@
 	"TextLabel",
 	"addContent",
 	"PopupMenu",
-	"SubMenu",
 	"ContentLabel",
         "DisplayContent",
         "BoolContent",
 	"AStringContent",
 	"ButtonContent",
-	"MenuButtonContent",
 	"GridLayout",
 	"FloatingArea",
 	"Switch",
@@ -540,19 +594,14 @@
     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
+    bool mAIPPW;			// IPPW variable
+    IPopupParentWidget childIPPW;	// child IPPW, if any active
+    
     // 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
+    IChildWidget keyFocus;	// widget receiving keyboard input
+    IChildWidget underMouse;	// widget under the mouse pointer
     
     Mutex mutex;			// lock on methods for use outside the package.
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/AChildWidget.d	Wed Jan 21 13:01:40 2009 +0000
@@ -0,0 +1,300 @@
+/* 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/>. */
+
+/******************************************************************************
+ * Contains the AChildWidget class, intended for IChildWidgets to inherit.
+ *
+ * Abstract widget classes have an 'A' prepended to the name, similar to the
+ * 'I' convention for interfaces.
+ *****************************************************************************/
+module mde.gui.widget.AChildWidget;
+
+public import mde.gui.widget.Ifaces;
+import mde.content.Content;
+import mde.gui.exception;
+
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this () {
+        logger = Log.getLogger ("mde.gui.widget.AChildWidget");
+    }
+}
+
+/******************************************************************************
+ * An abstract base widget class for IChildWidgets.
+ *
+ * This abstract class, and the more concrete FixedWidget and ScalableWidget
+ * classes provides useful basic implementations for widgets.
+ *****************************************************************************/
+abstract class AChildWidget : IChildWidget
+{
+//BEGIN Load and save
+    // Base this() for child Widgets.
+    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().
+    override bool setup (uint,uint) {
+	return false;
+    }
+    
+    // Don't save any data: fine for many widgets.
+    override bool saveChanges () {
+        return false;
+    }
+//END Load and save
+    
+//BEGIN Size and position
+    // default to not resizable
+    override bool isWSizable () {
+        return false;
+    }
+    override bool isHSizable () {
+        return false;
+    }
+    
+    /* Return minimal/fixed size. */
+    override wdim minWidth () {
+        return mw;
+    }
+    override wdim minHeight () {
+        return mh;
+    }
+    
+    override wdim width () {
+        return w;
+    }
+    override wdim height() {
+        return h;
+    }
+    
+    override wdabs xPos () {
+	return x;
+    }
+    override wdabs yPos () {
+	return y;
+    }
+    
+    /* Set size: minimal size is (mw,mh). Note that both resizable and fixed widgets should allow
+     * enlarging, so in both cases this is a correct implementation. */
+    override void setWidth (wdim nw, int) {
+        debug if (nw < mw) logger.warn ("Widget width set below minimal size");
+        w = (nw >= mw ? nw : mw);
+    }
+    override void setHeight (wdim nh, int) {
+        debug if (nh < mh) logger.warn ("Widget height set below minimal size");
+        h = (nh >= mh ? nh : mh);
+    }
+    
+    override void setPosition (wdim nx, wdim ny) {
+        x = nx;
+        y = ny;
+    }
+//END Size and position
+    
+//BEGIN Events
+    /* This method is only called when the location is over this widget; hence for all widgets
+     * without children this method is valid. */
+    override IChildWidget getWidget (wdim cx, wdim cy) {
+        debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
+        return this;
+    }
+    
+    // Should be valid for any widget.
+    override bool onSelf (wdabs cx, wdabs cy) {
+        return cx >= x && cx < x + w && cy >= y && cy < y + h;
+    }
+    
+    /* Dummy event method (suitable for all widgets which don't respond to events). */
+    override int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
+	return 0;
+    }
+    
+    /* Dummy functions: suitable for widgets with no text input. */
+    override void keyEvent (ushort, char[]) {}
+    override void keyFocusLost () {}
+    
+    // Called when mouse moves over or off this
+    override void underMouse (bool state) {}
+    
+    // Only useful to widgets creating popups.
+    override void popupClose () {}
+    override bool popupParentClick () {
+        return true;
+    }
+//END Events
+    
+    /* Basic draw method: draw the background (all widgets should do this). */
+    override void draw () {
+        mgr.renderer.drawWidgetBack (x,y, w,h);
+    }
+    
+    // Debug function to print size info. Intended to be correct not optimal.
+    debug override void logWidgetSize () {
+        logger.trace ("size: {,4},{,4}; minimal: {,4},{,4}; sizable: {},{} - {,-50} {}", this.width, this.height, this.minWidth, this.minHeight, cast(int)this.isWSizable, cast(int)this.isHSizable, this, id);
+    }
+    
+protected:
+    /**************************************************************************
+     * Widgets may use W*Check as a utility to check for existance of data. Its use is encouraged,
+     * so that the checks can easily be updated should WidgetData be changed.
+     * 
+     * Variants:
+     *	WDCheck checks the exact length of integer and string data.
+     *	WDCCheck checks data as WDCheck and that the content passed is valid.
+     *	WDCMinCheck does the same as WDCCheck, but allows more data than required (used by some
+     *	generic widgets).
+     * 
+     * Params:
+     *  data    = the WidgetData to check lengths of
+     *  n_ints  = number of integers wanted
+     *  n_strings= number of strings (default 0 since not all widgets use strings)
+     *************************************************************************/
+    void WDCheck (WidgetData data, size_t n_ints, size_t n_strings = 0) {
+	if (data.ints.length    != n_ints ||
+	    data.strings.length != n_strings)
+	    throw new WidgetDataException (this);
+    }
+    /** ditto */
+    void WDCCheck (WidgetData data, size_t n_ints, size_t n_strings, IContent c) {
+	if (data.ints.length    != n_ints ||
+	    data.strings.length != n_strings)
+	    throw new WidgetDataException (this);
+	if (c is null)
+	    throw new ContentException (this);
+    }
+    /** ditto */
+    void WDCMinCheck (WidgetData data, size_t n_ints, size_t n_strings, IContent c) {
+	if (data.ints.length    < n_ints ||
+	    data.strings.length < n_strings)
+	    throw new WidgetDataException (this);
+	if (c is null)
+	    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
+    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.
+}
+
+/** A base for fixed-size widgets taking their size from the creation data. */
+class FixedWidget : AChildWidget {
+    // Check data.length is at least 3 before calling!
+    /** Constructor for a fixed-size [blank] widget.
+     *
+     * Widget uses the initialisation data:
+     * [widgetID, w, h]
+     * where w, h is the fixed size. */
+    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];
+    }
+}
+
+/** A base for resizable widgets. */
+class SizableWidget : AChildWidget {
+    // Check data.length is at least 1 before calling!
+    /// Constructor for a completely resizable [blank] widget.
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
+    }
+    
+    override bool isWSizable () {
+        return true;
+    }
+    override bool isHSizable () {
+        return true;
+    }
+}
+
+/** For pressable buttons.
+ *
+ * Overriding classes should implement this() (setting the size), draw() and activated(). */
+abstract class AButtonWidget : AChildWidget
+{
+    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
+        parentIPPW = parent.getParentIPPW;
+    }
+    
+    /// May be over-ridden. Pushed is true if the button has been pushed and not released.
+    override void draw () {
+        mgr.renderer.drawButton (x,y, w,h, pushed);
+    }
+    
+    /// Handles the down-click
+    override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
+        if (b != 1) return 0;
+        if (state) {
+            pushed = true;
+            mgr.requestRedraw;
+            mgr.addClickCallback (&clickWhilePushed);
+            mgr.addMotionCallback (&motionWhilePushed);
+        }
+        if (parentIPPW.menuActive) {
+            parentIPPW.menuDone;
+            activated;
+        }
+	return 0;
+    }
+    
+    /// When menuActive, highlight on mouse-over
+    override void underMouse (bool state) {
+        if (!parentIPPW.menuActive) return;
+        pushed = state;
+        mgr.requestRedraw;
+    }
+    
+    /// Called when a mouse click event occurs while held; handles up-click
+    bool clickWhilePushed (wdabs cx, wdabs cy, ubyte b, bool state) {
+        if (b == 1 && state == false) {
+            if (cx >= x && cx < x+w && cy >= y && cy < y+h) {	// button event
+                parentIPPW.menuDone;
+                activated();
+            }
+            
+            pushed = false;
+            mgr.requestRedraw;
+            mgr.removeCallbacks (cast(void*) this);
+            
+            return true;
+        }
+        return false;
+    }
+    /// Called when a mouse motion event occurs while held; handles pushing in/out on hover
+    void motionWhilePushed (wdabs cx, wdabs cy) {
+        bool oldPushed = pushed;
+        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
+        else pushed = false;
+        if (oldPushed != pushed)
+            mgr.requestRedraw;
+    }
+    
+    /// The action triggered when the button is clicked...
+    void activated ();
+    
+protected:
+    bool pushed = false;        /// True if button is pushed in (visually)
+    IPopupParentWidget parentIPPW;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/AParentWidget.d	Wed Jan 21 13:01:40 2009 +0000
@@ -0,0 +1,208 @@
+/* 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 base widget classes for parent widgets.
+ * 
+ * Abstract widget classes have an 'A' prepended to the name, similar to the
+ * 'I' convention for interfaces.
+ *****************************************************************************/
+module mde.gui.widget.AParentWidget;
+
+public import mde.gui.widget.AChildWidget;
+import mde.gui.exception;
+
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this () {
+        logger = Log.getLogger ("mde.gui.widget.AParentWidget");
+    }
+}
+
+/******************************************************************************
+ * Abstract base widget classes to facilitate parent widgets.
+ * 
+ * To improve code sharing, there's no separate version for parents taking a
+ * single subwidget.
+ * 
+ * Parent widgets probably need to overload these functions (from AChildWidget):
+ * setup, saveChanges, setPosition, getWidget, draw, setWidth and setHeight.
+ *****************************************************************************/
+abstract class AParentWidget : AChildWidget, IParentWidget
+{
+    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
+    }
+    
+    override bool setup (uint n, uint flags) {
+        debug (mdeWidgets) logger.trace ("AParentWidget.setup");
+        bool c = false;
+	foreach (w; subWidgets) {
+            debug assert (w, "AParentWidget: w is null");
+	    c |= w.setup (n,flags);
+	}
+	return c;
+    }
+    
+    override bool saveChanges () {
+        bool c = false;
+        foreach (w; subWidgets)
+            c |= w.saveChanges;
+        return c;
+    }
+    
+    size_t getWidgetIndex (IChildWidget widg) {
+        foreach (i,w; subWidgets)
+            if (w is widg)
+                return i;
+        
+        throw new GuiException ("getWidgetIndex: widget not found (code error)");
+    }
+    
+    // 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);
+    }
+    
+    IPopupParentWidget getParentIPPW () {
+        return parent.getParentIPPW;
+    }
+    
+    // Most parent widgets need to implement these, although not all
+    void minWChange (IChildWidget widget, wdim mw) {}
+    void minHChange (IChildWidget widget, wdim mh) {}
+    
+    debug override void logWidgetSize () {
+        super.logWidgetSize;
+        foreach (widg; subWidgets)
+            widg.logWidgetSize;
+    }
+    
+protected:
+    IChildWidget[] subWidgets;
+}
+
+
+/******************************************************************************
+ * Base code for implementing IPopupParentWidget.
+ * 
+ * The current intention is that an IPPW (excluding the widget manager) may
+ * only have one popup; this class follows this intention.
+ *****************************************************************************/
+abstract class APopupParentWidget : AParentWidget, IPopupParentWidget
+{
+    protected this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
+        super (mgr, parent, id);
+        
+        parentIPPW = parent.getParentIPPW;
+    }
+    
+    override IPopupParentWidget getParentIPPW () {
+        return this;
+    }
+    
+    override void addChildIPPW (IPopupParentWidget ippw) {
+        if (childIPPW)
+            childIPPW.removedIPPW;
+        childIPPW = ippw;
+        mgr.requestRedraw;
+    }
+    override bool removeChildIPPW (IPopupParentWidget ippw) {
+        if (childIPPW !is ippw) return false;
+        childIPPW.removedIPPW;
+        childIPPW = null;
+        mgr.requestRedraw;
+        mAIPPW = false;
+        return true;
+    }
+    
+    // If this function is overriden, you MUST still call it (super.removedIPPW)!
+    // Or invariant will throw assert errors.
+    override void removedIPPW () {
+        if (childIPPW) {
+            childIPPW.removedIPPW;
+            childIPPW = null;
+        }
+        mAIPPW = false;
+    }
+    
+    override void menuActive (bool mA) {
+        mAIPPW = mA;
+        if (childIPPW)
+            childIPPW.menuActive = mA;
+    }
+    override bool menuActive () {
+        return mAIPPW;
+    }
+    
+    override void menuDone () {	// default actions, for popup menus:
+        parentIPPW.removeChildIPPW (this);	// remove self
+        parentIPPW.menuDone;			// and propegate
+    }
+    
+    override IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup) {
+        IChildWidget ret;
+        if (childIPPW) {
+            ret = childIPPW.getPopupWidget (cx, cy, closePopup);
+            if (closePopup && ret is null) {
+                menuActive = false;
+                removeChildIPPW (childIPPW);
+            }
+        }
+        if (ret is null) {
+            if (popup.onSelf (cx, cy))
+            	ret = popup.getWidget (cx, cy);
+            else if (onSelf (cx, cy))
+                ret = getWidget (cx, cy);
+        }
+        return ret;
+    }
+    
+    override void drawPopup () {
+        popup.draw;
+        if (childIPPW)
+            childIPPW.drawPopup;
+    }
+    
+    debug invariant () {
+        // True as long as removedIPPW gets called:
+        if (!parentIPPW.isChild (this)) {
+            assert (childIPPW is null, "APPW: childIPPW");
+            assert (mAIPPW is false, "APPW: mAIPPW");
+        }
+    }
+    
+protected:
+    // How to activate a popup:
+    /+void activatePopup (IChildWidget widget) {
+        parentIPPW.addChildIPPW (this);
+        popup = widget;
+        mgr.positionPopup (this, popup);
+    }+/
+    
+    debug override bool isChild (IPopupParentWidget ippw) {
+        return ippw is childIPPW;
+    }
+    
+    IPopupParentWidget parentIPPW;
+    IPopupParentWidget childIPPW;
+    IChildWidget popup;
+    bool mAIPPW;
+}
--- a/mde/gui/widget/Floating.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/widget/Floating.d	Wed Jan 21 13:01:40 2009 +0000
@@ -16,7 +16,7 @@
 /** The Window class. Becoming a widget. */
 module mde.gui.widget.Floating;
 
-import mde.gui.widget.Widget;
+import mde.gui.widget.AParentWidget;
 import mde.gui.exception;
 import mde.content.Content;
 
--- a/mde/gui/widget/Ifaces.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/widget/Ifaces.d	Wed Jan 21 13:01:40 2009 +0000
@@ -13,17 +13,20 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/*************************************************************************************************
+/******************************************************************************
  * Widget interfaces.
  * 
- * This module contains the primary documentation for the declared methods; also the Widget module
- * has some brief comments and basic implementations.
+ * This module contains the primary documentation for the declared methods;
+ * also the Widget module has some brief comments and basic implementations.
  *
- * Widgets are connected as the nodes of a tree. Widgets know their parent as a IParentWidget
- * class and their children as IChildWidget classes. The gui manager is a special widget only
- * implementing IParentWidget; all other widgets must implement IChildWidget and optionally
- * IParentWidget.
- *************************************************************************************************/
+ * Widgets are connected as the nodes of a tree. Widgets know their parent as a
+ * IParentWidget class and their children as IChildWidget classes. The gui
+ * manager is a special widget only implementing IParentWidget; all other
+ * widgets must implement IChildWidget and optionally IParentWidget.
+ * 
+ * It's recommended that widgets inherit one of the A*Widget classes rather
+ * than impement I*Widget directly.
+ *****************************************************************************/
 module mde.gui.widget.Ifaces;
 
 public import mde.gui.types;
@@ -31,21 +34,24 @@
 import mde.content.Content;
 
 
-/*************************************************************************************************
+/******************************************************************************
  * Interface for parent widgets, including IWidgetManager.
  *
  * All widgets implement this via AWidget to make things simpler (code sharing).
  *
  * 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).
- *************************************************************************************************/
+ *  Layout widget: a widget containing multiple sub-widges (which hence
+ *   controls how they are laid out).
+ *****************************************************************************/
 interface IParentWidget
 {
     /** Checks for recursion of unsafe widgets to prevent infinite recursion. */
     void recursionCheck (widgetID);
     
+    /** IPPWs return self, other widgets recurse call on parent. */
+    IPopupParentWidget getParentIPPW ();
+    
     /** Child widgets should call this on their parent if their minimal size changes, since they
      * cannot resize themselves.
      * 
@@ -65,7 +71,7 @@
      * NEVER and ALWAYS often don't result in usable GUIs, and aren't expected to be used except
      * for debugging.
      * 
-     * NOTE: ANY_SUBWIDGETS can cause problems like enlarging a menu bar containing a resizable
+     * Note: ANY_SUBWIDGETS can cause problems like enlarging a menu bar containing a resizable
      * blank instead of the main part of a window. */
     enum SIZABILITY_ENUM {
         NEVER		= 0,	/// Parents are never resizable
@@ -78,13 +84,86 @@
     static const SIZABILITY = SIZABILITY_ENUM.ALL_SUBWIDGETS;	/// ditto
 }
 
+/******************************************************************************
+ * Interface for parents of popups and the widget manager.
+ * 
+ * An IPopupParentWidget (IPPW) may have 0 or 1 child IPPWs; the child may have
+ * its own IPPW. Each IPPW when added to a parent IPPW is responsible for
+ * managing one popup.
+ * 
+ * So any child IPPW should also be a descendant widget, and is usually the or
+ * a descendant of the popup.
+ * 
+ * An IPPW usually has one popup to manage (the popup being one of its child
+ * widgets), but the popup is only active if the IPPW is added as a child of
+ * its parent IPPW.
+ * 
+ * The widget manager is an IPPW, but unlike most IPPWs its popup(s), if
+ * existing, probably have nothing to do with its child IPPWs.
+ *****************************************************************************/
+interface IPopupParentWidget : IParentWidget
+{
+    /** Add caller ippw as current child IPopupParentWidget of called IPPW.
+     *
+     * ippw is added as called IPPW's child IPPW, and its functions are
+     * called to draw popup and pass events. The called IPPW's previous child
+     * IPPW is replaced.
+     * 
+     * ippw is then responsible for managing a popup. */
+    void addChildIPPW (IPopupParentWidget ippw);
+    /** Remove ippw from being the called IPPW's child IPPW and disable
+     * menuActive.
+     * 
+     * Do nothing if ippw is not the called IPPW's child IPPW.
+     *
+     * Returns: true if ippw was the child IPPW.
+     *
+     * I.e. this deactivates ippw's popup. */
+    bool removeChildIPPW (IPopupParentWidget ippw);
+    
+    /** Notify the called IPPW that it has been removed. */
+    void removedIPPW ();
+    
+    /** Set/get menuActive state.
+     *
+     * This is set on the parent IPPW when a popup menu is opened and unset
+     * when the menu is closed.
+     * If set on the parent IPPW, popup menus can be opened with just a mouse-
+     * over and buttons activated with an up-click. */
+    void menuActive (bool);
+    bool menuActive ();
+    
+    /** Called by descendant widgets such as buttons when an action occurred,
+     * which should close a menu. (But also called when not in a menu.) */
+    void menuDone ();
+    
+    /** Get the widget under cx,cy.
+     *
+     * The IPPW should first recurse the call to its child IPPW if it exists.
+     * If this doesn't yield a widget, it should try getting one from its popup
+     * and then itself. It should return the first widget found or null.
+     * 
+     * If closePopup is true and a widget isn't returned from the childIPPW,
+     * the childIPPW should be removed (to close popups when a click is not on
+     * the popup or its parent) and menuActive set false. */
+    IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup);
+    
+    /** Draw.
+     *
+     * The IPPW should first draw its popup, then call on its child IPPW if
+     * that exists. */
+    void drawPopup ();
+    
+    /// Returns true if ippw is child IPPW.
+    debug bool isChild (IPopupParentWidget ippw);
+}
 
-/*************************************************************************************************
+/******************************************************************************
  * Interface for the widget manager.
  * 
  * This class handles widget rendering, input, loading and saving.
- *************************************************************************************************/
-interface IWidgetManager : IParentWidget
+ *****************************************************************************/
+interface IWidgetManager : IPopupParentWidget
 {
     // Loading/saving:
     /** Create a widget by looking up the data for id then looking up data.ints[0] in WIDGET_TYPES.
@@ -112,6 +191,10 @@
     wdims dimData (widgetID id);			/// ditto
     void dimData (widgetID id, wdims d);		/// ditto
     
+    /** Position popup left or right of parent, or left or right of parent
+     * (flags & 1 == 1). */
+    void positionPopup (IChildWidget parent, IChildWidget popup, int flags = 0);
+    
     // Rendering:
     /** For when a widget needs redrawing.
      * 
@@ -126,21 +209,6 @@
     * provides the possibility of per-window renderers (if desired). */
     IRenderer renderer ();
     
-    /** Creates and positions a pop-up widget (widg),
-     *
-     * Set flags = 1 to place widg left or right of parent, otherwise it will be placed above or
-     * below parent.
-     *
-     * WidgetManager sets its position, draws it, and passes it click events. If a click event
-     * occurs which isn't on the popup or it's parent, it is removed (unless a newer
-     * popup is not removed).
-     *
-     * Popups currently should not change their size while active. */
-    void addPopup (IChildWidget parent, IChildWidget popup, int flags = 0);
-    /** Remove the popup added by this parent, and any popups added afterwards.
-     * If parent added multiple popups, this would just remove the top one. */
-    void removePopup (IChildWidget parent);
-    
     // User input:
     /** Add a mouse click callback.
      * 
@@ -161,7 +229,7 @@
 }
 
 
-/*************************************************************************************************
+/******************************************************************************
  * Interface for (child) widgets, i.e. all widgets other than the manager.
  *
  * A widget is a region of a GUI window which handles rendering and user-interaction for itself
@@ -190,9 +258,9 @@
  *
  * All widgets should set their own size in this() or setup() (must be setup() if it could change),
  * 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 : IParentWidget
+interface IChildWidget
 {
 //BEGIN Load and save
     /** 2nd stage of initialization for widgets; also called on some changes.
@@ -309,9 +377,9 @@
     /** Called when keyboard input focus is lost. */
     void keyFocusLost ();
     
-    /** Called when the mouse moves over the button and when it leaves. No need to call
-     * requestRedraw. */
-    void highlight (bool state);
+    /** Called on all widgets when the mouse moves over it (state == true) and
+     * when it leaves (state == false). */
+    void underMouse (bool state);
     
     /** When a pop-up is closed the manager calls requestRedraw and this function on its parent. */
     void popupClose ();
--- a/mde/gui/widget/PopupMenu.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/widget/PopupMenu.d	Wed Jan 21 13:01:40 2009 +0000
@@ -13,14 +13,12 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/*************************************************************************************************
+/******************************************************************************
  * Pop-up menus based on content structures.
- *************************************************************************************************/
+ *****************************************************************************/
 module mde.gui.widget.PopupMenu;
 
-import mde.gui.widget.Widget;
-import mde.gui.widget.TextWidget;
-import mde.gui.widget.layout;
+import mde.gui.widget.AParentWidget;
 
 import mde.content.miscContent;
 import mde.gui.exception;
@@ -33,17 +31,18 @@
     }
 }
 
-/*************************************************************************************************
+/******************************************************************************
  * Widget which pops up a menu based on a content.
- *************************************************************************************************/
-class PopupMenuWidget : AParentSingleWidget
+ *****************************************************************************/
+class PopupMenuWidget : APopupParentWidget
 {
     this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
 	content = c;
 	WDCMinCheck (data, 1,1, content);
         super (mgr, parent, id);
         
-        subWidget = mgr.makeWidget (this, data.strings[0], content);
+        popup = mgr.makeWidget (this, data.strings[0], content);
+        subWidgets = [popup];
 	
 	adapter = mgr.renderer.getAdapter;
 	adapter.text = content.toString (1);
@@ -54,22 +53,33 @@
     
     override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
 	if (b == 1 && state == true) {
-	    // If active, the popup is closed by WidgetManager since the click isn't on the popup.
 	    if (!pushed) {
-		pushed = true;
-		mgr.addPopup (this, subWidget);	// causes redraw
-		mgr.addClickCallback (&openMenuCallback);	// prevents first up-click from closing menu, if on self.
-	    }
+                parentIPPW.addChildIPPW (this);
+                parentIPPW.menuActive = true;
+                mgr.positionPopup (this, popup);
+                pushed = true;
+            } else {
+                // NOTE: perhaps shouldn't do anything when
+                // parentIPPW.parentIPPW.menuActive
+                // (this causes funny behaviour when clicking a submenu):
+                parentIPPW.removeChildIPPW (this);
+            }
 	}
 	return 0;
     }
     
-    override void popupClose () {
+    override void removedIPPW () {
+        super.removedIPPW;
 	pushed = false;
     }
-    override bool popupParentClick () {
-        pushed = false;
-        return true;
+    
+    override void underMouse (bool state) {
+        if (state && !pushed && parentIPPW.menuActive) {
+            parentIPPW.addChildIPPW (this);
+            menuActive = true;
+            mgr.positionPopup (this, popup, 1);	// causes redraw
+            pushed = true;
+        }
     }
     
     override void draw () {
@@ -78,103 +88,7 @@
     }
     
 protected:
-    bool openMenuCallback (wdabs cx, wdabs cy, ubyte b, bool state) {
-	if (b == 1 && state == false) {	// receive first up-click
-	    mgr.removeCallbacks (cast(void*) this);
-	    if (cx >= x && cx < x+w && cy >= y && cy < y+h)
-		return true;		// up-click is on self; don't close the menu
-	}
-	return false;
-    }
     bool pushed = false;
     IRenderer.TextAdapter adapter;
     IContent content;
 }
-
-/*************************************************************************************************
- * Widget which pops up a sub-menu based on a content on mouse-over.
- *************************************************************************************************/
-class SubMenuWidget : PopupMenuWidget
-{
-    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) {
-        return 0;
-    }
-    
-    override void highlight (bool state) {
-        if (state && !pushed) {
-            pushed = true;
-            mgr.addPopup (this, subWidget, 1);	// causes redraw
-        }
-    }
-}
-
-/*************************************************************************************************
- * A function which returns a ContentListWidget or MenuButtonContentWidget.
- *************************************************************************************************/
-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,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, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
-    if (c is null) throw new ContentException;
-    if (cast(IContentList) 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);
-}
-
-/*************************************************************************************************
- * A menu content-button, like ButtonContentWidget, but which can be activated with the up-click.
- *************************************************************************************************/
-class MenuButtonContentWidget : ATextWidget
-{
-    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) {
-	if (!(flags & 3)) return false;	// string or renderer (and possibly font) changed
-	adapter.text = content.toString(1);
-	return super.setup (n, 3);	// force redimensioning
-    }
-    
-    override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
-	if (b == 1) {	// on up or down click
-	    pushed = false;
-	    mgr.requestRedraw;
-	    content.endEvent;
-	}
-	return 0;
-    }
-    
-    override void highlight (bool state) {
-	pushed = state;
-    }
-    
-    override void draw () {
-	mgr.renderer.drawButton (x,y, w,h, pushed);
-	adapter.draw (x,y);
-    }
-    
-protected:
-    EventContent content;
-    bool pushed;
-}
--- a/mde/gui/widget/TextWidget.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/widget/TextWidget.d	Wed Jan 21 13:01:40 2009 +0000
@@ -16,7 +16,7 @@
 /** Simple text widgets. */
 module mde.gui.widget.TextWidget;
 
-import mde.gui.widget.Widget;
+import mde.gui.widget.AChildWidget;
 import mde.gui.exception;
 import mde.gui.renderer.IRenderer;
 import mde.content.AStringContent;
@@ -28,7 +28,7 @@
 }
 
 /** Base text widget. */
-class ATextWidget : AWidget
+class ATextWidget : AChildWidget
 {
     /** Set the adapter first:
      * adapter = mgr.renderer.getAdapter (...); */
--- a/mde/gui/widget/Widget.d	Sat Jan 17 16:11:26 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,377 +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/>. */
-
-/*************************************************************************************************
- * GUI Widget module.
- *
- * This module contains some base widget classes suitable for widget classes to inherit. However,
- * inheriting one of them is by no means necessary for a widget so long as the IWidget interface
- * is implemented.
- * 
- * Abstract widget classes have an 'A' prepended to the name, similar to the 'I' convention for
- * interfaces.
- *************************************************************************************************/
-module mde.gui.widget.Widget;
-
-public import mde.gui.widget.Ifaces;
-import mde.content.Content;
-import mde.gui.exception;
-
-debug {
-    import tango.util.log.Log : Log, Logger;
-    private Logger logger;
-    static this () {
-        logger = Log.getLogger ("mde.gui.widget.Widget");
-    }
-}
-
-
-/*************************************************************************************************
- * An abstract base widget class.
- *
- * This abstract class, and the more concrete FixedWidget and ScalableWidget classes provides a
- * useful basic implementation for widgets. Widgets need not inherit these (they only need
- * implement IWidget); they are simply provided for convenience and to promote code reuse.
- *************************************************************************************************/
-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);
-    }
-    
-    // Parent widgets need to implement this.
-    override void minWChange (IChildWidget widget, wdim mw) {}
-    override void minHChange (IChildWidget widget, wdim mh) {}
-//END IParentWidget methods
-    
-//BEGIN Load and save
-    // Base this() for child Widgets.
-    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().
-    override bool setup (uint,uint) {
-	return false;
-    }
-    
-    // Don't save any data: fine for many widgets.
-    override bool saveChanges () {
-        return false;
-    }
-//END Load and save
-    
-//BEGIN Size and position
-    // default to not resizable
-    override bool isWSizable () {
-        return false;
-    }
-    override bool isHSizable () {
-        return false;
-    }
-    
-    /* Return minimal/fixed size. */
-    override wdim minWidth () {
-        return mw;
-    }
-    override wdim minHeight () {
-        return mh;
-    }
-    
-    override wdim width () {
-        return w;
-    }
-    override wdim height() {
-        return h;
-    }
-    
-    override wdabs xPos () {
-	return x;
-    }
-    override wdabs yPos () {
-	return y;
-    }
-    
-    /* Set size: minimal size is (mw,mh). Note that both resizable and fixed widgets should allow
-     * enlarging, so in both cases this is a correct implementation. */
-    override void setWidth (wdim nw, int) {
-        debug if (nw < mw) logger.warn ("Widget width set below minimal size");
-        w = (nw >= mw ? nw : mw);
-    }
-    override void setHeight (wdim nh, int) {
-        debug if (nh < mh) logger.warn ("Widget height set below minimal size");
-        h = (nh >= mh ? nh : mh);
-    }
-    
-    override void setPosition (wdim nx, wdim ny) {
-        x = nx;
-        y = ny;
-    }
-//END Size and position
-    
-//BEGIN Events
-    /* This method is only called when the location is over this widget; hence for all widgets
-     * without children this method is valid. */
-    override IChildWidget getWidget (wdim cx, wdim cy) {
-        debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
-        return this;
-    }
-    
-    // Should be valid for any widget.
-    override bool onSelf (wdabs cx, wdabs cy) {
-        return cx >= x && cx < x + w && cy >= y && cy < y + h;
-    }
-    
-    /* Dummy event method (suitable for all widgets which don't respond to events). */
-    override int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
-	return 0;
-    }
-    
-    /* Dummy functions: suitable for widgets with no text input. */
-    override void keyEvent (ushort, char[]) {}
-    override void keyFocusLost () {}
-    
-    // Currently only applies to popup widgets.
-    override void highlight (bool state) {}
-    
-    // Only useful to widgets creating popups.
-    override void popupClose () {}
-    override bool popupParentClick () {
-        return true;
-    }
-//END Events
-    
-    /* Basic draw method: draw the background (all widgets should do this). */
-    override void draw () {
-        mgr.renderer.drawWidgetBack (x,y, w,h);
-    }
-    
-    // Debug function to print size info. Intended to be correct not optimal.
-    debug override void logWidgetSize () {
-        logger.trace ("Current size: {,4},{,4}; minimal: {,4},{,4}; sizable: {,5},{,5} - {,-50} {}", this.width, this.height, this.minWidth, this.minHeight, this.isWSizable, this.isHSizable, this, id);
-    }
-    
-protected:
-    /**********************************************************************************************
-     * Widgets may use W*Check as a utility to check for existance of data. Its use is encouraged,
-     * so that the checks can easily be updated should WidgetData be changed.
-     * 
-     * Variants:
-     *	WDCheck checks the exact length of integer and string data.
-     *	WDCCheck checks data as WDCheck and that the content passed is valid.
-     *	WDCMinCheck does the same as WDCCheck, but allows more data than required (used by some
-     *	generic widgets).
-     * 
-     * Params:
-     *  data    = the WidgetData to check lengths of
-     *  n_ints  = number of integers wanted
-     *  n_strings= number of strings (default 0 since not all widgets use strings)
-     *********************************************************************************************/
-    void WDCheck (WidgetData data, size_t n_ints, size_t n_strings = 0) {
-	if (data.ints.length    != n_ints ||
-	    data.strings.length != n_strings)
-	    throw new WidgetDataException (this);
-    }
-    /** ditto */
-    void WDCCheck (WidgetData data, size_t n_ints, size_t n_strings, IContent c) {
-	if (data.ints.length    != n_ints ||
-	    data.strings.length != n_strings)
-	    throw new WidgetDataException (this);
-	if (c is null)
-	    throw new ContentException (this);
-    }
-    /** ditto */
-    void WDCMinCheck (WidgetData data, size_t n_ints, size_t n_strings, IContent c) {
-	if (data.ints.length    < n_ints ||
-	    data.strings.length < n_strings)
-	    throw new WidgetDataException (this);
-	if (c is null)
-	    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
-    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.
-}
-
-/*************************************************************************************************
- * Abstract base widget classes to facilitate parent widgets.
- * 
- * Parent widgets probably need to overload these functions (from AWidget):
- * setup, saveChanges, setPosition, getWidget, draw, setWidth and setHeight.
- *************************************************************************************************/
-abstract class AParentWidget : AWidget
-{
-    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
-        super (mgr, parent, id);
-    }
-    
-    override bool setup (uint n, uint flags) {
-        debug (mdeWidgets) logger.trace ("AParentWidget.setup");
-        bool c = false;
-	foreach (w; subWidgets) {
-            debug assert (w, "AParentWidget: w is null");
-	    c |= w.setup (n,flags);
-	}
-	return c;
-    }
-    
-    override bool saveChanges () {
-        bool c = false;
-        foreach (w; subWidgets)
-            c |= w.saveChanges;
-        return c;
-    }
-    
-    size_t getWidgetIndex (IChildWidget widg) {
-        foreach (i,w; subWidgets)
-            if (w is widg)
-                return i;
-        
-        throw new GuiException ("getWidgetIndex: widget not found (code error)");
-    }
-    
-    debug override void logWidgetSize () {
-        super.logWidgetSize;
-        foreach (widg; subWidgets)
-            widg.logWidgetSize;
-    }
-    
-protected:
-    IChildWidget[] subWidgets;
-}
-/** ditto */
-abstract class AParentSingleWidget : AWidget
-{
-    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
-        super (mgr, parent, id);
-    }
-    
-    override bool setup (uint n, uint flags) {
-        debug (mdeWidgets) logger.trace ("AParentSingleWidget.setup");
-        debug assert (subWidget, "AParentSingleWidget: subWidget is null");
-	return subWidget.setup (n,flags);
-    }
-    
-    override bool saveChanges () {
-	return subWidget.saveChanges;
-    }
-    
-    debug override void logWidgetSize () {
-        super.logWidgetSize;
-        subWidget.logWidgetSize;
-    }
-	
-protected:
-    IChildWidget subWidget;
-}
-
-/** A base for fixed-size widgets taking their size from the creation data. */
-class FixedWidget : AWidget {
-    // Check data.length is at least 3 before calling!
-    /** Constructor for a fixed-size [blank] widget.
-     *
-     * Widget uses the initialisation data:
-     * [widgetID, w, h]
-     * where w, h is the fixed size. */
-    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];
-    }
-}
-
-/** A base for resizable widgets. */
-class SizableWidget : AWidget {
-    // Check data.length is at least 1 before calling!
-    /// Constructor for a completely resizable [blank] widget.
-    this (IWidgetManager mgr, IParentWidget parent, widgetID id) {
-        super (mgr, parent, id);
-    }
-    
-    override bool isWSizable () {
-        return true;
-    }
-    override bool isHSizable () {
-        return true;
-    }
-}
-
-/** For pressable buttons.
- *
- * Overriding classes should implement this() (setting the size), draw() and activated(). */
-abstract class AButtonWidget : AWidget
-{
-    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.
-    override void draw () {
-        mgr.renderer.drawButton (x,y, w,h, pushed);
-    }
-    
-    /// Handles the down-click
-    override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
-        if (b == 1 && state == true) {
-            pushed = true;
-            mgr.requestRedraw;
-            mgr.addClickCallback (&clickWhilePushed);
-            mgr.addMotionCallback (&motionWhilePushed);
-        }
-	return 0;
-    }
-    
-    /// Called when a mouse click event occurs while held; handles up-click
-    bool clickWhilePushed (wdabs cx, wdabs cy, ubyte b, bool state) {
-        if (b == 1 && state == false) {
-            if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event
-                activated();
-            
-            pushed = false;
-            mgr.requestRedraw;
-            mgr.removeCallbacks (cast(void*) this);
-            
-            return true;
-        }
-        return false;
-    }
-    /// Called when a mouse motion event occurs while held; handles pushing in/out on hover
-    void motionWhilePushed (wdabs cx, wdabs cy) {
-        bool oldPushed = pushed;
-        if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true;
-        else pushed = false;
-        if (oldPushed != pushed)
-            mgr.requestRedraw;
-    }
-    
-    /// The action triggered when the button is clicked...
-    void activated ();
-    
-protected:
-    bool pushed = false;        /// True if button is pushed in (visually)
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/contentFunctions.d	Wed Jan 21 13:01:40 2009 +0000
@@ -0,0 +1,91 @@
+/* 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/>. */
+
+/******************************************************************************
+ * Functions returning new content widgets dependant on content.
+ *****************************************************************************/
+module mde.gui.widget.contentFunctions;
+
+import mde.gui.exception;
+import mde.gui.widget.Ifaces;
+import mde.gui.widget.TextWidget;
+import mde.gui.widget.layout;
+import mde.gui.widget.PopupMenu;
+import mde.gui.widget.miscContent;
+
+import mde.content.AStringContent;
+import mde.content.miscContent;
+import Items = mde.content.Items;
+
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+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, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
+    // Note: SAFE_RECURSION enabled
+    if (c is null) throw new ContentException;
+    if (cast(AStringContent) c) {
+        if (cast(EnumContent) c)	// can be PopupMenuWidget or ContentListWidget
+            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);
+}
+
+/******************************************************************************
+ * A function which returns a ContentListWidget or MenuButtonContentWidget.
+ *****************************************************************************/
+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,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);
+}
+
+/******************************************************************************
+ * A function which returns a PopupMenuWidget or MenuButtonContentWidget.
+ *****************************************************************************/
+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 PopupMenuWidget(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);
+}
--- a/mde/gui/widget/layout.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/widget/layout.d	Wed Jan 21 13:01:40 2009 +0000
@@ -16,7 +16,7 @@
 /// Gui layout widgets.
 module mde.gui.widget.layout;
 
-import mde.gui.widget.Widget;
+import mde.gui.widget.AParentWidget;
 import mde.gui.exception;
 
 import mde.content.miscContent;
@@ -35,11 +35,7 @@
  * Encapsulates a grid of Widgets.
  *
  * Currently there is no support for changing number of cells, sub-widgets or sub-widget properties
- * (namely isW/HSizable and minimal size) after this() has run.
- *
- * Since a grid with either dimension zero is not useful, there must be at least one sub-widget.
- *
- * The grid has no border but has spacing between widgets.
+ * (namely isW/HSizable) after this() has run.
  *************************************************************************************************/
 class GridLayoutWidget : GridWidget
 {
@@ -48,9 +44,9 @@
      * Widget uses the initialisation data:
      * ---
      * ints = [widget_type, align_flags, rows, cols]
-     * // or with column widths and row heights:
-     * ints = [widget_type, align_flags, rows, cols, col1width, ..., colCwidth, row1height, ..., rowRheight]
      * strings = [w11, w12, ..., w1C, ..., wR1, ..., wRC]
+     * // dimData may be:
+     * dimData = [col1width, ..., colCwidth, row1height, ..., rowRheight]
      * ---
      * where R and C are the number of rows and columns, and wij is the ID (from parent Window's
      * list) for the widget in row i and column j. The number of parameters must be r*c + 3.
@@ -136,11 +132,19 @@
  * Backend for grid-based (includes column/row) layout widgets.
  *
  * A deriving class must at least do some work in it's constructor (see Ddoc for this() below)
- * and provide an implementation of getCreationData() (unless Widget's version is sufficient).
+ * and provide an implementation of saveChanges() (unless Widget's version is sufficient).
  *
  * Since a grid with either dimension zero is not useful, there must be at least one sub-widget.
  *
- * The grid has no border but has spacing between widgets.
+ * The grid has no border but optionally has spacing between widgets.
+ * 
+ * Several flags are tested against ints[1]:
+ * $(TABLE
+ * $(TR $(TD 1) $(TD Column alignment is shared against other instances of thes widget id))
+ * $(TR $(TD 2) $(TD Row alignment is shared against other instances of thes widget id))
+ * $(TR $(TD 4) $(TD Spacing is inserted between elements; the renderer may draw this))
+ * $(TR $(TD 8) $(TD For ContentListWidget only, list is horizontal instead of vertical))
+ * )
  *************************************************************************************************/
 // Note: mw, mh inherited from AWidget are not used; use col.mw, row.mw instead.
 abstract class GridWidget : AParentWidget
@@ -851,8 +855,8 @@
         instances = new HashMap!(widgetID,AlignColumns);
     }
     
-    alias IChildWidget.SIZABILITY SIZABILITY;
-    alias IChildWidget.SIZABILITY_ENUM SIZABILITY_ENUM;
+    alias IParentWidget.SIZABILITY SIZABILITY;
+    alias IParentWidget.SIZABILITY_ENUM SIZABILITY_ENUM;
     
     debug invariant()
     {
--- a/mde/gui/widget/miscContent.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/widget/miscContent.d	Wed Jan 21 13:01:40 2009 +0000
@@ -13,21 +13,16 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/*************************************************************************************************
- * A function to return content widgets and some miscellaneous content display/editing widgets.
- *************************************************************************************************/
+/******************************************************************************
+ * Widgets using content not organised into other modules.
+ *****************************************************************************/
 module mde.gui.widget.miscContent;
 
-import mde.gui.widget.Widget;
+import mde.gui.widget.AParentWidget;
 import mde.gui.exception;
-import mde.gui.widget.TextWidget;
-import mde.gui.widget.layout;
-import mde.gui.widget.PopupMenu;
 
-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,42 +32,6 @@
     }
 }
 
-/*************************************************************************************************
- * 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, IParentWidget parent, widgetID id, WidgetData data, IContent c) {
-    // Note: SAFE_RECURSION enabled
-    if (c is null) throw new ContentException;
-    if (cast(AStringContent) c) {
-        if (cast(EnumContent) c)	// can be PopupMenuWidget or ContentListWidget
-            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
 {
--- a/mde/gui/widget/miscWidgets.d	Sat Jan 17 16:11:26 2009 +0000
+++ b/mde/gui/widget/miscWidgets.d	Wed Jan 21 13:01:40 2009 +0000
@@ -16,7 +16,7 @@
 /** Some GUI Miscelaneas widgets. */
 module mde.gui.widget.miscWidgets;
 
-import mde.gui.widget.Widget;
+import mde.gui.widget.AChildWidget;
 import mde.gui.exception;
 import mde.gui.renderer.IRenderer;