changeset 171:7f7b2011b759

Partially complete commit: code runs but context menus don't work. Moved WMScreen.createRootWidget to WidgetManager.createWidgets. Put childContext under a popupHandler widget. TODO: implement IChildWidget.setContent(Content) (see AParentWidget.d:237).
author Diggory Hardy <diggory.hardy@gmail.com>
date Sun, 26 Jul 2009 11:04:17 +0200
parents e45226d3deae
children 0dd49f333189
files codeDoc/ideas.txt data/conf/guiDemo.mtt mde/content/ServiceContent.d mde/gui/WMScreen.d mde/gui/WidgetLoader.d mde/gui/WidgetManager.d mde/gui/widget/AParentWidget.d mde/gui/widget/Ifaces.d
diffstat 8 files changed, 287 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/ideas.txt	Mon Jun 29 21:20:16 2009 +0200
+++ b/codeDoc/ideas.txt	Sun Jul 26 11:04:17 2009 +0200
@@ -9,6 +9,9 @@
 
 GUI:
 ->  Widgets:
+  > generalise concept of 2D widgets:
+    > some other way of specifying a widget's dimensions
+    > divide layout widget into a sub-widget handler and a placement component, such that placement can use a rectangle, hexagon or whatever component
 ->  scripted widgets
 ->  decent rendering/theme system
 ->  events:
--- a/data/conf/guiDemo.mtt	Mon Jun 29 21:20:16 2009 +0200
+++ b/data/conf/guiDemo.mtt	Sun Jul 26 11:04:17 2009 +0200
@@ -46,7 +46,7 @@
 
 <WidgetData|context={0:[0x4204, 1],1:["contextLayout"]}>
 <WidgetData|contextLayout={0:[0x4100,4,2,1],1:["optDesc","contextServices"]}>
-<WidgetData|contextServices={0:[0x2031],1:["menus.services","contextMenuCollapse"]}>
+<WidgetData|contextServices={0:[0x2031],1:["menus.services.guiDemo","contextMenuCollapse"]}>
 <WidgetData|contextMenuCollapse={0:[0x4214],1:["contextMenu"]}>
 <WidgetData|contextMenu={0:[0x6030,0,1],1:["contextMenuCollapse2"]}>
 <WidgetData|contextMenuCollapse2={0:[0x4214],1:["contextMenu2"]}>
--- a/mde/content/ServiceContent.d	Mon Jun 29 21:20:16 2009 +0200
+++ b/mde/content/ServiceContent.d	Sun Jul 26 11:04:17 2009 +0200
@@ -26,7 +26,7 @@
 import mde.content.AStringContent;
 
 /** Interface for ServiceContent and ServiceContentList. */
-interface IServiceContent {
+interface IServiceContent : IContent {
     void setContent (Content cont);
 }
 
@@ -87,54 +87,54 @@
     }
     // Doesn't support directly setting the value
     override void opAssign (bool val) {}
+    
+    
+    /** Create all services.
+     *
+     * Each WidgetManager has it's own instance, but these share one clipboard, etc.
+     * 
+     * Currently all clipboard operations convert to/from a string.
+     * TODO: Possible extensions: multi-item clipboard displaying value in menu,
+     * copying without converting everything to/from a string. */
+    static ServiceContentList createItems (char[] name) {
+	char[] lName = "menus.services."~name;
+	auto ret = new ServiceContentList (lName);
+	
+	// Many use callbacks on a generic class type. For this, we should be sure of the type.
+	(new AStringService(lName~".copy")).addCallback ((IContent c) {
+	    debug assert (cast(AStringService)c);
+	    with (cast(AStringService)c) {
+		if (activeCont !is null)
+		    clipboard = activeCont.toString(0);
+	    }
+	});
+	(new AStringService(lName~".paste")).addCallback ((IContent c) {
+	    debug assert (cast(AStringService)c);
+	    with (cast(AStringService)c) {
+		if (activeCont !is null)
+		    activeCont = clipboard;
+	    }
+	});
+	
+	new ServiceContentList(lName~".calculator");
+	(new IntService(lName~".calculator.increment")).addCallback ((IContent c) {
+	    debug assert (cast(IntService)c);
+	    with (cast(IntService)c) {
+		if (activeCont !is null)
+		    activeCont = activeCont()+1;
+	    }
+	});
+	(new IntService(lName~".calculator.decrement")).addCallback ((IContent c) {
+	    debug assert (cast(IntService)c);
+	    with (cast(IntService)c) {
+		if (activeCont !is null)
+		    activeCont = activeCont()-1;
+	    }
+	});
+	return ret;
+    }
+    
+private:
+    static char[] clipboard;
+    //TODO: lock on clipboard: static Mutex mutex;
 }
-
-/** Create all services.
-*
-* For now, all are under the menu.
-* 
-* Currently all clipboard operations convert to/from a string.
-* TODO: Possible extensions: multi-item clipboard displaying value in menu,
-* copying without converting everything to/from a string. */
-static this () {
-    // Create ContentLists so they have the correct type.
-    new ServiceContentList ("menus.services");
-    // Many use callbacks on a generic class type. For this, we should be sure of the type.
-    auto copy = new AStringService("menus.services.copy");
-    copy.addCallback ((IContent c) {
-	debug assert (cast(AStringService)c);
-	with (cast(AStringService)c) {
-	    if (activeCont !is null)
-		clipboard = activeCont.toString(0);
-	}
-    });
-    auto paste = new AStringService("menus.services.paste");
-    paste.addCallback ((IContent c) {
-	debug assert (cast(AStringService)c);
-	with (cast(AStringService)c) {
-	    if (activeCont !is null)
-		activeCont = clipboard;
-	}
-    });
-    
-    new ServiceContentList("menus.services.calculator");
-    auto inc = new IntService("menus.services.calculator.increment");
-    inc.addCallback ((IContent c) {
-	debug assert (cast(IntService)c);
-	with (cast(IntService)c) {
-	    if (activeCont !is null)
-		activeCont = activeCont()+1;
-	}
-    });
-    auto dec = new IntService("menus.services.calculator.decrement");
-    dec.addCallback ((IContent c) {
-	debug assert (cast(IntService)c);
-	with (cast(IntService)c) {
-	    if (activeCont !is null)
-		activeCont = activeCont()-1;
-	}
-    });
-}
-private {
-    char[] clipboard;
-}
--- a/mde/gui/WMScreen.d	Mon Jun 29 21:20:16 2009 +0200
+++ b/mde/gui/WMScreen.d	Sun Jul 26 11:04:17 2009 +0200
@@ -24,7 +24,6 @@
 import mde.gui.WidgetManager;
 import mde.gui.WidgetLoader;
 import mde.gui.widget.Ifaces;
-import mde.gui.renderer.createRenderer;
 
 import mde.setup.Screen;
 import mde.input.Input;
@@ -68,6 +67,10 @@
         input.addMouseClickCallback(&clickEvent)
              .addMouseMotionCallback(&motionEvent);
     }
+    ~this () {
+	// Make sure the keyboard is not locked in text-entry mode.
+	input.setLetterCallback (null);
+    }
     
     /** Draw the gui. */
     void draw() {
@@ -100,27 +103,22 @@
         }
     }
     
-    
+    /** Notification of externally-caused screen resize.
+     *
+     * Should be called before createWidgets to prevent widgets being squashed
+     * to min-dims on loading (losing saved dimensions of columns, etc). */
     void sizeEvent (int nw, int nh) {   // IDrawable function
         mutex.lock;
         scope(exit) mutex.unlock;
         
         w = cast(wdim) nw;
         h = cast(wdim) nh;
+        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;
-        }
-        
-        if (!child) return;     // if not created yet.
-        child.setWidth  (w, -1);
-        child.setHeight (h, -1);
-        child.setPosition (0,0);
+        if (!childRoot) return;     // if not created yet.
+        childRoot.setWidth  (w, -1);
+        childRoot.setHeight (h, -1);
+        childRoot.setPosition (0,0);
     }
     
 protected:
@@ -128,36 +126,6 @@
 	input.setLetterCallback (dlg);
     }
     
-    /* Second stage of widget loading.
-     * Note: sizeEvent should be called with window size before this. */
-    final override void createRootWidget () {
-        // The renderer needs to be created on the first load, but not after this.
-        if (rend is null)
-            rend = createRenderer (rendName);
-        
-        debug (mdeWidgets) logger.trace ("Creating root widget...");
-        child = makeWidget (this, "root");
-        debug (mdeWidgets) logger.trace ("Setting up root widget...");
-        child.setup (0, 3);
-        
-        mw = child.minWidth;
-        mh = child.minHeight;
-        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;
-        }
-        
-        debug (mdeWidgets) logger.trace ("Setting size and position of root widget...");
-        child.setWidth  (w, -1);
-        child.setHeight (h, -1);
-        child.setPosition (0,0);
-        debug (mdeWidgets) logger.trace ("Done creating root widget.");
-    }
-    
     final override void preSave () {
 	if (keyFocus) {
 	    keyFocus.keyFocusLost;
--- a/mde/gui/WidgetLoader.d	Mon Jun 29 21:20:16 2009 +0200
+++ b/mde/gui/WidgetLoader.d	Sun Jul 26 11:04:17 2009 +0200
@@ -157,8 +157,7 @@
         }
         
         // Create the widgets:
-        createRootWidget;
-        underMouse = child;	// must be something
+        createWidgets;
     }
     
     /** Save changes, if any exist.
@@ -171,7 +170,7 @@
         scope(exit) mutex.unlock;
         
         // Make all widgets save any changed data:
-        child.saveChanges;
+        saveChanges;
         
         if (changes.noChanges)
             return;
@@ -223,24 +222,8 @@
 protected:
     // Called by derived classes, not thread safe for the same instance
     //BEGIN WidgetManagement methods
-    /** Second stage of loading the widgets.
-    * 
-    * loadDesign handles the data; this method needs to:
-    * ---
-    * // 1. Create the root widget:
-    * child = makeWidget ("root");
-    * child.setup (0, 3);
-    * // 2. Set the size:
-    * child.setWidth  (child.minWidth,  1);
-    * child.setHeight (child.minHeight, 1);
-    * // 3. Set the position (necessary part of initialization):
-    * child.setPosition (0,0);
-    * ---
-    */
-    void createRootWidget();
-    
-    /** Called before saving (usually when the GUI is about to be destroyed, although not
-    *  necessarily). */
+    /** Called before saving to make sure no data is being edited (and thus
+     *  would not be saved). */
     void preSave ();
     //END WidgetManagement methods
     
--- a/mde/gui/WidgetManager.d	Mon Jun 29 21:20:16 2009 +0200
+++ b/mde/gui/WidgetManager.d	Sun Jul 26 11:04:17 2009 +0200
@@ -26,6 +26,7 @@
 
 import mde.gui.WidgetDataSet;
 import mde.gui.widget.Ifaces;
+import mde.gui.renderer.createRenderer;
 
 import imde = mde.imde;
 import mde.content.Content;
@@ -41,6 +42,7 @@
 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;
@@ -75,9 +77,8 @@
         assert (p, "MiscOptions.l10n not created!");
         p.addCallback (&reloadStrings);
 	
-	serviceContent = cast (IServiceContent) Content.get ("menus.services");
-	assert (Content.get ("menus.services"));
-	assert (serviceContent !is null, "Content service menu doesn't exist or has wrong type");
+	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");
@@ -92,33 +93,43 @@
     final void recursionCheck (widgetID, IContent) {}
     
     override void minWChange (IChildWidget widget, wdim nmw) {
-	if (widget !is child)	// Usually because widget is a floating widget
-	    // This may get called from a CTOR, hence we can't check widget is one of popupContext, etc.
+	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.
 	    return;
         mw = nmw;
         if (w < nmw) {
-            child.setWidth (nmw, -1);
+            childRoot.setWidth (nmw, -1);
             w = nmw;
         }
-        child.setPosition (0,0);
+        childRoot.setPosition (0,0);
         requestRedraw;
     }
     override void minHChange (IChildWidget widget, wdim nmh) {
-	if (widget !is child)
+	if (widget !is childRoot)
 	    return;
         mh = nmh;
         if (h < nmh) {
-            child.setHeight (nmh, -1);
+            childRoot.setHeight (nmh, -1);
             h = nmh;
         }
-        child.setPosition (0,0);
+        childRoot.setPosition (0,0);
         requestRedraw;
     }
+    //END IParentWidget methods
+    
+    //BEGIN IWidget methods
+    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 IParentWidget methods
+    //END IWidget methods
     
     //BEGIN IPopupParentWidget methods
     override IPopupParentWidget getParentIPPW () {
@@ -126,10 +137,14 @@
     }
     
     override void addChildIPPW (IPopupParentWidget ippw) {
+	requestRedraw;
+	if (ippw is childContext) {	// special handling - a separate IPPW
+	    contextActive = true;
+	    return;
+	}
         if (childIPPW)
             childIPPW.removedIPPW;
         childIPPW = ippw;
-        requestRedraw;
     }
     override bool removeChildIPPW (IPopupParentWidget ippw) {
         if (childIPPW !is ippw) return false;
@@ -144,6 +159,8 @@
         mAIPPW = mA;
         if (childIPPW)
             childIPPW.menuActive = mA;
+	if (contextActive)
+	    childContext.menuActive = mA;
     }
     override MenuPosition menuActive () {
         return mAIPPW;
@@ -152,31 +169,24 @@
         return MenuPosition.INACTIVE;
     }
     
-    override void menuDone () {
-	// close context menu, but not childIPPW
-	if (childIPPW is null)
-	    menuActive = MenuPosition.INACTIVE;
-	popupContext = null;
-	requestRedraw;
-    }
+    // Note: also triggered by non-popup widgets
+    override void menuDone () {}
     
     override IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup) {
-        if (popupContext) {
-            if (popupContext.onSelf (cx, cy))
-            	return popupContext.getWidget (cx, cy);
-            if (closePopup) {
-            	if (childIPPW is null)
-		    menuActive = MenuPosition.INACTIVE;
-            	popupContext = null;
-                requestRedraw;
-            }
+	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;
+		requestRedraw;
+	    }
         }
         if (childIPPW) {
-            IChildWidget ret =
-                    childIPPW.getPopupWidget (cx, cy, closePopup);
+            ret = childIPPW.getPopupWidget (cx, cy, closePopup);
             if (ret) return ret;
             if (closePopup) {
-                menuActive = MenuPosition.INACTIVE;
                 removeChildIPPW (childIPPW);
             }
         }
@@ -184,13 +194,15 @@
     }
     
     override void drawPopup () {
-        if (popupContext)
-            popupContext.draw();
-	if (dragContentDisplay)
-	    dragContentDisplay.draw();
+	if (contextActive)
+	    childContext.draw();
+	if (childDragged)
+	    childDragged.draw();
     }
     
     debug protected override bool isChild (IPopupParentWidget ippw) {
+	if (contextActive && ippw is childContext)
+	    return true;
         return ippw is childIPPW;
     }
     
@@ -293,16 +305,53 @@
     
     debug void logWidgetSize (IContent) {
         logger.trace ("size: {,4},{,4}; minimal: {,4},{,4} - WidgetManager", w,h, mw,mh);
-        child.logWidgetSize;
+	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 */
-    void wmDrawWidgets() {
-	if (child)
-	    child.draw;
+    final void wmDrawWidgets() {
+	if (childRoot)
+	    childRoot.draw;
 	if (childIPPW)
 	    childIPPW.drawPopup;
 	drawPopup;
@@ -311,8 +360,8 @@
     /** For mouse click events.
      *
      * Sends the event on to the relevant windows and all click callbacks. */
-    void wmMouseClick (wdabs cx, wdabs cy, ubyte b, bool state) {
-	if (child is null) return;
+    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);
@@ -321,7 +370,7 @@
 	if (dragStart !is null && b == dragButton && state == false) {
 	    IChildWidget dS = dragStart;
 	    dragStart = null;
-	    dragContentDisplay = null;
+	    childDragged = null;
 	    requestRedraw;
 	    if (dS.dragRelease (cx, cy, underMouse))
 		return;
@@ -336,16 +385,11 @@
 	
 	// Finally, post the actual event:
 	if (b == 3 && state) {	// right click - open context menu
-	    if (popupContext !is null) return;
 	    Content contextContent = cast(Content)underMouse.content;
-	    if (contextContent is null) return;
-	    // NOTE: Creates new widgets every time; not optimal
-	    serviceContent.setContent (contextContent);
-	    popupContext = makeWidget (this, "context", contextContent);
-	    popupContext.setup (0, 3);
-	    //NOTE: usually set parentIPPW.menuActive:
-	    menuActive = positionPopup (underMouse, popupContext);
-	    requestRedraw;
+	    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
@@ -358,11 +402,11 @@
 		if (ret & 4) {
 		    IContent c = underMouse.content();
 		    if (c) {	// NOTE: creates a new widget, not optimal
-			dragContentDisplay = new DisplayContentWidget (this, this, "dragContentDisplay", WidgetData ([0], []), c);
-			dragContentDisplay.setup (0, 3);
+			childDragged = new DisplayContentWidget (this, this, "dragContentDisplay", WidgetData ([0], []), c);
+			childDragged.setup (0, 3);
 			dragX = underMouse.xPos - cx;
 			dragY = underMouse.yPos - cy;
-			dragContentDisplay.setPosition (cx + dragX, cy + dragY);
+			childDragged.setPosition (cx + dragX, cy + dragY);
 		    }
 		}
 	    }
@@ -372,13 +416,13 @@
     /** For mouse motion events.
      *
      * Lock on mutex before calling. Pass new mouse coordinates. */
-    void wmMouseMotion (wdabs cx, wdabs cy) {
+    final void wmMouseMotion (wdabs cx, wdabs cy) {
 	updateUnderMouse (cx, cy, false);
 	
 	if (dragStart !is null) {
 	    dragStart.dragMotion (cx, cy, underMouse);
-	    if (dragContentDisplay !is null) {
-		dragContentDisplay.setPosition (cx + dragX, cy + dragY);
+	    if (childDragged !is null) {
+		childDragged.setPosition (cx + dragX, cy + dragY);
 		requestRedraw;
 	    }
 	}
@@ -388,25 +432,25 @@
     /** A change callback on MiscOptions.l10n content to update widgets.
      *
      * Relies on another callback reloading translations to content first! */
-    void reloadStrings (IContent) {
+    final void reloadStrings (IContent) {
         synchronized(mutex) {
-            if (child is null) return;
-            child.setup (++setupN, 2);
-            child.setWidth  (w, -1);
-            child.setHeight (h, -1);
-            child.setPosition (0,0);
-	    popupContext.setup (setupN, 2);
-	    //NOTE: does popupContext need to be re-scaled?
+            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
-    void updateUnderMouse (wdabs cx, wdabs cy, bool closePopup) {
+    final 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);
+            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");
@@ -417,6 +461,23 @@
         }
     }
     
+    /** 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
@@ -529,32 +590,47 @@
     //END makeWidget metacode
     
 protected:
-    WidgetDataSet curData;		// Current data
-    WidgetDataChanges changes;		// Changes for the current design.
+    // Main child widget:
+    IChildWidget childRoot;		// Root of the main GUI widget tree
     
-    char[] rendName;			// Name of renderer; for saving and creating renderers
-    IRenderer rend;
-    
-    // Widgets:
+    // 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
-    scope IChildWidget child;		// The primary widget.
     uint setupN;			// n to pass to IChildWidget.setup
     
+    // IPopupParentWidget stuff for childRoot:
     MenuPosition mAIPPW;		// IPPW variable
     IPopupParentWidget childIPPW;	// child IPPW, if any active
     
-    // Popup(s) handled directly by AWidgetManager:
-    IChildWidget popupContext;		// context menu (active if not null)
-    IServiceContent serviceContent;	// context menu content tree
-    IChildWidget dragContentDisplay;	// displays dragged content; no interaction
+    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
     
-    IChildWidget keyFocus;		// widget receiving keyboard input
-    IChildWidget underMouse;		// widget under the mouse pointer
+    
+    // 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/AParentWidget.d	Mon Jun 29 21:20:16 2009 +0200
+++ b/mde/gui/widget/AParentWidget.d	Sun Jul 26 11:04:17 2009 +0200
@@ -77,7 +77,7 @@
     // widget id and the content are the same (as its it and content).
     override void recursionCheck (widgetID wID, IContent c) {
         debug assert (id !is null && parent !is null, "recursionCheck called before parent and id set");
-        if (wID is id)
+        if (wID == id)
             throw new WidgetRecursionException (wID);
         parent.recursionCheck (wID, c);
     }
@@ -166,7 +166,6 @@
         if (childIPPW) {
             ret = childIPPW.getPopupWidget (cx, cy, closePopup);
             if (closePopup && ret is null) {
-                menuActive = MenuPosition.INACTIVE;
                 removeChildIPPW (childIPPW);
             }
         }
@@ -210,3 +209,35 @@
     IChildWidget popup;
     MenuPosition mAIPPW;
 }
+
+import mde.content.Content;
+/******************************************************************************
+ * Not "really" a widget (zero size, no direct interaction), but a handler for
+ * popup widgets.
+ * 
+ * Intended to manage a context menu for WidgetManager, and not to work quite
+ * like a regular IPPW.
+ *****************************************************************************/
+class PopupHandlerWidget : APopupParentWidget
+{
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, char[] subWidg, IContent c) {
+        super (mgr, parent, id);
+        
+	popup = mgr.makeWidget (this, subWidg, c);
+        subWidgets = [popup];
+	
+	w = mw = 0;
+	h = mh = 0;
+    }
+    
+    /// Open a context menu
+    void openMenu (IChildWidget underMouse, Content contextContent) {
+	if (mAIPPW != MenuPosition.INACTIVE) return;	// already in use
+	//TODO:
+	//subWidgets[0].setContent (contextContent);
+	parentIPPW.addChildIPPW (this);
+	// For context menus, don't set parentIPPW.menuActive like for clicked menus:
+	menuActive = mgr.positionPopup (underMouse, popup);
+	mgr.requestRedraw;
+    }
+}
--- a/mde/gui/widget/Ifaces.d	Mon Jun 29 21:20:16 2009 +0200
+++ b/mde/gui/widget/Ifaces.d	Sun Jul 26 11:04:17 2009 +0200
@@ -40,6 +40,16 @@
  *****************************************************************************/
 interface IWidget
 {
+    /** When this is called, if the widget has any changed data to save it
+     * should do so (see widgetData/dimData) and return true. Otherwise it
+     * should return false.
+     * 
+     * If the widget has subwidgets, it should also be recursively called on
+     * these, returning true if any data was saved.
+     * 
+     * Actually the return value is ignored; I think widgets still return it
+     * correctly though. */
+    bool saveChanges ();
     
     /** Called on a widget when something is dragged onto it.
      *
@@ -176,6 +186,15 @@
      * 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.
+     * So given IPPWs U-Z (indentation shows ancestry):
+     * 
+     * ---
+     * X (window manager): menuActive false
+     *   Y (popup window): menuActive true
+     *     U (menu button): menuActive true, open popup menu
+     *     V (menu button): menuActive false, menu closed but opens on hover
+     *   Z (menu button): menuActive false, menu closed
+     * ---
      * 
      * It is also used by positionPopup to signal if the popup was left or
      * right of the previous popup, to enable a further popup to be placed by
@@ -216,6 +235,12 @@
  * Interface for the widget manager.
  * 
  * This class handles widget rendering, input, loading and saving.
+ * 
+ * The widget manager is also a popup-parent for it's main child widget
+ * (childRoot), working as a normal popup-parent except that parentMenuActive
+ * is always false. It also has two special popup children: childContext and
+ * childDragged, which are handled indirectly via PopupHandlerWidget so that
+ * their menuActive state doesn't affect childRoot or decendants.
  *****************************************************************************/
 interface IWidgetManager : IPopupParentWidget
 {
@@ -328,14 +353,6 @@
      *	(may) have changed. */
     bool setup (uint n, uint flags);
     
-    /** When this is called, if the widget has any changed data to save it should call
-     * IWidgetManager.setData (id, data) to set it and return true. Otherwise it should return
-     * false.
-     * 
-     * If the widget has subwidgets, it should also be recursively called on these (passing their 
-     * ids). */
-    bool saveChanges ();
-    
     /+ Use when widget editing is available? Requires widgets to know their parents.
     /** Called when a child widget's size has changed.
      *