diff mde/gui/widget/WidgetManager.d @ 175:1cbde9807293

Compile/link-time fixes for ldc & non-debug builds. Moved WidgetManager to widget/ Reverted IChildWidget to an interface, not an abstract class. Introduced a work-around for a compiler problem. May not cover all cases.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 11 Sep 2009 20:56:53 +0200
parents mde/gui/WidgetManager.d@a1ba9157510e
children af40e9679436
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/WidgetManager.d	Fri Sep 11 20:56:53 2009 +0200
@@ -0,0 +1,658 @@
+/* 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 widget manager; root of the widget tree.
+ *
+ * Rendering is handled separately by an IRenderer.
+ *****************************************************************************/
+module mde.gui.widget.WidgetManager;
+
+import mde.gui.WidgetDataSet;
+import mde.gui.widget.Ifaces;
+import mde.gui.renderer.createRenderer;
+
+import imde = mde.imde;
+import mde.content.Content;
+import mde.content.ServiceContent;
+debug import mde.content.miscContent;	// Debug menu
+debug import mde.content.Debug;
+
+// Widgets to create:
+import mde.gui.widget.layout;
+import mde.gui.widget.miscWidgets;
+import mde.gui.widget.TextWidget;
+import mde.gui.widget.contentFunctions;
+import mde.gui.widget.miscContent;
+import mde.gui.widget.Floating;
+import mde.gui.widget.ParentContent;
+import mde.gui.widget.AParentWidget;
+
+public import tango.core.sync.Mutex;
+import tango.util.log.Log : Log, Logger;
+import tango.io.Console;	// to print exception stack-trace
+import tango.util.container.SortedMap;
+
+private Logger logger;
+static this () {
+    logger = Log.getLogger ("mde.gui.WidgetManager");
+}
+
+/******************************************************************************
+ * Methods in this class are only intended for use within the gui package,
+ * either by widgets (the IXXXWidget methods implementing from an interface in
+ * widgets.Ifaces.d) or by a derived class (back-end methods doing widget
+ * work). None of these methods are intended to be thread-safe when called
+ * concurrently on the same WidgetManager instance, but they should be thread-
+ * safe for calling on separate instances.
+ *****************************************************************************/
+abstract class AWidgetManager : IWidgetManager
+{
+    //BEGIN Public methods, for use outside the widget package
+    /** Construct a new widget manager.
+     *
+     * Params:
+     *	name = The file name of the config for this GUI (to identify multiple GUIs). */
+    this (char[] name) {
+        auto p = "MiscOptions.l10n" in Content.allContent;
+        assert (p, "MiscOptions.l10n not created!");
+        p.addCallback (&reloadStrings);
+	
+	serviceContent = ServiceContentList.createItems (name);
+	assert (cast (IServiceContent) Content.get ("menus.services."~name));
+	
+        debug {	// add a debug-mode menu
+            auto lWS = new EventContent ("menus.debug."~name~".logWidgetSize");
+            lWS.addCallback (&logWidgetSize);
+        }
+    }
+    
+    /** A change callback on MiscOptions.l10n content to update widgets.
+     *
+     * Relies on another callback reloading translations to content first! */
+    final void reloadStrings (IContent) {
+        synchronized(mutex) {
+            if (childRoot is null) return;
+            childRoot.setup (++setupN, 2);
+            childRoot.setWidth  (w, -1);
+            childRoot.setHeight (h, -1);
+            childRoot.setPosition (0,0);
+	    childContext.setup (setupN, 2);
+	    //TODO: possibly childDragged?
+            requestRedraw;
+        }
+    }
+    
+    debug public void logWidgetSize (IContent) {
+	logger.trace ("size: {,4},{,4}; minimal: {,4},{,4} - WidgetManager", w,h, mw,mh);
+	logger.trace ("childRoot:");
+	childRoot.logWidgetSize;
+	logger.trace ("childContext:");
+	childContext.logWidgetSize;
+	if (childDragged !is null) {
+	    logger.trace ("childDragged:");
+	    childDragged.logWidgetSize;
+	}
+    }
+    
+    
+    //BEGIN Public IWidget methods
+    override bool saveChanges () {
+	bool ret = childRoot.saveChanges;
+	ret |= childContext.saveChanges;
+	if (childDragged !is null)
+	    ret |= childDragged.saveChanges;
+	return ret;
+    }
+    
+    /** Draw all widgets */
+    override void draw () {
+	if (childRoot)
+	    childRoot.draw;
+	drawPopup;
+    }
+    //END Public IWidget methods
+    //END Public methods, for use outside the widget package
+    
+    //BEGIN IWidget methods for widgets
+    public override bool dropContent (IContent content) {
+	return false;
+    }
+    //END IWidget methods for widgets
+    
+    //BEGIN IParentWidget methods
+    // If call reaches the widget manager there isn't any recursion.
+    //NOTE: should be override
+    final void recursionCheck (widgetID, IContent) {}
+    
+    override void minWChange (IChildWidget widget, wdim nmw) {
+	if (widget !is childRoot) {	// Probably because widget is a popup widget
+	    // This may get called from a CTOR, hence we can't check widget is one of childContext, etc.
+	    if (widget.width < nmw)
+		widget.setWidth (nmw, -1);
+	    return;
+	}
+        mw = nmw;
+        if (w < nmw) {
+            childRoot.setWidth (nmw, -1);
+            w = nmw;
+        }
+        childRoot.setPosition (0,0);
+        requestRedraw;
+    }
+    override void minHChange (IChildWidget widget, wdim nmh) {
+	if (widget !is childRoot) {
+	    if (widget.height < nmh)
+		widget.setHeight (nmh, -1);
+	    return;
+	}
+        mh = nmh;
+        if (h < nmh) {
+            childRoot.setHeight (nmh, -1);
+            h = nmh;
+        }
+        childRoot.setPosition (0,0);
+        requestRedraw;
+    }
+    //END IParentWidget methods
+    
+    //BEGIN IPopupParentWidget methods
+    override IPopupParentWidget getParentIPPW () {
+        return this;
+    }
+    
+    override void addChildIPPW (IPopupParentWidget ippw) {
+	requestRedraw;
+	if (ippw is childContext) {	// special handling - a separate IPPW
+	    contextActive = true;
+	    return;
+	}
+        if (childIPPW)
+            childIPPW.removedIPPW;
+        childIPPW = ippw;
+    }
+    override bool removeChildIPPW (IPopupParentWidget ippw) {
+	if (ippw is childContext && contextActive) {
+	    childContext.removedIPPW;
+	    contextActive = false;
+	    return true;
+	}
+        if (childIPPW !is ippw) return false;
+        childIPPW.removedIPPW;
+        childIPPW = null;
+        mAIPPW = MenuPosition.INACTIVE;
+        requestRedraw;
+        return true;
+    }
+    
+    override void menuActive (MenuPosition mA) {
+        mAIPPW = mA;
+        if (childIPPW)
+            childIPPW.menuActive = mA;
+	if (contextActive)
+	    childContext.menuActive = mA;
+    }
+    override MenuPosition menuActive () {
+        return mAIPPW;
+    }
+    override MenuPosition parentMenuActive () {
+        return MenuPosition.INACTIVE;
+    }
+    
+    // Note: also triggered by non-popup widgets
+    override void menuDone () {}
+    
+    override IChildWidget getPopupWidget (wdabs cx, wdabs cy, bool closePopup) {
+	IChildWidget ret;
+	// Don't bother with childDragged; it has no interaction
+	if (contextActive) {
+	    ret = childContext.getPopupWidget (cx, cy, closePopup);
+	    if (ret) return ret;
+	    if (closePopup) {
+		childContext.removedIPPW;
+		contextActive = false;
+		requestRedraw;
+	    }
+        }
+        if (childIPPW) {
+            ret = childIPPW.getPopupWidget (cx, cy, closePopup);
+            if (ret) return ret;
+            if (closePopup) {
+                removeChildIPPW (childIPPW);
+            }
+        }
+        return null;
+    }
+    
+    override void drawPopup () {
+	if (childIPPW)
+	    childIPPW.drawPopup;
+	if (contextActive)
+	    childContext.drawPopup();
+	if (childDragged)
+	    childDragged.draw();
+    }
+    
+    debug  override bool isChild (IPopupParentWidget ippw) {
+	if (contextActive && ippw is childContext)
+	    return true;
+        return ippw is childIPPW;
+    }
+    
+    override void removedIPPW () {}	// irrelevant
+    //END IPopupParentWidget methods
+    
+    //BEGIN IWidgetManager methods
+    override IChildWidget makeWidget (IParentWidget parent, widgetID id, IContent content = null)
+    {
+        debug assert (parent, "makeWidget: parent is null (code error)");
+        debug scope (failure)
+                logger.warn ("Creating widget \""~id~"\" failed.");
+        
+        WidgetData data = curData[id];
+        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 {
+            // Statically programmed binary search on type, returning a new widget or calling a
+            // function:
+            //pragma (msg, binarySearch ("type", WIDGETS));
+            mixin (binarySearch ("type", WIDGETS));
+            // Not returned a new widget:
+            logger.error ("Bad widget type: {}; creating a debug widget instead",type);
+        } catch (Exception e) {
+            logger.error ("Error creating widget; creating a debug widget instead. Exception printed to stderr.");
+	    //TODO: find a standard way to output exceptions, and implement everywhere:
+	    e.writeOut(delegate void(char[]s){ Cerr(s); });
+        }
+    
+        return new DebugWidget (this, parent, id, data, content);
+    }
+    
+    override WidgetData widgetData (widgetID id) {
+        return curData[id];
+    }
+    override void widgetData (widgetID id, WidgetData d) {
+        changes[id] = d;		// also updates WidgetDataSet in data.
+    }
+    
+    override wdims dimData (widgetID id) {
+        return curData.dims (id);
+    }
+    override void dimData (widgetID id, wdims d) {
+        changes.setDims(id, d);		// also updates WidgetDataSet in data.
+    }
+    
+    IRenderer renderer () {
+        assert (rend !is null, "WidgetManager.renderer: rend is null");
+        return rend;
+    }
+    
+    MenuPosition positionPopup (IChildWidget parent, IChildWidget popup, MenuPosition position = MenuPosition.INACTIVE) {
+	debug assert (parent && popup, "positionPopup: null widget");
+	debug if (Debug.logPopupPositioning())
+	    logger.trace ("Placing popup {} in relation to parent {}; input position: {}", popup, parent, position);
+        wdim w = popup.width,
+             h = popup.height,
+             x, y;
+        if (position & MenuPosition.ACTIVE) {
+            y = parent.yPos;				// height flush with top
+            if (y+h > this.h) y += parent.height - h;	// or bottom
+	    if (position & MenuPosition.LEFT) {		// previously left
+		x = parent.xPos - w;			// on left
+		if (x < 0) {
+		    x = parent.xPos + parent.width;	// on right
+		    position = MenuPosition.RIGHT;
+		}
+	    } else {					// previously right or above/below
+		x = parent.xPos + parent.width;		// on right
+		position = MenuPosition.RIGHT;
+		if (x+w > this.w) {
+		    x = parent.xPos - w;		// or left
+		    position = MenuPosition.LEFT;
+		}
+	    }
+        } else {
+	    wdim pw = parent.width;
+	    if (popup.minWidth <= pw)
+		popup.setWidth (pw, -1);		// neatness
+	    x = parent.xPos;				// align on left edge
+            if (x+w > this.w) x += pw - w;		// align on right edge
+            y = parent.yPos + parent.height;		// place below
+            if (y+h > this.h) y = parent.yPos - h;	// or above
+	    position = MenuPosition.ACTIVE;
+        }
+        if (x < 0) x = 0;	// may be placed partially off-screen
+        if (y < 0) y = 0;
+        popup.setPosition (x, y);
+	debug if (Debug.logPopupPositioning())
+	    logger.trace ("Placed popup {} of size ({},{}) at ({},{}); output position: {}", popup, w,h, x,y, position);
+	return position;
+    }
+
+    void requestRedraw () {
+        imde.mainSchedule.request(imde.SCHEDULE.DRAW);
+    }
+    //END IWidgetManager methods
+    
+protected:
+    // These methods are called by derived classes to do the widget-management work
+    //BEGIN WidgetManagement methods
+    /** Second stage of widget loading.
+     *
+     * Widget data should be loaded before this is called. */
+    final void createWidgets () {
+        // The renderer needs to be created on the first load, but not after this.
+        if (rend is null)
+            rend = createRenderer (rendName);
+        
+        debug (mdeWidgets) logger.trace ("Creating root widget...");
+        childRoot = makeWidget (this, "root");
+	underMouse = childRoot;	// don't leave null due to a check
+        debug (mdeWidgets) logger.trace ("Setting up root widget...");
+        childRoot.setup (0, 3);
+        
+        mw = childRoot.minWidth;
+        mh = childRoot.minHeight;
+	matchMinimalSize ();
+        
+        debug (mdeWidgets) logger.trace ("Setting size and position of root widget...");
+        childRoot.setWidth  (w, -1);
+        childRoot.setHeight (h, -1);
+        childRoot.setPosition (0,0);
+        debug (mdeWidgets) logger.trace ("Done creating root widget.");
+	
+	childContext = new PopupHandlerWidget (this, this, "contextHandler", "context", serviceContent);
+	childContext.setup (0,3);
+	debug (mdeWidgets) logger.trace ("Created context handler widget.");
+	
+	underMouse = childRoot;	// must be something
+    }
+    
+    final void wmSizeEvent (int nw, int nh) {
+        w = cast(wdim) nw;
+        h = cast(wdim) nh;
+        matchMinimalSize;
+        
+        if (!childRoot) return;     // if not created yet.
+        childRoot.setWidth  (w, -1);
+        childRoot.setHeight (h, -1);
+        childRoot.setPosition (0,0);
+	debug logWidgetSize (null);
+    }
+    
+    /** For mouse click events.
+     *
+     * Sends the event on to the relevant windows and all click callbacks. */
+    final void wmMouseClick (wdabs cx, wdabs cy, ubyte b, bool state) {
+	if (childRoot is null) return;
+	
+	// Update underMouse to get the widget clicked on
+	updateUnderMouse (cx, cy, state);
+	
+	// end of a drag?
+	if (dragStart !is null && b == dragButton && state == false) {
+	    IChildWidget dS = dragStart;
+	    dragStart = null;
+	    childDragged = null;
+	    requestRedraw;
+	    if (dS.dragRelease (cx, cy, underMouse))
+		return;
+	}
+	
+	// Disable keyboard input if on another widget:
+	if (keyFocus && keyFocus !is underMouse) {
+	    keyFocus.keyFocusLost;
+	    keyFocus = null;
+	    setLetterCallback (null);
+	}
+	
+	// Finally, post the actual event:
+	if (b == 3 && state) {	// right click - open context menu
+	    Content contextContent = cast(Content) underMouse.content;
+	    if (contextContent !is null) {
+		serviceContent.setContent (contextContent);
+		childContext.openMenu (underMouse, contextContent);
+	    }
+	} else {	// post other button presses to clickEvent
+	    int ret = underMouse.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state);
+	    if (ret & 1) {	// keyboard input requested
+		keyFocus = underMouse;
+		setLetterCallback (&underMouse.keyEvent);
+	    }
+	    if (ret & 2 && dragStart is null) {	// drag events requested
+		dragStart = underMouse;
+		dragButton = b;	// currently we allow any button to be used for a drag, but.. ?
+		if (ret & 4) {
+		    IContent c = underMouse.content();
+		    if (c) {	// NOTE: creates a new widget, not optimal
+			childDragged = new DisplayContentWidget (this, this, "dragContentDisplay", WidgetData ([0], []), c);
+			childDragged.setup (0, 3);
+			dragX = underMouse.xPos - cx;
+			dragY = underMouse.yPos - cy;
+			childDragged.setPosition (cx + dragX, cy + dragY);
+		    }
+		}
+	    }
+	}
+    }
+    
+    /** For mouse motion events.
+     *
+     * Lock on mutex before calling. Pass new mouse coordinates. */
+    final void wmMouseMotion (wdabs cx, wdabs cy) {
+	updateUnderMouse (cx, cy, false);
+	
+	if (dragStart !is null) {
+	    dragStart.dragMotion (cx, cy, underMouse);
+	    if (childDragged !is null) {
+		childDragged.setPosition (cx + dragX, cy + dragY);
+		requestRedraw;
+	    }
+	}
+    }
+    
+    // for internal use
+    private final void updateUnderMouse (wdabs cx, wdabs cy, bool closePopup) {
+        auto oUM = underMouse;
+        underMouse = getPopupWidget (cx, cy, closePopup);
+        if (underMouse is null) {
+            debug assert (childRoot.onSelf (cx, cy), "WidgetManager: childRoot doesn't cover whole area");
+            underMouse = childRoot.getWidget (cx, cy);
+        }
+	debug assert (oUM && underMouse, "no widget under mouse: error");
+	if (underMouse !is oUM) {
+            oUM.underMouse (false);
+            underMouse.underMouse (true);
+	    debug if (Debug.logUnderMouse())
+		logger.trace ("Widget under mouse: {}", underMouse);
+        }
+    }
+    
+    /** If possible, the screen-interaction derived class should override to
+     * make sure the window is at least (mw,mh) in size. In any case, this
+     * method MUST make sure w >= mw and h >= mh even if the window isn't this
+     * big.
+     * 
+     * A resize may not be required when this is called, however. */
+    void matchMinimalSize () {
+	if (w < mw) {
+	    logger.warn ("Min width for gui, {}, not met: {}", mw, w);
+	    w = mw;
+	}
+	if (h < mh) {
+	    logger.warn ("Min height for gui, {}, not met: {}", mh, h);
+	    h = mh;
+	}
+    }
+    
+    /// This should be overloaded to set a callback receiving keyboard input.
+    abstract void setLetterCallback(void delegate(ushort, char[]));
+    //END WidgetManagement methods
+    
+    //BEGIN makeWidget metacode
+private static {
+/// Widget types. Items match widget names without the "Widget" suffix.
+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.
+    
+    // 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		= TAKES_CONTENT | 0xF,
+    
+    // popup widgets: 0x10
+    PopupMenu		= TAKES_CONTENT | 0x11,
+    
+    // labels: 0x20
+    TextLabel		= 0x21,
+    
+    // content functions: 0x30
+    editContent		= FUNCTION | TAKES_CONTENT | 0x30,
+    addContent		= FUNCTION | 0x31,
+    popupListContent	= FUNCTION | TAKES_CONTENT | 0x33,
+    
+    // content widgets: 0x40
+    DisplayContent	= TAKES_CONTENT | 0x40,
+    BoolContent		= TAKES_CONTENT | 0x41,
+    AStringContent	= TAKES_CONTENT | 0x42,
+    ButtonContent	= TAKES_CONTENT | 0x43,
+    SliderContent	= TAKES_CONTENT | 0x44,
+    
+    GridLayout		= TAKES_CONTENT | 0x100,
+    ContentList		= TAKES_CONTENT | 0x110,
+    
+    FloatingArea	= TAKES_CONTENT | 0x200,
+    Border		= TAKES_CONTENT | 0x204,
+    Switch		= TAKES_CONTENT | 0x210,
+    Collapsible		= TAKES_CONTENT | 0x214,
+}
+
+// Only used for binarySearch algorithm generation; must be ordered by numerical values.
+const char[][] WIDGETS = [
+        "FixedBlank",
+        "SizableBlank",
+	"TextLabel",
+	"addContent",
+        "Debug",
+	"PopupMenu",
+        "DisplayContent",
+        "BoolContent",
+	"AStringContent",
+	"ButtonContent",
+	"SliderContent",
+	"GridLayout",
+	"ContentList",
+	"FloatingArea",
+	"Border",
+	"Switch",
+	"Collapsible",
+	"editContent",
+	"popupListContent"];
+
+/* Generates a binary search algorithm for makeWidget. */
+char[] binarySearch (char[] var, char[][] consts) {
+    if (consts.length > 3) {
+        return `if (`~var~` <= WIDGET_TYPE.`~consts[$/2 - 1]~`) {` ~
+                binarySearch (var, consts[0 .. $/2]) ~
+                `} else {` ~
+                binarySearch (var, consts[$/2 .. $]) ~
+                `}`;
+    } else {
+        char[] ret;
+        foreach (c; consts) {
+            ret ~= `if (` ~ var ~ ` == WIDGET_TYPE.` ~ c ~ `) {
+                        debug (mdeWidgets) logger.trace ("Creating new `~c~`.");
+                        parent.recursionCheck (id, content);
+                        static if (WIDGET_TYPE.`~c~` & WIDGET_TYPE.FUNCTION)
+                          return `~c~` (this, parent, id, data, content);
+                        else static if (WIDGET_TYPE.`~c~` & WIDGET_TYPE.TAKES_CONTENT)
+                          return new `~c~`Widget (this, parent, id, data, content);
+                        else
+                          return new `~c~`Widget (this, parent, id, data);
+                    } else `;
+        }
+        ret = ret[0..$-6];	// 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!\");");
+}
+}
+    //END makeWidget metacode
+    
+protected:
+    // Main child widget:
+    IChildWidget childRoot;		// Root of the main GUI widget tree
+    
+    // Dimensions and child set-up data (fit to childRoot):
+    wdim w,h;				// current widget size; should be at least (mw,mh) even if not displayable
+    wdim mw,mh;				// minimal area required by widgets
+    uint setupN;			// n to pass to IChildWidget.setup
+    
+    // IPopupParentWidget stuff for childRoot:
+    MenuPosition mAIPPW;		// IPPW variable
+    IPopupParentWidget childIPPW;	// child IPPW, if any active
+    
+    IChildWidget keyFocus;		// widget receiving keyboard input
+    IChildWidget underMouse;		// widget under the mouse pointer; should never be null when childRoot is non-null
+    
+    
+    // Context menu:
+    // Essentially, we consider childContext a full child IPPW, but handle it separately from
+    // childIPPW. Instead of providing another ref. for this IPPW, shortcut by using this reference
+    // and the boolean contextActive:
+    scope PopupHandlerWidget childContext;	// context menu popup (handler)
+    bool contextActive = false;		// If true, consider childContext a child IPPW
+    scope IServiceContent serviceContent;	// context menu content tree
+    
+    
+    // Drag-and-drop data:
+    //NOTE: could be wrapped with a PopupHandlerWidget, but can't set position then?
+    scope IChildWidget childDragged;	// displays dragged content; no interaction
+    IChildWidget dragStart;		// if non-null, this widget should receive motion and click-release events
+    int dragButton;			// index of button in use for drag
+    wdrel dragX, dragY;			// coordinates of dragged content relative to mouse
+    
+    
+    // Renderer:
+    char[] rendName;			// Name of renderer; for saving and creating renderers
+    scope IRenderer rend;
+    
+    
+    // Data loaded/to save:
+    WidgetDataSet curData;		// Current data
+    WidgetDataChanges changes;		// Changes for the current design.
+    
+    Mutex mutex;			// lock on methods for use outside the package.
+}