view mde/input/config.d @ 10:4c3575400769

DefaultData largely rewritten with unittest, SDL input event handling completed with unittest, changes to Init threading. Init threads now catch own exceptions. Doc: assigned some inputID devisions. Added support for remaining SDL events. Input axes' output is now stored with a short instead of a real. Input unittest written (for SDL event handling). Rewrote most of mde.mergetag.defaultdata using generic programming to generate read & write rules for all types. As a direct result, defaultdata can now write properly. DefaultData unittest written (also provides testing for mergetag read/write). Moved mde.text.parse/format to tango.scrapple.text.convert.parseTo/parseFrom with many changes. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 18 Feb 2008 11:54:56 +0000
parents 1885a9080f2a
children bff0d802cb7d
line wrap: on
line source

/// This module contains a class for holding configs and handles saving, loading and editing.
module mde.input.config;

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

import mde.input.exception;

import MT = mde.mergetag.read;
import tango.scrapple.text.convert.parseTo : parseTo;

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

private Logger logger;
static this() {
    logger = Log.getLogger ("mde.input.config");
}

/** Class to hold the configuration for the input system. Thus loading and switching between
 *  multiple configurations should be easy.
 *
 * Class extends DataSection so that it can be loaded by mergetag easily.
 */
class Config : MT.DataSection
{
    alias uint[] outQueue;		// This is the type for the out queue config data.
    /** Button event type bit-codes
     *
     *  These bitcodes are OR'd to the identifier code for the input device, to indicate which type
     *  of input they are for. E.g. when a key event is recieved with code x, look up
     *  $(_B _B.SDLKEY) | x in button. Keyboard events are SDL-specific since the codes may differ
     *  for other libraries.
     *
     *  For joystick hat events, a motion is converted into up and down events on separate U,L,D,R
     *  positions and up and down events sent to the appropriate outputs (effectively making four
     *  buttons, some pairs of which can be pressed simultaneously). For output to axes, see
     *  A.JOYHAT_* .
     */
    enum B : uint {
        KEY		= 0x8000_0000u,		/// 0x8000_0000
        SDLKEY		= 0x8800_0000u,		/// 0x8800_0000
        MOUSE		= 0x4000_0000u,		/// 0x4000_0000
        JOYBUTTON	= 0x2000_0000u,		/// 0x2000_0000
        JOYHAT		= 0x1000_0000u,		/// 0x1000_0000
        JOYHAT_U	= 0x1800_0000u,		/// 0x1800_0000
        JOYHAT_D	= 0x1400_0000u,		/// 0x1400_0000
        JOYHAT_L	= 0x1200_0000u,		/// 0x1200_0000
        JOYHAT_R	= 0x1100_0000u,		/// 0x1100_0000
    }
    
    /** Axis event type bit-codes
     *
     *  SDL only supports one type of axis now, but this could be extended in the future.
     *
     *  This can also be used to make joystick hats output to two axes (and can be used in
     *  conjuction with B.JOYHAT_* to output as buttons as well).
    */
    enum A : uint {
        JOYAXIS		= 0x8000_0000u,		/// 0x8000_0000
        JOYHAT		= 0x1000_0000u,		/// 0x1000_0000
        JOYHAT_LR	= 0x1300_0000u,		/// 0x1300_0000
        JOYHAT_UD	= 0x1C00_0000u,		/// 0x1C00_0000
    }
    
    /** Mouse & Joystick ball event type bit-codes
     *
     *  Currently, mouse input only comes from the window manager: the code is exactly M.WMMOUSE.
     */
    enum M : uint {
        MOUSE		= 0x8000_0000u,		/// 0x8000_0000
        WMMOUSE		= 0x8800_0000u,		/// 0x8800_0000
        JOYBALL		= 0x4000_0000u,		/// 0x4000_0000
    }
    
    /** Output queues: the core of the input configuration.
    *
    *  These are all mapped with a uint key; upon any event outQueues are looked for in the
    *  appropriate associative array with a key as follows.
    *
    *  The index is split into two parts;
    *  the first byte specifies the type of input (given by the above enums), and the last three
    *  bytes define where the input comes from.
    *
    *  For type B.SDLKEY, the last three bytes are for the SDL keysym.
    *  For B.MOUSE, B.JOY*, A.JOY* & M.JOY*, the last three bytes are split into two sets of 12
    *  bits (with masks 0x00FF_F000 and 0x0000_0FFF), the higher of which specifies the device
    *  (which mouse or joystick), and the lower of which specifies the button/axis/ball.
    *
    *  For all three types of output, the outQueues are used as follows. The first value in the queue is
    *  read, and a function is called with the event details dependant on this; most of the time an
    *  output function is called directly. Other functions may be used, however, to allow further
    *  functionality such as modifier keys, timed keys, and key sequences.
    *
    *  The output functions all have code 0x100; these read a single item from the outQueue which
    *  is the inputID the event outputs to (i.e. any callbacks at that ID called and status set for
    *  that ID).
    *
    */
    outQueue[][uint] button;
    outQueue[][uint] axis;	/// ditto
    outQueue[][uint] relMotion;	/// ditto
    
    // FIXME:
    char[] name;		/// Name for user to save this under.
    uint[] inheritants;		/// Other profiles to inherit.
    
    static Config[char[]] configs;	/// All configs loaded by load().
    private static TreeBag!(char[]) loadedFiles;	// all filenames load tried to read
    
//BEGIN File loading/saving code
    static this () {
        loadedFiles = new TreeBag!(char[]);
    }
    
    // Load all configs from a file.
    static void load (char[] filename) {
        if (loadedFiles.contains (filename)) return;	// forget it; already done that
        loadedFiles.add (filename);
        
        MT.Reader file;
        
        try {
            file = new MT.Reader(filename, null, true);	// open and read header
            // TODO: also load user-config file
            
            file.dataSecCreator =
            function MT.DataSection (MT.ID) {	return new Config;	};
            
            // D2.0: enum MT.ID CONFIGS = "Configs";
            const MT.ID CONFIGS = cast(MT.ID)"Configs";
            MT.ID[] file_configs;	// active config sections (may not exist)
            MT.ID[]* file_configs_p = cast(MT.ID[]*) (CONFIGS in file.dataset.header._charAA);
            debug foreach (i,d; file.dataset.header._charAA) logger.trace ("ID: "~cast(char[])i);
            debug foreach (i,d; file.dataset.header._charA) logger.trace ("ID: "~cast(char[])i);
                        
            if (file_configs_p)	file.read(*file_configs_p);	// restrict to this set IF a restriction was given
            else		file.read();			// otherwise read all
        }
        catch (MT.MTException) {
            logger.error ("Unable to load configs from: " ~ filename);
            throw new ConfigLoadException;
        }
        
        // Trying to directly cast dataset.sec to configs resulted in the Configs losing their data.
        // Also this is safer since it checks types (and must be done if configs wasn't previously empty).
        foreach (i, sec; file.dataset.sec) {
            Config c = cast(Config) sec;
            if (c) configs[i] = c;		// Check, because we don't want null entries in configs
            else debug logger.warn ("Ended up with DataSection of wrong type; this should never happen.");
        }
        
        debug {
            char tmp[128] = void;
            logger.trace (logger.format (tmp, "Loaded {} config sections.", configs.length));
            foreach (id, cfg; configs) {
                logger.trace ("Section " ~ id ~
                ":\n\tbutton:\t\t" ~ parseFrom!(uint[][][uint])(cfg.button) ~
                "\n\taxis:\t\t" ~ parseFrom!(uint[][][uint])(cfg.axis) ~
                "\n\trelMotion:\t" ~ parseFrom!(uint[][][uint])(cfg.relMotion) );
            }
        }
    }
    
    // D2.0: private enum QUEUE : MT.ID { BUTTON = "B", AXIS = "A", MOUSE = "M" }
    private struct QUEUE {
        static const MT.ID BUTTON = cast(MT.ID)"B", AXIS = cast(MT.ID)"A", MOUSE = cast(MT.ID)"M";
    }
    private this() {}	// Private since this class should only be created from here.
    
    void addTag (char[] tp, MT.ID id, char[] dt) {
        if (tp == "uint[][uint]") {
            if (id == QUEUE.BUTTON) button = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
            else if (id == QUEUE.AXIS) axis = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
            else if (id == QUEUE.MOUSE) relMotion = cast(outQueue[][uint]) parseTo!(uint[][][uint]) (dt);
            else logger.warn ("Unexpected tag encountered with ID " ~ cast(char[])id);
        } // FIXME: add support for name and inheritants.
        else throw new MT.MTUnknownTypeException ("Input Config: only uint[][][uint] type supported");
    }
    void writeAll (ItemDelg) {
        // FIXME
    }
//END File loading/saving code
}