changeset 104:ee209602770d

Cleaned up Options.d removing old storage method. It's now possible to get a ContentList of the whole of Options. Tweaked translation strings (added name and desc to Options classes). Replaced Options.addSubClass (class, "name") with Options.this("name").
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 26 Nov 2008 13:07:46 +0000
parents 42e241e7be3e
children 08651e8a8c51
files data/L10n/FontOptions.mtt data/L10n/MiscOptions.mtt data/L10n/VideoOptions.mtt data/conf/gui.mtt examples/guiDemo.d mde/font/FontTexture.d mde/gui/content/Content.d mde/gui/content/Items.d mde/lookup/Options.d mde/setup/Screen.d
diffstat 10 files changed, 139 insertions(+), 247 deletions(-) [+]
line wrap: on
line diff
--- a/data/L10n/FontOptions.mtt	Tue Nov 25 18:01:44 2008 +0000
+++ b/data/L10n/FontOptions.mtt	Wed Nov 26 13:07:46 2008 +0000
@@ -1,4 +1,5 @@
 {MT01}
 {en-GB}
+<entry|FontOptions={0:"Font options"}>
 <entry|lcdFilter={0:"LCD filtering",1:"Enable or disable sub-pixel rendering. Note that the FreeType library may be compiled without support due to patent issues."}>
 <entry|renderMode={0:"Font rendering mode",1:"Controls how fonts are rendered: in gray-scale, or for LCDs (with a horizontal (usual) or vertical layout, with an RGB (usual) or BGR sub-pixel mode."}>
--- a/data/L10n/MiscOptions.mtt	Tue Nov 25 18:01:44 2008 +0000
+++ b/data/L10n/MiscOptions.mtt	Wed Nov 26 13:07:46 2008 +0000
@@ -1,5 +1,6 @@
 {MT01}
 {en-GB}
+<entry|MiscOptions={0:"Miscellaneous options"}>
 <entry|maxThreads={0:"Max threads",1:"Maximum number of threads to use in mde (currently only applies to init stages run in parallel)."}>
 <entry|logLevel={0:"Logging level",1:"Lowest level to log messages; 0=trace, 1=info (default), 2=warn, 3=error, 4=fatal, 5=none."}>
 <entry|logOutput={0:"Logging output",1:"Output to: 0=nowhere, 1=the console, 2=a file, 3=both"}>
--- a/data/L10n/VideoOptions.mtt	Tue Nov 25 18:01:44 2008 +0000
+++ b/data/L10n/VideoOptions.mtt	Wed Nov 26 13:07:46 2008 +0000
@@ -1,5 +1,6 @@
 {MT01}
 {en-GB}
+<entry|VideoOptions={0:"Video options"}>
 <entry|fullscreen={0:"Fullscreen",1:"If true use the whole screen, if false use a window."}>
 <entry|hardware={0:"Hardware",1:"Create the video surface in hardware or software memory."}>
 <entry|resizable={0:"Resizable",1:"In windowed mode, allow the window to be resized by the window manager."}>
--- a/data/conf/gui.mtt	Tue Nov 25 18:01:44 2008 +0000
+++ b/data/conf/gui.mtt	Wed Nov 26 13:07:46 2008 +0000
@@ -5,7 +5,9 @@
 <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:[0x2031,0x6030,0],1:["Options.VideoOptions","optDBox"]}>
+<WidgetData|opts={0:[0x2031,0x6030,0],1:["Options","optCls"]}>
+<WidgetData|optCls={0:[0xC100,0,2,1],1:["optName","optVars"]}>
+<WidgetData|optVars={0:[0x6030,0],1:["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]}>
--- a/examples/guiDemo.d	Tue Nov 25 18:01:44 2008 +0000
+++ b/examples/guiDemo.d	Wed Nov 26 13:07:46 2008 +0000
@@ -67,10 +67,11 @@
     mainSchedule.add (mainSchedule.getNewID, &mde.events.pollEvents).frame = true;
     //END Main loop setup
     
+    double pollInterval = miscOpts.pollInterval();
     while (run) {
-        mainSchedule.execute (Clock.now());
-        
-        Thread.sleep (miscOpts.pollInterval());	// sleep this many seconds
+	mainSchedule.execute (Clock.now());
+	
+	Thread.sleep (pollInterval);	// sleep this many seconds
     }
     
     return 0;		// cleanup handled by init's DTOR
--- a/mde/font/FontTexture.d	Tue Nov 25 18:01:44 2008 +0000
+++ b/mde/font/FontTexture.d	Wed Nov 26 13:07:46 2008 +0000
@@ -464,8 +464,7 @@
     }
     
     static this() {
-        fontOpts = new FontOptions;
-        Options.addOptionsClass (fontOpts, "FontOptions");
+        fontOpts = new FontOptions ("FontOptions");
     }
 }
 
--- a/mde/gui/content/Content.d	Tue Nov 25 18:01:44 2008 +0000
+++ b/mde/gui/content/Content.d	Wed Nov 26 13:07:46 2008 +0000
@@ -65,20 +65,25 @@
 /** A generic way to handle a list of type IContent. */
 class ContentList : IContent
 {
-    this (IContent[] list = null) {
+    this (IContent[] list = null, char[] n = null, char[] d = null) {
 	list_ = list;
+	name_ = n;
+	desc_ = d;
     }
-    this (ValueContent[char[]] l) {
+    this (ValueContent[char[]] l, char[] n = null, char[] d = null) {
 	list_.length = l.length;
 	size_t i;
 	foreach (c; l)
 	    list_[i++] = c;
+	name_ = n;
+	desc_ = d;
     }
     
     char[] toString (uint i) {
 	return i == 0 ? Int.toString (list_.length) ~ " elements"
-	: i == 1 ? "ContentList"
-	: null;
+             : i == 1 ? name_
+             : i == 2 ? desc_
+             : null;
     }
     
     IContent[] list () {
@@ -91,6 +96,7 @@
     
 protected:
     IContent[] list_;
+    char[] name_, desc_;	// name and description
 }
 
 /** Created on errors to display and log a message. */
@@ -135,9 +141,9 @@
     
     /// Get the text.
     char[] toString (uint i) {
-        return (i == 0) ? sv
-             : (i == 1) ? name_
-             : (i == 2) ? desc_
+        return i == 0 ? sv
+             : i == 1 ? name_
+             : i == 2 ? desc_
              : null;
     }
     
@@ -201,7 +207,7 @@
     char[] sv;		// string of value; updated on assignment for displaying and editing
     size_t pos;		// editing position; used by keyStroke
     char[] symb;
-    char[] name_, desc_;// name and description, loaded by lookup.Translation
+    char[] name_, desc_;// name and description
 }
 
 template VContentN(T) {
--- a/mde/gui/content/Items.d	Tue Nov 25 18:01:44 2008 +0000
+++ b/mde/gui/content/Items.d	Wed Nov 26 13:07:46 2008 +0000
@@ -24,6 +24,14 @@
 import mde.lookup.Options;
 import mde.lookup.Translation;
 
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this () {
+	logger = Log.getLogger ("mde.gui.content.Items");
+    }
+}
+
     /** Get a specific content item.
      *
      * E.g. get ("Options.MiscOptions.L10n") returns miscOpts.L10n,
@@ -37,20 +45,24 @@
     
     /** Same as calling get("Options."~item). */
     IContent getOptions (char[] item) {
+	if (item is null) {
+	    IContent[] list;
+	    list.length = Options.optionsClasses.length;
+	    size_t i;
+	    foreach (n,opts; Options.optionsClasses) {
+		if (opts.name is null) loadTransl (opts, n);
+		list[i++] = new ContentList (opts.content, opts.name, opts.desc);
+	    }
+		
+	    return new ContentList (list, "Options");
+	}
 	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 (p.name is null) loadTransl (*p, h);
 	    
 	    if (item == null)
-		return new ContentList (p.content);
+		return new ContentList (p.content, p.name, p.desc);
 	    
 	    auto q = (h = head (item)) in p.content;
 	    if (q && item is null)	// enforce item is an exact match
@@ -73,3 +85,15 @@
 	    str = str[i+1..$];
 	return ret;
     }
+    
+    void loadTransl (Options p, char[] n) {
+	debug logger.trace ("Loading translation strings for Options."~n);
+	Translation trans = Translation.load ("L10n/"~n);
+	Translation.Entry transled = trans.getStruct (n);
+	p.name = transled.name;
+	p.desc = transled.desc;
+	foreach (s, v; p.content) {
+	    transled = trans.getStruct (s);
+	    v.name (transled.name, transled.desc);
+	}
+    }
--- a/mde/lookup/Options.d	Tue Nov 25 18:01:44 2008 +0000
+++ b/mde/lookup/Options.d	Wed Nov 26 13:07:46 2008 +0000
@@ -13,19 +13,8 @@
 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
- * and ease saving and loading of values. The Options class is only really designed around handling
- * small numbers of variables for now.
- *
- * Note: This module uses some non-spec functionality, which "works for me", but may need to be
- * changed if it throws up problems. Specifically: templated virtual functions (Options.set, get
- * and list), and accessing private templates from an unrelated class (Options.TName, TYPES).
- * OptionChanges used to have a templated set member function (used by Options.set), which caused
- * linker problems when the module wasn't compiled from scratch.
- */
+/** This module handles loading and saving of, and allows generic access to named option variables
+ * of a simple type (see Options.TYPES). */
 module mde.lookup.Options;
 
 import mde.setup.paths;
@@ -45,50 +34,43 @@
     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
-* base for generic options handling. Also, the static portion of this class tracks sub-class
-* instances and provides loading and saving methods.
-*
-* Each sub-class provides named variables for maximal-speed reading. Local sub-class references
-* should be used for reading variables, and via the addOptionsClass() hook will be loaded from
-* files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will
-* not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an
-* example like MiscOptions as a template for creating a new Options sub-class.
-*
-* Optionally, overload the validate() function. This is called after loading, allowing conditions
-* to be enforced on variables. Use set!()() to change the variables. If an exception is thrown,
-* init will abort and the executable won't start.
-*
-* Details: Options sub-classes hold associative arrays of pointers to all option variables, with a
-* char[] id. This list is used for saving, loading and to provide generic GUI options screens. The
-* built-in support in Options is only for bool, int and char[] types (a float type may get added).
-* Further to this, a generic class is used to store all options which have been changed, and if any
-* have been changed, is merged with options from the user conf dir and saved on exit.
-*/
-/* An idea for potentially extending Options, but which doesn't seem necessary now:
-Move static code from Options to an OptionSet class, which may be sub-classed. These sub-classes
-may be hooked in to the master OptionSet class to shadow all Options classes and be notified of
-changes, and may or may not have values loaded from files during init. Change-sets could be
-rewritten to use this.
-However, only the changesets should need to be notified of each change (gui interfaces could simply
-be notified that a change occured and redraw everything; users of options can just re-take their
-values every time they use them). */
+/*************************************************************************************************
+ * This class and the OptionChanges class contain all the functionality.
+ * 
+ * Options are stored in derived class instances, tracked by the static portion of Options. Each
+ * value is stored in a ValueContent class, whose value can be accessed with opCall, opCast and
+ * opAssign. These class objects can be given callbacks called whenever their value is changed.
+ * 
+ * Public static methods allow getting the list of tracked sub-class instances, and loading and saving
+ * option values. A public non-static method allows generic access to option variables.
+ * 
+ * Generic access to Options is of most use to a gui, allowing Options to be edited generically.
+ * 
+ * The easiest way to use Options is to use an existing sub-class as a template, e.g. MiscOptions.
+ *************************************************************************************************/
 class Options : IDataSection
 {
-    protected this() {}   /// Do not instantiate directly.
+    /** Do not instantiate directly; use a sub-class.
+     *
+     * CTOR adds any created instance to the list of classes tracked statically for loading/saving
+     * and generic access.
+     * 
+     * Normally instances are created by a static CTOR. */
+    protected this(char[] name)
+    in {
+	assert (((cast(ID) name) in subClasses) is null);  // Don't allow a silent replacement
+    } body {
+	subClasses[cast(ID) name] = this;
+    }
     
-    // All supported types, for generic handling via templates. It should be possible to change
-    // the supported types simply by changing this list now (untested).
-    template store(A...) { alias A store; }
-    // NOTE: currently all types have transitioned to the new method, but the old method remains
-    alias store!(bool, int, double, char[]) TYPES;    // all types
-    alias store!(bool, int, double, char[]) CTYPES;   // types stored with a content
     //BEGIN Templates: internal
-    private {
-        // Get name of a type. Basically just stringof, but special handling for arrays.
+    package {
+	// All supported types, for generic handling via templates. It should be possible to change
+	// the supported types simply by changing this list.
+	template store(A...) { alias A store; }
+	alias store!(bool, int, double, char[]) TYPES;   // types handled
+	
+	// Get name of a type. Basically just stringof, but special handling for arrays.
         // Use TName!(T) for a valid symbol name, and T.stringof for a type.
         template TName(T : T[]) {
             const char[] TName = TName!(T) ~ "A";
@@ -96,18 +78,8 @@
         template TName(T) {
             const char[] TName = T.stringof;
         }
-        
-        // Pointer lists
-        template PLists(A...) {
-            static if (A.length) {
-                static if (TIsIn!(A[0], CTYPES)) {
-                    const char[] PLists = PLists!(A[1..$]);
-                } else
-                    const char[] PLists = A[0].stringof~"*[ID] opts"~TName!(A[0])~";\n" ~ PLists!(A[1..$]);
-            } else
-                const char[] PLists = "";
-        }
-        
+    }
+    private {
         // True if type is one of A
         template TIsIn(T, A...) {
             static if (A.length) {
@@ -121,54 +93,25 @@
         
         // For addTag
         template addTagMixin(T, A...) {
-            static if (TIsIn!(T, CTYPES)) {
-                const char[] ifBlock = `if (tp == "`~T.stringof~`") {
+            const char[] ifBlock = `if (tp == "`~T.stringof~`") {
     auto p = id in opts;
     if (p) {
         auto q = cast(`~VContentN!(T)~`) (*p);
         if (q) q.assignNoCB = parseTo!(`~T.stringof~`) (dt);
     }
 }`;
-            } else
-                const char[] ifBlock = `if (tp == "`~T.stringof~`") {
-    `~T.stringof~`** p = id in opts`~TName!(T)~`;
-    if (p !is null) **p = parseTo!(`~T.stringof~`) (dt);
-}`;
             static if (A.length)
                 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin;
             else
                 const char[] addTagMixin = ifBlock;
         }
-        
-        // For list
-        template listMixin(A...) {
-            static if (A.length) {
-                static if (TIsIn!(A, CTYPES))
-                    const char[] listMixin = listMixin!(A[1..$]);
-                else
-                    const char[] listMixin = `ret ~= opts`~TName!(A[0])~`.keys;` ~ listMixin!(A[1..$]);
-            } else
-                const char[] listMixin = ``;
-        }
     }
     //END Templates: internal
     
     
     //BEGIN Static
     static {
-    	/** Add an options sub-class to the list for loading and saving.
-    	 *
-    	 * Call from static this() (before Init calls load()). */
-    	void addOptionsClass (Options c, char[] i)
-    	in {    // Trap a couple of potential coding errors:
-            assert (c !is null);    // Instance must be created before calling addOptionsClass
-            assert (((cast(ID) i) in subClasses) is null);  // Don't allow a silent replacement
-        } body {
-            c.secName = i;
-            subClasses[cast(ID) i] = c;
-        }
-	
-	/** Get the hash map of Options classes. */
+	/** Get the hash map of Options classes. READ-ONLY. */
 	Options[ID] optionsClasses () {
 	    return subClasses;
 	}
@@ -239,81 +182,23 @@
     
     
     //BEGIN Non-static
-    /+ NOTE: according to spec: "Templates cannot be used to add non-static members or virtual
-    functions to classes." However, this appears to work (but linking problems did occur).
-    Alternative: use mixins. From OptionsChanges:
-        // setT (used to be a template, but:
-        // Templates cannot be used to add non-static members or virtual functions to classes. )
-        template setMixin(A...) {
-            static if (A.length) {
-                const char[] setMixin = `void set`~TName!(A[0])~` (ID id, `~A[0].stringof~` x) {
-                    `~TName!(T)~`s[id] = x;
-                }
-                ` ~ setMixin!(A[1..$]);
-            } 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
-     * via hash-maps, which is a little slower than direct access but necessary since the option
-     * must be changed in two separate places. */
-    /+deprecated void set(T) (char[] symbol, T val) {
-        static assert (TIsIn!(T,TYPES) && !TIsIn!(T, CTYPES), "Options.set does not support type "~T.stringof);
-        
-        changed = true;     // something got set (don't bother checking this isn't what it already was)
-        
-        try {
-            mixin (`*(opts`~TName!(T)~`[cast(ID) symbol]) = val;`);
-            mixin (`optionChanges.`~TName!(T)~`s[symbol] = val;`);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            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.  */
-    deprecated T get(T) (char[] symbol) {
-        static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof);
-        
-        mixin (`alias opts`~TName!(T)~` optsVars;`);
-        
-        try {
-            return *(optsVars[cast(ID) symbol]);
-        } catch (ArrayBoundsException) {
-            // log and ignore:
-            logger.error ("Options.get: invalid symbol");
-        }
-    }+/
-    
-    /** List the names of all options of a specific type. */
-    deprecated char[][] list () {
-        char[][] ret;
-        mixin (listMixin!(TYPES));
-        return ret;
-    }
-    
     /// Get all Options stored with a ValueContent.
     ValueContent[char[]] content() {
         return opts;
     }
     
-    /** Variable validate function, called when options are loaded from file. This implementation
-     * does nothing. */
-    void validate() {}
+    /** Variable validate function, called when options are loaded from file.
+     *
+     * This can be overridden to enforce limits on option variables, etc. */
+    protected void validate() {}
     
-    /** Boolean, telling whether translation strings have been loaded for the instance. */
-    bool transLoaded;
+    /** Translated name and description of the instance. mde.gui.content.Items loads these and the
+     * translation strings of all enclosed options simultaneously. */
+    char[] name, desc;
     
     protected {
-        char[] secName;         // name of this option setting; set null after translation is loaded
         OptionChanges optionChanges;	// all changes to options (for saving)
-    	
-        // The "pointer lists", e.g. char[]*[ID] optscharA;
-        mixin (PLists!(TYPES));
-        ValueContent[char[]] opts;      // generic list of option values
+        ValueContent[char[]] opts;	// generic list of option values
     }
     
     //BEGIN Mergetag loading/saving code
@@ -328,13 +213,6 @@
     
     //BEGIN Templates: impl & optionsThis
     private {
-        // Replace, e.g., bool, with BoolContent
-        template contentName(A) {
-            static if (TIsIn!(A, CTYPES)) {
-                const char[] contentName = VContentN!(A);
-            } else
-                const char[] contentName = A.stringof;
-        }
         // Return index of first comma, or halts if not found.
         template cIndex(char[] A) {
             static if (A.length == 0)
@@ -368,26 +246,6 @@
             else					// no match
                 const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]);
         }
-        // May have a trailing comma. Assumes cIndex always returns less than A.$ .
-        template aaVars(char[] A) {
-            static if (A.length == 0)
-                const char[] aaVars = "";
-            else static if (A[0] == ' ')
-                const char[] aaVars = aaVars!(A[1..$]);
-            else
-                const char[] aaVars = "\""~A[0..cIndex!(A)]~"\"[]:&"~A[0..cIndex!(A)] ~ "," ~
-                        aaVars!(A[cIndex!(A)+1..$]);
-        }
-        // May have a trailing comma. Assumes cIndex always returns less than A.$ .
-        template aaVarsContent(char[] A) {//FIXME
-            static if (A.length == 0)
-            const char[] aaVarsContent = "";
-            else static if (A[0] == ' ')
-                const char[] aaVarsContent = aaVarsContent!(A[1..$]);
-            else
-                const char[] aaVarsContent = "\""~A[0..cIndex!(A)]~"\"[]:cast(ValueContent)"~A[0..cIndex!(A)] ~ "," ~
-                aaVarsContent!(A[cIndex!(A)+1..$]);
-        }
         // strip Trailing Comma
         template sTC(char[] A) {
             static if (A.length && A[$-1] == ',')
@@ -395,15 +253,6 @@
             else
                 const char[] sTC = A;
         }
-        // if string is empty (other than space) return null, otherwise enclose: [A]
-        template listOrNull(char[] A) {
-            static if (A.length == 0)
-                const char[] listOrNull = "null";
-            else static if (A[0] == ' ')
-                const char[] listOrNull = listOrNull!(A[1..$]);
-            else
-                const char[] listOrNull = "["~A~"]";
-        }
         // if B is empty return an empty string otherswise return what's below:
         template catOrNothing(char[] A,char[] B) {
             static if (B.length)
@@ -424,17 +273,14 @@
         // for recursing on TYPES
         template optionsThisInternal(char[] A, B...) {
             static if (B.length) {
-                static if (TIsIn!(B[0], CTYPES)) {
-                    const char[] optionsThisInternal = createContents!(B[0],parseT!(B[0].stringof,A))~
-                    optionsThisInternal!(A,B[1..$]);
-                } else
-                const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]);
-            } else
+                const char[] optionsThisInternal = createContents!(B[0],parseT!(B[0].stringof,A))~
+		    optionsThisInternal!(A,B[1..$]);
+	    } else
                 const char[] optionsThisInternal = ``;
         }
         template declValsInternal(char[] A, B...) {
             static if (B.length) {
-                const char[] declValsInternal = catOrNothing!(contentName!(B[0]),parseT!(B[0].stringof,A)) ~ declValsInternal!(A,B[1..$]);
+                const char[] declValsInternal = catOrNothing!(VContentN!(B[0]),parseT!(B[0].stringof,A)) ~ declValsInternal!(A,B[1..$]);
             } else
                 const char[] declValsInternal = ``;
         }
@@ -450,6 +296,7 @@
         template optionsThis(char[] A) {
             const char[] optionsThis =
                     "optionChanges = new OptionChanges;\n" ~
+                    "super (name);\n" ~
                     optionsThisInternal!(A,TYPES);
         }
         /+ Needs too many custom parameters to be worth it? Plus makes class less readable.
@@ -467,8 +314,8 @@
          * In case this() needs to be customized, mixin(impl!(A)) is equivalent to:
          * ---
          * mixin (declVals!(A)~`
-        this () {
-    	`~optionsThis!(A)~`
+	this (char[] name) {
+	`~optionsThis!(A)~`
 	}`);
          * ---
          *
@@ -480,14 +327,20 @@
          * Extending: mixins could also be used for the static this() {...} or even the whole
          * class, but doing so would rather decrease readability of any implementation. */
         template impl(char[] A /+, char[] symb+/) {
-            const char[] impl = declVals!(A)~"\nthis(){\n"~optionsThis!(A)~"}";
+            const char[] impl = declVals!(A)~"\nthis(char[] name){\n"~optionsThis!(A)~"}";
             // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}"
         }
     }
     //END Templates: impl & optionsThis
 }
 
-/** Special class to store all locally changed options, whatever the section. */
+/*************************************************************************************************
+ * Special class to store all locally changed options.
+ * 
+ * This allows only changed options and those already stored in the user directory to be saved, so
+ * that other options can be merged in from a global directory, allowing any options not locally
+ * set to be changed globally.
+ *************************************************************************************************/
 class OptionChanges : IDataSection
 {
     //BEGIN Templates
@@ -553,23 +406,27 @@
     //END Mergetag loading/saving code
 }
 
-/* NOTE: Options sub-classes are expected to use a template to ease inserting contents and
-* hide some of the "backend" functionality. Use impl as below, or read the documentation for impl.
-*
-* Each entry should have a Translation entry with humanized names and descriptions in
-* data/L10n/ClassName.mtt
-*
-* To create a new Options sub-class, just copy, paste and adjust.
-*/
-
-/** A home for all miscellaneous options, at least for now. */
+/** A home for all miscellaneous options.
+ *
+ * Also a template for deriving Options; comments explain what does what.
+ * 
+ * Translation strings for the options are looked for in data/L10n/SectionName.mtt where
+ * this ("SectionName") names the instance. */
 MiscOptions miscOpts;
 class MiscOptions : Options {
+    /* The key step is to mixin impl.
+    The syntax is just as simple variables are declared, which is how these options used to be
+    stored. Now they're enclosed in ValueContent classes; e.g. "char[] L10n;" is replaced with
+    "TextContent L10n;". The pragma statement can be uncommented to see what code gets injected
+    (note: pragma () gets called each time the module is imported as well as when it's compiled).
+    impl creates a this() function; if you want to include your own CTOR see impl's ddoc. */
     const A = "bool exitImmediately; int maxThreads, logLevel, logOutput; double pollInterval; char[] L10n;";
     //pragma (msg, impl!(A));
     mixin (impl!(A));
     
-    void validate() {
+    // Overriding validate allows limits to be enforced on variables at load time. Currently
+    // there's no infrastructure for enforcing limits when options are set at run-time.
+    override void validate() {
         // Try to enforce sensible values, whilst being reasonably flexible:
         if (maxThreads() < 1 || maxThreads() > 64) {
             logger.warn ("maxThreads must be in the range 1-64. Defaulting to 4.");
@@ -579,8 +436,9 @@
             pollInterval = 0.01;
     }
     
+    // A static CTOR is a good place to create the instance (it must be created before init runs).
     static this() {
-        miscOpts = new MiscOptions;
-        Options.addOptionsClass (miscOpts, "MiscOptions");
+	// Adds instance to Options's tracking; the string is the section name in the config files.
+	miscOpts = new MiscOptions ("MiscOptions");
     }
 }
--- a/mde/setup/Screen.d	Tue Nov 25 18:01:44 2008 +0000
+++ b/mde/setup/Screen.d	Wed Nov 26 13:07:46 2008 +0000
@@ -264,8 +264,7 @@
     static this() {
         logger = Log.getLogger ("mde.setup.Screen");
         
-        videoOpts = new VideoOptions;
-        Options.addOptionsClass (videoOpts, "VideoOptions");
+        videoOpts = new VideoOptions ("VideoOptions");
     }
     
     // DATA: