view mde/options.d @ 16:9cb7b9310168

Improvements to Options and Init. Revamped Options with sections and auto saving/loading. Moved some of init's functions outside the module. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 15 Mar 2008 11:56:13 +0000
parents 4608be19ebe2
children 5f90774ea1ef
line wrap: on
line source

/** 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.
*/
module mde.options;

import mde.exception;

import mde.mergetag.Reader;
import mde.mergetag.Writer;
import mde.mergetag.DataSet;
import mde.mergetag.exception;
import mde.resource.paths;

import tango.scrapple.text.convert.parseTo : parseTo;
import tango.scrapple.text.convert.parseFrom : parseFrom;

import tango.util.log.Log : Log, Logger;

/** Base class for handling options. This class itself will handle no options and should not be
* instantiated, but sub-classes handle options.
*
* Each sub-class provides named variables for maximal-speed reading & writing. Sub-class references
* are stored by name, and will be available after pre-init has run (DO NOT access before this).
*
* The static class keeps track of all Options class instances for global loading and saving.
*
* 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).
*/
class Options : IDataSection
{
    // No actual options are stored by this class. However, much of the infrastructure is
    // present since it need not be redefined in sub-classes.
    
    // The "pointer lists":
    protected bool*  [ID]   optsBool;
    protected char[]*[ID]   optsCharA;
    protected int*   [ID]   optsInt;
    
    //BEGIN Mergtag loading/saving code
    void addTag (char[] tp, ID id, char[] dt) {
        if (tp == "bool") {
            bool** p = id in optsBool;
            if (p !is null) **p = parseTo!(bool) (dt);
        } else if (tp == "char[]") {
            char[]** p = id in optsCharA;
            if (p !is null) **p = parseTo!(char[]) (dt);
        } else if (tp == "int") {
            int** p = id in optsInt;
            if (p !is null) **p = parseTo!(int) (dt);
        }
    }
    void writeAll (ItemDelg dlg) {
        foreach (ID id, bool*   val; optsBool)  dlg ("bool"  , id, parseFrom!(bool  ) (*val));
        foreach (ID id, char[]* val; optsCharA) dlg ("char[]", id, parseFrom!(char[]) (*val));
        foreach (ID id, int*    val; optsInt)   dlg ("int"   , id, parseFrom!(int   ) (*val));
    }
    //END Mergtag loading/saving code
        
    //BEGIN Static
    // Each individual section
    static OptionsMisc  misc;
    
    /* Load/save options from file.
    *
    * If the file doesn't exist, no reading is attempted (options are left at default values).
    */
    private static const fileName = "options";
    static void load () {
        // Create all uncreated sections now, so that if we return early they are still created.
        if (misc is null) misc = new OptionsMisc;
        
        // Check it exists (if not it should still be created on exit).
        // Don't bother checking it's not a folder, because it could still be a block or something.
        if (!confDir.exists (fileName)) return;
        
        IReader reader;
        try {
            reader = confDir.makeMTReader (fileName, PRIORITY.LOW_HIGH);
            reader.dataSecCreator = delegate IDataSection(ID id) {
                /* Recognise each defined section, and return null for unrecognised sections. */
                
                if (id == cast(ID) "misc") return misc;
                else return null;
            };
            reader.read;
        } catch (MTException e) {
            logger.error ("Mergetag exception occurred:");
            logger.error (e.msg);
            throw new optionsLoadException ("Loading aborted: mergetag exception");
        }
        
        if (misc is null) throw new optionsLoadException ("Loading failed: section \"misc\" not found");
    }
    static void save () {
        DataSet ds = new DataSet();
        ds.sec[cast(ID) "misc"] = misc;
        
        IWriter writer;
        try {
            writer = confDir.makeMTWriter (fileName, ds);
            writer.write();
        } catch (MTException e) {
            logger.error ("Mergetag exception occurred; saving aborted:");
            logger.error (e.msg);
            //FIXME: currently nothing done besides logging the error
        }
    }
    
    private static Logger logger;
    static this() {
        logger = Log.getLogger ("mde.options");
    }
    //END Static
}

/** A home for all miscellaneous options, at least for now. */
class OptionsMisc : Options {
    bool useThreads;    // set 0 to disable threading
    char[] L10n;        // locale, e.g. en-GB
    int logLevel;       // tango logger level
    
    this () {
        optsBool    = ["useThreads":&useThreads];
        optsCharA   = ["L10n":&L10n];
        optsInt     = ["logLevel":&logLevel];
    }
}