From cf75d2d5d12618852787d4a9346b66a934133cfd Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 20 Oct 2008 08:47:49 +0000 Subject: [PATCH] New OrderedLayout implementation and related fixes svn changeset:5671/svn branch:trunk --- .../ITMILL/themes/default/caption/caption.css | 6 +- .../default/orderedlayout/orderedlayout.css | 2 +- .../ITMILL/themes/default/select/select.css | 7 +- .../gwt/client/ApplicationConnection.java | 70 +- .../terminal/gwt/client/Container.java | 4 +- .../terminal/gwt/client/DefaultWidgetSet.java | 6 + .../toolkit/terminal/gwt/client/ICaption.java | 143 +- .../toolkit/terminal/gwt/client/Util.java | 42 +- .../terminal/gwt/client/ui/IFilterSelect.java | 10 + .../gwt/client/ui/IOldOrderedLayout.java | 1603 ++++++++++++ .../gwt/client/ui/IOrderedLayout.java | 2239 +++++------------ .../terminal/gwt/client/ui/IPanel.java | 30 +- .../gwt/client/ui/layout/CellBasedLayout.java | 287 +++ .../ui/layout/ChildComponentContainer.java | 613 +++++ .../gwt/client/ui/layout/Margins.java | 83 + src/com/itmill/toolkit/ui/ExpandLayout.java | 103 +- .../itmill/toolkit/ui/OldOrderedLayout.java | 18 + 17 files changed, 3518 insertions(+), 1748 deletions(-) create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/IOldOrderedLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/layout/CellBasedLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/layout/ChildComponentContainer.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/layout/Margins.java create mode 100644 src/com/itmill/toolkit/ui/OldOrderedLayout.java 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 relativeSizeChanges = new ArrayList();; + public ApplicationConnection(WidgetSet widgetSet, ApplicationConfiguration cnf) { this.widgetSet = widgetSet; @@ -552,6 +555,7 @@ public class ApplicationConnection { .get("changes"); Vector updatedWidgets = new Vector(); + 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 sizeUpdatedWidgets = new HashSet(); + 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 child); + boolean requestLayout(Set 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 w = new HashSet(); + // 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. + * + * + *

Features

+ * + *

Orientation

+ * + *

+ * Orientation of the ordered layout declared whether the children are layouted + * horizontally or vertically. + *

+ * + * + * + *

Spacing

+ * + *

+ * Spacing determines if there should be space between the children. Note that + * this does not imply margin. + *

+ * + * + * + *

Margin

+ * + *

+ * Margin determines if there should be margin around children. Note that this + * does not imply spacing. + *

+ * + * + * + *

Positioning the caption, icon, required indicator and error

+ * + *

+ * 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: + *

+ * + * + * + *

+ * 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: + *

+ * + * + * + *

+ * In case the child want to handle the caption by itself, layout does not + * repeat the caption. + *

+ * + * + * + *

Aligning the children

+ * + *

+ * The children of the layout can be aligned horizontally and vertically: + *

+ * + * + * + *

Fixed height, width or both

+ * + *

+ * 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. + *

+ * + *

+ * Horizontal layout with fixed width of 300px and height of 150px: + *

+ * + * + *

+ * Horizontal layout with fixed width of 300px: + *

+ * + * + *

+ * Horizontal layout with fixed height of 150px: + *

+ * + * + * + *

CSS attributes

+ * + *

+ * Sizes for marginals and spacing can be specified for the ordered layout in + * CSS. For example, here are the defaults for OrderedLayout: + *

+ * + *
+ * .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;
+ * }
+ * 
+ * + *

+ * 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: + *

+ * + *
+ * .i-orderedlayout-tested-layout-margin-right {
+ *         padding-right: 100px;
+ * }
+ * 
+ * + *

+ * 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: + *

+ * + * + * + *

DOM-structure

+ * + * 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. + * + *
OUTERDIV + * + *
Optional STRUCTURE + * + *
CHILDWRAPPER (for each + * child) + * + *
Optional ALIGNMENTWRAPPER + * + *
Optional CLIPPER + * + *
CAPTION ICON-IMG CAPTION-SPAN REQUIRED-SPAN ERRORINDICATOR-DIV + *
+ * + *
Widget + * component
+ * + *
+ * + *
+ * + *

+ * Notes: + *

    + *
  • If caption and icon are missing from child, Widget component and + * CAPTION elements are swithched
  • + *
  • If either child manages caption, or it has no caption, icon, required or + * error, CAPTION element is not needed at all
  • + *
  • If layout is vertical and its width is specified, Optional + * STRUCTURE is not present. Otherwise it looks like
    TABLE
    TBODY
    Optional TR only included in + * case of horizontal layouts
  • + *
  • CHILDWRAPPER 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.
  • + *
  • Optionasl ALIGNMENTWRAPPER 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.
  • + *
  • Optional CLIPPERDIV included in the structure only if alignment + * structure is in place and CHILDWRAPPER is not a div and thus can not + * be used for clipping
  • + *
+ *

+ * + * + * @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 childWidgetWrappers = new Vector(); + + /** 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. + * + *

+ * There are two modes - vertical and horizontal. + *

    + *
  • Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap + * ( child ))).
  • + *
  • Horizontal mode uses structure: table ( tbody ( tr-childcontainer ( + * td-wrap ( child ) td-wrap ( child) )) )
  • + *
+ * where root and childcontainer refer to the root element and the element + * that contain WidgetWrappers. + *

+ * + */ + 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 = "" + : "") + "
"; + + 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: + *
    + *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • + *
  • (b) TD in just below the root TR of the WrapperElement when in + * tableMode
  • + *
  • (c) clipperDiv inside the (a) or (b)
  • + *
  • (d) The innermost TD within alignment structures located in (a), + * (b) or (c)
  • + *
+ * + * @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: + *
    + *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • + *
  • (b) TD in just below the root TR of the WrapperElement when in + * tableMode
  • + *
  • (c) clipperDiv inside the (a) or (b)
  • + *
+ * + * @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: + *
    + *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • + *
  • (b) TD in just below the root TR of the WrapperElement when in + * tableMode
  • + *
+ * + * @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 = "
" + + "
"; + 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) { + /* + * Validate: 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; + } + + /* + * Adjust for Reinsertion: 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; + } + + /* + * Detach Child: Remove the Widget from its existing parent, if + * any. Most Panels will simply call {@link Widget#removeFromParent()} + * on the Widget. + */ + child.removeFromParent(); + + /* + * Logical Attach: 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); + + /* + * Physical Attach: 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()); + + /* + * Adopt: Call {@link #adopt(Widget)} to finalize the add as the + * very last step. + */ + adopt(child); + } + + /* documented at super */ + public boolean remove(Widget child) { + + /* + * Validate: Make sure this Panel is actually the parent of the + * child Widget; return false if it is not. + */ + if (!childWidgets.contains(child)) { + return false; + } + + /* + * Orphan: Call {@link #orphan(Widget)} first while the child + * Widget is still attached. + */ + orphan(child); + + /* + * Physical Detach: 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); + + /* + * Logical Detach: 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 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. - * - * - *

Features

- * - *

Orientation

- * - *

- * Orientation of the ordered layout declared whether the children are layouted - * horizontally or vertically. - *

- * - * - * - *

Spacing

- * - *

- * Spacing determines if there should be space between the children. Note that - * this does not imply margin. - *

- * - * - * - *

Margin

- * - *

- * Margin determines if there should be margin around children. Note that this - * does not imply spacing. - *

- * - * - * - *

Positioning the caption, icon, required indicator and error

- * - *

- * 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: - *

- * - * - * - *

- * 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: - *

- * - * - * - *

- * In case the child want to handle the caption by itself, layout does not - * repeat the caption. - *

- * - * - * - *

Aligning the children

- * - *

- * The children of the layout can be aligned horizontally and vertically: - *

- * - * - * - *

Fixed height, width or both

- * - *

- * 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. - *

- * - *

- * Horizontal layout with fixed width of 300px and height of 150px: - *

- * - * - *

- * Horizontal layout with fixed width of 300px: - *

- * - * - *

- * Horizontal layout with fixed height of 150px: - *

- * - * - * - *

CSS attributes

- * - *

- * Sizes for marginals and spacing can be specified for the ordered layout in - * CSS. For example, here are the defaults for OrderedLayout: - *

- * - *
- * .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;
- * }
- * 
- * - *

- * 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: - *

- * - *
- * .i-orderedlayout-tested-layout-margin-right {
- *         padding-right: 100px;
- * }
- * 
- * - *

- * 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: - *

- * - * - * - *

DOM-structure

- * - * 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. - * - *
OUTERDIV - * - *
Optional STRUCTURE - * - *
CHILDWRAPPER (for each - * child) - * - *
Optional ALIGNMENTWRAPPER - * - *
Optional CLIPPER - * - *
CAPTION ICON-IMG CAPTION-SPAN REQUIRED-SPAN ERRORINDICATOR-DIV - *
- * - *
Widget - * component
- * - *
- * - *
- * - *

- * Notes: - *

    - *
  • If caption and icon are missing from child, Widget component and - * CAPTION elements are swithched
  • - *
  • If either child manages caption, or it has no caption, icon, required or - * error, CAPTION element is not needed at all
  • - *
  • If layout is vertical and its width is specified, Optional - * STRUCTURE is not present. Otherwise it looks like
    TABLE
    TBODY
    Optional TR only included in - * case of horizontal layouts
  • - *
  • CHILDWRAPPER 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.
  • - *
  • Optionasl ALIGNMENTWRAPPER 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.
  • - *
  • Optional CLIPPERDIV included in the structure only if alignment - * structure is in place and CHILDWRAPPER is not a div and thus can not - * be used for clipping
  • - *
- *

- * - * - * @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 childWidgetWrappers = new Vector(); - - /** 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. - * - *

- * There are two modes - vertical and horizontal. - *

    - *
  • Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap - * ( child ))).
  • - *
  • Horizontal mode uses structure: table ( tbody ( tr-childcontainer ( - * td-wrap ( child ) td-wrap ( child) )) )
  • - *
- * where root and childcontainer refer to the root element and the element - * that contain WidgetWrappers. - *

- * - */ - 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 = "" - : "") + "
"; - - 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: - *
    - *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • - *
  • (b) TD in just below the root TR of the WrapperElement when in - * tableMode
  • - *
  • (c) clipperDiv inside the (a) or (b)
  • - *
  • (d) The innermost TD within alignment structures located in (a), - * (b) or (c)
  • - *
- * - * @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: - *
    - *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • - *
  • (b) TD in just below the root TR of the WrapperElement when in - * tableMode
  • - *
  • (c) clipperDiv inside the (a) or (b)
  • - *
- * - * @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: - *
    - *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • - *
  • (b) TD in just below the root TR of the WrapperElement when in - * tableMode
  • - *
- * - * @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 = "
" - + "
"; - 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) { - /* - * Validate: 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; - } - - /* - * Adjust for Reinsertion: 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; - } - - /* - * Detach Child: Remove the Widget from its existing parent, if - * any. Most Panels will simply call {@link Widget#removeFromParent()} - * on the Widget. - */ - child.removeFromParent(); - - /* - * Logical Attach: 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); - - /* - * Physical Attach: 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()); - - /* - * Adopt: Call {@link #adopt(Widget)} to finalize the add as the - * very last step. - */ - adopt(child); - } - - /* documented at super */ - public boolean remove(Widget child) { - - /* - * Validate: Make sure this Panel is actually the parent of the - * child Widget; return false if it is not. - */ - if (!childWidgets.contains(child)) { - return false; - } - - /* - * Orphan: Call {@link #orphan(Widget)} first while the child - * Widget is still attached. - */ - orphan(child); - - /* - * Physical Detach: 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); - - /* - * Logical Detach: 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 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 uidlWidgets = new ArrayList(uidl + .getChildCount()); + ArrayList relativeSizeComponents = new ArrayList(); + ArrayList relativeSizeComponentUIDL = new ArrayList(); + ArrayList relativeSizeWidgets = new ArrayList(); + + int pos = 0; + for (final Iterator 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 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 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 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 widgetToComponentContainer = new HashMap(); + + 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 iterator() { + return new ChildComponentContainerIterator(); + } + + public class ChildComponentContainerIterator implements Iterator { + 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"; + } + +} -- 2.39.5