changeset 111:1655693702fc

Resolved ticket #4, allowing widgets to reload strings and recalculate sizes mid-run. Removed prefinalize and finalize and added setup as the new second initialization phase, which can be re-run.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 06 Dec 2008 17:41:42 +0000
parents 6acd96f8685f
children fe061009029d
files codeDoc/jobs.txt data/L10n/en-GB.mtt data/conf/gui.mtt mde/file/mergetag/Reader.d mde/gui/WidgetManager.d mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Floating.d mde/gui/widget/Ifaces.d mde/gui/widget/Popup.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/layout.d mde/gui/widget/miscContent.d mde/gui/widget/textContent.d mde/setup/InitStage.d
diffstat 16 files changed, 203 insertions(+), 194 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Fri Dec 05 11:29:39 2008 +0000
+++ b/codeDoc/jobs.txt	Sat Dec 06 17:41:42 2008 +0000
@@ -8,6 +8,7 @@
 
 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.
+3   Synchronization of IContent with gui (e.g. multiple edit widgets): worth adding (temporary) callbacks?
 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.
--- a/data/L10n/en-GB.mtt	Fri Dec 05 11:29:39 2008 +0000
+++ b/data/L10n/en-GB.mtt	Sat Dec 06 17:41:42 2008 +0000
@@ -1,4 +1,5 @@
 {MT01}
+!{en-GB British English}
 {imde}
 <entry|quit={0:"Quit"}>
 {Options}
--- a/data/conf/gui.mtt	Fri Dec 05 11:29:39 2008 +0000
+++ b/data/conf/gui.mtt	Sat Dec 06 17:41:42 2008 +0000
@@ -2,10 +2,10 @@
 <char[]|Renderer="Simple">
 <char[]|Design="Working">
 {Working}
-<WidgetData|root={0:[0xC100,0,2,1],1:["bar","floating"]}>
+<WidgetData|root={0:[0xC100,0,2,1],1:["bar","opts"]}>
 <WidgetData|bar={0:[0xC100,0,1,3],1:["menu","blank","quit"]}>
 <WidgetData|menu={0:[0xC011],1:["quit","Menu"]}>
-<WidgetData|square={0:[0x1,6,6]}>
+<WidgetData|quit={0:[0x2031,0x4043],1:["imde.quit"]}>
 <WidgetData|blank={0:[0x2]}>
 <WidgetData|opts={0:[0x2031,0xC100,4,2,1],1:["Options","optName","optSecs"]}>
 <WidgetData|optSecs={0:[0x6030,4],1:["optSec"]}>
@@ -17,7 +17,5 @@
 <WidgetData|optDesc={0:[0x4020, 2, 0xaf6000]}>
 <WidgetData|optVal={0:[0x6030]}>
 <WidgetData|optSep={0:[0x21, 0xff],1:[" = "]}>
-<WidgetData|floating={0:[0x8200,14,6],1:["blank","opts"]}>
-<WidgetData|quit={0:[0x2031,0x4043],1:["imde.quit"]}>
 {Basic}
 <WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}>
--- a/mde/file/mergetag/Reader.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/file/mergetag/Reader.d	Sat Dec 06 17:41:42 2008 +0000
@@ -356,7 +356,7 @@
     
     After analysing tags, the function passes the type, ID and data to addTag.
     
-    NOTE: from performance tests on indexing char[]'s and dereferencing char*'s, the char*'s are
+    Note: from performance tests on indexing char[]'s and dereferencing char*'s, the char*'s are
     slightly faster, but a tiny difference isn't worth the extra effort/risk of using char*'s.
     */
     private size_t parseSection (size_t pos, IDataSection dsec) {
--- a/mde/gui/WidgetManager.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/WidgetManager.d	Sat Dec 06 17:41:42 2008 +0000
@@ -173,6 +173,10 @@
         return rend;
     }
     
+    /** Place a pop-up widget near px,py.
+     *
+     * WidgetManager sets its position, draws it, passes it click events and removes it; other
+     * functionality should be handled by the widget's parent. */
     void addPopup (wdabs px, wdabs py, IChildWidget widg) {
         ActivePopup popup;
         with (popup) {
@@ -214,7 +218,7 @@
 	popups = new CircularList!(ActivePopup);
         
         child = makeWidget ("root");
-        finalize;
+        child.setup (0, 3);
         
         mw = child.minWidth;
         mh = child.minHeight;
@@ -444,7 +448,8 @@
     /** Called when translation strings have been reloaded. */
     void reloadStrings (AContent c) {
 	Items.loadTranslation;
-	child.reloadStrings;	//FIXME : all widgets, resize
+	child.setup (++setupN, 2);
+	child.setPosition (0,0);
 	requestRedraw;
     }
     
@@ -455,45 +460,16 @@
     * ---
     * // 1. Create the root widget:
     * child = makeWidget ("root");
-    * finalize;
-    * // 2. Set the setSize, e.g.:
+    * 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();
     
-    /** Runs finalize for all descendants, in a deepest first order. */
-    /* NOTE: The way this function works may seem a little odd, but it's designed to allow
-    * shared alignments to be initialized properly:
-    * 1. all sharing members need to know their children's min size
-    * 2. all sharing members need to add their children's min size to the alignment
-    * 3. all sharing members can only then get their min size
-    * This method will fail if alignment members are not all of the same generation. An alternate
-    * method without this drawback would be to have shared alignments created with a list of
-    * pointers to their members, and once all members have been created the alignment could
-    * initialize itself, first making sure each members' children have been initialized. */
-    void finalize () {
-	IChildWidget[][] descendants;   // first index: depth; is a list of widgets at each depth
-	
-	void recurseChildren (size_t depth, IChildWidget widget) {
-	    foreach (child; widget.children)
-		recurseChildren (depth+1, child);
-	    
-	    if (descendants.length <= depth)
-		descendants.length = depth * 2 + 1;
-	    descendants[depth] ~= widget;
-	}
-	
-	recurseChildren (0, child);
-	foreach_reverse (generation; descendants) {
-	    foreach (widget; generation)
-		widget.prefinalize;
-	    foreach (widget; generation)
-		widget.finalize;
-	}
-    }
-    
     /** Called before saving (usually when the GUI is about to be destroyed, although not
     *  necessarily). */
     void preSave ();
@@ -534,6 +510,7 @@
     bool loadUserFile = true;		// still need to load user file for saving?
     
     scope IChildWidget child;		// The primary widget.
+    uint setupN;			// n to pass to IChildWidget.setup
     
     Mutex mutex;			// lock on methods for use outside the package.
 }
--- a/mde/gui/renderer/IRenderer.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/renderer/IRenderer.d	Sat Dec 06 17:41:42 2008 +0000
@@ -170,6 +170,6 @@
     /** Get a TextAdapter to draw some text.
      *
      * If no colour is passes, a default is used (white). */
-    TextAdapter getAdapter (char[] text, int colour = 0xFFFFFF);
+    TextAdapter getAdapter (int colour = 0xFFFFFF);
     //END Methods
 }
--- a/mde/gui/renderer/SimpleRenderer.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/renderer/SimpleRenderer.d	Sat Dec 06 17:41:42 2008 +0000
@@ -148,10 +148,9 @@
         glRecti (x+2,y+14, x+14,y+2);
     }
     
-    TextAdapter getAdapter (char[] text, int col) {
+    TextAdapter getAdapter (int col) {
         TextAdapter a;
         a.font = defaultFont;
-        a.content = text;
 	a.colour_ = Colour (col);
 	a.index_ = size_t.max;
 	return a;
--- a/mde/gui/widget/Floating.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/Floating.d	Sat Dec 06 17:41:42 2008 +0000
@@ -60,10 +60,13 @@
         super (mgr, id, data);
     }
     
-    void finalize () {
+    bool setup (uint n, uint flags) {
         foreach (i, ref d; sWData) with (d) {
             auto widg = subWidgets[i];
-            d.border = mgr.renderer.getBorder (borderType, widg.isWSizable, widg.isHSizable);
+	    if (!widg.setup (n, flags) && n != 0 && !(flags & 1))
+		continue;	// no changes; skip the rest
+	    
+	    d.border = mgr.renderer.getBorder (borderType, widg.isWSizable, widg.isHSizable);
             mw = widg.minWidth  + border.x1 + border.x2;
             mh = widg.minHeight + border.y1 + border.y2;
 	    if (w < mw || !widg.isWSizable) w = mw;
@@ -71,6 +74,7 @@
             widg.setWidth  (w - border.x1 - border.x2, -1);
             widg.setHeight (h - border.y1 - border.y2, -1);
         }
+	return false;	// floating area size is not changed
     }
     
     bool saveChanges () {
--- a/mde/gui/widget/Ifaces.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/Ifaces.d	Sat Dec 06 17:41:42 2008 +0000
@@ -133,35 +133,39 @@
  *  + Widget uses the initialisation data:
  *  + [widgetID, x, y]
  *  + where x is ... and y is ... +/
- * this (IWidgetManager mgr, WidgetData data);
+ * this (IWidgetManager mgr, widgetID id, WidgetData data);
  * 
  * /// The CTOR may take an IContent reference:
- * this (IWidgetManager mgr, WidgetData data, IContent content);
+ * this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content);
  * ----------------------------------
- * Where mgr is the widget manager and data is
+ * Where mgr is the widget manager, id is the _id passed to makeWidget() and data is
  * initialisation data. The method should throw a WidgetDataException (created without
  * parameters) if the data has wrong length or is otherwise invalid.
  *
- * All widgets should set their own size in this() or finalize(), although some parents may set
- * child-widgets' size during their creation. Widgets may rely on setPosition() being called after
- * finalize().
- * 
- * Also see finalize().
+ * 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 : IWidget
 {
 //BEGIN Load and save
-    // NOTE - change?
-    /** Called on all widgets after all widgets have been created in a deepest first order.
+    /** 2nd stage of initialization for widgets; also called on some changes.
      *
-     * finalize must be called before any other methods on the widget, which means this() cannot
-     * call sub-widgets' methods, but finalize() can. */
-    void prefinalize ();
-    void finalize ();   /// ditto
-    
-    /** Widget should return a list of all its children. */
-    IChildWidget[] children ();
+     * Widgets should call recursively on their children, redo anything indicated by flags, and
+     * adjust their size and other cached data dependant on any thing which may have changed.
+     * Widgets may rely on setPosition being called afterwards.
+     * 
+     * Params:
+     *	n =	Indicates this is the (n+1)-th time the function has been called.
+     *	flags =	if (flags & 1) the renderer has been changed,
+     *		if (flags & 2) translation strings are being reloaded.
+     *		These flags are always true on first run.
+     *
+     * Returns:
+     *	The method should return true if the dimensions (may) have been changed. This may not be
+     *	the case on the first run (when n == 0)!.
+     */
+    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
@@ -171,14 +175,6 @@
      * ids). */
     bool saveChanges ();
     
-    /** Called when the renderer is changed (at least when the changes affect dimensions).
-     * Also called after widget creation, before any other methods are called.
-     * 
-     * Returns: true when widget's dimensions (may) have changed.
-     * 
-     * Should be propegated down to all child widgets. */
-    bool rendererChanged ();
-    
     /+ Use when widget editing is available? Requires widgets to know their parents.
     /** Called when a child widget's size has changed.
      * 
@@ -186,13 +182,6 @@
     void childChanged ();
     +/
     
-    /** Called if translated strings have been reloaded and widgets need to reload theirs.
-     * 
-     * Returns: true when widget's dimensions (may) have changed.
-     * 
-     * Should be propegated down to all child widgets. */
-    bool reloadStrings ();
-    
 //END Load and save
     
 //BEGIN Size and position
@@ -232,7 +221,7 @@
     void setWidth (wdim nw, int dir);
     void setHeight (wdim nh, int dir);	/// ditto
     
-    /** Set the current position (i.e. called on init and move). */
+    /** Set the current position (called after setup and to move widget). */
     void setPosition (wdim x, wdim y);
 //END Size and position
     
--- a/mde/gui/widget/Popup.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/Popup.d	Sat Dec 06 17:41:42 2008 +0000
@@ -26,13 +26,22 @@
 	WDCheck (data, 1,2);
 	subWidget = mgr.makeWidget (data.strings[0], content);
         
-        adapter = mgr.renderer.getAdapter (data.strings[1]);
+        adapter = mgr.renderer.getAdapter;
+	adapter.text = data.strings[1];
         adapter.getDimensions (mw, mh);
         w = mw;
         h = mh;
         super (mgr, id, data);
     }
     
+    bool setup (uint n, uint flags) {
+	return subWidget.setup (n,flags);
+    }
+    
+    bool saveChanges () {
+	return subWidget.saveChanges;
+    }
+    
     void activated () {
         mgr.addPopup (x,y, subWidget);
     }
--- a/mde/gui/widget/TextWidget.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/TextWidget.d	Sat Dec 06 17:41:42 2008 +0000
@@ -38,18 +38,20 @@
     /** Set the adapter first:
      * adapter = mgr.renderer.getAdapter ("string", 0xRRGGBB); */
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
-        adapter.getDimensions (mw, mh);
-        w = mw;
-        h = mh;
         super (mgr, id, data);
     }
     
-    bool reloadStrings () {
-	adapter.getDimensions (mw, mh);
-	bool r = (mw != w || mh != h) ? true : false;
-	w = mw;
-	h = mh;
-	return true;
+    /** Recalculates dims if the renderer changed. */
+    bool setup (uint,uint flags) {
+	if (flags & 1) {
+	    adapter.getDimensions (mw, mh);
+	    if (mw != w || mh != h) {
+		w = mw;
+		h = mh;
+		return true;
+	    }
+	}
+	return false;
     }
     
     void draw () {
@@ -73,7 +75,8 @@
      * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
         WDCheck (data, 2, 1);
-        adapter = mgr.renderer.getAdapter (data.strings[0], data.ints[1]);
+        adapter = mgr.renderer.getAdapter (data.ints[1]);
+	adapter.text = data.strings[0];
         super (mgr, id, data);
     }
 }
@@ -86,13 +89,14 @@
         content = c;
         if (!content) throw new ContentException ();
         index = data.ints[1];
-        adapter = mgr.renderer.getAdapter (content.toString(index), data.ints[2]);
+        adapter = mgr.renderer.getAdapter (data.ints[2]);
         super (mgr, id,data);
     }
     
-    bool reloadStrings () {
+    bool setup (uint n, uint flags) {
+	if (!(flags & 3)) return false;	// string or renderer (and possibly font) changed
 	adapter.text = content.toString(index);
-	return super.reloadStrings;
+	return super.setup (n, 3);	// force redimensioning
     }
     
 protected:
--- a/mde/gui/widget/Widget.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/Widget.d	Sat Dec 06 17:41:42 2008 +0000
@@ -53,29 +53,15 @@
         this.id = id;
     }
     
-    // Most widgets don't need this; all initialization os usually done in this()
-    void prefinalize () {}
-    void finalize () {}
-    
-    // ParentWidget is inteded for parent widgets to derive
-    IChildWidget[] children () {
-        return null;
+    // Widgets need to do their initialization either in this() or setup().
+    bool setup (uint,uint) {
+	return false;
     }
     
     // Don't save any data: fine for many widgets.
     bool saveChanges () {
         return false;
     }
-    
-    // Very basic implementation which assumes the renderer cannot affect the widget's size.
-    bool rendererChanged () {
-        return false;
-    }
-    
-    // Widgets displaying strings will need this.
-    bool reloadStrings () {
-	return false;
-    }
 //END Load and save
     
 //BEGIN Size and position
@@ -174,23 +160,22 @@
 }
 
 /*************************************************************************************************
-* An abstract base widget class for parent widgets.
-*************************************************************************************************/
+ * An abstract base widget class for parent widgets (many parent widgets don't use these methods).
+ * 
+ * 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, widgetID id, WidgetData data) {
         super (mgr, id, data);
     }
     
-    // Tells all subWidgets to reload strings, but cannot adjust size of self, so not ultimately useful.
-    bool reloadStrings () {
-	foreach (widg; subWidgets)
-	    widg.reloadStrings;
-	return false;
-    }
-    
-    IChildWidget[] children () {
-        return subWidgets;
+    bool setup (uint n, uint flags) {
+	bool c = false;
+	foreach (w; subWidgets)
+	    c |= w.setup (n,flags);
+	return c;
     }
     
     bool saveChanges () {
--- a/mde/gui/widget/layout.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/layout.d	Sat Dec 06 17:41:42 2008 +0000
@@ -152,57 +152,58 @@
      * the call to genCachedConstructionData can be moved to the derived this() methods.)
      * 
      * Derived constructors may also set initWidths to the array of column widths followed by
-     * row heights used to initially set the row/column dimensions.
-     * 
-     * Sub-widgets are finalized here, so no methods should be called on sub-widgets before calling
-     * this super. */
+     * row heights used to initially set the row/column dimensions. */
     protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
         super (mgr, id, data);
         
         // Create cell aligners with appropriate col/row adjustment function
         if (data.ints[1] & 1)
-            col = AlignColumns.getInstance (id, cols);
-        else
-            col = (new AlignColumns (cols));
-        col.addSetCallback (&setColWidth);
-        if (data.ints[1] & 2)
-            row = AlignColumns.getInstance (id~"R", rows);      // id must be unique to that for cols!
+	    col = AlignColumns.getInstance (id, cols);
         else
-            row = (new AlignColumns (rows));
-        row.addSetCallback (&setRowHeight);
+	    col = (new AlignColumns (cols));
+        col.addCallbacks (&setColWidth, &setupAlignDimData);
+        if (data.ints[1] & 2)
+	    row = AlignColumns.getInstance (id~"R", rows);      // id must be unique to that for cols!
+        else
+	    row = (new AlignColumns (rows));
+        row.addCallbacks (&setRowHeight, &setupAlignDimData);
         useSpacing = (data.ints[1] & 4) != 0;
     }
     
-    /** Prior to finalizing but after sub-widgets are finalized, some information needs to be
-     * passed to the AlignColumns. */
-    void prefinalize () {
-        genCachedConstructionData;  // min widths, sizableness
-    }
-    
     /** Responsible for calculating the minimal size and initializing some stuff.
      *
      * As such, this must be the first function called after this(). */
-    void finalize () {
-        if (initWidths.length == cols + rows) {
-            col.setWidths (initWidths[0..cols]);
-            row.setWidths (initWidths[cols..$]);
-        } else {
-            col.setWidths;
-            row.setWidths;
-        }
-        initWidths = null;  // free
-        
-        mw = col.mw;
-        mh = row.mw;
-        w = col.w;
-        h = row.w;
-        
-        // Tell subwidgets their new sizes. Positions are given by a later call to setPosition.
-        foreach (i,widget; subWidgets) {
-            // Resizing direction is arbitrarily set to negative:
-            widget.setWidth  (col.width[i % cols], -1);
-            widget.setHeight (row.width[i / cols], -1);
-        }
+    bool setup (uint n, uint flags) {
+	// Run all internal calculations regardless of changes, then check dimensions for changes.
+	// Don't try shortcutting internal calculations when there are no changes - I've tried, and
+	// doing so adds enough overhead to make doing so almost(?) worthless (or at least large
+	// increases in complexity).
+	wdim ow = w, oh = h;
+	
+	col.setup (n, flags);
+	row.setup (n, flags);
+	
+	if (initWidths.length == cols + rows) {
+	    col.setWidths (initWidths[0..cols]);
+	    row.setWidths (initWidths[cols..$]);
+	} else {
+	    col.setWidths;
+	    row.setWidths;
+	}
+	initWidths = null;  // free
+	
+	mw = col.mw;
+	mh = row.mw;
+	w = col.w;
+	h = row.w;
+	
+	// Tell subwidgets their new sizes. Positions are given by a later call to setPosition.
+	foreach (i,widget; subWidgets) {
+	    // Resizing direction is arbitrarily set to negative:
+	    widget.setWidth  (col.width[i % cols], -1);
+	    widget.setHeight (row.width[i / cols], -1);
+	}
+	return (ow != w || oh != h);
     }
     //END Creation & saving
     
@@ -284,26 +285,34 @@
     
 package:
     /* Calculations which need to be run whenever a new sub-widget structure is set
-     * (i.e. to produce cached data calculated from construction data).
-     * Also need to be re-run if the renderer changes.
+     * or other changes affecting widget sizes. Most of these need to happen regardless of whether
+     * changes have occurred, since AlignColumns have been reset.
      *
      * rows, cols and subWidgets must be set before calling. Part of the set-up for AlignColumns
      * (col and row). subWidgets need to know their minimal size and resizability. */
-    void genCachedConstructionData () {
-        // Will only change if renderer changes:
-        // NOTE shared AlignColumns get this set by all sharing GridWidgets
+    void setupAlignDimData (uint n, uint flags) {
+	if (sADD_n == n) return;	// cached data is current
+	sADD_n = n;
+	
+	foreach (widg; subWidgets)	// make sure all subwidgets have been set up
+	    widg.setup (n,flags);
+	// make sure both AlignColumns are set up (since first call to setup(n) calls reset):
+	col.setup (n, flags);
+	row.setup (n, flags);
+	
+	// Note: shared AlignColumns get this set by all sharing GridWidgets
         col.spacing = row.spacing = useSpacing ? mgr.renderer.layoutSpacing : 0;
         
         // Calculate the minimal column and row sizes:
         // AlignColumns (row, col) takes care of initializing minWidth.
         foreach (i,widget; subWidgets) {
             // Increase dimensions if current minimal size is larger:
-            myIt n = i % cols;	// column
+            myIt j = i % cols;	// column
             wdim md = widget.minWidth;
-            if (col.minWidth[n] < md) col.minWidth[n] = md;
-            n = i / cols;		// row
+            if (col.minWidth[j] < md) col.minWidth[j] = md;
+            j = i / cols;		// row
             md = widget.minHeight;
-            if (row.minWidth[n] < md) row.minWidth[n] = md;
+            if (row.minWidth[j] < md) row.minWidth[j] = md;
         }
         
         // Find which cols/rows are resizable:
@@ -370,7 +379,8 @@
     
     myIt cols, rows;	// number of cells in grid
     wdim[] initWidths;  // see this / setInitialSize
-    bool useSpacing;	// true if spacing should be applied
+    uint sADD_n = uint.max;	// param n of last setup call after setupAlignDimData has run
+    bool useSpacing;	// add inter-row/col spacing?
     
     /* All widgets in the grid, by row. Order:  [ 0 1 ]
      *                                          [ 2 3 ] */
@@ -417,18 +427,31 @@
      * After creation, minimal widths should be set for all columns (minWidth) and
      * setWidths must be called before other functions are used. */
     this (myIt columns) {
-        reset (columns);
+	if (columns < 1)
+	    throw new GuiException("AlignColumns: created with <1 column (code error)");
+	minWidth.length = columns;
+	sizable.length = columns;
+    }
+    
+    /** Like IChildWidget's setup; calls sADD delegates. */
+    void setup (uint n, uint flags) {
+	if (n != setup_n) {
+	    logger.trace ("AlignColumns.setup ({}): {}", n, cast(void*)this);
+	    setup_n = n;
+	    setupWidths = false;
+	    reset (minWidth.length);
+	    
+	    foreach (dg; sADD)
+		dg (n, flags);	// set flag 1
+	}
     }
     
     /** Reset all column information (only keep set callbacks).
      *
      * Widths should be set after calling, as on creation. */
     void reset (myIt columns) {
-        if (columns < 1)
-            throw new GuiException("AlignColumns: created with <1 column (code error)");
-        minWidth = new wdim[columns];
-        sizable = new bool[columns];
-        width = null;   // enforce calling setWidths after this
+        minWidth[] = 0;
+        sizable[] = false;
         firstSizable = -1;
         lastSizable = -1;
     }
@@ -438,11 +461,14 @@
      * Also calculates first/lastSizable from sizable, overall minimal width and column positions.
      */
     void setWidths (wdim[] data = null) {
-        if (!width) {
-            if (data) {
-                debug assert (data.length == minWidth.length, "setWidths called with bad data length (code error)");
-                width = data.dup;       // data is shared by other widgets with same id so must be .dup'ed
-                // And check sizes are valid:
+	if (!setupWidths) {
+	    logger.trace ("setWidths");
+	    setupWidths = true;
+            if (data || width) {	// use existing/external data: need to check validity
+                if (data) {
+                    assert (data.length == minWidth.length, "setWidths called with bad data length (code error)");
+                    width = data.dup;	// data is shared by other widgets with same id so must be .dup'ed
+                }
                 foreach (i, m; minWidth) {
                     if (!sizable[i] || width[i] < m)    // if width is fixed or less than minimum
                         width[i] = m;
@@ -474,13 +500,12 @@
         }
     }
     
-    /** Add a callback to be called to notify changes in a column's width.
-    * 
-    * All callbacks added are called on a width change so that multiple objects may share a
-    * CellAlign object. */
-    typeof(this) addSetCallback (void delegate (myIt,wdim,int) setCW) {
-        assert (setCW, "CellAlign.this: setCW is null (code error)");
+    /** Add a callback to be called to notify changes in a column's width, and the sADD callback.
+     */
+    typeof(this) addCallbacks (void delegate (myIt,wdim,int) setCW, void delegate (uint,uint) sDg) {
+	assert (setCW && sDg, "AlignColumns.addCallbacks: null callback (code error)");
         setWidthCb ~= setCW;
+	sADD ~= sDg;
         return this;
     }
     
@@ -669,6 +694,10 @@
     myDiff  firstSizable = -1, lastSizable = -1;  // set by calcFLSbl
     // Callbacks used to actually adjust a column's width:
     void delegate (myIt,wdim,int) setWidthCb[]; // set width of a column, with resize direction
+    void delegate (uint,uint) sADD[];	// setupAlignDimData dlgs
+    
+    uint setup_n = uint.max;	// param n of last setup call
+    bool setupWidths;		// setWidths has been run
     
     static HashMap!(widgetID,AlignColumns) instances;
     static this () {
--- a/mde/gui/widget/miscContent.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/miscContent.d	Sat Dec 06 17:41:42 2008 +0000
@@ -92,11 +92,19 @@
 	WDCheck (data, 1);
 	content = cast(EventContent) c;
 	if (!content) throw new ContentException ();
-	adapter = mgr.renderer.getAdapter (content.toString (1));
+	adapter = mgr.renderer.getAdapter ();
+	super (mgr, id, data);
+    }
+    
+    bool setup (uint n, uint flags) {
+	if (!(flags & 3)) return false;	// string or renderer (and possibly font) changed
+	adapter.text = content.toString(1);
 	adapter.getDimensions (mw, mh);
-	w = mw;
-	h = mh;
-	super (mgr, id, data);
+	if (mw != w || mh != h) {
+	    w = mw;
+	    h = mh;
+	}
+	return true;
     }
     
     void draw () {
--- a/mde/gui/widget/textContent.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/gui/widget/textContent.d	Sat Dec 06 17:41:42 2008 +0000
@@ -38,7 +38,8 @@
 	WDMinCheck(data, 1);
 	content = c;
 	if (!content) throw new ContentException ();
-	adapter = mgr.renderer.getAdapter (content.toString(0));
+	adapter = mgr.renderer.getAdapter ();
+	adapter.text = content.toString(0);
 	super (mgr, id, data);
     }
     
@@ -54,10 +55,14 @@
         content = cast(AStringContent) c;
         if (!content) //content = new TextContent (null, null);
 	    throw new ContentException ();
-        adapter = mgr.renderer.getAdapter (content.toString(0));
-        super (mgr, id, data);
+        adapter = mgr.renderer.getAdapter ();
+	adapter.text = content.toString(0);
+	super (mgr, id, data);
     }
     
+    bool isWSizable () {    return true;    }
+    bool isHSizable () {    return true;    }
+    
     /** On click, request keyboard input. */
     int clickEvent (wdabs, wdabs, ubyte, bool state) {
 	adapter.index = content.editIndex;
--- a/mde/setup/InitStage.d	Fri Dec 05 11:29:39 2008 +0000
+++ b/mde/setup/InitStage.d	Sat Dec 06 17:41:42 2008 +0000
@@ -27,7 +27,7 @@
     // use as hash type for better performance than char[]
     alias uint StageName;
     StageName toStageName (char[4] x) {
-        return *cast(uint*) x.ptr;      // NOTE - little hack to read as a uint
+        return *cast(uint*) x.ptr;      // convert to a unique uint
     }
     
     /** Initialization and cleanup functions for one stage.