changeset 94:9520cc0448e5

Boolean options are now encapsulated within a Content class (currently an experiment). This should facilitate generic option editing widgets.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 23 Oct 2008 17:45:49 +0100
parents 08a4ae11454b
children 2a364c7d82c9
files data/conf/gui.mtt data/conf/options.mtt mde/font/FontTexture.d mde/gui/content/Content.d mde/gui/content/options.d mde/gui/widget/TextWidget.d mde/gui/widget/layout.d mde/lookup/Options.d mde/setup/Init.d mde/setup/Screen.d
diffstat 10 files changed, 274 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/data/conf/gui.mtt	Tue Oct 21 11:35:15 2008 +0100
+++ b/data/conf/gui.mtt	Thu Oct 23 17:45:49 2008 +0100
@@ -7,7 +7,7 @@
 <WidgetData|content={0:[0xC100,0,4,2],1:["floating","button","blank","blank","f2","opts","blank","blank"]}>
 <WidgetData|button={0:[0x10,50,50]}>
 <WidgetData|blank={0:[0x2]}>
-<WidgetData|opts={0:[0x8110,0],1:["optDBox"]}>
+<WidgetData|opts={0:[0x8110,0],1:["optDBox","MiscOptions"]}>
 <WidgetData|optDBox={0:[0xC100,0,2,1],1:["optBox","optDesc"]}>
 <WidgetData|optBox={0:[0xC100,1,1,3],1:["optName","optSep","optVal"]}>
 <WidgetData|optName={0:[0x4020, 1, 0xfe8c00]}>
--- a/data/conf/options.mtt	Tue Oct 21 11:35:15 2008 +0100
+++ b/data/conf/options.mtt	Thu Oct 23 17:45:49 2008 +0100
@@ -1,16 +1,16 @@
 {MT01}
-{misc}
+{MiscOptions}
 <int|maxThreads=1>
 <bool|exitImmediately=false>
 <char[]|L10n="en-GB">
 <int|logOptions=0x3000>
 <double|pollInterval=0.01>
 
-{font}
+{FontOptions}
 <int|lcdFilter=2>
 <int|renderMode=0x30000>
 
-{video}
+{VideoOptions}
 <bool|noFrame=false>
 <bool|resizable=true>
 <bool|hardware=false>
--- a/mde/font/FontTexture.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/font/FontTexture.d	Thu Oct 23 17:45:49 2008 +0100
@@ -433,8 +433,8 @@
 
 // this bit of renderMode, if set, means read glyph as BGR not RGB when using LCD rendering
 enum { RENDER_LCD_BGR = 1 << 30 }
-OptionsFont fontOpts;
-class OptionsFont : Options {
+FontOptions fontOpts;
+class FontOptions : Options {
     /* renderMode have one of the following values, possibly with bit 31 set (see RENDER_LCD_BGR):
      * FT_LOAD_TARGET_NORMAL    (0x00000)
      * FT_LOAD_TARGET_LIGHT     (0x10000)
@@ -447,8 +447,8 @@
     mixin (impl!("int renderMode, lcdFilter;"));
     
     static this() {
-        fontOpts = new OptionsFont;
-        Options.addOptionsClass (fontOpts, "font");
+        fontOpts = new FontOptions;
+        Options.addOptionsClass (fontOpts, "FontOptions");
     }
 }
 
--- a/mde/gui/content/Content.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/gui/content/Content.d	Thu Oct 23 17:45:49 2008 +0100
@@ -13,13 +13,21 @@
 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 − type agnostic part.
+/** The content system − common types.
  */
 module mde.gui.content.Content;
 
 import Int = tango.text.convert.Integer;
 
-/** Content − universal part.
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this () {
+        logger = Log.getLogger ("mde.gui.content.Content");
+    }
+}
+
+/** 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?).
@@ -55,47 +63,70 @@
     
     /** Generically return strings.
      *
-     * Every Content should return a string for i == 0; preferably its value. Other values of i
-     * can be used to return other strings. For unsupported values of i, null should be returned.
-     */
+     * 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);
 }
-/+
-/** Extension to interface providing text-specific tools. */
-interface IContentText : IContent
+
+/** 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. */
+abstract class ValueContent : IContent
 {
-    char[] text ();            /// Get/set the value.
-    void text (char[] v);      /// ditto
-}
-+/
-/+ FIXME - use content lists or drop?
-/** Get a content from the list (what list?). */
-ContentText getContentText (char[] id) {
-    return new ContentText (id);	// forget the list for now
+    protected this () {}
+    
+    void name (char[] n, char[] d = null) {
+        name_ = n;
+        desc_ = d;
+    }
+protected:
+    char[] name_, desc_;// name and description, loaded by lookup.Translation
 }
 
-/** ditto */
-ContentInt getContentInt (char[] id) {
-    return new ContentInt (42);	// forget the list for now
-}
-+/
-
-/+FIXME - currently unused
-/** Text content. */
-/* May end up extending a universal content type.
- *  Services like copy/paste could work on universal content.
- *
- * NOTE: Needs to be a reference type really.
- *  Could alternately be:
- *      alias ContentTextStruct* ContentText
- *  where ContentTextStruct is a struct. */
-class ContentText : IContent
+class BoolContent : ValueContent
 {
     this () {}
-    this (char[] text) {
-        text_ = text;
+    this (bool val) {
+        v = val;
+    }
+    
+    /// Get the text.
+    char[] toString (uint i) {
+        debug logger.trace ("BoolContent.toString");
+        return (i == 0) ? v ? "true" : "false"
+             : (i == 1) ? name_
+             : (i == 2) ? desc_
+             : null;
     }
     
+    void opAssign (bool val) {
+        v = val;
+    }
+    bool opCall () {
+        return v;
+    }
+    
+    protected bool v;
+}
+
+/** Text content. */
+class TextContent : ValueContent
+{
+    this () {}
+    this (char[] text, char[] name = null) {
+        text_ = text;
+        name_ = name;
+    }
+    /+
     ContentText dup () {
         return new ContentText (text_);
     }
@@ -107,19 +138,21 @@
         // FIXME: convert
         return null;
     }
-    
-    alias toString text;
+    +/
     
     /// Get the text.
-    char[] toString () {
-        return text_;
+    char[] toString (uint i) {
+        debug logger.trace ("TextContent.toString");
+        return (i == 0) ? text_
+            : (i == 1) ? name_
+            : (i == 2) ? desc_
+            : null;
     }
     
 protected:
-    //NOTE: need to allow cache-invalidating when text changes!
     char[] text_;
 }
-
+/+
 /** Integer content. */
 class ContentInt : IContent
 {
--- a/mde/gui/content/options.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/gui/content/options.d	Thu Oct 23 17:45:49 2008 +0100
@@ -25,36 +25,53 @@
 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.options");
+    }
+}
+
 class OptionList
 {
-    this (Options opts, char[] i18nOptionsName)
-    in { assert (opts !is null, "OptionList: invalid Options instance"); }
-    body {
-        Translation trans = Translation.load (i18nOptionsName);
+    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;
         
-        char[][] list = opts.list!(char[])();
+        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;
+        }
         
-        textOpts.length = list.length;
-        foreach (i,s; list) {
-            Translation.Entry transled = trans.getStruct (s);
-            textOpts[i] = new ContentOptionText(opts, s, transled.name, transled.desc);
-        }
     }
     
-    ContentOption[] list () {
+    IContent[] list () {
         return textOpts;
     }
     
-    static OptionList trial () {
-        return new OptionList (miscOpts, "L10n/OptionsMisc");
-    }
-    
 protected:
-    ContentOption[] textOpts;
+    IContent[] textOpts;
 }
 
 //FIXME - todo.txt
-class ContentOptionText : ContentOption
+class OptionContent : IContent
 {
     this (Options o, char[] s, char[] name, char[] desc) {
         opts = o;
@@ -65,7 +82,7 @@
     
     char[] toString (uint i) {
         if (i == 0)
-            return opts.get!(char[])(symb);
+            return "dummy"; //opts.get!(char[])(symb);
         else if (i == 1)
             return name_;
         else if (i == 2)
@@ -77,22 +94,6 @@
     void value (char[] v) {
         opts.set!(char[])(symb, v);
     }+/
-}
-
-abstract class ContentOption : IContent
-{
-    // Get the symbol name (useful?)
-    /+
-    /// Get the translated name
-    char[] name () {
-        return name_;
-    }
-    
-    /// Get the description (translated)
-    char[] description () {
-        return desc_;
-    }
-    +/
 protected:
     Options opts;	// the set of options within which our option lies
     char[]  symb;	// the symbol name of our option
--- a/mde/gui/widget/TextWidget.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/gui/widget/TextWidget.d	Thu Oct 23 17:45:49 2008 +0100
@@ -24,6 +24,14 @@
 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");
+    }
+}
+
 /// Basic text widget
 class TextLabelWidget : Widget
 {
@@ -53,6 +61,7 @@
 class ContentLabelWidget : Widget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+        debug assert (c, "content is null (code error)");
         WDCheck (data, 3, 0);
         content = c;
         index = data.ints[1];
--- a/mde/gui/widget/layout.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/gui/widget/layout.d	Thu Oct 23 17:45:49 2008 +0100
@@ -100,16 +100,19 @@
     this (IWidgetManager mgr, widgetID id, WidgetData data) {
         debug scope (failure)
                 logger.warn ("TrialContentLayoutWidget: failure");
-        WDCheck (data, 2, 1);
+        WDCheck (data, 2, 2);
         
-        OptionList optsList = OptionList.trial();
-        rows = optsList.list.length;
+        OptionList optsList = new OptionList(data.strings[1]);
         cols = 1;
-        
-        // Get all sub-widgets
-        subWidgets.length = rows*cols;
-        foreach (i, c; optsList.list) {
-            subWidgets[i] = mgr.makeWidget (data.strings[0], c);
+        if ((rows = optsList.list.length) > 0) {
+            // Get all sub-widgets
+            subWidgets.length = rows*cols;
+            foreach (i, c; optsList.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"))];
         }
         super (mgr, id, data);
     }
@@ -176,7 +179,6 @@
      *
      * As such, this must be the first function called after this(). */
     void finalize () {
-        logger.trace ("initWidths.length: {}", initWidths.length);
         if (initWidths.length == cols + rows) {
             col.setWidths (initWidths[0..cols]);
             row.setWidths (initWidths[cols..$]);
--- a/mde/lookup/Options.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/lookup/Options.d	Thu Oct 23 17:45:49 2008 +0100
@@ -30,6 +30,8 @@
 import mde.setup.paths;
 import mde.exception;
 
+public import mde.gui.content.Content;
+
 import mde.file.mergetag.Reader;
 import mde.file.mergetag.Writer;
 import mde.file.mergetag.DataSet;
@@ -52,7 +54,7 @@
 * 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 OptionsMisc as a template for creating a new Options sub-class.
+* 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,
@@ -79,7 +81,7 @@
     // 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; }
-    alias store!(bool, int, double, char[]) TYPES;
+    alias store!(int, double, char[]) TYPES;//FIXME removed bool
     //BEGIN Templates: internal
     private {
         // Get name of a type. Basically just stringof, but special handling for arrays.
@@ -92,11 +94,14 @@
         }
         
         // Pointer lists
-        template PLists(T, A...) {
+        template PLists(A...) {
             static if (A.length) {
-                const char[] PLists = T.stringof~"*[ID] opts"~TName!(T)~";\n" ~ PLists!(A);
+                static if (is (T == bool)) {
+                    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 = T.stringof~"*[ID] opts"~TName!(T)~";\n";
+                const char[] PLists = "";
         }
         
         // True if type is one of A
@@ -112,10 +117,19 @@
         
         // For addTag
         template addTagMixin(T, A...) {
-            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 (is(T == bool)) {
+                const char[] ifBlock = `if (tp == "`~T.stringof~`") {
+    auto p = id in opts;
+    if (p) {
+        auto q = cast(BoolContent) (*p);
+        if (q) q = 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
@@ -130,6 +144,14 @@
             } else
                 const char[] writeAllMixin = ``;
         }
+        
+        // For list
+        template listMixin(A...) {
+            static if (A.length) {
+                const char[] listMixin = `ret ~= opts`~TName!(A[0])~`.keys;` ~ listMixin!(A[1..$]);
+            } else
+                const char[] listMixin = ``;
+        }
     }
     //END Templates: internal
     
@@ -144,6 +166,7 @@
             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;
         }
     
@@ -233,7 +256,7 @@
      * via hash-maps, which is a little slower than direct access but necessary since the option
      * must be changed in two separate places. */
     void set(T) (char[] symbol, T val) {
-        static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof);
+        static assert (TIsIn!(T,TYPES) && !is(T == bool), "Options does not support type "~T.stringof);
         
         changed = true;     // something got set (don't bother checking this isn't what it already was)
         
@@ -245,7 +268,7 @@
             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.  */
@@ -260,25 +283,30 @@
             // log and ignore:
             logger.error ("Options.get: invalid symbol");
         }
-    }
+    }+/
     
     /** List the names of all options of a specific type. */
-    char[][] list(T) () {
-        static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof);
-        
-        mixin (`alias opts`~TName!(T)~` optsVars;`);
-        
-        return optsVars.keys;
+    char[][] list () {
+        char[][] ret;
+        mixin (listMixin!(TYPES));
+        return ret;
+    }
+    
+    /// Get all Options stored with a ValueContent.
+    ValueContent[char[]] content() {
+        return opts;
     }
     
     /// Variable validate function. This implementation does nothing.
     void validate() {}
     
     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));
+        mixin (PLists!(TYPES)); //FIXME adds unused optsbool
+        ValueContent[char[]] opts;      // generic list of option values
     }
     
     //BEGIN Mergetag loading/saving code
@@ -295,6 +323,19 @@
     
     //BEGIN Templates: impl & optionsThis
     private {
+        // Replace, e.g., bool, with BoolContent
+        template contentName(A) {
+            static if (is(A == bool)) {
+                const char[] contentName = "BoolContent";
+            } else static if (is(A == int)) {
+                const char[] contentName = "int";// no IntContent yet
+            } else static if (is(A == double)) {
+                const char[] contentName = "double";
+            } else static if (is(A == char[])) {
+                const char[] contentName = "char[]";
+            } else
+                static assert (false, "unsuppurted type: "~ A);
+        }
         // Return index of first comma, or halts if not found.
         template cIndex(char[] A) {
             static if (A.length == 0)
@@ -313,9 +354,10 @@
             else
                 const size_t scIndex = 1 + scIndex!(A[1..$]);
         }
-        // Look for "type symbols;" in A and return symbols as a comma separated list of names
-        // (even if type is encountered more than once). Output may contain spaces and, if
-        // non-empty, will contain a trailing comma. Assumes scIndex always returns less than A.$.
+        /* Look for "type symbols;" in A and return symbols as a comma separated list of names
+         (even if type is encountered more than once). Output may contain spaces and will have a
+         trailing comma unless no match was found in which case an empty string is returned.
+         Assumes scIndex always returns less than A.$ . */
         template parseT(char[] type, char[] A) {
             static if (A.length < type.length + 1)	// end of input, no match
                 const char[] parseT = "";
@@ -327,7 +369,7 @@
             else					// no match
                 const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]);
         }
-        // May have a trailing comma. Assumes cIndex always returns less than A.$.
+        // May have a trailing comma. Assumes cIndex always returns less than A.$ .
         template aaVars(char[] A) {
             static if (A.length == 0)
                 const char[] aaVars = "";
@@ -337,6 +379,16 @@
                 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 aaVarsBool(char[] A) {//FIXME
+            static if (A.length == 0)
+            const char[] aaVarsBool = "";
+            else static if (A[0] == ' ')
+                const char[] aaVarsBool = aaVarsBool!(A[1..$]);
+            else
+                const char[] aaVarsBool = "\""~A[0..cIndex!(A)]~"\"[]:"~A[0..cIndex!(A)] ~ "," ~
+                aaVarsBool!(A[cIndex!(A)+1..$]);
+        }
         // strip Trailing Comma
         template sTC(char[] A) {
             static if (A.length && A[$-1] == ',')
@@ -353,19 +405,54 @@
             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)
+                const char[] catOrNothing = A~` `~sTC!(B)~";\n";
+            else
+                const char[] catOrNothing = ``;
+        }
+        // foreach decl...
+        template createBCs(char[] A) {
+            static if (A.length == 0)
+                const char[] createBCs = "";
+            else static if (A[0] == ' ')
+                const char[] createBCs = createBCs!(A[1..$]);
+            else
+                const char[] createBCs = A[0..cIndex!(A)]~ " = new BoolContent (false);\n"~
+                createBCs!(A[cIndex!(A)+1..$]);
+        }
         // for recursing on TYPES
         template optionsThisInternal(char[] A, B...) {
             static if (B.length) {
+                static if (is(B[0] == bool)) {//FIXME
+                    const char[] optionsThisInternal = createBCs!(parseT!(B[0].stringof,A))~
+                    `opts = `~listOrNull!(sTC!(aaVarsBool!(parseT!(B[0].stringof,A))))~";\n" ~
+                    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 = ``;
         }
+        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..$]);
+            } else
+                const char[] declValsInternal = ``;
+        }
     } protected {
+        /** Declares the values.
+         *
+         * Basic types are replaced with a ValueContent class to keep the option synchronized and
+         * generalize use. */
+        template declVals(char[] A) {
+            const char[] declVals = declValsInternal!(A, TYPES,bool);
+        }
         /** Produces the implementation code to go in the constuctor. */
         template optionsThis(char[] A) {
             const char[] optionsThis =
                     "optionChanges = new OptionChanges;\n" ~
-                    optionsThisInternal!(A,TYPES);
+                    optionsThisInternal!(A,TYPES,bool);
         }
         /+ Needs too many custom parameters to be worth it? Plus makes class less readable.
         /** Produces the implementation code to go in the static constuctor. */
@@ -381,7 +468,7 @@
          *
          * In case this() needs to be customized, mixin(impl!(A)) is equivalent to:
          * ---
-         * mixin (A~`
+         * mixin (declVals!(A)~`
         this () {
     	`~optionsThis!(A)~`
 	}`);
@@ -395,7 +482,7 @@
          * 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 = A~"\nthis(){\n"~optionsThis!(A)~"}";
+            const char[] impl = declVals!(A)~"\nthis(){\n"~optionsThis!(A)~"}";
             // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}"
         }
     }
@@ -466,9 +553,11 @@
 */
 
 /** A home for all miscellaneous options, at least for now. */
-OptionsMisc miscOpts;
-class OptionsMisc : Options {
-    mixin (impl!("bool exitImmediately; int maxThreads, logOptions; double pollInterval; char[] L10n, a,b,c,g,z;"));
+MiscOptions miscOpts;
+class MiscOptions : Options {
+    const A = "bool exitImmediately; int maxThreads, logOptions; double pollInterval; char[] L10n;";
+    //pragma (msg, impl!(A));
+    mixin (impl!(A));
     
     void validate() {
         // Try to enforce sensible values, whilst being reasonably flexible:
@@ -481,7 +570,7 @@
     }
     
     static this() {
-        miscOpts = new OptionsMisc;
-        Options.addOptionsClass (miscOpts, "misc");
+        miscOpts = new MiscOptions;
+        Options.addOptionsClass (miscOpts, "MiscOptions");
     }
 }
--- a/mde/setup/Init.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/setup/Init.d	Thu Oct 23 17:45:49 2008 +0100
@@ -182,7 +182,7 @@
         }
         
         // a debugging option:
-        imde.run = !args.contains("q") && !miscOpts.exitImmediately;
+        imde.run = !args.contains("q") && !miscOpts.exitImmediately();
         debug logger.trace ("Init: applied pre-init options");
         
         //BEGIN Load dynamic libraries
--- a/mde/setup/Screen.d	Tue Oct 21 11:35:15 2008 +0100
+++ b/mde/setup/Screen.d	Thu Oct 23 17:45:49 2008 +0100
@@ -48,7 +48,7 @@
     }
     
     /** All video options. */
-    class OptionsVideo : Options {
+    class VideoOptions : Options {
         mixin (impl!("bool fullscreen,hardware,resizable,noFrame; int screenW,screenH,windowW,windowH;"));
     }
     
@@ -75,19 +75,19 @@
         //BEGIN Create window and initialize OpenGL
         // Window creation flags and size
         flags = SDL_OPENGL;
-        if (vidOpts.hardware) flags |= SDL_HWSURFACE | SDL_DOUBLEBUF;
+        if (videoOpts.hardware()) flags |= SDL_HWSURFACE | SDL_DOUBLEBUF;
         else flags |= SDL_SWSURFACE;
         int w, h;
-        if (vidOpts.fullscreen) {
+        if (videoOpts.fullscreen()) {
             flags |= SDL_FULLSCREEN;
-            w = vidOpts.screenW;
-            h = vidOpts.screenH;
+            w = videoOpts.screenW;
+            h = videoOpts.screenH;
         }
         else {
-            if (vidOpts.resizable) flags |= SDL_RESIZABLE;
-            if (vidOpts.noFrame) flags |= SDL_NOFRAME;
-            w = vidOpts.windowW;
-            h = vidOpts.windowH;
+            if (videoOpts.resizable()) flags |= SDL_RESIZABLE;
+            if (videoOpts.noFrame()) flags |= SDL_NOFRAME;
+            w = videoOpts.windowW;
+            h = videoOpts.windowH;
         }
         
         // OpenGL attributes
@@ -155,12 +155,12 @@
     /** Called when a resize event occurs (when the window manager resizes the window). */
     void resizeEvent (int w, int h) {
         // Save new size to config
-        if (vidOpts.fullscreen) {       // probably resizeEvent only called when not fullscreen
-            vidOpts.set!(int) ("screenW", w);
-            vidOpts.set!(int) ("screenH", h);
+        if (videoOpts.fullscreen()) {       // probably resizeEvent only called when not fullscreen
+            videoOpts.set!(int) ("screenW", w);
+            videoOpts.set!(int) ("screenH", h);
         } else {
-            vidOpts.set!(int) ("windowW", w);
-            vidOpts.set!(int) ("windowH", h);
+            videoOpts.set!(int) ("windowW", w);
+            videoOpts.set!(int) ("windowH", h);
         }
         
         if (setWindow (w,h))
@@ -262,8 +262,8 @@
     static this() {
         logger = Log.getLogger ("mde.setup.Screen");
         
-        vidOpts = new OptionsVideo;
-        Options.addOptionsClass (vidOpts, "video");
+        videoOpts = new VideoOptions;
+        Options.addOptionsClass (videoOpts, "VideoOptions");
     }
     
     // DATA:
@@ -271,5 +271,5 @@
     uint flags = 0;
     IDrawable[] drawables;
     Logger logger;
-    OptionsVideo vidOpts;
+    VideoOptions videoOpts;
 }