view mde/gui/widget/createWidget.d @ 116:5ee69b3ed9c9

Partial implementation of infinite widget recursion protection; some cleanup. Implemented recursion protection which only allowed unsafe widgets to be instantiated once; realised this was far too restrictive (multiple sibling instances are needed) and a check against parent widgets would be needed. Could be implemented by passing widgets a reference to parents. Removed ButtonWidget aka First interactible widget.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sun, 21 Dec 2008 12:03:50 +0000
parents 1b1e2297e2fc
children aba2dd815a1f
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/>. */

/** This module contains code to create a widget based on an enumeration value passed at runtime.
 *
 * It could be a part of the WidgetLoader.makeWidget function, but having it here makes things
 * tidier. */
module mde.gui.widget.createWidget;

import mde.gui.widget.Ifaces;
import mde.gui.exception;
import mde.content.Content;
import Items = mde.content.Items;

// Widgets to create:
import mde.gui.widget.layout;
import mde.gui.widget.miscWidgets;
import mde.gui.widget.TextWidget;
import mde.gui.widget.miscContent;
import mde.gui.widget.textContent;
import mde.gui.widget.Floating;
import mde.gui.widget.PopupMenu;

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

private Logger logger;
static this () {
    logger = Log.getLogger ("mde.gui.widget.createWidget");
}

/** Create a widget.
 *
 * Usually called by the widget manager's makeWidget function.
 *
 * Widget created of type data.ints[0] (see enum WIDGET_TYPES), with one of the following CTORs:
 * ---
 * this (IWidgetManager mgr, WidgetData data);
 * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_CONTENT):
 * this (IWidgetManager mgr, WidgetData data, IContent content);
 * ---
 *************************************************************************************************/
IChildWidget createWidget (IWidgetManager mgr, widgetID id, WidgetData data, IContent content)
in {
    assert (mgr !is null, "createWidget: mgr is null");
} body {
    if (data.ints.length < 1) {
        logger.error ("No int data; creating a debug widget");
        data.ints = [WIDGET_TYPE.Debug];
    }
    int type = data.ints[0];    // type is first element of data
    
    try {
        //pragma (msg, binarySearch ("type", WIDGETS));
        mixin (binarySearch ("type", WIDGETS)); // creates widget by type: new XWidget (mgr, data [, parent]);
        // Not returned a new widget or thrown:
        logger.error ("Bad widget type: {}; creating a debug widget instead",type);
    } catch (Exception e) {
        logger.error ("Error creating widget: {}; creating a debug widget instead.", e.msg);
    }
    
    return new DebugWidget (mgr, id, data);
}

/*************************************************************************************************
 * A function which uses Items.get (data.strings[0]) to get a content and creates a widget from
 * data.ints[1]. The first item in each ints and strings is removed before passing data to the new
 * widget.
 * 
 * The function only takes an IContent parameter to satisfy createWidget; it's value is ignored.
 * 
 * Circularly depends on createWidget, so should be in this module.
 *************************************************************************************************/
IChildWidget addContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent) {
    if (data.ints.length < 2 || data.strings.length < 1) throw new WidgetDataException;
    char[] cItem = data.strings[0];
    data.strings = data.strings[1..$];
    data.ints    = data.ints   [1..$];
    return createWidget (mgr, id, data, Items.get (cItem));
}

private:
/// Widget types.
enum WIDGET_TYPE : int {
    FUNCTION		= 0x2000,   // Function called instead of widget created (no "Widget" appended to fct name)
    TAKES_CONTENT	= 0x4000,   // Flag indicates widget's this should be passed an IContent reference.
    SAFE_RECURSION	= 0x8000,   // Safe to instantiate recursively without infinite looping.
    
    // Use widget names rather than usual capitals convention
    Unnamed		= 0x0,      // Only for use by widgets not created with createWidget
    
    // blank: 0x1
    FixedBlank		= 0x1,
    SizableBlank	= 0x2,
    Debug		= 0xF,
    
    // buttons: 0x10
    PopupMenu		= TAKES_CONTENT | 0x11,
    
    // labels: 0x20
    ContentLabel	= TAKES_CONTENT | 0x20,
    TextLabel		= 0x21,
    
    // content functions: 0x30
    editContent		= FUNCTION | TAKES_CONTENT | SAFE_RECURSION | 0x30,
    addContent		= FUNCTION | 0x31,
    
    // content widgets: 0x40
    DisplayContent	= TAKES_CONTENT | 0x40,
    BoolContent		= TAKES_CONTENT | 0x41,
    AStringContent	= TAKES_CONTENT | 0x42,
    ButtonContent	= TAKES_CONTENT | 0x43,
    
    GridLayout		= TAKES_CONTENT | 0x100,
    ContentList		= TAKES_CONTENT | SAFE_RECURSION | 0x110,
    
    FloatingArea	= 0x200,
}

//const char[][int] WIDGET_NAMES;

// Only used for binarySearch algorithm generation; must be ordered by numerical values.
const char[][] WIDGETS = [
        "FixedBlank",
        "SizableBlank",
        "Debug",
	"TextLabel",
	"FloatingArea",
	"addContent",
	"PopupMenu",
	"ContentLabel",
        "DisplayContent",
        "BoolContent",
	"AStringContent",
	"ButtonContent",
	"GridLayout",
	"ContentList",
	"editContent"];

/* Generates a binary search algorithm. */
char[] binarySearch (char[] var, char[][] consts) {
    if (consts.length > 3) {
        return "if (" ~ var ~ " <= WIDGET_TYPE." ~ consts[$/2 - 1] ~ ") {\n" ~
                binarySearch (var, consts[0 .. $/2]) ~
                "} else {\n" ~
                binarySearch (var, consts[$/2 .. $]) ~
                "}\n";
    } else {
        char[] ret;
        foreach (c; consts) {
            ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n"~
                      /+if ((WIDGET_TYPE."~c~" & WIDGET_TYPE.SAFE_RECURSION) ||
			  "not being recursed (no parent with same id)") {\n
                    	For recursion detection; would probably work with above check and some modification to widgets. +/
                       "debug (mdeWidgets) logger.trace (\"Creating new "~c~"Widget.\");\n
                        static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.FUNCTION)\n
                          return " ~ c ~ " (mgr, id, data, content);\n
                        else static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n
                          return new " ~ c ~ "Widget (mgr, id, data, content);\n
                        else\n
                          return new " ~ c ~ "Widget (mgr, id, data);\n"~
                      /+} else
                    	throw new GuiException (\"Widget not safe to be recursed: "~c~"\");+/
                   "} else ";
        }
        ret = ret[0..$-6] ~ '\n';  // remove last else
        return ret;
    }
}

debug { // check items in WIDGETS are listed in order
    char[] WIDGETS_check () {
        char[] ret;
        for (int i = WIDGETS.length-2; i > 0; --i) {
            ret ~= "WIDGET_TYPE."~WIDGETS[i] ~" >= WIDGET_TYPE."~ WIDGETS[i+1];
            if (i>1) ret ~= " || ";
        }
        return ret;
    }
    mixin ("static if ("~WIDGETS_check~")
        static assert (false, \"WIDGETS is not in order!\");");
}