changeset 91:4d5d53e4f881

Shared alignment for dynamic content lists - finally implemented! Lots of smaller changes too. Some debugging improvements. When multiple .mtt files are read for merging, files with invalid headers are ignored and no error is thrown so long as at least one file os valid.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 16 Oct 2008 17:43:48 +0100
parents b525ff28774b
children 085f2ca31914
files codeDoc/jobs.txt codeDoc/todo.txt data/conf/gui.mtt data/conf/options.mtt mde/gui/WidgetDataSet.d mde/gui/WidgetManager.d mde/gui/content/Content.d mde/gui/content/options.d mde/gui/exception.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Floating.d mde/gui/widget/Ifaces.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/gui/widget/miscWidgets.d mde/setup/Init.d mde/setup/InitStage.d mde/setup/Screen.d mde/setup/paths.d mde/types/Colour.d
diffstat 22 files changed, 536 insertions(+), 383 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Wed Oct 01 23:37:51 2008 +0100
+++ b/codeDoc/jobs.txt	Thu Oct 16 17:43:48 2008 +0100
@@ -3,12 +3,11 @@
 
 
 In progress:
-Layout alignment sharing for instances of same widgetID.
 
 
 
 Bugs:
-Sometimes nothing is drawn until a resize, and fonts are blocks? External bug?
+Sometimes nothing is drawn until a resize, and fonts are blocks? External bug? Info: doesn't happen when limited to one thread.
 
 
 
--- a/codeDoc/todo.txt	Wed Oct 01 23:37:51 2008 +0100
+++ b/codeDoc/todo.txt	Thu Oct 16 17:43:48 2008 +0100
@@ -2,24 +2,25 @@
 License: GNU General Public License version 2 or later (see COPYING)
 
 
+Content:
+Need a way for a single content to contain multiple "sub-contents", and a way for widgets to be bound to a sub-content.
+->  how
+    ->  Use indecies; widget's use an integer value to choose index (or a string)?
+    ->  Protection: read-only and read-write contents?
+->  why
+    ->  Dynamic lists of name, value and possibly description fields.
+        ->  Done in a basic case, so is it needed?
+Generic Content creation:
+->  Created centrally, e.g. from options or translation strings.
+    ->  Creator keeps a hash-map of all created content, so that if the same content is asked for again the same content object will be returned, not a new object (besides efficiency, this keeps content synchronized).
+->  Generic ContentList type for instancing on at run-time.
+->  Items in ContentList may represent a cluster of items; e.g. an option plus it's name and description. These may be special classes, but should use a generic interface allowing getting sub-contents (e.g. value/name/desc.) via an index.
+
+
 GUI:
 ->  Widgets:
-->  rethink how widgets are created and receive creation data, so that they don't have to be created by the Window
 ->  scripted widgets
 ->  decent rendering/theme system
-->  lists from content lists
 
 
 Scratchpad area for ideas:
-
-
-Redesign:
-->  possibilities
-    ->  How widgets get their sub-widgets:
-        ->  (current) Ask manager to create widget from manager's data with ID from widget's data.
-            ->  seems to work well; allows widgets some control over creation of children
-            ->  now extended to support instancing & passing content; seems to fulfill requirements
-        ->  (alternate) Pass widget a list of pointers to all sub-widgets
-            ->  manager creates all static (from data files) widets
-            ->  other widgets can create dynamic widgets as or to pass to sub-widgets
-            ->  widget data needs a portion which is explicitly IDs for subwidgets, or data needs reforming into a tree
--- a/data/conf/gui.mtt	Wed Oct 01 23:37:51 2008 +0100
+++ b/data/conf/gui.mtt	Thu Oct 16 17:43:48 2008 +0100
@@ -2,15 +2,16 @@
 <char[]|Renderer="Simple">
 <char[]|Design="Working">
 {Working}
-<WidgetData|root={0:[0xC100,3,3],1:["square","blank","square","blank","content","blank","square","blank","square"]}>
+<WidgetData|root={0:[0xC100,0,3,3],1:["square","blank","square","blank","content","blank","square","blank","square"]}>
 <WidgetData|square={0:[0x1,6,6]}>
-<WidgetData|content={0:[0xC100,4,2],1:["floating","button","blank","blank","blank","opts","blank","blank"]}>
-<WidgetData|button={0:[0x10,200,200]}>
+<WidgetData|content={0:[0xC100,0,4,2],1:["floating","button","blank","blank","blank","opts","blank","blank"]}>
+<WidgetData|button={0:[0x10,50,50]}>
 <WidgetData|blank={0:[0x2]}>
-<WidgetData|opts={0:[0x8110],1:["optBox"]}>
-<WidgetData|optBox={0:[0xC100, 1,3],1:["optVal","optSep","optVal"]}>
-<WidgetData|optVal={0:[0x4020, 0xfe8c00]}>
-<WidgetData|optSep={0:[0x21, 0xff],1:["-=-"]}>
+<WidgetData|opts={0:[0x8110,0],1:["optBox"]}>
+<WidgetData|optBox={0:[0xC100,1,1,3],1:["optName","optSep","optVal"]}>
+<WidgetData|optName={0:[0x4020, 1, 0xfe8c00]}>
+<WidgetData|optVal={0:[0x4020, 0, 0xBF00]}>
+<WidgetData|optSep={0:[0x21, 0xff],1:["="]}>
 <WidgetData|floating={0:[0x8200,20,20],1:["text"]}>
 <WidgetData|text={0:[0x21,0xFF0000],1:["Floating text"]}>
 {Basic}
--- a/data/conf/options.mtt	Wed Oct 01 23:37:51 2008 +0100
+++ b/data/conf/options.mtt	Thu Oct 16 17:43:48 2008 +0100
@@ -1,10 +1,15 @@
 {MT01}
 {misc}
-<int|maxThreads=4>
+<int|maxThreads=1>
 <bool|exitImmediately=false>
 <char[]|L10n="en-GB">
 <int|logOptions=0x3000>
 <double|pollInterval=0.01>
+<char[]|a="tildb\naeouc\ngpqyg">
+<char[]|b="tildb">
+<char[]|c="aeouc">
+<char[]|g="gpqy">
+<char[]|z="fghijklpq">
 
 {font}
 <int|lcdFilter=2>
--- a/mde/gui/WidgetDataSet.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/WidgetDataSet.d	Thu Oct 16 17:43:48 2008 +0100
@@ -66,8 +66,8 @@
         return *p;
     }
     
-    // Per-widget data:
-    protected WidgetData[widgetID] widgetData;
+protected:
+    WidgetData[widgetID] widgetData;    // Per-widget data:
 }
 
 /*************************************************************************************************
--- a/mde/gui/WidgetManager.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/WidgetManager.d	Thu Oct 16 17:43:48 2008 +0100
@@ -95,8 +95,12 @@
             // See IWidgetManager.addClickCallback's documentation:
             if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return;
         
-        // NOTE: do we need to test if the click was on the gui (and thus child)?
-        // FIXME: yes, unless we can guarantee this!
+        // test the click was on the child widget
+        // cx/cy are unsigned, thus >= 0. Widget starts at (0,0)
+        if (cx >= child.width || cy >= child.height) {
+            debug logger.warn ("WidgetManager received click not on child; potentially an error");
+            return;
+        }
         IChildWidget widg = child.getWidget (cast(wdabs)cx,cast(wdabs)cy);
         if (widg !is null)
             widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
@@ -387,7 +391,7 @@
      * given in that module has the flag TAKES_CONTENT. */
     IChildWidget makeWidget (widgetID id, IContent content = null) {
         debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"');
-        return createWidget (this, curData[id], content);
+        return createWidget (this, id, curData[id], content);
     }
     
     /** For making changes. */
--- a/mde/gui/content/Content.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/content/Content.d	Thu Oct 16 17:43:48 2008 +0100
@@ -34,6 +34,7 @@
  * Use as a state switch (one option from an enumeration). E.g. a button/selection box could set a
  * state, and a tabbed box could show a tab based on this. Or could represent an option.
  */
+//TODO - write a generic IContent displaying widget. Also a generic editable?
 // Don't include dimension/drawing stuff because it's renderer specific and content should not be!
 // NOTE: an interface or a class?
 interface IContent
@@ -50,11 +51,24 @@
     ContentInt	toInt ();	/// ditto
     +/
     
-    /** Every Content should be convertible to a string, which, if possible, should be a sensible
-     * conversion of its content. */
-    char[] toString ();
+    
+    
+    /** Generically return strings.
+     *
+     * Every Content should return a string for i == 0; preferably its value. Other values of i
+     * can be used to return other strings. For unsupported values of i, null should be returned.
+     */
+    char[] toString (uint i);
 }
-
+/+
+/** Extension to interface providing text-specific tools. */
+interface IContentText : IContent
+{
+    char[] text ();            /// Get/set the value.
+    void text (char[] v);      /// ditto
+}
++/
+/+ FIXME - use content lists or drop?
 /** Get a content from the list (what list?). */
 ContentText getContentText (char[] id) {
     return new ContentText (id);	// forget the list for now
@@ -64,7 +78,9 @@
 ContentInt getContentInt (char[] id) {
     return new ContentInt (42);	// forget the list for now
 }
++/
 
+/+FIXME - currently unused
 /** Text content. */
 /* May end up extending a universal content type.
  *  Services like copy/paste could work on universal content.
@@ -135,3 +151,4 @@
 protected:
     int int_;
 }
++/
\ No newline at end of file
--- a/mde/gui/content/options.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/content/options.d	Thu Oct 16 17:43:48 2008 +0100
@@ -53,6 +53,7 @@
     ContentOption[] textOpts;
 }
 
+//FIXME - todo.txt
 class ContentOptionText : ContentOption
 {
     this (Options o, char[] s, char[] name, char[] desc) {
@@ -62,23 +63,26 @@
         desc_ = desc;
     }
     
-    char[] toString () {
+    char[] toString (uint i) {
+        if (i == 0)
+            return opts.get!(char[])(symb);
+        else if (i == 1)
+            return name_;
+        else if (i == 2)
+            return desc_;
+    }
+    /+char[] value () {
         return opts.get!(char[])(symb);
     }
     void value (char[] v) {
         opts.set!(char[])(symb, v);
-    }
+    }+/
 }
 
 abstract class ContentOption : IContent
 {
-    abstract char[] toString ();
-    
-    //char[] value ();		/// Get/set the value.
-    void value (char[] v);	/// ditto
-    
     // Get the symbol name (useful?)
-    
+    /+
     /// Get the translated name
     char[] name () {
         return name_;
@@ -88,7 +92,7 @@
     char[] description () {
         return desc_;
     }
-    
+    +/
 protected:
     Options opts;	// the set of options within which our option lies
     char[]  symb;	// the symbol name of our option
--- a/mde/gui/exception.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/exception.d	Thu Oct 16 17:43:48 2008 +0100
@@ -32,10 +32,20 @@
 /// Thrown when createWidget or a Widget class's this() is called with invalid data.
 class WidgetDataException : GuiException
 {
-    this () {   // Default, by Widget class's this
-        super ("Bad widget data");
+    this (Object o) {   // Default, by Widget class's this / WDCheck
+        super ("Bad widget data for "~o.classinfo.name);
     }
-    this (char[] msg) { // From createWidget
+}
+
+class ContentException : GuiException
+{
+    char[] getSymbol () {
+        return super.getSymbol ~ ".content";
+    }
+    this () {
+        super ("Unexpected content type");
+    }
+    this (char[] msg) {
         super (msg);
     }
 }
--- a/mde/gui/renderer/SimpleRenderer.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/renderer/SimpleRenderer.d	Thu Oct 16 17:43:48 2008 +0100
@@ -94,7 +94,12 @@
         gl.drawBox (x+border.l, y+border.t, w-border.l-border.r, h-border.t-border.b);
     }
 
-    void drawWidgetBack (wdim x, wdim y, wdim w, wdim h) {}
+    void drawWidgetBack (wdim x, wdim y, wdim w, wdim h) {
+        debug {
+            gl.setColor (0f, .2f, .2f);
+            gl.drawBox (x,y, w,h);
+        }
+    }
     
     void drawBlank (wdim x, wdim y, wdim w, wdim h) {
         gl.setColor (.4f, .4f, .4f);
--- a/mde/gui/widget/Floating.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/widget/Floating.d	Thu Oct 16 17:43:48 2008 +0100
@@ -55,7 +55,7 @@
  */
 class FloatingAreaWidget : SizableWidget
 {
-    this (IWidgetManager mgr, WidgetData data) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
         subWidgets.length = data.strings.length;
         foreach (i,s; data.strings)
             subWidgets[i] = mgr.makeWidget (s);
@@ -63,7 +63,7 @@
         
         if (data.ints.length != 1) {
             if (data.ints.length != 2*subWidgets.length + 1) {
-                throw new WidgetDataException;
+                throw new WidgetDataException (this);
             }
             foreach (i, ref c; sWCoords) {
                 c.x = cast(wdim) data.ints[i + 1];
@@ -71,7 +71,7 @@
             }
         }
         
-        super (mgr, data);
+        super (mgr, id, data);
         
         foreach (w; subWidgets) {
             //FIXME: set default size
--- a/mde/gui/widget/Ifaces.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/widget/Ifaces.d	Thu Oct 16 17:43:48 2008 +0100
@@ -61,7 +61,8 @@
      */
     IChildWidget makeWidget (widgetID id, IContent content = null);
     
-    /** Record some changes, for saving. */
+    /** Record some changes, for saving. Should only be called from IWidget.saveChanges() to avoid
+     * multiple calls for instanced widgets of same id. */
     void setData (widgetID id, WidgetData);
     
     // Rendering:
@@ -145,6 +146,7 @@
      * 
      * If the widget has subwidgets, it should also be recursively called on these (passing their 
      * ids). */
+    // FIXME - no longer necessary to pass id!
     bool saveChanges (widgetID id);
     
     /** Called when the renderer is changed (at least when the changes affect dimensions).
@@ -164,23 +166,27 @@
 //END Load and save
     
 //BEGIN Size and position
-    /** is the width / height resizable?
+    /** Is the width / height resizable?
      *
-     * If not, the widget has fixed dimensions equal the output of getMinimalSize. */
+     * This really means does the widget benifit from being enlarged? Any widget should occupy
+     * additional area when expanded.
+     *
+     * If not, the widget has fixed dimensions equal to it's minimal size. */
     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. */
+     * Takes into account child-widgets and any other contents.
+     * 
+     * Note: layout uses these calls to initialize it's alignment device. So, after creating a
+     * (layout) widget, minWidth should be the first function called on it! */
     wdim minWidth ();
     wdim minHeight ();	/// ditto
     
-    /** Get the current size of the widget.
-     * 
-     * Deprecated: is it needed now?
-     */
-    deprecated void getCurrentSize (out wdim w, out wdim h);
+    /** Get the current size of the widget. */
+    wdim width ();
+    wdim height();      /// ditto
     
     /** Used to adjust the size.
      *
@@ -191,10 +197,9 @@
      *  	  index as necessary), or +1 (resize from the lowest index, i.e. 0).
      *  	  Most widgets can simply ignore it.
      *
-     * If called with dimensions less than minWidth/minHeight return: the widget may set its size
-     * to either the dimension given or its minimal dimension (even though this is larger). If the
-     * larger size is set, events won't be received in the extra area. FIXME: sort out rendering.
-     * Otherwise, the dimensions should always be set exactly.
+     * A widget should never be resized smaller than it's minimal size (if it is, it should assume
+     * it's minimal size and print a warning when in debug mode).
+     * 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);
--- a/mde/gui/widget/TextWidget.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/widget/TextWidget.d	Thu Oct 16 17:43:48 2008 +0100
@@ -26,6 +26,32 @@
 
 import mde.font.font;
 
+/// Adapter to ease use of ContentLabelWidget
+struct TextAdapter {
+    void set (char[] c, int col) {
+        //FIXME tie font to renderer or so
+        if (font is null) font = FontStyle.get("default");
+        
+        content = c;
+        colour = Colour (col);
+    }
+    
+    void getDimensions (out wdsize w, out wdsize h) {
+        font.updateBlock (content, textCache);
+        w = cast(wdim) textCache.w;
+        h = cast(wdim) textCache.h;
+    }
+    
+    void draw (wdabs x, wdabs y) {
+        font.textBlock (x,y, content, textCache, colour);
+    }
+    
+    char[] content;
+    TextBlock textCache;
+    Colour colour;
+    static FontStyle font;
+}
+
 /// Basic text widget
 class TextLabelWidget : Widget
 {
@@ -35,64 +61,11 @@
      * [widgetID, contentID, colour]
      * where contentID is an ID for the string ID of the contained content
      * and colour is an 8-bit-per-channel RGB colour of the form 0xRRGGBB. */
-    this (IWidgetManager mgr, WidgetData data) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
         WDCheck (data, 2, 1);
-        if (font is null) font = FontStyle.get("default");
-        font.updateBlock (data.strings[0], textCache);
-        mw = cast(wdim) textCache.w;
-        mh = cast(wdim) textCache.h;
-        colour = Colour (data.ints[1]);
-        super (mgr,data);
-    }
-    
-    void draw () {
-        super.draw();
-        font.textBlock (x,y, text, textCache, colour);
-    }
-    
-protected:
-    char[] text;
-    Colour colour;
-    TextBlock textCache;
-    static FontStyle font;
-}
-
-
-/// Adapter to ease use of ContentLabelWidget
-struct ContentLabelAdapter {
-    void set (IContent c, int col) {
-        if (font is null) font = FontStyle.get("default");
-        
-        content = c;
-        colour = Colour (cast(ubyte) (col >> 16u),
-                         cast(ubyte) (col >> 8u),
-                         cast(ubyte) col );
-    }
-    
-    void getDimensions (out wdsize w, out wdsize h) {
-        font.updateBlock (content.toString, textCache);
-        w = cast(wdim) textCache.w;
-        h = cast(wdim) textCache.h;
-    }
-    
-    void draw (wdabs x, wdabs y) {
-        font.textBlock (x,y, content.toString, textCache, colour);
-    }
-    
-    IContent content;
-    TextBlock textCache;
-    Colour colour;
-    static FontStyle font;
-}
-
-/// Basic widget displaying a label from a content.
-class ContentLabelWidget : Widget
-{
-    this (IWidgetManager mgr, WidgetData data, IContent c) {
-        WDCheck (data, 2, 0);
-        adapter.set (c, data.ints[1]);
+        adapter.set (data.strings[0], data.ints[1]);
         adapter.getDimensions (mw, mh);
-        super (mgr,data);
+        super (mgr, id, data);
     }
     
     void draw () {
@@ -101,5 +74,28 @@
     }
     
 protected:
-    ContentLabelAdapter adapter;
+    TextAdapter adapter;
 }
+
+/// Basic widget displaying a label from a content.
+class ContentLabelWidget : Widget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+        WDCheck (data, 3, 0);
+        content = c;
+        index = data.ints[1];
+        adapter.set (content.toString(index), data.ints[2]);
+        adapter.getDimensions (mw, mh);
+        super (mgr, id,data);
+    }
+    
+    void draw () {
+        super.draw();
+        adapter.draw (x,y);
+    }
+    
+protected:
+    TextAdapter adapter;
+    IContent content;
+    int index;
+}
--- a/mde/gui/widget/Widget.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/widget/Widget.d	Thu Oct 16 17:43:48 2008 +0100
@@ -25,20 +25,12 @@
 public import mde.gui.widget.Ifaces;
 import mde.gui.exception;
 
-
-/*************************************************************************************************
- * Widgets may use WDCheck as a utility to check what data holds. Its use is encouraged, so that
- * the checks can easily be updated should WidgetData be changed.
- * 
- * Params:
- *  data    = the WidgetData to check lengths of
- *  n_ints  = number of integers wanted
- *  n_strings= number of strings (default 0 since not all widgets use strings)
- *************************************************************************************************/
-void WDCheck (WidgetData data, size_t n_ints, size_t n_strings = 0) {
-    if (data.ints.length    != n_ints ||
-        data.strings.length != n_strings)
-        throw new WidgetDataException;
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this () {
+        logger = Log.getLogger ("mde.gui.widget.Widget");
+    }
 }
 
 
@@ -53,7 +45,7 @@
 {
 //BEGIN Load and save
     // Base this() for child Widgets.
-    this (IWidgetManager mgr, WidgetData data) {
+    this (IWidgetManager mgr, widgetID, WidgetData) {
         this.mgr = mgr;
     }
     
@@ -80,6 +72,13 @@
         return mh;
     }
     
+    wdim width () {
+        return w;
+    }
+    wdim height() {
+        return h;
+    }
+    
     deprecated void getCurrentSize (out wdim cw, out wdim ch) {
         cw = w;
         ch = h;
@@ -88,9 +87,11 @@
     /* Set size: minimal size is (mw,mh). Note that both resizable and fixed widgets should allow
      * enlarging, so in both cases this is a correct implementation. */
     void setWidth (wdim nw, int) {
+        debug if (nw < mw) logger.warn ("Widget width set below minimal size");
         w = (nw >= mw ? nw : mw);
     }
     void setHeight (wdim nh, int) {
+        debug if (nh < mh) logger.warn ("Widget height set below minimal size");
         h = (nh >= mh ? nh : mh);
     }
     
@@ -103,7 +104,8 @@
 //BEGIN Events
     /* This method is only called when the location is over this widget; hence for all widgets
      * without children this method is valid. */
-    IChildWidget getWidget (wdim,wdim) {
+    IChildWidget getWidget (wdim cx, wdim cy) {
+        debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
         return this;
     }
     
@@ -117,6 +119,21 @@
     }
     
 protected:
+    /**********************************************************************************************
+     * Widgets may use WDCheck as a utility to check what data holds. Its use is encouraged, so
+     * that the checks can easily be updated should WidgetData be changed.
+     * 
+     * Params:
+     *  data    = the WidgetData to check lengths of
+     *  n_ints  = number of integers wanted
+     *  n_strings= number of strings (default 0 since not all widgets use strings)
+     *********************************************************************************************/
+    void WDCheck (WidgetData data, size_t n_ints, size_t n_strings = 0) {
+    if (data.ints.length    != n_ints ||
+        data.strings.length != n_strings)
+        throw new WidgetDataException (this);
+    }
+    
     IWidgetManager mgr;		// the enclosing window
     wdim x, y;			// position
     wdim w, h;			// size
@@ -132,8 +149,8 @@
      * Widget uses the initialisation data:
      * [widgetID, w, h]
      * where w, h is the fixed size. */
-    this (IWidgetManager mgr, WidgetData data) {
-        super (mgr, data);
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+        super (mgr, id, data);
         mw = cast(wdim) data.ints[1];
         mh = cast(wdim) data.ints[2];
         w = mw;
@@ -145,8 +162,8 @@
 class SizableWidget : Widget {
     // Check data.length is at least 1 before calling!
     /// Constructor for a completely resizable [blank] widget.
-    this (IWidgetManager mgr, WidgetData data) {
-        super (mgr, data);
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+        super (mgr, id, data);
     }
     
     bool isWSizable () {    return true;    }
--- a/mde/gui/widget/createWidget.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/widget/createWidget.d	Thu Oct 16 17:43:48 2008 +0100
@@ -46,7 +46,7 @@
  * this (IWidgetManager mgr, WidgetData data, IContent content);
  * ---
  *************************************************************************************************/
-IChildWidget createWidget (IWidgetManager mgr, WidgetData data, IContent content)
+IChildWidget createWidget (IWidgetManager mgr, widgetID id, WidgetData data, IContent content)
 in {
     assert (mgr !is null, "createWidget: mgr is null");
 } body {
@@ -61,7 +61,7 @@
     
     // Not returned a new widget...
     logger.error ("Bad widget type: {}; creating a debug widget instead.",type);
-    return new DebugWidget (mgr, data);
+    return new DebugWidget (mgr, id, data);
 }
 
 /+ for converting to a char[] name (unused)
@@ -129,9 +129,9 @@
             ret ~=  "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~
                     "   debug (mdeWidgets) logger.trace (\"Creating new "~c~"Widget.\");\n" ~
                     "   static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n" ~
-                    "       return new " ~ c ~ "Widget (mgr, data, content);\n" ~
+                    "       return new " ~ c ~ "Widget (mgr, id, data, content);\n" ~
                     "   else\n" ~
-                    "       return new " ~ c ~ "Widget (mgr, data);\n" ~
+                    "       return new " ~ c ~ "Widget (mgr, id, data);\n" ~
                     "} else ";
         }
         ret = ret[0..$-6] ~ '\n';  // remove last else
--- a/mde/gui/widget/layout.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/widget/layout.d	Thu Oct 16 17:43:48 2008 +0100
@@ -23,6 +23,8 @@
 import mde.gui.content.options;
 import mde.gui.content.Content;
 
+import tango.util.container.HashMap;
+
 debug {
     import tango.util.log.Log : Log, Logger;
     private Logger logger;
@@ -46,24 +48,29 @@
     /** Constructor for a grid layout widget.
      *
      * Widget uses the initialisation data:
-     * [widgetID, r, c, w11, w12, ..., w1c, ..., wr1, ..., wrc]
-     * where r and c are the number of rows and columns, and wij is the ID (from parent Window's
+     * ---
+     * ints = [widget_type, align_flags, rows, cols]
+     * // or with column widths and row heights:
+     * ints = [widget_type, align_flags, rows, cols, col1width, ..., colCwidth, row1height, ..., rowRheight]
+     * strings = [w11, w12, ..., w1C, ..., wR1, ..., wRC]
+     * ---
+     * where R and C are the number of rows and columns, and wij is the ID (from parent Window's
      * list) for the widget in row i and column j. The number of parameters must be r*c + 3.
      * 
      * The content parameter is passed on to all children accepting an IContent. */
-    this (IWidgetManager mgr, WidgetData data, IContent content) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
         // Get grid size and check data
-        // Check sufficient data for rows, cols, and possibly row/col widths.
-        if (data.ints.length < 3) throw new WidgetDataException;
+        // Check sufficient data for type, align-flags, rows, cols, and possibly row/col widths.
+        if (data.ints.length < 4) throw new WidgetDataException (this);
         
-        rows = data.ints[1];
-        cols = data.ints[2];
+        rows = data.ints[2];
+        cols = data.ints[3];
         // Check: at least one sub-widget, ints length == 3 or also contains row & col widths,
         // strings' length is correct:
         if (rows < 1 || cols < 1 ||
-            (data.ints.length != 3 && data.ints.length != 3 + rows + cols) ||
+            (data.ints.length != 4 && data.ints.length != 4 + rows + cols) ||
             data.strings.length != rows * cols)
-            throw new WidgetDataException;
+            throw new WidgetDataException (this);
         this.data = data;
         
         // Get all sub-widgets
@@ -72,16 +79,10 @@
             subWidget = mgr.makeWidget (data.strings[i], content);
         }
         
-        super (mgr, data);
+        if (data.ints.length == 4 + rows + cols)
+            initWidths = cast(wdim[]) data.ints[4..$];
         
-        if (data.ints.length == 3 + rows + cols) {
-            col.setWidths (cast(wdim[]) data.ints[3..cols+3]);
-            row.setWidths (cast(wdim[]) data.ints[cols+3..$]);
-        } else {
-            col.setWidths;
-            row.setWidths;
-        }
-        adjustCache;
+        super (mgr, id, data);
     }
     
     // Save column/row sizes. Currently always do so.
@@ -90,7 +91,7 @@
             foreach (i, widget; subWidgets) // recurse on subwidgets
                 widget.saveChanges (strings[i]);
             
-            ints = ints[0..3] ~ cast(int[])col.width ~ cast(int[])row.width;
+            ints = ints[0..4] ~ cast(int[])col.width ~ cast(int[])row.width;
         }
         mgr.setData (id, data);
         return true;
@@ -105,10 +106,10 @@
  *************************************************************************************************/
 class TrialContentLayoutWidget : GridWidget
 {
-    this (IWidgetManager mgr, WidgetData data) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
         debug scope (failure)
                 logger.warn ("TrialContentLayoutWidget: failure");
-        WDCheck (data, 1, 1);
+        WDCheck (data, 2, 1);
         
         OptionList optsList = OptionList.trial();
         rows = optsList.list.length;
@@ -119,12 +120,7 @@
         foreach (i, c; optsList.list) {
             subWidgets[i] = mgr.makeWidget (data.strings[0], c);
         }
-        super (mgr, data);
-        
-        // Set col/row widths to minimals.
-        col.setWidths;
-        row.setWidths;
-        adjustCache;
+        super (mgr, id, data);
     }
     
 private:
@@ -151,34 +147,56 @@
      * before calling this super constructor. (If it's necessary to call super(...) first,
      * the call to genCachedConstructionData can be moved to the derived this() methods.)
      * 
-     * Derived constructors should call setWidths on col and row, and then call
-     * adjustCache, after calling this. */
-    protected this (IWidgetManager mgr, WidgetData data) {
-        super (mgr, data);
+     * Derived constructors may also set initWidths to the array of column widths followed by
+     * row heights used to initially set the row/column dimensions. */
+    protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
+        super (mgr, id, data);
         
         // Create cell aligners with appropriate col/row adjustment function
-        col = (new AlignColumns (cols)).addSetCallback (&setColWidth);
-        row = (new AlignColumns (rows)).addSetCallback (&setRowHeight);
+        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!
+        else
+            row = (new AlignColumns (rows));
+        row.addSetCallback (&setRowHeight);
         
         // Calculate cached construction data
         genCachedConstructionData;
     }
     
-    /** Generates cached mutable data.
+    /** Responsible for calculating the minimal size and initializing some stuff.
      *
-     * Should be called by adjust() after calling setWidths. */
-    void adjustCache () {
-        // Generate cached mutable data
-        // Calculate column and row locations:
-        w = col.genPositions;
-        h = row.genPositions;
-        
-        // 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);
+     * As such, this must be the first function called after this(). */
+    wdim minWidth () {
+        if (!alignInit) {       // assumes col & row.width are initialized simultaneously
+            alignInit = true;
+            if (initWidths) {
+                debug assert (initWidths.length == cols + rows, "initWidths provided but has bad length");
+                col.setWidths (initWidths[0..cols]);
+                row.setWidths (initWidths[cols..$]);
+                initWidths = null;  // free
+            } else {
+                col.setWidths;
+                row.setWidths;
+            }
+            
+            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 mw;
     }
     //END Creation & saving
     
@@ -191,17 +209,11 @@
     }
     
     void setWidth (wdim nw, int dir) {
-        if (nw == w) return;
-        
-        w += col.adjustCellSizes (nw - w, (dir == -1 ? col.lastSizable : col.firstSizable), dir);
-        
+        w = col.resizeWidth (nw, dir);
         // Note: setPosition must be called after!
     }
     void setHeight (wdim nh, int dir) {
-        if (nh == h) return;
-        
-        h += row.adjustCellSizes (nh - h, (dir == -1 ? row.lastSizable : row.firstSizable), dir);
-        
+        h = row.resizeWidth (nh, dir);
         // Note: setPosition must be called after!
     }
     
@@ -209,6 +221,7 @@
         this.x = x;
         this.y = y;
         
+        debug assert (col.pos && row.pos, "setPosition: col/row.pos not set (code error)");
         foreach (i,widget; subWidgets)
             widget.setPosition (x + col.pos[i % cols], y + row.pos[i / cols]);
     }
@@ -218,7 +231,9 @@
     // Find the relevant widget.
     IChildWidget getWidget (wdim cx, wdim cy) {
         debug scope (failure)
-                logger.warn ("getWidget: failure");
+            logger.warn ("getWidget: failure; values: click, pos, width - {}, {}, {} - {}, {}, {}", cx, x, w, cy, y, h);
+        debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
+        
         // Find row/column:
         myDiff i = col.getCell (cx - x);
         myDiff j = row.getCell (cy - y);
@@ -239,7 +254,7 @@
             * resizeRow is non-negative). */
             
             // find col/row's resizeD & resizeU
-            if (col.findResize (cx - x) && row.findResize (cy - y))
+            if (col.findResizeCols (cx - x) && row.findResizeCols (cy - y))
                 return;		// unable to resize
             
             dragX = cx;
@@ -263,15 +278,14 @@
      * (i.e. to produce cached data calculated from construction data).
      * Also need to be re-run if the renderer changes.
      *
-     * rows, cols and subWidgets must be set before calling. */
+     * rows, cols and subWidgets must be set before calling. Part of the set-up for AlignColumns
+     * (col and row). */
     void genCachedConstructionData () {
         // Will only change if renderer changes:
         col.spacing = row.spacing = mgr.renderer.layoutSpacing;
         
         // Calculate the minimal column and row sizes:
-        // set length, making sure the arrays are initialised to zero:
-        col.minWidth = new wdim[cols];
-        row.minWidth = new wdim[rows];
+        // 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
@@ -282,35 +296,16 @@
             if (row.minWidth[n] < md) row.minWidth[n] = md;
         }
         
-        
-        // Calculate the overall minimal size, starting with the spacing:
-        mh = mgr.renderer.layoutSpacing;	// use mh temporarily
-        mw = mh * cast(wdim)(cols - 1);
-        mh *= cast(wdim)(rows - 1);
-        
-        foreach (x; col.minWidth)		// add the column/row's dimensions
-            mw += x;
-        foreach (x; row.minWidth)
-            mh += x;
-        
-        
         // Find which cols/rows are resizable:
-        // reset:
-        col.sizable = new bool[cols];
-        row.sizable = new bool[rows];
-        col.firstSizable = row.firstSizable = -1;
-        
+        // AlignColumns initializes sizable, and sets first and last sizables.
         forCols:
         for (myIt i = 0; i < cols; ++i) {				// for each column
             for (myIt j = 0; j < subWidgets.length; j += cols)	// for each row
                 if (!subWidgets[i+j].isWSizable)	// column not resizable
                     continue forCols;			// continue the outer for loop
-                
+            
             // column is resizable if we get to here
             col.sizable[i] = true;
-            if (col.firstSizable < 0)
-                col.firstSizable = i;
-            col.lastSizable = i;
         }
         
         forRows:
@@ -319,10 +314,7 @@
                 if (!subWidgets[i+j].isHSizable)
                     continue forRows;
             
-            row.lastSizable = i / cols;
-            row.sizable[row.lastSizable] = true;
-            if (row.firstSizable < 0)
-                row.firstSizable = row.lastSizable;
+            row.sizable[i / cols] = true;
         }
     }
     //END Cache calculation functions
@@ -342,8 +334,8 @@
     
     //BEGIN Col/row resizing callback
     void resizeCallback (wdim cx, wdim cy) {
-        col.resize (cx - dragX);
-        row.resize (cy - dragY);
+        col.resizeCols (cx - dragX);
+        row.resizeCols (cy - dragY);
         
         // NOTE: all adjustments are relative; might be better if they were absolute?
         dragX = cx;
@@ -367,8 +359,9 @@
     wdim dragX, dragY;	// coords where drag starts
     //END Col/row resizing callback
     
-    
     myIt cols, rows;	// number of cells in grid
+    wdim[] initWidths;  // see this / setInitialSize
+    bool alignInit;     // have AlignColumns instances been initialized?
     
     /* All widgets in the grid, by row. Order:  [ 0 1 ]
      *                                          [ 2 3 ] */
@@ -377,13 +370,81 @@
     AlignColumns col, row;
 }
 
-/// Position information on top of widths.
-//FIXME - merge classes back together?
-class AlignColumns : AlignWidths
+
+/**************************************************************************************************
+ * Alignment device
+ * 
+ * E.g. can control widths of columns within a grid, and provide sensible resizing, respecting the
+ * minimal width required by each cell in a column. Is not restricted to horizontal widths, but to
+ * ease descriptions, a horizontal context (column widths) is assumed.
+ * 
+ * Cells should be of type IChildWidget.
+ * 
+ * Cells are not directly interacted with, but minimal widths for each column are passed, and
+ * callback functions are used to adjust the width of any column.
+ *************************************************************************************************/
+class AlignColumns
 {
-    /// See AlignWidths.this
+    /** Instance returned will be shared with any other widgets of same widgetID.
+     *
+     * Also ensures each widget sharing an instance expects the same number of columns. */
+    static AlignColumns getInstance (widgetID id, myIt columns) {
+        AlignColumns* p = id in instances;
+        if (p) {
+            if (p.minWidth.length != columns)
+                throw new GuiException ("AlignColumns: no. of columns varies between sharing widgets (code error)");
+            return *p;
+        } else {
+            auto a = new AlignColumns (columns);
+            instances[id] = a;
+            return a;
+        }
+    }
+    
+    /** Create an instance. After creation, the number of columns can only be changed by calling
+     * reset.
+     * 
+     * After creation, minimal widths should be set for all columns (minWidth) and
+     * setWidths must be called before other functions are used. */
     this (myIt columns) {
-        super (columns);
+        reset (columns);
+    }
+    
+    /** 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");
+        minWidth = new wdim[columns];
+        sizable = new bool[columns];
+        width = null;   // enforce calling setWidths after this
+        firstSizable = -1;
+        lastSizable = -1;
+        spare = 0;
+    }
+    
+    /** Initialize widths as minimal widths. */
+    void setWidths () {
+        if (!width) {
+            width = minWidth.dup;
+            initCalc;
+        }
+    }
+    /** Initialize widths from supplied list, checking validity. */
+    void setWidths (wdim[] data) {
+        if (!width) {
+            // Set to provided data:
+            width = data;
+            // And check sizes are valid:
+            foreach (i, m; minWidth) {
+                // if fixed width or width is less than minimum:
+                if (!sizable[i] || width[i] < m) {
+                    width[i] = m;
+                }
+            }
+            initCalc;
+        }
     }
     
     /** Add a callback to be called to notify changes in a column's width.
@@ -396,39 +457,62 @@
         return this;
     }
     
-    /** Generate position infomation and return total width of columns. */
-    wdim genPositions () {
-        pos.length = minWidth.length;
-        
-        wdim x = 0;
-        foreach (i, w; width) {
-            pos[i] = x;
-            x += w + spacing;
-        }
-        return x - spacing;
-    }
-    
-    // Get the row/column a click occured in
-    // Returns -i if in space to left of col i, or i if on col i
+    /** Get the row/column of relative position l.
+     *
+     * returns:
+     * -i if in space to left of col i, or i if on col i, or -(num cols + 1) if in $(I spare)
+     * space to right of last column. */
     myDiff getCell (wdim l) {
         myDiff i = minWidth.length - 1;     // starting from right...
         while (l < pos[i]) {                // decrement while left of this column
             debug assert (i > 0, "getCell: l < pos[0] (code error)");
             --i;
         }                                   // now (l >= pos[i])
-        if (l >= pos[i] + width[i]) {       // between columns
-            debug assert (i+1 < minWidth.length, "getCell: l >= pos[$-1] + width[$-1] (code error)");
+        if (l >= pos[i] + width[i]) {       // between columns or in spare space after last column
+            debug assert (i+1 < minWidth.length ||
+                          l < pos[i] + width[i] + spare,
+                          "getCell: l >= total width (code error)");
             return -i - 1;                  // note: i might be 0 so cannot just return -i
         }
         return i;
     }
     
-    // Calculate resizeU/resizeD, and return true if unable to resize.
-    bool findResize (wdim l) {
+    /** Adjust total size with direction dir.
+     *
+     * nw should be at least the minimal width. */
+    wdim resizeWidth (wdim nw, int dir) {
+        if (nw < mw) {
+            debug logger.warn ("Widget dimension set below minimal");
+            nw = mw;
+        }
+        if (nw == w) return w;
+        
+        wdim diff = nw - w;
+        if (firstSizable == -1) {
+            spare += diff;
+            w = nw;
+        } else
+            adjustCellSizes (diff, (dir == -1 ? lastSizable : firstSizable), dir);
+        
+        debug if (nw != w) {
+            logger.trace ("resizeWidth to {} failed, new width: {}",nw,w);
+            /+ Also print column widths & positions:
+            logger.trace ("resizeWidth to {} failed! Column dimensions and positions:",nw);
+            foreach (i,w; width)
+                logger.trace ("\t{}\t{}", w,pos[i]);+/
+        }
+        return w;
+    }
+    
+    /** Calculate resizeU/resizeD, and return true if unable to resize.
+     *
+     * This and resizeCols are for moving dividers between cells. */
+    bool findResizeCols (wdim l) {
         resizeU = -getCell (l);             // potential start for upward-resizes
-        if (resizeU <= 0) return true;      // not on a space between cells
-            resizeD = resizeU - 1;              // potential start for downward-resizes
-            
+        if (resizeU <= 0 || resizeU > minWidth.length)
+            return true;        // not on a space between cells or in spare space after last cell
+        resizeD = resizeU - 1;              // potential start for downward-resizes
+        
         while (!sizable[resizeU]) {         // find first actually resizable column (upwards)
             ++resizeU;
             if (resizeU >= minWidth.length) {       // cannot resize
@@ -447,6 +531,59 @@
         
         return false;                       // can resize
     }
+    /// Resize columns based on findResizeCols
+    void resizeCols (wdim diff)
+    {
+        if (resizeU <= 0) return;
+        
+        // do shrinking first (in case we hit the minimum)
+        if (diff >= 0) {
+            diff = -adjustCellSizes (-diff, resizeU, 1);
+            adjustCellSizes (diff, resizeD, -1);
+        } else {
+            diff = -adjustCellSizes (diff, resizeD, -1);
+            adjustCellSizes (diff, resizeU, 1);
+        }
+    }
+    
+    /** Intitialization triggered by setWidths.
+     * 
+     * Calculates first/lastSizable from sizable, minimal width and positions. */
+    private void initCalc () {
+        /* Calculate the minimal width of all columns plus spacing. */
+        mw = spacing * cast(wdim)(minWidth.length - 1);
+        foreach (w; minWidth)
+            mw += w;
+        
+        genPositions;
+        foreach (i,s; sizable) {
+            if (s) {
+                firstSizable = i;
+                goto gotFirst;
+            }
+        }
+        return; // none resizable - don't search for lastSizable
+        gotFirst:
+        
+        foreach_reverse (i,s; sizable) {
+            if (s) {
+                lastSizable = i;
+                return; // done
+            }
+        }
+    }
+    
+    /* Generate position infomation for each column and set w. */
+    private void genPositions () {
+        pos.length = minWidth.length;
+        
+        w = 0;
+        foreach (i, cw; width) {
+            pos[i] = w;
+            w += cw + spacing;
+        }
+        w -= spacing;
+    }
     
     /* Adjust the total size of rows/columns (including spacing) by diff.
     *
@@ -461,15 +598,14 @@
     * Note: Check variable used for start is valid before calling! If a non-sizable column's
     *  index is passed, this should get increased (if diff > 0) but not decreased.
     */
-    wdim adjustCellSizes (wdim diff, myDiff start, int incr)
+    private wdim adjustCellSizes (wdim diff, myDiff start, int incr)
     in {
-        assert (width, "CellAlign.adjustCellSizes: width is null (code error)");
+        assert (width.length == minWidth.length, "CellAlign.adjustCellSizes: width is null (code error)");
         // Most likely if passed negative when sizing is disabled:
         assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start");
         debug assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr");
     } body {
-        debug scope(failure)
-        logger.trace ("adjustCellSizes: failure");
+        debug scope(failure) logger.trace ("adjustCellSizes: failure");
         myDiff i = start;
         if (diff > 0) {             // increase size of first resizable cell
             width[i] += diff;
@@ -512,103 +648,38 @@
         return diff;
     }
     
-    void resize (wdim diff)
-    {
-        if (resizeU <= 0) return;
-        
-        // do shrinking first (in case we hit the minimum)
-        if (diff >= 0) {
-            diff = -adjustCellSizes (-diff, resizeU, 1);
-            adjustCellSizes (diff, resizeD, -1);
-        } else {
-            diff = -adjustCellSizes (diff, resizeD, -1);
-            adjustCellSizes (diff, resizeU, 1);
-        }
-    }
-    
-private:
-    wdim[]  pos;                // relative position (cumulative width[i-1] plus spacing)
-    wdim    spacing;            // used by genPositions (which cannot access the layout class's data)
-    // Callbacks used to actually adjust a column's width:
-    void delegate (myIt,wdim,int) setWidthCb[]; // set width of a column, with resize direction
-}
-
-/**************************************************************************************************
- * Alignment device
- * 
- * E.g. can control widths of columns within a grid, and provide sensible resizing, respecting the
- * minimal width required by each cell in a column. Is not restricted to horizontal widths, but to
- * ease descriptions, a horizontal context (column widths) is assumed.
- * 
- * Cells should be of type IChildWidget.
- * 
- * FIXME:
- * Cells are not directly interacted with, but minimal widths for each column are passed, and
- * callback functions are used to adjust the width of any column.
- *************************************************************************************************/
-//FIXME: how to set cell positions after resizes?
-/+ FIXME - remove position information from here, removing need of horizontal alignment of cells
- into columns. On resizing, don't permanently adjust sizes until callback ends (mouse button
- released). Until then, always readjust from current "permanent" size. +/
-class AlignWidths
-{
-    /** Create an instance. After creation, the number of columns can only be changed by calling
-     * reset.
-     * 
-     * 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);
-    }
-    
-    /** 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("AlignWidths: created with <1 column");
-        minWidth = new wdim[columns];
-        width = null;   // enforce calling setWidths after this
-    }
-    
-    /** Initialize widths as minimal widths. */
-    void setWidths () {
-        width = minWidth.dup;
-    }
-    /** Initialize widths from supplied list, checking validity. */
-    void setWidths (wdim[] data) {
-        // Set to provided data:
-        width = data;
-        // And check sizes are valid:
-        foreach (i, m; minWidth)
-            // if fixed width or width is less than minimum:
-            if (!sizable[i] || width[i] < m)
-                width[i] = m;
-    }
-    
     /** Minimal width for each column.
      *
-     * Initialized to zero. Each class using this AlignWidths should, for each column, increase
+     * Initialized to zero. Each class using this AlignColumns should, for each column, increase
      * this value to the maximum of the minimal widths (in other words, set
      * minWidth[i] = max(minWidth[i], cell.minWidth) for each cell in column i). */
     wdim[]  minWidth;           // minimal widths (set by genCachedConstructionData)
     
     /** For each column i, sizable[i] is true if that column is resizable.
-     * firstSizable and lastSizable are the indicies of the first/last resizable column (negative
-     * if none are resizable).
      * 
      * Set along with minWidth before calling setWidths. */
     bool[]  sizable;            // set by genCachedConstructionData
-    /// ditto
-    myDiff  firstSizable, lastSizable;  // set by genCachedConstructionData
     
-    /** Current width for each column.
+    /** Current width, relative position (for each column)
      *
      * Treat as READ ONLY! */
     wdim[]  width;              // only adjusted within the class
-protected:
-    myDiff  resizeD,            // resize down from this index (<0 if not resizing)
+    wdim[]  pos;                /// ditto
+    protected:
+    myDiff  resizeD,            // resizeCols works down from this index (<0 if not resizing)
             resizeU;            // and up from this index
+    wdim    spacing;            // used by genPositions (which cannot access the layout class's data)
+    wdim    spare;              // fixed size only: extra blank space filler
+    wdim    w,mw;               // current & minimal widths
+    /* indicies of the first/last resizable column (negative if none are resizable). */
+    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
+    
+    static HashMap!(widgetID,AlignColumns) instances;
+    static this () {
+        instances = new HashMap!(widgetID,AlignColumns);
+    }
 }
 
 // Index types. Note that in some cases they need to hold negative values.
--- a/mde/gui/widget/miscWidgets.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/gui/widget/miscWidgets.d	Thu Oct 16 17:43:48 2008 +0100
@@ -26,9 +26,9 @@
 /// A fixed-size blank widget.
 class FixedBlankWidget : FixedWidget
 {
-    this (IWidgetManager mgr, WidgetData data) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
         WDCheck (data, 3);
-        super (mgr, data);
+        super (mgr, id, data);
     }
     
     void draw () {
@@ -41,9 +41,9 @@
 /// A completely resizable blank widget (initial size zero).
 class SizableBlankWidget : SizableWidget
 {
-    this (IWidgetManager mgr, WidgetData data) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
         WDCheck (data, 1);
-        super (mgr, data);
+        super (mgr, id, data);
     }
     
     void draw () {
@@ -56,8 +56,8 @@
 /// A debug widget. Essentially as SizableBlankWidget but doesn't mind any amount of data and prints it.
 class DebugWidget : SizableWidget
 {
-    this (IWidgetManager mgr, WidgetData data) {
-        super (mgr, data);
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+        super (mgr, id, data);
         
         Stdout ("Debug widget - parameters: int: [");
         foreach (x; data.ints)
@@ -82,9 +82,9 @@
     // pushed is not the same as the button being clicked but not yet released.
     // it is whether the mouse is over the button after being clicked.
     
-    this (IWidgetManager mgr, WidgetData data) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
         WDCheck (data, 3);
-        super (mgr, data);
+        super (mgr, id, data);
     }
     
     void draw () {
--- a/mde/setup/Init.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/setup/Init.d	Thu Oct 16 17:43:48 2008 +0100
@@ -51,7 +51,7 @@
 import tango.core.Thread;
 import tango.core.sync.Condition;
 import tango.core.Exception;
-import tango.util.container.CircularList;
+import tango.util.container.LinkedList;
 
 //import tango.stdc.stringz : fromStringz;
 import tango.io.Console;	// for printing command-line usage
@@ -233,7 +233,7 @@
     
     // run init stages or cleanup if startup is false
     private static void runStages(bool startup) () {
-        CircularList!(InitStage*) toRun = new CircularList!(InitStage*);
+        auto toRun = new LinkedList!(InitStage*);
         foreach (v; stages) {
             // Filter only stages with the relevant delegate. It is not checked later that the
             // delegate exists!
@@ -255,7 +255,6 @@
                 }
             }
         }
-        auto toRunIt = toRun.iterator;
         // Counts number of active threads, and before threads are started is number to use:
         size_t numWorking = (toRun.size < miscOpts.maxThreads) ? toRun.size : miscOpts.maxThreads;
         enum STATE {    WORKING = 0,    DONE = 1,       ABORT = 2 }
@@ -292,13 +291,10 @@
                     
                     getStage:
                     while (true) {
-                        static if (false)       // An addition to CircularList to allow continuous circular iteration; not yet in tango.
-                            toRunIt.start;                  // start circling from here
-                        else
-                            toRunIt = toRun.iterator;       // new iterator
+                        auto toRunIt = toRun.iterator;      // iterates toRun
                         itStages:
-                        while (toRunIt.next (stage)) {   // get next element of toRun
-                            debug logger.trace ("Iterating: {}", stage);
+                        while (toRunIt.next (stage)) {      // get next element of toRun
+                            debug assert (stage !is null, "stage is null");
                             static if (startup) {
                                 foreach (d; stage.depends)
                                     if (stages[d].state != StageState.ACTIVE)
@@ -332,19 +328,21 @@
                     // Do a job:
                     try {
                         // FIXME - old stage start&finish trace messages - we don't have a name!
-                        debug logger.trace ("InitStage {}: starting", stage);
-                        static if (startup)
-                        stage.state = (*stage).init();  // init is a property of a pointer (oh no!)
-                        else
+                        static if (startup) {
+                            debug logger.trace ("InitStage {}: starting init", stage.name);
+                            stage.state = (*stage).init();  // init is a property of a pointer (oh no!)
+                        } else {
+                            debug logger.trace ("InitStage {}: starting cleanup", stage.name);
                             stage.state = stage.cleanup();
-                        debug logger.trace ("InitStage {}: completed; state: {}", stage, stage.state);
+                        }
+                        debug logger.trace ("InitStage {}: completed; state: {}", stage.name, stage.state);
                     } catch (InitStageException e) {
-                        debug logger.trace ("InitStage {}: failed", stage);
+                        debug logger.trace ("InitStage {}: failed: "~e.msg, stage.name);
                         stage.state = e.state;
                         doneInit = STATE.ABORT;
                         break threadLoop;
                     } catch (Exception e) {
-                        debug logger.trace ("InitStage {}: failed", stage);
+                        debug logger.trace ("InitStage {}: failed: "~e.msg, stage.name);
                         doneInit = STATE.ABORT;
                         break threadLoop;
                     }
@@ -377,7 +375,7 @@
         
         if (toRun.size)
             foreach (stage; toRun)
-                logger.warn ("InitStage {}: was not run due to unmet dependencies", stage);
+                logger.warn ("InitStage {}: was not run due to unmet dependencies", stage.name);
     }
     
     private static {
@@ -445,10 +443,11 @@
         foreach (key,stage_p; stages)
             foreach (name; stage_p.depends)
                 stages[name].rdepends ~= key;
-        auto realNumThreads = miscOpts.numThreads;
-        miscOpts.set!(int)("numThreads", 4);    // force 4 threads for unittest
+        auto realMaxThreads = miscOpts.maxThreads;
+        miscOpts.set!(int)("maxThreads", 4);    // force up to 4 threads for unittest
         
-        
+        logger.level(Logger.Info);              // hide a lot of trace messages
+        logger.info ("You should see some warning messages starting \"InitStage\":");
         // Run the above.
         runStages!(true);
         assert (init1);
@@ -485,7 +484,7 @@
         assert (stages[toStageName("stg3")].state == cast(StageState)7);        // set by the exception
         
         stages = realInit;      // restore the real init stages
-        miscOpts.set!(int)("numThreads", realNumThreads);
+        miscOpts.set!(int)("maxThreads", realMaxThreads);
         logger.info ("Unittest complete.");
     }
 }
--- a/mde/setup/InitStage.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/setup/InitStage.d	Thu Oct 16 17:43:48 2008 +0100
@@ -40,6 +40,7 @@
      * Not setting a cleanup function will result in the state being left at ACTIVE on shutdown, so
      * if a second initialization occurs (not currently possible), init will not be re-run. */
     struct InitStage {
+        char[] name;                    // used for debug-mode trace messages about stages
         StageState delegate() init;     // the initialization function
         StageState delegate() cleanup;  // the associated cleanup function
         StageName[] depends;            // anything function depends on completing before its run
@@ -49,6 +50,7 @@
     
     /// Add a stage to be initialized.
     void addInitStage (char[4] name, InitStage* stage) {
+        stage.name = name;
         stages[toStageName(name)] = stage;
     }
     /// Add a stage to be initialized.
@@ -61,6 +63,7 @@
     /// Add a stage to be initialized.
     void addInitStage (char[4] name, StageState delegate() init, StageState delegate() cleanup = null, char[4][] depends = null) {
         InitStage* stage = new InitStage;
+        stage.name = name;
         (*stage).init = init;
         stage.cleanup = cleanup;
         stage.depends.length = depends.length;
--- a/mde/setup/Screen.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/setup/Screen.d	Thu Oct 16 17:43:48 2008 +0100
@@ -180,8 +180,13 @@
     void draw (TimeSpan) {
         glClear(GL_COLOR_BUFFER_BIT);
         
-        foreach (IDrawable d; drawables)
-            d.draw;
+        foreach (d; drawables) {
+            try {
+                d.draw;
+            } catch (Exception e) {
+                logger.error ("Drawable failed to draw: "~e.msg);
+            }
+        }
         
         debug (drawGlyphCache) FontStyle.drawTexture;
         
@@ -199,8 +204,13 @@
     /** Set a new window size. Returns true on failure due to the different ways this must be
      * handled. */
     private bool setWindow (int w, int h) {
-        foreach (d; drawables)  // Tell all drawables the new window size.
-            d.sizeEvent (w,h);
+        foreach (d; drawables) {        // Tell all drawables the new window size.
+            try {
+                d.sizeEvent (w,h);
+            } catch (Exception e) {
+                logger.error ("Drawable failed while setting size: "~e.msg);
+            }
+        }
         
         //debug logger.trace ("Setting video mode {}x{}, 32-bit, flags: {}", w,h,flags);
         if (SDL_SetVideoMode (w, h, 32, flags) is null) {
--- a/mde/setup/paths.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/setup/paths.d	Thu Oct 16 17:43:48 2008 +0100
@@ -312,12 +312,18 @@
         } body {
             // Don't let sub-readers create their own, separate, datasets:
             if (ds is null) ds = new DataSet;
-        
+            
+            Exception exc;
             foreach (file; files) {
-                IReader r = makeReader (file, ds, rdHeader);
-            
-                readers[readersLen++] = r;
+                try {   // try reading header of each file
+                    IReader r = makeReader (file, ds, rdHeader);
+                    readers[readersLen++] = r;
+                } catch (Exception e) {
+                    exc = e;
+                }
             }
+            if (readersLen == 0)        // no files have valid headers
+                throw exc;              // fail: re-throw last exception
         }
     
         DataSet dataset () {                /// Get the DataSet
--- a/mde/types/Colour.d	Wed Oct 01 23:37:51 2008 +0100
+++ b/mde/types/Colour.d	Thu Oct 16 17:43:48 2008 +0100
@@ -49,9 +49,9 @@
         /// Construct from 0xRRGGBB
         Colour opCall (int col) {
             Colour c;
-            c.r = cast(float) (col >> 16u) / 255f;
-            c.g = cast(float) (col >> 8u)  / 255f;
-            c.b = cast(float) col          / 255f;
+            c.r = cast(float) ((col >> 16u) & 0xFF) / 255f;
+            c.g = cast(float) ((col >> 8u)  & 0xFF) / 255f;
+            c.b = cast(float) (col          & 0xFF) / 255f;
             return c;
         }
     }