/*
* Copyright 2010 IT Mill 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;
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.vaadin.terminal.gwt.client.ui.Icon;
public class VCaption extends HTML {
public static final String CLASSNAME = "v-caption";
private final Paintable owner;
private Element errorIndicatorElement;
private Element requiredFieldIndicator;
private Icon icon;
private Element captionText;
private Element clearElement;
private final ApplicationConnection client;
private boolean placedAfterComponent = false;
private boolean iconOnloadHandled = false;
private int maxWidth = -1;
protected static final String ATTRIBUTE_ICON = "icon";
protected static final String ATTRIBUTE_CAPTION = "caption";
protected static final String ATTRIBUTE_DESCRIPTION = "description";
protected static final String ATTRIBUTE_REQUIRED = "required";
protected static final String ATTRIBUTE_ERROR = "error";
protected static final String ATTRIBUTE_HIDEERRORS = "hideErrors";
private static final String CLASSNAME_CLEAR = CLASSNAME + "-clearelem";
/**
*
* @param component
* optional owner of caption. If not set, getOwner will return
* null
* @param client
*/
public VCaption(Paintable component, ApplicationConnection client) {
super();
this.client = client;
owner = component;
if (client != null && owner != null) {
setOwnerPid(getElement(), client.getPid(owner));
}
setStyleName(CLASSNAME);
sinkEvents(VTooltip.TOOLTIP_EVENTS);
}
/**
* 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"));
boolean wasPlacedAfterComponent = placedAfterComponent;
// Caption is placed after component unless there is some part which
// moves it above.
placedAfterComponent = true;
String style = CLASSNAME;
if (uidl.hasAttribute("style")) {
final String[] styles = uidl.getStringAttribute("style").split(" ");
for (int i = 0; i < styles.length; i++) {
style += " " + CLASSNAME + "-" + styles[i];
}
}
if (uidl.hasAttribute("disabled")) {
style += " " + "v-disabled";
}
setStyleName(style);
boolean hasIcon = uidl.hasAttribute(ATTRIBUTE_ICON);
boolean hasText = uidl.hasAttribute(ATTRIBUTE_CAPTION);
boolean hasDescription = uidl.hasAttribute(ATTRIBUTE_DESCRIPTION);
boolean showRequired = uidl.getBooleanAttribute(ATTRIBUTE_REQUIRED);
boolean showError = uidl.hasAttribute(ATTRIBUTE_ERROR)
&& !uidl.getBooleanAttribute(ATTRIBUTE_HIDEERRORS);
if (hasIcon) {
if (icon == null) {
icon = new Icon(client);
icon.setWidth("0");
icon.setHeight("0");
DOM.insertChild(getElement(), icon.getElement(),
getInsertPosition(ATTRIBUTE_ICON));
}
// Icon forces the caption to be above the component
placedAfterComponent = false;
iconOnloadHandled = false;
icon.setUri(uidl.getStringAttribute(ATTRIBUTE_ICON));
} else if (icon != null) {
// Remove existing
DOM.removeChild(getElement(), icon.getElement());
icon = null;
}
if (hasText) {
// A caption text should be shown if the attribute is set
// If the caption is null the ATTRIBUTE_CAPTION should not be set to
// avoid ending up here.
if (captionText == null) {
captionText = DOM.createDiv();
captionText.setClassName("v-captiontext");
DOM.insertChild(getElement(), captionText,
getInsertPosition(ATTRIBUTE_CAPTION));
}
// Update caption text
String c = uidl.getStringAttribute(ATTRIBUTE_CAPTION);
// A text forces the caption to be above the component.
placedAfterComponent = false;
if (c == null || c.trim().equals("")) {
// Not sure if c even can be null. Should not.
// This is required to ensure that the caption uses space in all
// browsers when it is set to the empty string. If there is an
// icon, error indicator or required indicator they will ensure
// that space is reserved.
if (!hasIcon && !showRequired && !showError) {
captionText.setInnerHTML(" ");
}
} else {
DOM.setInnerText(captionText, c);
}
} else if (captionText != null) {
// Remove existing
DOM.removeChild(getElement(), captionText);
captionText = null;
}
if (hasDescription) {
if (captionText != null) {
addStyleDependentName("hasdescription");
} else {
removeStyleDependentName("hasdescription");
}
}
if (showRequired) {
if (requiredFieldIndicator == null) {
requiredFieldIndicator = DOM.createDiv();
requiredFieldIndicator
.setClassName("v-required-field-indicator");
DOM.setInnerText(requiredFieldIndicator, "*");
DOM.insertChild(getElement(), requiredFieldIndicator,
getInsertPosition(ATTRIBUTE_REQUIRED));
}
} else if (requiredFieldIndicator != null) {
// Remove existing
DOM.removeChild(getElement(), requiredFieldIndicator);
requiredFieldIndicator = null;
}
if (showError) {
if (errorIndicatorElement == null) {
errorIndicatorElement = DOM.createDiv();
DOM.setInnerHTML(errorIndicatorElement, " ");
DOM.setElementProperty(errorIndicatorElement, "className",
"v-errorindicator");
DOM.insertChild(getElement(), errorIndicatorElement,
getInsertPosition(ATTRIBUTE_ERROR));
}
} else if (errorIndicatorElement != null) {
// Remove existing
getElement().removeChild(errorIndicatorElement);
errorIndicatorElement = null;
}
if (clearElement == null) {
clearElement = DOM.createDiv();
clearElement.setClassName(CLASSNAME_CLEAR);
getElement().appendChild(clearElement);
}
return (wasPlacedAfterComponent != placedAfterComponent);
}
private int getInsertPosition(String element) {
int pos = 0;
if (element.equals(ATTRIBUTE_ICON)) {
return pos;
}
if (icon != null) {
pos++;
}
if (element.equals(ATTRIBUTE_CAPTION)) {
return pos;
}
if (captionText != null) {
pos++;
}
if (element.equals(ATTRIBUTE_REQUIRED)) {
return pos;
}
if (requiredFieldIndicator != null) {
pos++;
}
// if (element.equals(ATTRIBUTE_ERROR)) {
// }
return pos;
}
@Override
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
final Element target = DOM.eventGetTarget(event);
if (client != null && owner != null && target != getElement()) {
client.handleTooltipEvent(event, owner);
}
if (DOM.eventGetType(event) == Event.ONLOAD
&& icon.getElement() == target && !iconOnloadHandled) {
icon.setWidth("");
icon.setHeight("");
/*
* IE6 pngFix causes two onload events to be fired and we want to
* react only to the first one
*/
iconOnloadHandled = true;
// if max width defined, recalculate
if (maxWidth != -1) {
setMaxWidth(maxWidth);
} else {
String width = getElement().getStyle().getProperty("width");
if (width != null && !width.equals("")) {
setWidth(getRequiredWidth() + "px");
}
}
/*
* The size of the icon might affect the size of the component so we
* must report the size change to the parent TODO consider moving
* the responsibility of reacting to ONLOAD from VCaption to layouts
*/
if (owner != null) {
Util.notifyParentOfSizeChange(owner, true);
} else {
VConsole.log("Warning: Icon load event was not propagated because VCaption owner is unknown.");
}
}
}
public static boolean isNeeded(UIDL uidl) {
if (uidl.getStringAttribute(ATTRIBUTE_CAPTION) != null) {
return true;
}
if (uidl.hasAttribute(ATTRIBUTE_ERROR)) {
return true;
}
if (uidl.hasAttribute(ATTRIBUTE_ICON)) {
return true;
}
if (uidl.hasAttribute(ATTRIBUTE_REQUIRED)) {
return true;
}
return false;
}
/**
* Returns Paintable for which this Caption belongs to.
*
* @return owner Widget
*/
public Paintable getOwner() {
return owner;
}
public boolean shouldBePlacedAfterComponent() {
return placedAfterComponent;
}
public int getRenderedWidth() {
int width = 0;
if (icon != null) {
width += Util.getRequiredWidth(icon.getElement());
}
if (captionText != null) {
width += Util.getRequiredWidth(captionText);
}
if (requiredFieldIndicator != null) {
width += Util.getRequiredWidth(requiredFieldIndicator);
}
if (errorIndicatorElement != null) {
width += Util.getRequiredWidth(errorIndicatorElement);
}
return width;
}
public int getRequiredWidth() {
int width = 0;
if (icon != null) {
width += Util.getRequiredWidth(icon.getElement());
}
if (captionText != null) {
int textWidth = captionText.getScrollWidth();
if (BrowserInfo.get().isFirefox()) {
/*
* In Firefox3 the caption might require more space than the
* scrollWidth returns as scrollWidth is rounded down.
*/
int requiredWidth = Util.getRequiredWidth(captionText);
if (requiredWidth > textWidth) {
textWidth = requiredWidth;
}
}
width += textWidth;
}
if (requiredFieldIndicator != null) {
width += Util.getRequiredWidth(requiredFieldIndicator);
}
if (errorIndicatorElement != null) {
width += Util.getRequiredWidth(errorIndicatorElement);
}
return width;
}
public int getHeight() {
int height = 0;
int h;
if (icon != null) {
h = Util.getRequiredHeight(icon.getElement());
if (h > height) {
height = h;
}
}
if (captionText != null) {
h = Util.getRequiredHeight(captionText);
if (h > height) {
height = h;
}
}
if (requiredFieldIndicator != null) {
h = Util.getRequiredHeight(requiredFieldIndicator);
if (h > height) {
height = h;
}
}
if (errorIndicatorElement != null) {
h = Util.getRequiredHeight(errorIndicatorElement);
if (h > height) {
height = h;
}
}
return height;
}
public void setAlignment(String alignment) {
DOM.setStyleAttribute(getElement(), "textAlign", alignment);
}
public void setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
DOM.setStyleAttribute(getElement(), "width", maxWidth + "px");
if (icon != null) {
DOM.setStyleAttribute(icon.getElement(), "width", "");
}
if (captionText != null) {
DOM.setStyleAttribute(captionText, "width", "");
}
int requiredWidth = getRequiredWidth();
/*
* ApplicationConnection.getConsole().log( "Caption maxWidth: " +
* maxWidth + ", requiredWidth: " + requiredWidth);
*/
if (requiredWidth > maxWidth) {
// Needs to truncate and clip
int availableWidth = maxWidth;
// DOM.setStyleAttribute(getElement(), "width", maxWidth + "px");
if (requiredFieldIndicator != null) {
availableWidth -= Util.getRequiredWidth(requiredFieldIndicator);
}
if (errorIndicatorElement != null) {
availableWidth -= Util.getRequiredWidth(errorIndicatorElement);
}
if (availableWidth < 0) {
availableWidth = 0;
}
if (icon != null) {
int iconRequiredWidth = Util
.getRequiredWidth(icon.getElement());
if (availableWidth > iconRequiredWidth) {
availableWidth -= iconRequiredWidth;
} else {
DOM.setStyleAttribute(icon.getElement(), "width",
availableWidth + "px");
availableWidth = 0;
}
}
if (captionText != null) {
int captionWidth = Util.getRequiredWidth(captionText);
if (availableWidth > captionWidth) {
availableWidth -= captionWidth;
} else {
DOM.setStyleAttribute(captionText, "width", availableWidth
+ "px");
availableWidth = 0;
}
}
}
}
protected Element getTextElement() {
return captionText;
}
public static String getCaptionOwnerPid(Element e) {
return getOwnerPid(e);
}
private native static void setOwnerPid(Element el, String pid)
/*-{
el.vOwnerPid = pid;
}-*/;
public native static String getOwnerPid(Element el)
/*-{
return el.vOwnerPid;
}-*/;
}