]> source.dussan.org Git - vaadin-framework.git/commitdiff
Merge master
authorJouni Koivuviita <jouni@vaadin.com>
Fri, 17 Aug 2012 10:30:54 +0000 (13:30 +0300)
committerJouni Koivuviita <jouni@vaadin.com>
Fri, 17 Aug 2012 10:30:54 +0000 (13:30 +0300)
1  2 
client/src/com/vaadin/terminal/gwt/client/ComponentLocator.java
client/src/com/vaadin/terminal/gwt/client/ComputedStyle.java
client/src/com/vaadin/terminal/gwt/client/LayoutManager.java
client/src/com/vaadin/terminal/gwt/client/ServerConnector.java
client/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java
client/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java
shared/src/com/vaadin/shared/ComponentState.java

index 0000000000000000000000000000000000000000,959f03e46d16f7e4d212d0a1f5c0ced7489aed1d..f30c73156237a34517206e21a16a4807d5d258cd
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,622 +1,631 @@@
+ /*
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.terminal.gwt.client;
+ import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.List;
+ import com.google.gwt.user.client.DOM;
+ import com.google.gwt.user.client.Element;
+ import com.google.gwt.user.client.ui.HasWidgets;
+ import com.google.gwt.user.client.ui.RootPanel;
+ import com.google.gwt.user.client.ui.Widget;
+ import com.vaadin.shared.ComponentState;
+ import com.vaadin.shared.Connector;
+ import com.vaadin.shared.communication.SharedState;
+ import com.vaadin.terminal.gwt.client.ui.SubPartAware;
++import com.vaadin.terminal.gwt.client.ui.VBoxLayout;
+ import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout;
+ import com.vaadin.terminal.gwt.client.ui.orderedlayout.VMeasuringOrderedLayout;
+ import com.vaadin.terminal.gwt.client.ui.root.VRoot;
+ import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheetPanel;
+ import com.vaadin.terminal.gwt.client.ui.window.VWindow;
+ import com.vaadin.terminal.gwt.client.ui.window.WindowConnector;
+ /**
+  * ComponentLocator provides methods for generating a String locator for a given
+  * DOM element and for locating a DOM element using a String locator.
+  */
+ public class ComponentLocator {
+     /**
+      * Separator used in the String locator between a parent and a child widget.
+      */
+     private static final String PARENTCHILD_SEPARATOR = "/";
+     /**
+      * Separator used in the String locator between the part identifying the
+      * containing widget and the part identifying the target element within the
+      * widget.
+      */
+     private static final String SUBPART_SEPARATOR = "#";
+     /**
+      * String that identifies the root panel when appearing first in the String
+      * locator.
+      */
+     private static final String ROOT_ID = "Root";
+     /**
+      * Reference to ApplicationConnection instance.
+      */
+     private ApplicationConnection client;
+     /**
+      * Construct a ComponentLocator for the given ApplicationConnection.
+      * 
+      * @param client
+      *            ApplicationConnection instance for the application.
+      */
+     public ComponentLocator(ApplicationConnection client) {
+         this.client = client;
+     }
+     /**
+      * Generates a String locator which uniquely identifies the target element.
+      * The {@link #getElementByPath(String)} method can be used for the inverse
+      * operation, i.e. locating an element based on the return value from this
+      * method.
+      * <p>
+      * Note that getElementByPath(getPathForElement(element)) == element is not
+      * always true as {@link #getPathForElement(Element)} can return a path to
+      * another element if the widget determines an action on the other element
+      * will give the same result as the action on the target element.
+      * </p>
+      * 
+      * @since 5.4
+      * @param targetElement
+      *            The element to generate a path for.
+      * @return A String locator that identifies the target element or null if a
+      *         String locator could not be created.
+      */
+     public String getPathForElement(Element targetElement) {
+         String pid = null;
+         Element e = targetElement;
+         while (true) {
+             pid = ConnectorMap.get(client).getConnectorId(e);
+             if (pid != null) {
+                 break;
+             }
+             e = DOM.getParent(e);
+             if (e == null) {
+                 break;
+             }
+         }
+         Widget w = null;
+         if (pid != null) {
+             // If we found a Paintable then we use that as reference. We should
+             // find the Paintable for all but very special cases (like
+             // overlays).
+             w = ((ComponentConnector) ConnectorMap.get(client)
+                     .getConnector(pid)).getWidget();
+             /*
+              * Still if the Paintable contains a widget that implements
+              * SubPartAware, we want to use that as a reference
+              */
+             Widget targetParent = findParentWidget(targetElement, w);
+             while (targetParent != w && targetParent != null) {
+                 if (targetParent instanceof SubPartAware) {
+                     /*
+                      * The targetParent widget is a child of the Paintable and
+                      * the first parent (of the targetElement) that implements
+                      * SubPartAware
+                      */
+                     w = targetParent;
+                     break;
+                 }
+                 targetParent = targetParent.getParent();
+             }
+         }
+         if (w == null) {
+             // Check if the element is part of a widget that is attached
+             // directly to the root panel
+             RootPanel rootPanel = RootPanel.get();
+             int rootWidgetCount = rootPanel.getWidgetCount();
+             for (int i = 0; i < rootWidgetCount; i++) {
+                 Widget rootWidget = rootPanel.getWidget(i);
+                 if (rootWidget.getElement().isOrHasChild(targetElement)) {
+                     // The target element is contained by this root widget
+                     w = findParentWidget(targetElement, rootWidget);
+                     break;
+                 }
+             }
+             if (w != null) {
+                 // We found a widget but we should still see if we find a
+                 // SubPartAware implementor (we cannot find the Paintable as
+                 // there is no link from VOverlay to its paintable/owner).
+                 Widget subPartAwareWidget = findSubPartAwareParentWidget(w);
+                 if (subPartAwareWidget != null) {
+                     w = subPartAwareWidget;
+                 }
+             }
+         }
+         if (w == null) {
+             // Containing widget not found
+             return null;
+         }
+         // Determine the path for the target widget
+         String path = getPathForWidget(w);
+         if (path == null) {
+             /*
+              * No path could be determined for the target widget. Cannot create
+              * a locator string.
+              */
+             return null;
+         }
+         if (w.getElement() == targetElement) {
+             /*
+              * We are done if the target element is the root of the target
+              * widget.
+              */
+             return path;
+         } else if (w instanceof SubPartAware) {
+             /*
+              * If the widget can provide an identifier for the targetElement we
+              * let it do that
+              */
+             String elementLocator = ((SubPartAware) w)
+                     .getSubPartName(targetElement);
+             if (elementLocator != null) {
+                 return path + SUBPART_SEPARATOR + elementLocator;
+             }
+         }
+         /*
+          * If everything else fails we use the DOM path to identify the target
+          * element
+          */
+         return path + getDOMPathForElement(targetElement, w.getElement());
+     }
+     /**
+      * Finds the first widget in the hierarchy (moving upwards) that implements
+      * SubPartAware. Returns the SubPartAware implementor or null if none is
+      * found.
+      * 
+      * @param w
+      *            The widget to start from. This is returned if it implements
+      *            SubPartAware.
+      * @return The first widget (upwards in hierarchy) that implements
+      *         SubPartAware or null
+      */
+     private Widget findSubPartAwareParentWidget(Widget w) {
+         while (w != null) {
+             if (w instanceof SubPartAware) {
+                 return w;
+             }
+             w = w.getParent();
+         }
+         return null;
+     }
+     /**
+      * Returns the first widget found when going from {@code targetElement}
+      * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a
+      * parent of {@code targetElement}.
+      * 
+      * @param targetElement
+      * @param ancestorWidget
+      * @return The widget whose root element is a parent of
+      *         {@code targetElement}.
+      */
+     private Widget findParentWidget(Element targetElement, Widget ancestorWidget) {
+         /*
+          * As we cannot resolve Widgets from the element we start from the
+          * widget and move downwards to the correct child widget, as long as we
+          * find one.
+          */
+         if (ancestorWidget instanceof HasWidgets) {
+             for (Widget w : ((HasWidgets) ancestorWidget)) {
+                 if (w.getElement().isOrHasChild(targetElement)) {
+                     return findParentWidget(targetElement, w);
+                 }
+             }
+         }
+         // No children found, this is it
+         return ancestorWidget;
+     }
+     /**
+      * Locates an element based on a DOM path and a base element.
+      * 
+      * @param baseElement
+      *            The base element which the path is relative to
+      * @param path
+      *            String locator (consisting of domChild[x] parts) that
+      *            identifies the element
+      * @return The element identified by path, relative to baseElement or null
+      *         if the element could not be found.
+      */
+     private Element getElementByDOMPath(Element baseElement, String path) {
+         String parts[] = path.split(PARENTCHILD_SEPARATOR);
+         Element element = baseElement;
+         for (String part : parts) {
+             if (part.startsWith("domChild[")) {
+                 String childIndexString = part.substring("domChild[".length(),
+                         part.length() - 1);
+                 try {
+                     int childIndex = Integer.parseInt(childIndexString);
+                     element = DOM.getChild(element, childIndex);
+                 } catch (Exception e) {
+                     return null;
+                 }
+             }
+         }
+         return element;
+     }
+     /**
+      * Generates a String locator using domChild[x] parts for the element
+      * relative to the baseElement.
+      * 
+      * @param element
+      *            The target element
+      * @param baseElement
+      *            The starting point for the locator. The generated path is
+      *            relative to this element.
+      * @return A String locator that can be used to locate the target element
+      *         using {@link #getElementByDOMPath(Element, String)} or null if
+      *         the locator String cannot be created.
+      */
+     private String getDOMPathForElement(Element element, Element baseElement) {
+         Element e = element;
+         String path = "";
+         while (true) {
+             Element parent = DOM.getParent(e);
+             if (parent == null) {
+                 return null;
+             }
+             int childIndex = -1;
+             int childCount = DOM.getChildCount(parent);
+             for (int i = 0; i < childCount; i++) {
+                 if (e == DOM.getChild(parent, i)) {
+                     childIndex = i;
+                     break;
+                 }
+             }
+             if (childIndex == -1) {
+                 return null;
+             }
+             path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]"
+                     + path;
+             if (parent == baseElement) {
+                 break;
+             }
+             e = parent;
+         }
+         return path;
+     }
+     /**
+      * Locates an element using a String locator (path) which identifies a DOM
+      * element. The {@link #getPathForElement(Element)} method can be used for
+      * the inverse operation, i.e. generating a string expression for a DOM
+      * element.
+      * 
+      * @since 5.4
+      * @param path
+      *            The String locater which identifies the target element.
+      * @return The DOM element identified by {@code path} or null if the element
+      *         could not be located.
+      */
+     public Element getElementByPath(String path) {
+         /*
+          * Path is of type "targetWidgetPath#componentPart" or
+          * "targetWidgetPath".
+          */
+         String parts[] = path.split(SUBPART_SEPARATOR, 2);
+         String widgetPath = parts[0];
+         Widget w = getWidgetFromPath(widgetPath);
+         if (w == null || !Util.isAttachedAndDisplayed(w)) {
+             return null;
+         }
+         if (parts.length == 1) {
+             int pos = widgetPath.indexOf("domChild");
+             if (pos == -1) {
+                 return w.getElement();
+             }
+             // Contains dom reference to a sub element of the widget
+             String subPath = widgetPath.substring(pos);
+             return getElementByDOMPath(w.getElement(), subPath);
+         } else if (parts.length == 2) {
+             if (w instanceof SubPartAware) {
+                 return ((SubPartAware) w).getSubPartElement(parts[1]);
+             }
+         }
+         return null;
+     }
+     /**
+      * Creates a locator String for the given widget. The path can be used to
+      * locate the widget using {@link #getWidgetFromPath(String)}.
+      * 
+      * Returns null if no path can be determined for the widget or if the widget
+      * is null.
+      * 
+      * @param w
+      *            The target widget
+      * @return A String locator for the widget
+      */
+     private String getPathForWidget(Widget w) {
+         if (w == null) {
+             return null;
+         }
+         if (w instanceof VRoot) {
+             return "";
+         } else if (w instanceof VWindow) {
+             Connector windowConnector = ConnectorMap.get(client)
+                     .getConnector(w);
+             List<WindowConnector> subWindowList = client.getRootConnector()
+                     .getSubWindows();
+             int indexOfSubWindow = subWindowList.indexOf(windowConnector);
+             return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]";
+         } else if (w instanceof RootPanel) {
+             return ROOT_ID;
+         }
+         Widget parent = w.getParent();
+         String basePath = getPathForWidget(parent);
+         if (basePath == null) {
+             return null;
+         }
+         String simpleName = Util.getSimpleName(w);
+         /*
+          * Check if the parent implements Iterable. At least VPopupView does not
+          * implement HasWdgets so we cannot check for that.
+          */
+         if (!(parent instanceof Iterable<?>)) {
+             // Parent does not implement Iterable so we cannot find out which
+             // child this is
+             return null;
+         }
+         Iterator<?> i = ((Iterable<?>) parent).iterator();
+         int pos = 0;
+         while (i.hasNext()) {
+             Object child = i.next();
+             if (child == w) {
+                 return basePath + PARENTCHILD_SEPARATOR + simpleName + "["
+                         + pos + "]";
+             }
+             String simpleName2 = Util.getSimpleName(child);
+             if (simpleName.equals(simpleName2)) {
+                 pos++;
+             }
+         }
+         return null;
+     }
+     /**
+      * Locates the widget based on a String locator.
+      * 
+      * @param path
+      *            The String locator that identifies the widget.
+      * @return The Widget identified by the String locator or null if the widget
+      *         could not be identified.
+      */
+     private Widget getWidgetFromPath(String path) {
+         Widget w = null;
+         String parts[] = path.split(PARENTCHILD_SEPARATOR);
+         for (int i = 0; i < parts.length; i++) {
+             String part = parts[i];
+             if (part.equals(ROOT_ID)) {
+                 w = RootPanel.get();
+             } else if (part.equals("")) {
+                 w = client.getRootConnector().getWidget();
+             } else if (w == null) {
+                 String id = part;
+                 // Must be old static pid (PID_S*)
+                 ServerConnector connector = ConnectorMap.get(client)
+                         .getConnector(id);
+                 if (connector == null) {
+                     // Lookup by debugId
+                     // TODO Optimize this
+                     connector = findConnectorById(client.getRootConnector(),
+                             id.substring(5));
+                 }
+                 if (connector instanceof ComponentConnector) {
+                     w = ((ComponentConnector) connector).getWidget();
+                 } else {
+                     // Not found
+                     return null;
+                 }
+             } else if (part.startsWith("domChild[")) {
+                 // The target widget has been found and the rest identifies the
+                 // element
+                 break;
+             } else if (w instanceof Iterable) {
+                 // W identifies a widget that contains other widgets, as it
+                 // should. Try to locate the child
+                 Iterable<?> parent = (Iterable<?>) w;
+                 // Part is of type "VVerticalLayout[0]", split this into
+                 // VVerticalLayout and 0
+                 String[] split = part.split("\\[", 2);
+                 String widgetClassName = split[0];
+                 String indexString = split[1];
+                 int widgetPosition = Integer.parseInt(indexString.substring(0,
+                         indexString.length() - 1));
+                 // AbsolutePanel in GridLayout has been removed -> skip it
+                 if (w instanceof VGridLayout
+                         && "AbsolutePanel".equals(widgetClassName)) {
+                     continue;
+                 }
+                 if (w instanceof VTabsheetPanel && widgetPosition != 0) {
+                     // TabSheetPanel now only contains 1 connector => the index
+                     // is always 0 which indicates the widget in the active tab
+                     widgetPosition = 0;
+                 }
++                if ("VVerticalLayout".equals(widgetClassName)
++                        || "VHorizontalLayout".equals(widgetClassName)) {
++                    widgetClassName = "VBoxLayout";
++                }
++                if (w instanceof VBoxLayout
++                        && "ChildComponentContainer".equals(widgetClassName)) {
++                    widgetClassName = "VBoxLayout$Slot";
++                }
+                 /*
+                  * The new grid and ordered layotus do not contain
+                  * ChildComponentContainer widgets. This is instead simulated by
+                  * constructing a path step that would find the desired widget
+                  * from the layout and injecting it as the next search step
+                  * (which would originally have found the widget inside the
+                  * ChildComponentContainer)
+                  */
+                 if ((w instanceof VMeasuringOrderedLayout || w instanceof VGridLayout)
+                         && "ChildComponentContainer".equals(widgetClassName)
+                         && i + 1 < parts.length) {
+                     HasWidgets layout = (HasWidgets) w;
+                     String nextPart = parts[i + 1];
+                     String[] nextSplit = nextPart.split("\\[", 2);
+                     String nextWidgetClassName = nextSplit[0];
+                     // Find the n:th child and count the number of children with
+                     // the same type before it
+                     int nextIndex = 0;
+                     for (Widget child : layout) {
+                         boolean matchingType = nextWidgetClassName.equals(Util
+                                 .getSimpleName(child));
+                         if (matchingType && widgetPosition == 0) {
+                             // This is the n:th child that we looked for
+                             break;
+                         } else if (widgetPosition < 0) {
+                             // Error if we're past the desired position without
+                             // a match
+                             return null;
+                         } else if (matchingType) {
+                             // If this was another child of the expected type,
+                             // increase the count for the next step
+                             nextIndex++;
+                         }
+                         // Don't count captions
+                         if (!(child instanceof VCaption)) {
+                             widgetPosition--;
+                         }
+                     }
+                     // Advance to the next step, this time checking for the
+                     // actual child widget
+                     parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']';
+                     continue;
+                 }
+                 // Locate the child
+                 Iterator<? extends Widget> iterator;
+                 /*
+                  * VWindow and VContextMenu workarounds for backwards
+                  * compatibility
+                  */
+                 if (widgetClassName.equals("VWindow")) {
+                     List<WindowConnector> windows = client.getRootConnector()
+                             .getSubWindows();
+                     List<VWindow> windowWidgets = new ArrayList<VWindow>(
+                             windows.size());
+                     for (WindowConnector wc : windows) {
+                         windowWidgets.add(wc.getWidget());
+                     }
+                     iterator = windowWidgets.iterator();
+                 } else if (widgetClassName.equals("VContextMenu")) {
+                     return client.getContextMenu();
+                 } else {
+                     iterator = (Iterator<? extends Widget>) parent.iterator();
+                 }
+                 boolean ok = false;
+                 // Find the widgetPosition:th child of type "widgetClassName"
+                 while (iterator.hasNext()) {
+                     Widget child = iterator.next();
+                     String simpleName2 = Util.getSimpleName(child);
+                     if (widgetClassName.equals(simpleName2)) {
+                         if (widgetPosition == 0) {
+                             w = child;
+                             ok = true;
+                             break;
+                         }
+                         widgetPosition--;
+                     }
+                 }
+                 if (!ok) {
+                     // Did not find the child
+                     return null;
+                 }
+             } else {
+                 // W identifies something that is not a "HasWidgets". This
+                 // should not happen as all widget containers should implement
+                 // HasWidgets.
+                 return null;
+             }
+         }
+         return w;
+     }
+     private ServerConnector findConnectorById(ServerConnector root, String id) {
+         SharedState state = root.getState();
+         if (state instanceof ComponentState
+                 && id.equals(((ComponentState) state).getDebugId())) {
+             return root;
+         }
+         for (ServerConnector child : root.getChildren()) {
+             ServerConnector found = findConnectorById(child, id);
+             if (found != null) {
+                 return found;
+             }
+         }
+         return null;
+     }
+ }
index 0000000000000000000000000000000000000000,7662ba634b90e8e391bbb0414bcfb44684074634..6e17dcfab5d6a57cb3b64b04440bd62461271147
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,198 +1,198 @@@
 -            if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
 -                // Remember the original values
 -                var left = style.left, rsLeft = elem.runtimeStyle.left;
 -
 -                // Put in the new values to get a computed value out
 -                elem.runtimeStyle.left = cs.left;
 -                style.left = ret || 0;
 -                ret = style.pixelLeft + "px";
 -
 -                // Revert the changed values
 -                style.left = left;
 -                elem.runtimeStyle.left = rsLeft;
 -            }
+ /*
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.terminal.gwt.client;
+ import com.google.gwt.core.client.JavaScriptObject;
+ import com.google.gwt.dom.client.Element;
+ public class ComputedStyle {
+     protected final JavaScriptObject computedStyle;
+     private final Element elem;
+     /**
+      * Gets this element's computed style object which can be used to gather
+      * information about the current state of the rendered node.
+      * <p>
+      * Note that this method is expensive. Wherever possible, reuse the returned
+      * object.
+      * 
+      * @param elem
+      *            the element
+      * @return the computed style
+      */
+     public ComputedStyle(Element elem) {
+         computedStyle = getComputedStyle(elem);
+         this.elem = elem;
+     }
+     private static native JavaScriptObject getComputedStyle(Element elem)
+     /*-{
+       if(elem.nodeType != 1) {
+           return {};
+       }
+       
+       if($wnd.document.defaultView && $wnd.document.defaultView.getComputedStyle) {
+           return $wnd.document.defaultView.getComputedStyle(elem, null);
+       }
+       
+       if(elem.currentStyle) {
+           return elem.currentStyle;
+       }
+     }-*/;
+     /**
+      * 
+      * @param name
+      *            name of the CSS property in camelCase
+      * @return the value of the property, normalized for across browsers (each
+      *         browser returns pixel values whenever possible).
+      */
+     public final native String getProperty(String name)
+     /*-{
+         var cs = this.@com.vaadin.terminal.gwt.client.ComputedStyle::computedStyle;
+         var elem = this.@com.vaadin.terminal.gwt.client.ComputedStyle::elem;
+         
+         // Border values need to be checked separately. The width might have a 
+         // meaningful value even if the border style is "none". In that case the 
+         // value should be 0.
+         if(name.indexOf("border") > -1 && name.indexOf("Width") > -1) {
+             var borderStyleProp = name.substring(0,name.length-5) + "Style";
+             if(cs.getPropertyValue)
+                 var borderStyle = cs.getPropertyValue(borderStyleProp);
+             else // IE
+                 var borderStyle = cs[borderStyleProp];
+             if(borderStyle == "none")
+                 return "0px";
+         }
+         if(cs.getPropertyValue) {
+         
+             // Convert name to dashed format
+             name = name.replace(/([A-Z])/g, "-$1").toLowerCase();
+             var ret = cs.getPropertyValue(name);
+             
+         } else {
+         
+             var ret = cs[name];
+             var style = elem.style;
+             // From the awesome hack by Dean Edwards
+             // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+             // If we're not dealing with a regular pixel number
+             // but a number that has a weird ending, we need to convert it to pixels
++                if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
++                    // Remember the original values
++                    var left = style.left, rsLeft = elem.runtimeStyle.left;
++    
++                    // Put in the new values to get a computed value out
++                    elem.runtimeStyle.left = cs.left;
++                    style.left = ret || 0;
++                    ret = style.pixelLeft + "px";
++    
++                    // Revert the changed values
++                    style.left = left;
++                    elem.runtimeStyle.left = rsLeft;
++                }
+             
+         }
+         
+         // Normalize margin values. This is not totally valid, but in most cases 
+         // it is what the user wants to know.
+         if(name.indexOf("margin") > -1 && ret == "auto") {
+             return "0px";
+         }
+         
+         // Some browsers return undefined width and height values as "auto", so
+         // we need to retrieve those ourselves.
+         if (name == "width" && ret == "auto") {
+             ret = elem.clientWidth + "px";
+         } else if (name == "height" && ret == "auto") {
+             ret = elem.clientHeight + "px";
+         }
+         return ret;
+         
+     }-*/;
+     public final int getIntProperty(String name) {
+         Integer parsed = parseInt(getProperty(name));
+         if (parsed != null) {
+             return parsed.intValue();
+         }
+         return 0;
+     }
+     /**
+      * Get current margin values from the DOM. The array order is the default
+      * CSS order: top, right, bottom, left.
+      */
+     public final int[] getMargin() {
+         int[] margin = { 0, 0, 0, 0 };
+         margin[0] = getIntProperty("marginTop");
+         margin[1] = getIntProperty("marginRight");
+         margin[2] = getIntProperty("marginBottom");
+         margin[3] = getIntProperty("marginLeft");
+         return margin;
+     }
+     /**
+      * Get current padding values from the DOM. The array order is the default
+      * CSS order: top, right, bottom, left.
+      */
+     public final int[] getPadding() {
+         int[] padding = { 0, 0, 0, 0 };
+         padding[0] = getIntProperty("paddingTop");
+         padding[1] = getIntProperty("paddingRight");
+         padding[2] = getIntProperty("paddingBottom");
+         padding[3] = getIntProperty("paddingLeft");
+         return padding;
+     }
+     /**
+      * Get current border values from the DOM. The array order is the default
+      * CSS order: top, right, bottom, left.
+      */
+     public final int[] getBorder() {
+         int[] border = { 0, 0, 0, 0 };
+         border[0] = getIntProperty("borderTopWidth");
+         border[1] = getIntProperty("borderRightWidth");
+         border[2] = getIntProperty("borderBottomWidth");
+         border[3] = getIntProperty("borderLeftWidth");
+         return border;
+     }
+     /**
+      * Takes a String value e.g. "12px" and parses that to int 12.
+      * 
+      * @param String
+      *            a value starting with a number
+      * @return int the value from the string before any non-numeric characters.
+      *         If the value cannot be parsed to a number, returns
+      *         <code>null</code>.
+      */
+     public static native Integer parseInt(final String value)
+     /*-{
+         var number = parseInt(value, 10);
+         if (isNaN(number))
+             return null;
+         else
+             // $entry not needed as function is not exported
+             return @java.lang.Integer::valueOf(I)(number);
+     }-*/;
+ }
index 0000000000000000000000000000000000000000,c95860a0294a37c8fe58ba465135914cdf1f813d..5bd784d1c6c6bbe0fc426f3250e3b360d94b6db6
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1227 +1,1271 @@@
 -    private void layoutLater() {
+ /* 
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.terminal.gwt.client;
+ import java.util.Collection;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.Map;
+ import java.util.Set;
+ import com.google.gwt.core.client.Duration;
+ import com.google.gwt.core.client.JsArrayString;
+ import com.google.gwt.dom.client.Element;
+ import com.google.gwt.dom.client.Style;
+ import com.google.gwt.dom.client.Style.Overflow;
+ import com.google.gwt.user.client.Timer;
+ import com.vaadin.terminal.gwt.client.MeasuredSize.MeasureResult;
+ import com.vaadin.terminal.gwt.client.ui.ManagedLayout;
+ import com.vaadin.terminal.gwt.client.ui.PostLayoutListener;
+ import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout;
+ import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent;
+ import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener;
+ import com.vaadin.terminal.gwt.client.ui.layout.LayoutDependencyTree;
+ import com.vaadin.terminal.gwt.client.ui.notification.VNotification;
+ public class LayoutManager {
+     private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";
+     private static final boolean debugLogging = false;
+     private ApplicationConnection connection;
+     private final Set<Element> measuredNonConnectorElements = new HashSet<Element>();
+     private final MeasuredSize nullSize = new MeasuredSize();
+     private LayoutDependencyTree currentDependencyTree;
+     private final Collection<ManagedLayout> needsHorizontalLayout = new HashSet<ManagedLayout>();
+     private final Collection<ManagedLayout> needsVerticalLayout = new HashSet<ManagedLayout>();
+     private final Collection<ComponentConnector> needsMeasure = new HashSet<ComponentConnector>();
+     private Collection<ComponentConnector> pendingOverflowFixes = new HashSet<ComponentConnector>();
+     private final Map<Element, Collection<ElementResizeListener>> elementResizeListeners = new HashMap<Element, Collection<ElementResizeListener>>();
+     private final Set<Element> listenersToFire = new HashSet<Element>();
+     private boolean layoutPending = false;
+     private Timer layoutTimer = new Timer() {
+         @Override
+         public void run() {
+             cancel();
+             layoutNow();
+         }
+     };
+     private boolean everythingNeedsMeasure = false;
+     public void setConnection(ApplicationConnection connection) {
+         if (this.connection != null) {
+             throw new RuntimeException(
+                     "LayoutManager connection can never be changed");
+         }
+         this.connection = connection;
+     }
+     /**
+      * Gets the layout manager associated with the given
+      * {@link ApplicationConnection}.
+      * 
+      * @param connection
+      *            the application connection to get a layout manager for
+      * @return the layout manager associated with the provided application
+      *         connection
+      */
+     public static LayoutManager get(ApplicationConnection connection) {
+         return connection.getLayoutManager();
+     }
+     /**
+      * Registers that a ManagedLayout is depending on the size of an Element.
+      * This causes this layout manager to measure the element in the beginning
+      * of every layout phase and call the appropriate layout method of the
+      * managed layout if the size of the element has changed.
+      * 
+      * @param owner
+      *            the ManagedLayout that depends on an element
+      * @param element
+      *            the Element that should be measured
+      */
+     public void registerDependency(ManagedLayout owner, Element element) {
+         MeasuredSize measuredSize = ensureMeasured(element);
+         setNeedsLayout(owner);
+         measuredSize.addDependent(owner.getConnectorId());
+     }
+     private MeasuredSize ensureMeasured(Element element) {
+         MeasuredSize measuredSize = getMeasuredSize(element, null);
+         if (measuredSize == null) {
+             measuredSize = new MeasuredSize();
+             if (ConnectorMap.get(connection).getConnector(element) == null) {
+                 measuredNonConnectorElements.add(element);
+             }
+             setMeasuredSize(element, measuredSize);
+         }
+         return measuredSize;
+     }
+     private boolean needsMeasure(Element e) {
+         if (connection.getConnectorMap().getConnectorId(e) != null) {
+             return true;
+         } else if (elementResizeListeners.containsKey(e)) {
+             return true;
+         } else if (getMeasuredSize(e, nullSize).hasDependents()) {
+             return true;
+         } else {
+             return false;
+         }
+     }
+     /**
+      * Assigns a measured size to an element. Method defined as protected to
+      * allow separate implementation for IE8.
+      * 
+      * @param element
+      *            the dom element to attach the measured size to
+      * @param measuredSize
+      *            the measured size to attach to the element. If
+      *            <code>null</code>, any previous measured size is removed.
+      */
+     protected native void setMeasuredSize(Element element,
+             MeasuredSize measuredSize)
+     /*-{
+         if (measuredSize) {
+             element.vMeasuredSize = measuredSize;
+         } else {
+             delete element.vMeasuredSize;
+         }
+     }-*/;
+     /**
+      * Gets the measured size for an element. Method defined as protected to
+      * allow separate implementation for IE8.
+      * 
+      * @param element
+      *            The element to get measured size for
+      * @param defaultSize
+      *            The size to return if no measured size could be found
+      * @return The measured size for the element or {@literal defaultSize}
+      */
+     protected native MeasuredSize getMeasuredSize(Element element,
+             MeasuredSize defaultSize)
+     /*-{
+         return element.vMeasuredSize || defaultSize;
+     }-*/;
+     private final MeasuredSize getMeasuredSize(ComponentConnector connector) {
+         Element element = connector.getWidget().getElement();
+         MeasuredSize measuredSize = getMeasuredSize(element, null);
+         if (measuredSize == null) {
+             measuredSize = new MeasuredSize();
+             setMeasuredSize(element, measuredSize);
+         }
+         return measuredSize;
+     }
+     /**
+      * Registers that a ManagedLayout is no longer depending on the size of an
+      * Element.
+      * 
+      * @see #registerDependency(ManagedLayout, Element)
+      * 
+      * @param owner
+      *            the ManagedLayout no longer depends on an element
+      * @param element
+      *            the Element that that no longer needs to be measured
+      */
+     public void unregisterDependency(ManagedLayout owner, Element element) {
+         MeasuredSize measuredSize = getMeasuredSize(element, null);
+         if (measuredSize == null) {
+             return;
+         }
+         measuredSize.removeDependent(owner.getConnectorId());
+         stopMeasuringIfUnecessary(element);
+     }
+     public boolean isLayoutRunning() {
+         return currentDependencyTree != null;
+     }
+     private void countLayout(Map<ManagedLayout, Integer> layoutCounts,
+             ManagedLayout layout) {
+         Integer count = layoutCounts.get(layout);
+         if (count == null) {
+             count = Integer.valueOf(0);
+         } else {
+             count = Integer.valueOf(count.intValue() + 1);
+         }
+         layoutCounts.put(layout, count);
+         if (count.intValue() > 2) {
+             VConsole.error(Util.getConnectorString(layout)
+                     + " has been layouted " + count.intValue() + " times");
+         }
+     }
++    public void layoutLater() {
+         if (!layoutPending) {
+             layoutPending = true;
+             layoutTimer.schedule(100);
+         }
+     }
+     public void layoutNow() {
+         if (isLayoutRunning()) {
+             throw new IllegalStateException(
+                     "Can't start a new layout phase before the previous layout phase ends.");
+         }
+         layoutPending = false;
+         try {
+             currentDependencyTree = new LayoutDependencyTree();
+             doLayout();
+         } finally {
+             currentDependencyTree = null;
+         }
+     }
+     private void doLayout() {
+         VConsole.log("Starting layout phase");
+         Map<ManagedLayout, Integer> layoutCounts = new HashMap<ManagedLayout, Integer>();
+         int passes = 0;
+         Duration totalDuration = new Duration();
+         for (ManagedLayout layout : needsHorizontalLayout) {
+             currentDependencyTree.setNeedsHorizontalLayout(layout, true);
+         }
+         for (ManagedLayout layout : needsVerticalLayout) {
+             currentDependencyTree.setNeedsVerticalLayout(layout, true);
+         }
+         needsHorizontalLayout.clear();
+         needsVerticalLayout.clear();
+         for (ComponentConnector connector : needsMeasure) {
+             currentDependencyTree.setNeedsMeasure(connector, true);
+         }
+         needsMeasure.clear();
+         measureNonConnectors();
+         VConsole.log("Layout init in " + totalDuration.elapsedMillis() + " ms");
+         while (true) {
+             Duration passDuration = new Duration();
+             passes++;
+             int measuredConnectorCount = measureConnectors(
+                     currentDependencyTree, everythingNeedsMeasure);
+             everythingNeedsMeasure = false;
+             if (measuredConnectorCount == 0) {
+                 VConsole.log("No more changes in pass " + passes);
+                 break;
+             }
+             int measureTime = passDuration.elapsedMillis();
+             VConsole.log("  Measured " + measuredConnectorCount
+                     + " elements in " + measureTime + " ms");
+             if (!listenersToFire.isEmpty()) {
+                 for (Element element : listenersToFire) {
+                     Collection<ElementResizeListener> listeners = elementResizeListeners
+                             .get(element);
+                     ElementResizeListener[] array = listeners
+                             .toArray(new ElementResizeListener[listeners.size()]);
+                     ElementResizeEvent event = new ElementResizeEvent(this,
+                             element);
+                     for (ElementResizeListener listener : array) {
+                         try {
+                             listener.onElementResize(event);
+                         } catch (RuntimeException e) {
+                             VConsole.error(e);
+                         }
+                     }
+                 }
+                 int measureListenerTime = passDuration.elapsedMillis();
+                 VConsole.log("  Fired resize listeners for  "
+                         + listenersToFire.size() + " elements in "
+                         + (measureListenerTime - measureTime) + " ms");
+                 measureTime = measuredConnectorCount;
+                 listenersToFire.clear();
+             }
+             FastStringSet updatedSet = FastStringSet.create();
+             while (currentDependencyTree.hasHorizontalConnectorToLayout()
+                     || currentDependencyTree.hasVerticaConnectorToLayout()) {
+                 for (ManagedLayout layout : currentDependencyTree
+                         .getHorizontalLayoutTargets()) {
+                     if (layout instanceof DirectionalManagedLayout) {
+                         currentDependencyTree
+                                 .markAsHorizontallyLayouted(layout);
+                         DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
+                         try {
+                             cl.layoutHorizontally();
+                         } catch (RuntimeException e) {
+                             VConsole.log(e);
+                         }
+                         countLayout(layoutCounts, cl);
+                     } else {
+                         currentDependencyTree
+                                 .markAsHorizontallyLayouted(layout);
+                         currentDependencyTree.markAsVerticallyLayouted(layout);
+                         SimpleManagedLayout rr = (SimpleManagedLayout) layout;
+                         try {
+                             rr.layout();
+                         } catch (RuntimeException e) {
+                             VConsole.log(e);
+                         }
+                         countLayout(layoutCounts, rr);
+                     }
+                     if (debugLogging) {
+                         updatedSet.add(layout.getConnectorId());
+                     }
+                 }
+                 for (ManagedLayout layout : currentDependencyTree
+                         .getVerticalLayoutTargets()) {
+                     if (layout instanceof DirectionalManagedLayout) {
+                         currentDependencyTree.markAsVerticallyLayouted(layout);
+                         DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
+                         try {
+                             cl.layoutVertically();
+                         } catch (RuntimeException e) {
+                             VConsole.log(e);
+                         }
+                         countLayout(layoutCounts, cl);
+                     } else {
+                         currentDependencyTree
+                                 .markAsHorizontallyLayouted(layout);
+                         currentDependencyTree.markAsVerticallyLayouted(layout);
+                         SimpleManagedLayout rr = (SimpleManagedLayout) layout;
+                         try {
+                             rr.layout();
+                         } catch (RuntimeException e) {
+                             VConsole.log(e);
+                         }
+                         countLayout(layoutCounts, rr);
+                     }
+                     if (debugLogging) {
+                         updatedSet.add(layout.getConnectorId());
+                     }
+                 }
+             }
+             if (debugLogging) {
+                 JsArrayString changedCids = updatedSet.dump();
+                 StringBuilder b = new StringBuilder("  ");
+                 b.append(changedCids.length());
+                 b.append(" requestLayout invocations in ");
+                 b.append(passDuration.elapsedMillis() - measureTime);
+                 b.append(" ms");
+                 if (changedCids.length() < 30) {
+                     for (int i = 0; i < changedCids.length(); i++) {
+                         if (i != 0) {
+                             b.append(", ");
+                         } else {
+                             b.append(": ");
+                         }
+                         String connectorString = changedCids.get(i);
+                         if (changedCids.length() < 10) {
+                             ServerConnector connector = ConnectorMap.get(
+                                     connection).getConnector(connectorString);
+                             connectorString = Util
+                                     .getConnectorString(connector);
+                         }
+                         b.append(connectorString);
+                     }
+                 }
+                 VConsole.log(b.toString());
+             }
+             VConsole.log("Pass " + passes + " completed in "
+                     + passDuration.elapsedMillis() + " ms");
+             if (passes > 100) {
+                 VConsole.log(LOOP_ABORT_MESSAGE);
+                 VNotification.createNotification(VNotification.DELAY_FOREVER)
+                         .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED,
+                                 "error");
+                 break;
+             }
+         }
+         int postLayoutStart = totalDuration.elapsedMillis();
+         for (ComponentConnector connector : connection.getConnectorMap()
+                 .getComponentConnectors()) {
+             if (connector instanceof PostLayoutListener) {
+                 ((PostLayoutListener) connector).postLayout();
+             }
+         }
+         int postLayoutDone = (totalDuration.elapsedMillis() - postLayoutStart);
+         VConsole.log("Invoke post layout listeners in " + postLayoutDone
+                 + " ms");
+         cleanMeasuredSizes();
+         int cleaningDone = (totalDuration.elapsedMillis() - postLayoutDone);
+         VConsole.log("Cleaned old measured sizes in " + cleaningDone + "ms");
+         VConsole.log("Total layout phase time: "
+                 + totalDuration.elapsedMillis() + "ms");
+     }
+     private void logConnectorStatus(int connectorId) {
+         currentDependencyTree
+                 .logDependencyStatus((ComponentConnector) ConnectorMap.get(
+                         connection).getConnector(Integer.toString(connectorId)));
+     }
+     private int measureConnectors(LayoutDependencyTree layoutDependencyTree,
+             boolean measureAll) {
+         if (!pendingOverflowFixes.isEmpty()) {
+             Duration duration = new Duration();
+             HashMap<Element, String> originalOverflows = new HashMap<Element, String>();
+             HashSet<ComponentConnector> delayedOverflowFixes = new HashSet<ComponentConnector>();
+             // First set overflow to hidden (and save previous value so it can
+             // be restored later)
+             for (ComponentConnector componentConnector : pendingOverflowFixes) {
+                 // Delay the overflow fix if the involved connectors might still
+                 // change
+                 boolean connectorChangesExpected = !currentDependencyTree
+                         .noMoreChangesExpected(componentConnector);
+                 boolean parentChangesExcpected = componentConnector.getParent() instanceof ComponentConnector
+                         && !currentDependencyTree
+                                 .noMoreChangesExpected((ComponentConnector) componentConnector
+                                         .getParent());
+                 if (connectorChangesExpected || parentChangesExcpected) {
+                     delayedOverflowFixes.add(componentConnector);
+                     continue;
+                 }
+                 if (debugLogging) {
+                     VConsole.log("Doing overflow fix for "
+                             + Util.getConnectorString(componentConnector)
+                             + " in "
+                             + Util.getConnectorString(componentConnector
+                                     .getParent()));
+                 }
+                 Element parentElement = componentConnector.getWidget()
+                         .getElement().getParentElement();
+                 Style style = parentElement.getStyle();
+                 String originalOverflow = style.getOverflow();
+                 if (originalOverflow != null
+                         && !originalOverflows.containsKey(parentElement)) {
+                     // Store original value for restore, but only the first time
+                     // the value is changed
+                     originalOverflows.put(parentElement, originalOverflow);
+                 }
+                 style.setOverflow(Overflow.HIDDEN);
+             }
+             pendingOverflowFixes.removeAll(delayedOverflowFixes);
+             // Then ensure all scrolling elements are reflowed by measuring
+             for (ComponentConnector componentConnector : pendingOverflowFixes) {
+                 componentConnector.getWidget().getElement().getParentElement()
+                         .getOffsetHeight();
+             }
+             // Finally restore old overflow value and update bookkeeping
+             for (ComponentConnector componentConnector : pendingOverflowFixes) {
+                 Element parentElement = componentConnector.getWidget()
+                         .getElement().getParentElement();
+                 parentElement.getStyle().setProperty("overflow",
+                         originalOverflows.get(parentElement));
+                 layoutDependencyTree.setNeedsMeasure(componentConnector, true);
+             }
+             if (!pendingOverflowFixes.isEmpty()) {
+                 VConsole.log("Did overflow fix for "
+                         + pendingOverflowFixes.size() + " elements  in "
+                         + duration.elapsedMillis() + " ms");
+             }
+             pendingOverflowFixes = delayedOverflowFixes;
+         }
+         int measureCount = 0;
+         if (measureAll) {
+             ComponentConnector[] connectors = ConnectorMap.get(connection)
+                     .getComponentConnectors();
+             for (ComponentConnector connector : connectors) {
+                 measureConnector(connector);
+             }
+             for (ComponentConnector connector : connectors) {
+                 layoutDependencyTree.setNeedsMeasure(connector, false);
+             }
+             measureCount += connectors.length;
+         }
+         while (layoutDependencyTree.hasConnectorsToMeasure()) {
+             Collection<ComponentConnector> measureTargets = layoutDependencyTree
+                     .getMeasureTargets();
+             for (ComponentConnector connector : measureTargets) {
+                 measureConnector(connector);
+                 measureCount++;
+             }
+             for (ComponentConnector connector : measureTargets) {
+                 layoutDependencyTree.setNeedsMeasure(connector, false);
+             }
+         }
+         return measureCount;
+     }
+     private void measureConnector(ComponentConnector connector) {
+         Element element = connector.getWidget().getElement();
+         MeasuredSize measuredSize = getMeasuredSize(connector);
+         MeasureResult measureResult = measuredAndUpdate(element, measuredSize);
+         if (measureResult.isChanged()) {
+             onConnectorChange(connector, measureResult.isWidthChanged(),
+                     measureResult.isHeightChanged());
+         }
+     }
+     private void onConnectorChange(ComponentConnector connector,
+             boolean widthChanged, boolean heightChanged) {
+         setNeedsOverflowFix(connector);
+         if (heightChanged) {
+             currentDependencyTree.markHeightAsChanged(connector);
+         }
+         if (widthChanged) {
+             currentDependencyTree.markWidthAsChanged(connector);
+         }
+     }
+     private void setNeedsOverflowFix(ComponentConnector connector) {
+         // IE9 doesn't need the original fix, but for some reason it needs this
+         if (BrowserInfo.get().requiresOverflowAutoFix()
+                 || BrowserInfo.get().isIE9()) {
+             ComponentConnector scrollingBoundary = currentDependencyTree
+                     .getScrollingBoundary(connector);
+             if (scrollingBoundary != null) {
+                 pendingOverflowFixes.add(scrollingBoundary);
+             }
+         }
+     }
+     private void measureNonConnectors() {
+         for (Element element : measuredNonConnectorElements) {
+             measuredAndUpdate(element, getMeasuredSize(element, null));
+         }
+         VConsole.log("Measured " + measuredNonConnectorElements.size()
+                 + " non connector elements");
+     }
+     private MeasureResult measuredAndUpdate(Element element,
+             MeasuredSize measuredSize) {
+         MeasureResult measureResult = measuredSize.measure(element);
+         if (measureResult.isChanged()) {
+             notifyListenersAndDepdendents(element,
+                     measureResult.isWidthChanged(),
+                     measureResult.isHeightChanged());
+         }
+         return measureResult;
+     }
+     private void notifyListenersAndDepdendents(Element element,
+             boolean widthChanged, boolean heightChanged) {
+         assert widthChanged || heightChanged;
+         MeasuredSize measuredSize = getMeasuredSize(element, nullSize);
+         JsArrayString dependents = measuredSize.getDependents();
+         for (int i = 0; i < dependents.length(); i++) {
+             String pid = dependents.get(i);
+             ManagedLayout dependent = (ManagedLayout) connection
+                     .getConnectorMap().getConnector(pid);
+             if (dependent != null) {
+                 if (heightChanged) {
+                     currentDependencyTree.setNeedsVerticalLayout(dependent,
+                             true);
+                 }
+                 if (widthChanged) {
+                     currentDependencyTree.setNeedsHorizontalLayout(dependent,
+                             true);
+                 }
+             }
+         }
+         if (elementResizeListeners.containsKey(element)) {
+             listenersToFire.add(element);
+         }
+     }
+     private static boolean isManagedLayout(ComponentConnector connector) {
+         return connector instanceof ManagedLayout;
+     }
+     public void forceLayout() {
+         ConnectorMap connectorMap = connection.getConnectorMap();
+         ComponentConnector[] componentConnectors = connectorMap
+                 .getComponentConnectors();
+         for (ComponentConnector connector : componentConnectors) {
+             if (connector instanceof ManagedLayout) {
+                 setNeedsLayout((ManagedLayout) connector);
+             }
+         }
+         setEverythingNeedsMeasure();
+         layoutNow();
+     }
+     /**
+      * Marks that a ManagedLayout should be layouted in the next layout phase
+      * even if none of the elements managed by the layout have been resized.
+      * 
+      * @param layout
+      *            the managed layout that should be layouted
+      */
+     public final void setNeedsLayout(ManagedLayout layout) {
+         setNeedsHorizontalLayout(layout);
+         setNeedsVerticalLayout(layout);
+     }
+     /**
+      * Marks that a ManagedLayout should be layouted horizontally in the next
+      * layout phase even if none of the elements managed by the layout have been
+      * resized horizontally.
+      * 
+      * For SimpleManagedLayout which is always layouted in both directions, this
+      * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
+      * 
+      * @param layout
+      *            the managed layout that should be layouted
+      */
+     public final void setNeedsHorizontalLayout(ManagedLayout layout) {
+         needsHorizontalLayout.add(layout);
+     }
+     /**
+      * Marks that a ManagedLayout should be layouted vertically in the next
+      * layout phase even if none of the elements managed by the layout have been
+      * resized vertically.
+      * 
+      * For SimpleManagedLayout which is always layouted in both directions, this
+      * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
+      * 
+      * @param layout
+      *            the managed layout that should be layouted
+      */
+     public final void setNeedsVerticalLayout(ManagedLayout layout) {
+         needsVerticalLayout.add(layout);
+     }
+     /**
+      * Gets the outer height (including margins, paddings and borders) of the
+      * given element, provided that it has been measured. These elements are
+      * guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * -1 is returned if the element has not been measured. If 0 is returned, it
+      * might indicate that the element is not attached to the DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured outer height (including margins, paddings and
+      *         borders) of the element in pixels.
+      */
+     public final int getOuterHeight(Element element) {
+         return getMeasuredSize(element, nullSize).getOuterHeight();
+     }
+     /**
+      * Gets the outer width (including margins, paddings and borders) of the
+      * given element, provided that it has been measured. These elements are
+      * guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * -1 is returned if the element has not been measured. If 0 is returned, it
+      * might indicate that the element is not attached to the DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured outer width (including margins, paddings and
+      *         borders) of the element in pixels.
+      */
+     public final int getOuterWidth(Element element) {
+         return getMeasuredSize(element, nullSize).getOuterWidth();
+     }
+     /**
+      * Gets the inner height (excluding margins, paddings and borders) of the
+      * given element, provided that it has been measured. These elements are
+      * guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * -1 is returned if the element has not been measured. If 0 is returned, it
+      * might indicate that the element is not attached to the DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured inner height (excluding margins, paddings and
+      *         borders) of the element in pixels.
+      */
+     public final int getInnerHeight(Element element) {
+         return getMeasuredSize(element, nullSize).getInnerHeight();
+     }
+     /**
+      * Gets the inner width (excluding margins, paddings and borders) of the
+      * given element, provided that it has been measured. These elements are
+      * guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * -1 is returned if the element has not been measured. If 0 is returned, it
+      * might indicate that the element is not attached to the DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured inner width (excluding margins, paddings and
+      *         borders) of the element in pixels.
+      */
+     public final int getInnerWidth(Element element) {
+         return getMeasuredSize(element, nullSize).getInnerWidth();
+     }
+     /**
+      * Gets the border height (top border + bottom border) of the given element,
+      * provided that it has been measured. These elements are guaranteed to be
+      * measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured border height (top border + bottom border) of the
+      *         element in pixels.
+      */
+     public final int getBorderHeight(Element element) {
+         return getMeasuredSize(element, nullSize).getBorderHeight();
+     }
+     /**
+      * Gets the padding height (top padding + bottom padding) of the given
+      * element, provided that it has been measured. These elements are
+      * guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured padding height (top padding + bottom padding) of the
+      *         element in pixels.
+      */
+     public int getPaddingHeight(Element element) {
+         return getMeasuredSize(element, nullSize).getPaddingHeight();
+     }
+     /**
+      * Gets the border width (left border + right border) of the given element,
+      * provided that it has been measured. These elements are guaranteed to be
+      * measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured border width (left border + right border) of the
+      *         element in pixels.
+      */
+     public int getBorderWidth(Element element) {
+         return getMeasuredSize(element, nullSize).getBorderWidth();
+     }
+     /**
+      * Gets the padding width (left padding + right padding) of the given
+      * element, provided that it has been measured. These elements are
+      * guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured padding width (left padding + right padding) of the
+      *         element in pixels.
+      */
+     public int getPaddingWidth(Element element) {
+         return getMeasuredSize(element, nullSize).getPaddingWidth();
+     }
+     /**
+      * Gets the top padding of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured top padding of the element in pixels.
+      */
+     public int getPaddingTop(Element element) {
+         return getMeasuredSize(element, nullSize).getPaddingTop();
+     }
+     /**
+      * Gets the left padding of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured left padding of the element in pixels.
+      */
+     public int getPaddingLeft(Element element) {
+         return getMeasuredSize(element, nullSize).getPaddingLeft();
+     }
+     /**
+      * Gets the bottom padding of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured bottom padding of the element in pixels.
+      */
+     public int getPaddingBottom(Element element) {
+         return getMeasuredSize(element, nullSize).getPaddingBottom();
+     }
+     /**
+      * Gets the right padding of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured right padding of the element in pixels.
+      */
+     public int getPaddingRight(Element element) {
+         return getMeasuredSize(element, nullSize).getPaddingRight();
+     }
+     /**
+      * Gets the top margin of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured top margin of the element in pixels.
+      */
+     public int getMarginTop(Element element) {
+         return getMeasuredSize(element, nullSize).getMarginTop();
+     }
+     /**
+      * Gets the right margin of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured right margin of the element in pixels.
+      */
+     public int getMarginRight(Element element) {
+         return getMeasuredSize(element, nullSize).getMarginRight();
+     }
+     /**
+      * Gets the bottom margin of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured bottom margin of the element in pixels.
+      */
+     public int getMarginBottom(Element element) {
+         return getMeasuredSize(element, nullSize).getMarginBottom();
+     }
+     /**
+      * Gets the left margin of the given element, provided that it has been
+      * measured. These elements are guaranteed to be measured:
+      * <ul>
+      * <li>ManagedLayotus and their child Connectors
+      * <li>Elements for which there is at least one ElementResizeListener
+      * <li>Elements for which at least one ManagedLayout has registered a
+      * dependency
+      * </ul>
+      * 
+      * A negative number is returned if the element has not been measured. If 0
+      * is returned, it might indicate that the element is not attached to the
+      * DOM.
+      * 
+      * @param element
+      *            the element to get the measured size for
+      * @return the measured left margin of the element in pixels.
+      */
+     public int getMarginLeft(Element element) {
+         return getMeasuredSize(element, nullSize).getMarginLeft();
+     }
++    /**
++     * Gets the combined top & bottom margin of the given element, provided that
++     * they have been measured. These elements are guaranteed to be measured:
++     * <ul>
++     * <li>ManagedLayotus and their child Connectors
++     * <li>Elements for which there is at least one ElementResizeListener
++     * <li>Elements for which at least one ManagedLayout has registered a
++     * dependency
++     * </ul>
++     * 
++     * A negative number is returned if the element has not been measured. If 0
++     * is returned, it might indicate that the element is not attached to the
++     * DOM.
++     * 
++     * @param element
++     *            the element to get the measured margin for
++     * @return the measured top+bottom margin of the element in pixels.
++     */
++    public int getMarginHeight(Element element) {
++        return getMarginTop(element) + getMarginBottom(element);
++    }
++
++    /**
++     * Gets the combined left & right margin of the given element, provided that
++     * they have been measured. These elements are guaranteed to be measured:
++     * <ul>
++     * <li>ManagedLayotus and their child Connectors
++     * <li>Elements for which there is at least one ElementResizeListener
++     * <li>Elements for which at least one ManagedLayout has registered a
++     * dependency
++     * </ul>
++     * 
++     * A negative number is returned if the element has not been measured. If 0
++     * is returned, it might indicate that the element is not attached to the
++     * DOM.
++     * 
++     * @param element
++     *            the element to get the measured margin for
++     * @return the measured left+right margin of the element in pixels.
++     */
++    public int getMarginWidth(Element element) {
++        return getMarginLeft(element) + getMarginRight(element);
++    }
++
+     /**
+      * Registers the outer height (including margins, borders and paddings) of a
+      * component. This can be used as an optimization by ManagedLayouts; by
+      * informing the LayoutManager about what size a component will have, the
+      * layout propagation can continue directly without first measuring the
+      * potentially resized elements.
+      * 
+      * @param component
+      *            the component for which the size is reported
+      * @param outerHeight
+      *            the new outer height (including margins, borders and paddings)
+      *            of the component in pixels
+      */
+     public void reportOuterHeight(ComponentConnector component, int outerHeight) {
+         MeasuredSize measuredSize = getMeasuredSize(component);
+         if (isLayoutRunning()) {
+             boolean heightChanged = measuredSize.setOuterHeight(outerHeight);
+             if (heightChanged) {
+                 onConnectorChange(component, false, true);
+                 notifyListenersAndDepdendents(component.getWidget()
+                         .getElement(), false, true);
+             }
+             currentDependencyTree.setNeedsVerticalMeasure(component, false);
+         } else if (measuredSize.getOuterHeight() != outerHeight) {
+             setNeedsMeasure(component);
+         }
+     }
+     /**
+      * Registers the height reserved for a relatively sized component. This can
+      * be used as an optimization by ManagedLayouts; by informing the
+      * LayoutManager about what size a component will have, the layout
+      * propagation can continue directly without first measuring the potentially
+      * resized elements.
+      * 
+      * @param component
+      *            the relatively sized component for which the size is reported
+      * @param assignedHeight
+      *            the inner height of the relatively sized component's parent
+      *            element in pixels
+      */
+     public void reportHeightAssignedToRelative(ComponentConnector component,
+             int assignedHeight) {
+         assert component.isRelativeHeight();
+         float percentSize = parsePercent(component.getState().getHeight());
+         int effectiveHeight = Math.round(assignedHeight * (percentSize / 100));
+         reportOuterHeight(component, effectiveHeight);
+     }
+     /**
+      * Registers the width reserved for a relatively sized component. This can
+      * be used as an optimization by ManagedLayouts; by informing the
+      * LayoutManager about what size a component will have, the layout
+      * propagation can continue directly without first measuring the potentially
+      * resized elements.
+      * 
+      * @param component
+      *            the relatively sized component for which the size is reported
+      * @param assignedWidth
+      *            the inner width of the relatively sized component's parent
+      *            element in pixels
+      */
+     public void reportWidthAssignedToRelative(ComponentConnector component,
+             int assignedWidth) {
+         assert component.isRelativeWidth();
+         float percentSize = parsePercent(component.getState().getWidth());
+         int effectiveWidth = Math.round(assignedWidth * (percentSize / 100));
+         reportOuterWidth(component, effectiveWidth);
+     }
+     private static float parsePercent(String size) {
+         return Float.parseFloat(size.substring(0, size.length() - 1));
+     }
+     /**
+      * Registers the outer width (including margins, borders and paddings) of a
+      * component. This can be used as an optimization by ManagedLayouts; by
+      * informing the LayoutManager about what size a component will have, the
+      * layout propagation can continue directly without first measuring the
+      * potentially resized elements.
+      * 
+      * @param component
+      *            the component for which the size is reported
+      * @param outerWidth
+      *            the new outer width (including margins, borders and paddings)
+      *            of the component in pixels
+      */
+     public void reportOuterWidth(ComponentConnector component, int outerWidth) {
+         MeasuredSize measuredSize = getMeasuredSize(component);
+         if (isLayoutRunning()) {
+             boolean widthChanged = measuredSize.setOuterWidth(outerWidth);
+             if (widthChanged) {
+                 onConnectorChange(component, true, false);
+                 notifyListenersAndDepdendents(component.getWidget()
+                         .getElement(), true, false);
+             }
+             currentDependencyTree.setNeedsHorizontalMeasure(component, false);
+         } else if (measuredSize.getOuterWidth() != outerWidth) {
+             setNeedsMeasure(component);
+         }
+     }
+     /**
+      * Adds a listener that will be notified whenever the size of a specific
+      * element changes. Adding a listener to an element also ensures that all
+      * sizes for that element will be available starting from the next layout
+      * phase.
+      * 
+      * @param element
+      *            the element that should be checked for size changes
+      * @param listener
+      *            an ElementResizeListener that will be informed whenever the
+      *            size of the target element has changed
+      */
+     public void addElementResizeListener(Element element,
+             ElementResizeListener listener) {
+         Collection<ElementResizeListener> listeners = elementResizeListeners
+                 .get(element);
+         if (listeners == null) {
+             listeners = new HashSet<ElementResizeListener>();
+             elementResizeListeners.put(element, listeners);
+             ensureMeasured(element);
+         }
+         listeners.add(listener);
+     }
+     /**
+      * Removes an element resize listener from the provided element. This might
+      * cause this LayoutManager to stop tracking the size of the element if no
+      * other sources are interested in the size.
+      * 
+      * @param element
+      *            the element to which the element resize listener was
+      *            previously added
+      * @param listener
+      *            the ElementResizeListener that should no longer get informed
+      *            about size changes to the target element.
+      */
+     public void removeElementResizeListener(Element element,
+             ElementResizeListener listener) {
+         Collection<ElementResizeListener> listeners = elementResizeListeners
+                 .get(element);
+         if (listeners != null) {
+             listeners.remove(listener);
+             if (listeners.isEmpty()) {
+                 elementResizeListeners.remove(element);
+                 stopMeasuringIfUnecessary(element);
+             }
+         }
+     }
+     private void stopMeasuringIfUnecessary(Element element) {
+         if (!needsMeasure(element)) {
+             measuredNonConnectorElements.remove(element);
+             setMeasuredSize(element, null);
+         }
+     }
+     /**
+      * Informs this LayoutManager that the size of a component might have
+      * changed. If there is no upcoming layout phase, a new layout phase is
+      * scheduled. This method should be used whenever a size might have changed
+      * from outside of Vaadin's normal update phase, e.g. when an icon has been
+      * loaded or when the user resizes some part of the UI using the mouse.
+      * 
+      * @param component
+      *            the component whose size might have changed.
+      */
+     public void setNeedsMeasure(ComponentConnector component) {
+         if (isLayoutRunning()) {
+             currentDependencyTree.setNeedsMeasure(component, true);
+         } else {
+             needsMeasure.add(component);
+             layoutLater();
+         }
+     }
+     public void setEverythingNeedsMeasure() {
+         everythingNeedsMeasure = true;
+     }
+     /**
+      * Clean measured sizes which are no longer needed. Only for IE8.
+      */
+     protected void cleanMeasuredSizes() {
+     }
+ }
index 0000000000000000000000000000000000000000,ff37f04f04329e4cab127d712814f10dbcba61b1..fa78f886a7805a77f2d2120e7071e78a71bb0788
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,130 +1,139 @@@
+ /*
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.terminal.gwt.client;
+ import java.util.Collection;
+ import java.util.List;
+ import com.google.gwt.event.shared.GwtEvent;
+ import com.google.web.bindery.event.shared.HandlerRegistration;
+ import com.vaadin.shared.Connector;
+ import com.vaadin.shared.communication.ClientRpc;
+ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler;
+ /**
+  * Interface implemented by all client side classes that can be communicate with
+  * the server. Classes implementing this interface are initialized by the
+  * framework when needed and have the ability to communicate with the server.
+  * 
+  * @author Vaadin Ltd
+  * @since 7.0.0
+  */
+ public interface ServerConnector extends Connector {
+     /**
+      * Gets ApplicationConnection instance that created this connector.
+      * 
+      * @return The ApplicationConnection as set by
+      *         {@link #doInit(String, ApplicationConnection)}
+      */
+     public ApplicationConnection getConnection();
+     /**
+      * Tests whether the connector is enabled or not. This method checks that
+      * the connector is enabled in context, i.e. if the parent connector is
+      * disabled, this method must return false.
+      * 
+      * @return true if the connector is enabled, false otherwise
+      */
+     public boolean isEnabled();
+     /**
+      * 
+      * Called once by the framework to initialize the connector.
+      * <p>
+      * Note that the shared state is not yet available at this point nor any
+      * hierarchy information.
+      */
+     public void doInit(String connectorId, ApplicationConnection connection);
+     /**
+      * For internal use by the framework: returns the registered RPC
+      * implementations for an RPC interface identifier.
+      * 
+      * TODO interface identifier type or format may change
+      * 
+      * @param rpcInterfaceId
+      *            RPC interface identifier: fully qualified interface type name
+      * @return RPC interface implementations registered for an RPC interface,
+      *         not null
+      */
+     public <T extends ClientRpc> Collection<T> getRpcImplementations(
+             String rpcInterfaceId);
+     /**
+      * Adds a handler that is called whenever some part of the state has been
+      * updated by the server.
+      * 
+      * @param handler
+      *            The handler that should be added.
+      * @return A handler registration reference that can be used to unregister
+      *         the handler
+      */
+     public HandlerRegistration addStateChangeHandler(StateChangeHandler handler);
++    /**
++     * Removes the specified StateChangeHandler from this connector. The handler
++     * will no longer be notified of the state is updated by the server.
++     * 
++     * @param handler
++     *            The handler that should be removed.
++     */
++    public void removeStateChangeHandler(StateChangeHandler handler);
++
+     /**
+      * Sends the given event to all registered handlers.
+      * 
+      * @param event
+      *            The event to send.
+      */
+     public void fireEvent(GwtEvent<?> event);
+     /**
+      * Event called when connector has been unregistered.
+      */
+     public void onUnregister();
+     /**
+      * Returns the parent of this connector. Can be null for only the root
+      * connector.
+      * 
+      * @return The parent of this connector, as set by
+      *         {@link #setParent(ServerConnector)}.
+      */
+     @Override
+     public ServerConnector getParent();
+     /**
+      * Sets the parent for this connector. This method should only be called by
+      * the framework to ensure that the connector hierarchy on the client side
+      * and the server side are in sync.
+      * <p>
+      * Note that calling this method does not fire a
+      * {@link ConnectorHierarchyChangeEvent}. The event is fired only when the
+      * whole hierarchy has been updated.
+      * 
+      * @param parent
+      *            The new parent of the connector
+      */
+     public void setParent(ServerConnector parent);
+     public void updateEnabledState(boolean enabledState);
+     public void setChildren(List<ServerConnector> children);
+     public List<ServerConnector> getChildren();
+ }
index 0000000000000000000000000000000000000000,48842e29a028a84b21513870414f13f3e536f6f8..63955ddcc8f0a2068efe209d23cd41d7bbbd148e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,419 +1,419 @@@
 -        // Set v-connector style names for the widget
 -        getWidget().setStyleName("v-connector", true);
+ /*
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.terminal.gwt.client.ui;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Set;
+ import com.google.gwt.dom.client.Element;
+ import com.google.gwt.user.client.ui.Focusable;
+ import com.google.gwt.user.client.ui.HasEnabled;
+ import com.google.gwt.user.client.ui.Widget;
+ import com.vaadin.shared.ComponentState;
+ import com.vaadin.shared.Connector;
+ import com.vaadin.shared.ui.TabIndexState;
+ import com.vaadin.terminal.gwt.client.ApplicationConnection;
+ import com.vaadin.terminal.gwt.client.ComponentConnector;
+ import com.vaadin.terminal.gwt.client.ComponentContainerConnector;
+ import com.vaadin.terminal.gwt.client.ConnectorMap;
+ import com.vaadin.terminal.gwt.client.LayoutManager;
+ import com.vaadin.terminal.gwt.client.ServerConnector;
+ import com.vaadin.terminal.gwt.client.TooltipInfo;
+ import com.vaadin.terminal.gwt.client.UIDL;
+ import com.vaadin.terminal.gwt.client.Util;
+ import com.vaadin.terminal.gwt.client.VConsole;
+ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
+ import com.vaadin.terminal.gwt.client.ui.datefield.PopupDateFieldConnector;
+ import com.vaadin.terminal.gwt.client.ui.root.RootConnector;
+ public abstract class AbstractComponentConnector extends AbstractConnector
+         implements ComponentConnector {
+     private Widget widget;
+     private String lastKnownWidth = "";
+     private String lastKnownHeight = "";
+     /**
+      * The style names from getState().getStyles() which are currently applied
+      * to the widget.
+      */
+     protected List<String> styleNames = new ArrayList<String>();
+     /**
+      * Default constructor
+      */
+     public AbstractComponentConnector() {
+     }
+     @Override
+     protected void init() {
+         super.init();
+         getConnection().getVTooltip().connectHandlersToWidget(getWidget());
++        // Set the core 'v' style name for the widget
++        getWidget().setStyleName("v", true);
+     }
+     /**
+      * Creates and returns the widget for this VPaintableWidget. This method
+      * should only be called once when initializing the paintable.
+      * 
+      * @return
+      */
+     protected Widget createWidget() {
+         return ConnectorWidgetFactory.createWidget(getClass());
+     }
+     /**
+      * Returns the widget associated with this paintable. The widget returned by
+      * this method must not changed during the life time of the paintable.
+      * 
+      * @return The widget associated with this paintable
+      */
+     @Override
+     public Widget getWidget() {
+         if (widget == null) {
+             widget = createWidget();
+         }
+         return widget;
+     }
+     @Deprecated
+     public static boolean isRealUpdate(UIDL uidl) {
+         return !uidl.hasAttribute("cached");
+     }
+     @Override
+     public ComponentState getState() {
+         return (ComponentState) super.getState();
+     }
+     @Override
+     public void onStateChanged(StateChangeEvent stateChangeEvent) {
+         ConnectorMap paintableMap = ConnectorMap.get(getConnection());
+         if (getState().getDebugId() != null) {
+             getWidget().getElement().setId(getState().getDebugId());
+         } else {
+             getWidget().getElement().removeAttribute("id");
+         }
+         /*
+          * Disabled state may affect (override) tabindex so the order must be
+          * first setting tabindex, then enabled state (through super
+          * implementation).
+          */
+         if (getState() instanceof TabIndexState
+                 && getWidget() instanceof Focusable) {
+             ((Focusable) getWidget()).setTabIndex(((TabIndexState) getState())
+                     .getTabIndex());
+         }
+         super.onStateChanged(stateChangeEvent);
+         // Style names
+         updateWidgetStyleNames();
+         // Set captions
+         if (delegateCaptionHandling()) {
+             ServerConnector parent = getParent();
+             if (parent instanceof ComponentContainerConnector) {
+                 ((ComponentContainerConnector) parent).updateCaption(this);
+             } else if (parent == null && !(this instanceof RootConnector)) {
+                 VConsole.error("Parent of connector "
+                         + Util.getConnectorString(this)
+                         + " is null. This is typically an indication of a broken component hierarchy");
+             }
+         }
+         /*
+          * updateComponentSize need to be after caption update so caption can be
+          * taken into account
+          */
+         updateComponentSize();
+     }
+     @Override
+     public void setWidgetEnabled(boolean widgetEnabled) {
+         // add or remove v-disabled style name from the widget
+         setWidgetStyleName(ApplicationConnection.DISABLED_CLASSNAME,
+                 !widgetEnabled);
+         if (getWidget() instanceof HasEnabled) {
+             // set widget specific enabled state
+             ((HasEnabled) getWidget()).setEnabled(widgetEnabled);
+             // make sure the caption has or has not v-disabled style
+             if (delegateCaptionHandling()) {
+                 ServerConnector parent = getParent();
+                 if (parent instanceof ComponentContainerConnector) {
+                     ((ComponentContainerConnector) parent).updateCaption(this);
+                 } else if (parent == null && !(this instanceof RootConnector)) {
+                     VConsole.error("Parent of connector "
+                             + Util.getConnectorString(this)
+                             + " is null. This is typically an indication of a broken component hierarchy");
+                 }
+             }
+         }
+     }
+     private void updateComponentSize() {
+         String newWidth = getState().getWidth();
+         String newHeight = getState().getHeight();
+         // Parent should be updated if either dimension changed between relative
+         // and non-relative
+         if (newWidth.endsWith("%") != lastKnownWidth.endsWith("%")) {
+             Connector parent = getParent();
+             if (parent instanceof ManagedLayout) {
+                 getLayoutManager().setNeedsHorizontalLayout(
+                         (ManagedLayout) parent);
+             }
+         }
+         if (newHeight.endsWith("%") != lastKnownHeight.endsWith("%")) {
+             Connector parent = getParent();
+             if (parent instanceof ManagedLayout) {
+                 getLayoutManager().setNeedsVerticalLayout(
+                         (ManagedLayout) parent);
+             }
+         }
+         lastKnownWidth = newWidth;
+         lastKnownHeight = newHeight;
+         // Set defined sizes
+         Widget widget = getWidget();
+         widget.setStyleName("v-has-width", !isUndefinedWidth());
+         widget.setStyleName("v-has-height", !isUndefinedHeight());
+         widget.setHeight(newHeight);
+         widget.setWidth(newWidth);
+     }
+     @Override
+     public boolean isRelativeHeight() {
+         return getState().getHeight().endsWith("%");
+     }
+     @Override
+     public boolean isRelativeWidth() {
+         return getState().getWidth().endsWith("%");
+     }
+     @Override
+     public boolean isUndefinedHeight() {
+         return getState().getHeight().length() == 0;
+     }
+     @Override
+     public boolean isUndefinedWidth() {
+         return getState().getWidth().length() == 0;
+     }
+     /*
+      * (non-Javadoc)
+      * 
+      * @see
+      * com.vaadin.terminal.gwt.client.ComponentConnector#delegateCaptionHandling
+      * ()
+      */
+     @Override
+     public boolean delegateCaptionHandling() {
+         return true;
+     }
+     /**
+      * Updates the user defined, read-only and error style names for the widget
+      * based the shared state. User defined style names are prefixed with the
+      * primary style name of the widget returned by {@link #getWidget()}
+      * <p>
+      * This method can be overridden to provide additional style names for the
+      * component, for example see
+      * {@link AbstractFieldConnector#updateWidgetStyleNames()}
+      * </p>
+      */
+     protected void updateWidgetStyleNames() {
+         ComponentState state = getState();
+         String primaryStyleName = getWidget().getStylePrimaryName();
+         // should be in AbstractFieldConnector ?
+         // add / remove read-only style name
+         setWidgetStyleName("v-readonly", isReadOnly());
+         // add / remove error style name
+         setWidgetStyleNameWithPrefix(primaryStyleName,
+                 ApplicationConnection.ERROR_CLASSNAME_EXT,
+                 null != state.getErrorMessage());
+         // add additional user defined style names as class names, prefixed with
+         // component default class name. remove nonexistent style names.
+         if (state.hasStyles()) {
+             // add new style names
+             List<String> newStyles = new ArrayList<String>();
+             newStyles.addAll(state.getStyles());
+             newStyles.removeAll(styleNames);
+             for (String newStyle : newStyles) {
+                 setWidgetStyleName(newStyle, true);
+                 setWidgetStyleNameWithPrefix(primaryStyleName + "-", newStyle,
+                         true);
+             }
+             // remove nonexistent style names
+             styleNames.removeAll(state.getStyles());
+             for (String oldStyle : styleNames) {
+                 setWidgetStyleName(oldStyle, false);
+                 setWidgetStyleNameWithPrefix(primaryStyleName + "-", oldStyle,
+                         false);
+             }
+             styleNames.clear();
+             styleNames.addAll(state.getStyles());
+         } else {
+             // remove all old style names
+             for (String oldStyle : styleNames) {
+                 setWidgetStyleName(oldStyle, false);
+                 setWidgetStyleNameWithPrefix(primaryStyleName + "-", oldStyle,
+                         false);
+             }
+             styleNames.clear();
+         }
+     }
+     /**
+      * This is used to add / remove state related style names from the widget.
+      * <p>
+      * Override this method for example if the style name given here should be
+      * updated in another widget in addition to the one returned by the
+      * {@link #getWidget()}.
+      * </p>
+      * 
+      * @param styleName
+      *            the style name to be added or removed
+      * @param add
+      *            <code>true</code> to add the given style, <code>false</code>
+      *            to remove it
+      */
+     protected void setWidgetStyleName(String styleName, boolean add) {
+         getWidget().setStyleName(styleName, add);
+     }
+     /**
+      * This is used to add / remove state related prefixed style names from the
+      * widget.
+      * <p>
+      * Override this method if the prefixed style name given here should be
+      * updated in another widget in addition to the one returned by the
+      * <code>Connector</code>'s {@link #getWidget()}, or if the prefix should be
+      * different. For example see
+      * {@link PopupDateFieldConnector#setWidgetStyleNameWithPrefix(String, String, boolean)}
+      * </p>
+      * 
+      * @param styleName
+      *            the style name to be added or removed
+      * @param add
+      *            <code>true</code> to add the given style, <code>false</code>
+      *            to remove it
+      * @deprecated This will be removed once styles are no longer added with
+      *             prefixes.
+      */
+     @Deprecated
+     protected void setWidgetStyleNameWithPrefix(String prefix,
+             String styleName, boolean add) {
+         if (!styleName.startsWith("-")) {
+             if (!prefix.endsWith("-")) {
+                 prefix += "-";
+             }
+         } else {
+             if (prefix.endsWith("-")) {
+                 styleName.replaceFirst("-", "");
+             }
+         }
+         getWidget().setStyleName(prefix + styleName, add);
+     }
+     /*
+      * (non-Javadoc)
+      * 
+      * @see com.vaadin.terminal.gwt.client.ComponentConnector#isReadOnly()
+      */
+     @Override
+     @Deprecated
+     public boolean isReadOnly() {
+         return getState().isReadOnly();
+     }
+     @Override
+     public LayoutManager getLayoutManager() {
+         return LayoutManager.get(getConnection());
+     }
+     /**
+      * Checks if there is a registered server side listener for the given event
+      * identifier.
+      * 
+      * @param eventIdentifier
+      *            The identifier to check for
+      * @return true if an event listener has been registered with the given
+      *         event identifier on the server side, false otherwise
+      */
+     @Override
+     public boolean hasEventListener(String eventIdentifier) {
+         Set<String> reg = getState().getRegisteredEventListeners();
+         return (reg != null && reg.contains(eventIdentifier));
+     }
+     @Override
+     public void updateEnabledState(boolean enabledState) {
+         super.updateEnabledState(enabledState);
+         setWidgetEnabled(isEnabled());
+     }
+     @Override
+     public void onUnregister() {
+         super.onUnregister();
+         // Show an error if widget is still attached to DOM. It should never be
+         // at this point.
+         if (getWidget() != null && getWidget().isAttached()) {
+             getWidget().removeFromParent();
+             VConsole.error("Widget is still attached to the DOM after the connector ("
+                     + Util.getConnectorString(this)
+                     + ") has been unregistered. Widget was removed.");
+         }
+     }
+     /*
+      * (non-Javadoc)
+      * 
+      * @see
+      * com.vaadin.terminal.gwt.client.ComponentConnector#getTooltipInfo(com.
+      * google.gwt.dom.client.Element)
+      */
+     @Override
+     public TooltipInfo getTooltipInfo(Element element) {
+         return new TooltipInfo(getState().getDescription(), getState()
+                 .getErrorMessage());
+     }
+ }
index 0000000000000000000000000000000000000000,514f63fdd8672c600fcf0cdc4e226d3c23635ed4..09b755629303685d9703ab3ab3a6846ddafd4b7c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,289 +1,293 @@@
+ /* 
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.terminal.gwt.client.ui;
+ import java.util.ArrayList;
+ import java.util.Collection;
+ import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+ import com.google.gwt.event.shared.GwtEvent;
+ import com.google.gwt.event.shared.HandlerManager;
+ import com.google.web.bindery.event.shared.HandlerRegistration;
+ import com.vaadin.shared.communication.ClientRpc;
+ import com.vaadin.shared.communication.SharedState;
+ import com.vaadin.terminal.gwt.client.ApplicationConnection;
+ import com.vaadin.terminal.gwt.client.ServerConnector;
+ import com.vaadin.terminal.gwt.client.Util;
+ import com.vaadin.terminal.gwt.client.VConsole;
+ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
+ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler;
+ /**
+  * An abstract implementation of Connector.
+  * 
+  * @author Vaadin Ltd
+  * @since 7.0.0
+  * 
+  */
+ public abstract class AbstractConnector implements ServerConnector,
+         StateChangeHandler {
+     private ApplicationConnection connection;
+     private String id;
+     private HandlerManager handlerManager;
+     private Map<String, Collection<ClientRpc>> rpcImplementations;
+     private final boolean debugLogging = false;
+     private SharedState state;
+     private ServerConnector parent;
+     /**
+      * Temporary storage for last enabled state to be able to see if it has
+      * changed. Can be removed once we are able to listen specifically for
+      * enabled changes in the state. Widget.isEnabled() cannot be used as all
+      * Widgets do not implement HasEnabled
+      */
+     private boolean lastEnabledState = true;
+     private List<ServerConnector> children;
+     /*
+      * (non-Javadoc)
+      * 
+      * @see com.vaadin.terminal.gwt.client.VPaintable#getConnection()
+      */
+     @Override
+     public final ApplicationConnection getConnection() {
+         return connection;
+     }
+     /*
+      * (non-Javadoc)
+      * 
+      * @see com.vaadin.terminal.gwt.client.Connector#getId()
+      */
+     @Override
+     public String getConnectorId() {
+         return id;
+     }
+     /**
+      * Called once by the framework to initialize the connector.
+      * <p>
+      * Note that the shared state is not yet available when this method is
+      * called.
+      * <p>
+      * Connector classes should override {@link #init()} instead of this method.
+      */
+     @Override
+     public final void doInit(String connectorId,
+             ApplicationConnection connection) {
+         this.connection = connection;
+         id = connectorId;
+         addStateChangeHandler(this);
+         init();
+     }
+     /**
+      * Called when the connector has been initialized. Override this method to
+      * perform initialization of the connector.
+      */
+     // FIXME: It might make sense to make this abstract to force users to
+     // use init instead of constructor, where connection and id has not yet been
+     // set.
+     protected void init() {
+     }
+     /**
+      * Registers an implementation for a server to client RPC interface.
+      * 
+      * Multiple registrations can be made for a single interface, in which case
+      * all of them receive corresponding RPC calls.
+      * 
+      * @param rpcInterface
+      *            RPC interface
+      * @param implementation
+      *            implementation that should receive RPC calls
+      * @param <T>
+      *            The type of the RPC interface that is being registered
+      */
+     protected <T extends ClientRpc> void registerRpc(Class<T> rpcInterface,
+             T implementation) {
+         String rpcInterfaceId = rpcInterface.getName().replaceAll("\\$", ".");
+         if (null == rpcImplementations) {
+             rpcImplementations = new HashMap<String, Collection<ClientRpc>>();
+         }
+         if (null == rpcImplementations.get(rpcInterfaceId)) {
+             rpcImplementations.put(rpcInterfaceId, new ArrayList<ClientRpc>());
+         }
+         rpcImplementations.get(rpcInterfaceId).add(implementation);
+     }
+     /**
+      * Unregisters an implementation for a server to client RPC interface.
+      * 
+      * @param rpcInterface
+      *            RPC interface
+      * @param implementation
+      *            implementation to unregister
+      */
+     protected <T extends ClientRpc> void unregisterRpc(Class<T> rpcInterface,
+             T implementation) {
+         String rpcInterfaceId = rpcInterface.getName().replaceAll("\\$", ".");
+         if (null != rpcImplementations
+                 && null != rpcImplementations.get(rpcInterfaceId)) {
+             rpcImplementations.get(rpcInterfaceId).remove(implementation);
+         }
+     }
+     @Override
+     public <T extends ClientRpc> Collection<T> getRpcImplementations(
+             String rpcInterfaceId) {
+         if (null == rpcImplementations) {
+             return Collections.emptyList();
+         }
+         return (Collection<T>) rpcImplementations.get(rpcInterfaceId);
+     }
+     @Override
+     public void fireEvent(GwtEvent<?> event) {
+         if (handlerManager != null) {
+             handlerManager.fireEvent(event);
+         }
+     }
+     protected HandlerManager ensureHandlerManager() {
+         if (handlerManager == null) {
+             handlerManager = new HandlerManager(this);
+         }
+         return handlerManager;
+     }
+     @Override
+     public HandlerRegistration addStateChangeHandler(StateChangeHandler handler) {
+         return ensureHandlerManager()
+                 .addHandler(StateChangeEvent.TYPE, handler);
+     }
+     @Override
++    public void removeStateChangeHandler(StateChangeHandler handler) {
++        ensureHandlerManager().removeHandler(StateChangeEvent.TYPE, handler);
++    }
++
+     public void onStateChanged(StateChangeEvent stateChangeEvent) {
+         if (debugLogging) {
+             VConsole.log("State change event for "
+                     + Util.getConnectorString(stateChangeEvent.getConnector())
+                     + " received by " + Util.getConnectorString(this));
+         }
+         updateEnabledState(isEnabled());
+     }
+     /*
+      * (non-Javadoc)
+      * 
+      * @see com.vaadin.terminal.gwt.client.ServerConnector#onUnregister()
+      */
+     @Override
+     public void onUnregister() {
+         if (debugLogging) {
+             VConsole.log("Unregistered connector "
+                     + Util.getConnectorString(this));
+         }
+     }
+     /**
+      * Returns the shared state object for this connector.
+      * 
+      * Override this method to define the shared state type for your connector.
+      * 
+      * @return the current shared state (never null)
+      */
+     @Override
+     public SharedState getState() {
+         if (state == null) {
+             state = createState();
+         }
+         return state;
+     }
+     /**
+      * Creates a state object with default values for this connector. The
+      * created state object must be compatible with the return type of
+      * {@link #getState()}. The default implementation creates a state object
+      * using GWT.create() using the defined return type of {@link #getState()}.
+      * 
+      * @return A new state object
+      */
+     protected SharedState createState() {
+         return ConnectorStateFactory.createState(getClass());
+     }
+     @Override
+     public ServerConnector getParent() {
+         return parent;
+     }
+     @Override
+     public void setParent(ServerConnector parent) {
+         this.parent = parent;
+     }
+     @Override
+     public List<ServerConnector> getChildren() {
+         if (children == null) {
+             return Collections.emptyList();
+         }
+         return children;
+     }
+     @Override
+     public void setChildren(List<ServerConnector> children) {
+         this.children = children;
+     }
+     @Override
+     public boolean isEnabled() {
+         if (!getState().isEnabled()) {
+             return false;
+         }
+         if (getParent() == null) {
+             return true;
+         } else {
+             return getParent().isEnabled();
+         }
+     }
+     @Override
+     public void updateEnabledState(boolean enabledState) {
+         if (lastEnabledState == enabledState) {
+             return;
+         }
+         lastEnabledState = enabledState;
+         for (ServerConnector c : getChildren()) {
+             // Update children as they might be affected by the enabled state of
+             // their parent
+             c.updateEnabledState(c.isEnabled());
+         }
+     }
+ }
index 0000000000000000000000000000000000000000,92afad05209679c68000178b496570602c8cdc23..6c9f05573277456fba01ca13e9ccd8872dfa58e1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,393 +1,413 @@@
+ /* 
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.shared;
+ import java.util.HashSet;
+ import java.util.List;
+ import java.util.Set;
+ import com.vaadin.shared.communication.SharedState;
+ import com.vaadin.shared.communication.URLReference;
+ /**
+  * Default shared state implementation for UI components.
+  * 
+  * State classes of concrete components should extend this class.
+  * 
+  * @since 7.0
+  */
+ public class ComponentState extends SharedState {
+     private String height = "";
+     private String width = "";
+     private boolean readOnly = false;
+     private boolean immediate = false;
+     private String description = "";
+     // Note: for the caption, there is a difference between null and an empty
+     // string!
+     private String caption = null;
+     private boolean visible = true;
+     private URLReference icon = null;
+     private List<String> styles = null;
+     private String debugId = null;
+     /**
+      * A set of event identifiers with registered listeners.
+      */
+     private Set<String> registeredEventListeners = null;
+     // HTML formatted error message for the component
+     // TODO this could be an object with more information, but currently the UI
+     // only uses the message
+     private String errorMessage = null;
+     /**
+      * Returns the component height as set by the server.
+      * 
+      * Can be relative (containing the percent sign) or absolute, or empty
+      * string for undefined height.
+      * 
+      * @return component height as defined by the server, not null
+      */
+     public String getHeight() {
+         if (height == null) {
+             return "";
+         }
+         return height;
+     }
+     /**
+      * Sets the height of the component in the server format.
+      * 
+      * Can be relative (containing the percent sign) or absolute, or null or
+      * empty string for undefined height.
+      * 
+      * @param height
+      *            component height
+      */
+     public void setHeight(String height) {
+         this.height = height;
+     }
+     /**
+      * Returns true if the component height is undefined, false if defined
+      * (absolute or relative).
+      * 
+      * @return true if component height is undefined
+      */
+     public boolean isUndefinedHeight() {
+         return "".equals(getHeight());
+     }
++    /**
++     * Returns true if the component height is relative to the parent, i.e.
++     * percentage, false if it is fixed/auto.
++     * 
++     * @return true if component height is relative (percentage)
++     */
++    public boolean isRelativeHeight() {
++        return getHeight().endsWith("%");
++    }
++
+     /**
+      * Returns the component width as set by the server.
+      * 
+      * Can be relative (containing the percent sign) or absolute, or empty
+      * string for undefined height.
+      * 
+      * @return component width as defined by the server, not null
+      */
+     public String getWidth() {
+         if (width == null) {
+             return "";
+         }
+         return width;
+     }
+     /**
+      * Sets the width of the component in the server format.
+      * 
+      * Can be relative (containing the percent sign) or absolute, or null or
+      * empty string for undefined width.
+      * 
+      * @param width
+      *            component width
+      */
+     public void setWidth(String width) {
+         this.width = width;
+     }
+     /**
+      * Returns true if the component width is undefined, false if defined
+      * (absolute or relative).
+      * 
+      * @return true if component width is undefined
+      */
+     public boolean isUndefinedWidth() {
+         return "".equals(getWidth());
+     }
++    /**
++     * Returns true if the component width is relative to the parent, i.e.
++     * percentage, false if it is fixed/auto.
++     * 
++     * @return true if component width is relative (percentage)
++     */
++    public boolean isRelativeWidth() {
++        return getWidth().endsWith("%");
++    }
++
+     /**
+      * Returns true if the component is in read-only mode.
+      * 
+      * @see com.vaadin.ui.Component#isReadOnly()
+      * 
+      * @return true if the component is in read-only mode
+      */
+     public boolean isReadOnly() {
+         return readOnly;
+     }
+     /**
+      * Sets or resets the read-only mode for a component.
+      * 
+      * @see com.vaadin.ui.Component#setReadOnly()
+      * 
+      * @param readOnly
+      *            new mode for the component
+      */
+     public void setReadOnly(boolean readOnly) {
+         this.readOnly = readOnly;
+     }
+     /**
+      * Returns true if the component is in immediate mode.
+      * 
+      * @see com.vaadin.terminal.VariableOwner#isImmediate()
+      * 
+      * @return true if the component is in immediate mode
+      */
+     public boolean isImmediate() {
+         return immediate;
+     }
+     /**
+      * Sets or resets the immediate mode for a component.
+      * 
+      * @see com.vaadin.terminal.VariableOwner#setImmediate()
+      * 
+      * @param immediate
+      *            new mode for the component
+      */
+     public void setImmediate(boolean immediate) {
+         this.immediate = immediate;
+     }
+     /**
+      * Returns true if the component has user-defined styles.
+      * 
+      * @return true if the component has user-defined styles
+      */
+     public boolean hasStyles() {
+         return styles != null && !styles.isEmpty();
+     }
+     /**
+      * Gets the description of the component (typically shown as tooltip).
+      * 
+      * @see com.vaadin.ui.AbstractComponent#getDescription()
+      * 
+      * @return component description (not null, can be empty string)
+      */
+     public String getDescription() {
+         return description;
+     }
+     /**
+      * Sets the description of the component (typically shown as tooltip).
+      * 
+      * @see com.vaadin.ui.AbstractComponent#setDescription(String)
+      * 
+      * @param description
+      *            new component description (can be null)
+      */
+     public void setDescription(String description) {
+         this.description = description;
+     }
+     /**
+      * Returns true if the component has a description.
+      * 
+      * @return true if the component has a description
+      */
+     public boolean hasDescription() {
+         return getDescription() != null && !"".equals(getDescription());
+     }
+     /**
+      * Gets the caption of the component (typically shown by the containing
+      * layout).
+      * 
+      * @see com.vaadin.ui.Component#getCaption()
+      * 
+      * @return component caption - can be null (no caption) or empty string
+      *         (reserve space for an empty caption)
+      */
+     public String getCaption() {
+         return caption;
+     }
+     /**
+      * Sets the caption of the component (typically shown by the containing
+      * layout).
+      * 
+      * @see com.vaadin.ui.Component#setCaption(String)
+      * 
+      * @param caption
+      *            new component caption - can be null (no caption) or empty
+      *            string (reserve space for an empty caption)
+      */
+     public void setCaption(String caption) {
+         this.caption = caption;
+     }
+     /**
+      * Returns the visibility state of the component. Note that this state is
+      * related to the component only, not its parent. This might differ from
+      * what {@link com.vaadin.ui.Component#isVisible()} returns as this takes
+      * the hierarchy into account.
+      * 
+      * @return The visibility state.
+      */
+     public boolean isVisible() {
+         return visible;
+     }
+     /**
+      * Sets the visibility state of the component.
+      * 
+      * @param visible
+      *            The new visibility state.
+      */
+     public void setVisible(boolean visible) {
+         this.visible = visible;
+     }
+     public URLReference getIcon() {
+         return icon;
+     }
+     public void setIcon(URLReference icon) {
+         this.icon = icon;
+     }
+     /**
+      * Gets the style names for the component.
+      * 
+      * @return A List of style names or null if no styles have been set.
+      */
+     public List<String> getStyles() {
+         return styles;
+     }
+     /**
+      * Sets the style names for the component.
+      * 
+      * @param styles
+      *            A list containing style names
+      */
+     public void setStyles(List<String> styles) {
+         this.styles = styles;
+     }
+     /**
+      * Gets the debug id for the component. The debugId is added as DOM id for
+      * the component.
+      * 
+      * @return The debug id for the component or null if not set
+      */
+     public String getDebugId() {
+         return debugId;
+     }
+     /**
+      * Sets the debug id for the component. The debugId is added as DOM id for
+      * the component.
+      * 
+      * @param debugId
+      *            The new debugId for the component or null for no debug id
+      * 
+      */
+     public void setDebugId(String debugId) {
+         this.debugId = debugId;
+     }
+     /**
+      * Gets the identifiers for the event listeners that have been registered
+      * for the component (using an event id)
+      * 
+      * @return A set of event identifiers or null if no identifiers have been
+      *         registered
+      */
+     public Set<String> getRegisteredEventListeners() {
+         return registeredEventListeners;
+     }
+     /**
+      * Sets the identifiers for the event listeners that have been registered
+      * for the component (using an event id)
+      * 
+      * @param registeredEventListeners
+      *            The new set of identifiers or null if no identifiers have been
+      *            registered
+      */
+     public void setRegisteredEventListeners(Set<String> registeredEventListeners) {
+         this.registeredEventListeners = registeredEventListeners;
+     }
+     /**
+      * Adds an event listener id.
+      * 
+      * @param eventListenerId
+      *            The event identifier to add
+      */
+     public void addRegisteredEventListener(String eventListenerId) {
+         if (registeredEventListeners == null) {
+             registeredEventListeners = new HashSet<String>();
+         }
+         registeredEventListeners.add(eventListenerId);
+     }
+     /**
+      * Removes an event listener id.
+      * 
+      * @param eventListenerId
+      *            The event identifier to remove
+      */
+     public void removeRegisteredEventListener(String eventIdentifier) {
+         if (registeredEventListeners == null) {
+             return;
+         }
+         registeredEventListeners.remove(eventIdentifier);
+         if (registeredEventListeners.size() == 0) {
+             registeredEventListeners = null;
+         }
+     }
+     /**
+      * Returns the current error message for the component.
+      * 
+      * @return HTML formatted error message to show for the component or null if
+      *         none
+      */
+     public String getErrorMessage() {
+         return errorMessage;
+     }
+     /**
+      * Sets the current error message for the component.
+      * 
+      * TODO this could use an object with more details about the error
+      * 
+      * @param errorMessage
+      *            HTML formatted error message to show for the component or null
+      *            for none
+      */
+     public void setErrorMessage(String errorMessage) {
+         this.errorMessage = errorMessage;
+     }
+ }