changeset 103:42e241e7be3e

ContentList content type; getting content items/lists from Options generically via content.Items, and a new addContent widget function. Several improvements to generic handling of content. New button-with-text widget. Some tidy-up. Some name changes, to increase uniformity. Bug-fix: floating widgets of fixed size could previously be made larger than intended from config dimdata.
author Diggory Hardy <diggory.hardy@gmail.com>
date Tue, 25 Nov 2008 18:01:44 +0000
parents ba035eba07b4
children ee209602770d
files codeDoc/jobs.txt data/conf/gui.mtt data/conf/options.mtt mde/gui/WidgetManager.d mde/gui/content/Content.d mde/gui/content/Items.d mde/gui/content/options.d mde/gui/exception.d mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/types.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/miscContent.d mde/gui/widget/textContent.d mde/lookup/Options.d mde/lookup/Translation.d mde/mde.d mde/setup/Init.d mde/setup/paths.d
diffstat 24 files changed, 339 insertions(+), 259 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Sat Nov 22 20:59:36 2008 +0000
+++ b/codeDoc/jobs.txt	Tue Nov 25 18:01:44 2008 +0000
@@ -6,14 +6,9 @@
 
 
 
-Bugs:
-Sometimes nothing is drawn until a resize, and fonts are blocks. Cause: init-stages always appear to get divided between two threads. If Inpt, Font and SWnd are called by the main thread and not a sub-thread, the bug doesn't occur. A temporary fix is just to set maxThreads=1. A redesign of threading init stages could solve this, but doesn't seem worth the effort right now.
-Drawing of floating widgets is not restricted to the floating area; see SimpleRenderer.restrict().
-
-
-
 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   Widget saving: how to deal with modifier functions, esp. when they discard parameters? Remove feature except for dimdata and handle gui editing separately?
 3   Use of dtors - don't rely on them? Or what happens when init throws during creation - relying on undefined behaviour.
 3   glBindTexture not working with non-0 index - perhaps use a higher level graphics library at some point.
 3   Windows building/compatibility (currently partial) - tango/sys/win32/SpecialPath.d
@@ -53,4 +48,4 @@
 1   Mergetag binary support / other file formats.
 
 
-Done (for mercurial log message):
+Done (since last commit):
--- a/data/conf/gui.mtt	Sat Nov 22 20:59:36 2008 +0000
+++ b/data/conf/gui.mtt	Tue Nov 25 18:01:44 2008 +0000
@@ -2,18 +2,19 @@
 <char[]|Renderer="Simple">
 <char[]|Design="Working">
 {Working}
-<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,0,3,1],1:["floating","opts","blank"]}>
-<WidgetData|button={0:[0x10,50,50]}>
+<WidgetData|root={0:[0xC100,0,3,3],1:["square","blank","square","blank","floating","blank","square","blank","square"]}>
+<WidgetData|square={0:[0x10,10,10]}>
 <WidgetData|blank={0:[0x2]}>
-<WidgetData|opts={0:[0x8110,0],1:["optDBox","MiscOptions"]}>
+<WidgetData|opts={0:[0x2031,0x6030,0],1:["Options.VideoOptions","optDBox"]}>
 <WidgetData|optDBox={0:[0xC100,1,2,1],1:["optBox","optDesc"]}>
 <WidgetData|optBox={0:[0xC100,1,1,3],1:["optName","optSep","optVal"]}>
 <WidgetData|optName={0:[0x4020, 1, 0xfe8c00]}>
 <WidgetData|optDesc={0:[0x4020, 2, 0xaf6000]}>
 <WidgetData|optVal={0:[0x6030]}>
 <WidgetData|optSep={0:[0x21, 0xff],1:["="]}>
-<WidgetData|floating={0:[0x8200,6,14],1:["button","blank"]}>
+<WidgetData|floating={0:[0x8200,6,14,6],1:["L10n","blank","opts"]}>
+<WidgetData|L10n={0:[0x2031,0xC100,0,2,1],1:["Options.MiscOptions.L10n","optBox2","apply"]}>
+<WidgetData|optBox2={0:[0xC100,0,1,3],1:["optName","optSep","optVal"]}>!{cloned to avoid bug #4}
+<WidgetData|apply={0:[0x11],1:["Apply (unimplemented)"]}>
 {Basic}
 <WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}>
--- a/data/conf/options.mtt	Sat Nov 22 20:59:36 2008 +0000
+++ b/data/conf/options.mtt	Tue Nov 25 18:01:44 2008 +0000
@@ -21,6 +21,5 @@
 <bool|fullscreen=false>
 <int|screenW=1280>
 <int|screenH=1024>
-<int|windowW=800>
-<int|windowH=600>
-
+<int|windowW=1024>
+<int|windowH=768>
--- a/mde/gui/WidgetManager.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/WidgetManager.d	Tue Nov 25 18:01:44 2008 +0000
@@ -399,19 +399,14 @@
         }
     }
     
-    
-    /** Create a widget by ID.
-     *
-     * A widget instance is created from data found under ID. Multiple instances may be created.
-     * NOTE - data conflicts when saving?
-     *
-     * An IContent may be passed. This could contain many things, e.g. some basic data, a widget,
-     * multiple sub-IContents. It is only passed to the widget by createWidget if it's enumeration
-     * 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, id, curData[id], content);
     }
+    IChildWidget makeWidget (widgetID id, WidgetData data, IContent content = null) {
+	debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"');
+	return createWidget (this, id, data, content);
+    }
     
     /** 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
@@ -444,17 +439,12 @@
         }
     }
     
-    /** Get dimension data for a widget. */
     wdims dimData (widgetID id) {
         return curData.dims (id);
     }
-    
-    /** For making changes to widget structure. */
     void setData (widgetID id, WidgetData d) {
         changes[id] = d;        // also updates WidgetDataSet in data.
     }
-    
-    /** For making changes to widget dimensions. */
     void setDimData (widgetID id, wdims d) {
         changes.setDims(id, d);    // also updates WidgetDataSet in data.
     }
--- a/mde/gui/content/Content.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/content/Content.d	Tue Nov 25 18:01:44 2008 +0000
@@ -45,25 +45,8 @@
  * 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
 {
-    /+ NOTE: None of this is really used yet, but was (mostly) intended for clipboard copying.
-    /** Return a copy of self. */
-    IContent dup ();
-    
-    /** Attempt to convert the content to a specific type (may simply return this if appropriate).
-     *
-     * Annoyingly we can't use cast because overloading by return type isn't supported. */
-    // FIXME: throw or return null on error or unsupported conversion?
-    ContentText	toText ();
-    ContentInt	toInt ();	/// ditto
-    +/
-    
-    
-    
     /** Generically return strings.
      *
      * This serves two purposes: generically returning a string of/related to the content (i == 0),
@@ -79,10 +62,68 @@
     char[] toString (uint i);
 }
 
+/** A generic way to handle a list of type IContent. */
+class ContentList : IContent
+{
+    this (IContent[] list = null) {
+	list_ = list;
+    }
+    this (ValueContent[char[]] l) {
+	list_.length = l.length;
+	size_t i;
+	foreach (c; l)
+	    list_[i++] = c;
+    }
+    
+    char[] toString (uint i) {
+	return i == 0 ? Int.toString (list_.length) ~ " elements"
+	: i == 1 ? "ContentList"
+	: null;
+    }
+    
+    IContent[] list () {
+	return list_;
+    }
+    ContentList list (IContent[] list) {
+	list_ = list;
+	return this;
+    }
+    
+protected:
+    IContent[] list_;
+}
+
+/** Created on errors to display and log a message. */
+class ErrorContent : IContent
+{
+    this (char[] msg) {
+	msg_ = msg;
+    }
+    
+    char[] toString (uint i) {
+	return i == 0 ? msg_
+	     : i == 1 ? "Error"
+	     : null;
+    }
+    
+protected:
+    char[] msg_;
+}
+
 /** Base class for content containing a simple value.
  *
- * All derived classes should support functions to set/get any ValueContent type, but return the 
- * default value of any type other than it's own. */
+ * All derived classes should have the following functions:
+ * ---
+ *  this (char[] symbol, T val = /+ default value +/);
+ *  BoolContent addChangeCb (void delegate (char[] symbol,T value) cb);	// add a callback called on any change
+ *  void assignNoCB (T val);	// assign val, but without calling callbacks (for Options)
+ *  void opAssign (T val);	// assign val, calling callbacks
+ *  T opCall ();		// return value
+ *  alias opCall opCast;
+ *  void endEdit ();		// Converts sv and assigns to self, calling callbacks
+ * ---
+ * On any assignation (by this, assignNoCB, opAssign) the value should be converted to a string and
+ * assigned to sv, and pos should be clamped to [0,sv.length] (i.e. enforce pos <= sv.length). */
 abstract class ValueContent : IContent
 {
     protected this () {}
@@ -95,9 +136,9 @@
     /// Get the text.
     char[] toString (uint i) {
         return (i == 0) ? sv
-            : (i == 1) ? name_
-            : (i == 2) ? desc_
-            : null;
+             : (i == 1) ? name_
+             : (i == 2) ? desc_
+             : null;
     }
     
     /** Acts on a keystroke and returns the new value.
@@ -146,7 +187,7 @@
 	return sv;
     }
     
-    size_t getEditIndex () {
+    size_t editIndex () {
 	size_t i = 0;
 	for (size_t p = 0; p < pos; ++p)
 	    if (!(sv[p] & 0x80) || sv[p] & 0x40)
@@ -193,6 +234,7 @@
     void assignNoCB (bool val) {
 	v = val;
 	sv = v ? "true" : "false";
+	if (pos > sv.length) pos = sv.length;
     }
     void opAssign (bool val) {
 	assignNoCB (val);
@@ -204,7 +246,7 @@
     }
     alias opCall opCast;
     
-    void endEdit () {
+    override void endEdit () {
 	v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1');
 	foreach (cb; cngCb)
 	    cb(symb, v);
@@ -220,7 +262,7 @@
 {
     this (char[] symbol, char[] val = null) {
         symb = symbol;
-        v = val;
+	v = val;
     }
     
     /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
@@ -231,9 +273,10 @@
     
     void assignNoCB (char[] val) {
 	v = val;
+	if (pos > sv.length) pos = sv.length;
     }
     void opAssign (char[] val) {
-        v = val;
+	assignNoCB (val);
         foreach (cb; cngCb)
             cb(symb, val);
     }
@@ -242,7 +285,7 @@
     }
     alias opCall opCast;
     
-    void endEdit () {
+    override void endEdit () {
 	foreach (cb; cngCb)
 	    cb(symb, v);
     }
@@ -270,6 +313,7 @@
     void assignNoCB (int val) {
 	v = val;
 	sv = Int.toString (v);
+	if (pos > sv.length) pos = sv.length;
     }
     void opAssign (int val) {
 	assignNoCB (val);
@@ -281,7 +325,7 @@
     }
     alias opCall opCast;
     
-    void endEdit () {
+    override void endEdit () {
 	v = Int.toInt (sv);
 	foreach (cb; cngCb)
 	    cb(symb, v);
@@ -310,6 +354,7 @@
     void assignNoCB (double val) {
 	v = val;
 	sv = Float.toString (v);
+	if (pos > sv.length) pos = sv.length;
     }
     void opAssign (double val) {
 	assignNoCB (val);
@@ -321,7 +366,7 @@
     }
     alias opCall opCast;
     
-    void endEdit () {
+    override void endEdit () {
 	v = Float.toFloat (sv);
 	foreach (cb; cngCb)
 	    cb(symb, v);
--- a/mde/gui/content/Items.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/content/Items.d	Tue Nov 25 18:01:44 2008 +0000
@@ -13,9 +13,63 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/** List of content items, somewhat like options.
- *
- * Both named and non-named items like options, but each item is a content.
- * Separation by types of content or not?
- */
-module mde.gui.content.Items;
+/**************************************************************************************************
+ * A generic way to access content items.
+ *************************************************************************************************/
+module mde.gui.content.ContentItems;
+
+import mde.gui.content.Content;
+import mde.gui.exception;
+
+import mde.lookup.Options;
+import mde.lookup.Translation;
+
+    /** Get a specific content item.
+     *
+     * E.g. get ("Options.MiscOptions.L10n") returns miscOpts.L10n,
+     * Items.get ("Options.MiscOptions") returns a ContentList of all misc options. */
+    IContent get (char[] item) {
+	char[] h = head (item);
+	if (h == "Options")
+	    return getOptions (item);
+	throw new ContentItemException (h);
+    }
+    
+    /** Same as calling get("Options."~item). */
+    IContent getOptions (char[] item) {
+	char[] h = head (item);
+	auto p = h in Options.optionsClasses;
+	if (p) {
+	    if (!p.transLoaded) {
+		Translation trans = Translation.load ("L10n/"~h);
+		foreach (s, v; p.content) {
+		    Translation.Entry transled = trans.getStruct (s);
+		    v.name (transled.name, transled.desc);
+		}
+		p.transLoaded = true;
+	    }
+	    
+	    if (item == null)
+		return new ContentList (p.content);
+	    
+	    auto q = (h = head (item)) in p.content;
+	    if (q && item is null)	// enforce item is an exact match
+		return *q;
+	}
+	throw new ContentItemException (h);
+    }
+    
+private:
+    /** Takes the string "head.tail" where tail may contain '.' but head does not, returns "head",
+     * with str set to "tail". */
+    char[] head (ref char[] str) {
+	size_t i = 0;
+	while (i < str.length && str[i] != '.')
+	    ++i;
+	char[] ret = str[0..i];
+	if (i == str.length)
+	    str = null;
+	else
+	    str = str[i+1..$];
+	return ret;
+    }
--- a/mde/gui/content/options.d	Sat Nov 22 20:59:36 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/* LICENSE BLOCK
-Part of mde: a Modular D game-oriented Engine
-Copyright © 2007-2008 Diggory Hardy
-
-This program is free software: you can redistribute it and/or modify it under the terms
-of the GNU General Public License as published by the Free Software Foundation, either
-version 2 of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-/** Content classes for holding options.
- *
- * Currently they use a rather poor implementation: each is responsible for grabbing its value and
- * updating the option, and cannot be notified if that option is changed externally.
- */
-module mde.gui.content.options;
-
-import mde.gui.content.Content;
-
-import mde.lookup.Options;
-import mde.lookup.Translation;
-
-import tango.util.log.Log : Log, Logger;
-private Logger logger;
-static this () {
-    logger = Log.getLogger ("mde.gui.content.options");
-}
-
-class OptionList
-{
-    this (char[] optsName)
-    {
-        auto p = optsName in Options.subClasses;
-        if (p is null) {
-            logger.error ("OptionList created with invalid options class name.");
-            return;     // list is empty, nothing displayed
-        }
-        Options opts = *p;
-        
-        Translation trans = Translation.load ("L10n/"~optsName);
-        char[][] list = opts.list;
-        
-        textOpts.length = list.length + opts.content.length;
-        size_t i;
-        foreach (s; list) {
-            Translation.Entry transled = trans.getStruct (s);
-            textOpts[i] = new OptionContent(opts, s, transled.name, transled.desc);
-            ++i;
-        }
-        foreach (s, v; opts.content) {
-            Translation.Entry transled = trans.getStruct (s);
-            v.name (transled.name, transled.desc);      // set Content name & desc. Only needs doing once
-            textOpts[i++] = v;
-        }
-        
-    }
-    
-    IContent[] list () {
-        return textOpts;
-    }
-    
-protected:
-    IContent[] textOpts;
-}
-
-//FIXME - todo.txt
-class OptionContent : IContent
-{
-    this (Options o, char[] s, char[] name, char[] desc) {
-        opts = o;
-        symb = s;
-        name_ = name;
-        desc_ = desc;
-    }
-    
-    char[] toString (uint i) {
-        if (i == 0)
-            return "dummy"; //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);
-    }+/
-protected:
-    Options opts;	// the set of options within which our option lies
-    char[]  symb;	// the symbol name of our option
-    char[] name_, desc_;// name and description, loaded by lookup.Translation
-}
--- a/mde/gui/exception.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/exception.d	Tue Nov 25 18:01:44 2008 +0000
@@ -32,8 +32,11 @@
 /// Thrown when createWidget or a Widget class's this() is called with invalid data.
 class WidgetDataException : GuiException
 {
-    this (Object o) {   // Default, by Widget class's this / WDCheck
-        super ("Bad widget data for "~o.classinfo.name);
+    this () {		// Other CTOR should be used by classes
+        super ("Bad widget data");
+    }
+    this (Object o) {	// Default, by Widget class's this / WDCheck
+	super ("Bad widget data for "~o.classinfo.name);
     }
 }
 
@@ -49,3 +52,10 @@
         super (msg);
     }
 }
+
+/// Thrown when getting a content item fails
+class ContentItemException : GuiException {
+    this (char[] msg) {
+	super ("Bad content item specifier: "~msg);
+    }
+}
--- a/mde/gui/renderer/IRenderer.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/renderer/IRenderer.d	Tue Nov 25 18:01:44 2008 +0000
@@ -87,17 +87,21 @@
      * NOTE: currently inflexible. Could use (function) pointers, class interfaces or struct
      * interfaces when available to allow flexibility. */
     struct TextAdapter {
-        void setText (char[] c) {
-            content = c;
+	char[] text () {
+	    return content;
+	}
+        void text (char[] str) {
+            content = str;
 	    textCache.cacheVer = -1;	// force update
         }
 	
-	void setColour (int col = 0xFFFFFF) {
-	    colour = Colour (col);
+	void colour (int colour = 0xFFFFFF) {
+	    colour_ = Colour (colour);
 	}
 	
-	void setIndex (size_t index = size_t.max) {
-	    this.index = index;
+	/** If not size_t.max, an edit marker is drawn before character index. */
+	void index (size_t index = size_t.max) {
+	    index_ = index;
 	}
         
         void getDimensions (out wdsize w, out wdsize h) {
@@ -107,13 +111,13 @@
         }
         
         void draw (wdabs x, wdabs y) {
-            font.textBlock (x,y, content, textCache, colour, index);
+            font.textBlock (x,y, content, textCache, colour_, index_);
         }
         
         char[] content;
         TextBlock textCache;
-	size_t index;
-        Colour colour;
+	size_t index_;
+        Colour colour_;
         FontStyle font;
     }
     //END Types
--- a/mde/gui/renderer/SimpleRenderer.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/renderer/SimpleRenderer.d	Tue Nov 25 18:01:44 2008 +0000
@@ -59,6 +59,8 @@
     
     
     //FIXME - make these do something
+    // They should restrict the drawing of floating widgets to the floating area, for instance,
+    // although this isn't strictly necessary.
     void restrict (wdim x, wdim y, wdim w, wdim h) {}
     void relax () {}
     
@@ -67,7 +69,7 @@
         glRecti(x, y+h, x+w, y);
         
         if (border.capability != 0) {
-            glColor3f (0f, 0f, .7f);
+            glColor3f (0f, 0f, .6f);
             glBegin (GL_TRIANGLES);
             wdim t = border.x1 + border.y1;
             glVertex2i (x, y);
@@ -134,8 +136,8 @@
         TextAdapter a;
         a.font = defaultFont;
         a.content = text;
-	a.colour = Colour (col);
-	a.index = size_t.max;
+	a.colour_ = Colour (col);
+	a.index_ = size_t.max;
 	return a;
     }
     
--- a/mde/gui/types.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/types.d	Tue Nov 25 18:01:44 2008 +0000
@@ -19,9 +19,7 @@
 module mde.gui.types;
 
 
-/** Widget ID type. Each ID is unique under this window.
-*
-* Type is int since this is the widget data type. */
+/** Widget ID type. Each ID is unique under this window. */
 alias char[] widgetID;
 
 /** Window coordinate and dimension/size type (int).
--- a/mde/gui/widget/Floating.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/Floating.d	Tue Nov 25 18:01:44 2008 +0000
@@ -66,8 +66,8 @@
             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) w = mw;
-            if (h < mh) h = mh;
+	    if (w < mw || !widg.isWSizable) w = mw;
+	    if (h < mh || !widg.isHSizable) h = mh;
             widg.setWidth  (w - border.x1 - border.x2, -1);
             widg.setHeight (h - border.y1 - border.y2, -1);
         }
--- a/mde/gui/widget/Ifaces.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/Ifaces.d	Tue Nov 25 18:01:44 2008 +0000
@@ -54,14 +54,18 @@
      *
      * Params:
      *  id      = Identifier, within data files, of the data for the widget.
+     *  data    = Pass this data to the widget, not data looked up via id.
      *  content = An IContent may be passed to some widgets on creation.
      *
      * Creates a widget, using the widget data with index id. Widget data is loaded from files,
      * and per design (multiple gui layouts, called designs, may exist; data is per design).
-     */
+     * 
+     * The function taking a WidgetData is intended for modifier functions and only exists to
+     * avoid circular dependencies between the modifier function's module and createWidget. */
     IChildWidget makeWidget (widgetID id, IContent content = null);
+    IChildWidget makeWidget (widgetID id, WidgetData data, IContent content = null);
     
-    /// Get dimensional data.
+    /** Get dimension data for a widget. */
     wdims dimData (widgetID id);
     
     /** Record some changes, for saving. Should only be called from IWidget.saveChanges() to avoid
--- a/mde/gui/widget/TextWidget.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/TextWidget.d	Tue Nov 25 18:01:44 2008 +0000
@@ -24,12 +24,10 @@
 import mde.gui.renderer.IRenderer;
 import mde.gui.content.Content;
 
-debug {
-    import tango.util.log.Log : Log, Logger;
-    private Logger logger;
-    static this () {
-        logger = Log.getLogger ("mde.gui.widget.TextWidget");
-    }
+import tango.util.log.Log : Log, Logger;
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.widget.TextWidget");
 }
 
 /** Base text widget.
@@ -88,3 +86,29 @@
     IContent content;
     int index;
 }
+
+class TextButtonWidget : AButtonWidget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+	WDCheck (data, 1, 1);
+	adapter = mgr.renderer.getAdapter (data.strings[0]);
+	adapter.getDimensions (mw, mh);
+	w = mw;
+	h = mh;
+	super (mgr, id, data);
+    }
+    
+    void draw () {
+	super.draw();
+	adapter.draw (x,y);
+    }
+    
+    void activated () {
+	logger.info ("Button \""~adapter.text~"\" pressed");
+    }
+    
+protected:
+    IRenderer.TextAdapter adapter;
+    IContent content;
+    int index;
+}
\ No newline at end of file
--- a/mde/gui/widget/Widget.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/Widget.d	Tue Nov 25 18:01:44 2008 +0000
@@ -140,7 +140,8 @@
 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.
+     * that the checks can easily be updated should WidgetData be changed. WDMinCheck is similar,
+     * but allows more data than required; it is used by some generic content widgets.
      * 
      * Params:
      *  data    = the WidgetData to check lengths of
@@ -148,9 +149,15 @@
      *  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);
+	if (data.ints.length    != n_ints ||
+	    data.strings.length != n_strings)
+	    throw new WidgetDataException (this);
+    }
+    /** ditto */
+    void WDMinCheck (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);
     }
     
     widgetID id;                // The widget's ID, used for saving data
--- a/mde/gui/widget/createWidget.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/createWidget.d	Tue Nov 25 18:01:44 2008 +0000
@@ -97,19 +97,23 @@
     
     // buttons: 0x10
     Button		= 0x10,
+    TextButton		= 0x11,
     
     // labels: 0x20
     ContentLabel	= TAKES_CONTENT | 0x20,
     TextLabel		= 0x21,
     
-    // content editables: 0x30
+    // content functions: 0x30
     editContent		= FUNCTION | TAKES_CONTENT | 0x30,
-    DisplayContent	= TAKES_CONTENT | 0x30,
-    BoolContent		= TAKES_CONTENT | 0x31,
-    ValueContent		= TAKES_CONTENT | 0x32,
+    addContent		= FUNCTION | 0x31,
+    
+    // content widgets: 0x40
+    DisplayContent	= TAKES_CONTENT | 0x40,
+    BoolContent		= TAKES_CONTENT | 0x41,
+    ValueContent	= TAKES_CONTENT | 0x42,
     
     GridLayout		= TAKES_CONTENT | PARENT | 0x100,
-    TrialContentLayout	= PARENT | 0x110,
+    ContentList		= TAKES_CONTENT | PARENT | 0x110,
     
     FloatingArea	= PARENT | 0x200,
 }
@@ -122,15 +126,17 @@
         "SizableBlank",
         "Debug",
         "Button",
-        "TextLabel",
-        "ContentLabel",
+	"TextButton",
+	"TextLabel",
+	"addContent",
+	"ContentLabel",
         "DisplayContent",
         "BoolContent",
 	"ValueContent",
         "editContent",
-        "TrialContentLayout",
         "FloatingArea",
-        "GridLayout"];
+        "GridLayout",
+	"ContentList"];
 
 /* Generates a binary search algorithm. */
 char[] binarySearch (char[] var, char[][] consts) {
--- a/mde/gui/widget/layout.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/layout.d	Tue Nov 25 18:01:44 2008 +0000
@@ -19,8 +19,6 @@
 import mde.gui.widget.Widget;
 import mde.gui.exception;
 
-import mde.gui.widget.TextWidget;
-import mde.gui.content.options;
 import mde.gui.content.Content;
 
 import tango.util.container.HashMap;
@@ -93,26 +91,31 @@
 
 
 /*************************************************************************************************
- * Trial layout of sub-widgets of one type only.
+ * Iterates on an ContentList to produce a list of widgets, each of which is created with widgetID
+ * data.strings[0]. If an IContent is passed, this is cast to a ContentList, otherwise
+ * content.Items is used to get an IContent. It is an error if the content fails to cast to
+ * ContentList.
  *************************************************************************************************/
-class TrialContentLayoutWidget : GridWidget
+class ContentListWidget : GridWidget
 {
-    this (IWidgetManager mgr, widgetID id, WidgetData data) {
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
         debug scope (failure)
                 logger.warn ("TrialContentLayoutWidget: failure");
-        WDCheck (data, 2, 2);
-        
-        OptionList optsList = new OptionList(data.strings[1]);
+        WDCheck (data, 2, 1);
+	
+	cList = cast(ContentList) content;
+	if (cList is null)
+	    throw new ContentException;
+	
         cols = 1;
-        if ((rows = optsList.list.length) > 0) {
-            // Get all sub-widgets
+        if ((rows = cList.list.length) > 0) {
             subWidgets.length = rows;
-            foreach (i, c; optsList.list) {
+            foreach (i, c; cList.list) {
                 subWidgets[i] = mgr.makeWidget (data.strings[0], c);
             }
         } else {
             rows = 1;
-            subWidgets = [mgr.makeWidget (data.strings[0], new TextContent (data.strings[1], "Invalid Options section"))];
+            subWidgets = [mgr.makeWidget (data.strings[0], new ErrorContent ("<empty list>"))];
         }
         super (mgr, id, data);
     }
@@ -125,7 +128,7 @@
     }
     
 private:
-    OptionList optsList;
+    ContentList cList;
 }
 
 
--- a/mde/gui/widget/miscContent.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/miscContent.d	Tue Nov 25 18:01:44 2008 +0000
@@ -13,42 +13,53 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
-/** Some content widgets. */
+/*************************************************************************************************
+ * A function to return content widgets and some miscellaneous content widgets.
+ *************************************************************************************************/
 module mde.gui.widget.miscContent;
 
+import mde.gui.widget.Widget;
+import mde.gui.exception;
 import mde.gui.widget.textContent;
-import mde.gui.widget.Widget;
-import mde.gui.widget.TextWidget;
-import mde.gui.exception;
+import mde.gui.widget.layout;
+
 import mde.gui.renderer.IRenderer;
-
 import mde.gui.content.Content;
+import Items = mde.gui.content.Items;
 
-/// Chooses the most appropriate content editing widget
+/*************************************************************************************************
+ * A function which uses Items.get (data.strings[0]) to get a content and creates a widget from
+ * data.ints[1]. The first item in each ints and strings is removed before passing data to the new
+ * widget.
+ * 
+ * The function only takes an IContent parameter to satisfy createWidget; it's value is ignored.
+ *************************************************************************************************/
+IChildWidget addContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent) {
+    if (data.ints.length < 2 || data.strings.length < 1) throw new WidgetDataException;
+    char[] cItem = data.strings[0];
+    data.strings = data.strings[1..$];
+    data.ints    = data.ints   [1..$];
+    return mgr.makeWidget (id, data, Items.get (cItem));
+}
+
+/*************************************************************************************************
+ * A function which returns the most appropriate content editing widget.
+ *
+ * Widgets which can be returned: BoolContentWidget (toggle button), ValueContentWidget (generic
+ * text-box editor), DisplayContentWidget (generic text label).
+ *************************************************************************************************/
 IChildWidget editContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    if (c is null) throw new ContentException;
     if (cast(BoolContent) c)
         return new BoolContentWidget(mgr,id,data,c);
     else if (cast(ValueContent) c)
 	return new ValueContentWidget(mgr,id,data,c);
+    else if (cast(ContentList) c)
+	return new ContentListWidget(mgr,id,data,c);
     else        // generic uneditable option
         return new DisplayContentWidget(mgr,id,data,c);
 }
 
-/// Just displays the content
-class DisplayContentWidget : ATextWidget
-{
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
-        WDCheck(data, 1);
-        content = c;
-        if (!content) throw new ContentException ();
-        adapter = mgr.renderer.getAdapter (content.toString(0));
-        super (mgr, id, data);
-    }
-    
-protected:
-    IContent content;
-}
-
 /// Editable boolean widget
 class BoolContentWidget : AButtonWidget
 {
--- a/mde/gui/widget/textContent.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/gui/widget/textContent.d	Tue Nov 25 18:01:44 2008 +0000
@@ -31,11 +31,26 @@
     }
 }
 
+/// Just displays the content
+class DisplayContentWidget : ATextWidget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+	WDMinCheck(data, 1);
+	content = c;
+	if (!content) throw new ContentException ();
+	adapter = mgr.renderer.getAdapter (content.toString(0));
+	super (mgr, id, data);
+    }
+    
+    protected:
+	IContent content;
+}
+
 /// Capable of editing any ValueContent class
 class ValueContentWidget : ATextWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
-        WDCheck(data, 1);
+        WDMinCheck(data, 1);
         content = cast(ValueContent) c;
         if (!content) //content = new TextContent (null, null);
 	    throw new ContentException ();
@@ -45,19 +60,19 @@
     
     /** On click, request keyboard input. */
     int clickEvent (wdabs, wdabs, ubyte, bool state) {
-	adapter.setIndex = content.getEditIndex;
+	adapter.index = content.editIndex;
 	mgr.requestRedraw;
 	return 1;	// get keyboard input via keyEvent
     }
     
     void keyEvent (ushort s, char[] i) {
-	adapter.setText = content.keyStroke (s, i);
-	adapter.setIndex = content.getEditIndex;
+	adapter.text = content.keyStroke (s, i);
+	adapter.index = content.editIndex;
 	adapter.getDimensions (mw, mh);	// NOTE: only passively change size: next resize will see new minimal size
 	mgr.requestRedraw;
     }
     void keyFocusLost () {
-	adapter.setIndex;
+	adapter.index;
 	content.endEdit;	// update other users of content relying on callbacks
 	mgr.requestRedraw;
     }
--- a/mde/lookup/Options.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/lookup/Options.d	Tue Nov 25 18:01:44 2008 +0000
@@ -13,6 +13,7 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
+//FIXME: Ddoc is outdated
 /** This module handles stored options, currently all except input maps.
  *
  * The purpose of having all options centrally controlled is to allow generic handling by the GUI
@@ -44,6 +45,7 @@
     logger = Log.getLogger ("mde.lookup.Options");
 }
 
+//FIXME: Ddoc is outdated
 /** Base class for handling options.
 *
 * This class itself handles no options and should not be instantiated, but provides a sub-classable
@@ -165,11 +167,16 @@
             c.secName = i;
             subClasses[cast(ID) i] = c;
         }
-    
-    	// Track all sections for saving/loading/other generic handling.
-        Options[ID] subClasses;
-        bool changed = false;    // any changes at all, i.e. do we need to save?
-    
+	
+	/** Get the hash map of Options classes. */
+	Options[ID] optionsClasses () {
+	    return subClasses;
+	}
+	
+	// Track all sections for saving/loading/other generic handling.
+        private Options[ID] subClasses;
+        private bool changed = false;	// any changes at all, i.e. do we need to save?
+	
     	/* Load/save options from file.
          *
          * If the file doesn't exist, no reading is attempted (options are left at default values).
@@ -246,6 +253,7 @@
             } else
                 const char[] setMixin = ``;
         }+/
+    /+
     /** Set option symbol of an Options sub-class to val.
      *
      * Due to the way options are handled generically, string IDs must be used to access the options
@@ -264,7 +272,6 @@
             logger.error ("Options.set: invalid symbol");
         }
     }+/
-    /+
     /** Get option symbol of an Options sub-class.
      *
      * Using this method to read an option is not necessary, but allows for generic use.  */
@@ -293,9 +300,13 @@
         return opts;
     }
     
-    /// Variable validate function. This implementation does nothing.
+    /** Variable validate function, called when options are loaded from file. This implementation
+     * does nothing. */
     void validate() {}
     
+    /** Boolean, telling whether translation strings have been loaded for the instance. */
+    bool transLoaded;
+    
     protected {
         char[] secName;         // name of this option setting; set null after translation is loaded
         OptionChanges optionChanges;	// all changes to options (for saving)
--- a/mde/lookup/Translation.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/lookup/Translation.d	Tue Nov 25 18:01:44 2008 +0000
@@ -225,8 +225,8 @@
         
         // Hack a specific locale...
         // Also to allow unittest to run without init.
-        char[] currentL10n = miscOpts.L10n;
-        miscOpts.L10n = "test-1";
+        TextContent realL10n = miscOpts.L10n;
+        miscOpts.L10n = new TextContent ("L10n", "test-1");
         
         Translation transl = load ("unittest/Translation");
         
@@ -245,7 +245,8 @@
         // Only check extra entries are allowed but ignored.
         
         // Restore
-        miscOpts.L10n = currentL10n;
+	delete miscOpts.L10n;
+        miscOpts.L10n = realL10n;
         
         logger.info ("Unittest complete.");
     }
--- a/mde/mde.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/mde.d	Tue Nov 25 18:01:44 2008 +0000
@@ -1,4 +1,4 @@
-/* LICENSE BLOCK
+/* LICENSE BLOCK
 Part of mde: a Modular D game-oriented Engine
 Copyright © 2007-2008 Diggory Hardy
 
@@ -81,7 +81,7 @@
     mainSchedule.add (mainSchedule.getNewID, &mde.events.pollEvents).frame = true;
     //END Main loop setup
     
-	double pollInterval = miscOpts.pollInterval();
+    double pollInterval = miscOpts.pollInterval();
     while (run) {
         mainSchedule.execute (Clock.now());
         
--- a/mde/setup/Init.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/setup/Init.d	Tue Nov 25 18:01:44 2008 +0000
@@ -325,7 +325,6 @@
                     
                     // Do a job:
                     try {
-                        // FIXME - old stage start&finish trace messages - we don't have a name!
                         static if (startup) {
                             debug logger.trace ("({}) InitStage {}: starting init", threadNum, stage.name);
                             stage.state = (*stage).init();  // init is a property of a pointer (oh no!)
@@ -449,8 +448,8 @@
         foreach (key,stage_p; stages)
             foreach (name; stage_p.depends)
                 stages[name].rdepends ~= key;
-        int realMaxThreads = miscOpts.maxThreads();
-        miscOpts.maxThreads = 4;	// force up to 4 threads for unittest
+        IntContent realMaxThreads = miscOpts.maxThreads;
+        miscOpts.maxThreads = new IntContent ("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\":");
@@ -490,6 +489,7 @@
         assert (stages[toStageName("stg3")].state == cast(StageState)7);        // set by the exception
         
         stages = realInit;      // restore the real init stages
+	delete miscOpts.maxThreads;
         miscOpts.maxThreads = realMaxThreads;
         logger.info ("Unittest complete.");
     }
--- a/mde/setup/paths.d	Sat Nov 22 20:59:36 2008 +0000
+++ b/mde/setup/paths.d	Tue Nov 25 18:01:44 2008 +0000
@@ -220,7 +220,6 @@
 /** Find at least one path for each required directory.
 *
 * Note: the logger cannot be used yet, so only output is exception messages. */
-// FIXME: use tango/sys/Environment.d
 version (linux) {
     SortedMap!(char[],char[]) fontFiles;	// key is file name, value is CString path
     /** Get the actual path of a font file, or throw NoFileException if not found.