diff options
Diffstat (limited to 'src')
11 files changed, 1580 insertions, 26 deletions
diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml index 74dc78d9b8..3487a655e5 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -16,7 +16,15 @@ name="com.vaadin.terminal.gwt.DefaultWidgetSetBrowserSpecificOverrides" /> <source path="client" /> - + + <!-- TODO only for development --> + <replace-with class="com.vaadin.terminal.gwt.client.ui.VerticalBoxLayoutConnector"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.orderedlayout.VerticalLayoutConnector" /> + </replace-with> + <replace-with class="com.vaadin.terminal.gwt.client.ui.HorizontalBoxLayoutConnector"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.orderedlayout.HorizontalLayoutConnector" /> + </replace-with> + <!-- Use own Scheduler implementation to be able to track if commands are running --> <replace-with class="com.vaadin.terminal.gwt.client.VSchedulerImpl"> diff --git a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java index d847d49e6f..a4df6c6cc4 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java @@ -13,6 +13,7 @@ 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.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; @@ -483,6 +484,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 9390c719fc..0a2e90f2f2 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManager.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java @@ -138,7 +138,21 @@ public class LayoutManager { } }-*/; - private static native final MeasuredSize getMeasuredSize(Element element, + /** + * Get the measured size of the given element. If no size is set, use the + * default size instead. + * + * Method defined as protected to allow separate implementation for IE8 + * (performance reason: storing any data in the DOM causes a reflow). + * + * @param element + * the dom element whose measured size to get + * @param defaultSize + * a fallback size if the element doesn't have a measured size + * stored + * @return + */ + protected native MeasuredSize getMeasuredSize(Element element, MeasuredSize defaultSize) /*-{ return element.vMeasuredSize || defaultSize; @@ -171,7 +185,7 @@ public class LayoutManager { return; } measuredSize.removeDependent(owner.getConnectorId()); - stopMeasuringIfUnecessary(element); + stopMeasuringIfUnnecessary(element); } public boolean isLayoutRunning() { @@ -193,7 +207,7 @@ public class LayoutManager { } } - private void layoutLater() { + public void layoutLater() { if (!layoutPending) { layoutPending = true; layoutTimer.schedule(100); @@ -256,6 +270,9 @@ public class LayoutManager { VConsole.log(" Measured " + measuredConnectorCount + " elements in " + measureTime + " ms"); + VConsole.log(" Total of " + measureCount + + " measurement operations"); + if (!listenersToFire.isEmpty()) { for (Element element : listenersToFire) { Collection<ElementResizeListener> listeners = elementResizeListeners @@ -478,7 +495,7 @@ public class LayoutManager { ComponentConnector[] connectors = ConnectorMap.get(connection) .getComponentConnectors(); for (ComponentConnector connector : connectors) { - measueConnector(connector); + measureConnector(connector); } for (ComponentConnector connector : connectors) { layoutDependencyTree.setNeedsMeasure(connector, false); @@ -490,7 +507,7 @@ public class LayoutManager { Collection<ComponentConnector> measureTargets = layoutDependencyTree .getMeasureTargets(); for (ComponentConnector connector : measureTargets) { - measueConnector(connector); + measureConnector(connector); measureCount++; } for (ComponentConnector connector : measureTargets) { @@ -500,9 +517,16 @@ public class LayoutManager { return measureCount; } - private void measueConnector(ComponentConnector connector) { - Element element = connector.getWidget().getElement(); + private void measureConnector(ComponentConnector connector) { MeasuredSize measuredSize = getMeasuredSize(connector); + if (!isManagedLayout(connector) + && !isManagedLayout(connector.getParent()) + && elementResizeListeners.get(connector.getWidget() + .getElement()) == null && !measuredSize.hasDependents()) { + return; + } + + Element element = connector.getWidget().getElement(); MeasureResult measureResult = measuredAndUpdate(element, measuredSize); if (measureResult.isChanged()) { @@ -542,8 +566,11 @@ public class LayoutManager { + " non connector elements"); } + int measureCount = 0; + private MeasureResult measuredAndUpdate(Element element, MeasuredSize measuredSize) { + measureCount++; MeasureResult measureResult = measuredSize.measure(element); if (measureResult.isChanged()) { notifyListenersAndDepdendents(element, @@ -1002,6 +1029,14 @@ public class LayoutManager { return getMeasuredSize(element, nullSize).getMarginLeft(); } + public int getMarginWidth(Element element) { + return getMeasuredSize(element, nullSize).getMarginWidth(); + } + + public int getMarginHeight(Element element) { + return getMeasuredSize(element, nullSize).getMarginHeight(); + } + /** * Registers the outer height (including margins, borders and paddings) of a * component. This can be used as an optimization by ManagedLayouts; by @@ -1154,12 +1189,12 @@ public class LayoutManager { listeners.remove(listener); if (listeners.isEmpty()) { elementResizeListeners.remove(element); - stopMeasuringIfUnecessary(element); + stopMeasuringIfUnnecessary(element); } } } - private void stopMeasuringIfUnecessary(Element element) { + private void stopMeasuringIfUnnecessary(Element element) { if (!needsMeasure(element)) { measuredNonConnectorElements.remove(element); setMeasuredSize(element, null); @@ -1188,4 +1223,5 @@ public class LayoutManager { public void setEverythingNeedsMeasure() { everythingNeedsMeasure = true; } + } diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java index 2b677985b5..fc55007969 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java @@ -3,13 +3,26 @@ */ package com.vaadin.terminal.gwt.client; +import java.util.HashMap; + import com.google.gwt.dom.client.Element; public class LayoutManagerIE8 extends LayoutManager { + protected HashMap<Element, MeasuredSize> sizes = new HashMap<Element, MeasuredSize>(); + @Override - protected native void setMeasuredSize(Element element, - MeasuredSize measuredSize) + protected void setMeasuredSize(Element element, MeasuredSize measuredSize) { + if (measuredSize != null) { + sizes.put(element, measuredSize); + } else { + sizes.remove(element); + } + } + + // @Override + // protected native void setMeasuredSize(Element element, + // MeasuredSize measuredSize) // IE8 cannot do delete element.vMeasuredSize, at least in the case when // element is not attached to the document (e.g. when a caption is removed) /*-{ @@ -18,6 +31,16 @@ public class LayoutManagerIE8 extends LayoutManager { } else { element.vMeasuredSize = undefined; } - }-*/; + // }-*/; + + @Override + protected MeasuredSize getMeasuredSize(Element element, + MeasuredSize defaultSize) { + MeasuredSize size = sizes.get(element); + if (size != null) { + return size; + } + return defaultSize; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ServerConnector.java b/src/com/vaadin/terminal/gwt/client/ServerConnector.java index 44f65fc6a7..02b894e831 100644 --- a/src/com/vaadin/terminal/gwt/client/ServerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ServerConnector.java @@ -73,6 +73,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..26ec96003e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java @@ -0,0 +1,659 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ValueMap; +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 Paintable, /* PreLayoutListener, */ +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 + protected Widget createWidget() { + return GWT.create(VBoxLayout.class); + } + + @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>(); + + // For debugging + private static int resizeCount = 0; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + clickEventHandler.handleEventHandlerRegistration(); + + VBoxLayout layout = getWidget(); + + ValueMap expandRatios = uidl.getMapAttribute("expandRatios"); + ValueMap alignments = uidl.getMapAttribute("alignments"); + + for (ComponentConnector child : getChildren()) { + Slot slot = layout.getSlot(child); + String pid = child.getConnectorId(); + + AlignmentInfo alignment; + if (alignments.containsKey(pid)) { + alignment = new AlignmentInfo(alignments.getInt(pid)); + if (alignment.isVerticalCenter() || alignment.isBottom()) { + hasVerticalAlignment.add(child); + } else { + hasVerticalAlignment.remove(child); + } + } else { + alignment = AlignmentInfo.TOP_LEFT; + hasVerticalAlignment.remove(child); + } + slot.setAlignment(alignment); + + double expandRatio; + // TODO discuss the layout specs, is this what we want: distribute + // extra space equally if no expand ratios are specified inside a + // layout with specified size + if (expandRatios.getKeySet().size() == 0 + && ((getWidget().vertical && !isUndefinedHeight()) || (!getWidget().vertical && !isUndefinedWidth()))) { + expandRatio = 1; + hasExpandRatio.add(child); + } else if (expandRatios.containsKey(pid) + && expandRatios.getRawNumber(pid) > 0) { + expandRatio = expandRatios.getRawNumber(pid); + hasExpandRatio.add(child); + } else { + expandRatio = -1; + hasExpandRatio.remove(child); + if (expandRatios.getKeySet().size() > 0 + || hasExpandRatio.size() > 0) { + // Only add resize listeners if there are expand ratios in + // use + getLayoutManager().addElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + if (slot.hasCaption()) { + getLayoutManager().addElementResizeListener( + slot.getCaptionElement(), + slotCaptionResizeListener); + } + } + } + slot.setExpandRatio(expandRatio); + + // TODO only needed if expand ratios are used + if (slot.getSpacingElement() != null) { + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } else if (slot.getSpacingElement() != null) { + getLayoutManager().removeElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + + } + + if (needsFixedHeight()) { + for (ComponentConnector child : getChildren()) { + Slot slot = layout.getSlot(child); + getLayoutManager().addElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + if (slot.hasCaption()) { + getLayoutManager() + .addElementResizeListener(slot.getCaptionElement(), + slotCaptionResizeListener); + } + } + } + + if (needsExpand()) { + updateExpand(); + } else { + getWidget().clearExpand(); + } + + } + + 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(); + // TODO Description is handled from somewhere else? + + 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 { + // getLayoutManager().removeElementResizeListener( + // slot.getCaptionElement(), slotCaptionResizeListener); + } + + if (!slot.hasCaption()) { + childCaptionElementHeight.remove(child.getWidget().getElement()); + } + + if (needsFixedHeight()) { + getWidget().clearHeight(); + getLayoutManager().setNeedsMeasure(this); + } + + 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 : getChildren()) { + Widget childWidget = child.getWidget(); + 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 (needsExpand()) { + getWidget().updateExpand(); + } else { + getWidget().clearExpand(); + } + + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().setMargin(new VMarginInfo(getState().getMarginsBitmask())); + getWidget().setSpacing(getState().isSpacing()); + + if (needsFixedHeight()) { + getWidget().clearHeight(); + setLayoutHeightListener(true); + getLayoutManager().setNeedsMeasure(this); + } else { + setLayoutHeightListener(false); + } + + // TODO recognize special cases here, using child states + } + + 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()); + } + } + + // TODO 'needsExpand' might return false during the first render, + // since updateFromUidl is called last + + // 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 + if ((child.isRelativeHeight() || needsFixedHeight()) + && slot.hasCaption()) { + getLayoutManager().addElementResizeListener( + slot.getCaptionElement(), slotCaptionResizeListener); + } else if (!needsExpand()) { + // TODO recheck if removing the listener here breaks anything. + // Should be cleaned up. + // getLayoutManager().removeElementResizeListener( + // slot.getCaptionElement(), slotCaptionResizeListener); + } + + if (slot.getSpacingElement() != null && needsExpand()) { + // Spacing is on + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } else if (slot.getSpacingElement() != null) { + getLayoutManager().removeElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + + if (child.isRelativeHeight()) { + hasRelativeHeight.add(child); + needsMeasure.remove(child.getWidget().getElement()); + } else { + hasRelativeHeight.remove(child); + needsMeasure.add(child.getWidget().getElement()); + } + + if (needsFixedHeight()) { + getLayoutManager().addElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + } else if (!needsExpand()) { + getLayoutManager().removeElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + } + + if (needsFixedHeight()) { + getWidget().clearHeight(); + setLayoutHeightListener(true); + getLayoutManager().setNeedsMeasure( + AbstractBoxLayoutConnector.this); + } else { + setLayoutHeightListener(false); + } + + } + }; + + private boolean needsFixedHeight() { + if (!getWidget().vertical + && isUndefinedHeight() + && (hasRelativeHeight.size() > 0 || hasVerticalAlignment.size() > 0)) { + return true; + } + return false; + } + + private boolean needsExpand() { + boolean canApplyExpand = (getWidget().vertical && !isUndefinedHeight()) + || (!getWidget().vertical && !isUndefinedWidth()); + return hasExpandRatio.size() > 0 && canApplyExpand; + } + + // public void preLayout() { + // resizeCount = 0; + // } + + 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()); + + // If no height has been set, use the natural height for the + // component (this is mostly just a precaution so that something + // renders correctly) + // String h = getWidget().getElement().getStyle().getHeight(); + // if (h == null || h.equals("")) { + // int height = getLayoutManager().getOuterHeight( + // getWidget().getElement()) + // - getLayoutManager().getMarginHeight( + // getWidget().getElement()); + int height = getMaxHeight() + + getLayoutManager().getBorderHeight( + getWidget().getElement()) + + getLayoutManager().getPaddingHeight( + getWidget().getElement()); + getWidget().getElement().getStyle().setHeight(height, Unit.PX); + // } + } + // System.err.println("Element resize listeners fired for " + + // resizeCount + // + " times"); + } + + private ElementResizeListener layoutResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + updateLayoutHeight(); + if (needsExpand() && (isUndefinedHeight() || isUndefinedWidth())) { + updateExpand(); + } + } + }; + + private ElementResizeListener slotCaptionResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + + Element captionElement = (Element) e.getElement().cast(); + + CaptionPosition pos = getWidget().getCaptionPositionFromElement( + (Element) captionElement.getParentElement().cast()); + + Element widgetElement = captionElement.getParentElement() + .getLastChild().cast(); + if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) { + widgetElement = captionElement.getParentElement() + .getFirstChildElement().cast(); + } + + if (captionElement == widgetElement) { + // Caption element already detached + getLayoutManager().removeElementResizeListener(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); + // System.out.println("Caption size: " + h); + + if (needsFixedHeight()) { + getWidget().clearHeight(); + getLayoutManager().setNeedsMeasure( + AbstractBoxLayoutConnector.this); + } + + updateLayoutHeight(); + + if (needsExpand()) { + updateExpand(); + } + } + }; + + private ElementResizeListener childComponentResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + 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) { + resizeCount++; + 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() { + int highestNonRelative = -1; + int highestRelative = -1; + 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 + + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), layoutResizeListener); + + for (int i = 0; i < getWidget().getWidgetCount(); i++) { + // TODO unsafe + Slot slot = (Slot) getWidget().getWidget(i); + + if (slot.hasCaption()) { + getLayoutManager().removeElementResizeListener( + slot.getCaptionElement(), slotCaptionResizeListener); + } + + if (slot.getSpacingElement() != null) { + getLayoutManager().removeElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + + getLayoutManager() + .removeElementResizeListener(slot.getWidget().getElement(), + childComponentResizeListener); + + } + + super.onUnregister(); + } + + private void setLayoutHeightListener(boolean add) { + if (add) { + getLayoutManager().addElementResizeListener( + getWidget().getElement(), layoutResizeListener); + } else { + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), layoutResizeListener); + if (!needsExpand()) { + childElementHeight.clear(); + childCaptionElementHeight.clear(); + } + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java index dc960f90a5..54812a7a42 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java @@ -166,6 +166,10 @@ public abstract class AbstractConnector implements ServerConnector, .addHandler(StateChangeEvent.TYPE, handler); } + 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..dd15528cf7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +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); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // TODO remove when Vaadin style name handling is improved so that it + // won't override extra client side style names + getWidget().setVertical(false); + super.updateFromUIDL(uidl, client); + 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..3a6beaa48a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java @@ -0,0 +1,748 @@ +/* +@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; + } + + 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-connector"); + 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(" "); + } 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..7e10680afb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +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); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // TODO fix when Vaadin style name handling is improved so that it won't + // override extra client side style names + getWidget().setVertical(true); + super.updateFromUIDL(uidl, client); + getWidget().setVertical(true); + } + +} |