changeset 98:49e7cfed4b34

All types of Option have been converted to use ValueContent classes, and their values can be displayed.
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 12 Nov 2008 13:18:51 +0000
parents 30470bc19ca4
children 5de5810e3516
files data/L10n/MiscOptions.mtt data/conf/gui.mtt data/conf/options.mtt examples/guiDemo.d mde/font/FontTexture.d mde/font/font.d mde/gui/content/Content.d mde/gui/widget/createWidget.d mde/lookup/Options.d mde/lookup/Translation.d mde/mde.d mde/setup/Init.d mde/setup/Screen.d
diffstat 13 files changed, 172 insertions(+), 120 deletions(-) [+]
line wrap: on
line diff
--- a/data/L10n/MiscOptions.mtt	Mon Nov 10 16:44:44 2008 +0000
+++ b/data/L10n/MiscOptions.mtt	Wed Nov 12 13:18:51 2008 +0000
@@ -1,7 +1,8 @@
 {MT01}
 {en-GB}
 <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:"Controls which messages are logged, from 0=trace to 6=none (default: 1=info)."}>
+<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"}>
 <entry|L10n={0:"Localisation",1:"Specifies the language to use."}>
 <entry|pollInterval={0:"Polling interval",1:"Delay in main loop to limit CPU usage"}>
 <entry|exitImmediately={0:"Exit immediately",1:"Load files and exit immediately, without running main loop (for debugging)"}>
--- a/data/conf/gui.mtt	Mon Nov 10 16:44:44 2008 +0000
+++ b/data/conf/gui.mtt	Wed Nov 12 13:18:51 2008 +0000
@@ -7,7 +7,7 @@
 <WidgetData|content={0:[0xC100,0,3,1],1:["floating","opts","blank"]}>
 <WidgetData|button={0:[0x10,50,50]}>
 <WidgetData|blank={0:[0x2]}>
-<WidgetData|opts={0:[0x8110,0],1:["optDBox","VideoOptions"]}>
+<WidgetData|opts={0:[0x8110,0],1:["optDBox","MiscOptions"]}>
 <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]}>
@@ -16,8 +16,8 @@
 <WidgetData|optSep={0:[0x21, 0xff],1:["="]}>
 <WidgetData|floating={0:[0x8200,1,6,14],1:["text","button","blank"]}>
 <WidgetData|text={0:[0x21,0xE0E000],1:["Floating text"]}>
-<WDims|root=[6,590,196,6,580,6]>
-<WDims|content=[590,190,364,18]>
-<WDims|floating=[25,21,103,27,437,23,74,74,217,90,123,100]>
+<WDims|content=[736,221,272,79]>
+<WDims|root=[6,736,50,6,580,6]>
+<WDims|floating=[25,0,103,27,572,26,74,74,242,128,168,93]>
 {Basic}
 <WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}>
--- a/data/conf/options.mtt	Mon Nov 10 16:44:44 2008 +0000
+++ b/data/conf/options.mtt	Wed Nov 12 13:18:51 2008 +0000
@@ -3,7 +3,8 @@
 <int|maxThreads=1>
 <bool|exitImmediately=false>
 <char[]|L10n="en-GB">
-<int|logOptions=0x3000>
+<int|logLevel=1>
+<int|logOutput=3>
 <double|pollInterval=0.01>
 
 {FontOptions}
--- a/examples/guiDemo.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/examples/guiDemo.d	Wed Nov 12 13:18:51 2008 +0000
@@ -57,10 +57,6 @@
     
     scope Init init = new Init(args);	// initialize mde
     
-    // Make sure pollInterval has a sane value. FIXME: get Options class to enforce range
-    if (miscOpts.pollInterval !<= 1.0 || miscOpts.pollInterval !>= 0.0)
-        miscOpts.set!(double) ("pollInterval", 0.01);
-    
     //BEGIN Main loop setup
     /* Note: the main loop is currently controlled by the scheduler. This is not really ideal,
      * since it provides no direct control of the order in which components are executed and does
@@ -74,7 +70,7 @@
     while (run) {
         mainSchedule.execute (Clock.now());
         
-        Thread.sleep (miscOpts.pollInterval);	// sleep this many seconds
+        Thread.sleep (miscOpts.pollInterval());	// sleep this many seconds
     }
     
     return 0;		// cleanup handled by init's DTOR
--- a/mde/font/FontTexture.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/font/FontTexture.d	Wed Nov 12 13:18:51 2008 +0000
@@ -229,7 +229,7 @@
         auto g = face.glyph;
         
         // Use renderMode from options, masking bits which are allowable:
-        if (FT_Load_Glyph (face, gi, FT_LOAD_RENDER | (fontOpts.renderMode & 0xF0000)))
+        if (FT_Load_Glyph (face, gi, FT_LOAD_RENDER | (fontOpts.renderMode() & 0xF0000)))
             throw new fontGlyphException ("Unable to render glyph");
         
         auto b = g.bitmap;
@@ -280,7 +280,7 @@
                 buffer[i*b.width + j + 2] = b.buffer[i*b.pitch + j + 2];
             }
             
-            format = (fontOpts.renderMode & RENDER_LCD_BGR) ? GL_BGR : GL_RGB;
+            format = (fontOpts.renderMode() & RENDER_LCD_BGR) ? GL_BGR : GL_RGB;
         } else if (b.pixel_mode == FT_Pixel_Mode.FT_PIXEL_MODE_LCD_V) {
             // NOTE: Notes above apply. But in this case converting the buffers seems essential.
             buffer = new ubyte[b.width*b.rows];
@@ -292,7 +292,7 @@
                 buffer[i/3*b.width*3 + 3*j + i%3] = b.buffer[i*b.pitch + j];
             }
             
-            format = (fontOpts.renderMode & RENDER_LCD_BGR) ? GL_BGR : GL_RGB;
+            format = (fontOpts.renderMode() & RENDER_LCD_BGR) ? GL_BGR : GL_RGB;
         } else
             throw new fontGlyphException ("Unsupported freetype bitmap format");
         
--- a/mde/font/font.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/font/font.d	Wed Nov 12 13:18:51 2008 +0000
@@ -71,19 +71,19 @@
             
             // Set LCD filtering method if LCD rendering is enabled.
             const RMF = FT_LOAD_TARGET_LCD | FT_LOAD_TARGET_LCD_V;
-            if (fontOpts.renderMode & RMF &&
-                FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter)) {
+            if (fontOpts.renderMode() & RMF &&
+                FT_Library_SetLcdFilter(library, cast(FT_LcdFilter)fontOpts.lcdFilter())) {
                 /* An error occurred, presumably because LCD rendering support
                 * is not compiled into the library. */
                 logger.warn ("Bad/unsupported LCD filter option; disabling LCD font rendering.");
                 logger.warn ("Your FreeType 2 library may be compiled without support for LCD/sub-pixel rendering.");
                 
                 // Reset the default filter (in case an invalid value was set in config files).
-                fontOpts.set!(int) ("lcdFilter", FT_LcdFilter.FT_LCD_FILTER_DEFAULT);
+                fontOpts.lcdFilter = FT_LcdFilter.FT_LCD_FILTER_DEFAULT;
                 
                 /* If no support for LCD filtering, then LCD rendering only emulates NORMAL with 3
                  * times wider glyphs. So disable and save the extra work. */
-                fontOpts.set!(int) ("renderMode", FT_LOAD_TARGET_NORMAL);
+                fontOpts.renderMode = FT_LOAD_TARGET_NORMAL;
             }
             
             /* Load font settings
--- a/mde/gui/content/Content.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/gui/content/Content.d	Wed Nov 12 13:18:51 2008 +0000
@@ -17,7 +17,9 @@
  */
 module mde.gui.content.Content;
 
+//FIXME: efficient conversions? Need to dup result when formatting a string anyway?
 import Int = tango.text.convert.Integer;
+import Float = tango.text.convert.Float;
 
 debug {
     import tango.util.log.Log : Log, Logger;
@@ -92,6 +94,19 @@
     char[] name_, desc_;// name and description, loaded by lookup.Translation
 }
 
+template VContentN(T) {
+    static if (is(T == bool)) {
+        const char[] VContentN = "BoolContent";
+    } else static if (is(T == int)) {
+        const char[] VContentN = "IntContent";
+    } else static if (is(T == double)) {
+        const char[] VContentN = "DoubleContent";
+    } else static if (is(T == char[])) {
+        const char[] VContentN = "TextContent";
+    } else
+        static assert (false, "No ValueContent of type "~T.stringof);
+}
+
 class BoolContent : ValueContent
 {
     /** Create a content with _symbol name symbol. */
@@ -122,6 +137,7 @@
     bool opCall () {
         return v;
     }
+    alias opCall opCast;
     
     bool v;     //FIXME: should be protected but Options needs to set without calling callbacks
 protected:
@@ -132,67 +148,115 @@
 /** Text content. */
 class TextContent : ValueContent
 {
-    this () {}
-    this (char[] text, char[] name = null) {
-        text_ = text;
-        name_ = name;
-    }
-    /+
-    ContentText dup () {
-        return new ContentText (text_);
+    this (char[] symbol, char[] val = null) {
+        symb = symbol;
+        v = val;
     }
     
-    ContentText toText () {
+    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
+    TextContent addChangeCb (void delegate (char[] symbol,char[] value) cb) {
+        cngCb ~= cb;
         return this;
     }
-    ContentInt  toInt () {
-        // FIXME: convert
-        return null;
-    }
-    +/
     
     /// Get the text.
     char[] toString (uint i) {
-        debug logger.trace ("TextContent.toString");
-        return (i == 0) ? text_
+        return (i == 0) ? v
             : (i == 1) ? name_
             : (i == 2) ? desc_
             : null;
     }
     
+    void opAssign (char[] val) {
+        v = val;
+        foreach (cb; cngCb)
+            cb(symb, val);
+    }
+    char[] opCall () {
+        return v;
+    }
+    alias opCall opCast;
+    
+    char[] v;
 protected:
-    char[] text_;
+    char[] symb;
+    void delegate (char[],char[])[] cngCb;
 }
-/+
+
 /** Integer content. */
-class ContentInt : IContent
+class IntContent : ValueContent
 {
-    this () {}
-    this (int integer) {
-        int_ = integer;
+    /** Create a content with _symbol name symbol. */
+    this (char[] symbol, int val = 0) {
+        symb = symbol;
+        v = val;
     }
     
-    ContentInt dup () {
-        return new ContentInt (int_);
-    }
-    
-    ContentText toText () {
-        return new ContentText(toString);
-    }
-    ContentInt  toInt () {
+    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
+    IntContent addChangeCb (void delegate (char[] symbol,int value) cb) {
+        cngCb ~= cb;
         return this;
     }
     
-    /// Get the integer.
-    int integer () {
-        return int_;
+    /// Get the text.
+    char[] toString (uint i) {
+        return (i == 0) ? Int.toString (v)
+        : (i == 1) ? name_
+        : (i == 2) ? desc_
+        : null;
     }
     
-    char[] toString () {
-        return Int.toString (int_);
+    void opAssign (int val) {
+        v = val;
+        foreach (cb; cngCb)
+            cb(symb, val);
+    }
+    int opCall () {
+        return v;
+    }
+    alias opCall opCast;
+    
+    int v;
+protected:
+    char[] symb;
+    void delegate (char[],int)[] cngCb;
+}
+
+/** Double content. */
+class DoubleContent : ValueContent
+{
+    /** Create a content with _symbol name symbol. */
+    this (char[] symbol, double val = 0) {
+        symb = symbol;
+        v = val;
     }
     
+    /** Adds cb to the list of callback functions called when the value is changed. Returns this. */
+    DoubleContent addChangeCb (void delegate (char[] symbol,double value) cb) {
+        cngCb ~= cb;
+        return this;
+    }
+    
+    /// Get the text.
+    char[] toString (uint i) {
+        return (i == 0) ? Float.toString (v)
+        : (i == 1) ? name_
+        : (i == 2) ? desc_
+        : null;
+    }
+    
+    void opAssign (double val) {
+        v = val;
+        foreach (cb; cngCb)
+            cb(symb, val);
+    }
+    double opCall () {
+        return v;
+    }
+    alias opCall opCast;
+    
+    double v;
 protected:
-    int int_;
+    char[] symb;
+    void delegate (char[],double)[] cngCb;
 }
-+/
\ No newline at end of file
--- a/mde/gui/widget/createWidget.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/gui/widget/createWidget.d	Wed Nov 12 13:18:51 2008 +0000
@@ -103,6 +103,7 @@
     
     // content editables: 0x30
     editContent             = FUNCTION | TAKES_CONTENT | 0x30,
+    DisplayContent          = TAKES_CONTENT | 0x30,
     BoolContent             = TAKES_CONTENT | 0x31,
     
     GridLayout              = TAKES_CONTENT | PARENT | 0x100,
@@ -121,6 +122,7 @@
         "Button",
         "TextLabel",
         "ContentLabel",
+        "DisplayContent",
         "BoolContent",
         "editContent",
         "TrialContentLayout",
--- a/mde/lookup/Options.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/lookup/Options.d	Wed Nov 12 13:18:51 2008 +0000
@@ -81,8 +81,9 @@
     // 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[]) TYPES2;
+    // 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.
@@ -97,7 +98,7 @@
         // Pointer lists
         template PLists(A...) {
             static if (A.length) {
-                static if (is (T == bool)) {
+                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..$]);
@@ -118,11 +119,11 @@
         
         // For addTag
         template addTagMixin(T, A...) {
-            static if (is(T == bool)) {
+            static if (TIsIn!(T, CTYPES)) {
                 const char[] ifBlock = `if (tp == "`~T.stringof~`") {
     auto p = id in opts;
     if (p) {
-        auto q = cast(BoolContent) (*p);
+        auto q = cast(`~VContentN!(T)~`) (*p);
         if (q) q.v = parseTo!(`~T.stringof~`) (dt);
     }
 }`;
@@ -140,7 +141,10 @@
         // For list
         template listMixin(A...) {
             static if (A.length) {
-                const char[] listMixin = `ret ~= opts`~TName!(A[0])~`.keys;` ~ listMixin!(A[1..$]);
+                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 = ``;
         }
@@ -248,7 +252,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) && !is(T == bool), "Options does not support type "~T.stringof);
+        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)
         
@@ -280,7 +284,7 @@
     /** List the names of all options of a specific type. */
     char[][] list () {
         char[][] ret;
-        mixin (listMixin!(TYPES2));
+        mixin (listMixin!(TYPES));
         return ret;
     }
     
@@ -297,7 +301,7 @@
         OptionChanges optionChanges;	// all changes to options (for saving)
     	
         // The "pointer lists", e.g. char[]*[ID] optscharA;
-        mixin (PLists!(TYPES2)); //FIXME
+        mixin (PLists!(TYPES));
         ValueContent[char[]] opts;      // generic list of option values
     }
     
@@ -315,16 +319,10 @@
     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[]";
+            static if (TIsIn!(A, CTYPES)) {
+                const char[] contentName = VContentN!(A);
             } else
-                static assert (false, "unsuppurted type: "~ A);
+                const char[] contentName = A.stringof;
         }
         // Return index of first comma, or halts if not found.
         template cIndex(char[] A) {
@@ -370,14 +368,14 @@
                         aaVars!(A[cIndex!(A)+1..$]);
         }
         // May have a trailing comma. Assumes cIndex always returns less than A.$ .
-        template aaVarsBool(char[] A) {//FIXME
+        template aaVarsContent(char[] A) {//FIXME
             static if (A.length == 0)
-            const char[] aaVarsBool = "";
+            const char[] aaVarsContent = "";
             else static if (A[0] == ' ')
-                const char[] aaVarsBool = aaVarsBool!(A[1..$]);
+                const char[] aaVarsContent = aaVarsContent!(A[1..$]);
             else
-                const char[] aaVarsBool = "\""~A[0..cIndex!(A)]~"\"[]:"~A[0..cIndex!(A)] ~ "," ~
-                aaVarsBool!(A[cIndex!(A)+1..$]);
+                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) {
@@ -403,21 +401,20 @@
                 const char[] catOrNothing = ``;
         }
         // foreach decl...
-        template createBCs(char[] A) {
+        template createContents(T, char[] A) {
             static if (A.length == 0)
-                const char[] createBCs = "";
+                const char[] createContents = "";
             else static if (A[0] == ' ')
-                const char[] createBCs = createBCs!(A[1..$]);
+                const char[] createContents = createContents!(T,A[1..$]);
             else
-                const char[] createBCs = A[0..cIndex!(A)]~ ` = (new BoolContent ("`~A[0..cIndex!(A)]~"\")).addChangeCb (&optionChanges.set);\n"~
-                createBCs!(A[cIndex!(A)+1..$]);
+                const char[] createContents = "opts[\""~A[0..cIndex!(A)]~"\"] = " ~ A[0..cIndex!(A)]~ " = (new "~VContentN!(T)~" (\""~A[0..cIndex!(A)]~"\")).addChangeCb (&optionChanges.set);\n"~
+                createContents!(T,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" ~
+                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..$]);
@@ -557,18 +554,18 @@
 /** A home for all miscellaneous options, at least for now. */
 MiscOptions miscOpts;
 class MiscOptions : Options {
-    const A = "bool exitImmediately; int maxThreads, logOptions; double pollInterval; char[] L10n;";
+    const A = "bool exitImmediately; int maxThreads, logLevel, logOutput; double pollInterval; char[] L10n;";
     //pragma (msg, impl!(A));
     mixin (impl!(A));
     
     void validate() {
         // Try to enforce sensible values, whilst being reasonably flexible:
-        if (miscOpts.maxThreads < 1 || miscOpts.maxThreads > 64) {
+        if (maxThreads() < 1 || maxThreads() > 64) {
             logger.warn ("maxThreads must be in the range 1-64. Defaulting to 4.");
-            miscOpts.set!(int)("maxThreads", 4);
+            maxThreads = 4;
         }
-        if (pollInterval !<= 1.0 || pollInterval !>= 0.0)
-            set!(double) ("pollInterval", 0.01);
+        if (pollInterval() !<= 1.0 || pollInterval() !>= 0.0)
+            pollInterval = 0.01;
     }
     
     static this() {
--- a/mde/lookup/Translation.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/lookup/Translation.d	Wed Nov 12 13:18:51 2008 +0000
@@ -113,7 +113,7 @@
         ID[] secsToLoad             // locales/sections to load (dependancies may be added)
         = [cast(ID) miscOpts.L10n];  // start by loading the current locale
         
-        Translation transl = new Translation (name, miscOpts.L10n);
+        Translation transl = new Translation (name, miscOpts.L10n());
         
         IReader reader;
         try {
--- a/mde/mde.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/mde.d	Wed Nov 12 13:18:51 2008 +0000
@@ -36,6 +36,8 @@
 }
 
 //BEGIN A simple drawable to print a message in the window.
+/* This block of code is to draw the message you see on the screen. Most users of mde would be
+better off using the gui drawable in gui.WidgetManager than this. */
 import mde.font.font;
 class SimpleDrawable : Screen.IDrawable {
     this () {
--- a/mde/setup/Init.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/setup/Init.d	Wed Nov 12 13:18:51 2008 +0000
@@ -146,32 +146,21 @@
 	// Set up the logger:
         Logger root;
 	try {
-            enum LOG {
-                LEVEL	= 0xF,		// mask for log level
-                CONSOLE	= 0x1000,	// log to console?
-                ROLLFILE= 0x2000	// use Rolling/Switching File Appender?
-            }
-            
 	    // Where logging is done to is determined at compile-time, currently just via static ifs.
             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 (cast(Level) (miscOpts.logOptions & LOG.LEVEL), true);
+            root.level (miscOpts.logOutput() == 0 ? Level.None : cast(Level) miscOpts.logLevel(), true);
             
-            // Log to a file (first appender so root seperator messages don't show on console):
-            if (miscOpts.logOptions & LOG.ROLLFILE) {
-                // Use 2 log files with a maximum size of 1 MB:
-                root.add (new AppendFiles (paths.logDir~"/log-.txt", 2, 1024*1024));
-                root.info (""); // some kind of separation between runs
-                root.info ("");
-            } else if (!(miscOpts.logOptions & LOG.CONSOLE)) {
-                // make sure at least one logger is enabled
-                miscOpts.set!(int) ("logOptions", miscOpts.logOptions | LOG.CONSOLE);
+            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:
+                root.add (new AppendFiles (paths.logDir~"/log-.txt", 2, 16*1024));
+                root.append (Level.None, ""); // some kind of separation between runs
+                root.append (Level.None, "");
             }
-            if (miscOpts.logOptions & LOG.CONSOLE) {	// Log to the console
+            if (miscOpts.logOutput() & 1)
                 root.add(new AppendConsole);
-            }
             logger.info ("Starting mde [no version] on " ~ TimeStamp.toString(WallClock.now));
         } catch (Exception e) {
             // Presumably it was only adding a file appender which failed; set up a new console
@@ -256,7 +245,7 @@
             }
         }
         // Counts number of active threads, and before threads are started is number to use:
-        size_t numWorking = (toRun.size < miscOpts.maxThreads) ? toRun.size : miscOpts.maxThreads;
+        size_t numWorking = (toRun.size < miscOpts.maxThreads()) ? toRun.size : miscOpts.maxThreads();
         enum STATE {    WORKING = 0,    DONE = 1,       ABORT = 2 }
         STATE doneInit = STATE.WORKING;
         Mutex toRunM = new Mutex;       // synchronization on toRun, numWorking
@@ -381,7 +370,7 @@
         } catch (ThreadException e) {
             logger.error ("Exception while using threads: "~e.msg);
             logger.error ("Disabling threads and attempting to continue.");
-            miscOpts.set!(int)("NumThreads", 1);        // count includes current thread
+            miscOpts.maxThreads = 1;    // count includes current thread
             auto x = new InitStageThread (0);
             x.initThreadFct();                            // try with just this thread
         }       // any other exception will be caught in main() and abort program
--- a/mde/setup/Screen.d	Mon Nov 10 16:44:44 2008 +0000
+++ b/mde/setup/Screen.d	Wed Nov 12 13:18:51 2008 +0000
@@ -82,14 +82,14 @@
         int w, h;
         if (videoOpts.fullscreen()) {
             flags |= SDL_FULLSCREEN;
-            w = videoOpts.screenW;
-            h = videoOpts.screenH;
+            w = videoOpts.screenW();
+            h = videoOpts.screenH();
         }
         else {
             if (videoOpts.resizable()) flags |= SDL_RESIZABLE;
             if (videoOpts.noFrame()) flags |= SDL_NOFRAME;
-            w = videoOpts.windowW;
-            h = videoOpts.windowH;
+            w = videoOpts.windowW();
+            h = videoOpts.windowH();
         }
         
         // OpenGL attributes
@@ -157,11 +157,11 @@
     void resizeEvent (int w, int h) {
         // Save new size to config
         if (videoOpts.fullscreen()) {       // probably resizeEvent only called when not fullscreen
-            videoOpts.set!(int) ("screenW", w);
-            videoOpts.set!(int) ("screenH", h);
+            videoOpts.screenW = w;
+            videoOpts.screenH = h;
         } else {
-            videoOpts.set!(int) ("windowW", w);
-            videoOpts.set!(int) ("windowH", h);
+            videoOpts.windowW = w;
+            videoOpts.windowH = h;
         }
         
         if (setWindow (w,h))