view mde/content/Content.d @ 167:620d4ea30228

Context menus: added a clipboard (functions accessible from main menu rather than context menu).
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 27 Jun 2009 11:57:26 +0200
parents bb2f1a76346d
children da8d3091fdaf
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 as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

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, see <http://www.gnu.org/licenses/>. */

/******************************************************************************
 * The content system − interfaces and base Content class.
 *****************************************************************************/
module mde.content.Content;

public import mde.content.IContent;
import mde.content.Translation;	// loading strings
import mde.content.ValueCache;	// saving/loading content values
import util = mde.util;		// fp -> dlg
import mde.exception;
import mde.setup.logger;

    import tango.util.log.Log : Log, Logger;
    private Logger logger;
    static this () {
        logger = Log.getLogger ("mde.gui.content.Content");
    }

/** Content lists. Impemented by EnumContent as well as ContentList. */
interface IContentList : IContent
{
    /** Return all sub-contents. */
    Content[] list ();
    
    /** Append x to the content list.
     *
     * Done automatically in creation, from symbol name, so don't call
     * externally. */
    void append (Content x);
}


/******************************************************************************
 * The base for most or all content classes.
 *
 * Includes generic callback support, toString implementation and symbol access.
 * 
 * Derived classes should impement:
 * ---
 *  this (char[] symbol, T val = /+ default value +/);
 *  void opAssign (T val);	// assign val, calling callbacks
 *  T opCall ();		// return value
 *  alias opCall opCast;
 *  void endEvent ();		// extend to set new value in changed (if Content has a value)
 * ---
 *****************************************************************************/
class Content : IContent
{
    protected this (char[] symbol) {
        //debug logger.trace ("Creating a {}: {}", this, symbol);
        if (symbol in allContent)
            throw new ContentException ("Multiple content with symbol "~ symbol);
        allContent[symbol] = this;
        this.symbol = symbol;
        
        // Name:
        auto l10nP = symbol in translations;
	if (l10nP)
            name (*l10nP);
        else
            name_ = symbol;	// provide a temporary name
        
        // Add to a parent list:
	ptrdiff_t i = symbol.length-1;
	while (i >= 0 && symbol[i] != '.')
	    --i;
        IContentList parent;
        if (i <= 0) {
            if (symbol.length == 0)
            	return;		// special case: is tree (content tree root)
            parent = tree;	// otherwise, use tree as root
        } else {
            char[] parentSym = symbol[0..i];
            Content* parObj = parentSym in allContent;
            if (parObj) {	// existing object
            	parent = cast(IContentList) *parObj;
            	if (parent is null) {	// it's not an IContentList!
                    logger.warn ("{} is not an IContentList, but a child, {}, exists!", parentSym, symbol);
                    return;
            	}
            } else		// no existing object
            	parent = new ContentList (parentSym);
        }
        parent.append (this);
	//logger.trace ("Created: {}\t{}", symbol, this);
    }
    
    void name (Translation.Entry e) {
	name_ = e.name;
	desc_ = e.desc;
    }
   
    /** Add a callback. Callbacks are called on a change or event, in the order
     * added. */
    Content addCallback (void delegate (Content) cb) {
	this.cb ~= cb;
	return this;
    }
    /// ditto
    Content addCallback (void function (Content) cb) {
	this.cb ~= util.toDg (cb);
	return this;
    }
    
    /** End of an event, e.g. a button release or end of an edit (calls callbacks).
     *
     * Content holding a value should override this, setting its new value in
     * changed as well as calling callbacks, e.g.:
     * ---
     * Content.changed.boolData[symbol] = val;
     * super();
     * --- */
    final void endEvent () {
	foreach (dg; cb)
	    dg (this);
    }
    
    override char[] toString (uint i) {
	return i == 0 ? "No value"
	: i == 1 ? name_
	: i == 2 ? desc_
	: null;
    }
    
    /** A naive implementation which assumes the passed content is
     * incompatible. */
    override bool set (IContent) {
	return false;
    }
    /// ditto
    override void opAssign (char[]) {}
    
protected:
    char[] symbol;
    char[] name_, desc_;	// translated name and description
    void delegate (Content) cb[];
    
public static:
    /** Get Content with _symbol name symbol from the list of all content, or
     * null if no such Content exists. */
    Content get (char[] symbol) {
        auto p = symbol in allContent;
        if (p)
            return *p;
        logger.warn ("Content {} does not exist",symbol);
        return null;
    }
    
    // Would use tango.container.HashMap, but mde.workaround2371 doesn't work here.
    Content[char[]] allContent;	// all content hashed by symbol name
    ContentList tree;	// tree of all content.
    
    static this () {
        tree = new ContentList ("");
    }
    
package static:
    // Kept to set the value of other content as it is created:
    ValueCache loaded;	// loaded values not matching any content created yet
    // The values in changed are what is written to file when saving:
    ValueCache changed;	// changed values and values from local config file
    
    Translation.Entry[char[]] translations;
}

/** A generic way to handle a list of type IContent. */
class ContentList : Content, IContentList
{
    this (char[] symbol) {
	super (symbol);
    }
    
    override Content[] list () {
	return list_;
    }
    
    override void append (Content x) {
        list_ ~= x;
    }
    
    override bool set (IContent c) {
	IContentList cl = cast (IContentList) c;
	if (cl !is null) {
	    list_ = cl.list();
	    return true;
	}
	return false;
    }
    
protected:
    final Content[] list_;
}

/** Created on errors to display and log a message. */
class ErrorContent : Content
{
    this (char[] symbol, char[] msg) {
	super (symbol);
        this.msg = msg;
        logger.error (symbol ~ ": " ~ msg);
    }
    
    override char[] toString (uint i) {
	return i == 0 ? msg
	     : i == 1 ? name_
	     : null;
    }
    
protected:
    char[] msg;
}