summaryrefslogtreecommitdiffstats
path: root/src/com/vaadin/terminal/gwt
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/vaadin/terminal/gwt')
-rw-r--r--src/com/vaadin/terminal/gwt/client/ComponentLocator.java9
-rw-r--r--src/com/vaadin/terminal/gwt/client/ComputedStyle.java26
-rw-r--r--src/com/vaadin/terminal/gwt/client/LayoutManager.java46
-rw-r--r--src/com/vaadin/terminal/gwt/client/ServerConnector.java9
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java590
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java4
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java18
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java752
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java18
9 files changed, 1458 insertions, 14 deletions
diff --git a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java
index 28252a9efb..8df9dc41b9 100644
--- a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java
+++ b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java
@@ -16,6 +16,7 @@ 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;
@@ -486,6 +487,14 @@ public class ComponentLocator {
// 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
diff --git a/src/com/vaadin/terminal/gwt/client/ComputedStyle.java b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java
index 29b02b4dde..76f2328711 100644
--- a/src/com/vaadin/terminal/gwt/client/ComputedStyle.java
+++ b/src/com/vaadin/terminal/gwt/client/ComputedStyle.java
@@ -83,19 +83,19 @@ public class ComputedStyle {
// 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;
- }
+ 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;
+ }
}
diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManager.java b/src/com/vaadin/terminal/gwt/client/LayoutManager.java
index 74586a6def..41ad0e9f47 100644
--- a/src/com/vaadin/terminal/gwt/client/LayoutManager.java
+++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java
@@ -203,7 +203,7 @@ public class LayoutManager {
}
}
- private void layoutLater() {
+ public void layoutLater() {
if (!layoutPending) {
layoutPending = true;
layoutTimer.schedule(100);
@@ -1020,6 +1020,50 @@ public class LayoutManager {
}
/**
+ * 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
diff --git a/src/com/vaadin/terminal/gwt/client/ServerConnector.java b/src/com/vaadin/terminal/gwt/client/ServerConnector.java
index ce46a3fb5e..a0f08b92b9 100644
--- a/src/com/vaadin/terminal/gwt/client/ServerConnector.java
+++ b/src/com/vaadin/terminal/gwt/client/ServerConnector.java
@@ -75,6 +75,15 @@ public interface ServerConnector extends Connector {
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
diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java
new file mode 100644
index 0000000000..413c605a88
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java
@@ -0,0 +1,590 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Element;
+import com.vaadin.terminal.gwt.client.AbstractFieldState;
+import com.vaadin.terminal.gwt.client.ComponentConnector;
+import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent;
+import com.vaadin.terminal.gwt.client.Util;
+import com.vaadin.terminal.gwt.client.communication.RpcProxy;
+import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
+import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler;
+import com.vaadin.terminal.gwt.client.ui.VBoxLayout.CaptionPosition;
+import com.vaadin.terminal.gwt.client.ui.VBoxLayout.Slot;
+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.orderedlayout.AbstractOrderedLayoutServerRpc;
+import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutState;
+
+public abstract class AbstractBoxLayoutConnector extends
+ AbstractLayoutConnector /* implements PostLayoutListener */{
+
+ AbstractOrderedLayoutServerRpc rpc;
+
+ private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler(
+ this) {
+
+ @Override
+ protected ComponentConnector getChildComponent(Element element) {
+ return Util.getConnectorForElement(getConnection(), getWidget(),
+ element);
+ }
+
+ @Override
+ protected LayoutClickRpc getLayoutClickRPC() {
+ return rpc;
+ };
+
+ };
+
+ @Override
+ public void init() {
+ rpc = RpcProxy.create(AbstractOrderedLayoutServerRpc.class, this);
+ getWidget().setLayoutManager(getLayoutManager());
+ }
+
+ @Override
+ public AbstractOrderedLayoutState getState() {
+ return (AbstractOrderedLayoutState) super.getState();
+ }
+
+ @Override
+ public VBoxLayout getWidget() {
+ return (VBoxLayout) super.getWidget();
+ }
+
+ /**
+ * For bookkeeping. Used to determine if extra calculations are needed for
+ * horizontal layout.
+ */
+ private HashSet<ComponentConnector> hasVerticalAlignment = new HashSet<ComponentConnector>();
+
+ /**
+ * For bookkeeping. Used to determine if extra calculations are needed for
+ * horizontal layout.
+ */
+ private HashSet<ComponentConnector> hasRelativeHeight = new HashSet<ComponentConnector>();
+
+ /**
+ * For bookkeeping. Used to determine if extra calculations are needed for
+ * horizontal layout.
+ */
+ private HashSet<ComponentConnector> hasExpandRatio = new HashSet<ComponentConnector>();
+
+ /**
+ * For bookkeeping. Used in extra calculations for horizontal layout.
+ */
+ private HashSet<Element> needsMeasure = new HashSet<Element>();
+
+ /**
+ * For bookkeeping. Used in extra calculations for horizontal layout.
+ */
+ private HashMap<Element, Integer> childElementHeight = new HashMap<Element, Integer>();
+
+ /**
+ * For bookkeeping. Used in extra calculations for horizontal layout.
+ */
+ private HashMap<Element, Integer> childCaptionElementHeight = new HashMap<Element, Integer>();
+
+ public void updateCaption(ComponentConnector child) {
+ Slot slot = getWidget().getSlot(child);
+
+ String caption = child.getState().getCaption();
+ String iconUrl = child.getState().getIcon() != null ? child.getState()
+ .getIcon().getURL() : null;
+ List<String> styles = child.getState().getStyles();
+ String error = child.getState().getErrorMessage();
+ boolean showError = error != null;
+ if (child.getState() instanceof AbstractFieldState) {
+ AbstractFieldState abstractFieldState = (AbstractFieldState) child
+ .getState();
+ showError = showError && !abstractFieldState.isHideErrors();
+ }
+ boolean required = false;
+ if (child instanceof AbstractFieldConnector) {
+ required = ((AbstractFieldConnector) child).isRequired();
+ }
+ boolean enabled = child.getState().isEnabled();
+
+ slot.setCaption(caption, iconUrl, styles, error, showError, required,
+ enabled);
+
+ slot.setRelativeWidth(child.isRelativeWidth());
+ slot.setRelativeHeight(child.isRelativeHeight());
+
+ if (slot.hasCaption()) {
+ CaptionPosition pos = slot.getCaptionPosition();
+ getLayoutManager().addElementResizeListener(
+ slot.getCaptionElement(), slotCaptionResizeListener);
+ if (child.isRelativeHeight()
+ && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
+ getWidget().updateCaptionOffset(slot.getCaptionElement());
+ } else if (child.isRelativeWidth()
+ && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) {
+ getWidget().updateCaptionOffset(slot.getCaptionElement());
+ }
+ } else {
+ childCaptionElementHeight.remove(child.getWidget().getElement());
+ }
+
+ updateLayoutHeight();
+
+ if (needsExpand()) {
+ updateExpand();
+ }
+ }
+
+ @Override
+ public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
+ super.onConnectorHierarchyChange(event);
+
+ List<ComponentConnector> previousChildren = event.getOldChildren();
+ int currentIndex = 0;
+ VBoxLayout layout = getWidget();
+
+ for (ComponentConnector child : getChildComponents()) {
+ Slot slot = layout.getSlot(child);
+ if (slot.getParent() != layout) {
+ child.addStateChangeHandler(childStateChangeHandler);
+ }
+ layout.addOrMoveSlot(slot, currentIndex++);
+ }
+
+ for (ComponentConnector child : previousChildren) {
+ if (child.getParent() != this) {
+ Slot slot = layout.getSlot(child);
+ hasVerticalAlignment.remove(child);
+ hasRelativeHeight.remove(child);
+ hasExpandRatio.remove(child);
+ needsMeasure.remove(child.getWidget().getElement());
+ childElementHeight.remove(child.getWidget().getElement());
+ childCaptionElementHeight
+ .remove(child.getWidget().getElement());
+ getLayoutManager().removeElementResizeListener(
+ child.getWidget().getElement(),
+ childComponentResizeListener);
+ if (slot.hasCaption()) {
+ getLayoutManager()
+ .removeElementResizeListener(
+ slot.getCaptionElement(),
+ slotCaptionResizeListener);
+ }
+ if (slot.getSpacingElement() != null) {
+ getLayoutManager().removeElementResizeListener(
+ slot.getSpacingElement(), spacingResizeListener);
+ }
+ child.removeStateChangeHandler(childStateChangeHandler);
+ layout.removeSlot(child.getWidget());
+ }
+ }
+
+ // If some component is added/removed, we need to recalculate the expand
+ if (needsExpand()) {
+ updateExpand();
+ } else {
+ getWidget().clearExpand();
+ }
+
+ }
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+
+ clickEventHandler.handleEventHandlerRegistration();
+
+ getWidget().setMargin(new VMarginInfo(getState().getMarginsBitmask()));
+ getWidget().setSpacing(getState().isSpacing());
+
+ hasExpandRatio.clear();
+ hasVerticalAlignment.clear();
+ hasRelativeHeight.clear();
+ needsMeasure.clear();
+
+ boolean equalExpandRatio = getWidget().vertical ? !isUndefinedHeight()
+ : !isUndefinedWidth();
+ for (ComponentConnector child : getChildComponents()) {
+ double expandRatio = getState().getChildData().get(child)
+ .getExpandRatio();
+ if (expandRatio > 0) {
+ equalExpandRatio = false;
+ break;
+ }
+ }
+
+ for (ComponentConnector child : getChildComponents()) {
+ Slot slot = getWidget().getSlot(child);
+
+ AlignmentInfo alignment = new AlignmentInfo(getState()
+ .getChildData().get(child).getAlignmentBitmask());
+ slot.setAlignment(alignment);
+
+ double expandRatio = getState().getChildData().get(child)
+ .getExpandRatio();
+ if (equalExpandRatio) {
+ expandRatio = 1;
+ } else if (expandRatio == 0) {
+ expandRatio = -1;
+ }
+ slot.setExpandRatio(expandRatio);
+
+ // Bookkeeping to identify special cases that need extra
+ // calculations
+ if (alignment.isVerticalCenter() || alignment.isBottom()) {
+ hasVerticalAlignment.add(child);
+ }
+
+ if (expandRatio > 0) {
+ hasExpandRatio.add(child);
+ }
+
+ if (child.getState().isRelativeHeight()) {
+ hasRelativeHeight.add(child);
+ } else {
+ needsMeasure.add(child.getWidget().getElement());
+ }
+ }
+
+ updateAllSlotListeners();
+
+ updateLayoutHeight();
+ }
+
+ StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+
+ ComponentConnector child = (ComponentConnector) stateChangeEvent
+ .getConnector();
+
+ // We need to update the slot size if the component size is changed
+ // to relative
+ Slot slot = getWidget().getSlot(child);
+ slot.setRelativeWidth(child.isRelativeWidth());
+ slot.setRelativeHeight(child.isRelativeHeight());
+
+ // For relative sized widgets, we need to set the caption offset
+ // if (slot.hasCaption()) {
+ // CaptionPosition pos = slot.getCaptionPosition();
+ // if (child.isRelativeHeight()
+ // && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM))
+ // {
+ // getWidget().updateCaptionOffset(slot.getCaptionElement());
+ // } else if (child.isRelativeWidth()
+ // && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT))
+ // {
+ // getWidget().updateCaptionOffset(slot.getCaptionElement());
+ // }
+ // }
+
+ updateSlotListeners(child);
+ }
+ };
+
+ private boolean needsFixedHeight() {
+ if (!getWidget().vertical
+ && isUndefinedHeight()
+ && (hasRelativeHeight.size() > 0 || (hasVerticalAlignment
+ .size() > 0 && hasVerticalAlignment.size() < getChildren()
+ .size()))) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean needsExpand() {
+ boolean canApplyExpand = (getWidget().vertical && !isUndefinedHeight())
+ || (!getWidget().vertical && !isUndefinedWidth());
+ return hasExpandRatio.size() > 0 && canApplyExpand;
+ }
+
+ private void updateAllSlotListeners() {
+ for (ComponentConnector child : getChildComponents()) {
+ updateSlotListeners(child);
+ }
+ // if (needsFixedHeight()) {
+ // getWidget().clearHeight();
+ // setLayoutHeightListener(true);
+ // getLayoutManager().setNeedsMeasure(AbstractBoxLayoutConnector.this);
+ // } else {
+ // setLayoutHeightListener(false);
+ // }
+ }
+
+ /**
+ * Add/remove necessary ElementResizeListeners for one slot. This should be
+ * called after each update to the slot's or it's widget.
+ */
+ private void updateSlotListeners(ComponentConnector child) {
+ Slot slot = getWidget().getSlot(child);
+
+ // Clear all possible listeners first
+ dontListen(slot.getWidget().getElement(), childComponentResizeListener);
+ if (slot.hasCaption()) {
+ dontListen(slot.getCaptionElement(), slotCaptionResizeListener);
+ }
+ if (slot.hasSpacing()) {
+ dontListen(slot.getSpacingElement(), spacingResizeListener);
+ }
+
+ // Add all necessary listeners
+ if (needsFixedHeight()) {
+ listen(slot.getWidget().getElement(), childComponentResizeListener);
+ if (slot.hasCaption()) {
+ listen(slot.getCaptionElement(), slotCaptionResizeListener);
+ }
+ } else if ((child.isRelativeHeight() || child.isRelativeWidth())
+ && slot.hasCaption()) {
+ // If the slot has caption, we need to listen for it's size changes
+ // in order to update the padding/margin offset for relative sized
+ // components
+ listen(slot.getCaptionElement(), slotCaptionResizeListener);
+ }
+
+ if (needsExpand()) {
+ listen(slot.getWidget().getElement(), childComponentResizeListener);
+ if (slot.hasSpacing()) {
+ listen(slot.getSpacingElement(), spacingResizeListener);
+ }
+ }
+
+ if (child.isRelativeHeight()) {
+ hasRelativeHeight.add(child);
+ needsMeasure.remove(child.getWidget().getElement());
+ } else {
+ hasRelativeHeight.remove(child);
+ needsMeasure.add(child.getWidget().getElement());
+ }
+
+ }
+
+ // public void postLayout() {
+ // if (needsFixedHeight()) {
+ // // Re-measure all elements that are available
+ // for (Element el : needsMeasure) {
+ // childElementHeight.put(el, getLayoutManager()
+ // .getOuterHeight(el));
+ //
+ // Element captionElement = el.getParentElement()
+ // .getFirstChildElement().cast();
+ // if (captionElement.getClassName().contains("v-caption")) {
+ // childCaptionElementHeight.put(el, getLayoutManager()
+ // .getOuterHeight(captionElement));
+ // }
+ // }
+ // // System.out.println(" ### Child sizes: "
+ // // + childElementHeight.values().toString());
+ // // System.out.println(" ### Caption sizes: "
+ // // + childCaptionElementHeight.values().toString());
+ //
+ // int height = getMaxHeight()
+ // + getLayoutManager().getBorderHeight(
+ // getWidget().getElement())
+ // + getLayoutManager().getPaddingHeight(
+ // getWidget().getElement());
+ // getWidget().getElement().getStyle().setHeight(height, Unit.PX);
+ // }
+ // }
+
+ // private ElementResizeListener layoutResizeListener = new
+ // ElementResizeListener() {
+ // public void onElementResize(ElementResizeEvent e) {
+ // updateLayoutHeight();
+ // if (needsExpand() && (isUndefinedHeight() || isUndefinedWidth())) {
+ // updateExpand();
+ // }
+ // }
+ // };
+
+ private ElementResizeListener slotCaptionResizeListener = new ElementResizeListener() {
+ public void onElementResize(ElementResizeEvent e) {
+
+ // Get all needed element references
+ Element captionElement = (Element) e.getElement().cast();
+
+ // Caption position determines if the widget element is the first or
+ // last child inside the caption wrap
+ CaptionPosition pos = getWidget().getCaptionPositionFromElement(
+ (Element) captionElement.getParentElement().cast());
+
+ // The default is the last child
+ Element widgetElement = captionElement.getParentElement()
+ .getLastChild().cast();
+
+ // ...but if caption position is bottom or right, the widget is the
+ // first child
+ if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) {
+ widgetElement = captionElement.getParentElement()
+ .getFirstChildElement().cast();
+ }
+
+ if (captionElement == widgetElement) {
+ // Caption element already detached
+ dontListen(captionElement, slotCaptionResizeListener);
+ childCaptionElementHeight.remove(widgetElement);
+ return;
+ }
+
+ String widgetWidth = widgetElement.getStyle().getWidth();
+ String widgetHeight = widgetElement.getStyle().getHeight();
+
+ if (widgetHeight.endsWith("%")
+ && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
+ getWidget().updateCaptionOffset(captionElement);
+ } else if (widgetWidth.endsWith("%")
+ && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) {
+ getWidget().updateCaptionOffset(captionElement);
+ }
+
+ int h = getLayoutManager().getOuterHeight(captionElement)
+ - getLayoutManager().getMarginHeight(captionElement);
+ childCaptionElementHeight.put(widgetElement, h);
+
+ // if (needsFixedHeight()) {
+ // getWidget().clearHeight();
+ // getLayoutManager().setNeedsMeasure(
+ // AbstractBoxLayoutConnector.this);
+ // }
+
+ updateLayoutHeight();
+
+ if (needsExpand()) {
+ updateExpand();
+ }
+ }
+ };
+
+ private ElementResizeListener childComponentResizeListener = new ElementResizeListener() {
+ public void onElementResize(ElementResizeEvent e) {
+ int h = getLayoutManager().getOuterHeight(e.getElement());
+ childElementHeight.put((Element) e.getElement().cast(), h);
+ updateLayoutHeight();
+
+ if (needsExpand()) {
+ updateExpand();
+ }
+ }
+ };
+
+ private ElementResizeListener spacingResizeListener = new ElementResizeListener() {
+ public void onElementResize(ElementResizeEvent e) {
+ if (needsExpand()) {
+ updateExpand();
+ }
+ }
+ };
+
+ private void updateLayoutHeight() {
+ if (needsFixedHeight() && childElementHeight.size() > 0) {
+ int h = getMaxHeight();
+ h += getLayoutManager().getBorderHeight(getWidget().getElement())
+ + getLayoutManager().getPaddingHeight(
+ getWidget().getElement());
+ getWidget().getElement().getStyle().setHeight(h, Unit.PX);
+ getLayoutManager().setNeedsMeasure(this);
+ }
+ }
+
+ private void updateExpand() {
+ // System.out.println("All sizes: "
+ // + childElementHeight.values().toString() + " - Caption sizes: "
+ // + childCaptionElementHeight.values().toString());
+ getWidget().updateExpand();
+ }
+
+ private int getMaxHeight() {
+ // TODO should use layout manager instead of inner lists of element
+ // sizes
+ int highestNonRelative = -1;
+ int highestRelative = -1;
+ // System.out.println("Child sizes: "
+ // + childElementHeight.values().toString());
+ for (Element el : childElementHeight.keySet()) {
+ // TODO would be more efficient to measure the slot element if both
+ // caption and child widget elements need to be measured. Keeping
+ // track of what to measure is the most difficult part of this
+ // layout.
+ CaptionPosition pos = getWidget().getCaptionPositionFromElement(
+ (Element) el.getParentElement().cast());
+ if (needsMeasure.contains(el)) {
+ int h = childElementHeight.get(el);
+ String sHeight = el.getStyle().getHeight();
+ // Only add the caption size to the height of the slot if
+ // coption position is top or bottom
+ if (childCaptionElementHeight.containsKey(el)
+ && (sHeight == null || !sHeight.endsWith("%"))
+ && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
+ h += childCaptionElementHeight.get(el);
+ }
+ if (h > highestNonRelative) {
+ highestNonRelative = h;
+ }
+ } else {
+ int h = childElementHeight.get(el);
+ if (childCaptionElementHeight.containsKey(el)
+ && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
+ h += childCaptionElementHeight.get(el);
+ }
+ if (h > highestRelative) {
+ highestRelative = h;
+ }
+ }
+ }
+ return highestNonRelative > -1 ? highestNonRelative : highestRelative;
+ }
+
+ @Override
+ public void onUnregister() {
+ // Cleanup all ElementResizeListeners
+
+ // dontListen(getWidget().getElement(), layoutResizeListener);
+
+ for (ComponentConnector child : getChildComponents()) {
+ Slot slot = getWidget().getSlot(child);
+ if (slot.hasCaption()) {
+ dontListen(slot.getCaptionElement(), slotCaptionResizeListener);
+ }
+
+ if (slot.getSpacingElement() != null) {
+ dontListen(slot.getSpacingElement(), spacingResizeListener);
+ }
+
+ dontListen(slot.getWidget().getElement(),
+ childComponentResizeListener);
+ }
+
+ super.onUnregister();
+ }
+
+ // private void setLayoutHeightListener(boolean add) {
+ // if (add) {
+ // listen(getWidget().getElement(), layoutResizeListener);
+ // } else {
+ // dontListen(getWidget().getElement(), layoutResizeListener);
+ // if (!needsExpand()) {
+ // System.out.println("Clearing element sizes");
+ // childElementHeight.clear();
+ // childCaptionElementHeight.clear();
+ // }
+ // }
+ // }
+
+ /*
+ * Convenience methods
+ */
+
+ private void listen(Element el, ElementResizeListener listener) {
+ getLayoutManager().addElementResizeListener(el, listener);
+ }
+
+ private void dontListen(Element el, ElementResizeListener listener) {
+ getLayoutManager().removeElementResizeListener(el, listener);
+ }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java
index 1a504b7a75..4efd2f5c2f 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java
@@ -174,6 +174,10 @@ public abstract class AbstractConnector implements ServerConnector,
}
@Override
+ public void removeStateChangeHandler(StateChangeHandler handler) {
+ ensureHandlerManager().removeHandler(StateChangeEvent.TYPE, handler);
+ }
+
public void onStateChanged(StateChangeEvent stateChangeEvent) {
if (debugLogging) {
VConsole.log("State change event for "
diff --git a/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java
new file mode 100644
index 0000000000..53477571ed
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java
@@ -0,0 +1,18 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle;
+import com.vaadin.ui.HorizontalLayout;
+
+@Connect(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER)
+public class HorizontalBoxLayoutConnector extends AbstractBoxLayoutConnector {
+
+ @Override
+ public void init() {
+ super.init();
+ getWidget().setVertical(false);
+ }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java
new file mode 100644
index 0000000000..c49587c29b
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java
@@ -0,0 +1,752 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.terminal.gwt.client.ComponentConnector;
+import com.vaadin.terminal.gwt.client.LayoutManager;
+
+public class VBoxLayout extends FlowPanel {
+
+ private static final String ALIGN_CLASS_PREFIX = "v-align-";
+
+ protected boolean spacing = false;
+
+ protected boolean vertical = true;
+
+ protected boolean definedHeight = false;
+
+ private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>();
+
+ private LayoutManager layoutManager;
+
+ public VBoxLayout() {
+ setStylePrimaryName("v-boxlayout");
+ setVertical(true);
+ }
+
+ public void setVertical(boolean isVertical) {
+ vertical = isVertical;
+ if (vertical) {
+ addStyleName("v-vertical");
+ removeStyleName("v-horizontal");
+ } else {
+ addStyleName("v-horizontal");
+ removeStyleName("v-vertical");
+ }
+ }
+
+ public void addOrMoveSlot(Slot slot, int index) {
+ if (slot.getParent() == this) {
+ int currentIndex = getWidgetIndex(slot);
+ if (index == currentIndex) {
+ return;
+ }
+ }
+ insert(slot, index);
+ }
+
+ @Override
+ protected void insert(Widget child, Element container, int beforeIndex,
+ boolean domInsert) {
+ // Validate index; adjust if the widget is already a child of this
+ // panel.
+ beforeIndex = adjustIndex(child, beforeIndex);
+
+ // Detach new child.
+ child.removeFromParent();
+
+ // Logical attach.
+ getChildren().insert(child, beforeIndex);
+
+ // Physical attach.
+ container = expandWrapper != null ? expandWrapper : getElement();
+ if (domInsert) {
+ DOM.insertChild(container, child.getElement(),
+ spacing ? beforeIndex * 2 : beforeIndex);
+ } else {
+ DOM.appendChild(container, child.getElement());
+ }
+
+ // Adopt.
+ adopt(child);
+ }
+
+ public Slot removeSlot(Widget widget) {
+ Slot slot = widgetToSlot.get(widget);
+ remove(slot);
+ widgetToSlot.remove(widget);
+ return slot;
+ }
+
+ public Slot getSlot(ComponentConnector connector) {
+ Slot slot = widgetToSlot.get(connector.getWidget());
+ if (slot == null) {
+ slot = new Slot(connector);
+ widgetToSlot.put(connector.getWidget(), slot);
+ }
+ return slot;
+ }
+
+ public enum CaptionPosition {
+ TOP, RIGHT, BOTTOM, LEFT
+ }
+
+ protected class Slot extends SimplePanel {
+
+ private ComponentConnector connector;
+
+ private Element spacer;
+
+ private Element captionWrap;
+ private Element caption;
+ private Element captionText;
+ private Icon icon;
+ private Element errorIcon;
+ private Element requiredIcon;
+
+ // Caption is placed after component unless there is some part which
+ // moves it above.
+ private CaptionPosition captionPosition = CaptionPosition.RIGHT;
+
+ private AlignmentInfo alignment;
+ private double expandRatio = -1;
+
+ public Slot(ComponentConnector connector) {
+ this.connector = connector;
+ setWidget(connector.getWidget());
+ setStylePrimaryName("v-slot");
+ }
+
+ public AlignmentInfo getAlignment() {
+ return alignment;
+ }
+
+ public void setAlignment(AlignmentInfo alignment) {
+ this.alignment = alignment;
+
+ if (alignment.isHorizontalCenter()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "center");
+ removeStyleName(ALIGN_CLASS_PREFIX + "right");
+ } else if (alignment.isRight()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "right");
+ removeStyleName(ALIGN_CLASS_PREFIX + "center");
+ } else {
+ removeStyleName(ALIGN_CLASS_PREFIX + "right");
+ removeStyleName(ALIGN_CLASS_PREFIX + "center");
+ }
+ if (alignment.isVerticalCenter()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "middle");
+ removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
+ } else if (alignment.isBottom()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "bottom");
+ removeStyleName(ALIGN_CLASS_PREFIX + "middle");
+ } else {
+ removeStyleName(ALIGN_CLASS_PREFIX + "middle");
+ removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
+ }
+ }
+
+ public void setExpandRatio(double expandRatio) {
+ this.expandRatio = expandRatio;
+ }
+
+ public double getExpandRatio() {
+ return expandRatio;
+ }
+
+ public void setSpacing(boolean spacing) {
+ if (spacing && spacer == null) {
+ spacer = DOM.createDiv();
+ spacer.addClassName("v-spacing");
+ getElement().getParentElement().insertBefore(spacer,
+ getElement());
+ } else if (!spacing && spacer != null) {
+ spacer.removeFromParent();
+ spacer = null;
+ }
+ }
+
+ public Element getSpacingElement() {
+ return spacer;
+ }
+
+ public boolean hasSpacing() {
+ return getSpacingElement() != null;
+ }
+
+ protected int getSpacingSize(boolean vertical) {
+ if (spacer == null) {
+ return 0;
+ }
+
+ if (layoutManager != null) {
+ if (vertical) {
+ return layoutManager.getOuterHeight(spacer);
+ } else {
+ return layoutManager.getOuterWidth(spacer);
+ }
+ }
+ // TODO place for optimization (in expense of theme
+ // flexibility): only measure one of the elements and cache the
+ // value
+ return vertical ? spacer.getOffsetHeight() : spacer
+ .getOffsetWidth();
+ // }
+ }
+
+ public void setCaptionPosition(CaptionPosition captionPosition) {
+ if (caption == null) {
+ return;
+ }
+
+ captionWrap.removeClassName("v-caption-on-"
+ + this.captionPosition.name().toLowerCase());
+
+ this.captionPosition = captionPosition;
+ if (captionPosition == CaptionPosition.BOTTOM
+ || captionPosition == CaptionPosition.RIGHT) {
+ captionWrap.appendChild(caption);
+ } else {
+ captionWrap.insertFirst(caption);
+ }
+
+ captionWrap.addClassName("v-caption-on-"
+ + captionPosition.name().toLowerCase());
+ }
+
+ public CaptionPosition getCaptionPosition() {
+ return captionPosition;
+ }
+
+ // TODO refactor VCaption and use that instead: creates a tight coupling
+ // between this layout and Vaadin, but it's already coupled
+ public void setCaption(String captionText, String iconUrl,
+ List<String> styles, String error, boolean showError,
+ boolean required, boolean enabled) {
+
+ // TODO place for optimization: check if any of these have changed
+ // since last time, and only run those changes
+
+ // Caption wrappers
+ if (captionText != null || iconUrl != null || error != null
+ || required) {
+ if (caption == null) {
+ caption = DOM.createDiv();
+ captionWrap = DOM.createDiv();
+ captionWrap.addClassName("v");
+ captionWrap.addClassName("v-has-caption");
+ getElement().appendChild(captionWrap);
+ captionWrap.appendChild(getWidget().getElement());
+ }
+ } else if (caption != null) {
+ getElement().appendChild(getWidget().getElement());
+ captionWrap.removeFromParent();
+ caption = null;
+ captionWrap = null;
+ }
+
+ // Caption text
+ if (captionText != null) {
+ if (this.captionText == null) {
+ this.captionText = DOM.createSpan();
+ this.captionText.addClassName("v-captiontext");
+ caption.appendChild(this.captionText);
+ }
+ if (captionText.trim().equals("")) {
+ this.captionText.setInnerHTML("&nbsp;");
+ } else {
+ this.captionText.setInnerText(captionText);
+ }
+ } else if (this.captionText != null) {
+ this.captionText.removeFromParent();
+ this.captionText = null;
+ }
+
+ // Icon
+ if (iconUrl != null) {
+ if (icon == null) {
+ icon = new Icon();
+ // icon = DOM.createImg();
+ // icon.setClassName("v-icon");
+ caption.insertFirst(icon.getElement());
+ }
+ // icon.setAttribute("src", iconUrl);
+ icon.setUri(iconUrl);
+ } else if (icon != null) {
+ icon.getElement().removeFromParent();
+ icon = null;
+ }
+
+ // Required
+ if (required) {
+ if (requiredIcon == null) {
+ requiredIcon = DOM.createSpan();
+ // TODO decide something better
+ requiredIcon.setInnerHTML("*");
+ requiredIcon.setClassName("v-required-field-indicator");
+ }
+ caption.appendChild(requiredIcon);
+ } else if (requiredIcon != null) {
+ requiredIcon.removeFromParent();
+ requiredIcon = null;
+ }
+
+ // Error
+ if (error != null && showError) {
+ if (errorIcon == null) {
+ errorIcon = DOM.createSpan();
+ errorIcon.setClassName("v-errorindicator");
+ }
+ caption.appendChild(errorIcon);
+ } else if (errorIcon != null) {
+ errorIcon.removeFromParent();
+ errorIcon = null;
+ }
+
+ if (caption != null) {
+ // Styles
+ caption.setClassName("v-caption");
+
+ if (styles != null) {
+ for (String style : styles) {
+ caption.addClassName("v-caption-" + style);
+ }
+ }
+
+ if (enabled) {
+ caption.removeClassName("v-disabled");
+ } else {
+ caption.addClassName("v-disabled");
+ }
+
+ // Caption position
+ if (captionText != null || iconUrl != null) {
+ setCaptionPosition(CaptionPosition.TOP);
+ } else {
+ setCaptionPosition(CaptionPosition.RIGHT);
+ }
+ }
+
+ // TODO theme flexibility: add extra styles to captionWrap as well?
+
+ }
+
+ public boolean hasCaption() {
+ return caption != null;
+ }
+
+ public Element getCaptionElement() {
+ return caption;
+ }
+
+ public void setRelativeWidth(boolean relativeWidth) {
+ updateRelativeSize(relativeWidth, "width");
+ }
+
+ public void setRelativeHeight(boolean relativeHeight) {
+ updateRelativeSize(relativeHeight, "height");
+ }
+
+ private void updateRelativeSize(boolean isRelativeSize, String direction) {
+ if (isRelativeSize && hasCaption()) {
+ captionWrap.getStyle().setProperty(
+ direction,
+ getWidget().getElement().getStyle()
+ .getProperty(direction));
+ captionWrap.addClassName("v-has-" + direction);
+ } else if (hasCaption()) {
+ if (direction.equals("height")) {
+ captionWrap.getStyle().clearHeight();
+ } else {
+ captionWrap.getStyle().clearWidth();
+ }
+ captionWrap.removeClassName("v-has-" + direction);
+ captionWrap.getStyle().clearPaddingTop();
+ captionWrap.getStyle().clearPaddingRight();
+ captionWrap.getStyle().clearPaddingBottom();
+ captionWrap.getStyle().clearPaddingLeft();
+ caption.getStyle().clearMarginTop();
+ caption.getStyle().clearMarginRight();
+ caption.getStyle().clearMarginBottom();
+ caption.getStyle().clearMarginLeft();
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (DOM.eventGetType(event) == Event.ONLOAD
+ && icon.getElement() == DOM.eventGetTarget(event)) {
+ if (layoutManager != null) {
+ layoutManager.layoutLater();
+ } else {
+ updateCaptionOffset(caption);
+ }
+ }
+ }
+
+ @Override
+ protected Element getContainerElement() {
+ if (captionWrap == null) {
+ return getElement();
+ } else {
+ return captionWrap;
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ if (spacer != null) {
+ spacer.removeFromParent();
+ }
+ super.onDetach();
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ if (spacer != null) {
+ getElement().getParentElement().insertBefore(spacer,
+ getElement());
+ }
+ }
+
+ }
+
+ protected class Icon extends UIObject {
+ public static final String CLASSNAME = "v-icon";
+ private String myUrl;
+
+ public Icon() {
+ setElement(DOM.createImg());
+ DOM.setElementProperty(getElement(), "alt", "");
+ setStyleName(CLASSNAME);
+ }
+
+ public void setUri(String url) {
+ if (!url.equals(myUrl)) {
+ /*
+ * Start sinking onload events, widgets responsibility to react.
+ * We must do this BEFORE we set src as IE fires the event
+ * immediately if the image is found in cache (#2592).
+ */
+ sinkEvents(Event.ONLOAD);
+
+ DOM.setElementProperty(getElement(), "src", url);
+ myUrl = url;
+ }
+ }
+
+ }
+
+ void setLayoutManager(LayoutManager manager) {
+ layoutManager = manager;
+ }
+
+ private static final RegExp captionPositionRegexp = RegExp
+ .compile("v-caption-on-(\\S+)");
+
+ CaptionPosition getCaptionPositionFromElement(Element captionWrap) {
+ // Get caption position from the classname
+ MatchResult matcher = captionPositionRegexp.exec(captionWrap
+ .getClassName());
+ if (matcher == null || matcher.getGroupCount() < 2) {
+ return CaptionPosition.TOP;
+ }
+ String captionClass = matcher.getGroup(1);
+ CaptionPosition captionPosition = CaptionPosition.valueOf(
+ CaptionPosition.class, captionClass.toUpperCase());
+ return captionPosition;
+ }
+
+ void updateCaptionOffset(Element caption) {
+
+ Element captionWrap = caption.getParentElement().cast();
+
+ Style captionWrapStyle = captionWrap.getStyle();
+ captionWrapStyle.clearPaddingTop();
+ captionWrapStyle.clearPaddingRight();
+ captionWrapStyle.clearPaddingBottom();
+ captionWrapStyle.clearPaddingLeft();
+
+ Style captionStyle = caption.getStyle();
+ captionStyle.clearMarginTop();
+ captionStyle.clearMarginRight();
+ captionStyle.clearMarginBottom();
+ captionStyle.clearMarginLeft();
+
+ // Get caption position from the classname
+ CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap);
+
+ if (captionPosition == CaptionPosition.LEFT
+ || captionPosition == CaptionPosition.RIGHT) {
+ int captionWidth;
+ if (layoutManager != null) {
+ captionWidth = layoutManager.getOuterWidth(caption)
+ - layoutManager.getMarginWidth(caption);
+ } else {
+ captionWidth = caption.getOffsetWidth();
+ }
+ if (captionWidth > 0) {
+ if (captionPosition == CaptionPosition.LEFT) {
+ captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX);
+ captionStyle.setMarginLeft(-captionWidth, Unit.PX);
+ } else {
+ captionWrapStyle.setPaddingRight(captionWidth, Unit.PX);
+ captionStyle.setMarginRight(-captionWidth, Unit.PX);
+ }
+ }
+ }
+ if (captionPosition == CaptionPosition.TOP
+ || captionPosition == CaptionPosition.BOTTOM) {
+ int captionHeight;
+ if (layoutManager != null) {
+ captionHeight = layoutManager.getOuterHeight(caption)
+ - layoutManager.getMarginHeight(caption);
+ } else {
+ captionHeight = caption.getOffsetHeight();
+ }
+ if (captionHeight > 0) {
+ if (captionPosition == CaptionPosition.TOP) {
+ captionWrapStyle.setPaddingTop(captionHeight, Unit.PX);
+ captionStyle.setMarginTop(-captionHeight, Unit.PX);
+ } else {
+ captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX);
+ captionStyle.setMarginBottom(-captionHeight, Unit.PX);
+ }
+ }
+ }
+ }
+
+ private void toggleStyleName(String name, boolean enabled) {
+ if (enabled) {
+ addStyleName(name);
+ } else {
+ removeStyleName(name);
+ }
+ }
+
+ void setMargin(VMarginInfo marginInfo) {
+ toggleStyleName("v-margin-top", marginInfo.hasTop());
+ toggleStyleName("v-margin-right", marginInfo.hasRight());
+ toggleStyleName("v-margin-bottom", marginInfo.hasBottom());
+ toggleStyleName("v-margin-left", marginInfo.hasLeft());
+ }
+
+ protected void setSpacing(boolean spacingEnabled) {
+ spacing = spacingEnabled;
+ for (Slot slot : widgetToSlot.values()) {
+ if (getWidgetIndex(slot) > 0) {
+ slot.setSpacing(spacingEnabled);
+ }
+ }
+ }
+
+ private void recalculateExpands() {
+ double total = 0;
+ for (Slot slot : widgetToSlot.values()) {
+ if (slot.getExpandRatio() > -1) {
+ total += slot.getExpandRatio();
+ } else {
+ if (vertical) {
+ slot.getElement().getStyle().clearHeight();
+ } else {
+ slot.getElement().getStyle().clearWidth();
+ }
+ }
+ }
+ for (Slot slot : widgetToSlot.values()) {
+ if (slot.getExpandRatio() > -1) {
+ if (vertical) {
+ slot.setHeight((100 * (slot.getExpandRatio() / total))
+ + "%");
+ if (slot.connector.isRelativeHeight()) {
+ layoutManager.setNeedsMeasure(slot.connector);
+ }
+ } else {
+ slot.setWidth((100 * (slot.getExpandRatio() / total)) + "%");
+ if (slot.connector.isRelativeWidth()) {
+ layoutManager.setNeedsMeasure(slot.connector);
+ }
+ }
+ }
+ }
+ }
+
+ private Element expandWrapper;
+
+ void clearExpand() {
+ if (expandWrapper != null) {
+ for (; expandWrapper.getChildCount() > 0;) {
+ Element el = expandWrapper.getChild(0).cast();
+ getElement().appendChild(el);
+ if (vertical) {
+ el.getStyle().clearHeight();
+ el.getStyle().clearMarginTop();
+ } else {
+ el.getStyle().clearWidth();
+ el.getStyle().clearMarginLeft();
+ }
+ }
+ expandWrapper.removeFromParent();
+ expandWrapper = null;
+ }
+ }
+
+ public void updateExpand() {
+ boolean isExpanding = false;
+ for (Widget slot : getChildren()) {
+ if (((Slot) slot).getExpandRatio() > -1) {
+ isExpanding = true;
+ } else {
+ if (vertical) {
+ slot.getElement().getStyle().clearHeight();
+ } else {
+ slot.getElement().getStyle().clearWidth();
+ }
+ }
+ slot.getElement().getStyle().clearMarginLeft();
+ slot.getElement().getStyle().clearMarginTop();
+ }
+
+ if (isExpanding) {
+ if (expandWrapper == null) {
+ expandWrapper = DOM.createDiv();
+ expandWrapper.setClassName("v-expand");
+ for (; getElement().getChildCount() > 0;) {
+ Node el = getElement().getChild(0);
+ expandWrapper.appendChild(el);
+ }
+ getElement().appendChild(expandWrapper);
+ }
+
+ int totalSize = 0;
+ for (Widget w : getChildren()) {
+ Slot slot = (Slot) w;
+ if (slot.getExpandRatio() == -1) {
+ if (layoutManager != null) {
+ // TODO check caption position
+ if (vertical) {
+ int size = layoutManager.getOuterHeight(slot
+ .getWidget().getElement())
+ - layoutManager.getMarginHeight(slot
+ .getWidget().getElement());
+ if (slot.hasCaption()) {
+ size += layoutManager.getOuterHeight(slot
+ .getCaptionElement())
+ - layoutManager.getMarginHeight(slot
+ .getCaptionElement());
+ }
+ if (size > 0) {
+ totalSize += size;
+ }
+ } else {
+ int max = -1;
+ max = layoutManager.getOuterWidth(slot.getWidget()
+ .getElement())
+ - layoutManager.getMarginWidth(slot
+ .getWidget().getElement());
+ if (slot.hasCaption()) {
+ int max2 = layoutManager.getOuterWidth(slot
+ .getCaptionElement())
+ - layoutManager.getMarginWidth(slot
+ .getCaptionElement());
+ max = Math.max(max, max2);
+ }
+ if (max > 0) {
+ totalSize += max;
+ }
+ }
+ } else {
+ totalSize += vertical ? slot.getOffsetHeight() : slot
+ .getOffsetWidth();
+ }
+ }
+ // TODO fails in Opera, always returns 0
+ int spacingSize = slot.getSpacingSize(vertical);
+ if (spacingSize > 0) {
+ totalSize += spacingSize;
+ }
+ }
+
+ // When we set the margin to the first child, we don't need
+ // overflow:hidden in the layout root element, since the wrapper
+ // would otherwise be placed outside of the layout root element
+ // and block events on elements below it.
+ if (vertical) {
+ expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX);
+ expandWrapper.getFirstChildElement().getStyle()
+ .setMarginTop(-totalSize, Unit.PX);
+ } else {
+ expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX);
+ expandWrapper.getFirstChildElement().getStyle()
+ .setMarginLeft(-totalSize, Unit.PX);
+ }
+
+ recalculateExpands();
+ }
+ }
+
+ public void recalculateLayoutHeight() {
+ // Only needed if a horizontal layout is undefined high, and contains
+ // relative height children or vertical alignments
+ if (vertical || definedHeight) {
+ return;
+ }
+
+ boolean hasRelativeHeightChildren = false;
+ boolean hasVAlign = false;
+
+ for (Widget slot : getChildren()) {
+ Widget widget = ((Slot) slot).getWidget();
+ String h = widget.getElement().getStyle().getHeight();
+ if (h != null && h.indexOf("%") > -1) {
+ hasRelativeHeightChildren = true;
+ }
+ AlignmentInfo a = ((Slot) slot).getAlignment();
+ if (a != null && (a.isVerticalCenter() || a.isBottom())) {
+ hasVAlign = true;
+ }
+ }
+
+ if (hasRelativeHeightChildren || hasVAlign) {
+ int newHeight;
+ if (layoutManager != null) {
+ newHeight = layoutManager.getOuterHeight(getElement())
+ - layoutManager.getMarginHeight(getElement());
+ } else {
+ newHeight = getElement().getOffsetHeight();
+ }
+ VBoxLayout.this.getElement().getStyle()
+ .setHeight(newHeight, Unit.PX);
+ }
+
+ }
+
+ void clearHeight() {
+ getElement().getStyle().clearHeight();
+ }
+
+ @Override
+ public void setHeight(String height) {
+ super.setHeight(height);
+ definedHeight = (height != null && !"".equals(height));
+ }
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java
new file mode 100644
index 0000000000..9953b02b2d
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java
@@ -0,0 +1,18 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle;
+import com.vaadin.ui.VerticalLayout;
+
+@Connect(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER)
+public class VerticalBoxLayoutConnector extends AbstractBoxLayoutConnector {
+
+ @Override
+ public void init() {
+ super.init();
+ getWidget().setVertical(true);
+ }
+
+}