view mde/options.d @ 22:249eb6620685

Changes to Options, particularly regarding window sizes. Window sizes for fullscreen and windowed modes are now independant. Window resizes by the WM are now persistant. Options class contents are now generated by templates. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Tue, 25 Mar 2008 12:24:04 +0000
parents a60cbb7359dd
children 47478557428d
line wrap: on
line source

/* LICENSE BLOCK
Part of mde: a Modular D game-oriented Engine
Copyright © 2007-2008 Diggory Hardy

This program is free software; you can redistribute it and/or modify it under the terms of
the GNU General Public License, version 2, as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

/** 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;
    static OptionsVideo video;
    
    /* 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;
        if (video is null) video = new OptionsVideo;
                
        // 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 if (id == cast(ID) "video") return video;
                else return null;
            };
            reader.read;
        } catch (MTException e) {
            logger.error ("Mergetag exception occurred:");
            logger.error (e.msg);
            throw new optionsLoadException ("Loading aborted: mergetag exception");
        }
    }
    static void save () {
        DataSet ds = new DataSet();
        ds.sec[cast(ID) "misc"] = misc;
        ds.sec[cast(ID) "video"] = video;
        
        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
    
    //BEGIN Templates
    template store(A...) {
        alias A a;
    }
    template decRecurse(char[] A, B...) {
        static if (B.length) const char[] decRecurse = A ~ ", " ~ decRecurse!(B);
        else                 const char[] decRecurse = A;
    }
    template decBool(A...) {
        const char[] decBool = "bool " ~ decRecurse!(A) ~ ";\n";
    }
    template decCharA(A...) {
        const char[] decCharA = "char[] " ~ decRecurse!(A) ~ ";\n";
    }
    template decInt(A...) {
        const char[] decInt = "int " ~ decRecurse!(A) ~ ";\n";
    }
    template aaRecurse(char[] A, B...) {
        static if (B.length) const char[] aaRecurse = "\""~A~"\"[]:&"~A ~ ", " ~ aaRecurse!(B);
        else                 const char[] aaRecurse = "\""~A~"\"[]:&"~A;
    }
    template aaBool(A...) {
        const char[] aaBool = "optsBool = [" ~ aaRecurse!(A) ~ "];\n";
    }
    template aaInt(A...) {
        const char[] aaInt = "optsInt = [" ~ aaRecurse!(A) ~ "];\n";
    }
    template aaCharA(A...) {
        const char[] aaCharA = "optsCharA = [" ~ aaRecurse!(A) ~ "];\n";
    }
    //END Templates
}

/* NOTE: These Options classes use templates to ease inserting contents.
*
* Each entry has an I18nTranslation entry; see data/L10n/ClassName.mtt for descriptions.
*
* To create a new class, copy and paste, and update Options.
*/

/** A home for all miscellaneous options, at least for now. */
class OptionsMisc : Options {
    alias store!("useThreads") BOOL;
    alias store!("logLevel") INT;
    alias store!("L10n") CHARA;
    
    mixin (decBool!(BOOL.a));
    mixin (decInt!(INT.a));
    mixin (decCharA!(CHARA.a));
    
    this () {
        mixin (aaBool!(BOOL.a));
        mixin (aaInt!(INT.a));
        mixin (aaCharA!(CHARA.a));
    }
}

/** All video options. */
class OptionsVideo : Options {
    alias store!("fullscreen","hardware","resizable","noFrame") BOOL;
    alias store!("screenW","screenH","windowW","windowH") INT;
    //alias store!() CHARA;
    
    mixin (decBool!(BOOL.a));
    mixin (decInt!(INT.a));
    //mixin (decCharA!(CHARA.a));
    
    this () {
        mixin (aaBool!(BOOL.a));
        mixin (aaInt!(INT.a));
        //mixin (aaCharA!(CHARA.a));
    }
}