/* @VaadinApache2LicenseForJavaFiles@ */ package com.vaadin.terminal.gwt.client.ui; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Set; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; 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.FlexTable; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.SimplePanel; 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.Container; import com.vaadin.terminal.gwt.client.Focusable; import com.vaadin.terminal.gwt.client.RenderSpace; import com.vaadin.terminal.gwt.client.StyleConstants; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VPaintableMap; import com.vaadin.terminal.gwt.client.VPaintableWidget; import com.vaadin.terminal.gwt.client.VTooltip; /** * Two col Layout that places caption on left col and field on right col */ public class VFormLayout extends SimplePanel implements Container { private final static String CLASSNAME = "v-formlayout"; private ApplicationConnection client; private VFormLayoutTable table; private String width = ""; private String height = ""; private boolean rendering = false; public VFormLayout() { super(); setStyleName(CLASSNAME); table = new VFormLayoutTable(); setWidget(table); } /** * Parses the stylenames from an uidl * * @param uidl * The uidl to get the stylenames from * @return An array of stylenames */ private String[] getStylesFromUIDL(UIDL uidl) { List styles = new ArrayList(); if (uidl.hasAttribute("style")) { String[] stylesnames = uidl.getStringAttribute("style").split(" "); for (String name : stylesnames) { styles.add(name); } } if (uidl.hasAttribute("disabled")) { styles.add(ApplicationConnection.DISABLED_CLASSNAME); } return styles.toArray(new String[styles.size()]); } public class VFormLayoutTable extends FlexTable implements ClickHandler { private static final int COLUMN_CAPTION = 0; private static final int COLUMN_ERRORFLAG = 1; private static final int COLUMN_WIDGET = 2; private HashMap widgetToCaption = new HashMap(); private HashMap widgetToError = new HashMap(); public VFormLayoutTable() { DOM.setElementProperty(getElement(), "cellPadding", "0"); DOM.setElementProperty(getElement(), "cellSpacing", "0"); } public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { final VMarginInfo margins = new VMarginInfo( uidl.getIntAttribute("margins")); Element margin = getElement(); setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, margins.hasTop()); setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, margins.hasRight()); setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, margins.hasBottom()); setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, margins.hasLeft()); setStyleName(margin, CLASSNAME + "-" + "spacing", uidl.hasAttribute("spacing")); int i = 0; for (final Iterator it = uidl.getChildIterator(); it.hasNext(); i++) { prepareCell(i, 1); final UIDL childUidl = (UIDL) it.next(); final VPaintableWidget childPaintable = client .getPaintable(childUidl); Widget childWidget = childPaintable.getWidgetForPaintable(); Caption caption = widgetToCaption.get(childWidget); if (caption == null) { caption = new Caption(childPaintable, client, getStylesFromUIDL(childUidl)); caption.addClickHandler(this); widgetToCaption.put(childWidget, caption); } ErrorFlag error = widgetToError.get(childWidget); if (error == null) { error = new ErrorFlag(); widgetToError.put(childWidget, error); } prepareCell(i, COLUMN_WIDGET); Widget oldWidget = getWidget(i, COLUMN_WIDGET); if (oldWidget == null) { setWidget(i, COLUMN_WIDGET, childWidget); } else if (oldWidget != childWidget) { final VPaintableWidget oldPaintable = VPaintableMap.get( client).getPaintable(oldWidget); client.unregisterPaintable(oldPaintable); setWidget(i, COLUMN_WIDGET, childWidget); } getCellFormatter().setStyleName(i, COLUMN_WIDGET, CLASSNAME + "-contentcell"); getCellFormatter().setStyleName(i, COLUMN_CAPTION, CLASSNAME + "-captioncell"); setWidget(i, COLUMN_CAPTION, caption); setContentWidth(i); getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG, CLASSNAME + "-errorcell"); setWidget(i, COLUMN_ERRORFLAG, error); childPaintable.updateFromUIDL(childUidl, client); String rowstyles = CLASSNAME + "-row"; if (i == 0) { rowstyles += " " + CLASSNAME + "-firstrow"; } if (!it.hasNext()) { rowstyles += " " + CLASSNAME + "-lastrow"; } getRowFormatter().setStyleName(i, rowstyles); } while (getRowCount() > i) { Widget w = getWidget(i, COLUMN_WIDGET); final VPaintableWidget p = VPaintableMap.get(client) .getPaintable(w); client.unregisterPaintable(p); widgetToCaption.remove(w); removeRow(i); } /* * Must update relative sized fields last when it is clear how much * space they are allowed to use */ for (Widget p : widgetToCaption.keySet()) { client.handleComponentRelativeSize(p); } } public void setContentWidths() { for (int row = 0; row < getRowCount(); row++) { setContentWidth(row); } } private void setContentWidth(int row) { String width = ""; if (!isDynamicWidth()) { width = "100%"; } getCellFormatter().setWidth(row, COLUMN_WIDGET, width); } public void replaceChildComponent(Widget oldComponent, Widget newComponent) { int i; for (i = 0; i < getRowCount(); i++) { Widget candidate = getWidget(i, COLUMN_WIDGET); if (oldComponent == candidate) { VPaintableMap paintableMap = VPaintableMap.get(client); VPaintableWidget oldPaintable = paintableMap .getPaintable(oldComponent); VPaintableWidget newPaintable = paintableMap .getPaintable(newComponent); Caption oldCap = widgetToCaption.get(oldComponent); final Caption newCap = new Caption(newPaintable, client, null); newCap.addClickHandler(this); newCap.setStyleName(oldCap.getStyleName()); widgetToCaption.put(newComponent, newCap); ErrorFlag error = widgetToError.get(newComponent); if (error == null) { error = new ErrorFlag(); widgetToError.put(newComponent, error); } setWidget(i, COLUMN_CAPTION, newCap); setWidget(i, COLUMN_ERRORFLAG, error); setWidget(i, COLUMN_WIDGET, newComponent); break; } } } public boolean hasChildComponent(Widget component) { return widgetToCaption.containsKey(component); } public void updateCaption(VPaintableWidget paintable, UIDL uidl) { final Caption c = widgetToCaption.get(paintable .getWidgetForPaintable()); if (c != null) { c.updateCaption(uidl); } final ErrorFlag e = widgetToError.get(paintable .getWidgetForPaintable()); if (e != null) { e.updateFromUIDL(uidl, paintable); } } public int getAllocatedWidth(Widget child, int availableWidth) { Caption caption = widgetToCaption.get(child); ErrorFlag error = widgetToError.get(child); int width = availableWidth; if (caption != null) { width -= DOM.getParent(caption.getElement()).getOffsetWidth(); } if (error != null) { width -= DOM.getParent(error.getElement()).getOffsetWidth(); } return width; } /* * (non-Javadoc) * * @see * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt * .event.dom.client.ClickEvent) */ public void onClick(ClickEvent event) { Caption caption = (Caption) event.getSource(); if (caption.getOwner() != null) { if (caption.getOwner() instanceof Focusable) { ((Focusable) caption.getOwner()).focus(); } else if (caption.getOwner() instanceof com.google.gwt.user.client.ui.Focusable) { ((com.google.gwt.user.client.ui.Focusable) caption .getOwner()).setFocus(true); } } } } public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { rendering = true; this.client = client; if (client.updateComponent(this, uidl, true)) { rendering = false; return; } table.updateFromUIDL(uidl, client); rendering = false; } public boolean isDynamicWidth() { return width.equals(""); } public boolean hasChildComponent(Widget component) { return table.hasChildComponent(component); } public void replaceChildComponent(Widget oldComponent, Widget newComponent) { table.replaceChildComponent(oldComponent, newComponent); } public void updateCaption(VPaintableWidget component, UIDL uidl) { table.updateCaption(component, uidl); } public class Caption extends HTML { public static final String CLASSNAME = "v-caption"; private final VPaintableWidget owner; private Element requiredFieldIndicator; private Icon icon; private Element captionText; private final ApplicationConnection client; /** * * @param component * optional owner of caption. If not set, getOwner will * return null * @param client */ public Caption(VPaintableWidget component, ApplicationConnection client, String[] styles) { super(); this.client = client; owner = component; String style = CLASSNAME; if (styles != null) { for (int i = 0; i < styles.length; i++) { style += " " + CLASSNAME + "-" + styles[i]; } } setStyleName(style); sinkEvents(VTooltip.TOOLTIP_EVENTS); } public void updateCaption(UIDL uidl) { setVisible(!uidl.getBooleanAttribute("invisible")); setStyleName(getElement(), ApplicationConnection.DISABLED_CLASSNAME, uidl.hasAttribute("disabled")); boolean isEmpty = true; if (uidl.hasAttribute("icon")) { if (icon == null) { icon = new Icon(client); DOM.insertChild(getElement(), icon.getElement(), 0); } icon.setUri(uidl.getStringAttribute("icon")); isEmpty = false; } else { if (icon != null) { DOM.removeChild(getElement(), icon.getElement()); icon = null; } } if (uidl.hasAttribute("caption")) { if (captionText == null) { captionText = DOM.createSpan(); DOM.insertChild(getElement(), captionText, icon == null ? 0 : 1); } String c = uidl.getStringAttribute("caption"); if (c == null) { c = ""; } else { isEmpty = false; } DOM.setInnerText(captionText, c); } else { // TODO should span also be removed } if (uidl.hasAttribute("description")) { if (captionText != null) { addStyleDependentName("hasdescription"); } else { removeStyleDependentName("hasdescription"); } } if (uidl.getBooleanAttribute("required")) { if (requiredFieldIndicator == null) { requiredFieldIndicator = DOM.createSpan(); DOM.setInnerText(requiredFieldIndicator, "*"); DOM.setElementProperty(requiredFieldIndicator, "className", "v-required-field-indicator"); DOM.appendChild(getElement(), requiredFieldIndicator); } } else { if (requiredFieldIndicator != null) { DOM.removeChild(getElement(), requiredFieldIndicator); requiredFieldIndicator = null; } } // 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... // FIXME: This could be unnecessary for IE8+ if (BrowserInfo.get().isIE()) { if (isEmpty) { setHeight("0px"); DOM.setStyleAttribute(getElement(), "overflow", "hidden"); } else { setHeight(""); DOM.setStyleAttribute(getElement(), "overflow", ""); } } } /** * Returns Paintable for which this Caption belongs to. * * @return owner Widget */ public VPaintableWidget getOwner() { return owner; } @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (client != null) { client.handleTooltipEvent(event, owner); } } } private class ErrorFlag extends HTML { private static final String CLASSNAME = VFormLayout.CLASSNAME + "-error-indicator"; Element errorIndicatorElement; private VPaintableWidget owner; public ErrorFlag() { setStyleName(CLASSNAME); sinkEvents(VTooltip.TOOLTIP_EVENTS); } public void updateFromUIDL(UIDL uidl, VPaintableWidget component) { owner = component; if (uidl.hasAttribute("error") && !uidl.getBooleanAttribute("hideErrors")) { if (errorIndicatorElement == null) { errorIndicatorElement = DOM.createDiv(); DOM.setInnerHTML(errorIndicatorElement, " "); DOM.setElementProperty(errorIndicatorElement, "className", "v-errorindicator"); DOM.appendChild(getElement(), errorIndicatorElement); } } else if (errorIndicatorElement != null) { DOM.removeChild(getElement(), errorIndicatorElement); errorIndicatorElement = null; } } @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); if (owner != null) { client.handleTooltipEvent(event, owner); } } } public boolean requestLayout(Set children) { if (height.equals("") || width.equals("")) { // A dynamic size might change due to children changes return false; } return true; } public RenderSpace getAllocatedSpace(Widget child) { int width = 0; int height = 0; if (!this.width.equals("")) { int availableWidth = getOffsetWidth(); width = table.getAllocatedWidth(child, availableWidth); } return new RenderSpace(width, height, false); } @Override public void setHeight(String height) { if (this.height.equals(height)) { return; } this.height = height; super.setHeight(height); } @Override public void setWidth(String width) { if (this.width.equals(width)) { return; } this.width = width; super.setWidth(width); if (!rendering) { table.setContentWidths(); if (height.equals("")) { // Width might affect height Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, this, this); } } } public Widget getWidgetForPaintable() { return this; } }