changeset 175:1cbde9807293

Compile/link-time fixes for ldc & non-debug builds. Moved WidgetManager to widget/ Reverted IChildWidget to an interface, not an abstract class. Introduced a work-around for a compiler problem. May not cover all cases.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 11 Sep 2009 20:56:53 +0200
parents 3d58adc17d20
children d5d5fe04ca6c
files codeDoc/jobs.txt mde/content/AStringContent.d mde/file/mergetag/MTTagWriter.d mde/gui/WidgetLoader.d mde/gui/WidgetManager.d mde/gui/widget/AChildWidget.d mde/gui/widget/Ifaces.d mde/gui/widget/ParentContent.d mde/gui/widget/TextWidget.d mde/gui/widget/WidgetManager.d mde/gui/widget/layout.d mde/gui/widget/miscContent.d
diffstat 12 files changed, 689 insertions(+), 692 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Mon Aug 31 13:54:23 2009 +0200
+++ b/codeDoc/jobs.txt	Fri Sep 11 20:56:53 2009 +0200
@@ -26,14 +26,6 @@
 Implement a RootWidget moving functionality out of AWidgetManager, etc., now, or later?
 RequestRedraw becomes a function of the renderer.
 
-Undefined reference problem: (see ~/d/small/tests/compilerErrors/packageFunc.d)
-Solution: access via a class base instead of an interface base.
-Two ways to implement:
-  Leave IChildWidget an interface, but change stored refs to AChildWidget and cast references passed in functions before calling member functions.
-  Use AChildWidget directly or an abstract base class instead of IChildWidget, so that interfaced functions can pass the same type stored.
-Making IChildWidget an abstact class seems to have partially solved it. But IPopupParentWidget (needs to be an interface), etc., still need some help.
-Presumably setWidth is getting called from IChildWidget instead of AChildWidget now. ??
-More link errors with ldc.
 
 
 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent):
--- a/mde/content/AStringContent.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/content/AStringContent.d	Fri Sep 11 20:56:53 2009 +0200
@@ -184,7 +184,7 @@
      * Returns: true if string successfully converted to value.
      * 
      * Should never throw; should reset sv at least when returning false. */
-    bool endEdit ();
+    abstract bool endEdit ();
     
 protected:
     //TODO: copy-on-assign, copy-on-edit, or what?
--- a/mde/file/mergetag/MTTagWriter.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/file/mergetag/MTTagWriter.d	Fri Sep 11 20:56:53 2009 +0200
@@ -44,10 +44,10 @@
 abstract class MTTagWriter
 {
     /// Set the current section
-    void sectionTag (char[] section);
+    abstract void sectionTag (char[] section);
     
     /// Write a data tag
-    void dataTag (char[] type, char[] id, char[] data);
+    abstract void dataTag (char[] type, char[] id, char[] data);
     
     /// Change the section if necessary and write a data tag
     void writeTag (char[] section, char[] type, char[] id, char[] data) {
@@ -57,7 +57,7 @@
     }
     
     /// Close the file
-    void close ();
+    abstract void close ();
     
 protected:
     char[] sec;		// current section
--- a/mde/gui/WidgetLoader.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/gui/WidgetLoader.d	Fri Sep 11 20:56:53 2009 +0200
@@ -224,7 +224,7 @@
     //BEGIN WidgetManagement methods
     /** Called before saving to make sure no data is being edited (and thus
      *  would not be saved). */
-    void preSave ();
+    abstract void preSave ();
     //END WidgetManagement methods
     
     
--- a/mde/gui/WidgetManager.d	Mon Aug 31 13:54:23 2009 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,651 +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/>. */
-
-/******************************************************************************
- * 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.
- * 
- * Public non IWidget* methods should be thread-safe.
- *****************************************************************************/
-module mde.gui.WidgetManager;
-
-import mde.gui.WidgetDataSet;
-import mde.gui.widget.Ifaces;
-import mde.gui.renderer.createRenderer;
-
-import imde = mde.imde;
-import mde.content.Content;
-import mde.content.ServiceContent;
-debug import mde.content.miscContent;	// Debug menu
-debug import mde.content.Debug;
-
-// Widgets to create:
-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.ParentContent;
-import mde.gui.widget.AParentWidget;
-
-public import tango.core.sync.Mutex;
-import tango.util.log.Log : Log, Logger;
-import tango.io.Console;	// to print exception stack-trace
-import tango.util.container.SortedMap;
-
-private Logger logger;
-static this () {
-    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.
- *
- * Methods in this class are only intended for use within the gui package,
- * either by widgets (the IXXXWidget methods implementing from an interface in
- * widgets.Ifaces.d) or by a derived class (back-end methods doing widget
- * work). None of these methods are intended to be thread-safe when called
- * concurrently on the same WidgetManager instance, but they should be thread-
- * safe for calling on separate instances.
- * 
- * This abstract class exists solely for separating out some of the functionality.
- *****************************************************************************/
-abstract scope class AWidgetManager : IWidgetManager
-{
-    /** Construct a new widget manager.
-     *
-     * Params:
-     *	name = The file name of the config for this GUI (to identify multiple GUIs). */
-    protected this (char[] name) {
-        auto p = "MiscOptions.l10n" in Content.allContent;
-        assert (p, "MiscOptions.l10n not created!");
-        p.addCallback (&reloadStrings);
-	
-	serviceContent = ServiceContentList.createItems (name);
-	assert (cast (IServiceContent) Content.get ("menus.services."~name));
-	
-        debug {	// add a debug-mode menu
-            auto lWS = new EventContent ("menus.debug."~name~".logWidgetSize");
-            lWS.addCallback (&logWidgetSize);
-        }
-    }
-    
-public:
-    //BEGIN IParentWidget methods
-    // If call reaches the widget manager there isn't any recursion.
-    //NOTE: should be override
-    final void recursionCheck (widgetID, IContent) {}
-    
-    override void minWChange (IChildWidget widget, wdim nmw) {
-	if (widget !is childRoot) {	// Probably because widget is a popup widget
-	    // This may get called from a CTOR, hence we can't check widget is one of childContext, etc.
-	    if (widget.width < nmw)
-		widget.setWidth (nmw, -1);
-	    return;
-	}
-        mw = nmw;
-        if (w < nmw) {
-            childRoot.setWidth (nmw, -1);
-            w = nmw;
-        }
-        childRoot.setPosition (0,0);
-        requestRedraw;
-    }
-    override void minHChange (IChildWidget widget, wdim nmh) {
-	if (widget !is childRoot) {
-	    if (widget.height < nmh)
-		widget.setHeight (nmh, -1);
-	    return;
-	}
-        mh = nmh;
-        if (h < nmh) {
-            childRoot.setHeight (nmh, -1);
-            h = nmh;
-        }
-        childRoot.setPosition (0,0);
-        requestRedraw;
-    }
-    //END IParentWidget methods
-    
-    //BEGIN IWidget methods
-    public override bool saveChanges () {
-	bool ret = childRoot.saveChanges;
-	ret |= childContext.saveChanges;
-	if (childDragged !is null)
-	    ret |= childDragged.saveChanges;
-	return ret;
-    }
-    
-    override bool dropContent (IContent content) {
-	return false;
-    }
-    //END IWidget methods
-    
-    //BEGIN IPopupParentWidget methods
-    override IPopupParentWidget getParentIPPW () {
-        return this;
-    }
-    
-    override void addChildIPPW (IPopupParentWidget ippw) {
-	requestRedraw;
-	if (ippw is childContext) {	// special handling - a separate IPPW
-	    contextActive = true;
-	    return;
-	}
-        if (childIPPW)
-            childIPPW.removedIPPW;
-        childIPPW = ippw;
-    }
-    override bool removeChildIPPW (IPopupParentWidget ippw) {
-	if (ippw is childContext && contextActive) {
-	    childContext.removedIPPW;
-	    contextActive = false;
-	    return true;
-	}
-        if (childIPPW !is ippw) return false;
-        childIPPW.removedIPPW;
-        childIPPW = null;
-        mAIPPW = MenuPosition.INACTIVE;
-        requestRedraw;
-        return true;
-    }
-    
-    override void menuActive (MenuPosition mA) {
-        mAIPPW = mA;
-        if (childIPPW)
-            childIPPW.menuActive = mA;
-	if (contextActive)
-	    childContext.menuActive = mA;
-    }
-    override MenuPosition menuActive () {
-        return mAIPPW;
-    }
-    override MenuPosition parentMenuActive () {
-        return MenuPosition.INACTIVE;
-    }
-    
-    // Note: also triggered by non-popup widgets
-    override void menuDone () {}
-    
-    override IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup) {
-	IChildWidget ret;
-	// Don't bother with childDragged; it has no interaction
-	if (contextActive) {
-	    ret = childContext.getPopupWidget (cx, cy, closePopup);
-	    if (ret) return ret;
-	    if (closePopup) {
-		childContext.removedIPPW;
-		contextActive = false;
-		requestRedraw;
-	    }
-        }
-        if (childIPPW) {
-            ret = childIPPW.getPopupWidget (cx, cy, closePopup);
-            if (ret) return ret;
-            if (closePopup) {
-                removeChildIPPW (childIPPW);
-            }
-        }
-        return null;
-    }
-    
-    override void drawPopup () {
-	if (childIPPW)
-	    childIPPW.drawPopup;
-	if (contextActive)
-	    childContext.drawPopup();
-	if (childDragged)
-	    childDragged.draw();
-    }
-    
-    debug protected override bool isChild (IPopupParentWidget ippw) {
-	if (contextActive && ippw is childContext)
-	    return true;
-        return ippw is childIPPW;
-    }
-    
-    override void removedIPPW () {}	// irrelevant
-    //END IPopupParentWidget 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. Exception printed to stderr.");
-	    //TODO: find a standard way to output exceptions, and implement everywhere:
-	    e.writeOut(delegate void(char[]s){ Cerr(s); });
-        }
-    
-        return new DebugWidget (this, parent, id, data, content);
-    }
-    
-    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.
-    }
-    
-    IRenderer renderer () {
-        assert (rend !is null, "WidgetManager.renderer: rend is null");
-        return rend;
-    }
-    
-    MenuPosition positionPopup (IChildWidget parent, IChildWidget popup, MenuPosition position = MenuPosition.INACTIVE) {
-	debug assert (parent && popup, "positionPopup: null widget");
-	debug if (Debug.logPopupPositioning())
-	    logger.trace ("Placing popup {} in relation to parent {}; input position: {}", popup, parent, position);
-        wdim w = popup.width,
-             h = popup.height,
-             x, y;
-        if (position & MenuPosition.ACTIVE) {
-            y = parent.yPos;				// height flush with top
-            if (y+h > this.h) y += parent.height - h;	// or bottom
-	    if (position & MenuPosition.LEFT) {		// previously left
-		x = parent.xPos - w;			// on left
-		if (x < 0) {
-		    x = parent.xPos + parent.width;	// on right
-		    position = MenuPosition.RIGHT;
-		}
-	    } else {					// previously right or above/below
-		x = parent.xPos + parent.width;		// on right
-		position = MenuPosition.RIGHT;
-		if (x+w > this.w) {
-		    x = parent.xPos - w;		// or left
-		    position = MenuPosition.LEFT;
-		}
-	    }
-        } else {
-	    wdim pw = parent.width;
-	    if (popup.minWidth <= pw)
-		popup.setWidth (pw, -1);		// neatness
-	    x = parent.xPos;				// align on left edge
-            if (x+w > this.w) x += pw - w;		// align on right edge
-            y = parent.yPos + parent.height;		// place below
-            if (y+h > this.h) y = parent.yPos - h;	// or above
-	    position = MenuPosition.ACTIVE;
-        }
-        if (x < 0) x = 0;	// may be placed partially off-screen
-        if (y < 0) y = 0;
-        popup.setPosition (x, y);
-	debug if (Debug.logPopupPositioning())
-	    logger.trace ("Placed popup {} of size ({},{}) at ({},{}); output position: {}", popup, w,h, x,y, position);
-	return position;
-    }
-
-    void requestRedraw () {
-        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
-    }
-    //END IWidgetManager methods
-    
-    debug void logWidgetSize (IContent) {
-        logger.trace ("size: {,4},{,4}; minimal: {,4},{,4} - WidgetManager", w,h, mw,mh);
-	logger.trace ("childRoot:");
-	childRoot.logWidgetSize;
-	logger.trace ("childContext:");
-	childContext.logWidgetSize;
-	if (childDragged !is null) {
-	    logger.trace ("childDragged:");
-	    childDragged.logWidgetSize;
-	}
-    }
-    
-protected:
-    // These methods are called by derived classes to do the widget-management work
-    //BEGIN WidgetManagement methods
-    /** Second stage of widget loading.
-     *
-     * Widget data should be loaded before this is called. */
-    final void createWidgets () {
-        // The renderer needs to be created on the first load, but not after this.
-        if (rend is null)
-            rend = createRenderer (rendName);
-        
-        debug (mdeWidgets) logger.trace ("Creating root widget...");
-        childRoot = makeWidget (this, "root");
-        debug (mdeWidgets) logger.trace ("Setting up root widget...");
-        childRoot.setup (0, 3);
-        
-        mw = childRoot.minWidth;
-        mh = childRoot.minHeight;
-	matchMinimalSize ();
-        
-        debug (mdeWidgets) logger.trace ("Setting size and position of root widget...");
-        childRoot.setWidth  (w, -1);
-        childRoot.setHeight (h, -1);
-        childRoot.setPosition (0,0);
-        debug (mdeWidgets) logger.trace ("Done creating root widget.");
-	
-	childContext = new PopupHandlerWidget (this, this, "contextHandler", "context", serviceContent);
-	childContext.setup (0,3);
-	debug (mdeWidgets) logger.trace ("Created context handler widget.");
-	
-	underMouse = childRoot;	// must be something
-    }
-    
-    /** Draw all widgets */
-    final void wmDrawWidgets() {
-	if (childRoot)
-	    childRoot.draw;
-	drawPopup;
-    }
-    
-    /** For mouse click events.
-     *
-     * Sends the event on to the relevant windows and all click callbacks. */
-    final void wmMouseClick (wdabs cx, wdabs cy, ubyte b, bool state) {
-	if (childRoot is null) return;
-	
-	// Update underMouse to get the widget clicked on
-	updateUnderMouse (cx, cy, state);
-	
-	// end of a drag?
-	if (dragStart !is null && b == dragButton && state == false) {
-	    IChildWidget dS = dragStart;
-	    dragStart = null;
-	    childDragged = null;
-	    requestRedraw;
-	    if (dS.dragRelease (cx, cy, underMouse))
-		return;
-	}
-	
-	// Disable keyboard input if on another widget:
-	if (keyFocus && keyFocus !is underMouse) {
-	    keyFocus.keyFocusLost;
-	    keyFocus = null;
-	    setLetterCallback (null);
-	}
-	
-	// Finally, post the actual event:
-	if (b == 3 && state) {	// right click - open context menu
-	    Content contextContent = cast(Content)underMouse.content;
-	    if (contextContent !is null) {
-		serviceContent.setContent (contextContent);
-		childContext.openMenu (underMouse, contextContent);
-	    }
-	} else {	// post other button presses to clickEvent
-	    int ret = underMouse.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
-	    if (ret & 1) {	// keyboard input requested
-		keyFocus = underMouse;
-		setLetterCallback (&underMouse.keyEvent);
-	    }
-	    if (ret & 2 && dragStart is null) {	// drag events requested
-		dragStart = underMouse;
-		dragButton = b;	// currently we allow any button to be used for a drag, but.. ?
-		if (ret & 4) {
-		    IContent c = underMouse.content();
-		    if (c) {	// NOTE: creates a new widget, not optimal
-			childDragged = new DisplayContentWidget (this, this, "dragContentDisplay", WidgetData ([0], []), c);
-			childDragged.setup (0, 3);
-			dragX = underMouse.xPos - cx;
-			dragY = underMouse.yPos - cy;
-			childDragged.setPosition (cx + dragX, cy + dragY);
-		    }
-		}
-	    }
-	}
-    }
-    
-    /** For mouse motion events.
-     *
-     * Lock on mutex before calling. Pass new mouse coordinates. */
-    final void wmMouseMotion (wdabs cx, wdabs cy) {
-	updateUnderMouse (cx, cy, false);
-	
-	if (dragStart !is null) {
-	    dragStart.dragMotion (cx, cy, underMouse);
-	    if (childDragged !is null) {
-		childDragged.setPosition (cx + dragX, cy + dragY);
-		requestRedraw;
-	    }
-	}
-    }
-    
-    
-    /** A change callback on MiscOptions.l10n content to update widgets.
-     *
-     * Relies on another callback reloading translations to content first! */
-    final void reloadStrings (IContent) {
-        synchronized(mutex) {
-            if (childRoot is null) return;
-            childRoot.setup (++setupN, 2);
-            childRoot.setWidth  (w, -1);
-            childRoot.setHeight (h, -1);
-            childRoot.setPosition (0,0);
-	    childContext.setup (setupN, 2);
-	    //TODO: possibly childDragged?
-            requestRedraw;
-        }
-    }
-    // for internal use
-    final void updateUnderMouse (wdabs cx, wdabs cy, bool closePopup) {
-        auto oUM = underMouse;
-        underMouse = getPopupWidget (cx, cy, closePopup);
-        if (underMouse is null) {
-            debug assert (childRoot.onSelf (cx, cy), "WidgetManager: childRoot doesn't cover whole area");
-            underMouse = childRoot.getWidget (cx, cy);
-        }
-        if (underMouse !is oUM) {
-            debug assert (oUM && underMouse, "no widget under mouse: error");
-            oUM.underMouse (false);
-            underMouse.underMouse (true);
-	    debug if (Debug.logUnderMouse())
-		logger.trace ("Widget under mouse: {}", underMouse);
-        }
-    }
-    
-    /** If possible, the screen-interaction derived class should override to
-     * make sure the window is at least (mw,mh) in size. In any case, this
-     * method MUST make sure w >= mw and h >= mh even if the window isn't this
-     * big.
-     * 
-     * A resize may not be required when this is called, however. */
-    void matchMinimalSize () {
-	if (w < mw) {
-	    logger.warn ("Min width for gui, {}, not met: {}", mw, w);
-	    w = mw;
-	}
-	if (h < mh) {
-	    logger.warn ("Min height for gui, {}, not met: {}", mh, h);
-	    h = mh;
-	}
-    }
-    
-    /// This should be overloaded to set a callback receiving keyboard input.
-    abstract void setLetterCallback(void delegate(ushort, char[]));
-    //END WidgetManagement methods
-    
-public:
-    //BEGIN makeWidget metacode
-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.
-    
-    // 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		= TAKES_CONTENT | 0xF,
-    
-    // popup widgets: 0x10
-    PopupMenu		= TAKES_CONTENT | 0x11,
-    
-    // labels: 0x20
-    TextLabel		= 0x21,
-    
-    // content functions: 0x30
-    editContent		= FUNCTION | TAKES_CONTENT | 0x30,
-    addContent		= FUNCTION | 0x31,
-    popupListContent	= FUNCTION | TAKES_CONTENT | 0x33,
-    
-    // content widgets: 0x40
-    DisplayContent	= TAKES_CONTENT | 0x40,
-    BoolContent		= TAKES_CONTENT | 0x41,
-    AStringContent	= TAKES_CONTENT | 0x42,
-    ButtonContent	= TAKES_CONTENT | 0x43,
-    SliderContent	= TAKES_CONTENT | 0x44,
-    
-    GridLayout		= TAKES_CONTENT | 0x100,
-    ContentList		= TAKES_CONTENT | 0x110,
-    
-    FloatingArea	= TAKES_CONTENT | 0x200,
-    Border		= TAKES_CONTENT | 0x204,
-    Switch		= TAKES_CONTENT | 0x210,
-    Collapsible		= TAKES_CONTENT | 0x214,
-}
-
-// Only used for binarySearch algorithm generation; must be ordered by numerical values.
-const char[][] WIDGETS = [
-        "FixedBlank",
-        "SizableBlank",
-	"TextLabel",
-	"addContent",
-        "Debug",
-	"PopupMenu",
-        "DisplayContent",
-        "BoolContent",
-	"AStringContent",
-	"ButtonContent",
-	"SliderContent",
-	"GridLayout",
-	"ContentList",
-	"FloatingArea",
-	"Border",
-	"Switch",
-	"Collapsible",
-	"editContent",
-	"popupListContent"];
-
-/* 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~`.");
-                        parent.recursionCheck (id, content);
-                        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;
-    }
-}
-
-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!\");");
-}
-}
-    //END makeWidget metacode
-    
-protected:
-    // Main child widget:
-    IChildWidget childRoot;		// Root of the main GUI widget tree
-    
-    // Dimensions and child set-up data (fit to childRoot):
-    wdim w,h;				// current widget size; should be at least (mw,mh) even if not displayable
-    wdim mw,mh;				// minimal area required by widgets
-    uint setupN;			// n to pass to IChildWidget.setup
-    
-    // IPopupParentWidget stuff for childRoot:
-    MenuPosition mAIPPW;		// IPPW variable
-    IPopupParentWidget childIPPW;	// child IPPW, if any active
-    
-    IChildWidget keyFocus;		// widget receiving keyboard input
-    IChildWidget underMouse;		// widget under the mouse pointer
-    
-    
-    // Context menu:
-    // Essentially, we consider childContext a full child IPPW, but handle it separately from
-    // childIPPW. Instead of providing another ref. for this IPPW, shortcut by using this reference
-    // and the boolean contextActive:
-    scope PopupHandlerWidget childContext;	// context menu popup (handler)
-    bool contextActive = false;		// If true, consider childContext a child IPPW
-    scope IServiceContent serviceContent;	// context menu content tree
-    
-    
-    // Drag-and-drop data:
-    //NOTE: could be wrapped with a PopupHandlerWidget, but can't set position then?
-    scope IChildWidget childDragged;	// displays dragged content; no interaction
-    IChildWidget dragStart;		// if non-null, this widget should receive motion and click-release events
-    int dragButton;			// index of button in use for drag
-    wdrel dragX, dragY;			// coordinates of dragged content relative to mouse
-    
-    
-    // Renderer:
-    char[] rendName;			// Name of renderer; for saving and creating renderers
-    scope IRenderer rend;
-    
-    
-    // Data loaded/to save:
-    WidgetDataSet curData;		// Current data
-    WidgetDataChanges changes;		// Changes for the current design.
-    
-    Mutex mutex;			// lock on methods for use outside the package.
-}
--- a/mde/gui/widget/AChildWidget.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/gui/widget/AChildWidget.d	Fri Sep 11 20:56:53 2009 +0200
@@ -61,7 +61,7 @@
     
     // Widgets with content need to override these
     override IContent content () {
-        return null;
+       return null;
     }
     override void setContent (IContent) {}
 //END Load and save
--- a/mde/gui/widget/Ifaces.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/gui/widget/Ifaces.d	Fri Sep 11 20:56:53 2009 +0200
@@ -350,7 +350,7 @@
  * during their creation.
  *****************************************************************************/
 //NOTE: add another this() without the data for default initialization, for the GUI editor?
-abstract class IChildWidget : IWidget
+interface IChildWidget : IWidget
 {
 //BEGIN Load and save
     /** 2nd stage of initialization for widgets; also called on some changes.
@@ -369,7 +369,7 @@
      * Returns:
      *	The method must return true on initial setup and if its dimensions
      *	(may) have changed. */
-    bool setup (uint n, uint flags) {return 0;}
+    bool setup (uint n, uint flags);
     
     /+ Use when widget editing is available? Requires widgets to know their parents.
     /** Called when a child widget's size has changed.
@@ -387,22 +387,22 @@
      * 
      * Parents normally take their resizability from sub-widgets; see SIZABILITY for how they do
      * this. */
-    bool isWSizable () {return 0;}
-    bool isHSizable () {return 0;} /// ditto
+    bool isWSizable ();
+    bool isHSizable (); /// ditto
     
     /** The minimal size the widget could be shrunk to (or its fixed size).
      *
      * Takes into account child-widgets and any other contents. */
-    wdim minWidth () {return 0;}
-    wdim minHeight() {return 0;}	/// ditto
+    wdim minWidth ();
+    wdim minHeight();	/// ditto
     
     /** Get the current size of the widget. */
-    wdim width () {return 0;}
-    wdim height() {return 0;}      /// ditto
+    wdim width ();
+    wdim height();      /// ditto
     
     /** (Smallest) coordinates of widget. */
-    wdabs xPos () {return 0;}
-    wdabs yPos () {return 0;}	/// ditto
+    wdabs xPos ();
+    wdabs yPos ();	/// ditto
     
     /** Used to adjust the size.
      *
@@ -418,21 +418,21 @@
      * A "fixed" size widget should enlarge itself as requested.
      *
      * setPosition must be called after calling either setWidth or setHeight. */
-    void setWidth (wdim nw, int dir) {}
-    void setHeight (wdim nh, int dir) {}	/// ditto
+    void setWidth (wdim nw, int dir);
+    void setHeight (wdim nh, int dir);	/// ditto
     
     /** Set the current position (called after setup and to move widget). */
-    void setPosition (wdim x, wdim y) {}
+    void setPosition (wdim x, wdim y);
 //END Size and position
     
 //BEGIN Content
     /** Return the widget's content, or null. */
-    IContent content () {return null;}
+    IContent content ();
     
     /** Set the widget's content, if the widget takes content and changing it
      * at this stage is feasible. (Also pass to sub-widgets, where the
      * constructor normally does so.) */
-    void setContent (IContent) {}
+    void setContent (IContent);
 //END Content
     
 //BEGIN Events
@@ -445,10 +445,10 @@
      * (cx,cy).
      *
      * Note: use global coordinates (cx,cy) not coordinates relative to the widget. */
-    IChildWidget getWidget (wdabs cx, wdabs cy) {return null;}
+    IChildWidget getWidget (wdabs cx, wdabs cy);
     
     /** Return true if (cx,cy) is on self's box (doesn't matter if actually on a subwidget). */
-    bool onSelf (wdabs cx, wdabs cy) {return 0;}
+    bool onSelf (wdabs cx, wdabs cy);
     
     /** Receive a mouse click event at cx,cy from button b (1-5 correspond to L,M,B, wheel up,down)
      * which is a down-click if state is true.
@@ -461,14 +461,14 @@
      * $(TR $(TD 2) $(TD Request the functions dragMotion and dragRelease are called))
      * $(TR $(TD 4) $(TD Display the widget's content while dragging (requires 2)))
      * ) */
-    int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {return 0;}
+    int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state);
     
     /** Called when dragging motion occurs, originating from this widget.
      *
      * Params: target = The widget under the mouse
      *
      * Only called if requested by clickEvent. */
-    void dragMotion (wdabs cx, wdabs cy, IChildWidget target) {}
+    void dragMotion (wdabs cx, wdabs cy, IChildWidget target);
     
     /** Called at the end of a drag which originated from this widget.
      *
@@ -478,7 +478,7 @@
      * clickEvent on the relevent widget.
      *
      * Only called if requested by clickEvent. */
-    bool dragRelease (wdabs cx, wdabs cy, IChildWidget target) {return 0;}
+    bool dragRelease (wdabs cx, wdabs cy, IChildWidget target);
     
     /** Receives keyboard events when requested.
      *
@@ -492,7 +492,7 @@
     
     /** Called on all widgets when the mouse moves over it (state == true) and
      * when it leaves (state == false). */
-    void underMouse (bool state) {}
+    void underMouse (bool state);
     
     /** When a pop-up is closed the manager calls requestRedraw and this function on its parent. */
     protected void popupClose ();
--- a/mde/gui/widget/ParentContent.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/gui/widget/ParentContent.d	Fri Sep 11 20:56:53 2009 +0200
@@ -26,13 +26,11 @@
 import mde.content.AStringContent;
 import mde.gui.exception;
 
-debug {
     import tango.util.log.Log : Log, Logger;
     private Logger logger;
     static this () {
 	logger = Log.getLogger ("mde.gui.widget.ParentContent");
     }
-}
 
 /******************************************************************************
  * Widget which pops up a ContentListWidget created with its content.
--- a/mde/gui/widget/TextWidget.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/gui/widget/TextWidget.d	Fri Sep 11 20:56:53 2009 +0200
@@ -48,6 +48,10 @@
 	}
 	return false;
     }
+    //COMPILER BUG: without defining here, I get a SIGSEGV when calling from a TextLabelWidget
+    override IContent content () {
+	return null;
+    }
     
     override void draw () {
         super.draw();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/WidgetManager.d	Fri Sep 11 20:56:53 2009 +0200
@@ -0,0 +1,658 @@
+/* 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/>. */
+
+/******************************************************************************
+ * The widget manager; root of the widget tree.
+ *
+ * Rendering is handled separately by an IRenderer.
+ *****************************************************************************/
+module mde.gui.widget.WidgetManager;
+
+import mde.gui.WidgetDataSet;
+import mde.gui.widget.Ifaces;
+import mde.gui.renderer.createRenderer;
+
+import imde = mde.imde;
+import mde.content.Content;
+import mde.content.ServiceContent;
+debug import mde.content.miscContent;	// Debug menu
+debug import mde.content.Debug;
+
+// Widgets to create:
+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.ParentContent;
+import mde.gui.widget.AParentWidget;
+
+public import tango.core.sync.Mutex;
+import tango.util.log.Log : Log, Logger;
+import tango.io.Console;	// to print exception stack-trace
+import tango.util.container.SortedMap;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.WidgetManager");
+}
+
+/******************************************************************************
+ * Methods in this class are only intended for use within the gui package,
+ * either by widgets (the IXXXWidget methods implementing from an interface in
+ * widgets.Ifaces.d) or by a derived class (back-end methods doing widget
+ * work). None of these methods are intended to be thread-safe when called
+ * concurrently on the same WidgetManager instance, but they should be thread-
+ * safe for calling on separate instances.
+ *****************************************************************************/
+abstract class AWidgetManager : IWidgetManager
+{
+    //BEGIN Public methods, for use outside the widget package
+    /** Construct a new widget manager.
+     *
+     * Params:
+     *	name = The file name of the config for this GUI (to identify multiple GUIs). */
+    this (char[] name) {
+        auto p = "MiscOptions.l10n" in Content.allContent;
+        assert (p, "MiscOptions.l10n not created!");
+        p.addCallback (&reloadStrings);
+	
+	serviceContent = ServiceContentList.createItems (name);
+	assert (cast (IServiceContent) Content.get ("menus.services."~name));
+	
+        debug {	// add a debug-mode menu
+            auto lWS = new EventContent ("menus.debug."~name~".logWidgetSize");
+            lWS.addCallback (&logWidgetSize);
+        }
+    }
+    
+    /** A change callback on MiscOptions.l10n content to update widgets.
+     *
+     * Relies on another callback reloading translations to content first! */
+    final void reloadStrings (IContent) {
+        synchronized(mutex) {
+            if (childRoot is null) return;
+            childRoot.setup (++setupN, 2);
+            childRoot.setWidth  (w, -1);
+            childRoot.setHeight (h, -1);
+            childRoot.setPosition (0,0);
+	    childContext.setup (setupN, 2);
+	    //TODO: possibly childDragged?
+            requestRedraw;
+        }
+    }
+    
+    debug public void logWidgetSize (IContent) {
+	logger.trace ("size: {,4},{,4}; minimal: {,4},{,4} - WidgetManager", w,h, mw,mh);
+	logger.trace ("childRoot:");
+	childRoot.logWidgetSize;
+	logger.trace ("childContext:");
+	childContext.logWidgetSize;
+	if (childDragged !is null) {
+	    logger.trace ("childDragged:");
+	    childDragged.logWidgetSize;
+	}
+    }
+    
+    
+    //BEGIN Public IWidget methods
+    override bool saveChanges () {
+	bool ret = childRoot.saveChanges;
+	ret |= childContext.saveChanges;
+	if (childDragged !is null)
+	    ret |= childDragged.saveChanges;
+	return ret;
+    }
+    
+    /** Draw all widgets */
+    override void draw () {
+	if (childRoot)
+	    childRoot.draw;
+	drawPopup;
+    }
+    //END Public IWidget methods
+    //END Public methods, for use outside the widget package
+    
+    //BEGIN IWidget methods for widgets
+    public override bool dropContent (IContent content) {
+	return false;
+    }
+    //END IWidget methods for widgets
+    
+    //BEGIN IParentWidget methods
+    // If call reaches the widget manager there isn't any recursion.
+    //NOTE: should be override
+    final void recursionCheck (widgetID, IContent) {}
+    
+    override void minWChange (IChildWidget widget, wdim nmw) {
+	if (widget !is childRoot) {	// Probably because widget is a popup widget
+	    // This may get called from a CTOR, hence we can't check widget is one of childContext, etc.
+	    if (widget.width < nmw)
+		widget.setWidth (nmw, -1);
+	    return;
+	}
+        mw = nmw;
+        if (w < nmw) {
+            childRoot.setWidth (nmw, -1);
+            w = nmw;
+        }
+        childRoot.setPosition (0,0);
+        requestRedraw;
+    }
+    override void minHChange (IChildWidget widget, wdim nmh) {
+	if (widget !is childRoot) {
+	    if (widget.height < nmh)
+		widget.setHeight (nmh, -1);
+	    return;
+	}
+        mh = nmh;
+        if (h < nmh) {
+            childRoot.setHeight (nmh, -1);
+            h = nmh;
+        }
+        childRoot.setPosition (0,0);
+        requestRedraw;
+    }
+    //END IParentWidget methods
+    
+    //BEGIN IPopupParentWidget methods
+    override IPopupParentWidget getParentIPPW () {
+        return this;
+    }
+    
+    override void addChildIPPW (IPopupParentWidget ippw) {
+	requestRedraw;
+	if (ippw is childContext) {	// special handling - a separate IPPW
+	    contextActive = true;
+	    return;
+	}
+        if (childIPPW)
+            childIPPW.removedIPPW;
+        childIPPW = ippw;
+    }
+    override bool removeChildIPPW (IPopupParentWidget ippw) {
+	if (ippw is childContext && contextActive) {
+	    childContext.removedIPPW;
+	    contextActive = false;
+	    return true;
+	}
+        if (childIPPW !is ippw) return false;
+        childIPPW.removedIPPW;
+        childIPPW = null;
+        mAIPPW = MenuPosition.INACTIVE;
+        requestRedraw;
+        return true;
+    }
+    
+    override void menuActive (MenuPosition mA) {
+        mAIPPW = mA;
+        if (childIPPW)
+            childIPPW.menuActive = mA;
+	if (contextActive)
+	    childContext.menuActive = mA;
+    }
+    override MenuPosition menuActive () {
+        return mAIPPW;
+    }
+    override MenuPosition parentMenuActive () {
+        return MenuPosition.INACTIVE;
+    }
+    
+    // Note: also triggered by non-popup widgets
+    override void menuDone () {}
+    
+    override IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup) {
+	IChildWidget ret;
+	// Don't bother with childDragged; it has no interaction
+	if (contextActive) {
+	    ret = childContext.getPopupWidget (cx, cy, closePopup);
+	    if (ret) return ret;
+	    if (closePopup) {
+		childContext.removedIPPW;
+		contextActive = false;
+		requestRedraw;
+	    }
+        }
+        if (childIPPW) {
+            ret = childIPPW.getPopupWidget (cx, cy, closePopup);
+            if (ret) return ret;
+            if (closePopup) {
+                removeChildIPPW (childIPPW);
+            }
+        }
+        return null;
+    }
+    
+    override void drawPopup () {
+	if (childIPPW)
+	    childIPPW.drawPopup;
+	if (contextActive)
+	    childContext.drawPopup();
+	if (childDragged)
+	    childDragged.draw();
+    }
+    
+    debug  override bool isChild (IPopupParentWidget ippw) {
+	if (contextActive && ippw is childContext)
+	    return true;
+        return ippw is childIPPW;
+    }
+    
+    override void removedIPPW () {}	// irrelevant
+    //END IPopupParentWidget 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. Exception printed to stderr.");
+	    //TODO: find a standard way to output exceptions, and implement everywhere:
+	    e.writeOut(delegate void(char[]s){ Cerr(s); });
+        }
+    
+        return new DebugWidget (this, parent, id, data, content);
+    }
+    
+    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.
+    }
+    
+    IRenderer renderer () {
+        assert (rend !is null, "WidgetManager.renderer: rend is null");
+        return rend;
+    }
+    
+    MenuPosition positionPopup (IChildWidget parent, IChildWidget popup, MenuPosition position = MenuPosition.INACTIVE) {
+	debug assert (parent && popup, "positionPopup: null widget");
+	debug if (Debug.logPopupPositioning())
+	    logger.trace ("Placing popup {} in relation to parent {}; input position: {}", popup, parent, position);
+        wdim w = popup.width,
+             h = popup.height,
+             x, y;
+        if (position & MenuPosition.ACTIVE) {
+            y = parent.yPos;				// height flush with top
+            if (y+h > this.h) y += parent.height - h;	// or bottom
+	    if (position & MenuPosition.LEFT) {		// previously left
+		x = parent.xPos - w;			// on left
+		if (x < 0) {
+		    x = parent.xPos + parent.width;	// on right
+		    position = MenuPosition.RIGHT;
+		}
+	    } else {					// previously right or above/below
+		x = parent.xPos + parent.width;		// on right
+		position = MenuPosition.RIGHT;
+		if (x+w > this.w) {
+		    x = parent.xPos - w;		// or left
+		    position = MenuPosition.LEFT;
+		}
+	    }
+        } else {
+	    wdim pw = parent.width;
+	    if (popup.minWidth <= pw)
+		popup.setWidth (pw, -1);		// neatness
+	    x = parent.xPos;				// align on left edge
+            if (x+w > this.w) x += pw - w;		// align on right edge
+            y = parent.yPos + parent.height;		// place below
+            if (y+h > this.h) y = parent.yPos - h;	// or above
+	    position = MenuPosition.ACTIVE;
+        }
+        if (x < 0) x = 0;	// may be placed partially off-screen
+        if (y < 0) y = 0;
+        popup.setPosition (x, y);
+	debug if (Debug.logPopupPositioning())
+	    logger.trace ("Placed popup {} of size ({},{}) at ({},{}); output position: {}", popup, w,h, x,y, position);
+	return position;
+    }
+
+    void requestRedraw () {
+        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
+    }
+    //END IWidgetManager methods
+    
+protected:
+    // These methods are called by derived classes to do the widget-management work
+    //BEGIN WidgetManagement methods
+    /** Second stage of widget loading.
+     *
+     * Widget data should be loaded before this is called. */
+    final void createWidgets () {
+        // The renderer needs to be created on the first load, but not after this.
+        if (rend is null)
+            rend = createRenderer (rendName);
+        
+        debug (mdeWidgets) logger.trace ("Creating root widget...");
+        childRoot = makeWidget (this, "root");
+	underMouse = childRoot;	// don't leave null due to a check
+        debug (mdeWidgets) logger.trace ("Setting up root widget...");
+        childRoot.setup (0, 3);
+        
+        mw = childRoot.minWidth;
+        mh = childRoot.minHeight;
+	matchMinimalSize ();
+        
+        debug (mdeWidgets) logger.trace ("Setting size and position of root widget...");
+        childRoot.setWidth  (w, -1);
+        childRoot.setHeight (h, -1);
+        childRoot.setPosition (0,0);
+        debug (mdeWidgets) logger.trace ("Done creating root widget.");
+	
+	childContext = new PopupHandlerWidget (this, this, "contextHandler", "context", serviceContent);
+	childContext.setup (0,3);
+	debug (mdeWidgets) logger.trace ("Created context handler widget.");
+	
+	underMouse = childRoot;	// must be something
+    }
+    
+    final void wmSizeEvent (int nw, int nh) {
+        w = cast(wdim) nw;
+        h = cast(wdim) nh;
+        matchMinimalSize;
+        
+        if (!childRoot) return;     // if not created yet.
+        childRoot.setWidth  (w, -1);
+        childRoot.setHeight (h, -1);
+        childRoot.setPosition (0,0);
+	debug logWidgetSize (null);
+    }
+    
+    /** For mouse click events.
+     *
+     * Sends the event on to the relevant windows and all click callbacks. */
+    final void wmMouseClick (wdabs cx, wdabs cy, ubyte b, bool state) {
+	if (childRoot is null) return;
+	
+	// Update underMouse to get the widget clicked on
+	updateUnderMouse (cx, cy, state);
+	
+	// end of a drag?
+	if (dragStart !is null && b == dragButton && state == false) {
+	    IChildWidget dS = dragStart;
+	    dragStart = null;
+	    childDragged = null;
+	    requestRedraw;
+	    if (dS.dragRelease (cx, cy, underMouse))
+		return;
+	}
+	
+	// Disable keyboard input if on another widget:
+	if (keyFocus && keyFocus !is underMouse) {
+	    keyFocus.keyFocusLost;
+	    keyFocus = null;
+	    setLetterCallback (null);
+	}
+	
+	// Finally, post the actual event:
+	if (b == 3 && state) {	// right click - open context menu
+	    Content contextContent = cast(Content) underMouse.content;
+	    if (contextContent !is null) {
+		serviceContent.setContent (contextContent);
+		childContext.openMenu (underMouse, contextContent);
+	    }
+	} else {	// post other button presses to clickEvent
+	    int ret = underMouse.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
+	    if (ret & 1) {	// keyboard input requested
+		keyFocus = underMouse;
+		setLetterCallback (&underMouse.keyEvent);
+	    }
+	    if (ret & 2 && dragStart is null) {	// drag events requested
+		dragStart = underMouse;
+		dragButton = b;	// currently we allow any button to be used for a drag, but.. ?
+		if (ret & 4) {
+		    IContent c = underMouse.content();
+		    if (c) {	// NOTE: creates a new widget, not optimal
+			childDragged = new DisplayContentWidget (this, this, "dragContentDisplay", WidgetData ([0], []), c);
+			childDragged.setup (0, 3);
+			dragX = underMouse.xPos - cx;
+			dragY = underMouse.yPos - cy;
+			childDragged.setPosition (cx + dragX, cy + dragY);
+		    }
+		}
+	    }
+	}
+    }
+    
+    /** For mouse motion events.
+     *
+     * Lock on mutex before calling. Pass new mouse coordinates. */
+    final void wmMouseMotion (wdabs cx, wdabs cy) {
+	updateUnderMouse (cx, cy, false);
+	
+	if (dragStart !is null) {
+	    dragStart.dragMotion (cx, cy, underMouse);
+	    if (childDragged !is null) {
+		childDragged.setPosition (cx + dragX, cy + dragY);
+		requestRedraw;
+	    }
+	}
+    }
+    
+    // for internal use
+    private final void updateUnderMouse (wdabs cx, wdabs cy, bool closePopup) {
+        auto oUM = underMouse;
+        underMouse = getPopupWidget (cx, cy, closePopup);
+        if (underMouse is null) {
+            debug assert (childRoot.onSelf (cx, cy), "WidgetManager: childRoot doesn't cover whole area");
+            underMouse = childRoot.getWidget (cx, cy);
+        }
+	debug assert (oUM && underMouse, "no widget under mouse: error");
+	if (underMouse !is oUM) {
+            oUM.underMouse (false);
+            underMouse.underMouse (true);
+	    debug if (Debug.logUnderMouse())
+		logger.trace ("Widget under mouse: {}", underMouse);
+        }
+    }
+    
+    /** If possible, the screen-interaction derived class should override to
+     * make sure the window is at least (mw,mh) in size. In any case, this
+     * method MUST make sure w >= mw and h >= mh even if the window isn't this
+     * big.
+     * 
+     * A resize may not be required when this is called, however. */
+    void matchMinimalSize () {
+	if (w < mw) {
+	    logger.warn ("Min width for gui, {}, not met: {}", mw, w);
+	    w = mw;
+	}
+	if (h < mh) {
+	    logger.warn ("Min height for gui, {}, not met: {}", mh, h);
+	    h = mh;
+	}
+    }
+    
+    /// This should be overloaded to set a callback receiving keyboard input.
+    abstract void setLetterCallback(void delegate(ushort, char[]));
+    //END WidgetManagement methods
+    
+    //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.
+    
+    // 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		= TAKES_CONTENT | 0xF,
+    
+    // popup widgets: 0x10
+    PopupMenu		= TAKES_CONTENT | 0x11,
+    
+    // labels: 0x20
+    TextLabel		= 0x21,
+    
+    // content functions: 0x30
+    editContent		= FUNCTION | TAKES_CONTENT | 0x30,
+    addContent		= FUNCTION | 0x31,
+    popupListContent	= FUNCTION | TAKES_CONTENT | 0x33,
+    
+    // content widgets: 0x40
+    DisplayContent	= TAKES_CONTENT | 0x40,
+    BoolContent		= TAKES_CONTENT | 0x41,
+    AStringContent	= TAKES_CONTENT | 0x42,
+    ButtonContent	= TAKES_CONTENT | 0x43,
+    SliderContent	= TAKES_CONTENT | 0x44,
+    
+    GridLayout		= TAKES_CONTENT | 0x100,
+    ContentList		= TAKES_CONTENT | 0x110,
+    
+    FloatingArea	= TAKES_CONTENT | 0x200,
+    Border		= TAKES_CONTENT | 0x204,
+    Switch		= TAKES_CONTENT | 0x210,
+    Collapsible		= TAKES_CONTENT | 0x214,
+}
+
+// Only used for binarySearch algorithm generation; must be ordered by numerical values.
+const char[][] WIDGETS = [
+        "FixedBlank",
+        "SizableBlank",
+	"TextLabel",
+	"addContent",
+        "Debug",
+	"PopupMenu",
+        "DisplayContent",
+        "BoolContent",
+	"AStringContent",
+	"ButtonContent",
+	"SliderContent",
+	"GridLayout",
+	"ContentList",
+	"FloatingArea",
+	"Border",
+	"Switch",
+	"Collapsible",
+	"editContent",
+	"popupListContent"];
+
+/* 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~`.");
+                        parent.recursionCheck (id, content);
+                        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;
+    }
+}
+
+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!\");");
+}
+}
+    //END makeWidget metacode
+    
+protected:
+    // Main child widget:
+    IChildWidget childRoot;		// Root of the main GUI widget tree
+    
+    // Dimensions and child set-up data (fit to childRoot):
+    wdim w,h;				// current widget size; should be at least (mw,mh) even if not displayable
+    wdim mw,mh;				// minimal area required by widgets
+    uint setupN;			// n to pass to IChildWidget.setup
+    
+    // IPopupParentWidget stuff for childRoot:
+    MenuPosition mAIPPW;		// IPPW variable
+    IPopupParentWidget childIPPW;	// child IPPW, if any active
+    
+    IChildWidget keyFocus;		// widget receiving keyboard input
+    IChildWidget underMouse;		// widget under the mouse pointer; should never be null when childRoot is non-null
+    
+    
+    // Context menu:
+    // Essentially, we consider childContext a full child IPPW, but handle it separately from
+    // childIPPW. Instead of providing another ref. for this IPPW, shortcut by using this reference
+    // and the boolean contextActive:
+    scope PopupHandlerWidget childContext;	// context menu popup (handler)
+    bool contextActive = false;		// If true, consider childContext a child IPPW
+    scope IServiceContent serviceContent;	// context menu content tree
+    
+    
+    // Drag-and-drop data:
+    //NOTE: could be wrapped with a PopupHandlerWidget, but can't set position then?
+    scope IChildWidget childDragged;	// displays dragged content; no interaction
+    IChildWidget dragStart;		// if non-null, this widget should receive motion and click-release events
+    int dragButton;			// index of button in use for drag
+    wdrel dragX, dragY;			// coordinates of dragged content relative to mouse
+    
+    
+    // Renderer:
+    char[] rendName;			// Name of renderer; for saving and creating renderers
+    scope IRenderer rend;
+    
+    
+    // Data loaded/to save:
+    WidgetDataSet curData;		// Current data
+    WidgetDataChanges changes;		// Changes for the current design.
+    
+    Mutex mutex;			// lock on methods for use outside the package.
+}
--- a/mde/gui/widget/layout.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/gui/widget/layout.d	Fri Sep 11 20:56:53 2009 +0200
@@ -23,13 +23,11 @@
 
 import tango.util.container.HashMap;
 
-debug {
     import tango.util.log.Log : Log, Logger;
     private Logger logger;
     static this () {
         logger = Log.getLogger ("mde.gui.widget.layout");
     }
-}
 
 /*************************************************************************************************
  * Encapsulates a grid of Widgets.
--- a/mde/gui/widget/miscContent.d	Mon Aug 31 13:54:23 2009 +0200
+++ b/mde/gui/widget/miscContent.d	Fri Sep 11 20:56:53 2009 +0200
@@ -25,13 +25,11 @@
 import mde.gui.exception;
 
 
-debug {
     import tango.util.log.Log : Log, Logger;
     private Logger logger;
     static this () {
 	logger = Log.getLogger ("mde.gui.widget.miscContent");
     }
-}
 
 /// Editable boolean widget
 class BoolContentWidget : AButtonWidget