diff options
author | Artur Signell <artur.signell@itmill.com> | 2008-10-20 08:47:49 +0000 |
---|---|---|
committer | Artur Signell <artur.signell@itmill.com> | 2008-10-20 08:47:49 +0000 |
commit | cf75d2d5d12618852787d4a9346b66a934133cfd (patch) | |
tree | 69ab8cb14e3d4c85c7467698e8cd08041f7d9131 | |
parent | efe75a19f87aebcf3f4bc1c72f25d3954036b4c9 (diff) | |
download | vaadin-framework-cf75d2d5d12618852787d4a9346b66a934133cfd.tar.gz vaadin-framework-cf75d2d5d12618852787d4a9346b66a934133cfd.zip |
New OrderedLayout implementation and related fixes
svn changeset:5671/svn branch:trunk
17 files changed, 3518 insertions, 1748 deletions
diff --git a/WebContent/ITMILL/themes/default/caption/caption.css b/WebContent/ITMILL/themes/default/caption/caption.css index 761162d753..959b3ae37d 100644 --- a/WebContent/ITMILL/themes/default/caption/caption.css +++ b/WebContent/ITMILL/themes/default/caption/caption.css @@ -7,10 +7,9 @@ } .i-errorindicator { - width: 10px; + width: 12px; height: 16px; - padding-right:13px; - display: inline; + float: left; background: transparent url(../icons/16/error.png) no-repeat top right; } @@ -22,7 +21,6 @@ *+html .i-errorindicator { margin-left:-3px; } - .i-caption .i-icon { padding-right: 2px; vertical-align: middle; diff --git a/WebContent/ITMILL/themes/default/orderedlayout/orderedlayout.css b/WebContent/ITMILL/themes/default/orderedlayout/orderedlayout.css index 6e9ef37f92..43c26d19d3 100644 --- a/WebContent/ITMILL/themes/default/orderedlayout/orderedlayout.css +++ b/WebContent/ITMILL/themes/default/orderedlayout/orderedlayout.css @@ -12,7 +12,7 @@ } .i-orderedlayout-vspacing { - margin-top: 8px; + padding-top: 8px; } .i-orderedlayout-hspacing { padding-left: 8px; diff --git a/WebContent/ITMILL/themes/default/select/select.css b/WebContent/ITMILL/themes/default/select/select.css index 66803dc01f..fc4ca30b28 100644 --- a/WebContent/ITMILL/themes/default/select/select.css +++ b/WebContent/ITMILL/themes/default/select/select.css @@ -68,16 +68,13 @@ .i-filterselect { height: 23px; - background: transparent url(img/bg-left-filter.png) no-repeat; - margin-right: 1px; white-space: nowrap; text-align: left /* Force default alignment */ } .i-filterselect-input { - width: 99%; + background: transparent url(img/bg-left-filter.png) no-repeat; border: none; - background: transparent; height: 20px; margin: 0; padding: 3px 0 0 4px; @@ -90,7 +87,7 @@ .i-filterselect-button { float: right; - margin: -23px -1px 0 0; + margin-left: -23px; width: 25px; height: 23px; cursor: pointer; diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java b/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java index f26badf08a..00b72f9a42 100755 --- a/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java @@ -4,6 +4,7 @@ package com.itmill.toolkit.terminal.gwt.client; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -119,6 +120,8 @@ public class ApplicationConnection { /** redirectTimer scheduling interval in seconds */ private int sessionExpirationInterval; + private ArrayList<Widget> relativeSizeChanges = new ArrayList<Widget>();; + public ApplicationConnection(WidgetSet widgetSet, ApplicationConfiguration cnf) { this.widgetSet = widgetSet; @@ -552,6 +555,7 @@ public class ApplicationConnection { .get("changes"); Vector<Widget> updatedWidgets = new Vector<Widget>(); + relativeSizeChanges.clear(); for (int i = 0; i < changes.size(); i++) { try { @@ -591,6 +595,8 @@ public class ApplicationConnection { // Check which widgets' size has been updated Set<Widget> sizeUpdatedWidgets = new HashSet<Widget>(); + updatedWidgets.addAll(relativeSizeChanges); + for (Widget widget : updatedWidgets) { Size oldSize = componentOffsetSizes.get(widget); Size newSize = new Size(widget.getOffsetWidth(), widget @@ -624,7 +630,7 @@ public class ApplicationConnection { } if (html.length() != 0) { - INotification n = new INotification(1000 * 60 * 45); //45min + INotification n = new INotification(1000 * 60 * 45); // 45min n.addEventListener(new NotificationRedirect(url)); n.show(html, INotification.CENTERED_TOP, INotification.STYLE_SYSTEM); @@ -1016,11 +1022,17 @@ public class ApplicationConnection { // One or both is relative FloatSize relativeSize = new FloatSize(relativeWidth, relativeHeight); - componentRelativeSizes.put(component, relativeSize); + if (componentRelativeSizes.put(component, relativeSize) == null) { + // The component has changed from absolute size to relative size + relativeSizeChanges.add(component); + } handleComponentRelativeSize(component); } else if (relativeHeight < 0.0 && relativeWidth < 0.0) { - // No relative sizes - componentRelativeSizes.remove(component); + + if (componentRelativeSizes.remove(component) != null) { + // The component has changed from relative size to absolute size + relativeSizeChanges.add(component); + } } } @@ -1042,24 +1054,36 @@ public class ApplicationConnection { final Widget child = (Widget) childWidgets.next(); if (child instanceof Paintable) { - handleComponentRelativeSize(child); - } - if (child instanceof ContainerResizedListener) { - ((ContainerResizedListener) child).iLayout(); - } else if (child instanceof HasWidgets) { - final HasWidgets childContainer = (HasWidgets) child; - runDescendentsLayout(childContainer); + if (handleComponentRelativeSize(child)) { + /* + * Only need to propagate event if "child" has a relative + * size + */ + + if (child instanceof ContainerResizedListener) { + ((ContainerResizedListener) child).iLayout(); + } else if (child instanceof HasWidgets) { + final HasWidgets childContainer = (HasWidgets) child; + runDescendentsLayout(childContainer); + } + } } } } - public void handleComponentRelativeSize(Widget child) { + /** + * Converts relative sizes into pixel sizes. + * + * @param child + * @return true if the child has a relative size + */ + public boolean handleComponentRelativeSize(Widget child) { Widget widget = child; - FloatSize relativeSize = componentRelativeSizes.get(widget); + FloatSize relativeSize = getRelativeSize(child); if (relativeSize == null) { - return; + return false; } boolean horizontalScrollBar = false; @@ -1104,8 +1128,10 @@ public class ApplicationConnection { // getConsole().log( // "Widget " + widget.getClass().getName() + "/" // + widget.hashCode() + " relative height " - // + relativeSize.getHeight() + "%: " + height - // + "px"); + // + relativeSize.getHeight() + "% of " + // + renderSpace.getHeight() + "px (" + // + renderSpace.getReservedHeight() + // + "px reserved): " + height + "px"); widget.setHeight(height + "px"); } else { widget.setHeight(relativeSize.getHeight() + "%"); @@ -1145,8 +1171,10 @@ public class ApplicationConnection { // getConsole().log( // "Widget " + widget.getClass().getName() + "/" // + widget.hashCode() + " relative width " - // + relativeSize.getWidth() + "%: " + width - // + "px"); + // + relativeSize.getWidth() + "% of " + // + renderSpace.getWidth() + "px (" + // + renderSpace.getReservedWidth() + // + "px reserved): " + width + "px"); widget.setWidth(width + "px"); } else { widget.setWidth(relativeSize.getWidth() + "%"); @@ -1157,6 +1185,12 @@ public class ApplicationConnection { } } + return true; + + } + + private FloatSize getRelativeSize(Widget widget) { + return componentRelativeSizes.get(widget); } /** diff --git a/src/com/itmill/toolkit/terminal/gwt/client/Container.java b/src/com/itmill/toolkit/terminal/gwt/client/Container.java index 1a22a87d18..7d67c2f082 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/Container.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/Container.java @@ -55,12 +55,12 @@ public interface Container extends Paintable { * Called when a child components size has been updated in the rendering * phase. * - * @param child + * @param children * Set of child widgets whose size have changed * @return true if the size of the Container remains the same, false if the * event need to be propagated to the Containers parent */ - boolean requestLayout(Set<Paintable> child); + boolean requestLayout(Set<Paintable> children); /** * Returns the size currently allocated for the child component. diff --git a/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java b/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java index e68a7bac7e..bc094a527a 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetSet.java @@ -24,6 +24,7 @@ import com.itmill.toolkit.terminal.gwt.client.ui.ILink; import com.itmill.toolkit.terminal.gwt.client.ui.IListSelect; import com.itmill.toolkit.terminal.gwt.client.ui.IMenuBar; import com.itmill.toolkit.terminal.gwt.client.ui.INativeSelect; +import com.itmill.toolkit.terminal.gwt.client.ui.IOldOrderedLayout; import com.itmill.toolkit.terminal.gwt.client.ui.IOptionGroup; import com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayout; import com.itmill.toolkit.terminal.gwt.client.ui.IPanel; @@ -70,6 +71,9 @@ public class DefaultWidgetSet implements WidgetSet { } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IWindow" .equals(className)) { return new IWindow(); + } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IOldOrderedLayout" + .equals(className)) { + return new IOldOrderedLayout(); } else if ("com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayout" .equals(className)) { return new IOrderedLayout(); @@ -207,6 +211,8 @@ public class DefaultWidgetSet implements WidgetSet { return "com.itmill.toolkit.terminal.gwt.client.ui.IWindow"; } else if ("orderedlayout".equals(tag)) { return "com.itmill.toolkit.terminal.gwt.client.ui.IOrderedLayout"; + } else if ("oldorderedlayout".equals(tag)) { + return "com.itmill.toolkit.terminal.gwt.client.ui.IOldOrderedLayout"; } else if ("label".equals(tag)) { return "com.itmill.toolkit.terminal.gwt.client.ui.ILabel"; } else if ("link".equals(tag)) { diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java b/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java index 644d05c5ed..1033c128c2 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ICaption.java @@ -8,9 +8,9 @@ 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.HTML; -import com.itmill.toolkit.terminal.gwt.client.RenderInformation.Size; import com.itmill.toolkit.terminal.gwt.client.ui.Icon; +//TODO Move styles to CSS public class ICaption extends HTML { public static final String CLASSNAME = "i-caption"; @@ -25,11 +25,13 @@ public class ICaption extends HTML { private Element captionText; + private Element clearElement; + private final ApplicationConnection client; private boolean placedAfterComponent = false; - private Size requiredSpace = new Size(0, 0); + private int maxWidth = -1; /** * @@ -44,20 +46,35 @@ public class ICaption extends HTML { owner = component; setStyleName(CLASSNAME); sinkEvents(ITooltip.TOOLTIP_EVENTS); + + DOM.setStyleAttribute(getElement(), "whiteSpace", "nowrap"); + } - public void updateCaption(UIDL uidl) { + /** + * Updates the caption from UIDL. + * + * @param uidl + * @return true if the position where the caption should be placed has + * changed + */ + public boolean updateCaption(UIDL uidl) { setVisible(!uidl.getBooleanAttribute("invisible")); setStyleName(getElement(), "i-disabled", uidl.hasAttribute("disabled")); boolean isEmpty = true; + boolean wasPlacedAfterComponent = placedAfterComponent; + placedAfterComponent = true; if (uidl.hasAttribute("icon")) { if (icon == null) { icon = new Icon(client); + DOM.sinkEvents(icon.getElement(), Event.ONLOAD); + + Util.setFloat(icon.getElement(), "left"); placedAfterComponent = false; DOM.insertChild(getElement(), icon.getElement(), 0); } @@ -72,7 +89,11 @@ public class ICaption extends HTML { if (uidl.hasAttribute("caption")) { if (captionText == null) { - captionText = DOM.createSpan(); + captionText = DOM.createDiv(); + Util.setFloat(captionText, "left"); + DOM.setStyleAttribute(captionText, "overflow", "hidden"); + // DOM.setStyleAttribute(captionText, "textOverflow", + // "ellipsis"); DOM .insertChild(getElement(), captionText, icon == null ? 0 : 1); @@ -86,7 +107,7 @@ public class ICaption extends HTML { } DOM.setInnerText(captionText, c); } else { - // TODO should span also be removed + // TODO should element also be removed } if (uidl.hasAttribute("description")) { @@ -100,10 +121,13 @@ public class ICaption extends HTML { if (uidl.getBooleanAttribute("required")) { isEmpty = false; if (requiredFieldIndicator == null) { - requiredFieldIndicator = DOM.createSpan(); + requiredFieldIndicator = DOM.createDiv(); + Util.setFloat(requiredFieldIndicator, "left"); DOM.setInnerText(requiredFieldIndicator, "*"); DOM.setElementProperty(requiredFieldIndicator, "className", "i-required-field-indicator"); + + // TODO Insert before if errorIndicatorElement exists DOM.appendChild(getElement(), requiredFieldIndicator); } } else { @@ -127,6 +151,14 @@ public class ICaption extends HTML { errorIndicatorElement = null; } + if (clearElement == null) { + clearElement = DOM.createDiv(); + DOM.setStyleAttribute(clearElement, "clear", "both"); + DOM.setStyleAttribute(clearElement, "width", "0px"); + DOM.setStyleAttribute(clearElement, "height", "0px"); + DOM.setStyleAttribute(clearElement, "overflow", "hidden"); + DOM.appendChild(getElement(), clearElement); + } // Workaround for IE weirdness, sometimes returns bad height in some // circumstances when Caption is empty. See #1444 // IE7 bugs more often. I wonder what happens when IE8 arrives... @@ -141,8 +173,7 @@ public class ICaption extends HTML { } - requiredSpace.setWidth(getOffsetWidth()); - requiredSpace.setHeight(getOffsetHeight()); + return (wasPlacedAfterComponent != placedAfterComponent); } public void onBrowserEvent(Event event) { @@ -151,6 +182,17 @@ public class ICaption extends HTML { if (client != null && !DOM.compare(target, getElement())) { client.handleTooltipEvent(event, owner); } + + if (DOM.eventGetType(event) == Event.ONLOAD) { + setMaxWidth(maxWidth); + + // TODO: What if the caption's height changes drastically. Should we + // send the size updated message? + // Set<Widget> w = new HashSet<Widget>(); + // w.add(this); + // Util.componentSizeUpdated(w); + } + } public static boolean isNeeded(UIDL uidl) { @@ -183,10 +225,6 @@ public class ICaption extends HTML { return placedAfterComponent; } - public Size getRequiredSpace() { - return requiredSpace; - } - public int getWidth() { int width = 0; if (icon != null) { @@ -202,22 +240,89 @@ public class ICaption extends HTML { width += errorIndicatorElement.getOffsetWidth(); } - if (BrowserInfo.get().isIE6() && shouldBePlacedAfterComponent()) { - // FIXME: Should find out what the real issue is - // Workaround for IE6 weirdness - width += 3; - } - return width; } public int getHeight() { - return getOffsetHeight(); + return clearElement.getOffsetTop() - getElement().getOffsetTop(); } public void setAlignment(String alignment) { DOM.setStyleAttribute(getElement(), "textAlign", alignment); + } + + public void setMaxWidth(int maxWidth) { + + this.maxWidth = maxWidth; + DOM.setStyleAttribute(getElement(), "width", ""); + + if (icon != null) { + DOM.setStyleAttribute(icon.getElement(), "width", ""); + } + + if (captionText != null) { + DOM.setStyleAttribute(captionText, "width", ""); + } + + if (maxWidth < 0) { + return; + } + + int currentWidth = getWidth(); + if (currentWidth > maxWidth) { + // Needs to truncate and clip + int availableWidth = maxWidth; + + // ApplicationConnection.getConsole().log( + // "Caption maxWidth: " + maxWidth); + + DOM.setStyleAttribute(getElement(), "width", maxWidth + "px"); + if (requiredFieldIndicator != null) { + // ApplicationConnection.getConsole().log( + // "requiredFieldIndicator width: " + // + requiredFieldIndicator.getOffsetWidth()); + availableWidth -= requiredFieldIndicator.getOffsetWidth(); + } + + if (errorIndicatorElement != null) { + // ApplicationConnection.getConsole().log( + // "errorIndicatorElement width: " + // + errorIndicatorElement.getOffsetWidth()); + availableWidth -= errorIndicatorElement.getOffsetWidth(); + } + if (icon != null) { + if (availableWidth > icon.getOffsetWidth()) { + // ApplicationConnection.getConsole().log( + // "icon width: " + icon.getOffsetWidth()); + availableWidth -= icon.getOffsetWidth(); + } else { + // ApplicationConnection.getConsole().log( + // "icon forced width: " + availableWidth); + DOM.setStyleAttribute(icon.getElement(), "width", + availableWidth + "px"); + availableWidth = 0; + } + } + if (captionText != null) { + if (availableWidth > captionText.getOffsetWidth()) { + // ApplicationConnection.getConsole().log( + // "captionText width: " + // + captionText.getOffsetWidth()); + availableWidth -= captionText.getOffsetWidth(); + + } else { + // ApplicationConnection.getConsole().log( + // "captionText forced width: " + availableWidth); + DOM.setStyleAttribute(captionText, "width", availableWidth + + "px"); + availableWidth = 0; + } + + } + + } } + } diff --git a/src/com/itmill/toolkit/terminal/gwt/client/Util.java b/src/com/itmill/toolkit/terminal/gwt/client/Util.java index 10c3bcaee0..64ff918a12 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/Util.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/Util.java @@ -16,6 +16,7 @@ import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.RenderInformation.FloatSize; public class Util { @@ -195,9 +196,9 @@ public class Util { public static int measureHorizontalPadding(Element element, int paddingGuess) { String originalWidth = DOM.getStyleAttribute(element, "width"); int originalOffsetWidth = element.getOffsetWidth(); - int widthGuess = (originalOffsetWidth + paddingGuess); + int widthGuess = (originalOffsetWidth - paddingGuess); DOM.setStyleAttribute(element, "width", widthGuess + "px"); - int padding = widthGuess - element.getOffsetWidth(); + int padding = element.getOffsetWidth() - widthGuess; DOM.setStyleAttribute(element, "width", originalWidth); return padding; @@ -225,6 +226,23 @@ public class Util { } + public static String getSimpleName(Widget widget) { + if (widget == null) { + return "(null)"; + } + + String name = widget.getClass().getName(); + return name.substring(name.lastIndexOf('.') + 1); + } + + public static void setFloat(Element element, String value) { + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(element, "styleFloat", value); + } else { + DOM.setStyleAttribute(element, "cssFloat", value); + } + } + private static int detectedScrollbarSize = -1; public static int getNativeScrollbarSize() { @@ -272,4 +290,24 @@ public class Util { } + public static FloatSize parseRelativeSize(UIDL uidl) { + String w = uidl.hasAttribute("width") ? uidl + .getStringAttribute("width") : ""; + + String h = uidl.hasAttribute("height") ? uidl + .getStringAttribute("height") : ""; + + float relativeWidth = Util.parseRelativeSize(w); + float relativeHeight = Util.parseRelativeSize(h); + + if (relativeHeight >= 0.0 || relativeWidth >= 0.0) { + // One or both is relative + FloatSize relativeSize = new FloatSize(relativeWidth, + relativeHeight); + return relativeSize; + } else { + return null; + } + + } } diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IFilterSelect.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IFilterSelect.java index dd2d3286d5..8e565212ae 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IFilterSelect.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IFilterSelect.java @@ -812,4 +812,14 @@ public class IFilterSelect extends Composite implements Paintable, Field, public void focus() { tb.setFocus(true); } + + @Override + public void setWidth(String width) { + super.setWidth(width); + + int padding = Util.measureHorizontalPadding(tb.getElement(), 4); + tb.setWidth((getOffsetWidth() - padding - popupOpener.getOffsetWidth()) + + "px"); + + } } diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IOldOrderedLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOldOrderedLayout.java new file mode 100644 index 0000000000..f6d9848952 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOldOrderedLayout.java @@ -0,0 +1,1603 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.ICaption; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.RenderInformation; +import com.itmill.toolkit.terminal.gwt.client.RenderSpace; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.RenderInformation.Size; + +/** + * Full implementation of OrderedLayout client peer. + * + * This class implements all features of OrderedLayout. It currently only + * supports use through UIDL updates. Direct client side use is not (currently) + * suported in all operation modes. + * + * + * <h2>Features</h2> + * + * <h3>Orientation</h3> + * + * <p> + * Orientation of the ordered layout declared whether the children are layouted + * horizontally or vertically. + * </p> + * + * <img src="doc-files/IOrderedLayout_horizontal.png"/> <img + * src="doc-files/IOrderedLayout_vertical.png"/> + * + * <h3>Spacing</h3> + * + * <p> + * Spacing determines if there should be space between the children. Note that + * this does not imply margin. + * </p> + * + * <img src="doc-files/IOrderedLayout_horizontal_spacing.png"/> <img + * src="doc-files/IOrderedLayout_vertical_spacing.png"/> + * + * <h3>Margin</h3> + * + * <p> + * Margin determines if there should be margin around children. Note that this + * does not imply spacing. + * </p> + * + * <img src="doc-files/IOrderedLayout_margin.png"/> + * + * <h3>Positioning the caption, icon, required indicator and error</h3> + * + * <p> + * If the child lets the layout to handle captions, by icon, caption, required + * marker (*) and error icon are placed on top of the component area. Icon will + * be first and is followed by the caption. Required marker is placed right + * after the caption text and error icon is placed last. Note that all of these + * are optional: + * </p> + * + * <img src="doc-files/IOrderedLayout_normal_caption.png"/> + * + * <p> + * If the child lets the layout to handle captions, but the caption and icon are + * both missing, no line is reserved for the required marker (*) and error icon. + * Instead they are placed on the right side of the top of the component area. + * Required marker is placed right after the component text and error icon is + * placed last. If the component is tall, the indicators are aligned along the + * top of the component. Note that both of these indicators are optional: + * </p> + * + * <img src="doc-files/IOrderedLayout_no_caption.png"/> + * + * <p> + * In case the child want to handle the caption by itself, layout does not + * repeat the caption. + * </p> + * + * <img src="doc-files/IOrderedLayout_component_handles_the_caption.png"/> + * + * <h3>Aligning the children</h3> + * + * <p> + * The children of the layout can be aligned horizontally and vertically: + * </p> + * + * <img src="doc-files/IOrderedLayout_alignment.png"/> + * + * <h3>Fixed height, width or both</h3> + * + * <p> + * When no size is explicitly specified, the size of the layout depends on the + * size of its children. If the size if specified, either explicitly or as + * percertages of the parent size, the size is equally divided between the + * children. In case some children might overflow out of the given space, they + * are cut to fit the given space. Note that the size can be independently + * specified for horizontal and vertical dimensions and is independent of the + * orientation. For example, layout can be horizontal and have fixed 300px + * height, but still measure its width from the child sizes. + * </p> + * + * <p> + * Horizontal layout with fixed width of 300px and height of 150px: + * </p> + * <img src="doc-files/IOrderedLayout_w300_h150.png"/> + * + * <p> + * Horizontal layout with fixed width of 300px: + * </p> + * <img src="doc-files/IOrderedLayout_w300.png"/> + * + * <p> + * Horizontal layout with fixed height of 150px: + * </p> + * <img src="doc-files/IOrderedLayout_h150.png"/> + * + * + * <h3>CSS attributes</h3> + * + * <p> + * Sizes for marginals and spacing can be specified for the ordered layout in + * CSS. For example, here are the defaults for OrderedLayout: + * </p> + * + * <pre> + * .i-orderedlayout-margin-top { + * padding-top: 15px; + * } + * .i-orderedlayout-margin-right { + * padding-right: 18px; + * } + * .i-orderedlayout-margin-bottom { + * padding-bottom: 15px; + * } + * .i-orderedlayout-margin-left { + * padding-left: 18px; + * } + * + * .i-orderedlayout-vspacing { + * margin-top: 8px; + * } + * .i-orderedlayout-hspacing { + * padding-left: 8px; + * } + * </pre> + * + * <p> + * When a style-name is set for the layout, this name is included in the style. + * Note that the unspecified dimensions still default to the values given for + * the layout without style. For example, if we would like to give each layout + * with "tested-layout" style quite a bit larger right margin: + * </p> + * + * <pre> + * .i-orderedlayout-tested-layout-margin-right { + * padding-right: 100px; + * } + * </pre> + * + * <p> + * Here is the rendering with getMargin(true). Note that all the other margins + * are set to the default values defined for the layout without stylename: + * </p> + * <img src="doc-files/IOrderedLayout_special-margin.png"/> + * + * + * <h3>DOM-structure</h3> + * + * Note that DOM-structure is an implementation specific and might change in the + * future versions of IT Mill Toolkit. The purpose of this documentation is to + * to ease reading of the implementation and thus to make implementation of your + * own layouts easier. + * + * <div style="border: 1px solid black; padding: 3px;">OUTERDIV + * + * <div style="border: 1px solid black; padding: 3px;">Optional STRUCTURE + * + * <div style="border: 1px solid black; padding: 3px;">CHILDWRAPPER (for each + * child) + * + * <div style="border: 1px solid black; padding: 3px;">Optional ALIGNMENTWRAPPER + * + * <div style="border: 1px solid black; padding: 3px;">Optional CLIPPER + * + * <div style="border: 1px solid black; padding: 3px;">CAPTION <span + * style="border: 1px solid black; padding: 3px;">ICON-IMG</span> <span + * style="border: 1px solid black; padding: 3px;">CAPTION-SPAN</span> <span + * style="border: 1px solid black; padding: 3px;">REQUIRED-SPAN</span> <span + * style="border: 1px solid black; padding: 3px;">ERRORINDICATOR-DIV</span> + * </div> + * + * <div style="border: 1px solid black; padding: 3px; margin-top:3px;">Widget + * component</div> + * + * </div></div></div> + * + * </div></div> + * + * <p> + * Notes: + * <ul> + * <li>If caption and icon are missing from child, <i>Widget component</i> and + * <i>CAPTION</i> elements are swithched</li> + * <li>If either child manages caption, or it has no caption, icon, required or + * error, <i>CAPTION</i> element is not needed at all</li> + * <li>If layout is vertical and its width is specified, <i>Optional + * STRUCTURE</i> is not present. Otherwise it looks like <div + * style="border: 1px solid black; padding: 3px;">TABLE <div + * style="border: 1px solid black; padding: 3px;">TBODY <div + * style="border: 1px solid black; padding: 3px;">Optional TR only included in + * case of horizontal layouts </div></div></div></li> + * <li><i>CHILDWRAPPER</i> is a DIV in case of the layout is vertical and width + * is specified. For vertical layouts with unknown width it is TR-TD. For + * horizontal layouts, it is TR-TD.</li> + * <li><i>Optionasl ALIGNMENTWRAPPER</i> are only used alignment is not the + * default - top-left. Alignment wrapper structure is + * TABLE-TBODY-TR-TD-TABLE-TBODY-TR-TD, where the outer table td is used to + * specify the alignments and inner table td to reset the table defaults to + * top-left.</li> + * <li><i>Optional CLIPPERDIV</i> included in the structure only if alignment + * structure is in place and <i>CHILDWRAPPER</i> is not a div and thus can not + * be used for clipping</li> + * </ul> + * </p> + * + * + * @author IT Mill Ltd + */ +public class IOldOrderedLayout extends Panel implements Container, + ContainerResizedListener { + + public static final String CLASSNAME = "i-orderedlayout"; + + public static final int ORIENTATION_VERTICAL = 0; + public static final int ORIENTATION_HORIZONTAL = 1; + + /** + * If margin and spacing values has been calculated, this holds the values + * for the given UIDL style attribute . + */ + private static HashMap measuredMargins = new HashMap(); + + /** + * Spacing. Correct values will be set in + * updateMarginAndSpacingFromCSS(UIDL) + */ + private int hSpacing, vSpacing; + + /** + * Margin. Correct values will be set in updateMarginAndSpacingFromCSS(UIDL) + */ + private int marginTop, marginBottom, marginLeft, marginRight; + + int orientationMode = ORIENTATION_VERTICAL; + + protected ApplicationConnection client; + + /** + * Reference to Element where wrapped childred are contained. Normally a + * DIV, TR or a TBODY element. + */ + private Element wrappedChildContainer; + + /** + * List of child widgets. This is not the list of wrappers, but the actual + * widgets + */ + private final Vector childWidgets = new Vector(); + + /** + * In table mode, the root element is table instead of div. + */ + private boolean tableMode = false; + + /** + * Root element. This element points to the outmost table-element (in table + * mode) or outmost div (in non-table-mode). This non-table-mode this equals + * to the getElement(). + */ + private Element root = null; + + /** + * Last set width of the component. Null if undefined (instead of being ""). + */ + private String width = null; + + /** + * Last set height of the component. Null if undefined (instead of being + * ""). + */ + private String height = null; + /** + * List of child widget wrappers. These wrappers are in exact same indexes + * as the widgets in childWidgets list. + */ + private final Vector<WidgetWrapper> childWidgetWrappers = new Vector<WidgetWrapper>(); + + /** Whether the component has spacing enabled. */ + private boolean hasComponentSpacing; + + /** Information about margin states. */ + private MarginInfo margins = new MarginInfo(0); + + /** + * Flag that indicates that the child layouts must be updated as soon as + * possible. This will be done in the end of updateFromUIDL. + */ + private boolean childLayoutsHaveChanged = false; + + private boolean isRendering = false; + + private RenderInformation renderInformation = new RenderInformation(); + + /** + * Construct the DOM of the orderder layout. + * + * <p> + * There are two modes - vertical and horizontal. + * <ul> + * <li>Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap + * ( child ))).</li> + * <li>Horizontal mode uses structure: table ( tbody ( tr-childcontainer ( + * td-wrap ( child ) td-wrap ( child) )) )</li> + * </ul> + * where root and childcontainer refer to the root element and the element + * that contain WidgetWrappers. + * </p> + * + */ + public IOldOrderedLayout() { + wrappedChildContainer = root = DOM.createDiv(); + setElement(wrappedChildContainer); + setStyleName(CLASSNAME); + } + + /** + * Update orientation, if it has changed. + * + * @param newOrientationMode + */ + private void rebuildRootDomStructure(int oldOrientationMode) { + + // Should we have table as a root element? + boolean newTableMode = !(orientationMode == ORIENTATION_VERTICAL && width != null); + + // Already in correct mode? + if (oldOrientationMode == orientationMode && newTableMode == tableMode) { + return; + } + boolean oldTableMode = tableMode; + tableMode = newTableMode; + + /* + * If the child are not detached before the parent is cleared with + * setInnerHTML the children will also be cleared in IE + */ + if (BrowserInfo.get().isIE()) { + while (true) { + Element child = DOM.getFirstChild(getElement()); + if (child != null) { + DOM.removeChild(getElement(), child); + } else { + break; + } + } + } + + // Constuct base DOM-structure and clean any already attached + // widgetwrappers from DOM. + if (tableMode) { + String structure = "<table cellspacing=\"0\" cellpadding=\"0\""; + + if (orientationMode == ORIENTATION_HORIZONTAL) { + // Needed for vertical alignment to work + structure += " height=\"100%\""; + } + structure += "><tbody>" + + (orientationMode == ORIENTATION_HORIZONTAL ? "<tr valign=\"top\"></tr>" + : "") + "</tbody></table>"; + + DOM.setInnerHTML(getElement(), structure); + root = DOM.getFirstChild(getElement()); + // set TBODY to be the wrappedChildContainer + wrappedChildContainer = DOM.getFirstChild(root); + // In case of horizontal layouts, we must user TR instead of TBODY + if (orientationMode == ORIENTATION_HORIZONTAL) { + wrappedChildContainer = DOM + .getFirstChild(wrappedChildContainer); + } + } else { + root = wrappedChildContainer = getElement(); + DOM.setInnerHTML(getElement(), ""); + } + + // Reinsert all widget wrappers to this container + final int currentOrientationMode = orientationMode; + for (int i = 0; i < childWidgetWrappers.size(); i++) { + WidgetWrapper wr = childWidgetWrappers.get(i); + orientationMode = oldOrientationMode; + tableMode = oldTableMode; + Element oldWrElement = wr.getElementWrappingWidgetAndCaption(); + orientationMode = currentOrientationMode; + tableMode = newTableMode; + String classe = DOM.getElementAttribute(oldWrElement, "class"); + wr.resetRootElement(); + Element newWrElement = wr.getElementWrappingWidgetAndCaption(); + if (classe != null && classe.length() > 0) { + DOM.setElementAttribute(newWrElement, "class", classe); + } + while (DOM.getChildCount(oldWrElement) > 0) { + Element c = DOM.getFirstChild(oldWrElement); + DOM.removeChild(oldWrElement, c); + DOM.appendChild(newWrElement, c); + } + + DOM.appendChild(wrappedChildContainer, wr.getElement()); + } + + // Update child layouts + childLayoutsHaveChanged = true; + } + + /** Update the contents of the layout from UIDL. */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + isRendering = true; + this.client = client; + + // Only non-cached UIDL:s can introduce changes + if (uidl.getBooleanAttribute("cached")) { + return; + } + + updateMarginAndSpacingSizesFromCSS(uidl); + + // Update sizes, ... + if (client.updateComponent(this, uidl, true)) { + return; + } + + // Rebuild DOM tree root if necessary + int oldO = orientationMode; + orientationMode = "horizontal".equals(uidl + .getStringAttribute("orientation")) ? ORIENTATION_HORIZONTAL + : ORIENTATION_VERTICAL; + rebuildRootDomStructure(oldO); + + // Handle component spacing later in handleAlignments() method + hasComponentSpacing = uidl.getBooleanAttribute("spacing"); + + // Collect the list of contained widgets after this update + final Vector newWidgets = new Vector(); + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL uidlForChild = (UIDL) it.next(); + final Paintable child = client.getPaintable(uidlForChild); + newWidgets.add(child); + } + + // Iterator for old widgets + final Iterator oldWidgetsIterator = (new Vector(childWidgets)) + .iterator(); + + // Iterator for new widgets + final Iterator newWidgetsIterator = newWidgets.iterator(); + + // Iterator for new UIDL + final Iterator newUIDLIterator = uidl.getChildIterator(); + + // List to collect all now painted widgets to in order to remove + // unpainted ones later + final Vector paintedWidgets = new Vector(); + + final Vector childsToPaint = new Vector(); + + // Add any new widgets to the ordered layout + Widget oldChild = null; + while (newWidgetsIterator.hasNext()) { + + final Widget newChild = (Widget) newWidgetsIterator.next(); + final UIDL newChildUIDL = (UIDL) newUIDLIterator.next(); + + // Remove any unneeded old widgets + if (oldChild == null && oldWidgetsIterator.hasNext()) { + // search for next old Paintable which still exists in layout + // and delete others + while (oldWidgetsIterator.hasNext()) { + oldChild = (Widget) oldWidgetsIterator.next(); + // now oldChild is an instance of Paintable + if (paintedWidgets.contains(oldChild)) { + continue; + } else if (newWidgets.contains(oldChild)) { + break; + } else { + remove(oldChild); + oldChild = null; + } + } + } + + if (oldChild == null) { + // we are adding components to the end of layout + add(newChild); + } else if (newChild == oldChild) { + // child already attached in correct position + oldChild = null; + } else if (hasChildComponent(newChild)) { + + // current child has been moved, re-insert before current + // oldChild + add(newChild, childWidgets.indexOf(oldChild)); + + } else { + // insert new child before old one + add(newChild, childWidgets.indexOf(oldChild)); + } + + // Update the child component + childsToPaint.add(new Object[] { newChild, newChildUIDL }); + + // Add this newly handled component to the list of painted + // components + paintedWidgets.add(newChild); + } + + // Remove possibly remaining old widgets which were not in painted UIDL + while (oldWidgetsIterator.hasNext()) { + oldChild = (Widget) oldWidgetsIterator.next(); + if (!newWidgets.contains(oldChild)) { + remove(oldChild); + } + } + + // Handle component alignments + handleAlignmentsSpacingAndMargins(uidl); + + // Reset sizes for the children + updateChildSizes(); + + // Paint children + for (int i = 0; i < childsToPaint.size(); i++) { + Object[] t = (Object[]) childsToPaint.get(i); + ((Paintable) t[0]).updateFromUIDL((UIDL) t[1], client); + } + + // Update child layouts + // TODO This is most probably unnecessary and should be done within + // update Child H/W + // if (childLayoutsHaveChanged) { + // client.runDescendentsLayout(this); + // childLayoutsHaveChanged = false; + // } + + /* Store the rendered size so we later can see if it has changed */ + if (renderInformation.updateSize(root)) { + client.runDescendentsLayout(this); + } + + isRendering = false; + + } + + private void updateMarginAndSpacingSizesFromCSS(UIDL uidl) { + + // Style for this layout + String style = uidl.getStringAttribute("style"); + if (style == null) { + style = ""; + } + + // Try to find measured from cache + int[] r = (int[]) measuredMargins.get(style); + + // Measure from DOM + if (r == null) { + r = new int[] { 0, 0, 0, 0, 0, 0 }; + + // Construct DOM for measurements + Element e1 = DOM.createTable(); + DOM.setStyleAttribute(e1, "position", "absolute"); + DOM.setElementProperty(e1, "cellSpacing", "0"); + DOM.setElementProperty(e1, "cellPadding", "0"); + Element e11 = DOM.createTBody(); + Element e12 = DOM.createTR(); + Element e13 = DOM.createTD(); + Element e2 = DOM.createDiv(); + Element e3 = DOM.createDiv(); + DOM.setStyleAttribute(e3, "width", "100px"); + DOM.setStyleAttribute(e3, "height", "100px"); + DOM.appendChild(getElement(), e1); + DOM.appendChild(e1, e11); + DOM.appendChild(e11, e12); + DOM.appendChild(e12, e13); + DOM.appendChild(e13, e2); + DOM.appendChild(e2, e3); + DOM.setInnerText(e3, "."); + + // Measure different properties + final String[] classes = { "margin-top", "margin-right", + "margin-bottom", "margin-left", "vspacing", "hspacing" }; + for (int c = 0; c < 6; c++) { + StringBuffer styleBuf = new StringBuffer(); + final String primaryName = getStylePrimaryName(); + styleBuf.append(primaryName + "-" + classes[c]); + if (style.length() > 0) { + final String[] styles = style.split(" "); + for (int i = 0; i < styles.length; i++) { + styleBuf.append(" "); + styleBuf.append(primaryName); + styleBuf.append("-"); + styleBuf.append(styles[i]); + styleBuf.append("-"); + styleBuf.append(classes[c]); + } + } + DOM.setElementProperty(e2, "className", styleBuf.toString()); + + // Measure + r[c] = DOM.getElementPropertyInt(e1, + (c % 2) == 1 ? "offsetWidth" : "offsetHeight") - 100; + } + + // Clean-up + DOM.removeChild(getElement(), e1); + + // Cache for further use + measuredMargins.put(style, r); + } + + // Set the properties + marginTop = r[0]; + marginRight = r[1]; + marginBottom = r[2]; + marginLeft = r[3]; + vSpacing = r[4]; + hSpacing = r[5]; + } + + /** + * While setting width, ensure that margin div is also resized properly. + * Furthermore, enable/disable fixed mode + */ + public void setWidth(String newWidth) { + super.setWidth(newWidth); + + if (newWidth == null || newWidth.equals("")) { + width = null; + } else { + width = newWidth; + } + + // Update child layouts + childLayoutsHaveChanged = true; + } + + /** + * While setting height, ensure that margin div is also resized properly. + * Furthermore, enable/disable fixed mode + */ + public void setHeight(String newHeight) { + super.setHeight(newHeight); + height = newHeight == null || "".equals(newHeight) ? null : newHeight; + + // Update child layouts + childLayoutsHaveChanged = true; + } + + /** Recalculate and apply the space given for each child in this layout. */ + private void updateChildSizes() { + + int numChild = childWidgets.size(); + int childHeightTotal = -1; + int childHeightDivisor = 1; + int childWidthTotal = -1; + int childWidthDivisor = 1; + + // Vertical layout is calculated by us + if (height != null) { + + if (height.endsWith("px")) { + childHeightTotal = Integer.parseInt(height.substring(0, height + .length() - 2)); + } else { + // TODO This might be wrong but only reached if height is + // specified by other means than pixels or % + childHeightTotal = getElement().getOffsetHeight(); + } + + // Calculate the space for fixed contents minus marginals + childHeightTotal = getElement().getOffsetHeight(); + + childHeightTotal -= margins.hasTop() ? marginTop : 0; + childHeightTotal -= margins.hasBottom() ? marginBottom : 0; + + // Reduce spacing from the size + if (hasComponentSpacing) { + childHeightTotal -= ((orientationMode == ORIENTATION_HORIZONTAL) ? hSpacing + : vSpacing) + * (numChild - 1); + } + + // Total space is divided among the children + if (orientationMode == ORIENTATION_VERTICAL) { + childHeightDivisor = numChild; + } + } + + // layout is calculated by us + if (width != null) { + + if (width.endsWith("px")) { + childWidthTotal = Integer.parseInt(width.substring(0, width + .length() - 2)); + } else { + // TODO This might be wrong but only reached if width is + // specified by other means than pixels or % + childWidthTotal = getElement().getOffsetWidth(); + } + + childWidthTotal -= margins.hasLeft() ? marginLeft : 0; + childWidthTotal -= margins.hasRight() ? marginRight : 0; + + // Reduce spacing from the size + if (hasComponentSpacing + && orientationMode == ORIENTATION_HORIZONTAL) { + childWidthTotal -= hSpacing * (numChild - 1); + } + + // Total space is divided among the children + if (orientationMode == ORIENTATION_HORIZONTAL) { + childWidthDivisor = numChild; + } + } + + // Set the sizes for each child + for (Iterator i = childWidgetWrappers.iterator(); i.hasNext();) { + int w, h; + if (childHeightDivisor > 1) { + h = Math.round(((float) childHeightTotal) + / (childHeightDivisor--)); + childHeightTotal -= h; + } else { + h = childHeightTotal; + } + if (childWidthDivisor > 1) { + w = Math.round(((float) childWidthTotal) + / (childWidthDivisor--)); + childWidthTotal -= w; + } else { + w = childWidthTotal; + } + WidgetWrapper ww = (WidgetWrapper) i.next(); + ww.forceSize(w, h); + + } + } + + /** Parse alignments from UIDL and pass whem to correct widgetwrappers */ + private void handleAlignmentsSpacingAndMargins(UIDL uidl) { + + // Only update margins when they have changed + // TODO this should be optimized to avoid reupdating these + margins = new MarginInfo(uidl.getIntAttribute("margins")); + + // Component alignments as a comma separated list. + // See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for + // possible values. + final int[] alignments = uidl.getIntArrayAttribute("alignments"); + int alignmentIndex = 0; + + // Insert alignment attributes + final Iterator it = childWidgetWrappers.iterator(); + + while (it.hasNext()) { + + // Calculate alignment info + final AlignmentInfo ai = new AlignmentInfo( + alignments[alignmentIndex++]); + + final WidgetWrapper wr = (WidgetWrapper) it.next(); + + wr.setAlignment(ai.getVerticalAlignment(), ai + .getHorizontalAlignment()); + + // Handle spacing and margins in this loop as well + wr.setSpacingAndMargins(alignmentIndex == 1, + alignmentIndex == alignments.length); + } + } + + /** + * Wrapper around single child in the layout. + * + * This helper also manages spacing, margins and alignment for individual + * cells handling. It also can put hard size limits for its contens by + * clipping the content to given pixel size. + * + */ + class WidgetWrapper { + + /** + * When alignment table structure is used, these elements correspond to + * the TD elements within the structure. If alignment is not used, these + * are null. + */ + Element alignmentTD, innermostTDinAlignmnetStructure; + + /** + * When clipping must be done and the element wrapping clipped content + * would be TD instead of DIV, this element points to additional DIV + * that is used for clipping. + */ + Element clipperDiv; + + /** Caption element when used. */ + ICaption caption = null; + Size captionSize = new Size(-1, -1); + + /** + * Last set pixel height for the wrapper. -1 if vertical clipping is not + * used. + */ + int lastForcedPixelHeight = -1; + + /** + * Last set pixel width for the wrapper. -1 if horizontal clipping is + * not used. + */ + int lastForcedPixelWidth = -1; + + int horizontalPadding = 0, verticalPadding = 0; + + /** Widget Wrapper root element */ + Element wrapperElement; + + private String horizontalAlignment = "left"; + + private String verticalAlignment = "top"; + + /** Set the root element */ + public WidgetWrapper() { + resetRootElement(); + } + + public Element getElement() { + return wrapperElement; + } + + /** + * Set the width and height given for the wrapped widget in pixels. + * + * -1 if unconstrained. + */ + public void forceSize(int pixelWidth, int pixelHeight) { + + // If we are already at the correct size, do nothing + if (lastForcedPixelHeight == pixelHeight + && lastForcedPixelWidth == pixelWidth) { + return; + } + + // Clipper DIV is needed? + if (tableMode && (pixelHeight >= 0 || pixelWidth >= 0)) { + if (clipperDiv == null) { + createClipperDiv(); + } + } + + // ClipperDiv is not needed, remove if necessary + else if (clipperDiv != null) { + removeClipperDiv(); + } + + Element e = clipperDiv != null ? clipperDiv + : getElementWrappingAlignmentStructures(); + + // Overflow + DOM.setStyleAttribute(e, "overflow", pixelWidth < 0 + && pixelHeight < 0 ? "" : "hidden"); + + // Set size + DOM.setStyleAttribute(e, "width", pixelWidth < 0 ? "" : pixelWidth + + "px"); + + if (pixelHeight >= 0) { + DOM.setStyleAttribute(e, "height", pixelHeight + "px"); + } else { + if (e == clipperDiv && !BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(e, "height", "100%"); + } + + } + + // Set cached values + lastForcedPixelWidth = pixelWidth; + lastForcedPixelHeight = pixelHeight; + } + + /** Create a DIV for clipping the child */ + private void createClipperDiv() { + clipperDiv = DOM.createDiv(); + final Element e = getElementWrappingClipperDiv(); + String clipperClass = "i-orderedlayout-cl-" + + (orientationMode == ORIENTATION_HORIZONTAL ? "h" : "v"); + + String elementClass = e.getClassName(); + + if (elementClass != null && elementClass.length() > 0) { + clipperClass += " " + elementClass; + } + + while (DOM.getChildCount(e) > 0) { + final Element c = DOM.getFirstChild(e); + DOM.removeChild(e, c); + DOM.appendChild(clipperDiv, c); + } + + if (elementClass != null && elementClass.length() > 0) { + e.setClassName(""); + } + + DOM.appendChild(e, clipperDiv); + clipperDiv.setClassName(clipperClass); + + } + + /** Undo createClipperDiv() */ + private void removeClipperDiv() { + final Element e = getElementWrappingClipperDiv(); + String clipperClass = clipperDiv.getClassName(); + + String elementClass = clipperClass.replaceAll( + "i-orderedlayout-cl-.", "").trim(); + while (DOM.getChildCount(clipperDiv) > 0) { + final Element c = DOM.getFirstChild(clipperDiv); + DOM.removeChild(clipperDiv, c); + DOM.appendChild(e, c); + } + DOM.removeChild(e, clipperDiv); + clipperDiv = null; + if (elementClass != null && elementClass.length() > 0) { + e.setClassName(elementClass); + } + } + + /** + * Get the element containing the caption and the wrapped widget. + * Returned element can one of the following: + * <ul> + * <li>(a) Root DIV of the WrapperElement when not in tableMode</li> + * <li>(b) TD in just below the root TR of the WrapperElement when in + * tableMode</li> + * <li>(c) clipperDiv inside the (a) or (b)</li> + * <li>(d) The innermost TD within alignment structures located in (a), + * (b) or (c)</li> + * </ul> + * + * @return Element described above + */ + private Element getElementWrappingWidgetAndCaption() { + + // When alignment is used, we will can safely return the innermost + // TD + if (innermostTDinAlignmnetStructure != null) { + return innermostTDinAlignmnetStructure; + } + + // In all other cases element wrapping the potential alignment + // structures is the correct one + return getElementWrappingAlignmentStructures(); + } + + /** + * Get the element where alignment structures should be placed in if + * they are in use. + * + * Returned element can one of the following: + * <ul> + * <li>(a) Root DIV of the WrapperElement when not in tableMode</li> + * <li>(b) TD in just below the root TR of the WrapperElement when in + * tableMode</li> + * <li>(c) clipperDiv inside the (a) or (b)</li> + * </ul> + * + * @return Element described above + */ + private Element getElementWrappingAlignmentStructures() { + + // Clipper DIV wraps the alignment structures if present + if (clipperDiv != null) { + return clipperDiv; + } + + // When Clipper DIV is not used, we just give the element + // that would wrap it if it would be used + return getElementWrappingClipperDiv(); + } + + /** + * Get the element where clipperDiv should be placed in if they it is in + * use. + * + * Returned element can one of the following: + * <ul> + * <li>(a) Root DIV of the WrapperElement when not in tableMode</li> + * <li>(b) TD in just below the root TR of the WrapperElement when in + * tableMode</li> + * </ul> + * + * @return Element described above + */ + private Element getElementWrappingClipperDiv() { + + // Only vertical layouts in non-table mode use TR as root, for the + // rest we can safely give root element + if (!tableMode || orientationMode == ORIENTATION_HORIZONTAL) { + return wrapperElement; + } + + // The root is TR, we'll thus give the TD that is immediately within + // the root + return DOM.getFirstChild(wrapperElement); + } + + /** + * Create tr, td or div - depending on the orientation of the layout and + * set it as root. + * + * All contents of the wrapper are cleared. Caller is responsible for + * preserving the contents and moving them into new root. + * + * @return Previous root element. + */ + private void resetRootElement() { + // TODO Should we remove the existing element? + if (tableMode) { + if (orientationMode == ORIENTATION_HORIZONTAL) { + wrapperElement = DOM.createTD(); + DOM.setStyleAttribute(wrapperElement, "height", "100%"); + } else { + wrapperElement = DOM.createTR(); + DOM.appendChild(wrapperElement, DOM.createTD()); + } + } else { + wrapperElement = DOM.createDiv(); + // Apply 'hasLayout' for IE (needed to get accurate dimension + // calculations) + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(wrapperElement, "zoom", "1"); + } + } + + // Clear any references to intermediate elements + clipperDiv = alignmentTD = innermostTDinAlignmnetStructure = null; + } + + /** Update the caption of the element contained in this wrapper. */ + public void updateCaption(UIDL uidl, Paintable paintable) { + + final Widget widget = (Widget) paintable; + final Element captionWrapper = getElementWrappingWidgetAndCaption(); + + boolean captionDimensionOrPositionUpdated = false; + + // The widget needs caption + if (ICaption.isNeeded(uidl)) { + + // If the caption element is missing, create it + boolean justAdded = false; + if (caption == null) { + justAdded = true; + caption = new ICaption(paintable, client); + } + + // Update caption contents + caption.updateCaption(uidl); + + caption.setAlignment(horizontalAlignment); + + int captionHeight = caption.getElement().getOffsetHeight(); + int captionWidth = caption.getElement().getOffsetWidth(); + if (captionHeight != captionSize.getHeight() + || captionWidth != captionSize.getWidth()) { + captionSize = new Size(captionWidth, captionHeight); + captionDimensionOrPositionUpdated = true; + } + + final boolean after = caption.shouldBePlacedAfterComponent(); + final Element captionElement = caption.getElement(); + final Element widgetElement = widget.getElement(); + + if (justAdded) { + + // As the caption has just been created, insert it to DOM + if (after) { + DOM.appendChild(captionWrapper, captionElement); + DOM.setElementAttribute(captionWrapper, "class", + "i-orderedlayout-w"); + caption.addStyleName("i-orderedlayout-c"); + widget.addStyleName("i-orderedlayout-w-e"); + } else { + DOM.insertChild(captionWrapper, captionElement, 0); + } + + captionDimensionOrPositionUpdated = true; + } else if (after == (DOM.getChildIndex(captionWrapper, + widgetElement) > DOM.getChildIndex(captionWrapper, + captionElement))) { + // Caption exists. Move it to correct position if it is not + // there + Element firstElement = DOM.getChild(captionWrapper, DOM + .getChildCount(captionWrapper) - 2); + if (firstElement != null) { + DOM.removeChild(captionWrapper, firstElement); + DOM.appendChild(captionWrapper, firstElement); + } + DOM.setElementAttribute(captionWrapper, "class", + after ? "i-orderedlayout-w" : ""); + if (after) { + caption.addStyleName("i-orderedlayout-c"); + widget.addStyleName("i-orderedlayout-w-e"); + } else { + widget.removeStyleName("i-orderedlayout-w-e"); + caption.removeStyleName("i-orderedlayout-w-c"); + } + + captionDimensionOrPositionUpdated = true; + } else { + if (after) { + widget.addStyleName("i-orderedlayout-w-e"); + } else { + widget.removeStyleName("i-orderedlayout-w-e"); + } + } + + } else { + // Caption is not needed + + // Remove existing caption from DOM + if (caption != null) { + DOM.removeChild(captionWrapper, caption.getElement()); + caption = null; + DOM.setElementAttribute(captionWrapper, "class", ""); + widget.removeStyleName("i-orderedlayout-w-e"); + + captionDimensionOrPositionUpdated = true; + } + } + + if (captionDimensionOrPositionUpdated) { + client.handleComponentRelativeSize((Widget) paintable); + } + } + + /** + * Set alignments for this wrapper. + */ + void setAlignment(String verticalAlignment, String horizontalAlignment) { + this.verticalAlignment = verticalAlignment; + this.horizontalAlignment = horizontalAlignment; + + // use one-cell table to implement horizontal alignments, only + // for values other than top-left (which is default) + if (!horizontalAlignment.equals("left") + || !verticalAlignment.equals("top")) { + + // The previous positioning has been left (or unspecified). + // Thus we need to create a one-cell-table to position + // this element. + if (alignmentTD == null) { + + // Store and remove the current childs (widget and caption) + Element c1 = DOM + .getFirstChild(getElementWrappingWidgetAndCaption()); + if (c1 != null) { + DOM.removeChild(getElementWrappingWidgetAndCaption(), + c1); + } + Element c2 = DOM + .getFirstChild(getElementWrappingWidgetAndCaption()); + if (c2 != null) { + DOM.removeChild(getElementWrappingWidgetAndCaption(), + c2); + } + + // Construct table structure to align children + String alignmentTableStructure = "<table cellpadding='0' cellspacing='0' width='100%'"; + if (BrowserInfo.get().isIE()) { + alignmentTableStructure += " style='height: expression(this.parentElement.offsetHeight+\"px\")'"; + } else { + alignmentTableStructure += " height='100%'"; + } + alignmentTableStructure += "><tbody><tr><td>" + + "<table cellpadding='0' cellspacing='0' ><tbody><tr><td align='left'>" + + "</td></tr></tbody></table></td></tr></tbody></table>"; + DOM.setInnerHTML(getElementWrappingWidgetAndCaption(), + alignmentTableStructure); + alignmentTD = DOM + .getFirstChild(DOM + .getFirstChild(DOM + .getFirstChild(DOM + .getFirstChild(getElementWrappingWidgetAndCaption())))); + innermostTDinAlignmnetStructure = DOM.getFirstChild(DOM + .getFirstChild(DOM.getFirstChild(DOM + .getFirstChild(alignmentTD)))); + + // Restore children inside the + if (c1 != null) { + DOM.appendChild(innermostTDinAlignmnetStructure, c1); + if (c2 != null) { + DOM + .appendChild( + innermostTDinAlignmnetStructure, c2); + } + } + + } else { + + // Go around optimization bug in WebKit and ensure repaint + if (BrowserInfo.get().isSafari()) { + String prevValue = DOM.getElementAttribute(alignmentTD, + "align"); + if (!horizontalAlignment.equals(prevValue)) { + Element parent = DOM.getParent(alignmentTD); + DOM.removeChild(parent, alignmentTD); + DOM.appendChild(parent, alignmentTD); + } + } + + } + + // Set the alignment in td + DOM.setElementAttribute(alignmentTD, "align", + horizontalAlignment); + DOM.setElementAttribute(alignmentTD, "vAlign", + verticalAlignment); + + } else { + + // In this case we are requested to position this left + // while as it has had some other position in the past. + // Thus the one-cell wrapper table must be removed. + if (alignmentTD != null) { + + // Move content to main container + final Element itd = innermostTDinAlignmnetStructure; + final Element alignmentTable = DOM.getParent(DOM + .getParent(DOM.getParent(alignmentTD))); + final Element target = DOM.getParent(alignmentTable); + while (DOM.getChildCount(itd) > 0) { + Element content = DOM.getFirstChild(itd); + if (content != null) { + DOM.removeChild(itd, content); + DOM.appendChild(target, content); + } + } + + // Remove unneeded table element + DOM.removeChild(target, alignmentTable); + + alignmentTD = innermostTDinAlignmnetStructure = null; + } + } + + DOM.setStyleAttribute(getElementWrappingWidgetAndCaption(), + "textAlign", horizontalAlignment); + + } + + /** Set class for spacing */ + void setSpacingAndMargins(boolean first, boolean last) { + + final Element e = getElementWrappingWidgetAndCaption(); + + int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0; + + if (orientationMode == ORIENTATION_HORIZONTAL) { + if (first) { + if (margins.hasLeft()) { + paddingLeft = marginLeft; + } + } else if (hasComponentSpacing) { + paddingLeft = hSpacing; + } + + if (last) { + if (margins.hasRight()) { + paddingRight = marginRight; + } + } + + if (margins.hasTop()) { + paddingTop = marginTop; + } + if (margins.hasBottom()) { + paddingBottom = marginBottom; + } + + } else { + if (margins.hasLeft()) { + paddingLeft = marginLeft; + } + if (margins.hasRight()) { + paddingRight = marginRight; + } + + if (first) { + if (margins.hasTop()) { + paddingTop = marginTop; + } + } else if (hasComponentSpacing) { + paddingTop = vSpacing; + } + if (last && margins.hasBottom()) { + paddingBottom = marginBottom; + } + + } + + horizontalPadding = paddingLeft + paddingRight; + verticalPadding = paddingTop + paddingBottom; + + DOM.setStyleAttribute(e, "paddingLeft", paddingLeft + "px"); + DOM.setStyleAttribute(e, "paddingRight", paddingRight + "px"); + + DOM.setStyleAttribute(e, "paddingTop", paddingTop + "px"); + DOM.setStyleAttribute(e, "paddingBottom", paddingBottom + "px"); + } + + public int getAllocatedHeight() { + int reduce = verticalPadding; + if (caption != null) { + if (orientationMode == ORIENTATION_VERTICAL + || (orientationMode == ORIENTATION_HORIZONTAL && !caption + .shouldBePlacedAfterComponent())) { + reduce += caption.getHeight(); + } + } + + if (lastForcedPixelHeight == -1) { + if (height == null) { + /* + * We have no height specified so return the space allocated + * by components so far + */ + + return getElementWrappingClipperDiv().getOffsetHeight() + - reduce; + } + + // This should not be possible... + return 0; + } else { + return lastForcedPixelHeight; + } + + } + + public int getAllocatedWidth() { + int reduce = horizontalPadding; + if (caption != null && caption.shouldBePlacedAfterComponent()) { + reduce += caption.getWidth(); + } + + if (width == null) { + /* + * We have no width specified so return the space allocated by + * components so far + */ + return getElementWrappingClipperDiv().getOffsetWidth() - reduce; + } else if (lastForcedPixelWidth > -1) { + return lastForcedPixelWidth; + } else { + return 0; + } + } + + } + + /* documented at super */ + public void add(Widget child) { + add(child, childWidgets.size()); + } + + /** + * Add widget to this layout at given position. + * + * This methods supports reinserting exiting child into layout - it just + * moves the position of the child in the layout. + */ + public void add(Widget child, int atIndex) { + /* + * <b>Validate:</b> Perform any sanity checks to ensure the Panel can + * accept a new Widget. Examples: checking for a valid index on + * insertion; checking that the Panel is not full if there is a max + * capacity. + */ + if (atIndex < 0 || atIndex > childWidgets.size()) { + return; + } + + /* + * <b>Adjust for Reinsertion:</b> Some Panels need to handle the case + * where the Widget is already a child of this Panel. Example: when + * performing a reinsert, the index might need to be adjusted to account + * for the Widget's removal. See {@link ComplexPanel#adjustIndex(Widget, + * int)}. + */ + if (childWidgets.contains(child)) { + if (childWidgets.indexOf(child) == atIndex) { + return; + } + + final int removeFromIndex = childWidgets.indexOf(child); + final WidgetWrapper wrapper = childWidgetWrappers + .get(removeFromIndex); + Element wrapperElement = wrapper.getElement(); + final int nonWidgetChildElements = DOM + .getChildCount(wrappedChildContainer) + - childWidgets.size(); + DOM.removeChild(wrappedChildContainer, wrapperElement); + DOM.insertChild(wrappedChildContainer, wrapperElement, atIndex + + nonWidgetChildElements); + childWidgets.remove(removeFromIndex); + childWidgetWrappers.remove(removeFromIndex); + childWidgets.insertElementAt(child, atIndex); + childWidgetWrappers.insertElementAt(wrapper, atIndex); + return; + } + + /* + * <b>Detach Child:</b> Remove the Widget from its existing parent, if + * any. Most Panels will simply call {@link Widget#removeFromParent()} + * on the Widget. + */ + child.removeFromParent(); + + /* + * <b>Logical Attach:</b> Any state variables of the Panel should be + * updated to reflect the addition of the new Widget. Example: the + * Widget is added to the Panel's {@link WidgetCollection} at the + * appropriate index. + */ + childWidgets.insertElementAt(child, atIndex); + + /* + * <b>Physical Attach:</b> The Widget's Element must be physically + * attached to the Panel's Element, either directly or indirectly. + */ + final WidgetWrapper wrapper = new WidgetWrapper(); + final int nonWidgetChildElements = DOM + .getChildCount(wrappedChildContainer) + - childWidgetWrappers.size(); + childWidgetWrappers.insertElementAt(wrapper, atIndex); + DOM.insertChild(wrappedChildContainer, wrapper.getElement(), atIndex + + nonWidgetChildElements); + DOM.appendChild(wrapper.getElementWrappingWidgetAndCaption(), child + .getElement()); + + /* + * <b>Adopt:</b> Call {@link #adopt(Widget)} to finalize the add as the + * very last step. + */ + adopt(child); + } + + /* documented at super */ + public boolean remove(Widget child) { + + /* + * <b>Validate:</b> Make sure this Panel is actually the parent of the + * child Widget; return <code>false</code> if it is not. + */ + if (!childWidgets.contains(child)) { + return false; + } + + /* + * <b>Orphan:</b> Call {@link #orphan(Widget)} first while the child + * Widget is still attached. + */ + orphan(child); + + /* + * <b>Physical Detach:</b> Adjust the DOM to account for the removal of + * the child Widget. The Widget's Element must be physically removed + * from the DOM. + */ + final int index = childWidgets.indexOf(child); + final WidgetWrapper wrapper = childWidgetWrappers.get(index); + DOM.removeChild(wrappedChildContainer, wrapper.getElement()); + childWidgetWrappers.remove(index); + + /* + * <b>Logical Detach:</b> Update the Panel's state variables to reflect + * the removal of the child Widget. Example: the Widget is removed from + * the Panel's {@link WidgetCollection}. + */ + childWidgets.remove(index); + + if (child instanceof Paintable) { + client.unregisterPaintable((Paintable) child); + } + + return true; + } + + /* documented at super */ + public boolean hasChildComponent(Widget component) { + return childWidgets.contains(component); + } + + /* documented at super */ + public void replaceChildComponent(Widget oldComponent, Widget newComponent) { + final int index = childWidgets.indexOf(oldComponent); + if (index >= 0) { + client.unregisterPaintable((Paintable) oldComponent); + remove(oldComponent); + add(newComponent, index); + } + } + + /* documented at super */ + public void updateCaption(Paintable component, UIDL uidl) { + final int index = childWidgets.indexOf(component); + if (index >= 0) { + childWidgetWrappers.get(index).updateCaption(uidl, component); + } + } + + /* documented at super */ + public Iterator iterator() { + return childWidgets.iterator(); + } + + /* documented at super */ + public void iLayout() { + if (isRendering) { + return; + } + + updateChildSizes(); + if (client != null) { + client.runDescendentsLayout(this); + } + childLayoutsHaveChanged = false; + } + + public boolean requestLayout(Set<Paintable> children) { + if (height != null && width != null) { + /* + * If the height and width has been specified for this container the + * child components cannot make the size of the layout change + */ + + return true; + } + + if (renderInformation.updateSize(root)) { + /* + * Size has changed so we let the child components know about the + * new size. + */ + iLayout(); + + return false; + } else { + /* + * Size has not changed so we do not need to propagate the event + * further + */ + return true; + } + + } + + public RenderSpace getAllocatedSpace(Widget child) { + final int index = childWidgets.indexOf(child); + if (index >= 0) { + WidgetWrapper wrapper = childWidgetWrappers.get(index); + int w = wrapper.getAllocatedWidth(); + int h = wrapper.getAllocatedHeight(); + + return new RenderSpace(w, h); + + } else { + return new RenderSpace(); + } + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java index 6422f369e4..5a8fd0f1df 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java @@ -1,1603 +1,636 @@ -/* -@ITMillApache2LicenseForJavaFiles@ - */ - -package com.itmill.toolkit.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Set; -import java.util.Vector; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.Widget; -import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; -import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; -import com.itmill.toolkit.terminal.gwt.client.Container; -import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; -import com.itmill.toolkit.terminal.gwt.client.ICaption; -import com.itmill.toolkit.terminal.gwt.client.Paintable; -import com.itmill.toolkit.terminal.gwt.client.RenderInformation; -import com.itmill.toolkit.terminal.gwt.client.RenderSpace; -import com.itmill.toolkit.terminal.gwt.client.UIDL; -import com.itmill.toolkit.terminal.gwt.client.RenderInformation.Size; - -/** - * Full implementation of OrderedLayout client peer. - * - * This class implements all features of OrderedLayout. It currently only - * supports use through UIDL updates. Direct client side use is not (currently) - * suported in all operation modes. - * - * - * <h2>Features</h2> - * - * <h3>Orientation</h3> - * - * <p> - * Orientation of the ordered layout declared whether the children are layouted - * horizontally or vertically. - * </p> - * - * <img src="doc-files/IOrderedLayout_horizontal.png"/> <img - * src="doc-files/IOrderedLayout_vertical.png"/> - * - * <h3>Spacing</h3> - * - * <p> - * Spacing determines if there should be space between the children. Note that - * this does not imply margin. - * </p> - * - * <img src="doc-files/IOrderedLayout_horizontal_spacing.png"/> <img - * src="doc-files/IOrderedLayout_vertical_spacing.png"/> - * - * <h3>Margin</h3> - * - * <p> - * Margin determines if there should be margin around children. Note that this - * does not imply spacing. - * </p> - * - * <img src="doc-files/IOrderedLayout_margin.png"/> - * - * <h3>Positioning the caption, icon, required indicator and error</h3> - * - * <p> - * If the child lets the layout to handle captions, by icon, caption, required - * marker (*) and error icon are placed on top of the component area. Icon will - * be first and is followed by the caption. Required marker is placed right - * after the caption text and error icon is placed last. Note that all of these - * are optional: - * </p> - * - * <img src="doc-files/IOrderedLayout_normal_caption.png"/> - * - * <p> - * If the child lets the layout to handle captions, but the caption and icon are - * both missing, no line is reserved for the required marker (*) and error icon. - * Instead they are placed on the right side of the top of the component area. - * Required marker is placed right after the component text and error icon is - * placed last. If the component is tall, the indicators are aligned along the - * top of the component. Note that both of these indicators are optional: - * </p> - * - * <img src="doc-files/IOrderedLayout_no_caption.png"/> - * - * <p> - * In case the child want to handle the caption by itself, layout does not - * repeat the caption. - * </p> - * - * <img src="doc-files/IOrderedLayout_component_handles_the_caption.png"/> - * - * <h3>Aligning the children</h3> - * - * <p> - * The children of the layout can be aligned horizontally and vertically: - * </p> - * - * <img src="doc-files/IOrderedLayout_alignment.png"/> - * - * <h3>Fixed height, width or both</h3> - * - * <p> - * When no size is explicitly specified, the size of the layout depends on the - * size of its children. If the size if specified, either explicitly or as - * percertages of the parent size, the size is equally divided between the - * children. In case some children might overflow out of the given space, they - * are cut to fit the given space. Note that the size can be independently - * specified for horizontal and vertical dimensions and is independent of the - * orientation. For example, layout can be horizontal and have fixed 300px - * height, but still measure its width from the child sizes. - * </p> - * - * <p> - * Horizontal layout with fixed width of 300px and height of 150px: - * </p> - * <img src="doc-files/IOrderedLayout_w300_h150.png"/> - * - * <p> - * Horizontal layout with fixed width of 300px: - * </p> - * <img src="doc-files/IOrderedLayout_w300.png"/> - * - * <p> - * Horizontal layout with fixed height of 150px: - * </p> - * <img src="doc-files/IOrderedLayout_h150.png"/> - * - * - * <h3>CSS attributes</h3> - * - * <p> - * Sizes for marginals and spacing can be specified for the ordered layout in - * CSS. For example, here are the defaults for OrderedLayout: - * </p> - * - * <pre> - * .i-orderedlayout-margin-top { - * padding-top: 15px; - * } - * .i-orderedlayout-margin-right { - * padding-right: 18px; - * } - * .i-orderedlayout-margin-bottom { - * padding-bottom: 15px; - * } - * .i-orderedlayout-margin-left { - * padding-left: 18px; - * } - * - * .i-orderedlayout-vspacing { - * margin-top: 8px; - * } - * .i-orderedlayout-hspacing { - * padding-left: 8px; - * } - * </pre> - * - * <p> - * When a style-name is set for the layout, this name is included in the style. - * Note that the unspecified dimensions still default to the values given for - * the layout without style. For example, if we would like to give each layout - * with "tested-layout" style quite a bit larger right margin: - * </p> - * - * <pre> - * .i-orderedlayout-tested-layout-margin-right { - * padding-right: 100px; - * } - * </pre> - * - * <p> - * Here is the rendering with getMargin(true). Note that all the other margins - * are set to the default values defined for the layout without stylename: - * </p> - * <img src="doc-files/IOrderedLayout_special-margin.png"/> - * - * - * <h3>DOM-structure</h3> - * - * Note that DOM-structure is an implementation specific and might change in the - * future versions of IT Mill Toolkit. The purpose of this documentation is to - * to ease reading of the implementation and thus to make implementation of your - * own layouts easier. - * - * <div style="border: 1px solid black; padding: 3px;">OUTERDIV - * - * <div style="border: 1px solid black; padding: 3px;">Optional STRUCTURE - * - * <div style="border: 1px solid black; padding: 3px;">CHILDWRAPPER (for each - * child) - * - * <div style="border: 1px solid black; padding: 3px;">Optional ALIGNMENTWRAPPER - * - * <div style="border: 1px solid black; padding: 3px;">Optional CLIPPER - * - * <div style="border: 1px solid black; padding: 3px;">CAPTION <span - * style="border: 1px solid black; padding: 3px;">ICON-IMG</span> <span - * style="border: 1px solid black; padding: 3px;">CAPTION-SPAN</span> <span - * style="border: 1px solid black; padding: 3px;">REQUIRED-SPAN</span> <span - * style="border: 1px solid black; padding: 3px;">ERRORINDICATOR-DIV</span> - * </div> - * - * <div style="border: 1px solid black; padding: 3px; margin-top:3px;">Widget - * component</div> - * - * </div></div></div> - * - * </div></div> - * - * <p> - * Notes: - * <ul> - * <li>If caption and icon are missing from child, <i>Widget component</i> and - * <i>CAPTION</i> elements are swithched</li> - * <li>If either child manages caption, or it has no caption, icon, required or - * error, <i>CAPTION</i> element is not needed at all</li> - * <li>If layout is vertical and its width is specified, <i>Optional - * STRUCTURE</i> is not present. Otherwise it looks like <div - * style="border: 1px solid black; padding: 3px;">TABLE <div - * style="border: 1px solid black; padding: 3px;">TBODY <div - * style="border: 1px solid black; padding: 3px;">Optional TR only included in - * case of horizontal layouts </div></div></div></li> - * <li><i>CHILDWRAPPER</i> is a DIV in case of the layout is vertical and width - * is specified. For vertical layouts with unknown width it is TR-TD. For - * horizontal layouts, it is TR-TD.</li> - * <li><i>Optionasl ALIGNMENTWRAPPER</i> are only used alignment is not the - * default - top-left. Alignment wrapper structure is - * TABLE-TBODY-TR-TD-TABLE-TBODY-TR-TD, where the outer table td is used to - * specify the alignments and inner table td to reset the table defaults to - * top-left.</li> - * <li><i>Optional CLIPPERDIV</i> included in the structure only if alignment - * structure is in place and <i>CHILDWRAPPER</i> is not a div and thus can not - * be used for clipping</li> - * </ul> - * </p> - * - * - * @author IT Mill Ltd - */ -public class IOrderedLayout extends Panel implements Container, - ContainerResizedListener { - - public static final String CLASSNAME = "i-orderedlayout"; - - public static final int ORIENTATION_VERTICAL = 0; - public static final int ORIENTATION_HORIZONTAL = 1; - - /** - * If margin and spacing values has been calculated, this holds the values - * for the given UIDL style attribute . - */ - private static HashMap measuredMargins = new HashMap(); - - /** - * Spacing. Correct values will be set in - * updateMarginAndSpacingFromCSS(UIDL) - */ - private int hSpacing, vSpacing; - - /** - * Margin. Correct values will be set in updateMarginAndSpacingFromCSS(UIDL) - */ - private int marginTop, marginBottom, marginLeft, marginRight; - - int orientationMode = ORIENTATION_VERTICAL; - - protected ApplicationConnection client; - - /** - * Reference to Element where wrapped childred are contained. Normally a - * DIV, TR or a TBODY element. - */ - private Element wrappedChildContainer; - - /** - * List of child widgets. This is not the list of wrappers, but the actual - * widgets - */ - private final Vector childWidgets = new Vector(); - - /** - * In table mode, the root element is table instead of div. - */ - private boolean tableMode = false; - - /** - * Root element. This element points to the outmost table-element (in table - * mode) or outmost div (in non-table-mode). This non-table-mode this equals - * to the getElement(). - */ - private Element root = null; - - /** - * Last set width of the component. Null if undefined (instead of being ""). - */ - private String width = null; - - /** - * Last set height of the component. Null if undefined (instead of being - * ""). - */ - private String height = null; - /** - * List of child widget wrappers. These wrappers are in exact same indexes - * as the widgets in childWidgets list. - */ - private final Vector<WidgetWrapper> childWidgetWrappers = new Vector<WidgetWrapper>(); - - /** Whether the component has spacing enabled. */ - private boolean hasComponentSpacing; - - /** Information about margin states. */ - private MarginInfo margins = new MarginInfo(0); - - /** - * Flag that indicates that the child layouts must be updated as soon as - * possible. This will be done in the end of updateFromUIDL. - */ - private boolean childLayoutsHaveChanged = false; - - private boolean isRendering = false; - - private RenderInformation renderInformation = new RenderInformation(); - - /** - * Construct the DOM of the orderder layout. - * - * <p> - * There are two modes - vertical and horizontal. - * <ul> - * <li>Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap - * ( child ))).</li> - * <li>Horizontal mode uses structure: table ( tbody ( tr-childcontainer ( - * td-wrap ( child ) td-wrap ( child) )) )</li> - * </ul> - * where root and childcontainer refer to the root element and the element - * that contain WidgetWrappers. - * </p> - * - */ - public IOrderedLayout() { - wrappedChildContainer = root = DOM.createDiv(); - setElement(wrappedChildContainer); - setStyleName(CLASSNAME); - } - - /** - * Update orientation, if it has changed. - * - * @param newOrientationMode - */ - private void rebuildRootDomStructure(int oldOrientationMode) { - - // Should we have table as a root element? - boolean newTableMode = !(orientationMode == ORIENTATION_VERTICAL && width != null); - - // Already in correct mode? - if (oldOrientationMode == orientationMode && newTableMode == tableMode) { - return; - } - boolean oldTableMode = tableMode; - tableMode = newTableMode; - - /* - * If the child are not detached before the parent is cleared with - * setInnerHTML the children will also be cleared in IE - */ - if (BrowserInfo.get().isIE()) { - while (true) { - Element child = DOM.getFirstChild(getElement()); - if (child != null) { - DOM.removeChild(getElement(), child); - } else { - break; - } - } - } - - // Constuct base DOM-structure and clean any already attached - // widgetwrappers from DOM. - if (tableMode) { - String structure = "<table cellspacing=\"0\" cellpadding=\"0\""; - - if (orientationMode == ORIENTATION_HORIZONTAL) { - // Needed for vertical alignment to work - structure += " height=\"100%\""; - } - structure += "><tbody>" - + (orientationMode == ORIENTATION_HORIZONTAL ? "<tr valign=\"top\"></tr>" - : "") + "</tbody></table>"; - - DOM.setInnerHTML(getElement(), structure); - root = DOM.getFirstChild(getElement()); - // set TBODY to be the wrappedChildContainer - wrappedChildContainer = DOM.getFirstChild(root); - // In case of horizontal layouts, we must user TR instead of TBODY - if (orientationMode == ORIENTATION_HORIZONTAL) { - wrappedChildContainer = DOM - .getFirstChild(wrappedChildContainer); - } - } else { - root = wrappedChildContainer = getElement(); - DOM.setInnerHTML(getElement(), ""); - } - - // Reinsert all widget wrappers to this container - final int currentOrientationMode = orientationMode; - for (int i = 0; i < childWidgetWrappers.size(); i++) { - WidgetWrapper wr = childWidgetWrappers.get(i); - orientationMode = oldOrientationMode; - tableMode = oldTableMode; - Element oldWrElement = wr.getElementWrappingWidgetAndCaption(); - orientationMode = currentOrientationMode; - tableMode = newTableMode; - String classe = DOM.getElementAttribute(oldWrElement, "class"); - wr.resetRootElement(); - Element newWrElement = wr.getElementWrappingWidgetAndCaption(); - if (classe != null && classe.length() > 0) { - DOM.setElementAttribute(newWrElement, "class", classe); - } - while (DOM.getChildCount(oldWrElement) > 0) { - Element c = DOM.getFirstChild(oldWrElement); - DOM.removeChild(oldWrElement, c); - DOM.appendChild(newWrElement, c); - } - - DOM.appendChild(wrappedChildContainer, wr.getElement()); - } - - // Update child layouts - childLayoutsHaveChanged = true; - } - - /** Update the contents of the layout from UIDL. */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - isRendering = true; - this.client = client; - - // Only non-cached UIDL:s can introduce changes - if (uidl.getBooleanAttribute("cached")) { - return; - } - - updateMarginAndSpacingSizesFromCSS(uidl); - - // Update sizes, ... - if (client.updateComponent(this, uidl, true)) { - return; - } - - // Rebuild DOM tree root if necessary - int oldO = orientationMode; - orientationMode = "horizontal".equals(uidl - .getStringAttribute("orientation")) ? ORIENTATION_HORIZONTAL - : ORIENTATION_VERTICAL; - rebuildRootDomStructure(oldO); - - // Handle component spacing later in handleAlignments() method - hasComponentSpacing = uidl.getBooleanAttribute("spacing"); - - // Collect the list of contained widgets after this update - final Vector newWidgets = new Vector(); - for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { - final UIDL uidlForChild = (UIDL) it.next(); - final Paintable child = client.getPaintable(uidlForChild); - newWidgets.add(child); - } - - // Iterator for old widgets - final Iterator oldWidgetsIterator = (new Vector(childWidgets)) - .iterator(); - - // Iterator for new widgets - final Iterator newWidgetsIterator = newWidgets.iterator(); - - // Iterator for new UIDL - final Iterator newUIDLIterator = uidl.getChildIterator(); - - // List to collect all now painted widgets to in order to remove - // unpainted ones later - final Vector paintedWidgets = new Vector(); - - final Vector childsToPaint = new Vector(); - - // Add any new widgets to the ordered layout - Widget oldChild = null; - while (newWidgetsIterator.hasNext()) { - - final Widget newChild = (Widget) newWidgetsIterator.next(); - final UIDL newChildUIDL = (UIDL) newUIDLIterator.next(); - - // Remove any unneeded old widgets - if (oldChild == null && oldWidgetsIterator.hasNext()) { - // search for next old Paintable which still exists in layout - // and delete others - while (oldWidgetsIterator.hasNext()) { - oldChild = (Widget) oldWidgetsIterator.next(); - // now oldChild is an instance of Paintable - if (paintedWidgets.contains(oldChild)) { - continue; - } else if (newWidgets.contains(oldChild)) { - break; - } else { - remove(oldChild); - oldChild = null; - } - } - } - - if (oldChild == null) { - // we are adding components to the end of layout - add(newChild); - } else if (newChild == oldChild) { - // child already attached in correct position - oldChild = null; - } else if (hasChildComponent(newChild)) { - - // current child has been moved, re-insert before current - // oldChild - add(newChild, childWidgets.indexOf(oldChild)); - - } else { - // insert new child before old one - add(newChild, childWidgets.indexOf(oldChild)); - } - - // Update the child component - childsToPaint.add(new Object[] { newChild, newChildUIDL }); - - // Add this newly handled component to the list of painted - // components - paintedWidgets.add(newChild); - } - - // Remove possibly remaining old widgets which were not in painted UIDL - while (oldWidgetsIterator.hasNext()) { - oldChild = (Widget) oldWidgetsIterator.next(); - if (!newWidgets.contains(oldChild)) { - remove(oldChild); - } - } - - // Handle component alignments - handleAlignmentsSpacingAndMargins(uidl); - - // Reset sizes for the children - updateChildSizes(); - - // Paint children - for (int i = 0; i < childsToPaint.size(); i++) { - Object[] t = (Object[]) childsToPaint.get(i); - ((Paintable) t[0]).updateFromUIDL((UIDL) t[1], client); - } - - // Update child layouts - // TODO This is most probably unnecessary and should be done within - // update Child H/W - // if (childLayoutsHaveChanged) { - // client.runDescendentsLayout(this); - // childLayoutsHaveChanged = false; - // } - - /* Store the rendered size so we later can see if it has changed */ - if (renderInformation.updateSize(root)) { - client.runDescendentsLayout(this); - } - - isRendering = false; - - } - - private void updateMarginAndSpacingSizesFromCSS(UIDL uidl) { - - // Style for this layout - String style = uidl.getStringAttribute("style"); - if (style == null) { - style = ""; - } - - // Try to find measured from cache - int[] r = (int[]) measuredMargins.get(style); - - // Measure from DOM - if (r == null) { - r = new int[] { 0, 0, 0, 0, 0, 0 }; - - // Construct DOM for measurements - Element e1 = DOM.createTable(); - DOM.setStyleAttribute(e1, "position", "absolute"); - DOM.setElementProperty(e1, "cellSpacing", "0"); - DOM.setElementProperty(e1, "cellPadding", "0"); - Element e11 = DOM.createTBody(); - Element e12 = DOM.createTR(); - Element e13 = DOM.createTD(); - Element e2 = DOM.createDiv(); - Element e3 = DOM.createDiv(); - DOM.setStyleAttribute(e3, "width", "100px"); - DOM.setStyleAttribute(e3, "height", "100px"); - DOM.appendChild(getElement(), e1); - DOM.appendChild(e1, e11); - DOM.appendChild(e11, e12); - DOM.appendChild(e12, e13); - DOM.appendChild(e13, e2); - DOM.appendChild(e2, e3); - DOM.setInnerText(e3, "."); - - // Measure different properties - final String[] classes = { "margin-top", "margin-right", - "margin-bottom", "margin-left", "vspacing", "hspacing" }; - for (int c = 0; c < 6; c++) { - StringBuffer styleBuf = new StringBuffer(); - final String primaryName = getStylePrimaryName(); - styleBuf.append(primaryName + "-" + classes[c]); - if (style.length() > 0) { - final String[] styles = style.split(" "); - for (int i = 0; i < styles.length; i++) { - styleBuf.append(" "); - styleBuf.append(primaryName); - styleBuf.append("-"); - styleBuf.append(styles[i]); - styleBuf.append("-"); - styleBuf.append(classes[c]); - } - } - DOM.setElementProperty(e2, "className", styleBuf.toString()); - - // Measure - r[c] = DOM.getElementPropertyInt(e1, - (c % 2) == 1 ? "offsetWidth" : "offsetHeight") - 100; - } - - // Clean-up - DOM.removeChild(getElement(), e1); - - // Cache for further use - measuredMargins.put(style, r); - } - - // Set the properties - marginTop = r[0]; - marginRight = r[1]; - marginBottom = r[2]; - marginLeft = r[3]; - vSpacing = r[4]; - hSpacing = r[5]; - } - - /** - * While setting width, ensure that margin div is also resized properly. - * Furthermore, enable/disable fixed mode - */ - public void setWidth(String newWidth) { - super.setWidth(newWidth); - - if (newWidth == null || newWidth.equals("")) { - width = null; - } else { - width = newWidth; - } - - // Update child layouts - childLayoutsHaveChanged = true; - } - - /** - * While setting height, ensure that margin div is also resized properly. - * Furthermore, enable/disable fixed mode - */ - public void setHeight(String newHeight) { - super.setHeight(newHeight); - height = newHeight == null || "".equals(newHeight) ? null : newHeight; - - // Update child layouts - childLayoutsHaveChanged = true; - } - - /** Recalculate and apply the space given for each child in this layout. */ - private void updateChildSizes() { - - int numChild = childWidgets.size(); - int childHeightTotal = -1; - int childHeightDivisor = 1; - int childWidthTotal = -1; - int childWidthDivisor = 1; - - // Vertical layout is calculated by us - if (height != null) { - - if (height.endsWith("px")) { - childHeightTotal = Integer.parseInt(height.substring(0, height - .length() - 2)); - } else { - // TODO This might be wrong but only reached if height is - // specified by other means than pixels or % - childHeightTotal = getElement().getOffsetHeight(); - } - - // Calculate the space for fixed contents minus marginals - childHeightTotal = getElement().getOffsetHeight(); - - childHeightTotal -= margins.hasTop() ? marginTop : 0; - childHeightTotal -= margins.hasBottom() ? marginBottom : 0; - - // Reduce spacing from the size - if (hasComponentSpacing) { - childHeightTotal -= ((orientationMode == ORIENTATION_HORIZONTAL) ? hSpacing - : vSpacing) - * (numChild - 1); - } - - // Total space is divided among the children - if (orientationMode == ORIENTATION_VERTICAL) { - childHeightDivisor = numChild; - } - } - - // layout is calculated by us - if (width != null) { - - if (width.endsWith("px")) { - childWidthTotal = Integer.parseInt(width.substring(0, width - .length() - 2)); - } else { - // TODO This might be wrong but only reached if width is - // specified by other means than pixels or % - childWidthTotal = getElement().getOffsetWidth(); - } - - childWidthTotal -= margins.hasLeft() ? marginLeft : 0; - childWidthTotal -= margins.hasRight() ? marginRight : 0; - - // Reduce spacing from the size - if (hasComponentSpacing - && orientationMode == ORIENTATION_HORIZONTAL) { - childWidthTotal -= hSpacing * (numChild - 1); - } - - // Total space is divided among the children - if (orientationMode == ORIENTATION_HORIZONTAL) { - childWidthDivisor = numChild; - } - } - - // Set the sizes for each child - for (Iterator i = childWidgetWrappers.iterator(); i.hasNext();) { - int w, h; - if (childHeightDivisor > 1) { - h = Math.round(((float) childHeightTotal) - / (childHeightDivisor--)); - childHeightTotal -= h; - } else { - h = childHeightTotal; - } - if (childWidthDivisor > 1) { - w = Math.round(((float) childWidthTotal) - / (childWidthDivisor--)); - childWidthTotal -= w; - } else { - w = childWidthTotal; - } - WidgetWrapper ww = (WidgetWrapper) i.next(); - ww.forceSize(w, h); - - } - } - - /** Parse alignments from UIDL and pass whem to correct widgetwrappers */ - private void handleAlignmentsSpacingAndMargins(UIDL uidl) { - - // Only update margins when they have changed - // TODO this should be optimized to avoid reupdating these - margins = new MarginInfo(uidl.getIntAttribute("margins")); - - // Component alignments as a comma separated list. - // See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for - // possible values. - final int[] alignments = uidl.getIntArrayAttribute("alignments"); - int alignmentIndex = 0; - - // Insert alignment attributes - final Iterator it = childWidgetWrappers.iterator(); - - while (it.hasNext()) { - - // Calculate alignment info - final AlignmentInfo ai = new AlignmentInfo( - alignments[alignmentIndex++]); - - final WidgetWrapper wr = (WidgetWrapper) it.next(); - - wr.setAlignment(ai.getVerticalAlignment(), ai - .getHorizontalAlignment()); - - // Handle spacing and margins in this loop as well - wr.setSpacingAndMargins(alignmentIndex == 1, - alignmentIndex == alignments.length); - } - } - - /** - * Wrapper around single child in the layout. - * - * This helper also manages spacing, margins and alignment for individual - * cells handling. It also can put hard size limits for its contens by - * clipping the content to given pixel size. - * - */ - class WidgetWrapper { - - /** - * When alignment table structure is used, these elements correspond to - * the TD elements within the structure. If alignment is not used, these - * are null. - */ - Element alignmentTD, innermostTDinAlignmnetStructure; - - /** - * When clipping must be done and the element wrapping clipped content - * would be TD instead of DIV, this element points to additional DIV - * that is used for clipping. - */ - Element clipperDiv; - - /** Caption element when used. */ - ICaption caption = null; - Size captionSize = new Size(-1, -1); - - /** - * Last set pixel height for the wrapper. -1 if vertical clipping is not - * used. - */ - int lastForcedPixelHeight = -1; - - /** - * Last set pixel width for the wrapper. -1 if horizontal clipping is - * not used. - */ - int lastForcedPixelWidth = -1; - - int horizontalPadding = 0, verticalPadding = 0; - - /** Widget Wrapper root element */ - Element wrapperElement; - - private String horizontalAlignment = "left"; - - private String verticalAlignment = "top"; - - /** Set the root element */ - public WidgetWrapper() { - resetRootElement(); - } - - public Element getElement() { - return wrapperElement; - } - - /** - * Set the width and height given for the wrapped widget in pixels. - * - * -1 if unconstrained. - */ - public void forceSize(int pixelWidth, int pixelHeight) { - - // If we are already at the correct size, do nothing - if (lastForcedPixelHeight == pixelHeight - && lastForcedPixelWidth == pixelWidth) { - return; - } - - // Clipper DIV is needed? - if (tableMode && (pixelHeight >= 0 || pixelWidth >= 0)) { - if (clipperDiv == null) { - createClipperDiv(); - } - } - - // ClipperDiv is not needed, remove if necessary - else if (clipperDiv != null) { - removeClipperDiv(); - } - - Element e = clipperDiv != null ? clipperDiv - : getElementWrappingAlignmentStructures(); - - // Overflow - DOM.setStyleAttribute(e, "overflow", pixelWidth < 0 - && pixelHeight < 0 ? "" : "hidden"); - - // Set size - DOM.setStyleAttribute(e, "width", pixelWidth < 0 ? "" : pixelWidth - + "px"); - - if (pixelHeight >= 0) { - DOM.setStyleAttribute(e, "height", pixelHeight + "px"); - } else { - if (e == clipperDiv && !BrowserInfo.get().isIE()) { - DOM.setStyleAttribute(e, "height", "100%"); - } - - } - - // Set cached values - lastForcedPixelWidth = pixelWidth; - lastForcedPixelHeight = pixelHeight; - } - - /** Create a DIV for clipping the child */ - private void createClipperDiv() { - clipperDiv = DOM.createDiv(); - final Element e = getElementWrappingClipperDiv(); - String clipperClass = "i-orderedlayout-cl-" - + (orientationMode == ORIENTATION_HORIZONTAL ? "h" : "v"); - - String elementClass = e.getClassName(); - - if (elementClass != null && elementClass.length() > 0) { - clipperClass += " " + elementClass; - } - - while (DOM.getChildCount(e) > 0) { - final Element c = DOM.getFirstChild(e); - DOM.removeChild(e, c); - DOM.appendChild(clipperDiv, c); - } - - if (elementClass != null && elementClass.length() > 0) { - e.setClassName(""); - } - - DOM.appendChild(e, clipperDiv); - clipperDiv.setClassName(clipperClass); - - } - - /** Undo createClipperDiv() */ - private void removeClipperDiv() { - final Element e = getElementWrappingClipperDiv(); - String clipperClass = clipperDiv.getClassName(); - - String elementClass = clipperClass.replaceAll( - "i-orderedlayout-cl-.", "").trim(); - while (DOM.getChildCount(clipperDiv) > 0) { - final Element c = DOM.getFirstChild(clipperDiv); - DOM.removeChild(clipperDiv, c); - DOM.appendChild(e, c); - } - DOM.removeChild(e, clipperDiv); - clipperDiv = null; - if (elementClass != null && elementClass.length() > 0) { - e.setClassName(elementClass); - } - } - - /** - * Get the element containing the caption and the wrapped widget. - * Returned element can one of the following: - * <ul> - * <li>(a) Root DIV of the WrapperElement when not in tableMode</li> - * <li>(b) TD in just below the root TR of the WrapperElement when in - * tableMode</li> - * <li>(c) clipperDiv inside the (a) or (b)</li> - * <li>(d) The innermost TD within alignment structures located in (a), - * (b) or (c)</li> - * </ul> - * - * @return Element described above - */ - private Element getElementWrappingWidgetAndCaption() { - - // When alignment is used, we will can safely return the innermost - // TD - if (innermostTDinAlignmnetStructure != null) { - return innermostTDinAlignmnetStructure; - } - - // In all other cases element wrapping the potential alignment - // structures is the correct one - return getElementWrappingAlignmentStructures(); - } - - /** - * Get the element where alignment structures should be placed in if - * they are in use. - * - * Returned element can one of the following: - * <ul> - * <li>(a) Root DIV of the WrapperElement when not in tableMode</li> - * <li>(b) TD in just below the root TR of the WrapperElement when in - * tableMode</li> - * <li>(c) clipperDiv inside the (a) or (b)</li> - * </ul> - * - * @return Element described above - */ - private Element getElementWrappingAlignmentStructures() { - - // Clipper DIV wraps the alignment structures if present - if (clipperDiv != null) { - return clipperDiv; - } - - // When Clipper DIV is not used, we just give the element - // that would wrap it if it would be used - return getElementWrappingClipperDiv(); - } - - /** - * Get the element where clipperDiv should be placed in if they it is in - * use. - * - * Returned element can one of the following: - * <ul> - * <li>(a) Root DIV of the WrapperElement when not in tableMode</li> - * <li>(b) TD in just below the root TR of the WrapperElement when in - * tableMode</li> - * </ul> - * - * @return Element described above - */ - private Element getElementWrappingClipperDiv() { - - // Only vertical layouts in non-table mode use TR as root, for the - // rest we can safely give root element - if (!tableMode || orientationMode == ORIENTATION_HORIZONTAL) { - return wrapperElement; - } - - // The root is TR, we'll thus give the TD that is immediately within - // the root - return DOM.getFirstChild(wrapperElement); - } - - /** - * Create tr, td or div - depending on the orientation of the layout and - * set it as root. - * - * All contents of the wrapper are cleared. Caller is responsible for - * preserving the contents and moving them into new root. - * - * @return Previous root element. - */ - private void resetRootElement() { - // TODO Should we remove the existing element? - if (tableMode) { - if (orientationMode == ORIENTATION_HORIZONTAL) { - wrapperElement = DOM.createTD(); - DOM.setStyleAttribute(wrapperElement, "height", "100%"); - } else { - wrapperElement = DOM.createTR(); - DOM.appendChild(wrapperElement, DOM.createTD()); - } - } else { - wrapperElement = DOM.createDiv(); - // Apply 'hasLayout' for IE (needed to get accurate dimension - // calculations) - if (BrowserInfo.get().isIE()) { - DOM.setStyleAttribute(wrapperElement, "zoom", "1"); - } - } - - // Clear any references to intermediate elements - clipperDiv = alignmentTD = innermostTDinAlignmnetStructure = null; - } - - /** Update the caption of the element contained in this wrapper. */ - public void updateCaption(UIDL uidl, Paintable paintable) { - - final Widget widget = (Widget) paintable; - final Element captionWrapper = getElementWrappingWidgetAndCaption(); - - boolean captionDimensionOrPositionUpdated = false; - - // The widget needs caption - if (ICaption.isNeeded(uidl)) { - - // If the caption element is missing, create it - boolean justAdded = false; - if (caption == null) { - justAdded = true; - caption = new ICaption(paintable, client); - } - - // Update caption contents - caption.updateCaption(uidl); - - caption.setAlignment(horizontalAlignment); - - int captionHeight = caption.getElement().getOffsetHeight(); - int captionWidth = caption.getElement().getOffsetWidth(); - if (captionHeight != captionSize.getHeight() - || captionWidth != captionSize.getWidth()) { - captionSize = new Size(captionWidth, captionHeight); - captionDimensionOrPositionUpdated = true; - } - - final boolean after = caption.shouldBePlacedAfterComponent(); - final Element captionElement = caption.getElement(); - final Element widgetElement = widget.getElement(); - - if (justAdded) { - - // As the caption has just been created, insert it to DOM - if (after) { - DOM.appendChild(captionWrapper, captionElement); - DOM.setElementAttribute(captionWrapper, "class", - "i-orderedlayout-w"); - caption.addStyleName("i-orderedlayout-c"); - widget.addStyleName("i-orderedlayout-w-e"); - } else { - DOM.insertChild(captionWrapper, captionElement, 0); - } - - captionDimensionOrPositionUpdated = true; - } else if (after == (DOM.getChildIndex(captionWrapper, - widgetElement) > DOM.getChildIndex(captionWrapper, - captionElement))) { - // Caption exists. Move it to correct position if it is not - // there - Element firstElement = DOM.getChild(captionWrapper, DOM - .getChildCount(captionWrapper) - 2); - if (firstElement != null) { - DOM.removeChild(captionWrapper, firstElement); - DOM.appendChild(captionWrapper, firstElement); - } - DOM.setElementAttribute(captionWrapper, "class", - after ? "i-orderedlayout-w" : ""); - if (after) { - caption.addStyleName("i-orderedlayout-c"); - widget.addStyleName("i-orderedlayout-w-e"); - } else { - widget.removeStyleName("i-orderedlayout-w-e"); - caption.removeStyleName("i-orderedlayout-w-c"); - } - - captionDimensionOrPositionUpdated = true; - } else { - if (after) { - widget.addStyleName("i-orderedlayout-w-e"); - } else { - widget.removeStyleName("i-orderedlayout-w-e"); - } - } - - } else { - // Caption is not needed - - // Remove existing caption from DOM - if (caption != null) { - DOM.removeChild(captionWrapper, caption.getElement()); - caption = null; - DOM.setElementAttribute(captionWrapper, "class", ""); - widget.removeStyleName("i-orderedlayout-w-e"); - - captionDimensionOrPositionUpdated = true; - } - } - - if (captionDimensionOrPositionUpdated) { - client.handleComponentRelativeSize((Widget) paintable); - } - } - - /** - * Set alignments for this wrapper. - */ - void setAlignment(String verticalAlignment, String horizontalAlignment) { - this.verticalAlignment = verticalAlignment; - this.horizontalAlignment = horizontalAlignment; - - // use one-cell table to implement horizontal alignments, only - // for values other than top-left (which is default) - if (!horizontalAlignment.equals("left") - || !verticalAlignment.equals("top")) { - - // The previous positioning has been left (or unspecified). - // Thus we need to create a one-cell-table to position - // this element. - if (alignmentTD == null) { - - // Store and remove the current childs (widget and caption) - Element c1 = DOM - .getFirstChild(getElementWrappingWidgetAndCaption()); - if (c1 != null) { - DOM.removeChild(getElementWrappingWidgetAndCaption(), - c1); - } - Element c2 = DOM - .getFirstChild(getElementWrappingWidgetAndCaption()); - if (c2 != null) { - DOM.removeChild(getElementWrappingWidgetAndCaption(), - c2); - } - - // Construct table structure to align children - String alignmentTableStructure = "<table cellpadding='0' cellspacing='0' width='100%'"; - if (BrowserInfo.get().isIE()) { - alignmentTableStructure += " style='height: expression(this.parentElement.offsetHeight+\"px\")'"; - } else { - alignmentTableStructure += " height='100%'"; - } - alignmentTableStructure += "><tbody><tr><td>" - + "<table cellpadding='0' cellspacing='0' ><tbody><tr><td align='left'>" - + "</td></tr></tbody></table></td></tr></tbody></table>"; - DOM.setInnerHTML(getElementWrappingWidgetAndCaption(), - alignmentTableStructure); - alignmentTD = DOM - .getFirstChild(DOM - .getFirstChild(DOM - .getFirstChild(DOM - .getFirstChild(getElementWrappingWidgetAndCaption())))); - innermostTDinAlignmnetStructure = DOM.getFirstChild(DOM - .getFirstChild(DOM.getFirstChild(DOM - .getFirstChild(alignmentTD)))); - - // Restore children inside the - if (c1 != null) { - DOM.appendChild(innermostTDinAlignmnetStructure, c1); - if (c2 != null) { - DOM - .appendChild( - innermostTDinAlignmnetStructure, c2); - } - } - - } else { - - // Go around optimization bug in WebKit and ensure repaint - if (BrowserInfo.get().isSafari()) { - String prevValue = DOM.getElementAttribute(alignmentTD, - "align"); - if (!horizontalAlignment.equals(prevValue)) { - Element parent = DOM.getParent(alignmentTD); - DOM.removeChild(parent, alignmentTD); - DOM.appendChild(parent, alignmentTD); - } - } - - } - - // Set the alignment in td - DOM.setElementAttribute(alignmentTD, "align", - horizontalAlignment); - DOM.setElementAttribute(alignmentTD, "vAlign", - verticalAlignment); - - } else { - - // In this case we are requested to position this left - // while as it has had some other position in the past. - // Thus the one-cell wrapper table must be removed. - if (alignmentTD != null) { - - // Move content to main container - final Element itd = innermostTDinAlignmnetStructure; - final Element alignmentTable = DOM.getParent(DOM - .getParent(DOM.getParent(alignmentTD))); - final Element target = DOM.getParent(alignmentTable); - while (DOM.getChildCount(itd) > 0) { - Element content = DOM.getFirstChild(itd); - if (content != null) { - DOM.removeChild(itd, content); - DOM.appendChild(target, content); - } - } - - // Remove unneeded table element - DOM.removeChild(target, alignmentTable); - - alignmentTD = innermostTDinAlignmnetStructure = null; - } - } - - DOM.setStyleAttribute(getElementWrappingWidgetAndCaption(), - "textAlign", horizontalAlignment); - - } - - /** Set class for spacing */ - void setSpacingAndMargins(boolean first, boolean last) { - - final Element e = getElementWrappingWidgetAndCaption(); - - int paddingLeft = 0, paddingRight = 0, paddingTop = 0, paddingBottom = 0; - - if (orientationMode == ORIENTATION_HORIZONTAL) { - if (first) { - if (margins.hasLeft()) { - paddingLeft = marginLeft; - } - } else if (hasComponentSpacing) { - paddingLeft = hSpacing; - } - - if (last) { - if (margins.hasRight()) { - paddingRight = marginRight; - } - } - - if (margins.hasTop()) { - paddingTop = marginTop; - } - if (margins.hasBottom()) { - paddingBottom = marginBottom; - } - - } else { - if (margins.hasLeft()) { - paddingLeft = marginLeft; - } - if (margins.hasRight()) { - paddingRight = marginRight; - } - - if (first) { - if (margins.hasTop()) { - paddingTop = marginTop; - } - } else if (hasComponentSpacing) { - paddingTop = vSpacing; - } - if (last && margins.hasBottom()) { - paddingBottom = marginBottom; - } - - } - - horizontalPadding = paddingLeft + paddingRight; - verticalPadding = paddingTop + paddingBottom; - - DOM.setStyleAttribute(e, "paddingLeft", paddingLeft + "px"); - DOM.setStyleAttribute(e, "paddingRight", paddingRight + "px"); - - DOM.setStyleAttribute(e, "paddingTop", paddingTop + "px"); - DOM.setStyleAttribute(e, "paddingBottom", paddingBottom + "px"); - } - - public int getAllocatedHeight() { - int reduce = verticalPadding; - if (caption != null) { - if (orientationMode == ORIENTATION_VERTICAL - || (orientationMode == ORIENTATION_HORIZONTAL && !caption - .shouldBePlacedAfterComponent())) { - reduce += caption.getHeight(); - } - } - - if (lastForcedPixelHeight == -1) { - if (height == null) { - /* - * We have no height specified so return the space allocated - * by components so far - */ - - return getElementWrappingClipperDiv().getOffsetHeight() - - reduce; - } - - // This should not be possible... - return 0; - } else { - return lastForcedPixelHeight; - } - - } - - public int getAllocatedWidth() { - int reduce = horizontalPadding; - if (caption != null && caption.shouldBePlacedAfterComponent()) { - reduce += caption.getWidth(); - } - - if (width == null) { - /* - * We have no width specified so return the space allocated by - * components so far - */ - return getElementWrappingClipperDiv().getOffsetWidth() - reduce; - } else if (lastForcedPixelWidth > -1) { - return lastForcedPixelWidth; - } else { - return 0; - } - } - - } - - /* documented at super */ - public void add(Widget child) { - add(child, childWidgets.size()); - } - - /** - * Add widget to this layout at given position. - * - * This methods supports reinserting exiting child into layout - it just - * moves the position of the child in the layout. - */ - public void add(Widget child, int atIndex) { - /* - * <b>Validate:</b> Perform any sanity checks to ensure the Panel can - * accept a new Widget. Examples: checking for a valid index on - * insertion; checking that the Panel is not full if there is a max - * capacity. - */ - if (atIndex < 0 || atIndex > childWidgets.size()) { - return; - } - - /* - * <b>Adjust for Reinsertion:</b> Some Panels need to handle the case - * where the Widget is already a child of this Panel. Example: when - * performing a reinsert, the index might need to be adjusted to account - * for the Widget's removal. See {@link ComplexPanel#adjustIndex(Widget, - * int)}. - */ - if (childWidgets.contains(child)) { - if (childWidgets.indexOf(child) == atIndex) { - return; - } - - final int removeFromIndex = childWidgets.indexOf(child); - final WidgetWrapper wrapper = childWidgetWrappers - .get(removeFromIndex); - Element wrapperElement = wrapper.getElement(); - final int nonWidgetChildElements = DOM - .getChildCount(wrappedChildContainer) - - childWidgets.size(); - DOM.removeChild(wrappedChildContainer, wrapperElement); - DOM.insertChild(wrappedChildContainer, wrapperElement, atIndex - + nonWidgetChildElements); - childWidgets.remove(removeFromIndex); - childWidgetWrappers.remove(removeFromIndex); - childWidgets.insertElementAt(child, atIndex); - childWidgetWrappers.insertElementAt(wrapper, atIndex); - return; - } - - /* - * <b>Detach Child:</b> Remove the Widget from its existing parent, if - * any. Most Panels will simply call {@link Widget#removeFromParent()} - * on the Widget. - */ - child.removeFromParent(); - - /* - * <b>Logical Attach:</b> Any state variables of the Panel should be - * updated to reflect the addition of the new Widget. Example: the - * Widget is added to the Panel's {@link WidgetCollection} at the - * appropriate index. - */ - childWidgets.insertElementAt(child, atIndex); - - /* - * <b>Physical Attach:</b> The Widget's Element must be physically - * attached to the Panel's Element, either directly or indirectly. - */ - final WidgetWrapper wrapper = new WidgetWrapper(); - final int nonWidgetChildElements = DOM - .getChildCount(wrappedChildContainer) - - childWidgetWrappers.size(); - childWidgetWrappers.insertElementAt(wrapper, atIndex); - DOM.insertChild(wrappedChildContainer, wrapper.getElement(), atIndex - + nonWidgetChildElements); - DOM.appendChild(wrapper.getElementWrappingWidgetAndCaption(), child - .getElement()); - - /* - * <b>Adopt:</b> Call {@link #adopt(Widget)} to finalize the add as the - * very last step. - */ - adopt(child); - } - - /* documented at super */ - public boolean remove(Widget child) { - - /* - * <b>Validate:</b> Make sure this Panel is actually the parent of the - * child Widget; return <code>false</code> if it is not. - */ - if (!childWidgets.contains(child)) { - return false; - } - - /* - * <b>Orphan:</b> Call {@link #orphan(Widget)} first while the child - * Widget is still attached. - */ - orphan(child); - - /* - * <b>Physical Detach:</b> Adjust the DOM to account for the removal of - * the child Widget. The Widget's Element must be physically removed - * from the DOM. - */ - final int index = childWidgets.indexOf(child); - final WidgetWrapper wrapper = childWidgetWrappers.get(index); - DOM.removeChild(wrappedChildContainer, wrapper.getElement()); - childWidgetWrappers.remove(index); - - /* - * <b>Logical Detach:</b> Update the Panel's state variables to reflect - * the removal of the child Widget. Example: the Widget is removed from - * the Panel's {@link WidgetCollection}. - */ - childWidgets.remove(index); - - if (child instanceof Paintable) { - client.unregisterPaintable((Paintable) child); - } - - return true; - } - - /* documented at super */ - public boolean hasChildComponent(Widget component) { - return childWidgets.contains(component); - } - - /* documented at super */ - public void replaceChildComponent(Widget oldComponent, Widget newComponent) { - final int index = childWidgets.indexOf(oldComponent); - if (index >= 0) { - client.unregisterPaintable((Paintable) oldComponent); - remove(oldComponent); - add(newComponent, index); - } - } - - /* documented at super */ - public void updateCaption(Paintable component, UIDL uidl) { - final int index = childWidgets.indexOf(component); - if (index >= 0) { - childWidgetWrappers.get(index).updateCaption(uidl, component); - } - } - - /* documented at super */ - public Iterator iterator() { - return childWidgets.iterator(); - } - - /* documented at super */ - public void iLayout() { - if (isRendering) { - return; - } - - updateChildSizes(); - if (client != null) { - client.runDescendentsLayout(this); - } - childLayoutsHaveChanged = false; - } - - public boolean requestLayout(Set<Paintable> children) { - if (height != null && width != null) { - /* - * If the height and width has been specified for this container the - * child components cannot make the size of the layout change - */ - - return true; - } - - if (renderInformation.updateSize(root)) { - /* - * Size has changed so we let the child components know about the - * new size. - */ - iLayout(); - - return false; - } else { - /* - * Size has not changed so we do not need to propagate the event - * further - */ - return true; - } - - } - - public RenderSpace getAllocatedSpace(Widget child) { - final int index = childWidgets.indexOf(child); - if (index >= 0) { - WidgetWrapper wrapper = childWidgetWrappers.get(index); - int w = wrapper.getAllocatedWidth(); - int h = wrapper.getAllocatedHeight(); - - return new RenderSpace(w, h); - - } else { - return new RenderSpace(); - } - } -} +package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.RenderSpace;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+import com.itmill.toolkit.terminal.gwt.client.RenderInformation.FloatSize;
+import com.itmill.toolkit.terminal.gwt.client.RenderInformation.Size;
+import com.itmill.toolkit.terminal.gwt.client.ui.layout.CellBasedLayout;
+import com.itmill.toolkit.terminal.gwt.client.ui.layout.ChildComponentContainer;
+
+public class IOrderedLayout extends CellBasedLayout {
+
+ public static final String CLASSNAME = "i-orderedlayout";
+
+ private static final String MARGIN_TOP_LEFT_CLASSNAMES = "i-orderedlayout-margin-top i-orderedlayout-margin-left";
+ private static final String MARGIN_BOTTOM_RIGHT_CLASSNAMES = "i-orderedlayout-margin-bottom i-orderedlayout-margin-right";
+ private static final String SPACING_CLASSNAMES = "i-orderedlayout-hspacing i-orderedlayout-vspacing";
+
+ private String marginsMeasureStyleName = "";
+ private int orientation = ORIENTATION_HORIZONTAL;
+
+ /**
+ * Size of the layout excluding any margins.
+ */
+ private Size activeLayoutSize = new Size(0, 0);
+
+ // private int spaceForExpansion = 0;
+ private int spaceNobodyWantedToUse = 0;
+
+ private boolean isRendering = false;
+
+ @Override
+ public void setStyleName(String styleName) {
+ super.setStyleName(styleName);
+
+ if (marginsMeasureStyleName.equals(styleName)) {
+ return;
+ }
+
+ String spacingStyleNames = styleName + " " + SPACING_CLASSNAMES;
+ String marginBottomRightStyleNames = styleName + " "
+ + MARGIN_BOTTOM_RIGHT_CLASSNAMES;
+ String marginTopLeftStyleNames = styleName + " "
+ + MARGIN_TOP_LEFT_CLASSNAMES;
+ if (measureMarginsAndSpacing(styleName, marginTopLeftStyleNames,
+ marginBottomRightStyleNames, spacingStyleNames)) {
+ marginsMeasureStyleName = styleName;
+ }
+ }
+
+ public IOrderedLayout() {
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ isRendering = true;
+ super.updateFromUIDL(uidl, client);
+
+ // Only non-cached UIDL:s can introduce changes
+ if (uidl.getBooleanAttribute("cached")) {
+ return;
+ }
+
+ handleOrientationUpdate(uidl);
+
+ // IStopWatch w = new IStopWatch("OrderedLayout.updateFromUIDL");
+
+ ArrayList<Widget> uidlWidgets = new ArrayList<Widget>(uidl
+ .getChildCount());
+ ArrayList<ChildComponentContainer> relativeSizeComponents = new ArrayList<ChildComponentContainer>();
+ ArrayList<UIDL> relativeSizeComponentUIDL = new ArrayList<UIDL>();
+ ArrayList<Widget> relativeSizeWidgets = new ArrayList<Widget>();
+
+ int pos = 0;
+ for (final Iterator<UIDL> it = uidl.getChildIterator(); it.hasNext();) {
+ final UIDL childUIDL = it.next();
+ final Paintable child = client.getPaintable(childUIDL);
+ Widget widget = (Widget) child;
+
+ // Create container for component
+ ChildComponentContainer childComponentContainer = getComponentContainer(widget);
+
+ if (childComponentContainer == null) {
+ // This is a new component
+ childComponentContainer = createChildContainer(widget);
+ }
+
+ addOrMoveChild(childComponentContainer, pos++);
+
+ /*
+ * Components which are to be expanded in the same orientation as
+ * the layout are rendered later when it is clear how much space
+ * they can use
+ */
+ FloatSize relativeSize = Util.parseRelativeSize(childUIDL);
+ childComponentContainer.setRelativeSize(relativeSize);
+
+ if (hasRelativeSize(relativeSize, orientation)) {
+ relativeSizeComponents.add(childComponentContainer);
+ relativeSizeComponentUIDL.add(childUIDL);
+ relativeSizeWidgets.add(widget);
+ } else {
+ childComponentContainer.renderChild(childUIDL, client);
+ }
+
+ uidlWidgets.add(widget);
+
+ }
+
+ // w.mark("Rendering of "
+ // + (uidlWidgets.size() - relativeSizeComponents.size())
+ // + " absolute size components done");
+
+ /*
+ * Remove any children after pos. These are the ones that previously
+ * were in the layout but have now been removed
+ */
+ removeChildrenAfter(pos);
+
+ // w.mark("Old children removed");
+
+ /* Fetch alignments and expand ratio from UIDL */
+ updateAlignmentsAndExpandRatios(uidl, uidlWidgets);
+
+ /* Fetch widget sizes from rendered components */
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+
+ /*
+ * Update widget size from DOM
+ */
+ childComponentContainer.updateWidgetSize();
+ }
+
+ recalculateLayout();
+
+ /* Render relative size components */
+ for (int i = 0; i < relativeSizeComponents.size(); i++) {
+ ChildComponentContainer childComponentContainer = relativeSizeComponents
+ .get(i);
+ UIDL childUIDL = relativeSizeComponentUIDL.get(i);
+
+ childComponentContainer.renderChild(childUIDL, client);
+ // childComponentContainer.updateWidgetSize();
+ }
+
+ /* Fetch widget sizes for relative size components */
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+
+ /* Update widget size from DOM */
+ childComponentContainer.updateWidgetSize();
+ }
+
+ // w.mark("Rendering of " + (relativeSizeComponents.size())
+ // + " relative size components done");
+
+ /* Recalculate component sizes and alignments */
+ recalculateComponentSizesAndAlignments();
+ // w.mark("recalculateComponentSizesAndAlignments done");
+
+ /* Must inform child components about possible size updates */
+ client.runDescendentsLayout(this);
+ // w.mark("runDescendentsLayout done");
+
+ isRendering = false;
+ }
+
+ private void recalculateLayout() {
+ /* Calculate space for relative size components */
+ int spaceForExpansion = calculateLayoutDimensions();
+
+ /* Divide expansion space between component containers */
+ expandComponentContainers(spaceForExpansion);
+
+ /* Update container sizes */
+ calculateContainerSize();
+
+ }
+
+ private void expandComponentContainers(int spaceForExpansion) {
+
+ int remaining = spaceForExpansion;
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+ remaining -= childComponentContainer.expand(orientation,
+ spaceForExpansion);
+ }
+
+ if (remaining > 0) {
+ // Some left-over pixels due to rounding errors
+
+ // Add extra pixels to first container
+ getFirstChildComponentContainer().expandExtra(orientation,
+ remaining);
+
+ }
+
+ }
+
+ private static boolean hasRelativeSize(FloatSize relativeSize,
+ int orientation) {
+ if (relativeSize == null) {
+ return false;
+ }
+ if (orientation == ORIENTATION_HORIZONTAL) {
+ return relativeSize.getWidth() >= 0;
+ } else {
+ return relativeSize.getHeight() >= 0;
+ }
+ }
+
+ private void handleOrientationUpdate(UIDL uidl) {
+ int newOrientation = ORIENTATION_VERTICAL;
+ if ("horizontal".equals(uidl.getStringAttribute("orientation"))) {
+ newOrientation = ORIENTATION_HORIZONTAL;
+ }
+
+ if (orientation != newOrientation) {
+ orientation = newOrientation;
+
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+ childComponentContainer.setOrientation(orientation);
+ }
+ }
+
+ }
+
+ private void recalculateComponentSizesAndAlignments() {
+ if (widgetToComponentContainer.isEmpty()) {
+ return;
+ }
+
+ updateContainerMargins();
+
+ /*
+ * Update the height of relative height components in a horizontal
+ * layout or the width for relative width components in a vertical
+ * layout
+ */
+ updateRelativeSizesInNonMainDirection();
+
+ /* Calculate alignments */
+ calculateAlignments();
+
+ }
+
+ private void updateRelativeSizesInNonMainDirection() {
+ int updateDirection = 1 - orientation;
+ for (ChildComponentContainer componentContainer : widgetToComponentContainer
+ .values()) {
+ if (componentContainer.isComponentRelativeSized(updateDirection)) {
+ client.handleComponentRelativeSize(componentContainer
+ .getWidget());
+ }
+ }
+
+ }
+
+ private int calculateLayoutDimensions() {
+ int summedWidgetWidth = 0;
+ int summedWidgetHeight = 0;
+
+ int maxWidgetWidth = 0;
+ int maxWidgetHeight = 0;
+
+ // Calculate layout dimensions from component dimensions
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+
+ if (childComponentContainer.isComponentRelativeSized(orientation)) {
+ continue;
+ }
+
+ Size s = childComponentContainer.getWidgetSize();
+ int widgetWidth = s.getWidth()
+ + childComponentContainer.getCaptionWidthAfterComponent();
+
+ if (isDynamicWidth()) {
+ /*
+ * For a dynamic width layout the max of caption/widget defines
+ * the required size
+ */
+ int captionWidth = childComponentContainer.getCaptionWidth();
+ if (captionWidth > widgetWidth) {
+ widgetWidth = captionWidth;
+ }
+ }
+
+ int widgetHeight = s.getHeight()
+ + childComponentContainer.getCaptionHeightAboveComponent();
+
+ // ApplicationConnection.getConsole().log(
+ // "Container width: " + widgetWidth);
+
+ summedWidgetWidth += widgetWidth;
+ summedWidgetHeight += widgetHeight;
+
+ maxWidgetHeight = Math.max(maxWidgetHeight, widgetHeight);
+ maxWidgetWidth = Math.max(maxWidgetWidth, widgetWidth);
+ }
+
+ if (isHorizontal()) {
+ summedWidgetWidth += activeSpacing.hSpacing
+ * (widgetToComponentContainer.size() - 1);
+ } else {
+ summedWidgetHeight += activeSpacing.vSpacing
+ * (widgetToComponentContainer.size() - 1);
+ }
+
+ Size layoutSize = updateLayoutDimensions(summedWidgetWidth,
+ summedWidgetHeight, maxWidgetWidth, maxWidgetHeight);
+
+ int remainingSpace;
+ if (isHorizontal()) {
+ remainingSpace = layoutSize.getWidth() - summedWidgetWidth;
+ } else {
+ remainingSpace = layoutSize.getHeight() - summedWidgetHeight;
+ }
+ if (remainingSpace < 0) {
+ remainingSpace = 0;
+ }
+
+ return remainingSpace;
+ }
+
+ private void calculateAlignments() {
+ int w = 0;
+ int h = 0;
+
+ if (isHorizontal()) {
+ // HORIZONTAL
+ h = activeLayoutSize.getHeight();
+ if (!isDynamicWidth()) {
+ w = -1;
+ }
+
+ } else {
+ // VERTICAL
+ w = activeLayoutSize.getWidth();
+ if (!isDynamicHeight()) {
+ h = -1;
+ }
+ }
+
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+ childComponentContainer.updateAlignments(w, h);
+ }
+
+ }
+
+ private void calculateContainerSize() {
+
+ /*
+ * Container size here means the size the container gets from the
+ * component. The expansion size is not include in this but taken
+ * separately into account.
+ */
+ int height = 0, width = 0;
+ if (isHorizontal()) {
+ height = activeLayoutSize.getHeight();
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+ if (!childComponentContainer
+ .isComponentRelativeSized(ORIENTATION_HORIZONTAL)) {
+ /*
+ * Only components with non-relative size in the main
+ * direction has a container size
+ */
+ width = childComponentContainer.getWidgetSize().getWidth()
+ + childComponentContainer
+ .getCaptionWidthAfterComponent();
+ int captionWidth = childComponentContainer
+ .getCaptionWidth();
+ if (captionWidth > width) {
+ width = captionWidth;
+ }
+ } else {
+ width = 0;
+ }
+
+ childComponentContainer.setContainerSize(width, height);
+ }
+ } else {
+ width = activeLayoutSize.getWidth();
+ for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
+ .values()) {
+
+ if (!childComponentContainer
+ .isComponentRelativeSized(ORIENTATION_VERTICAL)) {
+ /*
+ * Only components with non-relative size in the main
+ * direction has a container size
+ */
+ height = childComponentContainer.getWidgetSize()
+ .getHeight()
+ + childComponentContainer
+ .getCaptionHeightAboveComponent();
+ } else {
+ height = 0;
+ }
+
+ childComponentContainer.setContainerSize(width, height);
+ }
+
+ }
+
+ }
+
+ private Size updateLayoutDimensions(int totalComponentWidth,
+ int totalComponentHeight, int maxComponentWidth,
+ int maxComponentHeight) {
+
+ int activeLayoutWidth = 0;
+ int activeLayoutHeight = 0;
+
+ // Update layout dimensions
+ if (isHorizontal()) {
+ // Horizontal
+ if (isDynamicWidth()) {
+ activeLayoutWidth = totalComponentWidth;
+ setOuterLayoutWidth(activeLayoutWidth);
+ } else {
+ activeLayoutWidth = getOffsetWidth()
+ - activeMargins.getHorizontal();
+ }
+
+ if (isDynamicHeight()) {
+ activeLayoutHeight = maxComponentHeight;
+ setOuterLayoutHeight(maxComponentHeight);
+ } else {
+ activeLayoutHeight = getOffsetHeight()
+ - activeMargins.getVertical();
+
+ }
+
+ } else {
+ // Vertical
+ if (isDynamicHeight()) {
+ activeLayoutWidth = maxComponentWidth;
+ setOuterLayoutWidth(maxComponentWidth);
+ } else {
+ activeLayoutWidth = getOffsetWidth()
+ - activeMargins.getHorizontal();
+ }
+
+ if (isDynamicHeight()) {
+ activeLayoutHeight = totalComponentHeight;
+ setOuterLayoutHeight(totalComponentHeight);
+ } else {
+ activeLayoutHeight = getOffsetHeight()
+ - activeMargins.getVertical();
+ }
+ }
+
+ activeLayoutSize.setWidth(activeLayoutWidth);
+ activeLayoutSize.setHeight(activeLayoutHeight);
+
+ return activeLayoutSize;
+ }
+
+ private void setOuterLayoutWidth(int activeLayoutWidth) {
+ super.setWidth((activeLayoutWidth + activeMargins.getHorizontal())
+ + "px");
+
+ }
+
+ private void setOuterLayoutHeight(int activeLayoutHeight) {
+ super.setHeight((activeLayoutHeight + activeMargins.getVertical())
+ + "px");
+
+ }
+
+ private void updateContainerMargins() {
+ ChildComponentContainer firstChildComponent = getFirstChildComponentContainer();
+
+ firstChildComponent.setMarginLeft(0);
+ firstChildComponent.setMarginTop(0);
+
+ for (ChildComponentContainer childComponent : widgetToComponentContainer
+ .values()) {
+ if (childComponent == firstChildComponent) {
+ continue;
+ }
+
+ if (isHorizontal()) {
+ childComponent.setMarginLeft(activeSpacing.hSpacing);
+ } else {
+ childComponent.setMarginTop(activeSpacing.vSpacing);
+ }
+ }
+ }
+
+ private boolean isHorizontal() {
+ return orientation == ORIENTATION_HORIZONTAL;
+ }
+
+ private boolean isVertical() {
+ return orientation == ORIENTATION_VERTICAL;
+ }
+
+ private ChildComponentContainer createChildContainer(Widget child) {
+
+ // Create a container DIV for the child
+ ChildComponentContainer childComponent = new ChildComponentContainer(
+ child, orientation);
+
+ return childComponent;
+
+ }
+
+ public RenderSpace getAllocatedSpace(Widget child) {
+ int width = 0;
+ int height = 0;
+ ChildComponentContainer childComponentContainer = getComponentContainer(child);
+ // WIDTH CALCULATION
+ if (isVertical()) {
+ width = activeLayoutSize.getWidth();
+ width -= childComponentContainer.getCaptionWidthAfterComponent();
+ } else if (!isDynamicWidth()) {
+ // HORIZONTAL
+ width = childComponentContainer.getContSize().getWidth();
+ width -= childComponentContainer.getCaptionWidthAfterComponent();
+ }
+
+ // HEIGHT CALCULATION
+ if (isHorizontal()) {
+ height = activeLayoutSize.getHeight();
+ height -= childComponentContainer.getCaptionHeightAboveComponent();
+ } else if (!isDynamicHeight()) {
+ // VERTICAL
+ height = childComponentContainer.getContSize().getHeight();
+ height -= childComponentContainer.getCaptionHeightAboveComponent();
+ }
+
+ // ApplicationConnection.getConsole().log(
+ // "allocatedSpace for " + Util.getSimpleName(child) + ": "
+ // + width + "," + height);
+ RenderSpace space = new RenderSpace(width, height);
+ return space;
+ }
+
+ private boolean recalculateLayoutAndComponentSizes() {
+ Size sizeBefore = new Size(activeLayoutSize.getWidth(),
+ activeLayoutSize.getHeight());
+
+ recalculateLayout();
+
+ recalculateComponentSizesAndAlignments();
+
+ boolean sameSize = (sizeBefore.equals(activeLayoutSize));
+
+ return sameSize;
+ }
+
+ public boolean requestLayout(Set<Paintable> children) {
+ for (Paintable p : children) {
+ /* Update widget size from DOM */
+ getComponentContainer((Widget) p).updateWidgetSize();
+ }
+
+ boolean sameSize = recalculateLayoutAndComponentSizes();
+ if (!sameSize) {
+ /* Must inform child components about possible size updates */
+ client.runDescendentsLayout(this);
+ }
+
+ /* Automatically propagated upwards if the size has changed */
+
+ return sameSize;
+ }
+
+ @Override
+ public void setHeight(String height) {
+ super.setHeight(height);
+
+ if (!isRendering) {
+ if (recalculateLayoutAndComponentSizes()) {
+ /* Must inform child components about possible size updates */
+ client.runDescendentsLayout(this);
+ }
+ }
+ }
+
+ @Override
+ public void setWidth(String width) {
+ super.setWidth(width);
+
+ if (!isRendering) {
+ if (recalculateLayoutAndComponentSizes()) {
+ /* Must inform child components about possible size updates */
+ client.runDescendentsLayout(this);
+ }
+ }
+ }
+
+ protected void updateAlignmentsAndExpandRatios(UIDL uidl,
+ ArrayList<Widget> renderedWidgets) {
+
+ /*
+ * UIDL contains component alignments as a comma separated list.
+ *
+ * See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for
+ * possible values.
+ */
+ final int[] alignments = uidl.getIntArrayAttribute("alignments");
+
+ /*
+ * UIDL contains normalized expand ratios as a comma separated list.
+ */
+ final int[] expandRatios = uidl.getIntArrayAttribute("expandRatios");
+
+ for (int i = 0; i < renderedWidgets.size(); i++) {
+ Widget widget = renderedWidgets.get(i);
+
+ ChildComponentContainer container = getComponentContainer(widget);
+
+ // Calculate alignment info
+ container.setAlignment(new AlignmentInfo(alignments[i]));
+
+ // Update expand ratio
+ container.setExpandRatio(expandRatios[i]);
+ }
+ }
+
+}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java index 7e4297a7bd..2847900a88 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java @@ -152,7 +152,7 @@ public class IPanel extends SimplePanel implements Container, } // Height adjustment - iLayout(false); + // iLayout(false); // Render content final UIDL layoutUidl = uidl.getChildUIDL(0); @@ -164,8 +164,13 @@ public class IPanel extends SimplePanel implements Container, setWidget((Widget) newLayout); layout = newLayout; } - (layout).updateFromUIDL(layoutUidl, client); + layout.updateFromUIDL(layoutUidl, client); + + if (BrowserInfo.get().isIE7()) { + // IE is not able to make the offsetWidth for contentNode correct for some reason... + iLayout(false); + } // We may have actions attached to this panel if (uidl.getChildCount() > 1) { final int cnt = uidl.getChildCount(); @@ -279,6 +284,25 @@ public class IPanel extends SimplePanel implements Container, } + if (BrowserInfo.get().isIE7() && (width == null || width.equals(""))) { + //FIXME This won't work if the panel's content gets narrower later on... + int captionMarginLeft = captionNode.getAbsoluteLeft() + - getElement().getAbsoluteLeft(); + int captionWidth = captionNode.getOffsetWidth() + captionMarginLeft; + int contentWidth = contentNode.getOffsetWidth(); + int layoutWidth = ((Widget) layout).getOffsetWidth() + + getContainerBorderWidth(); + int width = contentWidth; + if (captionWidth > width) { + width = captionWidth; + } + if (layoutWidth > width) { + width = layoutWidth; + } + + super.setWidth(width + "px"); + } + if (runGeckoFix && BrowserInfo.get().isGecko()) { // workaround for #1764 if (width == null || width.equals("")) { @@ -447,7 +471,7 @@ public class IPanel extends SimplePanel implements Container, } public boolean requestLayout(Set<Paintable> child) { - if (height != null && width != null) { + if (height != null && height != "" && width != null && width != "") { /* * If the height and width has been specified the child components * cannot make the size of the layout change diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/CellBasedLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/CellBasedLayout.java new file mode 100644 index 0000000000..121cdf9ad8 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/CellBasedLayout.java @@ -0,0 +1,287 @@ +package com.itmill.toolkit.terminal.gwt.client.ui.layout;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.ui.MarginInfo;
+
+public abstract class CellBasedLayout extends ComplexPanel implements Container {
+
+ protected Map<Widget, ChildComponentContainer> widgetToComponentContainer = new HashMap<Widget, ChildComponentContainer>();
+
+ protected ApplicationConnection client = null;
+
+ private Element root;
+
+ public static final int ORIENTATION_VERTICAL = 0;
+ public static final int ORIENTATION_HORIZONTAL = 1;
+
+ protected final Margins marginsFromCSS = new Margins(10, 10, 10, 10);
+ protected final Margins activeMargins = new Margins(0, 0, 0, 0);
+ protected MarginInfo activeMarginsInfo = new MarginInfo(false, false,
+ false, false);
+
+ protected boolean spacingEnabled = false;
+ protected final Spacing spacingFromCSS = new Spacing(12, 12);
+ protected final Spacing activeSpacing = new Spacing(0, 0);
+
+ private Widget clearWidget;
+
+ private boolean dynamicWidth;
+
+ private boolean dynamicHeight;
+
+ private static class ClearWidget extends Widget {
+ public ClearWidget() {
+ Element clearElement = DOM.createDiv();
+ DOM.setStyleAttribute(clearElement, "clear", "both");
+ DOM.setStyleAttribute(clearElement, "width", "0px");
+ DOM.setStyleAttribute(clearElement, "height", "0px");
+ setElement(clearElement);
+ }
+ }
+
+ public static class Spacing {
+
+ public int hSpacing = 0;
+ public int vSpacing = 0;
+
+ public Spacing(int hSpacing, int vSpacing) {
+ this.hSpacing = hSpacing;
+ this.vSpacing = vSpacing;
+ }
+
+ @Override
+ public String toString() {
+ return "Spacing [hSpacing=" + hSpacing + ",vSpacing=" + vSpacing
+ + "]";
+ }
+
+ }
+
+ public CellBasedLayout() {
+ super();
+
+ setElement(DOM.createDiv());
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+
+ root = DOM.createDiv();
+ DOM.setStyleAttribute(root, "width", "500%");
+ getElement().appendChild(root);
+
+ clearWidget = new ClearWidget();
+ add(clearWidget, root);
+
+ }
+
+ public boolean hasChildComponent(Widget component) {
+ return widgetToComponentContainer.containsKey(component);
+ }
+
+ public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
+ // TODO: Must support
+ throw new UnsupportedOperationException();
+ }
+
+ public void updateCaption(Paintable component, UIDL uidl) {
+ ChildComponentContainer componentContainer = getComponentContainer((Widget) component);
+ componentContainer.updateCaption(uidl, client);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ this.client = client;
+
+ // Only non-cached UIDL:s can introduce changes
+ if (uidl.getBooleanAttribute("cached")) {
+ return;
+ }
+
+ // This call should be made first. Ensure correct implementation,
+ // and don't let the containing coordinateLayout manage caption, etc.
+
+ if (client.updateComponent(this, uidl, true)) {
+ return;
+ }
+
+ handleMarginsAndSpacing(uidl);
+
+ handleDynamicDimensions(uidl);
+
+ }
+
+ private void handleDynamicDimensions(UIDL uidl) {
+ String w = uidl.hasAttribute("width") ? uidl
+ .getStringAttribute("width") : "";
+
+ String h = uidl.hasAttribute("height") ? uidl
+ .getStringAttribute("height") : "";
+
+ if (w.equals("")) {
+ dynamicWidth = true;
+ } else {
+ dynamicWidth = false;
+ }
+
+ if (h.equals("")) {
+ dynamicHeight = true;
+ } else {
+ dynamicHeight = false;
+ }
+
+ }
+
+ protected void addOrMoveChild(ChildComponentContainer childComponent,
+ int position) {
+ widgetToComponentContainer.put(childComponent.getWidget(),
+ childComponent);
+ super.insert(childComponent, root, position, true);
+
+ }
+
+ protected ChildComponentContainer getComponentContainer(Widget child) {
+ return widgetToComponentContainer.get(child);
+ }
+
+ protected boolean isDynamicWidth() {
+ return dynamicWidth;
+ }
+
+ protected boolean isDynamicHeight() {
+ return dynamicHeight;
+ }
+
+ private void handleMarginsAndSpacing(UIDL uidl) {
+ MarginInfo newMargins = new MarginInfo(uidl.getIntAttribute("margins"));
+ updateMargins(newMargins);
+ boolean spacing = uidl.getBooleanAttribute("spacing");
+ updateSpacing(spacing);
+ }
+
+ private void updateSpacing(boolean spacing) {
+ if (spacing != spacingEnabled) {
+ spacingEnabled = spacing;
+ if (spacing) {
+ activeSpacing.hSpacing = spacingFromCSS.hSpacing;
+ activeSpacing.vSpacing = spacingFromCSS.vSpacing;
+ } else {
+ activeSpacing.hSpacing = 0;
+ activeSpacing.vSpacing = 0;
+ }
+ }
+
+ }
+
+ private void updateMargins(MarginInfo newMargins) {
+ if (newMargins.equals(activeMarginsInfo)) {
+ return;
+ }
+
+ // Update active margins
+ activeMarginsInfo = newMargins;
+ if (newMargins.hasTop()) {
+ activeMargins.setMarginTop(marginsFromCSS.getMarginTop());
+ } else {
+ activeMargins.setMarginTop(0);
+ }
+ if (newMargins.hasBottom()) {
+ activeMargins.setMarginBottom(marginsFromCSS.getMarginBottom());
+ } else {
+ activeMargins.setMarginBottom(0);
+ }
+ if (newMargins.hasLeft()) {
+ activeMargins.setMarginLeft(marginsFromCSS.getMarginLeft());
+ } else {
+ activeMargins.setMarginLeft(0);
+ }
+ if (newMargins.hasRight()) {
+ activeMargins.setMarginRight(marginsFromCSS.getMarginRight());
+ } else {
+ activeMargins.setMarginRight(0);
+ }
+
+ DOM.setStyleAttribute(root, "marginLeft", activeMargins.getMarginLeft()
+ + "px");
+ DOM.setStyleAttribute(root, "marginRight", activeMargins
+ .getMarginRight()
+ + "px");
+ DOM.setStyleAttribute(root, "marginTop", activeMargins.getMarginTop()
+ + "px");
+ DOM.setStyleAttribute(root, "marginBottom", activeMargins
+ .getMarginBottom()
+ + "px");
+
+ }
+
+ protected boolean measureMarginsAndSpacing(String styleName,
+ String marginTopLeftStyleNames, String marginBottomRightStyleNames,
+ String spacingStyleNames) {
+ if (!isAttached()) {
+ return false;
+ }
+
+ Element measurement = DOM.createDiv();
+ DOM.setStyleAttribute(measurement, "position", "absolute");
+ DOM.setStyleAttribute(measurement, "width", "1px");
+ DOM.setStyleAttribute(measurement, "height", "1px");
+ DOM.setStyleAttribute(measurement, "visibility", "hidden");
+
+ root.appendChild(measurement);
+
+ // Measure top and left margins (actually CSS padding)
+ measurement.setClassName(marginTopLeftStyleNames);
+
+ marginsFromCSS.setMarginTop(measurement.getOffsetHeight() - 1);
+ marginsFromCSS.setMarginLeft(measurement.getOffsetWidth() - 1);
+
+ // Measure bottom and right margins (actually CSS padding)
+ measurement.setClassName(marginBottomRightStyleNames);
+
+ marginsFromCSS.setMarginBottom(measurement.getOffsetHeight() - 1);
+ marginsFromCSS.setMarginRight(measurement.getOffsetWidth() - 1);
+
+ // Measure spacing (actually CSS padding)
+ measurement.setClassName(spacingStyleNames);
+
+ spacingFromCSS.vSpacing = measurement.getOffsetHeight() - 1;
+ spacingFromCSS.hSpacing = measurement.getOffsetWidth() - 1;
+
+ ApplicationConnection.getConsole().log("Margins: " + marginsFromCSS);
+ ApplicationConnection.getConsole().log("Spacing: " + spacingFromCSS);
+
+ root.removeChild(measurement);
+
+ return true;
+ }
+
+ protected ChildComponentContainer getFirstChildComponentContainer() {
+ int size = getChildren().size();
+ if (size < 2) {
+ return null;
+ }
+
+ return (ChildComponentContainer) getChildren().get(0);
+ }
+
+ protected void removeChildrenAfter(int pos) {
+ // Remove all children after position "pos" but leave the clear element
+ // in place
+
+ int toRemove = getChildren().size() - pos - 1;
+ while (toRemove-- > 0) {
+ ChildComponentContainer child = (ChildComponentContainer) getChildren()
+ .get(pos);
+ widgetToComponentContainer.remove(child.getWidget());
+ remove(child);
+ }
+
+ }
+
+}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/ChildComponentContainer.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/ChildComponentContainer.java new file mode 100644 index 0000000000..81c41a5949 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/ChildComponentContainer.java @@ -0,0 +1,613 @@ +package com.itmill.toolkit.terminal.gwt.client.ui.layout;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.ICaption;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+import com.itmill.toolkit.terminal.gwt.client.RenderInformation.FloatSize;
+import com.itmill.toolkit.terminal.gwt.client.RenderInformation.Size;
+import com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo;
+
+public class ChildComponentContainer extends Panel {
+
+ /**
+ * Size of the container DIV excluding any margins and also excluding the
+ * expansion amount (containerExpansion)
+ */
+ private Size contSize = new Size(0, 0);
+
+ /**
+ * Size of the widget inside the container DIV
+ */
+ private Size widgetSize = new Size(0, 0);
+
+ /**
+ * Padding added to the container when it is larger than the component.
+ */
+ private Size containerExpansion = new Size(0, 0);
+
+ private float expandRatio;
+
+ private int containerMarginLeft = 0;
+ private int containerMarginTop = 0;
+
+ AlignmentInfo alignment = new AlignmentInfo(AlignmentInfo.ALIGNMENT_LEFT,
+ AlignmentInfo.ALIGNMENT_TOP);
+ private int alignmentLeftOffsetForWidget = 0;
+ private int alignmentLeftOffsetForCaption = 0;
+ /**
+ * Top offset for implementing alignment. Top offset is set to the container
+ * DIV as it otherwise would have to be set to either the Caption or the
+ * Widget depending on whether there is a caption and where the caption is
+ * located.
+ */
+ private int alignmentTopOffset = 0;
+
+ // private Margins alignmentOffset = new Margins(0, 0, 0, 0);
+ private ICaption caption = null;
+ private Element containerDIV;
+ private Element widgetDIV;
+ private Widget widget;
+ private FloatSize relativeSize = null;
+
+ public ChildComponentContainer(Widget widget, int orientation) {
+ super();
+
+ containerDIV = DOM.createDiv();
+ setElement(containerDIV);
+
+ DOM.setStyleAttribute(containerDIV, "height", "0px");
+ // DOM.setStyleAttribute(containerDIV, "width", "0px");
+ DOM.setStyleAttribute(containerDIV, "overflow", "hidden");
+
+ widgetDIV = DOM.createDiv();
+ setFloat(widgetDIV, "left");
+
+ containerDIV.appendChild(widgetDIV);
+
+ setOrientation(orientation);
+
+ setWidget(widget);
+
+ }
+
+ private void setWidget(Widget w) {
+ // Validate
+ if (w == widget) {
+ return;
+ }
+
+ // Detach new child.
+ if (w != null) {
+ w.removeFromParent();
+ }
+
+ // Remove old child.
+ if (widget != null) {
+ remove(widget);
+ }
+
+ // Logical attach.
+ widget = w;
+
+ if (w != null) {
+ // Physical attach.
+ DOM.appendChild(widgetDIV, widget.getElement());
+
+ adopt(w);
+ }
+ }
+
+ private static void setFloat(Element e, String floatString) {
+ Util.setFloat(e, floatString);
+ if (BrowserInfo.get().isIE()) {
+ // IE requires display:inline for margin-left to work together
+ // with float:left
+ if (floatString.equals("left")) {
+ DOM.setStyleAttribute(e, "display", "inline");
+ } else {
+ DOM.setStyleAttribute(e, "display", "block");
+ }
+
+ }
+ }
+
+ public void setOrientation(int orientation) {
+ if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
+ setFloat(containerDIV, "left");
+ } else {
+ setFloat(containerDIV, "");
+ }
+ setHeight("0px");
+ // setWidth("0px");
+ contSize.setHeight(0);
+ contSize.setWidth(0);
+ containerMarginLeft = 0;
+ containerMarginTop = 0;
+ DOM.setStyleAttribute(getElement(), "paddingLeft", "0px");
+ DOM.setStyleAttribute(getElement(), "paddingTop", "0px");
+
+ containerExpansion.setHeight(0);
+ containerExpansion.setWidth(0);
+
+ // Clear old alignments
+ clearAlignments();
+
+ }
+
+ public void renderChild(UIDL childUIDL, ApplicationConnection client) {
+ /*
+ * Must remove width specification from container before rendering to
+ * allow components to grow in horizontal direction
+ */
+ DOM.setStyleAttribute(containerDIV, "width", "");
+ ((Paintable) widget).updateFromUIDL(childUIDL, client);
+ }
+
+ public void updateWidgetSize() {
+ int w = widget.getOffsetWidth();
+ int h = widget.getOffsetHeight();
+
+ widgetSize.setHeight(h);
+ widgetSize.setWidth(w);
+ }
+
+ public void setMarginLeft(int marginLeft) {
+ containerMarginLeft = marginLeft;
+ DOM.setStyleAttribute(getElement(), "paddingLeft", marginLeft + "px");
+ }
+
+ public void setMarginTop(int marginTop) {
+ containerMarginTop = marginTop;
+ DOM.setStyleAttribute(getElement(), "paddingTop", marginTop
+ + alignmentTopOffset + "px");
+
+ updateContainerDOMSize();
+ }
+
+ public void updateAlignments(int parentWidth, int parentHeight) {
+ if (parentHeight == -1) {
+ parentHeight = contSize.getHeight();
+ }
+ if (parentWidth == -1) {
+ parentWidth = contSize.getWidth();
+ }
+
+ alignmentTopOffset = calculateVerticalAlignmentTopOffset(parentHeight);
+
+ calculateHorizontalAlignment(parentWidth);
+
+ applyAlignments();
+
+ }
+
+ private void applyAlignments() {
+
+ // Update top margin to take alignment into account
+ setMarginTop(containerMarginTop);
+
+ if (caption != null) {
+ DOM.setStyleAttribute(caption.getElement(), "marginLeft",
+ alignmentLeftOffsetForCaption + "px");
+ }
+ DOM.setStyleAttribute(widgetDIV, "marginLeft",
+ alignmentLeftOffsetForWidget + "px");
+ }
+
+ public int getCaptionWidth() {
+ if (caption == null) {
+ return 0;
+ }
+
+ return caption.getWidth();
+ }
+
+ public int getCaptionHeight() {
+ if (caption == null) {
+ return 0;
+ }
+
+ return caption.getHeight();
+ }
+
+ public int getCaptionWidthAfterComponent() {
+ if (caption == null || !caption.shouldBePlacedAfterComponent()) {
+ return 0;
+ }
+
+ return caption.getWidth();
+ }
+
+ public int getCaptionHeightAboveComponent() {
+ if (caption == null || caption.shouldBePlacedAfterComponent()) {
+ return 0;
+ }
+
+ return caption.getHeight();
+ }
+
+ public int calculateVerticalAlignmentTopOffset(int emptySpace) {
+ if (alignment.isTop()) {
+ return 0;
+ }
+
+ if (caption != null) {
+ if (caption.shouldBePlacedAfterComponent()) {
+ /*
+ * Take into account the rare case that the caption on the right
+ * side of the component AND is higher than the component
+ */
+ emptySpace -= Math.max(widgetSize.getHeight(), caption
+ .getHeight());
+ } else {
+ emptySpace -= widgetSize.getHeight();
+ emptySpace -= caption.getHeight();
+ }
+ } else {
+ /*
+ * There is no caption and thus we do not need to take anything but
+ * the widget into account
+ */
+ emptySpace -= widgetSize.getHeight();
+ }
+
+ int top = 0;
+ if (alignment.isVerticalCenter()) {
+ top = emptySpace / 2;
+ } else if (alignment.isBottom()) {
+ top = emptySpace;
+ }
+
+ if (top < 0) {
+ top = 0;
+ }
+ return top;
+ }
+
+ private void calculateHorizontalAlignment(int emptySpace) {
+ alignmentLeftOffsetForCaption = 0;
+ alignmentLeftOffsetForWidget = 0;
+
+ if (alignment.isLeft()) {
+ return;
+ }
+
+ int captionSpace = emptySpace;
+ int widgetSpace = emptySpace;
+
+ if (caption != null) {
+ // There is a caption
+ if (caption.shouldBePlacedAfterComponent()) {
+ /*
+ * The caption is after component. In this case the caption
+ * needs no alignment.
+ */
+ captionSpace = 0;
+ widgetSpace -= widgetSize.getWidth();
+ widgetSpace -= caption.getWidth();
+ } else {
+ /*
+ * The caption is above the component. Caption and widget needs
+ * separate alignment offsets.
+ */
+ widgetSpace -= widgetSize.getWidth();
+ captionSpace -= caption.getWidth();
+ }
+ } else {
+ /*
+ * There is no caption and thus we do not need to take anything but
+ * the widget into account
+ */
+ captionSpace = 0;
+ widgetSpace -= widgetSize.getWidth();
+ }
+
+ if (alignment.isHorizontalCenter()) {
+ alignmentLeftOffsetForCaption = captionSpace / 2;
+ alignmentLeftOffsetForWidget = widgetSpace / 2;
+ } else if (alignment.isRight()) {
+ alignmentLeftOffsetForCaption = captionSpace;
+ alignmentLeftOffsetForWidget = widgetSpace;
+ }
+
+ if (alignmentLeftOffsetForCaption < 0) {
+ alignmentLeftOffsetForCaption = 0;
+ }
+ if (alignmentLeftOffsetForWidget < 0) {
+ alignmentLeftOffsetForWidget = 0;
+ }
+
+ }
+
+ public void setAlignment(AlignmentInfo alignmentInfo) {
+ alignment = alignmentInfo;
+
+ }
+
+ public Size getWidgetSize() {
+ return widgetSize;
+ }
+
+ public void updateCaption(UIDL uidl, ApplicationConnection client) {
+ if (ICaption.isNeeded(uidl)) {
+ // We need a caption
+
+ ICaption newCaption = caption;
+
+ if (newCaption == null) {
+ newCaption = new ICaption((Paintable) widget, client);
+ }
+
+ boolean positionChanged = newCaption.updateCaption(uidl);
+
+ if (newCaption != caption || positionChanged) {
+ setCaption(newCaption);
+ }
+
+ } else {
+ // Caption is not needed
+ if (caption != null) {
+ remove(caption);
+ }
+
+ }
+
+ }
+
+ private void setCaption(ICaption newCaption) {
+ // Validate
+ // if (newCaption == caption) {
+ // return;
+ // }
+
+ // Detach new child.
+ if (newCaption != null) {
+ newCaption.removeFromParent();
+ }
+
+ // Remove old child.
+ if (caption != null && newCaption != caption) {
+ remove(caption);
+ }
+
+ // Logical attach.
+ caption = newCaption;
+
+ if (caption != null) {
+ // Physical attach.
+ if (caption.shouldBePlacedAfterComponent()) {
+ Util.setFloat(caption.getElement(), "left");
+ containerDIV.appendChild(caption.getElement());
+ } else {
+ Util.setFloat(caption.getElement(), "");
+ containerDIV.insertBefore(caption.getElement(), widgetDIV);
+ }
+
+ adopt(caption);
+ }
+
+ }
+
+ @Override
+ public boolean remove(Widget child) {
+ // Validate
+ if (child != caption && child != widget) {
+ return false;
+ }
+
+ // Orphan
+ orphan(child);
+
+ // Physical && Logical Detach
+ if (child == caption) {
+ containerDIV.removeChild(caption.getElement());
+ caption = null;
+ } else {
+ containerDIV.removeChild(widget.getElement());
+ widget = null;
+ }
+
+ return true;
+ }
+
+ public Iterator<Widget> iterator() {
+ return new ChildComponentContainerIterator<Widget>();
+ }
+
+ public class ChildComponentContainerIterator<T> implements Iterator<Widget> {
+ private int id = 0;
+
+ public boolean hasNext() {
+ return (id < size());
+ }
+
+ public Widget next() {
+ Widget w = get(id);
+ id++;
+ return w;
+ }
+
+ private Widget get(int i) {
+ if (i == 0) {
+ if (widget != null) {
+ return widget;
+ } else if (caption != null) {
+ return caption;
+ } else {
+ throw new NoSuchElementException();
+ }
+ } else if (i == 1) {
+ if (widget != null && caption != null) {
+ return caption;
+ } else {
+ throw new NoSuchElementException();
+ }
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ public void remove() {
+ int toRemove = id - 1;
+ if (toRemove == 0) {
+ if (widget != null) {
+ ChildComponentContainer.this.remove(widget);
+ } else if (caption != null) {
+ ChildComponentContainer.this.remove(caption);
+ } else {
+ throw new IllegalStateException();
+ }
+
+ } else if (toRemove == 1) {
+ if (widget != null && caption != null) {
+ ChildComponentContainer.this.remove(caption);
+ } else {
+ throw new IllegalStateException();
+ }
+ } else {
+ throw new IllegalStateException();
+ }
+
+ id--;
+ }
+ }
+
+ public int size() {
+ if (widget != null) {
+ if (caption != null) {
+ return 2;
+ } else {
+ return 1;
+ }
+ } else {
+ if (caption != null) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ public Widget getWidget() {
+ return widget;
+ }
+
+ public boolean isComponentRelativeSized(int orientation) {
+ if (relativeSize == null) {
+ return false;
+ }
+ if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
+ return relativeSize.getWidth() >= 0;
+ } else {
+ return relativeSize.getHeight() >= 0;
+ }
+ }
+
+ public void setRelativeSize(FloatSize relativeSize) {
+ this.relativeSize = relativeSize;
+ }
+
+ public Size getContSize() {
+ return contSize;
+ }
+
+ public void clearAlignments() {
+ alignmentLeftOffsetForCaption = 0;
+ alignmentLeftOffsetForWidget = 0;
+ alignmentTopOffset = 0;
+ applyAlignments();
+
+ }
+
+ public void setExpandRatio(int expandRatio) {
+ this.expandRatio = (expandRatio / 1000.0f);
+ }
+
+ public int expand(int orientation, int spaceForExpansion) {
+ int expansionAmount = (int) ((double) spaceForExpansion * expandRatio);
+
+ if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
+ // HORIZONTAL
+ containerExpansion.setWidth(expansionAmount);
+ } else {
+ // VERTICAL
+ containerExpansion.setHeight(expansionAmount);
+ }
+
+ return expansionAmount;
+ }
+
+ public void expandExtra(int orientation, int extra) {
+ if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
+ // HORIZONTAL
+ containerExpansion.setWidth(containerExpansion.getWidth() + extra);
+ } else {
+ // VERTICAL
+ containerExpansion
+ .setHeight(containerExpansion.getHeight() + extra);
+ }
+
+ }
+
+ public void setContainerSize(int widgetAndCaptionWidth,
+ int widgetAndCaptionHeight) {
+
+ int containerWidth = widgetAndCaptionWidth;
+ containerWidth += containerExpansion.getWidth();
+
+ int containerHeight = widgetAndCaptionHeight;
+ containerHeight += containerExpansion.getHeight();
+
+ ApplicationConnection.getConsole().log(
+ "Setting container size for " + Util.getSimpleName(widget)
+ + " to " + containerWidth + "," + containerHeight);
+
+ if (containerWidth < 0) {
+ ApplicationConnection.getConsole().error(
+ "containerWidth should never be negative: "
+ + containerWidth);
+ containerWidth = 0;
+ }
+ if (containerHeight < 0) {
+ ApplicationConnection.getConsole().error(
+ "containerHeight should never be negative: "
+ + containerHeight);
+ containerHeight = 0;
+ }
+
+ contSize.setWidth(containerWidth);
+ contSize.setHeight(containerHeight);
+
+ updateContainerDOMSize();
+ }
+
+ public void updateContainerDOMSize() {
+ int width = contSize.getWidth();
+ int height = contSize.getHeight() - alignmentTopOffset;
+ if (width < 0) {
+ width = 0;
+ }
+ if (height < 0) {
+ height = 0;
+ }
+
+ setWidth(width + "px");
+ setHeight(height + "px");
+
+ // Also update caption max width
+ if (caption != null) {
+ caption.setMaxWidth(width);
+ }
+
+ }
+
+}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/Margins.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/Margins.java new file mode 100644 index 0000000000..06332c93ea --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/layout/Margins.java @@ -0,0 +1,83 @@ +package com.itmill.toolkit.terminal.gwt.client.ui.layout;
+
+public class Margins {
+
+ private int marginTop;
+ private int marginBottom;
+ private int marginLeft;
+ private int marginRight;
+
+ private int horizontal = 0;
+ private int vertical = 0;
+
+ public Margins(int marginTop, int marginBottom, int marginLeft,
+ int marginRight) {
+ super();
+ this.marginTop = marginTop;
+ this.marginBottom = marginBottom;
+ this.marginLeft = marginLeft;
+ this.marginRight = marginRight;
+
+ updateHorizontal();
+ updateVertical();
+ }
+
+ public int getMarginTop() {
+ return marginTop;
+ }
+
+ public int getMarginBottom() {
+ return marginBottom;
+ }
+
+ public int getMarginLeft() {
+ return marginLeft;
+ }
+
+ public int getMarginRight() {
+ return marginRight;
+ }
+
+ public int getHorizontal() {
+ return horizontal;
+ }
+
+ public int getVertical() {
+ return vertical;
+ }
+
+ public void setMarginTop(int marginTop) {
+ this.marginTop = marginTop;
+ updateVertical();
+ }
+
+ public void setMarginBottom(int marginBottom) {
+ this.marginBottom = marginBottom;
+ updateVertical();
+ }
+
+ public void setMarginLeft(int marginLeft) {
+ this.marginLeft = marginLeft;
+ updateHorizontal();
+ }
+
+ public void setMarginRight(int marginRight) {
+ this.marginRight = marginRight;
+ updateHorizontal();
+ }
+
+ private void updateVertical() {
+ vertical = marginTop + marginBottom;
+ }
+
+ private void updateHorizontal() {
+ horizontal = marginLeft + marginRight;
+ }
+
+ @Override
+ public String toString() {
+ return "Margins [marginLeft=" + marginLeft + ",marginTop=" + marginTop
+ + ",marginRight=" + marginRight + ",marginBottom="
+ + marginBottom + "]";
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/ExpandLayout.java b/src/com/itmill/toolkit/ui/ExpandLayout.java index 2377c1f052..7530d7c241 100644 --- a/src/com/itmill/toolkit/ui/ExpandLayout.java +++ b/src/com/itmill/toolkit/ui/ExpandLayout.java @@ -4,11 +4,6 @@ package com.itmill.toolkit.ui; -import java.util.Iterator; - -import com.itmill.toolkit.terminal.PaintException; -import com.itmill.toolkit.terminal.PaintTarget; - /** * A layout that will give one of it's components as much space as possible, * while still showing the other components in the layout. The other components @@ -21,18 +16,21 @@ import com.itmill.toolkit.terminal.PaintTarget; * fixed size. If the layout fails to show up, check that the parent layout is * actually giving some space. * + * @deprecated Deprecated in favor of new OrderedLayout */ +@Deprecated public class ExpandLayout extends OrderedLayout { - private Component expanded; + private Component expanded = null; public ExpandLayout() { - setSizeFull(); + this(ORIENTATION_VERTICAL); } public ExpandLayout(int orientation) { - this(); - setOrientation(orientation); + super(orientation); + + setSizeFull(); } /** @@ -40,90 +38,13 @@ public class ExpandLayout extends OrderedLayout { * Component which container will be maximized */ public void expand(Component c) { - expanded = c; - requestRepaint(); - } - - public String getTag() { - return "expandlayout"; - } - - public void paintContent(PaintTarget target) throws PaintException { - - // Add margin info. Defaults to false. - target.addAttribute("margins", margins.getBitMask()); - - // Add spacing attribute (omitted if false) - if (isSpacingEnabled()) { - target.addAttribute("spacing", true); - } - - // Adds the attributes: orientation - // note that the default values (b/vertival) are omitted - if (getOrientation() == ORIENTATION_HORIZONTAL) { - target.addAttribute("orientation", "horizontal"); - } - - final String[] alignmentsArray = new String[components.size()]; - - // Adds all items in all the locations - int index = 0; - for (final Iterator i = getComponentIterator(); i.hasNext();) { - final Component c = (Component) i.next(); - if (c != null) { - target.startTag("cc"); - if (c == expanded) { - target.addAttribute("expanded", true); - } - c.paint(target); - target.endTag("cc"); - } - alignmentsArray[index++] = String.valueOf(getComponentAlignment(c)); - + if (expanded != null) { + setExpandRatio(expanded, 0.0f); } - // Add child component alignment info to layout tag - target.addAttribute("alignments", alignmentsArray); - - } - - public void addComponent(Component c, int index) { - if (expanded == null) { - expanded = c; - } - super.addComponent(c, index); - } - - public void addComponent(Component c) { - if (expanded == null) { - expanded = c; - } - super.addComponent(c); - } - - public void addComponentAsFirst(Component c) { - if (expanded == null) { - expanded = c; - } - super.addComponentAsFirst(c); - } - - public void removeComponent(Component c) { - super.removeComponent(c); - if (c == expanded) { - if (getComponentIterator().hasNext()) { - expanded = (Component) getComponentIterator().next(); - } else { - expanded = null; - } - } - } - - public void replaceComponent(Component oldComponent, Component newComponent) { - super.replaceComponent(oldComponent, newComponent); - if (oldComponent == expanded) { - expanded = newComponent; - } + expanded = c; + setExpandRatio(expanded, 1.0f); + requestRepaint(); } } diff --git a/src/com/itmill/toolkit/ui/OldOrderedLayout.java b/src/com/itmill/toolkit/ui/OldOrderedLayout.java new file mode 100644 index 0000000000..37b1e1b174 --- /dev/null +++ b/src/com/itmill/toolkit/ui/OldOrderedLayout.java @@ -0,0 +1,18 @@ +package com.itmill.toolkit.ui;
+
+public class OldOrderedLayout extends OrderedLayout {
+
+ public OldOrderedLayout() {
+ super();
+ }
+
+ public OldOrderedLayout(int orientation) {
+ super(orientation);
+ }
+
+ @Override
+ public String getTag() {
+ return "oldorderedlayout";
+ }
+
+}
|