changeset 115:1b1e2297e2fc

Enums handled more generically now via either a popup list or flat list of BoolContentWidgets. EnumContent is an IContentList with BoolContent sub-contents. Content modules moved around (again). ContentListWidget can now list horizontally. Log-level setting callback.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 20 Dec 2008 17:57:05 +0000
parents b16a534f5302
children 5ee69b3ed9c9
files codeDoc/ideas.txt codeDoc/jobs.txt codeDoc/todo.txt data/conf/gui.mtt dsss.conf mde/content/AStringContent.d mde/content/Content.d mde/content/IContent.d mde/content/Items.d mde/content/miscContent.d mde/gui/WidgetManager.d mde/gui/widget/Ifaces.d mde/gui/widget/PopupMenu.d mde/gui/widget/Widget.d mde/gui/widget/createWidget.d mde/gui/widget/layout.d mde/gui/widget/miscContent.d mde/imde.d mde/lookup/Options.d mde/setup/Init.d mde/setup/paths.d
diffstat 21 files changed, 226 insertions(+), 304 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/ideas.txt	Fri Dec 19 15:15:06 2008 +0000
+++ b/codeDoc/ideas.txt	Sat Dec 20 17:57:05 2008 +0000
@@ -2,8 +2,16 @@
 License: GNU General Public License version 2 or later (see COPYING)
 
 
-Miscelaneous ideas for mde.
-
 Make a special "trace" logger which keeps the last 20, say, trace messages and only output them when asked to do so, which might happen when an exception is caught. It might output them via the normal mechanisms, but only when asked (and not all messages may be in the correct order: trace messages might be logged later than they were added to the list).
 
-Use debug scope(failure) to output log messages in many places.
\ No newline at end of file
+Use debug scope(failure) to output log messages in many places.
+
+
+GUI:
+->  Widgets:
+->  scripted widgets
+->  decent rendering/theme system
+->  events:
+    ->	Click events: widgets only receive clickEvent for left-button press, other button events handled alternatively from WidgetManager?
+    ->	Click callbacks: replace with "drag callback" notifying widget of release position (and widget)?
+	->  possibly better for drag-and drop support
--- a/codeDoc/jobs.txt	Fri Dec 19 15:15:06 2008 +0000
+++ b/codeDoc/jobs.txt	Sat Dec 20 17:57:05 2008 +0000
@@ -8,13 +8,17 @@
 
 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.
+5   Protection on recursion of widgets; can cause sigsegv (enum using ContentListWidget & optVal refering to optBox). Only allow non-content widgets to be instanced once?
+4   Make popups use ordinary widgets with int to determine type like other widgets.
 4   Close popup menu on button activation/click.
 4   When child widgets are resized: must tell parent (bug);
+3   Make EnumContent a derivative of BoolContent to use to solve callback problems.
 3   Synchronization of IContent with gui (e.g. multiple edit widgets): worth adding (temporary) callbacks?
 3   Widget saving: how to deal with modifier functions, esp. when they discard parameters? Remove feature except for dimdata and handle gui editing separately?
 3   Use of dtors - don't rely on them? Or what happens when init throws during creation - relying on undefined behaviour.
 3   glBindTexture not working with non-0 index - perhaps use a higher level graphics library at some point.
 3   Windows building/compatibility (currently partial) - tango/sys/win32/SpecialPath.d
+2   Popup help boxes on hover/right click to display content description.
 2   clickEvent: don't evaluate b directly but pass to a WidgetManager function to allow button configuration. Alternately, evaluate in WidgetManager's clickEvent and pass flags on to IChildWidget's clickEvent.
 2   Reduce variable passing (e.g. for some Content widgets data and id aren't even relevant). Wait for gui editor.
 2   Possibility: Could have a content method "update (T*)" to add a synchronized variable. Or could store both a content and a syncronized variable for each option within Options.
--- a/codeDoc/todo.txt	Fri Dec 19 15:15:06 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-Copyright © 2007-2008 Diggory Hardy
-License: GNU General Public License version 2 or later (see COPYING)
-
-
-GUI:
-->  Widgets:
-->  scripted widgets
-->  decent rendering/theme system
-->  events:
-    ->	Click events: widgets only receive clickEvent for left-button press, other button events handled alternatively from WidgetManager?
-    ->	Click callbacks: replace with "drag callback" notifying widget of release position (and widget)?
-	->  possibly better for drag-and drop support
-
-EnumContent:
-->	Sub-content for each possibility of type EventContent, with callback to update EnumContent.
-->	"Tab" widget with corresponding EnumContent to control which is active.
-
-
-Scratchpad area for ideas:
--- a/data/conf/gui.mtt	Fri Dec 19 15:15:06 2008 +0000
+++ b/data/conf/gui.mtt	Sat Dec 20 17:57:05 2008 +0000
@@ -14,7 +14,8 @@
 <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|optVal={0:[0x6030,12],1:["optEnum"]}>
+<WidgetData|optEnum={0:[0xC100,0,1,2],1:["optVal","optName"]}>
 <WidgetData|optSep={0:[0x21, 0xff],1:[" = "]}>
 {Basic}
 <WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}>
--- a/dsss.conf	Fri Dec 19 15:15:06 2008 +0000
+++ b/dsss.conf	Sat Dec 20 17:57:05 2008 +0000
@@ -5,8 +5,7 @@
 
 [*]
 version (Posix) {
-    # SMALLFILE is used to work-around bug http://www.dsource.org/projects/tango/ticket/1375
-    buildflags=-L-ldl -version=SMALLFILE
+    buildflags=-L-ldl
 }
 
 [mde/mde.d]
--- a/mde/content/AStringContent.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/content/AStringContent.d	Sat Dec 20 17:57:05 2008 +0000
@@ -23,12 +23,10 @@
 import Float = tango.text.convert.Float;
 import derelict.sdl.keysym;
 
-debug {
-    import tango.util.log.Log : Log, Logger;
-    private Logger logger;
-    static this () {
-        logger = Log.getLogger ("mde.content.AStringContent");
-    }
+import tango.util.log.Log : Log, Logger;
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.content.AStringContent");
 }
 
 // Used by Options
@@ -57,14 +55,14 @@
  * ---
  * 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 AStringContent : AContent
+abstract class AStringContent : Content
 {
     protected this (char[] symbol) {
 	super (symbol);
     }
     
     /// Get the text.
-    char[] toString (uint i) {
+    override char[] toString (uint i) {
         return i == 0 ? sv
              : i == 1 ? name_
              : i == 2 ? desc_
@@ -226,7 +224,7 @@
     }
     
 protected:
-    int v;
+    int v = 0;	// must be a valid value for EnumContent (i.e. 0)
 }
 
 /** Double content. */
@@ -262,8 +260,11 @@
     double v;
 }
 
-/** A content representing an enumeration. */
-class EnumContent : IntContent
+/** A content representing an enumeration.
+ *
+ * Extends IntContent for Options support. */
+// maybe avoid problems by adding a special callback to BoolContent subclass?
+class EnumContent : IntContent, IContentList
 {
     /** CTOR.
     *
@@ -272,57 +273,66 @@
     *	val = which value is active; must be in [0,length-1]
     */
     this (char[] symbol, char[][] enumSymbols, int val = 0) {
-	this.enumSymbols = enumSymbols;
-	enumName = enumSymbols.dup;	// dup top array but not strings; elements of top array are replaced when tranlated
-	enumDesc.length = enumSymbols.length;	// top array should always be allocated
+        enums.length = enumSymbols.length;
+        foreach (i, ref e; enums) {
+            e = new BoolContent (enumSymbols[i], false);
+            e.addCallback (&enumAssignCb);
+        }
 	super (symbol, val);
-    }
-    
-    void nameEnum (uint i, char[] name, char[] desc = null) {
-	enumName[i] = name;
-	enumDesc[i] = desc;
-	if (v == i)
-	    sv = name;	// keep sv in sync
+        //NOTE: if val is 0, no enumeration should be set; however it seems to work!
     }
     
-    char[] toString (uint i) {
-	return i >= 3 ? enumName[i-3]
-	: i == 0 ? enumName[v]
-	: i == 1 ? name_
-	: i == 2 ? desc_
-	: null;
-    }
-    
-    void assignNoCb (int val) {
-	if (val >= enumSymbols.length || val < 0) {
-	    logger.error ("EnumContent "~name_~" assigned invalid value; keeping value: "~enumName[v]);
+    override void assignNoCb (int val) {
+        if (val == v) return;
+        if (val >= enums.length || val < 0) {
+	    logger.error ("EnumContent "~name_~" assigned invalid value; keeping value: "~sv);
 	    return;
 	}
+        enums[v] = false;
+        enums[val] = true;
 	v = val;
-	sv = enumName[v];
+        svAssign;
+    }
+    void svAssign () {
+	sv = enums[v].name_;
 	if (pos > sv.length) pos = sv.length;
+        endEvent;
     }
     
     override char[] endEdit () {
-	foreach (i,n; enumName)
-	    if (sv == n) {
+	foreach (i,e; enums)
+	    if (sv == e.name_) {
 		v = i;
 		goto break1;
 	    }
 	
-	logger.error ("EnumContent "~name_~" assigned invalid value; keeping value: "~enumName[v]);
-	sv = enumName[v];	// sv was edited; revert
+        sv = enums[v].name_;	// sv was edited; revert
+        logger.error ("EnumContent "~name_~" assigned invalid value; keeping value: "~sv);
 	if (pos > sv.length) pos = sv.length;
 	
 	break1:
 	endEvent;
-	logger.trace ("All logging levels: {}", enumName);
 	return sv;
     }
     
-    final char[][] enumSymbols;
+    override Content[] list () {
+        return enums;
+    }
+    
 protected:
-    char[][] enumName;
-    char[][] enumDesc;
-    uint value;
+    void enumAssignCb (Content c) {
+        if (enums[v] is c) {	// No other is set so persist value
+            enums[v].assignNoCb (true);
+        }
+        foreach (i,e; enums)
+            if (c is e) {
+            	enums[v].assignNoCb = false;	// NOTE: opAssign causes sigsegv (due to recursive calling?)
+                v = i;
+                svAssign;
+                return;
+            }
+        debug logger.error ("EnumContent.enumAssignCb: invalid value (code error)");
+    }
+    
+    BoolContent[] enums;	// NOTE: an array isn't always fastest
 }
--- a/mde/content/Content.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/content/Content.d	Sat Dec 20 17:57:05 2008 +0000
@@ -14,22 +14,41 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 /*************************************************************************************************
- * The content system − a few content classes.
+ * The content system − interfaces and base Content class.
  *************************************************************************************************/
 module mde.content.Content;
 
-public import mde.content.IContent;
 import util = mde.util;
 
-debug {
-    import tango.util.log.Log : Log, Logger;
-    private Logger logger;
-    static this () {
-        logger = Log.getLogger ("mde.content.Content");
-    }
+/** IContent − interface for all Content classes.
+ *
+ * Very little code uses IContent (except for passing opaquely). */
+interface IContent
+{
+    /** Generically return strings.
+    *
+    * This serves two purposes: generically returning a string of/related to the content (i == 0),
+    * and returning associated descriptors. Functions should adhere to (or add to) this table.
+    *
+    *  $(TABLE
+    *  $(TR $(TH i) $(TH returns))
+    *  $(TR $(TD 0) $(TD value))
+    *  $(TR $(TD 1) $(TD Translated name or null))
+    *  $(TR $(TD 2) $(TD Translated description or null))
+    *  $(TR $(TD other) $(TD null))
+    *  ) */
+    char[] toString (uint i);
 }
 
-/** The base for $(I most) content classes.
+/** Content lists. Impemented by EnumContent as well as ContentList. */
+interface IContentList : IContent
+{
+    /** Return all sub-contents. */
+    Content[] list ();
+}
+
+
+/** The base for most or all content classes.
  *
  * Includes generic callback support, toString implementation and symbol access.
  * 
@@ -40,7 +59,7 @@
  *  T opCall ();		// return value
  *  alias opCall opCast;
  * --- */
-class AContent : IContent
+class Content : IContent
 {
     this (char[] symbol) {
 	this.symbol = symbol;
@@ -53,17 +72,17 @@
     }
     
     /** Add a callback. Callbacks are called in the order added. */
-    EventContent addCallback (void delegate (AContent) cb) {
+    Content addCallback (void delegate (Content) cb) {
 	this.cb ~= cb;
 	return this;
     }
     /// ditto
-    EventContent addCallback (void function (AContent) cb) {
+    Content addCallback (void function (Content) cb) {
 	this.cb ~= util.toDg (cb);
 	return this;
     }
     
-    char[] toString (uint i) {
+    override char[] toString (uint i) {
 	return i == 0 ? "No value"
 	: i == 1 ? name_
 	: i == 2 ? desc_
@@ -79,52 +98,5 @@
     final char[] symbol;	// Symbol name for this content
 protected:
     char[] name_, desc_;	// name and description
-    void delegate (AContent) cb[];
+    void delegate (Content) cb[];
 }
-
-/** A generic way to handle a list of type IContent. */
-class ContentList : AContent
-{
-    this (char[] symbol, AContent[] list = null) {
-	list_.length = list.length;
-	foreach (i,c; list)
-	    list_[i] = c;
-	super (symbol);
-    }
-    this (char[] symbol, AContent[char[]] l) {
-	list_.length = l.length;
-	size_t i;
-	foreach (c; l)
-	    list_[i++] = c;
-	super (symbol);
-    }
-    
-    IContent[] list () {
-	return list_;
-    }
-    
-protected:
-    final IContent[] list_;
-}
-
-/** Created on errors to display and log a message. */
-class ErrorContent : IContent
-{
-    this (char[] msg) {
-	this.msg = msg;
-    }
-    
-    char[] toString (uint i) {
-	return i == 0 ? msg
-	     : i == 1 ? "Error"
-	     : null;
-    }
-    
-protected:
-    char[] msg;
-}
-
-/** A Content with no value but able to pass on an event.
-*
-* The point being that a button can be tied to one of these. */
-alias AContent EventContent;
--- a/mde/content/IContent.d	Fri Dec 19 15:15:06 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/* LICENSE BLOCK
-Part of mde: a Modular D game-oriented Engine
-Copyright © 2007-2008 Diggory Hardy
-
-This program is free software: you can redistribute it and/or modify it under the terms
-of the GNU General Public License as published by the Free Software Foundation, either
-version 2 of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-/*************************************************************************************************
- * The content system − common interface.
- *************************************************************************************************/
-module mde.content.IContent;
-
-/** IContent − interface for all Content classes.
-*
-* Services like copy/paste could work on universal content. However, they would need to run a
-* conversion to the appropriate type (or try next-oldest item on clipboard?). */
-interface IContent
-{
-    /** Generically return strings.
-    *
-    * This serves two purposes: generically returning a string of/related to the content (i == 0),
-    * and returning associated descriptors. Functions should adhere to (or add to) this table.
-    *
-    *  $(TABLE
-    *  $(TR $(TH i) $(TH returns))
-    *  $(TR $(TD 0) $(TD value))
-    *  $(TR $(TD 1) $(TD Translated name or null))
-    *  $(TR $(TD 2) $(TD Translated description or null))
-    *  $(TR $(TD other) $(TD null))
-    *  ) */
-    char[] toString (uint i);
-}
--- a/mde/content/Items.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/content/Items.d	Sat Dec 20 17:57:05 2008 +0000
@@ -18,7 +18,7 @@
  *************************************************************************************************/
 module mde.content.Items;
 
-import mde.content.Content;
+import mde.content.miscContent;
 import mde.gui.exception;
 
 import imde = mde.imde;
@@ -39,7 +39,7 @@
      *
      * E.g. get ("Options.MiscOptions.L10n") returns miscOpts.L10n,
      * Items.get ("Options.MiscOptions") returns a ContentList of all misc options. */
-    AContent get (char[] item) {
+    Content get (char[] item) {
 	assert (currentL10n is miscOpts.L10n(), "must call loadTranslation (code error)");
 	
 	char[] h = head (item);
@@ -75,7 +75,7 @@
 	
 	// Create Option classes' ContentLists if necessary:
 	if (Options.allContentList is null) {
-	    AContent[] list;
+	    Content[] list;
 	    list.length = Options.optionsClasses.length;
 	    size_t i;
 	    foreach (n,opts; Options.optionsClasses) {
@@ -99,12 +99,11 @@
 	    foreach (s, v; opts.content) {
 		trle = trl.getStruct (s);
 		v.name (trle.name, trle.desc);
-		EnumContent ec = cast(EnumContent) v;
-		if (ec) {
-		    char[] tp = s ~ ".";
-		    foreach (i,sym; ec.enumSymbols) {
-			trle = trl.getStruct (tp~sym);
-			ec.nameEnum (i, trle.name, trle.desc);
+		ContentList cl = cast(ContentList) v;
+		if (cl) {
+		    foreach (i,c; cl.list) {
+			trle = trl.getStruct (c.symbol);
+			c.name (trle.name, trle.desc);
 		    }
 		}
 	    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/content/miscContent.d	Sat Dec 20 17:57:05 2008 +0000
@@ -0,0 +1,74 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software: you can redistribute it and/or modify it under the terms
+of the GNU General Public License as published by the Free Software Foundation, either
+version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+/*************************************************************************************************
+ * The content system − a few content classes.
+ *************************************************************************************************/
+module mde.content.miscContent;
+
+public import mde.content.Content;
+
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this () {
+        logger = Log.getLogger ("mde.content.miscContent");
+    }
+}
+
+/** A generic way to handle a list of type IContent. */
+class ContentList : Content, IContentList
+{
+    this (char[] symbol, Content[] list = null) {
+	list_ = list;
+	super (symbol);
+    }
+    this (char[] symbol, Content[char[]] l) {
+	list_.length = l.length;
+	size_t i;
+	foreach (c; l)
+	    list_[i++] = c;
+	super (symbol);
+    }
+    
+    override Content[] list () {
+	return list_;
+    }
+    
+protected:
+    final Content[] list_;
+}
+
+/** Created on errors to display and log a message. */
+class ErrorContent : IContent
+{
+    this (char[] msg) {
+	this.msg = msg;
+    }
+    
+    override char[] toString (uint i) {
+	return i == 0 ? msg
+	     : i == 1 ? "Error"
+	     : null;
+    }
+    
+protected:
+    char[] msg;
+}
+
+/** A Content with no value but able to pass on an event.
+*
+* The point being that a button can be tied to one of these. */
+alias Content EventContent;
--- a/mde/gui/WidgetManager.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/gui/WidgetManager.d	Sat Dec 20 17:57:05 2008 +0000
@@ -299,7 +299,7 @@
 
 
 import mde.gui.exception;
-import mde.content.Content;	// AContent passed to a callback
+import mde.content.Content;	// Content passed to a callback
 import mde.gui.widget.createWidget;
 
 import mde.file.mergetag.Reader;
@@ -487,7 +487,7 @@
     }
     
     /** Called when translation strings have been reloaded. */
-    void reloadStrings (AContent) {
+    void reloadStrings (Content) {
 	Items.loadTranslation;
 	child.setup (++setupN, 2);
 	child.setWidth  (w, -1);
--- a/mde/gui/widget/Ifaces.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/gui/widget/Ifaces.d	Sat Dec 20 17:57:05 2008 +0000
@@ -25,7 +25,7 @@
 
 public import mde.gui.types;
 public import mde.gui.renderer.IRenderer;
-import mde.content.IContent;
+import mde.content.Content;
 
 
 /*************************************************************************************************
--- a/mde/gui/widget/PopupMenu.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/gui/widget/PopupMenu.d	Sat Dec 20 17:57:05 2008 +0000
@@ -23,7 +23,7 @@
 import mde.gui.widget.TextWidget;
 import mde.gui.widget.layout;
 
-import mde.content.Content;
+import mde.content.miscContent;
 import mde.gui.exception;
 
 debug {
@@ -91,7 +91,7 @@
  * A function which returns the most appropriate content menu widget.
  *************************************************************************************************/
 IChildWidget menuContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) { if (c is null) throw new ContentException;
-    if (cast(ContentList) c)
+    if (cast(IContentList) c)
 	return new MenuContentListWidget(mgr,id,data,c);
     else if (cast(EventContent) c)
 	return new MenuButtonContentWidget(mgr,id,data,c);
@@ -143,8 +143,8 @@
 class MenuContentListWidget : GridWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
-	cList = cast(ContentList) content;
-	WDCCheck (data, 2, 0, cList);
+	cList = cast(IContentList) content;
+	WDCMinCheck (data, 2, 0, cList);
 	
 	cols = 1;
 	if ((rows = cList.list.length) > 0) {
@@ -164,5 +164,5 @@
     }
     
 private:
-    ContentList cList;
+    IContentList cList;
 }
--- a/mde/gui/widget/Widget.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/gui/widget/Widget.d	Sat Dec 20 17:57:05 2008 +0000
@@ -26,7 +26,7 @@
 module mde.gui.widget.Widget;
 
 public import mde.gui.widget.Ifaces;
-import mde.content.IContent;
+import mde.content.Content;
 import mde.gui.exception;
 
 debug {
--- a/mde/gui/widget/createWidget.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/gui/widget/createWidget.d	Sat Dec 20 17:57:05 2008 +0000
@@ -21,7 +21,7 @@
 
 import mde.gui.widget.Ifaces;
 import mde.gui.exception : WidgetDataException;
-import mde.content.IContent;
+import mde.content.Content;
 
 // Widgets to create:
 import mde.gui.widget.layout;
--- a/mde/gui/widget/layout.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/gui/widget/layout.d	Sat Dec 20 17:57:05 2008 +0000
@@ -19,7 +19,7 @@
 import mde.gui.widget.Widget;
 import mde.gui.exception;
 
-import mde.content.Content;
+import mde.content.miscContent;
 
 import tango.util.container.HashMap;
 
@@ -99,7 +99,7 @@
 class ContentListWidget : GridWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
-	cList = cast(ContentList) content;
+	cList = cast(IContentList) content;
 	WDCCheck (data, 2, 1, cList);
 	
         cols = 1;
@@ -112,6 +112,10 @@
             rows = 1;
             subWidgets = [mgr.makeWidget (data.strings[0], new ErrorContent ("<empty list>"))];
         }
+        if (data.ints[1] & 8) {	// orient horizontally
+            cols = rows;
+            rows = 1;
+        }
         super (mgr, id, data);
     }
     
@@ -123,7 +127,7 @@
     }
     
 private:
-    ContentList cList;
+    IContentList cList;
 }
 
 
--- a/mde/gui/widget/miscContent.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/gui/widget/miscContent.d	Sat Dec 20 17:57:05 2008 +0000
@@ -22,9 +22,11 @@
 import mde.gui.exception;
 import mde.gui.widget.textContent;
 import mde.gui.widget.layout;
+import mde.gui.widget.PopupMenu;
 
 import mde.gui.renderer.IRenderer;
 import mde.content.AStringContent;
+import mde.content.miscContent;
 import Items = mde.content.Items;
 
 debug {
@@ -58,14 +60,14 @@
  *************************************************************************************************/
 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(AStringContent) c) {
-	if (cast(EnumContent) c)
-	    return new EnumContentWidget(mgr,id,data,c);
-	else
+        if (cast(EnumContent) c)	// can be PopupMenuWidget or ContentListWidget
+            return new ContentListWidget(mgr,id,data,c);
+        else if (cast(BoolContent) c)
+            return new BoolContentWidget(mgr,id,data,c);
+        else
 	    return new AStringContentWidget(mgr,id,data,c);
-    } else if (cast(ContentList) c)
+    } else if (cast(IContentList) c)
 	return new ContentListWidget(mgr,id,data,c);
     else if (cast(EventContent) c)
 	return new ButtonContentWidget(mgr,id,data,c);
@@ -78,7 +80,7 @@
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
 	content = cast(BoolContent) c;
-	WDCCheck(data, 1,0, content);
+	WDCMinCheck(data, 1,0, content);
         wdimPair s = mgr.renderer.getToggleSize;
         w = mw = s.x;
         h = mh = s.y;
@@ -102,7 +104,7 @@
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
 	content = cast(EventContent) c;
-	WDCCheck (data, 1,0, content);
+	WDCMinCheck (data, 1,0, content);
 	adapter = mgr.renderer.getAdapter ();
 	super (mgr, id, data);
     }
@@ -132,101 +134,3 @@
     EventContent content;
     int index;
 }
-/// Pops up a list of selectable values
-class EnumContentWidget : AButtonWidget
-{
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
-	content = cast(EnumContent) c;
-	WDCCheck (data, 1,0, content);
-	adapter = mgr.renderer.getAdapter;
-	adapter.text = content.toString (0);
-	adapter.getDimensions (mw, mh);
-	w = mw;
-	h = mh;
-	internalWidg = new EnumContentPopup (mgr, id, data, content);
-	super (mgr, id, data);
-    }
-    
-    void changeContent () {
-	adapter.text = content.toString (0);
-	adapter.getDimensions (mw, mh);
-	w = mw;
-	h = mh;
-    }
-    
-    override void activated () {
-	mgr.addPopup (this, internalWidg);
-    }
-    
-    override void draw () {
-	super.draw;
-	adapter.draw (x,y);
-    }
-    
-protected:
-    EnumContentPopup internalWidg;	// setup & saveChanges calls not propegated
-    EnumContent content;
-    IRenderer.TextAdapter adapter;
-    
-    /// The widget which gets popped up
-    class EnumContentPopup : AButtonWidget
-    {
-	/// Assumes EnumContent c isn't null
-	this (IWidgetManager mgr, widgetID id, WidgetData data, EnumContent c) {
-	    content = c;
-	    adapters.length = content.enumSymbols.length;
-	    yPos.length = adapters.length;
-	    // mh is 0
-	    foreach (i, ref adapter; adapters) {
-		adapter = mgr.renderer.getAdapter;
-		adapter.text = content.toString(i+3);
-		wdim aw, ah;
-		adapter.getDimensions (aw,ah);
-		if (mw < aw) mw = aw;
-		yPos[i] = mh;
-		mh += ah;
-	    }
-	    w = mw;
-	    h = mh;
-	    super (mgr, id, data);
-	}
-	
-        override void draw () {
-	    super.draw;
-	    foreach (i,yp; yPos) {
-		adapters[i].draw (x,y+yp);
-	    }
-	}
-	
-	/// Called when a mouse click event occurs while held; handles up-click
-	override bool clickWhilePushed (wdabs cx, wdabs cy, ubyte b, bool state) {
-	    if (b == 1 && state == false) {
-		cy -= y;
-		if (cx >= x && cx < x+w && cy >= 0 && cy < h) {
-		    uint i = yPos.length-1;
-		    for (; i > 0; --i)
-			if (cy >= yPos[i])
-			    break;
-		    logger.trace ("Setting value: {}", i);
-		    content = i;
-		    //mgr.removePopup (this);
-		    changeContent;
-		}
-		
-		pushed = false;
-		mgr.requestRedraw;
-		mgr.removeCallbacks (cast(void*) this);
-		
-		return true;
-	    }
-	    return false;
-	}
-	
-        override void activated () {}
-	
-    protected:
-	EnumContent content;
-	IRenderer.TextAdapter[] adapters;	// NOTE: replace with multi-line adapter
-	wdrel[] yPos;	// y position of each adapter
-    }
-}
--- a/mde/imde.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/imde.d	Sat Dec 20 17:57:05 2008 +0000
@@ -19,14 +19,14 @@
 
 import mde.input.Input;
 import mde.scheduler.Scheduler;
-import mde.content.Content;
+import mde.content.miscContent;
 
 static this () {
     // Make these available to all importing modules' static CTORs, as well as during init.
     input = new Input();
     mainSchedule = new Scheduler;
     
-    quit = (new EventContent("quit")).addCallback ((AContent){
+    quit = (new EventContent("quit")).addCallback ((Content){
 	run = false;
     });
     a = new EventContent("a");
--- a/mde/lookup/Options.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/lookup/Options.d	Sat Dec 20 17:57:05 2008 +0000
@@ -21,6 +21,7 @@
 import mde.exception;
 
 public import mde.content.AStringContent;
+import mde.content.miscContent;	// ContentLists used by content.Items
 
 import mde.file.mergetag.Reader;
 import mde.file.mergetag.Writer;
@@ -185,7 +186,7 @@
     
     //BEGIN Non-static
     /// Get all Options stored with a ValueContent.
-    AContent[char[]] content() {
+    Content[char[]] content() {
         return opts;
     }
     
@@ -200,7 +201,7 @@
     
     protected {
         OptionChanges optionChanges;	// all changes to options (for saving)
-	AContent[char[]] opts;	// generic list of option values
+	Content[char[]] opts;	// generic list of option values
     }
     
     //BEGIN Mergetag loading/saving code
@@ -382,7 +383,7 @@
     // optsX store pointers to each item added along with the ID and are used for access.
     mixin(Vars!(TYPES));
     
-    void set (AContent c) {
+    void set (Content c) {
 	union U {
 	    BoolContent bc;
 	    StringContent sc;
--- a/mde/setup/Init.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/setup/Init.d	Sat Dec 20 17:57:05 2008 +0000
@@ -151,8 +151,9 @@
             root = Log.root;
 	    root.clear;			// we may no longer want to log to the console
 	    
-            // Now re-set the logging level, using the value from the config file:
-            root.level (miscOpts.logOutput() == 0 ? Level.None : cast(Level) miscOpts.logLevel(), true);
+            // Now re-set the logging level and add callback to set on change:
+            setLogLevel ();
+            miscOpts.logLevel.addCallback (&setLogLevel);
             
             if (miscOpts.logOutput() & 2) {     // first appender so root seperator messages don't show on console
                 // Use 2 log files with a maximum size of 16kiB:
@@ -386,6 +387,11 @@
     private static {
         Logger logger;
         
+        // Callback on miscOpts.logLevel
+        void setLogLevel (Content = null) {
+            Log.root.level (miscOpts.logOutput() == 0 ? Level.None : cast(Level) miscOpts.logLevel(), true);
+        }
+        
         void printUsage (char[] progName) {
             Cout ("mde [no version]").newline;
             Cout ("Usage:").newline;
--- a/mde/setup/paths.d	Fri Dec 19 15:15:06 2008 +0000
+++ b/mde/setup/paths.d	Sat Dec 20 17:57:05 2008 +0000
@@ -264,7 +264,6 @@
 	fontFiles = new SortedMap!(char[],char[]);
 	foreach (fp; fs.files)
 	    fontFiles.add (fp.file, fp.cString);	// both strings should be slices of same memory
-	logger.trace ("found {} font files, {} dirs", fs.files.length, fs.folders.length);
     }
 } else version (Windows) {
     char[] fontDir;