/*
* Copyright 2011 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.terminal.gwt.client.ui.layout;
import java.util.Iterator;
import java.util.NoSuchElementException;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
import com.vaadin.terminal.gwt.client.RenderInformation.Size;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.Util;
import com.vaadin.terminal.gwt.client.VCaption;
import com.vaadin.terminal.gwt.client.VConsole;
import com.vaadin.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);
/**
* Size of the caption
*/
private int captionRequiredWidth = 0;
private int captionWidth = 0;
private int captionHeight = 0;
/**
*
* Padding added to the container when it is larger than the component.
*/
private Size containerExpansion = new Size(0, 0);
private double expandRatio;
// private int containerMarginLeft = 0;
private int containerMarginTop = 0;
AlignmentInfo alignment = AlignmentInfo.TOP_LEFT;
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 VCaption caption = null;
private DivElement containerDIV;
private DivElement widgetDIV;
private Widget widget;
private FloatSize relativeSize = null;
public ChildComponentContainer(Widget widget, int orientation) {
super();
containerDIV = Document.get().createDivElement();
widgetDIV = Document.get().createDivElement();
if (BrowserInfo.get().isFF2()) {
// Style style = widgetDIV.getStyle();
// FF2 chokes on some floats very easily. Measuring size escpecially
// becomes terribly slow
TableElement tableEl = Document.get().createTableElement();
tableEl.setInnerHTML("<tbody><tr><td><div></div></td></tr></tbody>");
DivElement div = (DivElement) tableEl.getFirstChildElement()
.getFirstChildElement().getFirstChildElement()
.getFirstChildElement();
tableEl.setCellPadding(0);
tableEl.setCellSpacing(0);
tableEl.setBorder(0);
div.getStyle().setProperty("padding", "0");
setElement(tableEl);
containerDIV = div;
} else {
setFloat(widgetDIV, "left");
setElement(containerDIV);
containerDIV.getStyle().setProperty("height", "0");
containerDIV.getStyle().setProperty("width", "0px");
containerDIV.getStyle().setProperty("overflow", "hidden");
}
if (BrowserInfo.get().isIE()) {
/*
* IE requires position: relative on overflow:hidden elements if
* they should hide position:relative elements. Without this e.g. a
* 1000x1000 Panel inside an 500x500 OrderedLayout will not be
* clipped but fully shown.
*/
containerDIV.getStyle().setProperty("position", "relative");
widgetDIV.getStyle().setProperty("position", "relative");
}
containerDIV.appendChild(widgetDIV);
setOrientation(orientation);
setWidget(widget);
}
public 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.
widgetDIV.appendChild(widget.getElement());
adopt(w);
}
}
private static void setFloat(Element div, String floatString) {
if (BrowserInfo.get().isIE()) {
div.getStyle().setProperty("styleFloat", floatString);
// IE requires display:inline for margin-left to work together
// with float:left
if (floatString.equals("left")) {
div.getStyle().setProperty("display", "inline");
} else {
div.getStyle().setProperty("display", "block");
}
} else {
div.getStyle().setProperty("cssFloat", floatString);
}
}
public void setOrientation(int orientation) {
if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
setFloat(getElement(), "left");
} else {
setFloat(getElement(), "");
}
setHeight("0px");
// setWidth("0px");
contSize.setHeight(0);
contSize.setWidth(0);
// containerMarginLeft = 0;
containerMarginTop = 0;
containerDIV.getStyle().setProperty("paddingLeft", "0");
containerDIV.getStyle().setProperty("paddingTop", "0");
containerExpansion.setHeight(0);
containerExpansion.setWidth(0);
// Clear old alignments
clearAlignments();
}
public void renderChild(UIDL childUIDL, ApplicationConnection client,
int fixedWidth) {
/*
* Must remove width specification from container before rendering to
* allow components to grow in horizontal direction.
*
* For fixed width layouts we specify the width directly so that height
* is automatically calculated correctly (e.g. for Labels).
*/
/*
* This should no longer be needed (after #2563) as all components are
* such that they can be rendered inside a 0x0 DIV.
*
* The exception seems to be complex components (Tree and Table) on
* Opera (#3444).
*/
if (fixedWidth < 0 && BrowserInfo.get().isOpera()) {
setUnlimitedContainerWidth();
}
((Paintable) widget).updateFromUIDL(childUIDL, client);
}
public void setUnlimitedContainerWidth() {
setLimitedContainerWidth(1000000);
}
public void setLimitedContainerWidth(int width) {
containerDIV.getStyle().setProperty("width", width + "px");
}
public void updateWidgetSize() {
/*
* Widget wrapper includes margin which the widget offsetWidth/Height
* does not include
*/
int w = Util.getRequiredWidth(widgetDIV);
int h = Util.getRequiredHeight(widgetDIV);
widgetSize.setWidth(w);
widgetSize.setHeight(h);
// ApplicationConnection.getConsole().log(
// Util.getSimpleName(widget) + " size is " + w + "," + h);
}
public void setMarginLeft(int marginLeft) {
// containerMarginLeft = marginLeft;
containerDIV.getStyle().setPropertyPx("paddingLeft", marginLeft);
}
public void setMarginTop(int marginTop) {
containerMarginTop = marginTop;
containerDIV.getStyle().setPropertyPx("paddingTop",
marginTop + alignmentTopOffset);
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) {
caption.getElement().getStyle()
.setPropertyPx("marginLeft", alignmentLeftOffsetForCaption);
}
widgetDIV.getStyle().setPropertyPx("marginLeft",
alignmentLeftOffsetForWidget);
}
public int getCaptionRequiredWidth() {
if (caption == null) {
return 0;
}
return captionRequiredWidth;
}
public int getCaptionWidth() {
if (caption == null) {
return 0;
}
return captionWidth;
}
public int getCaptionHeight() {
if (caption == null) {
return 0;
}
return captionHeight;
}
public int getCaptionWidthAfterComponent() {
if (caption == null || !caption.shouldBePlacedAfterComponent()) {
return 0;
}
return getCaptionWidth();
}
public int getCaptionHeightAboveComponent() {
if (caption == null || caption.shouldBePlacedAfterComponent()) {
return 0;
}
return getCaptionHeight();
}
private 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 -= getCaptionHeight();
}
} 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 -= getCaptionWidth();
} else {
/*
* The caption is above the component. Caption and widget needs
* separate alignment offsets.
*/
widgetSpace -= widgetSize.getWidth();
captionSpace -= getCaptionWidth();
}
} 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 (VCaption.isNeeded(uidl)) {
// We need a caption
VCaption newCaption = caption;
if (newCaption == null) {
newCaption = new VCaption((Paintable) widget, client);
// Set initial height to avoid Safari flicker
newCaption.setHeight("18px");
// newCaption.setHeight(newCaption.getHeight()); // This might
// be better... ??
if (BrowserInfo.get().isIE()) {
/*
* Must attach caption here so IE sends an immediate onload
* event for images coming from the cache
*/
setCaption(newCaption);
}
}
boolean positionChanged = newCaption.updateCaption(uidl);
if (newCaption != caption || positionChanged) {
setCaption(newCaption);
}
} else {
// Caption is not needed
if (caption != null) {
remove(caption);
}
}
updateCaptionSize();
if (relativeSize == null) {
/*
* relativeSize may be null if component is updated via independent
* update, after it has initially been hidden. See #4608
*
* It might also change in which case there would be similar issues.
*
* Yes, it is an ugly hack. Don't come telling me about it.
*/
setRelativeSize(Util.parseRelativeSize(uidl));
}
}
public void updateCaptionSize() {
captionWidth = 0;
captionHeight = 0;
if (caption != null) {
captionWidth = caption.getRenderedWidth();
captionHeight = caption.getHeight();
captionRequiredWidth = caption.getRequiredWidth();
/*
* ApplicationConnection.getConsole().log(
* "Caption rendered width: " + captionWidth +
* ", caption required width: " + captionRequiredWidth +
* ", caption height: " + captionHeight);
*/
}
}
private void setCaption(VCaption 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(child.getElement());
caption = null;
} else {
widgetDIV.removeChild(child.getElement());
widget = null;
}
return true;
}
public Iterator<Widget> iterator() {
return new ChildComponentContainerIterator<Widget>();
}
public class ChildComponentContainerIterator<T> implements Iterator<Widget> {
private int id = 0;
public boolean hasNext() {
return (id < size());
}
public Widget next() {
Widget w = get(id);
id++;
return w;
}
private Widget get(int i) {
if (i == 0) {
if (widget != null) {
return widget;
} else if (caption != null) {
return caption;
} else {
throw new NoSuchElementException();
}
} else if (i == 1) {
if (widget != null && caption != null) {
return caption;
} else {
throw new NoSuchElementException();
}
} else {
throw new NoSuchElementException();
}
}
public void remove() {
int toRemove = id - 1;
if (toRemove == 0) {
if (widget != null) {
ChildComponentContainer.this.remove(widget);
} else if (caption != null) {
ChildComponentContainer.this.remove(caption);
} else {
throw new IllegalStateException();
}
} else if (toRemove == 1) {
if (widget != null && caption != null) {
ChildComponentContainer.this.remove(caption);
} else {
throw new IllegalStateException();
}
} else {
throw new IllegalStateException();
}
id--;
}
}
public int size() {
if (widget != null) {
if (caption != null) {
return 2;
} else {
return 1;
}
} else {
if (caption != null) {
return 1;
} else {
return 0;
}
}
}
public Widget getWidget() {
return widget;
}
/**
* Return true if the size of the widget has been specified in the selected
* orientation.
*
* @return
*/
public boolean widgetHasSizeSpecified(int orientation) {
String size;
if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
size = widget.getElement().getStyle().getProperty("width");
} else {
size = widget.getElement().getStyle().getProperty("height");
}
return (size != null && !size.equals(""));
}
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();
}
/**
* Sets the normalized expand ratio of this slot. The fraction that this
* slot will use of "excess space".
*
* @param expandRatio
*/
public void setNormalizedExpandRatio(double expandRatio) {
this.expandRatio = expandRatio;
}
public int expand(int orientation, int spaceForExpansion) {
int expansionAmount = (int) (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) {
VConsole.log("containerWidth should never be negative: "
+ containerWidth);
containerWidth = 0;
}
if (containerHeight < 0) {
VConsole.log("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) {
if (caption.shouldBePlacedAfterComponent()) {
caption.setMaxWidth(captionWidth);
} else {
caption.setMaxWidth(width);
}
captionWidth = caption.getRenderedWidth();
// Remove initial height
caption.setHeight("");
}
}
}