aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJouni Koivuviita <jouni@jounikoivuviita.com>2012-04-08 00:34:22 +0300
committerJouni Koivuviita <jouni@jounikoivuviita.com>2012-04-08 00:34:22 +0300
commitf64a5927e92791c0b1a03899bb7b2487bc08896c (patch)
treef3256337fac345f98bb3db0fd4a0223e43071e06
parentda9d8e962a4ed773ceafeac657f79d52ef087bc6 (diff)
downloadvaadin-framework-f64a5927e92791c0b1a03899bb7b2487bc08896c.tar.gz
vaadin-framework-f64a5927e92791c0b1a03899bb7b2487bc08896c.zip
95% functional VerticalLayout and HorizontalLayout using mainly CSS
-rw-r--r--WebContent/VAADIN/themes/tests-components/styles.css144
-rw-r--r--src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml8
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java183
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java17
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java508
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java17
-rw-r--r--tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java382
-rw-r--r--tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java2
8 files changed, 1261 insertions, 0 deletions
diff --git a/WebContent/VAADIN/themes/tests-components/styles.css b/WebContent/VAADIN/themes/tests-components/styles.css
index c38f32f132..8ab31f511d 100644
--- a/WebContent/VAADIN/themes/tests-components/styles.css
+++ b/WebContent/VAADIN/themes/tests-components/styles.css
@@ -36,4 +36,148 @@
.v-table-row-tables-test-cell-style-red-row,
.v-table-cell-content-tables-test-cell-style-red-row {
background: #f00;
+}
+
+
+
+
+
+
+/* BoxLayout styles */
+.v-layout.v-margin-top {padding-top: 1.5em;}
+.v-layout.v-margin-right {padding-right: 1.5em;}
+.v-layout.v-margin-bottom {padding-bottom: 1.5em;}
+.v-layout.v-margin-left {padding-left: 1.5em;}
+
+.v-layout.v-box {
+ display: inline-block;
+}
+
+.v-paintable {
+ text-align: left;
+}
+
+.v-layout.v-box.v-horizontal {
+ white-space: nowrap;
+}
+
+.v-box-expand {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+}
+
+.v-slot,
+.v-spacing {
+ display: inline-block;
+ white-space: normal;
+ vertical-align: top;
+}
+
+.v-vertical > .v-slot,
+.v-vertical > .v-box-expand > .v-slot {
+ display: block;
+}
+
+.v-horizontal > .v-slot,
+.v-horizontal > .v-box-expand > .v-slot {
+ height: 100%;
+}
+
+.v-spacing {
+ width: 1em;
+ height: 1em;
+}
+
+.v-box > .v-align-middle:before,
+.v-box > .v-align-bottom:before,
+.v-box-expand > .v-align-middle:before,
+.v-box-expand > .v-align-bottom:before {
+ content: "";
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
+ width: 0;
+}
+
+.v-align-middle,
+.v-align-bottom {
+ white-space: nowrap;;
+}
+
+.v-align-middle > .v-paintable,
+.v-align-bottom > .v-paintable {
+ display: inline-block;
+ /* TODO this is a bit tricky, since it will override component defaults in some cases */
+ white-space: normal;
+}
+
+.v-align-middle > .v-paintable {
+ vertical-align: middle;
+}
+
+.v-align-bottom > .v-paintable {
+ vertical-align: bottom;
+}
+
+.v-align-center {
+ text-align: center;
+}
+
+.v-align-center > .v-paintable {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.v-align-right {
+ text-align: right;
+}
+
+.v-align-right > .v-paintable {
+ margin-left: auto;
+}
+
+.v-has-caption {
+ display: inline-block;
+}
+
+.v-caption {
+ overflow: visible;
+ vertical-align: middle;
+}
+
+.v-caption-on-left,
+.v-caption-on-right {
+ white-space: nowrap;
+}
+
+.v-caption-on-left > .v-caption,
+.v-caption-on-right > .v-caption {
+ display: inline-block;
+}
+
+.v-caption-on-left > .v-caption {
+ padding-right: .5em;
+}
+
+.v-caption-on-right > .v-caption {
+ padding-left: .5em;
+}
+
+.v-caption-on-left > .v-paintable,
+.v-caption-on-right > .v-paintable {
+ display: inline-block;
+ vertical-align: middle;
+ /* TODO this is a bit tricky, since it will override component defaults in some cases */
+ white-space: normal;
+}
+
+.v-has-caption.v-has-width > .v-paintable {
+ width: 100% !important;
+}
+
+.v-has-caption.v-has-height > .v-paintable {
+ height: 100% !important;
} \ No newline at end of file
diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml
index 854464f1ee..e82fa6acd6 100644
--- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml
+++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml
@@ -16,6 +16,14 @@
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.VerticalLayoutConnector" />
+ </replace-with>
+ <replace-with class="com.vaadin.terminal.gwt.client.ui.HorizontalBoxLayoutConnector">
+ <when-type-is class="com.vaadin.terminal.gwt.client.ui.HorizontalLayoutConnector" />
+ </replace-with>
<!-- Use own Scheduler implementation to be able to track if commands are
running -->
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..0cebb72da7
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java
@@ -0,0 +1,183 @@
+package com.vaadin.terminal.gwt.client.ui;
+
+import java.util.List;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Widget;
+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.communication.URLReference;
+import com.vaadin.terminal.gwt.client.ui.AbstractOrderedLayoutConnector.AbstractOrderedLayoutServerRPC;
+import com.vaadin.terminal.gwt.client.ui.AbstractOrderedLayoutConnector.AbstractOrderedLayoutState;
+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;
+
+public abstract class AbstractBoxLayoutConnector extends
+ AbstractLayoutConnector implements Paintable, ElementResizeListener {
+
+ 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);
+ }
+
+ @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();
+ }
+
+ 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.getWidget());
+ String pid = child.getConnectorId();
+
+ AlignmentInfo alignment;
+ if (alignments.containsKey(pid)) {
+ alignment = new AlignmentInfo(alignments.getInt(pid));
+ } else {
+ alignment = AlignmentInfo.TOP_LEFT;
+ }
+ slot.setAlignment(alignment);
+
+ double expandRatio;
+ if (expandRatios.containsKey(pid)
+ && expandRatios.getRawNumber(pid) > 0) {
+ expandRatio = expandRatios.getRawNumber(pid);
+ } else {
+ expandRatio = -1;
+ }
+ slot.setExpandRatio(expandRatio);
+
+ }
+
+ layout.setMargin(new VMarginInfo(getState().getMarginsBitmask()));
+ layout.setSpacing(getState().isSpacing());
+
+ getWidget().recalculateUsedSpace();
+ getWidget().recalculateExpands();
+ }
+
+ public void updateCaption(ComponentConnector connector) {
+ Slot slot = getWidget().getSlot(connector.getWidget());
+ URLReference icon = connector.getState().getIcon();
+ slot.setCaption(connector.getState().getCaption(),
+ icon != null ? icon.getURL() : null, connector.getState()
+ .getStyles());
+ // Description is handled from somewhere else?
+ }
+
+ @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(childWidget);
+ if (slot.getParent() != layout) {
+ getLayoutManager().addElementResizeListener(
+ slot.getWidget().getElement(), this);
+ }
+ layout.addOrMoveSlot(slot, currentIndex++);
+ child.addStateChangeHandler(childStateChange);
+ }
+
+ for (ComponentConnector child : previousChildren) {
+ if (child.getParent() != this) {
+ Slot removed = layout.removeSlot(child.getWidget());
+ getLayoutManager().removeElementResizeListener(
+ removed.getWidget().getElement(), this);
+ }
+ }
+ getWidget().recalculateUsedSpace();
+ getWidget().recalculateLayoutHeight();
+ }
+
+ StateChangeHandler childStateChange = new StateChangeHandler() {
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ ComponentConnector child = (ComponentConnector) stateChangeEvent
+ .getConnector();
+ // TODO handle captions here as well, once 'updateCaption' is
+ // removed
+
+ // We need to update the slot size if the component size is changed
+ // to relative
+ Slot slot = getWidget().getSlot(child.getWidget());
+ slot.updateSize();
+
+ getWidget().recalculateLayoutHeight();
+ }
+ };
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+ getWidget().setMargin(new VMarginInfo(getState().getMarginsBitmask()));
+ getWidget().setSpacing(getState().isSpacing());
+ getWidget().recalculateLayoutHeight();
+ }
+
+ @Override
+ public void onUnregister() {
+ for (int i = 0; i < getWidget().getWidgetCount(); i++) {
+ Slot slot = (Slot) getWidget().getWidget(i);
+ getLayoutManager().removeElementResizeListener(
+ slot.getWidget().getElement(), this);
+ }
+ super.onUnregister();
+ }
+
+ public void onElementResize(ElementResizeEvent e) {
+ getWidget().recalculateUsedSpace();
+ getWidget().recalculateLayoutHeight();
+ }
+}
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..4731081c02
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java
@@ -0,0 +1,17 @@
+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.Component.LoadStyle;
+import com.vaadin.ui.HorizontalLayout;
+
+@Component(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER)
+public class HorizontalBoxLayoutConnector extends AbstractBoxLayoutConnector {
+
+ @Override
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ 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..1413c1c8bd
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java
@@ -0,0 +1,508 @@
+package com.vaadin.terminal.gwt.client.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+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.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.terminal.gwt.client.VConsole;
+
+public class VBoxLayout extends FlowPanel {
+
+ protected boolean spacing = false;
+
+ protected boolean vertical = true;
+
+ private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>();
+
+ public VBoxLayout() {
+ setStylePrimaryName("v-layout");
+ setVertical(true);
+ }
+
+ public void setVertical(boolean isVertical) {
+ vertical = isVertical;
+ addStyleName("v-box");
+ 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 = getSlot(widget);
+ remove(slot);
+ widgetToSlot.remove(widget);
+ return slot;
+ }
+
+ public Slot getSlot(Widget widget) {
+ Slot slot = widgetToSlot.get(widget);
+ if (slot == null) {
+ slot = new Slot(widget);
+ widgetToSlot.put(widget, slot);
+ }
+ return slot;
+ }
+
+ protected static class Slot extends SimplePanel {
+
+ public enum CaptionPosition {
+ TOP, RIGHT, BOTTOM, LEFT
+ }
+
+ private static final String ALIGN_CLASS_PREFIX = "v-align-";
+
+ private DivElement spacer;
+
+ private Element caption;
+ private Element captionText;
+ private Element captionWrap;
+ private Element icon;
+ private CaptionPosition captionPosition = CaptionPosition.TOP;
+
+ private AlignmentInfo alignment;
+ private double expandRatio = -1;
+
+ public Slot(Widget widget) {
+ setWidget(widget);
+ 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 = Document.get().createDivElement();
+ spacer.addClassName("v-spacing");
+ getElement().getParentElement().insertBefore(spacer,
+ getElement());
+ } else if (!spacing && spacer != null) {
+ spacer.removeFromParent();
+ spacer = null;
+ }
+ }
+
+ protected int getSpacingSize(boolean vertical) {
+ if (spacer == null) {
+ return 0;
+ }
+ if (vertical) {
+ return spacer.getOffsetHeight();
+ } else {
+ return spacer.getOffsetWidth();
+ }
+ }
+
+ public void setCaptionPosition(CaptionPosition captionPosition) {
+ this.captionPosition = captionPosition;
+ if (caption == null) {
+ return;
+ }
+ if (captionPosition == CaptionPosition.BOTTOM
+ || captionPosition == CaptionPosition.RIGHT) {
+ captionWrap.appendChild(caption);
+ } else {
+ captionWrap.insertFirst(caption);
+ }
+ captionWrap.addClassName("v-caption-on-"
+ + captionPosition.name().toLowerCase());
+ }
+
+ public void setCaption(String captionText, String iconUrl,
+ List<String> styles) {
+
+ // 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) {
+ if (caption == null) {
+ caption = DOM.createDiv();
+ captionWrap = DOM.createDiv();
+ captionWrap.addClassName("v-paintable");
+ captionWrap.addClassName("v-has-caption");
+ getElement().appendChild(captionWrap);
+ captionWrap.appendChild(getWidget().getElement());
+ setCaptionPosition(captionPosition);
+ }
+ } 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);
+ }
+ this.captionText.setInnerText(captionText);
+ } else if (this.captionText != null) {
+ this.captionText.removeFromParent();
+ this.captionText = null;
+ }
+
+ // Icon
+ if (iconUrl != null) {
+ if (icon == null) {
+ icon = DOM.createImg();
+ icon.setClassName("v-icon");
+ caption.insertFirst(icon);
+ }
+ icon.setAttribute("src", iconUrl);
+ } else if (icon != null) {
+ icon.removeFromParent();
+ icon = null;
+ }
+
+ // Styles
+ caption.setClassName("v-caption");
+ for (String style : styles) {
+ caption.addClassName("v-caption-" + style);
+ }
+
+ // TODO add extra styles to captionWrap as well?
+
+ // updateSize();
+
+ }
+
+ public void updateSize() {
+ if (caption == null) {
+ return;
+ }
+
+ VConsole.log("####################### updateSize");
+
+ Style style = captionWrap.getStyle();
+
+ style.clearWidth();
+ style.clearHeight();
+ style.clearPaddingTop();
+ style.clearPaddingRight();
+ style.clearPaddingBottom();
+ style.clearPaddingLeft();
+
+ caption.getStyle().clearMarginTop();
+ caption.getStyle().clearMarginRight();
+ caption.getStyle().clearMarginBottom();
+ caption.getStyle().clearMarginLeft();
+
+ captionWrap.removeClassName("v-has-width");
+ captionWrap.removeClassName("v-has-height");
+
+ // Relative sized widgets need extra calculations
+ if (getWidget().getElement().getStyle().getWidth().endsWith("%")) {
+ if (captionPosition == CaptionPosition.LEFT) {
+ int offset = caption.getOffsetWidth();
+ style.setPaddingLeft(offset, Unit.PX);
+ caption.getStyle().setMarginLeft(-offset, Unit.PX);
+ } else if (captionPosition == CaptionPosition.RIGHT) {
+ int offset = caption.getOffsetWidth();
+ style.setPaddingRight(offset, Unit.PX);
+ caption.getStyle().setMarginRight(-offset, Unit.PX);
+ }
+ captionWrap.addClassName("v-has-width");
+ style.setProperty("width", getWidget().getElement().getStyle()
+ .getWidth());
+ }
+ if (getWidget().getElement().getStyle().getHeight().endsWith("%")) {
+ if (captionPosition == CaptionPosition.TOP) {
+ int offset = caption.getOffsetHeight();
+ style.setPaddingTop(offset, Unit.PX);
+ caption.getStyle().setMarginTop(-offset, Unit.PX);
+ } else if (captionPosition == CaptionPosition.BOTTOM) {
+ int offset = caption.getOffsetHeight();
+ style.setPaddingBottom(offset, Unit.PX);
+ caption.getStyle().setMarginBottom(-offset, Unit.PX);
+ }
+ captionWrap.addClassName("v-has-height");
+ style.setProperty("height", getWidget().getElement().getStyle()
+ .getHeight());
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ if (spacer != null) {
+ spacer.removeFromParent();
+ }
+ super.onDetach();
+ }
+
+ }
+
+ 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 boolean recalculateExpandsScheduled = false;
+
+ public void recalculateExpands() {
+ if (!recalculateExpandsScheduled) {
+ Scheduler.get().scheduleDeferred(calculateExpands);
+ recalculateExpandsScheduled = true;
+ }
+ }
+
+ private ScheduledCommand calculateExpands = new ScheduledCommand() {
+ public void execute() {
+ 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))
+ + "%");
+ } else {
+ slot.setWidth((100 * (slot.getExpandRatio() / total))
+ + "%");
+ }
+ }
+ }
+ recalculateExpandsScheduled = false;
+ }
+ };
+
+ private Element expandWrapper;
+
+ private boolean recalculateUsedSpaceScheduled = false;
+
+ public void recalculateUsedSpace() {
+ if (!recalculateUsedSpaceScheduled) {
+ Scheduler.get().scheduleDeferred(updateExpandSlotSize);
+ recalculateUsedSpaceScheduled = true;
+ }
+ }
+
+ private ScheduledCommand updateExpandSlotSize = new ScheduledCommand() {
+ public void execute() {
+ boolean isExpanding = false;
+ for (Widget w : getChildren()) {
+ if (((Slot) w).getExpandRatio() > -1) {
+ isExpanding = true;
+ } else {
+ if (vertical) {
+ w.getElement().getStyle().clearHeight();
+ } else {
+ w.getElement().getStyle().clearWidth();
+ }
+ }
+ w.getElement().getStyle().clearMarginLeft();
+ w.getElement().getStyle().clearMarginTop();
+ }
+ if (isExpanding) {
+ if (expandWrapper == null) {
+ expandWrapper = DOM.createDiv();
+ expandWrapper.setClassName("v-box-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) {
+ totalSize += vertical ? slot.getOffsetHeight() : slot
+ .getOffsetWidth();
+ }
+ totalSize += slot.getSpacingSize(vertical);
+ }
+
+ // 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);
+ }
+ calculateExpands.execute();
+
+ } else if (expandWrapper != null) {
+ for (; expandWrapper.getChildCount() > 0;) {
+ Node el = expandWrapper.getChild(0);
+ getElement().appendChild(el);
+ if (vertical) {
+ ((Element) el.cast()).getStyle().clearHeight();
+ } else {
+ ((Element) el.cast()).getStyle().clearWidth();
+ }
+ }
+ expandWrapper.removeFromParent();
+ expandWrapper = null;
+ }
+
+ recalculateUsedSpaceScheduled = false;
+ }
+ };
+
+ private boolean recalculateLayoutHeightScheduled = false;
+
+ public void recalculateLayoutHeight() {
+ if (vertical || getStyleName().contains("v-has-height")) {
+ return;
+ }
+ if (!recalculateLayoutHeightScheduled) {
+ recalculateLayoutHeightScheduled = true;
+ Scheduler.get().scheduleDeferred(calculateLayoutHeight);
+ }
+ }
+
+ private ScheduledCommand calculateLayoutHeight = new ScheduledCommand() {
+ public void execute() {
+ // Clear previous height
+ getElement().getStyle().clearHeight();
+
+ boolean hasRelativeHeight = 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) {
+ hasRelativeHeight = true;
+ }
+ AlignmentInfo a = ((Slot) slot).getAlignment();
+ if (a.isVerticalCenter() || a.isBottom()) {
+ hasVAlign = true;
+ }
+ }
+
+ if (hasRelativeHeight || hasVAlign) {
+ int newHeight = getOffsetHeight();
+ VBoxLayout.this.getElement().getStyle()
+ .setHeight(newHeight, Unit.PX);
+ }
+
+ recalculateLayoutHeightScheduled = false;
+ }
+ };
+}
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..12e9db7a2f
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java
@@ -0,0 +1,17 @@
+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.Component.LoadStyle;
+import com.vaadin.ui.VerticalLayout;
+
+@Component(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER)
+public class VerticalBoxLayoutConnector extends AbstractBoxLayoutConnector {
+
+ @Override
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ super.updateFromUIDL(uidl, client);
+ getWidget().setVertical(true);
+ }
+
+}
diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java
new file mode 100644
index 0000000000..979e26ef7b
--- /dev/null
+++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java
@@ -0,0 +1,382 @@
+package com.vaadin.tests.components.orderedlayout;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import com.vaadin.annotations.Theme;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.event.LayoutEvents.LayoutClickEvent;
+import com.vaadin.event.LayoutEvents.LayoutClickListener;
+import com.vaadin.terminal.ThemeResource;
+import com.vaadin.terminal.WrappedRequest;
+import com.vaadin.tests.components.AbstractTestRoot;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.AbstractOrderedLayout;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.CheckBox;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Label.ContentMode;
+import com.vaadin.ui.NativeSelect;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.themes.Reindeer;
+
+@Theme("tests-components")
+public class BoxLayoutTest extends AbstractTestRoot {
+
+ protected AbstractOrderedLayout view;
+
+ protected AbstractOrderedLayout l;
+
+ protected AbstractComponent target;
+
+ protected NativeSelect componentWidth;
+ protected NativeSelect componentHeight;
+ protected NativeSelect componentCaption;
+ protected NativeSelect componentIcon;
+ protected TextField componentDescription;
+
+ protected NativeSelect align;
+ protected CheckBox expand;
+
+ @Override
+ protected void setup(WrappedRequest request) {
+
+ view = new VerticalLayout();
+ view.setSizeFull();
+ view.setMargin(true);
+ view.setSpacing(true);
+
+ view.addComponent(createControls(false));
+ view.addComponent(createTestLayout(false));
+ view.setExpandRatio(view.getComponent(1), 1);
+
+ setContent(view);
+ getApplication().setRootPreserved(true);
+ }
+
+ protected AbstractOrderedLayout createControls(boolean horizontal) {
+ VerticalLayout root = new VerticalLayout();
+ root.setSpacing(true);
+
+ // First row
+ HorizontalLayout header = new HorizontalLayout();
+ header.setSpacing(true);
+ root.addComponent(header);
+
+ Label title = new Label("BoxLayout Test");
+ title.addStyleName(Reindeer.LABEL_H1);
+ header.addComponent(title);
+
+ final CheckBox vertical = new CheckBox("Vertical", !horizontal);
+ vertical.setImmediate(true);
+ vertical.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ view.removeAllComponents();
+
+ view.addComponent(createControls(!vertical.getValue()
+ .booleanValue()));
+ view.addComponent(createTestLayout(!vertical.getValue()
+ .booleanValue()));
+
+ view.setExpandRatio(view.getComponent(1), 1);
+
+ }
+ });
+ header.addComponent(vertical);
+
+ Button addComponent = new Button("Add Component",
+ new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ l.addComponent(new ComboBox("ComboBox "
+ + (l.getComponentCount() + 1)));
+ }
+ });
+ header.addComponent(addComponent);
+
+ Button removeComponent = new Button("Remove Component",
+ new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ Component last = l.getComponent(l.getComponentCount() - 1);
+ l.removeComponent(last);
+ }
+ });
+ header.addComponent(removeComponent);
+
+ // Second row
+ HorizontalLayout controls = new HorizontalLayout();
+ controls.setSpacing(true);
+ root.addComponent(controls);
+
+ // Layout controls
+ HorizontalLayout layout = new HorizontalLayout();
+ layout.addStyleName("fieldset");
+ layout.setSpacing(true);
+ controls.addComponent(layout);
+ layout.addComponent(new Label("Layout"));
+
+ ArrayList<String> sizes = new ArrayList<String>();
+ sizes.addAll(Arrays.asList("100px", "30em", "100%"));
+
+ final NativeSelect width = new NativeSelect(null, sizes);
+ width.setImmediate(true);
+ width.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (width.getValue() != null) {
+ l.setWidth(width.getValue().toString());
+ } else {
+ l.setWidth(null);
+ }
+ }
+ });
+ layout.addComponent(width);
+ layout.addComponent(new Label("&times;", ContentMode.XHTML));
+ final NativeSelect height = new NativeSelect(null, sizes);
+ height.setImmediate(true);
+ height.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (height.getValue() != null) {
+ l.setHeight(height.getValue().toString());
+ } else {
+ l.setHeight(null);
+ }
+ }
+ });
+ layout.addComponent(height);
+
+ final CheckBox margin = new CheckBox("Margin", false);
+ margin.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ l.setMargin(margin.getValue().booleanValue());
+ }
+ });
+ margin.setImmediate(true);
+ layout.addComponent(margin);
+ layout.addComponent(margin);
+
+ final CheckBox spacing = new CheckBox("Spacing", false);
+ spacing.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ l.setSpacing(spacing.getValue().booleanValue());
+ }
+ });
+ spacing.setImmediate(true);
+ layout.addComponent(spacing);
+ layout.setComponentAlignment(spacing, Alignment.MIDDLE_LEFT);
+
+ // Cell controls
+ HorizontalLayout cell = new HorizontalLayout();
+ cell.addStyleName("fieldset");
+ cell.setSpacing(true);
+ controls.addComponent(cell);
+ cell.addComponent(new Label("Cell"));
+
+ ArrayList<Alignment> alignments = new ArrayList<Alignment>();
+ alignments.addAll(Arrays.asList(Alignment.TOP_LEFT,
+ Alignment.MIDDLE_LEFT, Alignment.BOTTOM_LEFT,
+ Alignment.TOP_CENTER, Alignment.MIDDLE_CENTER,
+ Alignment.BOTTOM_CENTER, Alignment.TOP_RIGHT,
+ Alignment.MIDDLE_RIGHT, Alignment.BOTTOM_RIGHT));
+
+ align = new NativeSelect(null, alignments);
+ for (Alignment a : alignments) {
+ align.setItemCaption(a,
+ a.getVerticalAlignment() + "-" + a.getHorizontalAlignment());
+ }
+ align.setImmediate(true);
+ align.setEnabled(false);
+ align.setNullSelectionAllowed(false);
+ align.select(Alignment.TOP_LEFT);
+ align.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (target == null) {
+ return;
+ }
+ l.setComponentAlignment(target, ((Alignment) align.getValue()));
+ }
+ });
+ cell.addComponent(align);
+
+ expand = new CheckBox("Expand");
+ expand.setImmediate(true);
+ expand.setEnabled(false);
+ expand.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (target != null) {
+ l.setExpandRatio(target, expand.getValue() ? 1 : 0);
+ }
+ }
+ });
+ cell.addComponent(expand);
+
+ // Component controls
+ HorizontalLayout component = new HorizontalLayout();
+ component.addStyleName("fieldset");
+ component.setSpacing(true);
+ controls.addComponent(component);
+ component.addComponent(new Label("Component"));
+
+ sizes = new ArrayList<String>();
+ sizes.addAll(Arrays.asList("50px", "200px", "10em", "50%", "100%"));
+
+ componentWidth = new NativeSelect(null, sizes);
+ componentWidth.setImmediate(true);
+ componentWidth.setEnabled(false);
+ componentWidth.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (target == null) {
+ return;
+ }
+ if (componentWidth.getValue() != null) {
+ target.setWidth(componentWidth.getValue().toString());
+ } else {
+ target.setWidth(null);
+ }
+ }
+ });
+ component.addComponent(componentWidth);
+ component.addComponent(new Label("&times;", ContentMode.XHTML));
+
+ componentHeight = new NativeSelect(null, sizes);
+ componentHeight.setImmediate(true);
+ componentHeight.setEnabled(false);
+ componentHeight.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (componentHeight.getValue() != null) {
+ target.setHeight(componentHeight.getValue().toString());
+ } else {
+ target.setHeight(null);
+ }
+ }
+ });
+ component.addComponent(componentHeight);
+
+ componentCaption = new NativeSelect("Caption", Arrays.asList("Short",
+ "Slightly Longer Caption"));
+ componentCaption.setImmediate(true);
+ componentCaption.setEnabled(false);
+ componentCaption.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (componentCaption.getValue() != null) {
+ target.setCaption(componentCaption.getValue().toString());
+ } else {
+ target.setCaption(null);
+ }
+ }
+ });
+ component.addComponent(componentCaption);
+
+ componentIcon = new NativeSelect("Icon", Arrays.asList(
+ "../runo/icons/16/folder.png", "../runo/icons/32/document.png"));
+ componentIcon.setImmediate(true);
+ componentIcon.setEnabled(false);
+ componentIcon.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ if (componentIcon.getValue() != null) {
+ target.setIcon(new ThemeResource(componentIcon.getValue()
+ .toString()));
+ } else {
+ target.setIcon(null);
+ }
+ }
+ });
+ component.addComponent(componentIcon);
+
+ componentDescription = new TextField("Description");
+ componentDescription.setImmediate(true);
+ componentDescription.setEnabled(false);
+ componentDescription.addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ target.setDescription(componentDescription.getValue());
+ }
+ });
+ component.addComponent(componentDescription);
+
+ return root;
+ }
+
+ protected AbstractOrderedLayout createTestLayout(boolean horizontal) {
+ l = horizontal ? new HorizontalLayout() : new VerticalLayout();
+ l.setSizeUndefined();
+ l.addStyleName("test");
+
+ Label label = new Label("Component 1");
+ l.addComponent(label);
+ l.addComponent(new Button("Component 2"));
+
+ l.addListener(new LayoutClickListener() {
+ public void layoutClick(LayoutClickEvent event) {
+ if (event.getChildComponent() != null) {
+ if (target != null || target == event.getChildComponent()) {
+ target.removeStyleName("target");
+ }
+ if (target != event.getChildComponent()) {
+ target = (AbstractComponent) event.getChildComponent();
+ target.addStyleName("target");
+ } else {
+ target = null;
+ }
+ componentWidth.setEnabled(target != null);
+ componentHeight.setEnabled(target != null);
+ componentCaption.setEnabled(target != null);
+ componentIcon.setEnabled(target != null);
+ componentDescription.setEnabled(target != null);
+ align.setEnabled(target != null);
+ expand.setEnabled(target != null);
+ if (target != null) {
+ if (target.getWidth() > -1) {
+ componentWidth.select(new Float(target.getWidth())
+ .intValue()
+ + target.getWidthUnits().getSymbol());
+ } else {
+ componentWidth.select(null);
+ }
+ if (target.getHeight() > -1) {
+ componentHeight.select(new Float(target.getHeight())
+ .intValue()
+ + target.getHeightUnits().getSymbol());
+ } else {
+ componentHeight.select(null);
+ }
+
+ align.select(l.getComponentAlignment(target));
+ expand.setValue(new Boolean(
+ l.getExpandRatio(target) > 0));
+
+ componentCaption.select(target.getCaption());
+ if (target.getIcon() != null) {
+ componentIcon.select(((ThemeResource) target
+ .getIcon()).getResourceId());
+ } else {
+ componentIcon.select(null);
+ }
+ componentDescription.setValue(target.getDescription());
+ }
+ }
+ }
+ });
+
+ target = null;
+
+ return l;
+ }
+
+ @Override
+ protected String getTestDescription() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java
index 172e808070..a4a7098f52 100644
--- a/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java
+++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java
@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import com.vaadin.annotations.Theme;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.terminal.WrappedRequest;
@@ -20,6 +21,7 @@ import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.VerticalLayout;
+@Theme("tests-components")
public class OrderedLayoutCases extends AbstractTestRoot {
private static final String[] dimensionValues = { "-1px", "5px", "350px",
"800px", "100%", "50%" };