]> source.dussan.org Git - vaadin-framework.git/commitdiff
Copied CoordinateLayout from incubator to trunk
authorRisto Yrjänä <risto.yrjana@itmill.com>
Mon, 29 Sep 2008 10:47:58 +0000 (10:47 +0000)
committerRisto Yrjänä <risto.yrjana@itmill.com>
Mon, 29 Sep 2008 10:47:58 +0000 (10:47 +0000)
svn changeset:5542/svn branch:trunk

WebContent/ITMILL/themes/default/coordinatelayout/coordinatelayout.css [new file with mode: 0644]
WebContent/ITMILL/themes/default/styles.css
src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java
src/com/itmill/toolkit/terminal/gwt/client/ui/ICoordinateLayout.java [new file with mode: 0644]
src/com/itmill/toolkit/tests/tickets/Ticket1267.java [new file with mode: 0644]
src/com/itmill/toolkit/ui/CoordinateLayout.java [new file with mode: 0644]

diff --git a/WebContent/ITMILL/themes/default/coordinatelayout/coordinatelayout.css b/WebContent/ITMILL/themes/default/coordinatelayout/coordinatelayout.css
new file mode 100644 (file)
index 0000000..f85c43a
--- /dev/null
@@ -0,0 +1,10 @@
+/*
+ *     CoordinateLayout
+ */
+
+.i-coordinatelayout-margin-values{
+       margin-top: 0px;
+       margin-right: 0px;
+       margin-bottom: 0px;
+       margin-left: 0px;
+}
\ No newline at end of file
index 713150016b45f24d9de7fdd2f47994ae29e1bc05..06bdfbf14af3fcf74f988cc87058d933b4275ad6 100644 (file)
@@ -367,6 +367,16 @@ input.i-modified,
        font-size:xx-small;
 }
 
+/*
+ *     CoordinateLayout
+ */
+
+.i-coordinatelayout-margin-values{
+       margin-top: 0px;
+       margin-right: 0px;
+       margin-bottom: 0px;
+       margin-left: 0px;
+}
 .i-datefield-button {
        font-size:13px;
        width: 22px;
index 7ee91ca5ce6f2cc0fcbedf1f09c45751bc3f002e..560ea4524d1055f71f016d7de94714ffb34d20aa 100644 (file)
@@ -11,6 +11,7 @@ import com.google.gwt.user.client.ui.Widget;
 import com.itmill.toolkit.terminal.gwt.client.ui.IAccordion;
 import com.itmill.toolkit.terminal.gwt.client.ui.IButton;
 import com.itmill.toolkit.terminal.gwt.client.ui.ICheckBox;
+import com.itmill.toolkit.terminal.gwt.client.ui.ICoordinateLayout;
 import com.itmill.toolkit.terminal.gwt.client.ui.ICustomComponent;
 import com.itmill.toolkit.terminal.gwt.client.ui.ICustomLayout;
 import com.itmill.toolkit.terminal.gwt.client.ui.IDateFieldCalendar;
@@ -189,6 +190,9 @@ public class DefaultWidgetSet implements WidgetSet {
         } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IPopupView"
                 .equals(className)) {
             return new IPopupView();
+        } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ICoordinateLayout"
+                .equals(className)) {
+            return new ICoordinateLayout();
         }
 
         return new IUnknownComponent();
@@ -311,6 +315,8 @@ public class DefaultWidgetSet implements WidgetSet {
             return "com.itmill.toolkit.terminal.gwt.client.ui.IMenuBar";
         } else if ("popupview".equals(tag)) {
             return "com.itmill.toolkit.terminal.gwt.client.ui.IPopupView";
+        } else if ("coordinatelayout".equals(tag)) {
+            return "com.itmill.toolkit.terminal.gwt.client.ui.ICoordinateLayout";
         }
 
         return "com.itmill.toolkit.terminal.gwt.client.ui.IUnknownComponent";
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ICoordinateLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ICoordinateLayout.java
new file mode 100644 (file)
index 0000000..93b0fcb
--- /dev/null
@@ -0,0 +1,1165 @@
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.ICaption;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+import com.itmill.toolkit.terminal.gwt.client.Util.WidgetSpaceAllocator;
+
+public class ICoordinateLayout extends ComplexPanel implements Container,
+        ContainerResizedListener, WidgetSpaceAllocator {
+
+    /** Class name, prefix in styling */
+    public static final String CLASSNAME = "i-coordinatelayout";
+
+    /** CSS class identifier for margin values */
+    public static final String CSSID = "margin-values";
+
+    /** For server-client communication */
+    protected String uidlId;
+    protected ApplicationConnection client;
+
+    /** For inner classes */
+    protected final ICoordinateLayout hostReference = this;
+
+    /** Indexes to coordinate arrays */
+    protected static final int LEFT = 0;
+    protected static final int TOP = 1;
+    protected static final int WIDTH = 2;
+    protected static final int HEIGHT = 3;
+    protected static final int RIGHT = 4;
+    protected static final int BOTTOM = 5;
+
+    /** Data structures for components */
+    // current components, including captions
+    protected final ArrayList<Widget> componentList;
+
+    // component -> caption mappers
+    protected final HashMap<Paintable, CustomCaption> componentToHeader;
+    protected final HashMap<Paintable, CustomCaption> componentToMarker;
+
+    // component -> properties mappers
+    protected final HashMap<Widget, String> componentToCoords;
+    protected final HashMap<Widget, Integer> componentToZ;
+    protected final HashMap<Widget, Element> componentToArea;
+
+    // components to draw on the next layout update
+    protected ArrayList<Widget> toUpdate;
+
+    // current layout margins and width/height
+    protected int[] layout = new int[6];
+
+    protected MarginInfo marginInfo; // from UIDL
+    protected int[] margins; // from CSS
+
+    public ICoordinateLayout() {
+        super();
+        setElement(DOM.createDiv());
+
+        // Set style attributes
+        setStyleName(CLASSNAME);
+        DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+
+        if (BrowserInfo.get().isIE()) {
+            DOM.setStyleAttribute(getElement(), "zoom", "1");
+        }
+
+        marginInfo = new MarginInfo(0);
+
+        // Init data structures
+        componentList = new ArrayList<Widget>();
+        toUpdate = new ArrayList<Widget>();
+        componentToCoords = new HashMap<Widget, String>();
+        componentToHeader = new HashMap<Paintable, CustomCaption>();
+        componentToMarker = new HashMap<Paintable, CustomCaption>();
+        componentToZ = new HashMap<Widget, Integer>();
+        componentToArea = new HashMap<Widget, Element>();
+
+    }
+
+    /**
+     * This enables us to read the margins when the layout is actually attached
+     * to the DOM-tree.
+     * 
+     * @see com.google.gwt.user.client.ui.Panel#onLoad()
+     */
+    protected void onLoad() {
+        margins = readMarginsFromCSS();
+        super.onLoad();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.itmill.toolkit.terminal.gwt.client.Paintable#updateFromUIDL(com.itmill
+     * .toolkit.terminal.gwt.client.UIDL,
+     * com.itmill.toolkit.terminal.gwt.client.ApplicationConnection)
+     */
+    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+        // This call should be made first. Ensure correct implementation,
+        // and don't let the containing coordinateLayout manage caption, etc.
+
+        if (client.updateComponent(this, uidl, false)) {
+            return;
+        }
+
+        // These are for future server connections
+        this.client = client;
+
+        // Enable / disable margins
+        if (uidl.hasAttribute("margins")) {
+            marginInfo = new MarginInfo(uidl.getIntAttribute("margins"));
+        } else {
+            marginInfo = new MarginInfo(0);
+        }
+
+        // Start going through the component tree
+        UIDL componentsFromUIDL = uidl;
+        ArrayList<Widget> newComponents = new ArrayList<Widget>();
+        ArrayList<Widget> removedComponents = new ArrayList<Widget>();
+        int zIndex = 0;
+
+        for (Iterator<UIDL> componentIterator = componentsFromUIDL
+                .getChildIterator(); componentIterator.hasNext();) {
+
+            // Extract the values from the UIDL
+            final UIDL componentUIDL = componentIterator.next();
+            final UIDL componentDataUIDL = componentUIDL.getChildUIDL(0);
+            final Paintable componentDataPaintable = client
+                    .getPaintable(componentDataUIDL);
+            final Widget componentWidget = (Widget) componentDataPaintable;
+            final String coordString = componentUIDL
+                    .getStringAttribute("position");
+
+            if (!componentList.contains(componentWidget)) { // Initial draw
+                add(componentWidget);
+                componentDataPaintable
+                        .updateFromUIDL(componentDataUIDL, client);
+
+            } else { // normal update
+                componentDataPaintable
+                        .updateFromUIDL(componentDataUIDL, client);
+            }
+
+            // Save the coordinate string from the UIDL
+            if (!coordString.equals(componentToCoords.get(componentWidget))) {
+                toUpdate.add(componentWidget);
+            }
+            componentToCoords.put(componentWidget, coordString);
+
+            // Components come from the server in the correct order, client just
+            // needs to set the z-index
+            updateZ(componentWidget, zIndex++);
+
+            // Add the component to the list. This list is later checked against
+            // the list of current components
+            newComponents.add(componentWidget);
+
+        }// for
+
+        // Clean children that don't exist in the current UIDL
+        // (except for captions)
+        for (Iterator<Widget> iterator = componentList.iterator(); iterator
+                .hasNext();) {
+            Widget w = iterator.next();
+            if (!newComponents.contains(w) && !(w instanceof CustomCaption)) {
+                removedComponents.add(w);
+            }
+        }
+
+        while (!removedComponents.isEmpty()) {
+            removePaintable((Paintable) removedComponents.get(0));
+            removedComponents.remove(0);
+        }
+
+        componentList.clear();
+        componentList.addAll(newComponents);
+
+        // Make sure coordinateLayout gets done every time
+        iLayout(-1, -1);
+
+    }// updateFromUIDL
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener#iLayout
+     * (int, int)
+     */
+    public void iLayout(int availableWidth, int availableHeight) {
+        // shake
+        // TODO is this necessary?
+        getOffsetWidth();
+        getOffsetHeight();
+
+        // Get our current area and check if it has changed
+        // If it has, we need to recalculate all
+        int[] newSize = getDimensionsArray();
+        if ((newSize[WIDTH] != layout[WIDTH])
+                || (newSize[HEIGHT] != layout[HEIGHT])) {
+            toUpdate.clear();
+            toUpdate = getDrawableComponents();
+            layout = newSize;
+        }
+
+        if (!toUpdate.isEmpty()) {
+
+            // Run layout functions for children
+            Util.runDescendentsLayout(this);
+
+            // Go over all children and calculate their positions
+            for (Iterator<Widget> componentIterator = toUpdate.iterator(); componentIterator
+                    .hasNext();) {
+
+                Widget componentWidget = componentIterator.next();
+                CustomCaption componentHeader = componentToHeader
+                        .get((Paintable) componentWidget);
+                CustomCaption componentMarker = componentToMarker
+                        .get((Paintable) componentWidget);
+
+                // Reset position info
+                resetPositionAttributes(componentWidget);
+                maximizeArea(componentWidget);
+
+                // Calculate real pixel values from the coordinate string
+                int[] coords = parseCoordString(componentToCoords
+                        .get(componentWidget));
+
+                // Determine which sides of the component must be attached
+                // ( == sides with a determined position)
+                boolean[] sidesToAttach = new boolean[6];
+                for (int i = 0; i < coords.length; i++) {
+                    sidesToAttach[i] = (coords[i] != -1);
+                }
+
+                // At least one of (TOP, BOTTOM) must be attached to ensure the
+                // captions are displayed properly
+                if (!sidesToAttach[TOP] && !sidesToAttach[BOTTOM]) {
+                    sidesToAttach[TOP] = true;
+                }
+                // At least one of (LEFT, RIGHT) must be attached to ensure the
+                // captions are displayed properly
+                if (!sidesToAttach[LEFT] && !sidesToAttach[RIGHT]) {
+                    sidesToAttach[LEFT] = true;
+                }
+
+                // If width or height is not given, try to calculate from other
+                // values
+                if (coords[WIDTH] == -1) {
+                    coords[WIDTH] = layout[WIDTH];
+
+                    if (coords[LEFT] != -1) {
+                        coords[WIDTH] -= coords[LEFT];
+                    }
+                    if (coords[RIGHT] != -1) {
+                        coords[WIDTH] -= coords[RIGHT];
+                    }
+
+                }
+
+                if (coords[HEIGHT] == -1) {
+                    coords[HEIGHT] = layout[HEIGHT];
+
+                    if (coords[TOP] != -1) {
+                        coords[HEIGHT] -= coords[TOP];
+                    }
+                    if (coords[BOTTOM] != -1) {
+                        coords[HEIGHT] -= coords[BOTTOM];
+                    }
+                }
+
+                // Sanity check for width & height
+                if (coords[HEIGHT] > layout[HEIGHT]) {
+                    coords[HEIGHT] = layout[HEIGHT];
+                } else if (coords[HEIGHT] < 0) {
+                    coords[HEIGHT] = 0;
+                }
+
+                if (coords[WIDTH] > layout[WIDTH]) {
+                    coords[WIDTH] = layout[WIDTH];
+                } else if (coords[WIDTH] < 0) {
+                    coords[WIDTH] = 0;
+                }
+
+                // Force width and height on margins so that they
+                // have some effect
+                calculateMargins(coords);
+
+                // Sanity checks. Some browsers render incorrectly when for
+                // example relative[LEFT] + relative[RIGHT] > layout[WIDTH]
+                for (int i = 0; i < coords.length; i++) {
+                    if (coords[i] < 0) {
+                        coords[i] = 0;
+                    }
+                }
+
+                // height overflows
+                if ((coords[TOP] + coords[BOTTOM]) > getOffsetHeight()) {
+                    if (coords[TOP] > coords[BOTTOM]) {
+                        coords[TOP] -= coords[TOP] - coords[BOTTOM];
+                    } else {
+                        coords[BOTTOM] -= coords[BOTTOM] - coords[TOP];
+                    }
+                }
+
+                // width overflows
+                if (coords[RIGHT] + coords[LEFT] > getOffsetWidth()) {
+                    if (coords[RIGHT] > coords[LEFT]) {
+                        coords[RIGHT] -= coords[RIGHT] - coords[LEFT];
+                    } else {
+                        coords[LEFT] -= coords[LEFT] - coords[RIGHT];
+                    }
+                }
+
+                // this call puts the component to the componentToPosition
+                // map and sets the style attributes
+                setWidgetPosition(componentWidget, coords, sidesToAttach);
+
+                // Update caption position
+                // Header has icon and caption text
+                if (componentHeader != null) {
+                    updateCaptionPosition(componentHeader, componentWidget);
+                }
+                // Marker has error and required
+                if (componentMarker != null) {
+                    updateCaptionPosition(componentMarker, componentWidget);
+                }
+            }
+        }
+
+        toUpdate.clear();
+    }
+
+    /**
+     * Set positional attributes for given widget
+     * 
+     * @param w
+     * @param newCoords
+     * @param sidesToAttach
+     */
+    public void setWidgetPosition(Widget w, int[] newCoords,
+            boolean[] sidesToAttach) {
+
+        resetPositionAttributes(w);
+        Element widgetElement = w.getElement();
+        Element areaElement;
+        CustomCaption componentHeader = componentToHeader.get((Paintable) w);
+        CustomCaption componentMarker = componentToMarker.get((Paintable) w);
+
+        // Attach the widget to the sides of the surrounding area element
+        if (sidesToAttach[TOP] || sidesToAttach[HEIGHT]) {
+            int margin = 0;
+            if (componentHeader != null) {
+                margin += componentHeader.getOffsetHeight();
+            }
+            DOM.setStyleAttribute(widgetElement, "top", Integer
+                    .toString(margin)
+                    + "px");
+        }
+        if (sidesToAttach[RIGHT] || sidesToAttach[WIDTH]) {
+            int margin = 0;
+            if (componentMarker != null) {
+                margin += componentMarker.getOffsetWidth();
+            }
+            DOM.setStyleAttribute(widgetElement, "right", Integer
+                    .toString(margin)
+                    + "px");
+        }
+        if (sidesToAttach[BOTTOM] || sidesToAttach[HEIGHT]) {
+            DOM.setStyleAttribute(widgetElement, "bottom", "0px");
+        }
+        if (sidesToAttach[LEFT] || sidesToAttach[WIDTH]) {
+            DOM.setStyleAttribute(widgetElement, "left", "0px");
+        }
+
+        // Create / get the area element for this component
+        if (componentToArea.get(w) == null) {
+            areaElement = DOM.createDiv();
+            // Element tree should be layoutElement -> areaElement ->
+            // componentElement
+            DOM.appendChild(getElement(), areaElement);
+            DOM.removeChild(getElement(), widgetElement);
+            DOM.appendChild(areaElement, widgetElement);
+
+            DOM.setStyleAttribute(areaElement, "position", "absolute");
+            DOM.setStyleAttribute(areaElement, "overflow", "hidden");
+
+            // Component growth areas are hidden so that they don't interfere
+            // with other components
+            DOM.setStyleAttribute(areaElement, "visibility", "hidden");
+
+            componentToArea.put(w, areaElement);
+        } else {
+            areaElement = componentToArea.get(w);
+        }
+
+        // set the margin according to the values given
+        DOM.setStyleAttribute(areaElement, "margin", (newCoords[TOP] + "px "
+                + newCoords[RIGHT] + "px " + newCoords[BOTTOM] + "px "
+                + newCoords[LEFT] + "px"));
+        DOM.setStyleAttribute(areaElement, "width", newCoords[WIDTH] + "px");
+        DOM.setStyleAttribute(areaElement, "height", newCoords[HEIGHT] + "px");
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.itmill.toolkit.terminal.gwt.client.Container#updateCaption(com.itmill
+     * .toolkit.terminal.gwt.client.Paintable,
+     * com.itmill.toolkit.terminal.gwt.client.UIDL)
+     */
+    public void updateCaption(Paintable component, UIDL uidl) {
+        CustomCaption header = componentToHeader.get(component);
+        CustomCaption marker = componentToMarker.get(component);
+
+        if (ICaption.isNeeded(uidl)) {
+            boolean headerNeeded = uidl.getStringAttribute("caption") != null
+                    || uidl.hasAttribute("icon");
+
+            boolean markerNeeded = uidl.hasAttribute("error")
+                    || uidl.hasAttribute("required");
+
+            // if we have both, we display them on the header
+            if (headerNeeded && markerNeeded) {
+                // header might be in wrong format (separate marker & header)
+                if (header != null
+                        && !header.isMode(CustomCaption.COMBINED_MODE)) {
+                    componentToHeader.remove(component);
+                    remove(header);
+                    header = null;
+                }
+                // header might not exist
+                if (header == null) {
+                    header = new CustomCaption(
+                            component,
+                            client,
+                            (CustomCaption.HEADER_MODE | CustomCaption.MARKER_MODE));
+                    add(header);
+                    componentToHeader.put(component, header);
+                }
+
+                // update
+                header.updateCaption(uidl);
+                updateCaptionPosition(header, (Widget) component);
+
+                // if we have both we don't need a separate marker
+                if (marker != null) {
+                    componentToMarker.remove(component);
+                    remove(marker);
+                }
+
+            }
+            // if we only need a header
+            else if (headerNeeded) {
+
+                if (header == null) {
+
+                    header = new CustomCaption(component, client,
+                            CustomCaption.HEADER_MODE);
+                    add(header);
+                    componentToHeader.put(component, header);
+
+                }
+
+                header.updateCaption(uidl);
+
+                // caption position
+                updateCaptionPosition(header, (Widget) component);
+
+            }
+
+            // if we only need a marker
+            else if (markerNeeded) {
+
+                if (marker == null) {
+
+                    marker = new CustomCaption(component, client,
+                            CustomCaption.MARKER_MODE);
+                    add(marker);
+                    componentToMarker.put(component, marker);
+
+                }
+
+                marker.updateCaption(uidl);
+
+                // caption position
+                updateCaptionPosition(marker, (Widget) component);
+            }
+        } else { // No caption
+            if (header != null) {
+                remove(header);
+                componentToHeader.remove(component);
+            } else if (marker != null) {
+                remove(marker);
+                componentToMarker.remove(component);
+            }
+        }
+
+    }
+
+    /**
+     * Update caption position according to the parent component position.
+     * 
+     * @param c
+     * @param parent
+     */
+    public void updateCaptionPosition(CustomCaption c, Widget parent) {
+
+        int parentTop, parentRight, parentBottom, parentLeft, parentHeight, parentWidth;
+
+        Element captionElement = c.getElement();
+        Element parentElement = parent.getElement();
+        Element areaElement = componentToArea.get(parent);
+
+        if (areaElement != null
+                && !DOM.isOrHasChild(areaElement, c.getElement())) {
+            DOM.removeChild(getElement(), c.getElement());
+            DOM.appendChild(areaElement, c.getElement());
+        }
+
+        resetPositionAttributes((Widget) c);
+
+        parentTop = getPositionValue(parentElement, "top");
+        parentRight = getPositionValue(parentElement, "right");
+        parentBottom = getPositionValue(parentElement, "bottom");
+        parentLeft = getPositionValue(parentElement, "left");
+        parentHeight = parent.getOffsetHeight();
+        parentWidth = parent.getOffsetWidth();
+
+        if (c.isMode(CustomCaption.HEADER_MODE)) {
+            if (parentBottom == -1 || parentTop != -1) {
+                DOM.setStyleAttribute(captionElement, "top", "0px");
+            } else {
+                DOM.setStyleAttribute(captionElement, "bottom", parentHeight
+                        + "px");
+            }
+
+            if (parentRight == -1 || parentLeft != -1) {
+                DOM.setStyleAttribute(captionElement, "left", "0px");
+            } else {
+                int right = parentRight + parentWidth - c.getOffsetWidth();
+                DOM.setStyleAttribute(captionElement, "right", right + "px");
+            }
+        } else if (c.isMode(CustomCaption.MARKER_MODE)) {
+            if (parentBottom == -1 || parentTop != -1) {
+                DOM.setStyleAttribute(captionElement, "top", parentTop + "px");
+            } else {
+                int bottom = parentBottom + parentHeight - c.getOffsetHeight();
+                DOM.setStyleAttribute(captionElement, "bottom", bottom + "px");
+            }
+
+            if (parentRight == -1 || parentLeft != -1) {
+                DOM.setStyleAttribute(captionElement, "left", parentWidth
+                        + "px");
+            } else {
+                int right = parentRight - c.getOffsetWidth();
+                DOM.setStyleAttribute(captionElement, "right", right + "px");
+            }
+        }
+
+    }
+
+    /**
+     * Update the z-index of a component + the z-index of the caption
+     * 
+     * @param component
+     * @param zIndex
+     */
+    public void updateZ(Widget component, int zIndex) {
+
+        DOM.setStyleAttribute(component.getElement(), "zIndex", ""
+                + String.valueOf(zIndex));
+        componentToZ.put(component, new Integer(zIndex));
+
+        // Set caption z-index (same as parent components z)
+        if (componentToHeader.get((Paintable) component) != null) {
+            CustomCaption h = componentToHeader.get((Paintable) component);
+            DOM.setStyleAttribute(h.getElement(), "zIndex", "" + zIndex);
+        }
+        if (componentToMarker.get((Paintable) component) != null) {
+            CustomCaption m = componentToMarker.get((Paintable) component);
+            DOM.setStyleAttribute(m.getElement(), "zIndex", "" + zIndex);
+        }
+    }
+
+    /**
+     * Remove & unregister a paintable component
+     * 
+     * @param p
+     * @return
+     */
+    public boolean removePaintable(Paintable p) {
+        CustomCaption header = componentToHeader.get(p);
+        CustomCaption marker = componentToMarker.get(p);
+        if (header != null) {
+            componentToHeader.remove(p);
+            remove(header);
+        }
+        if (marker != null) {
+            componentToHeader.remove(p);
+            remove(marker);
+        }
+        client.unregisterPaintable(p);
+        return remove((Widget) p);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.itmill.toolkit.terminal.gwt.client.Container#childComponentSizesUpdated
+     * ()
+     */
+    public boolean childComponentSizesUpdated() {
+        return false;
+    }
+
+    /**
+     * Remove all position-related attributes from a given element
+     * 
+     * @param e
+     */
+    protected void resetPositionAttributes(Widget w) {
+        Element e = w.getElement();
+        DOM.setStyleAttribute(e, "top", "");
+        DOM.setStyleAttribute(e, "right", "");
+        DOM.setStyleAttribute(e, "bottom", "");
+        DOM.setStyleAttribute(e, "left", "");
+        DOM.setStyleAttribute(e, "width", "");
+        DOM.setStyleAttribute(e, "height", "");
+    }
+
+    /**
+     * Calculates percentage from value if needed
+     * 
+     * @param coordString
+     * @return
+     */
+    protected int[] parseCoordString(String coordString) {
+        int[] relative = new int[6];
+        boolean[] isPercent = new boolean[6];
+        String[] values = coordString.split(",");
+
+        for (int i = 0; i < values.length; i++) {
+
+            // string parsing
+            if (values[i].startsWith("-")) { // anything with '-' equals -1
+                relative[i] = -1;
+            } else {
+                if (values[i].endsWith("%")) {
+                    isPercent[i] = true;
+                    relative[i] = Integer.parseInt(values[i].substring(0,
+                            values[i].length() - 1)); // without '%'
+                } else {
+                    relative[i] = Integer.parseInt(values[i]);
+                }
+
+            }
+
+            // relative calculations
+            if (isPercent[i]) {
+                float multiplier = (float) relative[i] / 100;
+
+                if (i == LEFT || i == RIGHT || i == WIDTH) {
+                    float width = (float) layout[WIDTH];
+                    relative[i] = (int) (width * multiplier);
+                } else {
+                    float height = (float) layout[HEIGHT];
+                    relative[i] = (int) (height * multiplier);
+                }
+            }
+        }
+        return relative;
+    }
+
+    /**
+     * Get the dimensions for the layout, including margins.
+     * 
+     * @return
+     */
+    protected int[] getDimensionsArray() {
+        int[] newSize = new int[6];
+
+        newSize[LEFT] = marginInfo.hasLeft() ? margins[3] : 0;
+        newSize[TOP] = marginInfo.hasTop() ? margins[0] : 0;
+        newSize[BOTTOM] = marginInfo.hasBottom() ? margins[2] : 0;
+        newSize[RIGHT] = marginInfo.hasRight() ? margins[1] : 0;
+        newSize[WIDTH] = this.getOffsetWidth() - newSize[LEFT] - newSize[RIGHT];
+        newSize[HEIGHT] = this.getOffsetHeight() - newSize[TOP]
+                - newSize[BOTTOM];
+
+        return newSize;
+    }
+
+    /*
+     * This is called to "reset" area information so we can calculate component
+     * size correctly. The layout must have correct dimensions when this is
+     * called.
+     */
+    protected void maximizeArea(Widget component) {
+        if (componentToArea.get(component) != null) {
+            Element areaElement = componentToArea.get(component);
+            DOM.setStyleAttribute(areaElement, "margin", "0");
+            DOM.setStyleAttribute(areaElement, "width", layout[WIDTH] + "px");
+            DOM.setStyleAttribute(areaElement, "height", layout[HEIGHT] + "px");
+        }
+    }
+
+    /*
+     * This method calculates the final coordinates for a given component. It
+     * "fills in" the gaps from the given coordinates.
+     */
+    protected void calculateMargins(int[] coords) {
+        int left, top, right, bottom;
+
+        // left
+        if (coords[LEFT] == -1 & coords[RIGHT] == -1) {
+            left = layout[LEFT];
+        } else if (coords[LEFT] == -1) {
+            left = layout[WIDTH] - coords[RIGHT] - coords[WIDTH] + layout[LEFT];
+
+        } else {
+            left = coords[LEFT] + layout[LEFT];
+        }
+
+        // right
+        if (coords[RIGHT] == -1 & coords[LEFT] == -1) {
+            right = layout[RIGHT];
+        } else if (coords[RIGHT] == -1) {
+            right = layout[WIDTH] - coords[LEFT] - coords[WIDTH]
+                    + layout[RIGHT];
+        } else {
+            right = coords[RIGHT] + layout[RIGHT];
+        }
+
+        // top
+        if (coords[TOP] == -1 & coords[BOTTOM] == -1) {
+            top = layout[TOP];
+        } else if (coords[TOP] == -1) {
+            top = layout[HEIGHT] - coords[HEIGHT] - coords[BOTTOM]
+                    + layout[TOP];
+        } else {
+            top = coords[TOP] + layout[TOP];
+        }
+
+        // bottom
+        if (coords[BOTTOM] == -1 & coords[TOP] == -1) {
+            bottom = layout[BOTTOM];
+        } else if (coords[BOTTOM] == -1) {
+            bottom = layout[HEIGHT] - coords[TOP] - coords[HEIGHT]
+                    + layout[BOTTOM];
+        } else {
+            bottom = coords[BOTTOM] + layout[BOTTOM];
+        }
+
+        coords[LEFT] = left;
+        coords[TOP] = top;
+        coords[RIGHT] = right;
+        coords[BOTTOM] = bottom;
+
+    }
+
+    /*
+     * Used to retrieve e.g. the "top" and "right" values for an element
+     */
+    protected int getPositionValue(Element e, String position) {
+        String value = DOM.getStyleAttribute(e, position);
+        if (!"".equals(value)) {
+            return parsePixel(value);
+        } else {
+            return -1;
+        }
+    }
+
+    /* Read margin info from CSS */
+    protected int[] readMarginsFromCSS() {
+        int[] margins = new int[4];
+        String style = CLASSNAME + "-" + CSSID;
+        String[] cssProperties = new String[] { "margin-top", "margin-right",
+                "margin-bottom", "margin-left" };
+
+        String cssValue = "";
+        String[] cssValues = new String[cssProperties.length];
+
+        Element e = DOM.createDiv();
+        DOM.appendChild(this.getElement(), e);
+        DOM.setStyleAttribute(e, "position", "absolute");
+        DOM.setStyleAttribute(e, "height", "100px");
+        DOM.setStyleAttribute(e, "width", "100px");
+        DOM.setElementProperty(e, "className", style);
+        DOM.setInnerHTML(e, ".");
+
+        try {
+            if (BrowserInfo.get().isIE()) {
+                cssValue = getIEMargin(e);
+
+                cssValues = cssValue.split("\\s");
+
+                switch (cssValues.length) {
+
+                case 4:
+                    for (int i = 0; i < 4; i++) {
+                        margins[i] = parsePixel(cssValues[i]);
+                    }
+                    break;
+
+                case 3:
+                    margins[0] = parsePixel(cssValues[0]);
+                    margins[1] = margins[3] = parsePixel(cssValues[1]);
+                    margins[2] = parsePixel(cssValues[2]);
+                    break;
+
+                case 2:
+                    margins[0] = margins[2] = parsePixel(cssValues[0]);
+                    margins[1] = margins[3] = parsePixel(cssValues[1]);
+                    break;
+
+                case 1:
+                    int intValue = parsePixel(cssValues[0]);
+                    for (int i = 0; i < margins.length; i++) {
+                        margins[i] = intValue;
+                    }
+                }
+            } else {
+                for (int i = 0; i < cssValues.length; i++) {
+                    cssValues[i] = getMargin(e, cssProperties[i]);
+                    margins[i] = parsePixel(cssValues[i]);
+                }
+            }
+
+        } catch (Exception exc) {
+            // Error in CSS margin format or missing value, defaulting all to 0
+            GWT.log("Error reading CSS", exc);
+            for (int i = 0; i < margins.length; i++) {
+                margins[i] = 0;
+            }
+        }
+        DOM.removeChild(this.getElement(), e);
+        return margins;
+    }
+
+    /* "10px" => 10 */
+    protected int parsePixel(String str) throws NumberFormatException {
+        return Integer.parseInt(str.substring(0, str.length() - 2));
+    }
+
+    /**
+     * Retrieve margin for other browsers
+     * 
+     * @param e
+     * @param CSSProp
+     * @return
+     */
+    protected native String getMargin(Element e, String CSSProp)/*-{
+                                                                                                                                                                                                                                                                                                                                                                                                        return $wnd.getComputedStyle(e,null).getPropertyValue(CSSProp);
+                                                                                                                                                                                                                                                                                                                                                                                                        }-*/;
+
+    /**
+     * Retrieves margin info in IE
+     * 
+     * @param e
+     * @return
+     */
+    protected native String getIEMargin(Element e)/*-{ 
+                                                                                                                                                                                                                                                                                                                                                                                                        return e.currentStyle.margin;
+                                                                                                                                                                                                                                                                                                                                                                                                        }-*/;
+
+    /**
+     * @return all components that are not captions
+     */
+    protected ArrayList<Widget> getDrawableComponents() {
+        ArrayList<Widget> list = new ArrayList<Widget>();
+        for (Widget w : componentList) {
+            if (!(w instanceof CustomCaption)) {
+                list.add(w);
+            }
+        }
+        return list;
+    }
+
+    /*
+     * Widget methods
+     */
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.google.gwt.user.client.ui.ComplexPanel#iterator()
+     */
+    public Iterator<Widget> iterator() {
+        return componentList.iterator();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.itmill.toolkit.terminal.gwt.client.Container#replaceChildComponent
+     * (com.google.gwt.user.client.ui.Widget,
+     * com.google.gwt.user.client.ui.Widget)
+     */
+    public void replaceChildComponent(Widget from, Widget to) {
+        throw new UnsupportedOperationException();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.itmill.toolkit.terminal.gwt.client.Container#hasChildComponent(com
+     * .google.gwt.user.client.ui.Widget)
+     */
+    public boolean hasChildComponent(Widget w) {
+        return componentList.contains(w);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui
+     * .Widget)
+     */
+    public void add(Widget w) {
+        if (!componentList.contains(w)) {
+            w.removeFromParent();
+        }
+
+        // these attributes are common to all elements
+        DOM.setStyleAttribute(w.getElement(), "position", "absolute");
+        DOM.setStyleAttribute(w.getElement(), "overflow", "hidden");
+        DOM.setStyleAttribute(w.getElement(), "visibility", "visible");
+        componentList.add(w);
+
+        super.add(w, getElement());
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.ComplexPanel#remove(com.google.gwt.user
+     * .client.ui.Widget)
+     */
+    public boolean remove(Widget w) {
+        boolean wasRemoved = super.remove(w);
+        if (wasRemoved) {
+            resetPositionAttributes(w);
+
+            // If removing a component, remove its area
+            if (!(w instanceof CustomCaption)) {
+                DOM.removeChild(this.getElement(), componentToArea.get(w));
+            }
+
+            componentList.remove(w);
+            componentToZ.remove(w);
+            componentToArea.remove(w);
+        }
+        return wasRemoved;
+    }
+
+    public int getAllocatedHeight(Widget child) {
+        Element area = componentToArea.get(child);
+        if (area != null) {
+            return area.getOffsetHeight();
+        } else {
+            return layout[HEIGHT];
+        }
+    }
+
+    public int getAllocatedWidth(Widget child) {
+        Element area = componentToArea.get(child);
+        if (area != null) {
+            return area.getOffsetWidth();
+        } else {
+            return layout[WIDTH];
+        }
+    }
+
+    /**
+     * Custom caption class to encapsulate different caption parts
+     */
+    public class CustomCaption extends HTML {
+        public static final String CLASSNAME = "i-caption";
+
+        // Bit masks for different modes
+        public static final int HEADER_MODE = 1;
+        public static final int MARKER_MODE = 2;
+        public static final int COMBINED_MODE = HEADER_MODE | MARKER_MODE;
+
+        private final Paintable owner;
+
+        private Element errorIndicatorElement;
+
+        private Element requiredFieldIndicator;
+
+        private Icon icon;
+
+        private Element captionText;
+
+        private final ApplicationConnection client;
+
+        private int mode;
+
+        /**
+         * 
+         * @param component
+         *            optional owner of caption. If not set, getOwner will
+         *            return null
+         * @param client
+         */
+        public CustomCaption(Paintable component, ApplicationConnection client,
+                int mode) {
+            super();
+            this.client = client;
+            owner = component;
+            setStyleName(CLASSNAME);
+            this.mode = mode;
+        }
+
+        public void updateCaption(UIDL uidl) {
+            setVisible(!uidl.getBooleanAttribute("invisible"));
+
+            setStyleName(getElement(), "i-disabled", uidl
+                    .hasAttribute("disabled"));
+
+            boolean isEmpty = true;
+
+            if (uidl.hasAttribute("description")) {
+                if (captionText != null) {
+                    addStyleDependentName("hasdescription");
+                } else {
+                    removeStyleDependentName("hasdescription");
+                }
+            }
+
+            if (isMode(HEADER_MODE)) {
+                if (uidl.hasAttribute("icon")) {
+                    if (icon == null) {
+                        icon = new Icon(client);
+
+                        DOM.insertChild(getElement(), icon.getElement(), 0);
+                    }
+                    icon.setUri(uidl.getStringAttribute("icon"));
+                    isEmpty = false;
+                } else {
+                    if (icon != null) {
+                        DOM.removeChild(getElement(), icon.getElement());
+                        icon = null;
+                    }
+                }
+
+                if (uidl.hasAttribute("caption")) {
+                    if (captionText == null) {
+                        captionText = DOM.createSpan();
+                        DOM.insertChild(getElement(), captionText,
+                                icon == null ? 0 : 1);
+                    }
+                    String c = uidl.getStringAttribute("caption");
+                    if (c == null) {
+                        c = "";
+                    } else {
+                        isEmpty = false;
+                    }
+                    DOM.setInnerText(captionText, c);
+                } else {
+                    // TODO should span also be removed
+                }
+            }
+
+            if (isMode(MARKER_MODE)) {
+                if (uidl.getBooleanAttribute("required")) {
+                    isEmpty = false;
+                    if (requiredFieldIndicator == null) {
+                        requiredFieldIndicator = DOM.createSpan();
+                        DOM.setInnerText(requiredFieldIndicator, "*");
+                        DOM.setElementProperty(requiredFieldIndicator,
+                                "className", "i-required-field-indicator");
+                        DOM.appendChild(getElement(), requiredFieldIndicator);
+                    }
+                } else {
+                    if (requiredFieldIndicator != null) {
+                        DOM.removeChild(getElement(), requiredFieldIndicator);
+                        requiredFieldIndicator = null;
+                    }
+                }
+
+                if (uidl.hasAttribute("error")) {
+                    isEmpty = false;
+                    if (errorIndicatorElement == null) {
+                        errorIndicatorElement = DOM.createDiv();
+                        DOM.setInnerHTML(errorIndicatorElement, "&nbsp;");
+                        DOM.setElementProperty(errorIndicatorElement,
+                                "className", "i-errorindicator");
+                        DOM.appendChild(getElement(), errorIndicatorElement);
+                    }
+                } else if (errorIndicatorElement != null) {
+                    DOM.removeChild(getElement(), errorIndicatorElement);
+                    errorIndicatorElement = null;
+                }
+            }
+            // Workaround for IE weirdness, sometimes returns bad height in some
+            // circumstances when Caption is empty. See #1444
+            // IE7 bugs more often. I wonder what happens when IE8 arrives...
+            if (BrowserInfo.get().isIE()) {
+                if (isEmpty) {
+                    setHeight("0px");
+                    DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+                } else {
+                    setHeight("");
+                    DOM.setStyleAttribute(getElement(), "overflow", "");
+                }
+
+            }
+
+        }
+
+        public void onBrowserEvent(Event event) {
+            super.onBrowserEvent(event);
+            final Element target = DOM.eventGetTarget(event);
+            if (client != null && !(target == getElement())) {
+                client.handleTooltipEvent(event, owner);
+            }
+        }
+
+        /**
+         * Returns Paintable for which this Caption belongs to.
+         * 
+         * @return owner Widget
+         */
+        public Paintable getOwner() {
+            return owner;
+        }
+
+        /**
+         * Test if this CustomCaption is in a certain mode
+         * 
+         * @param value
+         * @return
+         */
+        public boolean isMode(int value) {
+            return (mode & value) == value;
+        }
+    }
+
+}// class ICoordinateLayout
diff --git a/src/com/itmill/toolkit/tests/tickets/Ticket1267.java b/src/com/itmill/toolkit/tests/tickets/Ticket1267.java
new file mode 100644 (file)
index 0000000..4c022b7
--- /dev/null
@@ -0,0 +1,477 @@
+package com.itmill.toolkit.tests.tickets;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.data.Property.ValueChangeEvent;
+import com.itmill.toolkit.data.Property.ValueChangeListener;
+import com.itmill.toolkit.data.util.HierarchicalContainer;
+import com.itmill.toolkit.data.util.IndexedContainer;
+import com.itmill.toolkit.data.validator.StringLengthValidator;
+import com.itmill.toolkit.terminal.ThemeResource;
+import com.itmill.toolkit.ui.Button;
+import com.itmill.toolkit.ui.CheckBox;
+import com.itmill.toolkit.ui.Component;
+import com.itmill.toolkit.ui.CoordinateLayout;
+import com.itmill.toolkit.ui.ExpandLayout;
+import com.itmill.toolkit.ui.GridLayout;
+import com.itmill.toolkit.ui.Label;
+import com.itmill.toolkit.ui.MenuBar;
+import com.itmill.toolkit.ui.OrderedLayout;
+import com.itmill.toolkit.ui.Panel;
+import com.itmill.toolkit.ui.Slider;
+import com.itmill.toolkit.ui.Table;
+import com.itmill.toolkit.ui.TextField;
+import com.itmill.toolkit.ui.Tree;
+import com.itmill.toolkit.ui.Window;
+import com.itmill.toolkit.ui.CoordinateLayout.Coordinates;
+import com.itmill.toolkit.ui.MenuBar.Command;
+import com.itmill.toolkit.ui.MenuBar.MenuItem;
+import com.itmill.toolkit.ui.Window.CloseEvent;
+import com.itmill.toolkit.ui.Window.CloseListener;
+
+public class Ticket1267 extends Application {
+
+    ArrayList<Component> componentList;
+
+    CoordinateLayout coordinateLayout = new CoordinateLayout();
+    Window main = new Window("Coordinatelayout demo");
+    HashMap<Component, Panel> control = new HashMap<Component, Panel>();
+    ExpandLayout mainLayout = new ExpandLayout();
+    // Components
+    MenuBar mainMenu;
+    Window controlPanel = new Window("Control panel for components");
+
+    OrderedLayout ol = new OrderedLayout();
+
+    boolean defaultmargins = false;
+
+    Application hostApp = this;
+    Class<Ticket1267> hostClass = Ticket1267.class;
+
+    private int numberOfTables;
+
+    private IndexedContainer emptyContainer = new IndexedContainer();
+    private HierarchicalContainer emptyHierarchy = new HierarchicalContainer();
+
+    private int numberOfPanels;
+
+    private int numberOfTextFields;
+
+    private int numberOfTrees;
+
+    public void init() {
+
+        // Setup container
+        emptyContainer.addContainerProperty("First Name", String.class, null);
+        emptyContainer.addContainerProperty("Last Name", String.class, null);
+        emptyContainer.addContainerProperty("Year", Integer.class, null);
+
+        // Setup menubar
+        mainMenu = getMainMenuBar();
+
+        // Setup control panel window
+        controlPanel.addListener(new CloseListener() {
+
+            public void windowClose(CloseEvent e) {
+                main.removeWindow(e.getWindow());
+
+            }
+
+        });
+
+        // TestField
+        TextField testField = new TextField("Pixel values");
+        testField.setImmediate(true);
+        final Coordinates xy = new Coordinates("0,0");
+
+        testField.addListener(new ValueChangeListener() {
+
+            /*
+             * (non-Javadoc)
+             * 
+             * @see
+             * com.itmill.toolkit.data.Property.ValueChangeListener#valueChange
+             * (com.itmill.toolkit.data.Property.ValueChangeEvent)
+             */
+            public void valueChange(ValueChangeEvent event) {
+                String str = (String) event.getProperty().getValue();
+                try {
+                    xy.setCoordinates(str);
+                } catch (Exception e) {
+                    main.showNotification("Wrong string format", e.toString(),
+                            Window.Notification.TYPE_WARNING_MESSAGE);
+                    return;
+                }
+                main.showNotification("Component added to " + xy.toString(),
+                        Window.Notification.TYPE_TRAY_NOTIFICATION);
+            }
+        });
+
+        coordinateLayout.addComponent(testField, xy);
+
+        // Setup coordinatelayout
+        coordinateLayout.setMargin(defaultmargins);
+        mainLayout.addComponent(mainMenu);
+        mainLayout.addComponent(coordinateLayout);
+        mainLayout.expand(coordinateLayout);
+
+        main.setLayout(mainLayout);
+
+        setMainWindow(main);
+
+    }
+
+    private MenuBar getMainMenuBar() {
+        MenuBar menu = new MenuBar();
+        MenuItem add = menu.addItem("Add component", null);
+        MenuItem remove = menu.addItem("Remove component", null);
+        MenuItem modify = menu.addItem("Modify component", null);
+        MenuItem cPanel = menu.addItem("Show / hide control panel", null);
+        cPanel.setCommand(new Command() {
+
+            public void menuSelected(MenuItem selectedItem) {
+                if (main.getChildWindows().contains(controlPanel)) {
+                    main.removeWindow(controlPanel);
+                } else {
+                    main.addWindow(controlPanel);
+
+                }
+            }
+        });
+
+        final MenuItem margins = menu.addItem("Toggle margins", null);
+        margins.setCommand(new Command() {
+
+            boolean marginsEnabled = defaultmargins;
+
+            public void menuSelected(MenuItem selectedItem) {
+                if (marginsEnabled) {
+                    margins.setIcon(null);
+                    marginsEnabled = false;
+                    coordinateLayout.setMargin(false);
+                } else {
+                    margins.setIcon(new ThemeResource("icons/16/ok.png"));
+                    marginsEnabled = true;
+                    coordinateLayout.setMargin(true);
+                }
+            }
+
+        });
+        try {
+            add.addItem("Table", new AddCommand(hostClass
+                    .getMethod("getNewTable"), remove, modify));
+
+            add.addItem("Panel", new AddCommand(hostClass
+                    .getMethod("getNewPanel"), remove, modify));
+
+            add.addItem("TextField", new AddCommand(hostClass
+                    .getMethod("getNewTextField"), remove, modify));
+            add.addItem("Tree", new AddCommand(hostClass
+                    .getMethod("getNewTree"), remove, modify));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return menu;
+    }
+
+    public Table getNewTable() {
+        Table table = new Table("Table " + numberOfTables++);
+        table.setContainerDataSource(emptyContainer);
+        table.setImmediate(true);
+        table.setIcon(new ThemeResource("icons/16/globe.png"));
+
+        return table;
+    }
+
+    public Panel getNewPanel() {
+        final Panel panel = new Panel("Panel " + numberOfPanels++);
+        Button addButton = new Button("Add component");
+        addButton.addListener(new Button.ClickListener() {
+            int count = 0;
+
+            public void buttonClick(Button.ClickEvent event) {
+                panel.addComponent(new Label("Component nro. " + count++));
+
+            }
+        });
+        panel.addComponent(addButton);
+
+        return panel;
+    }
+
+    public TextField getNewTextField() {
+        TextField textField = new TextField(
+                "TextField " + numberOfTextFields++,
+                "This textfield has a validator");
+        textField.setImmediate(true);
+        textField.setIcon(new ThemeResource("icons/16/document-image.png"));
+        textField.setRequired(true);
+        textField.addValidator(new StringLengthValidator(
+                "5 < String.length() < 10", 5, 10, false));
+
+        return textField;
+    }
+
+    public Tree getNewTree() {
+
+        Tree tree = new Tree("Tree " + numberOfTrees++);
+
+        return tree;
+    }
+
+    private class AddCommand implements Command {
+
+        private Method addMethod;
+        private MenuItem removeMenu;
+        private MenuItem modifyMenu;
+
+        public void menuSelected(MenuItem selectedItem) {
+            Component toAdd = null;
+            try {
+                toAdd = (Component) addMethod.invoke(hostApp);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            String caption = toAdd.getCaption();
+            Coordinates xy = coordinateLayout.addComponent(toAdd, "50%,50%");
+            getMainWindow().showNotification(
+                    "Component added to " + xy.toString(),
+                    Window.Notification.TYPE_TRAY_NOTIFICATION);
+
+            MenuItem modItem = getModMenuItem(modifyMenu, caption, toAdd);
+            removeMenu.addItem(caption, new RemoveCommand(toAdd, modItem,
+                    modifyMenu));
+
+            controlPanel.addComponent(getControlPanel(toAdd));
+
+        }
+
+        AddCommand(Method addMethod, MenuItem removeMenu, MenuItem modifyMenu) {
+
+            this.addMethod = addMethod;
+            this.removeMenu = removeMenu;
+            this.modifyMenu = modifyMenu;
+        }
+
+    }
+
+    private class RemoveCommand implements Command {
+
+        private Component toRemove;
+        private MenuItem modifyItem;
+        private MenuItem modifyMenu;
+
+        public RemoveCommand(Component toRemove, MenuBar.MenuItem modifyItem,
+                MenuItem modifyMenu) {
+            this.toRemove = toRemove;
+            this.modifyItem = modifyItem;
+            this.modifyMenu = modifyMenu;
+        }
+
+        public void menuSelected(MenuItem selectedItem) {
+            if (coordinateLayout.contains(toRemove)) {
+                coordinateLayout.removeComponent(toRemove);
+                MenuItem parent = selectedItem.getParent();
+                parent.removeChild(selectedItem);
+
+                if (modifyItem != null) {
+                    modifyMenu.removeChild(modifyItem);
+                }
+
+                controlPanel.removeComponent(getControlPanel(toRemove));
+                control.remove(toRemove);
+            }
+        }
+    }
+
+    public Panel getControlPanel(final Component c) {
+
+        if (control.get(c) == null) {
+            Panel newPanel = new Panel("Controls for " + c.getCaption());
+            newPanel.setLayout(new GridLayout(3, 6));
+
+            final int[] values = new int[6];
+            Arrays.fill(values, -1);
+
+            final CheckBox[] checkBoxArray = new CheckBox[6];
+            final Slider[] sliderArray = new Slider[6];
+
+            String[] directions = { "Left", "Top", "Width", "Height", "Right",
+                    "Bottom" };
+
+            final Label[] labelArray = new Label[6];
+
+            for (int i = 0; i < checkBoxArray.length; i++) {
+                final int j = i;
+
+                checkBoxArray[i] = new CheckBox(directions[i]);
+                sliderArray[i] = new Slider(-1, 100, 1);
+
+                labelArray[j] = new Label("-1");
+                labelArray[j].setEnabled(false);
+                checkBoxArray[j].setImmediate(true);
+                sliderArray[j].setImmediate(true);
+                sliderArray[j].setEnabled(false);
+                sliderArray[j].setWidth("300px");
+
+                checkBoxArray[j].addListener(new ValueChangeListener() {
+                    public void valueChange(ValueChangeEvent event) {
+                        Boolean isChecked = (Boolean) checkBoxArray[j]
+                                .getValue();
+                        if (isChecked.booleanValue()) {
+                            sliderArray[j].setEnabled(true);
+                            labelArray[j].setEnabled(true);
+                        } else {
+                            values[j] = -1;
+                            labelArray[j].setValue(new Integer(values[j]));
+                            sliderArray[j].setEnabled(false);
+                            labelArray[j].setEnabled(false);
+                            CoordinateLayout.Coordinates coords = coordinateLayout
+                                    .getCoordinates(c);
+                            coords.setCoordinates(values[0], values[1],
+                                    values[2], values[3], values[4], values[5]);
+                            coords.setUnitsPercent(true, true, true, true,
+                                    true, true);
+
+                        }
+                    }
+                });
+
+                sliderArray[j].addListener(new ValueChangeListener() {
+                    public void valueChange(ValueChangeEvent event) {
+                        Double newValue = (Double) event.getProperty()
+                                .getValue();
+                        values[j] = (int) newValue.doubleValue();
+                        CoordinateLayout.Coordinates coords = coordinateLayout
+                                .getCoordinates(c);
+                        coords.setCoordinates(values[0], values[1], values[2],
+                                values[3], values[4], values[5]);
+                        coords.setUnitsPercent(true, true, true, true, true,
+                                true);
+
+                        labelArray[j].setValue(new Integer(values[j]));
+
+                    }
+                });
+            }
+
+            for (int i = 0; i < sliderArray.length; i++) {
+                newPanel.addComponent(checkBoxArray[i]);
+                newPanel.addComponent(sliderArray[i]);
+                newPanel.addComponent(labelArray[i]);
+            }
+            control.put(c, newPanel);
+            return newPanel;
+        } else {
+            return control.get(c);
+        }
+    }
+
+    public MenuBar.MenuItem getModMenuItem(MenuItem parent, String id,
+            Component c) {
+        MenuBar.MenuItem item = null;
+        if (c instanceof Table) {
+            final Table table = (Table) c;
+            item = parent.addItem(id, null);
+            MenuBar.Command addContent = new MenuBar.Command() {
+
+                public void menuSelected(MenuItem selectedItem) {
+                    table
+                            .addContainerProperty("First Name", String.class,
+                                    null);
+                    table.addContainerProperty("Last Name", String.class, null);
+                    table.addContainerProperty("Year", Integer.class, null);
+
+                    table.addItem(new Object[] { "Nicolaus", "Copernicus",
+                            new Integer(1473) }, Integer.valueOf(1));
+                    table.addItem(new Object[] { "Tycho", "Brahe",
+                            new Integer(1546) }, Integer.valueOf(2));
+                    table.addItem(new Object[] { "Giordano", "Bruno",
+                            new Integer(1548) }, Integer.valueOf(3));
+                    table.addItem(new Object[] { "Galileo", "Galilei",
+                            new Integer(1564) }, Integer.valueOf(4));
+                    table.addItem(new Object[] { "Johannes", "Kepler",
+                            new Integer(1571) }, Integer.valueOf(5));
+                    table.addItem(new Object[] { "Isaac", "Newton",
+                            new Integer(1643) }, Integer.valueOf(6));
+                }
+
+            };
+
+            MenuBar.Command removeContent = new MenuBar.Command() {
+
+                public void menuSelected(MenuItem selectedItem) {
+                    table.setContainerDataSource(emptyContainer);
+
+                }
+
+            };
+
+            item.addItem("Add content", addContent);
+            item.addItem("Remove content", removeContent);
+
+        } else if (c instanceof Tree) {
+            final Tree tree = (Tree) c;
+            item = parent.addItem(id, null);
+
+            MenuBar.Command addContent = new MenuBar.Command() {
+                public void menuSelected(MenuBar.MenuItem selectedItem) {
+                    final Object[][] planets = new Object[][] {
+                            new Object[] { "Mercury" },
+                            new Object[] { "Venus" },
+                            new Object[] { "Earth", "The Moon" },
+                            new Object[] { "Mars", "Phobos", "Deimos" },
+                            new Object[] { "Jupiter", "Io", "Europa",
+                                    "Ganymedes", "Callisto" },
+
+                            new Object[] { "Saturn", "Titan", "Tethys",
+                                    "Dione", "Rhea", "Iapetus" },
+                            new Object[] { "Uranus", "Miranda", "Ariel",
+                                    "Umbriel", "Titania", "Oberon" },
+                            new Object[] { "Neptune", "Triton", "Proteus",
+                                    "Nereid", "Larissa" } };
+
+                    /* Add planets as root items in the tree. */
+                    for (int i = 0; i < planets.length; i++) {
+                        String planet = (String) (planets[i][0]);
+                        tree.addItem(planet);
+                        if (planets[i].length == 1) {
+                            /* The planet has no moons so make it a leaf. */
+                            tree.setChildrenAllowed(planet, false);
+                        } else {
+                            /* Add children (moons) under the planets. */
+                            for (int j = 1; j < planets[i].length; j++) {
+                                String moon = (String) planets[i][j];
+                                /* Add the item as a regular item. */
+                                tree.addItem(moon);
+                                /* Set it to be a child. */
+                                tree.setParent(moon, planet);
+                                /* Make the moons look like leaves. */
+                                tree.setChildrenAllowed(moon, false);
+                            }
+                            /* Expand the subtree. */
+                            tree.expandItemsRecursively(planet);
+                        }
+                    }
+                }
+            };
+
+            MenuBar.Command removeContent = new MenuBar.Command() {
+
+                public void menuSelected(MenuBar.MenuItem selectedItem) {
+                    tree.setContainerDataSource(emptyHierarchy);
+                }
+
+            };
+
+            item.addItem("Add content", addContent);
+            item.addItem("Remove content", removeContent);
+        }
+
+        return item;
+    }
+}
diff --git a/src/com/itmill/toolkit/ui/CoordinateLayout.java b/src/com/itmill/toolkit/ui/CoordinateLayout.java
new file mode 100644 (file)
index 0000000..87feebd
--- /dev/null
@@ -0,0 +1,768 @@
+package com.itmill.toolkit.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/**
+ * 
+ * A layout that enables absolute positioning for its children.
+ * 
+ * 
+ */
+public class CoordinateLayout extends AbstractLayout {
+
+    protected final ArrayList<Component> componentList;
+    protected final HashMap<Component, Coordinates> componentToCoord;
+
+    public static final int LEFT = 0;
+    public static final int TOP = 1;
+    public static final int WIDTH = 2;
+    public static final int HEIGHT = 3;
+    public static final int RIGHT = 4;
+    public static final int BOTTOM = 5;
+
+    /**
+     * Creates an empty coordinatelayout. The coordinateLayout is full size by
+     * default.
+     */
+    public CoordinateLayout() {
+        super();
+        componentList = new ArrayList<Component>();
+        componentToCoord = new HashMap<Component, Coordinates>();
+
+        this.setSizeFull();
+    }
+
+    /**
+     * 
+     * <p>
+     * Adds a component to the layout at the specified coordinates. The values
+     * are treated as pixel values by default and for example
+     * {@link com.itmill.toolkit.ui.CoordinateLayout.Coordinates#setUnitPercent(int, boolean)}
+     * can be used to set the values as percentage.
+     * </p>
+     * 
+     * <p>
+     * If the value is negative, it is interpreted as automatic (terminal
+     * decides). Null argument is not permitted and will throw an
+     * IllegalArgumentException.
+     * </p>
+     * <dl>
+     * <dt><b>Examples:</b></dt>
+     * 
+     * <dd>
+     * <code>addComponent(c, 0, 0, -1, -1, -1, -1)</code> to attach to
+     * upper-left corner and let terminal decide size</dd>
+     * 
+     * <dd><code>Coordinates c = addComponent(c, 5, 5, 200, 200, -1, -1)</dd>
+     * <dd>c.setUnitPercent(CoordinateLayout.LEFT, true)</dd>
+     * <dd>c.setUnitPercent(CoordinateLayout.TOP, true) </code> to attach 5%
+     * from upper-left corner and define size to 200 x 200 pixels</dd>
+     * 
+     * <dd>
+     * <code>addComponent(c, 0, 0, -1, -1, 0, 0)</code> to stretch component to
+     * cover layout area (attached to all four corners)</dd>
+     * </dl>
+     * 
+     * 
+     * @param c
+     *            component to be added
+     * @param left
+     *            distance of component left from layout left
+     * @param top
+     *            distance of component top from layout top
+     * @param width
+     *            component width
+     * @param height
+     *            component height
+     * @param right
+     *            distance of component right from layout right
+     * @param bottom
+     *            distance of component bottom from layout bottom
+     * @return the coordinates object for the component
+     * 
+     * @throws IllegalArgumentException
+     */
+    public Coordinates addComponent(Component c, int left, int top, int width,
+            int height, int right, int bottom) throws IllegalArgumentException {
+        if (c == null) {
+            throw new IllegalArgumentException();
+        }
+
+        Coordinates newCoords = new Coordinates(left, top, width, height,
+                right, bottom);
+        componentToCoord.put(c, newCoords);
+        addComponent(c);
+
+        return newCoords;
+    }
+
+    /**
+     * <p>
+     * Add a component to the specified coordinates. The string format is
+     * <i>"left[%], top[%] [,width[%], height[%] [,right[%], bottom[%]]]"</i>.
+     * Null arguments will throw an IllegalArgumentException.
+     * </p>
+     * 
+     * <dl>
+     * <dt><b>Examples for string format:</b></dt>
+     * 
+     * <dd>
+     * <code>addComponent(c, "0, 0")</code> to attach to upper-left corner and
+     * let terminal decide size</dd>
+     * 
+     * <dd>
+     * <code>addComponent(c, "5%, 5%, 200, 200")</code> to attach 5% from
+     * upper-left corner and define size to 200 x 200 pixels</dd>
+     * 
+     * <dd>
+     * <code>addComponent(c,"0, 0, -1, -1,0, 0")</code> to stretch component to
+     * cover coordinateLayout area (attached to all four corners)</dd>
+     * 
+     * </dl>
+     * 
+     * @param c
+     *            the component to be added
+     * @param coordString
+     *            coordinates in the specified string format
+     * @return the created Coordinates object for the component
+     * 
+     * @throws IllegalArgumentException
+     */
+    public Coordinates addComponent(Component c, String coordString)
+            throws IllegalArgumentException {
+        if (c == null) {
+            throw new IllegalArgumentException();
+        }
+        Coordinates newCoords = new Coordinates(coordString);
+        componentToCoord.put(c, newCoords);
+        addComponent(c);
+
+        return newCoords;
+    }
+
+    /**
+     * Adds a component to the layout at the specified coordinates. Null
+     * arguments are not permitted and will throw an IllegalArgumentException.
+     * 
+     * @param c
+     *            the component to be added
+     * @param coord
+     *            the coordinates of the component as a Coordinates object
+     * @throws IllegalArgument
+     *             Exception
+     */
+    public void addComponent(Component c, CoordinateLayout.Coordinates coord)
+            throws IllegalArgumentException {
+        if (c == null || coord == null) {
+            throw new IllegalArgumentException();
+        }
+        componentToCoord.put(c, coord);
+        addComponent(c);
+    }
+
+    /**
+     * Sets the coordinates of a child component. Null arguments are not
+     * permitted and will throw an IllegalArgumentException. In addition, the
+     * user of this method must make sure that the given component is a child of
+     * this layout.
+     * 
+     * @param c
+     *            the target component
+     * @param left
+     *            distance of component left from layout left
+     * @param top
+     *            distance of component top from layout top
+     * @param width
+     *            component width
+     * @param height
+     *            component height
+     * @param right
+     *            distance of component right from layout right
+     * @param bottom
+     *            distance of component bottom from layout bottom
+     * @throws IllegalArgumentException
+     */
+    public void setCoordinates(Component c, int left, int top, int width,
+            int height, int right, int bottom) throws IllegalArgumentException {
+        if (c == null || !componentList.contains(c)) {
+            throw new IllegalArgumentException();
+        }
+        Coordinates coords = (Coordinates) componentToCoord.get(c);
+        coords.setCoordinates(left, top, width, height, right, bottom);
+    }
+
+    /**
+     * Sets the coordinates of a child component. Null arguments are not
+     * permitted and will throw an IllegalArgumentException. In addition, the
+     * user of this method must make sure that the given component is a child of
+     * this coordinateLayout.
+     * 
+     * @param c
+     *            the component thats position is changed
+     * @param newCoord
+     *            the new coordinates as a Coordinates object
+     * @throws IllegalArgumentException
+     */
+    public void setCoordinates(Component c,
+            CoordinateLayout.Coordinates newCoord)
+            throws IllegalArgumentException {
+        if (c == null || newCoord == null || !componentList.contains(c)) {
+            throw new IllegalArgumentException();
+        }
+        // Detach old coordinate object from this layout
+        Coordinates oldCoord = (Coordinates) componentToCoord.remove(c);
+        oldCoord.removeParentLayout(this);
+
+        // Attach new
+        newCoord.addParentLayout(this);
+        componentToCoord.put(c, newCoord);
+
+        requestRepaint();
+    }
+
+    /**
+     * This will put the given component at the top of the list.
+     * 
+     * @param c
+     *            the component to be put to the top
+     */
+    public void sendToTop(Component c) {
+        componentList.remove(c);
+        componentList.add(c);
+
+        requestRepaint();
+    }
+
+    /**
+     * This will put the given component at the bottom of the list.
+     * 
+     * @param c
+     *            the component to be put to the bottom
+     */
+    public void sendToBottom(Component c) {
+        componentList.remove(c);
+        componentList.add(0, c);
+
+        requestRepaint();
+    }
+
+    /**
+     * If a component is added with this method the component is added to the
+     * top-left corner of the layout. The coordinates can later be accessed via
+     * {@link CoordinateLayout#getCoordinates(Component)}
+     * 
+     * @param c
+     *            the component to be added
+     * @throws IllegalArgument
+     *             Exception
+     * 
+     * @see com.itmill.toolkit.ui.AbstractComponentContainer#addComponent(com.itmill.toolkit.ui.Component)
+     */
+    public void addComponent(Component c) throws IllegalArgumentException {
+        if (c == null) {
+            throw new IllegalArgumentException();
+        }
+        componentList.add(c);
+
+        // Someone might call this outside this object
+        if (componentToCoord.get(c) == null) {
+            componentToCoord.put(c, new Coordinates(0, 0, -1, -1, -1, -1));
+
+        }
+
+        super.addComponent(c);
+        requestRepaint();
+        ((Coordinates) componentToCoord.get(c)).addParentLayout(this);
+    }
+
+    /**
+     * Removes the component from the coordinateLayout. Method will throw
+     * IllegalArgumentException if the given component is not a child of this
+     * layout.
+     * 
+     * @param c
+     *            the component to be removed
+     * @throws IllegalArgumentException
+     * 
+     * @see com.itmill.toolkit.ui.AbstractComponentContainer#removeComponent(com.itmill.toolkit.ui.Component)
+     */
+    public void removeComponent(Component c) throws IllegalArgumentException {
+        if (c == null || !componentList.contains(c)) {
+            throw new IllegalArgumentException();
+        }
+        ((Coordinates) componentToCoord.get(c)).removeParentLayout(this);
+
+        super.removeComponent(c);
+        componentList.remove(c);
+        componentToCoord.remove(c);
+
+        requestRepaint();
+    }
+
+    /**
+     * Replaces a child component with a new one. The new component uses the
+     * same coordinates as the previous component. If the arguments are null or
+     * the component is not a child of this layout IllegalArgumentException is
+     * thrown.
+     * 
+     * @param c1
+     *            the existing child component
+     * @param c2
+     *            the new component
+     * 
+     * @throws IllegalArgumentException
+     * 
+     * @see com.itmill.toolkit.ui.ComponentContainer#replaceComponent(com.itmill.toolkit.ui.Component,
+     *      com.itmill.toolkit.ui.Component)
+     */
+    public void replaceComponent(Component c1, Component c2) {
+        if (c1 == null || c2 == null || !componentList.contains(c1)) {
+            throw new IllegalArgumentException();
+        }
+        componentList.set(componentList.indexOf(c1), c2);
+        requestRepaint();
+    }
+
+    /**
+     * Get the coordinates for a given component in this layout
+     * 
+     * @param c
+     * @return the Coordinates object for the given component
+     * @throws IllegalArgumentException
+     *             thrown if component is not in this coordinateLayout
+     */
+    public Coordinates getCoordinates(Component c)
+            throws IllegalArgumentException {
+        if (c == null || componentToCoord.get(c) == null) {
+            throw new IllegalArgumentException();
+        }
+        return (Coordinates) componentToCoord.get(c);
+    }
+
+    /**
+     * @see com.itmill.toolkit.ui.ComponentContainer#getComponentIterator()
+     */
+    public Iterator<Component> getComponentIterator() {
+        return componentList.iterator();
+    }
+
+    /**
+     * Returns the iterator for the coordinates of the child components in this
+     * layout.
+     * 
+     * @return the iterator for the coordinate list
+     */
+    public Iterator<Coordinates> getComponentCoordinateIterator() {
+        return componentToCoord.values().iterator();
+    }
+
+    /**
+     * Check if this layout has components.
+     * 
+     * @return true if this coordinateLayout has children, false otherwise
+     */
+    public boolean hasComponents() {
+        return componentList.isEmpty();
+    }
+
+    /**
+     * Check if a given component is in this layout
+     * 
+     * @param c
+     * @return true if the component is in this layout, false otherwise
+     */
+    public boolean contains(Component c) {
+        return componentList.contains(c);
+    }
+
+    /**
+     * Get child component by index.
+     * 
+     * @param index
+     *            must be index >= 0 and index < size() -1
+     * @return the child component
+     * @throws IllegalArgumentException
+     *             if given index is illegal
+     */
+    public Component getComponentByIndex(int index)
+            throws IllegalArgumentException {
+        if (index < 0 || index > componentList.size() - 1) {
+            throw new IllegalArgumentException("Illegal index");
+        }
+
+        return (Component) componentList.get(index);
+    }
+
+    /**
+     * Returns the number of child components.
+     * 
+     * @return number of children
+     */
+    public int size() {
+        return componentList.size();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.itmill.toolkit.ui.AbstractLayout#getTag()
+     */
+    public String getTag() {
+        return "coordinatelayout";
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.itmill.toolkit.ui.AbstractLayout#paintContent(com.itmill.toolkit.
+     * terminal.PaintTarget)
+     */
+    public void paintContent(PaintTarget target) throws PaintException {
+        // Superclass writes any common attributes in the paint target.
+        super.paintContent(target);
+
+        for (Iterator<Component> componentIterator = getComponentIterator(); componentIterator
+                .hasNext();) {
+
+            Component component = (Component) componentIterator.next();
+            Coordinates coords = (Coordinates) componentToCoord.get(component);
+
+            target.startTag("component");
+            target.addAttribute("position", coords.toString());
+
+            component.paint(target);
+
+            target.endTag("component");
+
+        }// for
+
+    }// paintContent
+
+    /**
+     * 
+     * This class is used as a container for the coordinates used by
+     * CoordinateLayout. When attached to a CoordinateLayout object, changes to
+     * this object are reflected on the layout.
+     * 
+     */
+    public static class Coordinates {
+
+        // Length of the property arrays
+        protected static final int NUMBEROFPROPERTIES = 6;
+
+        // The actual coordinates
+        protected final int[] properties;
+
+        // These help to decipher the coordinates
+        protected final boolean[] isUnitPercent;
+
+        // Host layout(s)
+        protected final ArrayList<CoordinateLayout> listeners = new ArrayList<CoordinateLayout>(
+                1);
+
+        /**
+         * Creates a new Coordinates object with the specified values. If the
+         * user does not wish to specify a value, -1 can be used to let the
+         * terminal decide the value.
+         * 
+         * The values are treated as absolute. You can change them to be
+         * relative with setUnitsPercent or setAllUnitsPercent.
+         * 
+         * @param top
+         *            distance of component top from coordinateLayout top
+         * @param right
+         *            distance of component right from coordinateLayout right
+         * @param bottom
+         *            distance of component bottom from coordinateLayout bottom
+         * @param left
+         *            distance of component left from coordinateLayout left
+         * @param width
+         *            component width
+         * @param height
+         *            component height
+         * 
+         * @throws NumberFormatException
+         */
+        public Coordinates(int left, int top, int width, int height, int right,
+                int bottom) throws IllegalArgumentException {
+
+            properties = new int[NUMBEROFPROPERTIES];
+            isUnitPercent = new boolean[NUMBEROFPROPERTIES];
+
+            properties[LEFT] = left;
+            properties[TOP] = top;
+            properties[WIDTH] = width;
+            properties[HEIGHT] = height;
+            properties[RIGHT] = right;
+            properties[BOTTOM] = bottom;
+
+            for (int i = 0; i < NUMBEROFPROPERTIES; i++) {
+                if (properties[i] < -1) {
+                    throw new IllegalArgumentException(
+                            "Illegal coordinate value " + properties[i]);
+                }
+            }
+
+        }
+
+        /**
+         * Create a new Coordinates object with the given value string.
+         * 
+         * @param coordinateString
+         * @throws IllegalArgumentException
+         * @see com.itmill.toolkit.ui.CoordinateLayout#addComponent(Component,
+         *      String)
+         */
+        public Coordinates(String coordinateString)
+                throws IllegalArgumentException {
+
+            properties = new int[NUMBEROFPROPERTIES];
+            isUnitPercent = new boolean[NUMBEROFPROPERTIES];
+
+            Arrays.fill(properties, -1);
+
+            setValuesFromString(coordinateString);
+
+        }
+
+        /**
+         * Set unit of the given argument to be percentage. Initially all are
+         * false.
+         * 
+         * @param top
+         * @param right
+         * @param bottom
+         * @param left
+         * @param width
+         * @param height
+         */
+        public void setUnitsPercent(boolean left, boolean top, boolean width,
+                boolean height, boolean right, boolean bottom) {
+            isUnitPercent[TOP] = top;
+            isUnitPercent[LEFT] = left;
+            isUnitPercent[WIDTH] = width;
+            isUnitPercent[HEIGHT] = height;
+            isUnitPercent[RIGHT] = right;
+            isUnitPercent[BOTTOM] = bottom;
+
+            notifyParents();
+        }
+
+        /**
+         * Set if a property should be treated as a percentage value.
+         * 
+         * @param valueId
+         *            either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+         *            CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+         *            CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+         * @param isPercent
+         *            true if the value should be treated as a percentage, false
+         *            otherwise
+         */
+        public void setUnitPercent(int valueId, boolean isPercent) {
+            isUnitPercent[valueId] = isPercent;
+
+            notifyParents();
+        }
+
+        /**
+         * Set if the values should be treated as percentage.
+         * 
+         * @param value
+         *            true if all the values should be treated as percentage,
+         *            false if all the values should in treated as abslute pixel
+         *            values
+         */
+        public void setAllUnitsPercent(boolean value) {
+            java.util.Arrays.fill(isUnitPercent, value);
+            notifyParents();
+        }
+
+        /**
+         * Set a value for a direction.
+         * 
+         * @param direction
+         *            either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+         *            CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+         *            CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+         * @value the value to be set
+         */
+        public void setCoordinate(int direction, int value) {
+            properties[direction] = value;
+            notifyParents();
+        }
+
+        /**
+         * Set coordinates for this object.
+         * 
+         * @param top
+         * @param right
+         * @param bottom
+         * @param left
+         */
+        public void setCoordinates(int left, int top, int width, int height,
+                int right, int bottom) {
+            properties[LEFT] = left;
+            properties[TOP] = top;
+            properties[WIDTH] = width;
+            properties[HEIGHT] = height;
+            properties[RIGHT] = right;
+            properties[BOTTOM] = bottom;
+
+            for (int i = 0; i < NUMBEROFPROPERTIES; i++) {
+                if (properties[i] < -1) {
+                    throw new IllegalArgumentException(
+                            "Illegal coordinate value " + properties[i]);
+                }
+            }
+            notifyParents();
+        }
+
+        /**
+         * Set the values in string format. For reference, see
+         * {@link CoordinateLayout#addComponent(Component, String)}
+         * 
+         * @param coordinateString
+         * @throws IllegalArgumentException
+         */
+        public void setCoordinates(String coordinateString)
+                throws IllegalArgumentException {
+            setValuesFromString(coordinateString);
+            notifyParents();
+        }
+
+        /**
+         * Returns the coordinates for this object.
+         * 
+         * @param direction
+         *            either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+         *            CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+         *            CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+         * @return coordinates for the given direction
+         */
+        public int getCoordinate(int direction) {
+            return properties[direction];
+        }
+
+        /**
+         * Check if a given value is a percentage value. The default is false.
+         * 
+         * @param value
+         *            either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+         *            CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+         *            CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+         * 
+         * @return true if the given value has percentages as its unit, false
+         *         otherwise
+         */
+        public boolean isValuePercent(int value) {
+            return isUnitPercent[value];
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.lang.Object#toString()
+         */
+        public String toString() {
+            StringBuffer returnString = new StringBuffer();
+
+            for (int i = 0; i < properties.length; i++) {
+                returnString.append(properties[i]);
+                if (isUnitPercent[i]) {
+                    returnString.append("%");
+                }
+                if (i != properties.length - 1) {
+                    returnString.append(",");
+                }
+            }
+
+            return returnString.toString();
+        }
+
+        /*
+         * Parses the string to integer values
+         */
+        protected void setValuesFromString(String coordinateString) {
+
+            int[] newProperties = new int[6];
+            boolean[] newPercent = new boolean[6];
+
+            resetContents(newProperties, newPercent);
+
+            String coordStringArray[] = coordinateString.split(",");
+            if (coordStringArray.length > 6) {
+                throw new IllegalArgumentException(
+                        "Incorrect string syntax: too many arguments");
+            }
+
+            Pattern numberPattern = Pattern.compile("[-]??\\d+");
+            String percentRegex = ".*%.*";
+            Matcher matcher = null;
+            try {
+                for (int i = 0; i < coordStringArray.length; i++) {
+
+                    matcher = numberPattern.matcher(coordStringArray[i].trim());
+                    if (matcher.find()) {
+                        newProperties[i] = Integer.parseInt(matcher.group());
+
+                        newPercent[i] = coordStringArray[i]
+                                .matches(percentRegex);
+                    } else {
+                        throw new IllegalArgumentException(
+                                "Error parsing number: " + coordStringArray[i]);
+                    }
+                }
+            } catch (IllegalArgumentException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new IllegalArgumentException("Error parsing string: "
+                        + e.toString());
+            }
+
+            // Only set new values if there were no parsing errors
+            resetContents(properties, isUnitPercent);
+            System.arraycopy(newProperties, 0, properties, 0,
+                    NUMBEROFPROPERTIES);
+            System.arraycopy(newPercent, 0, isUnitPercent, 0,
+                    NUMBEROFPROPERTIES);
+        }
+
+        /*
+         * Reset coordinate properties. Does not notify listeners
+         */
+        protected void resetContents(int[] prop, boolean[] perc) {
+            Arrays.fill(prop, -1);
+            Arrays.fill(perc, false);
+        }
+
+        /*
+         * These methods ensure that changes to the coordinate objects are
+         * reflected to the host layout
+         */
+        protected void addParentLayout(CoordinateLayout listener) {
+            listeners.add(listener);
+        }
+
+        protected void removeParentLayout(CoordinateLayout listener) {
+            listeners.remove(listener);
+        }
+
+        protected void notifyParents() {
+            for (int i = 0; i < listeners.size(); i++) {
+                ((CoordinateLayout) listeners.get(i)).requestRepaint();
+            }
+        }
+    }// class Coordinates
+
+}// class CoordinateLayout
+