]> source.dussan.org Git - vaadin-framework.git/commitdiff
BoxLayout now respects the current layout specs, dividing unused space equally if...
authorJouni Koivuviita <jouni@jounikoivuviita.com>
Mon, 16 Apr 2012 15:21:16 +0000 (18:21 +0300)
committerJouni Koivuviita <jouni@jounikoivuviita.com>
Mon, 16 Apr 2012 15:21:16 +0000 (18:21 +0300)
WebContent/VAADIN/themes/base/boxlayout/boxlayout.css
src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java
src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java
tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java [new file with mode: 0644]

index b2514d23823c769156bb92cca1a6ebb688839aef..e5229897697647a245a86ac9b739626734a793c0 100644 (file)
@@ -1,3 +1,11 @@
+/*
+TODO
+- separate styles to proper places
+- decide a good class name structure for core layouts (e.g. 'v-layout', 'v-vertical', 'v-grid' etc.)
+- use !important in carefully selected places to prevent accidental layout breakage by custom theming (e.g. alignments should be forced)
+
+*/
+
 .v-boxlayout.v-margin-top              {padding-top:           18px;}
 .v-boxlayout.v-margin-right    {padding-right:         18px;}
 .v-boxlayout.v-margin-bottom   {padding-bottom:        18px;}
@@ -13,7 +21,7 @@
 }
 
 .v-boxlayout.v-horizontal {
-       white-space: nowrap;
+       white-space: nowrap !important;
 }
 
 .v-boxlayout > .v-expand {
 }
 
 .v-caption {
+       display: inline-block; /* Force default width to zero */
        overflow: visible;
        vertical-align: middle;
 }
        white-space: nowrap;
 }
 
-.v-caption-on-left > .v-caption,
-.v-caption-on-right > .v-caption {
-       display: inline-block;
+.v-caption-on-top > .v-caption,
+.v-caption-on-bottom > .v-caption {
+       display: block;
 }
 
 .v-caption-on-left > .v-caption {
 
 .v-has-caption.v-has-height > .v-paintable {
        height: 100% !important;
+}
+
+.v-errorindicator {
+       vertical-align: middle;
 }
\ No newline at end of file
index 8a0471912fb1ba27c9dd8b558cc147a8294b90f9..5f0ebfda1c5a5a9e3d0a72d870ecbeb3370209ca 100644 (file)
@@ -18,6 +18,7 @@ import com.vaadin.terminal.gwt.client.ValueMap;
 import com.vaadin.terminal.gwt.client.communication.RpcProxy;
 import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
 import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler;
+import com.vaadin.terminal.gwt.client.ui.VBoxLayout.CaptionPosition;
 import com.vaadin.terminal.gwt.client.ui.VBoxLayout.Slot;
 import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent;
 import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener;
@@ -101,7 +102,7 @@ public abstract class AbstractBoxLayoutConnector extends
     private HashMap<Element, Integer> childCaptionElementHeight = new HashMap<Element, Integer>();
 
     // For debugging
-    private int resizeCount = 0;
+    private static int resizeCount = 0;
 
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
         if (!isRealUpdate(uidl)) {
@@ -134,7 +135,14 @@ public abstract class AbstractBoxLayoutConnector extends
             slot.setAlignment(alignment);
 
             double expandRatio;
-            if (expandRatios.containsKey(pid)
+            // TODO discuss the layout specs, is this what we want: distribute
+            // extra space equally if no expand ratios are specified inside a
+            // layout with specified size
+            if (expandRatios.getKeySet().size() == 0
+                    && ((!getWidget().vertical && !isUndefinedHeight()) || !isUndefinedWidth())) {
+                expandRatio = 1;
+                hasExpandRatio.add(child);
+            } else if (expandRatios.containsKey(pid)
                     && expandRatios.getRawNumber(pid) > 0) {
                 expandRatio = expandRatios.getRawNumber(pid);
                 hasExpandRatio.add(child);
@@ -152,6 +160,11 @@ public abstract class AbstractBoxLayoutConnector extends
             }
             slot.setExpandRatio(expandRatio);
 
+            if (slot.getSpacingElement() != null) {
+                getLayoutManager().addElementResizeListener(
+                        slot.getSpacingElement(), spacingResizeListener);
+            }
+
         }
 
         if (needsExpand()) {
@@ -170,18 +183,28 @@ public abstract class AbstractBoxLayoutConnector extends
                 .getIcon().getURL() : null;
         List<String> styles = child.getState().getStyles();
         String error = child.getState().getErrorMessage();
+        boolean required = false;
+        if (child instanceof AbstractFieldConnector) {
+            required = ((AbstractFieldConnector) child).isRequired();
+        }
         // TODO Description is handled from somewhere else?
 
-        slot.setCaption(caption, iconUrl, styles, error);
+        slot.setCaption(caption, iconUrl, styles, error, required);
 
         slot.setRelativeWidth(child.isRelativeWidth());
         slot.setRelativeHeight(child.isRelativeHeight());
 
-        // TODO Should also check captionposition: && captionPosition==TOP ||
-        // captionPosition==BOTTOM
         if (slot.hasCaption()) {
+            CaptionPosition pos = slot.getCaptionPosition();
             getLayoutManager().addElementResizeListener(
                     slot.getCaptionElement(), slotCaptionResizeListener);
+            if (child.isRelativeHeight()
+                    && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
+                getWidget().updateCaptionOffset(slot.getCaptionElement());
+            } else if (child.isRelativeWidth()
+                    && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) {
+                getWidget().updateCaptionOffset(slot.getCaptionElement());
+            }
         } else {
             getLayoutManager().removeElementResizeListener(
                     slot.getCaptionElement(), slotCaptionResizeListener);
@@ -228,6 +251,10 @@ public abstract class AbstractBoxLayoutConnector extends
                                     slot.getCaptionElement(),
                                     slotCaptionResizeListener);
                 }
+                if (slot.getSpacingElement() != null) {
+                    getLayoutManager().removeElementResizeListener(
+                            slot.getSpacingElement(), spacingResizeListener);
+                }
                 layout.removeSlot(child.getWidget());
             }
         }
@@ -265,10 +292,22 @@ public abstract class AbstractBoxLayoutConnector extends
             Slot slot = getWidget().getSlot(child.getWidget());
             slot.setRelativeWidth(child.isRelativeWidth());
             slot.setRelativeHeight(child.isRelativeHeight());
-            if (slot.hasCaption() && child.isRelativeHeight()) {
-                getWidget().updateCaptionOffset(slot.getCaptionElement());
+
+            // For relative sized widgets, we need to set the caption offset
+            if (slot.hasCaption()) {
+                CaptionPosition pos = slot.getCaptionPosition();
+                if (child.isRelativeHeight()
+                        && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
+                    getWidget().updateCaptionOffset(slot.getCaptionElement());
+                } else if (child.isRelativeWidth()
+                        && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) {
+                    getWidget().updateCaptionOffset(slot.getCaptionElement());
+                }
             }
 
+            // TODO 'needsExpand' might return false during the first render,
+            // since updateFromUidl is called last
+
             // If the slot has caption, we need to listen for it's size changes
             // in order to update the padding/margin offset for relative sized
             // components
@@ -277,14 +316,22 @@ public abstract class AbstractBoxLayoutConnector extends
                 getLayoutManager().addElementResizeListener(
                         slot.getCaptionElement(), slotCaptionResizeListener);
             } else if (!needsExpand()) {
-                getLayoutManager().removeElementResizeListener(
-                        slot.getCaptionElement(), slotCaptionResizeListener);
+                // getLayoutManager().removeElementResizeListener(
+                // slot.getCaptionElement(), slotCaptionResizeListener);
+            }
+
+            if (slot.getSpacingElement() != null && needsExpand()) {
+                // Spacing is on
+                getLayoutManager().addElementResizeListener(
+                        slot.getSpacingElement(), spacingResizeListener);
+            } else if (slot.getSpacingElement() != null) {
+                getLayoutManager().addElementResizeListener(
+                        slot.getSpacingElement(), spacingResizeListener);
             }
 
             if (child.isRelativeHeight()) {
                 hasRelativeHeight.add(child);
                 needsMeasure.remove(child.getWidget().getElement());
-                // childElementHeight.remove(child.getWidget().getElement());
             } else {
                 hasRelativeHeight.remove(child);
                 needsMeasure.add(child.getWidget().getElement());
@@ -376,9 +423,15 @@ public abstract class AbstractBoxLayoutConnector extends
 
             Element captionElement = (Element) e.getElement().cast();
 
-            // TODO take caption position into account
+            CaptionPosition pos = getWidget().getCaptionPositionFromElement(
+                    (Element) captionElement.getParentElement().cast());
+
             Element widgetElement = captionElement.getParentElement()
                     .getLastChild().cast();
+            if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) {
+                widgetElement = captionElement.getParentElement()
+                        .getFirstChildElement().cast();
+            }
 
             if (captionElement == widgetElement) {
                 // Caption element already detached
@@ -387,8 +440,14 @@ public abstract class AbstractBoxLayoutConnector extends
                 return;
             }
 
+            String widgetWidth = widgetElement.getStyle().getWidth();
             String widgetHeight = widgetElement.getStyle().getHeight();
-            if (widgetHeight.endsWith("%")) {
+
+            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);
             }
 
@@ -417,6 +476,15 @@ public abstract class AbstractBoxLayoutConnector extends
         }
     };
 
+    private ElementResizeListener spacingResizeListener = new ElementResizeListener() {
+        public void onElementResize(ElementResizeEvent e) {
+            resizeCount++;
+            if (needsExpand()) {
+                updateExpand();
+            }
+        }
+    };
+
     private void updateLayoutHeight() {
         if (needsFixedHeight() && childElementHeight.size() > 0) {
             int h = getMaxHeight();
@@ -429,9 +497,9 @@ public abstract class AbstractBoxLayoutConnector extends
     }
 
     private void updateExpand() {
-        System.out.println("All sizes: "
-                + childElementHeight.values().toString() + " - Caption sizes: "
-                + childCaptionElementHeight.values().toString());
+        // System.out.println("All sizes: "
+        // + childElementHeight.values().toString() + " - Caption sizes: "
+        // + childCaptionElementHeight.values().toString());
         getWidget().updateExpand();
     }
 
@@ -439,11 +507,20 @@ public abstract class AbstractBoxLayoutConnector extends
         int highestNonRelative = -1;
         int highestRelative = -1;
         for (Element el : childElementHeight.keySet()) {
+            // TODO would be more efficient to measure the slot element if both
+            // caption and child widget elements need to be measured. Keeping
+            // track of what to measure is the most difficult part of this
+            // layout.
+            CaptionPosition pos = getWidget().getCaptionPositionFromElement(
+                    (Element) el.getParentElement().cast());
             if (needsMeasure.contains(el)) {
                 int h = childElementHeight.get(el);
                 String sHeight = el.getStyle().getHeight();
+                // Only add the caption size to the height of the slot if
+                // coption position is top or bottom
                 if (childCaptionElementHeight.containsKey(el)
-                        && (sHeight == null || !sHeight.endsWith("%"))) {
+                        && (sHeight == null || !sHeight.endsWith("%"))
+                        && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
                     h += childCaptionElementHeight.get(el);
                 }
                 if (h > highestNonRelative) {
@@ -451,7 +528,8 @@ public abstract class AbstractBoxLayoutConnector extends
                 }
             } else {
                 int h = childElementHeight.get(el);
-                if (childCaptionElementHeight.containsKey(el)) {
+                if (childCaptionElementHeight.containsKey(el)
+                        && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) {
                     h += childCaptionElementHeight.get(el);
                 }
                 if (h > highestRelative) {
@@ -478,6 +556,11 @@ public abstract class AbstractBoxLayoutConnector extends
                         slot.getCaptionElement(), slotCaptionResizeListener);
             }
 
+            if (slot.getSpacingElement() != null) {
+                getLayoutManager().removeElementResizeListener(
+                        slot.getSpacingElement(), spacingResizeListener);
+            }
+
             getLayoutManager()
                     .removeElementResizeListener(slot.getWidget().getElement(),
                             childComponentResizeListener);
index ab13e0503b9e89ea6b78339cc75eaa62231c9247..cc4150e9fe31f6a213d7694ad05142c3eb3bef38 100644 (file)
@@ -113,8 +113,11 @@ public class VBoxLayout extends FlowPanel {
         private Element captionText;
         private Icon icon;
         private Element errorIcon;
+        private Element requiredIcon;
 
-        private CaptionPosition captionPosition = CaptionPosition.TOP;
+        // 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;
@@ -178,15 +181,17 @@ public class VBoxLayout extends FlowPanel {
         }
 
         protected int getSpacingSize(boolean vertical) {
-            // No spacer attached
             if (spacer == null) {
                 return 0;
             }
 
-            // if (layoutManager != null) {
-            // return vertical ? layoutManager.getOuterHeight(spacer)
-            // : layoutManager.getOuterWidth(spacer);
-            // } else {
+            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
@@ -196,28 +201,38 @@ public class VBoxLayout extends FlowPanel {
         }
 
         public void setCaptionPosition(CaptionPosition captionPosition) {
-            this.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;
+        }
+
         public void setCaption(String captionText, String iconUrl,
-                List<String> styles, String error) {
+                List<String> styles, String error, boolean required) {
 
             // 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) {
+            if (captionText != null || iconUrl != null || error != null
+                    || required) {
                 if (caption == null) {
                     caption = DOM.createDiv();
                     captionWrap = DOM.createDiv();
@@ -225,7 +240,6 @@ public class VBoxLayout extends FlowPanel {
                     captionWrap.addClassName("v-has-caption");
                     getElement().appendChild(captionWrap);
                     captionWrap.appendChild(getWidget().getElement());
-                    setCaptionPosition(captionPosition);
                 }
             } else if (caption != null) {
                 getElement().appendChild(getWidget().getElement());
@@ -274,6 +288,18 @@ public class VBoxLayout extends FlowPanel {
                 errorIcon = null;
             }
 
+            // Required
+            if (required) {
+                if (requiredIcon == null) {
+                    requiredIcon = DOM.createSpan();
+                    requiredIcon.setClassName("v-required-indicator");
+                }
+                caption.appendChild(requiredIcon);
+            } else if (requiredIcon != null) {
+                requiredIcon.removeFromParent();
+                requiredIcon = null;
+            }
+
             // Styles
             if (caption != null) {
                 caption.setClassName("v-caption");
@@ -285,6 +311,14 @@ public class VBoxLayout extends FlowPanel {
                 }
             }
 
+            if (caption != null) {
+                if (captionText != null || iconUrl != null) {
+                    setCaptionPosition(CaptionPosition.TOP);
+                } else {
+                    setCaptionPosition(CaptionPosition.RIGHT);
+                }
+            }
+
             // TODO theme flexibility: add extra styles to captionWrap as well?
 
         }
@@ -382,6 +416,19 @@ public class VBoxLayout extends FlowPanel {
     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();
@@ -399,11 +446,7 @@ public class VBoxLayout extends FlowPanel {
         captionStyle.clearMarginLeft();
 
         // Get caption position from the classname
-        MatchResult matcher = captionPositionRegexp.exec(captionWrap
-                .getClassName());
-        String captionClass = matcher.getGroup(1);
-        CaptionPosition captionPosition = CaptionPosition.valueOf(
-                CaptionPosition.class, captionClass.toUpperCase());
+        CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap);
 
         if (captionPosition == CaptionPosition.LEFT
                 || captionPosition == CaptionPosition.RIGHT) {
@@ -414,12 +457,14 @@ public class VBoxLayout extends FlowPanel {
             } else {
                 captionWidth = caption.getOffsetWidth();
             }
-            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 (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
@@ -431,12 +476,14 @@ public class VBoxLayout extends FlowPanel {
             } else {
                 captionHeight = caption.getOffsetHeight();
             }
-            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);
+            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);
+                }
             }
         }
     }
@@ -525,64 +572,69 @@ public class VBoxLayout extends FlowPanel {
             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);
+            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);
                 }
-                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) {
-                            totalSize += layoutManager.getOuterHeight(slot
-                                    .getWidget().getElement())
-                                    - layoutManager.getMarginHeight(slot
-                                            .getWidget().getElement());
-                            if (slot.hasCaption()) {
+
+                int totalSize = 0;
+                for (Widget w : getChildren()) {
+                    Slot slot = (Slot) w;
+                    if (slot.getExpandRatio() == -1) {
+                        if (layoutManager != null) {
+                            // TODO check caption position
+                            if (vertical) {
                                 totalSize += layoutManager.getOuterHeight(slot
-                                        .getCaptionElement())
+                                        .getWidget().getElement())
                                         - layoutManager.getMarginHeight(slot
-                                                .getCaptionElement());
+                                                .getWidget().getElement());
+                                if (slot.hasCaption()) {
+                                    totalSize += layoutManager
+                                            .getOuterHeight(slot
+                                                    .getCaptionElement())
+                                            - layoutManager
+                                                    .getMarginHeight(slot
+                                                            .getCaptionElement());
+                                }
+                            } else {
+                                totalSize += layoutManager.getOuterWidth(slot
+                                        .getWidget().getElement())
+                                        - layoutManager.getMarginWidth(slot
+                                                .getWidget().getElement());
                             }
                         } else {
-                            totalSize += layoutManager.getOuterWidth(slot
-                                    .getWidget().getElement())
-                                    - layoutManager.getMarginWidth(slot
-                                            .getWidget().getElement());
+                            totalSize += vertical ? slot.getOffsetHeight()
+                                    : slot.getOffsetWidth();
                         }
-                    } else {
-                        totalSize += vertical ? slot.getOffsetHeight() : slot
-                                .getOffsetWidth();
                     }
+                    // TODO fails in Opera, always returns 0
+                    totalSize += slot.getSpacingSize(vertical);
                 }
-                // TODO fails in Opera, always returns 0
-                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);
-            }
-            recalculateExpands();
 
+                // 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();
+
+            }
         }
     }
 
diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java
new file mode 100644 (file)
index 0000000..973bd63
--- /dev/null
@@ -0,0 +1,341 @@
+package com.vaadin.tests.components.orderedlayout;
+
+import com.vaadin.annotations.Theme;
+import com.vaadin.terminal.Sizeable;
+import com.vaadin.terminal.ThemeResource;
+import com.vaadin.terminal.WrappedRequest;
+import com.vaadin.tests.components.AbstractTestRoot;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.Embedded;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.HorizontalSplitPanel;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.NativeButton;
+import com.vaadin.ui.NativeSelect;
+import com.vaadin.ui.Slider;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.VerticalLayout;
+
+@Theme("tests-components")
+public class VaadinTunesLayout extends AbstractTestRoot {
+
+    @Override
+    public void setup(WrappedRequest request) {
+
+        /*
+         * We'll build the whole UI here, since the application will not contain
+         * any logic. Otherwise it would be more practical to separate parts of
+         * the UI into different classes and methods.
+         */
+
+        // Main (browser) window, needed in all Vaadin applications
+        VerticalLayout rootLayout = new VerticalLayout();
+        // final Window root = new Window("VaadinTunes", rootLayout);
+
+        /*
+         * We'll attach the window to the browser view already here, so we won't
+         * forget it later.
+         */
+        setContent(rootLayout);
+
+        // root.showNotification(
+        // "This is an example of how you can do layouts in Vaadin.<br/>It is not a working sound player.",
+        // Notification.TYPE_HUMANIZED_MESSAGE);
+
+        // Our root window contains one VerticalLayout, let's make
+        // sure it's 100% sized, and remove unwanted margins
+        rootLayout.setSizeFull();
+        rootLayout.setMargin(false);
+
+        // Top area, containing playback and volume controls, play status, view
+        // modes and search
+        HorizontalLayout top = new HorizontalLayout();
+        top.setWidth("100%");
+        top.setMargin(false, true, false, true); // Enable horizontal margins
+        top.setSpacing(true);
+
+        // Let's attach that one straight away too
+        rootLayout.addComponent(top);
+
+        // Create the placeholders for all the components in the top area
+        HorizontalLayout playback = new HorizontalLayout();
+        HorizontalLayout volume = new HorizontalLayout();
+        HorizontalLayout status = new HorizontalLayout();
+        HorizontalLayout viewmodes = new HorizontalLayout();
+        ComboBox search = new ComboBox();
+
+        // Add the components and align them properly
+        top.addComponent(playback);
+        top.addComponent(volume);
+        top.addComponent(status);
+        top.addComponent(viewmodes);
+        top.addComponent(search);
+        top.setComponentAlignment(playback, Alignment.MIDDLE_LEFT);
+        top.setComponentAlignment(volume, Alignment.MIDDLE_LEFT);
+        top.setComponentAlignment(status, Alignment.MIDDLE_CENTER);
+        top.setComponentAlignment(viewmodes, Alignment.MIDDLE_LEFT);
+        top.setComponentAlignment(search, Alignment.MIDDLE_LEFT);
+
+        /*
+         * We want our status area to expand if the user resizes the root
+         * window, and we want it to accommodate as much space as there is
+         * available. All other components in the top layout should stay fixed
+         * sized, so we don't need to specify any expand ratios for them (they
+         * will automatically revert to zero after the following line).
+         */
+        top.setExpandRatio(status, 1.0F);
+
+        // Playback controls
+        Button prev = new NativeButton("Previous");
+        Button play = new NativeButton("Play/pause");
+        Button next = new NativeButton("Next");
+        playback.addComponent(prev);
+        playback.addComponent(play);
+        playback.addComponent(next);
+        // Set spacing between the buttons
+        playback.setSpacing(true);
+
+        // Volume controls
+        Button mute = new NativeButton("mute");
+        Slider vol = new Slider();
+        vol.setOrientation(Slider.ORIENTATION_HORIZONTAL);
+        vol.setWidth("100px");
+        Button max = new NativeButton("max");
+        volume.addComponent(mute);
+        volume.addComponent(vol);
+        volume.addComponent(max);
+
+        // Status area
+        status.setWidth("80%");
+        status.setSpacing(true);
+
+        Button toggleVisualization = new NativeButton("Mode");
+        Label timeFromStart = new Label("0:00");
+
+        // We'll need another layout to show currently playing track and
+        // progress
+        VerticalLayout trackDetails = new VerticalLayout();
+        trackDetails.setWidth("100%");
+        Label track = new Label("Track Name");
+        Label album = new Label("Album Name - Artist");
+        track.setWidth(null);
+        album.setWidth(null);
+        Slider progress = new Slider();
+        progress.setOrientation(Slider.ORIENTATION_HORIZONTAL);
+        progress.setWidth("100%");
+        trackDetails.addComponent(track);
+        trackDetails.addComponent(album);
+        trackDetails.addComponent(progress);
+        trackDetails.setComponentAlignment(track, Alignment.TOP_CENTER);
+        trackDetails.setComponentAlignment(album, Alignment.TOP_CENTER);
+
+        Label timeToEnd = new Label("-4:46");
+        Button jumpToTrack = new NativeButton("Show");
+
+        // Place all components to the status layout and align them properly
+        status.addComponent(toggleVisualization);
+        status.setComponentAlignment(toggleVisualization, Alignment.MIDDLE_LEFT);
+        status.addComponent(timeFromStart);
+        status.setComponentAlignment(timeFromStart, Alignment.BOTTOM_LEFT);
+        status.addComponent(trackDetails);
+        status.addComponent(timeToEnd);
+        status.setComponentAlignment(timeToEnd, Alignment.BOTTOM_LEFT);
+        status.addComponent(jumpToTrack);
+        status.setComponentAlignment(jumpToTrack, Alignment.MIDDLE_LEFT);
+
+        // Then remember to specify the expand ratio
+        status.setExpandRatio(trackDetails, 1.0F);
+
+        // View mode buttons
+        Button viewAsTable = new NativeButton("Table");
+        Button viewAsGrid = new NativeButton("Grid");
+        Button coverflow = new NativeButton("Coverflow");
+        viewmodes.addComponent(viewAsTable);
+        viewmodes.addComponent(viewAsGrid);
+        viewmodes.addComponent(coverflow);
+
+        /*
+         * That covers the top bar. Now let's move on to the sidebar and track
+         * listing
+         */
+
+        // We'll need one splitpanel to separate the sidebar and track listing
+        HorizontalSplitPanel bottom = new HorizontalSplitPanel();
+        rootLayout.addComponent(bottom);
+
+        // The splitpanel is by default 100% x 100%, but we'll need to adjust
+        // our main window layout to accomodate the height
+        rootLayout.setExpandRatio(bottom, 1.0F);
+
+        // Give the sidebar less space than the listing
+        bottom.setSplitPosition(200, Sizeable.UNITS_PIXELS);
+
+        // Let's add some content to the sidebar
+        // First, we need a layout to but all components in
+        VerticalLayout sidebar = new VerticalLayout();
+        sidebar.setSizeFull();
+        bottom.setFirstComponent(sidebar);
+
+        /*
+         * Then we need some labels and buttons, and an album cover image The
+         * labels and buttons go into their own vertical layout, since we want
+         * the 'sidebar' layout to be expanding (cover image in the bottom).
+         * VerticalLayout is by default 100% wide.
+         */
+        VerticalLayout selections = new VerticalLayout();
+        Label library = new Label("Library");
+        Button music = new NativeButton("Music");
+        music.setWidth("100%");
+
+        Label store = new Label("Store");
+        Button vaadinTunesStore = new NativeButton("VaadinTunes Store");
+        vaadinTunesStore.setWidth("100%");
+        Button purchased = new NativeButton("Purchased");
+        purchased.setWidth("100%");
+
+        Label playlists = new Label("Playlists");
+        Button genius = new NativeButton("Geniues");
+        genius.setWidth("100%");
+        Button recent = new NativeButton("Recently Added");
+        recent.setWidth("100%");
+
+        // Lets add them to the 'selections' layout
+        selections.addComponent(library);
+        selections.addComponent(music);
+        selections.addComponent(store);
+        selections.addComponent(vaadinTunesStore);
+        selections.addComponent(purchased);
+        selections.addComponent(playlists);
+        selections.addComponent(genius);
+        selections.addComponent(recent);
+
+        // Then add the selections to the sidebar, and set it expanding
+        sidebar.addComponent(selections);
+        sidebar.setExpandRatio(selections, 1.0F);
+
+        // Then comes the cover artwork (we'll add the actual image in the
+        // themeing section)
+        Embedded cover = new Embedded("Currently Playing");
+        sidebar.addComponent(cover);
+
+        /*
+         * And lastly, we need the track listing table It should fill the whole
+         * left side of our bottom layout
+         */
+        Table listing = new Table();
+        listing.setSizeFull();
+        listing.setSelectable(true);
+        bottom.setSecondComponent(listing);
+
+        // Add the table headers
+        listing.addContainerProperty("Name", String.class, "");
+        listing.addContainerProperty("Time", String.class, "0:00");
+        listing.addContainerProperty("Artist", String.class, "");
+        listing.addContainerProperty("Album", String.class, "");
+        listing.addContainerProperty("Genre", String.class, "");
+        listing.addContainerProperty("Rating", NativeSelect.class,
+                new NativeSelect());
+
+        // Lets populate the table with random data
+        String[] tracks = new String[] { "Red Flag", "Millstone",
+                "Not The Sun", "Breath", "Here We Are", "Deep Heaven",
+                "Her Voice Resides", "Natural Tan", "End It All", "Kings",
+                "Daylight Slaving", "Mad Man", "Resolve", "Teargas",
+                "African Air", "Passing Bird" };
+        String[] times = new String[] { "4:12", "6:03", "5:43", "4:32", "3:42",
+                "4:45", "2:56", "9:34", "2:10", "3:44", "5:49", "6:30", "5:18",
+                "7:42", "3:13", "2:52" };
+        String[] artists = new String[] { "Billy Talent", "Brand New",
+                "Breaking Benjamin", "Becoming The Archetype",
+                "Bullet For My Valentine", "Chasing Victory", "Chimaira",
+                "Danko Jones", "Deadlock", "Deftones", "From Autumn To Ashes",
+                "Haste The Day", "Four Year Strong", "In Flames", "Kemopetrol",
+                "John Legend" };
+        String[] albums = new String[] { "Once Again", "The Caitiff Choir",
+                "The Devil And God", "Light Grenades", "Dicthonomy",
+                "Back In Black", "Dreamer", "Come Clarity", "Year Zero",
+                "Frames", "Fortress", "Phobia", "The Poison", "Manifesto",
+                "White Pony", "The Big Dirty" };
+        String[] genres = new String[] { "Rock", "Metal", "Hardcore", "Indie",
+                "Pop", "Alternative", "Blues", "Jazz", "Hip Hop",
+                "Electronica", "Punk", "Hard Rock", "Dance", "R'n'B", "Gospel",
+                "Country" };
+        for (int i = 0; i < 1000; i++) {
+            NativeSelect s = new NativeSelect();
+            s.addItem("1 star");
+            s.addItem("2 stars");
+            s.addItem("3 stars");
+            s.addItem("4 stars");
+            s.addItem("5 stars");
+            s.select(i % 5 + " stars");
+            final int index = i % 16;
+            listing.addItem(new Object[] { tracks[index], times[index],
+                    artists[index], albums[index], genres[index], s }, i);
+        }
+
+        // We'll align the track time column to right as well
+        listing.setColumnAlignment("Time", Table.ALIGN_RIGHT);
+
+        // TODO the footer
+
+        // Now what's left to do? Themeing of course.
+        // setTheme("vaadintunes");
+
+        /*
+         * Let's give a namespace to our application window. This way, if
+         * someone uses the same theme for different applications, we don't get
+         * unwanted style conflicts.
+         */
+        // root.setStyleName("tTunes");
+
+        top.setStyleName("top");
+        top.setHeight("75px"); // Same as the background image height
+
+        playback.setStyleName("playback");
+        playback.setMargin(false, true, false, false); // Add right-side margin
+        play.setStyleName("play");
+        next.setStyleName("next");
+        prev.setStyleName("prev");
+        playback.setComponentAlignment(prev, Alignment.MIDDLE_LEFT);
+        playback.setComponentAlignment(next, Alignment.MIDDLE_LEFT);
+
+        volume.setStyleName("volume");
+        mute.setStyleName("mute");
+        max.setStyleName("max");
+        vol.setWidth("78px");
+
+        status.setStyleName("status");
+        status.setMargin(true);
+        status.setHeight("46px"); // Height of the background image
+
+        toggleVisualization.setStyleName("toggle-vis");
+        jumpToTrack.setStyleName("jump");
+
+        viewAsTable.setStyleName("viewmode-table");
+        viewAsGrid.setStyleName("viewmode-grid");
+        coverflow.setStyleName("viewmode-coverflow");
+
+        sidebar.setStyleName("sidebar");
+
+        music.setStyleName("selected");
+
+        cover.setSource(new ThemeResource("images/album-cover.jpg"));
+        // Because this is an image, it will retain it's aspect ratio
+        cover.setWidth("100%");
+    }
+
+    @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