diff options
Diffstat (limited to 'src/com/vaadin/terminal/gwt')
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(" "); + } 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); + } + +} |