From: Jouni Koivuviita Date: Fri, 17 Aug 2012 10:37:10 +0000 (+0300) Subject: Moved BoxLayout to new package X-Git-Tag: 7.0.0.beta1~227 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=5c5a1ce24031f6dbb82bbc76fb24d5363aff38cc;p=vaadin-framework.git Moved BoxLayout to new package --- diff --git a/client/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/client/src/com/vaadin/terminal/gwt/client/ComponentLocator.java index f30c731562..e515c2626c 100644 --- a/client/src/com/vaadin/terminal/gwt/client/ComponentLocator.java +++ b/client/src/com/vaadin/terminal/gwt/client/ComponentLocator.java @@ -28,8 +28,8 @@ 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.VBoxLayout; import com.vaadin.terminal.gwt.client.ui.orderedlayout.VMeasuringOrderedLayout; import com.vaadin.terminal.gwt.client.ui.root.VRoot; import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheetPanel; diff --git a/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractBoxLayoutConnector.java b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractBoxLayoutConnector.java new file mode 100644 index 0000000000..bbf9aed570 --- /dev/null +++ b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractBoxLayoutConnector.java @@ -0,0 +1,598 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +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.shared.AbstractFieldState; +import com.vaadin.shared.ui.AlignmentInfo; +import com.vaadin.shared.ui.LayoutClickRpc; +import com.vaadin.shared.ui.VMarginInfo; +import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutServerRpc; +import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState; +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.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +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.VBoxLayout.CaptionPosition; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.VBoxLayout.Slot; + +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() { + super.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 hasVerticalAlignment = new HashSet(); + + /** + * For bookkeeping. Used to determine if extra calculations are needed for + * horizontal layout. + */ + private HashSet hasRelativeHeight = new HashSet(); + + /** + * For bookkeeping. Used to determine if extra calculations are needed for + * horizontal layout. + */ + private HashSet hasExpandRatio = new HashSet(); + + /** + * For bookkeeping. Used in extra calculations for horizontal layout. + */ + private HashSet needsMeasure = new HashSet(); + + /** + * For bookkeeping. Used in extra calculations for horizontal layout. + */ + // private HashMap childElementHeight = new + // HashMap(); + + /** + * For bookkeeping. Used in extra calculations for horizontal layout. + */ + private HashMap childCaptionElementHeight = new HashMap(); + + 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 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 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); + // updateAllSlotListeners(); + + updateLayoutHeight(); + } + }; + + 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()) { + 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 (ComponentConnector child : getChildComponents()) { + // 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. + Element el = child.getWidget().getElement(); + CaptionPosition pos = getWidget().getCaptionPositionFromElement( + (Element) el.getParentElement().cast()); + if (needsMeasure.contains(el)) { + int h = getLayoutManager().getOuterHeight(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 = getLayoutManager().getOuterHeight(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/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalBoxLayoutConnector.java b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalBoxLayoutConnector.java new file mode 100644 index 0000000000..d18ea8a178 --- /dev/null +++ b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalBoxLayoutConnector.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.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/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java index 195622854d..f22b558afd 100644 --- a/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java +++ b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java @@ -15,11 +15,8 @@ */ package com.vaadin.terminal.gwt.client.ui.orderedlayout; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.ui.HorizontalLayout; -@Connect(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) +//@Connect(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) public class HorizontalLayoutConnector extends AbstractOrderedLayoutConnector { @Override diff --git a/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VBoxLayout.java b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VBoxLayout.java new file mode 100644 index 0000000000..4e847082e4 --- /dev/null +++ b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VBoxLayout.java @@ -0,0 +1,756 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +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.shared.ui.AlignmentInfo; +import com.vaadin.shared.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.LayoutManager; + +public class VBoxLayout extends FlowPanel { + + public static final String CLASSNAME = "v-boxlayout"; + + private static final String ALIGN_CLASS_PREFIX = "v-align-"; + + protected boolean spacing = false; + + protected boolean vertical = true; + + protected boolean definedHeight = false; + + private Map widgetToSlot = new HashMap(); + + private LayoutManager layoutManager; + + public VBoxLayout() { + setStyleName(CLASSNAME); + 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 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/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalBoxLayoutConnector.java b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalBoxLayoutConnector.java new file mode 100644 index 0000000000..a480a79b2e --- /dev/null +++ b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalBoxLayoutConnector.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.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); + } + +} diff --git a/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java index 441ba9c156..c53494775c 100644 --- a/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java +++ b/client/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java @@ -15,11 +15,8 @@ */ package com.vaadin.terminal.gwt.client.ui.orderedlayout; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.ui.VerticalLayout; -@Connect(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) +//@Connect(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) public class VerticalLayoutConnector extends AbstractOrderedLayoutConnector { @Override