diff mde/lookup/Options.d @ 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 97e6dce08037
children 2a364c7d82c9
line wrap: on
line diff
--- 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");
     }
 }