summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/ui
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2012-08-13 18:34:33 +0300
committerArtur Signell <artur@vaadin.com>2012-08-13 19:18:33 +0300
commite85d933b25cc3c5cc85eb7eb4b13b950fd8e1569 (patch)
tree9ab6f13f7188cab44bbd979b1cf620f15328a03f /server/src/com/vaadin/ui
parent14dd4d0b28c76eb994b181a4570f3adec53342e6 (diff)
downloadvaadin-framework-e85d933b25cc3c5cc85eb7eb4b13b950fd8e1569.tar.gz
vaadin-framework-e85d933b25cc3c5cc85eb7eb4b13b950fd8e1569.zip
Moved server files to a server src folder (#9299)
Diffstat (limited to 'server/src/com/vaadin/ui')
-rw-r--r--server/src/com/vaadin/ui/AbsoluteLayout.java632
-rw-r--r--server/src/com/vaadin/ui/AbstractComponent.java1382
-rw-r--r--server/src/com/vaadin/ui/AbstractComponentContainer.java351
-rw-r--r--server/src/com/vaadin/ui/AbstractField.java1657
-rw-r--r--server/src/com/vaadin/ui/AbstractJavaScriptComponent.java165
-rw-r--r--server/src/com/vaadin/ui/AbstractLayout.java77
-rw-r--r--server/src/com/vaadin/ui/AbstractMedia.java196
-rw-r--r--server/src/com/vaadin/ui/AbstractOrderedLayout.java383
-rw-r--r--server/src/com/vaadin/ui/AbstractSelect.java2029
-rw-r--r--server/src/com/vaadin/ui/AbstractSplitPanel.java521
-rw-r--r--server/src/com/vaadin/ui/AbstractTextField.java674
-rw-r--r--server/src/com/vaadin/ui/Accordion.java19
-rw-r--r--server/src/com/vaadin/ui/Alignment.java158
-rw-r--r--server/src/com/vaadin/ui/Audio.java55
-rw-r--r--server/src/com/vaadin/ui/Button.java539
-rw-r--r--server/src/com/vaadin/ui/CheckBox.java141
-rw-r--r--server/src/com/vaadin/ui/ComboBox.java116
-rw-r--r--server/src/com/vaadin/ui/Component.java1047
-rw-r--r--server/src/com/vaadin/ui/ComponentContainer.java222
-rw-r--r--server/src/com/vaadin/ui/ConnectorTracker.java320
-rw-r--r--server/src/com/vaadin/ui/CssLayout.java308
-rw-r--r--server/src/com/vaadin/ui/CustomComponent.java189
-rw-r--r--server/src/com/vaadin/ui/CustomField.java237
-rw-r--r--server/src/com/vaadin/ui/CustomLayout.java329
-rw-r--r--server/src/com/vaadin/ui/DateField.java869
-rw-r--r--server/src/com/vaadin/ui/DefaultFieldFactory.java146
-rw-r--r--server/src/com/vaadin/ui/DragAndDropWrapper.java407
-rw-r--r--server/src/com/vaadin/ui/Embedded.java531
-rw-r--r--server/src/com/vaadin/ui/Field.java97
-rw-r--r--server/src/com/vaadin/ui/Form.java1420
-rw-r--r--server/src/com/vaadin/ui/FormFieldFactory.java41
-rw-r--r--server/src/com/vaadin/ui/FormLayout.java31
-rw-r--r--server/src/com/vaadin/ui/GridLayout.java1415
-rw-r--r--server/src/com/vaadin/ui/HasComponents.java49
-rw-r--r--server/src/com/vaadin/ui/HorizontalLayout.java24
-rw-r--r--server/src/com/vaadin/ui/HorizontalSplitPanel.java34
-rw-r--r--server/src/com/vaadin/ui/Html5File.java65
-rw-r--r--server/src/com/vaadin/ui/InlineDateField.java46
-rw-r--r--server/src/com/vaadin/ui/JavaScript.java157
-rw-r--r--server/src/com/vaadin/ui/JavaScriptFunction.java41
-rw-r--r--server/src/com/vaadin/ui/Label.java483
-rw-r--r--server/src/com/vaadin/ui/Layout.java229
-rw-r--r--server/src/com/vaadin/ui/Link.java242
-rw-r--r--server/src/com/vaadin/ui/ListSelect.java96
-rw-r--r--server/src/com/vaadin/ui/LoginForm.java353
-rw-r--r--server/src/com/vaadin/ui/MenuBar.java890
-rw-r--r--server/src/com/vaadin/ui/NativeButton.java21
-rw-r--r--server/src/com/vaadin/ui/NativeSelect.java91
-rw-r--r--server/src/com/vaadin/ui/Notification.java367
-rw-r--r--server/src/com/vaadin/ui/OptionGroup.java203
-rw-r--r--server/src/com/vaadin/ui/Panel.java486
-rw-r--r--server/src/com/vaadin/ui/PasswordField.java67
-rw-r--r--server/src/com/vaadin/ui/PopupDateField.java80
-rw-r--r--server/src/com/vaadin/ui/PopupView.java453
-rw-r--r--server/src/com/vaadin/ui/ProgressIndicator.java257
-rw-r--r--server/src/com/vaadin/ui/RichTextArea.java344
-rw-r--r--server/src/com/vaadin/ui/Root.java1227
-rw-r--r--server/src/com/vaadin/ui/Select.java803
-rw-r--r--server/src/com/vaadin/ui/Slider.java372
-rw-r--r--server/src/com/vaadin/ui/TabSheet.java1328
-rw-r--r--server/src/com/vaadin/ui/Table.java5449
-rw-r--r--server/src/com/vaadin/ui/TableFieldFactory.java45
-rw-r--r--server/src/com/vaadin/ui/TextArea.java121
-rw-r--r--server/src/com/vaadin/ui/TextField.java92
-rw-r--r--server/src/com/vaadin/ui/Tree.java1615
-rw-r--r--server/src/com/vaadin/ui/TreeTable.java824
-rw-r--r--server/src/com/vaadin/ui/TwinColSelect.java180
-rw-r--r--server/src/com/vaadin/ui/UniqueSerializable.java30
-rw-r--r--server/src/com/vaadin/ui/Upload.java1055
-rw-r--r--server/src/com/vaadin/ui/VerticalLayout.java25
-rw-r--r--server/src/com/vaadin/ui/VerticalSplitPanel.java30
-rw-r--r--server/src/com/vaadin/ui/Video.java81
-rw-r--r--server/src/com/vaadin/ui/Window.java853
-rw-r--r--server/src/com/vaadin/ui/doc-files/component_class_hierarchy.gifbin0 -> 11077 bytes
-rw-r--r--server/src/com/vaadin/ui/doc-files/component_interfaces.gifbin0 -> 2272 bytes
-rw-r--r--server/src/com/vaadin/ui/package.html76
-rw-r--r--server/src/com/vaadin/ui/themes/BaseTheme.java59
-rw-r--r--server/src/com/vaadin/ui/themes/ChameleonTheme.java365
-rw-r--r--server/src/com/vaadin/ui/themes/LiferayTheme.java31
-rw-r--r--server/src/com/vaadin/ui/themes/Reindeer.java217
-rw-r--r--server/src/com/vaadin/ui/themes/Runo.java183
81 files changed, 36843 insertions, 0 deletions
diff --git a/server/src/com/vaadin/ui/AbsoluteLayout.java b/server/src/com/vaadin/ui/AbsoluteLayout.java
new file mode 100644
index 0000000000..1c84ca2865
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbsoluteLayout.java
@@ -0,0 +1,632 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.vaadin.event.LayoutEvents.LayoutClickEvent;
+import com.vaadin.event.LayoutEvents.LayoutClickListener;
+import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.absolutelayout.AbsoluteLayoutServerRpc;
+import com.vaadin.shared.ui.absolutelayout.AbsoluteLayoutState;
+import com.vaadin.terminal.Sizeable;
+import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler;
+
+/**
+ * AbsoluteLayout is a layout implementation that mimics html absolute
+ * positioning.
+ *
+ */
+@SuppressWarnings("serial")
+public class AbsoluteLayout extends AbstractLayout implements
+ LayoutClickNotifier {
+
+ private AbsoluteLayoutServerRpc rpc = new AbsoluteLayoutServerRpc() {
+
+ @Override
+ public void layoutClick(MouseEventDetails mouseDetails,
+ Connector clickedConnector) {
+ fireEvent(LayoutClickEvent.createEvent(AbsoluteLayout.this,
+ mouseDetails, clickedConnector));
+ }
+ };
+ // Maps each component to a position
+ private LinkedHashMap<Component, ComponentPosition> componentToCoordinates = new LinkedHashMap<Component, ComponentPosition>();
+
+ /**
+ * Creates an AbsoluteLayout with full size.
+ */
+ public AbsoluteLayout() {
+ registerRpc(rpc);
+ setSizeFull();
+ }
+
+ @Override
+ public AbsoluteLayoutState getState() {
+ return (AbsoluteLayoutState) super.getState();
+ }
+
+ /**
+ * Gets an iterator for going through all components enclosed in the
+ * absolute layout.
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return componentToCoordinates.keySet().iterator();
+ }
+
+ /**
+ * Gets the number of contained components. Consistent with the iterator
+ * returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components
+ */
+ @Override
+ public int getComponentCount() {
+ return componentToCoordinates.size();
+ }
+
+ /**
+ * Replaces one component with another one. The new component inherits the
+ * old components position.
+ */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ ComponentPosition position = getPosition(oldComponent);
+ removeComponent(oldComponent);
+ addComponent(newComponent, position);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.AbstractComponentContainer#addComponent(com.vaadin.ui.Component
+ * )
+ */
+ @Override
+ public void addComponent(Component c) {
+ addComponent(c, new ComponentPosition());
+ }
+
+ /**
+ * Adds a component to the layout. The component can be positioned by
+ * providing a string formatted in CSS-format.
+ * <p>
+ * For example the string "top:10px;left:10px" will position the component
+ * 10 pixels from the left and 10 pixels from the top. The identifiers:
+ * "top","left","right" and "bottom" can be used to specify the position.
+ * </p>
+ *
+ * @param c
+ * The component to add to the layout
+ * @param cssPosition
+ * The css position string
+ */
+ public void addComponent(Component c, String cssPosition) {
+ ComponentPosition position = new ComponentPosition();
+ position.setCSSString(cssPosition);
+ addComponent(c, position);
+ }
+
+ /**
+ * Adds the component using the given position. Ensures the position is only
+ * set if the component is added correctly.
+ *
+ * @param c
+ * The component to add
+ * @param position
+ * The position info for the component. Must not be null.
+ * @throws IllegalArgumentException
+ * If adding the component failed
+ */
+ private void addComponent(Component c, ComponentPosition position)
+ throws IllegalArgumentException {
+ /*
+ * Create position instance and add it to componentToCoordinates map. We
+ * need to do this before we call addComponent so the attachListeners
+ * can access this position. #6368
+ */
+ internalSetPosition(c, position);
+ try {
+ super.addComponent(c);
+ } catch (IllegalArgumentException e) {
+ internalRemoveComponent(c);
+ throw e;
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Removes the component from all internal data structures. Does not
+ * actually remove the component from the layout (this is assumed to have
+ * been done by the caller).
+ *
+ * @param c
+ * The component to remove
+ */
+ private void internalRemoveComponent(Component c) {
+ componentToCoordinates.remove(c);
+ }
+
+ @Override
+ public void updateState() {
+ super.updateState();
+
+ // This could be in internalRemoveComponent and internalSetComponent if
+ // Map<Connector,String> was supported. We cannot get the child
+ // connectorId unless the component is attached to the application so
+ // the String->String map cannot be populated in internal* either.
+ Map<String, String> connectorToPosition = new HashMap<String, String>();
+ for (Iterator<Component> ci = getComponentIterator(); ci.hasNext();) {
+ Component c = ci.next();
+ connectorToPosition.put(c.getConnectorId(), getPosition(c)
+ .getCSSString());
+ }
+ getState().setConnectorToCssPosition(connectorToPosition);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.AbstractComponentContainer#removeComponent(com.vaadin.ui
+ * .Component)
+ */
+ @Override
+ public void removeComponent(Component c) {
+ internalRemoveComponent(c);
+ super.removeComponent(c);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the position of a component in the layout. Returns null if component
+ * is not attached to the layout.
+ * <p>
+ * Note that you cannot update the position by updating this object. Call
+ * {@link #setPosition(Component, ComponentPosition)} with the updated
+ * {@link ComponentPosition} object.
+ * </p>
+ *
+ * @param component
+ * The component which position is needed
+ * @return An instance of ComponentPosition containing the position of the
+ * component, or null if the component is not enclosed in the
+ * layout.
+ */
+ public ComponentPosition getPosition(Component component) {
+ return componentToCoordinates.get(component);
+ }
+
+ /**
+ * Sets the position of a component in the layout.
+ *
+ * @param component
+ * @param position
+ */
+ public void setPosition(Component component, ComponentPosition position) {
+ if (!componentToCoordinates.containsKey(component)) {
+ throw new IllegalArgumentException(
+ "Component must be a child of this layout");
+ }
+ internalSetPosition(component, position);
+ }
+
+ /**
+ * Updates the position for a component. Caller must ensure component is a
+ * child of this layout.
+ *
+ * @param component
+ * The component. Must be a child for this layout. Not enforced.
+ * @param position
+ * New position. Must not be null.
+ */
+ private void internalSetPosition(Component component,
+ ComponentPosition position) {
+ componentToCoordinates.put(component, position);
+ requestRepaint();
+ }
+
+ /**
+ * The CompontPosition class represents a components position within the
+ * absolute layout. It contains the attributes for left, right, top and
+ * bottom and the units used to specify them.
+ */
+ public class ComponentPosition implements Serializable {
+
+ private int zIndex = -1;
+ private Float topValue = null;
+ private Float rightValue = null;
+ private Float bottomValue = null;
+ private Float leftValue = null;
+
+ private Unit topUnits = Unit.PIXELS;
+ private Unit rightUnits = Unit.PIXELS;
+ private Unit bottomUnits = Unit.PIXELS;
+ private Unit leftUnits = Unit.PIXELS;
+
+ /**
+ * Sets the position attributes using CSS syntax. Attributes not
+ * included in the string are reset to their unset states.
+ *
+ * <code><pre>
+ * setCSSString("top:10px;left:20%;z-index:16;");
+ * </pre></code>
+ *
+ * @param css
+ */
+ public void setCSSString(String css) {
+ topValue = rightValue = bottomValue = leftValue = null;
+ topUnits = rightUnits = bottomUnits = leftUnits = Unit.PIXELS;
+ zIndex = -1;
+ if (css == null) {
+ return;
+ }
+
+ String[] cssProperties = css.split(";");
+ for (int i = 0; i < cssProperties.length; i++) {
+ String[] keyValuePair = cssProperties[i].split(":");
+ String key = keyValuePair[0].trim();
+ if (key.equals("")) {
+ continue;
+ }
+ if (key.equals("z-index")) {
+ zIndex = Integer.parseInt(keyValuePair[1].trim());
+ } else {
+ String value;
+ if (keyValuePair.length > 1) {
+ value = keyValuePair[1].trim();
+ } else {
+ value = "";
+ }
+ String symbol = value.replaceAll("[0-9\\.\\-]+", "");
+ if (!symbol.equals("")) {
+ value = value.substring(0, value.indexOf(symbol))
+ .trim();
+ }
+ float v = Float.parseFloat(value);
+ Unit unit = Unit.getUnitFromSymbol(symbol);
+ if (key.equals("top")) {
+ topValue = v;
+ topUnits = unit;
+ } else if (key.equals("right")) {
+ rightValue = v;
+ rightUnits = unit;
+ } else if (key.equals("bottom")) {
+ bottomValue = v;
+ bottomUnits = unit;
+ } else if (key.equals("left")) {
+ leftValue = v;
+ leftUnits = unit;
+ }
+ }
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Converts the internal values into a valid CSS string.
+ *
+ * @return A valid CSS string
+ */
+ public String getCSSString() {
+ String s = "";
+ if (topValue != null) {
+ s += "top:" + topValue + topUnits.getSymbol() + ";";
+ }
+ if (rightValue != null) {
+ s += "right:" + rightValue + rightUnits.getSymbol() + ";";
+ }
+ if (bottomValue != null) {
+ s += "bottom:" + bottomValue + bottomUnits.getSymbol() + ";";
+ }
+ if (leftValue != null) {
+ s += "left:" + leftValue + leftUnits.getSymbol() + ";";
+ }
+ if (zIndex >= 0) {
+ s += "z-index:" + zIndex + ";";
+ }
+ return s;
+ }
+
+ /**
+ * Sets the 'top' attribute; distance from the top of the component to
+ * the top edge of the layout.
+ *
+ * @param topValue
+ * The value of the 'top' attribute
+ * @param topUnits
+ * The unit of the 'top' attribute. See UNIT_SYMBOLS for a
+ * description of the available units.
+ */
+ public void setTop(Float topValue, Unit topUnits) {
+ this.topValue = topValue;
+ this.topUnits = topUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the 'right' attribute; distance from the right of the component
+ * to the right edge of the layout.
+ *
+ * @param rightValue
+ * The value of the 'right' attribute
+ * @param rightUnits
+ * The unit of the 'right' attribute. See UNIT_SYMBOLS for a
+ * description of the available units.
+ */
+ public void setRight(Float rightValue, Unit rightUnits) {
+ this.rightValue = rightValue;
+ this.rightUnits = rightUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the 'bottom' attribute; distance from the bottom of the
+ * component to the bottom edge of the layout.
+ *
+ * @param bottomValue
+ * The value of the 'bottom' attribute
+ * @param units
+ * The unit of the 'bottom' attribute. See UNIT_SYMBOLS for a
+ * description of the available units.
+ */
+ public void setBottom(Float bottomValue, Unit bottomUnits) {
+ this.bottomValue = bottomValue;
+ this.bottomUnits = bottomUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the 'left' attribute; distance from the left of the component to
+ * the left edge of the layout.
+ *
+ * @param leftValue
+ * The value of the 'left' attribute
+ * @param units
+ * The unit of the 'left' attribute. See UNIT_SYMBOLS for a
+ * description of the available units.
+ */
+ public void setLeft(Float leftValue, Unit leftUnits) {
+ this.leftValue = leftValue;
+ this.leftUnits = leftUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the 'z-index' attribute; the visual stacking order
+ *
+ * @param zIndex
+ * The z-index for the component.
+ */
+ public void setZIndex(int zIndex) {
+ this.zIndex = zIndex;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the value of the 'top' attribute; distance from the top of the
+ * component to the top edge of the layout.
+ *
+ * @param topValue
+ * The value of the 'left' attribute
+ */
+ public void setTopValue(Float topValue) {
+ this.topValue = topValue;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the 'top' attributes value in current units.
+ *
+ * @see #getTopUnits()
+ * @return The value of the 'top' attribute, null if not set
+ */
+ public Float getTopValue() {
+ return topValue;
+ }
+
+ /**
+ * Gets the 'right' attributes value in current units.
+ *
+ * @return The value of the 'right' attribute, null if not set
+ * @see #getRightUnits()
+ */
+ public Float getRightValue() {
+ return rightValue;
+ }
+
+ /**
+ * Sets the 'right' attribute value (distance from the right of the
+ * component to the right edge of the layout). Currently active units
+ * are maintained.
+ *
+ * @param rightValue
+ * The value of the 'right' attribute
+ * @see #setRightUnits(int)
+ */
+ public void setRightValue(Float rightValue) {
+ this.rightValue = rightValue;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the 'bottom' attributes value using current units.
+ *
+ * @return The value of the 'bottom' attribute, null if not set
+ * @see #getBottomUnits()
+ */
+ public Float getBottomValue() {
+ return bottomValue;
+ }
+
+ /**
+ * Sets the 'bottom' attribute value (distance from the bottom of the
+ * component to the bottom edge of the layout). Currently active units
+ * are maintained.
+ *
+ * @param bottomValue
+ * The value of the 'bottom' attribute
+ * @see #setBottomUnits(int)
+ */
+ public void setBottomValue(Float bottomValue) {
+ this.bottomValue = bottomValue;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the 'left' attributes value using current units.
+ *
+ * @return The value of the 'left' attribute, null if not set
+ * @see #getLeftUnits()
+ */
+ public Float getLeftValue() {
+ return leftValue;
+ }
+
+ /**
+ * Sets the 'left' attribute value (distance from the left of the
+ * component to the left edge of the layout). Currently active units are
+ * maintained.
+ *
+ * @param leftValue
+ * The value of the 'left' CSS-attribute
+ * @see #setLeftUnits(int)
+ */
+ public void setLeftValue(Float leftValue) {
+ this.leftValue = leftValue;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the unit for the 'top' attribute
+ *
+ * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public Unit getTopUnits() {
+ return topUnits;
+ }
+
+ /**
+ * Sets the unit for the 'top' attribute
+ *
+ * @param topUnits
+ * See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public void setTopUnits(Unit topUnits) {
+ this.topUnits = topUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the unit for the 'right' attribute
+ *
+ * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public Unit getRightUnits() {
+ return rightUnits;
+ }
+
+ /**
+ * Sets the unit for the 'right' attribute
+ *
+ * @param rightUnits
+ * See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public void setRightUnits(Unit rightUnits) {
+ this.rightUnits = rightUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the unit for the 'bottom' attribute
+ *
+ * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public Unit getBottomUnits() {
+ return bottomUnits;
+ }
+
+ /**
+ * Sets the unit for the 'bottom' attribute
+ *
+ * @param bottomUnits
+ * See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public void setBottomUnits(Unit bottomUnits) {
+ this.bottomUnits = bottomUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the unit for the 'left' attribute
+ *
+ * @return See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public Unit getLeftUnits() {
+ return leftUnits;
+ }
+
+ /**
+ * Sets the unit for the 'left' attribute
+ *
+ * @param leftUnits
+ * See {@link Sizeable} UNIT_SYMBOLS for a description of the
+ * available units.
+ */
+ public void setLeftUnits(Unit leftUnits) {
+ this.leftUnits = leftUnits;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the 'z-index' attribute.
+ *
+ * @return the zIndex The z-index attribute
+ */
+ public int getZIndex() {
+ return zIndex;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getCSSString();
+ }
+
+ }
+
+ @Override
+ public void addListener(LayoutClickListener listener) {
+ addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener,
+ LayoutClickListener.clickMethod);
+ }
+
+ @Override
+ public void removeListener(LayoutClickListener listener) {
+ removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java
new file mode 100644
index 0000000000..e7cb38256c
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractComponent.java
@@ -0,0 +1,1382 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.vaadin.Application;
+import com.vaadin.event.ActionManager;
+import com.vaadin.event.EventRouter;
+import com.vaadin.event.MethodEventSource;
+import com.vaadin.event.ShortcutListener;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.terminal.AbstractClientConnector;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Terminal;
+import com.vaadin.terminal.gwt.server.ClientConnector;
+import com.vaadin.terminal.gwt.server.ComponentSizeValidator;
+import com.vaadin.terminal.gwt.server.ResourceReference;
+import com.vaadin.tools.ReflectTools;
+
+/**
+ * An abstract class that defines default implementation for the
+ * {@link Component} interface. Basic UI components that are not derived from an
+ * external component can inherit this class to easily qualify as Vaadin
+ * components. Most components in Vaadin do just that.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractComponent extends AbstractClientConnector
+ implements Component, MethodEventSource {
+
+ /* Private members */
+
+ /**
+ * Application specific data object. The component does not use or modify
+ * this.
+ */
+ private Object applicationData;
+
+ /**
+ * The EventRouter used for the event model.
+ */
+ private EventRouter eventRouter = null;
+
+ /**
+ * The internal error message of the component.
+ */
+ private ErrorMessage componentError = null;
+
+ /**
+ * Locale of this component.
+ */
+ private Locale locale;
+
+ /**
+ * The component should receive focus (if {@link Focusable}) when attached.
+ */
+ private boolean delayedFocus;
+
+ /* Sizeable fields */
+
+ private float width = SIZE_UNDEFINED;
+ private float height = SIZE_UNDEFINED;
+ private Unit widthUnit = Unit.PIXELS;
+ private Unit heightUnit = Unit.PIXELS;
+ private static final Pattern sizePattern = Pattern
+ .compile("^(-?\\d+(\\.\\d+)?)(%|px|em|ex|in|cm|mm|pt|pc)?$");
+
+ private ComponentErrorHandler errorHandler = null;
+
+ /**
+ * Keeps track of the Actions added to this component; the actual
+ * handling/notifying is delegated, usually to the containing window.
+ */
+ private ActionManager actionManager;
+
+ /* Constructor */
+
+ /**
+ * Constructs a new Component.
+ */
+ public AbstractComponent() {
+ // ComponentSizeValidator.setCreationLocation(this);
+ }
+
+ /* Get/Set component properties */
+
+ @Override
+ public void setDebugId(String id) {
+ getState().setDebugId(id);
+ }
+
+ @Override
+ public String getDebugId() {
+ return getState().getDebugId();
+ }
+
+ /**
+ * Gets style for component. Multiple styles are joined with spaces.
+ *
+ * @return the component's styleValue of property style.
+ * @deprecated Use getStyleName() instead; renamed for consistency and to
+ * indicate that "style" should not be used to switch client
+ * side implementation, only to style the component.
+ */
+ @Deprecated
+ public String getStyle() {
+ return getStyleName();
+ }
+
+ /**
+ * Sets and replaces all previous style names of the component. This method
+ * will trigger a {@link RepaintRequestEvent}.
+ *
+ * @param style
+ * the new style of the component.
+ * @deprecated Use setStyleName() instead; renamed for consistency and to
+ * indicate that "style" should not be used to switch client
+ * side implementation, only to style the component.
+ */
+ @Deprecated
+ public void setStyle(String style) {
+ setStyleName(style);
+ }
+
+ /*
+ * Gets the component's style. Don't add a JavaDoc comment here, we use the
+ * default documentation from implemented interface.
+ */
+ @Override
+ public String getStyleName() {
+ String s = "";
+ if (getState().getStyles() != null) {
+ for (final Iterator<String> it = getState().getStyles().iterator(); it
+ .hasNext();) {
+ s += it.next();
+ if (it.hasNext()) {
+ s += " ";
+ }
+ }
+ }
+ return s;
+ }
+
+ /*
+ * Sets the component's style. Don't add a JavaDoc comment here, we use the
+ * default documentation from implemented interface.
+ */
+ @Override
+ public void setStyleName(String style) {
+ if (style == null || "".equals(style)) {
+ getState().setStyles(null);
+ requestRepaint();
+ return;
+ }
+ if (getState().getStyles() == null) {
+ getState().setStyles(new ArrayList<String>());
+ }
+ List<String> styles = getState().getStyles();
+ styles.clear();
+ String[] styleParts = style.split(" +");
+ for (String part : styleParts) {
+ if (part.length() > 0) {
+ styles.add(part);
+ }
+ }
+ requestRepaint();
+ }
+
+ @Override
+ public void addStyleName(String style) {
+ if (style == null || "".equals(style)) {
+ return;
+ }
+ if (style.contains(" ")) {
+ // Split space separated style names and add them one by one.
+ for (String realStyle : style.split(" ")) {
+ addStyleName(realStyle);
+ }
+ return;
+ }
+
+ if (getState().getStyles() == null) {
+ getState().setStyles(new ArrayList<String>());
+ }
+ List<String> styles = getState().getStyles();
+ if (!styles.contains(style)) {
+ styles.add(style);
+ requestRepaint();
+ }
+ }
+
+ @Override
+ public void removeStyleName(String style) {
+ if (getState().getStyles() != null) {
+ String[] styleParts = style.split(" +");
+ for (String part : styleParts) {
+ if (part.length() > 0) {
+ getState().getStyles().remove(part);
+ }
+ }
+ requestRepaint();
+ }
+ }
+
+ /*
+ * Get's the component's caption. Don't add a JavaDoc comment here, we use
+ * the default documentation from implemented interface.
+ */
+ @Override
+ public String getCaption() {
+ return getState().getCaption();
+ }
+
+ /**
+ * Sets the component's caption <code>String</code>. Caption is the visible
+ * name of the component. This method will trigger a
+ * {@link RepaintRequestEvent}.
+ *
+ * @param caption
+ * the new caption <code>String</code> for the component.
+ */
+ @Override
+ public void setCaption(String caption) {
+ getState().setCaption(caption);
+ requestRepaint();
+ }
+
+ /*
+ * Don't add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public Locale getLocale() {
+ if (locale != null) {
+ return locale;
+ }
+ HasComponents parent = getParent();
+ if (parent != null) {
+ return parent.getLocale();
+ }
+ final Application app = getApplication();
+ if (app != null) {
+ return app.getLocale();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the locale of this component.
+ *
+ * <pre>
+ * // Component for which the locale is meaningful
+ * InlineDateField date = new InlineDateField(&quot;Datum&quot;);
+ *
+ * // German language specified with ISO 639-1 language
+ * // code and ISO 3166-1 alpha-2 country code.
+ * date.setLocale(new Locale(&quot;de&quot;, &quot;DE&quot;));
+ *
+ * date.setResolution(DateField.RESOLUTION_DAY);
+ * layout.addComponent(date);
+ * </pre>
+ *
+ *
+ * @param locale
+ * the locale to become this component's locale.
+ */
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+
+ // FIXME: Reload value if there is a converter
+ requestRepaint();
+ }
+
+ /*
+ * Gets the component's icon resource. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ @Override
+ public Resource getIcon() {
+ return ResourceReference.getResource(getState().getIcon());
+ }
+
+ /**
+ * Sets the component's icon. This method will trigger a
+ * {@link RepaintRequestEvent}.
+ *
+ * @param icon
+ * the icon to be shown with the component's caption.
+ */
+ @Override
+ public void setIcon(Resource icon) {
+ getState().setIcon(ResourceReference.create(icon));
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ return getState().isEnabled();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#setEnabled(boolean)
+ */
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (getState().isEnabled() != enabled) {
+ getState().setEnabled(enabled);
+ requestRepaint();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.Connector#isConnectorEnabled()
+ */
+ @Override
+ public boolean isConnectorEnabled() {
+ if (!isVisible()) {
+ return false;
+ } else if (!isEnabled()) {
+ return false;
+ } else if (!super.isConnectorEnabled()) {
+ return false;
+ } else if (!getParent().isComponentVisible(this)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /*
+ * Tests if the component is in the immediate mode. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public boolean isImmediate() {
+ return getState().isImmediate();
+ }
+
+ /**
+ * Sets the component's immediate mode to the specified status. This method
+ * will trigger a {@link RepaintRequestEvent}.
+ *
+ * @param immediate
+ * the boolean value specifying if the component should be in the
+ * immediate mode after the call.
+ * @see Component#isImmediate()
+ */
+ public void setImmediate(boolean immediate) {
+ getState().setImmediate(immediate);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#isVisible()
+ */
+ @Override
+ public boolean isVisible() {
+ return getState().isVisible();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#setVisible(boolean)
+ */
+ @Override
+ public void setVisible(boolean visible) {
+ if (getState().isVisible() == visible) {
+ return;
+ }
+
+ getState().setVisible(visible);
+ requestRepaint();
+ if (getParent() != null) {
+ // Must always repaint the parent (at least the hierarchy) when
+ // visibility of a child component changes.
+ getParent().requestRepaint();
+ }
+ }
+
+ /**
+ * <p>
+ * Gets the component's description, used in tooltips and can be displayed
+ * directly in certain other components such as forms. The description can
+ * be used to briefly describe the state of the component to the user. The
+ * description string may contain certain XML tags:
+ * </p>
+ *
+ * <p>
+ * <table border=1>
+ * <tr>
+ * <td width=120><b>Tag</b></td>
+ * <td width=120><b>Description</b></td>
+ * <td width=120><b>Example</b></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;b></td>
+ * <td>bold</td>
+ * <td><b>bold text</b></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;i></td>
+ * <td>italic</td>
+ * <td><i>italic text</i></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;u></td>
+ * <td>underlined</td>
+ * <td><u>underlined text</u></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;br></td>
+ * <td>linebreak</td>
+ * <td>N/A</td>
+ * </tr>
+ * <tr>
+ * <td>&lt;ul><br>
+ * &lt;li>item1<br>
+ * &lt;li>item1<br>
+ * &lt;/ul></td>
+ * <td>item list</td>
+ * <td>
+ * <ul>
+ * <li>item1
+ * <li>item2
+ * </ul>
+ * </td>
+ * </tr>
+ * </table>
+ * </p>
+ *
+ * <p>
+ * These tags may be nested.
+ * </p>
+ *
+ * @return component's description <code>String</code>
+ */
+ public String getDescription() {
+ return getState().getDescription();
+ }
+
+ /**
+ * Sets the component's description. See {@link #getDescription()} for more
+ * information on what the description is. This method will trigger a
+ * {@link RepaintRequestEvent}.
+ *
+ * The description is displayed as HTML/XHTML in tooltips or directly in
+ * certain components so care should be taken to avoid creating the
+ * possibility for HTML injection and possibly XSS vulnerabilities.
+ *
+ * @param description
+ * the new description string for the component.
+ */
+ public void setDescription(String description) {
+ getState().setDescription(description);
+ requestRepaint();
+ }
+
+ /*
+ * Gets the component's parent component. Don't add a JavaDoc comment here,
+ * we use the default documentation from implemented interface.
+ */
+ @Override
+ public HasComponents getParent() {
+ return (HasComponents) super.getParent();
+ }
+
+ @Override
+ public void setParent(ClientConnector parent) {
+ if (parent == null || parent instanceof HasComponents) {
+ super.setParent(parent);
+ } else {
+ throw new IllegalArgumentException(
+ "The parent of a Component must implement HasComponents, which "
+ + parent.getClass() + " doesn't do.");
+ }
+ }
+
+ /**
+ * Returns the closest ancestor with the given type.
+ * <p>
+ * To find the Window that contains the component, use {@code Window w =
+ * getParent(Window.class);}
+ * </p>
+ *
+ * @param <T>
+ * The type of the ancestor
+ * @param parentType
+ * The ancestor class we are looking for
+ * @return The first ancestor that can be assigned to the given class. Null
+ * if no ancestor with the correct type could be found.
+ */
+ public <T extends HasComponents> T findAncestor(Class<T> parentType) {
+ HasComponents p = getParent();
+ while (p != null) {
+ if (parentType.isAssignableFrom(p.getClass())) {
+ return parentType.cast(p);
+ }
+ p = p.getParent();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the error message for this component.
+ *
+ * @return ErrorMessage containing the description of the error state of the
+ * component or null, if the component contains no errors. Extending
+ * classes should override this method if they support other error
+ * message types such as validation errors or buffering errors. The
+ * returned error message contains information about all the errors.
+ */
+ public ErrorMessage getErrorMessage() {
+ return componentError;
+ }
+
+ /**
+ * Gets the component's error message.
+ *
+ * @link Terminal.ErrorMessage#ErrorMessage(String, int)
+ *
+ * @return the component's error message.
+ */
+ public ErrorMessage getComponentError() {
+ return componentError;
+ }
+
+ /**
+ * Sets the component's error message. The message may contain certain XML
+ * tags, for more information see
+ *
+ * @link Component.ErrorMessage#ErrorMessage(String, int)
+ *
+ * @param componentError
+ * the new <code>ErrorMessage</code> of the component.
+ */
+ public void setComponentError(ErrorMessage componentError) {
+ this.componentError = componentError;
+ fireComponentErrorEvent();
+ requestRepaint();
+ }
+
+ /*
+ * Tests if the component is in read-only mode. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public boolean isReadOnly() {
+ return getState().isReadOnly();
+ }
+
+ /*
+ * Sets the component's read-only mode. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ getState().setReadOnly(readOnly);
+ requestRepaint();
+ }
+
+ /*
+ * Gets the parent window of the component. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public Root getRoot() {
+ // Just make method from implemented Component interface public
+ return super.getRoot();
+ }
+
+ /*
+ * Notify the component that it's attached to a window. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public void attach() {
+ super.attach();
+ if (delayedFocus) {
+ focus();
+ }
+ setActionManagerViewer();
+ }
+
+ /*
+ * Detach the component from application. Don't add a JavaDoc comment here,
+ * we use the default documentation from implemented interface.
+ */
+ @Override
+ public void detach() {
+ super.detach();
+ if (actionManager != null) {
+ // Remove any existing viewer. Root cast is just to make the
+ // compiler happy
+ actionManager.setViewer((Root) null);
+ }
+ }
+
+ /**
+ * Sets the focus for this component if the component is {@link Focusable}.
+ */
+ protected void focus() {
+ if (this instanceof Focusable) {
+ final Application app = getApplication();
+ if (app != null) {
+ getRoot().setFocusedComponent((Focusable) this);
+ delayedFocus = false;
+ } else {
+ delayedFocus = true;
+ }
+ }
+ }
+
+ /**
+ * Gets the application object to which the component is attached.
+ *
+ * <p>
+ * The method will return {@code null} if the component is not currently
+ * attached to an application. This is often a problem in constructors of
+ * regular components and in the initializers of custom composite
+ * components. A standard workaround is to move the problematic
+ * initialization to {@link #attach()}, as described in the documentation of
+ * the method.
+ * </p>
+ * <p>
+ * <b>This method is not meant to be overridden. Due to CDI requirements we
+ * cannot declare it as final even though it should be final.</b>
+ * </p>
+ *
+ * @return the parent application of the component or <code>null</code>.
+ * @see #attach()
+ */
+ @Override
+ public Application getApplication() {
+ // Just make method inherited from Component interface public
+ return super.getApplication();
+ }
+
+ /**
+ * Build CSS compatible string representation of height.
+ *
+ * @return CSS height
+ */
+ private String getCSSHeight() {
+ if (getHeightUnits() == Unit.PIXELS) {
+ return ((int) getHeight()) + getHeightUnits().getSymbol();
+ } else {
+ return getHeight() + getHeightUnits().getSymbol();
+ }
+ }
+
+ /**
+ * Build CSS compatible string representation of width.
+ *
+ * @return CSS width
+ */
+ private String getCSSWidth() {
+ if (getWidthUnits() == Unit.PIXELS) {
+ return ((int) getWidth()) + getWidthUnits().getSymbol();
+ } else {
+ return getWidth() + getWidthUnits().getSymbol();
+ }
+ }
+
+ /**
+ * Returns the shared state bean with information to be sent from the server
+ * to the client.
+ *
+ * Subclasses should override this method and set any relevant fields of the
+ * state returned by super.getState().
+ *
+ * @since 7.0
+ *
+ * @return updated component shared state
+ */
+ @Override
+ public ComponentState getState() {
+ return (ComponentState) super.getState();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#updateState()
+ */
+ @Override
+ public void updateState() {
+ // TODO This logic should be on the client side and the state should
+ // simply be a data object with "width" and "height".
+ if (getHeight() >= 0
+ && (getHeightUnits() != Unit.PERCENTAGE || ComponentSizeValidator
+ .parentCanDefineHeight(this))) {
+ getState().setHeight("" + getCSSHeight());
+ } else {
+ getState().setHeight("");
+ }
+
+ if (getWidth() >= 0
+ && (getWidthUnits() != Unit.PERCENTAGE || ComponentSizeValidator
+ .parentCanDefineWidth(this))) {
+ getState().setWidth("" + getCSSWidth());
+ } else {
+ getState().setWidth("");
+ }
+
+ ErrorMessage error = getErrorMessage();
+ if (null != error) {
+ getState().setErrorMessage(error.getFormattedHtmlMessage());
+ } else {
+ getState().setErrorMessage(null);
+ }
+ }
+
+ /* Documentation copied from interface */
+ @Override
+ public void requestRepaint() {
+ // Invisible components (by flag in this particular component) do not
+ // need repaints
+ if (!getState().isVisible()) {
+ return;
+ }
+ super.requestRepaint();
+ }
+
+ /* General event framework */
+
+ private static final Method COMPONENT_EVENT_METHOD = ReflectTools
+ .findMethod(Component.Listener.class, "componentEvent",
+ Component.Event.class);
+
+ /**
+ * <p>
+ * Registers a new listener with the specified activation method to listen
+ * events generated by this component. If the activation method does not
+ * have any arguments the event object will not be passed to it when it's
+ * called.
+ * </p>
+ *
+ * <p>
+ * This method additionally informs the event-api to route events with the
+ * given eventIdentifier to the components handleEvent function call.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package documentation}.
+ * </p>
+ *
+ * @param eventIdentifier
+ * the identifier of the event to listen for
+ * @param eventType
+ * the type of the listened event. Events of this type or its
+ * subclasses activate the listener.
+ * @param target
+ * the object instance who owns the activation method.
+ * @param method
+ * the activation method.
+ *
+ * @since 6.2
+ */
+ protected void addListener(String eventIdentifier, Class<?> eventType,
+ Object target, Method method) {
+ if (eventRouter == null) {
+ eventRouter = new EventRouter();
+ }
+ boolean needRepaint = !eventRouter.hasListeners(eventType);
+ eventRouter.addListener(eventType, target, method);
+
+ if (needRepaint) {
+ getState().addRegisteredEventListener(eventIdentifier);
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Checks if the given {@link Event} type is listened for this component.
+ *
+ * @param eventType
+ * the event type to be checked
+ * @return true if a listener is registered for the given event type
+ */
+ protected boolean hasListeners(Class<?> eventType) {
+ return eventRouter != null && eventRouter.hasListeners(eventType);
+ }
+
+ /**
+ * Removes all registered listeners matching the given parameters. Since
+ * this method receives the event type and the listener object as
+ * parameters, it will unregister all <code>object</code>'s methods that are
+ * registered to listen to events of type <code>eventType</code> generated
+ * by this component.
+ *
+ * <p>
+ * This method additionally informs the event-api to stop routing events
+ * with the given eventIdentifier to the components handleEvent function
+ * call.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package documentation}.
+ * </p>
+ *
+ * @param eventIdentifier
+ * the identifier of the event to stop listening for
+ * @param eventType
+ * the exact event type the <code>object</code> listens to.
+ * @param target
+ * the target object that has registered to listen to events of
+ * type <code>eventType</code> with one or more methods.
+ *
+ * @since 6.2
+ */
+ protected void removeListener(String eventIdentifier, Class<?> eventType,
+ Object target) {
+ if (eventRouter != null) {
+ eventRouter.removeListener(eventType, target);
+ if (!eventRouter.hasListeners(eventType)) {
+ getState().removeRegisteredEventListener(eventIdentifier);
+ requestRepaint();
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Registers a new listener with the specified activation method to listen
+ * events generated by this component. If the activation method does not
+ * have any arguments the event object will not be passed to it when it's
+ * called.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the type of the listened event. Events of this type or its
+ * subclasses activate the listener.
+ * @param target
+ * the object instance who owns the activation method.
+ * @param method
+ * the activation method.
+ */
+ @Override
+ public void addListener(Class<?> eventType, Object target, Method method) {
+ if (eventRouter == null) {
+ eventRouter = new EventRouter();
+ }
+ eventRouter.addListener(eventType, target, method);
+ }
+
+ /**
+ * <p>
+ * Convenience method for registering a new listener with the specified
+ * activation method to listen events generated by this component. If the
+ * activation method does not have any arguments the event object will not
+ * be passed to it when it's called.
+ * </p>
+ *
+ * <p>
+ * This version of <code>addListener</code> gets the name of the activation
+ * method as a parameter. The actual method is reflected from
+ * <code>object</code>, and unless exactly one match is found,
+ * <code>java.lang.IllegalArgumentException</code> is thrown.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package documentation}.
+ * </p>
+ *
+ * <p>
+ * Note: Using this method is discouraged because it cannot be checked
+ * during compilation. Use {@link #addListener(Class, Object, Method)} or
+ * {@link #addListener(com.vaadin.ui.Component.Listener)} instead.
+ * </p>
+ *
+ * @param eventType
+ * the type of the listened event. Events of this type or its
+ * subclasses activate the listener.
+ * @param target
+ * the object instance who owns the activation method.
+ * @param methodName
+ * the name of the activation method.
+ */
+ @Override
+ public void addListener(Class<?> eventType, Object target, String methodName) {
+ if (eventRouter == null) {
+ eventRouter = new EventRouter();
+ }
+ eventRouter.addListener(eventType, target, methodName);
+ }
+
+ /**
+ * Removes all registered listeners matching the given parameters. Since
+ * this method receives the event type and the listener object as
+ * parameters, it will unregister all <code>object</code>'s methods that are
+ * registered to listen to events of type <code>eventType</code> generated
+ * by this component.
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the exact event type the <code>object</code> listens to.
+ * @param target
+ * the target object that has registered to listen to events of
+ * type <code>eventType</code> with one or more methods.
+ */
+ @Override
+ public void removeListener(Class<?> eventType, Object target) {
+ if (eventRouter != null) {
+ eventRouter.removeListener(eventType, target);
+ }
+ }
+
+ /**
+ * Removes one registered listener method. The given method owned by the
+ * given object will no longer be called when the specified events are
+ * generated by this component.
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the exact event type the <code>object</code> listens to.
+ * @param target
+ * target object that has registered to listen to events of type
+ * <code>eventType</code> with one or more methods.
+ * @param method
+ * the method owned by <code>target</code> that's registered to
+ * listen to events of type <code>eventType</code>.
+ */
+ @Override
+ public void removeListener(Class<?> eventType, Object target, Method method) {
+ if (eventRouter != null) {
+ eventRouter.removeListener(eventType, target, method);
+ }
+ }
+
+ /**
+ * <p>
+ * Removes one registered listener method. The given method owned by the
+ * given object will no longer be called when the specified events are
+ * generated by this component.
+ * </p>
+ *
+ * <p>
+ * This version of <code>removeListener</code> gets the name of the
+ * activation method as a parameter. The actual method is reflected from
+ * <code>target</code>, and unless exactly one match is found,
+ * <code>java.lang.IllegalArgumentException</code> is thrown.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the exact event type the <code>object</code> listens to.
+ * @param target
+ * the target object that has registered to listen to events of
+ * type <code>eventType</code> with one or more methods.
+ * @param methodName
+ * the name of the method owned by <code>target</code> that's
+ * registered to listen to events of type <code>eventType</code>.
+ */
+ @Override
+ public void removeListener(Class<?> eventType, Object target,
+ String methodName) {
+ if (eventRouter != null) {
+ eventRouter.removeListener(eventType, target, methodName);
+ }
+ }
+
+ /**
+ * Returns all listeners that are registered for the given event type or one
+ * of its subclasses.
+ *
+ * @param eventType
+ * The type of event to return listeners for.
+ * @return A collection with all registered listeners. Empty if no listeners
+ * are found.
+ */
+ public Collection<?> getListeners(Class<?> eventType) {
+ if (eventRouter == null) {
+ return Collections.EMPTY_LIST;
+ }
+
+ return eventRouter.getListeners(eventType);
+ }
+
+ /**
+ * Sends the event to all listeners.
+ *
+ * @param event
+ * the Event to be sent to all listeners.
+ */
+ protected void fireEvent(Component.Event event) {
+ if (eventRouter != null) {
+ eventRouter.fireEvent(event);
+ }
+
+ }
+
+ /* Component event framework */
+
+ /*
+ * Registers a new listener to listen events generated by this component.
+ * Don't add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public void addListener(Component.Listener listener) {
+ addListener(Component.Event.class, listener, COMPONENT_EVENT_METHOD);
+ }
+
+ /*
+ * Removes a previously registered listener from this component. Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public void removeListener(Component.Listener listener) {
+ removeListener(Component.Event.class, listener, COMPONENT_EVENT_METHOD);
+ }
+
+ /**
+ * Emits the component event. It is transmitted to all registered listeners
+ * interested in such events.
+ */
+ protected void fireComponentEvent() {
+ fireEvent(new Component.Event(this));
+ }
+
+ /**
+ * Emits the component error event. It is transmitted to all registered
+ * listeners interested in such events.
+ */
+ protected void fireComponentErrorEvent() {
+ fireEvent(new Component.ErrorEvent(getComponentError(), this));
+ }
+
+ /**
+ * Sets the data object, that can be used for any application specific data.
+ * The component does not use or modify this data.
+ *
+ * @param data
+ * the Application specific data.
+ * @since 3.1
+ */
+ public void setData(Object data) {
+ applicationData = data;
+ }
+
+ /**
+ * Gets the application specific data. See {@link #setData(Object)}.
+ *
+ * @return the Application specific data set with setData function.
+ * @since 3.1
+ */
+ public Object getData() {
+ return applicationData;
+ }
+
+ /* Sizeable and other size related methods */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#getHeight()
+ */
+ @Override
+ public float getHeight() {
+ return height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#getHeightUnits()
+ */
+ @Override
+ public Unit getHeightUnits() {
+ return heightUnit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#getWidth()
+ */
+ @Override
+ public float getWidth() {
+ return width;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#getWidthUnits()
+ */
+ @Override
+ public Unit getWidthUnits() {
+ return widthUnit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setHeight(float, Unit)
+ */
+ @Override
+ public void setHeight(float height, Unit unit) {
+ if (unit == null) {
+ throw new IllegalArgumentException("Unit can not be null");
+ }
+ this.height = height;
+ heightUnit = unit;
+ requestRepaint();
+ // ComponentSizeValidator.setHeightLocation(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setSizeFull()
+ */
+ @Override
+ public void setSizeFull() {
+ setWidth(100, Unit.PERCENTAGE);
+ setHeight(100, Unit.PERCENTAGE);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setSizeUndefined()
+ */
+ @Override
+ public void setSizeUndefined() {
+ setWidth(-1, Unit.PIXELS);
+ setHeight(-1, Unit.PIXELS);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setWidth(float, Unit)
+ */
+ @Override
+ public void setWidth(float width, Unit unit) {
+ if (unit == null) {
+ throw new IllegalArgumentException("Unit can not be null");
+ }
+ this.width = width;
+ widthUnit = unit;
+ requestRepaint();
+ // ComponentSizeValidator.setWidthLocation(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setWidth(java.lang.String)
+ */
+ @Override
+ public void setWidth(String width) {
+ Size size = parseStringSize(width);
+ if (size != null) {
+ setWidth(size.getSize(), size.getUnit());
+ } else {
+ setWidth(-1, Unit.PIXELS);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setHeight(java.lang.String)
+ */
+ @Override
+ public void setHeight(String height) {
+ Size size = parseStringSize(height);
+ if (size != null) {
+ setHeight(size.getSize(), size.getUnit());
+ } else {
+ setHeight(-1, Unit.PIXELS);
+ }
+ }
+
+ /*
+ * Returns array with size in index 0 unit in index 1. Null or empty string
+ * will produce {-1,Unit#PIXELS}
+ */
+ private static Size parseStringSize(String s) {
+ if (s == null) {
+ return null;
+ }
+ s = s.trim();
+ if ("".equals(s)) {
+ return null;
+ }
+ float size = 0;
+ Unit unit = null;
+ Matcher matcher = sizePattern.matcher(s);
+ if (matcher.find()) {
+ size = Float.parseFloat(matcher.group(1));
+ if (size < 0) {
+ size = -1;
+ unit = Unit.PIXELS;
+ } else {
+ String symbol = matcher.group(3);
+ unit = Unit.getUnitFromSymbol(symbol);
+ }
+ } else {
+ throw new IllegalArgumentException("Invalid size argument: \"" + s
+ + "\" (should match " + sizePattern.pattern() + ")");
+ }
+ return new Size(size, unit);
+ }
+
+ private static class Size implements Serializable {
+ float size;
+ Unit unit;
+
+ public Size(float size, Unit unit) {
+ this.size = size;
+ this.unit = unit;
+ }
+
+ public float getSize() {
+ return size;
+ }
+
+ public Unit getUnit() {
+ return unit;
+ }
+ }
+
+ public interface ComponentErrorEvent extends Terminal.ErrorEvent {
+ }
+
+ public interface ComponentErrorHandler extends Serializable {
+ /**
+ * Handle the component error
+ *
+ * @param event
+ * @return True if the error has been handled False, otherwise
+ */
+ public boolean handleComponentError(ComponentErrorEvent event);
+ }
+
+ /**
+ * Gets the error handler for the component.
+ *
+ * The error handler is dispatched whenever there is an error processing the
+ * data coming from the client.
+ *
+ * @return
+ */
+ public ComponentErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ /**
+ * Sets the error handler for the component.
+ *
+ * The error handler is dispatched whenever there is an error processing the
+ * data coming from the client.
+ *
+ * If the error handler is not set, the application error handler is used to
+ * handle the exception.
+ *
+ * @param errorHandler
+ * AbstractField specific error handler
+ */
+ public void setErrorHandler(ComponentErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ /**
+ * Handle the component error event.
+ *
+ * @param error
+ * Error event to handle
+ * @return True if the error has been handled False, otherwise. If the error
+ * haven't been handled by this component, it will be handled in the
+ * application error handler.
+ */
+ public boolean handleError(ComponentErrorEvent error) {
+ if (errorHandler != null) {
+ return errorHandler.handleComponentError(error);
+ }
+ return false;
+
+ }
+
+ /*
+ * Actions
+ */
+
+ /**
+ * Gets the {@link ActionManager} used to manage the
+ * {@link ShortcutListener}s added to this {@link Field}.
+ *
+ * @return the ActionManager in use
+ */
+ protected ActionManager getActionManager() {
+ if (actionManager == null) {
+ actionManager = new ActionManager();
+ setActionManagerViewer();
+ }
+ return actionManager;
+ }
+
+ /**
+ * Set a viewer for the action manager to be the parent sub window (if the
+ * component is in a window) or the root (otherwise). This is still a
+ * simplification of the real case as this should be handled by the parent
+ * VOverlay (on the client side) if the component is inside an VOverlay
+ * component.
+ */
+ private void setActionManagerViewer() {
+ if (actionManager != null && getRoot() != null) {
+ // Attached and has action manager
+ Window w = findAncestor(Window.class);
+ if (w != null) {
+ actionManager.setViewer(w);
+ } else {
+ actionManager.setViewer(getRoot());
+ }
+ }
+
+ }
+
+ public void addShortcutListener(ShortcutListener shortcut) {
+ getActionManager().addAction(shortcut);
+ }
+
+ public void removeShortcutListener(ShortcutListener shortcut) {
+ if (actionManager != null) {
+ actionManager.removeAction(shortcut);
+ }
+ }
+}
diff --git a/server/src/com/vaadin/ui/AbstractComponentContainer.java b/server/src/com/vaadin/ui/AbstractComponentContainer.java
new file mode 100644
index 0000000000..bc27242bb8
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractComponentContainer.java
@@ -0,0 +1,351 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.vaadin.terminal.gwt.server.ComponentSizeValidator;
+
+/**
+ * Extension to {@link AbstractComponent} that defines the default
+ * implementation for the methods in {@link ComponentContainer}. Basic UI
+ * components that need to contain other components inherit this class to easily
+ * qualify as a component container.
+ *
+ * @author Vaadin Ltd
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractComponentContainer extends AbstractComponent
+ implements ComponentContainer {
+
+ /**
+ * Constructs a new component container.
+ */
+ public AbstractComponentContainer() {
+ super();
+ }
+
+ /**
+ * Removes all components from the container. This should probably be
+ * re-implemented in extending classes for a more powerful implementation.
+ */
+ @Override
+ public void removeAllComponents() {
+ final LinkedList<Component> l = new LinkedList<Component>();
+
+ // Adds all components
+ for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) {
+ l.add(i.next());
+ }
+
+ // Removes all component
+ for (final Iterator<Component> i = l.iterator(); i.hasNext();) {
+ removeComponent(i.next());
+ }
+ }
+
+ /*
+ * Moves all components from an another container into this container. Don't
+ * add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public void moveComponentsFrom(ComponentContainer source) {
+ final LinkedList<Component> components = new LinkedList<Component>();
+ for (final Iterator<Component> i = source.getComponentIterator(); i
+ .hasNext();) {
+ components.add(i.next());
+ }
+
+ for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
+ final Component c = i.next();
+ source.removeComponent(c);
+ addComponent(c);
+ }
+ }
+
+ /* Events */
+
+ private static final Method COMPONENT_ATTACHED_METHOD;
+
+ private static final Method COMPONENT_DETACHED_METHOD;
+
+ static {
+ try {
+ COMPONENT_ATTACHED_METHOD = ComponentAttachListener.class
+ .getDeclaredMethod("componentAttachedToContainer",
+ new Class[] { ComponentAttachEvent.class });
+ COMPONENT_DETACHED_METHOD = ComponentDetachListener.class
+ .getDeclaredMethod("componentDetachedFromContainer",
+ new Class[] { ComponentDetachEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in AbstractComponentContainer");
+ }
+ }
+
+ /* documented in interface */
+ @Override
+ public void addListener(ComponentAttachListener listener) {
+ addListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ /* documented in interface */
+ @Override
+ public void addListener(ComponentDetachListener listener) {
+ addListener(ComponentContainer.ComponentDetachEvent.class, listener,
+ COMPONENT_DETACHED_METHOD);
+ }
+
+ /* documented in interface */
+ @Override
+ public void removeListener(ComponentAttachListener listener) {
+ removeListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ /* documented in interface */
+ @Override
+ public void removeListener(ComponentDetachListener listener) {
+ removeListener(ComponentContainer.ComponentDetachEvent.class, listener,
+ COMPONENT_DETACHED_METHOD);
+ }
+
+ /**
+ * Fires the component attached event. This should be called by the
+ * addComponent methods after the component have been added to this
+ * container.
+ *
+ * @param component
+ * the component that has been added to this container.
+ */
+ protected void fireComponentAttachEvent(Component component) {
+ fireEvent(new ComponentAttachEvent(this, component));
+ }
+
+ /**
+ * Fires the component detached event. This should be called by the
+ * removeComponent methods after the component have been removed from this
+ * container.
+ *
+ * @param component
+ * the component that has been removed from this container.
+ */
+ protected void fireComponentDetachEvent(Component component) {
+ fireEvent(new ComponentDetachEvent(this, component));
+ }
+
+ /**
+ * This only implements the events and component parent calls. The extending
+ * classes must implement component list maintenance and call this method
+ * after component list maintenance.
+ *
+ * @see com.vaadin.ui.ComponentContainer#addComponent(Component)
+ */
+ @Override
+ public void addComponent(Component c) {
+ if (c instanceof ComponentContainer) {
+ // Make sure we're not adding the component inside it's own content
+ for (Component parent = this; parent != null; parent = parent
+ .getParent()) {
+ if (parent == c) {
+ throw new IllegalArgumentException(
+ "Component cannot be added inside it's own content");
+ }
+ }
+ }
+
+ if (c.getParent() != null) {
+ // If the component already has a parent, try to remove it
+ ComponentContainer oldParent = (ComponentContainer) c.getParent();
+ oldParent.removeComponent(c);
+
+ }
+
+ c.setParent(this);
+ fireComponentAttachEvent(c);
+ }
+
+ /**
+ * This only implements the events and component parent calls. The extending
+ * classes must implement component list maintenance and call this method
+ * before component list maintenance.
+ *
+ * @see com.vaadin.ui.ComponentContainer#removeComponent(Component)
+ */
+ @Override
+ public void removeComponent(Component c) {
+ if (c.getParent() == this) {
+ c.setParent(null);
+ fireComponentDetachEvent(c);
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (getState().isVisible() == visible) {
+ return;
+ }
+
+ super.setVisible(visible);
+ // If the visibility state is toggled it might affect all children
+ // aswell, e.g. make container visible should make children visible if
+ // they were only hidden because the container was hidden.
+ requestRepaintAll();
+ }
+
+ @Override
+ public void setWidth(float width, Unit unit) {
+ /*
+ * child tree repaints may be needed, due to our fall back support for
+ * invalid relative sizes
+ */
+ Collection<Component> dirtyChildren = null;
+ boolean childrenMayBecomeUndefined = false;
+ if (getWidth() == SIZE_UNDEFINED && width != SIZE_UNDEFINED) {
+ // children currently in invalid state may need repaint
+ dirtyChildren = getInvalidSizedChildren(false);
+ } else if ((width == SIZE_UNDEFINED && getWidth() != SIZE_UNDEFINED)
+ || (unit == Unit.PERCENTAGE
+ && getWidthUnits() != Unit.PERCENTAGE && !ComponentSizeValidator
+ .parentCanDefineWidth(this))) {
+ /*
+ * relative width children may get to invalid state if width becomes
+ * invalid. Width may also become invalid if units become percentage
+ * due to the fallback support
+ */
+ childrenMayBecomeUndefined = true;
+ dirtyChildren = getInvalidSizedChildren(false);
+ }
+ super.setWidth(width, unit);
+ repaintChangedChildTrees(dirtyChildren, childrenMayBecomeUndefined,
+ false);
+ }
+
+ private void repaintChangedChildTrees(
+ Collection<Component> invalidChildren,
+ boolean childrenMayBecomeUndefined, boolean vertical) {
+ if (childrenMayBecomeUndefined) {
+ Collection<Component> previouslyInvalidComponents = invalidChildren;
+ invalidChildren = getInvalidSizedChildren(vertical);
+ if (previouslyInvalidComponents != null && invalidChildren != null) {
+ for (Iterator<Component> iterator = invalidChildren.iterator(); iterator
+ .hasNext();) {
+ Component component = iterator.next();
+ if (previouslyInvalidComponents.contains(component)) {
+ // still invalid don't repaint
+ iterator.remove();
+ }
+ }
+ }
+ } else if (invalidChildren != null) {
+ Collection<Component> stillInvalidChildren = getInvalidSizedChildren(vertical);
+ if (stillInvalidChildren != null) {
+ for (Component component : stillInvalidChildren) {
+ // didn't become valid
+ invalidChildren.remove(component);
+ }
+ }
+ }
+ if (invalidChildren != null) {
+ repaintChildTrees(invalidChildren);
+ }
+ }
+
+ private Collection<Component> getInvalidSizedChildren(final boolean vertical) {
+ HashSet<Component> components = null;
+ if (this instanceof Panel) {
+ Panel p = (Panel) this;
+ ComponentContainer content = p.getContent();
+ boolean valid = vertical ? ComponentSizeValidator
+ .checkHeights(content) : ComponentSizeValidator
+ .checkWidths(content);
+
+ if (!valid) {
+ components = new HashSet<Component>(1);
+ components.add(content);
+ }
+ } else {
+ for (Iterator<Component> componentIterator = getComponentIterator(); componentIterator
+ .hasNext();) {
+ Component component = componentIterator.next();
+ boolean valid = vertical ? ComponentSizeValidator
+ .checkHeights(component) : ComponentSizeValidator
+ .checkWidths(component);
+ if (!valid) {
+ if (components == null) {
+ components = new HashSet<Component>();
+ }
+ components.add(component);
+ }
+ }
+ }
+ return components;
+ }
+
+ private void repaintChildTrees(Collection<Component> dirtyChildren) {
+ for (Component c : dirtyChildren) {
+ if (c instanceof ComponentContainer) {
+ ComponentContainer cc = (ComponentContainer) c;
+ cc.requestRepaintAll();
+ } else {
+ c.requestRepaint();
+ }
+ }
+ }
+
+ @Override
+ public void setHeight(float height, Unit unit) {
+ /*
+ * child tree repaints may be needed, due to our fall back support for
+ * invalid relative sizes
+ */
+ Collection<Component> dirtyChildren = null;
+ boolean childrenMayBecomeUndefined = false;
+ if (getHeight() == SIZE_UNDEFINED && height != SIZE_UNDEFINED) {
+ // children currently in invalid state may need repaint
+ dirtyChildren = getInvalidSizedChildren(true);
+ } else if ((height == SIZE_UNDEFINED && getHeight() != SIZE_UNDEFINED)
+ || (unit == Unit.PERCENTAGE
+ && getHeightUnits() != Unit.PERCENTAGE && !ComponentSizeValidator
+ .parentCanDefineHeight(this))) {
+ /*
+ * relative height children may get to invalid state if height
+ * becomes invalid. Height may also become invalid if units become
+ * percentage due to the fallback support.
+ */
+ childrenMayBecomeUndefined = true;
+ dirtyChildren = getInvalidSizedChildren(true);
+ }
+ super.setHeight(height, unit);
+ repaintChangedChildTrees(dirtyChildren, childrenMayBecomeUndefined,
+ true);
+ }
+
+ @Override
+ public Iterator<Component> iterator() {
+ return getComponentIterator();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.HasComponents#isComponentVisible(com.vaadin.ui.Component)
+ */
+ @Override
+ public boolean isComponentVisible(Component childComponent) {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/ui/AbstractField.java b/server/src/com/vaadin/ui/AbstractField.java
new file mode 100644
index 0000000000..6fe7f54df5
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractField.java
@@ -0,0 +1,1657 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Buffered;
+import com.vaadin.data.Property;
+import com.vaadin.data.Validatable;
+import com.vaadin.data.Validator;
+import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.Converter.ConversionException;
+import com.vaadin.data.util.converter.ConverterUtil;
+import com.vaadin.event.Action;
+import com.vaadin.event.ShortcutAction;
+import com.vaadin.event.ShortcutListener;
+import com.vaadin.shared.AbstractFieldState;
+import com.vaadin.terminal.AbstractErrorMessage;
+import com.vaadin.terminal.CompositeErrorMessage;
+import com.vaadin.terminal.ErrorMessage;
+
+/**
+ * <p>
+ * Abstract field component for implementing buffered property editors. The
+ * field may hold an internal value, or it may be connected to any data source
+ * that implements the {@link com.vaadin.data.Property}interface.
+ * <code>AbstractField</code> implements that interface itself, too, so
+ * accessing the Property value represented by it is straightforward.
+ * </p>
+ *
+ * <p>
+ * AbstractField also provides the {@link com.vaadin.data.Buffered} interface
+ * for buffering the data source value. By default the Field is in write
+ * through-mode and {@link #setWriteThrough(boolean)}should be called to enable
+ * buffering.
+ * </p>
+ *
+ * <p>
+ * The class also supports {@link com.vaadin.data.Validator validators} to make
+ * sure the value contained in the field is valid.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractField<T> extends AbstractComponent implements
+ Field<T>, Property.ReadOnlyStatusChangeListener,
+ Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier {
+
+ /* Private members */
+
+ private static final Logger logger = Logger.getLogger(AbstractField.class
+ .getName());
+
+ /**
+ * Value of the abstract field.
+ */
+ private T value;
+
+ /**
+ * A converter used to convert from the data model type to the field type
+ * and vice versa.
+ */
+ private Converter<T, Object> converter = null;
+ /**
+ * Connected data-source.
+ */
+ private Property<?> dataSource = null;
+
+ /**
+ * The list of validators.
+ */
+ private LinkedList<Validator> validators = null;
+
+ /**
+ * Auto commit mode.
+ */
+ private boolean writeThroughMode = true;
+
+ /**
+ * Reads the value from data-source, when it is not modified.
+ */
+ private boolean readThroughMode = true;
+
+ /**
+ * Flag to indicate that the field is currently committing its value to the
+ * datasource.
+ */
+ private boolean committingValueToDataSource = false;
+
+ /**
+ * Current source exception.
+ */
+ private Buffered.SourceException currentBufferedSourceException = null;
+
+ /**
+ * Are the invalid values allowed in fields ?
+ */
+ private boolean invalidAllowed = true;
+
+ /**
+ * Are the invalid values committed ?
+ */
+ private boolean invalidCommitted = false;
+
+ /**
+ * The error message for the exception that is thrown when the field is
+ * required but empty.
+ */
+ private String requiredError = "";
+
+ /**
+ * The error message that is shown when the field value cannot be converted.
+ */
+ private String conversionError = "Could not convert value to {0}";
+
+ /**
+ * Is automatic validation enabled.
+ */
+ private boolean validationVisible = true;
+
+ private boolean valueWasModifiedByDataSourceDuringCommit;
+
+ /**
+ * Whether this field is currently registered as listening to events from
+ * its data source.
+ *
+ * @see #setPropertyDataSource(Property)
+ * @see #addPropertyListeners()
+ * @see #removePropertyListeners()
+ */
+ private boolean isListeningToPropertyEvents = false;
+
+ /* Component basics */
+
+ /*
+ * Paints the field. Don't add a JavaDoc comment here, we use the default
+ * documentation from the implemented interface.
+ */
+
+ /**
+ * Returns true if the error indicator be hidden when painting the component
+ * even when there are errors.
+ *
+ * This is a mostly internal method, but can be overridden in subclasses
+ * e.g. if the error indicator should also be shown for empty fields in some
+ * cases.
+ *
+ * @return true to hide the error indicator, false to use the normal logic
+ * to show it when there are errors
+ */
+ protected boolean shouldHideErrors() {
+ // getErrorMessage() can still return something else than null based on
+ // validation etc.
+ return isRequired() && isEmpty() && getComponentError() == null;
+ }
+
+ /**
+ * Returns the type of the Field. The methods <code>getValue</code> and
+ * <code>setValue</code> must be compatible with this type: one must be able
+ * to safely cast the value returned from <code>getValue</code> to the given
+ * type and pass any variable assignable to this type as an argument to
+ * <code>setValue</code>.
+ *
+ * @return the type of the Field
+ */
+ @Override
+ public abstract Class<? extends T> getType();
+
+ /**
+ * The abstract field is read only also if the data source is in read only
+ * mode.
+ */
+ @Override
+ public boolean isReadOnly() {
+ return super.isReadOnly()
+ || (dataSource != null && dataSource.isReadOnly());
+ }
+
+ /**
+ * Changes the readonly state and throw read-only status change events.
+ *
+ * @see com.vaadin.ui.Component#setReadOnly(boolean)
+ */
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ fireReadOnlyStatusChange();
+ }
+
+ /**
+ * Tests if the invalid data is committed to datasource.
+ *
+ * @see com.vaadin.data.BufferedValidatable#isInvalidCommitted()
+ */
+ @Override
+ public boolean isInvalidCommitted() {
+ return invalidCommitted;
+ }
+
+ /**
+ * Sets if the invalid data should be committed to datasource.
+ *
+ * @see com.vaadin.data.BufferedValidatable#setInvalidCommitted(boolean)
+ */
+ @Override
+ public void setInvalidCommitted(boolean isCommitted) {
+ invalidCommitted = isCommitted;
+ }
+
+ /*
+ * Saves the current value to the data source Don't add a JavaDoc comment
+ * here, we use the default documentation from the implemented interface.
+ */
+ @Override
+ public void commit() throws Buffered.SourceException, InvalidValueException {
+ if (dataSource != null && !dataSource.isReadOnly()) {
+ if ((isInvalidCommitted() || isValid())) {
+ try {
+
+ // Commits the value to datasource.
+ valueWasModifiedByDataSourceDuringCommit = false;
+ committingValueToDataSource = true;
+ getPropertyDataSource().setValue(getConvertedValue());
+ } catch (final Throwable e) {
+
+ // Sets the buffering state.
+ SourceException sourceException = new Buffered.SourceException(
+ this, e);
+ setCurrentBufferedSourceException(sourceException);
+
+ // Throws the source exception.
+ throw sourceException;
+ } finally {
+ committingValueToDataSource = false;
+ }
+ } else {
+ /* An invalid value and we don't allow them, throw the exception */
+ validate();
+ }
+ }
+
+ // The abstract field is not modified anymore
+ if (isModified()) {
+ setModified(false);
+ }
+
+ // If successful, remove set the buffering state to be ok
+ if (getCurrentBufferedSourceException() != null) {
+ setCurrentBufferedSourceException(null);
+ }
+
+ if (valueWasModifiedByDataSourceDuringCommit) {
+ valueWasModifiedByDataSourceDuringCommit = false;
+ fireValueChange(false);
+ }
+
+ }
+
+ /*
+ * Updates the value from the data source. Don't add a JavaDoc comment here,
+ * we use the default documentation from the implemented interface.
+ */
+ @Override
+ public void discard() throws Buffered.SourceException {
+ if (dataSource != null) {
+
+ // Gets the correct value from datasource
+ T newFieldValue;
+ try {
+
+ // Discards buffer by overwriting from datasource
+ newFieldValue = convertFromDataSource(getDataSourceValue());
+
+ // If successful, remove set the buffering state to be ok
+ if (getCurrentBufferedSourceException() != null) {
+ setCurrentBufferedSourceException(null);
+ }
+ } catch (final Throwable e) {
+ // FIXME: What should really be done here if conversion fails?
+
+ // Sets the buffering state
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throws the source exception
+ throw currentBufferedSourceException;
+ }
+
+ final boolean wasModified = isModified();
+ setModified(false);
+
+ // If the new value differs from the previous one
+ if (!equals(newFieldValue, getInternalValue())) {
+ setInternalValue(newFieldValue);
+ fireValueChange(false);
+ } else if (wasModified) {
+ // If the value did not change, but the modification status did
+ requestRepaint();
+ }
+ }
+ }
+
+ /**
+ * Gets the value from the data source. This is only here because of clarity
+ * in the code that handles both the data model value and the field value.
+ *
+ * @return The value of the property data source
+ */
+ private Object getDataSourceValue() {
+ return dataSource.getValue();
+ }
+
+ /**
+ * Returns the field value. This is always identical to {@link #getValue()}
+ * and only here because of clarity in the code that handles both the data
+ * model value and the field value.
+ *
+ * @return The value of the field
+ */
+ private T getFieldValue() {
+ // Give the value from abstract buffers if the field if possible
+ if (dataSource == null || !isReadThrough() || isModified()) {
+ return getInternalValue();
+ }
+
+ // There is no buffered value so use whatever the data model provides
+ return convertFromDataSource(getDataSourceValue());
+ }
+
+ /*
+ * Has the field been modified since the last commit()? Don't add a JavaDoc
+ * comment here, we use the default documentation from the implemented
+ * interface.
+ */
+ @Override
+ public boolean isModified() {
+ return getState().isModified();
+ }
+
+ private void setModified(boolean modified) {
+ getState().setModified(modified);
+ requestRepaint();
+ }
+
+ /*
+ * Tests if the field is in write-through mode. Don't add a JavaDoc comment
+ * here, we use the default documentation from the implemented interface.
+ */
+ @Override
+ public boolean isWriteThrough() {
+ return writeThroughMode;
+ }
+
+ /**
+ * Sets the field's write-through mode to the specified status. When
+ * switching the write-through mode on, a {@link #commit()} will be
+ * performed.
+ *
+ * @see #setBuffered(boolean) for an easier way to control read through and
+ * write through modes
+ *
+ * @param writeThrough
+ * Boolean value to indicate if the object should be in
+ * write-through mode after the call.
+ * @throws SourceException
+ * If the operation fails because of an exception is thrown by
+ * the data source.
+ * @throws InvalidValueException
+ * If the implicit commit operation fails because of a
+ * validation error.
+ * @deprecated Use {@link #setBuffered(boolean)} instead. Note that
+ * setReadThrough(true), setWriteThrough(true) equals
+ * setBuffered(false)
+ */
+ @Override
+ @Deprecated
+ public void setWriteThrough(boolean writeThrough)
+ throws Buffered.SourceException, InvalidValueException {
+ if (writeThroughMode == writeThrough) {
+ return;
+ }
+ writeThroughMode = writeThrough;
+ if (writeThroughMode) {
+ commit();
+ }
+ }
+
+ /*
+ * Tests if the field is in read-through mode. Don't add a JavaDoc comment
+ * here, we use the default documentation from the implemented interface.
+ */
+ @Override
+ public boolean isReadThrough() {
+ return readThroughMode;
+ }
+
+ /**
+ * Sets the field's read-through mode to the specified status. When
+ * switching read-through mode on, the object's value is updated from the
+ * data source.
+ *
+ * @see #setBuffered(boolean) for an easier way to control read through and
+ * write through modes
+ *
+ * @param readThrough
+ * Boolean value to indicate if the object should be in
+ * read-through mode after the call.
+ *
+ * @throws SourceException
+ * If the operation fails because of an exception is thrown by
+ * the data source. The cause is included in the exception.
+ * @deprecated Use {@link #setBuffered(boolean)} instead. Note that
+ * setReadThrough(true), setWriteThrough(true) equals
+ * setBuffered(false)
+ */
+ @Override
+ @Deprecated
+ public void setReadThrough(boolean readThrough)
+ throws Buffered.SourceException {
+ if (readThroughMode == readThrough) {
+ return;
+ }
+ readThroughMode = readThrough;
+ if (!isModified() && readThroughMode && getPropertyDataSource() != null) {
+ setInternalValue(convertFromDataSource(getDataSourceValue()));
+ fireValueChange(false);
+ }
+ }
+
+ /**
+ * Sets the buffered mode of this Field.
+ * <p>
+ * When the field is in buffered mode, changes will not be committed to the
+ * property data source until {@link #commit()} is called.
+ * </p>
+ * <p>
+ * Changing buffered mode will change the read through and write through
+ * state for the field.
+ * </p>
+ * <p>
+ * Mixing calls to {@link #setBuffered(boolean)} and
+ * {@link #setReadThrough(boolean)} or {@link #setWriteThrough(boolean)} is
+ * generally a bad idea.
+ * </p>
+ *
+ * @param buffered
+ * true if buffered mode should be turned on, false otherwise
+ */
+ @Override
+ public void setBuffered(boolean buffered) {
+ setReadThrough(!buffered);
+ setWriteThrough(!buffered);
+ }
+
+ /**
+ * Checks the buffered mode of this Field.
+ * <p>
+ * This method only returns true if both read and write buffering is used.
+ *
+ * @return true if buffered mode is on, false otherwise
+ */
+ @Override
+ public boolean isBuffered() {
+ return !isReadThrough() && !isWriteThrough();
+ }
+
+ /* Property interface implementation */
+
+ /**
+ * Returns the (field) value converted to a String using toString().
+ *
+ * @see java.lang.Object#toString()
+ * @deprecated Instead use {@link #getValue()} to get the value of the
+ * field, {@link #getConvertedValue()} to get the field value
+ * converted to the data model type or
+ * {@link #getPropertyDataSource()} .getValue() to get the value
+ * of the data source.
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ logger.warning("You are using AbstractField.toString() to get the value for a "
+ + getClass().getSimpleName()
+ + ". This is not recommended and will not be supported in future versions.");
+ final Object value = getFieldValue();
+ if (value == null) {
+ return null;
+ }
+ return value.toString();
+ }
+
+ /**
+ * Gets the current value of the field.
+ *
+ * <p>
+ * This is the visible, modified and possible invalid value the user have
+ * entered to the field.
+ * </p>
+ *
+ * <p>
+ * Note that the object returned is compatible with getType(). For example,
+ * if the type is String, this returns Strings even when the underlying
+ * datasource is of some other type. In order to access the converted value,
+ * use {@link #getConvertedValue()} and to access the value of the property
+ * data source, use {@link Property#getValue()} for the property data
+ * source.
+ * </p>
+ *
+ * <p>
+ * Since Vaadin 7.0, no implicit conversions between other data types and
+ * String are performed, but a converter is used if set.
+ * </p>
+ *
+ * @return the current value of the field.
+ */
+ @Override
+ public T getValue() {
+ return getFieldValue();
+ }
+
+ /**
+ * Sets the value of the field.
+ *
+ * @param newFieldValue
+ * the New value of the field.
+ * @throws Property.ReadOnlyException
+ */
+ @Override
+ public void setValue(Object newFieldValue)
+ throws Property.ReadOnlyException, Converter.ConversionException {
+ // This check is needed as long as setValue accepts Object instead of T
+ if (newFieldValue != null) {
+ if (!getType().isAssignableFrom(newFieldValue.getClass())) {
+ throw new Converter.ConversionException("Value of type "
+ + newFieldValue.getClass() + " cannot be assigned to "
+ + getType().getName());
+ }
+ }
+ setValue((T) newFieldValue, false);
+ }
+
+ /**
+ * Sets the value of the field.
+ *
+ * @param newFieldValue
+ * the New value of the field.
+ * @param repaintIsNotNeeded
+ * True iff caller is sure that repaint is not needed.
+ * @throws Property.ReadOnlyException
+ */
+ protected void setValue(T newFieldValue, boolean repaintIsNotNeeded)
+ throws Property.ReadOnlyException, Converter.ConversionException,
+ InvalidValueException {
+
+ if (!equals(newFieldValue, getInternalValue())) {
+
+ // Read only fields can not be changed
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ // Repaint is needed even when the client thinks that it knows the
+ // new state if validity of the component may change
+ if (repaintIsNotNeeded
+ && (isRequired() || getValidators() != null || getConverter() != null)) {
+ repaintIsNotNeeded = false;
+ }
+
+ if (!isInvalidAllowed()) {
+ /*
+ * If invalid values are not allowed the value must be validated
+ * before it is set. If validation fails, the
+ * InvalidValueException is thrown and the internal value is not
+ * updated.
+ */
+ validate(newFieldValue);
+ }
+
+ // Changes the value
+ setInternalValue(newFieldValue);
+ setModified(dataSource != null);
+
+ valueWasModifiedByDataSourceDuringCommit = false;
+ // In write through mode , try to commit
+ if (isWriteThrough() && dataSource != null
+ && (isInvalidCommitted() || isValid())) {
+ try {
+
+ // Commits the value to datasource
+ committingValueToDataSource = true;
+ getPropertyDataSource().setValue(
+ convertToModel(newFieldValue));
+
+ // The buffer is now unmodified
+ setModified(false);
+
+ } catch (final Throwable e) {
+
+ // Sets the buffering state
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throws the source exception
+ throw currentBufferedSourceException;
+ } finally {
+ committingValueToDataSource = false;
+ }
+ }
+
+ // If successful, remove set the buffering state to be ok
+ if (getCurrentBufferedSourceException() != null) {
+ setCurrentBufferedSourceException(null);
+ }
+
+ if (valueWasModifiedByDataSourceDuringCommit) {
+ /*
+ * Value was modified by datasource. Force repaint even if
+ * repaint was not requested.
+ */
+ valueWasModifiedByDataSourceDuringCommit = repaintIsNotNeeded = false;
+ }
+
+ // Fires the value change
+ fireValueChange(repaintIsNotNeeded);
+
+ }
+ }
+
+ private static boolean equals(Object value1, Object value2) {
+ if (value1 == null) {
+ return value2 == null;
+ }
+ return value1.equals(value2);
+ }
+
+ /* External data source */
+
+ /**
+ * Gets the current data source of the field, if any.
+ *
+ * @return the current data source as a Property, or <code>null</code> if
+ * none defined.
+ */
+ @Override
+ public Property getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * <p>
+ * Sets the specified Property as the data source for the field. All
+ * uncommitted changes are replaced with a value from the new data source.
+ * </p>
+ *
+ * <p>
+ * If the datasource has any validators, the same validators are added to
+ * the field. Because the default behavior of the field is to allow invalid
+ * values, but not to allow committing them, this only adds visual error
+ * messages to fields and do not allow committing them as long as the value
+ * is invalid. After the value is valid, the error message is not shown and
+ * the commit can be done normally.
+ * </p>
+ *
+ * <p>
+ * If the data source implements
+ * {@link com.vaadin.data.Property.ValueChangeNotifier} and/or
+ * {@link com.vaadin.data.Property.ReadOnlyStatusChangeNotifier}, the field
+ * registers itself as a listener and updates itself according to the events
+ * it receives. To avoid memory leaks caused by references to a field no
+ * longer in use, the listener registrations are removed on
+ * {@link AbstractField#detach() detach} and re-added on
+ * {@link AbstractField#attach() attach}.
+ * </p>
+ *
+ * <p>
+ * Note: before 6.5 we actually called discard() method in the beginning of
+ * the method. This was removed to simplify implementation, avoid excess
+ * calls to backing property and to avoid odd value change events that were
+ * previously fired (developer expects 0-1 value change events if this
+ * method is called). Some complex field implementations might now need to
+ * override this method to do housekeeping similar to discard().
+ * </p>
+ *
+ * @param newDataSource
+ * the new data source Property.
+ */
+ @Override
+ public void setPropertyDataSource(Property newDataSource) {
+
+ // Saves the old value
+ final Object oldValue = getInternalValue();
+
+ // Stop listening to the old data source
+ removePropertyListeners();
+
+ // Sets the new data source
+ dataSource = newDataSource;
+ getState().setPropertyReadOnly(
+ dataSource == null ? false : dataSource.isReadOnly());
+
+ // Check if the current converter is compatible.
+ if (newDataSource != null
+ && !ConverterUtil.canConverterHandle(getConverter(), getType(),
+ newDataSource.getType())) {
+ // Changing from e.g. Number -> Double should set a new converter,
+ // changing from Double -> Number can keep the old one (Property
+ // accepts Number)
+
+ // Set a new converter if there is a new data source and
+ // there is no old converter or the old is incompatible.
+ setConverter(newDataSource.getType());
+ }
+ // Gets the value from source
+ try {
+ if (dataSource != null) {
+ T fieldValue = convertFromDataSource(getDataSourceValue());
+ setInternalValue(fieldValue);
+ }
+ setModified(false);
+ if (getCurrentBufferedSourceException() != null) {
+ setCurrentBufferedSourceException(null);
+ }
+ } catch (final Throwable e) {
+ setCurrentBufferedSourceException(new Buffered.SourceException(
+ this, e));
+ setModified(true);
+ }
+
+ // Listen to new data source if possible
+ addPropertyListeners();
+
+ // Copy the validators from the data source
+ if (dataSource instanceof Validatable) {
+ final Collection<Validator> validators = ((Validatable) dataSource)
+ .getValidators();
+ if (validators != null) {
+ for (final Iterator<Validator> i = validators.iterator(); i
+ .hasNext();) {
+ addValidator(i.next());
+ }
+ }
+ }
+
+ // Fires value change if the value has changed
+ T value = getInternalValue();
+ if ((value != oldValue)
+ && ((value != null && !value.equals(oldValue)) || value == null)) {
+ fireValueChange(false);
+ }
+ }
+
+ /**
+ * Retrieves a converter for the field from the converter factory defined
+ * for the application. Clears the converter if no application reference is
+ * available or if the factory returns null.
+ *
+ * @param datamodelType
+ * The type of the data model that we want to be able to convert
+ * from
+ */
+ public void setConverter(Class<?> datamodelType) {
+ Converter<T, ?> c = (Converter<T, ?>) ConverterUtil.getConverter(
+ getType(), datamodelType, getApplication());
+ setConverter(c);
+ }
+
+ /**
+ * Convert the given value from the data source type to the UI type.
+ *
+ * @param newValue
+ * The data source value to convert.
+ * @return The converted value that is compatible with the UI type or the
+ * original value if its type is compatible and no converter is set.
+ * @throws Converter.ConversionException
+ * if there is no converter and the type is not compatible with
+ * the data source type.
+ */
+ private T convertFromDataSource(Object newValue) {
+ return ConverterUtil.convertFromModel(newValue, getType(),
+ getConverter(), getLocale());
+ }
+
+ /**
+ * Convert the given value from the UI type to the data source type.
+ *
+ * @param fieldValue
+ * The value to convert. Typically returned by
+ * {@link #getFieldValue()}
+ * @return The converted value that is compatible with the data source type.
+ * @throws Converter.ConversionException
+ * if there is no converter and the type is not compatible with
+ * the data source type.
+ */
+ private Object convertToModel(T fieldValue)
+ throws Converter.ConversionException {
+ try {
+ Class<?> modelType = null;
+ Property pd = getPropertyDataSource();
+ if (pd != null) {
+ modelType = pd.getType();
+ } else if (getConverter() != null) {
+ modelType = getConverter().getModelType();
+ }
+ return ConverterUtil.convertToModel(fieldValue,
+ (Class<Object>) modelType, getConverter(), getLocale());
+ } catch (ConversionException e) {
+ throw new ConversionException(
+ getConversionError(converter.getModelType()), e);
+ }
+ }
+
+ /**
+ * Returns the conversion error with {0} replaced by the data source type.
+ *
+ * @param dataSourceType
+ * The type of the data source
+ * @return The value conversion error string with parameters replaced.
+ */
+ protected String getConversionError(Class<?> dataSourceType) {
+ if (dataSourceType == null) {
+ return getConversionError();
+ } else {
+ return getConversionError().replace("{0}",
+ dataSourceType.getSimpleName());
+ }
+ }
+
+ /**
+ * Returns the current value (as returned by {@link #getValue()}) converted
+ * to the data source type.
+ * <p>
+ * This returns the same as {@link AbstractField#getValue()} if no converter
+ * has been set. The value is not necessarily the same as the data source
+ * value e.g. if the field is in buffered mode and has been modified.
+ * </p>
+ *
+ * @return The converted value that is compatible with the data source type
+ */
+ public Object getConvertedValue() {
+ return convertToModel(getFieldValue());
+ }
+
+ /**
+ * Sets the value of the field using a value of the data source type. The
+ * value given is converted to the field type and then assigned to the
+ * field. This will update the property data source in the same way as when
+ * {@link #setValue(Object)} is called.
+ *
+ * @param value
+ * The value to set. Must be the same type as the data source.
+ */
+ public void setConvertedValue(Object value) {
+ setValue(convertFromDataSource(value));
+ }
+
+ /* Validation */
+
+ /**
+ * Adds a new validator for the field's value. All validators added to a
+ * field are checked each time the its value changes.
+ *
+ * @param validator
+ * the new validator to be added.
+ */
+ @Override
+ public void addValidator(Validator validator) {
+ if (validators == null) {
+ validators = new LinkedList<Validator>();
+ }
+ validators.add(validator);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the validators of the field.
+ *
+ * @return the Unmodifiable collection that holds all validators for the
+ * field.
+ */
+ @Override
+ public Collection<Validator> getValidators() {
+ if (validators == null || validators.isEmpty()) {
+ return null;
+ }
+ return Collections.unmodifiableCollection(validators);
+ }
+
+ /**
+ * Removes the validator from the field.
+ *
+ * @param validator
+ * the validator to remove.
+ */
+ @Override
+ public void removeValidator(Validator validator) {
+ if (validators != null) {
+ validators.remove(validator);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Removes all validators from the field.
+ */
+ public void removeAllValidators() {
+ if (validators != null) {
+ validators.clear();
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Tests the current value against registered validators if the field is not
+ * empty. If the field is empty it is considered valid if it is not required
+ * and invalid otherwise. Validators are never checked for empty fields.
+ *
+ * In most cases, {@link #validate()} should be used instead of
+ * {@link #isValid()} to also get the error message.
+ *
+ * @return <code>true</code> if all registered validators claim that the
+ * current value is valid or if the field is empty and not required,
+ * <code>false</code> otherwise.
+ */
+ @Override
+ public boolean isValid() {
+
+ try {
+ validate();
+ return true;
+ } catch (InvalidValueException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks the validity of the Field.
+ *
+ * A field is invalid if it is set as required (using
+ * {@link #setRequired(boolean)} and is empty, if one or several of the
+ * validators added to the field indicate it is invalid or if the value
+ * cannot be converted provided a converter has been set.
+ *
+ * The "required" validation is a built-in validation feature. If the field
+ * is required and empty this method throws an EmptyValueException with the
+ * error message set using {@link #setRequiredError(String)}.
+ *
+ * @see com.vaadin.data.Validatable#validate()
+ */
+ @Override
+ public void validate() throws Validator.InvalidValueException {
+
+ if (isRequired() && isEmpty()) {
+ throw new Validator.EmptyValueException(requiredError);
+ }
+ validate(getFieldValue());
+ }
+
+ /**
+ * Validates that the given value pass the validators for the field.
+ * <p>
+ * This method does not check the requiredness of the field.
+ *
+ * @param fieldValue
+ * The value to check
+ * @throws Validator.InvalidValueException
+ * if one or several validators fail
+ */
+ protected void validate(T fieldValue)
+ throws Validator.InvalidValueException {
+
+ Object valueToValidate = fieldValue;
+
+ // If there is a converter we start by converting the value as we want
+ // to validate the converted value
+ if (getConverter() != null) {
+ try {
+ valueToValidate = getConverter().convertToModel(fieldValue,
+ getLocale());
+ } catch (Exception e) {
+ throw new InvalidValueException(
+ getConversionError(getConverter().getModelType()));
+ }
+ }
+
+ List<InvalidValueException> validationExceptions = new ArrayList<InvalidValueException>();
+ if (validators != null) {
+ // Gets all the validation errors
+ for (Validator v : validators) {
+ try {
+ v.validate(valueToValidate);
+ } catch (final Validator.InvalidValueException e) {
+ validationExceptions.add(e);
+ }
+ }
+ }
+
+ // If there were no errors
+ if (validationExceptions.isEmpty()) {
+ return;
+ }
+
+ // If only one error occurred, throw it forwards
+ if (validationExceptions.size() == 1) {
+ throw validationExceptions.get(0);
+ }
+
+ InvalidValueException[] exceptionArray = validationExceptions
+ .toArray(new InvalidValueException[validationExceptions.size()]);
+
+ // Create a composite validator and include all exceptions
+ throw new Validator.InvalidValueException(null, exceptionArray);
+ }
+
+ /**
+ * Fields allow invalid values by default. In most cases this is wanted,
+ * because the field otherwise visually forget the user input immediately.
+ *
+ * @return true iff the invalid values are allowed.
+ * @see com.vaadin.data.Validatable#isInvalidAllowed()
+ */
+ @Override
+ public boolean isInvalidAllowed() {
+ return invalidAllowed;
+ }
+
+ /**
+ * Fields allow invalid values by default. In most cases this is wanted,
+ * because the field otherwise visually forget the user input immediately.
+ * <p>
+ * In common setting where the user wants to assure the correctness of the
+ * datasource, but allow temporarily invalid contents in the field, the user
+ * should add the validators to datasource, that should not allow invalid
+ * values. The validators are automatically copied to the field when the
+ * datasource is set.
+ * </p>
+ *
+ * @see com.vaadin.data.Validatable#setInvalidAllowed(boolean)
+ */
+ @Override
+ public void setInvalidAllowed(boolean invalidAllowed)
+ throws UnsupportedOperationException {
+ this.invalidAllowed = invalidAllowed;
+ }
+
+ /**
+ * Error messages shown by the fields are composites of the error message
+ * thrown by the superclasses (that is the component error message),
+ * validation errors and buffered source errors.
+ *
+ * @see com.vaadin.ui.AbstractComponent#getErrorMessage()
+ */
+ @Override
+ public ErrorMessage getErrorMessage() {
+
+ /*
+ * Check validation errors only if automatic validation is enabled.
+ * Empty, required fields will generate a validation error containing
+ * the requiredError string. For these fields the exclamation mark will
+ * be hidden but the error must still be sent to the client.
+ */
+ Validator.InvalidValueException validationError = null;
+ if (isValidationVisible()) {
+ try {
+ validate();
+ } catch (Validator.InvalidValueException e) {
+ if (!e.isInvisible()) {
+ validationError = e;
+ }
+ }
+ }
+
+ // Check if there are any systems errors
+ final ErrorMessage superError = super.getErrorMessage();
+
+ // Return if there are no errors at all
+ if (superError == null && validationError == null
+ && getCurrentBufferedSourceException() == null) {
+ return null;
+ }
+
+ // Throw combination of the error types
+ return new CompositeErrorMessage(
+ new ErrorMessage[] {
+ superError,
+ AbstractErrorMessage
+ .getErrorMessageForException(validationError),
+ AbstractErrorMessage
+ .getErrorMessageForException(getCurrentBufferedSourceException()) });
+
+ }
+
+ /* Value change events */
+
+ private static final Method VALUE_CHANGE_METHOD;
+
+ static {
+ try {
+ VALUE_CHANGE_METHOD = Property.ValueChangeListener.class
+ .getDeclaredMethod("valueChange",
+ new Class[] { Property.ValueChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in AbstractField");
+ }
+ }
+
+ /*
+ * Adds a value change listener for the field. Don't add a JavaDoc comment
+ * here, we use the default documentation from the implemented interface.
+ */
+ @Override
+ public void addListener(Property.ValueChangeListener listener) {
+ addListener(AbstractField.ValueChangeEvent.class, listener,
+ VALUE_CHANGE_METHOD);
+ }
+
+ /*
+ * Removes a value change listener from the field. Don't add a JavaDoc
+ * comment here, we use the default documentation from the implemented
+ * interface.
+ */
+ @Override
+ public void removeListener(Property.ValueChangeListener listener) {
+ removeListener(AbstractField.ValueChangeEvent.class, listener,
+ VALUE_CHANGE_METHOD);
+ }
+
+ /**
+ * Emits the value change event. The value contained in the field is
+ * validated before the event is created.
+ */
+ protected void fireValueChange(boolean repaintIsNotNeeded) {
+ fireEvent(new AbstractField.ValueChangeEvent(this));
+ if (!repaintIsNotNeeded) {
+ requestRepaint();
+ }
+ }
+
+ /* Read-only status change events */
+
+ private static final Method READ_ONLY_STATUS_CHANGE_METHOD;
+
+ static {
+ try {
+ READ_ONLY_STATUS_CHANGE_METHOD = Property.ReadOnlyStatusChangeListener.class
+ .getDeclaredMethod(
+ "readOnlyStatusChange",
+ new Class[] { Property.ReadOnlyStatusChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in AbstractField");
+ }
+ }
+
+ /**
+ * React to read only status changes of the property by requesting a
+ * repaint.
+ *
+ * @see Property.ReadOnlyStatusChangeListener
+ */
+ @Override
+ public void readOnlyStatusChange(Property.ReadOnlyStatusChangeEvent event) {
+ getState().setPropertyReadOnly(event.getProperty().isReadOnly());
+ requestRepaint();
+ }
+
+ /**
+ * An <code>Event</code> object specifying the Property whose read-only
+ * status has changed.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public static class ReadOnlyStatusChangeEvent extends Component.Event
+ implements Property.ReadOnlyStatusChangeEvent, Serializable {
+
+ /**
+ * New instance of text change event.
+ *
+ * @param source
+ * the Source of the event.
+ */
+ public ReadOnlyStatusChangeEvent(AbstractField source) {
+ super(source);
+ }
+
+ /**
+ * Property where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ @Override
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /*
+ * Adds a read-only status change listener for the field. Don't add a
+ * JavaDoc comment here, we use the default documentation from the
+ * implemented interface.
+ */
+ @Override
+ public void addListener(Property.ReadOnlyStatusChangeListener listener) {
+ addListener(Property.ReadOnlyStatusChangeEvent.class, listener,
+ READ_ONLY_STATUS_CHANGE_METHOD);
+ }
+
+ /*
+ * Removes a read-only status change listener from the field. Don't add a
+ * JavaDoc comment here, we use the default documentation from the
+ * implemented interface.
+ */
+ @Override
+ public void removeListener(Property.ReadOnlyStatusChangeListener listener) {
+ removeListener(Property.ReadOnlyStatusChangeEvent.class, listener,
+ READ_ONLY_STATUS_CHANGE_METHOD);
+ }
+
+ /**
+ * Emits the read-only status change event. The value contained in the field
+ * is validated before the event is created.
+ */
+ protected void fireReadOnlyStatusChange() {
+ fireEvent(new AbstractField.ReadOnlyStatusChangeEvent(this));
+ }
+
+ /**
+ * This method listens to data source value changes and passes the changes
+ * forwards.
+ *
+ * Changes are not forwarded to the listeners of the field during internal
+ * operations of the field to avoid duplicate notifications.
+ *
+ * @param event
+ * the value change event telling the data source contents have
+ * changed.
+ */
+ @Override
+ public void valueChange(Property.ValueChangeEvent event) {
+ if (isReadThrough()) {
+ if (committingValueToDataSource) {
+ boolean propertyNotifiesOfTheBufferedValue = equals(event
+ .getProperty().getValue(), getInternalValue());
+ if (!propertyNotifiesOfTheBufferedValue) {
+ /*
+ * Property (or chained property like PropertyFormatter) now
+ * reports different value than the one the field has just
+ * committed to it. In this case we respect the property
+ * value.
+ *
+ * Still, we don't fire value change yet, but instead
+ * postpone it until "commit" is done. See setValue(Object,
+ * boolean) and commit().
+ */
+ readValueFromProperty(event);
+ valueWasModifiedByDataSourceDuringCommit = true;
+ }
+ } else if (!isModified()) {
+ readValueFromProperty(event);
+ fireValueChange(false);
+ }
+ }
+ }
+
+ private void readValueFromProperty(Property.ValueChangeEvent event) {
+ setInternalValue(convertFromDataSource(event.getProperty().getValue()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void focus() {
+ super.focus();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#getTabIndex()
+ */
+ @Override
+ public int getTabIndex() {
+ return getState().getTabIndex();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ @Override
+ public void setTabIndex(int tabIndex) {
+ getState().setTabIndex(tabIndex);
+ requestRepaint();
+ }
+
+ /**
+ * Returns the internal field value, which might not match the data source
+ * value e.g. if the field has been modified and is not in write-through
+ * mode.
+ *
+ * This method can be overridden by subclasses together with
+ * {@link #setInternalValue(Object)} to compute internal field value at
+ * runtime. When doing so, typically also {@link #isModified()} needs to be
+ * overridden and care should be taken in the management of the empty state
+ * and buffering support.
+ *
+ * @return internal field value
+ */
+ protected T getInternalValue() {
+ return value;
+ }
+
+ /**
+ * Sets the internal field value. This is purely used by AbstractField to
+ * change the internal Field value. It does not trigger valuechange events.
+ * It can be overridden by the inheriting classes to update all dependent
+ * variables.
+ *
+ * Subclasses can also override {@link #getInternalValue()} if necessary.
+ *
+ * @param newValue
+ * the new value to be set.
+ */
+ protected void setInternalValue(T newValue) {
+ value = newValue;
+ if (validators != null && !validators.isEmpty()) {
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Notifies the component that it is connected to an application.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ super.attach();
+
+ if (!isListeningToPropertyEvents) {
+ addPropertyListeners();
+ if (!isModified() && isReadThrough()) {
+ // Update value from data source
+ discard();
+ }
+ }
+ }
+
+ @Override
+ public void detach() {
+ super.detach();
+ // Stop listening to data source events on detach to avoid a potential
+ // memory leak. See #6155.
+ removePropertyListeners();
+ }
+
+ /**
+ * Is this field required. Required fields must filled by the user.
+ *
+ * If the field is required, it is visually indicated in the user interface.
+ * Furthermore, setting field to be required implicitly adds "non-empty"
+ * validator and thus isValid() == false or any isEmpty() fields. In those
+ * cases validation errors are not painted as it is obvious that the user
+ * must fill in the required fields.
+ *
+ * On the other hand, for the non-required fields isValid() == true if the
+ * field isEmpty() regardless of any attached validators.
+ *
+ *
+ * @return <code>true</code> if the field is required, otherwise
+ * <code>false</code>.
+ */
+ @Override
+ public boolean isRequired() {
+ return getState().isRequired();
+ }
+
+ /**
+ * Sets the field required. Required fields must filled by the user.
+ *
+ * If the field is required, it is visually indicated in the user interface.
+ * Furthermore, setting field to be required implicitly adds "non-empty"
+ * validator and thus isValid() == false or any isEmpty() fields. In those
+ * cases validation errors are not painted as it is obvious that the user
+ * must fill in the required fields.
+ *
+ * On the other hand, for the non-required fields isValid() == true if the
+ * field isEmpty() regardless of any attached validators.
+ *
+ * @param required
+ * Is the field required.
+ */
+ @Override
+ public void setRequired(boolean required) {
+ getState().setRequired(required);
+ requestRepaint();
+ }
+
+ /**
+ * Set the error that is show if this field is required, but empty. When
+ * setting requiredMessage to be "" or null, no error pop-up or exclamation
+ * mark is shown for a empty required field. This faults to "". Even in
+ * those cases isValid() returns false for empty required fields.
+ *
+ * @param requiredMessage
+ * Message to be shown when this field is required, but empty.
+ */
+ @Override
+ public void setRequiredError(String requiredMessage) {
+ requiredError = requiredMessage;
+ requestRepaint();
+ }
+
+ @Override
+ public String getRequiredError() {
+ return requiredError;
+ }
+
+ /**
+ * Gets the error that is shown if the field value cannot be converted to
+ * the data source type.
+ *
+ * @return The error that is shown if conversion of the field value fails
+ */
+ public String getConversionError() {
+ return conversionError;
+ }
+
+ /**
+ * Sets the error that is shown if the field value cannot be converted to
+ * the data source type. If {0} is present in the message, it will be
+ * replaced by the simple name of the data source type.
+ *
+ * @param valueConversionError
+ * Message to be shown when conversion of the value fails
+ */
+ public void setConversionError(String valueConversionError) {
+ this.conversionError = valueConversionError;
+ requestRepaint();
+ }
+
+ /**
+ * Is the field empty?
+ *
+ * In general, "empty" state is same as null. As an exception, TextField
+ * also treats empty string as "empty".
+ */
+ protected boolean isEmpty() {
+ return (getFieldValue() == null);
+ }
+
+ /**
+ * Is automatic, visible validation enabled?
+ *
+ * If automatic validation is enabled, any validators connected to this
+ * component are evaluated while painting the component and potential error
+ * messages are sent to client. If the automatic validation is turned off,
+ * isValid() and validate() methods still work, but one must show the
+ * validation in their own code.
+ *
+ * @return True, if automatic validation is enabled.
+ */
+ public boolean isValidationVisible() {
+ return validationVisible;
+ }
+
+ /**
+ * Enable or disable automatic, visible validation.
+ *
+ * If automatic validation is enabled, any validators connected to this
+ * component are evaluated while painting the component and potential error
+ * messages are sent to client. If the automatic validation is turned off,
+ * isValid() and validate() methods still work, but one must show the
+ * validation in their own code.
+ *
+ * @param validateAutomatically
+ * True, if automatic validation is enabled.
+ */
+ public void setValidationVisible(boolean validateAutomatically) {
+ if (validationVisible != validateAutomatically) {
+ requestRepaint();
+ validationVisible = validateAutomatically;
+ }
+ }
+
+ /**
+ * Sets the current buffered source exception.
+ *
+ * @param currentBufferedSourceException
+ */
+ public void setCurrentBufferedSourceException(
+ Buffered.SourceException currentBufferedSourceException) {
+ this.currentBufferedSourceException = currentBufferedSourceException;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the current buffered source exception.
+ *
+ * @return The current source exception
+ */
+ protected Buffered.SourceException getCurrentBufferedSourceException() {
+ return currentBufferedSourceException;
+ }
+
+ /**
+ * A ready-made {@link ShortcutListener} that focuses the given
+ * {@link Focusable} (usually a {@link Field}) when the keyboard shortcut is
+ * invoked.
+ *
+ */
+ public static class FocusShortcut extends ShortcutListener {
+ protected Focusable focusable;
+
+ /**
+ * Creates a keyboard shortcut for focusing the given {@link Focusable}
+ * using the shorthand notation defined in {@link ShortcutAction}.
+ *
+ * @param focusable
+ * to focused when the shortcut is invoked
+ * @param shorthandCaption
+ * caption with keycode and modifiers indicated
+ */
+ public FocusShortcut(Focusable focusable, String shorthandCaption) {
+ super(shorthandCaption);
+ this.focusable = focusable;
+ }
+
+ /**
+ * Creates a keyboard shortcut for focusing the given {@link Focusable}.
+ *
+ * @param focusable
+ * to focused when the shortcut is invoked
+ * @param keyCode
+ * keycode that invokes the shortcut
+ * @param modifiers
+ * modifiers required to invoke the shortcut
+ */
+ public FocusShortcut(Focusable focusable, int keyCode, int... modifiers) {
+ super(null, keyCode, modifiers);
+ this.focusable = focusable;
+ }
+
+ /**
+ * Creates a keyboard shortcut for focusing the given {@link Focusable}.
+ *
+ * @param focusable
+ * to focused when the shortcut is invoked
+ * @param keyCode
+ * keycode that invokes the shortcut
+ */
+ public FocusShortcut(Focusable focusable, int keyCode) {
+ this(focusable, keyCode, null);
+ }
+
+ @Override
+ public void handleAction(Object sender, Object target) {
+ focusable.focus();
+ }
+ }
+
+ /**
+ * Gets the converter used to convert the property data source value to the
+ * field value.
+ *
+ * @return The converter or null if none is set.
+ */
+ public Converter<T, Object> getConverter() {
+ return converter;
+ }
+
+ /**
+ * Sets the converter used to convert the field value to property data
+ * source type. The converter must have a presentation type that matches the
+ * field type.
+ *
+ * @param converter
+ * The new converter to use.
+ */
+ public void setConverter(Converter<T, ?> converter) {
+ this.converter = (Converter<T, Object>) converter;
+ requestRepaint();
+ }
+
+ @Override
+ public AbstractFieldState getState() {
+ return (AbstractFieldState) super.getState();
+ }
+
+ @Override
+ public void updateState() {
+ super.updateState();
+
+ // Hide the error indicator if needed
+ getState().setHideErrors(shouldHideErrors());
+ }
+
+ /**
+ * Registers this as an event listener for events sent by the data source
+ * (if any). Does nothing if
+ * <code>isListeningToPropertyEvents == true</code>.
+ */
+ private void addPropertyListeners() {
+ if (!isListeningToPropertyEvents) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .addListener(this);
+ }
+ isListeningToPropertyEvents = true;
+ }
+ }
+
+ /**
+ * Stops listening to events sent by the data source (if any). Does nothing
+ * if <code>isListeningToPropertyEvents == false</code>.
+ */
+ private void removePropertyListeners() {
+ if (isListeningToPropertyEvents) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ isListeningToPropertyEvents = false;
+ }
+ }
+}
diff --git a/server/src/com/vaadin/ui/AbstractJavaScriptComponent.java b/server/src/com/vaadin/ui/AbstractJavaScriptComponent.java
new file mode 100644
index 0000000000..5ec80573ab
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractJavaScriptComponent.java
@@ -0,0 +1,165 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import com.vaadin.shared.ui.JavaScriptComponentState;
+import com.vaadin.terminal.JavaScriptCallbackHelper;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.ui.JavaScriptWidget;
+
+/**
+ * Base class for Components with all client-side logic implemented using
+ * JavaScript.
+ * <p>
+ * When a new JavaScript component is initialized in the browser, the framework
+ * will look for a globally defined JavaScript function that will initialize the
+ * component. The name of the initialization function is formed by replacing .
+ * with _ in the name of the server-side class. If no such function is defined,
+ * each super class is used in turn until a match is found. The framework will
+ * thus first attempt with <code>com_example_MyComponent</code> for the
+ * server-side
+ * <code>com.example.MyComponent extends AbstractJavaScriptComponent</code>
+ * class. If MyComponent instead extends <code>com.example.SuperComponent</code>
+ * , then <code>com_example_SuperComponent</code> will also be attempted if
+ * <code>com_example_MyComponent</code> has not been defined.
+ * <p>
+ * JavaScript components have a very simple GWT widget ({@link JavaScriptWidget}
+ * ) just consisting of a <code>div</code> element to which the JavaScript code
+ * should initialize its own user interface.
+ * <p>
+ * The initialization function will be called with <code>this</code> pointing to
+ * a connector wrapper object providing integration to Vaadin with the following
+ * functions:
+ * <ul>
+ * <li><code>getConnectorId()</code> - returns a string with the id of the
+ * connector.</li>
+ * <li><code>getParentId([connectorId])</code> - returns a string with the id of
+ * the connector's parent. If <code>connectorId</code> is provided, the id of
+ * the parent of the corresponding connector with the passed id is returned
+ * instead.</li>
+ * <li><code>getElement([connectorId])</code> - returns the DOM Element that is
+ * the root of a connector's widget. <code>null</code> is returned if the
+ * connector can not be found or if the connector doesn't have a widget. If
+ * <code>connectorId</code> is not provided, the connector id of the current
+ * connector will be used.</li>
+ * <li><code>getState()</code> - returns an object corresponding to the shared
+ * state defined on the server. The scheme for conversion between Java and
+ * JavaScript types is described bellow.</li>
+ * <li><code>registerRpc([name, ] rpcObject)</code> - registers the
+ * <code>rpcObject</code> as a RPC handler. <code>rpcObject</code> should be an
+ * object with field containing functions for all eligible RPC functions. If
+ * <code>name</code> is provided, the RPC handler will only used for RPC calls
+ * for the RPC interface with the same fully qualified Java name. If no
+ * <code>name</code> is provided, the RPC handler will be used for all incoming
+ * RPC invocations where the RPC method name is defined as a function field in
+ * the handler. The scheme for conversion between Java types in the RPC
+ * interface definition and the JavaScript values passed as arguments to the
+ * handler functions is described bellow.</li>
+ * <li><code>getRpcProxy([name])</code> - returns an RPC proxy object. If
+ * <code>name</code> is provided, the proxy object will contain functions for
+ * all methods in the RPC interface with the same fully qualified name, provided
+ * a RPC handler has been registered by the server-side code. If no
+ * <code>name</code> is provided, the returned RPC proxy object will contain
+ * functions for all methods in all RPC interfaces registered for the connector
+ * on the server. If the same method name is present in multiple registered RPC
+ * interfaces, the corresponding function in the RPC proxy object will throw an
+ * exception when called. The scheme for conversion between Java types in the
+ * RPC interface and the JavaScript values that should be passed to the
+ * functions is described bellow.</li>
+ * <li><code>translateVaadinUri(uri)</code> - Translates a Vaadin URI to a URL
+ * that can be used in the browser. This is just way of accessing
+ * {@link ApplicationConnection#translateVaadinUri(String)}</li>
+ * </ul>
+ * The connector wrapper also supports these special functions:
+ * <ul>
+ * <li><code>onStateChange</code> - If the JavaScript code assigns a function to
+ * the field, that function is called whenever the contents of the shared state
+ * is changed.</li>
+ * <li>Any field name corresponding to a call to
+ * {@link #addFunction(String, JavaScriptFunction)} on the server will
+ * automatically be present as a function that triggers the registered function
+ * on the server.</li>
+ * <li>Any field name referred to using
+ * {@link #callFunction(String, Object...)} on the server will be called if a
+ * function has been assigned to the field.</li>
+ * </ul>
+ * <p>
+ *
+ * Values in the Shared State and in RPC calls are converted between Java and
+ * JavaScript using the following conventions:
+ * <ul>
+ * <li>Primitive Java numbers (byte, char, int, long, float, double) and their
+ * boxed types (Byte, Character, Integer, Long, Float, Double) are represented
+ * by JavaScript numbers.</li>
+ * <li>The primitive Java boolean and the boxed Boolean are represented by
+ * JavaScript booleans.</li>
+ * <li>Java Strings are represented by JavaScript strings.</li>
+ * <li>List, Set and all arrays in Java are represented by JavaScript arrays.</li>
+ * <li>Map<String, ?> in Java is represented by JavaScript object with fields
+ * corresponding to the map keys.</li>
+ * <li>Any other Java Map is represented by a JavaScript array containing two
+ * arrays, the first contains the keys and the second contains the values in the
+ * same order.</li>
+ * <li>A Java Bean is represented by a JavaScript object with fields
+ * corresponding to the bean's properties.</li>
+ * <li>A Java Connector is represented by a JavaScript string containing the
+ * connector's id.</li>
+ * <li>A pluggable serialization mechanism is provided for types not described
+ * here. Please refer to the documentation for specific types for serialization
+ * information.</li>
+ * </ul>
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ */
+public abstract class AbstractJavaScriptComponent extends AbstractComponent {
+ private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper(
+ this);
+
+ @Override
+ protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) {
+ super.registerRpc(implementation, rpcInterfaceType);
+ callbackHelper.registerRpc(rpcInterfaceType);
+ }
+
+ /**
+ * Register a {@link JavaScriptFunction} that can be called from the
+ * JavaScript using the provided name. A JavaScript function with the
+ * provided name will be added to the connector wrapper object (initially
+ * available as <code>this</code>). Calling that JavaScript function will
+ * cause the call method in the registered {@link JavaScriptFunction} to be
+ * invoked with the same arguments.
+ *
+ * @param functionName
+ * the name that should be used for client-side function
+ * @param function
+ * the {@link JavaScriptFunction} object that will be invoked
+ * when the JavaScript function is called
+ */
+ protected void addFunction(String functionName, JavaScriptFunction function) {
+ callbackHelper.registerCallback(functionName, function);
+ }
+
+ /**
+ * Invoke a named function that the connector JavaScript has added to the
+ * JavaScript connector wrapper object. The arguments should only contain
+ * data types that can be represented in JavaScript including primitives,
+ * their boxed types, arrays, String, List, Set, Map, Connector and
+ * JavaBeans.
+ *
+ * @param name
+ * the name of the function
+ * @param arguments
+ * function arguments
+ */
+ protected void callFunction(String name, Object... arguments) {
+ callbackHelper.invokeCallback(name, arguments);
+ }
+
+ @Override
+ public JavaScriptComponentState getState() {
+ return (JavaScriptComponentState) super.getState();
+ }
+}
diff --git a/server/src/com/vaadin/ui/AbstractLayout.java b/server/src/com/vaadin/ui/AbstractLayout.java
new file mode 100644
index 0000000000..7b3a537d06
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractLayout.java
@@ -0,0 +1,77 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.shared.ui.AbstractLayoutState;
+import com.vaadin.ui.Layout.MarginHandler;
+
+/**
+ * An abstract class that defines default implementation for the {@link Layout}
+ * interface.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractLayout extends AbstractComponentContainer
+ implements Layout, MarginHandler {
+
+ protected MarginInfo margins = new MarginInfo(false);
+
+ @Override
+ public AbstractLayoutState getState() {
+ return (AbstractLayoutState) super.getState();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout#setMargin(boolean)
+ */
+ @Override
+ public void setMargin(boolean enabled) {
+ margins.setMargins(enabled);
+ getState().setMarginsBitmask(margins.getBitMask());
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.MarginHandler#getMargin()
+ */
+ @Override
+ public MarginInfo getMargin() {
+ return margins;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.MarginHandler#setMargin(MarginInfo)
+ */
+ @Override
+ public void setMargin(MarginInfo marginInfo) {
+ margins.setMargins(marginInfo);
+ getState().setMarginsBitmask(margins.getBitMask());
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout#setMargin(boolean, boolean, boolean, boolean)
+ */
+ @Override
+ public void setMargin(boolean topEnabled, boolean rightEnabled,
+ boolean bottomEnabled, boolean leftEnabled) {
+ margins.setMargins(topEnabled, rightEnabled, bottomEnabled, leftEnabled);
+ getState().setMarginsBitmask(margins.getBitMask());
+ requestRepaint();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/AbstractMedia.java b/server/src/com/vaadin/ui/AbstractMedia.java
new file mode 100644
index 0000000000..71b2e38ef3
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractMedia.java
@@ -0,0 +1,196 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vaadin.shared.communication.URLReference;
+import com.vaadin.shared.ui.AbstractMediaState;
+import com.vaadin.shared.ui.MediaControl;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.gwt.server.ResourceReference;
+
+/**
+ * Abstract base class for the HTML5 media components.
+ *
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractMedia extends AbstractComponent {
+
+ @Override
+ public AbstractMediaState getState() {
+ return (AbstractMediaState) super.getState();
+ }
+
+ /**
+ * Sets a single media file as the source of the media component.
+ *
+ * @param source
+ */
+ public void setSource(Resource source) {
+ clearSources();
+
+ addSource(source);
+ }
+
+ private void clearSources() {
+ getState().getSources().clear();
+ getState().getSourceTypes().clear();
+ }
+
+ /**
+ * Adds an alternative media file to the sources list. Which of the sources
+ * is used is selected by the browser depending on which file formats it
+ * supports. See <a
+ * href="http://en.wikipedia.org/wiki/HTML5_video#Table">wikipedia</a> for a
+ * table of formats supported by different browsers.
+ *
+ * @param source
+ */
+ public void addSource(Resource source) {
+ if (source != null) {
+ getState().getSources().add(new ResourceReference(source));
+ getState().getSourceTypes().add(source.getMIMEType());
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Set multiple sources at once. Which of the sources is used is selected by
+ * the browser depending on which file formats it supports. See <a
+ * href="http://en.wikipedia.org/wiki/HTML5_video#Table">wikipedia</a> for a
+ * table of formats supported by different browsers.
+ *
+ * @param sources
+ */
+ public void setSources(Resource... sources) {
+ clearSources();
+ for (Resource source : sources) {
+ addSource(source);
+ }
+ }
+
+ /**
+ * @return The sources pointed to in this media.
+ */
+ public List<Resource> getSources() {
+ ArrayList<Resource> sources = new ArrayList<Resource>();
+ for (URLReference ref : getState().getSources()) {
+ sources.add(((ResourceReference) ref).getResource());
+ }
+ return sources;
+ }
+
+ /**
+ * Sets whether or not the browser should show native media controls.
+ *
+ * @param showControls
+ */
+ public void setShowControls(boolean showControls) {
+ getState().setShowControls(showControls);
+ requestRepaint();
+ }
+
+ /**
+ * @return true if the browser is to show native media controls.
+ */
+ public boolean isShowControls() {
+ return getState().isShowControls();
+ }
+
+ /**
+ * Sets the alternative text to be displayed if the browser does not support
+ * HTML5. This text is rendered as HTML if
+ * {@link #setHtmlContentAllowed(boolean)} is set to true. With HTML
+ * rendering, this method can also be used to implement fallback to a
+ * flash-based player, see the <a href=
+ * "https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Using_Flash"
+ * >Mozilla Developer Network</a> for details.
+ *
+ * @param altText
+ */
+ public void setAltText(String altText) {
+ getState().setAltText(altText);
+ requestRepaint();
+ }
+
+ /**
+ * @return The text/html that is displayed when a browser doesn't support
+ * HTML5.
+ */
+ public String getAltText() {
+ return getState().getAltText();
+ }
+
+ /**
+ * Set whether the alternative text ({@link #setAltText(String)}) is
+ * rendered as HTML or not.
+ *
+ * @param htmlContentAllowed
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ getState().setHtmlContentAllowed(htmlContentAllowed);
+ requestRepaint();
+ }
+
+ /**
+ * @return true if the alternative text ({@link #setAltText(String)}) is to
+ * be rendered as HTML.
+ */
+ public boolean isHtmlContentAllowed() {
+ return getState().isHtmlContentAllowed();
+ }
+
+ /**
+ * Sets whether the media is to automatically start playback when enough
+ * data has been loaded.
+ *
+ * @param autoplay
+ */
+ public void setAutoplay(boolean autoplay) {
+ getState().setAutoplay(autoplay);
+ requestRepaint();
+ }
+
+ /**
+ * @return true if the media is set to automatically start playback.
+ */
+ public boolean isAutoplay() {
+ return getState().isAutoplay();
+ }
+
+ /**
+ * Set whether to mute the audio or not.
+ *
+ * @param muted
+ */
+ public void setMuted(boolean muted) {
+ getState().setMuted(muted);
+ requestRepaint();
+ }
+
+ /**
+ * @return true if the audio is muted.
+ */
+ public boolean isMuted() {
+ return getState().isMuted();
+ }
+
+ /**
+ * Pauses the media.
+ */
+ public void pause() {
+ getRpcProxy(MediaControl.class).pause();
+ }
+
+ /**
+ * Starts playback of the media.
+ */
+ public void play() {
+ getRpcProxy(MediaControl.class).play();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
new file mode 100644
index 0000000000..0581d0a279
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
@@ -0,0 +1,383 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.vaadin.event.LayoutEvents.LayoutClickEvent;
+import com.vaadin.event.LayoutEvents.LayoutClickListener;
+import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutServerRpc;
+import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState;
+import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState.ChildComponentData;
+import com.vaadin.terminal.Sizeable;
+import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler;
+
+@SuppressWarnings("serial")
+public abstract class AbstractOrderedLayout extends AbstractLayout implements
+ Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier {
+
+ private AbstractOrderedLayoutServerRpc rpc = new AbstractOrderedLayoutServerRpc() {
+
+ @Override
+ public void layoutClick(MouseEventDetails mouseDetails,
+ Connector clickedConnector) {
+ fireEvent(LayoutClickEvent.createEvent(AbstractOrderedLayout.this,
+ mouseDetails, clickedConnector));
+ }
+ };
+
+ public static final Alignment ALIGNMENT_DEFAULT = Alignment.TOP_LEFT;
+
+ /**
+ * Custom layout slots containing the components.
+ */
+ protected LinkedList<Component> components = new LinkedList<Component>();
+
+ /* Child component alignments */
+
+ /**
+ * Mapping from components to alignments (horizontal + vertical).
+ */
+ public AbstractOrderedLayout() {
+ registerRpc(rpc);
+ }
+
+ @Override
+ public AbstractOrderedLayoutState getState() {
+ return (AbstractOrderedLayoutState) super.getState();
+ }
+
+ /**
+ * Add a component into this container. The component is added to the right
+ * or under the previous component.
+ *
+ * @param c
+ * the component to be added.
+ */
+ @Override
+ public void addComponent(Component c) {
+ // Add to components before calling super.addComponent
+ // so that it is available to AttachListeners
+ components.add(c);
+ try {
+ super.addComponent(c);
+ } catch (IllegalArgumentException e) {
+ components.remove(c);
+ throw e;
+ }
+ componentAdded(c);
+ }
+
+ /**
+ * Adds a component into this container. The component is added to the left
+ * or on top of the other components.
+ *
+ * @param c
+ * the component to be added.
+ */
+ public void addComponentAsFirst(Component c) {
+ // If c is already in this, we must remove it before proceeding
+ // see ticket #7668
+ if (c.getParent() == this) {
+ removeComponent(c);
+ }
+ components.addFirst(c);
+ try {
+ super.addComponent(c);
+ } catch (IllegalArgumentException e) {
+ components.remove(c);
+ throw e;
+ }
+ componentAdded(c);
+
+ }
+
+ /**
+ * Adds a component into indexed position in this container.
+ *
+ * @param c
+ * the component to be added.
+ * @param index
+ * the index of the component position. The components currently
+ * in and after the position are shifted forwards.
+ */
+ public void addComponent(Component c, int index) {
+ // If c is already in this, we must remove it before proceeding
+ // see ticket #7668
+ if (c.getParent() == this) {
+ // When c is removed, all components after it are shifted down
+ if (index > getComponentIndex(c)) {
+ index--;
+ }
+ removeComponent(c);
+ }
+ components.add(index, c);
+ try {
+ super.addComponent(c);
+ } catch (IllegalArgumentException e) {
+ components.remove(c);
+ throw e;
+ }
+
+ componentAdded(c);
+ }
+
+ private void componentRemoved(Component c) {
+ getState().getChildData().remove(c);
+ requestRepaint();
+ }
+
+ private void componentAdded(Component c) {
+ getState().getChildData().put(c, new ChildComponentData());
+ requestRepaint();
+
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * the component to be removed.
+ */
+ @Override
+ public void removeComponent(Component c) {
+ components.remove(c);
+ super.removeComponent(c);
+ componentRemoved(c);
+ }
+
+ /**
+ * Gets the component container iterator for going trough all the components
+ * in the container.
+ *
+ * @return the Iterator of the components inside the container.
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return components.iterator();
+ }
+
+ /**
+ * Gets the number of contained components. Consistent with the iterator
+ * returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components
+ */
+ @Override
+ public int getComponentCount() {
+ return components.size();
+ }
+
+ /* Documented in superclass */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ // Gets the locations
+ int oldLocation = -1;
+ int newLocation = -1;
+ int location = 0;
+ for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
+ final Component component = i.next();
+
+ if (component == oldComponent) {
+ oldLocation = location;
+ }
+ if (component == newComponent) {
+ newLocation = location;
+ }
+
+ location++;
+ }
+
+ if (oldLocation == -1) {
+ addComponent(newComponent);
+ } else if (newLocation == -1) {
+ removeComponent(oldComponent);
+ addComponent(newComponent, oldLocation);
+ } else {
+ // Both old and new are in the layout
+ if (oldLocation > newLocation) {
+ components.remove(oldComponent);
+ components.add(newLocation, oldComponent);
+ components.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ } else {
+ components.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ components.remove(oldComponent);
+ components.add(newLocation, oldComponent);
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.AlignmentHandler#setComponentAlignment(com
+ * .vaadin.ui.Component, int, int)
+ */
+ @Override
+ public void setComponentAlignment(Component childComponent,
+ int horizontalAlignment, int verticalAlignment) {
+ Alignment a = new Alignment(horizontalAlignment + verticalAlignment);
+ setComponentAlignment(childComponent, a);
+ }
+
+ @Override
+ public void setComponentAlignment(Component childComponent,
+ Alignment alignment) {
+ ChildComponentData childData = getState().getChildData().get(
+ childComponent);
+ if (childData != null) {
+ // Alignments are bit masks
+ childData.setAlignmentBitmask(alignment.getBitMask());
+ requestRepaint();
+ } else {
+ throw new IllegalArgumentException(
+ "Component must be added to layout before using setComponentAlignment()");
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
+ * .vaadin.ui.Component)
+ */
+ @Override
+ public Alignment getComponentAlignment(Component childComponent) {
+ ChildComponentData childData = getState().getChildData().get(
+ childComponent);
+ if (childData == null) {
+ throw new IllegalArgumentException(
+ "The given component is not a child of this layout");
+ }
+
+ return new Alignment(childData.getAlignmentBitmask());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
+ */
+ @Override
+ public void setSpacing(boolean spacing) {
+ getState().setSpacing(spacing);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
+ */
+ @Override
+ public boolean isSpacing() {
+ return getState().isSpacing();
+ }
+
+ /**
+ * <p>
+ * This method is used to control how excess space in layout is distributed
+ * among components. Excess space may exist if layout is sized and contained
+ * non relatively sized components don't consume all available space.
+ *
+ * <p>
+ * Example how to distribute 1:3 (33%) for component1 and 2:3 (67%) for
+ * component2 :
+ *
+ * <code>
+ * layout.setExpandRatio(component1, 1);<br>
+ * layout.setExpandRatio(component2, 2);
+ * </code>
+ *
+ * <p>
+ * If no ratios have been set, the excess space is distributed evenly among
+ * all components.
+ *
+ * <p>
+ * Note, that width or height (depending on orientation) needs to be defined
+ * for this method to have any effect.
+ *
+ * @see Sizeable
+ *
+ * @param component
+ * the component in this layout which expand ratio is to be set
+ * @param ratio
+ */
+ public void setExpandRatio(Component component, float ratio) {
+ ChildComponentData childData = getState().getChildData().get(component);
+ if (childData == null) {
+ throw new IllegalArgumentException(
+ "The given component is not a child of this layout");
+ }
+
+ childData.setExpandRatio(ratio);
+ requestRepaint();
+ };
+
+ /**
+ * Returns the expand ratio of given component.
+ *
+ * @param component
+ * which expand ratios is requested
+ * @return expand ratio of given component, 0.0f by default.
+ */
+ public float getExpandRatio(Component component) {
+ ChildComponentData childData = getState().getChildData().get(component);
+ if (childData == null) {
+ throw new IllegalArgumentException(
+ "The given component is not a child of this layout");
+ }
+
+ return childData.getExpandRatio();
+ }
+
+ @Override
+ public void addListener(LayoutClickListener listener) {
+ addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener,
+ LayoutClickListener.clickMethod);
+ }
+
+ @Override
+ public void removeListener(LayoutClickListener listener) {
+ removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener);
+ }
+
+ /**
+ * Returns the index of the given component.
+ *
+ * @param component
+ * The component to look up.
+ * @return The index of the component or -1 if the component is not a child.
+ */
+ public int getComponentIndex(Component component) {
+ return components.indexOf(component);
+ }
+
+ /**
+ * Returns the component at the given position.
+ *
+ * @param index
+ * The position of the component.
+ * @return The component at the given index.
+ * @throws IndexOutOfBoundsException
+ * If the index is out of range.
+ */
+ public Component getComponent(int index) throws IndexOutOfBoundsException {
+ return components.get(index);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/AbstractSelect.java b/server/src/com/vaadin/ui/AbstractSelect.java
new file mode 100644
index 0000000000..0a97ceb649
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractSelect.java
@@ -0,0 +1,2029 @@
+/*
+ * @VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.event.DataBoundTransferable;
+import com.vaadin.event.Transferable;
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetailsImpl;
+import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
+import com.vaadin.event.dd.acceptcriteria.ContainsDataFlavor;
+import com.vaadin.event.dd.acceptcriteria.TargetDetailIs;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.terminal.KeyMapper;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.ui.AbstractSelect.ItemCaptionMode;
+
+/**
+ * <p>
+ * A class representing a selection of items the user has selected in a UI. The
+ * set of choices is presented as a set of {@link com.vaadin.data.Item}s in a
+ * {@link com.vaadin.data.Container}.
+ * </p>
+ *
+ * <p>
+ * A <code>Select</code> component may be in single- or multiselect mode.
+ * Multiselect mode means that more than one item can be selected
+ * simultaneously.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+// TODO currently cannot specify type more precisely in case of multi-select
+public abstract class AbstractSelect extends AbstractField<Object> implements
+ Container, Container.Viewer, Container.PropertySetChangeListener,
+ Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier,
+ Container.ItemSetChangeListener, Vaadin6Component {
+
+ public enum ItemCaptionMode {
+ /**
+ * Item caption mode: Item's ID's <code>String</code> representation is
+ * used as caption.
+ */
+ ID,
+ /**
+ * Item caption mode: Item's <code>String</code> representation is used
+ * as caption.
+ */
+ ITEM,
+ /**
+ * Item caption mode: Index of the item is used as caption. The index
+ * mode can only be used with the containers implementing the
+ * {@link com.vaadin.data.Container.Indexed} interface.
+ */
+ INDEX,
+ /**
+ * Item caption mode: If an Item has a caption it's used, if not, Item's
+ * ID's <code>String</code> representation is used as caption. <b>This
+ * is the default</b>.
+ */
+ EXPLICIT_DEFAULTS_ID,
+ /**
+ * Item caption mode: Captions must be explicitly specified.
+ */
+ EXPLICIT,
+ /**
+ * Item caption mode: Only icons are shown, captions are hidden.
+ */
+ ICON_ONLY,
+ /**
+ * Item caption mode: Item captions are read from property specified
+ * with <code>setItemCaptionPropertyId</code>.
+ */
+ PROPERTY;
+ }
+
+ /**
+ * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_ID = ItemCaptionMode.ID;
+
+ /**
+ * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_ITEM = ItemCaptionMode.ITEM;
+
+ /**
+ * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_INDEX = ItemCaptionMode.INDEX;
+
+ /**
+ * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT = ItemCaptionMode.EXPLICIT;
+
+ /**
+ * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_ICON_ONLY = ItemCaptionMode.ICON_ONLY;
+
+ /**
+ * @deprecated from 7.0, use {@link ItemCaptionMode.ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_PROPERTY = ItemCaptionMode.PROPERTY;
+
+ /**
+ * Interface for option filtering, used to filter options based on user
+ * entered value. The value is matched to the item caption.
+ * <code>FILTERINGMODE_OFF</code> (0) turns the filtering off.
+ * <code>FILTERINGMODE_STARTSWITH</code> (1) matches from the start of the
+ * caption. <code>FILTERINGMODE_CONTAINS</code> (1) matches anywhere in the
+ * caption.
+ */
+ public interface Filtering extends Serializable {
+ public static final int FILTERINGMODE_OFF = 0;
+ public static final int FILTERINGMODE_STARTSWITH = 1;
+ public static final int FILTERINGMODE_CONTAINS = 2;
+
+ /**
+ * Sets the option filtering mode.
+ *
+ * @param filteringMode
+ * the filtering mode to use
+ */
+ public void setFilteringMode(int filteringMode);
+
+ /**
+ * Gets the current filtering mode.
+ *
+ * @return the filtering mode in use
+ */
+ public int getFilteringMode();
+
+ }
+
+ /**
+ * Multi select modes that controls how multi select behaves.
+ */
+ public enum MultiSelectMode {
+ /**
+ * The default behavior of the multi select mode
+ */
+ DEFAULT,
+
+ /**
+ * The previous more simple behavior of the multselect
+ */
+ SIMPLE
+ }
+
+ /**
+ * Is the select in multiselect mode?
+ */
+ private boolean multiSelect = false;
+
+ /**
+ * Select options.
+ */
+ protected Container items;
+
+ /**
+ * Is the user allowed to add new options?
+ */
+ private boolean allowNewOptions;
+
+ /**
+ * Keymapper used to map key values.
+ */
+ protected KeyMapper<Object> itemIdMapper = new KeyMapper<Object>();
+
+ /**
+ * Item icons.
+ */
+ private final HashMap<Object, Resource> itemIcons = new HashMap<Object, Resource>();
+
+ /**
+ * Item captions.
+ */
+ private final HashMap<Object, String> itemCaptions = new HashMap<Object, String>();
+
+ /**
+ * Item caption mode.
+ */
+ private ItemCaptionMode itemCaptionMode = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * Item caption source property id.
+ */
+ private Object itemCaptionPropertyId = null;
+
+ /**
+ * Item icon source property id.
+ */
+ private Object itemIconPropertyId = null;
+
+ /**
+ * List of property set change event listeners.
+ */
+ private Set<Container.PropertySetChangeListener> propertySetEventListeners = null;
+
+ /**
+ * List of item set change event listeners.
+ */
+ private Set<Container.ItemSetChangeListener> itemSetEventListeners = null;
+
+ /**
+ * Item id that represents null selection of this select.
+ *
+ * <p>
+ * Data interface does not support nulls as item ids. Selecting the item
+ * identified by this id is the same as selecting no items at all. This
+ * setting only affects the single select mode.
+ * </p>
+ */
+ private Object nullSelectionItemId = null;
+
+ // Null (empty) selection is enabled by default
+ private boolean nullSelectionAllowed = true;
+ private NewItemHandler newItemHandler;
+
+ // Caption (Item / Property) change listeners
+ CaptionChangeListener captionChangeListener;
+
+ /* Constructors */
+
+ /**
+ * Creates an empty Select. The caption is not used.
+ */
+ public AbstractSelect() {
+ setContainerDataSource(new IndexedContainer());
+ }
+
+ /**
+ * Creates an empty Select with caption.
+ */
+ public AbstractSelect(String caption) {
+ setContainerDataSource(new IndexedContainer());
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new select that is connected to a data-source.
+ *
+ * @param caption
+ * the Caption of the component.
+ * @param dataSource
+ * the Container datasource to be selected from by this select.
+ */
+ public AbstractSelect(String caption, Container dataSource) {
+ setCaption(caption);
+ setContainerDataSource(dataSource);
+ }
+
+ /**
+ * Creates a new select that is filled from a collection of option values.
+ *
+ * @param caption
+ * the Caption of this field.
+ * @param options
+ * the Collection containing the options.
+ */
+ public AbstractSelect(String caption, Collection<?> options) {
+
+ // Creates the options container and add given options to it
+ final Container c = new IndexedContainer();
+ if (options != null) {
+ for (final Iterator<?> i = options.iterator(); i.hasNext();) {
+ c.addItem(i.next());
+ }
+ }
+
+ setCaption(caption);
+ setContainerDataSource(c);
+ }
+
+ /* Component methods */
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * the Paint Event.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Paints select attributes
+ if (isMultiSelect()) {
+ target.addAttribute("selectmode", "multi");
+ }
+ if (isNewItemsAllowed()) {
+ target.addAttribute("allownewitem", true);
+ }
+ if (isNullSelectionAllowed()) {
+ target.addAttribute("nullselect", true);
+ if (getNullSelectionItemId() != null) {
+ target.addAttribute("nullselectitem", true);
+ }
+ }
+
+ // Constructs selected keys array
+ String[] selectedKeys;
+ if (isMultiSelect()) {
+ selectedKeys = new String[((Set<?>) getValue()).size()];
+ } else {
+ selectedKeys = new String[(getValue() == null
+ && getNullSelectionItemId() == null ? 0 : 1)];
+ }
+
+ // ==
+ // first remove all previous item/property listeners
+ getCaptionChangeListener().clear();
+ // Paints the options and create array of selected id keys
+
+ target.startTag("options");
+ int keyIndex = 0;
+ // Support for external null selection item id
+ final Collection<?> ids = getItemIds();
+ if (isNullSelectionAllowed() && getNullSelectionItemId() != null
+ && !ids.contains(getNullSelectionItemId())) {
+ final Object id = getNullSelectionItemId();
+ // Paints option
+ target.startTag("so");
+ paintItem(target, id);
+ if (isSelected(id)) {
+ selectedKeys[keyIndex++] = itemIdMapper.key(id);
+ }
+ target.endTag("so");
+ }
+
+ final Iterator<?> i = getItemIds().iterator();
+ // Paints the available selection options from data source
+ while (i.hasNext()) {
+ // Gets the option attribute values
+ final Object id = i.next();
+ if (!isNullSelectionAllowed() && id != null
+ && id.equals(getNullSelectionItemId())) {
+ // Remove item if it's the null selection item but null
+ // selection is not allowed
+ continue;
+ }
+ final String key = itemIdMapper.key(id);
+ // add listener for each item, to cause repaint if an item changes
+ getCaptionChangeListener().addNotifierForItem(id);
+ target.startTag("so");
+ paintItem(target, id);
+ if (isSelected(id) && keyIndex < selectedKeys.length) {
+ selectedKeys[keyIndex++] = key;
+ }
+ target.endTag("so");
+ }
+ target.endTag("options");
+ // ==
+
+ // Paint variables
+ target.addVariable(this, "selected", selectedKeys);
+ if (isNewItemsAllowed()) {
+ target.addVariable(this, "newitem", "");
+ }
+
+ }
+
+ protected void paintItem(PaintTarget target, Object itemId)
+ throws PaintException {
+ final String key = itemIdMapper.key(itemId);
+ final String caption = getItemCaption(itemId);
+ final Resource icon = getItemIcon(itemId);
+ if (icon != null) {
+ target.addAttribute("icon", icon);
+ }
+ target.addAttribute("caption", caption);
+ if (itemId != null && itemId.equals(getNullSelectionItemId())) {
+ target.addAttribute("nullselection", true);
+ }
+ target.addAttribute("key", key);
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ }
+ }
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+
+ // New option entered (and it is allowed)
+ if (isNewItemsAllowed()) {
+ final String newitem = (String) variables.get("newitem");
+ if (newitem != null && newitem.length() > 0) {
+ getNewItemHandler().addNewItem(newitem);
+ }
+ }
+
+ // Selection change
+ if (variables.containsKey("selected")) {
+ final String[] clientSideSelectedKeys = (String[]) variables
+ .get("selected");
+
+ // Multiselect mode
+ if (isMultiSelect()) {
+
+ // TODO Optimize by adding repaintNotNeeded when applicable
+
+ // Converts the key-array to id-set
+ final LinkedList<Object> acceptedSelections = new LinkedList<Object>();
+ for (int i = 0; i < clientSideSelectedKeys.length; i++) {
+ final Object id = itemIdMapper
+ .get(clientSideSelectedKeys[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ requestRepaint();
+ } else if (id != null && containsId(id)) {
+ acceptedSelections.add(id);
+ }
+ }
+
+ if (!isNullSelectionAllowed() && acceptedSelections.size() < 1) {
+ // empty selection not allowed, keep old value
+ requestRepaint();
+ return;
+ }
+
+ // Limits the deselection to the set of visible items
+ // (non-visible items can not be deselected)
+ Collection<?> visibleNotSelected = getVisibleItemIds();
+ if (visibleNotSelected != null) {
+ visibleNotSelected = new HashSet<Object>(visibleNotSelected);
+ // Don't remove those that will be added to preserve order
+ visibleNotSelected.removeAll(acceptedSelections);
+
+ @SuppressWarnings("unchecked")
+ Set<Object> newsel = (Set<Object>) getValue();
+ if (newsel == null) {
+ newsel = new LinkedHashSet<Object>();
+ } else {
+ newsel = new LinkedHashSet<Object>(newsel);
+ }
+ newsel.removeAll(visibleNotSelected);
+ newsel.addAll(acceptedSelections);
+ setValue(newsel, true);
+ }
+ } else {
+ // Single select mode
+ if (!isNullSelectionAllowed()
+ && (clientSideSelectedKeys.length == 0
+ || clientSideSelectedKeys[0] == null || clientSideSelectedKeys[0] == getNullSelectionItemId())) {
+ requestRepaint();
+ return;
+ }
+ if (clientSideSelectedKeys.length == 0) {
+ // Allows deselection only if the deselected item is
+ // visible
+ final Object current = getValue();
+ final Collection<?> visible = getVisibleItemIds();
+ if (visible != null && visible.contains(current)) {
+ setValue(null, true);
+ }
+ } else {
+ final Object id = itemIdMapper
+ .get(clientSideSelectedKeys[0]);
+ if (!isNullSelectionAllowed() && id == null) {
+ requestRepaint();
+ } else if (id != null
+ && id.equals(getNullSelectionItemId())) {
+ setValue(null, true);
+ } else {
+ setValue(id, true);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * TODO refine doc Setter for new item handler that is called when user adds
+ * new item in newItemAllowed mode.
+ *
+ * @param newItemHandler
+ */
+ public void setNewItemHandler(NewItemHandler newItemHandler) {
+ this.newItemHandler = newItemHandler;
+ }
+
+ /**
+ * TODO refine doc
+ *
+ * @return
+ */
+ public NewItemHandler getNewItemHandler() {
+ if (newItemHandler == null) {
+ newItemHandler = new DefaultNewItemHandler();
+ }
+ return newItemHandler;
+ }
+
+ public interface NewItemHandler extends Serializable {
+ void addNewItem(String newItemCaption);
+ }
+
+ /**
+ * TODO refine doc
+ *
+ * This is a default class that handles adding new items that are typed by
+ * user to selects container.
+ *
+ * By extending this class one may implement some logic on new item addition
+ * like database inserts.
+ *
+ */
+ public class DefaultNewItemHandler implements NewItemHandler {
+ @Override
+ public void addNewItem(String newItemCaption) {
+ // Checks for readonly
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ // Adds new option
+ if (addItem(newItemCaption) != null) {
+
+ // Sets the caption property, if used
+ if (getItemCaptionPropertyId() != null) {
+ getContainerProperty(newItemCaption,
+ getItemCaptionPropertyId())
+ .setValue(newItemCaption);
+ }
+ if (isMultiSelect()) {
+ Set values = new HashSet((Collection) getValue());
+ values.add(newItemCaption);
+ setValue(values);
+ } else {
+ setValue(newItemCaption);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the visible item ids. In Select, this returns list of all item ids,
+ * but can be overriden in subclasses if they paint only part of the items
+ * to the terminal or null if no items is visible.
+ */
+ public Collection<?> getVisibleItemIds() {
+ return getItemIds();
+ }
+
+ /* Property methods */
+
+ /**
+ * Returns the type of the property. <code>getValue</code> and
+ * <code>setValue</code> methods must be compatible with this type: one can
+ * safely cast <code>getValue</code> to given type and pass any variable
+ * assignable to this type as a parameter to <code>setValue</code>.
+ *
+ * @return the Type of the property.
+ */
+ @Override
+ public Class<?> getType() {
+ if (isMultiSelect()) {
+ return Set.class;
+ } else {
+ return Object.class;
+ }
+ }
+
+ /**
+ * Gets the selected item id or in multiselect mode a set of selected ids.
+ *
+ * @see com.vaadin.ui.AbstractField#getValue()
+ */
+ @Override
+ public Object getValue() {
+ final Object retValue = super.getValue();
+
+ if (isMultiSelect()) {
+
+ // If the return value is not a set
+ if (retValue == null) {
+ return new HashSet<Object>();
+ }
+ if (retValue instanceof Set) {
+ return Collections.unmodifiableSet((Set<?>) retValue);
+ } else if (retValue instanceof Collection) {
+ return new HashSet<Object>((Collection<?>) retValue);
+ } else {
+ final Set<Object> s = new HashSet<Object>();
+ if (items.containsId(retValue)) {
+ s.add(retValue);
+ }
+ return s;
+ }
+
+ } else {
+ return retValue;
+ }
+ }
+
+ /**
+ * Sets the visible value of the property.
+ *
+ * <p>
+ * The value of the select is the selected item id. If the select is in
+ * multiselect-mode, the value is a set of selected item keys. In
+ * multiselect mode all collections of id:s can be assigned.
+ * </p>
+ *
+ * @param newValue
+ * the New selected item or collection of selected items.
+ * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object)
+ */
+ @Override
+ public void setValue(Object newValue) throws Property.ReadOnlyException {
+ if (newValue == getNullSelectionItemId()) {
+ newValue = null;
+ }
+
+ setValue(newValue, false);
+ }
+
+ /**
+ * Sets the visible value of the property.
+ *
+ * <p>
+ * The value of the select is the selected item id. If the select is in
+ * multiselect-mode, the value is a set of selected item keys. In
+ * multiselect mode all collections of id:s can be assigned.
+ * </p>
+ *
+ * @param newValue
+ * the New selected item or collection of selected items.
+ * @param repaintIsNotNeeded
+ * True if caller is sure that repaint is not needed.
+ * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object,
+ * java.lang.Boolean)
+ */
+ @Override
+ protected void setValue(Object newValue, boolean repaintIsNotNeeded)
+ throws Property.ReadOnlyException {
+
+ if (isMultiSelect()) {
+ if (newValue == null) {
+ super.setValue(new LinkedHashSet<Object>(), repaintIsNotNeeded);
+ } else if (Collection.class.isAssignableFrom(newValue.getClass())) {
+ super.setValue(new LinkedHashSet<Object>(
+ (Collection<?>) newValue), repaintIsNotNeeded);
+ }
+ } else if (newValue == null || items.containsId(newValue)) {
+ super.setValue(newValue, repaintIsNotNeeded);
+ }
+ }
+
+ /* Container methods */
+
+ /**
+ * Gets the item from the container with given id. If the container does not
+ * contain the requested item, null is returned.
+ *
+ * @param itemId
+ * the item id.
+ * @return the item from the container.
+ */
+ @Override
+ public Item getItem(Object itemId) {
+ return items.getItem(itemId);
+ }
+
+ /**
+ * Gets the item Id collection from the container.
+ *
+ * @return the Collection of item ids.
+ */
+ @Override
+ public Collection<?> getItemIds() {
+ return items.getItemIds();
+ }
+
+ /**
+ * Gets the property Id collection from the container.
+ *
+ * @return the Collection of property ids.
+ */
+ @Override
+ public Collection<?> getContainerPropertyIds() {
+ return items.getContainerPropertyIds();
+ }
+
+ /**
+ * Gets the property type.
+ *
+ * @param propertyId
+ * the Id identifying the property.
+ * @see com.vaadin.data.Container#getType(java.lang.Object)
+ */
+ @Override
+ public Class<?> getType(Object propertyId) {
+ return items.getType(propertyId);
+ }
+
+ /*
+ * Gets the number of items in the container.
+ *
+ * @return the Number of items in the container.
+ *
+ * @see com.vaadin.data.Container#size()
+ */
+ @Override
+ public int size() {
+ return items.size();
+ }
+
+ /**
+ * Tests, if the collection contains an item with given id.
+ *
+ * @param itemId
+ * the Id the of item to be tested.
+ */
+ @Override
+ public boolean containsId(Object itemId) {
+ if (itemId != null) {
+ return items.containsId(itemId);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the Property identified by the given itemId and propertyId from the
+ * Container
+ *
+ * @see com.vaadin.data.Container#getContainerProperty(Object, Object)
+ */
+ @Override
+ public Property<?> getContainerProperty(Object itemId, Object propertyId) {
+ return items.getContainerProperty(itemId, propertyId);
+ }
+
+ /**
+ * Adds the new property to all items. Adds a property with given id, type
+ * and default value to all items in the container.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns false.
+ *
+ * @return True if the operation succeeded.
+ * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object,
+ * java.lang.Class, java.lang.Object)
+ */
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+
+ final boolean retval = items.addContainerProperty(propertyId, type,
+ defaultValue);
+ if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
+ firePropertySetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Removes all items from the container.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns false.
+ *
+ * @return True if the operation succeeded.
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+
+ final boolean retval = items.removeAllItems();
+ itemIdMapper.removeAll();
+ if (retval) {
+ setValue(null);
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Creates a new item into container with container managed id. The id of
+ * the created new item is returned. The item can be fetched with getItem()
+ * method. if the creation fails, null is returned.
+ *
+ * @return the Id of the created item or null in case of failure.
+ * @see com.vaadin.data.Container#addItem()
+ */
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+
+ final Object retval = items.addItem();
+ if (retval != null
+ && !(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Create a new item into container. The created new item is returned and
+ * ready for setting property values. if the creation fails, null is
+ * returned. In case the container already contains the item, null is
+ * returned.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns null.
+ *
+ * @param itemId
+ * the Identification of the item to be created.
+ * @return the Created item with the given id, or null in case of failure.
+ * @see com.vaadin.data.Container#addItem(java.lang.Object)
+ */
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+
+ final Item retval = items.addItem(itemId);
+ if (retval != null
+ && !(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+
+ unselect(itemId);
+ final boolean retval = items.removeItem(itemId);
+ itemIdMapper.remove(itemId);
+ if (retval && !(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Removes the property from all items. Removes a property with given id
+ * from all the items in the container.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns false.
+ *
+ * @return True if the operation succeeded.
+ * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object)
+ */
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+
+ final boolean retval = items.removeContainerProperty(propertyId);
+ if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
+ firePropertySetChange();
+ }
+ return retval;
+ }
+
+ /* Container.Viewer methods */
+
+ /**
+ * Sets the Container that serves as the data source of the viewer.
+ *
+ * As a side-effect the fields value (selection) is set to null due old
+ * selection not necessary exists in new Container.
+ *
+ * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
+ *
+ * @param newDataSource
+ * the new data source.
+ */
+ @Override
+ public void setContainerDataSource(Container newDataSource) {
+ if (newDataSource == null) {
+ newDataSource = new IndexedContainer();
+ }
+
+ getCaptionChangeListener().clear();
+
+ if (items != newDataSource) {
+
+ // Removes listeners from the old datasource
+ if (items != null) {
+ if (items instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) items)
+ .removeListener(this);
+ }
+ if (items instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) items)
+ .removeListener(this);
+ }
+ }
+
+ // Assigns new data source
+ items = newDataSource;
+
+ // Clears itemIdMapper also
+ itemIdMapper.removeAll();
+
+ // Adds listeners
+ if (items != null) {
+ if (items instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) items).addListener(this);
+ }
+ if (items instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) items)
+ .addListener(this);
+ }
+ }
+
+ /*
+ * We expect changing the data source should also clean value. See
+ * #810, #4607, #5281
+ */
+ setValue(null);
+
+ requestRepaint();
+
+ }
+ }
+
+ /**
+ * Gets the viewing data-source container.
+ *
+ * @see com.vaadin.data.Container.Viewer#getContainerDataSource()
+ */
+ @Override
+ public Container getContainerDataSource() {
+ return items;
+ }
+
+ /* Select attributes */
+
+ /**
+ * Is the select in multiselect mode? In multiselect mode
+ *
+ * @return the Value of property multiSelect.
+ */
+ public boolean isMultiSelect() {
+ return multiSelect;
+ }
+
+ /**
+ * Sets the multiselect mode. Setting multiselect mode false may lose
+ * selection information: if selected items set contains one or more
+ * selected items, only one of the selected items is kept as selected.
+ *
+ * Subclasses of AbstractSelect can choose not to support changing the
+ * multiselect mode, and may throw {@link UnsupportedOperationException}.
+ *
+ * @param multiSelect
+ * the New value of property multiSelect.
+ */
+ public void setMultiSelect(boolean multiSelect) {
+ if (multiSelect && getNullSelectionItemId() != null) {
+ throw new IllegalStateException(
+ "Multiselect and NullSelectionItemId can not be set at the same time.");
+ }
+ if (multiSelect != this.multiSelect) {
+
+ // Selection before mode change
+ final Object oldValue = getValue();
+
+ this.multiSelect = multiSelect;
+
+ // Convert the value type
+ if (multiSelect) {
+ final Set<Object> s = new HashSet<Object>();
+ if (oldValue != null) {
+ s.add(oldValue);
+ }
+ setValue(s);
+ } else {
+ final Set<?> s = (Set<?>) oldValue;
+ if (s == null || s.isEmpty()) {
+ setValue(null);
+ } else {
+ // Set the single select to contain only the first
+ // selected value in the multiselect
+ setValue(s.iterator().next());
+ }
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Does the select allow adding new options by the user. If true, the new
+ * options can be added to the Container. The text entered by the user is
+ * used as id. Note that data-source must allow adding new items.
+ *
+ * @return True if additions are allowed.
+ */
+ public boolean isNewItemsAllowed() {
+
+ return allowNewOptions;
+ }
+
+ /**
+ * Enables or disables possibility to add new options by the user.
+ *
+ * @param allowNewOptions
+ * the New value of property allowNewOptions.
+ */
+ public void setNewItemsAllowed(boolean allowNewOptions) {
+
+ // Only handle change requests
+ if (this.allowNewOptions != allowNewOptions) {
+
+ this.allowNewOptions = allowNewOptions;
+
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Override the caption of an item. Setting caption explicitly overrides id,
+ * item and index captions.
+ *
+ * @param itemId
+ * the id of the item to be recaptioned.
+ * @param caption
+ * the New caption.
+ */
+ public void setItemCaption(Object itemId, String caption) {
+ if (itemId != null) {
+ itemCaptions.put(itemId, caption);
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the caption of an item. The caption is generated as specified by the
+ * item caption mode. See <code>setItemCaptionMode()</code> for more
+ * details.
+ *
+ * @param itemId
+ * the id of the item to be queried.
+ * @return the caption for specified item.
+ */
+ public String getItemCaption(Object itemId) {
+
+ // Null items can not be found
+ if (itemId == null) {
+ return null;
+ }
+
+ String caption = null;
+
+ switch (getItemCaptionMode()) {
+
+ case ID:
+ caption = itemId.toString();
+ break;
+
+ case INDEX:
+ if (items instanceof Container.Indexed) {
+ caption = String.valueOf(((Container.Indexed) items)
+ .indexOfId(itemId));
+ } else {
+ caption = "ERROR: Container is not indexed";
+ }
+ break;
+
+ case ITEM:
+ final Item i = getItem(itemId);
+ if (i != null) {
+ caption = i.toString();
+ }
+ break;
+
+ case EXPLICIT:
+ caption = itemCaptions.get(itemId);
+ break;
+
+ case EXPLICIT_DEFAULTS_ID:
+ caption = itemCaptions.get(itemId);
+ if (caption == null) {
+ caption = itemId.toString();
+ }
+ break;
+
+ case PROPERTY:
+ final Property<?> p = getContainerProperty(itemId,
+ getItemCaptionPropertyId());
+ if (p != null) {
+ Object value = p.getValue();
+ if (value != null) {
+ caption = value.toString();
+ }
+ }
+ break;
+ }
+
+ // All items must have some captions
+ return caption != null ? caption : "";
+ }
+
+ /**
+ * Sets tqhe icon for an item.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @param icon
+ * the icon to use or null.
+ */
+ public void setItemIcon(Object itemId, Resource icon) {
+ if (itemId != null) {
+ if (icon == null) {
+ itemIcons.remove(itemId);
+ } else {
+ itemIcons.put(itemId, icon);
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the item icon.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @return the icon for the item or null, if not specified.
+ */
+ public Resource getItemIcon(Object itemId) {
+ final Resource explicit = itemIcons.get(itemId);
+ if (explicit != null) {
+ return explicit;
+ }
+
+ if (getItemIconPropertyId() == null) {
+ return null;
+ }
+
+ final Property<?> ip = getContainerProperty(itemId,
+ getItemIconPropertyId());
+ if (ip == null) {
+ return null;
+ }
+ final Object icon = ip.getValue();
+ if (icon instanceof Resource) {
+ return (Resource) icon;
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the item caption mode.
+ *
+ * <p>
+ * The mode can be one of the following ones:
+ * <ul>
+ * <li><code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> : Items
+ * Id-objects <code>toString</code> is used as item caption. If caption is
+ * explicitly specified, it overrides the id-caption.
+ * <li><code>ITEM_CAPTION_MODE_ID</code> : Items Id-objects
+ * <code>toString</code> is used as item caption.</li>
+ * <li><code>ITEM_CAPTION_MODE_ITEM</code> : Item-objects
+ * <code>toString</code> is used as item caption.</li>
+ * <li><code>ITEM_CAPTION_MODE_INDEX</code> : The index of the item is used
+ * as item caption. The index mode can only be used with the containers
+ * implementing <code>Container.Indexed</code> interface.</li>
+ * <li><code>ITEM_CAPTION_MODE_EXPLICIT</code> : The item captions must be
+ * explicitly specified.</li>
+ * <li><code>ITEM_CAPTION_MODE_PROPERTY</code> : The item captions are read
+ * from property, that must be specified with
+ * <code>setItemCaptionPropertyId</code>.</li>
+ * </ul>
+ * The <code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> is the default
+ * mode.
+ * </p>
+ *
+ * @param mode
+ * the One of the modes listed above.
+ */
+ public void setItemCaptionMode(ItemCaptionMode mode) {
+ if (mode != null) {
+ itemCaptionMode = mode;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the item caption mode.
+ *
+ * <p>
+ * The mode can be one of the following ones:
+ * <ul>
+ * <li><code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> : Items
+ * Id-objects <code>toString</code> is used as item caption. If caption is
+ * explicitly specified, it overrides the id-caption.
+ * <li><code>ITEM_CAPTION_MODE_ID</code> : Items Id-objects
+ * <code>toString</code> is used as item caption.</li>
+ * <li><code>ITEM_CAPTION_MODE_ITEM</code> : Item-objects
+ * <code>toString</code> is used as item caption.</li>
+ * <li><code>ITEM_CAPTION_MODE_INDEX</code> : The index of the item is used
+ * as item caption. The index mode can only be used with the containers
+ * implementing <code>Container.Indexed</code> interface.</li>
+ * <li><code>ITEM_CAPTION_MODE_EXPLICIT</code> : The item captions must be
+ * explicitly specified.</li>
+ * <li><code>ITEM_CAPTION_MODE_PROPERTY</code> : The item captions are read
+ * from property, that must be specified with
+ * <code>setItemCaptionPropertyId</code>.</li>
+ * </ul>
+ * The <code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> is the default
+ * mode.
+ * </p>
+ *
+ * @return the One of the modes listed above.
+ */
+ public ItemCaptionMode getItemCaptionMode() {
+ return itemCaptionMode;
+ }
+
+ /**
+ * Sets the item caption property.
+ *
+ * <p>
+ * Setting the id to a existing property implicitly sets the item caption
+ * mode to <code>ITEM_CAPTION_MODE_PROPERTY</code>. If the object is in
+ * <code>ITEM_CAPTION_MODE_PROPERTY</code> mode, setting caption property id
+ * null resets the item caption mode to
+ * <code>ITEM_CAPTION_EXPLICIT_DEFAULTS_ID</code>.
+ * </p>
+ * <p>
+ * Note that the type of the property used for caption must be String
+ * </p>
+ * <p>
+ * Setting the property id to null disables this feature. The id is null by
+ * default
+ * </p>
+ * .
+ *
+ * @param propertyId
+ * the id of the property.
+ *
+ */
+ public void setItemCaptionPropertyId(Object propertyId) {
+ if (propertyId != null) {
+ itemCaptionPropertyId = propertyId;
+ setItemCaptionMode(ITEM_CAPTION_MODE_PROPERTY);
+ requestRepaint();
+ } else {
+ itemCaptionPropertyId = null;
+ if (getItemCaptionMode() == ITEM_CAPTION_MODE_PROPERTY) {
+ setItemCaptionMode(ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID);
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the item caption property.
+ *
+ * @return the Id of the property used as item caption source.
+ */
+ public Object getItemCaptionPropertyId() {
+ return itemCaptionPropertyId;
+ }
+
+ /**
+ * Sets the item icon property.
+ *
+ * <p>
+ * If the property id is set to a valid value, each item is given an icon
+ * got from the given property of the items. The type of the property must
+ * be assignable to Resource.
+ * </p>
+ *
+ * <p>
+ * Note : The icons set with <code>setItemIcon</code> function override the
+ * icons from the property.
+ * </p>
+ *
+ * <p>
+ * Setting the property id to null disables this feature. The id is null by
+ * default
+ * </p>
+ * .
+ *
+ * @param propertyId
+ * the id of the property that specifies icons for items or null
+ * @throws IllegalArgumentException
+ * If the propertyId is not in the container or is not of a
+ * valid type
+ */
+ public void setItemIconPropertyId(Object propertyId)
+ throws IllegalArgumentException {
+ if (propertyId == null) {
+ itemIconPropertyId = null;
+ } else if (!getContainerPropertyIds().contains(propertyId)) {
+ throw new IllegalArgumentException(
+ "Property id not found in the container");
+ } else if (Resource.class.isAssignableFrom(getType(propertyId))) {
+ itemIconPropertyId = propertyId;
+ } else {
+ throw new IllegalArgumentException(
+ "Property type must be assignable to Resource");
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Gets the item icon property.
+ *
+ * <p>
+ * If the property id is set to a valid value, each item is given an icon
+ * got from the given property of the items. The type of the property must
+ * be assignable to Icon.
+ * </p>
+ *
+ * <p>
+ * Note : The icons set with <code>setItemIcon</code> function override the
+ * icons from the property.
+ * </p>
+ *
+ * <p>
+ * Setting the property id to null disables this feature. The id is null by
+ * default
+ * </p>
+ * .
+ *
+ * @return the Id of the property containing the item icons.
+ */
+ public Object getItemIconPropertyId() {
+ return itemIconPropertyId;
+ }
+
+ /**
+ * Tests if an item is selected.
+ *
+ * <p>
+ * In single select mode testing selection status of the item identified by
+ * {@link #getNullSelectionItemId()} returns true if the value of the
+ * property is null.
+ * </p>
+ *
+ * @param itemId
+ * the Id the of the item to be tested.
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ *
+ */
+ public boolean isSelected(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+ if (isMultiSelect()) {
+ return ((Set<?>) getValue()).contains(itemId);
+ } else {
+ final Object value = getValue();
+ return itemId.equals(value == null ? getNullSelectionItemId()
+ : value);
+ }
+ }
+
+ /**
+ * Selects an item.
+ *
+ * <p>
+ * In single select mode selecting item identified by
+ * {@link #getNullSelectionItemId()} sets the value of the property to null.
+ * </p>
+ *
+ * @param itemId
+ * the identifier of Item to be selected.
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ *
+ */
+ public void select(Object itemId) {
+ if (!isMultiSelect()) {
+ setValue(itemId);
+ } else if (!isSelected(itemId) && itemId != null
+ && items.containsId(itemId)) {
+ final Set<Object> s = new HashSet<Object>((Set<?>) getValue());
+ s.add(itemId);
+ setValue(s);
+ }
+ }
+
+ /**
+ * Unselects an item.
+ *
+ * @param itemId
+ * the identifier of the Item to be unselected.
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ *
+ */
+ public void unselect(Object itemId) {
+ if (isSelected(itemId)) {
+ if (isMultiSelect()) {
+ final Set<Object> s = new HashSet<Object>((Set<?>) getValue());
+ s.remove(itemId);
+ setValue(s);
+ } else {
+ setValue(null);
+ }
+ }
+ }
+
+ /**
+ * Notifies this listener that the Containers contents has changed.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeListener#containerPropertySetChange(com.vaadin.data.Container.PropertySetChangeEvent)
+ */
+ @Override
+ public void containerPropertySetChange(
+ Container.PropertySetChangeEvent event) {
+ firePropertySetChange();
+ }
+
+ /**
+ * Adds a new Property set change listener for this Container.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeNotifier#addListener(com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ @Override
+ public void addListener(Container.PropertySetChangeListener listener) {
+ if (propertySetEventListeners == null) {
+ propertySetEventListeners = new LinkedHashSet<Container.PropertySetChangeListener>();
+ }
+ propertySetEventListeners.add(listener);
+ }
+
+ /**
+ * Removes a previously registered Property set change listener.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeNotifier#removeListener(com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ @Override
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ if (propertySetEventListeners != null) {
+ propertySetEventListeners.remove(listener);
+ if (propertySetEventListeners.isEmpty()) {
+ propertySetEventListeners = null;
+ }
+ }
+ }
+
+ /**
+ * Adds an Item set change listener for the object.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ @Override
+ public void addListener(Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners == null) {
+ itemSetEventListeners = new LinkedHashSet<Container.ItemSetChangeListener>();
+ }
+ itemSetEventListeners.add(listener);
+ }
+
+ /**
+ * Removes the Item set change listener from the object.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ @Override
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners != null) {
+ itemSetEventListeners.remove(listener);
+ if (itemSetEventListeners.isEmpty()) {
+ itemSetEventListeners = null;
+ }
+ }
+ }
+
+ @Override
+ public Collection<?> getListeners(Class<?> eventType) {
+ if (Container.ItemSetChangeEvent.class.isAssignableFrom(eventType)) {
+ if (itemSetEventListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(itemSetEventListeners);
+ }
+ } else if (Container.PropertySetChangeEvent.class
+ .isAssignableFrom(eventType)) {
+ if (propertySetEventListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(propertySetEventListeners);
+ }
+ }
+
+ return super.getListeners(eventType);
+ }
+
+ /**
+ * Lets the listener know a Containers Item set has changed.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange(com.vaadin.data.Container.ItemSetChangeEvent)
+ */
+ @Override
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ // Clears the item id mapping table
+ itemIdMapper.removeAll();
+
+ // Notify all listeners
+ fireItemSetChange();
+ }
+
+ /**
+ * Fires the property set change event.
+ */
+ protected void firePropertySetChange() {
+ if (propertySetEventListeners != null
+ && !propertySetEventListeners.isEmpty()) {
+ final Container.PropertySetChangeEvent event = new PropertySetChangeEvent();
+ final Object[] listeners = propertySetEventListeners.toArray();
+ for (int i = 0; i < listeners.length; i++) {
+ ((Container.PropertySetChangeListener) listeners[i])
+ .containerPropertySetChange(event);
+ }
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Fires the item set change event.
+ */
+ protected void fireItemSetChange() {
+ if (itemSetEventListeners != null && !itemSetEventListeners.isEmpty()) {
+ final Container.ItemSetChangeEvent event = new ItemSetChangeEvent();
+ final Object[] listeners = itemSetEventListeners.toArray();
+ for (int i = 0; i < listeners.length; i++) {
+ ((Container.ItemSetChangeListener) listeners[i])
+ .containerItemSetChange(event);
+ }
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Implementation of item set change event.
+ */
+ private class ItemSetChangeEvent implements Serializable,
+ Container.ItemSetChangeEvent {
+
+ /**
+ * Gets the Property where the event occurred.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeEvent#getContainer()
+ */
+ @Override
+ public Container getContainer() {
+ return AbstractSelect.this;
+ }
+
+ }
+
+ /**
+ * Implementation of property set change event.
+ */
+ private class PropertySetChangeEvent implements
+ Container.PropertySetChangeEvent, Serializable {
+
+ /**
+ * Retrieves the Container whose contents have been modified.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeEvent#getContainer()
+ */
+ @Override
+ public Container getContainer() {
+ return AbstractSelect.this;
+ }
+
+ }
+
+ /**
+ * For multi-selectable fields, also an empty collection of values is
+ * considered to be an empty field.
+ *
+ * @see AbstractField#isEmpty().
+ */
+ @Override
+ protected boolean isEmpty() {
+ if (!multiSelect) {
+ return super.isEmpty();
+ } else {
+ Object value = getValue();
+ return super.isEmpty()
+ || (value instanceof Collection && ((Collection<?>) value)
+ .isEmpty());
+ }
+ }
+
+ /**
+ * Allow or disallow empty selection by the user. If the select is in
+ * single-select mode, you can make an item represent the empty selection by
+ * calling <code>setNullSelectionItemId()</code>. This way you can for
+ * instance set an icon and caption for the null selection item.
+ *
+ * @param nullSelectionAllowed
+ * whether or not to allow empty selection
+ * @see #setNullSelectionItemId(Object)
+ * @see #isNullSelectionAllowed()
+ */
+ public void setNullSelectionAllowed(boolean nullSelectionAllowed) {
+ if (nullSelectionAllowed != this.nullSelectionAllowed) {
+ this.nullSelectionAllowed = nullSelectionAllowed;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Checks if null empty selection is allowed by the user.
+ *
+ * @return whether or not empty selection is allowed
+ * @see #setNullSelectionAllowed(boolean)
+ */
+ public boolean isNullSelectionAllowed() {
+ return nullSelectionAllowed;
+ }
+
+ /**
+ * Returns the item id that represents null value of this select in single
+ * select mode.
+ *
+ * <p>
+ * Data interface does not support nulls as item ids. Selecting the item
+ * identified by this id is the same as selecting no items at all. This
+ * setting only affects the single select mode.
+ * </p>
+ *
+ * @return the Object Null value item id.
+ * @see #setNullSelectionItemId(Object)
+ * @see #isSelected(Object)
+ * @see #select(Object)
+ */
+ public Object getNullSelectionItemId() {
+ return nullSelectionItemId;
+ }
+
+ /**
+ * Sets the item id that represents null value of this select.
+ *
+ * <p>
+ * Data interface does not support nulls as item ids. Selecting the item
+ * identified by this id is the same as selecting no items at all. This
+ * setting only affects the single select mode.
+ * </p>
+ *
+ * @param nullSelectionItemId
+ * the nullSelectionItemId to set.
+ * @see #getNullSelectionItemId()
+ * @see #isSelected(Object)
+ * @see #select(Object)
+ */
+ public void setNullSelectionItemId(Object nullSelectionItemId) {
+ if (nullSelectionItemId != null && isMultiSelect()) {
+ throw new IllegalStateException(
+ "Multiselect and NullSelectionItemId can not be set at the same time.");
+ }
+ this.nullSelectionItemId = nullSelectionItemId;
+ }
+
+ /**
+ * Notifies the component that it is connected to an application.
+ *
+ * @see com.vaadin.ui.AbstractField#attach()
+ */
+ @Override
+ public void attach() {
+ super.attach();
+ }
+
+ /**
+ * Detaches the component from application.
+ *
+ * @see com.vaadin.ui.AbstractComponent#detach()
+ */
+ @Override
+ public void detach() {
+ getCaptionChangeListener().clear();
+ super.detach();
+ }
+
+ // Caption change listener
+ protected CaptionChangeListener getCaptionChangeListener() {
+ if (captionChangeListener == null) {
+ captionChangeListener = new CaptionChangeListener();
+ }
+ return captionChangeListener;
+ }
+
+ /**
+ * This is a listener helper for Item and Property changes that should cause
+ * a repaint. It should be attached to all items that are displayed, and the
+ * default implementation does this in paintContent(). Especially
+ * "lazyloading" components should take care to add and remove listeners as
+ * appropriate. Call addNotifierForItem() for each painted item (and
+ * remember to clear).
+ *
+ * NOTE: singleton, use getCaptionChangeListener().
+ *
+ */
+ protected class CaptionChangeListener implements
+ Item.PropertySetChangeListener, Property.ValueChangeListener {
+
+ // TODO clean this up - type is either Item.PropertySetChangeNotifier or
+ // Property.ValueChangeNotifier
+ HashSet<Object> captionChangeNotifiers = new HashSet<Object>();
+
+ public void addNotifierForItem(Object itemId) {
+ switch (getItemCaptionMode()) {
+ case ITEM:
+ final Item i = getItem(itemId);
+ if (i == null) {
+ return;
+ }
+ if (i instanceof Item.PropertySetChangeNotifier) {
+ ((Item.PropertySetChangeNotifier) i)
+ .addListener(getCaptionChangeListener());
+ captionChangeNotifiers.add(i);
+ }
+ Collection<?> pids = i.getItemPropertyIds();
+ if (pids != null) {
+ for (Iterator<?> it = pids.iterator(); it.hasNext();) {
+ Property<?> p = i.getItemProperty(it.next());
+ if (p != null
+ && p instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) p)
+ .addListener(getCaptionChangeListener());
+ captionChangeNotifiers.add(p);
+ }
+ }
+
+ }
+ break;
+ case PROPERTY:
+ final Property<?> p = getContainerProperty(itemId,
+ getItemCaptionPropertyId());
+ if (p != null && p instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) p)
+ .addListener(getCaptionChangeListener());
+ captionChangeNotifiers.add(p);
+ }
+ break;
+
+ }
+ }
+
+ public void clear() {
+ for (Iterator<Object> it = captionChangeNotifiers.iterator(); it
+ .hasNext();) {
+ Object notifier = it.next();
+ if (notifier instanceof Item.PropertySetChangeNotifier) {
+ ((Item.PropertySetChangeNotifier) notifier)
+ .removeListener(getCaptionChangeListener());
+ } else {
+ ((Property.ValueChangeNotifier) notifier)
+ .removeListener(getCaptionChangeListener());
+ }
+ }
+ captionChangeNotifiers.clear();
+ }
+
+ @Override
+ public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
+ requestRepaint();
+ }
+
+ @Override
+ public void itemPropertySetChange(
+ com.vaadin.data.Item.PropertySetChangeEvent event) {
+ requestRepaint();
+ }
+
+ }
+
+ /**
+ * Criterion which accepts a drop only if the drop target is (one of) the
+ * given Item identifier(s). Criterion can be used only on a drop targets
+ * that extends AbstractSelect like {@link Table} and {@link Tree}. The
+ * target and identifiers of valid Items are given in constructor.
+ *
+ * @since 6.3
+ */
+ public static class TargetItemIs extends AbstractItemSetCriterion {
+
+ /**
+ * @param select
+ * the select implementation that is used as a drop target
+ * @param itemId
+ * the identifier(s) that are valid drop locations
+ */
+ public TargetItemIs(AbstractSelect select, Object... itemId) {
+ super(select, itemId);
+ }
+
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
+ .getTargetDetails();
+ if (dropTargetData.getTarget() != select) {
+ return false;
+ }
+ return itemIds.contains(dropTargetData.getItemIdOver());
+ }
+
+ }
+
+ /**
+ * Abstract helper class to implement item id based criterion.
+ *
+ * Note, inner class used not to open itemIdMapper for public access.
+ *
+ * @since 6.3
+ *
+ */
+ private static abstract class AbstractItemSetCriterion extends
+ ClientSideCriterion {
+ protected final Collection<Object> itemIds = new HashSet<Object>();
+ protected AbstractSelect select;
+
+ public AbstractItemSetCriterion(AbstractSelect select, Object... itemId) {
+ if (itemIds == null || select == null) {
+ throw new IllegalArgumentException(
+ "Accepted item identifiers must be accepted.");
+ }
+ Collections.addAll(itemIds, itemId);
+ this.select = select;
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ String[] keys = new String[itemIds.size()];
+ int i = 0;
+ for (Object itemId : itemIds) {
+ String key = select.itemIdMapper.key(itemId);
+ keys[i++] = key;
+ }
+ target.addAttribute("keys", keys);
+ target.addAttribute("s", select);
+ }
+
+ }
+
+ /**
+ * This criterion accepts a only a {@link Transferable} that contains given
+ * Item (practically its identifier) from a specific AbstractSelect.
+ *
+ * @since 6.3
+ */
+ public static class AcceptItem extends AbstractItemSetCriterion {
+
+ /**
+ * @param select
+ * the select from which the item id's are checked
+ * @param itemId
+ * the item identifier(s) of the select that are accepted
+ */
+ public AcceptItem(AbstractSelect select, Object... itemId) {
+ super(select, itemId);
+ }
+
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ DataBoundTransferable transferable = (DataBoundTransferable) dragEvent
+ .getTransferable();
+ if (transferable.getSourceComponent() != select) {
+ return false;
+ }
+ return itemIds.contains(transferable.getItemId());
+ }
+
+ /**
+ * A simple accept criterion which ensures that {@link Transferable}
+ * contains an {@link Item} (or actually its identifier). In other words
+ * the criterion check that drag is coming from a {@link Container} like
+ * {@link Tree} or {@link Table}.
+ */
+ public static final ClientSideCriterion ALL = new ContainsDataFlavor(
+ "itemId");
+
+ }
+
+ /**
+ * TargetDetails implementation for subclasses of {@link AbstractSelect}
+ * that implement {@link DropTarget}.
+ *
+ * @since 6.3
+ */
+ public class AbstractSelectTargetDetails extends TargetDetailsImpl {
+
+ /**
+ * The item id over which the drag event happened.
+ */
+ protected Object idOver;
+
+ /**
+ * Constructor that automatically converts itemIdOver key to
+ * corresponding item Id
+ *
+ */
+ protected AbstractSelectTargetDetails(Map<String, Object> rawVariables) {
+ super(rawVariables, (DropTarget) AbstractSelect.this);
+ // eagar fetch itemid, mapper may be emptied
+ String keyover = (String) getData("itemIdOver");
+ if (keyover != null) {
+ idOver = itemIdMapper.get(keyover);
+ }
+ }
+
+ /**
+ * If the drag operation is currently over an {@link Item}, this method
+ * returns the identifier of that {@link Item}.
+ *
+ */
+ public Object getItemIdOver() {
+ return idOver;
+ }
+
+ /**
+ * Returns a detailed vertical location where the drop happened on Item.
+ */
+ public VerticalDropLocation getDropLocation() {
+ String detail = (String) getData("detail");
+ if (detail == null) {
+ return null;
+ }
+ return VerticalDropLocation.valueOf(detail);
+ }
+
+ }
+
+ /**
+ * An accept criterion to accept drops only on a specific vertical location
+ * of an item.
+ * <p>
+ * This accept criterion is currently usable in Tree and Table
+ * implementations.
+ */
+ public static class VerticalLocationIs extends TargetDetailIs {
+ public static VerticalLocationIs TOP = new VerticalLocationIs(
+ VerticalDropLocation.TOP);
+ public static VerticalLocationIs BOTTOM = new VerticalLocationIs(
+ VerticalDropLocation.BOTTOM);
+ public static VerticalLocationIs MIDDLE = new VerticalLocationIs(
+ VerticalDropLocation.MIDDLE);
+
+ private VerticalLocationIs(VerticalDropLocation l) {
+ super("detail", l.name());
+ }
+ }
+
+ /**
+ * Implement this interface and pass it to Tree.setItemDescriptionGenerator
+ * or Table.setItemDescriptionGenerator to generate mouse over descriptions
+ * ("tooltips") for the rows and cells in Table or for the items in Tree.
+ */
+ public interface ItemDescriptionGenerator extends Serializable {
+
+ /**
+ * Called by Table when a cell (and row) is painted or a item is painted
+ * in Tree
+ *
+ * @param source
+ * The source of the generator, the Tree or Table the
+ * generator is attached to
+ * @param itemId
+ * The itemId of the painted cell
+ * @param propertyId
+ * The propertyId of the cell, null when getting row
+ * description
+ * @return The description or "tooltip" of the item.
+ */
+ public String generateDescription(Component source, Object itemId,
+ Object propertyId);
+ }
+}
diff --git a/server/src/com/vaadin/ui/AbstractSplitPanel.java b/server/src/com/vaadin/ui/AbstractSplitPanel.java
new file mode 100644
index 0000000000..90dc38ff65
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractSplitPanel.java
@@ -0,0 +1,521 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+import com.vaadin.event.ComponentEventListener;
+import com.vaadin.event.MouseEvents.ClickEvent;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelRpc;
+import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState;
+import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState.SplitterState;
+import com.vaadin.terminal.Sizeable;
+import com.vaadin.terminal.gwt.client.ui.ClickEventHandler;
+import com.vaadin.tools.ReflectTools;
+
+/**
+ * AbstractSplitPanel.
+ *
+ * <code>AbstractSplitPanel</code> is base class for a component container that
+ * can contain two components. The components are split by a divider element.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 6.5
+ */
+public abstract class AbstractSplitPanel extends AbstractComponentContainer {
+
+ // TODO use Unit in AbstractSplitPanelState and remove these
+ private Unit posUnit;
+ private Unit posMinUnit;
+ private Unit posMaxUnit;
+
+ private AbstractSplitPanelRpc rpc = new AbstractSplitPanelRpc() {
+
+ @Override
+ public void splitterClick(MouseEventDetails mouseDetails) {
+ fireEvent(new SplitterClickEvent(AbstractSplitPanel.this,
+ mouseDetails));
+ }
+
+ @Override
+ public void setSplitterPosition(float position) {
+ getSplitterState().setPosition(position);
+ }
+ };
+
+ public AbstractSplitPanel() {
+ registerRpc(rpc);
+ setSplitPosition(50, Unit.PERCENTAGE, false);
+ setSplitPositionLimits(0, Unit.PERCENTAGE, 100, Unit.PERCENTAGE);
+ }
+
+ /**
+ * Modifiable and Serializable Iterator for the components, used by
+ * {@link AbstractSplitPanel#getComponentIterator()}.
+ */
+ private class ComponentIterator implements Iterator<Component>,
+ Serializable {
+
+ int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ if (i < getComponentCount()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Component next() {
+ if (!hasNext()) {
+ return null;
+ }
+ i++;
+ if (i == 1) {
+ return (getFirstComponent() == null ? getSecondComponent()
+ : getFirstComponent());
+ } else if (i == 2) {
+ return getSecondComponent();
+ }
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ if (i == 1) {
+ if (getFirstComponent() != null) {
+ setFirstComponent(null);
+ i = 0;
+ } else {
+ setSecondComponent(null);
+ }
+ } else if (i == 2) {
+ setSecondComponent(null);
+ }
+ }
+ }
+
+ /**
+ * Add a component into this container. The component is added to the right
+ * or under the previous component.
+ *
+ * @param c
+ * the component to be added.
+ */
+
+ @Override
+ public void addComponent(Component c) {
+ if (getFirstComponent() == null) {
+ setFirstComponent(c);
+ } else if (getSecondComponent() == null) {
+ setSecondComponent(c);
+ } else {
+ throw new UnsupportedOperationException(
+ "Split panel can contain only two components");
+ }
+ }
+
+ /**
+ * Sets the first component of this split panel. Depending on the direction
+ * the first component is shown at the top or to the left.
+ *
+ * @param c
+ * The component to use as first component
+ */
+ public void setFirstComponent(Component c) {
+ if (getFirstComponent() == c) {
+ // Nothing to do
+ return;
+ }
+
+ if (getFirstComponent() != null) {
+ // detach old
+ removeComponent(getFirstComponent());
+ }
+ getState().setFirstChild(c);
+ if (c != null) {
+ super.addComponent(c);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Sets the second component of this split panel. Depending on the direction
+ * the second component is shown at the bottom or to the left.
+ *
+ * @param c
+ * The component to use as first component
+ */
+ public void setSecondComponent(Component c) {
+ if (getSecondComponent() == c) {
+ // Nothing to do
+ return;
+ }
+
+ if (getSecondComponent() != null) {
+ // detach old
+ removeComponent(getSecondComponent());
+ }
+ getState().setSecondChild(c);
+ if (c != null) {
+ super.addComponent(c);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Gets the first component of this split panel. Depending on the direction
+ * this is either the component shown at the top or to the left.
+ *
+ * @return the first component of this split panel
+ */
+ public Component getFirstComponent() {
+ return (Component) getState().getFirstChild();
+ }
+
+ /**
+ * Gets the second component of this split panel. Depending on the direction
+ * this is either the component shown at the top or to the left.
+ *
+ * @return the second component of this split panel
+ */
+ public Component getSecondComponent() {
+ return (Component) getState().getSecondChild();
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * the component to be removed.
+ */
+
+ @Override
+ public void removeComponent(Component c) {
+ super.removeComponent(c);
+ if (c == getFirstComponent()) {
+ getState().setFirstChild(null);
+ } else if (c == getSecondComponent()) {
+ getState().setSecondChild(null);
+ }
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.ComponentContainer#getComponentIterator()
+ */
+
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return new ComponentIterator();
+ }
+
+ /**
+ * Gets the number of contained components. Consistent with the iterator
+ * returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components (zero, one or two)
+ */
+
+ @Override
+ public int getComponentCount() {
+ int count = 0;
+ if (getFirstComponent() != null) {
+ count++;
+ }
+ if (getSecondComponent() != null) {
+ count++;
+ }
+ return count;
+ }
+
+ /* Documented in superclass */
+
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ if (oldComponent == getFirstComponent()) {
+ setFirstComponent(newComponent);
+ } else if (oldComponent == getSecondComponent()) {
+ setSecondComponent(newComponent);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Moves the position of the splitter.
+ *
+ * @param pos
+ * the new size of the first region in the unit that was last
+ * used (default is percentage). Fractions are only allowed when
+ * unit is percentage.
+ */
+ public void setSplitPosition(float pos) {
+ setSplitPosition(pos, posUnit, false);
+ }
+
+ /**
+ * Moves the position of the splitter.
+ *
+ * @param pos
+ * the new size of the region in the unit that was last used
+ * (default is percentage). Fractions are only allowed when unit
+ * is percentage.
+ *
+ * @param reverse
+ * if set to true the split splitter position is measured by the
+ * second region else it is measured by the first region
+ */
+ public void setSplitPosition(float pos, boolean reverse) {
+ setSplitPosition(pos, posUnit, reverse);
+ }
+
+ /**
+ * Moves the position of the splitter with given position and unit.
+ *
+ * @param pos
+ * the new size of the first region. Fractions are only allowed
+ * when unit is percentage.
+ * @param unit
+ * the unit (from {@link Sizeable}) in which the size is given.
+ */
+ public void setSplitPosition(float pos, Unit unit) {
+ setSplitPosition(pos, unit, false);
+ }
+
+ /**
+ * Moves the position of the splitter with given position and unit.
+ *
+ * @param pos
+ * the new size of the first region. Fractions are only allowed
+ * when unit is percentage.
+ * @param unit
+ * the unit (from {@link Sizeable}) in which the size is given.
+ * @param reverse
+ * if set to true the split splitter position is measured by the
+ * second region else it is measured by the first region
+ *
+ */
+ public void setSplitPosition(float pos, Unit unit, boolean reverse) {
+ if (unit != Unit.PERCENTAGE && unit != Unit.PIXELS) {
+ throw new IllegalArgumentException(
+ "Only percentage and pixel units are allowed");
+ }
+ if (unit != Unit.PERCENTAGE) {
+ pos = Math.round(pos);
+ }
+ SplitterState splitterState = getSplitterState();
+ splitterState.setPosition(pos);
+ splitterState.setPositionUnit(unit.getSymbol());
+ splitterState.setPositionReversed(reverse);
+ posUnit = unit;
+
+ requestRepaint();
+ }
+
+ /**
+ * Returns the current position of the splitter, in
+ * {@link #getSplitPositionUnit()} units.
+ *
+ * @return position of the splitter
+ */
+ public float getSplitPosition() {
+ return getSplitterState().getPosition();
+ }
+
+ /**
+ * Returns the unit of position of the splitter
+ *
+ * @return unit of position of the splitter
+ */
+ public Unit getSplitPositionUnit() {
+ return posUnit;
+ }
+
+ /**
+ * Sets the minimum split position to the given position and unit. If the
+ * split position is reversed, maximum and minimum are also reversed.
+ *
+ * @param pos
+ * the minimum position of the split
+ * @param unit
+ * the unit (from {@link Sizeable}) in which the size is given.
+ * Allowed units are UNITS_PERCENTAGE and UNITS_PIXELS
+ */
+ public void setMinSplitPosition(int pos, Unit unit) {
+ setSplitPositionLimits(pos, unit, getSplitterState().getMaxPosition(),
+ posMaxUnit);
+ }
+
+ /**
+ * Returns the current minimum position of the splitter, in
+ * {@link #getMinSplitPositionUnit()} units.
+ *
+ * @return the minimum position of the splitter
+ */
+ public float getMinSplitPosition() {
+ return getSplitterState().getMinPosition();
+ }
+
+ /**
+ * Returns the unit of the minimum position of the splitter.
+ *
+ * @return the unit of the minimum position of the splitter
+ */
+ public Unit getMinSplitPositionUnit() {
+ return posMinUnit;
+ }
+
+ /**
+ * Sets the maximum split position to the given position and unit. If the
+ * split position is reversed, maximum and minimum are also reversed.
+ *
+ * @param pos
+ * the maximum position of the split
+ * @param unit
+ * the unit (from {@link Sizeable}) in which the size is given.
+ * Allowed units are UNITS_PERCENTAGE and UNITS_PIXELS
+ */
+ public void setMaxSplitPosition(float pos, Unit unit) {
+ setSplitPositionLimits(getSplitterState().getMinPosition(), posMinUnit,
+ pos, unit);
+ }
+
+ /**
+ * Returns the current maximum position of the splitter, in
+ * {@link #getMaxSplitPositionUnit()} units.
+ *
+ * @return the maximum position of the splitter
+ */
+ public float getMaxSplitPosition() {
+ return getSplitterState().getMaxPosition();
+ }
+
+ /**
+ * Returns the unit of the maximum position of the splitter
+ *
+ * @return the unit of the maximum position of the splitter
+ */
+ public Unit getMaxSplitPositionUnit() {
+ return posMaxUnit;
+ }
+
+ /**
+ * Sets the maximum and minimum position of the splitter. If the split
+ * position is reversed, maximum and minimum are also reversed.
+ *
+ * @param minPos
+ * the new minimum position
+ * @param minPosUnit
+ * the unit (from {@link Sizeable}) in which the minimum position
+ * is given.
+ * @param maxPos
+ * the new maximum position
+ * @param maxPosUnit
+ * the unit (from {@link Sizeable}) in which the maximum position
+ * is given.
+ */
+ private void setSplitPositionLimits(float minPos, Unit minPosUnit,
+ float maxPos, Unit maxPosUnit) {
+ if ((minPosUnit != Unit.PERCENTAGE && minPosUnit != Unit.PIXELS)
+ || (maxPosUnit != Unit.PERCENTAGE && maxPosUnit != Unit.PIXELS)) {
+ throw new IllegalArgumentException(
+ "Only percentage and pixel units are allowed");
+ }
+
+ SplitterState state = getSplitterState();
+
+ state.setMinPosition(minPos);
+ state.setMinPositionUnit(minPosUnit.getSymbol());
+ posMinUnit = minPosUnit;
+
+ state.setMaxPosition(maxPos);
+ state.setMaxPositionUnit(maxPosUnit.getSymbol());
+ posMaxUnit = maxPosUnit;
+
+ requestRepaint();
+ }
+
+ /**
+ * Lock the SplitPanels position, disabling the user from dragging the split
+ * handle.
+ *
+ * @param locked
+ * Set <code>true</code> if locked, <code>false</code> otherwise.
+ */
+ public void setLocked(boolean locked) {
+ getSplitterState().setLocked(locked);
+ requestRepaint();
+ }
+
+ /**
+ * Is the SplitPanel handle locked (user not allowed to change split
+ * position by dragging).
+ *
+ * @return <code>true</code> if locked, <code>false</code> otherwise.
+ */
+ public boolean isLocked() {
+ return getSplitterState().isLocked();
+ }
+
+ /**
+ * <code>SplitterClickListener</code> interface for listening for
+ * <code>SplitterClickEvent</code> fired by a <code>SplitPanel</code>.
+ *
+ * @see SplitterClickEvent
+ * @since 6.2
+ */
+ public interface SplitterClickListener extends ComponentEventListener {
+
+ public static final Method clickMethod = ReflectTools.findMethod(
+ SplitterClickListener.class, "splitterClick",
+ SplitterClickEvent.class);
+
+ /**
+ * SplitPanel splitter has been clicked
+ *
+ * @param event
+ * SplitterClickEvent event.
+ */
+ public void splitterClick(SplitterClickEvent event);
+ }
+
+ public class SplitterClickEvent extends ClickEvent {
+
+ public SplitterClickEvent(Component source,
+ MouseEventDetails mouseEventDetails) {
+ super(source, mouseEventDetails);
+ }
+
+ }
+
+ public void addListener(SplitterClickListener listener) {
+ addListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER,
+ SplitterClickEvent.class, listener,
+ SplitterClickListener.clickMethod);
+ }
+
+ public void removeListener(SplitterClickListener listener) {
+ removeListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER,
+ SplitterClickEvent.class, listener);
+ }
+
+ @Override
+ public AbstractSplitPanelState getState() {
+ return (AbstractSplitPanelState) super.getState();
+ }
+
+ private SplitterState getSplitterState() {
+ return getState().getSplitterState();
+ }
+}
diff --git a/server/src/com/vaadin/ui/AbstractTextField.java b/server/src/com/vaadin/ui/AbstractTextField.java
new file mode 100644
index 0000000000..2326c07d97
--- /dev/null
+++ b/server/src/com/vaadin/ui/AbstractTextField.java
@@ -0,0 +1,674 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Map;
+
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.BlurNotifier;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.event.FieldEvents.FocusNotifier;
+import com.vaadin.event.FieldEvents.TextChangeEvent;
+import com.vaadin.event.FieldEvents.TextChangeListener;
+import com.vaadin.event.FieldEvents.TextChangeNotifier;
+import com.vaadin.shared.ui.textfield.AbstractTextFieldState;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.textfield.VTextField;
+
+public abstract class AbstractTextField extends AbstractField<String> implements
+ BlurNotifier, FocusNotifier, TextChangeNotifier, Vaadin6Component {
+
+ /**
+ * Null representation.
+ */
+ private String nullRepresentation = "null";
+ /**
+ * Is setting to null from non-null value allowed by setting with null
+ * representation .
+ */
+ private boolean nullSettingAllowed = false;
+ /**
+ * The text content when the last messages to the server was sent. Cleared
+ * when value is changed.
+ */
+ private String lastKnownTextContent;
+
+ /**
+ * The position of the cursor when the last message to the server was sent.
+ */
+ private int lastKnownCursorPosition;
+
+ /**
+ * Flag indicating that a text change event is pending to be triggered.
+ * Cleared by {@link #setInternalValue(Object)} and when the event is fired.
+ */
+ private boolean textChangeEventPending;
+
+ private boolean isFiringTextChangeEvent = false;
+
+ private TextChangeEventMode textChangeEventMode = TextChangeEventMode.LAZY;
+
+ private final int DEFAULT_TEXTCHANGE_TIMEOUT = 400;
+
+ private int textChangeEventTimeout = DEFAULT_TEXTCHANGE_TIMEOUT;
+
+ /**
+ * Temporarily holds the new selection position. Cleared on paint.
+ */
+ private int selectionPosition = -1;
+
+ /**
+ * Temporarily holds the new selection length.
+ */
+ private int selectionLength;
+
+ /**
+ * Flag used to determine whether we are currently handling a state change
+ * triggered by a user. Used to properly fire text change event before value
+ * change event triggered by the client side.
+ */
+ private boolean changingVariables;
+
+ protected AbstractTextField() {
+ super();
+ }
+
+ @Override
+ public AbstractTextFieldState getState() {
+ return (AbstractTextFieldState) super.getState();
+ }
+
+ @Override
+ public void updateState() {
+ super.updateState();
+
+ String value = getValue();
+ if (value == null) {
+ value = getNullRepresentation();
+ }
+ getState().setText(value);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (selectionPosition != -1) {
+ target.addAttribute("selpos", selectionPosition);
+ target.addAttribute("sellen", selectionLength);
+ selectionPosition = -1;
+ }
+
+ if (hasListeners(TextChangeEvent.class)) {
+ target.addAttribute(VTextField.ATTR_TEXTCHANGE_EVENTMODE,
+ getTextChangeEventMode().toString());
+ target.addAttribute(VTextField.ATTR_TEXTCHANGE_TIMEOUT,
+ getTextChangeTimeout());
+ if (lastKnownTextContent != null) {
+ /*
+ * The field has be repainted for some reason (e.g. caption,
+ * size, stylename), but the value has not been changed since
+ * the last text change event. Let the client side know about
+ * the value the server side knows. Client side may then ignore
+ * the actual value, depending on its state.
+ */
+ target.addAttribute(
+ VTextField.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS, true);
+ }
+ }
+
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ changingVariables = true;
+
+ try {
+
+ if (variables.containsKey(VTextField.VAR_CURSOR)) {
+ Integer object = (Integer) variables.get(VTextField.VAR_CURSOR);
+ lastKnownCursorPosition = object.intValue();
+ }
+
+ if (variables.containsKey(VTextField.VAR_CUR_TEXT)) {
+ /*
+ * NOTE, we might want to develop this further so that on a
+ * value change event the whole text content don't need to be
+ * sent from the client to server. Just "commit" the value from
+ * currentText to the value.
+ */
+ handleInputEventTextChange(variables);
+ }
+
+ // Sets the text
+ if (variables.containsKey("text") && !isReadOnly()) {
+
+ // Only do the setting if the string representation of the value
+ // has been updated
+ String newValue = (String) variables.get("text");
+
+ // server side check for max length
+ if (getMaxLength() != -1 && newValue.length() > getMaxLength()) {
+ newValue = newValue.substring(0, getMaxLength());
+ }
+ final String oldValue = getValue();
+ if (newValue != null
+ && (oldValue == null || isNullSettingAllowed())
+ && newValue.equals(getNullRepresentation())) {
+ newValue = null;
+ }
+ if (newValue != oldValue
+ && (newValue == null || !newValue.equals(oldValue))) {
+ boolean wasModified = isModified();
+ setValue(newValue, true);
+
+ // If the modified status changes, or if we have a
+ // formatter, repaint is needed after all.
+ if (wasModified != isModified()) {
+ requestRepaint();
+ }
+ }
+ }
+ firePendingTextChangeEvent();
+
+ if (variables.containsKey(FocusEvent.EVENT_ID)) {
+ fireEvent(new FocusEvent(this));
+ }
+ if (variables.containsKey(BlurEvent.EVENT_ID)) {
+ fireEvent(new BlurEvent(this));
+ }
+ } finally {
+ changingVariables = false;
+
+ }
+
+ }
+
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+
+ /**
+ * Gets the null-string representation.
+ *
+ * <p>
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ * </p>
+ *
+ * <p>
+ * The default value is string 'null'.
+ * </p>
+ *
+ * @return the String Textual representation for null strings.
+ * @see TextField#isNullSettingAllowed()
+ */
+ public String getNullRepresentation() {
+ return nullRepresentation;
+ }
+
+ /**
+ * Is setting nulls with null-string representation allowed.
+ *
+ * <p>
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ * </p>
+ *
+ * <p>
+ * By default this setting is false
+ * </p>
+ *
+ * @return boolean Should the null-string represenation be always converted
+ * to null-values.
+ * @see TextField#getNullRepresentation()
+ */
+ public boolean isNullSettingAllowed() {
+ return nullSettingAllowed;
+ }
+
+ /**
+ * Sets the null-string representation.
+ *
+ * <p>
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ * </p>
+ *
+ * <p>
+ * The default value is string 'null'
+ * </p>
+ *
+ * @param nullRepresentation
+ * Textual representation for null strings.
+ * @see TextField#setNullSettingAllowed(boolean)
+ */
+ public void setNullRepresentation(String nullRepresentation) {
+ this.nullRepresentation = nullRepresentation;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the null conversion mode.
+ *
+ * <p>
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ * </p>
+ *
+ * <p>
+ * By default this setting is false.
+ * </p>
+ *
+ * @param nullSettingAllowed
+ * Should the null-string representation always be converted to
+ * null-values.
+ * @see TextField#getNullRepresentation()
+ */
+ public void setNullSettingAllowed(boolean nullSettingAllowed) {
+ this.nullSettingAllowed = nullSettingAllowed;
+ requestRepaint();
+ }
+
+ @Override
+ protected boolean isEmpty() {
+ return super.isEmpty() || getValue().length() == 0;
+ }
+
+ /**
+ * Returns the maximum number of characters in the field. Value -1 is
+ * considered unlimited. Terminal may however have some technical limits.
+ *
+ * @return the maxLength
+ */
+ public int getMaxLength() {
+ return getState().getMaxLength();
+ }
+
+ /**
+ * Sets the maximum number of characters in the field. Value -1 is
+ * considered unlimited. Terminal may however have some technical limits.
+ *
+ * @param maxLength
+ * the maxLength to set
+ */
+ public void setMaxLength(int maxLength) {
+ getState().setMaxLength(maxLength);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ *
+ * @return the number of columns in the editor.
+ */
+ public int getColumns() {
+ return getState().getColumns();
+ }
+
+ /**
+ * Sets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ *
+ * @param columns
+ * the number of columns to set.
+ */
+ public void setColumns(int columns) {
+ if (columns < 0) {
+ columns = 0;
+ }
+ getState().setColumns(columns);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the current input prompt.
+ *
+ * @see #setInputPrompt(String)
+ * @return the current input prompt, or null if not enabled
+ */
+ public String getInputPrompt() {
+ return getState().getInputPrompt();
+ }
+
+ /**
+ * Sets the input prompt - a textual prompt that is displayed when the field
+ * would otherwise be empty, to prompt the user for input.
+ *
+ * @param inputPrompt
+ */
+ public void setInputPrompt(String inputPrompt) {
+ getState().setInputPrompt(inputPrompt);
+ requestRepaint();
+ }
+
+ /* ** Text Change Events ** */
+
+ private void firePendingTextChangeEvent() {
+ if (textChangeEventPending && !isFiringTextChangeEvent) {
+ isFiringTextChangeEvent = true;
+ textChangeEventPending = false;
+ try {
+ fireEvent(new TextChangeEventImpl(this));
+ } finally {
+ isFiringTextChangeEvent = false;
+ }
+ }
+ }
+
+ @Override
+ protected void setInternalValue(String newValue) {
+ if (changingVariables && !textChangeEventPending) {
+
+ /*
+ * TODO check for possible (minor?) issue (not tested)
+ *
+ * -field with e.g. PropertyFormatter.
+ *
+ * -TextChangeListener and it changes value.
+ *
+ * -if formatter again changes the value, do we get an extra
+ * simulated text change event ?
+ */
+
+ /*
+ * Fire a "simulated" text change event before value change event if
+ * change is coming from the client side.
+ *
+ * Iff there is both value change and textChangeEvent in same
+ * variable burst, it is a text field in non immediate mode and the
+ * text change event "flushed" queued value change event. In this
+ * case textChangeEventPending flag is already on and text change
+ * event will be fired after the value change event.
+ */
+ if (newValue == null && lastKnownTextContent != null
+ && !lastKnownTextContent.equals(getNullRepresentation())) {
+ // Value was changed from something to null representation
+ lastKnownTextContent = getNullRepresentation();
+ textChangeEventPending = true;
+ } else if (newValue != null
+ && !newValue.toString().equals(lastKnownTextContent)) {
+ // Value was changed to something else than null representation
+ lastKnownTextContent = newValue.toString();
+ textChangeEventPending = true;
+ }
+ firePendingTextChangeEvent();
+ }
+
+ super.setInternalValue(newValue);
+ }
+
+ @Override
+ public void setValue(Object newValue) throws ReadOnlyException {
+ super.setValue(newValue);
+ /*
+ * Make sure w reset lastKnownTextContent field on value change. The
+ * clearing must happen here as well because TextChangeListener can
+ * revert the original value. Client must respect the value in this
+ * case. AbstractField optimizes value change if the existing value is
+ * reset. Also we need to force repaint if the flag is on.
+ */
+ if (lastKnownTextContent != null) {
+ lastKnownTextContent = null;
+ requestRepaint();
+ }
+ }
+
+ private void handleInputEventTextChange(Map<String, Object> variables) {
+ /*
+ * TODO we could vastly optimize the communication of values by using
+ * some sort of diffs instead of always sending the whole text content.
+ * Also on value change events we could use the mechanism.
+ */
+ String object = (String) variables.get(VTextField.VAR_CUR_TEXT);
+ lastKnownTextContent = object;
+ textChangeEventPending = true;
+ }
+
+ /**
+ * Sets the mode how the TextField triggers {@link TextChangeEvent}s.
+ *
+ * @param inputEventMode
+ * the new mode
+ *
+ * @see TextChangeEventMode
+ */
+ public void setTextChangeEventMode(TextChangeEventMode inputEventMode) {
+ textChangeEventMode = inputEventMode;
+ requestRepaint();
+ }
+
+ /**
+ * @return the mode used to trigger {@link TextChangeEvent}s.
+ */
+ public TextChangeEventMode getTextChangeEventMode() {
+ return textChangeEventMode;
+ }
+
+ /**
+ * Different modes how the TextField can trigger {@link TextChangeEvent}s.
+ */
+ public enum TextChangeEventMode {
+
+ /**
+ * An event is triggered on each text content change, most commonly key
+ * press events.
+ */
+ EAGER,
+ /**
+ * Each text change event in the UI causes the event to be communicated
+ * to the application after a timeout. The length of the timeout can be
+ * controlled with {@link TextField#setInputEventTimeout(int)}. Only the
+ * last input event is reported to the server side if several text
+ * change events happen during the timeout.
+ * <p>
+ * In case of a {@link ValueChangeEvent} the schedule is not kept
+ * strictly. Before a {@link ValueChangeEvent} a {@link TextChangeEvent}
+ * is triggered if the text content has changed since the previous
+ * TextChangeEvent regardless of the schedule.
+ */
+ TIMEOUT,
+ /**
+ * An event is triggered when there is a pause of text modifications.
+ * The length of the pause can be modified with
+ * {@link TextField#setInputEventTimeout(int)}. Like with the
+ * {@link #TIMEOUT} mode, an event is forced before
+ * {@link ValueChangeEvent}s, even if the user did not keep a pause
+ * while entering the text.
+ * <p>
+ * This is the default mode.
+ */
+ LAZY
+ }
+
+ @Override
+ public void addListener(TextChangeListener listener) {
+ addListener(TextChangeListener.EVENT_ID, TextChangeEvent.class,
+ listener, TextChangeListener.EVENT_METHOD);
+ }
+
+ @Override
+ public void removeListener(TextChangeListener listener) {
+ removeListener(TextChangeListener.EVENT_ID, TextChangeEvent.class,
+ listener);
+ }
+
+ /**
+ * The text change timeout modifies how often text change events are
+ * communicated to the application when {@link #getTextChangeEventMode()} is
+ * {@link TextChangeEventMode#LAZY} or {@link TextChangeEventMode#TIMEOUT}.
+ *
+ *
+ * @see #getTextChangeEventMode()
+ *
+ * @param timeout
+ * the timeout in milliseconds
+ */
+ public void setTextChangeTimeout(int timeout) {
+ textChangeEventTimeout = timeout;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the timeout used to fire {@link TextChangeEvent}s when the
+ * {@link #getTextChangeEventMode()} is {@link TextChangeEventMode#LAZY} or
+ * {@link TextChangeEventMode#TIMEOUT}.
+ *
+ * @return the timeout value in milliseconds
+ */
+ public int getTextChangeTimeout() {
+ return textChangeEventTimeout;
+ }
+
+ public class TextChangeEventImpl extends TextChangeEvent {
+ private String curText;
+ private int cursorPosition;
+
+ private TextChangeEventImpl(final AbstractTextField tf) {
+ super(tf);
+ curText = tf.getCurrentTextContent();
+ cursorPosition = tf.getCursorPosition();
+ }
+
+ @Override
+ public AbstractTextField getComponent() {
+ return (AbstractTextField) super.getComponent();
+ }
+
+ @Override
+ public String getText() {
+ return curText;
+ }
+
+ @Override
+ public int getCursorPosition() {
+ return cursorPosition;
+ }
+
+ }
+
+ /**
+ * Gets the current (or the last known) text content in the field.
+ * <p>
+ * Note the text returned by this method is not necessary the same that is
+ * returned by the {@link #getValue()} method. The value is updated when the
+ * terminal fires a value change event via e.g. blurring the field or by
+ * pressing enter. The value returned by this method is updated also on
+ * {@link TextChangeEvent}s. Due to this high dependency to the terminal
+ * implementation this method is (at least at this point) not published.
+ *
+ * @return the text which is currently displayed in the field.
+ */
+ private String getCurrentTextContent() {
+ if (lastKnownTextContent != null) {
+ return lastKnownTextContent;
+ } else {
+ Object text = getValue();
+ if (text == null) {
+ return getNullRepresentation();
+ }
+ return text.toString();
+ }
+ }
+
+ /**
+ * Selects all text in the field.
+ *
+ * @since 6.4
+ */
+ public void selectAll() {
+ String text = getValue() == null ? "" : getValue().toString();
+ setSelectionRange(0, text.length());
+ }
+
+ /**
+ * Sets the range of text to be selected.
+ *
+ * As a side effect the field will become focused.
+ *
+ * @since 6.4
+ *
+ * @param pos
+ * the position of the first character to be selected
+ * @param length
+ * the number of characters to be selected
+ */
+ public void setSelectionRange(int pos, int length) {
+ selectionPosition = pos;
+ selectionLength = length;
+ focus();
+ requestRepaint();
+ }
+
+ /**
+ * Sets the cursor position in the field. As a side effect the field will
+ * become focused.
+ *
+ * @since 6.4
+ *
+ * @param pos
+ * the position for the cursor
+ * */
+ public void setCursorPosition(int pos) {
+ setSelectionRange(pos, 0);
+ lastKnownCursorPosition = pos;
+ }
+
+ /**
+ * Returns the last known cursor position of the field.
+ *
+ * <p>
+ * Note that due to the client server nature or the GWT terminal, Vaadin
+ * cannot provide the exact value of the cursor position in most situations.
+ * The value is updated only when the client side terminal communicates to
+ * TextField, like on {@link ValueChangeEvent}s and {@link TextChangeEvent}
+ * s. This may change later if a deep push integration is built to Vaadin.
+ *
+ * @return the cursor position
+ */
+ public int getCursorPosition() {
+ return lastKnownCursorPosition;
+ }
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Accordion.java b/server/src/com/vaadin/ui/Accordion.java
new file mode 100644
index 0000000000..b937c7bc2b
--- /dev/null
+++ b/server/src/com/vaadin/ui/Accordion.java
@@ -0,0 +1,19 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+/**
+ * An accordion is a component similar to a {@link TabSheet}, but with a
+ * vertical orientation and the selected component presented between tabs.
+ *
+ * Closable tabs are not supported by the accordion.
+ *
+ * The {@link Accordion} can be styled with the .v-accordion, .v-accordion-item,
+ * .v-accordion-item-first and .v-accordion-item-caption styles.
+ *
+ * @see TabSheet
+ */
+public class Accordion extends TabSheet {
+
+}
diff --git a/server/src/com/vaadin/ui/Alignment.java b/server/src/com/vaadin/ui/Alignment.java
new file mode 100644
index 0000000000..0d73da8504
--- /dev/null
+++ b/server/src/com/vaadin/ui/Alignment.java
@@ -0,0 +1,158 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.ui.AlignmentInfo.Bits;
+
+/**
+ * Class containing information about alignment of a component. Use the
+ * pre-instantiated classes.
+ */
+@SuppressWarnings("serial")
+public final class Alignment implements Serializable {
+
+ public static final Alignment TOP_RIGHT = new Alignment(Bits.ALIGNMENT_TOP
+ + Bits.ALIGNMENT_RIGHT);
+ public static final Alignment TOP_LEFT = new Alignment(Bits.ALIGNMENT_TOP
+ + Bits.ALIGNMENT_LEFT);
+ public static final Alignment TOP_CENTER = new Alignment(Bits.ALIGNMENT_TOP
+ + Bits.ALIGNMENT_HORIZONTAL_CENTER);
+ public static final Alignment MIDDLE_RIGHT = new Alignment(
+ Bits.ALIGNMENT_VERTICAL_CENTER + Bits.ALIGNMENT_RIGHT);
+ public static final Alignment MIDDLE_LEFT = new Alignment(
+ Bits.ALIGNMENT_VERTICAL_CENTER + Bits.ALIGNMENT_LEFT);
+ public static final Alignment MIDDLE_CENTER = new Alignment(
+ Bits.ALIGNMENT_VERTICAL_CENTER + Bits.ALIGNMENT_HORIZONTAL_CENTER);
+ public static final Alignment BOTTOM_RIGHT = new Alignment(
+ Bits.ALIGNMENT_BOTTOM + Bits.ALIGNMENT_RIGHT);
+ public static final Alignment BOTTOM_LEFT = new Alignment(
+ Bits.ALIGNMENT_BOTTOM + Bits.ALIGNMENT_LEFT);
+ public static final Alignment BOTTOM_CENTER = new Alignment(
+ Bits.ALIGNMENT_BOTTOM + Bits.ALIGNMENT_HORIZONTAL_CENTER);
+
+ private final int bitMask;
+
+ public Alignment(int bitMask) {
+ this.bitMask = bitMask;
+ }
+
+ /**
+ * Returns a bitmask representation of the alignment value. Used internally
+ * by terminal.
+ *
+ * @return the bitmask representation of the alignment value
+ */
+ public int getBitMask() {
+ return bitMask;
+ }
+
+ /**
+ * Checks if component is aligned to the top of the available space.
+ *
+ * @return true if aligned top
+ */
+ public boolean isTop() {
+ return (bitMask & Bits.ALIGNMENT_TOP) == Bits.ALIGNMENT_TOP;
+ }
+
+ /**
+ * Checks if component is aligned to the bottom of the available space.
+ *
+ * @return true if aligned bottom
+ */
+ public boolean isBottom() {
+ return (bitMask & Bits.ALIGNMENT_BOTTOM) == Bits.ALIGNMENT_BOTTOM;
+ }
+
+ /**
+ * Checks if component is aligned to the left of the available space.
+ *
+ * @return true if aligned left
+ */
+ public boolean isLeft() {
+ return (bitMask & Bits.ALIGNMENT_LEFT) == Bits.ALIGNMENT_LEFT;
+ }
+
+ /**
+ * Checks if component is aligned to the right of the available space.
+ *
+ * @return true if aligned right
+ */
+ public boolean isRight() {
+ return (bitMask & Bits.ALIGNMENT_RIGHT) == Bits.ALIGNMENT_RIGHT;
+ }
+
+ /**
+ * Checks if component is aligned middle (vertically center) of the
+ * available space.
+ *
+ * @return true if aligned bottom
+ */
+ public boolean isMiddle() {
+ return (bitMask & Bits.ALIGNMENT_VERTICAL_CENTER) == Bits.ALIGNMENT_VERTICAL_CENTER;
+ }
+
+ /**
+ * Checks if component is aligned center (horizontally) of the available
+ * space.
+ *
+ * @return true if aligned center
+ */
+ public boolean isCenter() {
+ return (bitMask & Bits.ALIGNMENT_HORIZONTAL_CENTER) == Bits.ALIGNMENT_HORIZONTAL_CENTER;
+ }
+
+ /**
+ * Returns string representation of vertical alignment.
+ *
+ * @return vertical alignment as CSS value
+ */
+ public String getVerticalAlignment() {
+ if (isBottom()) {
+ return "bottom";
+ } else if (isMiddle()) {
+ return "middle";
+ }
+ return "top";
+ }
+
+ /**
+ * Returns string representation of horizontal alignment.
+ *
+ * @return horizontal alignment as CSS value
+ */
+ public String getHorizontalAlignment() {
+ if (isRight()) {
+ return "right";
+ } else if (isCenter()) {
+ return "center";
+ }
+ return "left";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if ((obj == null) || (obj.getClass() != this.getClass())) {
+ return false;
+ }
+ Alignment a = (Alignment) obj;
+ return bitMask == a.bitMask;
+ }
+
+ @Override
+ public int hashCode() {
+ return bitMask;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(bitMask);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Audio.java b/server/src/com/vaadin/ui/Audio.java
new file mode 100644
index 0000000000..ac2ee869a6
--- /dev/null
+++ b/server/src/com/vaadin/ui/Audio.java
@@ -0,0 +1,55 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.terminal.Resource;
+
+/**
+ * The Audio component translates into an HTML5 &lt;audio&gt; element and as
+ * such is only supported in browsers that support HTML5 media markup. Browsers
+ * that do not support HTML5 display the text or HTML set by calling
+ * {@link #setAltText(String)}.
+ *
+ * A flash-player fallback can be implemented by setting HTML content allowed (
+ * {@link #setHtmlContentAllowed(boolean)} and calling
+ * {@link #setAltText(String)} with the flash player markup. An example of flash
+ * fallback can be found at the <a href=
+ * "https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Using_Flash"
+ * >Mozilla Developer Network</a>.
+ *
+ * Multiple sources can be specified. Which of the sources is used is selected
+ * by the browser depending on which file formats it supports. See <a
+ * href="http://en.wikipedia.org/wiki/HTML5_video#Table">wikipedia</a> for a
+ * table of formats supported by different browsers.
+ *
+ * @author Vaadin Ltd
+ * @since 6.7.0
+ */
+public class Audio extends AbstractMedia {
+
+ public Audio() {
+ this("", null);
+ }
+
+ /**
+ * @param caption
+ * The caption of the audio component.
+ */
+ public Audio(String caption) {
+ this(caption, null);
+ }
+
+ /**
+ * @param caption
+ * The caption of the audio component
+ * @param source
+ * The audio file to play.
+ */
+ public Audio(String caption, Resource source) {
+ setCaption(caption);
+ setSource(source);
+ setShowControls(true);
+ }
+}
diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java
new file mode 100644
index 0000000000..0cb667d527
--- /dev/null
+++ b/server/src/com/vaadin/ui/Button.java
@@ -0,0 +1,539 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+import com.vaadin.event.Action;
+import com.vaadin.event.FieldEvents;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.event.ShortcutAction;
+import com.vaadin.event.ShortcutAction.KeyCode;
+import com.vaadin.event.ShortcutAction.ModifierKey;
+import com.vaadin.event.ShortcutListener;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.button.ButtonServerRpc;
+import com.vaadin.shared.ui.button.ButtonState;
+import com.vaadin.tools.ReflectTools;
+import com.vaadin.ui.Component.Focusable;
+
+/**
+ * A generic button component.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Button extends AbstractComponent implements
+ FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Focusable,
+ Action.ShortcutNotifier {
+
+ private ButtonServerRpc rpc = new ButtonServerRpc() {
+
+ @Override
+ public void click(MouseEventDetails mouseEventDetails) {
+ fireClick(mouseEventDetails);
+ }
+
+ @Override
+ public void disableOnClick() {
+ // Could be optimized so the button is not repainted because of
+ // this (client side has already disabled the button)
+ setEnabled(false);
+ }
+ };
+
+ FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) {
+
+ @Override
+ protected void fireEvent(Event event) {
+ Button.this.fireEvent(event);
+ }
+ };
+
+ /**
+ * Creates a new push button.
+ */
+ public Button() {
+ registerRpc(rpc);
+ registerRpc(focusBlurRpc);
+ }
+
+ /**
+ * Creates a new push button with the given caption.
+ *
+ * @param caption
+ * the Button caption.
+ */
+ public Button(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new push button with a click listener.
+ *
+ * @param caption
+ * the Button caption.
+ * @param listener
+ * the Button click listener.
+ */
+ public Button(String caption, ClickListener listener) {
+ this(caption);
+ addListener(listener);
+ }
+
+ /**
+ * Click event. This event is thrown, when the button is clicked.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class ClickEvent extends Component.Event {
+
+ private final MouseEventDetails details;
+
+ /**
+ * New instance of text change event.
+ *
+ * @param source
+ * the Source of the event.
+ */
+ public ClickEvent(Component source) {
+ super(source);
+ details = null;
+ }
+
+ /**
+ * Constructor with mouse details
+ *
+ * @param source
+ * The source where the click took place
+ * @param details
+ * Details about the mouse click
+ */
+ public ClickEvent(Component source, MouseEventDetails details) {
+ super(source);
+ this.details = details;
+ }
+
+ /**
+ * Gets the Button where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Button getButton() {
+ return (Button) getSource();
+ }
+
+ /**
+ * Returns the mouse position (x coordinate) when the click took place.
+ * The position is relative to the browser client area.
+ *
+ * @return The mouse cursor x position or -1 if unknown
+ */
+ public int getClientX() {
+ if (null != details) {
+ return details.getClientX();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the mouse position (y coordinate) when the click took place.
+ * The position is relative to the browser client area.
+ *
+ * @return The mouse cursor y position or -1 if unknown
+ */
+ public int getClientY() {
+ if (null != details) {
+ return details.getClientY();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the relative mouse position (x coordinate) when the click
+ * took place. The position is relative to the clicked component.
+ *
+ * @return The mouse cursor x position relative to the clicked layout
+ * component or -1 if no x coordinate available
+ */
+ public int getRelativeX() {
+ if (null != details) {
+ return details.getRelativeX();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the relative mouse position (y coordinate) when the click
+ * took place. The position is relative to the clicked component.
+ *
+ * @return The mouse cursor y position relative to the clicked layout
+ * component or -1 if no y coordinate available
+ */
+ public int getRelativeY() {
+ if (null != details) {
+ return details.getRelativeY();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Checks if the Alt key was down when the mouse event took place.
+ *
+ * @return true if Alt was down when the event occured, false otherwise
+ * or if unknown
+ */
+ public boolean isAltKey() {
+ if (null != details) {
+ return details.isAltKey();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the Ctrl key was down when the mouse event took place.
+ *
+ * @return true if Ctrl was pressed when the event occured, false
+ * otherwise or if unknown
+ */
+ public boolean isCtrlKey() {
+ if (null != details) {
+ return details.isCtrlKey();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the Meta key was down when the mouse event took place.
+ *
+ * @return true if Meta was pressed when the event occured, false
+ * otherwise or if unknown
+ */
+ public boolean isMetaKey() {
+ if (null != details) {
+ return details.isMetaKey();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the Shift key was down when the mouse event took place.
+ *
+ * @return true if Shift was pressed when the event occured, false
+ * otherwise or if unknown
+ */
+ public boolean isShiftKey() {
+ if (null != details) {
+ return details.isShiftKey();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Interface for listening for a {@link ClickEvent} fired by a
+ * {@link Component}.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ClickListener extends Serializable {
+
+ public static final Method BUTTON_CLICK_METHOD = ReflectTools
+ .findMethod(ClickListener.class, "buttonClick",
+ ClickEvent.class);
+
+ /**
+ * Called when a {@link Button} has been clicked. A reference to the
+ * button is given by {@link ClickEvent#getButton()}.
+ *
+ * @param event
+ * An event containing information about the click.
+ */
+ public void buttonClick(ClickEvent event);
+
+ }
+
+ /**
+ * Adds the button click listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(ClickListener listener) {
+ addListener(ClickEvent.class, listener,
+ ClickListener.BUTTON_CLICK_METHOD);
+ }
+
+ /**
+ * Removes the button click listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(ClickListener listener) {
+ removeListener(ClickEvent.class, listener,
+ ClickListener.BUTTON_CLICK_METHOD);
+ }
+
+ /**
+ * Simulates a button click, notifying all server-side listeners.
+ *
+ * No action is taken is the button is disabled.
+ */
+ public void click() {
+ if (isEnabled() && !isReadOnly()) {
+ fireClick();
+ }
+ }
+
+ /**
+ * Fires a click event to all listeners without any event details.
+ *
+ * In subclasses, override {@link #fireClick(MouseEventDetails)} instead of
+ * this method.
+ */
+ protected void fireClick() {
+ fireEvent(new Button.ClickEvent(this));
+ }
+
+ /**
+ * Fires a click event to all listeners.
+ *
+ * @param details
+ * MouseEventDetails from which keyboard modifiers and other
+ * information about the mouse click can be obtained. If the
+ * button was clicked by a keyboard event, some of the fields may
+ * be empty/undefined.
+ */
+ protected void fireClick(MouseEventDetails details) {
+ fireEvent(new Button.ClickEvent(this, details));
+ }
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+
+ }
+
+ /*
+ * Actions
+ */
+
+ protected ClickShortcut clickShortcut;
+
+ /**
+ * Makes it possible to invoke a click on this button by pressing the given
+ * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/>
+ * The shortcut is global (bound to the containing Window).
+ *
+ * @param keyCode
+ * the keycode for invoking the shortcut
+ * @param modifiers
+ * the (optional) modifiers for invoking the shortcut, null for
+ * none
+ */
+ public void setClickShortcut(int keyCode, int... modifiers) {
+ if (clickShortcut != null) {
+ removeShortcutListener(clickShortcut);
+ }
+ clickShortcut = new ClickShortcut(this, keyCode, modifiers);
+ addShortcutListener(clickShortcut);
+ getState().setClickShortcutKeyCode(clickShortcut.getKeyCode());
+ }
+
+ /**
+ * Removes the keyboard shortcut previously set with
+ * {@link #setClickShortcut(int, int...)}.
+ */
+ public void removeClickShortcut() {
+ if (clickShortcut != null) {
+ removeShortcutListener(clickShortcut);
+ clickShortcut = null;
+ getState().setClickShortcutKeyCode(0);
+ }
+ }
+
+ /**
+ * A {@link ShortcutListener} specifically made to define a keyboard
+ * shortcut that invokes a click on the given button.
+ *
+ */
+ public static class ClickShortcut extends ShortcutListener {
+ protected Button button;
+
+ /**
+ * Creates a keyboard shortcut for clicking the given button using the
+ * shorthand notation defined in {@link ShortcutAction}.
+ *
+ * @param button
+ * to be clicked when the shortcut is invoked
+ * @param shorthandCaption
+ * the caption with shortcut keycode and modifiers indicated
+ */
+ public ClickShortcut(Button button, String shorthandCaption) {
+ super(shorthandCaption);
+ this.button = button;
+ }
+
+ /**
+ * Creates a keyboard shortcut for clicking the given button using the
+ * given {@link KeyCode} and {@link ModifierKey}s.
+ *
+ * @param button
+ * to be clicked when the shortcut is invoked
+ * @param keyCode
+ * KeyCode to react to
+ * @param modifiers
+ * optional modifiers for shortcut
+ */
+ public ClickShortcut(Button button, int keyCode, int... modifiers) {
+ super(null, keyCode, modifiers);
+ this.button = button;
+ }
+
+ /**
+ * Creates a keyboard shortcut for clicking the given button using the
+ * given {@link KeyCode}.
+ *
+ * @param button
+ * to be clicked when the shortcut is invoked
+ * @param keyCode
+ * KeyCode to react to
+ */
+ public ClickShortcut(Button button, int keyCode) {
+ this(button, keyCode, null);
+ }
+
+ @Override
+ public void handleAction(Object sender, Object target) {
+ button.click();
+ }
+ }
+
+ /**
+ * Determines if a button is automatically disabled when clicked. See
+ * {@link #setDisableOnClick(boolean)} for details.
+ *
+ * @return true if the button is disabled when clicked, false otherwise
+ */
+ public boolean isDisableOnClick() {
+ return getState().isDisableOnClick();
+ }
+
+ /**
+ * Determines if a button is automatically disabled when clicked. If this is
+ * set to true the button will be automatically disabled when clicked,
+ * typically to prevent (accidental) extra clicks on a button.
+ *
+ * @param disableOnClick
+ * true to disable button when it is clicked, false otherwise
+ */
+ public void setDisableOnClick(boolean disableOnClick) {
+ getState().setDisableOnClick(disableOnClick);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#getTabIndex()
+ */
+ @Override
+ public int getTabIndex() {
+ return getState().getTabIndex();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ @Override
+ public void setTabIndex(int tabIndex) {
+ getState().setTabIndex(tabIndex);
+ requestRepaint();
+ }
+
+ @Override
+ public void focus() {
+ // Overridden only to make public
+ super.focus();
+ }
+
+ @Override
+ public ButtonState getState() {
+ return (ButtonState) super.getState();
+ }
+
+ /**
+ * Set whether the caption text is rendered as HTML or not. You might need
+ * to retheme button to allow higher content than the original text style.
+ *
+ * If set to true, the captions are passed to the browser as html and the
+ * developer is responsible for ensuring no harmful html is used. If set to
+ * false, the content is passed to the browser as plain text.
+ *
+ * @param htmlContentAllowed
+ * <code>true</code> if caption is rendered as HTML,
+ * <code>false</code> otherwise
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ if (getState().isHtmlContentAllowed() != htmlContentAllowed) {
+ getState().setHtmlContentAllowed(htmlContentAllowed);
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Return HTML rendering setting
+ *
+ * @return <code>true</code> if the caption text is to be rendered as HTML,
+ * <code>false</code> otherwise
+ */
+ public boolean isHtmlContentAllowed() {
+ return getState().isHtmlContentAllowed();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/CheckBox.java b/server/src/com/vaadin/ui/CheckBox.java
new file mode 100644
index 0000000000..30ac9b4626
--- /dev/null
+++ b/server/src/com/vaadin/ui/CheckBox.java
@@ -0,0 +1,141 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.data.Property;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.checkbox.CheckBoxServerRpc;
+import com.vaadin.shared.ui.checkbox.CheckBoxState;
+
+public class CheckBox extends AbstractField<Boolean> {
+
+ private CheckBoxServerRpc rpc = new CheckBoxServerRpc() {
+
+ @Override
+ public void setChecked(boolean checked,
+ MouseEventDetails mouseEventDetails) {
+ if (isReadOnly()) {
+ return;
+ }
+
+ final Boolean oldValue = getValue();
+ final Boolean newValue = checked;
+
+ if (!newValue.equals(oldValue)) {
+ // The event is only sent if the switch state is changed
+ setValue(newValue);
+ }
+
+ }
+ };
+
+ FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) {
+ @Override
+ protected void fireEvent(Event event) {
+ CheckBox.this.fireEvent(event);
+ }
+ };
+
+ /**
+ * Creates a new checkbox.
+ */
+ public CheckBox() {
+ registerRpc(rpc);
+ registerRpc(focusBlurRpc);
+ setValue(Boolean.FALSE);
+ }
+
+ /**
+ * Creates a new checkbox with a set caption.
+ *
+ * @param caption
+ * the Checkbox caption.
+ */
+ public CheckBox(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new checkbox with a caption and a set initial state.
+ *
+ * @param caption
+ * the caption of the checkbox
+ * @param initialState
+ * the initial state of the checkbox
+ */
+ public CheckBox(String caption, boolean initialState) {
+ this(caption);
+ setValue(initialState);
+ }
+
+ /**
+ * Creates a new checkbox that is connected to a boolean property.
+ *
+ * @param state
+ * the Initial state of the switch-button.
+ * @param dataSource
+ */
+ public CheckBox(String caption, Property<?> dataSource) {
+ this(caption);
+ setPropertyDataSource(dataSource);
+ }
+
+ @Override
+ public Class<Boolean> getType() {
+ return Boolean.class;
+ }
+
+ @Override
+ public CheckBoxState getState() {
+ return (CheckBoxState) super.getState();
+ }
+
+ @Override
+ protected void setInternalValue(Boolean newValue) {
+ super.setInternalValue(newValue);
+ if (newValue == null) {
+ newValue = false;
+ }
+ getState().setChecked(newValue);
+ }
+
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ /**
+ * Get the boolean value of the button state.
+ *
+ * @return True iff the button is pressed down or checked.
+ *
+ * @deprecated Use {@link #getValue()} instead and, if needed, handle null
+ * values.
+ */
+ @Deprecated
+ public boolean booleanValue() {
+ Boolean value = getValue();
+ return (null == value) ? false : value.booleanValue();
+ }
+}
diff --git a/server/src/com/vaadin/ui/ComboBox.java b/server/src/com/vaadin/ui/ComboBox.java
new file mode 100644
index 0000000000..6286dad124
--- /dev/null
+++ b/server/src/com/vaadin/ui/ComboBox.java
@@ -0,0 +1,116 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.ui.combobox.VFilterSelect;
+
+/**
+ * A filtering dropdown single-select. Suitable for newItemsAllowed, but it's
+ * turned of by default to avoid mistakes. Items are filtered based on user
+ * input, and loaded dynamically ("lazy-loading") from the server. You can turn
+ * on newItemsAllowed and change filtering mode (and also turn it off), but you
+ * can not turn on multi-select mode.
+ *
+ */
+@SuppressWarnings("serial")
+public class ComboBox extends Select {
+
+ private String inputPrompt = null;
+
+ /**
+ * If text input is not allowed, the ComboBox behaves like a pretty
+ * NativeSelect - the user can not enter any text and clicking the text
+ * field opens the drop down with options
+ */
+ private boolean textInputAllowed = true;
+
+ public ComboBox() {
+ setNewItemsAllowed(false);
+ }
+
+ public ComboBox(String caption, Collection<?> options) {
+ super(caption, options);
+ setNewItemsAllowed(false);
+ }
+
+ public ComboBox(String caption, Container dataSource) {
+ super(caption, dataSource);
+ setNewItemsAllowed(false);
+ }
+
+ public ComboBox(String caption) {
+ super(caption);
+ setNewItemsAllowed(false);
+ }
+
+ /**
+ * Gets the current input prompt.
+ *
+ * @see #setInputPrompt(String)
+ * @return the current input prompt, or null if not enabled
+ */
+ public String getInputPrompt() {
+ return inputPrompt;
+ }
+
+ /**
+ * Sets the input prompt - a textual prompt that is displayed when the
+ * select would otherwise be empty, to prompt the user for input.
+ *
+ * @param inputPrompt
+ * the desired input prompt, or null to disable
+ */
+ public void setInputPrompt(String inputPrompt) {
+ this.inputPrompt = inputPrompt;
+ requestRepaint();
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (inputPrompt != null) {
+ target.addAttribute("prompt", inputPrompt);
+ }
+ super.paintContent(target);
+
+ if (!textInputAllowed) {
+ target.addAttribute(VFilterSelect.ATTR_NO_TEXT_INPUT, true);
+ }
+ }
+
+ /**
+ * Sets whether it is possible to input text into the field or whether the
+ * field area of the component is just used to show what is selected. By
+ * disabling text input, the comboBox will work in the same way as a
+ * {@link NativeSelect}
+ *
+ * @see #isTextInputAllowed()
+ *
+ * @param textInputAllowed
+ * true to allow entering text, false to just show the current
+ * selection
+ */
+ public void setTextInputAllowed(boolean textInputAllowed) {
+ this.textInputAllowed = textInputAllowed;
+ requestRepaint();
+ }
+
+ /**
+ * Returns true if the user can enter text into the field to either filter
+ * the selections or enter a new value if {@link #isNewItemsAllowed()}
+ * returns true. If text input is disabled, the comboBox will work in the
+ * same way as a {@link NativeSelect}
+ *
+ * @return
+ */
+ public boolean isTextInputAllowed() {
+ return textInputAllowed;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Component.java b/server/src/com/vaadin/ui/Component.java
new file mode 100644
index 0000000000..a2c257ab68
--- /dev/null
+++ b/server/src/com/vaadin/ui/Component.java
@@ -0,0 +1,1047 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.Locale;
+
+import com.vaadin.Application;
+import com.vaadin.event.FieldEvents;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Sizeable;
+import com.vaadin.terminal.VariableOwner;
+import com.vaadin.terminal.gwt.server.ClientConnector;
+
+/**
+ * {@code Component} is the top-level interface that is and must be implemented
+ * by all Vaadin components. {@code Component} is paired with
+ * {@link AbstractComponent}, which provides a default implementation for all
+ * the methods defined in this interface.
+ *
+ * <p>
+ * Components are laid out in the user interface hierarchically. The layout is
+ * managed by layout components, or more generally by components that implement
+ * the {@link ComponentContainer} interface. Such a container is the
+ * <i>parent</i> of the contained components.
+ * </p>
+ *
+ * <p>
+ * The {@link #getParent()} method allows retrieving the parent component of a
+ * component. While there is a {@link #setParent(Component) setParent()}, you
+ * rarely need it as you normally add components with the
+ * {@link ComponentContainer#addComponent(Component) addComponent()} method of
+ * the layout or other {@code ComponentContainer}, which automatically sets the
+ * parent.
+ * </p>
+ *
+ * <p>
+ * A component becomes <i>attached</i> to an application (and the
+ * {@link #attach()} is called) when it or one of its parents is attached to the
+ * main window of the application through its containment hierarchy.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Component extends ClientConnector, Sizeable, Serializable {
+
+ /**
+ * Gets all user-defined CSS style names of a component. If the component
+ * has multiple style names defined, the return string is a space-separated
+ * list of style names. Built-in style names defined in Vaadin or GWT are
+ * not returned.
+ *
+ * <p>
+ * The style names are returned only in the basic form in which they were
+ * added; each user-defined style name shows as two CSS style class names in
+ * the rendered HTML: one as it was given and one prefixed with the
+ * component-specific style name. Only the former is returned.
+ * </p>
+ *
+ * @return the style name or a space-separated list of user-defined style
+ * names of the component
+ * @see #setStyleName(String)
+ * @see #addStyleName(String)
+ * @see #removeStyleName(String)
+ */
+ public String getStyleName();
+
+ /**
+ * Sets one or more user-defined style names of the component, replacing any
+ * previous user-defined styles. Multiple styles can be specified as a
+ * space-separated list of style names. The style names must be valid CSS
+ * class names and should not conflict with any built-in style names in
+ * Vaadin or GWT.
+ *
+ * <pre>
+ * Label label = new Label(&quot;This text has a lot of style&quot;);
+ * label.setStyleName(&quot;myonestyle myotherstyle&quot;);
+ * </pre>
+ *
+ * <p>
+ * Each style name will occur in two versions: one as specified and one that
+ * is prefixed with the style name of the component. For example, if you
+ * have a {@code Button} component and give it "{@code mystyle}" style, the
+ * component will have both "{@code mystyle}" and "{@code v-button-mystyle}"
+ * styles. You could then style the component either with:
+ * </p>
+ *
+ * <pre>
+ * .myonestyle {background: blue;}
+ * </pre>
+ *
+ * <p>
+ * or
+ * </p>
+ *
+ * <pre>
+ * .v-button-myonestyle {background: blue;}
+ * </pre>
+ *
+ * <p>
+ * It is normally a good practice to use {@link #addStyleName(String)
+ * addStyleName()} rather than this setter, as different software
+ * abstraction layers can then add their own styles without accidentally
+ * removing those defined in other layers.
+ * </p>
+ *
+ * <p>
+ * This method will trigger a {@link RepaintRequestEvent}.
+ * </p>
+ *
+ * @param style
+ * the new style or styles of the component as a space-separated
+ * list
+ * @see #getStyleName()
+ * @see #addStyleName(String)
+ * @see #removeStyleName(String)
+ */
+ public void setStyleName(String style);
+
+ /**
+ * Adds a style name to component. The style name will be rendered as a HTML
+ * class name, which can be used in a CSS definition.
+ *
+ * <pre>
+ * Label label = new Label(&quot;This text has style&quot;);
+ * label.addStyleName(&quot;mystyle&quot;);
+ * </pre>
+ *
+ * <p>
+ * Each style name will occur in two versions: one as specified and one that
+ * is prefixed wil the style name of the component. For example, if you have
+ * a {@code Button} component and give it "{@code mystyle}" style, the
+ * component will have both "{@code mystyle}" and "{@code v-button-mystyle}"
+ * styles. You could then style the component either with:
+ * </p>
+ *
+ * <pre>
+ * .mystyle {font-style: italic;}
+ * </pre>
+ *
+ * <p>
+ * or
+ * </p>
+ *
+ * <pre>
+ * .v-button-mystyle {font-style: italic;}
+ * </pre>
+ *
+ * <p>
+ * This method will trigger a {@link RepaintRequestEvent}.
+ * </p>
+ *
+ * @param style
+ * the new style to be added to the component
+ * @see #getStyleName()
+ * @see #setStyleName(String)
+ * @see #removeStyleName(String)
+ */
+ public void addStyleName(String style);
+
+ /**
+ * Removes one or more style names from component. Multiple styles can be
+ * specified as a space-separated list of style names.
+ *
+ * <p>
+ * The parameter must be a valid CSS style name. Only user-defined style
+ * names added with {@link #addStyleName(String) addStyleName()} or
+ * {@link #setStyleName(String) setStyleName()} can be removed; built-in
+ * style names defined in Vaadin or GWT can not be removed.
+ * </p>
+ *
+ * * This method will trigger a {@link RepaintRequestEvent}.
+ *
+ * @param style
+ * the style name or style names to be removed
+ * @see #getStyleName()
+ * @see #setStyleName(String)
+ * @see #addStyleName(String)
+ */
+ public void removeStyleName(String style);
+
+ /**
+ * Tests whether the component is enabled or not. A user can not interact
+ * with disabled components. Disabled components are rendered in a style
+ * that indicates the status, usually in gray color. Children of a disabled
+ * component are also disabled. Components are enabled by default.
+ *
+ * <p>
+ * As a security feature, all updates for disabled components are blocked on
+ * the server-side.
+ * </p>
+ *
+ * <p>
+ * Note that this method only returns the status of the component and does
+ * not take parents into account. Even though this method returns true the
+ * component can be disabled to the user if a parent is disabled.
+ * </p>
+ *
+ * @return <code>true</code> if the component and its parent are enabled,
+ * <code>false</code> otherwise.
+ * @see VariableOwner#isEnabled()
+ */
+ public boolean isEnabled();
+
+ /**
+ * Enables or disables the component. The user can not interact disabled
+ * components, which are shown with a style that indicates the status,
+ * usually shaded in light gray color. Components are enabled by default.
+ *
+ * <pre>
+ * Button enabled = new Button(&quot;Enabled&quot;);
+ * enabled.setEnabled(true); // The default
+ * layout.addComponent(enabled);
+ *
+ * Button disabled = new Button(&quot;Disabled&quot;);
+ * disabled.setEnabled(false);
+ * layout.addComponent(disabled);
+ * </pre>
+ *
+ * <p>
+ * This method will trigger a {@link RepaintRequestEvent} for the component
+ * and, if it is a {@link ComponentContainer}, for all its children
+ * recursively.
+ * </p>
+ *
+ * @param enabled
+ * a boolean value specifying if the component should be enabled
+ * or not
+ */
+ public void setEnabled(boolean enabled);
+
+ /**
+ * Tests the <i>visibility</i> property of the component.
+ *
+ * <p>
+ * Visible components are drawn in the user interface, while invisible ones
+ * are not. The effect is not merely a cosmetic CSS change - no information
+ * about an invisible component will be sent to the client. The effect is
+ * thus the same as removing the component from its parent. Making a
+ * component invisible through this property can alter the positioning of
+ * other components.
+ * </p>
+ *
+ * <p>
+ * A component is visible only if all its parents are also visible. This is
+ * not checked by this method though, so even if this method returns true,
+ * the component can be hidden from the user because a parent is set to
+ * invisible.
+ * </p>
+ *
+ * @return <code>true</code> if the component has been set to be visible in
+ * the user interface, <code>false</code> if not
+ * @see #setVisible(boolean)
+ * @see #attach()
+ */
+ public boolean isVisible();
+
+ /**
+ * Sets the visibility of the component.
+ *
+ * <p>
+ * Visible components are drawn in the user interface, while invisible ones
+ * are not. The effect is not merely a cosmetic CSS change - no information
+ * about an invisible component will be sent to the client. The effect is
+ * thus the same as removing the component from its parent.
+ * </p>
+ *
+ * <pre>
+ * TextField readonly = new TextField(&quot;Read-Only&quot;);
+ * readonly.setValue(&quot;You can't see this!&quot;);
+ * readonly.setVisible(false);
+ * layout.addComponent(readonly);
+ * </pre>
+ *
+ * <p>
+ * A component is visible only if all of its parents are also visible. If a
+ * component is explicitly set to be invisible, changes in the visibility of
+ * its parents will not change the visibility of the component.
+ * </p>
+ *
+ * @param visible
+ * the boolean value specifying if the component should be
+ * visible after the call or not.
+ * @see #isVisible()
+ */
+ public void setVisible(boolean visible);
+
+ /**
+ * Gets the parent component of the component.
+ *
+ * <p>
+ * Components can be nested but a component can have only one parent. A
+ * component that contains other components, that is, can be a parent,
+ * should usually inherit the {@link ComponentContainer} interface.
+ * </p>
+ *
+ * @return the parent component
+ */
+ @Override
+ public HasComponents getParent();
+
+ /**
+ * Tests whether the component is in the read-only mode. The user can not
+ * change the value of a read-only component. As only {@link Field}
+ * components normally have a value that can be input or changed by the
+ * user, this is mostly relevant only to field components, though not
+ * restricted to them.
+ *
+ * <p>
+ * Notice that the read-only mode only affects whether the user can change
+ * the <i>value</i> of the component; it is possible to, for example, scroll
+ * a read-only table.
+ * </p>
+ *
+ * <p>
+ * The method will return {@code true} if the component or any of its
+ * parents is in the read-only mode.
+ * </p>
+ *
+ * @return <code>true</code> if the component or any of its parents is in
+ * read-only mode, <code>false</code> if not.
+ * @see #setReadOnly(boolean)
+ */
+ public boolean isReadOnly();
+
+ /**
+ * Sets the read-only mode of the component to the specified mode. The user
+ * can not change the value of a read-only component.
+ *
+ * <p>
+ * As only {@link Field} components normally have a value that can be input
+ * or changed by the user, this is mostly relevant only to field components,
+ * though not restricted to them.
+ * </p>
+ *
+ * <p>
+ * Notice that the read-only mode only affects whether the user can change
+ * the <i>value</i> of the component; it is possible to, for example, scroll
+ * a read-only table.
+ * </p>
+ *
+ * <p>
+ * This method will trigger a {@link RepaintRequestEvent}.
+ * </p>
+ *
+ * @param readOnly
+ * a boolean value specifying whether the component is put
+ * read-only mode or not
+ */
+ public void setReadOnly(boolean readOnly);
+
+ /**
+ * Gets the caption of the component.
+ *
+ * <p>
+ * See {@link #setCaption(String)} for a detailed description of the
+ * caption.
+ * </p>
+ *
+ * @return the caption of the component or {@code null} if the caption is
+ * not set.
+ * @see #setCaption(String)
+ */
+ public String getCaption();
+
+ /**
+ * Sets the caption of the component.
+ *
+ * <p>
+ * A <i>caption</i> is an explanatory textual label accompanying a user
+ * interface component, usually shown above, left of, or inside the
+ * component. <i>Icon</i> (see {@link #setIcon(Resource) setIcon()} is
+ * closely related to caption and is usually displayed horizontally before
+ * or after it, depending on the component and the containing layout.
+ * </p>
+ *
+ * <p>
+ * The caption can usually also be given as the first parameter to a
+ * constructor, though some components do not support it.
+ * </p>
+ *
+ * <pre>
+ * RichTextArea area = new RichTextArea();
+ * area.setCaption(&quot;You can edit stuff here&quot;);
+ * area.setValue(&quot;&lt;h1&gt;Helpful Heading&lt;/h1&gt;&quot;
+ * + &quot;&lt;p&gt;All this is for you to edit.&lt;/p&gt;&quot;);
+ * </pre>
+ *
+ * <p>
+ * The contents of a caption are automatically quoted, so no raw XHTML can
+ * be rendered in a caption. The validity of the used character encoding,
+ * usually UTF-8, is not checked.
+ * </p>
+ *
+ * <p>
+ * The caption of a component is, by default, managed and displayed by the
+ * layout component or component container in which the component is placed.
+ * For example, the {@link VerticalLayout} component shows the captions
+ * left-aligned above the contained components, while the {@link FormLayout}
+ * component shows the captions on the left side of the vertically laid
+ * components, with the captions and their associated components
+ * left-aligned in their own columns. The {@link CustomComponent} does not
+ * manage the caption of its composition root, so if the root component has
+ * a caption, it will not be rendered. Some components, such as
+ * {@link Button} and {@link Panel}, manage the caption themselves and
+ * display it inside the component.
+ * </p>
+ *
+ * <p>
+ * This method will trigger a {@link RepaintRequestEvent}. A
+ * reimplementation should call the superclass implementation.
+ * </p>
+ *
+ * @param caption
+ * the new caption for the component. If the caption is
+ * {@code null}, no caption is shown and it does not normally
+ * take any space
+ */
+ public void setCaption(String caption);
+
+ /**
+ * Gets the icon resource of the component.
+ *
+ * <p>
+ * See {@link #setIcon(Resource)} for a detailed description of the icon.
+ * </p>
+ *
+ * @return the icon resource of the component or {@code null} if the
+ * component has no icon
+ * @see #setIcon(Resource)
+ */
+ public Resource getIcon();
+
+ /**
+ * Sets the icon of the component.
+ *
+ * <p>
+ * An icon is an explanatory graphical label accompanying a user interface
+ * component, usually shown above, left of, or inside the component. Icon is
+ * closely related to caption (see {@link #setCaption(String) setCaption()})
+ * and is usually displayed horizontally before or after it, depending on
+ * the component and the containing layout.
+ * </p>
+ *
+ * <p>
+ * The image is loaded by the browser from a resource, typically a
+ * {@link com.vaadin.terminal.ThemeResource}.
+ * </p>
+ *
+ * <pre>
+ * // Component with an icon from a custom theme
+ * TextField name = new TextField(&quot;Name&quot;);
+ * name.setIcon(new ThemeResource(&quot;icons/user.png&quot;));
+ * layout.addComponent(name);
+ *
+ * // Component with an icon from another theme ('runo')
+ * Button ok = new Button(&quot;OK&quot;);
+ * ok.setIcon(new ThemeResource(&quot;../runo/icons/16/ok.png&quot;));
+ * layout.addComponent(ok);
+ * </pre>
+ *
+ * <p>
+ * The icon of a component is, by default, managed and displayed by the
+ * layout component or component container in which the component is placed.
+ * For example, the {@link VerticalLayout} component shows the icons
+ * left-aligned above the contained components, while the {@link FormLayout}
+ * component shows the icons on the left side of the vertically laid
+ * components, with the icons and their associated components left-aligned
+ * in their own columns. The {@link CustomComponent} does not manage the
+ * icon of its composition root, so if the root component has an icon, it
+ * will not be rendered.
+ * </p>
+ *
+ * <p>
+ * An icon will be rendered inside an HTML element that has the
+ * {@code v-icon} CSS style class. The containing layout may enclose an icon
+ * and a caption inside elements related to the caption, such as
+ * {@code v-caption} .
+ * </p>
+ *
+ * This method will trigger a {@link RepaintRequestEvent}.
+ *
+ * @param icon
+ * the icon of the component. If null, no icon is shown and it
+ * does not normally take any space.
+ * @see #getIcon()
+ * @see #setCaption(String)
+ */
+ public void setIcon(Resource icon);
+
+ /**
+ * Gets the Root the component is attached to.
+ *
+ * <p>
+ * If the component is not attached to a Root through a component
+ * containment hierarchy, <code>null</code> is returned.
+ * </p>
+ *
+ * @return the Root of the component or <code>null</code> if it is not
+ * attached to a Root
+ */
+ @Override
+ public Root getRoot();
+
+ /**
+ * Gets the application object to which the component is attached.
+ *
+ * <p>
+ * The method will return {@code null} if the component is not currently
+ * attached to an application.
+ * </p>
+ *
+ * <p>
+ * Getting a null value is often a problem in constructors of regular
+ * components and in the initializers of custom composite components. A
+ * standard workaround is to use {@link Application#getCurrent()} to
+ * retrieve the application instance that the current request relates to.
+ * Another way is to move the problematic initialization to
+ * {@link #attach()}, as described in the documentation of the method.
+ * </p>
+ *
+ * @return the parent application of the component or <code>null</code>.
+ * @see #attach()
+ */
+ public Application getApplication();
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>
+ * Reimplementing the {@code attach()} method is useful for tasks that need
+ * to get a reference to the parent, window, or application object with the
+ * {@link #getParent()}, {@link #getRoot()}, and {@link #getApplication()}
+ * methods. A component does not yet know these objects in the constructor,
+ * so in such case, the methods will return {@code null}. For example, the
+ * following is invalid:
+ * </p>
+ *
+ * <pre>
+ * public class AttachExample extends CustomComponent {
+ * public AttachExample() {
+ * // ERROR: We can't access the application object yet.
+ * ClassResource r = new ClassResource(&quot;smiley.jpg&quot;, getApplication());
+ * Embedded image = new Embedded(&quot;Image:&quot;, r);
+ * setCompositionRoot(image);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * Adding a component to an application triggers calling the
+ * {@link #attach()} method for the component. Correspondingly, removing a
+ * component from a container triggers calling the {@link #detach()} method.
+ * If the parent of an added component is already connected to the
+ * application, the {@code attach()} is called immediately from
+ * {@link #setParent(Component)}.
+ * </p>
+ * <p>
+ * This method must call {@link Root#componentAttached(Component)} to let
+ * the Root know that a new Component has been attached.
+ * </p>
+ *
+ *
+ * <pre>
+ * public class AttachExample extends CustomComponent {
+ * public AttachExample() {
+ * }
+ *
+ * &#064;Override
+ * public void attach() {
+ * super.attach(); // Must call.
+ *
+ * // Now we know who ultimately owns us.
+ * ClassResource r = new ClassResource(&quot;smiley.jpg&quot;, getApplication());
+ * Embedded image = new Embedded(&quot;Image:&quot;, r);
+ * setCompositionRoot(image);
+ * }
+ * }
+ * </pre>
+ */
+ @Override
+ public void attach();
+
+ /**
+ * Gets the locale of the component.
+ *
+ * <p>
+ * If a component does not have a locale set, the locale of its parent is
+ * returned, and so on. Eventually, if no parent has locale set, the locale
+ * of the application is returned. If the application does not have a locale
+ * set, it is determined by <code>Locale.getDefault()</code>.
+ * </p>
+ *
+ * <p>
+ * As the component must be attached before its locale can be acquired,
+ * using this method in the internationalization of component captions, etc.
+ * is generally not feasible. For such use case, we recommend using an
+ * otherwise acquired reference to the application locale.
+ * </p>
+ *
+ * @return Locale of this component or {@code null} if the component and
+ * none of its parents has a locale set and the component is not yet
+ * attached to an application.
+ */
+ public Locale getLocale();
+
+ /**
+ * Returns the current shared state bean for the component. The state (or
+ * changes to it) is communicated from the server to the client.
+ *
+ * Subclasses can use a more specific return type for this method.
+ *
+ * @return The state object for the component
+ *
+ * @since 7.0
+ */
+ @Override
+ public ComponentState getState();
+
+ /**
+ * Called before the shared state is sent to the client. Gives the component
+ * an opportunity to set computed/dynamic state values e.g. state values
+ * that depend on other component features.
+ * <p>
+ * This method must not alter the component hierarchy in any way.
+ * </p>
+ *
+ * @since 7.0
+ */
+ public void updateState();
+
+ /**
+ * Adds an unique id for component that get's transferred to terminal for
+ * testing purposes. Keeping identifiers unique is the responsibility of the
+ * programmer.
+ *
+ * @param id
+ * An alphanumeric id
+ */
+ public void setDebugId(String id);
+
+ /**
+ * Get's currently set debug identifier
+ *
+ * @return current debug id, null if not set
+ */
+ public String getDebugId();
+
+ /* Component event framework */
+
+ /**
+ * Superclass of all component originated events.
+ *
+ * <p>
+ * Events are the basis of all user interaction handling in Vaadin. To
+ * handle events, you provide a listener object that receives the events of
+ * the particular event type.
+ * </p>
+ *
+ * <pre>
+ * Button button = new Button(&quot;Click Me!&quot;);
+ * button.addListener(new Button.ClickListener() {
+ * public void buttonClick(ClickEvent event) {
+ * getWindow().showNotification(&quot;Thank You!&quot;);
+ * }
+ * });
+ * layout.addComponent(button);
+ * </pre>
+ *
+ * <p>
+ * Notice that while each of the event types have their corresponding
+ * listener types; the listener interfaces are not required to inherit the
+ * {@code Component.Listener} interface.
+ * </p>
+ *
+ * @see Component.Listener
+ */
+ @SuppressWarnings("serial")
+ public class Event extends EventObject {
+
+ /**
+ * Constructs a new event with the specified source component.
+ *
+ * @param source
+ * the source component of the event
+ */
+ public Event(Component source) {
+ super(source);
+ }
+
+ /**
+ * Gets the component where the event occurred.
+ *
+ * @return the source component of the event
+ */
+ public Component getComponent() {
+ return (Component) getSource();
+ }
+ }
+
+ /**
+ * Listener interface for receiving <code>Component.Event</code>s.
+ *
+ * <p>
+ * Listener interfaces are the basis of all user interaction handling in
+ * Vaadin. You have or create a listener object that receives the events.
+ * All event types have their corresponding listener types; they are not,
+ * however, required to inherit the {@code Component.Listener} interface,
+ * and they rarely do so.
+ * </p>
+ *
+ * <p>
+ * This generic listener interface is useful typically when you wish to
+ * handle events from different component types in a single listener method
+ * ({@code componentEvent()}. If you handle component events in an anonymous
+ * listener class, you normally use the component specific listener class,
+ * such as {@link com.vaadin.ui.Button.ClickEvent}.
+ * </p>
+ *
+ * <pre>
+ * class Listening extends CustomComponent implements Listener {
+ * Button ok; // Stored for determining the source of an event
+ *
+ * Label status; // For displaying info about the event
+ *
+ * public Listening() {
+ * VerticalLayout layout = new VerticalLayout();
+ *
+ * // Some miscellaneous component
+ * TextField name = new TextField(&quot;Say it all here&quot;);
+ * name.addListener(this);
+ * name.setImmediate(true);
+ * layout.addComponent(name);
+ *
+ * // Handle button clicks as generic events instead
+ * // of Button.ClickEvent events
+ * ok = new Button(&quot;OK&quot;);
+ * ok.addListener(this);
+ * layout.addComponent(ok);
+ *
+ * // For displaying information about an event
+ * status = new Label(&quot;&quot;);
+ * layout.addComponent(status);
+ *
+ * setCompositionRoot(layout);
+ * }
+ *
+ * public void componentEvent(Event event) {
+ * // Act according to the source of the event
+ * if (event.getSource() == ok
+ * &amp;&amp; event.getClass() == Button.ClickEvent.class)
+ * getWindow().showNotification(&quot;Click!&quot;);
+ *
+ * // Display source component and event class names
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
+ * }
+ * }
+ *
+ * Listening listening = new Listening();
+ * layout.addComponent(listening);
+ * </pre>
+ *
+ * @see Component#addListener(Listener)
+ */
+ public interface Listener extends EventListener, Serializable {
+
+ /**
+ * Notifies the listener of a component event.
+ *
+ * <p>
+ * As the event can typically come from one of many source components,
+ * you may need to differentiate between the event source by component
+ * reference, class, etc.
+ * </p>
+ *
+ * <pre>
+ * public void componentEvent(Event event) {
+ * // Act according to the source of the event
+ * if (event.getSource() == ok &amp;&amp; event.getClass() == Button.ClickEvent.class)
+ * getWindow().showNotification(&quot;Click!&quot;);
+ *
+ * // Display source component and event class names
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
+ * }
+ * </pre>
+ *
+ * @param event
+ * the event that has occured.
+ */
+ public void componentEvent(Component.Event event);
+ }
+
+ /**
+ * Registers a new (generic) component event listener for the component.
+ *
+ * <pre>
+ * class Listening extends CustomComponent implements Listener {
+ * // Stored for determining the source of an event
+ * Button ok;
+ *
+ * Label status; // For displaying info about the event
+ *
+ * public Listening() {
+ * VerticalLayout layout = new VerticalLayout();
+ *
+ * // Some miscellaneous component
+ * TextField name = new TextField(&quot;Say it all here&quot;);
+ * name.addListener(this);
+ * name.setImmediate(true);
+ * layout.addComponent(name);
+ *
+ * // Handle button clicks as generic events instead
+ * // of Button.ClickEvent events
+ * ok = new Button(&quot;OK&quot;);
+ * ok.addListener(this);
+ * layout.addComponent(ok);
+ *
+ * // For displaying information about an event
+ * status = new Label(&quot;&quot;);
+ * layout.addComponent(status);
+ *
+ * setCompositionRoot(layout);
+ * }
+ *
+ * public void componentEvent(Event event) {
+ * // Act according to the source of the event
+ * if (event.getSource() == ok)
+ * getWindow().showNotification(&quot;Click!&quot;);
+ *
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
+ * }
+ * }
+ *
+ * Listening listening = new Listening();
+ * layout.addComponent(listening);
+ * </pre>
+ *
+ * @param listener
+ * the new Listener to be registered.
+ * @see Component.Event
+ * @see #removeListener(Listener)
+ */
+ public void addListener(Component.Listener listener);
+
+ /**
+ * Removes a previously registered component event listener from this
+ * component.
+ *
+ * @param listener
+ * the listener to be removed.
+ * @see #addListener(Listener)
+ */
+ public void removeListener(Component.Listener listener);
+
+ /**
+ * Class of all component originated error events.
+ *
+ * <p>
+ * The component error event is normally fired by
+ * {@link AbstractComponent#setComponentError(ErrorMessage)}. The component
+ * errors are set by the framework in some situations and can be set by user
+ * code. They are indicated in a component with an error indicator.
+ * </p>
+ */
+ @SuppressWarnings("serial")
+ public class ErrorEvent extends Event {
+
+ private final ErrorMessage message;
+
+ /**
+ * Constructs a new event with a specified source component.
+ *
+ * @param message
+ * the error message.
+ * @param component
+ * the source component.
+ */
+ public ErrorEvent(ErrorMessage message, Component component) {
+ super(component);
+ this.message = message;
+ }
+
+ /**
+ * Gets the error message.
+ *
+ * @return the error message.
+ */
+ public ErrorMessage getErrorMessage() {
+ return message;
+ }
+ }
+
+ /**
+ * Listener interface for receiving <code>Component.Errors</code>s.
+ */
+ public interface ErrorListener extends EventListener, Serializable {
+
+ /**
+ * Notifies the listener of a component error.
+ *
+ * @param event
+ * the event that has occured.
+ */
+ public void componentError(Component.ErrorEvent event);
+ }
+
+ /**
+ * A sub-interface implemented by components that can obtain input focus.
+ * This includes all {@link Field} components as well as some other
+ * components, such as {@link Upload}.
+ *
+ * <p>
+ * Focus can be set with {@link #focus()}. This interface does not provide
+ * an accessor that would allow finding out the currently focused component;
+ * focus information can be acquired for some (but not all) {@link Field}
+ * components through the {@link com.vaadin.event.FieldEvents.FocusListener}
+ * and {@link com.vaadin.event.FieldEvents.BlurListener} interfaces.
+ * </p>
+ *
+ * @see FieldEvents
+ */
+ public interface Focusable extends Component {
+
+ /**
+ * Sets the focus to this component.
+ *
+ * <pre>
+ * Form loginBox = new Form();
+ * loginBox.setCaption(&quot;Login&quot;);
+ * layout.addComponent(loginBox);
+ *
+ * // Create the first field which will be focused
+ * TextField username = new TextField(&quot;User name&quot;);
+ * loginBox.addField(&quot;username&quot;, username);
+ *
+ * // Set focus to the user name
+ * username.focus();
+ *
+ * TextField password = new TextField(&quot;Password&quot;);
+ * loginBox.addField(&quot;password&quot;, password);
+ *
+ * Button login = new Button(&quot;Login&quot;);
+ * loginBox.getFooter().addComponent(login);
+ * </pre>
+ *
+ * <p>
+ * Notice that this interface does not provide an accessor that would
+ * allow finding out the currently focused component. Focus information
+ * can be acquired for some (but not all) {@link Field} components
+ * through the {@link com.vaadin.event.FieldEvents.FocusListener} and
+ * {@link com.vaadin.event.FieldEvents.BlurListener} interfaces.
+ * </p>
+ *
+ * @see com.vaadin.event.FieldEvents
+ * @see com.vaadin.event.FieldEvents.FocusEvent
+ * @see com.vaadin.event.FieldEvents.FocusListener
+ * @see com.vaadin.event.FieldEvents.BlurEvent
+ * @see com.vaadin.event.FieldEvents.BlurListener
+ */
+ public void focus();
+
+ /**
+ * Gets the <i>tabulator index</i> of the {@code Focusable} component.
+ *
+ * @return tab index set for the {@code Focusable} component
+ * @see #setTabIndex(int)
+ */
+ public int getTabIndex();
+
+ /**
+ * Sets the <i>tabulator index</i> of the {@code Focusable} component.
+ * The tab index property is used to specify the order in which the
+ * fields are focused when the user presses the Tab key. Components with
+ * a defined tab index are focused sequentially first, and then the
+ * components with no tab index.
+ *
+ * <pre>
+ * Form loginBox = new Form();
+ * loginBox.setCaption(&quot;Login&quot;);
+ * layout.addComponent(loginBox);
+ *
+ * // Create the first field which will be focused
+ * TextField username = new TextField(&quot;User name&quot;);
+ * loginBox.addField(&quot;username&quot;, username);
+ *
+ * // Set focus to the user name
+ * username.focus();
+ *
+ * TextField password = new TextField(&quot;Password&quot;);
+ * loginBox.addField(&quot;password&quot;, password);
+ *
+ * Button login = new Button(&quot;Login&quot;);
+ * loginBox.getFooter().addComponent(login);
+ *
+ * // An additional component which natural focus order would
+ * // be after the button.
+ * CheckBox remember = new CheckBox(&quot;Remember me&quot;);
+ * loginBox.getFooter().addComponent(remember);
+ *
+ * username.setTabIndex(1);
+ * password.setTabIndex(2);
+ * remember.setTabIndex(3); // Different than natural place
+ * login.setTabIndex(4);
+ * </pre>
+ *
+ * <p>
+ * After all focusable user interface components are done, the browser
+ * can begin again from the component with the smallest tab index, or it
+ * can take the focus out of the page, for example, to the location bar.
+ * </p>
+ *
+ * <p>
+ * If the tab index is not set (is set to zero), the default tab order
+ * is used. The order is somewhat browser-dependent, but generally
+ * follows the HTML structure of the page.
+ * </p>
+ *
+ * <p>
+ * A negative value means that the component is completely removed from
+ * the tabulation order and can not be reached by pressing the Tab key
+ * at all.
+ * </p>
+ *
+ * @param tabIndex
+ * the tab order of this component. Indexes usually start
+ * from 1. Zero means that default tab order should be used.
+ * A negative value means that the field should not be
+ * included in the tabbing sequence.
+ * @see #getTabIndex()
+ */
+ public void setTabIndex(int tabIndex);
+
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/ComponentContainer.java b/server/src/com/vaadin/ui/ComponentContainer.java
new file mode 100644
index 0000000000..8182d54b56
--- /dev/null
+++ b/server/src/com/vaadin/ui/ComponentContainer.java
@@ -0,0 +1,222 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+/**
+ * Extension to the {@link Component} interface which adds to it the capacity to
+ * contain other components. All UI elements that can have child elements
+ * implement this interface.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface ComponentContainer extends HasComponents {
+
+ /**
+ * Adds the component into this container.
+ *
+ * @param c
+ * the component to be added.
+ */
+ public void addComponent(Component c);
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * the component to be removed.
+ */
+ public void removeComponent(Component c);
+
+ /**
+ * Removes all components from this container.
+ */
+ public void removeAllComponents();
+
+ /**
+ * Replaces the component in the container with another one without changing
+ * position.
+ *
+ * <p>
+ * This method replaces component with another one is such way that the new
+ * component overtakes the position of the old component. If the old
+ * component is not in the container, the new component is added to the
+ * container. If the both component are already in the container, their
+ * positions are swapped. Component attach and detach events should be taken
+ * care as with add and remove.
+ * </p>
+ *
+ * @param oldComponent
+ * the old component that will be replaced.
+ * @param newComponent
+ * the new component to be replaced.
+ */
+ public void replaceComponent(Component oldComponent, Component newComponent);
+
+ /**
+ * Gets the number of children this {@link ComponentContainer} has. This
+ * must be symmetric with what {@link #getComponentIterator()} returns.
+ *
+ * @return The number of child components this container has.
+ * @since 7.0.0
+ */
+ public int getComponentCount();
+
+ /**
+ * Moves all components from an another container into this container. The
+ * components are removed from <code>source</code>.
+ *
+ * @param source
+ * the container which contains the components that are to be
+ * moved to this container.
+ */
+ public void moveComponentsFrom(ComponentContainer source);
+
+ /**
+ * Listens the component attach events.
+ *
+ * @param listener
+ * the listener to add.
+ */
+ public void addListener(ComponentAttachListener listener);
+
+ /**
+ * Stops the listening component attach events.
+ *
+ * @param listener
+ * the listener to removed.
+ */
+ public void removeListener(ComponentAttachListener listener);
+
+ /**
+ * Listens the component detach events.
+ */
+ public void addListener(ComponentDetachListener listener);
+
+ /**
+ * Stops the listening component detach events.
+ */
+ public void removeListener(ComponentDetachListener listener);
+
+ /**
+ * Component attach listener interface.
+ */
+ public interface ComponentAttachListener extends Serializable {
+
+ /**
+ * A new component is attached to container.
+ *
+ * @param event
+ * the component attach event.
+ */
+ public void componentAttachedToContainer(ComponentAttachEvent event);
+ }
+
+ /**
+ * Component detach listener interface.
+ */
+ public interface ComponentDetachListener extends Serializable {
+
+ /**
+ * A component has been detached from container.
+ *
+ * @param event
+ * the component detach event.
+ */
+ public void componentDetachedFromContainer(ComponentDetachEvent event);
+ }
+
+ /**
+ * Component attach event sent when a component is attached to container.
+ */
+ @SuppressWarnings("serial")
+ public class ComponentAttachEvent extends Component.Event {
+
+ private final Component component;
+
+ /**
+ * Creates a new attach event.
+ *
+ * @param container
+ * the component container the component has been detached
+ * to.
+ * @param attachedComponent
+ * the component that has been attached.
+ */
+ public ComponentAttachEvent(ComponentContainer container,
+ Component attachedComponent) {
+ super(container);
+ component = attachedComponent;
+ }
+
+ /**
+ * Gets the component container.
+ *
+ * @param the
+ * component container.
+ */
+ public ComponentContainer getContainer() {
+ return (ComponentContainer) getSource();
+ }
+
+ /**
+ * Gets the attached component.
+ *
+ * @param the
+ * attach component.
+ */
+ public Component getAttachedComponent() {
+ return component;
+ }
+ }
+
+ /**
+ * Component detach event sent when a component is detached from container.
+ */
+ @SuppressWarnings("serial")
+ public class ComponentDetachEvent extends Component.Event {
+
+ private final Component component;
+
+ /**
+ * Creates a new detach event.
+ *
+ * @param container
+ * the component container the component has been detached
+ * from.
+ * @param detachedComponent
+ * the component that has been detached.
+ */
+ public ComponentDetachEvent(ComponentContainer container,
+ Component detachedComponent) {
+ super(container);
+ component = detachedComponent;
+ }
+
+ /**
+ * Gets the component container.
+ *
+ * @param the
+ * component container.
+ */
+ public ComponentContainer getContainer() {
+ return (ComponentContainer) getSource();
+ }
+
+ /**
+ * Gets the detached component.
+ *
+ * @return the detached component.
+ */
+ public Component getDetachedComponent() {
+ return component;
+ }
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java
new file mode 100644
index 0000000000..e3d1bf86db
--- /dev/null
+++ b/server/src/com/vaadin/ui/ConnectorTracker.java
@@ -0,0 +1,320 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.terminal.AbstractClientConnector;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+import com.vaadin.terminal.gwt.server.ClientConnector;
+
+/**
+ * A class which takes care of book keeping of {@link ClientConnector}s for a
+ * Root.
+ * <p>
+ * Provides {@link #getConnector(String)} which can be used to lookup a
+ * connector from its id. This is for framework use only and should not be
+ * needed in applications.
+ * </p>
+ * <p>
+ * Tracks which {@link ClientConnector}s are dirty so they can be updated to the
+ * client when the following response is sent. A connector is dirty when an
+ * operation has been performed on it on the server and as a result of this
+ * operation new information needs to be sent to its {@link ServerConnector}.
+ * </p>
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ *
+ */
+public class ConnectorTracker implements Serializable {
+
+ private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<String, ClientConnector>();
+ private Set<ClientConnector> dirtyConnectors = new HashSet<ClientConnector>();
+
+ private Root root;
+
+ /**
+ * Gets a logger for this class
+ *
+ * @return A logger instance for logging within this class
+ *
+ */
+ public static Logger getLogger() {
+ return Logger.getLogger(ConnectorTracker.class.getName());
+ }
+
+ /**
+ * Creates a new ConnectorTracker for the given root. A tracker is always
+ * attached to a root and the root cannot be changed during the lifetime of
+ * a {@link ConnectorTracker}.
+ *
+ * @param root
+ * The root to attach to. Cannot be null.
+ */
+ public ConnectorTracker(Root root) {
+ this.root = root;
+ }
+
+ /**
+ * Register the given connector.
+ * <p>
+ * The lookup method {@link #getConnector(String)} only returns registered
+ * connectors.
+ * </p>
+ *
+ * @param connector
+ * The connector to register.
+ */
+ public void registerConnector(ClientConnector connector) {
+ String connectorId = connector.getConnectorId();
+ ClientConnector previouslyRegistered = connectorIdToConnector
+ .get(connectorId);
+ if (previouslyRegistered == null) {
+ connectorIdToConnector.put(connectorId, connector);
+ getLogger().fine(
+ "Registered " + connector.getClass().getSimpleName() + " ("
+ + connectorId + ")");
+ } else if (previouslyRegistered != connector) {
+ throw new RuntimeException("A connector with id " + connectorId
+ + " is already registered!");
+ } else {
+ getLogger().warning(
+ "An already registered connector was registered again: "
+ + connector.getClass().getSimpleName() + " ("
+ + connectorId + ")");
+ }
+
+ }
+
+ /**
+ * Unregister the given connector.
+ *
+ * <p>
+ * The lookup method {@link #getConnector(String)} only returns registered
+ * connectors.
+ * </p>
+ *
+ * @param connector
+ * The connector to unregister
+ */
+ public void unregisterConnector(ClientConnector connector) {
+ String connectorId = connector.getConnectorId();
+ if (!connectorIdToConnector.containsKey(connectorId)) {
+ getLogger().warning(
+ "Tried to unregister "
+ + connector.getClass().getSimpleName() + " ("
+ + connectorId + ") which is not registered");
+ return;
+ }
+ if (connectorIdToConnector.get(connectorId) != connector) {
+ throw new RuntimeException("The given connector with id "
+ + connectorId
+ + " is not the one that was registered for that id");
+ }
+
+ getLogger().fine(
+ "Unregistered " + connector.getClass().getSimpleName() + " ("
+ + connectorId + ")");
+ connectorIdToConnector.remove(connectorId);
+ }
+
+ /**
+ * Gets a connector by its id.
+ *
+ * @param connectorId
+ * The connector id to look for
+ * @return The connector with the given id or null if no connector has the
+ * given id
+ */
+ public ClientConnector getConnector(String connectorId) {
+ return connectorIdToConnector.get(connectorId);
+ }
+
+ /**
+ * Cleans the connector map from all connectors that are no longer attached
+ * to the application. This should only be called by the framework.
+ */
+ public void cleanConnectorMap() {
+ // remove detached components from paintableIdMap so they
+ // can be GC'ed
+ Iterator<String> iterator = connectorIdToConnector.keySet().iterator();
+
+ while (iterator.hasNext()) {
+ String connectorId = iterator.next();
+ ClientConnector connector = connectorIdToConnector.get(connectorId);
+ if (getRootForConnector(connector) != root) {
+ // If connector is no longer part of this root,
+ // remove it from the map. If it is re-attached to the
+ // application at some point it will be re-added through
+ // registerConnector(connector)
+
+ // This code should never be called as cleanup should take place
+ // in detach()
+ getLogger()
+ .warning(
+ "cleanConnectorMap unregistered connector "
+ + getConnectorAndParentInfo(connector)
+ + "). This should have been done when the connector was detached.");
+ iterator.remove();
+ }
+ }
+
+ }
+
+ /**
+ * Finds the root that the connector is attached to.
+ *
+ * @param connector
+ * The connector to lookup
+ * @return The root the connector is attached to or null if it is not
+ * attached to any root.
+ */
+ private Root getRootForConnector(ClientConnector connector) {
+ if (connector == null) {
+ return null;
+ }
+ if (connector instanceof Component) {
+ return ((Component) connector).getRoot();
+ }
+
+ return getRootForConnector(connector.getParent());
+ }
+
+ /**
+ * Mark the connector as dirty.
+ *
+ * @see #getDirtyConnectors()
+ *
+ * @param connector
+ * The connector that should be marked clean.
+ */
+ public void markDirty(ClientConnector connector) {
+ if (getLogger().isLoggable(Level.FINE)) {
+ if (!dirtyConnectors.contains(connector)) {
+ getLogger().fine(
+ getConnectorAndParentInfo(connector) + " "
+ + "is now dirty");
+ }
+ }
+
+ dirtyConnectors.add(connector);
+ }
+
+ /**
+ * Mark the connector as clean.
+ *
+ * @param connector
+ * The connector that should be marked clean.
+ */
+ public void markClean(ClientConnector connector) {
+ if (getLogger().isLoggable(Level.FINE)) {
+ if (dirtyConnectors.contains(connector)) {
+ getLogger().fine(
+ getConnectorAndParentInfo(connector) + " "
+ + "is no longer dirty");
+ }
+ }
+
+ dirtyConnectors.remove(connector);
+ }
+
+ /**
+ * Returns {@link #getConnectorString(ClientConnector)} for the connector
+ * and its parent (if it has a parent).
+ *
+ * @param connector
+ * The connector
+ * @return A string describing the connector and its parent
+ */
+ private String getConnectorAndParentInfo(ClientConnector connector) {
+ String message = getConnectorString(connector);
+ if (connector.getParent() != null) {
+ message += " (parent: " + getConnectorString(connector.getParent())
+ + ")";
+ }
+ return message;
+ }
+
+ /**
+ * Returns a string with the connector name and id. Useful mostly for
+ * debugging and logging.
+ *
+ * @param connector
+ * The connector
+ * @return A string that describes the connector
+ */
+ private String getConnectorString(ClientConnector connector) {
+ if (connector == null) {
+ return "(null)";
+ }
+
+ String connectorId;
+ try {
+ connectorId = connector.getConnectorId();
+ } catch (RuntimeException e) {
+ // This happens if the connector is not attached to the application.
+ // SHOULD not happen in this case but theoretically can.
+ connectorId = "@" + Integer.toHexString(connector.hashCode());
+ }
+ return connector.getClass().getName() + "(" + connectorId + ")";
+ }
+
+ /**
+ * Mark all connectors in this root as dirty.
+ */
+ public void markAllConnectorsDirty() {
+ markConnectorsDirtyRecursively(root);
+ getLogger().fine("All connectors are now dirty");
+ }
+
+ /**
+ * Mark all connectors in this root as clean.
+ */
+ public void markAllConnectorsClean() {
+ dirtyConnectors.clear();
+ getLogger().fine("All connectors are now clean");
+ }
+
+ /**
+ * Marks all visible connectors dirty, starting from the given connector and
+ * going downwards in the hierarchy.
+ *
+ * @param c
+ * The component to start iterating downwards from
+ */
+ private void markConnectorsDirtyRecursively(ClientConnector c) {
+ if (c instanceof Component && !((Component) c).isVisible()) {
+ return;
+ }
+ markDirty(c);
+ for (ClientConnector child : AbstractClientConnector
+ .getAllChildrenIterable(c)) {
+ markConnectorsDirtyRecursively(child);
+ }
+ }
+
+ /**
+ * Returns a collection of all connectors which have been marked as dirty.
+ * <p>
+ * The state and pending RPC calls for dirty connectors are sent to the
+ * client in the following request.
+ * </p>
+ *
+ * @return A collection of all dirty connectors for this root. This list may
+ * contain invisible connectors.
+ */
+ public Collection<ClientConnector> getDirtyConnectors() {
+ return dirtyConnectors;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/CssLayout.java b/server/src/com/vaadin/ui/CssLayout.java
new file mode 100644
index 0000000000..356f0a3843
--- /dev/null
+++ b/server/src/com/vaadin/ui/CssLayout.java
@@ -0,0 +1,308 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.vaadin.event.LayoutEvents.LayoutClickEvent;
+import com.vaadin.event.LayoutEvents.LayoutClickListener;
+import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.csslayout.CssLayoutServerRpc;
+import com.vaadin.shared.ui.csslayout.CssLayoutState;
+import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler;
+
+/**
+ * CssLayout is a layout component that can be used in browser environment only.
+ * It simply renders components and their captions into a same div element.
+ * Component layout can then be adjusted with css.
+ * <p>
+ * In comparison to {@link HorizontalLayout} and {@link VerticalLayout}
+ * <ul>
+ * <li>rather similar server side api
+ * <li>no spacing, alignment or expand ratios
+ * <li>much simpler DOM that can be styled by skilled web developer
+ * <li>no abstraction of browser differences (developer must ensure that the
+ * result works properly on each browser)
+ * <li>different kind of handling for relative sizes (that are set from server
+ * side) (*)
+ * <li>noticeably faster rendering time in some situations as we rely more on
+ * the browser's rendering engine.
+ * </ul>
+ * <p>
+ * With {@link CustomLayout} one can often achieve similar results (good looking
+ * layouts with web technologies), but with CustomLayout developer needs to work
+ * with fixed templates.
+ * <p>
+ * By extending CssLayout one can also inject some css rules straight to child
+ * components using {@link #getCss(Component)}.
+ *
+ * <p>
+ * (*) Relative sizes (set from server side) are treated bit differently than in
+ * other layouts in Vaadin. In cssLayout the size is calculated relatively to
+ * CSS layouts content area which is pretty much as in html and css. In other
+ * layouts the size of component is calculated relatively to the "slot" given by
+ * layout.
+ * <p>
+ * Also note that client side framework in Vaadin modifies inline style
+ * properties width and height. This happens on each update to component. If one
+ * wants to set component sizes with CSS, component must have undefined size on
+ * server side (which is not the default for all components) and the size must
+ * be defined with class styles - not by directly injecting width and height.
+ *
+ * @since 6.1 brought in from "FastLayouts" incubator project
+ *
+ */
+public class CssLayout extends AbstractLayout implements LayoutClickNotifier {
+
+ private CssLayoutServerRpc rpc = new CssLayoutServerRpc() {
+
+ @Override
+ public void layoutClick(MouseEventDetails mouseDetails,
+ Connector clickedConnector) {
+ fireEvent(LayoutClickEvent.createEvent(CssLayout.this,
+ mouseDetails, clickedConnector));
+ }
+ };
+ /**
+ * Custom layout slots containing the components.
+ */
+ protected LinkedList<Component> components = new LinkedList<Component>();
+
+ public CssLayout() {
+ registerRpc(rpc);
+ }
+
+ /**
+ * Add a component into this container. The component is added to the right
+ * or under the previous component.
+ *
+ * @param c
+ * the component to be added.
+ */
+ @Override
+ public void addComponent(Component c) {
+ // Add to components before calling super.addComponent
+ // so that it is available to AttachListeners
+ components.add(c);
+ try {
+ super.addComponent(c);
+ requestRepaint();
+ } catch (IllegalArgumentException e) {
+ components.remove(c);
+ throw e;
+ }
+ }
+
+ /**
+ * Adds a component into this container. The component is added to the left
+ * or on top of the other components.
+ *
+ * @param c
+ * the component to be added.
+ */
+ public void addComponentAsFirst(Component c) {
+ // If c is already in this, we must remove it before proceeding
+ // see ticket #7668
+ if (c.getParent() == this) {
+ removeComponent(c);
+ }
+ components.addFirst(c);
+ try {
+ super.addComponent(c);
+ requestRepaint();
+ } catch (IllegalArgumentException e) {
+ components.remove(c);
+ throw e;
+ }
+ }
+
+ /**
+ * Adds a component into indexed position in this container.
+ *
+ * @param c
+ * the component to be added.
+ * @param index
+ * the index of the component position. The components currently
+ * in and after the position are shifted forwards.
+ */
+ public void addComponent(Component c, int index) {
+ // If c is already in this, we must remove it before proceeding
+ // see ticket #7668
+ if (c.getParent() == this) {
+ // When c is removed, all components after it are shifted down
+ if (index > getComponentIndex(c)) {
+ index--;
+ }
+ removeComponent(c);
+ }
+ components.add(index, c);
+ try {
+ super.addComponent(c);
+ requestRepaint();
+ } catch (IllegalArgumentException e) {
+ components.remove(c);
+ throw e;
+ }
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * the component to be removed.
+ */
+ @Override
+ public void removeComponent(Component c) {
+ components.remove(c);
+ super.removeComponent(c);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the component container iterator for going trough all the components
+ * in the container.
+ *
+ * @return the Iterator of the components inside the container.
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return components.iterator();
+ }
+
+ /**
+ * Gets the number of contained components. Consistent with the iterator
+ * returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components
+ */
+ @Override
+ public int getComponentCount() {
+ return components.size();
+ }
+
+ @Override
+ public void updateState() {
+ super.updateState();
+ getState().getChildCss().clear();
+ for (Iterator<Component> ci = getComponentIterator(); ci.hasNext();) {
+ Component child = ci.next();
+ String componentCssString = getCss(child);
+ if (componentCssString != null) {
+ getState().getChildCss().put(child, componentCssString);
+ }
+
+ }
+ }
+
+ @Override
+ public CssLayoutState getState() {
+ return (CssLayoutState) super.getState();
+ }
+
+ /**
+ * Returns styles to be applied to given component. Override this method to
+ * inject custom style rules to components.
+ *
+ * <p>
+ * Note that styles are injected over previous styles before actual child
+ * rendering. Previous styles are not cleared, but overridden.
+ *
+ * <p>
+ * Note that one most often achieves better code style, by separating
+ * styling to theme (with custom theme and {@link #addStyleName(String)}.
+ * With own custom styles it is also very easy to break browser
+ * compatibility.
+ *
+ * @param c
+ * the component
+ * @return css rules to be applied to component
+ */
+ protected String getCss(Component c) {
+ return null;
+ }
+
+ /* Documented in superclass */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ // Gets the locations
+ int oldLocation = -1;
+ int newLocation = -1;
+ int location = 0;
+ for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
+ final Component component = i.next();
+
+ if (component == oldComponent) {
+ oldLocation = location;
+ }
+ if (component == newComponent) {
+ newLocation = location;
+ }
+
+ location++;
+ }
+
+ if (oldLocation == -1) {
+ addComponent(newComponent);
+ } else if (newLocation == -1) {
+ removeComponent(oldComponent);
+ addComponent(newComponent, oldLocation);
+ } else {
+ if (oldLocation > newLocation) {
+ components.remove(oldComponent);
+ components.add(newLocation, oldComponent);
+ components.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ } else {
+ components.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ components.remove(oldComponent);
+ components.add(newLocation, oldComponent);
+ }
+
+ requestRepaint();
+ }
+ }
+
+ @Override
+ public void addListener(LayoutClickListener listener) {
+ addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener,
+ LayoutClickListener.clickMethod);
+ }
+
+ @Override
+ public void removeListener(LayoutClickListener listener) {
+ removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener);
+ }
+
+ /**
+ * Returns the index of the given component.
+ *
+ * @param component
+ * The component to look up.
+ * @return The index of the component or -1 if the component is not a child.
+ */
+ public int getComponentIndex(Component component) {
+ return components.indexOf(component);
+ }
+
+ /**
+ * Returns the component at the given position.
+ *
+ * @param index
+ * The position of the component.
+ * @return The component at the given index.
+ * @throws IndexOutOfBoundsException
+ * If the index is out of range.
+ */
+ public Component getComponent(int index) throws IndexOutOfBoundsException {
+ return components.get(index);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/CustomComponent.java b/server/src/com/vaadin/ui/CustomComponent.java
new file mode 100644
index 0000000000..40b5dcd636
--- /dev/null
+++ b/server/src/com/vaadin/ui/CustomComponent.java
@@ -0,0 +1,189 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+/**
+ * Custom component provides simple implementation of Component interface for
+ * creation of new UI components by composition of existing components.
+ * <p>
+ * The component is used by inheriting the CustomComponent class and setting
+ * composite root inside the Custom component. The composite root itself can
+ * contain more components, but their interfaces are hidden from the users.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class CustomComponent extends AbstractComponentContainer {
+
+ /**
+ * The root component implementing the custom component.
+ */
+ private Component root = null;
+
+ /**
+ * Constructs a new custom component.
+ *
+ * <p>
+ * The component is implemented by wrapping the methods of the composition
+ * root component given as parameter. The composition root must be set
+ * before the component can be used.
+ * </p>
+ */
+ public CustomComponent() {
+ // expand horizontally by default
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Constructs a new custom component.
+ *
+ * <p>
+ * The component is implemented by wrapping the methods of the composition
+ * root component given as parameter. The composition root must not be null
+ * and can not be changed after the composition.
+ * </p>
+ *
+ * @param compositionRoot
+ * the root of the composition component tree.
+ */
+ public CustomComponent(Component compositionRoot) {
+ this();
+ setCompositionRoot(compositionRoot);
+ }
+
+ /**
+ * Returns the composition root.
+ *
+ * @return the Component Composition root.
+ */
+ protected Component getCompositionRoot() {
+ return root;
+ }
+
+ /**
+ * Sets the compositions root.
+ * <p>
+ * The composition root must be set to non-null value before the component
+ * can be used. The composition root can only be set once.
+ * </p>
+ *
+ * @param compositionRoot
+ * the root of the composition component tree.
+ */
+ protected void setCompositionRoot(Component compositionRoot) {
+ if (compositionRoot != root) {
+ if (root != null) {
+ // remove old component
+ super.removeComponent(root);
+ }
+ if (compositionRoot != null) {
+ // set new component
+ super.addComponent(compositionRoot);
+ }
+ root = compositionRoot;
+ requestRepaint();
+ }
+ }
+
+ /* Basic component features ------------------------------------------ */
+
+ private class ComponentIterator implements Iterator<Component>,
+ Serializable {
+ boolean first = getCompositionRoot() != null;
+
+ @Override
+ public boolean hasNext() {
+ return first;
+ }
+
+ @Override
+ public Component next() {
+ first = false;
+ return root;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return new ComponentIterator();
+ }
+
+ /**
+ * Gets the number of contained components. Consistent with the iterator
+ * returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components (zero or one)
+ */
+ @Override
+ public int getComponentCount() {
+ return (root != null ? 1 : 0);
+ }
+
+ /**
+ * This method is not supported by CustomComponent.
+ *
+ * @see com.vaadin.ui.ComponentContainer#replaceComponent(com.vaadin.ui.Component,
+ * com.vaadin.ui.Component)
+ */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * This method is not supported by CustomComponent. Use
+ * {@link CustomComponent#setCompositionRoot(Component)} to set
+ * CustomComponents "child".
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#addComponent(com.vaadin.ui.Component)
+ */
+ @Override
+ public void addComponent(Component c) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * This method is not supported by CustomComponent.
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#moveComponentsFrom(com.vaadin.ui.ComponentContainer)
+ */
+ @Override
+ public void moveComponentsFrom(ComponentContainer source) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * This method is not supported by CustomComponent.
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#removeAllComponents()
+ */
+ @Override
+ public void removeAllComponents() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * This method is not supported by CustomComponent.
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#removeComponent(com.vaadin.ui.Component)
+ */
+ @Override
+ public void removeComponent(Component c) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/CustomField.java b/server/src/com/vaadin/ui/CustomField.java
new file mode 100644
index 0000000000..ab3797a58c
--- /dev/null
+++ b/server/src/com/vaadin/ui/CustomField.java
@@ -0,0 +1,237 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+import com.vaadin.data.Property;
+
+/**
+ * A {@link Field} whose UI content can be constructed by the user, enabling the
+ * creation of e.g. form fields by composing Vaadin components. Customization of
+ * both the visual presentation and the logic of the field is possible.
+ *
+ * Subclasses must implement {@link #getType()} and {@link #initContent()}.
+ *
+ * Most custom fields can simply compose a user interface that calls the methods
+ * {@link #setInternalValue(Object)} and {@link #getInternalValue()} when
+ * necessary.
+ *
+ * It is also possible to override {@link #validate()},
+ * {@link #setInternalValue(Object)}, {@link #commit()},
+ * {@link #setPropertyDataSource(Property)}, {@link #isEmpty()} and other logic
+ * of the field. Methods overriding {@link #setInternalValue(Object)} should
+ * also call the corresponding superclass method.
+ *
+ * @param <T>
+ * field value type
+ *
+ * @since 7.0
+ */
+public abstract class CustomField<T> extends AbstractField<T> implements
+ ComponentContainer {
+
+ /**
+ * The root component implementing the custom component.
+ */
+ private Component root = null;
+
+ /**
+ * Constructs a new custom field.
+ *
+ * <p>
+ * The component is implemented by wrapping the methods of the composition
+ * root component given as parameter. The composition root must be set
+ * before the component can be used.
+ * </p>
+ */
+ public CustomField() {
+ // expand horizontally by default
+ setWidth(100, Unit.PERCENTAGE);
+ }
+
+ /**
+ * Constructs the content and notifies it that the {@link CustomField} is
+ * attached to a window.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ // First call super attach to notify all children (none if content has
+ // not yet been created)
+ super.attach();
+
+ // If the content has not yet been created, we create and attach it at
+ // this point.
+ if (root == null) {
+ // Ensure content is created and its parent is set.
+ // The getContent() call creates the content and attaches the
+ // content
+ fireComponentAttachEvent(getContent());
+ }
+ }
+
+ /**
+ * Returns the content (UI) of the custom component.
+ *
+ * @return Component
+ */
+ protected Component getContent() {
+ if (null == root) {
+ root = initContent();
+ root.setParent(this);
+ }
+ return root;
+ }
+
+ /**
+ * Create the content component or layout for the field. Subclasses of
+ * {@link CustomField} should implement this method.
+ *
+ * Note that this method is called when the CustomField is attached to a
+ * layout or when {@link #getContent()} is called explicitly for the first
+ * time. It is only called once for a {@link CustomField}.
+ *
+ * @return {@link Component} representing the UI of the CustomField
+ */
+ protected abstract Component initContent();
+
+ // Size related methods
+ // TODO might not be necessary to override but following the pattern from
+ // AbstractComponentContainer
+
+ @Override
+ public void setHeight(float height, Unit unit) {
+ super.setHeight(height, unit);
+ requestRepaintAll();
+ }
+
+ @Override
+ public void setWidth(float height, Unit unit) {
+ super.setWidth(height, unit);
+ requestRepaintAll();
+ }
+
+ // ComponentContainer methods
+
+ private class ComponentIterator implements Iterator<Component>,
+ Serializable {
+ boolean first = (root != null);
+
+ @Override
+ public boolean hasNext() {
+ return first;
+ }
+
+ @Override
+ public Component next() {
+ first = false;
+ return getContent();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return new ComponentIterator();
+ }
+
+ @Override
+ public Iterator<Component> iterator() {
+ return getComponentIterator();
+ }
+
+ @Override
+ public int getComponentCount() {
+ return (null != getContent()) ? 1 : 0;
+ }
+
+ /**
+ * Fires the component attached event. This should be called by the
+ * addComponent methods after the component have been added to this
+ * container.
+ *
+ * @param component
+ * the component that has been added to this container.
+ */
+ protected void fireComponentAttachEvent(Component component) {
+ fireEvent(new ComponentAttachEvent(this, component));
+ }
+
+ // TODO remove these methods when ComponentContainer interface is cleaned up
+
+ @Override
+ public void addComponent(Component c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeComponent(Component c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeAllComponents() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void moveComponentsFrom(ComponentContainer source) {
+ throw new UnsupportedOperationException();
+ }
+
+ private static final Method COMPONENT_ATTACHED_METHOD;
+
+ static {
+ try {
+ COMPONENT_ATTACHED_METHOD = ComponentAttachListener.class
+ .getDeclaredMethod("componentAttachedToContainer",
+ new Class[] { ComponentAttachEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in CustomField");
+ }
+ }
+
+ @Override
+ public void addListener(ComponentAttachListener listener) {
+ addListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ @Override
+ public void removeListener(ComponentAttachListener listener) {
+ removeListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ @Override
+ public void addListener(ComponentDetachListener listener) {
+ // content never detached
+ }
+
+ @Override
+ public void removeListener(ComponentDetachListener listener) {
+ // content never detached
+ }
+
+ @Override
+ public boolean isComponentVisible(Component childComponent) {
+ return true;
+ }
+}
diff --git a/server/src/com/vaadin/ui/CustomLayout.java b/server/src/com/vaadin/ui/CustomLayout.java
new file mode 100644
index 0000000000..d7830603f0
--- /dev/null
+++ b/server/src/com/vaadin/ui/CustomLayout.java
@@ -0,0 +1,329 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.shared.ui.customlayout.CustomLayoutState;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.server.JsonPaintTarget;
+
+/**
+ * <p>
+ * A container component with freely designed layout and style. The layout
+ * consists of items with textually represented locations. Each item contains
+ * one sub-component, which can be any Vaadin component, such as a layout. The
+ * adapter and theme are responsible for rendering the layout with a given style
+ * by placing the items in the defined locations.
+ * </p>
+ *
+ * <p>
+ * The placement of the locations is not fixed - different themes can define the
+ * locations in a way that is suitable for them. One typical example would be to
+ * create visual design for a web site as a custom layout: the visual design
+ * would define locations for "menu", "body", and "title", for example. The
+ * layout would then be implemented as an XHTML template for each theme.
+ * </p>
+ *
+ * <p>
+ * The default theme handles the styles that are not defined by drawing the
+ * subcomponents just as in OrderedLayout.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @author Duy B. Vo (<a
+ * href="mailto:devduy@gmail.com?subject=Vaadin">devduy@gmail.com</a>)
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class CustomLayout extends AbstractLayout implements Vaadin6Component {
+
+ private static final int BUFFER_SIZE = 10000;
+
+ /**
+ * Custom layout slots containing the components.
+ */
+ private final HashMap<String, Component> slots = new HashMap<String, Component>();
+
+ /**
+ * Default constructor only used by subclasses. Subclasses are responsible
+ * for setting the appropriate fields. Either
+ * {@link #setTemplateName(String)}, that makes layout fetch the template
+ * from theme, or {@link #setTemplateContents(String)}.
+ */
+ protected CustomLayout() {
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Constructs a custom layout with the template given in the stream.
+ *
+ * @param templateStream
+ * Stream containing template data. Must be using UTF-8 encoding.
+ * To use a String as a template use for instance new
+ * ByteArrayInputStream("<template>".getBytes()).
+ * @param streamLength
+ * Length of the templateStream
+ * @throws IOException
+ */
+ public CustomLayout(InputStream templateStream) throws IOException {
+ this();
+ initTemplateContentsFromInputStream(templateStream);
+ }
+
+ /**
+ * Constructor for custom layout with given template name. Template file is
+ * fetched from "<theme>/layout/<templateName>".
+ */
+ public CustomLayout(String template) {
+ this();
+ setTemplateName(template);
+ }
+
+ protected void initTemplateContentsFromInputStream(
+ InputStream templateStream) throws IOException {
+ InputStreamReader reader = new InputStreamReader(templateStream,
+ "UTF-8");
+ StringBuilder b = new StringBuilder(BUFFER_SIZE);
+
+ char[] cbuf = new char[BUFFER_SIZE];
+ int offset = 0;
+
+ while (true) {
+ int nrRead = reader.read(cbuf, offset, BUFFER_SIZE);
+ b.append(cbuf, 0, nrRead);
+ if (nrRead < BUFFER_SIZE) {
+ break;
+ }
+ }
+
+ setTemplateContents(b.toString());
+ }
+
+ @Override
+ public CustomLayoutState getState() {
+ return (CustomLayoutState) super.getState();
+ }
+
+ /**
+ * Adds the component into this container to given location. If the location
+ * is already populated, the old component is removed.
+ *
+ * @param c
+ * the component to be added.
+ * @param location
+ * the location of the component.
+ */
+ public void addComponent(Component c, String location) {
+ final Component old = slots.get(location);
+ if (old != null) {
+ removeComponent(old);
+ }
+ slots.put(location, c);
+ getState().getChildLocations().put(c, location);
+ c.setParent(this);
+ fireComponentAttachEvent(c);
+ requestRepaint();
+ }
+
+ /**
+ * Adds the component into this container. The component is added without
+ * specifying the location (empty string is then used as location). Only one
+ * component can be added to the default "" location and adding more
+ * components into that location overwrites the old components.
+ *
+ * @param c
+ * the component to be added.
+ */
+ @Override
+ public void addComponent(Component c) {
+ this.addComponent(c, "");
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * the component to be removed.
+ */
+ @Override
+ public void removeComponent(Component c) {
+ if (c == null) {
+ return;
+ }
+ slots.values().remove(c);
+ getState().getChildLocations().remove(c);
+ super.removeComponent(c);
+ requestRepaint();
+ }
+
+ /**
+ * Removes the component from this container from given location.
+ *
+ * @param location
+ * the Location identifier of the component.
+ */
+ public void removeComponent(String location) {
+ this.removeComponent(slots.get(location));
+ }
+
+ /**
+ * Gets the component container iterator for going trough all the components
+ * in the container.
+ *
+ * @return the Iterator of the components inside the container.
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return slots.values().iterator();
+ }
+
+ /**
+ * Gets the number of contained components. Consistent with the iterator
+ * returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components
+ */
+ @Override
+ public int getComponentCount() {
+ return slots.values().size();
+ }
+
+ /**
+ * Gets the child-component by its location.
+ *
+ * @param location
+ * the name of the location where the requested component
+ * resides.
+ * @return the Component in the given location or null if not found.
+ */
+ public Component getComponent(String location) {
+ return slots.get(location);
+ }
+
+ /* Documented in superclass */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ // Gets the locations
+ String oldLocation = null;
+ String newLocation = null;
+ for (final Iterator<String> i = slots.keySet().iterator(); i.hasNext();) {
+ final String location = i.next();
+ final Component component = slots.get(location);
+ if (component == oldComponent) {
+ oldLocation = location;
+ }
+ if (component == newComponent) {
+ newLocation = location;
+ }
+ }
+
+ if (oldLocation == null) {
+ addComponent(newComponent);
+ } else if (newLocation == null) {
+ removeComponent(oldLocation);
+ addComponent(newComponent, oldLocation);
+ } else {
+ slots.put(newLocation, oldComponent);
+ slots.put(oldLocation, newComponent);
+ getState().getChildLocations().put(newComponent, oldLocation);
+ getState().getChildLocations().put(oldComponent, newLocation);
+ requestRepaint();
+ }
+ }
+
+ /** Get the name of the template */
+ public String getTemplateName() {
+ return getState().getTemplateName();
+ }
+
+ /** Get the contents of the template */
+ public String getTemplateContents() {
+ return getState().getTemplateContents();
+ }
+
+ /**
+ * Set the name of the template used to draw custom layout.
+ *
+ * With GWT-adapter, the template with name 'templatename' is loaded from
+ * VAADIN/themes/themename/layouts/templatename.html. If the theme has not
+ * been set (with Application.setTheme()), themename is 'default'.
+ *
+ * @param templateName
+ */
+ public void setTemplateName(String templateName) {
+ getState().setTemplateName(templateName);
+ getState().setTemplateContents(null);
+ requestRepaint();
+ }
+
+ /**
+ * Set the contents of the template used to draw the custom layout.
+ *
+ * @param templateContents
+ */
+ public void setTemplateContents(String templateContents) {
+ getState().setTemplateContents(templateContents);
+ getState().setTemplateName(null);
+ requestRepaint();
+ }
+
+ /**
+ * Although most layouts support margins, CustomLayout does not. The
+ * behaviour of this layout is determined almost completely by the actual
+ * template.
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void setMargin(boolean enabled) {
+ throw new UnsupportedOperationException(
+ "CustomLayout does not support margins.");
+ }
+
+ /**
+ * Although most layouts support margins, CustomLayout does not. The
+ * behaviour of this layout is determined almost completely by the actual
+ * template.
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void setMargin(boolean topEnabled, boolean rightEnabled,
+ boolean bottomEnabled, boolean leftEnabled) {
+ throw new UnsupportedOperationException(
+ "CustomLayout does not support margins.");
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // Nothing to see here
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ // Workaround to make the CommunicationManager read the template file
+ // and send it to the client
+ String templateName = getState().getTemplateName();
+ if (templateName != null && templateName.length() != 0) {
+ Set<Object> usedResources = ((JsonPaintTarget) target)
+ .getUsedResources();
+ String resourceName = "layouts/" + templateName + ".html";
+ usedResources.add(resourceName);
+ }
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/DateField.java b/server/src/com/vaadin/ui/DateField.java
new file mode 100644
index 0000000000..d0a22f3c29
--- /dev/null
+++ b/server/src/com/vaadin/ui/DateField.java
@@ -0,0 +1,869 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import com.vaadin.data.Property;
+import com.vaadin.data.Validator;
+import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.event.FieldEvents;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.datefield.VDateField;
+
+/**
+ * <p>
+ * A date editor component that can be bound to any {@link Property} that is
+ * compatible with <code>java.util.Date</code>.
+ * </p>
+ * <p>
+ * Since <code>DateField</code> extends <code>AbstractField</code> it implements
+ * the {@link com.vaadin.data.Buffered}interface.
+ * </p>
+ * <p>
+ * A <code>DateField</code> is in write-through mode by default, so
+ * {@link com.vaadin.ui.AbstractField#setWriteThrough(boolean)}must be called to
+ * enable buffering.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class DateField extends AbstractField<Date> implements
+ FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Vaadin6Component {
+
+ /**
+ * Resolutions for DateFields
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 7.0
+ */
+ public enum Resolution {
+ SECOND(Calendar.SECOND), MINUTE(Calendar.MINUTE), HOUR(
+ Calendar.HOUR_OF_DAY), DAY(Calendar.DAY_OF_MONTH), MONTH(
+ Calendar.MONTH), YEAR(Calendar.YEAR);
+
+ private int calendarField;
+
+ private Resolution(int calendarField) {
+ this.calendarField = calendarField;
+ }
+
+ /**
+ * Returns the field in {@link Calendar} that corresponds to this
+ * resolution.
+ *
+ * @return one of the field numbers used by Calendar
+ */
+ public int getCalendarField() {
+ return calendarField;
+ }
+
+ /**
+ * Returns the resolutions that are higher or equal to the given
+ * resolution, starting from the given resolution. In other words
+ * passing DAY to this methods returns DAY,MONTH,YEAR
+ *
+ * @param r
+ * The resolution to start from
+ * @return An iterable for the resolutions higher or equal to r
+ */
+ public static Iterable<Resolution> getResolutionsHigherOrEqualTo(
+ Resolution r) {
+ List<Resolution> resolutions = new ArrayList<DateField.Resolution>();
+ Resolution[] values = Resolution.values();
+ for (int i = r.ordinal(); i < values.length; i++) {
+ resolutions.add(values[i]);
+ }
+ return resolutions;
+ }
+
+ /**
+ * Returns the resolutions that are lower than the given resolution,
+ * starting from the given resolution. In other words passing DAY to
+ * this methods returns HOUR,MINUTE,SECOND.
+ *
+ * @param r
+ * The resolution to start from
+ * @return An iterable for the resolutions lower than r
+ */
+ public static List<Resolution> getResolutionsLowerThan(Resolution r) {
+ List<Resolution> resolutions = new ArrayList<DateField.Resolution>();
+ Resolution[] values = Resolution.values();
+ for (int i = r.ordinal() - 1; i >= 0; i--) {
+ resolutions.add(values[i]);
+ }
+ return resolutions;
+ }
+ };
+
+ /**
+ * Resolution identifier: seconds.
+ *
+ * @deprecated Use {@link Resolution#SECOND}
+ */
+ @Deprecated
+ public static final Resolution RESOLUTION_SEC = Resolution.SECOND;
+
+ /**
+ * Resolution identifier: minutes.
+ *
+ * @deprecated Use {@link Resolution#MINUTE}
+ */
+ @Deprecated
+ public static final Resolution RESOLUTION_MIN = Resolution.MINUTE;
+
+ /**
+ * Resolution identifier: hours.
+ *
+ * @deprecated Use {@link Resolution#HOUR}
+ */
+ @Deprecated
+ public static final Resolution RESOLUTION_HOUR = Resolution.HOUR;
+
+ /**
+ * Resolution identifier: days.
+ *
+ * @deprecated Use {@link Resolution#DAY}
+ */
+ @Deprecated
+ public static final Resolution RESOLUTION_DAY = Resolution.DAY;
+
+ /**
+ * Resolution identifier: months.
+ *
+ * @deprecated Use {@link Resolution#MONTH}
+ */
+ @Deprecated
+ public static final Resolution RESOLUTION_MONTH = Resolution.MONTH;
+
+ /**
+ * Resolution identifier: years.
+ *
+ * @deprecated Use {@link Resolution#YEAR}
+ */
+ @Deprecated
+ public static final Resolution RESOLUTION_YEAR = Resolution.YEAR;
+
+ /**
+ * Specified smallest modifiable unit for the date field.
+ */
+ private Resolution resolution = Resolution.DAY;
+
+ /**
+ * The internal calendar to be used in java.utl.Date conversions.
+ */
+ private transient Calendar calendar;
+
+ /**
+ * Overridden format string
+ */
+ private String dateFormat;
+
+ private boolean lenient = false;
+
+ private String dateString = null;
+
+ /**
+ * Was the last entered string parsable? If this flag is false, datefields
+ * internal validator does not pass.
+ */
+ private boolean uiHasValidDateString = true;
+
+ /**
+ * Determines if week numbers are shown in the date selector.
+ */
+ private boolean showISOWeekNumbers = false;
+
+ private String currentParseErrorMessage;
+
+ private String defaultParseErrorMessage = "Date format not recognized";
+
+ private TimeZone timeZone = null;
+
+ private static Map<Resolution, String> variableNameForResolution = new HashMap<DateField.Resolution, String>();
+ {
+ variableNameForResolution.put(Resolution.SECOND, "sec");
+ variableNameForResolution.put(Resolution.MINUTE, "min");
+ variableNameForResolution.put(Resolution.HOUR, "hour");
+ variableNameForResolution.put(Resolution.DAY, "day");
+ variableNameForResolution.put(Resolution.MONTH, "month");
+ variableNameForResolution.put(Resolution.YEAR, "year");
+ }
+
+ /* Constructors */
+
+ /**
+ * Constructs an empty <code>DateField</code> with no caption.
+ */
+ public DateField() {
+ }
+
+ /**
+ * Constructs an empty <code>DateField</code> with caption.
+ *
+ * @param caption
+ * the caption of the datefield.
+ */
+ public DateField(String caption) {
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new <code>DateField</code> that's bound to the specified
+ * <code>Property</code> and has the given caption <code>String</code>.
+ *
+ * @param caption
+ * the caption <code>String</code> for the editor.
+ * @param dataSource
+ * the Property to be edited with this editor.
+ */
+ public DateField(String caption, Property dataSource) {
+ this(dataSource);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new <code>DateField</code> that's bound to the specified
+ * <code>Property</code> and has no caption.
+ *
+ * @param dataSource
+ * the Property to be edited with this editor.
+ */
+ public DateField(Property dataSource) throws IllegalArgumentException {
+ if (!Date.class.isAssignableFrom(dataSource.getType())) {
+ throw new IllegalArgumentException("Can't use "
+ + dataSource.getType().getName()
+ + " typed property as datasource");
+ }
+
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a new <code>DateField</code> with the given caption and
+ * initial text contents. The editor constructed this way will not be bound
+ * to a Property unless
+ * {@link com.vaadin.data.Property.Viewer#setPropertyDataSource(Property)}
+ * is called to bind it.
+ *
+ * @param caption
+ * the caption <code>String</code> for the editor.
+ * @param value
+ * the Date value.
+ */
+ public DateField(String caption, Date value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+ /* Component basic features */
+
+ /*
+ * Paints this component. Don't add a JavaDoc comment here, we use the
+ * default documentation from implemented interface.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Adds the locale as attribute
+ final Locale l = getLocale();
+ if (l != null) {
+ target.addAttribute("locale", l.toString());
+ }
+
+ if (getDateFormat() != null) {
+ target.addAttribute("format", dateFormat);
+ }
+
+ if (!isLenient()) {
+ target.addAttribute("strict", true);
+ }
+
+ target.addAttribute(VDateField.WEEK_NUMBERS, isShowISOWeekNumbers());
+ target.addAttribute("parsable", uiHasValidDateString);
+ /*
+ * TODO communicate back the invalid date string? E.g. returning back to
+ * app or refresh.
+ */
+
+ // Gets the calendar
+ final Calendar calendar = getCalendar();
+ final Date currentDate = getValue();
+
+ // Only paint variables for the resolution and up, e.g. Resolution DAY
+ // paints DAY,MONTH,YEAR
+ for (Resolution res : Resolution
+ .getResolutionsHigherOrEqualTo(resolution)) {
+ int value = -1;
+ if (currentDate != null) {
+ value = calendar.get(res.getCalendarField());
+ if (res == Resolution.MONTH) {
+ // Calendar month is zero based
+ value++;
+ }
+ }
+ target.addVariable(this, variableNameForResolution.get(res), value);
+ }
+ }
+
+ @Override
+ protected boolean shouldHideErrors() {
+ return super.shouldHideErrors() && uiHasValidDateString;
+ }
+
+ /*
+ * Invoked when a variable of the component changes. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+
+ if (!isReadOnly()
+ && (variables.containsKey("year")
+ || variables.containsKey("month")
+ || variables.containsKey("day")
+ || variables.containsKey("hour")
+ || variables.containsKey("min")
+ || variables.containsKey("sec")
+ || variables.containsKey("msec") || variables
+ .containsKey("dateString"))) {
+
+ // Old and new dates
+ final Date oldDate = getValue();
+ Date newDate = null;
+
+ // this enables analyzing invalid input on the server
+ final String newDateString = (String) variables.get("dateString");
+ dateString = newDateString;
+
+ // Gets the new date in parts
+ boolean hasChanges = false;
+ Map<Resolution, Integer> calendarFieldChanges = new HashMap<DateField.Resolution, Integer>();
+
+ for (Resolution r : Resolution
+ .getResolutionsHigherOrEqualTo(resolution)) {
+ // Only handle what the client is allowed to send. The same
+ // resolutions that are painted
+ String variableName = variableNameForResolution.get(r);
+
+ if (variables.containsKey(variableName)) {
+ Integer value = (Integer) variables.get(variableName);
+ if (r == Resolution.MONTH) {
+ // Calendar MONTH is zero based
+ value--;
+ }
+ if (value >= 0) {
+ hasChanges = true;
+ calendarFieldChanges.put(r, value);
+ }
+ }
+ }
+
+ // If no new variable values were received, use the previous value
+ if (!hasChanges) {
+ newDate = null;
+ } else {
+ // Clone the calendar for date operation
+ final Calendar cal = getCalendar();
+
+ // Update the value based on the received info
+ // Must set in this order to avoid invalid dates (or wrong
+ // dates if lenient is true) in calendar
+ for (int r = Resolution.YEAR.ordinal(); r >= 0; r--) {
+ Resolution res = Resolution.values()[r];
+ if (calendarFieldChanges.containsKey(res)) {
+
+ // Field resolution should be included. Others are
+ // skipped so that client can not make unexpected
+ // changes (e.g. day change even though resolution is
+ // year).
+ Integer newValue = calendarFieldChanges.get(res);
+ cal.set(res.getCalendarField(), newValue);
+ }
+ }
+ newDate = cal.getTime();
+ }
+
+ if (newDate == null && dateString != null && !"".equals(dateString)) {
+ try {
+ Date parsedDate = handleUnparsableDateString(dateString);
+ setValue(parsedDate, true);
+
+ /*
+ * Ensure the value is sent to the client if the value is
+ * set to the same as the previous (#4304). Does not repaint
+ * if handleUnparsableDateString throws an exception. In
+ * this case the invalid text remains in the DateField.
+ */
+ requestRepaint();
+ } catch (Converter.ConversionException e) {
+
+ /*
+ * Datefield now contains some text that could't be parsed
+ * into date.
+ */
+ if (oldDate != null) {
+ /*
+ * Set the logic value to null.
+ */
+ setValue(null);
+ /*
+ * Reset the dateString (overridden to null by setValue)
+ */
+ dateString = newDateString;
+ }
+
+ /*
+ * Saves the localized message of parse error. This can be
+ * overridden in handleUnparsableDateString. The message
+ * will later be used to show a validation error.
+ */
+ currentParseErrorMessage = e.getLocalizedMessage();
+
+ /*
+ * The value of the DateField should be null if an invalid
+ * value has been given. Not using setValue() since we do
+ * not want to cause the client side value to change.
+ */
+ uiHasValidDateString = false;
+
+ /*
+ * Because of our custom implementation of isValid(), that
+ * also checks the parsingSucceeded flag, we must also
+ * notify the form (if this is used in one) that the
+ * validity of this field has changed.
+ *
+ * Normally fields validity doesn't change without value
+ * change and form depends on this implementation detail.
+ */
+ notifyFormOfValidityChange();
+ requestRepaint();
+ }
+ } else if (newDate != oldDate
+ && (newDate == null || !newDate.equals(oldDate))) {
+ setValue(newDate, true); // Don't require a repaint, client
+ // updates itself
+ } else if (!uiHasValidDateString) { // oldDate ==
+ // newDate == null
+ // Empty value set, previously contained unparsable date string,
+ // clear related internal fields
+ setValue(null);
+ }
+ }
+
+ if (variables.containsKey(FocusEvent.EVENT_ID)) {
+ fireEvent(new FocusEvent(this));
+ }
+
+ if (variables.containsKey(BlurEvent.EVENT_ID)) {
+ fireEvent(new BlurEvent(this));
+ }
+ }
+
+ /**
+ * This method is called to handle a non-empty date string from the client
+ * if the client could not parse it as a Date.
+ *
+ * By default, a Converter.ConversionException is thrown, and the current
+ * value is not modified.
+ *
+ * This can be overridden to handle conversions, to return null (equivalent
+ * to empty input), to throw an exception or to fire an event.
+ *
+ * @param dateString
+ * @return parsed Date
+ * @throws Converter.ConversionException
+ * to keep the old value and indicate an error
+ */
+ protected Date handleUnparsableDateString(String dateString)
+ throws Converter.ConversionException {
+ currentParseErrorMessage = null;
+ throw new Converter.ConversionException(getParseErrorMessage());
+ }
+
+ /* Property features */
+
+ /*
+ * Gets the edited property's type. Don't add a JavaDoc comment here, we use
+ * the default documentation from implemented interface.
+ */
+ @Override
+ public Class<Date> getType() {
+ return Date.class;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object, boolean)
+ */
+ @Override
+ protected void setValue(Date newValue, boolean repaintIsNotNeeded)
+ throws Property.ReadOnlyException {
+
+ /*
+ * First handle special case when the client side component have a date
+ * string but value is null (e.g. unparsable date string typed in by the
+ * user). No value changes should happen, but we need to do some
+ * internal housekeeping.
+ */
+ if (newValue == null && !uiHasValidDateString) {
+ /*
+ * Side-effects of setInternalValue clears possible previous strings
+ * and flags about invalid input.
+ */
+ setInternalValue(null);
+
+ /*
+ * Due to DateField's special implementation of isValid(),
+ * datefields validity may change although the logical value does
+ * not change. This is an issue for Form which expects that validity
+ * of Fields cannot change unless actual value changes.
+ *
+ * So we check if this field is inside a form and the form has
+ * registered this as a field. In this case we repaint the form.
+ * Without this hacky solution the form might not be able to clean
+ * validation errors etc. We could avoid this by firing an extra
+ * value change event, but feels like at least as bad solution as
+ * this.
+ */
+ notifyFormOfValidityChange();
+ requestRepaint();
+ return;
+ }
+
+ super.setValue(newValue, repaintIsNotNeeded);
+ }
+
+ /**
+ * Detects if this field is used in a Form (logically) and if so, notifies
+ * it (by repainting it) that the validity of this field might have changed.
+ */
+ private void notifyFormOfValidityChange() {
+ Component parenOfDateField = getParent();
+ boolean formFound = false;
+ while (parenOfDateField != null || formFound) {
+ if (parenOfDateField instanceof Form) {
+ Form f = (Form) parenOfDateField;
+ Collection<?> visibleItemProperties = f.getItemPropertyIds();
+ for (Object fieldId : visibleItemProperties) {
+ Field<?> field = f.getField(fieldId);
+ if (field == this) {
+ /*
+ * this datefield is logically in a form. Do the same
+ * thing as form does in its value change listener that
+ * it registers to all fields.
+ */
+ f.requestRepaint();
+ formFound = true;
+ break;
+ }
+ }
+ }
+ if (formFound) {
+ break;
+ }
+ parenOfDateField = parenOfDateField.getParent();
+ }
+ }
+
+ @Override
+ protected void setInternalValue(Date newValue) {
+ // Also set the internal dateString
+ if (newValue != null) {
+ dateString = newValue.toString();
+ } else {
+ dateString = null;
+ }
+
+ if (!uiHasValidDateString) {
+ // clear component error and parsing flag
+ setComponentError(null);
+ uiHasValidDateString = true;
+ currentParseErrorMessage = null;
+ }
+
+ super.setInternalValue(newValue);
+ }
+
+ /**
+ * Gets the resolution.
+ *
+ * @return int
+ */
+ public Resolution getResolution() {
+ return resolution;
+ }
+
+ /**
+ * Sets the resolution of the DateField.
+ *
+ * The default resolution is {@link Resolution#DAY} since Vaadin 7.0.
+ *
+ * @param resolution
+ * the resolution to set.
+ */
+ public void setResolution(Resolution resolution) {
+ this.resolution = resolution;
+ requestRepaint();
+ }
+
+ /**
+ * Returns new instance calendar used in Date conversions.
+ *
+ * Returns new clone of the calendar object initialized using the the
+ * current date (if available)
+ *
+ * If this is no calendar is assigned the <code>Calendar.getInstance</code>
+ * is used.
+ *
+ * @return the Calendar.
+ * @see #setCalendar(Calendar)
+ */
+ private Calendar getCalendar() {
+
+ // Makes sure we have an calendar instance
+ if (calendar == null) {
+ calendar = Calendar.getInstance();
+ // Start by a zeroed calendar to avoid having values for lower
+ // resolution variables e.g. time when resolution is day
+ for (Resolution r : Resolution.getResolutionsLowerThan(resolution)) {
+ calendar.set(r.getCalendarField(), 0);
+ }
+ calendar.set(Calendar.MILLISECOND, 0);
+ }
+
+ // Clone the instance
+ final Calendar newCal = (Calendar) calendar.clone();
+
+ // Assigns the current time tom calendar.
+ final Date currentDate = getValue();
+ if (currentDate != null) {
+ newCal.setTime(currentDate);
+ }
+
+ final TimeZone currentTimeZone = getTimeZone();
+ if (currentTimeZone != null) {
+ newCal.setTimeZone(currentTimeZone);
+ }
+
+ return newCal;
+ }
+
+ /**
+ * Sets formatting used by some component implementations. See
+ * {@link SimpleDateFormat} for format details.
+ *
+ * By default it is encouraged to used default formatting defined by Locale,
+ * but due some JVM bugs it is sometimes necessary to use this method to
+ * override formatting. See Vaadin issue #2200.
+ *
+ * @param dateFormat
+ * the dateFormat to set
+ *
+ * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
+ */
+ public void setDateFormat(String dateFormat) {
+ this.dateFormat = dateFormat;
+ requestRepaint();
+ }
+
+ /**
+ * Returns a format string used to format date value on client side or null
+ * if default formatting from {@link Component#getLocale()} is used.
+ *
+ * @return the dateFormat
+ */
+ public String getDateFormat() {
+ return dateFormat;
+ }
+
+ /**
+ * Specifies whether or not date/time interpretation in component is to be
+ * lenient.
+ *
+ * @see Calendar#setLenient(boolean)
+ * @see #isLenient()
+ *
+ * @param lenient
+ * true if the lenient mode is to be turned on; false if it is to
+ * be turned off.
+ */
+ public void setLenient(boolean lenient) {
+ this.lenient = lenient;
+ requestRepaint();
+ }
+
+ /**
+ * Returns whether date/time interpretation is to be lenient.
+ *
+ * @see #setLenient(boolean)
+ *
+ * @return true if the interpretation mode of this calendar is lenient;
+ * false otherwise.
+ */
+ public boolean isLenient() {
+ return lenient;
+ }
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ /**
+ * Checks whether ISO 8601 week numbers are shown in the date selector.
+ *
+ * @return true if week numbers are shown, false otherwise.
+ */
+ public boolean isShowISOWeekNumbers() {
+ return showISOWeekNumbers;
+ }
+
+ /**
+ * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
+ * 8601 defines that a week always starts with a Monday so the week numbers
+ * are only shown if this is the case.
+ *
+ * @param showWeekNumbers
+ * true if week numbers should be shown, false otherwise.
+ */
+ public void setShowISOWeekNumbers(boolean showWeekNumbers) {
+ showISOWeekNumbers = showWeekNumbers;
+ requestRepaint();
+ }
+
+ /**
+ * Validates the current value against registered validators if the field is
+ * not empty. Note that DateField is considered empty (value == null) and
+ * invalid if it contains text typed in by the user that couldn't be parsed
+ * into a Date value.
+ *
+ * @see com.vaadin.ui.AbstractField#validate()
+ */
+ @Override
+ public void validate() throws InvalidValueException {
+ /*
+ * To work properly in form we must throw exception if there is
+ * currently a parsing error in the datefield. Parsing error is kind of
+ * an internal validator.
+ */
+ if (!uiHasValidDateString) {
+ throw new UnparsableDateString(currentParseErrorMessage);
+ }
+ super.validate();
+ }
+
+ /**
+ * Return the error message that is shown if the user inputted value can't
+ * be parsed into a Date object. If
+ * {@link #handleUnparsableDateString(String)} is overridden and it throws a
+ * custom exception, the message returned by
+ * {@link Exception#getLocalizedMessage()} will be used instead of the value
+ * returned by this method.
+ *
+ * @see #setParseErrorMessage(String)
+ *
+ * @return the error message that the DateField uses when it can't parse the
+ * textual input from user to a Date object
+ */
+ public String getParseErrorMessage() {
+ return defaultParseErrorMessage;
+ }
+
+ /**
+ * Sets the default error message used if the DateField cannot parse the
+ * text input by user to a Date field. Note that if the
+ * {@link #handleUnparsableDateString(String)} method is overridden, the
+ * localized message from its exception is used.
+ *
+ * @see #getParseErrorMessage()
+ * @see #handleUnparsableDateString(String)
+ * @param parsingErrorMessage
+ */
+ public void setParseErrorMessage(String parsingErrorMessage) {
+ defaultParseErrorMessage = parsingErrorMessage;
+ }
+
+ /**
+ * Sets the time zone used by this date field. The time zone is used to
+ * convert the absolute time in a Date object to a logical time displayed in
+ * the selector and to convert the select time back to a Date object.
+ *
+ * If no time zone has been set, the current default time zone returned by
+ * {@code TimeZone.getDefault()} is used.
+ *
+ * @see #getTimeZone()
+ * @param timeZone
+ * the time zone to use for time calculations.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the time zone used by this field. The time zone is used to convert
+ * the absolute time in a Date object to a logical time displayed in the
+ * selector and to convert the select time back to a Date object.
+ *
+ * If {@code null} is returned, the current default time zone returned by
+ * {@code TimeZone.getDefault()} is used.
+ *
+ * @return the current time zone
+ */
+ public TimeZone getTimeZone() {
+ return timeZone;
+ }
+
+ public static class UnparsableDateString extends
+ Validator.InvalidValueException {
+
+ public UnparsableDateString(String message) {
+ super(message);
+ }
+
+ }
+}
diff --git a/server/src/com/vaadin/ui/DefaultFieldFactory.java b/server/src/com/vaadin/ui/DefaultFieldFactory.java
new file mode 100644
index 0000000000..e17f08c1c6
--- /dev/null
+++ b/server/src/com/vaadin/ui/DefaultFieldFactory.java
@@ -0,0 +1,146 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.util.Date;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * This class contains a basic implementation for both {@link FormFieldFactory}
+ * and {@link TableFieldFactory}. The class is singleton, use {@link #get()}
+ * method to get reference to the instance.
+ *
+ * <p>
+ * There are also some static helper methods available for custom built field
+ * factories.
+ *
+ */
+public class DefaultFieldFactory implements FormFieldFactory, TableFieldFactory {
+
+ private static final DefaultFieldFactory instance = new DefaultFieldFactory();
+
+ /**
+ * Singleton method to get an instance of DefaultFieldFactory.
+ *
+ * @return an instance of DefaultFieldFactory
+ */
+ public static DefaultFieldFactory get() {
+ return instance;
+ }
+
+ protected DefaultFieldFactory() {
+ }
+
+ @Override
+ public Field<?> createField(Item item, Object propertyId,
+ Component uiContext) {
+ Class<?> type = item.getItemProperty(propertyId).getType();
+ Field<?> field = createFieldByPropertyType(type);
+ field.setCaption(createCaptionByPropertyId(propertyId));
+ return field;
+ }
+
+ @Override
+ public Field<?> createField(Container container, Object itemId,
+ Object propertyId, Component uiContext) {
+ Property<?> containerProperty = container.getContainerProperty(itemId,
+ propertyId);
+ Class<?> type = containerProperty.getType();
+ Field<?> field = createFieldByPropertyType(type);
+ field.setCaption(createCaptionByPropertyId(propertyId));
+ return field;
+ }
+
+ /**
+ * If name follows method naming conventions, convert the name to spaced
+ * upper case text. For example, convert "firstName" to "First Name"
+ *
+ * @param propertyId
+ * @return the formatted caption string
+ */
+ public static String createCaptionByPropertyId(Object propertyId) {
+ String name = propertyId.toString();
+ if (name.length() > 0) {
+
+ int dotLocation = name.lastIndexOf('.');
+ if (dotLocation > 0 && dotLocation < name.length() - 1) {
+ name = name.substring(dotLocation + 1);
+ }
+ if (name.indexOf(' ') < 0
+ && name.charAt(0) == Character.toLowerCase(name.charAt(0))
+ && name.charAt(0) != Character.toUpperCase(name.charAt(0))) {
+ StringBuffer out = new StringBuffer();
+ out.append(Character.toUpperCase(name.charAt(0)));
+ int i = 1;
+
+ while (i < name.length()) {
+ int j = i;
+ for (; j < name.length(); j++) {
+ char c = name.charAt(j);
+ if (Character.toLowerCase(c) != c
+ && Character.toUpperCase(c) == c) {
+ break;
+ }
+ }
+ if (j == name.length()) {
+ out.append(name.substring(i));
+ } else {
+ out.append(name.substring(i, j));
+ out.append(" " + name.charAt(j));
+ }
+ i = j + 1;
+ }
+
+ name = out.toString();
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Creates fields based on the property type.
+ * <p>
+ * The default field type is {@link TextField}. Other field types generated
+ * by this method:
+ * <p>
+ * <b>Boolean</b>: {@link CheckBox}.<br/>
+ * <b>Date</b>: {@link DateField}(resolution: day).<br/>
+ * <b>Item</b>: {@link Form}. <br/>
+ * <b>default field type</b>: {@link TextField}.
+ * <p>
+ *
+ * @param type
+ * the type of the property
+ * @return the most suitable generic {@link Field} for given type
+ */
+ public static Field<?> createFieldByPropertyType(Class<?> type) {
+ // Null typed properties can not be edited
+ if (type == null) {
+ return null;
+ }
+
+ // Item field
+ if (Item.class.isAssignableFrom(type)) {
+ return new Form();
+ }
+
+ // Date field
+ if (Date.class.isAssignableFrom(type)) {
+ final DateField df = new DateField();
+ df.setResolution(DateField.RESOLUTION_DAY);
+ return df;
+ }
+
+ // Boolean field
+ if (Boolean.class.isAssignableFrom(type)) {
+ return new CheckBox();
+ }
+
+ return new TextField();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/DragAndDropWrapper.java b/server/src/com/vaadin/ui/DragAndDropWrapper.java
new file mode 100644
index 0000000000..67229a45fe
--- /dev/null
+++ b/server/src/com/vaadin/ui/DragAndDropWrapper.java
@@ -0,0 +1,407 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.vaadin.event.Transferable;
+import com.vaadin.event.TransferableImpl;
+import com.vaadin.event.dd.DragSource;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetails;
+import com.vaadin.event.dd.TargetDetailsImpl;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.dd.HorizontalDropLocation;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.StreamVariable;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper;
+
+@SuppressWarnings("serial")
+public class DragAndDropWrapper extends CustomComponent implements DropTarget,
+ DragSource, Vaadin6Component {
+
+ public class WrapperTransferable extends TransferableImpl {
+
+ private Html5File[] files;
+
+ public WrapperTransferable(Component sourceComponent,
+ Map<String, Object> rawVariables) {
+ super(sourceComponent, rawVariables);
+ Integer fc = (Integer) rawVariables.get("filecount");
+ if (fc != null) {
+ files = new Html5File[fc];
+ for (int i = 0; i < fc; i++) {
+ Html5File file = new Html5File(
+ (String) rawVariables.get("fn" + i), // name
+ (Integer) rawVariables.get("fs" + i), // size
+ (String) rawVariables.get("ft" + i)); // mime
+ String id = (String) rawVariables.get("fi" + i);
+ files[i] = file;
+ receivers.put(id, file);
+ requestRepaint(); // paint Receivers
+ }
+ }
+ }
+
+ /**
+ * The component in wrapper that is being dragged or null if the
+ * transferable is not a component (most likely an html5 drag).
+ *
+ * @return
+ */
+ public Component getDraggedComponent() {
+ Component object = (Component) getData("component");
+ return object;
+ }
+
+ /**
+ * @return the mouse down event that started the drag and drop operation
+ */
+ public MouseEventDetails getMouseDownEvent() {
+ return MouseEventDetails.deSerialize((String) getData("mouseDown"));
+ }
+
+ public Html5File[] getFiles() {
+ return files;
+ }
+
+ public String getText() {
+ String data = (String) getData("Text"); // IE, html5
+ if (data == null) {
+ // check for "text/plain" (webkit)
+ data = (String) getData("text/plain");
+ }
+ return data;
+ }
+
+ public String getHtml() {
+ String data = (String) getData("Html"); // IE, html5
+ if (data == null) {
+ // check for "text/plain" (webkit)
+ data = (String) getData("text/html");
+ }
+ return data;
+ }
+
+ }
+
+ private Map<String, Html5File> receivers = new HashMap<String, Html5File>();
+
+ public class WrapperTargetDetails extends TargetDetailsImpl {
+
+ public WrapperTargetDetails(Map<String, Object> rawDropData) {
+ super(rawDropData, DragAndDropWrapper.this);
+ }
+
+ /**
+ * @return the absolute position of wrapper on the page
+ */
+ public Integer getAbsoluteLeft() {
+ return (Integer) getData("absoluteLeft");
+ }
+
+ /**
+ *
+ * @return the absolute position of wrapper on the page
+ */
+ public Integer getAbsoluteTop() {
+ return (Integer) getData("absoluteTop");
+ }
+
+ /**
+ * @return details about the actual event that caused the event details.
+ * Practically mouse move or mouse up.
+ */
+ public MouseEventDetails getMouseEvent() {
+ return MouseEventDetails
+ .deSerialize((String) getData("mouseEvent"));
+ }
+
+ /**
+ * @return a detail about the drags vertical position over the wrapper.
+ */
+ public VerticalDropLocation getVerticalDropLocation() {
+ return VerticalDropLocation
+ .valueOf((String) getData("verticalLocation"));
+ }
+
+ /**
+ * @return a detail about the drags horizontal position over the
+ * wrapper.
+ */
+ public HorizontalDropLocation getHorizontalDropLocation() {
+ return HorizontalDropLocation
+ .valueOf((String) getData("horizontalLocation"));
+ }
+
+ /**
+ * @deprecated use {@link #getVerticalDropLocation()} instead
+ */
+ @Deprecated
+ public VerticalDropLocation verticalDropLocation() {
+ return getVerticalDropLocation();
+ }
+
+ /**
+ * @deprecated use {@link #getHorizontalDropLocation()} instead
+ */
+ @Deprecated
+ public HorizontalDropLocation horizontalDropLocation() {
+ return getHorizontalDropLocation();
+ }
+
+ }
+
+ public enum DragStartMode {
+ /**
+ * {@link DragAndDropWrapper} does not start drag events at all
+ */
+ NONE,
+ /**
+ * The component on which the drag started will be shown as drag image.
+ */
+ COMPONENT,
+ /**
+ * The whole wrapper is used as a drag image when dragging.
+ */
+ WRAPPER,
+ /**
+ * The whole wrapper is used to start an HTML5 drag.
+ *
+ * NOTE: In Internet Explorer 6 to 8, this prevents user interactions
+ * with the wrapper's contents. For example, clicking a button inside
+ * the wrapper will no longer work.
+ */
+ HTML5,
+ }
+
+ private final Map<String, Object> html5DataFlavors = new LinkedHashMap<String, Object>();
+ private DragStartMode dragStartMode = DragStartMode.NONE;
+
+ /**
+ * Wraps given component in a {@link DragAndDropWrapper}.
+ *
+ * @param root
+ * the component to be wrapped
+ */
+ public DragAndDropWrapper(Component root) {
+ super(root);
+ }
+
+ /**
+ * Sets data flavors available in the DragAndDropWrapper is used to start an
+ * HTML5 style drags. Most commonly the "Text" flavor should be set.
+ * Multiple data types can be set.
+ *
+ * @param type
+ * the string identifier of the drag "payload". E.g. "Text" or
+ * "text/html"
+ * @param value
+ * the value
+ */
+ public void setHTML5DataFlavor(String type, Object value) {
+ html5DataFlavors.put(type, value);
+ requestRepaint();
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // TODO Remove once Vaadin6Component is no longer implemented
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute(VDragAndDropWrapper.DRAG_START_MODE,
+ dragStartMode.ordinal());
+ if (getDropHandler() != null) {
+ getDropHandler().getAcceptCriterion().paint(target);
+ }
+ if (receivers != null && receivers.size() > 0) {
+ for (Iterator<Entry<String, Html5File>> it = receivers.entrySet()
+ .iterator(); it.hasNext();) {
+ Entry<String, com.vaadin.ui.Html5File> entry = it.next();
+ String id = entry.getKey();
+ Html5File html5File = entry.getValue();
+ if (html5File.getStreamVariable() != null) {
+ target.addVariable(this, "rec-" + id, new ProxyReceiver(
+ html5File));
+ // these are cleaned from receivers once the upload has
+ // started
+ } else {
+ // instructs the client side not to send the file
+ target.addVariable(this, "rec-" + id, (String) null);
+ // forget the file from subsequent paints
+ it.remove();
+ }
+ }
+ }
+ target.addAttribute(VDragAndDropWrapper.HTML5_DATA_FLAVORS,
+ html5DataFlavors);
+ }
+
+ private DropHandler dropHandler;
+
+ @Override
+ public DropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ public void setDropHandler(DropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ requestRepaint();
+ }
+
+ @Override
+ public TargetDetails translateDropTargetDetails(
+ Map<String, Object> clientVariables) {
+ return new WrapperTargetDetails(clientVariables);
+ }
+
+ @Override
+ public Transferable getTransferable(final Map<String, Object> rawVariables) {
+ return new WrapperTransferable(this, rawVariables);
+ }
+
+ public void setDragStartMode(DragStartMode dragStartMode) {
+ this.dragStartMode = dragStartMode;
+ requestRepaint();
+ }
+
+ public DragStartMode getDragStartMode() {
+ return dragStartMode;
+ }
+
+ final class ProxyReceiver implements StreamVariable {
+
+ private Html5File file;
+
+ public ProxyReceiver(Html5File file) {
+ this.file = file;
+ }
+
+ private boolean listenProgressOfUploadedFile;
+
+ @Override
+ public OutputStream getOutputStream() {
+ if (file.getStreamVariable() == null) {
+ return null;
+ }
+ return file.getStreamVariable().getOutputStream();
+ }
+
+ @Override
+ public boolean listenProgress() {
+ return file.getStreamVariable().listenProgress();
+ }
+
+ @Override
+ public void onProgress(StreamingProgressEvent event) {
+ file.getStreamVariable().onProgress(
+ new ReceivingEventWrapper(event));
+ }
+
+ @Override
+ public void streamingStarted(StreamingStartEvent event) {
+ listenProgressOfUploadedFile = file.getStreamVariable() != null;
+ if (listenProgressOfUploadedFile) {
+ file.getStreamVariable().streamingStarted(
+ new ReceivingEventWrapper(event));
+ }
+ // no need tell to the client about this receiver on next paint
+ receivers.remove(file);
+ // let the terminal GC the streamvariable and not to accept other
+ // file uploads to this variable
+ event.disposeStreamVariable();
+ }
+
+ @Override
+ public void streamingFinished(StreamingEndEvent event) {
+ if (listenProgressOfUploadedFile) {
+ file.getStreamVariable().streamingFinished(
+ new ReceivingEventWrapper(event));
+ }
+ }
+
+ @Override
+ public void streamingFailed(final StreamingErrorEvent event) {
+ if (listenProgressOfUploadedFile) {
+ file.getStreamVariable().streamingFailed(
+ new ReceivingEventWrapper(event));
+ }
+ }
+
+ @Override
+ public boolean isInterrupted() {
+ return file.getStreamVariable().isInterrupted();
+ }
+
+ /*
+ * With XHR2 file posts we can't provide as much information from the
+ * terminal as with multipart request. This helper class wraps the
+ * terminal event and provides the lacking information from the
+ * Html5File.
+ */
+ class ReceivingEventWrapper implements StreamingErrorEvent,
+ StreamingEndEvent, StreamingStartEvent, StreamingProgressEvent {
+
+ private StreamingEvent wrappedEvent;
+
+ ReceivingEventWrapper(StreamingEvent e) {
+ wrappedEvent = e;
+ }
+
+ @Override
+ public String getMimeType() {
+ return file.getType();
+ }
+
+ @Override
+ public String getFileName() {
+ return file.getFileName();
+ }
+
+ @Override
+ public long getContentLength() {
+ return file.getFileSize();
+ }
+
+ public StreamVariable getReceiver() {
+ return ProxyReceiver.this;
+ }
+
+ @Override
+ public Exception getException() {
+ if (wrappedEvent instanceof StreamingErrorEvent) {
+ return ((StreamingErrorEvent) wrappedEvent).getException();
+ }
+ return null;
+ }
+
+ @Override
+ public long getBytesReceived() {
+ return wrappedEvent.getBytesReceived();
+ }
+
+ /**
+ * Calling this method has no effect. DD files are receive only once
+ * anyway.
+ */
+ @Override
+ public void disposeStreamVariable() {
+
+ }
+ }
+
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Embedded.java b/server/src/com/vaadin/ui/Embedded.java
new file mode 100644
index 0000000000..6088c5aa66
--- /dev/null
+++ b/server/src/com/vaadin/ui/Embedded.java
@@ -0,0 +1,531 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vaadin.event.MouseEvents.ClickEvent;
+import com.vaadin.event.MouseEvents.ClickListener;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.embedded.EmbeddedServerRpc;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.ClickEventHandler;
+import com.vaadin.terminal.gwt.client.ui.embedded.EmbeddedConnector;
+
+/**
+ * Component for embedding external objects.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Embedded extends AbstractComponent implements Vaadin6Component {
+
+ /**
+ * General object type.
+ */
+ public static final int TYPE_OBJECT = 0;
+
+ /**
+ * Image types.
+ */
+ public static final int TYPE_IMAGE = 1;
+
+ /**
+ * Browser ("iframe") type.
+ */
+ public static final int TYPE_BROWSER = 2;
+
+ /**
+ * Type of the object.
+ */
+ private int type = TYPE_OBJECT;
+
+ /**
+ * Source of the embedded object.
+ */
+ private Resource source = null;
+
+ /**
+ * Generic object attributes.
+ */
+ private String mimeType = null;
+
+ private String standby = null;
+
+ /**
+ * Hash of object parameters.
+ */
+ private final Map<String, String> parameters = new HashMap<String, String>();
+
+ /**
+ * Applet or other client side runnable properties.
+ */
+ private String codebase = null;
+
+ private String codetype = null;
+
+ private String classId = null;
+
+ private String archive = null;
+
+ private String altText;
+
+ private EmbeddedServerRpc rpc = new EmbeddedServerRpc() {
+ @Override
+ public void click(MouseEventDetails mouseDetails) {
+ fireEvent(new ClickEvent(Embedded.this, mouseDetails));
+ }
+ };
+
+ /**
+ * Creates a new empty Embedded object.
+ */
+ public Embedded() {
+ registerRpc(rpc);
+ }
+
+ /**
+ * Creates a new empty Embedded object with caption.
+ *
+ * @param caption
+ */
+ public Embedded(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new Embedded object whose contents is loaded from given
+ * resource. The dimensions are assumed if possible. The type is guessed
+ * from resource.
+ *
+ * @param caption
+ * @param source
+ * the Source of the embedded object.
+ */
+ public Embedded(String caption, Resource source) {
+ this(caption);
+ setSource(source);
+ }
+
+ /**
+ * Invoked when the component state should be painted.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ switch (type) {
+ case TYPE_IMAGE:
+ target.addAttribute("type", "image");
+ break;
+ case TYPE_BROWSER:
+ target.addAttribute("type", "browser");
+ break;
+ default:
+ break;
+ }
+
+ if (getSource() != null) {
+ target.addAttribute("src", getSource());
+ }
+
+ if (mimeType != null && !"".equals(mimeType)) {
+ target.addAttribute("mimetype", mimeType);
+ }
+ if (classId != null && !"".equals(classId)) {
+ target.addAttribute("classid", classId);
+ }
+ if (codebase != null && !"".equals(codebase)) {
+ target.addAttribute("codebase", codebase);
+ }
+ if (codetype != null && !"".equals(codetype)) {
+ target.addAttribute("codetype", codetype);
+ }
+ if (standby != null && !"".equals(standby)) {
+ target.addAttribute("standby", standby);
+ }
+ if (archive != null && !"".equals(archive)) {
+ target.addAttribute("archive", archive);
+ }
+ if (altText != null && !"".equals(altText)) {
+ target.addAttribute(EmbeddedConnector.ALTERNATE_TEXT, altText);
+ }
+
+ // Params
+ for (final Iterator<String> i = getParameterNames(); i.hasNext();) {
+ target.startTag("embeddedparam");
+ final String key = i.next();
+ target.addAttribute("name", key);
+ target.addAttribute("value", getParameter(key));
+ target.endTag("embeddedparam");
+ }
+ }
+
+ /**
+ * Sets this component's "alt-text", that is, an alternate text that can be
+ * presented instead of this component's normal content, for accessibility
+ * purposes. Does not work when {@link #setType(int)} has been called with
+ * {@link #TYPE_BROWSER}.
+ *
+ * @param altText
+ * A short, human-readable description of this component's
+ * content.
+ * @since 6.8
+ */
+ public void setAlternateText(String altText) {
+ if (altText != this.altText
+ || (altText != null && !altText.equals(this.altText))) {
+ this.altText = altText;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets this component's "alt-text".
+ *
+ * @see #setAlternateText(String)
+ */
+ public String getAlternateText() {
+ return altText;
+ }
+
+ /**
+ * Sets an object parameter. Parameters are optional information, and they
+ * are passed to the instantiated object. Parameters are are stored as name
+ * value pairs. This overrides the previous value assigned to this
+ * parameter.
+ *
+ * @param name
+ * the name of the parameter.
+ * @param value
+ * the value of the parameter.
+ */
+ public void setParameter(String name, String value) {
+ parameters.put(name, value);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the value of an object parameter. Parameters are optional
+ * information, and they are passed to the instantiated object. Parameters
+ * are are stored as name value pairs.
+ *
+ * @return the Value of parameter or null if not found.
+ */
+ public String getParameter(String name) {
+ return parameters.get(name);
+ }
+
+ /**
+ * Removes an object parameter from the list.
+ *
+ * @param name
+ * the name of the parameter to remove.
+ */
+ public void removeParameter(String name) {
+ parameters.remove(name);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the embedded object parameter names.
+ *
+ * @return the Iterator of parameters names.
+ */
+ public Iterator<String> getParameterNames() {
+ return parameters.keySet().iterator();
+ }
+
+ /**
+ * This attribute specifies the base path used to resolve relative URIs
+ * specified by the classid, data, and archive attributes. When absent, its
+ * default value is the base URI of the current document.
+ *
+ * @return the code base.
+ */
+ public String getCodebase() {
+ return codebase;
+ }
+
+ /**
+ * Gets the MIME-Type of the code.
+ *
+ * @return the MIME-Type of the code.
+ */
+ public String getCodetype() {
+ return codetype;
+ }
+
+ /**
+ * Gets the MIME-Type of the object.
+ *
+ * @return the MIME-Type of the object.
+ */
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ /**
+ * This attribute specifies a message that a user agent may render while
+ * loading the object's implementation and data.
+ *
+ * @return The text displayed when loading
+ */
+ public String getStandby() {
+ return standby;
+ }
+
+ /**
+ * This attribute specifies the base path used to resolve relative URIs
+ * specified by the classid, data, and archive attributes. When absent, its
+ * default value is the base URI of the current document.
+ *
+ * @param codebase
+ * The base path
+ */
+ public void setCodebase(String codebase) {
+ if (codebase != this.codebase
+ || (codebase != null && !codebase.equals(this.codebase))) {
+ this.codebase = codebase;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * This attribute specifies the content type of data expected when
+ * downloading the object specified by classid. This attribute is optional
+ * but recommended when classid is specified since it allows the user agent
+ * to avoid loading information for unsupported content types. When absent,
+ * it defaults to the value of the type attribute.
+ *
+ * @param codetype
+ * the codetype to set.
+ */
+ public void setCodetype(String codetype) {
+ if (codetype != this.codetype
+ || (codetype != null && !codetype.equals(this.codetype))) {
+ this.codetype = codetype;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets the mimeType, the MIME-Type of the object.
+ *
+ * @param mimeType
+ * the mimeType to set.
+ */
+ public void setMimeType(String mimeType) {
+ if (mimeType != this.mimeType
+ || (mimeType != null && !mimeType.equals(this.mimeType))) {
+ this.mimeType = mimeType;
+ if ("application/x-shockwave-flash".equals(mimeType)) {
+ /*
+ * Automatically add wmode transparent as we use lots of
+ * floating layers in Vaadin. If developers need better flash
+ * performance, they can override this value programmatically
+ * back to "window" (the defautl).
+ */
+ if (getParameter("wmode") == null) {
+ setParameter("wmode", "transparent");
+ }
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * This attribute specifies a message that a user agent may render while
+ * loading the object's implementation and data.
+ *
+ * @param standby
+ * The text to display while loading
+ */
+ public void setStandby(String standby) {
+ if (standby != this.standby
+ || (standby != null && !standby.equals(this.standby))) {
+ this.standby = standby;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * This attribute may be used to specify the location of an object's
+ * implementation via a URI.
+ *
+ * @return the classid.
+ */
+ public String getClassId() {
+ return classId;
+ }
+
+ /**
+ * This attribute may be used to specify the location of an object's
+ * implementation via a URI.
+ *
+ * @param classId
+ * the classId to set.
+ */
+ public void setClassId(String classId) {
+ if (classId != this.classId
+ || (classId != null && !classId.equals(this.classId))) {
+ this.classId = classId;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the resource contained in the embedded object.
+ *
+ * @return the Resource
+ */
+ public Resource getSource() {
+ return source;
+ }
+
+ /**
+ * Gets the type of the embedded object.
+ * <p>
+ * This can be one of the following:
+ * <ul>
+ * <li>TYPE_OBJECT <i>(This is the default)</i>
+ * <li>TYPE_IMAGE
+ * </ul>
+ * </p>
+ *
+ * @return the type.
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Sets the object source resource. The dimensions are assumed if possible.
+ * The type is guessed from resource.
+ *
+ * @param source
+ * the source to set.
+ */
+ public void setSource(Resource source) {
+ if (source != null && !source.equals(this.source)) {
+ this.source = source;
+ final String mt = source.getMIMEType();
+
+ if (mimeType == null) {
+ mimeType = mt;
+ }
+
+ if (mt.equals("image/svg+xml")) {
+ type = TYPE_OBJECT;
+ } else if ((mt.substring(0, mt.indexOf("/"))
+ .equalsIgnoreCase("image"))) {
+ type = TYPE_IMAGE;
+ } else {
+ // Keep previous type
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets the object type.
+ * <p>
+ * This can be one of the following:
+ * <ul>
+ * <li>TYPE_OBJECT <i>(This is the default)</i>
+ * <li>TYPE_IMAGE
+ * <li>TYPE_BROWSER
+ * </ul>
+ * </p>
+ *
+ * @param type
+ * the type to set.
+ */
+ public void setType(int type) {
+ if (type != TYPE_OBJECT && type != TYPE_IMAGE && type != TYPE_BROWSER) {
+ throw new IllegalArgumentException("Unsupported type");
+ }
+ if (type != this.type) {
+ this.type = type;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * This attribute may be used to specify a space-separated list of URIs for
+ * archives containing resources relevant to the object, which may include
+ * the resources specified by the classid and data attributes. Preloading
+ * archives will generally result in reduced load times for objects.
+ * Archives specified as relative URIs should be interpreted relative to the
+ * codebase attribute.
+ *
+ * @return Space-separated list of URIs with resources relevant to the
+ * object
+ */
+ public String getArchive() {
+ return archive;
+ }
+
+ /**
+ * This attribute may be used to specify a space-separated list of URIs for
+ * archives containing resources relevant to the object, which may include
+ * the resources specified by the classid and data attributes. Preloading
+ * archives will generally result in reduced load times for objects.
+ * Archives specified as relative URIs should be interpreted relative to the
+ * codebase attribute.
+ *
+ * @param archive
+ * Space-separated list of URIs with resources relevant to the
+ * object
+ */
+ public void setArchive(String archive) {
+ if (archive != this.archive
+ || (archive != null && !archive.equals(this.archive))) {
+ this.archive = archive;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Add a click listener to the component. The listener is called whenever
+ * the user clicks inside the component. Depending on the content the event
+ * may be blocked and in that case no event is fired.
+ *
+ * Use {@link #removeListener(ClickListener)} to remove the listener.
+ *
+ * @param listener
+ * The listener to add
+ */
+ public void addListener(ClickListener listener) {
+ addListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, ClickEvent.class,
+ listener, ClickListener.clickMethod);
+ }
+
+ /**
+ * Remove a click listener from the component. The listener should earlier
+ * have been added using {@link #addListener(ClickListener)}.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeListener(ClickListener listener) {
+ removeListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER,
+ ClickEvent.class, listener);
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // TODO Remove once Vaadin6Component is no longer implemented
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Field.java b/server/src/com/vaadin/ui/Field.java
new file mode 100644
index 0000000000..6dc40d192f
--- /dev/null
+++ b/server/src/com/vaadin/ui/Field.java
@@ -0,0 +1,97 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.data.BufferedValidatable;
+import com.vaadin.data.Property;
+import com.vaadin.ui.Component.Focusable;
+
+/**
+ * TODO document
+ *
+ * @author Vaadin Ltd.
+ *
+ * @param T
+ * the type of values in the field, which might not be the same type
+ * as that of the data source if converters are used
+ *
+ * @author IT Mill Ltd.
+ */
+public interface Field<T> extends Component, BufferedValidatable, Property<T>,
+ Property.ValueChangeNotifier, Property.ValueChangeListener,
+ Property.Editor, Focusable {
+
+ /**
+ * Is this field required.
+ *
+ * Required fields must filled by the user.
+ *
+ * @return <code>true</code> if the field is required,otherwise
+ * <code>false</code>.
+ * @since 3.1
+ */
+ public boolean isRequired();
+
+ /**
+ * Sets the field required. Required fields must filled by the user.
+ *
+ * @param required
+ * Is the field required.
+ * @since 3.1
+ */
+ public void setRequired(boolean required);
+
+ /**
+ * Sets the error message to be displayed if a required field is empty.
+ *
+ * @param requiredMessage
+ * Error message.
+ * @since 5.2.6
+ */
+ public void setRequiredError(String requiredMessage);
+
+ /**
+ * Gets the error message that is to be displayed if a required field is
+ * empty.
+ *
+ * @return Error message.
+ * @since 5.2.6
+ */
+ public String getRequiredError();
+
+ /**
+ * An <code>Event</code> object specifying the Field whose value has been
+ * changed.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public static class ValueChangeEvent extends Component.Event implements
+ Property.ValueChangeEvent {
+
+ /**
+ * Constructs a new event object with the specified source field object.
+ *
+ * @param source
+ * the field that caused the event.
+ */
+ public ValueChangeEvent(Field source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property which triggered the event.
+ *
+ * @return the Source Property of the event.
+ */
+ @Override
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+}
diff --git a/server/src/com/vaadin/ui/Form.java b/server/src/com/vaadin/ui/Form.java
new file mode 100644
index 0000000000..fbc4d5a8e6
--- /dev/null
+++ b/server/src/com/vaadin/ui/Form.java
@@ -0,0 +1,1420 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+import com.vaadin.data.Buffered;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.Validatable;
+import com.vaadin.data.Validator;
+import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.data.fieldgroup.FieldGroup;
+import com.vaadin.data.util.BeanItem;
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.Action.ShortcutNotifier;
+import com.vaadin.event.ActionManager;
+import com.vaadin.shared.ui.form.FormState;
+import com.vaadin.terminal.AbstractErrorMessage;
+import com.vaadin.terminal.CompositeErrorMessage;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.UserError;
+import com.vaadin.terminal.Vaadin6Component;
+
+/**
+ * Form component provides easy way of creating and managing sets fields.
+ *
+ * <p>
+ * <code>Form</code> is a container for fields implementing {@link Field}
+ * interface. It provides support for any layouts and provides buffering
+ * interface for easy connection of commit and discard buttons. All the form
+ * fields can be customized by adding validators, setting captions and icons,
+ * setting immediateness, etc. Also direct mechanism for replacing existing
+ * fields with selections is given.
+ * </p>
+ *
+ * <p>
+ * <code>Form</code> provides customizable editor for classes implementing
+ * {@link com.vaadin.data.Item} interface. Also the form itself implements this
+ * interface for easier connectivity to other items. To use the form as editor
+ * for an item, just connect the item to form with
+ * {@link Form#setItemDataSource(Item)}. If only a part of the item needs to be
+ * edited, {@link Form#setItemDataSource(Item,Collection)} can be used instead.
+ * After the item has been connected to the form, the automatically created
+ * fields can be customized and new fields can be added. If you need to connect
+ * a class that does not implement {@link com.vaadin.data.Item} interface, most
+ * properties of any class following bean pattern, can be accessed trough
+ * {@link com.vaadin.data.util.BeanItem}.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ * @deprecated Use {@link FieldGroup} instead of {@link Form} for more
+ * flexibility.
+ */
+@Deprecated
+public class Form extends AbstractField<Object> implements Item.Editor,
+ Buffered, Item, Validatable, Action.Notifier, HasComponents,
+ Vaadin6Component {
+
+ private Object propertyValue;
+
+ /**
+ * Item connected to this form as datasource.
+ */
+ private Item itemDatasource;
+
+ /**
+ * Ordered list of property ids in this editor.
+ */
+ private final LinkedList<Object> propertyIds = new LinkedList<Object>();
+
+ /**
+ * Current buffered source exception.
+ */
+ private Buffered.SourceException currentBufferedSourceException = null;
+
+ /**
+ * Is the form in write trough mode.
+ */
+ private boolean writeThrough = true;
+
+ /**
+ * Is the form in read trough mode.
+ */
+ private boolean readThrough = true;
+
+ /**
+ * Mapping from propertyName to corresponding field.
+ */
+ private final HashMap<Object, Field<?>> fields = new HashMap<Object, Field<?>>();
+
+ /**
+ * Form may act as an Item, its own properties are stored here.
+ */
+ private final HashMap<Object, Property<?>> ownProperties = new HashMap<Object, Property<?>>();
+
+ /**
+ * Field factory for this form.
+ */
+ private FormFieldFactory fieldFactory;
+
+ /**
+ * Visible item properties.
+ */
+ private Collection<?> visibleItemProperties;
+
+ /**
+ * Form needs to repaint itself if child fields value changes due possible
+ * change in form validity.
+ *
+ * TODO introduce ValidityChangeEvent (#6239) and start using it instead.
+ * See e.g. DateField#notifyFormOfValidityChange().
+ */
+ private final ValueChangeListener fieldValueChangeListener = new ValueChangeListener() {
+ @Override
+ public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
+ requestRepaint();
+ }
+ };
+
+ /**
+ * If this is true, commit implicitly calls setValidationVisible(true).
+ */
+ private boolean validationVisibleOnCommit = true;
+
+ // special handling for gridlayout; remember initial cursor pos
+ private int gridlayoutCursorX = -1;
+ private int gridlayoutCursorY = -1;
+
+ /**
+ * Keeps track of the Actions added to this component, and manages the
+ * painting and handling as well. Note that the extended AbstractField is a
+ * {@link ShortcutNotifier} and has a actionManager that delegates actions
+ * to the containing window. This one does not delegate.
+ */
+ private ActionManager ownActionManager = new ActionManager(this);
+
+ /**
+ * Constructs a new form with default layout.
+ *
+ * <p>
+ * By default the form uses {@link FormLayout}.
+ * </p>
+ */
+ public Form() {
+ this(null);
+ setValidationVisible(false);
+ }
+
+ /**
+ * Constructs a new form with given {@link Layout}.
+ *
+ * @param formLayout
+ * the layout of the form.
+ */
+ public Form(Layout formLayout) {
+ this(formLayout, DefaultFieldFactory.get());
+ }
+
+ /**
+ * Constructs a new form with given {@link Layout} and
+ * {@link FormFieldFactory}.
+ *
+ * @param formLayout
+ * the layout of the form.
+ * @param fieldFactory
+ * the FieldFactory of the form.
+ */
+ public Form(Layout formLayout, FormFieldFactory fieldFactory) {
+ super();
+ setLayout(formLayout);
+ setFooter(null);
+ setFormFieldFactory(fieldFactory);
+ setValidationVisible(false);
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ @Override
+ public FormState getState() {
+ return (FormState) super.getState();
+ }
+
+ /* Documented in interface */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (ownActionManager != null) {
+ ownActionManager.paintActions(null, target);
+ }
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // Actions
+ if (ownActionManager != null) {
+ ownActionManager.handleActions(variables, this);
+ }
+ }
+
+ /**
+ * The error message of a Form is the error of the first field with a
+ * non-empty error.
+ *
+ * Empty error messages of the contained fields are skipped, because an
+ * empty error indicator would be confusing to the user, especially if there
+ * are errors that have something to display. This is also the reason why
+ * the calculation of the error message is separate from validation, because
+ * validation fails also on empty errors.
+ */
+ @Override
+ public ErrorMessage getErrorMessage() {
+
+ // Reimplement the checking of validation error by using
+ // getErrorMessage() recursively instead of validate().
+ ErrorMessage validationError = null;
+ if (isValidationVisible()) {
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ Object f = fields.get(i.next());
+ if (f instanceof AbstractComponent) {
+ AbstractComponent field = (AbstractComponent) f;
+
+ validationError = field.getErrorMessage();
+ if (validationError != null) {
+ // Show caption as error for fields with empty errors
+ if ("".equals(validationError.toString())) {
+ validationError = new UserError(field.getCaption());
+ }
+ break;
+ } else if (f instanceof Field && !((Field<?>) f).isValid()) {
+ // Something is wrong with the field, but no proper
+ // error is given. Generate one.
+ validationError = new UserError(field.getCaption());
+ break;
+ }
+ }
+ }
+ }
+
+ // Return if there are no errors at all
+ if (getComponentError() == null && validationError == null
+ && currentBufferedSourceException == null) {
+ return null;
+ }
+
+ // Throw combination of the error types
+ return new CompositeErrorMessage(
+ new ErrorMessage[] {
+ getComponentError(),
+ validationError,
+ AbstractErrorMessage
+ .getErrorMessageForException(currentBufferedSourceException) });
+ }
+
+ /**
+ * Controls the making validation visible implicitly on commit.
+ *
+ * Having commit() call setValidationVisible(true) implicitly is the default
+ * behaviour. You can disable the implicit setting by setting this property
+ * as false.
+ *
+ * It is useful, because you usually want to start with the form free of
+ * errors and only display them after the user clicks Ok. You can disable
+ * the implicit setting by setting this property as false.
+ *
+ * @param makeVisible
+ * If true (default), validation is made visible when commit() is
+ * called. If false, the visibility is left as it is.
+ */
+ public void setValidationVisibleOnCommit(boolean makeVisible) {
+ validationVisibleOnCommit = makeVisible;
+ }
+
+ /**
+ * Is validation made automatically visible on commit?
+ *
+ * See setValidationVisibleOnCommit().
+ *
+ * @return true if validation is made automatically visible on commit.
+ */
+ public boolean isValidationVisibleOnCommit() {
+ return validationVisibleOnCommit;
+ }
+
+ /*
+ * Commit changes to the data source Don't add a JavaDoc comment here, we
+ * use the default one from the interface.
+ */
+ @Override
+ public void commit() throws Buffered.SourceException, InvalidValueException {
+
+ LinkedList<SourceException> problems = null;
+
+ // Only commit on valid state if so requested
+ if (!isInvalidCommitted() && !isValid()) {
+ /*
+ * The values are not ok and we are told not to commit invalid
+ * values
+ */
+ if (validationVisibleOnCommit) {
+ setValidationVisible(true);
+ }
+
+ // Find the first invalid value and throw the exception
+ validate();
+ }
+
+ // Try to commit all
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ try {
+ final Field<?> f = (fields.get(i.next()));
+ // Commit only non-readonly fields.
+ if (!f.isReadOnly()) {
+ f.commit();
+ }
+ } catch (final Buffered.SourceException e) {
+ if (problems == null) {
+ problems = new LinkedList<SourceException>();
+ }
+ problems.add(e);
+ }
+ }
+
+ // No problems occurred
+ if (problems == null) {
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ requestRepaint();
+ }
+ return;
+ }
+
+ // Commit problems
+ final Throwable[] causes = new Throwable[problems.size()];
+ int index = 0;
+ for (final Iterator<SourceException> i = problems.iterator(); i
+ .hasNext();) {
+ causes[index++] = i.next();
+ }
+ final Buffered.SourceException e = new Buffered.SourceException(this,
+ causes);
+ currentBufferedSourceException = e;
+ requestRepaint();
+ throw e;
+ }
+
+ /*
+ * Discards local changes and refresh values from the data source Don't add
+ * a JavaDoc comment here, we use the default one from the interface.
+ */
+ @Override
+ public void discard() throws Buffered.SourceException {
+
+ LinkedList<SourceException> problems = null;
+
+ // Try to discard all changes
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ try {
+ (fields.get(i.next())).discard();
+ } catch (final Buffered.SourceException e) {
+ if (problems == null) {
+ problems = new LinkedList<SourceException>();
+ }
+ problems.add(e);
+ }
+ }
+
+ // No problems occurred
+ if (problems == null) {
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ requestRepaint();
+ }
+ return;
+ }
+
+ // Discards problems occurred
+ final Throwable[] causes = new Throwable[problems.size()];
+ int index = 0;
+ for (final Iterator<SourceException> i = problems.iterator(); i
+ .hasNext();) {
+ causes[index++] = i.next();
+ }
+ final Buffered.SourceException e = new Buffered.SourceException(this,
+ causes);
+ currentBufferedSourceException = e;
+ requestRepaint();
+ throw e;
+ }
+
+ /*
+ * Is the object modified but not committed? Don't add a JavaDoc comment
+ * here, we use the default one from the interface.
+ */
+ @Override
+ public boolean isModified() {
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ final Field<?> f = fields.get(i.next());
+ if (f != null && f.isModified()) {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ /*
+ * Is the editor in a read-through mode? Don't add a JavaDoc comment here,
+ * we use the default one from the interface.
+ */
+ @Override
+ @Deprecated
+ public boolean isReadThrough() {
+ return readThrough;
+ }
+
+ /*
+ * Is the editor in a write-through mode? Don't add a JavaDoc comment here,
+ * we use the default one from the interface.
+ */
+ @Override
+ @Deprecated
+ public boolean isWriteThrough() {
+ return writeThrough;
+ }
+
+ /*
+ * Sets the editor's read-through mode to the specified status. Don't add a
+ * JavaDoc comment here, we use the default one from the interface.
+ */
+ @Override
+ public void setReadThrough(boolean readThrough) {
+ if (readThrough != this.readThrough) {
+ this.readThrough = readThrough;
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ (fields.get(i.next())).setReadThrough(readThrough);
+ }
+ }
+ }
+
+ /*
+ * Sets the editor's read-through mode to the specified status. Don't add a
+ * JavaDoc comment here, we use the default one from the interface.
+ */
+ @Override
+ public void setWriteThrough(boolean writeThrough) throws SourceException,
+ InvalidValueException {
+ if (writeThrough != this.writeThrough) {
+ this.writeThrough = writeThrough;
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ (fields.get(i.next())).setWriteThrough(writeThrough);
+ }
+ }
+ }
+
+ /**
+ * Adds a new property to form and create corresponding field.
+ *
+ * @see com.vaadin.data.Item#addItemProperty(Object, Property)
+ */
+ @Override
+ public boolean addItemProperty(Object id, Property property) {
+
+ // Checks inputs
+ if (id == null || property == null) {
+ throw new NullPointerException("Id and property must be non-null");
+ }
+
+ // Checks that the property id is not reserved
+ if (propertyIds.contains(id)) {
+ return false;
+ }
+
+ propertyIds.add(id);
+ ownProperties.put(id, property);
+
+ // Gets suitable field
+ final Field<?> field = fieldFactory.createField(this, id, this);
+ if (field == null) {
+ return false;
+ }
+
+ // Configures the field
+ bindPropertyToField(id, property, field);
+
+ // Register and attach the created field
+ addField(id, field);
+
+ return true;
+ }
+
+ /**
+ * Registers the field with the form and adds the field to the form layout.
+ *
+ * <p>
+ * The property id must not be already used in the form.
+ * </p>
+ *
+ * <p>
+ * This field is added to the layout using the
+ * {@link #attachField(Object, Field)} method.
+ * </p>
+ *
+ * @param propertyId
+ * the Property id the the field.
+ * @param field
+ * the field which should be added to the form.
+ */
+ public void addField(Object propertyId, Field<?> field) {
+ registerField(propertyId, field);
+ attachField(propertyId, field);
+ requestRepaint();
+ }
+
+ /**
+ * Register the field with the form. All registered fields are validated
+ * when the form is validated and also committed when the form is committed.
+ *
+ * <p>
+ * The property id must not be already used in the form.
+ * </p>
+ *
+ *
+ * @param propertyId
+ * the Property id of the field.
+ * @param field
+ * the Field that should be registered
+ */
+ private void registerField(Object propertyId, Field<?> field) {
+ if (propertyId == null || field == null) {
+ return;
+ }
+
+ fields.put(propertyId, field);
+ field.addListener(fieldValueChangeListener);
+ if (!propertyIds.contains(propertyId)) {
+ // adding a field directly
+ propertyIds.addLast(propertyId);
+ }
+
+ // Update the read and write through status and immediate to match the
+ // form.
+ // Should this also include invalidCommitted (#3993)?
+ field.setReadThrough(readThrough);
+ field.setWriteThrough(writeThrough);
+ if (isImmediate() && field instanceof AbstractComponent) {
+ ((AbstractComponent) field).setImmediate(true);
+ }
+ }
+
+ /**
+ * Adds the field to the form layout.
+ * <p>
+ * The field is added to the form layout in the default position (the
+ * position used by {@link Layout#addComponent(Component)}. If the
+ * underlying layout is a {@link CustomLayout} the field is added to the
+ * CustomLayout location given by the string representation of the property
+ * id using {@link CustomLayout#addComponent(Component, String)}.
+ * </p>
+ *
+ * <p>
+ * Override this method to control how the fields are added to the layout.
+ * </p>
+ *
+ * @param propertyId
+ * @param field
+ */
+ protected void attachField(Object propertyId, Field field) {
+ if (propertyId == null || field == null) {
+ return;
+ }
+
+ Layout layout = getLayout();
+ if (layout instanceof CustomLayout) {
+ ((CustomLayout) layout).addComponent(field, propertyId.toString());
+ } else {
+ layout.addComponent(field);
+ }
+
+ }
+
+ /**
+ * The property identified by the property id.
+ *
+ * <p>
+ * The property data source of the field specified with property id is
+ * returned. If there is a (with specified property id) having no data
+ * source, the field is returned instead of the data source.
+ * </p>
+ *
+ * @see com.vaadin.data.Item#getItemProperty(Object)
+ */
+ @Override
+ public Property<?> getItemProperty(Object id) {
+ final Field<?> field = fields.get(id);
+ if (field == null) {
+ // field does not exist or it is not (yet) created for this property
+ return ownProperties.get(id);
+ }
+ final Property<?> property = field.getPropertyDataSource();
+
+ if (property != null) {
+ return property;
+ } else {
+ return field;
+ }
+ }
+
+ /**
+ * Gets the field identified by the propertyid.
+ *
+ * @param propertyId
+ * the id of the property.
+ */
+ public Field<?> getField(Object propertyId) {
+ return fields.get(propertyId);
+ }
+
+ /* Documented in interface */
+ @Override
+ public Collection<?> getItemPropertyIds() {
+ return Collections.unmodifiableCollection(propertyIds);
+ }
+
+ /**
+ * Removes the property and corresponding field from the form.
+ *
+ * @see com.vaadin.data.Item#removeItemProperty(Object)
+ */
+ @Override
+ public boolean removeItemProperty(Object id) {
+ ownProperties.remove(id);
+
+ final Field<?> field = fields.get(id);
+
+ if (field != null) {
+ propertyIds.remove(id);
+ fields.remove(id);
+ detachField(field);
+ field.removeListener(fieldValueChangeListener);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when a form field is detached from a Form. Typically when a new
+ * Item is assigned to Form via {@link #setItemDataSource(Item)}.
+ * <p>
+ * Override this method to control how the fields are removed from the
+ * layout.
+ * </p>
+ *
+ * @param field
+ * the field to be detached from the forms layout.
+ */
+ protected void detachField(final Field field) {
+ Component p = field.getParent();
+ if (p instanceof ComponentContainer) {
+ ((ComponentContainer) p).removeComponent(field);
+ }
+ }
+
+ /**
+ * Removes all properties and fields from the form.
+ *
+ * @return the Success of the operation. Removal of all fields succeeded if
+ * (and only if) the return value is <code>true</code>.
+ */
+ public boolean removeAllProperties() {
+ final Object[] properties = propertyIds.toArray();
+ boolean success = true;
+
+ for (int i = 0; i < properties.length; i++) {
+ if (!removeItemProperty(properties[i])) {
+ success = false;
+ }
+ }
+
+ return success;
+ }
+
+ /* Documented in the interface */
+ @Override
+ public Item getItemDataSource() {
+ return itemDatasource;
+ }
+
+ /**
+ * Sets the item datasource for the form.
+ *
+ * <p>
+ * Setting item datasource clears any fields, the form might contain and
+ * adds all the properties as fields to the form.
+ * </p>
+ *
+ * @see com.vaadin.data.Item.Viewer#setItemDataSource(Item)
+ */
+ @Override
+ public void setItemDataSource(Item newDataSource) {
+ setItemDataSource(newDataSource,
+ newDataSource != null ? newDataSource.getItemPropertyIds()
+ : null);
+ }
+
+ /**
+ * Set the item datasource for the form, but limit the form contents to
+ * specified properties of the item.
+ *
+ * <p>
+ * Setting item datasource clears any fields, the form might contain and
+ * adds the specified the properties as fields to the form, in the specified
+ * order.
+ * </p>
+ *
+ * @see com.vaadin.data.Item.Viewer#setItemDataSource(Item)
+ */
+ public void setItemDataSource(Item newDataSource, Collection<?> propertyIds) {
+
+ if (getLayout() instanceof GridLayout) {
+ GridLayout gl = (GridLayout) getLayout();
+ if (gridlayoutCursorX == -1) {
+ // first setItemDataSource, remember initial cursor
+ gridlayoutCursorX = gl.getCursorX();
+ gridlayoutCursorY = gl.getCursorY();
+ } else {
+ // restore initial cursor
+ gl.setCursorX(gridlayoutCursorX);
+ gl.setCursorY(gridlayoutCursorY);
+ }
+ }
+
+ // Removes all fields first from the form
+ removeAllProperties();
+
+ // Sets the datasource
+ itemDatasource = newDataSource;
+
+ // If the new datasource is null, just set null datasource
+ if (itemDatasource == null) {
+ requestRepaint();
+ return;
+ }
+
+ // Adds all the properties to this form
+ for (final Iterator<?> i = propertyIds.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ final Property<?> property = itemDatasource.getItemProperty(id);
+ if (id != null && property != null) {
+ final Field<?> f = fieldFactory.createField(itemDatasource, id,
+ this);
+ if (f != null) {
+ bindPropertyToField(id, property, f);
+ addField(id, f);
+ }
+ }
+ }
+ }
+
+ /**
+ * Binds an item property to a field. The default behavior is to bind
+ * property straight to Field. If Property.Viewer type property (e.g.
+ * PropertyFormatter) is already set for field, the property is bound to
+ * that Property.Viewer.
+ *
+ * @param propertyId
+ * @param property
+ * @param field
+ * @since 6.7.3
+ */
+ protected void bindPropertyToField(final Object propertyId,
+ final Property property, final Field field) {
+ // check if field has a property that is Viewer set. In that case we
+ // expect developer has e.g. PropertyFormatter that he wishes to use and
+ // assign the property to the Viewer instead.
+ boolean hasFilterProperty = field.getPropertyDataSource() != null
+ && (field.getPropertyDataSource() instanceof Property.Viewer);
+ if (hasFilterProperty) {
+ ((Property.Viewer) field.getPropertyDataSource())
+ .setPropertyDataSource(property);
+ } else {
+ field.setPropertyDataSource(property);
+ }
+ }
+
+ /**
+ * Gets the layout of the form.
+ *
+ * <p>
+ * By default form uses <code>OrderedLayout</code> with <code>form</code>
+ * -style.
+ * </p>
+ *
+ * @return the Layout of the form.
+ */
+ public Layout getLayout() {
+ return (Layout) getState().getLayout();
+ }
+
+ /**
+ * Sets the layout of the form.
+ *
+ * <p>
+ * If set to null then Form uses a FormLayout by default.
+ * </p>
+ *
+ * @param layout
+ * the layout of the form.
+ */
+ public void setLayout(Layout layout) {
+
+ // Use orderedlayout by default
+ if (layout == null) {
+ layout = new FormLayout();
+ }
+
+ // reset cursor memory
+ gridlayoutCursorX = -1;
+ gridlayoutCursorY = -1;
+
+ // Move fields from previous layout
+ if (getLayout() != null) {
+ final Object[] properties = propertyIds.toArray();
+ for (int i = 0; i < properties.length; i++) {
+ Field<?> f = getField(properties[i]);
+ detachField(f);
+ if (layout instanceof CustomLayout) {
+ ((CustomLayout) layout).addComponent(f,
+ properties[i].toString());
+ } else {
+ layout.addComponent(f);
+ }
+ }
+
+ getLayout().setParent(null);
+ }
+
+ // Replace the previous layout
+ layout.setParent(this);
+ getState().setLayout(layout);
+
+ // Hierarchy has changed so we need to repaint (this could be a
+ // hierarchy repaint only)
+ requestRepaint();
+ }
+
+ /**
+ * Sets the form field to be selectable from static list of changes.
+ *
+ * <p>
+ * The list values and descriptions are given as array. The value-array must
+ * contain the current value of the field and the lengths of the arrays must
+ * match. Null values are not supported.
+ * </p>
+ *
+ * Note: since Vaadin 7.0, returns an {@link AbstractSelect} instead of a
+ * {@link Select}.
+ *
+ * @param propertyId
+ * the id of the property.
+ * @param values
+ * @param descriptions
+ * @return the select property generated
+ */
+ public AbstractSelect replaceWithSelect(Object propertyId, Object[] values,
+ Object[] descriptions) {
+
+ // Checks the parameters
+ if (propertyId == null || values == null || descriptions == null) {
+ throw new NullPointerException("All parameters must be non-null");
+ }
+ if (values.length != descriptions.length) {
+ throw new IllegalArgumentException(
+ "Value and description list are of different size");
+ }
+
+ // Gets the old field
+ final Field<?> oldField = fields.get(propertyId);
+ if (oldField == null) {
+ throw new IllegalArgumentException("Field with given propertyid '"
+ + propertyId.toString() + "' can not be found.");
+ }
+ final Object value = oldField.getPropertyDataSource() == null ? oldField
+ .getValue() : oldField.getPropertyDataSource().getValue();
+
+ // Checks that the value exists and check if the select should
+ // be forced in multiselect mode
+ boolean found = false;
+ boolean isMultiselect = false;
+ for (int i = 0; i < values.length && !found; i++) {
+ if (values[i] == value
+ || (value != null && value.equals(values[i]))) {
+ found = true;
+ }
+ }
+ if (value != null && !found) {
+ if (value instanceof Collection) {
+ for (final Iterator<?> it = ((Collection<?>) value).iterator(); it
+ .hasNext();) {
+ final Object val = it.next();
+ found = false;
+ for (int i = 0; i < values.length && !found; i++) {
+ if (values[i] == val
+ || (val != null && val.equals(values[i]))) {
+ found = true;
+ }
+ }
+ if (!found) {
+ throw new IllegalArgumentException(
+ "Currently selected value '" + val
+ + "' of property '"
+ + propertyId.toString()
+ + "' was not found");
+ }
+ }
+ isMultiselect = true;
+ } else {
+ throw new IllegalArgumentException("Current value '" + value
+ + "' of property '" + propertyId.toString()
+ + "' was not found");
+ }
+ }
+
+ // Creates the new field matching to old field parameters
+ final AbstractSelect newField = isMultiselect ? new ListSelect()
+ : new Select();
+ newField.setCaption(oldField.getCaption());
+ newField.setReadOnly(oldField.isReadOnly());
+ newField.setReadThrough(oldField.isReadThrough());
+ newField.setWriteThrough(oldField.isWriteThrough());
+
+ // Creates the options list
+ newField.addContainerProperty("desc", String.class, "");
+ newField.setItemCaptionPropertyId("desc");
+ for (int i = 0; i < values.length; i++) {
+ Object id = values[i];
+ final Item item;
+ if (id == null) {
+ id = newField.addItem();
+ item = newField.getItem(id);
+ newField.setNullSelectionItemId(id);
+ } else {
+ item = newField.addItem(id);
+ }
+
+ if (item != null) {
+ item.getItemProperty("desc").setValue(
+ descriptions[i].toString());
+ }
+ }
+
+ // Sets the property data source
+ final Property<?> property = oldField.getPropertyDataSource();
+ oldField.setPropertyDataSource(null);
+ newField.setPropertyDataSource(property);
+
+ // Replaces the old field with new one
+ getLayout().replaceComponent(oldField, newField);
+ fields.put(propertyId, newField);
+ newField.addListener(fieldValueChangeListener);
+ oldField.removeListener(fieldValueChangeListener);
+
+ return newField;
+ }
+
+ /**
+ * Checks the validity of the Form and all of its fields.
+ *
+ * @see com.vaadin.data.Validatable#validate()
+ */
+ @Override
+ public void validate() throws InvalidValueException {
+ super.validate();
+ for (final Iterator<Object> i = propertyIds.iterator(); i.hasNext();) {
+ (fields.get(i.next())).validate();
+ }
+ }
+
+ /**
+ * Checks the validabtable object accept invalid values.
+ *
+ * @see com.vaadin.data.Validatable#isInvalidAllowed()
+ */
+ @Override
+ public boolean isInvalidAllowed() {
+ return true;
+ }
+
+ /**
+ * Should the validabtable object accept invalid values.
+ *
+ * @see com.vaadin.data.Validatable#setInvalidAllowed(boolean)
+ */
+ @Override
+ public void setInvalidAllowed(boolean invalidValueAllowed)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the component's to read-only mode to the specified state.
+ *
+ * @see com.vaadin.ui.Component#setReadOnly(boolean)
+ */
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ for (final Iterator<?> i = propertyIds.iterator(); i.hasNext();) {
+ (fields.get(i.next())).setReadOnly(readOnly);
+ }
+ }
+
+ /**
+ * Sets the field factory used by this Form to genarate Fields for
+ * properties.
+ *
+ * {@link FormFieldFactory} is used to create fields for form properties.
+ * {@link DefaultFieldFactory} is used by default.
+ *
+ * @param fieldFactory
+ * the new factory used to create the fields.
+ * @see Field
+ * @see FormFieldFactory
+ */
+ public void setFormFieldFactory(FormFieldFactory fieldFactory) {
+ this.fieldFactory = fieldFactory;
+ }
+
+ /**
+ * Get the field factory of the form.
+ *
+ * @return the FormFieldFactory Factory used to create the fields.
+ */
+ public FormFieldFactory getFormFieldFactory() {
+ return fieldFactory;
+ }
+
+ /**
+ * Gets the field type.
+ *
+ * @see com.vaadin.ui.AbstractField#getType()
+ */
+ @Override
+ public Class<?> getType() {
+ if (getPropertyDataSource() != null) {
+ return getPropertyDataSource().getType();
+ }
+ return Object.class;
+ }
+
+ /**
+ * Sets the internal value.
+ *
+ * This is relevant when the Form is used as Field.
+ *
+ * @see com.vaadin.ui.AbstractField#setInternalValue(java.lang.Object)
+ */
+ @Override
+ protected void setInternalValue(Object newValue) {
+ // Stores the old value
+ final Object oldValue = propertyValue;
+
+ // Sets the current Value
+ super.setInternalValue(newValue);
+ propertyValue = newValue;
+
+ // Ignores form updating if data object has not changed.
+ if (oldValue != newValue) {
+ setFormDataSource(newValue, getVisibleItemProperties());
+ }
+ }
+
+ /**
+ * Gets the first focusable field in form. If there are enabled,
+ * non-read-only fields, the first one of them is returned. Otherwise, the
+ * field for the first property (or null if none) is returned.
+ *
+ * @return the Field.
+ */
+ private Field<?> getFirstFocusableField() {
+ if (getItemPropertyIds() != null) {
+ for (Object id : getItemPropertyIds()) {
+ if (id != null) {
+ Field<?> field = getField(id);
+ if (field.isEnabled() && !field.isReadOnly()) {
+ return field;
+ }
+ }
+ }
+ // fallback: first field if none of the fields is enabled and
+ // writable
+ Object id = getItemPropertyIds().iterator().next();
+ if (id != null) {
+ return getField(id);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Updates the internal form datasource.
+ *
+ * Method setFormDataSource.
+ *
+ * @param data
+ * @param properties
+ */
+ protected void setFormDataSource(Object data, Collection<?> properties) {
+
+ // If data is an item use it.
+ Item item = null;
+ if (data instanceof Item) {
+ item = (Item) data;
+ } else if (data != null) {
+ item = new BeanItem<Object>(data);
+ }
+
+ // Sets the datasource to form
+ if (item != null && properties != null) {
+ // Shows only given properties
+ this.setItemDataSource(item, properties);
+ } else {
+ // Shows all properties
+ this.setItemDataSource(item);
+ }
+ }
+
+ /**
+ * Returns the visibleProperties.
+ *
+ * @return the Collection of visible Item properites.
+ */
+ public Collection<?> getVisibleItemProperties() {
+ return visibleItemProperties;
+ }
+
+ /**
+ * Sets the visibleProperties.
+ *
+ * @param visibleProperties
+ * the visibleProperties to set.
+ */
+ public void setVisibleItemProperties(Collection<?> visibleProperties) {
+ visibleItemProperties = visibleProperties;
+ Object value = getValue();
+ if (value == null) {
+ value = itemDatasource;
+ }
+ setFormDataSource(value, getVisibleItemProperties());
+ }
+
+ /**
+ * Sets the visibleProperties.
+ *
+ * @param visibleProperties
+ * the visibleProperties to set.
+ */
+ public void setVisibleItemProperties(Object[] visibleProperties) {
+ LinkedList<Object> v = new LinkedList<Object>();
+ for (int i = 0; i < visibleProperties.length; i++) {
+ v.add(visibleProperties[i]);
+ }
+ setVisibleItemProperties(v);
+ }
+
+ /**
+ * Focuses the first field in the form.
+ *
+ * @see com.vaadin.ui.Component.Focusable#focus()
+ */
+ @Override
+ public void focus() {
+ final Field<?> f = getFirstFocusableField();
+ if (f != null) {
+ f.focus();
+ }
+ }
+
+ /**
+ * Sets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ @Override
+ public void setTabIndex(int tabIndex) {
+ super.setTabIndex(tabIndex);
+ for (final Iterator<?> i = getItemPropertyIds().iterator(); i.hasNext();) {
+ (getField(i.next())).setTabIndex(tabIndex);
+ }
+ }
+
+ /**
+ * Setting the form to be immediate also sets all the fields of the form to
+ * the same state.
+ */
+ @Override
+ public void setImmediate(boolean immediate) {
+ super.setImmediate(immediate);
+ for (Iterator<Field<?>> i = fields.values().iterator(); i.hasNext();) {
+ Field<?> f = i.next();
+ if (f instanceof AbstractComponent) {
+ ((AbstractComponent) f).setImmediate(immediate);
+ }
+ }
+ }
+
+ /** Form is empty if all of its fields are empty. */
+ @Override
+ protected boolean isEmpty() {
+
+ for (Iterator<Field<?>> i = fields.values().iterator(); i.hasNext();) {
+ Field<?> f = i.next();
+ if (f instanceof AbstractField) {
+ if (!((AbstractField<?>) f).isEmpty()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Adding validators directly to form is not supported.
+ *
+ * Add the validators to form fields instead.
+ */
+ @Override
+ public void addValidator(Validator validator) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns a layout that is rendered below normal form contents. This area
+ * can be used for example to include buttons related to form contents.
+ *
+ * @return layout rendered below normal form contents.
+ */
+ public Layout getFooter() {
+ return (Layout) getState().getFooter();
+ }
+
+ /**
+ * Sets the layout that is rendered below normal form contents. Setting this
+ * to null will cause an empty HorizontalLayout to be rendered in the
+ * footer.
+ *
+ * @param footer
+ * the new footer layout
+ */
+ public void setFooter(Layout footer) {
+ if (getFooter() != null) {
+ getFooter().setParent(null);
+ }
+ if (footer == null) {
+ footer = new HorizontalLayout();
+ }
+
+ getState().setFooter(footer);
+ footer.setParent(this);
+
+ // Hierarchy has changed so we need to repaint (this could be a
+ // hierarchy repaint only)
+ requestRepaint();
+
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (getParent() != null && !getParent().isEnabled()) {
+ // some ancestor still disabled, don't update children
+ return;
+ } else {
+ getLayout().requestRepaintAll();
+ }
+ }
+
+ /*
+ * ACTIONS
+ */
+
+ /**
+ * Gets the {@link ActionManager} responsible for handling {@link Action}s
+ * added to this Form.<br/>
+ * Note that Form has another ActionManager inherited from
+ * {@link AbstractField}. The ownActionManager handles Actions attached to
+ * this Form specifically, while the ActionManager in AbstractField
+ * delegates to the containing Window (i.e global Actions).
+ *
+ * @return
+ */
+ protected ActionManager getOwnActionManager() {
+ if (ownActionManager == null) {
+ ownActionManager = new ActionManager(this);
+ }
+ return ownActionManager;
+ }
+
+ @Override
+ public void addActionHandler(Handler actionHandler) {
+ getOwnActionManager().addActionHandler(actionHandler);
+ }
+
+ @Override
+ public void removeActionHandler(Handler actionHandler) {
+ if (ownActionManager != null) {
+ ownActionManager.removeActionHandler(actionHandler);
+ }
+ }
+
+ /**
+ * Removes all action handlers
+ */
+ public void removeAllActionHandlers() {
+ if (ownActionManager != null) {
+ ownActionManager.removeAllActionHandlers();
+ }
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void addAction(
+ T action) {
+ getOwnActionManager().addAction(action);
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void removeAction(
+ T action) {
+ if (ownActionManager != null) {
+ ownActionManager.removeAction(action);
+ }
+ }
+
+ @Override
+ public Iterator<Component> iterator() {
+ return getComponentIterator();
+ }
+
+ /**
+ * Modifiable and Serializable Iterator for the components, used by
+ * {@link Form#getComponentIterator()}.
+ */
+ private class ComponentIterator implements Iterator<Component>,
+ Serializable {
+
+ int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ if (i < getComponentCount()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Component next() {
+ if (!hasNext()) {
+ return null;
+ }
+ i++;
+ if (i == 1) {
+ return getLayout() != null ? getLayout() : getFooter();
+ } else if (i == 2) {
+ return getFooter();
+ }
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ if (i == 1) {
+ if (getLayout() != null) {
+ setLayout(null);
+ i = 0;
+ } else {
+ setFooter(null);
+ }
+ } else if (i == 2) {
+ setFooter(null);
+ }
+ }
+ }
+
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return new ComponentIterator();
+ }
+
+ public int getComponentCount() {
+ int count = 0;
+ if (getLayout() != null) {
+ count++;
+ }
+ if (getFooter() != null) {
+ count++;
+ }
+
+ return count;
+ }
+
+ @Override
+ public boolean isComponentVisible(Component childComponent) {
+ return true;
+ };
+}
diff --git a/server/src/com/vaadin/ui/FormFieldFactory.java b/server/src/com/vaadin/ui/FormFieldFactory.java
new file mode 100644
index 0000000000..1efa05c5f5
--- /dev/null
+++ b/server/src/com/vaadin/ui/FormFieldFactory.java
@@ -0,0 +1,41 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.data.Item;
+
+/**
+ * Factory interface for creating new Field-instances based on {@link Item},
+ * property id and uiContext (the component responsible for displaying fields).
+ * Currently this interface is used by {@link Form}, but might later be used by
+ * some other components for {@link Field} generation.
+ *
+ * <p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 6.0
+ * @see TableFieldFactory
+ */
+public interface FormFieldFactory extends Serializable {
+ /**
+ * Creates a field based on the item, property id and the component (most
+ * commonly {@link Form}) where the Field will be presented.
+ *
+ * @param item
+ * the item where the property belongs to.
+ * @param propertyId
+ * the Id of the property.
+ * @param uiContext
+ * the component where the field is presented, most commonly this
+ * is {@link Form}. uiContext will not necessary be the parent
+ * component of the field, but the one that is responsible for
+ * creating it.
+ * @return Field the field suitable for editing the specified data.
+ */
+ Field<?> createField(Item item, Object propertyId, Component uiContext);
+}
diff --git a/server/src/com/vaadin/ui/FormLayout.java b/server/src/com/vaadin/ui/FormLayout.java
new file mode 100644
index 0000000000..c0be784a7b
--- /dev/null
+++ b/server/src/com/vaadin/ui/FormLayout.java
@@ -0,0 +1,31 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+/**
+ * FormLayout is used by {@link Form} to layout fields. It may also be used
+ * separately without {@link Form}.
+ *
+ * FormLayout is a close relative to vertical {@link OrderedLayout}, but in
+ * FormLayout caption is rendered on left side of component. Required and
+ * validation indicators are between captions and fields.
+ *
+ * FormLayout does not currently support some advanced methods from
+ * OrderedLayout like setExpandRatio and setComponentAlignment.
+ *
+ * FormLayout by default has component spacing on. Also margin top and margin
+ * bottom are by default on.
+ *
+ */
+public class FormLayout extends AbstractOrderedLayout {
+
+ public FormLayout() {
+ super();
+ setSpacing(true);
+ setMargin(true, false, true, false);
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/GridLayout.java b/server/src/com/vaadin/ui/GridLayout.java
new file mode 100644
index 0000000000..2391a9cd3a
--- /dev/null
+++ b/server/src/com/vaadin/ui/GridLayout.java
@@ -0,0 +1,1415 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.vaadin.event.LayoutEvents.LayoutClickEvent;
+import com.vaadin.event.LayoutEvents.LayoutClickListener;
+import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc;
+import com.vaadin.shared.ui.gridlayout.GridLayoutState;
+import com.vaadin.terminal.LegacyPaint;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler;
+
+/**
+ * A layout where the components are laid out on a grid using cell coordinates.
+ *
+ * <p>
+ * The GridLayout also maintains a cursor for adding components in
+ * left-to-right, top-to-bottom order.
+ * </p>
+ *
+ * <p>
+ * Each component in a <code>GridLayout</code> uses a defined
+ * {@link GridLayout.Area area} (column1,row1,column2,row2) from the grid. The
+ * components may not overlap with the existing components - if you try to do so
+ * you will get an {@link OverlapsException}. Adding a component with cursor
+ * automatically extends the grid by increasing the grid height.
+ * </p>
+ *
+ * <p>
+ * The grid coordinates, which are specified by a row and column index, always
+ * start from 0 for the topmost row and the leftmost column.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class GridLayout extends AbstractLayout implements
+ Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier,
+ Vaadin6Component {
+
+ private GridLayoutServerRpc rpc = new GridLayoutServerRpc() {
+
+ @Override
+ public void layoutClick(MouseEventDetails mouseDetails,
+ Connector clickedConnector) {
+ fireEvent(LayoutClickEvent.createEvent(GridLayout.this,
+ mouseDetails, clickedConnector));
+
+ }
+ };
+ /**
+ * Cursor X position: this is where the next component with unspecified x,y
+ * is inserted
+ */
+ private int cursorX = 0;
+
+ /**
+ * Cursor Y position: this is where the next component with unspecified x,y
+ * is inserted
+ */
+ private int cursorY = 0;
+
+ /**
+ * Contains all items that are placed on the grid. These are components with
+ * grid area definition.
+ */
+ private final LinkedList<Area> areas = new LinkedList<Area>();
+
+ /**
+ * Mapping from components to their respective areas.
+ */
+ private final LinkedList<Component> components = new LinkedList<Component>();
+
+ /**
+ * Mapping from components to alignments (horizontal + vertical).
+ */
+ private Map<Component, Alignment> componentToAlignment = new HashMap<Component, Alignment>();
+
+ private static final Alignment ALIGNMENT_DEFAULT = Alignment.TOP_LEFT;
+
+ /**
+ * Has there been rows inserted or deleted in the middle of the layout since
+ * the last paint operation.
+ */
+ private boolean structuralChange = false;
+
+ private Map<Integer, Float> columnExpandRatio = new HashMap<Integer, Float>();
+ private Map<Integer, Float> rowExpandRatio = new HashMap<Integer, Float>();
+
+ /**
+ * Constructor for a grid of given size (number of columns and rows).
+ *
+ * The grid may grow or shrink later. Grid grows automatically if you add
+ * components outside its area.
+ *
+ * @param columns
+ * Number of columns in the grid.
+ * @param rows
+ * Number of rows in the grid.
+ */
+ public GridLayout(int columns, int rows) {
+ setColumns(columns);
+ setRows(rows);
+ registerRpc(rpc);
+ }
+
+ /**
+ * Constructs an empty (1x1) grid layout that is extended as needed.
+ */
+ public GridLayout() {
+ this(1, 1);
+ }
+
+ @Override
+ public GridLayoutState getState() {
+ return (GridLayoutState) super.getState();
+ }
+
+ /**
+ * <p>
+ * Adds a component to the grid in the specified area. The area is defined
+ * by specifying the upper left corner (column1, row1) and the lower right
+ * corner (column2, row2) of the area. The coordinates are zero-based.
+ * </p>
+ *
+ * <p>
+ * If the area overlaps with any of the existing components already present
+ * in the grid, the operation will fail and an {@link OverlapsException} is
+ * thrown.
+ * </p>
+ *
+ * @param component
+ * the component to be added.
+ * @param column1
+ * the column of the upper left corner of the area <code>c</code>
+ * is supposed to occupy. The leftmost column has index 0.
+ * @param row1
+ * the row of the upper left corner of the area <code>c</code> is
+ * supposed to occupy. The topmost row has index 0.
+ * @param column2
+ * the column of the lower right corner of the area
+ * <code>c</code> is supposed to occupy.
+ * @param row2
+ * the row of the lower right corner of the area <code>c</code>
+ * is supposed to occupy.
+ * @throws OverlapsException
+ * if the new component overlaps with any of the components
+ * already in the grid.
+ * @throws OutOfBoundsException
+ * if the cells are outside the grid area.
+ */
+ public void addComponent(Component component, int column1, int row1,
+ int column2, int row2) throws OverlapsException,
+ OutOfBoundsException {
+
+ if (component == null) {
+ throw new NullPointerException("Component must not be null");
+ }
+
+ // Checks that the component does not already exist in the container
+ if (components.contains(component)) {
+ throw new IllegalArgumentException(
+ "Component is already in the container");
+ }
+
+ // Creates the area
+ final Area area = new Area(component, column1, row1, column2, row2);
+
+ // Checks the validity of the coordinates
+ if (column2 < column1 || row2 < row1) {
+ throw new IllegalArgumentException(
+ "Illegal coordinates for the component");
+ }
+ if (column1 < 0 || row1 < 0 || column2 >= getColumns()
+ || row2 >= getRows()) {
+ throw new OutOfBoundsException(area);
+ }
+
+ // Checks that newItem does not overlap with existing items
+ checkExistingOverlaps(area);
+
+ // Inserts the component to right place at the list
+ // Respect top-down, left-right ordering
+ // component.setParent(this);
+ final Iterator<Area> i = areas.iterator();
+ int index = 0;
+ boolean done = false;
+ while (!done && i.hasNext()) {
+ final Area existingArea = i.next();
+ if ((existingArea.row1 >= row1 && existingArea.column1 > column1)
+ || existingArea.row1 > row1) {
+ areas.add(index, area);
+ components.add(index, component);
+ done = true;
+ }
+ index++;
+ }
+ if (!done) {
+ areas.addLast(area);
+ components.addLast(component);
+ }
+
+ // Attempt to add to super
+ try {
+ super.addComponent(component);
+ } catch (IllegalArgumentException e) {
+ areas.remove(area);
+ components.remove(component);
+ throw e;
+ }
+
+ // update cursor position, if it's within this area; use first position
+ // outside this area, even if it's occupied
+ if (cursorX >= column1 && cursorX <= column2 && cursorY >= row1
+ && cursorY <= row2) {
+ // cursor within area
+ cursorX = column2 + 1; // one right of area
+ if (cursorX >= getColumns()) {
+ // overflowed columns
+ cursorX = 0; // first col
+ // move one row down, or one row under the area
+ cursorY = (column1 == 0 ? row2 : row1) + 1;
+ } else {
+ cursorY = row1;
+ }
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Tests if the given area overlaps with any of the items already on the
+ * grid.
+ *
+ * @param area
+ * the Area to be checked for overlapping.
+ * @throws OverlapsException
+ * if <code>area</code> overlaps with any existing area.
+ */
+ private void checkExistingOverlaps(Area area) throws OverlapsException {
+ for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
+ final Area existingArea = i.next();
+ if (existingArea.overlaps(area)) {
+ // Component not added, overlaps with existing component
+ throw new OverlapsException(existingArea);
+ }
+ }
+ }
+
+ /**
+ * Adds the component to the grid in cells column1,row1 (NortWest corner of
+ * the area.) End coordinates (SouthEast corner of the area) are the same as
+ * column1,row1. The coordinates are zero-based. Component width and height
+ * is 1.
+ *
+ * @param component
+ * the component to be added.
+ * @param column
+ * the column index, starting from 0.
+ * @param row
+ * the row index, starting from 0.
+ * @throws OverlapsException
+ * if the new component overlaps with any of the components
+ * already in the grid.
+ * @throws OutOfBoundsException
+ * if the cell is outside the grid area.
+ */
+ public void addComponent(Component component, int column, int row)
+ throws OverlapsException, OutOfBoundsException {
+ this.addComponent(component, column, row, column, row);
+ }
+
+ /**
+ * Forces the next component to be added at the beginning of the next line.
+ *
+ * <p>
+ * Sets the cursor column to 0 and increments the cursor row by one.
+ * </p>
+ *
+ * <p>
+ * By calling this function you can ensure that no more components are added
+ * right of the previous component.
+ * </p>
+ *
+ * @see #space()
+ */
+ public void newLine() {
+ cursorX = 0;
+ cursorY++;
+ }
+
+ /**
+ * Moves the cursor forward by one. If the cursor goes out of the right grid
+ * border, it is moved to the first column of the next row.
+ *
+ * @see #newLine()
+ */
+ public void space() {
+ cursorX++;
+ if (cursorX >= getColumns()) {
+ cursorX = 0;
+ cursorY++;
+ }
+ }
+
+ /**
+ * Adds the component into this container to the cursor position. If the
+ * cursor position is already occupied, the cursor is moved forwards to find
+ * free position. If the cursor goes out from the bottom of the grid, the
+ * grid is automatically extended.
+ *
+ * @param component
+ * the component to be added.
+ */
+ @Override
+ public void addComponent(Component component) {
+
+ // Finds first available place from the grid
+ Area area;
+ boolean done = false;
+ while (!done) {
+ try {
+ area = new Area(component, cursorX, cursorY, cursorX, cursorY);
+ checkExistingOverlaps(area);
+ done = true;
+ } catch (final OverlapsException e) {
+ space();
+ }
+ }
+
+ // Extends the grid if needed
+ if (cursorX >= getColumns()) {
+ setColumns(cursorX + 1);
+ }
+ if (cursorY >= getRows()) {
+ setRows(cursorY + 1);
+ }
+
+ addComponent(component, cursorX, cursorY);
+ }
+
+ /**
+ * Removes the specified component from the layout.
+ *
+ * @param component
+ * the component to be removed.
+ */
+ @Override
+ public void removeComponent(Component component) {
+
+ // Check that the component is contained in the container
+ if (component == null || !components.contains(component)) {
+ return;
+ }
+
+ Area area = null;
+ for (final Iterator<Area> i = areas.iterator(); area == null
+ && i.hasNext();) {
+ final Area a = i.next();
+ if (a.getComponent() == component) {
+ area = a;
+ }
+ }
+
+ components.remove(component);
+ if (area != null) {
+ areas.remove(area);
+ }
+
+ componentToAlignment.remove(component);
+
+ super.removeComponent(component);
+
+ requestRepaint();
+ }
+
+ /**
+ * Removes the component specified by its cell coordinates.
+ *
+ * @param column
+ * the component's column, starting from 0.
+ * @param row
+ * the component's row, starting from 0.
+ */
+ public void removeComponent(int column, int row) {
+
+ // Finds the area
+ for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
+ final Area area = i.next();
+ if (area.getColumn1() == column && area.getRow1() == row) {
+ removeComponent(area.getComponent());
+ return;
+ }
+ }
+ }
+
+ /**
+ * Gets an Iterator for the components contained in the layout. By using the
+ * Iterator it is possible to step through the contents of the layout.
+ *
+ * @return the Iterator of the components inside the layout.
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return Collections.unmodifiableCollection(components).iterator();
+ }
+
+ /**
+ * Gets the number of components contained in the layout. Consistent with
+ * the iterator returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components
+ */
+ @Override
+ public int getComponentCount() {
+ return components.size();
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // TODO Remove once Vaadin6Component is no longer implemented
+ }
+
+ /**
+ * Paints the contents of this component.
+ *
+ * @param target
+ * the Paint Event.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ // TODO refactor attribute names in future release.
+ target.addAttribute("structuralChange", structuralChange);
+ structuralChange = false;
+
+ // Area iterator
+ final Iterator<Area> areaiterator = areas.iterator();
+
+ // Current item to be processed (fetch first item)
+ Area area = areaiterator.hasNext() ? (Area) areaiterator.next() : null;
+
+ // Collects rowspan related information here
+ final HashMap<Integer, Integer> cellUsed = new HashMap<Integer, Integer>();
+
+ // Empty cell collector
+ int emptyCells = 0;
+
+ final String[] alignmentsArray = new String[components.size()];
+ final Integer[] columnExpandRatioArray = new Integer[getColumns()];
+ final Integer[] rowExpandRatioArray = new Integer[getRows()];
+
+ int realColExpandRatioSum = 0;
+ float colSum = getExpandRatioSum(columnExpandRatio);
+ if (colSum == 0) {
+ // no columns has been expanded, all cols have same expand
+ // rate
+ float equalSize = 1 / (float) getColumns();
+ int myRatio = Math.round(equalSize * 1000);
+ for (int i = 0; i < getColumns(); i++) {
+ columnExpandRatioArray[i] = myRatio;
+ }
+ realColExpandRatioSum = myRatio * getColumns();
+ } else {
+ for (int i = 0; i < getColumns(); i++) {
+ int myRatio = Math
+ .round((getColumnExpandRatio(i) / colSum) * 1000);
+ columnExpandRatioArray[i] = myRatio;
+ realColExpandRatioSum += myRatio;
+ }
+ }
+
+ boolean equallyDividedRows = false;
+ int realRowExpandRatioSum = 0;
+ float rowSum = getExpandRatioSum(rowExpandRatio);
+ if (rowSum == 0) {
+ // no rows have been expanded
+ equallyDividedRows = true;
+ float equalSize = 1 / (float) getRows();
+ int myRatio = Math.round(equalSize * 1000);
+ for (int i = 0; i < getRows(); i++) {
+ rowExpandRatioArray[i] = myRatio;
+ }
+ realRowExpandRatioSum = myRatio * getRows();
+ }
+
+ int index = 0;
+
+ // Iterates every applicable row
+ for (int cury = 0; cury < getRows(); cury++) {
+ target.startTag("gr");
+
+ if (!equallyDividedRows) {
+ int myRatio = Math
+ .round((getRowExpandRatio(cury) / rowSum) * 1000);
+ rowExpandRatioArray[cury] = myRatio;
+ realRowExpandRatioSum += myRatio;
+
+ }
+ // Iterates every applicable column
+ for (int curx = 0; curx < getColumns(); curx++) {
+
+ // Checks if current item is located at curx,cury
+ if (area != null && (area.row1 == cury)
+ && (area.column1 == curx)) {
+
+ // First check if empty cell needs to be rendered
+ if (emptyCells > 0) {
+ target.startTag("gc");
+ target.addAttribute("x", curx - emptyCells);
+ target.addAttribute("y", cury);
+ if (emptyCells > 1) {
+ target.addAttribute("w", emptyCells);
+ }
+ target.endTag("gc");
+ emptyCells = 0;
+ }
+
+ // Now proceed rendering current item
+ final int cols = (area.column2 - area.column1) + 1;
+ final int rows = (area.row2 - area.row1) + 1;
+ target.startTag("gc");
+
+ target.addAttribute("x", curx);
+ target.addAttribute("y", cury);
+
+ if (cols > 1) {
+ target.addAttribute("w", cols);
+ }
+ if (rows > 1) {
+ target.addAttribute("h", rows);
+ }
+ LegacyPaint.paint(area.getComponent(), target);
+
+ alignmentsArray[index++] = String
+ .valueOf(getComponentAlignment(area.getComponent())
+ .getBitMask());
+
+ target.endTag("gc");
+
+ // Fetch next item
+ if (areaiterator.hasNext()) {
+ area = areaiterator.next();
+ } else {
+ area = null;
+ }
+
+ // Updates the cellUsed if rowspan needed
+ if (rows > 1) {
+ int spannedx = curx;
+ for (int j = 1; j <= cols; j++) {
+ cellUsed.put(new Integer(spannedx), new Integer(
+ cury + rows - 1));
+ spannedx++;
+ }
+ }
+
+ // Skips the current item's spanned columns
+ if (cols > 1) {
+ curx += cols - 1;
+ }
+
+ } else {
+
+ // Checks against cellUsed, render space or ignore cell
+ if (cellUsed.containsKey(new Integer(curx))) {
+
+ // Current column contains already an item,
+ // check if rowspan affects at current x,y position
+ final int rowspanDepth = cellUsed
+ .get(new Integer(curx)).intValue();
+
+ if (rowspanDepth >= cury) {
+
+ // ignore cell
+ // Check if empty cell needs to be rendered
+ if (emptyCells > 0) {
+ target.startTag("gc");
+ target.addAttribute("x", curx - emptyCells);
+ target.addAttribute("y", cury);
+ if (emptyCells > 1) {
+ target.addAttribute("w", emptyCells);
+ }
+ target.endTag("gc");
+
+ emptyCells = 0;
+ }
+ } else {
+
+ // empty cell is needed
+ emptyCells++;
+
+ // Removes the cellUsed key as it has become
+ // obsolete
+ cellUsed.remove(Integer.valueOf(curx));
+ }
+ } else {
+
+ // empty cell is needed
+ emptyCells++;
+ }
+ }
+
+ } // iterates every column
+
+ // Last column handled of current row
+
+ // Checks if empty cell needs to be rendered
+ if (emptyCells > 0) {
+ target.startTag("gc");
+ target.addAttribute("x", getColumns() - emptyCells);
+ target.addAttribute("y", cury);
+ if (emptyCells > 1) {
+ target.addAttribute("w", emptyCells);
+ }
+ target.endTag("gc");
+
+ emptyCells = 0;
+ }
+
+ target.endTag("gr");
+ } // iterates every row
+
+ // Last row handled
+
+ // correct possible rounding error
+ if (rowExpandRatioArray.length > 0) {
+ rowExpandRatioArray[0] -= realRowExpandRatioSum - 1000;
+ }
+ if (columnExpandRatioArray.length > 0) {
+ columnExpandRatioArray[0] -= realColExpandRatioSum - 1000;
+ }
+
+ target.addAttribute("colExpand", columnExpandRatioArray);
+ target.addAttribute("rowExpand", rowExpandRatioArray);
+
+ // Add child component alignment info to layout tag
+ target.addAttribute("alignments", alignmentsArray);
+
+ }
+
+ private float getExpandRatioSum(Map<Integer, Float> ratioMap) {
+ float sum = 0;
+ for (Iterator<Entry<Integer, Float>> iterator = ratioMap.entrySet()
+ .iterator(); iterator.hasNext();) {
+ sum += iterator.next().getValue();
+ }
+ return sum;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
+ * .vaadin.ui.Component)
+ */
+ @Override
+ public Alignment getComponentAlignment(Component childComponent) {
+ Alignment alignment = componentToAlignment.get(childComponent);
+ if (alignment == null) {
+ return ALIGNMENT_DEFAULT;
+ } else {
+ return alignment;
+ }
+ }
+
+ /**
+ * Defines a rectangular area of cells in a GridLayout.
+ *
+ * <p>
+ * Also maintains a reference to the component contained in the area.
+ * </p>
+ *
+ * <p>
+ * The area is specified by the cell coordinates of its upper left corner
+ * (column1,row1) and lower right corner (column2,row2). As otherwise with
+ * GridLayout, the column and row coordinates start from zero.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class Area implements Serializable {
+
+ /**
+ * The column of the upper left corner cell of the area.
+ */
+ private final int column1;
+
+ /**
+ * The row of the upper left corner cell of the area.
+ */
+ private int row1;
+
+ /**
+ * The column of the lower right corner cell of the area.
+ */
+ private final int column2;
+
+ /**
+ * The row of the lower right corner cell of the area.
+ */
+ private int row2;
+
+ /**
+ * Component painted in the area.
+ */
+ private Component component;
+
+ /**
+ * <p>
+ * Construct a new area on a grid.
+ * </p>
+ *
+ * @param component
+ * the component connected to the area.
+ * @param column1
+ * The column of the upper left corner cell of the area. The
+ * leftmost column has index 0.
+ * @param row1
+ * The row of the upper left corner cell of the area. The
+ * topmost row has index 0.
+ * @param column2
+ * The column of the lower right corner cell of the area. The
+ * leftmost column has index 0.
+ * @param row2
+ * The row of the lower right corner cell of the area. The
+ * topmost row has index 0.
+ */
+ public Area(Component component, int column1, int row1, int column2,
+ int row2) {
+ this.column1 = column1;
+ this.row1 = row1;
+ this.column2 = column2;
+ this.row2 = row2;
+ this.component = component;
+ }
+
+ /**
+ * Tests if this Area overlaps with another Area.
+ *
+ * @param other
+ * the other Area that is to be tested for overlap with this
+ * area
+ * @return <code>true</code> if <code>other</code> area overlaps with
+ * this on, <code>false</code> if it does not.
+ */
+ public boolean overlaps(Area other) {
+ return column1 <= other.getColumn2() && row1 <= other.getRow2()
+ && column2 >= other.getColumn1() && row2 >= other.getRow1();
+
+ }
+
+ /**
+ * Gets the component connected to the area.
+ *
+ * @return the Component.
+ */
+ public Component getComponent() {
+ return component;
+ }
+
+ /**
+ * Sets the component connected to the area.
+ *
+ * <p>
+ * This function only sets the value in the data structure and does not
+ * send any events or set parents.
+ * </p>
+ *
+ * @param newComponent
+ * the new connected overriding the existing one.
+ */
+ protected void setComponent(Component newComponent) {
+ component = newComponent;
+ }
+
+ /**
+ * @deprecated Use {@link #getColumn1()} instead.
+ */
+ @Deprecated
+ public int getX1() {
+ return getColumn1();
+ }
+
+ /**
+ * Gets the column of the top-left corner cell.
+ *
+ * @return the column of the top-left corner cell.
+ */
+ public int getColumn1() {
+ return column1;
+ }
+
+ /**
+ * @deprecated Use {@link #getColumn2()} instead.
+ */
+ @Deprecated
+ public int getX2() {
+ return getColumn2();
+ }
+
+ /**
+ * Gets the column of the bottom-right corner cell.
+ *
+ * @return the column of the bottom-right corner cell.
+ */
+ public int getColumn2() {
+ return column2;
+ }
+
+ /**
+ * @deprecated Use {@link #getRow1()} instead.
+ */
+ @Deprecated
+ public int getY1() {
+ return getRow1();
+ }
+
+ /**
+ * Gets the row of the top-left corner cell.
+ *
+ * @return the row of the top-left corner cell.
+ */
+ public int getRow1() {
+ return row1;
+ }
+
+ /**
+ * @deprecated Use {@link #getRow2()} instead.
+ */
+ @Deprecated
+ public int getY2() {
+ return getRow2();
+ }
+
+ /**
+ * Gets the row of the bottom-right corner cell.
+ *
+ * @return the row of the bottom-right corner cell.
+ */
+ public int getRow2() {
+ return row2;
+ }
+
+ }
+
+ /**
+ * Gridlayout does not support laying components on top of each other. An
+ * <code>OverlapsException</code> is thrown when a component already exists
+ * (even partly) at the same space on a grid with the new component.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class OverlapsException extends java.lang.RuntimeException {
+
+ private final Area existingArea;
+
+ /**
+ * Constructs an <code>OverlapsException</code>.
+ *
+ * @param existingArea
+ */
+ public OverlapsException(Area existingArea) {
+ this.existingArea = existingArea;
+ }
+
+ @Override
+ public String getMessage() {
+ StringBuilder sb = new StringBuilder();
+ Component component = existingArea.getComponent();
+ sb.append(component);
+ sb.append("( type = ");
+ sb.append(component.getClass().getName());
+ if (component.getCaption() != null) {
+ sb.append(", caption = \"");
+ sb.append(component.getCaption());
+ sb.append("\"");
+ }
+ sb.append(")");
+ sb.append(" is already added to ");
+ sb.append(existingArea.column1);
+ sb.append(",");
+ sb.append(existingArea.column1);
+ sb.append(",");
+ sb.append(existingArea.row1);
+ sb.append(",");
+ sb.append(existingArea.row2);
+ sb.append("(column1, column2, row1, row2).");
+
+ return sb.toString();
+ }
+
+ /**
+ * Gets the area .
+ *
+ * @return the existing area.
+ */
+ public Area getArea() {
+ return existingArea;
+ }
+ }
+
+ /**
+ * An <code>Exception</code> object which is thrown when an area exceeds the
+ * bounds of the grid.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class OutOfBoundsException extends java.lang.RuntimeException {
+
+ private final Area areaOutOfBounds;
+
+ /**
+ * Constructs an <code>OoutOfBoundsException</code> with the specified
+ * detail message.
+ *
+ * @param areaOutOfBounds
+ */
+ public OutOfBoundsException(Area areaOutOfBounds) {
+ this.areaOutOfBounds = areaOutOfBounds;
+ }
+
+ /**
+ * Gets the area that is out of bounds.
+ *
+ * @return the area out of Bound.
+ */
+ public Area getArea() {
+ return areaOutOfBounds;
+ }
+ }
+
+ /**
+ * Sets the number of columns in the grid. The column count can not be
+ * reduced if there are any areas that would be outside of the shrunk grid.
+ *
+ * @param columns
+ * the new number of columns in the grid.
+ */
+ public void setColumns(int columns) {
+
+ // The the param
+ if (columns < 1) {
+ throw new IllegalArgumentException(
+ "The number of columns and rows in the grid must be at least 1");
+ }
+
+ // In case of no change
+ if (getColumns() == columns) {
+ return;
+ }
+
+ // Checks for overlaps
+ if (getColumns() > columns) {
+ for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
+ final Area area = i.next();
+ if (area.column2 >= columns) {
+ throw new OutOfBoundsException(area);
+ }
+ }
+ }
+
+ getState().setColumns(columns);
+
+ requestRepaint();
+ }
+
+ /**
+ * Get the number of columns in the grid.
+ *
+ * @return the number of columns in the grid.
+ */
+ public int getColumns() {
+ return getState().getColumns();
+ }
+
+ /**
+ * Sets the number of rows in the grid. The number of rows can not be
+ * reduced if there are any areas that would be outside of the shrunk grid.
+ *
+ * @param rows
+ * the new number of rows in the grid.
+ */
+ public void setRows(int rows) {
+
+ // The the param
+ if (rows < 1) {
+ throw new IllegalArgumentException(
+ "The number of columns and rows in the grid must be at least 1");
+ }
+
+ // In case of no change
+ if (getRows() == rows) {
+ return;
+ }
+
+ // Checks for overlaps
+ if (getRows() > rows) {
+ for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
+ final Area area = i.next();
+ if (area.row2 >= rows) {
+ throw new OutOfBoundsException(area);
+ }
+ }
+ }
+
+ getState().setRows(rows);
+
+ requestRepaint();
+ }
+
+ /**
+ * Get the number of rows in the grid.
+ *
+ * @return the number of rows in the grid.
+ */
+ public int getRows() {
+ return getState().getRows();
+ }
+
+ /**
+ * Gets the current x-position (column) of the cursor.
+ *
+ * <p>
+ * The cursor position points the position for the next component that is
+ * added without specifying its coordinates (grid cell). When the cursor
+ * position is occupied, the next component will be added to first free
+ * position after the cursor.
+ * </p>
+ *
+ * @return the grid column the cursor is on, starting from 0.
+ */
+ public int getCursorX() {
+ return cursorX;
+ }
+
+ /**
+ * Sets the current cursor x-position. This is usually handled automatically
+ * by GridLayout.
+ *
+ * @param cursorX
+ */
+ public void setCursorX(int cursorX) {
+ this.cursorX = cursorX;
+ }
+
+ /**
+ * Gets the current y-position (row) of the cursor.
+ *
+ * <p>
+ * The cursor position points the position for the next component that is
+ * added without specifying its coordinates (grid cell). When the cursor
+ * position is occupied, the next component will be added to the first free
+ * position after the cursor.
+ * </p>
+ *
+ * @return the grid row the Cursor is on.
+ */
+ public int getCursorY() {
+ return cursorY;
+ }
+
+ /**
+ * Sets the current y-coordinate (row) of the cursor. This is usually
+ * handled automatically by GridLayout.
+ *
+ * @param cursorY
+ * the row number, starting from 0 for the topmost row.
+ */
+ public void setCursorY(int cursorY) {
+ this.cursorY = cursorY;
+ }
+
+ /* Documented in superclass */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ // Gets the locations
+ Area oldLocation = null;
+ Area newLocation = null;
+ for (final Iterator<Area> i = areas.iterator(); i.hasNext();) {
+ final Area location = i.next();
+ final Component component = location.getComponent();
+ if (component == oldComponent) {
+ oldLocation = location;
+ }
+ if (component == newComponent) {
+ newLocation = location;
+ }
+ }
+
+ if (oldLocation == null) {
+ addComponent(newComponent);
+ } else if (newLocation == null) {
+ removeComponent(oldComponent);
+ addComponent(newComponent, oldLocation.getColumn1(),
+ oldLocation.getRow1(), oldLocation.getColumn2(),
+ oldLocation.getRow2());
+ } else {
+ oldLocation.setComponent(newComponent);
+ newLocation.setComponent(oldComponent);
+ requestRepaint();
+ }
+ }
+
+ /*
+ * Removes all components from this container.
+ *
+ * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
+ */
+ @Override
+ public void removeAllComponents() {
+ super.removeAllComponents();
+ componentToAlignment = new HashMap<Component, Alignment>();
+ cursorX = 0;
+ cursorY = 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.AlignmentHandler#setComponentAlignment(com
+ * .vaadin.ui.Component, int, int)
+ */
+ @Override
+ public void setComponentAlignment(Component childComponent,
+ int horizontalAlignment, int verticalAlignment) {
+ componentToAlignment.put(childComponent, new Alignment(
+ horizontalAlignment + verticalAlignment));
+ requestRepaint();
+ }
+
+ @Override
+ public void setComponentAlignment(Component childComponent,
+ Alignment alignment) {
+ componentToAlignment.put(childComponent, alignment);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
+ */
+ @Override
+ public void setSpacing(boolean spacing) {
+ getState().setSpacing(spacing);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
+ */
+ @Override
+ public boolean isSpacing() {
+ return getState().isSpacing();
+ }
+
+ /**
+ * Inserts an empty row at the specified position in the grid.
+ *
+ * @param row
+ * Index of the row before which the new row will be inserted.
+ * The leftmost row has index 0.
+ */
+ public void insertRow(int row) {
+ if (row > getRows()) {
+ throw new IllegalArgumentException("Cannot insert row at " + row
+ + " in a gridlayout with height " + getRows());
+ }
+
+ for (Iterator<Area> i = areas.iterator(); i.hasNext();) {
+ Area existingArea = i.next();
+ // Areas ending below the row needs to be moved down or stretched
+ if (existingArea.row2 >= row) {
+ existingArea.row2++;
+
+ // Stretch areas that span over the selected row
+ if (existingArea.row1 >= row) {
+ existingArea.row1++;
+ }
+
+ }
+ }
+
+ if (cursorY >= row) {
+ cursorY++;
+ }
+
+ setRows(getRows() + 1);
+ structuralChange = true;
+ requestRepaint();
+ }
+
+ /**
+ * Removes a row and all the components in the row.
+ *
+ * <p>
+ * Components which span over several rows are removed if the selected row
+ * is on the first row of such a component.
+ * </p>
+ *
+ * <p>
+ * If the last row is removed then all remaining components will be removed
+ * and the grid will be reduced to one row. The cursor will be moved to the
+ * upper left cell of the grid.
+ * </p>
+ *
+ * @param row
+ * Index of the row to remove. The leftmost row has index 0.
+ */
+ public void removeRow(int row) {
+ if (row >= getRows()) {
+ throw new IllegalArgumentException("Cannot delete row " + row
+ + " from a gridlayout with height " + getRows());
+ }
+
+ // Remove all components in row
+ for (int col = 0; col < getColumns(); col++) {
+ removeComponent(col, row);
+ }
+
+ // Shrink or remove areas in the selected row
+ for (Iterator<Area> i = areas.iterator(); i.hasNext();) {
+ Area existingArea = i.next();
+ if (existingArea.row2 >= row) {
+ existingArea.row2--;
+
+ if (existingArea.row1 > row) {
+ existingArea.row1--;
+ }
+ }
+ }
+
+ if (getRows() == 1) {
+ /*
+ * Removing the last row means that the dimensions of the Grid
+ * layout will be truncated to 1 empty row and the cursor is moved
+ * to the first cell
+ */
+ cursorX = 0;
+ cursorY = 0;
+ } else {
+ setRows(getRows() - 1);
+ if (cursorY > row) {
+ cursorY--;
+ }
+ }
+
+ structuralChange = true;
+ requestRepaint();
+
+ }
+
+ /**
+ * Sets the expand ratio of given column.
+ *
+ * <p>
+ * The expand ratio defines how excess space is distributed among columns.
+ * Excess space means space that is left over from components that are not
+ * sized relatively. By default, the excess space is distributed evenly.
+ * </p>
+ *
+ * <p>
+ * Note that the component width of the GridLayout must be defined (fixed or
+ * relative, as opposed to undefined) for this method to have any effect.
+ * </p>
+ *
+ * @see #setWidth(float, int)
+ *
+ * @param columnIndex
+ * @param ratio
+ */
+ public void setColumnExpandRatio(int columnIndex, float ratio) {
+ columnExpandRatio.put(columnIndex, ratio);
+ requestRepaint();
+ }
+
+ /**
+ * Returns the expand ratio of given column
+ *
+ * @see #setColumnExpandRatio(int, float)
+ *
+ * @param columnIndex
+ * @return the expand ratio, 0.0f by default
+ */
+ public float getColumnExpandRatio(int columnIndex) {
+ Float r = columnExpandRatio.get(columnIndex);
+ return r == null ? 0 : r.floatValue();
+ }
+
+ /**
+ * Sets the expand ratio of given row.
+ *
+ * <p>
+ * Expand ratio defines how excess space is distributed among rows. Excess
+ * space means the space left over from components that are not sized
+ * relatively. By default, the excess space is distributed evenly.
+ * </p>
+ *
+ * <p>
+ * Note, that height needs to be defined (fixed or relative, as opposed to
+ * undefined height) for this method to have any effect.
+ * </p>
+ *
+ * @see #setHeight(float, int)
+ *
+ * @param rowIndex
+ * The row index, starting from 0 for the topmost row.
+ * @param ratio
+ */
+ public void setRowExpandRatio(int rowIndex, float ratio) {
+ rowExpandRatio.put(rowIndex, ratio);
+ requestRepaint();
+ }
+
+ /**
+ * Returns the expand ratio of given row.
+ *
+ * @see #setRowExpandRatio(int, float)
+ *
+ * @param rowIndex
+ * The row index, starting from 0 for the topmost row.
+ * @return the expand ratio, 0.0f by default
+ */
+ public float getRowExpandRatio(int rowIndex) {
+ Float r = rowExpandRatio.get(rowIndex);
+ return r == null ? 0 : r.floatValue();
+ }
+
+ /**
+ * Gets the Component at given index.
+ *
+ * @param x
+ * The column index, starting from 0 for the leftmost column.
+ * @param y
+ * The row index, starting from 0 for the topmost row.
+ * @return Component in given cell or null if empty
+ */
+ public Component getComponent(int x, int y) {
+ for (final Iterator<Area> iterator = areas.iterator(); iterator
+ .hasNext();) {
+ final Area area = iterator.next();
+ if (area.getColumn1() <= x && x <= area.getColumn2()
+ && area.getRow1() <= y && y <= area.getRow2()) {
+ return area.getComponent();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns information about the area where given component is laid in the
+ * GridLayout.
+ *
+ * @param component
+ * the component whose area information is requested.
+ * @return an Area object that contains information how component is laid in
+ * the grid
+ */
+ public Area getComponentArea(Component component) {
+ for (final Iterator<Area> iterator = areas.iterator(); iterator
+ .hasNext();) {
+ final Area area = iterator.next();
+ if (area.getComponent() == component) {
+ return area;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void addListener(LayoutClickListener listener) {
+ addListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener,
+ LayoutClickListener.clickMethod);
+ }
+
+ @Override
+ public void removeListener(LayoutClickListener listener) {
+ removeListener(LayoutClickEventHandler.LAYOUT_CLICK_EVENT_IDENTIFIER,
+ LayoutClickEvent.class, listener);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/HasComponents.java b/server/src/com/vaadin/ui/HasComponents.java
new file mode 100644
index 0000000000..3ebd63bff2
--- /dev/null
+++ b/server/src/com/vaadin/ui/HasComponents.java
@@ -0,0 +1,49 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.util.Iterator;
+
+/**
+ * Interface that must be implemented by all {@link Component}s that contain
+ * other {@link Component}s.
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ *
+ */
+public interface HasComponents extends Component, Iterable<Component> {
+ /**
+ * Gets an iterator to the collection of contained components. Using this
+ * iterator it is possible to step through all components contained in this
+ * container.
+ *
+ * @return the component iterator.
+ *
+ * @deprecated Use {@link #iterator()} instead.
+ */
+ @Deprecated
+ public Iterator<Component> getComponentIterator();
+
+ /**
+ * Checks if the child component is visible. This method allows hiding a
+ * child component from updates and communication to and from the client.
+ * This is useful for components that show only a limited number of its
+ * children at any given time and want to allow updates only for the
+ * children that are visible (e.g. TabSheet has one tab open at a time).
+ * <p>
+ * Note that this will prevent updates from reaching the child even though
+ * the child itself is set to visible. Also if a child is set to invisible
+ * this will not force it to be visible.
+ * </p>
+ *
+ * @param childComponent
+ * The child component to check
+ * @return true if the child component is visible to the user, false
+ * otherwise
+ */
+ public boolean isComponentVisible(Component childComponent);
+
+}
diff --git a/server/src/com/vaadin/ui/HorizontalLayout.java b/server/src/com/vaadin/ui/HorizontalLayout.java
new file mode 100644
index 0000000000..b9dc1c13ca
--- /dev/null
+++ b/server/src/com/vaadin/ui/HorizontalLayout.java
@@ -0,0 +1,24 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+/**
+ * Horizontal layout
+ *
+ * <code>HorizontalLayout</code> is a component container, which shows the
+ * subcomponents in the order of their addition (horizontally).
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.3
+ */
+@SuppressWarnings("serial")
+public class HorizontalLayout extends AbstractOrderedLayout {
+
+ public HorizontalLayout() {
+
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/HorizontalSplitPanel.java b/server/src/com/vaadin/ui/HorizontalSplitPanel.java
new file mode 100644
index 0000000000..5bd6c8a075
--- /dev/null
+++ b/server/src/com/vaadin/ui/HorizontalSplitPanel.java
@@ -0,0 +1,34 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+/**
+ * A horizontal split panel contains two components and lays them horizontally.
+ * The first component is on the left side.
+ *
+ * <pre>
+ *
+ * +---------------------++----------------------+
+ * | || |
+ * | The first component || The second component |
+ * | || |
+ * +---------------------++----------------------+
+ *
+ * ^
+ * |
+ * the splitter
+ *
+ * </pre>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 6.5
+ */
+public class HorizontalSplitPanel extends AbstractSplitPanel {
+ public HorizontalSplitPanel() {
+ super();
+ setSizeFull();
+ }
+}
diff --git a/server/src/com/vaadin/ui/Html5File.java b/server/src/com/vaadin/ui/Html5File.java
new file mode 100644
index 0000000000..aa3fb558fa
--- /dev/null
+++ b/server/src/com/vaadin/ui/Html5File.java
@@ -0,0 +1,65 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.terminal.StreamVariable;
+
+/**
+ * {@link DragAndDropWrapper} can receive also files from client computer if
+ * appropriate HTML 5 features are supported on client side. This class wraps
+ * information about dragged file on server side.
+ */
+public class Html5File implements Serializable {
+
+ private String name;
+ private long size;
+ private StreamVariable streamVariable;
+ private String type;
+
+ Html5File(String name, long size, String mimeType) {
+ this.name = name;
+ this.size = size;
+ type = mimeType;
+ }
+
+ public String getFileName() {
+ return name;
+ }
+
+ public long getFileSize() {
+ return size;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the {@link StreamVariable} that into which the file contents will be
+ * written. Usage of StreamVariable is similar to {@link Upload} component.
+ * <p>
+ * If the {@link StreamVariable} is not set in the {@link DropHandler} the
+ * file contents will not be sent to server.
+ * <p>
+ * <em>Note!</em> receiving file contents is experimental feature depending
+ * on HTML 5 API's. It is supported only by modern web browsers like Firefox
+ * 3.6 and above and recent webkit based browsers (Safari 5, Chrome 6) at
+ * this time.
+ *
+ * @param streamVariable
+ * the callback that returns stream where the implementation
+ * writes the file contents as it arrives.
+ */
+ public void setStreamVariable(StreamVariable streamVariable) {
+ this.streamVariable = streamVariable;
+ }
+
+ public StreamVariable getStreamVariable() {
+ return streamVariable;
+ }
+
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/ui/InlineDateField.java b/server/src/com/vaadin/ui/InlineDateField.java
new file mode 100644
index 0000000000..cf61703318
--- /dev/null
+++ b/server/src/com/vaadin/ui/InlineDateField.java
@@ -0,0 +1,46 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Date;
+
+import com.vaadin.data.Property;
+
+/**
+ * <p>
+ * A date entry component, which displays the actual date selector inline.
+ *
+ * </p>
+ *
+ * @see DateField
+ * @see PopupDateField
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+public class InlineDateField extends DateField {
+
+ public InlineDateField() {
+ super();
+ }
+
+ public InlineDateField(Property dataSource) throws IllegalArgumentException {
+ super(dataSource);
+ }
+
+ public InlineDateField(String caption, Date value) {
+ super(caption, value);
+ }
+
+ public InlineDateField(String caption, Property dataSource) {
+ super(caption, dataSource);
+ }
+
+ public InlineDateField(String caption) {
+ super(caption);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/JavaScript.java b/server/src/com/vaadin/ui/JavaScript.java
new file mode 100644
index 0000000000..0b4669728a
--- /dev/null
+++ b/server/src/com/vaadin/ui/JavaScript.java
@@ -0,0 +1,157 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.external.json.JSONArray;
+import com.vaadin.external.json.JSONException;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.shared.extension.javascriptmanager.ExecuteJavaScriptRpc;
+import com.vaadin.shared.extension.javascriptmanager.JavaScriptManagerState;
+import com.vaadin.terminal.AbstractExtension;
+import com.vaadin.terminal.Page;
+
+/**
+ * Provides access to JavaScript functionality in the web browser. To get an
+ * instance of JavaScript, either use Page.getJavaScript() or
+ * JavaScript.getCurrent() as a shorthand for getting the JavaScript object
+ * corresponding to the current Page.
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ */
+public class JavaScript extends AbstractExtension {
+ private Map<String, JavaScriptFunction> functions = new HashMap<String, JavaScriptFunction>();
+
+ // Can not be defined in client package as this JSONArray is not available
+ // in GWT
+ public interface JavaScriptCallbackRpc extends ServerRpc {
+ public void call(String name, JSONArray arguments);
+ }
+
+ /**
+ * Creates a new JavaScript object. You should typically not this, but
+ * instead use the JavaScript object already associated with your Page
+ * object.
+ */
+ public JavaScript() {
+ registerRpc(new JavaScriptCallbackRpc() {
+ @Override
+ public void call(String name, JSONArray arguments) {
+ JavaScriptFunction function = functions.get(name);
+ // TODO handle situation if name is not registered
+ try {
+ function.call(arguments);
+ } catch (JSONException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public JavaScriptManagerState getState() {
+ return (JavaScriptManagerState) super.getState();
+ }
+
+ /**
+ * Add a new function to the global JavaScript namespace (i.e. the window
+ * object). The <code>call</code> method in the passed
+ * {@link JavaScriptFunction} object will be invoked with the same
+ * parameters whenever the JavaScript function is called in the browser.
+ *
+ * A function added with the name <code>"myFunction"</code> can thus be
+ * invoked with the following JavaScript code:
+ * <code>window.myFunction(argument1, argument2)</code>.
+ *
+ * If the name parameter contains dots, simple objects are created on demand
+ * to allow calling the function using the same name (e.g.
+ * <code>window.myObject.myFunction</code>).
+ *
+ * @param name
+ * the name that the function should get in the global JavaScript
+ * namespace.
+ * @param function
+ * the JavaScriptFunction that will be invoked if the JavaScript
+ * function is called.
+ */
+ public void addFunction(String name, JavaScriptFunction function) {
+ functions.put(name, function);
+ if (getState().getNames().add(name)) {
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Removes a JavaScripFunction from the browser's global JavaScript
+ * namespace.
+ *
+ * If the name contains dots and intermediate objects were created by
+ * {@link #addFunction(String, JavaScriptFunction)}, these objects will not
+ * be removed by this method.
+ *
+ * @param name
+ * the name of the callback to remove
+ */
+ public void removeFunction(String name) {
+ functions.remove(name);
+ if (getState().getNames().remove(name)) {
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Executes the given JavaScript code in the browser.
+ *
+ * @param script
+ * The JavaScript code to run.
+ */
+ public void execute(String script) {
+ getRpcProxy(ExecuteJavaScriptRpc.class).executeJavaScript(script);
+ }
+
+ /**
+ * Executes the given JavaScript code in the browser.
+ *
+ * @param script
+ * The JavaScript code to run.
+ */
+ public static void eval(String script) {
+ getCurrent().execute(script);
+ }
+
+ /**
+ * Get the JavaScript object for the current Page, or null if there is no
+ * current page.
+ *
+ * @see Page#getCurrent()
+ *
+ * @return the JavaScript object corresponding to the current Page, or
+ * <code>null</code> if there is no current page.
+ */
+ public static JavaScript getCurrent() {
+ Page page = Page.getCurrent();
+ if (page == null) {
+ return null;
+ }
+ return page.getJavaScript();
+ }
+
+ /**
+ * JavaScript is not designed to be removed.
+ *
+ * @throws UnsupportedOperationException
+ * when invoked
+ */
+ @Override
+ public void removeFromTarget() {
+ throw new UnsupportedOperationException(
+ "JavaScript is not designed to be removed.");
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/JavaScriptFunction.java b/server/src/com/vaadin/ui/JavaScriptFunction.java
new file mode 100644
index 0000000000..e39ae9b87b
--- /dev/null
+++ b/server/src/com/vaadin/ui/JavaScriptFunction.java
@@ -0,0 +1,41 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.external.json.JSONArray;
+import com.vaadin.external.json.JSONException;
+import com.vaadin.terminal.AbstractJavaScriptExtension;
+
+/**
+ * Defines a method that is called by a client-side JavaScript function. When
+ * the corresponding JavaScript function is called, the {@link #call(JSONArray)}
+ * method is invoked.
+ *
+ * @see JavaScript#addFunction(String, JavaScriptCallback)
+ * @see AbstractJavaScriptComponent#addFunction(String, JavaScriptCallback)
+ * @see AbstractJavaScriptExtension#addFunction(String, JavaScriptCallback)
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ */
+public interface JavaScriptFunction extends Serializable {
+ /**
+ * Invoked whenever the corresponding JavaScript function is called in the
+ * browser.
+ * <p>
+ * Because of the asynchronous nature of the communication between client
+ * and server, no return value can be sent back to the browser.
+ *
+ * @param arguments
+ * an array with JSON representations of the arguments with which
+ * the JavaScript function was called.
+ * @throws JSONException
+ * if the arguments can not be interpreted
+ */
+ public void call(JSONArray arguments) throws JSONException;
+}
diff --git a/server/src/com/vaadin/ui/Label.java b/server/src/com/vaadin/ui/Label.java
new file mode 100644
index 0000000000..7e50a37805
--- /dev/null
+++ b/server/src/com/vaadin/ui/Label.java
@@ -0,0 +1,483 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Property;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.ConverterUtil;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.shared.ui.label.LabelState;
+
+/**
+ * Label component for showing non-editable short texts.
+ *
+ * The label content can be set to the modes specified by {@link ContentMode}
+ *
+ * <p>
+ * The contents of the label may contain simple formatting:
+ * <ul>
+ * <li><b>&lt;b></b> Bold
+ * <li><b>&lt;i></b> Italic
+ * <li><b>&lt;u></b> Underlined
+ * <li><b>&lt;br/></b> Linebreak
+ * <li><b>&lt;ul>&lt;li>item 1&lt;/li>&lt;li>item 2&lt;/li>&lt;/ul></b> List of
+ * items
+ * </ul>
+ * The <b>b</b>,<b>i</b>,<b>u</b> and <b>li</b> tags can contain all the tags in
+ * the list recursively.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Label extends AbstractComponent implements Property<String>,
+ Property.Viewer, Property.ValueChangeListener,
+ Property.ValueChangeNotifier, Comparable<Label> {
+
+ private static final Logger logger = Logger
+ .getLogger(Label.class.getName());
+
+ /**
+ * @deprecated From 7.0, use {@link ContentMode#TEXT} instead
+ */
+ @Deprecated
+ public static final ContentMode CONTENT_TEXT = ContentMode.TEXT;
+
+ /**
+ * @deprecated From 7.0, use {@link ContentMode#PREFORMATTED} instead
+ */
+ @Deprecated
+ public static final ContentMode CONTENT_PREFORMATTED = ContentMode.PREFORMATTED;
+
+ /**
+ * @deprecated From 7.0, use {@link ContentMode#XHTML} instead
+ */
+ @Deprecated
+ public static final ContentMode CONTENT_XHTML = ContentMode.XHTML;
+
+ /**
+ * @deprecated From 7.0, use {@link ContentMode#XML} instead
+ */
+ @Deprecated
+ public static final ContentMode CONTENT_XML = ContentMode.XML;
+
+ /**
+ * @deprecated From 7.0, use {@link ContentMode#RAW} instead
+ */
+ @Deprecated
+ public static final ContentMode CONTENT_RAW = ContentMode.RAW;
+
+ /**
+ * @deprecated From 7.0, use {@link ContentMode#TEXT} instead
+ */
+ @Deprecated
+ public static final ContentMode CONTENT_DEFAULT = ContentMode.TEXT;
+
+ /**
+ * A converter used to convert from the data model type to the field type
+ * and vice versa. Label type is always String.
+ */
+ private Converter<String, Object> converter = null;
+
+ private Property<String> dataSource = null;
+
+ /**
+ * Creates an empty Label.
+ */
+ public Label() {
+ this("");
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents.
+ *
+ * @param content
+ */
+ public Label(String content) {
+ this(content, ContentMode.TEXT);
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents read from given
+ * datasource.
+ *
+ * @param contentSource
+ */
+ public Label(Property contentSource) {
+ this(contentSource, ContentMode.TEXT);
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents.
+ *
+ * @param content
+ * @param contentMode
+ */
+ public Label(String content, ContentMode contentMode) {
+ setValue(content);
+ setContentMode(contentMode);
+ setWidth(100, Unit.PERCENTAGE);
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents read from given
+ * datasource.
+ *
+ * @param contentSource
+ * @param contentMode
+ */
+ public Label(Property contentSource, ContentMode contentMode) {
+ setPropertyDataSource(contentSource);
+ setContentMode(contentMode);
+ setWidth(100, Unit.PERCENTAGE);
+ }
+
+ @Override
+ public LabelState getState() {
+ return (LabelState) super.getState();
+ }
+
+ /**
+ * Gets the value of the label.
+ * <p>
+ * The value of the label is the text that is shown to the end user.
+ * Depending on the {@link ContentMode} it is plain text or markup.
+ * </p>
+ *
+ * @return the value of the label.
+ */
+ @Override
+ public String getValue() {
+ if (getPropertyDataSource() == null) {
+ // Use internal value if we are running without a data source
+ return getState().getText();
+ }
+ return ConverterUtil.convertFromModel(getPropertyDataSource()
+ .getValue(), String.class, getConverter(), getLocale());
+ }
+
+ /**
+ * Set the value of the label. Value of the label is the XML contents of the
+ * label.
+ *
+ * @param newStringValue
+ * the New value of the label.
+ */
+ @Override
+ public void setValue(Object newStringValue) {
+ if (newStringValue != null && newStringValue.getClass() != String.class) {
+ throw new Converter.ConversionException("Value of type "
+ + newStringValue.getClass() + " cannot be assigned to "
+ + String.class.getName());
+ }
+ if (getPropertyDataSource() == null) {
+ getState().setText((String) newStringValue);
+ requestRepaint();
+ } else {
+ throw new IllegalStateException(
+ "Label is only a Property.Viewer and cannot update its data source");
+ }
+ }
+
+ /**
+ * Returns the value displayed by this label.
+ *
+ * @see java.lang.Object#toString()
+ * @deprecated As of 7.0.0, use {@link #getValue()} to get the value of the
+ * label or {@link #getPropertyDataSource()} .getValue() to get
+ * the value of the data source.
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ logger.warning("You are using Label.toString() to get the value for a "
+ + getClass().getSimpleName()
+ + ". This is not recommended and will not be supported in future versions.");
+ return getValue();
+ }
+
+ /**
+ * Gets the type of the Property.
+ *
+ * @see com.vaadin.data.Property#getType()
+ */
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+
+ /**
+ * Gets the viewing data-source property.
+ *
+ * @return the data source property.
+ * @see com.vaadin.data.Property.Viewer#getPropertyDataSource()
+ */
+ @Override
+ public Property getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * Sets the property as data-source for viewing.
+ *
+ * @param newDataSource
+ * the new data source Property
+ * @see com.vaadin.data.Property.Viewer#setPropertyDataSource(com.vaadin.data.Property)
+ */
+ @Override
+ public void setPropertyDataSource(Property newDataSource) {
+ // Stops listening the old data source changes
+ if (dataSource != null
+ && Property.ValueChangeNotifier.class
+ .isAssignableFrom(dataSource.getClass())) {
+ ((Property.ValueChangeNotifier) dataSource).removeListener(this);
+ }
+
+ if (!ConverterUtil.canConverterHandle(getConverter(), String.class,
+ newDataSource.getType())) {
+ // Try to find a converter
+ Converter<String, ?> c = ConverterUtil.getConverter(String.class,
+ newDataSource.getType(), getApplication());
+ setConverter(c);
+ }
+ dataSource = newDataSource;
+
+ // Listens the new data source if possible
+ if (dataSource != null
+ && Property.ValueChangeNotifier.class
+ .isAssignableFrom(dataSource.getClass())) {
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Gets the content mode of the Label.
+ *
+ * @return the Content mode of the label.
+ *
+ * @see ContentMode
+ */
+ public ContentMode getContentMode() {
+ return getState().getContentMode();
+ }
+
+ /**
+ * Sets the content mode of the Label.
+ *
+ * @param contentMode
+ * the New content mode of the label.
+ *
+ * @see ContentMode
+ */
+ public void setContentMode(ContentMode contentMode) {
+ if (contentMode == null) {
+ throw new IllegalArgumentException("Content mode can not be null");
+ }
+
+ getState().setContentMode(contentMode);
+ requestRepaint();
+ }
+
+ /* Value change events */
+
+ private static final Method VALUE_CHANGE_METHOD;
+
+ static {
+ try {
+ VALUE_CHANGE_METHOD = Property.ValueChangeListener.class
+ .getDeclaredMethod("valueChange",
+ new Class[] { Property.ValueChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in Label");
+ }
+ }
+
+ /**
+ * Value change event
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public static class ValueChangeEvent extends Component.Event implements
+ Property.ValueChangeEvent {
+
+ /**
+ * New instance of text change event
+ *
+ * @param source
+ * the Source of the event.
+ */
+ public ValueChangeEvent(Label source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property that has been modified.
+ *
+ * @see com.vaadin.data.Property.ValueChangeEvent#getProperty()
+ */
+ @Override
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /**
+ * Adds the value change listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ * @see com.vaadin.data.Property.ValueChangeNotifier#addListener(com.vaadin.data.Property.ValueChangeListener)
+ */
+ @Override
+ public void addListener(Property.ValueChangeListener listener) {
+ addListener(Label.ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD);
+ }
+
+ /**
+ * Removes the value change listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ * @see com.vaadin.data.Property.ValueChangeNotifier#removeListener(com.vaadin.data.Property.ValueChangeListener)
+ */
+ @Override
+ public void removeListener(Property.ValueChangeListener listener) {
+ removeListener(Label.ValueChangeEvent.class, listener,
+ VALUE_CHANGE_METHOD);
+ }
+
+ /**
+ * Emits the options change event.
+ */
+ protected void fireValueChange() {
+ // Set the error message
+ fireEvent(new Label.ValueChangeEvent(this));
+ }
+
+ /**
+ * Listens the value change events from data source.
+ *
+ * @see com.vaadin.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent)
+ */
+ @Override
+ public void valueChange(Property.ValueChangeEvent event) {
+ // Update the internal value from the data source
+ getState().setText(getValue());
+ requestRepaint();
+
+ fireValueChange();
+ }
+
+ private String getComparableValue() {
+ String stringValue = getValue();
+ if (stringValue == null) {
+ stringValue = "";
+ }
+
+ if (getContentMode() == ContentMode.XHTML
+ || getContentMode() == ContentMode.XML) {
+ return stripTags(stringValue);
+ } else {
+ return stringValue;
+ }
+
+ }
+
+ /**
+ * Compares the Label to other objects.
+ *
+ * <p>
+ * Labels can be compared to other labels for sorting label contents. This
+ * is especially handy for sorting table columns.
+ * </p>
+ *
+ * <p>
+ * In RAW, PREFORMATTED and TEXT modes, the label contents are compared as
+ * is. In XML, UIDL and XHTML modes, only CDATA is compared and tags
+ * ignored. If the other object is not a Label, its toString() return value
+ * is used in comparison.
+ * </p>
+ *
+ * @param other
+ * the Other object to compare to.
+ * @return a negative integer, zero, or a positive integer as this object is
+ * less than, equal to, or greater than the specified object.
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(Label other) {
+
+ String thisValue = getComparableValue();
+ String otherValue = other.getComparableValue();
+
+ return thisValue.compareTo(otherValue);
+ }
+
+ /**
+ * Strips the tags from the XML.
+ *
+ * @param xml
+ * the String containing a XML snippet.
+ * @return the original XML without tags.
+ */
+ private String stripTags(String xml) {
+
+ final StringBuffer res = new StringBuffer();
+
+ int processed = 0;
+ final int xmlLen = xml.length();
+ while (processed < xmlLen) {
+ int next = xml.indexOf('<', processed);
+ if (next < 0) {
+ next = xmlLen;
+ }
+ res.append(xml.substring(processed, next));
+ if (processed < xmlLen) {
+ next = xml.indexOf('>', processed);
+ if (next < 0) {
+ next = xmlLen;
+ }
+ processed = next + 1;
+ }
+ }
+
+ return res.toString();
+ }
+
+ /**
+ * Gets the converter used to convert the property data source value to the
+ * label value.
+ *
+ * @return The converter or null if none is set.
+ */
+ public Converter<String, Object> getConverter() {
+ return converter;
+ }
+
+ /**
+ * Sets the converter used to convert the label value to the property data
+ * source type. The converter must have a presentation type of String.
+ *
+ * @param converter
+ * The new converter to use.
+ */
+ public void setConverter(Converter<String, ?> converter) {
+ this.converter = (Converter<String, Object>) converter;
+ requestRepaint();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Layout.java b/server/src/com/vaadin/ui/Layout.java
new file mode 100644
index 0000000000..d083f9afdc
--- /dev/null
+++ b/server/src/com/vaadin/ui/Layout.java
@@ -0,0 +1,229 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.ui.VMarginInfo;
+import com.vaadin.shared.ui.AlignmentInfo.Bits;
+
+/**
+ * Extension to the {@link ComponentContainer} interface which adds the
+ * layouting control to the elements in the container. This is required by the
+ * various layout components to enable them to place other components in
+ * specific locations in the UI.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Layout extends ComponentContainer, Serializable {
+
+ /**
+ * Enable layout margins. Affects all four sides of the layout. This will
+ * tell the client-side implementation to leave extra space around the
+ * layout. The client-side implementation decides the actual amount, and it
+ * can vary between themes.
+ *
+ * @param enabled
+ */
+ public void setMargin(boolean enabled);
+
+ /**
+ * Enable specific layout margins. This will tell the client-side
+ * implementation to leave extra space around the layout in specified edges,
+ * clockwise from top (top, right, bottom, left). The client-side
+ * implementation decides the actual amount, and it can vary between themes.
+ *
+ * @param top
+ * @param right
+ * @param bottom
+ * @param left
+ */
+ public void setMargin(boolean top, boolean right, boolean bottom,
+ boolean left);
+
+ /**
+ * AlignmentHandler is most commonly an advanced {@link Layout} that can
+ * align its components.
+ */
+ public interface AlignmentHandler extends Serializable {
+
+ /**
+ * Contained component should be aligned horizontally to the left.
+ *
+ * @deprecated Use of {@link Alignment} class and its constants
+ */
+ @Deprecated
+ public static final int ALIGNMENT_LEFT = Bits.ALIGNMENT_LEFT;
+
+ /**
+ * Contained component should be aligned horizontally to the right.
+ *
+ * @deprecated Use of {@link Alignment} class and its constants
+ */
+ @Deprecated
+ public static final int ALIGNMENT_RIGHT = Bits.ALIGNMENT_RIGHT;
+
+ /**
+ * Contained component should be aligned vertically to the top.
+ *
+ * @deprecated Use of {@link Alignment} class and its constants
+ */
+ @Deprecated
+ public static final int ALIGNMENT_TOP = Bits.ALIGNMENT_TOP;
+
+ /**
+ * Contained component should be aligned vertically to the bottom.
+ *
+ * @deprecated Use of {@link Alignment} class and its constants
+ */
+ @Deprecated
+ public static final int ALIGNMENT_BOTTOM = Bits.ALIGNMENT_BOTTOM;
+
+ /**
+ * Contained component should be horizontally aligned to center.
+ *
+ * @deprecated Use of {@link Alignment} class and its constants
+ */
+ @Deprecated
+ public static final int ALIGNMENT_HORIZONTAL_CENTER = Bits.ALIGNMENT_HORIZONTAL_CENTER;
+
+ /**
+ * Contained component should be vertically aligned to center.
+ *
+ * @deprecated Use of {@link Alignment} class and its constants
+ */
+ @Deprecated
+ public static final int ALIGNMENT_VERTICAL_CENTER = Bits.ALIGNMENT_VERTICAL_CENTER;
+
+ /**
+ * Set alignment for one contained component in this layout. Alignment
+ * is calculated as a bit mask of the two passed values.
+ *
+ * @deprecated Use {@link #setComponentAlignment(Component, Alignment)}
+ * instead
+ *
+ * @param childComponent
+ * the component to align within it's layout cell.
+ * @param horizontalAlignment
+ * the horizontal alignment for the child component (left,
+ * center, right). Use ALIGNMENT constants.
+ * @param verticalAlignment
+ * the vertical alignment for the child component (top,
+ * center, bottom). Use ALIGNMENT constants.
+ */
+ @Deprecated
+ public void setComponentAlignment(Component childComponent,
+ int horizontalAlignment, int verticalAlignment);
+
+ /**
+ * Set alignment for one contained component in this layout. Use
+ * predefined alignments from Alignment class.
+ *
+ * Example: <code>
+ * layout.setComponentAlignment(myComponent, Alignment.TOP_RIGHT);
+ * </code>
+ *
+ * @param childComponent
+ * the component to align within it's layout cell.
+ * @param alignment
+ * the Alignment value to be set
+ */
+ public void setComponentAlignment(Component childComponent,
+ Alignment alignment);
+
+ /**
+ * Returns the current Alignment of given component.
+ *
+ * @param childComponent
+ * @return the {@link Alignment}
+ */
+ public Alignment getComponentAlignment(Component childComponent);
+
+ }
+
+ /**
+ * This type of layout supports automatic addition of space between its
+ * components.
+ *
+ */
+ public interface SpacingHandler extends Serializable {
+ /**
+ * Enable spacing between child components within this layout.
+ *
+ * <p>
+ * <strong>NOTE:</strong> This will only affect the space between
+ * components, not the space around all the components in the layout
+ * (i.e. do not confuse this with the cellspacing attribute of a HTML
+ * Table). Use {@link #setMargin(boolean)} to add space around the
+ * layout.
+ * </p>
+ *
+ * <p>
+ * See the reference manual for more information about CSS rules for
+ * defining the amount of spacing to use.
+ * </p>
+ *
+ * @param enabled
+ * true if spacing should be turned on, false if it should be
+ * turned off
+ */
+ public void setSpacing(boolean enabled);
+
+ /**
+ *
+ * @return true if spacing between child components within this layout
+ * is enabled, false otherwise
+ */
+ public boolean isSpacing();
+ }
+
+ /**
+ * This type of layout supports automatic addition of margins (space around
+ * its components).
+ */
+ public interface MarginHandler extends Serializable {
+ /**
+ * Enable margins for this layout.
+ *
+ * <p>
+ * <strong>NOTE:</strong> This will only affect the space around the
+ * components in the layout, not space between the components in the
+ * layout. Use {@link #setSpacing(boolean)} to add space between the
+ * components in the layout.
+ * </p>
+ *
+ * <p>
+ * See the reference manual for more information about CSS rules for
+ * defining the size of the margin.
+ * </p>
+ *
+ * @param marginInfo
+ * MarginInfo object containing the new margins.
+ */
+ public void setMargin(MarginInfo marginInfo);
+
+ /**
+ *
+ * @return MarginInfo containing the currently enabled margins.
+ */
+ public MarginInfo getMargin();
+ }
+
+ @SuppressWarnings("serial")
+ public static class MarginInfo extends VMarginInfo implements Serializable {
+
+ public MarginInfo(boolean enabled) {
+ super(enabled, enabled, enabled, enabled);
+ }
+
+ public MarginInfo(boolean top, boolean right, boolean bottom,
+ boolean left) {
+ super(top, right, bottom, left);
+ }
+ }
+}
diff --git a/server/src/com/vaadin/ui/Link.java b/server/src/com/vaadin/ui/Link.java
new file mode 100644
index 0000000000..fd105f3255
--- /dev/null
+++ b/server/src/com/vaadin/ui/Link.java
@@ -0,0 +1,242 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Map;
+
+import com.vaadin.terminal.Page;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Vaadin6Component;
+
+/**
+ * Link is used to create external or internal URL links.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Link extends AbstractComponent implements Vaadin6Component {
+
+ /* Target window border type constant: No window border */
+ public static final int TARGET_BORDER_NONE = Page.BORDER_NONE;
+
+ /* Target window border type constant: Minimal window border */
+ public static final int TARGET_BORDER_MINIMAL = Page.BORDER_MINIMAL;
+
+ /* Target window border type constant: Default window border */
+ public static final int TARGET_BORDER_DEFAULT = Page.BORDER_DEFAULT;
+
+ private Resource resource = null;
+
+ private String targetName;
+
+ private int targetBorder = TARGET_BORDER_DEFAULT;
+
+ private int targetWidth = -1;
+
+ private int targetHeight = -1;
+
+ /**
+ * Creates a new link.
+ */
+ public Link() {
+
+ }
+
+ /**
+ * Creates a new instance of Link.
+ *
+ * @param caption
+ * @param resource
+ */
+ public Link(String caption, Resource resource) {
+ setCaption(caption);
+ this.resource = resource;
+ }
+
+ /**
+ * Creates a new instance of Link that opens a new window.
+ *
+ *
+ * @param caption
+ * the Link text.
+ * @param targetName
+ * the name of the target window where the link opens to. Empty
+ * name of null implies that the target is opened to the window
+ * containing the link.
+ * @param width
+ * the Width of the target window.
+ * @param height
+ * the Height of the target window.
+ * @param border
+ * the Border style of the target window.
+ *
+ */
+ public Link(String caption, Resource resource, String targetName,
+ int width, int height, int border) {
+ setCaption(caption);
+ this.resource = resource;
+ setTargetName(targetName);
+ setTargetWidth(width);
+ setTargetHeight(height);
+ setTargetBorder(border);
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * the Paint Event.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (resource != null) {
+ target.addAttribute("src", resource);
+ } else {
+ return;
+ }
+
+ // Target window name
+ final String name = getTargetName();
+ if (name != null && name.length() > 0) {
+ target.addAttribute("name", name);
+ }
+
+ // Target window size
+ if (getTargetWidth() >= 0) {
+ target.addAttribute("targetWidth", getTargetWidth());
+ }
+ if (getTargetHeight() >= 0) {
+ target.addAttribute("targetHeight", getTargetHeight());
+ }
+
+ // Target window border
+ switch (getTargetBorder()) {
+ case TARGET_BORDER_MINIMAL:
+ target.addAttribute("border", "minimal");
+ break;
+ case TARGET_BORDER_NONE:
+ target.addAttribute("border", "none");
+ break;
+ }
+ }
+
+ /**
+ * Returns the target window border.
+ *
+ * @return the target window border.
+ */
+ public int getTargetBorder() {
+ return targetBorder;
+ }
+
+ /**
+ * Returns the target window height or -1 if not set.
+ *
+ * @return the target window height.
+ */
+ public int getTargetHeight() {
+ return targetHeight < 0 ? -1 : targetHeight;
+ }
+
+ /**
+ * Returns the target window name. Empty name of null implies that the
+ * target is opened to the window containing the link.
+ *
+ * @return the target window name.
+ */
+ public String getTargetName() {
+ return targetName;
+ }
+
+ /**
+ * Returns the target window width or -1 if not set.
+ *
+ * @return the target window width.
+ */
+ public int getTargetWidth() {
+ return targetWidth < 0 ? -1 : targetWidth;
+ }
+
+ /**
+ * Sets the border of the target window.
+ *
+ * @param targetBorder
+ * the targetBorder to set.
+ */
+ public void setTargetBorder(int targetBorder) {
+ if (targetBorder == TARGET_BORDER_DEFAULT
+ || targetBorder == TARGET_BORDER_MINIMAL
+ || targetBorder == TARGET_BORDER_NONE) {
+ this.targetBorder = targetBorder;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets the target window height.
+ *
+ * @param targetHeight
+ * the targetHeight to set.
+ */
+ public void setTargetHeight(int targetHeight) {
+ this.targetHeight = targetHeight;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the target window name.
+ *
+ * @param targetName
+ * the targetName to set.
+ */
+ public void setTargetName(String targetName) {
+ this.targetName = targetName;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the target window width.
+ *
+ * @param targetWidth
+ * the targetWidth to set.
+ */
+ public void setTargetWidth(int targetWidth) {
+ this.targetWidth = targetWidth;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the resource this link opens.
+ *
+ * @return the Resource.
+ */
+ public Resource getResource() {
+ return resource;
+ }
+
+ /**
+ * Sets the resource this link opens.
+ *
+ * @param resource
+ * the resource to set.
+ */
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ requestRepaint();
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // TODO Remove once Vaadin6Component is no longer implemented
+ }
+}
diff --git a/server/src/com/vaadin/ui/ListSelect.java b/server/src/com/vaadin/ui/ListSelect.java
new file mode 100644
index 0000000000..35ccb34b3c
--- /dev/null
+++ b/server/src/com/vaadin/ui/ListSelect.java
@@ -0,0 +1,96 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * This is a simple list select without, for instance, support for new items,
+ * lazyloading, and other advanced features.
+ */
+@SuppressWarnings("serial")
+public class ListSelect extends AbstractSelect {
+
+ private int columns = 0;
+ private int rows = 0;
+
+ public ListSelect() {
+ super();
+ }
+
+ public ListSelect(String caption, Collection<?> options) {
+ super(caption, options);
+ }
+
+ public ListSelect(String caption, Container dataSource) {
+ super(caption, dataSource);
+ }
+
+ public ListSelect(String caption) {
+ super(caption);
+ }
+
+ /**
+ * Sets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ *
+ * @param columns
+ * the number of columns to set.
+ */
+ public void setColumns(int columns) {
+ if (columns < 0) {
+ columns = 0;
+ }
+ if (this.columns != columns) {
+ this.columns = columns;
+ requestRepaint();
+ }
+ }
+
+ public int getColumns() {
+ return columns;
+ }
+
+ public int getRows() {
+ return rows;
+ }
+
+ /**
+ * Sets the number of rows in the editor. If the number of rows is set 0,
+ * the actual number of displayed rows is determined implicitly by the
+ * adapter.
+ *
+ * @param rows
+ * the number of rows to set.
+ */
+ public void setRows(int rows) {
+ if (rows < 0) {
+ rows = 0;
+ }
+ if (this.rows != rows) {
+ this.rows = rows;
+ requestRepaint();
+ }
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute("type", "list");
+ // Adds the number of columns
+ if (columns != 0) {
+ target.addAttribute("cols", columns);
+ }
+ // Adds the number of rows
+ if (rows != 0) {
+ target.addAttribute("rows", rows);
+ }
+ super.paintContent(target);
+ }
+}
diff --git a/server/src/com/vaadin/ui/LoginForm.java b/server/src/com/vaadin/ui/LoginForm.java
new file mode 100644
index 0000000000..db7e5f9dd9
--- /dev/null
+++ b/server/src/com/vaadin/ui/LoginForm.java
@@ -0,0 +1,353 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vaadin.Application;
+import com.vaadin.terminal.ApplicationResource;
+import com.vaadin.terminal.DownloadStream;
+import com.vaadin.terminal.RequestHandler;
+import com.vaadin.terminal.WrappedRequest;
+import com.vaadin.terminal.WrappedResponse;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+
+/**
+ * LoginForm is a Vaadin component to handle common problem among Ajax
+ * applications: browsers password managers don't fill dynamically created forms
+ * like all those UI elements created by Vaadin.
+ * <p>
+ * For developer it is easy to use: add component to a desired place in you UI
+ * and add LoginListener to validate form input. Behind the curtain LoginForm
+ * creates an iframe with static html that browsers detect.
+ * <p>
+ * Login form is by default 100% width and height, so consider using it inside a
+ * sized {@link Panel} or {@link Window}.
+ * <p>
+ * Login page html can be overridden by replacing protected getLoginHTML method.
+ * As the login page is actually an iframe, styles must be handled manually. By
+ * default component tries to guess the right place for theme css.
+ *
+ * @since 5.3
+ */
+public class LoginForm extends CustomComponent {
+
+ private String usernameCaption = "Username";
+ private String passwordCaption = "Password";
+ private String loginButtonCaption = "Login";
+
+ private Embedded iframe = new Embedded();
+
+ private ApplicationResource loginPage = new ApplicationResource() {
+
+ @Override
+ public Application getApplication() {
+ return LoginForm.this.getApplication();
+ }
+
+ @Override
+ public int getBufferSize() {
+ return getLoginHTML().length;
+ }
+
+ @Override
+ public long getCacheTime() {
+ return -1;
+ }
+
+ @Override
+ public String getFilename() {
+ return "login";
+ }
+
+ @Override
+ public DownloadStream getStream() {
+ return new DownloadStream(new ByteArrayInputStream(getLoginHTML()),
+ getMIMEType(), getFilename());
+ }
+
+ @Override
+ public String getMIMEType() {
+ return "text/html; charset=utf-8";
+ }
+ };
+
+ private final RequestHandler requestHandler = new RequestHandler() {
+ @Override
+ public boolean handleRequest(Application application,
+ WrappedRequest request, WrappedResponse response)
+ throws IOException {
+ String requestPathInfo = request.getRequestPathInfo();
+ if ("/loginHandler".equals(requestPathInfo)) {
+ response.setCacheTime(-1);
+ response.setContentType("text/html; charset=utf-8");
+ response.getWriter()
+ .write("<html><body>Login form handled."
+ + "<script type='text/javascript'>parent.parent.vaadin.forceSync();"
+ + "</script></body></html>");
+
+ Map<String, String[]> parameters = request.getParameterMap();
+
+ HashMap<String, String> params = new HashMap<String, String>();
+ // expecting single params
+ for (Iterator<String> it = parameters.keySet().iterator(); it
+ .hasNext();) {
+ String key = it.next();
+ String value = (parameters.get(key))[0];
+ params.put(key, value);
+ }
+ LoginEvent event = new LoginEvent(params);
+ fireEvent(event);
+ return true;
+ }
+ return false;
+ }
+ };
+
+ public LoginForm() {
+ iframe.setType(Embedded.TYPE_BROWSER);
+ iframe.setSizeFull();
+ setSizeFull();
+ setCompositionRoot(iframe);
+ addStyleName("v-loginform");
+ }
+
+ /**
+ * Returns byte array containing login page html. If you need to override
+ * the login html, use the default html as basis. Login page sets its target
+ * with javascript.
+ *
+ * @return byte array containing login page html
+ */
+ protected byte[] getLoginHTML() {
+ String appUri = getApplication().getURL().toString();
+
+ try {
+ return ("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ + "XHTML 1.0 Transitional//EN\" "
+ + "\"http://www.w3.org/TR/xhtml1/"
+ + "DTD/xhtml1-transitional.dtd\">\n" + "<html>"
+ + "<head><script type='text/javascript'>"
+ + "var setTarget = function() {" + "var uri = '"
+ + appUri
+ + "loginHandler"
+ + "'; var f = document.getElementById('loginf');"
+ + "document.forms[0].action = uri;document.forms[0].username.focus();};"
+ + ""
+ + "var styles = window.parent.document.styleSheets;"
+ + "for(var j = 0; j < styles.length; j++) {\n"
+ + "if(styles[j].href) {"
+ + "var stylesheet = document.createElement('link');\n"
+ + "stylesheet.setAttribute('rel', 'stylesheet');\n"
+ + "stylesheet.setAttribute('type', 'text/css');\n"
+ + "stylesheet.setAttribute('href', styles[j].href);\n"
+ + "document.getElementsByTagName('head')[0].appendChild(stylesheet);\n"
+ + "}"
+ + "}\n"
+ + "function submitOnEnter(e) { var keycode = e.keyCode || e.which;"
+ + " if (keycode == 13) {document.forms[0].submit();} } \n"
+ + "</script>"
+ + "</head><body onload='setTarget();' style='margin:0;padding:0; background:transparent;' class=\""
+ + ApplicationConnection.GENERATED_BODY_CLASSNAME
+ + "\">"
+ + "<div class='v-app v-app-loginpage' style=\"background:transparent;\">"
+ + "<iframe name='logintarget' style='width:0;height:0;"
+ + "border:0;margin:0;padding:0;'></iframe>"
+ + "<form id='loginf' target='logintarget' onkeypress=\"submitOnEnter(event)\" method=\"post\">"
+ + "<div>"
+ + usernameCaption
+ + "</div><div >"
+ + "<input class='v-textfield v-connector' style='display:block;' type='text' name='username'></div>"
+ + "<div>"
+ + passwordCaption
+ + "</div>"
+ + "<div><input class='v-textfield v-connector' style='display:block;' type='password' name='password'></div>"
+ + "<div><div onclick=\"document.forms[0].submit();\" tabindex=\"0\" class=\"v-button\" role=\"button\" ><span class=\"v-button-wrap\"><span class=\"v-button-caption\">"
+ + loginButtonCaption
+ + "</span></span></div></div></form></div>" + "</body></html>")
+ .getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not avalable", e);
+ }
+ }
+
+ @Override
+ public void attach() {
+ super.attach();
+ getApplication().addResource(loginPage);
+ getApplication().addRequestHandler(requestHandler);
+ iframe.setSource(loginPage);
+ }
+
+ @Override
+ public void detach() {
+ getApplication().removeResource(loginPage);
+ getApplication().removeRequestHandler(requestHandler);
+
+ super.detach();
+ }
+
+ /**
+ * This event is sent when login form is submitted.
+ */
+ public class LoginEvent extends Event {
+
+ private Map<String, String> params;
+
+ private LoginEvent(Map<String, String> params) {
+ super(LoginForm.this);
+ this.params = params;
+ }
+
+ /**
+ * Access method to form values by field names.
+ *
+ * @param name
+ * @return value in given field
+ */
+ public String getLoginParameter(String name) {
+ if (params.containsKey(name)) {
+ return params.get(name);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Login listener is a class capable to listen LoginEvents sent from
+ * LoginBox
+ */
+ public interface LoginListener extends Serializable {
+ /**
+ * This method is fired on each login form post.
+ *
+ * @param event
+ */
+ public void onLogin(LoginForm.LoginEvent event);
+ }
+
+ private static final Method ON_LOGIN_METHOD;
+
+ private static final String UNDEFINED_HEIGHT = "140px";
+ private static final String UNDEFINED_WIDTH = "200px";
+
+ static {
+ try {
+ ON_LOGIN_METHOD = LoginListener.class.getDeclaredMethod("onLogin",
+ new Class[] { LoginEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in LoginForm");
+ }
+ }
+
+ /**
+ * Adds LoginListener to handle login logic
+ *
+ * @param listener
+ */
+ public void addListener(LoginListener listener) {
+ addListener(LoginEvent.class, listener, ON_LOGIN_METHOD);
+ }
+
+ /**
+ * Removes LoginListener
+ *
+ * @param listener
+ */
+ public void removeListener(LoginListener listener) {
+ removeListener(LoginEvent.class, listener, ON_LOGIN_METHOD);
+ }
+
+ @Override
+ public void setWidth(float width, Unit unit) {
+ super.setWidth(width, unit);
+ if (iframe != null) {
+ if (width < 0) {
+ iframe.setWidth(UNDEFINED_WIDTH);
+ } else {
+ iframe.setWidth("100%");
+ }
+ }
+ }
+
+ @Override
+ public void setHeight(float height, Unit unit) {
+ super.setHeight(height, unit);
+ if (iframe != null) {
+ if (height < 0) {
+ iframe.setHeight(UNDEFINED_HEIGHT);
+ } else {
+ iframe.setHeight("100%");
+ }
+ }
+ }
+
+ /**
+ * Returns the caption for the user name field.
+ *
+ * @return String
+ */
+ public String getUsernameCaption() {
+ return usernameCaption;
+ }
+
+ /**
+ * Sets the caption to show for the user name field. The caption cannot be
+ * changed after the form has been shown to the user.
+ *
+ * @param usernameCaption
+ */
+ public void setUsernameCaption(String usernameCaption) {
+ this.usernameCaption = usernameCaption;
+ }
+
+ /**
+ * Returns the caption for the password field.
+ *
+ * @return String
+ */
+ public String getPasswordCaption() {
+ return passwordCaption;
+ }
+
+ /**
+ * Sets the caption to show for the password field. The caption cannot be
+ * changed after the form has been shown to the user.
+ *
+ * @param passwordCaption
+ */
+ public void setPasswordCaption(String passwordCaption) {
+ this.passwordCaption = passwordCaption;
+ }
+
+ /**
+ * Returns the caption for the login button.
+ *
+ * @return String
+ */
+ public String getLoginButtonCaption() {
+ return loginButtonCaption;
+ }
+
+ /**
+ * Sets the caption (button text) to show for the login button. The caption
+ * cannot be changed after the form has been shown to the user.
+ *
+ * @param loginButtonCaption
+ */
+ public void setLoginButtonCaption(String loginButtonCaption) {
+ this.loginButtonCaption = loginButtonCaption;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/MenuBar.java b/server/src/com/vaadin/ui/MenuBar.java
new file mode 100644
index 0000000000..5b5dc13e20
--- /dev/null
+++ b/server/src/com/vaadin/ui/MenuBar.java
@@ -0,0 +1,890 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.menubar.VMenuBar;
+
+/**
+ * <p>
+ * A class representing a horizontal menu bar. The menu can contain MenuItem
+ * objects, which in turn can contain more MenuBars. These sub-level MenuBars
+ * are represented as vertical menu.
+ * </p>
+ */
+@SuppressWarnings("serial")
+public class MenuBar extends AbstractComponent implements Vaadin6Component {
+
+ // Items of the top-level menu
+ private final List<MenuItem> menuItems;
+
+ // Number of items in this menu
+ private int numberOfItems = 0;
+
+ private MenuItem moreItem;
+
+ private boolean openRootOnHover;
+
+ private boolean htmlContentAllowed;
+
+ /** Paint (serialise) the component for the client. */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute(VMenuBar.OPEN_ROOT_MENU_ON_HOWER, openRootOnHover);
+
+ if (isHtmlContentAllowed()) {
+ target.addAttribute(VMenuBar.HTML_CONTENT_ALLOWED, true);
+ }
+
+ target.startTag("options");
+
+ if (getWidth() > -1) {
+ target.startTag("moreItem");
+ target.addAttribute("text", moreItem.getText());
+ if (moreItem.getIcon() != null) {
+ target.addAttribute("icon", moreItem.getIcon());
+ }
+ target.endTag("moreItem");
+ }
+
+ target.endTag("options");
+ target.startTag("items");
+
+ // This generates the tree from the contents of the menu
+ for (MenuItem item : menuItems) {
+ paintItem(target, item);
+ }
+
+ target.endTag("items");
+ }
+
+ private void paintItem(PaintTarget target, MenuItem item)
+ throws PaintException {
+ if (!item.isVisible()) {
+ return;
+ }
+
+ target.startTag("item");
+
+ target.addAttribute("id", item.getId());
+
+ if (item.getStyleName() != null) {
+ target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_STYLE,
+ item.getStyleName());
+ }
+
+ if (item.isSeparator()) {
+ target.addAttribute("separator", true);
+ } else {
+ target.addAttribute("text", item.getText());
+
+ Command command = item.getCommand();
+ if (command != null) {
+ target.addAttribute("command", true);
+ }
+
+ Resource icon = item.getIcon();
+ if (icon != null) {
+ target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_ICON, icon);
+ }
+
+ if (!item.isEnabled()) {
+ target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_DISABLED, true);
+ }
+
+ String description = item.getDescription();
+ if (description != null && description.length() > 0) {
+ target.addAttribute(VMenuBar.ATTRIBUTE_ITEM_DESCRIPTION,
+ description);
+ }
+ if (item.isCheckable()) {
+ // if the "checked" attribute is present (either true or false),
+ // the item is checkable
+ target.addAttribute(VMenuBar.ATTRIBUTE_CHECKED,
+ item.isChecked());
+ }
+ if (item.hasChildren()) {
+ for (MenuItem child : item.getChildren()) {
+ paintItem(target, child);
+ }
+ }
+
+ }
+
+ target.endTag("item");
+ }
+
+ /** Deserialize changes received from client. */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ Stack<MenuItem> items = new Stack<MenuItem>();
+ boolean found = false;
+
+ if (variables.containsKey("clickedId")) {
+
+ Integer clickedId = (Integer) variables.get("clickedId");
+ Iterator<MenuItem> itr = getItems().iterator();
+ while (itr.hasNext()) {
+ items.push(itr.next());
+ }
+
+ MenuItem tmpItem = null;
+
+ // Go through all the items in the menu
+ while (!found && !items.empty()) {
+ tmpItem = items.pop();
+ found = (clickedId.intValue() == tmpItem.getId());
+
+ if (tmpItem.hasChildren()) {
+ itr = tmpItem.getChildren().iterator();
+ while (itr.hasNext()) {
+ items.push(itr.next());
+ }
+ }
+
+ }// while
+
+ // If we got the clicked item, launch the command.
+ if (found && tmpItem.isEnabled()) {
+ if (tmpItem.isCheckable()) {
+ tmpItem.setChecked(!tmpItem.isChecked());
+ }
+ if (null != tmpItem.getCommand()) {
+ tmpItem.getCommand().menuSelected(tmpItem);
+ }
+ }
+ }// if
+ }// changeVariables
+
+ /**
+ * Constructs an empty, horizontal menu
+ */
+ public MenuBar() {
+ menuItems = new ArrayList<MenuItem>();
+ setMoreMenuItem(null);
+ }
+
+ /**
+ * Add a new item to the menu bar. Command can be null, but a caption must
+ * be given.
+ *
+ * @param caption
+ * the text for the menu item
+ * @param command
+ * the command for the menu item
+ * @throws IllegalArgumentException
+ */
+ public MenuBar.MenuItem addItem(String caption, MenuBar.Command command) {
+ return addItem(caption, null, command);
+ }
+
+ /**
+ * Add a new item to the menu bar. Icon and command can be null, but a
+ * caption must be given.
+ *
+ * @param caption
+ * the text for the menu item
+ * @param icon
+ * the icon for the menu item
+ * @param command
+ * the command for the menu item
+ * @throws IllegalArgumentException
+ */
+ public MenuBar.MenuItem addItem(String caption, Resource icon,
+ MenuBar.Command command) {
+ if (caption == null) {
+ throw new IllegalArgumentException("caption cannot be null");
+ }
+ MenuItem newItem = new MenuItem(caption, icon, command);
+ menuItems.add(newItem);
+ requestRepaint();
+
+ return newItem;
+
+ }
+
+ /**
+ * Add an item before some item. If the given item does not exist the item
+ * is added at the end of the menu. Icon and command can be null, but a
+ * caption must be given.
+ *
+ * @param caption
+ * the text for the menu item
+ * @param icon
+ * the icon for the menu item
+ * @param command
+ * the command for the menu item
+ * @param itemToAddBefore
+ * the item that will be after the new item
+ * @throws IllegalArgumentException
+ */
+ public MenuBar.MenuItem addItemBefore(String caption, Resource icon,
+ MenuBar.Command command, MenuBar.MenuItem itemToAddBefore) {
+ if (caption == null) {
+ throw new IllegalArgumentException("caption cannot be null");
+ }
+
+ MenuItem newItem = new MenuItem(caption, icon, command);
+ if (menuItems.contains(itemToAddBefore)) {
+ int index = menuItems.indexOf(itemToAddBefore);
+ menuItems.add(index, newItem);
+
+ } else {
+ menuItems.add(newItem);
+ }
+
+ requestRepaint();
+
+ return newItem;
+ }
+
+ /**
+ * Returns a list with all the MenuItem objects in the menu bar
+ *
+ * @return a list containing the MenuItem objects in the menu bar
+ */
+ public List<MenuItem> getItems() {
+ return menuItems;
+ }
+
+ /**
+ * Remove first occurrence the specified item from the main menu
+ *
+ * @param item
+ * The item to be removed
+ */
+ public void removeItem(MenuBar.MenuItem item) {
+ if (item != null) {
+ menuItems.remove(item);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Empty the menu bar
+ */
+ public void removeItems() {
+ menuItems.clear();
+ requestRepaint();
+ }
+
+ /**
+ * Returns the size of the menu.
+ *
+ * @return The size of the menu
+ */
+ public int getSize() {
+ return menuItems.size();
+ }
+
+ /**
+ * Set the item that is used when collapsing the top level menu. All
+ * "overflowing" items will be added below this. The item command will be
+ * ignored. If set to null, the default item with a downwards arrow is used.
+ *
+ * The item command (if specified) is ignored.
+ *
+ * @param item
+ */
+ public void setMoreMenuItem(MenuItem item) {
+ if (item != null) {
+ moreItem = item;
+ } else {
+ moreItem = new MenuItem("", null, null);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Get the MenuItem used as the collapse menu item.
+ *
+ * @return
+ */
+ public MenuItem getMoreMenuItem() {
+ return moreItem;
+ }
+
+ /**
+ * Using this method menubar can be put into a special mode where top level
+ * menus opens without clicking on the menu, but automatically when mouse
+ * cursor is moved over the menu. In this mode the menu also closes itself
+ * if the mouse is moved out of the opened menu.
+ * <p>
+ * Note, that on touch devices the menu still opens on a click event.
+ *
+ * @param autoOpenTopLevelMenu
+ * true if menus should be opened without click, the default is
+ * false
+ */
+ public void setAutoOpen(boolean autoOpenTopLevelMenu) {
+ if (autoOpenTopLevelMenu != openRootOnHover) {
+ openRootOnHover = autoOpenTopLevelMenu;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Detects whether the menubar is in a mode where top level menus are
+ * automatically opened when the mouse cursor is moved over the menu.
+ * Normally root menu opens only by clicking on the menu. Submenus always
+ * open automatically.
+ *
+ * @return true if the root menus open without click, the default is false
+ */
+ public boolean isAutoOpen() {
+ return openRootOnHover;
+ }
+
+ /**
+ * Sets whether html is allowed in the item captions. If set to true, the
+ * captions are passed to the browser as html and the developer is
+ * responsible for ensuring no harmful html is used. If set to false, the
+ * content is passed to the browser as plain text.
+ *
+ * @param htmlContentAllowed
+ * true if the captions are used as html, false if used as plain
+ * text
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ requestRepaint();
+ }
+
+ /**
+ * Checks whether item captions are interpreted as html or plain text.
+ *
+ * @return true if the captions are used as html, false if used as plain
+ * text
+ * @see #setHtmlContentAllowed(boolean)
+ */
+ public boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+
+ /**
+ * This interface contains the layer for menu commands of the
+ * {@link com.vaadin.ui.MenuBar} class. It's method will fire when the user
+ * clicks on the containing {@link com.vaadin.ui.MenuBar.MenuItem}. The
+ * selected item is given as an argument.
+ */
+ public interface Command extends Serializable {
+ public void menuSelected(MenuBar.MenuItem selectedItem);
+ }
+
+ /**
+ * A composite class for menu items and sub-menus. You can set commands to
+ * be fired on user click by implementing the
+ * {@link com.vaadin.ui.MenuBar.Command} interface. You can also add
+ * multiple MenuItems to a MenuItem and create a sub-menu.
+ *
+ */
+ public class MenuItem implements Serializable {
+
+ /** Private members * */
+ private final int itsId;
+ private Command itsCommand;
+ private String itsText;
+ private List<MenuItem> itsChildren;
+ private Resource itsIcon;
+ private MenuItem itsParent;
+ private boolean enabled = true;
+ private boolean visible = true;
+ private boolean isSeparator = false;
+ private String styleName;
+ private String description;
+ private boolean checkable = false;
+ private boolean checked = false;
+
+ /**
+ * Constructs a new menu item that can optionally have an icon and a
+ * command associated with it. Icon and command can be null, but a
+ * caption must be given.
+ *
+ * @param text
+ * The text associated with the command
+ * @param command
+ * The command to be fired
+ * @throws IllegalArgumentException
+ */
+ public MenuItem(String caption, Resource icon, MenuBar.Command command) {
+ if (caption == null) {
+ throw new IllegalArgumentException("caption cannot be null");
+ }
+ itsId = ++numberOfItems;
+ itsText = caption;
+ itsIcon = icon;
+ itsCommand = command;
+ }
+
+ /**
+ * Checks if the item has children (if it is a sub-menu).
+ *
+ * @return True if this item has children
+ */
+ public boolean hasChildren() {
+ return !isSeparator() && itsChildren != null;
+ }
+
+ /**
+ * Adds a separator to this menu. A separator is a way to visually group
+ * items in a menu, to make it easier for users to find what they are
+ * looking for in the menu.
+ *
+ * @author Jouni Koivuviita / Vaadin Ltd.
+ * @since 6.2.0
+ */
+ public MenuBar.MenuItem addSeparator() {
+ MenuItem item = addItem("", null, null);
+ item.setSeparator(true);
+ return item;
+ }
+
+ public MenuBar.MenuItem addSeparatorBefore(MenuItem itemToAddBefore) {
+ MenuItem item = addItemBefore("", null, null, itemToAddBefore);
+ item.setSeparator(true);
+ return item;
+ }
+
+ /**
+ * Add a new item inside this item, thus creating a sub-menu. Command
+ * can be null, but a caption must be given.
+ *
+ * @param caption
+ * the text for the menu item
+ * @param command
+ * the command for the menu item
+ */
+ public MenuBar.MenuItem addItem(String caption, MenuBar.Command command) {
+ return addItem(caption, null, command);
+ }
+
+ /**
+ * Add a new item inside this item, thus creating a sub-menu. Icon and
+ * command can be null, but a caption must be given.
+ *
+ * @param caption
+ * the text for the menu item
+ * @param icon
+ * the icon for the menu item
+ * @param command
+ * the command for the menu item
+ * @throws IllegalStateException
+ * If the item is checkable and thus cannot have children.
+ */
+ public MenuBar.MenuItem addItem(String caption, Resource icon,
+ MenuBar.Command command) throws IllegalStateException {
+ if (isSeparator()) {
+ throw new UnsupportedOperationException(
+ "Cannot add items to a separator");
+ }
+ if (isCheckable()) {
+ throw new IllegalStateException(
+ "A checkable item cannot have children");
+ }
+ if (caption == null) {
+ throw new IllegalArgumentException("Caption cannot be null");
+ }
+
+ if (itsChildren == null) {
+ itsChildren = new ArrayList<MenuItem>();
+ }
+
+ MenuItem newItem = new MenuItem(caption, icon, command);
+
+ // The only place where the parent is set
+ newItem.setParent(this);
+ itsChildren.add(newItem);
+
+ requestRepaint();
+
+ return newItem;
+ }
+
+ /**
+ * Add an item before some item. If the given item does not exist the
+ * item is added at the end of the menu. Icon and command can be null,
+ * but a caption must be given.
+ *
+ * @param caption
+ * the text for the menu item
+ * @param icon
+ * the icon for the menu item
+ * @param command
+ * the command for the menu item
+ * @param itemToAddBefore
+ * the item that will be after the new item
+ * @throws IllegalStateException
+ * If the item is checkable and thus cannot have children.
+ */
+ public MenuBar.MenuItem addItemBefore(String caption, Resource icon,
+ MenuBar.Command command, MenuBar.MenuItem itemToAddBefore)
+ throws IllegalStateException {
+ if (isCheckable()) {
+ throw new IllegalStateException(
+ "A checkable item cannot have children");
+ }
+ MenuItem newItem = null;
+
+ if (hasChildren() && itsChildren.contains(itemToAddBefore)) {
+ int index = itsChildren.indexOf(itemToAddBefore);
+ newItem = new MenuItem(caption, icon, command);
+ newItem.setParent(this);
+ itsChildren.add(index, newItem);
+ } else {
+ newItem = addItem(caption, icon, command);
+ }
+
+ requestRepaint();
+
+ return newItem;
+ }
+
+ /**
+ * For the associated command.
+ *
+ * @return The associated command, or null if there is none
+ */
+ public Command getCommand() {
+ return itsCommand;
+ }
+
+ /**
+ * Gets the objects icon.
+ *
+ * @return The icon of the item, null if the item doesn't have an icon
+ */
+ public Resource getIcon() {
+ return itsIcon;
+ }
+
+ /**
+ * For the containing item. This will return null if the item is in the
+ * top-level menu bar.
+ *
+ * @return The containing {@link com.vaadin.ui.MenuBar.MenuItem} , or
+ * null if there is none
+ */
+ public MenuBar.MenuItem getParent() {
+ return itsParent;
+ }
+
+ /**
+ * This will return the children of this item or null if there are none.
+ *
+ * @return List of children items, or null if there are none
+ */
+ public List<MenuItem> getChildren() {
+ return itsChildren;
+ }
+
+ /**
+ * Gets the objects text
+ *
+ * @return The text
+ */
+ public java.lang.String getText() {
+ return itsText;
+ }
+
+ /**
+ * Returns the number of children.
+ *
+ * @return The number of child items
+ */
+ public int getSize() {
+ if (itsChildren != null) {
+ return itsChildren.size();
+ }
+ return -1;
+ }
+
+ /**
+ * Get the unique identifier for this item.
+ *
+ * @return The id of this item
+ */
+ public int getId() {
+ return itsId;
+ }
+
+ /**
+ * Set the command for this item. Set null to remove.
+ *
+ * @param command
+ * The MenuCommand of this item
+ */
+ public void setCommand(MenuBar.Command command) {
+ itsCommand = command;
+ }
+
+ /**
+ * Sets the icon. Set null to remove.
+ *
+ * @param icon
+ * The icon for this item
+ */
+ public void setIcon(Resource icon) {
+ itsIcon = icon;
+ requestRepaint();
+ }
+
+ /**
+ * Set the text of this object.
+ *
+ * @param text
+ * Text for this object
+ */
+ public void setText(java.lang.String text) {
+ if (text != null) {
+ itsText = text;
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Remove the first occurrence of the item.
+ *
+ * @param item
+ * The item to be removed
+ */
+ public void removeChild(MenuBar.MenuItem item) {
+ if (item != null && itsChildren != null) {
+ itsChildren.remove(item);
+ if (itsChildren.isEmpty()) {
+ itsChildren = null;
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Empty the list of children items.
+ */
+ public void removeChildren() {
+ if (itsChildren != null) {
+ itsChildren.clear();
+ itsChildren = null;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Set the parent of this item. This is called by the addItem method.
+ *
+ * @param parent
+ * The parent item
+ */
+ protected void setParent(MenuBar.MenuItem parent) {
+ itsParent = parent;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ requestRepaint();
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ requestRepaint();
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ private void setSeparator(boolean isSeparator) {
+ this.isSeparator = isSeparator;
+ requestRepaint();
+ }
+
+ public boolean isSeparator() {
+ return isSeparator;
+ }
+
+ public void setStyleName(String styleName) {
+ this.styleName = styleName;
+ requestRepaint();
+ }
+
+ public String getStyleName() {
+ return styleName;
+ }
+
+ /**
+ * Sets the items's description. See {@link #getDescription()} for more
+ * information on what the description is. This method will trigger a
+ * {@link RepaintRequestEvent}.
+ *
+ * @param description
+ * the new description string for the component.
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ requestRepaint();
+ }
+
+ /**
+ * <p>
+ * Gets the items's description. The description can be used to briefly
+ * describe the state of the item to the user. The description string
+ * may contain certain XML tags:
+ * </p>
+ *
+ * <p>
+ * <table border=1>
+ * <tr>
+ * <td width=120><b>Tag</b></td>
+ * <td width=120><b>Description</b></td>
+ * <td width=120><b>Example</b></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;b></td>
+ * <td>bold</td>
+ * <td><b>bold text</b></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;i></td>
+ * <td>italic</td>
+ * <td><i>italic text</i></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;u></td>
+ * <td>underlined</td>
+ * <td><u>underlined text</u></td>
+ * </tr>
+ * <tr>
+ * <td>&lt;br></td>
+ * <td>linebreak</td>
+ * <td>N/A</td>
+ * </tr>
+ * <tr>
+ * <td>&lt;ul><br>
+ * &lt;li>item1<br>
+ * &lt;li>item1<br>
+ * &lt;/ul></td>
+ * <td>item list</td>
+ * <td>
+ * <ul>
+ * <li>item1
+ * <li>item2
+ * </ul>
+ * </td>
+ * </tr>
+ * </table>
+ * </p>
+ *
+ * <p>
+ * These tags may be nested.
+ * </p>
+ *
+ * @return item's description <code>String</code>
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Gets the checkable state of the item - whether the item has checked
+ * and unchecked states. If an item is checkable its checked state (as
+ * returned by {@link #isChecked()}) is indicated in the UI.
+ *
+ * <p>
+ * An item is not checkable by default.
+ * </p>
+ *
+ * @return true if the item is checkable, false otherwise
+ * @since 6.6.2
+ */
+ public boolean isCheckable() {
+ return checkable;
+ }
+
+ /**
+ * Sets the checkable state of the item. If an item is checkable its
+ * checked state (as returned by {@link #isChecked()}) is indicated in
+ * the UI.
+ *
+ * <p>
+ * An item is not checkable by default.
+ * </p>
+ *
+ * <p>
+ * Items with sub items cannot be checkable.
+ * </p>
+ *
+ * @param checkable
+ * true if the item should be checkable, false otherwise
+ * @throws IllegalStateException
+ * If the item has children
+ * @since 6.6.2
+ */
+ public void setCheckable(boolean checkable)
+ throws IllegalStateException {
+ if (hasChildren()) {
+ throw new IllegalStateException(
+ "A menu item with children cannot be checkable");
+ }
+ this.checkable = checkable;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the checked state of the item (checked or unchecked). Only used
+ * if the item is checkable (as indicated by {@link #isCheckable()}).
+ * The checked state is indicated in the UI with the item, if the item
+ * is checkable.
+ *
+ * <p>
+ * An item is not checked by default.
+ * </p>
+ *
+ * <p>
+ * The CSS style corresponding to the checked state is "-checked".
+ * </p>
+ *
+ * @return true if the item is checked, false otherwise
+ * @since 6.6.2
+ */
+ public boolean isChecked() {
+ return checked;
+ }
+
+ /**
+ * Sets the checked state of the item. Only used if the item is
+ * checkable (indicated by {@link #isCheckable()}). The checked state is
+ * indicated in the UI with the item, if the item is checkable.
+ *
+ * <p>
+ * An item is not checked by default.
+ * </p>
+ *
+ * <p>
+ * The CSS style corresponding to the checked state is "-checked".
+ * </p>
+ *
+ * @return true if the item is checked, false otherwise
+ * @since 6.6.2
+ */
+ public void setChecked(boolean checked) {
+ this.checked = checked;
+ requestRepaint();
+ }
+
+ }// class MenuItem
+
+}// class MenuBar
diff --git a/server/src/com/vaadin/ui/NativeButton.java b/server/src/com/vaadin/ui/NativeButton.java
new file mode 100644
index 0000000000..6eb4379261
--- /dev/null
+++ b/server/src/com/vaadin/ui/NativeButton.java
@@ -0,0 +1,21 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+@SuppressWarnings("serial")
+public class NativeButton extends Button {
+
+ public NativeButton() {
+ super();
+ }
+
+ public NativeButton(String caption) {
+ super(caption);
+ }
+
+ public NativeButton(String caption, ClickListener listener) {
+ super(caption, listener);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/NativeSelect.java b/server/src/com/vaadin/ui/NativeSelect.java
new file mode 100644
index 0000000000..1f85f57c97
--- /dev/null
+++ b/server/src/com/vaadin/ui/NativeSelect.java
@@ -0,0 +1,91 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * This is a simple drop-down select without, for instance, support for
+ * multiselect, new items, lazyloading, and other advanced features. Sometimes
+ * "native" select without all the bells-and-whistles of the ComboBox is a
+ * better choice.
+ */
+@SuppressWarnings("serial")
+public class NativeSelect extends AbstractSelect {
+
+ // width in characters, mimics TextField
+ private int columns = 0;
+
+ public NativeSelect() {
+ super();
+ }
+
+ public NativeSelect(String caption, Collection<?> options) {
+ super(caption, options);
+ }
+
+ public NativeSelect(String caption, Container dataSource) {
+ super(caption, dataSource);
+ }
+
+ public NativeSelect(String caption) {
+ super(caption);
+ }
+
+ /**
+ * Sets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ *
+ * @param columns
+ * the number of columns to set.
+ */
+ public void setColumns(int columns) {
+ if (columns < 0) {
+ columns = 0;
+ }
+ if (this.columns != columns) {
+ this.columns = columns;
+ requestRepaint();
+ }
+ }
+
+ public int getColumns() {
+ return columns;
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute("type", "native");
+ // Adds the number of columns
+ if (columns != 0) {
+ target.addAttribute("cols", columns);
+ }
+
+ super.paintContent(target);
+ }
+
+ @Override
+ public void setMultiSelect(boolean multiSelect)
+ throws UnsupportedOperationException {
+ if (multiSelect == true) {
+ throw new UnsupportedOperationException("Multiselect not supported");
+ }
+ }
+
+ @Override
+ public void setNewItemsAllowed(boolean allowNewOptions)
+ throws UnsupportedOperationException {
+ if (allowNewOptions == true) {
+ throw new UnsupportedOperationException(
+ "newItemsAllowed not supported");
+ }
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Notification.java b/server/src/com/vaadin/ui/Notification.java
new file mode 100644
index 0000000000..502e5ff788
--- /dev/null
+++ b/server/src/com/vaadin/ui/Notification.java
@@ -0,0 +1,367 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.Page;
+import com.vaadin.terminal.Resource;
+
+/**
+ * A notification message, used to display temporary messages to the user - for
+ * example "Document saved", or "Save failed".
+ * <p>
+ * The notification message can consist of several parts: caption, description
+ * and icon. It is usually used with only caption - one should be wary of
+ * filling the notification with too much information.
+ * </p>
+ * <p>
+ * The notification message tries to be as unobtrusive as possible, while still
+ * drawing needed attention. There are several basic types of messages that can
+ * be used in different situations:
+ * <ul>
+ * <li>TYPE_HUMANIZED_MESSAGE fades away quickly as soon as the user uses the
+ * mouse or types something. It can be used to show fairly unimportant messages,
+ * such as feedback that an operation succeeded ("Document Saved") - the kind of
+ * messages the user ignores once the application is familiar.</li>
+ * <li>TYPE_WARNING_MESSAGE is shown for a short while after the user uses the
+ * mouse or types something. It's default style is also more noticeable than the
+ * humanized message. It can be used for messages that do not contain a lot of
+ * important information, but should be noticed by the user. Despite the name,
+ * it does not have to be a warning, but can be used instead of the humanized
+ * message whenever you want to make the message a little more noticeable.</li>
+ * <li>TYPE_ERROR_MESSAGE requires to user to click it before disappearing, and
+ * can be used for critical messages.</li>
+ * <li>TYPE_TRAY_NOTIFICATION is shown for a while in the lower left corner of
+ * the window, and can be used for "convenience notifications" that do not have
+ * to be noticed immediately, and should not interfere with the current task -
+ * for instance to show "You have a new message in your inbox" while the user is
+ * working in some other area of the application.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * In addition to the basic pre-configured types, a Notification can also be
+ * configured to show up in a custom position, for a specified time (or until
+ * clicked), and with a custom stylename. An icon can also be added.
+ * </p>
+ *
+ */
+public class Notification implements Serializable {
+ public static final int TYPE_HUMANIZED_MESSAGE = 1;
+ public static final int TYPE_WARNING_MESSAGE = 2;
+ public static final int TYPE_ERROR_MESSAGE = 3;
+ public static final int TYPE_TRAY_NOTIFICATION = 4;
+
+ public static final int POSITION_CENTERED = 1;
+ public static final int POSITION_CENTERED_TOP = 2;
+ public static final int POSITION_CENTERED_BOTTOM = 3;
+ public static final int POSITION_TOP_LEFT = 4;
+ public static final int POSITION_TOP_RIGHT = 5;
+ public static final int POSITION_BOTTOM_LEFT = 6;
+ public static final int POSITION_BOTTOM_RIGHT = 7;
+
+ public static final int DELAY_FOREVER = -1;
+ public static final int DELAY_NONE = 0;
+
+ private String caption;
+ private String description;
+ private Resource icon;
+ private int position = POSITION_CENTERED;
+ private int delayMsec = 0;
+ private String styleName;
+ private boolean htmlContentAllowed;
+
+ /**
+ * Creates a "humanized" notification message.
+ *
+ * The caption is rendered as plain text with HTML automatically escaped.
+ *
+ * @param caption
+ * The message to show
+ */
+ public Notification(String caption) {
+ this(caption, null, TYPE_HUMANIZED_MESSAGE);
+ }
+
+ /**
+ * Creates a notification message of the specified type.
+ *
+ * The caption is rendered as plain text with HTML automatically escaped.
+ *
+ * @param caption
+ * The message to show
+ * @param type
+ * The type of message
+ */
+ public Notification(String caption, int type) {
+ this(caption, null, type);
+ }
+
+ /**
+ * Creates a "humanized" notification message with a bigger caption and
+ * smaller description.
+ *
+ * The caption and description are rendered as plain text with HTML
+ * automatically escaped.
+ *
+ * @param caption
+ * The message caption
+ * @param description
+ * The message description
+ */
+ public Notification(String caption, String description) {
+ this(caption, description, TYPE_HUMANIZED_MESSAGE);
+ }
+
+ /**
+ * Creates a notification message of the specified type, with a bigger
+ * caption and smaller description.
+ *
+ * The caption and description are rendered as plain text with HTML
+ * automatically escaped.
+ *
+ * @param caption
+ * The message caption
+ * @param description
+ * The message description
+ * @param type
+ * The type of message
+ */
+ public Notification(String caption, String description, int type) {
+ this(caption, description, type, false);
+ }
+
+ /**
+ * Creates a notification message of the specified type, with a bigger
+ * caption and smaller description.
+ *
+ * Care should be taken to to avoid XSS vulnerabilities if html is allowed.
+ *
+ * @param caption
+ * The message caption
+ * @param description
+ * The message description
+ * @param type
+ * The type of message
+ * @param htmlContentAllowed
+ * Whether html in the caption and description should be
+ * displayed as html or as plain text
+ */
+ public Notification(String caption, String description, int type,
+ boolean htmlContentAllowed) {
+ this.caption = caption;
+ this.description = description;
+ this.htmlContentAllowed = htmlContentAllowed;
+ setType(type);
+ }
+
+ private void setType(int type) {
+ switch (type) {
+ case TYPE_WARNING_MESSAGE:
+ delayMsec = 1500;
+ styleName = "warning";
+ break;
+ case TYPE_ERROR_MESSAGE:
+ delayMsec = -1;
+ styleName = "error";
+ break;
+ case TYPE_TRAY_NOTIFICATION:
+ delayMsec = 3000;
+ position = POSITION_BOTTOM_RIGHT;
+ styleName = "tray";
+
+ case TYPE_HUMANIZED_MESSAGE:
+ default:
+ break;
+ }
+
+ }
+
+ /**
+ * Gets the caption part of the notification message.
+ *
+ * @return The message caption
+ */
+ public String getCaption() {
+ return caption;
+ }
+
+ /**
+ * Sets the caption part of the notification message
+ *
+ * @param caption
+ * The message caption
+ */
+ public void setCaption(String caption) {
+ this.caption = caption;
+ }
+
+ /**
+ * Gets the description part of the notification message.
+ *
+ * @return The message description.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the description part of the notification message.
+ *
+ * @param description
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Gets the position of the notification message.
+ *
+ * @return The position
+ */
+ public int getPosition() {
+ return position;
+ }
+
+ /**
+ * Sets the position of the notification message.
+ *
+ * @param position
+ * The desired notification position
+ */
+ public void setPosition(int position) {
+ this.position = position;
+ }
+
+ /**
+ * Gets the icon part of the notification message.
+ *
+ * @return The message icon
+ */
+ public Resource getIcon() {
+ return icon;
+ }
+
+ /**
+ * Sets the icon part of the notification message.
+ *
+ * @param icon
+ * The desired message icon
+ */
+ public void setIcon(Resource icon) {
+ this.icon = icon;
+ }
+
+ /**
+ * Gets the delay before the notification disappears.
+ *
+ * @return the delay in msec, -1 indicates the message has to be clicked.
+ */
+ public int getDelayMsec() {
+ return delayMsec;
+ }
+
+ /**
+ * Sets the delay before the notification disappears.
+ *
+ * @param delayMsec
+ * the desired delay in msec, -1 to require the user to click the
+ * message
+ */
+ public void setDelayMsec(int delayMsec) {
+ this.delayMsec = delayMsec;
+ }
+
+ /**
+ * Sets the style name for the notification message.
+ *
+ * @param styleName
+ * The desired style name.
+ */
+ public void setStyleName(String styleName) {
+ this.styleName = styleName;
+ }
+
+ /**
+ * Gets the style name for the notification message.
+ *
+ * @return
+ */
+ public String getStyleName() {
+ return styleName;
+ }
+
+ /**
+ * Sets whether html is allowed in the caption and description. If set to
+ * true, the texts are passed to the browser as html and the developer is
+ * responsible for ensuring no harmful html is used. If set to false, the
+ * texts are passed to the browser as plain text.
+ *
+ * @param htmlContentAllowed
+ * true if the texts are used as html, false if used as plain
+ * text
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ }
+
+ /**
+ * Checks whether caption and description are interpreted as html or plain
+ * text.
+ *
+ * @return true if the texts are used as html, false if used as plain text
+ * @see #setHtmlContentAllowed(boolean)
+ */
+ public boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+
+ /**
+ * Shows this notification on a Page.
+ *
+ * @param page
+ * The page on which the notification should be shown
+ */
+ public void show(Page page) {
+ // TODO Can avoid deprecated API when Notification extends Extension
+ page.showNotification(this);
+ }
+
+ /**
+ * Shows a notification message on the middle of the current page. The
+ * message automatically disappears ("humanized message").
+ *
+ * The caption is rendered as plain text with HTML automatically escaped.
+ *
+ * @see #Notification(String)
+ * @see #show(Page)
+ *
+ * @param caption
+ * The message
+ */
+ public static void show(String caption) {
+ new Notification(caption).show(Page.getCurrent());
+ }
+
+ /**
+ * Shows a notification message the current page. The position and behavior
+ * of the message depends on the type, which is one of the basic types
+ * defined in {@link Notification}, for instance
+ * Notification.TYPE_WARNING_MESSAGE.
+ *
+ * The caption is rendered as plain text with HTML automatically escaped.
+ *
+ * @see #Notification(String, int)
+ * @see #show(Page)
+ *
+ * @param caption
+ * The message
+ * @param type
+ * The message type
+ */
+ public static void show(String caption, int type) {
+ new Notification(caption, type).show(Page.getCurrent());
+ }
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/ui/OptionGroup.java b/server/src/com/vaadin/ui/OptionGroup.java
new file mode 100644
index 0000000000..e3bcdd61b7
--- /dev/null
+++ b/server/src/com/vaadin/ui/OptionGroup.java
@@ -0,0 +1,203 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.data.Container;
+import com.vaadin.event.FieldEvents;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroup;
+
+/**
+ * Configures select to be used as an option group.
+ */
+@SuppressWarnings("serial")
+public class OptionGroup extends AbstractSelect implements
+ FieldEvents.BlurNotifier, FieldEvents.FocusNotifier {
+
+ private Set<Object> disabledItemIds = new HashSet<Object>();
+ private boolean htmlContentAllowed = false;
+
+ public OptionGroup() {
+ super();
+ }
+
+ public OptionGroup(String caption, Collection<?> options) {
+ super(caption, options);
+ }
+
+ public OptionGroup(String caption, Container dataSource) {
+ super(caption, dataSource);
+ }
+
+ public OptionGroup(String caption) {
+ super(caption);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute("type", "optiongroup");
+ if (isHtmlContentAllowed()) {
+ target.addAttribute(VOptionGroup.HTML_CONTENT_ALLOWED, true);
+ }
+ super.paintContent(target);
+ }
+
+ @Override
+ protected void paintItem(PaintTarget target, Object itemId)
+ throws PaintException {
+ super.paintItem(target, itemId);
+ if (!isItemEnabled(itemId)) {
+ target.addAttribute(VOptionGroup.ATTRIBUTE_OPTION_DISABLED, true);
+ }
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ super.changeVariables(source, variables);
+
+ if (variables.containsKey(FocusEvent.EVENT_ID)) {
+ fireEvent(new FocusEvent(this));
+ }
+ if (variables.containsKey(BlurEvent.EVENT_ID)) {
+ fireEvent(new BlurEvent(this));
+ }
+ }
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+
+ }
+
+ @Override
+ protected void setValue(Object newValue, boolean repaintIsNotNeeded) {
+ if (repaintIsNotNeeded) {
+ /*
+ * Check that value from changeVariables() doesn't contain unallowed
+ * selections: In the multi select mode, the user has selected or
+ * deselected a disabled item. In the single select mode, the user
+ * has selected a disabled item.
+ */
+ if (isMultiSelect()) {
+ Set<?> currentValueSet = (Set<?>) getValue();
+ Set<?> newValueSet = (Set<?>) newValue;
+ for (Object itemId : currentValueSet) {
+ if (!isItemEnabled(itemId) && !newValueSet.contains(itemId)) {
+ requestRepaint();
+ return;
+ }
+ }
+ for (Object itemId : newValueSet) {
+ if (!isItemEnabled(itemId)
+ && !currentValueSet.contains(itemId)) {
+ requestRepaint();
+ return;
+ }
+ }
+ } else {
+ if (newValue == null) {
+ newValue = getNullSelectionItemId();
+ }
+ if (!isItemEnabled(newValue)) {
+ requestRepaint();
+ return;
+ }
+ }
+ }
+ super.setValue(newValue, repaintIsNotNeeded);
+ }
+
+ /**
+ * Sets an item disabled or enabled. In the multiselect mode, a disabled
+ * item cannot be selected or deselected by the user. In the single
+ * selection mode, a disable item cannot be selected.
+ *
+ * However, programmatical selection or deselection of an disable item is
+ * possible. By default, items are enabled.
+ *
+ * @param itemId
+ * the id of the item to be disabled or enabled
+ * @param enabled
+ * if true the item is enabled, otherwise the item is disabled
+ */
+ public void setItemEnabled(Object itemId, boolean enabled) {
+ if (itemId != null) {
+ if (enabled) {
+ disabledItemIds.remove(itemId);
+ } else {
+ disabledItemIds.add(itemId);
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Returns true if the item is enabled.
+ *
+ * @param itemId
+ * the id of the item to be checked
+ * @return true if the item is enabled, false otherwise
+ * @see #setItemEnabled(Object, boolean)
+ */
+ public boolean isItemEnabled(Object itemId) {
+ if (itemId != null) {
+ return !disabledItemIds.contains(itemId);
+ }
+ return true;
+ }
+
+ /**
+ * Sets whether html is allowed in the item captions. If set to true, the
+ * captions are passed to the browser as html and the developer is
+ * responsible for ensuring no harmful html is used. If set to false, the
+ * content is passed to the browser as plain text.
+ *
+ * @param htmlContentAllowed
+ * true if the captions are used as html, false if used as plain
+ * text
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ requestRepaint();
+ }
+
+ /**
+ * Checks whether captions are interpreted as html or plain text.
+ *
+ * @return true if the captions are used as html, false if used as plain
+ * text
+ * @see #setHtmlContentAllowed(boolean)
+ */
+ public boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+}
diff --git a/server/src/com/vaadin/ui/Panel.java b/server/src/com/vaadin/ui/Panel.java
new file mode 100644
index 0000000000..3c26b73f09
--- /dev/null
+++ b/server/src/com/vaadin/ui/Panel.java
@@ -0,0 +1,486 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.ActionManager;
+import com.vaadin.event.MouseEvents.ClickEvent;
+import com.vaadin.event.MouseEvents.ClickListener;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.panel.PanelServerRpc;
+import com.vaadin.shared.ui.panel.PanelState;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Scrollable;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.ClickEventHandler;
+import com.vaadin.ui.Component.Focusable;
+
+/**
+ * Panel - a simple single component container.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Panel extends AbstractComponentContainer implements Scrollable,
+ ComponentContainer.ComponentAttachListener,
+ ComponentContainer.ComponentDetachListener, Action.Notifier, Focusable,
+ Vaadin6Component {
+
+ /**
+ * Content of the panel.
+ */
+ private ComponentContainer content;
+
+ /**
+ * Keeps track of the Actions added to this component, and manages the
+ * painting and handling as well.
+ */
+ protected ActionManager actionManager;
+
+ private PanelServerRpc rpc = new PanelServerRpc() {
+ @Override
+ public void click(MouseEventDetails mouseDetails) {
+ fireEvent(new ClickEvent(Panel.this, mouseDetails));
+ }
+ };
+
+ /**
+ * Creates a new empty panel. A VerticalLayout is used as content.
+ */
+ public Panel() {
+ this((ComponentContainer) null);
+ }
+
+ /**
+ * Creates a new empty panel which contains the given content. The content
+ * cannot be null.
+ *
+ * @param content
+ * the content for the panel.
+ */
+ public Panel(ComponentContainer content) {
+ registerRpc(rpc);
+ setContent(content);
+ setWidth(100, Unit.PERCENTAGE);
+ getState().setTabIndex(-1);
+ }
+
+ /**
+ * Creates a new empty panel with caption. Default layout is used.
+ *
+ * @param caption
+ * the caption used in the panel (HTML/XHTML).
+ */
+ public Panel(String caption) {
+ this(caption, null);
+ }
+
+ /**
+ * Creates a new empty panel with the given caption and content.
+ *
+ * @param caption
+ * the caption of the panel (HTML/XHTML).
+ * @param content
+ * the content used in the panel.
+ */
+ public Panel(String caption, ComponentContainer content) {
+ this(content);
+ setCaption(caption);
+ }
+
+ /**
+ * Sets the caption of the panel.
+ *
+ * Note that the caption is interpreted as HTML/XHTML and therefore care
+ * should be taken not to enable HTML injection and XSS attacks using panel
+ * captions. This behavior may change in future versions.
+ *
+ * @see AbstractComponent#setCaption(String)
+ */
+ @Override
+ public void setCaption(String caption) {
+ super.setCaption(caption);
+ }
+
+ /**
+ * Returns the content of the Panel.
+ *
+ * @return
+ */
+ public ComponentContainer getContent() {
+ return content;
+ }
+
+ /**
+ *
+ * Set the content of the Panel. If null is given as the new content then a
+ * layout is automatically created and set as the content.
+ *
+ * @param content
+ * The new content
+ */
+ public void setContent(ComponentContainer newContent) {
+
+ // If the content is null we create the default content
+ if (newContent == null) {
+ newContent = createDefaultContent();
+ }
+
+ // if (newContent == null) {
+ // throw new IllegalArgumentException("Content cannot be null");
+ // }
+
+ if (newContent == content) {
+ // don't set the same content twice
+ return;
+ }
+
+ // detach old content if present
+ if (content != null) {
+ content.setParent(null);
+ content.removeListener((ComponentContainer.ComponentAttachListener) this);
+ content.removeListener((ComponentContainer.ComponentDetachListener) this);
+ }
+
+ // Sets the panel to be parent for the content
+ newContent.setParent(this);
+
+ // Sets the new content
+ content = newContent;
+
+ // Adds the event listeners for new content
+ newContent
+ .addListener((ComponentContainer.ComponentAttachListener) this);
+ newContent
+ .addListener((ComponentContainer.ComponentDetachListener) this);
+
+ content = newContent;
+ requestRepaint();
+ }
+
+ /**
+ * Create a ComponentContainer which is added by default to the Panel if
+ * user does not specify any content.
+ *
+ * @return
+ */
+ private ComponentContainer createDefaultContent() {
+ VerticalLayout layout = new VerticalLayout();
+ // Force margins by default
+ layout.setMargin(true);
+ return layout;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.Vaadin6Component#paintContent(com.vaadin.terminal
+ * .PaintTarget)
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (actionManager != null) {
+ actionManager.paintActions(null, target);
+ }
+ }
+
+ /**
+ * Adds the component into this container.
+ *
+ * @param c
+ * the component to be added.
+ * @see com.vaadin.ui.AbstractComponentContainer#addComponent(com.vaadin.ui.Component)
+ */
+ @Override
+ public void addComponent(Component c) {
+ content.addComponent(c);
+ // No repaint request is made as we except the underlying container to
+ // request repaints
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * The component to be removed.
+ * @see com.vaadin.ui.AbstractComponentContainer#removeComponent(com.vaadin.ui.Component)
+ */
+ @Override
+ public void removeComponent(Component c) {
+ content.removeComponent(c);
+ // No repaint request is made as we except the underlying container to
+ // request repaints
+ }
+
+ /**
+ * Gets the component container iterator for going through all the
+ * components in the container.
+ *
+ * @return the Iterator of the components inside the container.
+ * @see com.vaadin.ui.ComponentContainer#getComponentIterator()
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return Collections.singleton((Component) content).iterator();
+ }
+
+ /**
+ * Called when one or more variables handled by the implementing class are
+ * changed.
+ *
+ * @see com.vaadin.terminal.VariableOwner#changeVariables(Object, Map)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // Get new size
+ final Integer newWidth = (Integer) variables.get("width");
+ final Integer newHeight = (Integer) variables.get("height");
+ if (newWidth != null && newWidth.intValue() != getWidth()) {
+ setWidth(newWidth.intValue(), UNITS_PIXELS);
+ }
+ if (newHeight != null && newHeight.intValue() != getHeight()) {
+ setHeight(newHeight.intValue(), UNITS_PIXELS);
+ }
+
+ // Scrolling
+ final Integer newScrollX = (Integer) variables.get("scrollLeft");
+ final Integer newScrollY = (Integer) variables.get("scrollTop");
+ if (newScrollX != null && newScrollX.intValue() != getScrollLeft()) {
+ // set internally, not to fire request repaint
+ getState().setScrollLeft(newScrollX.intValue());
+ }
+ if (newScrollY != null && newScrollY.intValue() != getScrollTop()) {
+ // set internally, not to fire request repaint
+ getState().setScrollTop(newScrollY.intValue());
+ }
+
+ // Actions
+ if (actionManager != null) {
+ actionManager.handleActions(variables, this);
+ }
+
+ }
+
+ /* Scrolling functionality */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Scrollable#setScrollable(boolean)
+ */
+ @Override
+ public int getScrollLeft() {
+ return getState().getScrollLeft();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Scrollable#setScrollable(boolean)
+ */
+ @Override
+ public int getScrollTop() {
+ return getState().getScrollTop();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Scrollable#setScrollLeft(int)
+ */
+ @Override
+ public void setScrollLeft(int scrollLeft) {
+ if (scrollLeft < 0) {
+ throw new IllegalArgumentException(
+ "Scroll offset must be at least 0");
+ }
+ getState().setScrollLeft(scrollLeft);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Scrollable#setScrollTop(int)
+ */
+ @Override
+ public void setScrollTop(int scrollTop) {
+ if (scrollTop < 0) {
+ throw new IllegalArgumentException(
+ "Scroll offset must be at least 0");
+ }
+ getState().setScrollTop(scrollTop);
+ requestRepaint();
+ }
+
+ /* Documented in superclass */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ content.replaceComponent(oldComponent, newComponent);
+ }
+
+ /**
+ * A new component is attached to container.
+ *
+ * @see com.vaadin.ui.ComponentContainer.ComponentAttachListener#componentAttachedToContainer(com.vaadin.ui.ComponentContainer.ComponentAttachEvent)
+ */
+ @Override
+ public void componentAttachedToContainer(ComponentAttachEvent event) {
+ if (event.getContainer() == content) {
+ fireComponentAttachEvent(event.getAttachedComponent());
+ }
+ }
+
+ /**
+ * A component has been detached from container.
+ *
+ * @see com.vaadin.ui.ComponentContainer.ComponentDetachListener#componentDetachedFromContainer(com.vaadin.ui.ComponentContainer.ComponentDetachEvent)
+ */
+ @Override
+ public void componentDetachedFromContainer(ComponentDetachEvent event) {
+ if (event.getContainer() == content) {
+ fireComponentDetachEvent(event.getDetachedComponent());
+ }
+ }
+
+ /**
+ * Removes all components from this container.
+ *
+ * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
+ */
+ @Override
+ public void removeAllComponents() {
+ content.removeAllComponents();
+ }
+
+ /*
+ * ACTIONS
+ */
+ @Override
+ protected ActionManager getActionManager() {
+ if (actionManager == null) {
+ actionManager = new ActionManager(this);
+ }
+ return actionManager;
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void addAction(
+ T action) {
+ getActionManager().addAction(action);
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void removeAction(
+ T action) {
+ if (actionManager != null) {
+ actionManager.removeAction(action);
+ }
+ }
+
+ @Override
+ public void addActionHandler(Handler actionHandler) {
+ getActionManager().addActionHandler(actionHandler);
+ }
+
+ @Override
+ public void removeActionHandler(Handler actionHandler) {
+ if (actionManager != null) {
+ actionManager.removeActionHandler(actionHandler);
+ }
+ }
+
+ /**
+ * Removes all action handlers
+ */
+ public void removeAllActionHandlers() {
+ if (actionManager != null) {
+ actionManager.removeAllActionHandlers();
+ }
+ }
+
+ /**
+ * Add a click listener to the Panel. The listener is called whenever the
+ * user clicks inside the Panel. Also when the click targets a component
+ * inside the Panel, provided the targeted component does not prevent the
+ * click event from propagating.
+ *
+ * Use {@link #removeListener(ClickListener)} to remove the listener.
+ *
+ * @param listener
+ * The listener to add
+ */
+ public void addListener(ClickListener listener) {
+ addListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER, ClickEvent.class,
+ listener, ClickListener.clickMethod);
+ }
+
+ /**
+ * Remove a click listener from the Panel. The listener should earlier have
+ * been added using {@link #addListener(ClickListener)}.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeListener(ClickListener listener) {
+ removeListener(ClickEventHandler.CLICK_EVENT_IDENTIFIER,
+ ClickEvent.class, listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getTabIndex() {
+ return getState().getTabIndex();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setTabIndex(int tabIndex) {
+ getState().setTabIndex(tabIndex);
+ requestRepaint();
+ }
+
+ /**
+ * Moves keyboard focus to the component. {@see Focusable#focus()}
+ *
+ */
+ @Override
+ public void focus() {
+ super.focus();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.ComponentContainer#getComponentCount()
+ */
+ @Override
+ public int getComponentCount() {
+ // This is so wrong... (#2924)
+ return content.getComponentCount();
+ }
+
+ @Override
+ public PanelState getState() {
+ return (PanelState) super.getState();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/PasswordField.java b/server/src/com/vaadin/ui/PasswordField.java
new file mode 100644
index 0000000000..c1fccebbfe
--- /dev/null
+++ b/server/src/com/vaadin/ui/PasswordField.java
@@ -0,0 +1,67 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import com.vaadin.data.Property;
+
+/**
+ * A field that is used to enter secret text information like passwords. The
+ * entered text is not displayed on the screen.
+ */
+public class PasswordField extends AbstractTextField {
+
+ /**
+ * Constructs an empty PasswordField.
+ */
+ public PasswordField() {
+ setValue("");
+ }
+
+ /**
+ * Constructs a PasswordField with given property data source.
+ *
+ * @param dataSource
+ * the property data source for the field
+ */
+ public PasswordField(Property dataSource) {
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a PasswordField with given caption and property data source.
+ *
+ * @param caption
+ * the caption for the field
+ * @param dataSource
+ * the property data source for the field
+ */
+ public PasswordField(String caption, Property dataSource) {
+ this(dataSource);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a PasswordField with given value and caption.
+ *
+ * @param caption
+ * the caption for the field
+ * @param value
+ * the value for the field
+ */
+ public PasswordField(String caption, String value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a PasswordField with given caption.
+ *
+ * @param caption
+ * the caption for the field
+ */
+ public PasswordField(String caption) {
+ this();
+ setCaption(caption);
+ }
+}
diff --git a/server/src/com/vaadin/ui/PopupDateField.java b/server/src/com/vaadin/ui/PopupDateField.java
new file mode 100644
index 0000000000..3688d4035f
--- /dev/null
+++ b/server/src/com/vaadin/ui/PopupDateField.java
@@ -0,0 +1,80 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Date;
+
+import com.vaadin.data.Property;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * <p>
+ * A date entry component, which displays the actual date selector as a popup.
+ *
+ * </p>
+ *
+ * @see DateField
+ * @see InlineDateField
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+public class PopupDateField extends DateField {
+
+ private String inputPrompt = null;
+
+ public PopupDateField() {
+ super();
+ }
+
+ public PopupDateField(Property dataSource) throws IllegalArgumentException {
+ super(dataSource);
+ }
+
+ public PopupDateField(String caption, Date value) {
+ super(caption, value);
+ }
+
+ public PopupDateField(String caption, Property dataSource) {
+ super(caption, dataSource);
+ }
+
+ public PopupDateField(String caption) {
+ super(caption);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ if (inputPrompt != null) {
+ target.addAttribute("prompt", inputPrompt);
+ }
+ }
+
+ /**
+ * Gets the current input prompt.
+ *
+ * @see #setInputPrompt(String)
+ * @return the current input prompt, or null if not enabled
+ */
+ public String getInputPrompt() {
+ return inputPrompt;
+ }
+
+ /**
+ * Sets the input prompt - a textual prompt that is displayed when the field
+ * would otherwise be empty, to prompt the user for input.
+ *
+ * @param inputPrompt
+ */
+ public void setInputPrompt(String inputPrompt) {
+ this.inputPrompt = inputPrompt;
+ requestRepaint();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/PopupView.java b/server/src/com/vaadin/ui/PopupView.java
new file mode 100644
index 0000000000..766181b50f
--- /dev/null
+++ b/server/src/com/vaadin/ui/PopupView.java
@@ -0,0 +1,453 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vaadin.terminal.LegacyPaint;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+
+/**
+ *
+ * A component for displaying a two different views to data. The minimized view
+ * is normally used to render the component, and when it is clicked the full
+ * view is displayed on a popup. The inner class {@link PopupView.Content} is
+ * used to deliver contents to this component.
+ *
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class PopupView extends AbstractComponentContainer implements
+ Vaadin6Component {
+
+ private Content content;
+ private boolean hideOnMouseOut;
+ private Component visibleComponent;
+
+ private static final Method POPUP_VISIBILITY_METHOD;
+ static {
+ try {
+ POPUP_VISIBILITY_METHOD = PopupVisibilityListener.class
+ .getDeclaredMethod("popupVisibilityChange",
+ new Class[] { PopupVisibilityEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in PopupView");
+ }
+ }
+
+ /**
+ * Iterator for the visible components (zero or one components), used by
+ * {@link PopupView#getComponentIterator()}.
+ */
+ private static class SingleComponentIterator implements
+ Iterator<Component>, Serializable {
+
+ private final Component component;
+ private boolean first;
+
+ public SingleComponentIterator(Component component) {
+ this.component = component;
+ first = (component == null);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !first;
+ }
+
+ @Override
+ public Component next() {
+ if (!first) {
+ first = true;
+ return component;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /* Constructors */
+
+ /**
+ * A simple way to create a PopupPanel. Note that the minimal representation
+ * may not be dynamically updated, in order to achieve this create your own
+ * Content object and use {@link PopupView#PopupView(Content)}.
+ *
+ * @param small
+ * the minimal textual representation as HTML
+ * @param large
+ * the full, Component-type representation
+ */
+ public PopupView(final java.lang.String small, final Component large) {
+ this(new PopupView.Content() {
+ @Override
+ public java.lang.String getMinimizedValueAsHTML() {
+ return small;
+ }
+
+ @Override
+ public Component getPopupComponent() {
+ return large;
+ }
+ });
+
+ }
+
+ /**
+ * Creates a PopupView through the PopupView.Content interface. This allows
+ * the creator to dynamically change the contents of the PopupView.
+ *
+ * @param content
+ * the PopupView.Content that contains the information for this
+ */
+ public PopupView(PopupView.Content content) {
+ super();
+ hideOnMouseOut = true;
+ setContent(content);
+ }
+
+ /**
+ * This method will replace the current content of the panel with a new one.
+ *
+ * @param newContent
+ * PopupView.Content object containing new information for the
+ * PopupView
+ * @throws IllegalArgumentException
+ * if the method is passed a null value, or if one of the
+ * content methods returns null
+ */
+ public void setContent(PopupView.Content newContent)
+ throws IllegalArgumentException {
+ if (newContent == null) {
+ throw new IllegalArgumentException("Content must not be null");
+ }
+ content = newContent;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the content-package for this PopupView.
+ *
+ * @return the PopupView.Content for this object or null
+ */
+ public PopupView.Content getContent() {
+ return content;
+ }
+
+ /**
+ * @deprecated Use {@link #setPopupVisible()} instead.
+ */
+ @Deprecated
+ public void setPopupVisibility(boolean visible) {
+ setPopupVisible(visible);
+ }
+
+ /**
+ * @deprecated Use {@link #isPopupVisible()} instead.
+ */
+ @Deprecated
+ public boolean getPopupVisibility() {
+ return isPopupVisible();
+ }
+
+ /**
+ * Set the visibility of the popup. Does not hide the minimal
+ * representation.
+ *
+ * @param visible
+ */
+ public void setPopupVisible(boolean visible) {
+ if (isPopupVisible() != visible) {
+ if (visible) {
+ visibleComponent = content.getPopupComponent();
+ if (visibleComponent == null) {
+ throw new java.lang.IllegalStateException(
+ "PopupView.Content did not return Component to set visible");
+ }
+ super.addComponent(visibleComponent);
+ } else {
+ super.removeComponent(visibleComponent);
+ visibleComponent = null;
+ }
+ fireEvent(new PopupVisibilityEvent(this));
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Return whether the popup is visible.
+ *
+ * @return true if the popup is showing
+ */
+ public boolean isPopupVisible() {
+ return visibleComponent != null;
+ }
+
+ /**
+ * Check if this popup will be hidden when the user takes the mouse cursor
+ * out of the popup area.
+ *
+ * @return true if the popup is hidden on mouse out, false otherwise
+ */
+ public boolean isHideOnMouseOut() {
+ return hideOnMouseOut;
+ }
+
+ /**
+ * Should the popup automatically hide when the user takes the mouse cursor
+ * out of the popup area? If this is false, the user must click outside the
+ * popup to close it. The default is true.
+ *
+ * @param hideOnMouseOut
+ *
+ */
+ public void setHideOnMouseOut(boolean hideOnMouseOut) {
+ this.hideOnMouseOut = hideOnMouseOut;
+ }
+
+ /*
+ * Methods inherited from AbstractComponentContainer. These are unnecessary
+ * (but mandatory). Most of them are not supported in this implementation.
+ */
+
+ /**
+ * This class only contains other components when the popup is showing.
+ *
+ * @see com.vaadin.ui.ComponentContainer#getComponentIterator()
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return new SingleComponentIterator(visibleComponent);
+ }
+
+ /**
+ * Gets the number of contained components. Consistent with the iterator
+ * returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components (zero or one)
+ */
+ @Override
+ public int getComponentCount() {
+ return (visibleComponent != null ? 1 : 0);
+ }
+
+ /**
+ * Not supported in this implementation.
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#removeAllComponents()
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void removeAllComponents() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported in this implementation.
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#moveComponentsFrom(com.vaadin.ui.ComponentContainer)
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void moveComponentsFrom(ComponentContainer source)
+ throws UnsupportedOperationException {
+
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported in this implementation.
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#addComponent(com.vaadin.ui.Component)
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void addComponent(Component c) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ /**
+ * Not supported in this implementation.
+ *
+ * @see com.vaadin.ui.ComponentContainer#replaceComponent(com.vaadin.ui.Component,
+ * com.vaadin.ui.Component)
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent)
+ throws UnsupportedOperationException {
+
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported in this implementation
+ *
+ * @see com.vaadin.ui.AbstractComponentContainer#removeComponent(com.vaadin.ui.Component)
+ */
+ @Override
+ public void removeComponent(Component c)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ /*
+ * Methods for server-client communications.
+ */
+
+ /**
+ * Paint (serialize) the component for the client.
+ *
+ * @see com.vaadin.ui.AbstractComponent#paintContent(com.vaadin.terminal.PaintTarget)
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ String html = content.getMinimizedValueAsHTML();
+ if (html == null) {
+ html = "";
+ }
+ target.addAttribute("html", html);
+ target.addAttribute("hideOnMouseOut", hideOnMouseOut);
+
+ // Only paint component to client if we know that the popup is showing
+ if (isPopupVisible()) {
+ target.startTag("popupComponent");
+ LegacyPaint.paint(visibleComponent, target);
+ target.endTag("popupComponent");
+ }
+
+ target.addVariable(this, "popupVisibility", isPopupVisible());
+ }
+
+ /**
+ * Deserialize changes received from client.
+ *
+ * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ if (variables.containsKey("popupVisibility")) {
+ setPopupVisible(((Boolean) variables.get("popupVisibility"))
+ .booleanValue());
+ }
+ }
+
+ /**
+ * Used to deliver customized content-packages to the PopupView. These are
+ * dynamically loaded when they are redrawn. The user must take care that
+ * neither of these methods ever return null.
+ */
+ public interface Content extends Serializable {
+
+ /**
+ * This should return a small view of the full data.
+ *
+ * @return value in HTML format
+ */
+ public String getMinimizedValueAsHTML();
+
+ /**
+ * This should return the full Component representing the data
+ *
+ * @return a Component for the value
+ */
+ public Component getPopupComponent();
+ }
+
+ /**
+ * Add a listener that is called whenever the visibility of the popup is
+ * changed.
+ *
+ * @param listener
+ * the listener to add
+ * @see PopupVisibilityListener
+ * @see PopupVisibilityEvent
+ * @see #removeListener(PopupVisibilityListener)
+ *
+ */
+ public void addListener(PopupVisibilityListener listener) {
+ addListener(PopupVisibilityEvent.class, listener,
+ POPUP_VISIBILITY_METHOD);
+ }
+
+ /**
+ * Removes a previously added listener, so that it no longer receives events
+ * when the visibility of the popup changes.
+ *
+ * @param listener
+ * the listener to remove
+ * @see PopupVisibilityListener
+ * @see #addListener(PopupVisibilityListener)
+ */
+ public void removeListener(PopupVisibilityListener listener) {
+ removeListener(PopupVisibilityEvent.class, listener,
+ POPUP_VISIBILITY_METHOD);
+ }
+
+ /**
+ * This event is received by the PopupVisibilityListeners when the
+ * visibility of the popup changes. You can get the new visibility directly
+ * with {@link #isPopupVisible()}, or get the PopupView that produced the
+ * event with {@link #getPopupView()}.
+ *
+ */
+ public class PopupVisibilityEvent extends Event {
+
+ public PopupVisibilityEvent(PopupView source) {
+ super(source);
+ }
+
+ /**
+ * Get the PopupView instance that is the source of this event.
+ *
+ * @return the source PopupView
+ */
+ public PopupView getPopupView() {
+ return (PopupView) getSource();
+ }
+
+ /**
+ * Returns the current visibility of the popup.
+ *
+ * @return true if the popup is visible
+ */
+ public boolean isPopupVisible() {
+ return getPopupView().isPopupVisible();
+ }
+ }
+
+ /**
+ * Defines a listener that can receive a PopupVisibilityEvent when the
+ * visibility of the popup changes.
+ *
+ */
+ public interface PopupVisibilityListener extends Serializable {
+ /**
+ * Pass to {@link PopupView#PopupVisibilityEvent} to start listening for
+ * popup visibility changes.
+ *
+ * @param event
+ * the event
+ *
+ * @see {@link PopupVisibilityEvent}
+ * @see {@link PopupView#addListener(PopupVisibilityListener)}
+ */
+ public void popupVisibilityChange(PopupVisibilityEvent event);
+ }
+}
diff --git a/server/src/com/vaadin/ui/ProgressIndicator.java b/server/src/com/vaadin/ui/ProgressIndicator.java
new file mode 100644
index 0000000000..fef54a267c
--- /dev/null
+++ b/server/src/com/vaadin/ui/ProgressIndicator.java
@@ -0,0 +1,257 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Map;
+
+import com.vaadin.data.Property;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+
+/**
+ * <code>ProgressIndicator</code> is component that shows user state of a
+ * process (like long computing or file upload)
+ *
+ * <code>ProgressIndicator</code> has two mainmodes. One for indeterminate
+ * processes and other (default) for processes which progress can be measured
+ *
+ * May view an other property that indicates progress 0...1
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 4
+ */
+@SuppressWarnings("serial")
+public class ProgressIndicator extends AbstractField<Number> implements
+ Property.Viewer, Property.ValueChangeListener, Vaadin6Component {
+
+ /**
+ * Content mode, where the label contains only plain text. The getValue()
+ * result is coded to XML when painting.
+ */
+ public static final int CONTENT_TEXT = 0;
+
+ /**
+ * Content mode, where the label contains preformatted text.
+ */
+ public static final int CONTENT_PREFORMATTED = 1;
+
+ private boolean indeterminate = false;
+
+ private Property dataSource;
+
+ private int pollingInterval = 1000;
+
+ /**
+ * Creates an a new ProgressIndicator.
+ */
+ public ProgressIndicator() {
+ setPropertyDataSource(new ObjectProperty<Float>(new Float(0),
+ Float.class));
+ }
+
+ /**
+ * Creates a new instance of ProgressIndicator with given state.
+ *
+ * @param value
+ */
+ public ProgressIndicator(Float value) {
+ setPropertyDataSource(new ObjectProperty<Float>(value, Float.class));
+ }
+
+ /**
+ * Creates a new instance of ProgressIndicator with stae read from given
+ * datasource.
+ *
+ * @param contentSource
+ */
+ public ProgressIndicator(Property contentSource) {
+ setPropertyDataSource(contentSource);
+ }
+
+ /**
+ * Sets the component to read-only. Readonly is not used in
+ * ProgressIndicator.
+ *
+ * @param readOnly
+ * True to enable read-only mode, False to disable it.
+ */
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be se");
+ }
+ dataSource.setReadOnly(readOnly);
+ }
+
+ /**
+ * Is the component read-only ? Readonly is not used in ProgressIndicator -
+ * this returns allways false.
+ *
+ * @return True if the component is in read only mode.
+ */
+ @Override
+ public boolean isReadOnly() {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be se");
+ }
+ return dataSource.isReadOnly();
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * the Paint Event.
+ * @throws PaintException
+ * if the Paint Operation fails.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute("indeterminate", indeterminate);
+ target.addAttribute("pollinginterval", pollingInterval);
+ target.addAttribute("state", getValue().toString());
+ }
+
+ /**
+ * Gets the value of the ProgressIndicator. Value of the ProgressIndicator
+ * is Float between 0 and 1.
+ *
+ * @return the Value of the ProgressIndicator.
+ * @see com.vaadin.ui.AbstractField#getValue()
+ */
+ @Override
+ public Number getValue() {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be set");
+ }
+ // TODO conversions to eliminate cast
+ return (Number) dataSource.getValue();
+ }
+
+ /**
+ * Sets the value of the ProgressIndicator. Value of the ProgressIndicator
+ * is the Float between 0 and 1.
+ *
+ * @param newValue
+ * the New value of the ProgressIndicator.
+ * @see com.vaadin.ui.AbstractField#setValue()
+ */
+ @Override
+ public void setValue(Object newValue) {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be set");
+ }
+ dataSource.setValue(newValue);
+ }
+
+ /**
+ * @see com.vaadin.ui.AbstractField#getType()
+ */
+ @Override
+ public Class<? extends Number> getType() {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be set");
+ }
+ return dataSource.getType();
+ }
+
+ /**
+ * Gets the viewing data-source property.
+ *
+ * @return the datasource.
+ * @see com.vaadin.ui.AbstractField#getPropertyDataSource()
+ */
+ @Override
+ public Property getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * Sets the property as data-source for viewing.
+ *
+ * @param newDataSource
+ * the new data source.
+ * @see com.vaadin.ui.AbstractField#setPropertyDataSource(com.vaadin.data.Property)
+ */
+ @Override
+ public void setPropertyDataSource(Property newDataSource) {
+ // Stops listening the old data source changes
+ if (dataSource != null
+ && Property.ValueChangeNotifier.class
+ .isAssignableFrom(dataSource.getClass())) {
+ ((Property.ValueChangeNotifier) dataSource).removeListener(this);
+ }
+
+ // Sets the new data source
+ dataSource = newDataSource;
+
+ // Listens the new data source if possible
+ if (dataSource != null
+ && Property.ValueChangeNotifier.class
+ .isAssignableFrom(dataSource.getClass())) {
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+ }
+
+ /**
+ * Gets the mode of ProgressIndicator.
+ *
+ * @return true if in indeterminate mode.
+ */
+ public boolean getContentMode() {
+ return indeterminate;
+ }
+
+ /**
+ * Sets wheter or not the ProgressIndicator is indeterminate.
+ *
+ * @param newValue
+ * true to set to indeterminate mode.
+ */
+ public void setIndeterminate(boolean newValue) {
+ indeterminate = newValue;
+ requestRepaint();
+ }
+
+ /**
+ * Gets whether or not the ProgressIndicator is indeterminate.
+ *
+ * @return true to set to indeterminate mode.
+ */
+ public boolean isIndeterminate() {
+ return indeterminate;
+ }
+
+ /**
+ * Sets the interval that component checks for progress.
+ *
+ * @param newValue
+ * the interval in milliseconds.
+ */
+ public void setPollingInterval(int newValue) {
+ pollingInterval = newValue;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the interval that component checks for progress.
+ *
+ * @return the interval in milliseconds.
+ */
+ public int getPollingInterval() {
+ return pollingInterval;
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // TODO Remove once Vaadin6Component is no longer implemented
+
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/RichTextArea.java b/server/src/com/vaadin/ui/RichTextArea.java
new file mode 100644
index 0000000000..cec952926b
--- /dev/null
+++ b/server/src/com/vaadin/ui/RichTextArea.java
@@ -0,0 +1,344 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.text.Format;
+import java.util.Map;
+
+import com.vaadin.data.Property;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+
+/**
+ * A simple RichTextArea to edit HTML format text.
+ *
+ * Note, that using {@link TextField#setMaxLength(int)} method in
+ * {@link RichTextArea} may produce unexpected results as formatting is counted
+ * into length of field.
+ */
+public class RichTextArea extends AbstractField<String> implements
+ Vaadin6Component {
+
+ /**
+ * Value formatter used to format the string contents.
+ */
+ @Deprecated
+ private Format format;
+
+ /**
+ * Null representation.
+ */
+ private String nullRepresentation = "null";
+
+ /**
+ * Is setting to null from non-null value allowed by setting with null
+ * representation .
+ */
+ private boolean nullSettingAllowed = false;
+
+ /**
+ * Temporary flag that indicates all content will be selected after the next
+ * paint. Reset to false after painted.
+ */
+ private boolean selectAll = false;
+
+ /**
+ * Constructs an empty <code>RichTextArea</code> with no caption.
+ */
+ public RichTextArea() {
+ setValue("");
+ }
+
+ /**
+ *
+ * Constructs an empty <code>RichTextArea</code> with the given caption.
+ *
+ * @param caption
+ * the caption for the editor.
+ */
+ public RichTextArea(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new <code>RichTextArea</code> that's bound to the specified
+ * <code>Property</code> and has no caption.
+ *
+ * @param dataSource
+ * the data source for the editor value
+ */
+ public RichTextArea(Property dataSource) {
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a new <code>RichTextArea</code> that's bound to the specified
+ * <code>Property</code> and has the given caption.
+ *
+ * @param caption
+ * the caption for the editor.
+ * @param dataSource
+ * the data source for the editor value
+ */
+ public RichTextArea(String caption, Property dataSource) {
+ this(dataSource);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new <code>RichTextArea</code> with the given caption and
+ * initial text contents.
+ *
+ * @param caption
+ * the caption for the editor.
+ * @param value
+ * the initial text content of the editor.
+ */
+ public RichTextArea(String caption, String value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (selectAll) {
+ target.addAttribute("selectAll", true);
+ selectAll = false;
+ }
+
+ // Adds the content as variable
+ String value = getFormattedValue();
+ if (value == null) {
+ value = getNullRepresentation();
+ }
+ if (value == null) {
+ throw new IllegalStateException(
+ "Null values are not allowed if the null-representation is null");
+ }
+ target.addVariable(this, "text", value);
+
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ // IE6 cannot support multi-classname selectors properly
+ // TODO Can be optimized now that support for I6 is dropped
+ if (readOnly) {
+ addStyleName("v-richtextarea-readonly");
+ } else {
+ removeStyleName("v-richtextarea-readonly");
+ }
+ }
+
+ /**
+ * Selects all text in the rich text area. As a side effect, focuses the
+ * rich text area.
+ *
+ * @since 6.5
+ */
+ public void selectAll() {
+ /*
+ * Set selection range functionality is currently being
+ * planned/developed for GWT RTA. Only selecting all is currently
+ * supported. Consider moving selectAll and other selection related
+ * functions to AbstractTextField at that point to share the
+ * implementation. Some third party components extending
+ * AbstractTextField might however not want to support them.
+ */
+ selectAll = true;
+ focus();
+ requestRepaint();
+ }
+
+ /**
+ * Gets the formatted string value. Sets the field value by using the
+ * assigned Format.
+ *
+ * @return the Formatted value.
+ * @see #setFormat(Format)
+ * @see Format
+ * @deprecated
+ */
+ @Deprecated
+ protected String getFormattedValue() {
+ Object v = getValue();
+ if (v == null) {
+ return null;
+ }
+ return v.toString();
+ }
+
+ @Override
+ public String getValue() {
+ String v = super.getValue();
+ if (format == null || v == null) {
+ return v;
+ }
+ try {
+ return format.format(v);
+ } catch (final IllegalArgumentException e) {
+ return v;
+ }
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // Sets the text
+ if (variables.containsKey("text") && !isReadOnly()) {
+
+ // Only do the setting if the string representation of the value
+ // has been updated
+ String newValue = (String) variables.get("text");
+
+ final String oldValue = getFormattedValue();
+ if (newValue != null
+ && (oldValue == null || isNullSettingAllowed())
+ && newValue.equals(getNullRepresentation())) {
+ newValue = null;
+ }
+ if (newValue != oldValue
+ && (newValue == null || !newValue.equals(oldValue))) {
+ boolean wasModified = isModified();
+ setValue(newValue, true);
+
+ // If the modified status changes, or if we have a formatter,
+ // repaint is needed after all.
+ if (format != null || wasModified != isModified()) {
+ requestRepaint();
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+
+ /**
+ * Gets the null-string representation.
+ *
+ * <p>
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ * </p>
+ *
+ * <p>
+ * The default value is string 'null'.
+ * </p>
+ *
+ * @return the String Textual representation for null strings.
+ * @see TextField#isNullSettingAllowed()
+ */
+ public String getNullRepresentation() {
+ return nullRepresentation;
+ }
+
+ /**
+ * Is setting nulls with null-string representation allowed.
+ *
+ * <p>
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ * </p>
+ *
+ * <p>
+ * By default this setting is false
+ * </p>
+ *
+ * @return boolean Should the null-string represenation be always converted
+ * to null-values.
+ * @see TextField#getNullRepresentation()
+ */
+ public boolean isNullSettingAllowed() {
+ return nullSettingAllowed;
+ }
+
+ /**
+ * Sets the null-string representation.
+ *
+ * <p>
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ * </p>
+ *
+ * <p>
+ * The default value is string 'null'
+ * </p>
+ *
+ * @param nullRepresentation
+ * Textual representation for null strings.
+ * @see TextField#setNullSettingAllowed(boolean)
+ */
+ public void setNullRepresentation(String nullRepresentation) {
+ this.nullRepresentation = nullRepresentation;
+ }
+
+ /**
+ * Sets the null conversion mode.
+ *
+ * <p>
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ * </p>
+ *
+ * <p>
+ * By default this setting is false.
+ * </p>
+ *
+ * @param nullSettingAllowed
+ * Should the null-string represenation be always converted to
+ * null-values.
+ * @see TextField#getNullRepresentation()
+ */
+ public void setNullSettingAllowed(boolean nullSettingAllowed) {
+ this.nullSettingAllowed = nullSettingAllowed;
+ }
+
+ /**
+ * Gets the value formatter of TextField.
+ *
+ * @return the Format used to format the value.
+ * @deprecated replaced by {@link com.vaadin.data.util.PropertyFormatter}
+ */
+ @Deprecated
+ public Format getFormat() {
+ return format;
+ }
+
+ /**
+ * Gets the value formatter of TextField.
+ *
+ * @param format
+ * the Format used to format the value. Null disables the
+ * formatting.
+ * @deprecated replaced by {@link com.vaadin.data.util.PropertyFormatter}
+ */
+ @Deprecated
+ public void setFormat(Format format) {
+ this.format = format;
+ requestRepaint();
+ }
+
+ @Override
+ protected boolean isEmpty() {
+ return super.isEmpty() || getValue().length() == 0;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Root.java b/server/src/com/vaadin/ui/Root.java
new file mode 100644
index 0000000000..bd4842632b
--- /dev/null
+++ b/server/src/com/vaadin/ui/Root.java
@@ -0,0 +1,1227 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
+import com.vaadin.Application;
+import com.vaadin.annotations.EagerInit;
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.ActionManager;
+import com.vaadin.event.MouseEvents.ClickEvent;
+import com.vaadin.event.MouseEvents.ClickListener;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.root.RootServerRpc;
+import com.vaadin.shared.ui.root.RootState;
+import com.vaadin.terminal.Page;
+import com.vaadin.terminal.Page.BrowserWindowResizeEvent;
+import com.vaadin.terminal.Page.BrowserWindowResizeListener;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.WrappedRequest;
+import com.vaadin.terminal.WrappedRequest.BrowserDetails;
+import com.vaadin.terminal.gwt.client.ui.root.VRoot;
+import com.vaadin.ui.Window.CloseListener;
+
+/**
+ * The topmost component in any component hierarchy. There is one root for every
+ * Vaadin instance in a browser window. A root may either represent an entire
+ * browser window (or tab) or some part of a html page where a Vaadin
+ * application is embedded.
+ * <p>
+ * The root is the server side entry point for various client side features that
+ * are not represented as components added to a layout, e.g notifications, sub
+ * windows, and executing javascript in the browser.
+ * </p>
+ * <p>
+ * When a new application instance is needed, typically because the user opens
+ * the application in a browser window,
+ * {@link Application#gerRoot(WrappedRequest)} is invoked to get a root. That
+ * method does by default create a root according to the
+ * {@value Application#ROOT_PARAMETER} parameter from web.xml.
+ * </p>
+ * <p>
+ * After a root has been created by the application, it is initialized using
+ * {@link #init(WrappedRequest)}. This method is intended to be overridden by
+ * the developer to add components to the user interface and initialize
+ * non-component functionality. The component hierarchy is initialized by
+ * passing a {@link ComponentContainer} with the main layout of the view to
+ * {@link #setContent(ComponentContainer)}.
+ * </p>
+ * <p>
+ * If a {@link EagerInit} annotation is present on a class extending
+ * <code>Root</code>, the framework will use a faster initialization method
+ * which will not ensure that {@link BrowserDetails} are present in the
+ * {@link WrappedRequest} passed to the init method.
+ * </p>
+ *
+ * @see #init(WrappedRequest)
+ * @see Application#getRoot(WrappedRequest)
+ *
+ * @since 7.0
+ */
+public abstract class Root extends AbstractComponentContainer implements
+ Action.Container, Action.Notifier, Vaadin6Component {
+
+ /**
+ * Helper class to emulate the main window from Vaadin 6 using roots. This
+ * class should be used in the same way as Window used as a browser level
+ * window in Vaadin 6 with {@link com.vaadin.Application.LegacyApplication}
+ */
+ @Deprecated
+ @EagerInit
+ public static class LegacyWindow extends Root {
+ private String name;
+
+ /**
+ * Create a new legacy window
+ */
+ public LegacyWindow() {
+ super();
+ }
+
+ /**
+ * Creates a new legacy window with the given caption
+ *
+ * @param caption
+ * the caption of the window
+ */
+ public LegacyWindow(String caption) {
+ super(caption);
+ }
+
+ /**
+ * Creates a legacy window with the given caption and content layout
+ *
+ * @param caption
+ * @param content
+ */
+ public LegacyWindow(String caption, ComponentContainer content) {
+ super(caption, content);
+ }
+
+ @Override
+ protected void init(WrappedRequest request) {
+ // Just empty
+ }
+
+ /**
+ * Gets the unique name of the window. The name of the window is used to
+ * uniquely identify it.
+ * <p>
+ * The name also determines the URL that can be used for direct access
+ * to a window. All windows can be accessed through
+ * {@code http://host:port/app/win} where {@code http://host:port/app}
+ * is the application URL (as returned by {@link Application#getURL()}
+ * and {@code win} is the window name.
+ * </p>
+ * <p>
+ * Note! Portlets do not support direct window access through URLs.
+ * </p>
+ *
+ * @return the Name of the Window.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the unique name of the window. The name of the window is used to
+ * uniquely identify it inside the application.
+ * <p>
+ * The name also determines the URL that can be used for direct access
+ * to a window. All windows can be accessed through
+ * {@code http://host:port/app/win} where {@code http://host:port/app}
+ * is the application URL (as returned by {@link Application#getURL()}
+ * and {@code win} is the window name.
+ * </p>
+ * <p>
+ * This method can only be called before the window is added to an
+ * application.
+ * <p>
+ * Note! Portlets do not support direct window access through URLs.
+ * </p>
+ *
+ * @param name
+ * the new name for the window or null if the application
+ * should automatically assign a name to it
+ * @throws IllegalStateException
+ * if the window is attached to an application
+ */
+ public void setName(String name) {
+ this.name = name;
+ // The name can not be changed in application
+ if (getApplication() != null) {
+ throw new IllegalStateException(
+ "Window name can not be changed while "
+ + "the window is in application");
+ }
+
+ }
+
+ /**
+ * Gets the full URL of the window. The returned URL is window specific
+ * and can be used to directly refer to the window.
+ * <p>
+ * Note! This method can not be used for portlets.
+ * </p>
+ *
+ * @return the URL of the window or null if the window is not attached
+ * to an application
+ */
+ public URL getURL() {
+ Application application = getApplication();
+ if (application == null) {
+ return null;
+ }
+
+ try {
+ return new URL(application.getURL(), getName() + "/");
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(
+ "Internal problem getting window URL, please report");
+ }
+ }
+
+ /**
+ * Opens the given resource in this root. The contents of this Root is
+ * replaced by the {@code Resource}.
+ *
+ * @param resource
+ * the resource to show in this root
+ *
+ * @deprecated As of 7.0, use getPage().open instead
+ */
+ @Deprecated
+ public void open(Resource resource) {
+ getPage().open(resource);
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Opens the given resource in a window with the given name.
+ * <p>
+ * The supplied {@code windowName} is used as the target name in a
+ * window.open call in the client. This means that special values such
+ * as "_blank", "_self", "_top", "_parent" have special meaning. An
+ * empty or <code>null</code> window name is also a special case.
+ * </p>
+ * <p>
+ * "", null and "_self" as {@code windowName} all causes the resource to
+ * be opened in the current window, replacing any old contents. For
+ * downloadable content you should avoid "_self" as "_self" causes the
+ * client to skip rendering of any other changes as it considers them
+ * irrelevant (the page will be replaced by the resource). This can
+ * speed up the opening of a resource, but it might also put the client
+ * side into an inconsistent state if the window content is not
+ * completely replaced e.g., if the resource is downloaded instead of
+ * displayed in the browser.
+ * </p>
+ * <p>
+ * "_blank" as {@code windowName} causes the resource to always be
+ * opened in a new window or tab (depends on the browser and browser
+ * settings).
+ * </p>
+ * <p>
+ * "_top" and "_parent" as {@code windowName} works as specified by the
+ * HTML standard.
+ * </p>
+ * <p>
+ * Any other {@code windowName} will open the resource in a window with
+ * that name, either by opening a new window/tab in the browser or by
+ * replacing the contents of an existing window with that name.
+ * </p>
+ *
+ * @param resource
+ * the resource.
+ * @param windowName
+ * the name of the window.
+ * @deprecated As of 7.0, use getPage().open instead
+ */
+ @Deprecated
+ public void open(Resource resource, String windowName) {
+ getPage().open(resource, windowName);
+ }
+
+ /**
+ * Opens the given resource in a window with the given size, border and
+ * name. For more information on the meaning of {@code windowName}, see
+ * {@link #open(Resource, String)}.
+ *
+ * @param resource
+ * the resource.
+ * @param windowName
+ * the name of the window.
+ * @param width
+ * the width of the window in pixels
+ * @param height
+ * the height of the window in pixels
+ * @param border
+ * the border style of the window. See {@link #BORDER_NONE
+ * Window.BORDER_* constants}
+ * @deprecated As of 7.0, use getPage().open instead
+ */
+ @Deprecated
+ public void open(Resource resource, String windowName, int width,
+ int height, int border) {
+ getPage().open(resource, windowName, width, height, border);
+ }
+
+ /**
+ * Adds a new {@link BrowserWindowResizeListener} to this root. The
+ * listener will be notified whenever the browser window within which
+ * this root resides is resized.
+ *
+ * @param resizeListener
+ * the listener to add
+ *
+ * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent)
+ * @see #setResizeLazy(boolean)
+ *
+ * @deprecated As of 7.0, use the similarly named api in Page instead
+ */
+ @Deprecated
+ public void addListener(BrowserWindowResizeListener resizeListener) {
+ getPage().addListener(resizeListener);
+ }
+
+ /**
+ * Removes a {@link BrowserWindowResizeListener} from this root. The
+ * listener will no longer be notified when the browser window is
+ * resized.
+ *
+ * @param resizeListener
+ * the listener to remove
+ * @deprecated As of 7.0, use the similarly named api in Page instead
+ */
+ @Deprecated
+ public void removeListener(BrowserWindowResizeListener resizeListener) {
+ getPage().removeListener(resizeListener);
+ }
+
+ /**
+ * Gets the last known height of the browser window in which this root
+ * resides.
+ *
+ * @return the browser window height in pixels
+ * @deprecated As of 7.0, use the similarly named api in Page instead
+ */
+ @Deprecated
+ public int getBrowserWindowHeight() {
+ return getPage().getBrowserWindowHeight();
+ }
+
+ /**
+ * Gets the last known width of the browser window in which this root
+ * resides.
+ *
+ * @return the browser window width in pixels
+ *
+ * @deprecated As of 7.0, use the similarly named api in Page instead
+ */
+ @Deprecated
+ public int getBrowserWindowWidth() {
+ return getPage().getBrowserWindowWidth();
+ }
+
+ /**
+ * Executes JavaScript in this window.
+ *
+ * <p>
+ * This method allows one to inject javascript from the server to
+ * client. A client implementation is not required to implement this
+ * functionality, but currently all web-based clients do implement this.
+ * </p>
+ *
+ * <p>
+ * Executing javascript this way often leads to cross-browser
+ * compatibility issues and regressions that are hard to resolve. Use of
+ * this method should be avoided and instead it is recommended to create
+ * new widgets with GWT. For more info on creating own, reusable
+ * client-side widgets in Java, read the corresponding chapter in Book
+ * of Vaadin.
+ * </p>
+ *
+ * @param script
+ * JavaScript snippet that will be executed.
+ *
+ * @deprecated as of 7.0, use JavaScript.getCurrent().execute(String)
+ * instead
+ */
+ @Deprecated
+ public void executeJavaScript(String script) {
+ getPage().getJavaScript().execute(script);
+ }
+
+ @Override
+ public void setCaption(String caption) {
+ // Override to provide backwards compatibility
+ getState().setCaption(caption);
+ getPage().setTitle(caption);
+ }
+
+ }
+
+ /**
+ * The application to which this root belongs
+ */
+ private Application application;
+
+ /**
+ * List of windows in this root.
+ */
+ private final LinkedHashSet<Window> windows = new LinkedHashSet<Window>();
+
+ /**
+ * The component that should be scrolled into view after the next repaint.
+ * Null if nothing should be scrolled into view.
+ */
+ private Component scrollIntoView;
+
+ /**
+ * The id of this root, used to find the server side instance of the root
+ * form which a request originates. A negative value indicates that the root
+ * id has not yet been assigned by the Application.
+ *
+ * @see Application#nextRootId
+ */
+ private int rootId = -1;
+
+ /**
+ * Keeps track of the Actions added to this component, and manages the
+ * painting and handling as well.
+ */
+ protected ActionManager actionManager;
+
+ /**
+ * Thread local for keeping track of the current root.
+ */
+ private static final ThreadLocal<Root> currentRoot = new ThreadLocal<Root>();
+
+ /** Identifies the click event */
+ private static final String CLICK_EVENT_ID = VRoot.CLICK_EVENT_ID;
+
+ private ConnectorTracker connectorTracker = new ConnectorTracker(this);
+
+ private Page page = new Page(this);
+
+ private RootServerRpc rpc = new RootServerRpc() {
+ @Override
+ public void click(MouseEventDetails mouseDetails) {
+ fireEvent(new ClickEvent(Root.this, mouseDetails));
+ }
+ };
+
+ /**
+ * Creates a new empty root without a caption. This root will have a
+ * {@link VerticalLayout} with margins enabled as its content.
+ */
+ public Root() {
+ this((ComponentContainer) null);
+ }
+
+ /**
+ * Creates a new root with the given component container as its content.
+ *
+ * @param content
+ * the content container to use as this roots content.
+ *
+ * @see #setContent(ComponentContainer)
+ */
+ public Root(ComponentContainer content) {
+ registerRpc(rpc);
+ setSizeFull();
+ setContent(content);
+ }
+
+ /**
+ * Creates a new empty root with the given caption. This root will have a
+ * {@link VerticalLayout} with margins enabled as its content.
+ *
+ * @param caption
+ * the caption of the root, used as the page title if there's
+ * nothing but the application on the web page
+ *
+ * @see #setCaption(String)
+ */
+ public Root(String caption) {
+ this((ComponentContainer) null);
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new root with the given caption and content.
+ *
+ * @param caption
+ * the caption of the root, used as the page title if there's
+ * nothing but the application on the web page
+ * @param content
+ * the content container to use as this roots content.
+ *
+ * @see #setContent(ComponentContainer)
+ * @see #setCaption(String)
+ */
+ public Root(String caption, ComponentContainer content) {
+ this(content);
+ setCaption(caption);
+ }
+
+ @Override
+ public RootState getState() {
+ return (RootState) super.getState();
+ }
+
+ @Override
+ public Class<? extends RootState> getStateType() {
+ // This is a workaround for a problem with creating the correct state
+ // object during build
+ return RootState.class;
+ }
+
+ /**
+ * Overridden to return a value instead of referring to the parent.
+ *
+ * @return this root
+ *
+ * @see com.vaadin.ui.AbstractComponent#getRoot()
+ */
+ @Override
+ public Root getRoot() {
+ return this;
+ }
+
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Application getApplication() {
+ return application;
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ page.paintContent(target);
+
+ if (scrollIntoView != null) {
+ target.addAttribute("scrollTo", scrollIntoView);
+ scrollIntoView = null;
+ }
+
+ if (pendingFocus != null) {
+ // ensure focused component is still attached to this main window
+ if (pendingFocus.getRoot() == this
+ || (pendingFocus.getRoot() != null && pendingFocus
+ .getRoot().getParent() == this)) {
+ target.addAttribute("focused", pendingFocus);
+ }
+ pendingFocus = null;
+ }
+
+ if (actionManager != null) {
+ actionManager.paintActions(null, target);
+ }
+
+ if (isResizeLazy()) {
+ target.addAttribute(VRoot.RESIZE_LAZY, true);
+ }
+ }
+
+ /**
+ * Fire a click event to all click listeners.
+ *
+ * @param object
+ * The raw "value" of the variable change from the client side.
+ */
+ private void fireClick(Map<String, Object> parameters) {
+ MouseEventDetails mouseDetails = MouseEventDetails
+ .deSerialize((String) parameters.get("mouseDetails"));
+ fireEvent(new ClickEvent(this, mouseDetails));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ if (variables.containsKey(CLICK_EVENT_ID)) {
+ fireClick((Map<String, Object>) variables.get(CLICK_EVENT_ID));
+ }
+
+ // Actions
+ if (actionManager != null) {
+ actionManager.handleActions(variables, this);
+ }
+
+ if (variables.containsKey(VRoot.FRAGMENT_VARIABLE)) {
+ String fragment = (String) variables.get(VRoot.FRAGMENT_VARIABLE);
+ getPage().setFragment(fragment, true);
+ }
+
+ if (variables.containsKey("height") || variables.containsKey("width")) {
+ getPage().setBrowserWindowSize((Integer) variables.get("width"),
+ (Integer) variables.get("height"));
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.ComponentContainer#getComponentIterator()
+ */
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ // TODO could directly create some kind of combined iterator instead of
+ // creating a new ArrayList
+ ArrayList<Component> components = new ArrayList<Component>();
+
+ if (getContent() != null) {
+ components.add(getContent());
+ }
+
+ components.addAll(windows);
+
+ return components.iterator();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.ComponentContainer#getComponentCount()
+ */
+ @Override
+ public int getComponentCount() {
+ return windows.size() + (getContent() == null ? 0 : 1);
+ }
+
+ /**
+ * Sets the application to which this root is assigned. It is not legal to
+ * change the application once it has been set nor to set a
+ * <code>null</code> application.
+ * <p>
+ * This method is mainly intended for internal use by the framework.
+ * </p>
+ *
+ * @param application
+ * the application to set
+ *
+ * @throws IllegalStateException
+ * if the application has already been set
+ *
+ * @see #getApplication()
+ */
+ public void setApplication(Application application) {
+ if ((application == null) == (this.application == null)) {
+ throw new IllegalStateException("Application has already been set");
+ } else {
+ this.application = application;
+ }
+
+ if (application != null) {
+ attach();
+ } else {
+ detach();
+ }
+ }
+
+ /**
+ * Sets the id of this root within its application. The root id is used to
+ * route requests to the right root.
+ * <p>
+ * This method is mainly intended for internal use by the framework.
+ * </p>
+ *
+ * @param rootId
+ * the id of this root
+ *
+ * @throws IllegalStateException
+ * if the root id has already been set
+ *
+ * @see #getRootId()
+ */
+ public void setRootId(int rootId) {
+ if (this.rootId != -1) {
+ throw new IllegalStateException("Root id has already been defined");
+ }
+ this.rootId = rootId;
+ }
+
+ /**
+ * Gets the id of the root, used to identify this root within its
+ * application when processing requests. The root id should be present in
+ * every request to the server that originates from this root.
+ * {@link Application#getRootForRequest(WrappedRequest)} uses this id to
+ * find the route to which the request belongs.
+ *
+ * @return
+ */
+ public int getRootId() {
+ return rootId;
+ }
+
+ /**
+ * Adds a window as a subwindow inside this root. To open a new browser
+ * window or tab, you should instead use {@link open(Resource)} with an url
+ * pointing to this application and ensure
+ * {@link Application#getRoot(WrappedRequest)} returns an appropriate root
+ * for the request.
+ *
+ * @param window
+ * @throws IllegalArgumentException
+ * if the window is already added to an application
+ * @throws NullPointerException
+ * if the given <code>Window</code> is <code>null</code>.
+ */
+ public void addWindow(Window window) throws IllegalArgumentException,
+ NullPointerException {
+
+ if (window == null) {
+ throw new NullPointerException("Argument must not be null");
+ }
+
+ if (window.getApplication() != null) {
+ throw new IllegalArgumentException(
+ "Window is already attached to an application.");
+ }
+
+ attachWindow(window);
+ }
+
+ /**
+ * Helper method to attach a window.
+ *
+ * @param w
+ * the window to add
+ */
+ private void attachWindow(Window w) {
+ windows.add(w);
+ w.setParent(this);
+ requestRepaint();
+ }
+
+ /**
+ * Remove the given subwindow from this root.
+ *
+ * Since Vaadin 6.5, {@link CloseListener}s are called also when explicitly
+ * removing a window by calling this method.
+ *
+ * Since Vaadin 6.5, returns a boolean indicating if the window was removed
+ * or not.
+ *
+ * @param window
+ * Window to be removed.
+ * @return true if the subwindow was removed, false otherwise
+ */
+ public boolean removeWindow(Window window) {
+ if (!windows.remove(window)) {
+ // Window window is not a subwindow of this root.
+ return false;
+ }
+ window.setParent(null);
+ window.fireClose();
+ requestRepaint();
+
+ return true;
+ }
+
+ /**
+ * Gets all the windows added to this root.
+ *
+ * @return an unmodifiable collection of windows
+ */
+ public Collection<Window> getWindows() {
+ return Collections.unmodifiableCollection(windows);
+ }
+
+ @Override
+ public void focus() {
+ super.focus();
+ }
+
+ /**
+ * Component that should be focused after the next repaint. Null if no focus
+ * change should take place.
+ */
+ private Focusable pendingFocus;
+
+ private boolean resizeLazy = false;
+
+ /**
+ * This method is used by Component.Focusable objects to request focus to
+ * themselves. Focus renders must be handled at window level (instead of
+ * Component.Focusable) due we want the last focused component to be focused
+ * in client too. Not the one that is rendered last (the case we'd get if
+ * implemented in Focusable only).
+ *
+ * To focus component from Vaadin application, use Focusable.focus(). See
+ * {@link Focusable}.
+ *
+ * @param focusable
+ * to be focused on next paint
+ */
+ public void setFocusedComponent(Focusable focusable) {
+ pendingFocus = focusable;
+ requestRepaint();
+ }
+
+ /**
+ * Scrolls any component between the component and root to a suitable
+ * position so the component is visible to the user. The given component
+ * must belong to this root.
+ *
+ * @param component
+ * the component to be scrolled into view
+ * @throws IllegalArgumentException
+ * if {@code component} does not belong to this root
+ */
+ public void scrollIntoView(Component component)
+ throws IllegalArgumentException {
+ if (component.getRoot() != this) {
+ throw new IllegalArgumentException(
+ "The component where to scroll must belong to this root.");
+ }
+ scrollIntoView = component;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the content of this root. The content is a component container that
+ * serves as the outermost item of the visual contents of this root.
+ *
+ * @return a component container to use as content
+ *
+ * @see #setContent(ComponentContainer)
+ * @see #createDefaultLayout()
+ */
+ public ComponentContainer getContent() {
+ return (ComponentContainer) getState().getContent();
+ }
+
+ /**
+ * Helper method to create the default content layout that is used if no
+ * content has not been explicitly defined.
+ *
+ * @return a newly created layout
+ */
+ private static VerticalLayout createDefaultLayout() {
+ VerticalLayout layout = new VerticalLayout();
+ layout.setMargin(true);
+ return layout;
+ }
+
+ /**
+ * Sets the content of this root. The content is a component container that
+ * serves as the outermost item of the visual contents of this root. If no
+ * content has been set, a {@link VerticalLayout} with margins enabled will
+ * be used by default - see {@link #createDefaultLayout()}. The content can
+ * also be set in a constructor.
+ *
+ * @return a component container to use as content
+ *
+ * @see #Root(ComponentContainer)
+ * @see #createDefaultLayout()
+ */
+ public void setContent(ComponentContainer content) {
+ if (content == null) {
+ content = createDefaultLayout();
+ }
+
+ if (getState().getContent() != null) {
+ super.removeComponent((Component) getState().getContent());
+ }
+ getState().setContent(content);
+ if (content != null) {
+ super.addComponent(content);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Adds a component to this root. The component is not added directly to the
+ * root, but instead to the content container ({@link #getContent()}).
+ *
+ * @param component
+ * the component to add to this root
+ *
+ * @see #getContent()
+ */
+ @Override
+ public void addComponent(Component component) {
+ getContent().addComponent(component);
+ }
+
+ /**
+ * This implementation removes the component from the content container (
+ * {@link #getContent()}) instead of from the actual root.
+ */
+ @Override
+ public void removeComponent(Component component) {
+ getContent().removeComponent(component);
+ }
+
+ /**
+ * This implementation removes the components from the content container (
+ * {@link #getContent()}) instead of from the actual root.
+ */
+ @Override
+ public void removeAllComponents() {
+ getContent().removeAllComponents();
+ }
+
+ /**
+ * Internal initialization method, should not be overridden. This method is
+ * not declared as final because that would break compatibility with e.g.
+ * CDI.
+ *
+ * @param request
+ * the initialization request
+ */
+ public void doInit(WrappedRequest request) {
+ getPage().init(request);
+
+ // Call the init overridden by the application developer
+ init(request);
+ }
+
+ /**
+ * Initializes this root. This method is intended to be overridden by
+ * subclasses to build the view and configure non-component functionality.
+ * Performing the initialization in a constructor is not suggested as the
+ * state of the root is not properly set up when the constructor is invoked.
+ * <p>
+ * The {@link WrappedRequest} can be used to get information about the
+ * request that caused this root to be created. By default, the
+ * {@link BrowserDetails} will be available in the request. If the browser
+ * details are not required, loading the application in the browser can take
+ * some shortcuts giving a faster initial rendering. This can be indicated
+ * by adding the {@link EagerInit} annotation to the Root class.
+ * </p>
+ *
+ * @param request
+ * the wrapped request that caused this root to be created
+ */
+ protected abstract void init(WrappedRequest request);
+
+ /**
+ * Sets the thread local for the current root. This method is used by the
+ * framework to set the current application whenever a new request is
+ * processed and it is cleared when the request has been processed.
+ * <p>
+ * The application developer can also use this method to define the current
+ * root outside the normal request handling, e.g. when initiating custom
+ * background threads.
+ * </p>
+ *
+ * @param root
+ * the root to register as the current root
+ *
+ * @see #getCurrent()
+ * @see ThreadLocal
+ */
+ public static void setCurrent(Root root) {
+ currentRoot.set(root);
+ }
+
+ /**
+ * Gets the currently used root. The current root is automatically defined
+ * when processing requests to the server. In other cases, (e.g. from
+ * background threads), the current root is not automatically defined.
+ *
+ * @return the current root instance if available, otherwise
+ * <code>null</code>
+ *
+ * @see #setCurrent(Root)
+ */
+ public static Root getCurrent() {
+ return currentRoot.get();
+ }
+
+ public void setScrollTop(int scrollTop) {
+ throw new RuntimeException("Not yet implemented");
+ }
+
+ @Override
+ protected ActionManager getActionManager() {
+ if (actionManager == null) {
+ actionManager = new ActionManager(this);
+ }
+ return actionManager;
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void addAction(
+ T action) {
+ getActionManager().addAction(action);
+ }
+
+ @Override
+ public <T extends Action & com.vaadin.event.Action.Listener> void removeAction(
+ T action) {
+ if (actionManager != null) {
+ actionManager.removeAction(action);
+ }
+ }
+
+ @Override
+ public void addActionHandler(Handler actionHandler) {
+ getActionManager().addActionHandler(actionHandler);
+ }
+
+ @Override
+ public void removeActionHandler(Handler actionHandler) {
+ if (actionManager != null) {
+ actionManager.removeActionHandler(actionHandler);
+ }
+ }
+
+ /**
+ * Should resize operations be lazy, i.e. should there be a delay before
+ * layout sizes are recalculated. Speeds up resize operations in slow UIs
+ * with the penalty of slightly decreased usability.
+ * <p>
+ * Default value: <code>false</code>
+ *
+ * @param resizeLazy
+ * true to use a delay before recalculating sizes, false to
+ * calculate immediately.
+ */
+ public void setResizeLazy(boolean resizeLazy) {
+ this.resizeLazy = resizeLazy;
+ requestRepaint();
+ }
+
+ /**
+ * Checks whether lazy resize is enabled.
+ *
+ * @return <code>true</code> if lazy resize is enabled, <code>false</code>
+ * if lazy resize is not enabled
+ */
+ public boolean isResizeLazy() {
+ return resizeLazy;
+ }
+
+ /**
+ * Add a click listener to the Root. The listener is called whenever the
+ * user clicks inside the Root. Also when the click targets a component
+ * inside the Root, provided the targeted component does not prevent the
+ * click event from propagating.
+ *
+ * Use {@link #removeListener(ClickListener)} to remove the listener.
+ *
+ * @param listener
+ * The listener to add
+ */
+ public void addListener(ClickListener listener) {
+ addListener(CLICK_EVENT_ID, ClickEvent.class, listener,
+ ClickListener.clickMethod);
+ }
+
+ /**
+ * Remove a click listener from the Root. The listener should earlier have
+ * been added using {@link #addListener(ClickListener)}.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeListener(ClickListener listener) {
+ removeListener(CLICK_EVENT_ID, ClickEvent.class, listener);
+ }
+
+ @Override
+ public boolean isConnectorEnabled() {
+ // TODO How can a Root be invisible? What does it mean?
+ return isVisible() && isEnabled();
+ }
+
+ public ConnectorTracker getConnectorTracker() {
+ return connectorTracker;
+ }
+
+ public Page getPage() {
+ return page;
+ }
+
+ /**
+ * Setting the caption of a Root is not supported. To set the title of the
+ * HTML page, use Page.setTitle
+ *
+ * @deprecated as of 7.0.0, use {@link Page#setTitle(String)}
+ */
+ @Override
+ @Deprecated
+ public void setCaption(String caption) {
+ throw new IllegalStateException(
+ "You can not set the title of a Root. To set the title of the HTML page, use Page.setTitle");
+ }
+
+ /**
+ * Shows a notification message on the middle of the root. The message
+ * automatically disappears ("humanized message").
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption is
+ * rendered as html.
+ *
+ * @see #showNotification(Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message
+ *
+ * @deprecated As of 7.0, use Notification.show instead but be aware that
+ * Notification.show does not allow HTML.
+ */
+ @Deprecated
+ public void showNotification(String caption) {
+ Notification notification = new Notification(caption);
+ notification.setHtmlContentAllowed(true);// Backwards compatibility
+ getPage().showNotification(notification);
+ }
+
+ /**
+ * Shows a notification message the root. The position and behavior of the
+ * message depends on the type, which is one of the basic types defined in
+ * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE.
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption is
+ * rendered as html.
+ *
+ * @see #showNotification(Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message
+ * @param type
+ * The message type
+ *
+ * @deprecated As of 7.0, use Notification.show instead but be aware that
+ * Notification.show does not allow HTML.
+ */
+ @Deprecated
+ public void showNotification(String caption, int type) {
+ Notification notification = new Notification(caption, type);
+ notification.setHtmlContentAllowed(true);// Backwards compatibility
+ getPage().showNotification(notification);
+ }
+
+ /**
+ * Shows a notification consisting of a bigger caption and a smaller
+ * description on the middle of the root. The message automatically
+ * disappears ("humanized message").
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption and
+ * description are rendered as html.
+ *
+ * @see #showNotification(Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The caption of the message
+ * @param description
+ * The message description
+ *
+ * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
+ * be aware that HTML by default not allowed.
+ */
+ @Deprecated
+ public void showNotification(String caption, String description) {
+ Notification notification = new Notification(caption, description);
+ notification.setHtmlContentAllowed(true);// Backwards compatibility
+ getPage().showNotification(notification);
+ }
+
+ /**
+ * Shows a notification consisting of a bigger caption and a smaller
+ * description. The position and behavior of the message depends on the
+ * type, which is one of the basic types defined in {@link Notification} ,
+ * for instance Notification.TYPE_WARNING_MESSAGE.
+ *
+ * Care should be taken to to avoid XSS vulnerabilities as the caption and
+ * description are rendered as html.
+ *
+ * @see #showNotification(Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The caption of the message
+ * @param description
+ * The message description
+ * @param type
+ * The message type
+ *
+ * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
+ * be aware that HTML by default not allowed.
+ */
+ @Deprecated
+ public void showNotification(String caption, String description, int type) {
+ Notification notification = new Notification(caption, description, type);
+ notification.setHtmlContentAllowed(true);// Backwards compatibility
+ getPage().showNotification(notification);
+ }
+
+ /**
+ * Shows a notification consisting of a bigger caption and a smaller
+ * description. The position and behavior of the message depends on the
+ * type, which is one of the basic types defined in {@link Notification} ,
+ * for instance Notification.TYPE_WARNING_MESSAGE.
+ *
+ * Care should be taken to avoid XSS vulnerabilities if html content is
+ * allowed.
+ *
+ * @see #showNotification(Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message caption
+ * @param description
+ * The message description
+ * @param type
+ * The type of message
+ * @param htmlContentAllowed
+ * Whether html in the caption and description should be
+ * displayed as html or as plain text
+ *
+ * @deprecated As of 7.0, use new Notification(...).show(Page).
+ */
+ @Deprecated
+ public void showNotification(String caption, String description, int type,
+ boolean htmlContentAllowed) {
+ getPage()
+ .showNotification(
+ new Notification(caption, description, type,
+ htmlContentAllowed));
+ }
+
+ /**
+ * Shows a notification message.
+ *
+ * @see Notification
+ * @see #showNotification(String)
+ * @see #showNotification(String, int)
+ * @see #showNotification(String, String)
+ * @see #showNotification(String, String, int)
+ *
+ * @param notification
+ * The notification message to show
+ *
+ * @deprecated As of 7.0, use Notification.show instead
+ */
+ @Deprecated
+ public void showNotification(Notification notification) {
+ getPage().showNotification(notification);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Select.java b/server/src/com/vaadin/ui/Select.java
new file mode 100644
index 0000000000..f60935c64b
--- /dev/null
+++ b/server/src/com/vaadin/ui/Select.java
@@ -0,0 +1,803 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.event.FieldEvents;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+
+/**
+ * <p>
+ * A class representing a selection of items the user has selected in a UI. The
+ * set of choices is presented as a set of {@link com.vaadin.data.Item}s in a
+ * {@link com.vaadin.data.Container}.
+ * </p>
+ *
+ * <p>
+ * A <code>Select</code> component may be in single- or multiselect mode.
+ * Multiselect mode means that more than one item can be selected
+ * simultaneously.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Select extends AbstractSelect implements AbstractSelect.Filtering,
+ FieldEvents.BlurNotifier, FieldEvents.FocusNotifier {
+
+ /**
+ * Holds value of property pageLength. 0 disables paging.
+ */
+ protected int pageLength = 10;
+
+ private int columns = 0;
+
+ // Current page when the user is 'paging' trough options
+ private int currentPage = -1;
+
+ private int filteringMode = FILTERINGMODE_STARTSWITH;
+
+ private String filterstring;
+ private String prevfilterstring;
+
+ /**
+ * Number of options that pass the filter, excluding the null item if any.
+ */
+ private int filteredSize;
+
+ /**
+ * Cache of filtered options, used only by the in-memory filtering system.
+ */
+ private List<Object> filteredOptions;
+
+ /**
+ * Flag to indicate that request repaint is called by filter request only
+ */
+ private boolean optionRequest;
+
+ /**
+ * True if the container is being filtered temporarily and item set change
+ * notifications should be suppressed.
+ */
+ private boolean filteringContainer;
+
+ /**
+ * Flag to indicate whether to scroll the selected item visible (select the
+ * page on which it is) when opening the popup or not. Only applies to
+ * single select mode.
+ *
+ * This requires finding the index of the item, which can be expensive in
+ * many large lazy loading containers.
+ */
+ private boolean scrollToSelectedItem = true;
+
+ /* Constructors */
+
+ /* Component methods */
+
+ public Select() {
+ super();
+ }
+
+ public Select(String caption, Collection<?> options) {
+ super(caption, options);
+ }
+
+ public Select(String caption, Container dataSource) {
+ super(caption, dataSource);
+ }
+
+ public Select(String caption) {
+ super(caption);
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * the Paint Event.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (isMultiSelect()) {
+ // background compatibility hack. This object shouldn't be used for
+ // multiselect lists anymore (ListSelect instead). This fallbacks to
+ // a simpler paint method in super class.
+ super.paintContent(target);
+ // Fix for #4553
+ target.addAttribute("type", "legacy-multi");
+ return;
+ }
+
+ // clear caption change listeners
+ getCaptionChangeListener().clear();
+
+ // The tab ordering number
+ if (getTabIndex() != 0) {
+ target.addAttribute("tabindex", getTabIndex());
+ }
+
+ // If the field is modified, but not committed, set modified attribute
+ if (isModified()) {
+ target.addAttribute("modified", true);
+ }
+
+ if (isNewItemsAllowed()) {
+ target.addAttribute("allownewitem", true);
+ }
+
+ boolean needNullSelectOption = false;
+ if (isNullSelectionAllowed()) {
+ target.addAttribute("nullselect", true);
+ needNullSelectOption = (getNullSelectionItemId() == null);
+ if (!needNullSelectOption) {
+ target.addAttribute("nullselectitem", true);
+ }
+ }
+
+ // Constructs selected keys array
+ String[] selectedKeys;
+ if (isMultiSelect()) {
+ selectedKeys = new String[((Set<?>) getValue()).size()];
+ } else {
+ selectedKeys = new String[(getValue() == null
+ && getNullSelectionItemId() == null ? 0 : 1)];
+ }
+
+ target.addAttribute("pagelength", pageLength);
+
+ target.addAttribute("filteringmode", getFilteringMode());
+
+ // Paints the options and create array of selected id keys
+ int keyIndex = 0;
+
+ target.startTag("options");
+
+ if (currentPage < 0) {
+ optionRequest = false;
+ currentPage = 0;
+ filterstring = "";
+ }
+
+ boolean nullFilteredOut = filterstring != null
+ && !"".equals(filterstring)
+ && filteringMode != FILTERINGMODE_OFF;
+ // null option is needed and not filtered out, even if not on current
+ // page
+ boolean nullOptionVisible = needNullSelectOption && !nullFilteredOut;
+
+ // first try if using container filters is possible
+ List<?> options = getOptionsWithFilter(nullOptionVisible);
+ if (null == options) {
+ // not able to use container filters, perform explicit in-memory
+ // filtering
+ options = getFilteredOptions();
+ filteredSize = options.size();
+ options = sanitetizeList(options, nullOptionVisible);
+ }
+
+ final boolean paintNullSelection = needNullSelectOption
+ && currentPage == 0 && !nullFilteredOut;
+
+ if (paintNullSelection) {
+ target.startTag("so");
+ target.addAttribute("caption", "");
+ target.addAttribute("key", "");
+ target.endTag("so");
+ }
+
+ final Iterator<?> i = options.iterator();
+ // Paints the available selection options from data source
+
+ while (i.hasNext()) {
+
+ final Object id = i.next();
+
+ if (!isNullSelectionAllowed() && id != null
+ && id.equals(getNullSelectionItemId()) && !isSelected(id)) {
+ continue;
+ }
+
+ // Gets the option attribute values
+ final String key = itemIdMapper.key(id);
+ final String caption = getItemCaption(id);
+ final Resource icon = getItemIcon(id);
+ getCaptionChangeListener().addNotifierForItem(id);
+
+ // Paints the option
+ target.startTag("so");
+ if (icon != null) {
+ target.addAttribute("icon", icon);
+ }
+ target.addAttribute("caption", caption);
+ if (id != null && id.equals(getNullSelectionItemId())) {
+ target.addAttribute("nullselection", true);
+ }
+ target.addAttribute("key", key);
+ if (isSelected(id) && keyIndex < selectedKeys.length) {
+ target.addAttribute("selected", true);
+ selectedKeys[keyIndex++] = key;
+ }
+ target.endTag("so");
+ }
+ target.endTag("options");
+
+ target.addAttribute("totalitems", size()
+ + (needNullSelectOption ? 1 : 0));
+ if (filteredSize > 0 || nullOptionVisible) {
+ target.addAttribute("totalMatches", filteredSize
+ + (nullOptionVisible ? 1 : 0));
+ }
+
+ // Paint variables
+ target.addVariable(this, "selected", selectedKeys);
+ if (isNewItemsAllowed()) {
+ target.addVariable(this, "newitem", "");
+ }
+
+ target.addVariable(this, "filter", filterstring);
+ target.addVariable(this, "page", currentPage);
+
+ currentPage = -1; // current page is always set by client
+
+ optionRequest = true;
+ }
+
+ /**
+ * Returns the filtered options for the current page using a container
+ * filter.
+ *
+ * As a size effect, {@link #filteredSize} is set to the total number of
+ * items passing the filter.
+ *
+ * The current container must be {@link Filterable} and {@link Indexed}, and
+ * the filtering mode must be suitable for container filtering (tested with
+ * {@link #canUseContainerFilter()}).
+ *
+ * Use {@link #getFilteredOptions()} and
+ * {@link #sanitetizeList(List, boolean)} if this is not the case.
+ *
+ * @param needNullSelectOption
+ * @return filtered list of options (may be empty) or null if cannot use
+ * container filters
+ */
+ protected List<?> getOptionsWithFilter(boolean needNullSelectOption) {
+ Container container = getContainerDataSource();
+
+ if (pageLength == 0) {
+ // no paging: return all items
+ filteredSize = container.size();
+ return new ArrayList<Object>(container.getItemIds());
+ }
+
+ if (!(container instanceof Filterable)
+ || !(container instanceof Indexed)
+ || getItemCaptionMode() != ITEM_CAPTION_MODE_PROPERTY) {
+ return null;
+ }
+
+ Filterable filterable = (Filterable) container;
+
+ Filter filter = buildFilter(filterstring, filteringMode);
+
+ // adding and removing filters leads to extraneous item set
+ // change events from the underlying container, but the ComboBox does
+ // not process or propagate them based on the flag filteringContainer
+ if (filter != null) {
+ filteringContainer = true;
+ filterable.addContainerFilter(filter);
+ }
+
+ Indexed indexed = (Indexed) container;
+
+ int indexToEnsureInView = -1;
+
+ // if not an option request (item list when user changes page), go
+ // to page with the selected item after filtering if accepted by
+ // filter
+ Object selection = getValue();
+ if (isScrollToSelectedItem() && !optionRequest && !isMultiSelect()
+ && selection != null) {
+ // ensure proper page
+ indexToEnsureInView = indexed.indexOfId(selection);
+ }
+
+ filteredSize = container.size();
+ currentPage = adjustCurrentPage(currentPage, needNullSelectOption,
+ indexToEnsureInView, filteredSize);
+ int first = getFirstItemIndexOnCurrentPage(needNullSelectOption,
+ filteredSize);
+ int last = getLastItemIndexOnCurrentPage(needNullSelectOption,
+ filteredSize, first);
+
+ List<Object> options = new ArrayList<Object>();
+ for (int i = first; i <= last && i < filteredSize; ++i) {
+ options.add(indexed.getIdByIndex(i));
+ }
+
+ // to the outside, filtering should not be visible
+ if (filter != null) {
+ filterable.removeContainerFilter(filter);
+ filteringContainer = false;
+ }
+
+ return options;
+ }
+
+ /**
+ * Constructs a filter instance to use when using a Filterable container in
+ * the <code>ITEM_CAPTION_MODE_PROPERTY</code> mode.
+ *
+ * Note that the client side implementation expects the filter string to
+ * apply to the item caption string it sees, so changing the behavior of
+ * this method can cause problems.
+ *
+ * @param filterString
+ * @param filteringMode
+ * @return
+ */
+ protected Filter buildFilter(String filterString, int filteringMode) {
+ Filter filter = null;
+
+ if (null != filterString && !"".equals(filterString)) {
+ switch (filteringMode) {
+ case FILTERINGMODE_OFF:
+ break;
+ case FILTERINGMODE_STARTSWITH:
+ filter = new SimpleStringFilter(getItemCaptionPropertyId(),
+ filterString, true, true);
+ break;
+ case FILTERINGMODE_CONTAINS:
+ filter = new SimpleStringFilter(getItemCaptionPropertyId(),
+ filterString, true, false);
+ break;
+ }
+ }
+ return filter;
+ }
+
+ @Override
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ if (!filteringContainer) {
+ super.containerItemSetChange(event);
+ }
+ }
+
+ /**
+ * Makes correct sublist of given list of options.
+ *
+ * If paint is not an option request (affected by page or filter change),
+ * page will be the one where possible selection exists.
+ *
+ * Detects proper first and last item in list to return right page of
+ * options. Also, if the current page is beyond the end of the list, it will
+ * be adjusted.
+ *
+ * @param options
+ * @param needNullSelectOption
+ * flag to indicate if nullselect option needs to be taken into
+ * consideration
+ */
+ private List<?> sanitetizeList(List<?> options, boolean needNullSelectOption) {
+
+ if (pageLength != 0 && options.size() > pageLength) {
+
+ int indexToEnsureInView = -1;
+
+ // if not an option request (item list when user changes page), go
+ // to page with the selected item after filtering if accepted by
+ // filter
+ Object selection = getValue();
+ if (isScrollToSelectedItem() && !optionRequest && !isMultiSelect()
+ && selection != null) {
+ // ensure proper page
+ indexToEnsureInView = options.indexOf(selection);
+ }
+
+ int size = options.size();
+ currentPage = adjustCurrentPage(currentPage, needNullSelectOption,
+ indexToEnsureInView, size);
+ int first = getFirstItemIndexOnCurrentPage(needNullSelectOption,
+ size);
+ int last = getLastItemIndexOnCurrentPage(needNullSelectOption,
+ size, first);
+ return options.subList(first, last + 1);
+ } else {
+ return options;
+ }
+ }
+
+ /**
+ * Returns the index of the first item on the current page. The index is to
+ * the underlying (possibly filtered) contents. The null item, if any, does
+ * not have an index but takes up a slot on the first page.
+ *
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ * @return first item to show on the UI (index to the filtered list of
+ * options, not taking the null item into consideration if any)
+ */
+ private int getFirstItemIndexOnCurrentPage(boolean needNullSelectOption,
+ int size) {
+ // Not all options are visible, find out which ones are on the
+ // current "page".
+ int first = currentPage * pageLength;
+ if (needNullSelectOption && currentPage > 0) {
+ first--;
+ }
+ return first;
+ }
+
+ /**
+ * Returns the index of the last item on the current page. The index is to
+ * the underlying (possibly filtered) contents. If needNullSelectOption is
+ * true, the null item takes up the first slot on the first page,
+ * effectively reducing the first page size by one.
+ *
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ * @param first
+ * index in the filtered view of the first item of the page
+ * @return index in the filtered view of the last item on the page
+ */
+ private int getLastItemIndexOnCurrentPage(boolean needNullSelectOption,
+ int size, int first) {
+ // page length usable for non-null items
+ int effectivePageLength = pageLength
+ - (needNullSelectOption && (currentPage == 0) ? 1 : 0);
+ return Math.min(size - 1, first + effectivePageLength - 1);
+ }
+
+ /**
+ * Adjusts the index of the current page if necessary: make sure the current
+ * page is not after the end of the contents, and optionally go to the page
+ * containg a specific item. There are no side effects but the adjusted page
+ * index is returned.
+ *
+ * @param page
+ * page number to use as the starting point
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param indexToEnsureInView
+ * index of an item that should be included on the page (in the
+ * data set, not counting the null item if any), -1 for none
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ */
+ private int adjustCurrentPage(int page, boolean needNullSelectOption,
+ int indexToEnsureInView, int size) {
+ if (indexToEnsureInView != -1) {
+ int newPage = (indexToEnsureInView + (needNullSelectOption ? 1 : 0))
+ / pageLength;
+ page = newPage;
+ }
+ // adjust the current page if beyond the end of the list
+ if (page * pageLength > size) {
+ page = (size + (needNullSelectOption ? 1 : 0)) / pageLength;
+ }
+ return page;
+ }
+
+ /**
+ * Filters the options in memory and returns the full filtered list.
+ *
+ * This can be less efficient than using container filters, so use
+ * {@link #getOptionsWithFilter(boolean)} if possible (filterable container
+ * and suitable item caption mode etc.).
+ *
+ * @return
+ */
+ protected List<?> getFilteredOptions() {
+ if (null == filterstring || "".equals(filterstring)
+ || FILTERINGMODE_OFF == filteringMode) {
+ prevfilterstring = null;
+ filteredOptions = new LinkedList<Object>(getItemIds());
+ return filteredOptions;
+ }
+
+ if (filterstring.equals(prevfilterstring)) {
+ return filteredOptions;
+ }
+
+ Collection<?> items;
+ if (prevfilterstring != null
+ && filterstring.startsWith(prevfilterstring)) {
+ items = filteredOptions;
+ } else {
+ items = getItemIds();
+ }
+ prevfilterstring = filterstring;
+
+ filteredOptions = new LinkedList<Object>();
+ for (final Iterator<?> it = items.iterator(); it.hasNext();) {
+ final Object itemId = it.next();
+ String caption = getItemCaption(itemId);
+ if (caption == null || caption.equals("")) {
+ continue;
+ } else {
+ caption = caption.toLowerCase();
+ }
+ switch (filteringMode) {
+ case FILTERINGMODE_CONTAINS:
+ if (caption.indexOf(filterstring) > -1) {
+ filteredOptions.add(itemId);
+ }
+ break;
+ case FILTERINGMODE_STARTSWITH:
+ default:
+ if (caption.startsWith(filterstring)) {
+ filteredOptions.add(itemId);
+ }
+ break;
+ }
+ }
+
+ return filteredOptions;
+ }
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // Not calling super.changeVariables due the history of select
+ // component hierarchy
+
+ // Selection change
+ if (variables.containsKey("selected")) {
+ final String[] ka = (String[]) variables.get("selected");
+
+ if (isMultiSelect()) {
+ // Multiselect mode
+
+ // TODO Optimize by adding repaintNotNeeded whan applicaple
+
+ // Converts the key-array to id-set
+ final LinkedList<Object> s = new LinkedList<Object>();
+ for (int i = 0; i < ka.length; i++) {
+ final Object id = itemIdMapper.get(ka[i]);
+ if (id != null && containsId(id)) {
+ s.add(id);
+ }
+ }
+
+ // Limits the deselection to the set of visible items
+ // (non-visible items can not be deselected)
+ final Collection<?> visible = getVisibleItemIds();
+ if (visible != null) {
+ @SuppressWarnings("unchecked")
+ Set<Object> newsel = (Set<Object>) getValue();
+ if (newsel == null) {
+ newsel = new HashSet<Object>();
+ } else {
+ newsel = new HashSet<Object>(newsel);
+ }
+ newsel.removeAll(visible);
+ newsel.addAll(s);
+ setValue(newsel, true);
+ }
+ } else {
+ // Single select mode
+ if (ka.length == 0) {
+
+ // Allows deselection only if the deselected item is visible
+ final Object current = getValue();
+ final Collection<?> visible = getVisibleItemIds();
+ if (visible != null && visible.contains(current)) {
+ setValue(null, true);
+ }
+ } else {
+ final Object id = itemIdMapper.get(ka[0]);
+ if (id != null && id.equals(getNullSelectionItemId())) {
+ setValue(null, true);
+ } else {
+ setValue(id, true);
+ }
+ }
+ }
+ }
+
+ String newFilter;
+ if ((newFilter = (String) variables.get("filter")) != null) {
+ // this is a filter request
+ currentPage = ((Integer) variables.get("page")).intValue();
+ filterstring = newFilter;
+ if (filterstring != null) {
+ filterstring = filterstring.toLowerCase();
+ }
+ optionRepaint();
+ } else if (isNewItemsAllowed()) {
+ // New option entered (and it is allowed)
+ final String newitem = (String) variables.get("newitem");
+ if (newitem != null && newitem.length() > 0) {
+ getNewItemHandler().addNewItem(newitem);
+ // rebuild list
+ filterstring = null;
+ prevfilterstring = null;
+ }
+ }
+
+ if (variables.containsKey(FocusEvent.EVENT_ID)) {
+ fireEvent(new FocusEvent(this));
+ }
+ if (variables.containsKey(BlurEvent.EVENT_ID)) {
+ fireEvent(new BlurEvent(this));
+ }
+
+ }
+
+ @Override
+ public void requestRepaint() {
+ super.requestRepaint();
+ optionRequest = false;
+ prevfilterstring = filterstring;
+ filterstring = null;
+ }
+
+ private void optionRepaint() {
+ super.requestRepaint();
+ }
+
+ @Override
+ public void setFilteringMode(int filteringMode) {
+ this.filteringMode = filteringMode;
+ }
+
+ @Override
+ public int getFilteringMode() {
+ return filteringMode;
+ }
+
+ /**
+ * Note, one should use more generic setWidth(String) method instead of
+ * this. This now days actually converts columns to width with em css unit.
+ *
+ * Sets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ *
+ * @deprecated
+ *
+ * @param columns
+ * the number of columns to set.
+ */
+ @Deprecated
+ public void setColumns(int columns) {
+ if (columns < 0) {
+ columns = 0;
+ }
+ if (this.columns != columns) {
+ this.columns = columns;
+ setWidth(columns, Select.UNITS_EM);
+ requestRepaint();
+ }
+ }
+
+ /**
+ * @deprecated see setter function
+ * @return
+ */
+ @Deprecated
+ public int getColumns() {
+ return columns;
+ }
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+
+ }
+
+ /**
+ * @deprecated use {@link ListSelect}, {@link OptionGroup} or
+ * {@link TwinColSelect} instead
+ * @see com.vaadin.ui.AbstractSelect#setMultiSelect(boolean)
+ * @throws UnsupportedOperationException
+ * if trying to activate multiselect mode
+ */
+ @Deprecated
+ @Override
+ public void setMultiSelect(boolean multiSelect) {
+ if (multiSelect) {
+ throw new UnsupportedOperationException("Multiselect not supported");
+ }
+ }
+
+ /**
+ * @deprecated use {@link ListSelect}, {@link OptionGroup} or
+ * {@link TwinColSelect} instead
+ *
+ * @see com.vaadin.ui.AbstractSelect#isMultiSelect()
+ */
+ @Deprecated
+ @Override
+ public boolean isMultiSelect() {
+ return super.isMultiSelect();
+ }
+
+ /**
+ * Sets whether to scroll the selected item visible (directly open the page
+ * on which it is) when opening the combo box popup or not. Only applies to
+ * single select mode.
+ *
+ * This requires finding the index of the item, which can be expensive in
+ * many large lazy loading containers.
+ *
+ * @param scrollToSelectedItem
+ * true to find the page with the selected item when opening the
+ * selection popup
+ */
+ public void setScrollToSelectedItem(boolean scrollToSelectedItem) {
+ this.scrollToSelectedItem = scrollToSelectedItem;
+ }
+
+ /**
+ * Returns true if the select should find the page with the selected item
+ * when opening the popup (single select combo box only).
+ *
+ * @see #setScrollToSelectedItem(boolean)
+ *
+ * @return true if the page with the selected item will be shown when
+ * opening the popup
+ */
+ public boolean isScrollToSelectedItem() {
+ return scrollToSelectedItem;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Slider.java b/server/src/com/vaadin/ui/Slider.java
new file mode 100644
index 0000000000..94afe4e2bd
--- /dev/null
+++ b/server/src/com/vaadin/ui/Slider.java
@@ -0,0 +1,372 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Map;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+
+/**
+ * A component for selecting a numerical value within a range.
+ *
+ * Example code: <code>
+ * class MyPlayer extends CustomComponent implements ValueChangeListener {
+ *
+ * Label volumeIndicator = new Label();
+ * Slider slider;
+ *
+ * public MyPlayer() {
+ * VerticalLayout vl = new VerticalLayout();
+ * setCompositionRoot(vl);
+ * slider = new Slider("Volume", 0, 100);
+ * slider.setImmediate(true);
+ * slider.setValue(new Double(50));
+ * vl.addComponent(slider);
+ * vl.addComponent(volumeIndicator);
+ * volumeIndicator.setValue("Current volume:" + 50.0);
+ * slider.addListener(this);
+ *
+ * }
+ *
+ * public void setVolume(double d) {
+ * volumeIndicator.setValue("Current volume: " + d);
+ * }
+ *
+ * public void valueChange(ValueChangeEvent event) {
+ * Double d = (Double) event.getProperty().getValue();
+ * setVolume(d.doubleValue());
+ * }
+ * }
+ *
+ * </code>
+ *
+ * @author Vaadin Ltd.
+ */
+public class Slider extends AbstractField<Double> implements Vaadin6Component {
+
+ public static final int ORIENTATION_HORIZONTAL = 0;
+
+ public static final int ORIENTATION_VERTICAL = 1;
+
+ /** Minimum value of slider */
+ private double min = 0;
+
+ /** Maximum value of slider */
+ private double max = 100;
+
+ /**
+ * Resolution, how many digits are considered relevant after the decimal
+ * point. Must be a non-negative value
+ */
+ private int resolution = 0;
+
+ /**
+ * Slider orientation (horizontal/vertical), defaults .
+ */
+ private int orientation = ORIENTATION_HORIZONTAL;
+
+ /**
+ * Default slider constructor. Sets all values to defaults and the slide
+ * handle at minimum value.
+ *
+ */
+ public Slider() {
+ super();
+ super.setValue(new Double(min));
+ }
+
+ /**
+ * Create a new slider with the caption given as parameter.
+ *
+ * The range of the slider is set to 0-100 and only integer values are
+ * allowed.
+ *
+ * @param caption
+ * The caption for this slider (e.g. "Volume").
+ */
+ public Slider(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Create a new slider with the given range and resolution.
+ *
+ * @param min
+ * The minimum value of the slider
+ * @param max
+ * The maximum value of the slider
+ * @param resolution
+ * The number of digits after the decimal point.
+ */
+ public Slider(double min, double max, int resolution) {
+ this();
+ setMin(min);
+ setMax(max);
+ setResolution(resolution);
+ }
+
+ /**
+ * Create a new slider with the given range that only allows integer values.
+ *
+ * @param min
+ * The minimum value of the slider
+ * @param max
+ * The maximum value of the slider
+ */
+ public Slider(int min, int max) {
+ this();
+ setMin(min);
+ setMax(max);
+ setResolution(0);
+ }
+
+ /**
+ * Create a new slider with the given caption and range that only allows
+ * integer values.
+ *
+ * @param caption
+ * The caption for the slider
+ * @param min
+ * The minimum value of the slider
+ * @param max
+ * The maximum value of the slider
+ */
+ public Slider(String caption, int min, int max) {
+ this(min, max);
+ setCaption(caption);
+ }
+
+ /**
+ * Gets the maximum slider value
+ *
+ * @return the largest value the slider can have
+ */
+ public double getMax() {
+ return max;
+ }
+
+ /**
+ * Set the maximum slider value. If the current value of the slider is
+ * larger than this, the value is set to the new maximum.
+ *
+ * @param max
+ * The new maximum slider value
+ */
+ public void setMax(double max) {
+ this.max = max;
+ if (getValue() > max) {
+ setValue(max);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Gets the minimum slider value
+ *
+ * @return the smallest value the slider can have
+ */
+ public double getMin() {
+ return min;
+ }
+
+ /**
+ * Set the minimum slider value. If the current value of the slider is
+ * smaller than this, the value is set to the new minimum.
+ *
+ * @param max
+ * The new minimum slider value
+ */
+ public void setMin(double min) {
+ this.min = min;
+ if (getValue() < min) {
+ setValue(min);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Get the current orientation of the slider (horizontal or vertical).
+ *
+ * @return {@link #ORIENTATION_HORIZONTAL} or
+ * {@link #ORIENTATION_HORIZONTAL}
+ */
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Set the orientation of the slider.
+ *
+ * @param The
+ * new orientation, either {@link #ORIENTATION_HORIZONTAL} or
+ * {@link #ORIENTATION_VERTICAL}
+ */
+ public void setOrientation(int orientation) {
+ this.orientation = orientation;
+ requestRepaint();
+ }
+
+ /**
+ * Get the current resolution of the slider. The resolution is the number of
+ * digits after the decimal point.
+ *
+ * @return resolution
+ */
+ public int getResolution() {
+ return resolution;
+ }
+
+ /**
+ * Set a new resolution for the slider. The resolution is the number of
+ * digits after the decimal point.
+ *
+ * @param resolution
+ */
+ public void setResolution(int resolution) {
+ if (resolution < 0) {
+ return;
+ }
+ this.resolution = resolution;
+ requestRepaint();
+ }
+
+ /**
+ * Sets the value of the slider.
+ *
+ * @param value
+ * The new value of the slider.
+ * @param repaintIsNotNeeded
+ * If true, client-side is not requested to repaint itself.
+ * @throws ValueOutOfBoundsException
+ * If the given value is not inside the range of the slider.
+ * @see #setMin(double) {@link #setMax(double)}
+ */
+ @Override
+ protected void setValue(Double value, boolean repaintIsNotNeeded) {
+ final double v = value.doubleValue();
+ double newValue;
+ if (resolution > 0) {
+ // Round up to resolution
+ newValue = (int) (v * Math.pow(10, resolution));
+ newValue = newValue / Math.pow(10, resolution);
+ if (min > newValue || max < newValue) {
+ throw new ValueOutOfBoundsException(value);
+ }
+ } else {
+ newValue = (int) v;
+ if (min > newValue || max < newValue) {
+ throw new ValueOutOfBoundsException(value);
+ }
+ }
+ super.setValue(newValue, repaintIsNotNeeded);
+ }
+
+ @Override
+ public void setValue(Object newFieldValue)
+ throws com.vaadin.data.Property.ReadOnlyException {
+ if (newFieldValue != null && newFieldValue instanceof Number
+ && !(newFieldValue instanceof Double)) {
+ // Support setting all types of Numbers
+ newFieldValue = ((Number) newFieldValue).doubleValue();
+ }
+
+ super.setValue(newFieldValue);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ target.addAttribute("min", min);
+ if (max > min) {
+ target.addAttribute("max", max);
+ } else {
+ target.addAttribute("max", min);
+ }
+ target.addAttribute("resolution", resolution);
+
+ if (resolution > 0) {
+ target.addVariable(this, "value", getValue().doubleValue());
+ } else {
+ target.addVariable(this, "value", getValue().intValue());
+ }
+
+ if (orientation == ORIENTATION_VERTICAL) {
+ target.addAttribute("vertical", true);
+ }
+
+ }
+
+ /**
+ * Invoked when the value of a variable has changed. Slider listeners are
+ * notified if the slider value has changed.
+ *
+ * @param source
+ * @param variables
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ if (variables.containsKey("value")) {
+ final Object value = variables.get("value");
+ final Double newValue = new Double(value.toString());
+ if (newValue != null && newValue != getValue()
+ && !newValue.equals(getValue())) {
+ try {
+ setValue(newValue, true);
+ } catch (final ValueOutOfBoundsException e) {
+ // Convert to nearest bound
+ double out = e.getValue().doubleValue();
+ if (out < min) {
+ out = min;
+ }
+ if (out > max) {
+ out = max;
+ }
+ super.setValue(new Double(out), false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Thrown when the value of the slider is about to be set to a value that is
+ * outside the valid range of the slider.
+ *
+ * @author Vaadin Ltd.
+ *
+ */
+ public class ValueOutOfBoundsException extends RuntimeException {
+
+ private final Double value;
+
+ /**
+ * Constructs an <code>ValueOutOfBoundsException</code> with the
+ * specified detail message.
+ *
+ * @param valueOutOfBounds
+ */
+ public ValueOutOfBoundsException(Double valueOutOfBounds) {
+ value = valueOutOfBounds;
+ }
+
+ /**
+ * Gets the value that is outside the valid range of the slider.
+ *
+ * @return the value that is out of bounds
+ */
+ public Double getValue() {
+ return value;
+ }
+
+ }
+
+ @Override
+ public Class<Double> getType() {
+ return Double.class;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java
new file mode 100644
index 0000000000..c52e9394c0
--- /dev/null
+++ b/server/src/com/vaadin/ui/TabSheet.java
@@ -0,0 +1,1328 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.BlurNotifier;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.event.FieldEvents.FocusNotifier;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.KeyMapper;
+import com.vaadin.terminal.LegacyPaint;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.tabsheet.TabsheetBaseConnector;
+import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheet;
+import com.vaadin.ui.Component.Focusable;
+import com.vaadin.ui.themes.Reindeer;
+import com.vaadin.ui.themes.Runo;
+
+/**
+ * TabSheet component.
+ *
+ * Tabs are typically identified by the component contained on the tab (see
+ * {@link ComponentContainer}), and tab metadata (including caption, icon,
+ * visibility, enabledness, closability etc.) is kept in separate {@link Tab}
+ * instances.
+ *
+ * Tabs added with {@link #addComponent(Component)} get the caption and the icon
+ * of the component at the time when the component is created, and these are not
+ * automatically updated after tab creation.
+ *
+ * A tab sheet can have multiple tab selection listeners and one tab close
+ * handler ({@link CloseHandler}), which by default removes the tab from the
+ * TabSheet.
+ *
+ * The {@link TabSheet} can be styled with the .v-tabsheet, .v-tabsheet-tabs and
+ * .v-tabsheet-content styles. Themes may also have pre-defined variations of
+ * the tab sheet presentation, such as {@link Reindeer#TABSHEET_BORDERLESS},
+ * {@link Runo#TABSHEET_SMALL} and several other styles in {@link Reindeer}.
+ *
+ * The current implementation does not load the tabs to the UI before the first
+ * time they are shown, but this may change in future releases.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public class TabSheet extends AbstractComponentContainer implements Focusable,
+ FocusNotifier, BlurNotifier, Vaadin6Component {
+
+ /**
+ * List of component tabs (tab contents). In addition to being on this list,
+ * there is a {@link Tab} object in tabs for each tab with meta-data about
+ * the tab.
+ */
+ private final ArrayList<Component> components = new ArrayList<Component>();
+
+ /**
+ * Map containing information related to the tabs (caption, icon etc).
+ */
+ private final HashMap<Component, Tab> tabs = new HashMap<Component, Tab>();
+
+ /**
+ * Selected tab content component.
+ */
+ private Component selected = null;
+
+ /**
+ * Mapper between server-side component instances (tab contents) and keys
+ * given to the client that identify tabs.
+ */
+ private final KeyMapper<Component> keyMapper = new KeyMapper<Component>();
+
+ /**
+ * When true, the tab selection area is not displayed to the user.
+ */
+ private boolean tabsHidden;
+
+ /**
+ * Handler to be called when a tab is closed.
+ */
+ private CloseHandler closeHandler;
+
+ private int tabIndex;
+
+ /**
+ * Constructs a new Tabsheet. Tabsheet is immediate by default, and the
+ * default close handler removes the tab being closed.
+ */
+ public TabSheet() {
+ super();
+ // expand horizontally by default
+ setWidth(100, UNITS_PERCENTAGE);
+ setImmediate(true);
+ setCloseHandler(new CloseHandler() {
+
+ @Override
+ public void onTabClose(TabSheet tabsheet, Component c) {
+ tabsheet.removeComponent(c);
+ }
+ });
+ }
+
+ /**
+ * Gets the component container iterator for going through all the
+ * components (tab contents).
+ *
+ * @return the unmodifiable Iterator of the tab content components
+ */
+
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ return Collections.unmodifiableList(components).iterator();
+ }
+
+ /**
+ * Gets the number of contained components (tabs). Consistent with the
+ * iterator returned by {@link #getComponentIterator()}.
+ *
+ * @return the number of contained components
+ */
+
+ @Override
+ public int getComponentCount() {
+ return components.size();
+ }
+
+ /**
+ * Removes a component and its corresponding tab.
+ *
+ * If the tab was selected, the first eligible (visible and enabled)
+ * remaining tab is selected.
+ *
+ * @param c
+ * the component to be removed.
+ */
+
+ @Override
+ public void removeComponent(Component c) {
+ if (c != null && components.contains(c)) {
+ super.removeComponent(c);
+ keyMapper.remove(c);
+ components.remove(c);
+ tabs.remove(c);
+ if (c.equals(selected)) {
+ if (components.isEmpty()) {
+ setSelected(null);
+ } else {
+ // select the first enabled and visible tab, if any
+ updateSelection();
+ fireSelectedTabChange();
+ }
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Removes a {@link Tab} and the component associated with it, as previously
+ * added with {@link #addTab(Component)},
+ * {@link #addTab(Component, String, Resource)} or
+ * {@link #addComponent(Component)}.
+ * <p>
+ * If the tab was selected, the first eligible (visible and enabled)
+ * remaining tab is selected.
+ * </p>
+ *
+ * @see #addTab(Component)
+ * @see #addTab(Component, String, Resource)
+ * @see #addComponent(Component)
+ * @see #removeComponent(Component)
+ * @param tab
+ * the Tab to remove
+ */
+ public void removeTab(Tab tab) {
+ removeComponent(tab.getComponent());
+ }
+
+ /**
+ * Adds a new tab into TabSheet. Component caption and icon are copied to
+ * the tab metadata at creation time.
+ *
+ * @see #addTab(Component)
+ *
+ * @param c
+ * the component to be added.
+ */
+
+ @Override
+ public void addComponent(Component c) {
+ addTab(c);
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * The first tab added to a tab sheet is automatically selected and a tab
+ * selection event is fired.
+ *
+ * If the component is already present in the tab sheet, changes its caption
+ * and returns the corresponding (old) tab, preserving other tab metadata.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param caption
+ * the caption to be set for the component and used rendered in
+ * tab bar
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, String caption) {
+ return addTab(c, caption, null);
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * The first tab added to a tab sheet is automatically selected and a tab
+ * selection event is fired.
+ *
+ * If the component is already present in the tab sheet, changes its caption
+ * and icon and returns the corresponding (old) tab, preserving other tab
+ * metadata.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param caption
+ * the caption to be set for the component and used rendered in
+ * tab bar
+ * @param icon
+ * the icon to be set for the component and used rendered in tab
+ * bar
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, String caption, Resource icon) {
+ return addTab(c, caption, icon, components.size());
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * The first tab added to a tab sheet is automatically selected and a tab
+ * selection event is fired.
+ *
+ * If the component is already present in the tab sheet, changes its caption
+ * and icon and returns the corresponding (old) tab, preserving other tab
+ * metadata like the position.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param caption
+ * the caption to be set for the component and used rendered in
+ * tab bar
+ * @param icon
+ * the icon to be set for the component and used rendered in tab
+ * bar
+ * @param position
+ * the position at where the the tab should be added.
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, String caption, Resource icon, int position) {
+ if (c == null) {
+ return null;
+ } else if (tabs.containsKey(c)) {
+ Tab tab = tabs.get(c);
+ tab.setCaption(caption);
+ tab.setIcon(icon);
+ return tab;
+ } else {
+ components.add(position, c);
+
+ Tab tab = new TabSheetTabImpl(caption, icon);
+
+ tabs.put(c, tab);
+ if (selected == null) {
+ setSelected(c);
+ fireSelectedTabChange();
+ }
+ super.addComponent(c);
+ requestRepaint();
+ return tab;
+ }
+ }
+
+ /**
+ * Adds a new tab into TabSheet. Component caption and icon are copied to
+ * the tab metadata at creation time.
+ *
+ * If the tab sheet already contains the component, its tab is returned.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c) {
+ return addTab(c, components.size());
+ }
+
+ /**
+ * Adds a new tab into TabSheet. Component caption and icon are copied to
+ * the tab metadata at creation time.
+ *
+ * If the tab sheet already contains the component, its tab is returned.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param position
+ * The position where the tab should be added
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, int position) {
+ if (c == null) {
+ return null;
+ } else if (tabs.containsKey(c)) {
+ return tabs.get(c);
+ } else {
+ return addTab(c, c.getCaption(), c.getIcon(), position);
+ }
+ }
+
+ /**
+ * Moves all components from another container to this container. The
+ * components are removed from the other container.
+ *
+ * If the source container is a {@link TabSheet}, component captions and
+ * icons are copied from it.
+ *
+ * @param source
+ * the container components are removed from.
+ */
+
+ @Override
+ public void moveComponentsFrom(ComponentContainer source) {
+ for (final Iterator<Component> i = source.getComponentIterator(); i
+ .hasNext();) {
+ final Component c = i.next();
+ String caption = null;
+ Resource icon = null;
+ if (TabSheet.class.isAssignableFrom(source.getClass())) {
+ caption = ((TabSheet) source).getTabCaption(c);
+ icon = ((TabSheet) source).getTabIcon(c);
+ }
+ source.removeComponent(c);
+ addTab(c, caption, icon);
+
+ }
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * the paint target
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (areTabsHidden()) {
+ target.addAttribute("hidetabs", true);
+ }
+
+ if (tabIndex != 0) {
+ target.addAttribute("tabindex", tabIndex);
+ }
+
+ target.startTag("tabs");
+
+ for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) {
+ final Component component = i.next();
+
+ Tab tab = tabs.get(component);
+
+ target.startTag("tab");
+ if (!tab.isEnabled() && tab.isVisible()) {
+ target.addAttribute(
+ TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED, true);
+ }
+
+ if (!tab.isVisible()) {
+ target.addAttribute("hidden", true);
+ }
+
+ if (tab.isClosable()) {
+ target.addAttribute("closable", true);
+ }
+
+ // tab icon, caption and description, but used via
+ // VCaption.updateCaption(uidl)
+ final Resource icon = tab.getIcon();
+ if (icon != null) {
+ target.addAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ICON,
+ icon);
+ }
+ final String caption = tab.getCaption();
+ if (caption != null && caption.length() > 0) {
+ target.addAttribute(
+ TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION, caption);
+ }
+ ErrorMessage tabError = tab.getComponentError();
+ if (tabError != null) {
+ target.addAttribute(
+ TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE,
+ tabError.getFormattedHtmlMessage());
+ }
+ final String description = tab.getDescription();
+ if (description != null) {
+ target.addAttribute(
+ TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION,
+ description);
+ }
+
+ final String styleName = tab.getStyleName();
+ if (styleName != null && styleName.length() != 0) {
+ target.addAttribute(VTabsheet.TAB_STYLE_NAME, styleName);
+ }
+
+ target.addAttribute("key", keyMapper.key(component));
+ if (component.equals(selected)) {
+ target.addAttribute("selected", true);
+ LegacyPaint.paint(component, target);
+ }
+ target.endTag("tab");
+ }
+
+ target.endTag("tabs");
+
+ if (selected != null) {
+ target.addVariable(this, "selected", keyMapper.key(selected));
+ }
+
+ }
+
+ /**
+ * Are the tab selection parts ("tabs") hidden.
+ *
+ * @return true if the tabs are hidden in the UI
+ */
+ public boolean areTabsHidden() {
+ return tabsHidden;
+ }
+
+ /**
+ * Hides or shows the tab selection parts ("tabs").
+ *
+ * @param tabsHidden
+ * true if the tabs should be hidden
+ */
+ public void hideTabs(boolean tabsHidden) {
+ this.tabsHidden = tabsHidden;
+ requestRepaint();
+ }
+
+ /**
+ * Gets tab caption. The tab is identified by the tab content component.
+ *
+ * @param c
+ * the component in the tab
+ * @deprecated Use {@link #getTab(Component)} and {@link Tab#getCaption()}
+ * instead.
+ */
+ @Deprecated
+ public String getTabCaption(Component c) {
+ Tab info = tabs.get(c);
+ if (info == null) {
+ return "";
+ } else {
+ return info.getCaption();
+ }
+ }
+
+ /**
+ * Sets tab caption. The tab is identified by the tab content component.
+ *
+ * @param c
+ * the component in the tab
+ * @param caption
+ * the caption to set.
+ * @deprecated Use {@link #getTab(Component)} and
+ * {@link Tab#setCaption(String)} instead.
+ */
+ @Deprecated
+ public void setTabCaption(Component c, String caption) {
+ Tab info = tabs.get(c);
+ if (info != null) {
+ info.setCaption(caption);
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the icon for a tab. The tab is identified by the tab content
+ * component.
+ *
+ * @param c
+ * the component in the tab
+ * @deprecated Use {@link #getTab(Component)} and {@link Tab#getIcon()}
+ * instead.
+ */
+ @Deprecated
+ public Resource getTabIcon(Component c) {
+ Tab info = tabs.get(c);
+ if (info == null) {
+ return null;
+ } else {
+ return info.getIcon();
+ }
+ }
+
+ /**
+ * Sets icon for the given component. The tab is identified by the tab
+ * content component.
+ *
+ * @param c
+ * the component in the tab
+ * @param icon
+ * the icon to set
+ * @deprecated Use {@link #getTab(Component)} and
+ * {@link Tab#setIcon(Resource)} instead.
+ */
+ @Deprecated
+ public void setTabIcon(Component c, Resource icon) {
+ Tab info = tabs.get(c);
+ if (info != null) {
+ info.setIcon(icon);
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
+ * object can be used for setting caption,icon, etc for the tab.
+ *
+ * @param c
+ * the component
+ * @return The tab instance associated with the given component, or null if
+ * the tabsheet does not contain the component.
+ */
+ public Tab getTab(Component c) {
+ return tabs.get(c);
+ }
+
+ /**
+ * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
+ * object can be used for setting caption,icon, etc for the tab.
+ *
+ * @param position
+ * the position of the tab
+ * @return The tab in the given position, or null if the position is out of
+ * bounds.
+ */
+ public Tab getTab(int position) {
+ if (position >= 0 && position < getComponentCount()) {
+ return getTab(components.get(position));
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the selected tab. The tab is identified by the tab content
+ * component. Does nothing if the tabsheet doesn't contain the component.
+ *
+ * @param c
+ */
+ public void setSelectedTab(Component c) {
+ if (c != null && components.contains(c) && !c.equals(selected)) {
+ setSelected(c);
+ updateSelection();
+ fireSelectedTabChange();
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets the selected tab in the TabSheet. Ensures that the selected tab is
+ * repainted if needed.
+ *
+ * @param c
+ * The new selection or null for no selection
+ */
+ private void setSelected(Component c) {
+ selected = c;
+ // Repaint of the selected component is needed as only the selected
+ // component is communicated to the client. Otherwise this will be a
+ // "cached" update even though the client knows nothing about the
+ // connector
+ if (selected instanceof ComponentContainer) {
+ ((ComponentContainer) selected).requestRepaintAll();
+ } else if (selected instanceof Table) {
+ // Workaround until there's a generic way of telling a component
+ // that there is no client side state to rely on. See #8642
+ ((Table) selected).refreshRowCache();
+ } else if (selected != null) {
+ selected.requestRepaint();
+ }
+
+ }
+
+ /**
+ * Sets the selected tab. The tab is identified by the corresponding
+ * {@link Tab Tab} instance. Does nothing if the tabsheet doesn't contain
+ * the given tab.
+ *
+ * @param tab
+ */
+ public void setSelectedTab(Tab tab) {
+ if (tab != null) {
+ setSelectedTab(tab.getComponent());
+ }
+ }
+
+ /**
+ * Sets the selected tab, identified by its position. Does nothing if the
+ * position is out of bounds.
+ *
+ * @param position
+ */
+ public void setSelectedTab(int position) {
+ setSelectedTab(getTab(position));
+ }
+
+ /**
+ * Checks if the current selection is valid, and updates the selection if
+ * the previously selected component is not visible and enabled. The first
+ * visible and enabled tab is selected if the current selection is empty or
+ * invalid.
+ *
+ * This method does not fire tab change events, but the caller should do so
+ * if appropriate.
+ *
+ * @return true if selection was changed, false otherwise
+ */
+ private boolean updateSelection() {
+ Component originalSelection = selected;
+ for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) {
+ final Component component = i.next();
+
+ Tab tab = tabs.get(component);
+
+ /*
+ * If we have no selection, if the current selection is invisible or
+ * if the current selection is disabled (but the whole component is
+ * not) we select this tab instead
+ */
+ Tab selectedTabInfo = null;
+ if (selected != null) {
+ selectedTabInfo = tabs.get(selected);
+ }
+ if (selected == null || selectedTabInfo == null
+ || !selectedTabInfo.isVisible()
+ || !selectedTabInfo.isEnabled()) {
+
+ // The current selection is not valid so we need to change
+ // it
+ if (tab.isEnabled() && tab.isVisible()) {
+ setSelected(component);
+ break;
+ } else {
+ /*
+ * The current selection is not valid but this tab cannot be
+ * selected either.
+ */
+ setSelected(null);
+ }
+ }
+ }
+ return originalSelection != selected;
+ }
+
+ /**
+ * Gets the selected tab content component.
+ *
+ * @return the selected tab contents
+ */
+ public Component getSelectedTab() {
+ return selected;
+ }
+
+ // inherits javadoc
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ if (variables.containsKey("selected")) {
+ setSelectedTab(keyMapper.get((String) variables.get("selected")));
+ }
+ if (variables.containsKey("close")) {
+ final Component tab = keyMapper
+ .get((String) variables.get("close"));
+ if (tab != null) {
+ closeHandler.onTabClose(this, tab);
+ }
+ }
+ if (variables.containsKey(FocusEvent.EVENT_ID)) {
+ fireEvent(new FocusEvent(this));
+ }
+ if (variables.containsKey(BlurEvent.EVENT_ID)) {
+ fireEvent(new BlurEvent(this));
+ }
+ }
+
+ /**
+ * Replaces a component (tab content) with another. This can be used to
+ * change tab contents or to rearrange tabs. The tab position and some
+ * metadata are preserved when moving components within the same
+ * {@link TabSheet}.
+ *
+ * If the oldComponent is not present in the tab sheet, the new one is added
+ * at the end.
+ *
+ * If the oldComponent is already in the tab sheet but the newComponent
+ * isn't, the old tab is replaced with a new one, and the caption and icon
+ * of the old one are copied to the new tab.
+ *
+ * If both old and new components are present, their positions are swapped.
+ *
+ * {@inheritDoc}
+ */
+
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ if (selected == oldComponent) {
+ // keep selection w/o selectedTabChange event
+ setSelected(newComponent);
+ }
+
+ Tab newTab = tabs.get(newComponent);
+ Tab oldTab = tabs.get(oldComponent);
+
+ // Gets the locations
+ int oldLocation = -1;
+ int newLocation = -1;
+ int location = 0;
+ for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
+ final Component component = i.next();
+
+ if (component == oldComponent) {
+ oldLocation = location;
+ }
+ if (component == newComponent) {
+ newLocation = location;
+ }
+
+ location++;
+ }
+
+ if (oldLocation == -1) {
+ addComponent(newComponent);
+ } else if (newLocation == -1) {
+ removeComponent(oldComponent);
+ newTab = addTab(newComponent, oldLocation);
+ // Copy all relevant metadata to the new tab (#8793)
+ // TODO Should reuse the old tab instance instead?
+ copyTabMetadata(oldTab, newTab);
+ } else {
+ components.set(oldLocation, newComponent);
+ components.set(newLocation, oldComponent);
+
+ // Tab associations are not changed, but metadata is swapped between
+ // the instances
+ // TODO Should reassociate the instances instead?
+ Tab tmp = new TabSheetTabImpl(null, null);
+ copyTabMetadata(newTab, tmp);
+ copyTabMetadata(oldTab, newTab);
+ copyTabMetadata(tmp, oldTab);
+
+ requestRepaint();
+ }
+
+ }
+
+ /* Click event */
+
+ private static final Method SELECTED_TAB_CHANGE_METHOD;
+ static {
+ try {
+ SELECTED_TAB_CHANGE_METHOD = SelectedTabChangeListener.class
+ .getDeclaredMethod("selectedTabChange",
+ new Class[] { SelectedTabChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in TabSheet");
+ }
+ }
+
+ /**
+ * Selected tab change event. This event is sent when the selected (shown)
+ * tab in the tab sheet is changed.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class SelectedTabChangeEvent extends Component.Event {
+
+ /**
+ * New instance of selected tab change event
+ *
+ * @param source
+ * the Source of the event.
+ */
+ public SelectedTabChangeEvent(Component source) {
+ super(source);
+ }
+
+ /**
+ * TabSheet where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public TabSheet getTabSheet() {
+ return (TabSheet) getSource();
+ }
+ }
+
+ /**
+ * Selected tab change event listener. The listener is called whenever
+ * another tab is selected, including when adding the first tab to a
+ * tabsheet.
+ *
+ * @author Vaadin Ltd.
+ *
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface SelectedTabChangeListener extends Serializable {
+
+ /**
+ * Selected (shown) tab in tab sheet has has been changed.
+ *
+ * @param event
+ * the selected tab change event.
+ */
+ public void selectedTabChange(SelectedTabChangeEvent event);
+ }
+
+ /**
+ * Adds a tab selection listener
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(SelectedTabChangeListener listener) {
+ addListener(SelectedTabChangeEvent.class, listener,
+ SELECTED_TAB_CHANGE_METHOD);
+ }
+
+ /**
+ * Removes a tab selection listener
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(SelectedTabChangeListener listener) {
+ removeListener(SelectedTabChangeEvent.class, listener,
+ SELECTED_TAB_CHANGE_METHOD);
+ }
+
+ /**
+ * Sends an event that the currently selected tab has changed.
+ */
+ protected void fireSelectedTabChange() {
+ fireEvent(new SelectedTabChangeEvent(this));
+ }
+
+ /**
+ * Tab meta-data for a component in a {@link TabSheet}.
+ *
+ * The meta-data includes the tab caption, icon, visibility and enabledness,
+ * closability, description (tooltip) and an optional component error shown
+ * in the tab.
+ *
+ * Tabs are identified by the component contained on them in most cases, and
+ * the meta-data can be obtained with {@link TabSheet#getTab(Component)}.
+ */
+ public interface Tab extends Serializable {
+ /**
+ * Returns the visible status for the tab. An invisible tab is not shown
+ * in the tab bar and cannot be selected.
+ *
+ * @return true for visible, false for hidden
+ */
+ public boolean isVisible();
+
+ /**
+ * Sets the visible status for the tab. An invisible tab is not shown in
+ * the tab bar and cannot be selected, selection is changed
+ * automatically when there is an attempt to select an invisible tab.
+ *
+ * @param visible
+ * true for visible, false for hidden
+ */
+ public void setVisible(boolean visible);
+
+ /**
+ * Returns the closability status for the tab.
+ *
+ * @return true if the tab is allowed to be closed by the end user,
+ * false for not allowing closing
+ */
+ public boolean isClosable();
+
+ /**
+ * Sets the closability status for the tab. A closable tab can be closed
+ * by the user through the user interface. This also controls if a close
+ * button is shown to the user or not.
+ * <p>
+ * Note! Currently only supported by TabSheet, not Accordion.
+ * </p>
+ *
+ * @param visible
+ * true if the end user is allowed to close the tab, false
+ * for not allowing to close. Should default to false.
+ */
+ public void setClosable(boolean closable);
+
+ /**
+ * Returns the enabled status for the tab. A disabled tab is shown as
+ * such in the tab bar and cannot be selected.
+ *
+ * @return true for enabled, false for disabled
+ */
+ public boolean isEnabled();
+
+ /**
+ * Sets the enabled status for the tab. A disabled tab is shown as such
+ * in the tab bar and cannot be selected.
+ *
+ * @param enabled
+ * true for enabled, false for disabled
+ */
+ public void setEnabled(boolean enabled);
+
+ /**
+ * Sets the caption for the tab.
+ *
+ * @param caption
+ * the caption to set
+ */
+ public void setCaption(String caption);
+
+ /**
+ * Gets the caption for the tab.
+ */
+ public String getCaption();
+
+ /**
+ * Gets the icon for the tab.
+ */
+ public Resource getIcon();
+
+ /**
+ * Sets the icon for the tab.
+ *
+ * @param icon
+ * the icon to set
+ */
+ public void setIcon(Resource icon);
+
+ /**
+ * Gets the description for the tab. The description can be used to
+ * briefly describe the state of the tab to the user, and is typically
+ * shown as a tooltip when hovering over the tab.
+ *
+ * @return the description for the tab
+ */
+ public String getDescription();
+
+ /**
+ * Sets the description for the tab. The description can be used to
+ * briefly describe the state of the tab to the user, and is typically
+ * shown as a tooltip when hovering over the tab.
+ *
+ * @param description
+ * the new description string for the tab.
+ */
+ public void setDescription(String description);
+
+ /**
+ * Sets an error indicator to be shown in the tab. This can be used e.g.
+ * to communicate to the user that there is a problem in the contents of
+ * the tab.
+ *
+ * @see AbstractComponent#setComponentError(ErrorMessage)
+ *
+ * @param componentError
+ * error message or null for none
+ */
+ public void setComponentError(ErrorMessage componentError);
+
+ /**
+ * Gets the current error message shown for the tab.
+ *
+ * TODO currently not sent to the client
+ *
+ * @see AbstractComponent#setComponentError(ErrorMessage)
+ */
+ public ErrorMessage getComponentError();
+
+ /**
+ * Get the component related to the Tab
+ */
+ public Component getComponent();
+
+ /**
+ * Sets a style name for the tab. The style name will be rendered as a
+ * HTML class name, which can be used in a CSS definition.
+ *
+ * <pre>
+ * Tab tab = tabsheet.addTab(tabContent, &quot;Tab text&quot;);
+ * tab.setStyleName(&quot;mystyle&quot;);
+ * </pre>
+ * <p>
+ * The used style name will be prefixed with "
+ * {@code v-tabsheet-tabitemcell-}". For example, if you give a tab the
+ * style "{@code mystyle}", the tab will get a "
+ * {@code v-tabsheet-tabitemcell-mystyle}" style. You could then style
+ * the component with:
+ * </p>
+ *
+ * <pre>
+ * .v-tabsheet-tabitemcell-mystyle {font-style: italic;}
+ * </pre>
+ *
+ * <p>
+ * This method will trigger a {@link RepaintRequestEvent} on the
+ * TabSheet to which the Tab belongs.
+ * </p>
+ *
+ * @param styleName
+ * the new style to be set for tab
+ * @see #getStyleName()
+ */
+ public void setStyleName(String styleName);
+
+ /**
+ * Gets the user-defined CSS style name of the tab. Built-in style names
+ * defined in Vaadin or GWT are not returned.
+ *
+ * @return the style name or of the tab
+ * @see #setStyleName(String)
+ */
+ public String getStyleName();
+ }
+
+ /**
+ * TabSheet's implementation of {@link Tab} - tab metadata.
+ */
+ public class TabSheetTabImpl implements Tab {
+
+ private String caption = "";
+ private Resource icon = null;
+ private boolean enabled = true;
+ private boolean visible = true;
+ private boolean closable = false;
+ private String description = null;
+ private ErrorMessage componentError = null;
+ private String styleName;
+
+ public TabSheetTabImpl(String caption, Resource icon) {
+ if (caption == null) {
+ caption = "";
+ }
+ this.caption = caption;
+ this.icon = icon;
+ }
+
+ /**
+ * Returns the tab caption. Can never be null.
+ */
+
+ @Override
+ public String getCaption() {
+ return caption;
+ }
+
+ @Override
+ public void setCaption(String caption) {
+ this.caption = caption;
+ requestRepaint();
+ }
+
+ @Override
+ public Resource getIcon() {
+ return icon;
+ }
+
+ @Override
+ public void setIcon(Resource icon) {
+ this.icon = icon;
+ requestRepaint();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ if (updateSelection()) {
+ fireSelectedTabChange();
+ }
+ requestRepaint();
+ }
+
+ @Override
+ public boolean isVisible() {
+ return visible;
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ if (updateSelection()) {
+ fireSelectedTabChange();
+ }
+ requestRepaint();
+ }
+
+ @Override
+ public boolean isClosable() {
+ return closable;
+ }
+
+ @Override
+ public void setClosable(boolean closable) {
+ this.closable = closable;
+ requestRepaint();
+ }
+
+ public void close() {
+
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public void setDescription(String description) {
+ this.description = description;
+ requestRepaint();
+ }
+
+ @Override
+ public ErrorMessage getComponentError() {
+ return componentError;
+ }
+
+ @Override
+ public void setComponentError(ErrorMessage componentError) {
+ this.componentError = componentError;
+ requestRepaint();
+ }
+
+ @Override
+ public Component getComponent() {
+ for (Map.Entry<Component, Tab> entry : tabs.entrySet()) {
+ if (entry.getValue() == this) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void setStyleName(String styleName) {
+ this.styleName = styleName;
+ requestRepaint();
+ }
+
+ @Override
+ public String getStyleName() {
+ return styleName;
+ }
+ }
+
+ /**
+ * CloseHandler is used to process tab closing events. Default behavior is
+ * to remove the tab from the TabSheet.
+ *
+ * @author Jouni Koivuviita / Vaadin Ltd.
+ * @since 6.2.0
+ *
+ */
+ public interface CloseHandler extends Serializable {
+
+ /**
+ * Called when a user has pressed the close icon of a tab in the client
+ * side widget.
+ *
+ * @param tabsheet
+ * the TabSheet to which the tab belongs to
+ * @param tabContent
+ * the component that corresponds to the tab whose close
+ * button was clicked
+ */
+ void onTabClose(final TabSheet tabsheet, final Component tabContent);
+ }
+
+ /**
+ * Provide a custom {@link CloseHandler} for this TabSheet if you wish to
+ * perform some additional tasks when a user clicks on a tabs close button,
+ * e.g. show a confirmation dialogue before removing the tab.
+ *
+ * To remove the tab, if you provide your own close handler, you must call
+ * {@link #removeComponent(Component)} yourself.
+ *
+ * The default CloseHandler for TabSheet will only remove the tab.
+ *
+ * @param handler
+ */
+ public void setCloseHandler(CloseHandler handler) {
+ closeHandler = handler;
+ }
+
+ /**
+ * Sets the position of the tab.
+ *
+ * @param tab
+ * The tab
+ * @param position
+ * The new position of the tab
+ */
+ public void setTabPosition(Tab tab, int position) {
+ int oldPosition = getTabPosition(tab);
+ components.remove(oldPosition);
+ components.add(position, tab.getComponent());
+ requestRepaint();
+ }
+
+ /**
+ * Gets the position of the tab
+ *
+ * @param tab
+ * The tab
+ * @return
+ */
+ public int getTabPosition(Tab tab) {
+ return components.indexOf(tab.getComponent());
+ }
+
+ @Override
+ public void focus() {
+ super.focus();
+ }
+
+ @Override
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ @Override
+ public void setTabIndex(int tabIndex) {
+ this.tabIndex = tabIndex;
+ requestRepaint();
+ }
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+
+ }
+
+ @Override
+ public boolean isComponentVisible(Component childComponent) {
+ return childComponent == getSelectedTab();
+ }
+
+ /**
+ * Copies properties from one Tab to another.
+ *
+ * @param from
+ * The tab whose data to copy.
+ * @param to
+ * The tab to which copy the data.
+ */
+ private static void copyTabMetadata(Tab from, Tab to) {
+ to.setCaption(from.getCaption());
+ to.setIcon(from.getIcon());
+ to.setDescription(from.getDescription());
+ to.setVisible(from.isVisible());
+ to.setEnabled(from.isEnabled());
+ to.setClosable(from.isClosable());
+ to.setStyleName(from.getStyleName());
+ to.setComponentError(from.getComponentError());
+ }
+}
diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java
new file mode 100644
index 0000000000..39b7fb7473
--- /dev/null
+++ b/server/src/com/vaadin/ui/Table.java
@@ -0,0 +1,5449 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.ContainerOrderedWrapper;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.ConverterUtil;
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.DataBoundTransferable;
+import com.vaadin.event.ItemClickEvent;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.event.ItemClickEvent.ItemClickNotifier;
+import com.vaadin.event.MouseEvents.ClickEvent;
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DragSource;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.terminal.KeyMapper;
+import com.vaadin.terminal.LegacyPaint;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.gwt.client.ui.table.VScrollTable;
+
+/**
+ * <p>
+ * <code>Table</code> is used for representing data or components in a pageable
+ * and selectable table.
+ * </p>
+ *
+ * <p>
+ * Scalability of the Table is largely dictated by the container. A table does
+ * not have a limit for the number of items and is just as fast with hundreds of
+ * thousands of items as with just a few. The current GWT implementation with
+ * scrolling however limits the number of rows to around 500000, depending on
+ * the browser and the pixel height of rows.
+ * </p>
+ *
+ * <p>
+ * Components in a Table will not have their caption nor icon rendered.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings({ "deprecation" })
+public class Table extends AbstractSelect implements Action.Container,
+ Container.Ordered, Container.Sortable, ItemClickNotifier, DragSource,
+ DropTarget, HasComponents {
+
+ private transient Logger logger = null;
+
+ /**
+ * Modes that Table support as drag sourse.
+ */
+ public enum TableDragMode {
+ /**
+ * Table does not start drag and drop events. HTM5 style events started
+ * by browser may still happen.
+ */
+ NONE,
+ /**
+ * Table starts drag with a one row only.
+ */
+ ROW,
+ /**
+ * Table drags selected rows, if drag starts on a selected rows. Else it
+ * starts like in ROW mode. Note, that in Transferable there will still
+ * be only the row on which the drag started, other dragged rows need to
+ * be checked from the source Table.
+ */
+ MULTIROW
+ }
+
+ protected static final int CELL_KEY = 0;
+
+ protected static final int CELL_HEADER = 1;
+
+ protected static final int CELL_ICON = 2;
+
+ protected static final int CELL_ITEMID = 3;
+
+ protected static final int CELL_GENERATED_ROW = 4;
+
+ protected static final int CELL_FIRSTCOL = 5;
+
+ public enum Align {
+ /**
+ * Left column alignment. <b>This is the default behaviour. </b>
+ */
+ LEFT("b"),
+
+ /**
+ * Center column alignment.
+ */
+ CENTER("c"),
+
+ /**
+ * Right column alignment.
+ */
+ RIGHT("e");
+
+ private String alignment;
+
+ private Align(String alignment) {
+ this.alignment = alignment;
+ }
+
+ @Override
+ public String toString() {
+ return alignment;
+ }
+
+ public Align convertStringToAlign(String string) {
+ if (string == null) {
+ return null;
+ }
+ if (string.equals("b")) {
+ return Align.LEFT;
+ } else if (string.equals("c")) {
+ return Align.CENTER;
+ } else if (string.equals("e")) {
+ return Align.RIGHT;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * @deprecated from 7.0, use {@link Align#LEFT} instead
+ */
+ @Deprecated
+ public static final Align ALIGN_LEFT = Align.LEFT;
+
+ /**
+ * @deprecated from 7.0, use {@link Align#CENTER} instead
+ */
+ @Deprecated
+ public static final Align ALIGN_CENTER = Align.CENTER;
+
+ /**
+ * @deprecated from 7.0, use {@link Align#RIGHT} instead
+ */
+ @Deprecated
+ public static final Align ALIGN_RIGHT = Align.RIGHT;
+
+ public enum ColumnHeaderMode {
+ /**
+ * Column headers are hidden.
+ */
+ HIDDEN,
+ /**
+ * Property ID:s are used as column headers.
+ */
+ ID,
+ /**
+ * Column headers are explicitly specified with
+ * {@link #setColumnHeaders(String[])}.
+ */
+ EXPLICIT,
+ /**
+ * Column headers are explicitly specified with
+ * {@link #setColumnHeaders(String[])}. If a header is not specified for
+ * a given property, its property id is used instead.
+ * <p>
+ * <b>This is the default behavior. </b>
+ */
+ EXPLICIT_DEFAULTS_ID
+ }
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#HIDDEN} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_HIDDEN = ColumnHeaderMode.HIDDEN;
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#ID} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_ID = ColumnHeaderMode.ID;
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#EXPLICIT} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT = ColumnHeaderMode.EXPLICIT;
+
+ /**
+ * @deprecated from 7.0, use {@link ColumnHeaderMode#EXPLICIT_DEFAULTS_ID}
+ * instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = ColumnHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ public enum RowHeaderMode {
+ /**
+ * Row caption mode: The row headers are hidden. <b>This is the default
+ * mode. </b>
+ */
+ HIDDEN(null),
+ /**
+ * Row caption mode: Items Id-objects toString is used as row caption.
+ */
+ ID(ItemCaptionMode.ID),
+ /**
+ * Row caption mode: Item-objects toString is used as row caption.
+ */
+ ITEM(ItemCaptionMode.ITEM),
+ /**
+ * Row caption mode: Index of the item is used as item caption. The
+ * index mode can only be used with the containers implementing the
+ * {@link com.vaadin.data.Container.Indexed} interface.
+ */
+ INDEX(ItemCaptionMode.INDEX),
+ /**
+ * Row caption mode: Item captions are explicitly specified, but if the
+ * caption is missing, the item id objects <code>toString()</code> is
+ * used instead.
+ */
+ EXPLICIT_DEFAULTS_ID(ItemCaptionMode.EXPLICIT_DEFAULTS_ID),
+ /**
+ * Row caption mode: Item captions are explicitly specified.
+ */
+ EXPLICIT(ItemCaptionMode.EXPLICIT),
+ /**
+ * Row caption mode: Only icons are shown, the captions are hidden.
+ */
+ ICON_ONLY(ItemCaptionMode.ICON_ONLY),
+ /**
+ * Row caption mode: Item captions are read from property specified with
+ * {@link #setItemCaptionPropertyId(Object)}.
+ */
+ PROPERTY(ItemCaptionMode.PROPERTY);
+
+ ItemCaptionMode mode;
+
+ private RowHeaderMode(ItemCaptionMode mode) {
+ this.mode = mode;
+ }
+
+ public ItemCaptionMode getItemCaptionMode() {
+ return mode;
+ }
+ }
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#HIDDEN} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_HIDDEN = RowHeaderMode.HIDDEN;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#ID} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ID = RowHeaderMode.ID;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#ITEM} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ITEM = RowHeaderMode.ITEM;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#INDEX} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_INDEX = RowHeaderMode.INDEX;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#EXPLICIT_DEFAULTS_ID}
+ * instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = RowHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#EXPLICIT} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT = RowHeaderMode.EXPLICIT;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#ICON_ONLY} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ICON_ONLY = RowHeaderMode.ICON_ONLY;
+
+ /**
+ * @deprecated from 7.0, use {@link RowHeaderMode#PROPERTY} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_PROPERTY = RowHeaderMode.PROPERTY;
+
+ /**
+ * The default rate that table caches rows for smooth scrolling.
+ */
+ private static final double CACHE_RATE_DEFAULT = 2;
+
+ private static final String ROW_HEADER_COLUMN_KEY = "0";
+ private static final Object ROW_HEADER_FAKE_PROPERTY_ID = new UniqueSerializable() {
+ };
+
+ /* Private table extensions to Select */
+
+ /**
+ * True if column collapsing is allowed.
+ */
+ private boolean columnCollapsingAllowed = false;
+
+ /**
+ * True if reordering of columns is allowed on the client side.
+ */
+ private boolean columnReorderingAllowed = false;
+
+ /**
+ * Keymapper for column ids.
+ */
+ private final KeyMapper<Object> columnIdMap = new KeyMapper<Object>();
+
+ /**
+ * Holds visible column propertyIds - in order.
+ */
+ private LinkedList<Object> visibleColumns = new LinkedList<Object>();
+
+ /**
+ * Holds noncollapsible columns.
+ */
+ private HashSet<Object> noncollapsibleColumns = new HashSet<Object>();
+
+ /**
+ * Holds propertyIds of currently collapsed columns.
+ */
+ private final HashSet<Object> collapsedColumns = new HashSet<Object>();
+
+ /**
+ * Holds headers for visible columns (by propertyId).
+ */
+ private final HashMap<Object, String> columnHeaders = new HashMap<Object, String>();
+
+ /**
+ * Holds footers for visible columns (by propertyId).
+ */
+ private final HashMap<Object, String> columnFooters = new HashMap<Object, String>();
+
+ /**
+ * Holds icons for visible columns (by propertyId).
+ */
+ private final HashMap<Object, Resource> columnIcons = new HashMap<Object, Resource>();
+
+ /**
+ * Holds alignments for visible columns (by propertyId).
+ */
+ private HashMap<Object, Align> columnAlignments = new HashMap<Object, Align>();
+
+ /**
+ * Holds column widths in pixels (Integer) or expand ratios (Float) for
+ * visible columns (by propertyId).
+ */
+ private final HashMap<Object, Object> columnWidths = new HashMap<Object, Object>();
+
+ /**
+ * Holds column generators
+ */
+ private final HashMap<Object, ColumnGenerator> columnGenerators = new LinkedHashMap<Object, ColumnGenerator>();
+
+ /**
+ * Holds value of property pageLength. 0 disables paging.
+ */
+ private int pageLength = 15;
+
+ /**
+ * Id the first item on the current page.
+ */
+ private Object currentPageFirstItemId = null;
+
+ /**
+ * Index of the first item on the current page.
+ */
+ private int currentPageFirstItemIndex = 0;
+
+ /**
+ * Holds value of property selectable.
+ */
+ private boolean selectable = false;
+
+ /**
+ * Holds value of property columnHeaderMode.
+ */
+ private ColumnHeaderMode columnHeaderMode = ColumnHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * Holds value of property rowHeaderMode.
+ */
+ private RowHeaderMode rowHeaderMode = RowHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * Should the Table footer be visible?
+ */
+ private boolean columnFootersVisible = false;
+
+ /**
+ * Page contents buffer used in buffered mode.
+ */
+ private Object[][] pageBuffer = null;
+
+ /**
+ * Set of properties listened - the list is kept to release the listeners
+ * later.
+ */
+ private HashSet<Property<?>> listenedProperties = null;
+
+ /**
+ * Set of visible components - the is used for needsRepaint calculation.
+ */
+ private HashSet<Component> visibleComponents = null;
+
+ /**
+ * List of action handlers.
+ */
+ private LinkedList<Handler> actionHandlers = null;
+
+ /**
+ * Action mapper.
+ */
+ private KeyMapper<Action> actionMapper = null;
+
+ /**
+ * Table cell editor factory.
+ */
+ private TableFieldFactory fieldFactory = DefaultFieldFactory.get();
+
+ /**
+ * Is table editable.
+ */
+ private boolean editable = false;
+
+ /**
+ * Current sorting direction.
+ */
+ private boolean sortAscending = true;
+
+ /**
+ * Currently table is sorted on this propertyId.
+ */
+ private Object sortContainerPropertyId = null;
+
+ /**
+ * Is table sorting by the user enabled.
+ */
+ private boolean sortEnabled = true;
+
+ /**
+ * Number of rows explicitly requested by the client to be painted on next
+ * paint. This is -1 if no request by the client is made. Painting the
+ * component will automatically reset this to -1.
+ */
+ private int reqRowsToPaint = -1;
+
+ /**
+ * Index of the first rows explicitly requested by the client to be painted.
+ * This is -1 if no request by the client is made. Painting the component
+ * will automatically reset this to -1.
+ */
+ private int reqFirstRowToPaint = -1;
+
+ private int firstToBeRenderedInClient = -1;
+
+ private int lastToBeRenderedInClient = -1;
+
+ private boolean isContentRefreshesEnabled = true;
+
+ private int pageBufferFirstIndex;
+
+ private boolean containerChangeToBeRendered = false;
+
+ /**
+ * Table cell specific style generator
+ */
+ private CellStyleGenerator cellStyleGenerator = null;
+
+ /**
+ * Table cell specific tooltip generator
+ */
+ private ItemDescriptionGenerator itemDescriptionGenerator;
+
+ /*
+ * EXPERIMENTAL feature: will tell the client to re-calculate column widths
+ * if set to true. Currently no setter: extend to enable.
+ */
+ protected boolean alwaysRecalculateColumnWidths = false;
+
+ private double cacheRate = CACHE_RATE_DEFAULT;
+
+ private TableDragMode dragMode = TableDragMode.NONE;
+
+ private DropHandler dropHandler;
+
+ private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
+ private boolean rowCacheInvalidated;
+
+ private RowGenerator rowGenerator = null;
+
+ private final Map<Field<?>, Property<?>> associatedProperties = new HashMap<Field<?>, Property<?>>();
+
+ private boolean painted = false;
+
+ private HashMap<Object, Converter<String, Object>> propertyValueConverters = new HashMap<Object, Converter<String, Object>>();
+
+ /**
+ * Set to true if the client-side should be informed that the key mapper has
+ * been reset so it can avoid sending back references to keys that are no
+ * longer present.
+ */
+ private boolean keyMapperReset;
+
+ /* Table constructors */
+
+ /**
+ * Creates a new empty table.
+ */
+ public Table() {
+ setRowHeaderMode(ROW_HEADER_MODE_HIDDEN);
+ }
+
+ /**
+ * Creates a new empty table with caption.
+ *
+ * @param caption
+ */
+ public Table(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new table with caption and connect it to a Container.
+ *
+ * @param caption
+ * @param dataSource
+ */
+ public Table(String caption, Container dataSource) {
+ this();
+ setCaption(caption);
+ setContainerDataSource(dataSource);
+ }
+
+ /* Table functionality */
+
+ /**
+ * Gets the array of visible column id:s, including generated columns.
+ *
+ * <p>
+ * The columns are show in the order of their appearance in this array.
+ * </p>
+ *
+ * @return an array of currently visible propertyIds and generated column
+ * ids.
+ */
+ public Object[] getVisibleColumns() {
+ if (visibleColumns == null) {
+ return null;
+ }
+ return visibleColumns.toArray();
+ }
+
+ /**
+ * Sets the array of visible column property id:s.
+ *
+ * <p>
+ * The columns are show in the order of their appearance in this array.
+ * </p>
+ *
+ * @param visibleColumns
+ * the Array of shown property id:s.
+ */
+ public void setVisibleColumns(Object[] visibleColumns) {
+
+ // Visible columns must exist
+ if (visibleColumns == null) {
+ throw new NullPointerException(
+ "Can not set visible columns to null value");
+ }
+
+ // TODO add error check that no duplicate identifiers exist
+
+ // Checks that the new visible columns contains no nulls and properties
+ // exist
+ final Collection<?> properties = getContainerPropertyIds();
+ for (int i = 0; i < visibleColumns.length; i++) {
+ if (visibleColumns[i] == null) {
+ throw new NullPointerException("Ids must be non-nulls");
+ } else if (!properties.contains(visibleColumns[i])
+ && !columnGenerators.containsKey(visibleColumns[i])) {
+ throw new IllegalArgumentException(
+ "Ids must exist in the Container or as a generated column , missing id: "
+ + visibleColumns[i]);
+ }
+ }
+
+ // If this is called before the constructor is finished, it might be
+ // uninitialized
+ final LinkedList<Object> newVC = new LinkedList<Object>();
+ for (int i = 0; i < visibleColumns.length; i++) {
+ newVC.add(visibleColumns[i]);
+ }
+
+ // Removes alignments, icons and headers from hidden columns
+ if (this.visibleColumns != null) {
+ boolean disabledHere = disableContentRefreshing();
+ try {
+ for (final Iterator<Object> i = this.visibleColumns.iterator(); i
+ .hasNext();) {
+ final Object col = i.next();
+ if (!newVC.contains(col)) {
+ setColumnHeader(col, null);
+ setColumnAlignment(col, (Align) null);
+ setColumnIcon(col, null);
+ }
+ }
+ } finally {
+ if (disabledHere) {
+ enableContentRefreshing(false);
+ }
+ }
+ }
+
+ this.visibleColumns = newVC;
+
+ // Assures visual refresh
+ refreshRowCache();
+ }
+
+ /**
+ * Gets the headers of the columns.
+ *
+ * <p>
+ * The headers match the property id:s given my the set visible column
+ * headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
+ * headers. In the defaults mode any nulls in the headers array are replaced
+ * with id.toString().
+ * </p>
+ *
+ * @return the Array of column headers.
+ */
+ public String[] getColumnHeaders() {
+ if (columnHeaders == null) {
+ return null;
+ }
+ final String[] headers = new String[visibleColumns.size()];
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext(); i++) {
+ headers[i] = getColumnHeader(it.next());
+ }
+ return headers;
+ }
+
+ /**
+ * Sets the headers of the columns.
+ *
+ * <p>
+ * The headers match the property id:s given my the set visible column
+ * headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
+ * headers. In the defaults mode any nulls in the headers array are replaced
+ * with id.toString() outputs when rendering.
+ * </p>
+ *
+ * @param columnHeaders
+ * the Array of column headers that match the
+ * {@link #getVisibleColumns()} method.
+ */
+ public void setColumnHeaders(String[] columnHeaders) {
+
+ if (columnHeaders.length != visibleColumns.size()) {
+ throw new IllegalArgumentException(
+ "The length of the headers array must match the number of visible columns");
+ }
+
+ this.columnHeaders.clear();
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext() && i < columnHeaders.length; i++) {
+ this.columnHeaders.put(it.next(), columnHeaders[i]);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Gets the icons of the columns.
+ *
+ * <p>
+ * The icons in headers match the property id:s given my the set visible
+ * column headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the headers
+ * with icons.
+ * </p>
+ *
+ * @return the Array of icons that match the {@link #getVisibleColumns()}.
+ */
+ public Resource[] getColumnIcons() {
+ if (columnIcons == null) {
+ return null;
+ }
+ final Resource[] icons = new Resource[visibleColumns.size()];
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext(); i++) {
+ icons[i] = columnIcons.get(it.next());
+ }
+
+ return icons;
+ }
+
+ /**
+ * Sets the icons of the columns.
+ *
+ * <p>
+ * The icons in headers match the property id:s given my the set visible
+ * column headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the headers
+ * with icons.
+ * </p>
+ *
+ * @param columnIcons
+ * the Array of icons that match the {@link #getVisibleColumns()}
+ * .
+ */
+ public void setColumnIcons(Resource[] columnIcons) {
+
+ if (columnIcons.length != visibleColumns.size()) {
+ throw new IllegalArgumentException(
+ "The length of the icons array must match the number of visible columns");
+ }
+
+ this.columnIcons.clear();
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext() && i < columnIcons.length; i++) {
+ this.columnIcons.put(it.next(), columnIcons[i]);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Gets the array of column alignments.
+ *
+ * <p>
+ * The items in the array must match the properties identified by
+ * {@link #getVisibleColumns()}. The possible values for the alignments
+ * include:
+ * <ul>
+ * <li>{@link Align#LEFT}: Left alignment</li>
+ * <li>{@link Align#CENTER}: Centered</li>
+ * <li>{@link Align#RIGHT}: Right alignment</li>
+ * </ul>
+ * The alignments default to {@link Align#LEFT}: any null values are
+ * rendered as align lefts.
+ * </p>
+ *
+ * @return the Column alignments array.
+ */
+ public Align[] getColumnAlignments() {
+ if (columnAlignments == null) {
+ return null;
+ }
+ final Align[] alignments = new Align[visibleColumns.size()];
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext(); i++) {
+ alignments[i] = getColumnAlignment(it.next());
+ }
+
+ return alignments;
+ }
+
+ /**
+ * Sets the column alignments.
+ *
+ * <p>
+ * The amount of items in the array must match the amount of properties
+ * identified by {@link #getVisibleColumns()}. The possible values for the
+ * alignments include:
+ * <ul>
+ * <li>{@link Align#LEFT}: Left alignment</li>
+ * <li>{@link Align#CENTER}: Centered</li>
+ * <li>{@link Align#RIGHT}: Right alignment</li>
+ * </ul>
+ * The alignments default to {@link Align#LEFT}
+ * </p>
+ *
+ * @param columnAlignments
+ * the Column alignments array.
+ */
+ public void setColumnAlignments(Align... columnAlignments) {
+
+ if (columnAlignments.length != visibleColumns.size()) {
+ throw new IllegalArgumentException(
+ "The length of the alignments array must match the number of visible columns");
+ }
+
+ // Resets the alignments
+ final HashMap<Object, Align> newCA = new HashMap<Object, Align>();
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext() && i < columnAlignments.length; i++) {
+ newCA.put(it.next(), columnAlignments[i]);
+ }
+ this.columnAlignments = newCA;
+
+ // Assures the visual refresh. No need to reset the page buffer before
+ // as the content has not changed, only the alignments.
+ refreshRenderedCells();
+ }
+
+ /**
+ * Sets columns width (in pixels). Theme may not necessary respect very
+ * small or very big values. Setting width to -1 (default) means that theme
+ * will make decision of width.
+ *
+ * <p>
+ * Column can either have a fixed width or expand ratio. The latter one set
+ * is used. See @link {@link #setColumnExpandRatio(Object, float)}.
+ *
+ * @param propertyId
+ * colunmns property id
+ * @param width
+ * width to be reserved for colunmns content
+ * @since 4.0.3
+ */
+ public void setColumnWidth(Object propertyId, int width) {
+ if (propertyId == null) {
+ // Since propertyId is null, this is the row header. Use the magic
+ // id to store the width of the row header.
+ propertyId = ROW_HEADER_FAKE_PROPERTY_ID;
+ }
+ if (width < 0) {
+ columnWidths.remove(propertyId);
+ } else {
+ columnWidths.put(propertyId, Integer.valueOf(width));
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Sets the column expand ratio for given column.
+ * <p>
+ * Expand ratios can be defined to customize the way how excess space is
+ * divided among columns. Table can have excess space if it has its width
+ * defined and there is horizontally more space than columns consume
+ * naturally. Excess space is the space that is not used by columns with
+ * explicit width (see {@link #setColumnWidth(Object, int)}) or with natural
+ * width (no width nor expand ratio).
+ *
+ * <p>
+ * By default (without expand ratios) the excess space is divided
+ * proportionally to columns natural widths.
+ *
+ * <p>
+ * Only expand ratios of visible columns are used in final calculations.
+ *
+ * <p>
+ * Column can either have a fixed width or expand ratio. The latter one set
+ * is used.
+ *
+ * <p>
+ * A column with expand ratio is considered to be minimum width by default
+ * (if no excess space exists). The minimum width is defined by terminal
+ * implementation.
+ *
+ * <p>
+ * If terminal implementation supports re-sizable columns the column becomes
+ * fixed width column if users resizes the column.
+ *
+ * @param propertyId
+ * columns property id
+ * @param expandRatio
+ * the expandRatio used to divide excess space for this column
+ */
+ public void setColumnExpandRatio(Object propertyId, float expandRatio) {
+ if (expandRatio < 0) {
+ columnWidths.remove(propertyId);
+ } else {
+ columnWidths.put(propertyId, new Float(expandRatio));
+ }
+ }
+
+ public float getColumnExpandRatio(Object propertyId) {
+ final Object width = columnWidths.get(propertyId);
+ if (width == null || !(width instanceof Float)) {
+ return -1;
+ }
+ final Float value = (Float) width;
+ return value.floatValue();
+
+ }
+
+ /**
+ * Gets the pixel width of column
+ *
+ * @param propertyId
+ * @return width of column or -1 when value not set
+ */
+ public int getColumnWidth(Object propertyId) {
+ if (propertyId == null) {
+ // Since propertyId is null, this is the row header. Use the magic
+ // id to retrieve the width of the row header.
+ propertyId = ROW_HEADER_FAKE_PROPERTY_ID;
+ }
+ final Object width = columnWidths.get(propertyId);
+ if (width == null || !(width instanceof Integer)) {
+ return -1;
+ }
+ final Integer value = (Integer) width;
+ return value.intValue();
+ }
+
+ /**
+ * Gets the page length.
+ *
+ * <p>
+ * Setting page length 0 disables paging.
+ * </p>
+ *
+ * @return the Length of one page.
+ */
+ public int getPageLength() {
+ return pageLength;
+ }
+
+ /**
+ * Sets the page length.
+ *
+ * <p>
+ * Setting page length 0 disables paging. The page length defaults to 15.
+ * </p>
+ *
+ * <p>
+ * If Table has width set ({@link #setWidth(float, int)} ) the client side
+ * may update the page length automatically the correct value.
+ * </p>
+ *
+ * @param pageLength
+ * the length of one page.
+ */
+ public void setPageLength(int pageLength) {
+ if (pageLength >= 0 && this.pageLength != pageLength) {
+ this.pageLength = pageLength;
+ // Assures the visual refresh
+ refreshRowCache();
+ }
+ }
+
+ /**
+ * This method adjusts a possible caching mechanism of table implementation.
+ *
+ * <p>
+ * Table component may fetch and render some rows outside visible area. With
+ * complex tables (for example containing layouts and components), the
+ * client side may become unresponsive. Setting the value lower, UI will
+ * become more responsive. With higher values scrolling in client will hit
+ * server less frequently.
+ *
+ * <p>
+ * The amount of cached rows will be cacheRate multiplied with pageLength (
+ * {@link #setPageLength(int)} both below and above visible area..
+ *
+ * @param cacheRate
+ * a value over 0 (fastest rendering time). Higher value will
+ * cache more rows on server (smoother scrolling). Default value
+ * is 2.
+ */
+ public void setCacheRate(double cacheRate) {
+ if (cacheRate < 0) {
+ throw new IllegalArgumentException(
+ "cacheRate cannot be less than zero");
+ }
+ if (this.cacheRate != cacheRate) {
+ this.cacheRate = cacheRate;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * @see #setCacheRate(double)
+ *
+ * @return the current cache rate value
+ */
+ public double getCacheRate() {
+ return cacheRate;
+ }
+
+ /**
+ * Getter for property currentPageFirstItem.
+ *
+ * @return the Value of property currentPageFirstItem.
+ */
+ public Object getCurrentPageFirstItemId() {
+
+ // Priorise index over id if indexes are supported
+ if (items instanceof Container.Indexed) {
+ final int index = getCurrentPageFirstItemIndex();
+ Object id = null;
+ if (index >= 0 && index < size()) {
+ id = getIdByIndex(index);
+ }
+ if (id != null && !id.equals(currentPageFirstItemId)) {
+ currentPageFirstItemId = id;
+ }
+ }
+
+ // If there is no item id at all, use the first one
+ if (currentPageFirstItemId == null) {
+ currentPageFirstItemId = firstItemId();
+ }
+
+ return currentPageFirstItemId;
+ }
+
+ protected Object getIdByIndex(int index) {
+ return ((Container.Indexed) items).getIdByIndex(index);
+ }
+
+ /**
+ * Setter for property currentPageFirstItemId.
+ *
+ * @param currentPageFirstItemId
+ * the New value of property currentPageFirstItemId.
+ */
+ public void setCurrentPageFirstItemId(Object currentPageFirstItemId) {
+
+ // Gets the corresponding index
+ int index = -1;
+ if (items instanceof Container.Indexed) {
+ index = indexOfId(currentPageFirstItemId);
+ } else {
+ // If the table item container does not have index, we have to
+ // calculates the index by hand
+ Object id = firstItemId();
+ while (id != null && !id.equals(currentPageFirstItemId)) {
+ index++;
+ id = nextItemId(id);
+ }
+ if (id == null) {
+ index = -1;
+ }
+ }
+
+ // If the search for item index was successful
+ if (index >= 0) {
+ /*
+ * The table is not capable of displaying an item in the container
+ * as the first if there are not enough items following the selected
+ * item so the whole table (pagelength) is filled.
+ */
+ int maxIndex = size() - pageLength;
+ if (maxIndex < 0) {
+ maxIndex = 0;
+ }
+
+ if (index > maxIndex) {
+ // Note that we pass index, not maxIndex, letting
+ // setCurrentPageFirstItemIndex handle the situation.
+ setCurrentPageFirstItemIndex(index);
+ return;
+ }
+
+ this.currentPageFirstItemId = currentPageFirstItemId;
+ currentPageFirstItemIndex = index;
+ }
+
+ // Assures the visual refresh
+ refreshRowCache();
+
+ }
+
+ protected int indexOfId(Object itemId) {
+ return ((Container.Indexed) items).indexOfId(itemId);
+ }
+
+ /**
+ * Gets the icon Resource for the specified column.
+ *
+ * @param propertyId
+ * the propertyId indentifying the column.
+ * @return the icon for the specified column; null if the column has no icon
+ * set, or if the column is not visible.
+ */
+ public Resource getColumnIcon(Object propertyId) {
+ return columnIcons.get(propertyId);
+ }
+
+ /**
+ * Sets the icon Resource for the specified column.
+ * <p>
+ * Throws IllegalArgumentException if the specified column is not visible.
+ * </p>
+ *
+ * @param propertyId
+ * the propertyId identifying the column.
+ * @param icon
+ * the icon Resource to set.
+ */
+ public void setColumnIcon(Object propertyId, Resource icon) {
+
+ if (icon == null) {
+ columnIcons.remove(propertyId);
+ } else {
+ columnIcons.put(propertyId, icon);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Gets the header for the specified column.
+ *
+ * @param propertyId
+ * the propertyId identifying the column.
+ * @return the header for the specified column if it has one.
+ */
+ public String getColumnHeader(Object propertyId) {
+ if (getColumnHeaderMode() == ColumnHeaderMode.HIDDEN) {
+ return null;
+ }
+
+ String header = columnHeaders.get(propertyId);
+ if ((header == null && getColumnHeaderMode() == ColumnHeaderMode.EXPLICIT_DEFAULTS_ID)
+ || getColumnHeaderMode() == ColumnHeaderMode.ID) {
+ header = propertyId.toString();
+ }
+
+ return header;
+ }
+
+ /**
+ * Sets the column header for the specified column;
+ *
+ * @param propertyId
+ * the propertyId identifying the column.
+ * @param header
+ * the header to set.
+ */
+ public void setColumnHeader(Object propertyId, String header) {
+
+ if (header == null) {
+ columnHeaders.remove(propertyId);
+ } else {
+ columnHeaders.put(propertyId, header);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Gets the specified column's alignment.
+ *
+ * @param propertyId
+ * the propertyID identifying the column.
+ * @return the specified column's alignment if it as one; null otherwise.
+ */
+ public Align getColumnAlignment(Object propertyId) {
+ final Align a = columnAlignments.get(propertyId);
+ return a == null ? Align.LEFT : a;
+ }
+
+ /**
+ * Sets the specified column's alignment.
+ *
+ * <p>
+ * Throws IllegalArgumentException if the alignment is not one of the
+ * following: {@link Align#LEFT}, {@link Align#CENTER} or
+ * {@link Align#RIGHT}
+ * </p>
+ *
+ * @param propertyId
+ * the propertyID identifying the column.
+ * @param alignment
+ * the desired alignment.
+ */
+ public void setColumnAlignment(Object propertyId, Align alignment) {
+ if (alignment == null || alignment == Align.LEFT) {
+ columnAlignments.remove(propertyId);
+ } else {
+ columnAlignments.put(propertyId, alignment);
+ }
+
+ // Assures the visual refresh. No need to reset the page buffer before
+ // as the content has not changed, only the alignments.
+ refreshRenderedCells();
+ }
+
+ /**
+ * Checks if the specified column is collapsed.
+ *
+ * @param propertyId
+ * the propertyID identifying the column.
+ * @return true if the column is collapsed; false otherwise;
+ */
+ public boolean isColumnCollapsed(Object propertyId) {
+ return collapsedColumns != null
+ && collapsedColumns.contains(propertyId);
+ }
+
+ /**
+ * Sets whether the specified column is collapsed or not.
+ *
+ *
+ * @param propertyId
+ * the propertyID identifying the column.
+ * @param collapsed
+ * the desired collapsedness.
+ * @throws IllegalStateException
+ * if column collapsing is not allowed
+ */
+ public void setColumnCollapsed(Object propertyId, boolean collapsed)
+ throws IllegalStateException {
+ if (!isColumnCollapsingAllowed()) {
+ throw new IllegalStateException("Column collapsing not allowed!");
+ }
+ if (collapsed && noncollapsibleColumns.contains(propertyId)) {
+ throw new IllegalStateException("The column is noncollapsible!");
+ }
+
+ if (collapsed) {
+ collapsedColumns.add(propertyId);
+ } else {
+ collapsedColumns.remove(propertyId);
+ }
+
+ // Assures the visual refresh
+ refreshRowCache();
+ }
+
+ /**
+ * Checks if column collapsing is allowed.
+ *
+ * @return true if columns can be collapsed; false otherwise.
+ */
+ public boolean isColumnCollapsingAllowed() {
+ return columnCollapsingAllowed;
+ }
+
+ /**
+ * Sets whether column collapsing is allowed or not.
+ *
+ * @param collapsingAllowed
+ * specifies whether column collapsing is allowed.
+ */
+ public void setColumnCollapsingAllowed(boolean collapsingAllowed) {
+ columnCollapsingAllowed = collapsingAllowed;
+
+ if (!collapsingAllowed) {
+ collapsedColumns.clear();
+ }
+
+ // Assures the visual refresh. No need to reset the page buffer before
+ // as the content has not changed, only the alignments.
+ refreshRenderedCells();
+ }
+
+ /**
+ * Sets whether the given column is collapsible. Note that collapsible
+ * columns can only be actually collapsed (via UI or with
+ * {@link #setColumnCollapsed(Object, boolean) setColumnCollapsed()}) if
+ * {@link #isColumnCollapsingAllowed()} is true. By default all columns are
+ * collapsible.
+ *
+ * @param propertyId
+ * the propertyID identifying the column.
+ * @param collapsible
+ * true if the column should be collapsible, false otherwise.
+ */
+ public void setColumnCollapsible(Object propertyId, boolean collapsible) {
+ if (collapsible) {
+ noncollapsibleColumns.remove(propertyId);
+ } else {
+ noncollapsibleColumns.add(propertyId);
+ collapsedColumns.remove(propertyId);
+ }
+ refreshRowCache();
+ }
+
+ /**
+ * Checks if the given column is collapsible. Note that even if this method
+ * returns <code>true</code>, the column can only be actually collapsed (via
+ * UI or with {@link #setColumnCollapsed(Object, boolean)
+ * setColumnCollapsed()}) if {@link #isColumnCollapsingAllowed()} is also
+ * true.
+ *
+ * @return true if the column can be collapsed; false otherwise.
+ */
+ public boolean isColumnCollapsible(Object propertyId) {
+ return !noncollapsibleColumns.contains(propertyId);
+ }
+
+ /**
+ * Checks if column reordering is allowed.
+ *
+ * @return true if columns can be reordered; false otherwise.
+ */
+ public boolean isColumnReorderingAllowed() {
+ return columnReorderingAllowed;
+ }
+
+ /**
+ * Sets whether column reordering is allowed or not.
+ *
+ * @param columnReorderingAllowed
+ * specifies whether column reordering is allowed.
+ */
+ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
+ if (columnReorderingAllowed != this.columnReorderingAllowed) {
+ this.columnReorderingAllowed = columnReorderingAllowed;
+ requestRepaint();
+ }
+ }
+
+ /*
+ * Arranges visible columns according to given columnOrder. Silently ignores
+ * colimnId:s that are not visible columns, and keeps the internal order of
+ * visible columns left out of the ordering (trailing). Silently does
+ * nothing if columnReordering is not allowed.
+ */
+ private void setColumnOrder(Object[] columnOrder) {
+ if (columnOrder == null || !isColumnReorderingAllowed()) {
+ return;
+ }
+ final LinkedList<Object> newOrder = new LinkedList<Object>();
+ for (int i = 0; i < columnOrder.length; i++) {
+ if (columnOrder[i] != null
+ && visibleColumns.contains(columnOrder[i])) {
+ visibleColumns.remove(columnOrder[i]);
+ newOrder.add(columnOrder[i]);
+ }
+ }
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext();) {
+ final Object columnId = it.next();
+ if (!newOrder.contains(columnId)) {
+ newOrder.add(columnId);
+ }
+ }
+ visibleColumns = newOrder;
+
+ // Assure visual refresh
+ refreshRowCache();
+ }
+
+ /**
+ * Getter for property currentPageFirstItem.
+ *
+ * @return the Value of property currentPageFirstItem.
+ */
+ public int getCurrentPageFirstItemIndex() {
+ return currentPageFirstItemIndex;
+ }
+
+ void setCurrentPageFirstItemIndex(int newIndex, boolean needsPageBufferReset) {
+
+ if (newIndex < 0) {
+ newIndex = 0;
+ }
+
+ /*
+ * minimize Container.size() calls which may be expensive. For example
+ * it may cause sql query.
+ */
+ final int size = size();
+
+ /*
+ * The table is not capable of displaying an item in the container as
+ * the first if there are not enough items following the selected item
+ * so the whole table (pagelength) is filled.
+ */
+ int maxIndex = size - pageLength;
+ if (maxIndex < 0) {
+ maxIndex = 0;
+ }
+
+ /*
+ * FIXME #7607 Take somehow into account the case where we want to
+ * scroll to the bottom so that the last row is completely visible even
+ * if (table height) / (row height) is not an integer. Reverted the
+ * original fix because of #8662 regression.
+ */
+ if (newIndex > maxIndex) {
+ newIndex = maxIndex;
+ }
+
+ // Refresh first item id
+ if (items instanceof Container.Indexed) {
+ try {
+ currentPageFirstItemId = getIdByIndex(newIndex);
+ } catch (final IndexOutOfBoundsException e) {
+ currentPageFirstItemId = null;
+ }
+ currentPageFirstItemIndex = newIndex;
+ } else {
+
+ // For containers not supporting indexes, we must iterate the
+ // container forwards / backwards
+ // next available item forward or backward
+
+ currentPageFirstItemId = firstItemId();
+
+ // Go forwards in the middle of the list (respect borders)
+ while (currentPageFirstItemIndex < newIndex
+ && !isLastId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex++;
+ currentPageFirstItemId = nextItemId(currentPageFirstItemId);
+ }
+
+ // If we did hit the border
+ if (isLastId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex = size - 1;
+ }
+
+ // Go backwards in the middle of the list (respect borders)
+ while (currentPageFirstItemIndex > newIndex
+ && !isFirstId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex--;
+ currentPageFirstItemId = prevItemId(currentPageFirstItemId);
+ }
+
+ // If we did hit the border
+ if (isFirstId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex = 0;
+ }
+
+ // Go forwards once more
+ while (currentPageFirstItemIndex < newIndex
+ && !isLastId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex++;
+ currentPageFirstItemId = nextItemId(currentPageFirstItemId);
+ }
+
+ // If for some reason we do hit border again, override
+ // the user index request
+ if (isLastId(currentPageFirstItemId)) {
+ newIndex = currentPageFirstItemIndex = size - 1;
+ }
+ }
+ if (needsPageBufferReset) {
+ // Assures the visual refresh
+ refreshRowCache();
+ }
+ }
+
+ /**
+ * Setter for property currentPageFirstItem.
+ *
+ * @param newIndex
+ * the New value of property currentPageFirstItem.
+ */
+ public void setCurrentPageFirstItemIndex(int newIndex) {
+ setCurrentPageFirstItemIndex(newIndex, true);
+ }
+
+ /**
+ * Getter for property pageBuffering.
+ *
+ * @deprecated functionality is not needed in ajax rendering model
+ *
+ * @return the Value of property pageBuffering.
+ */
+ @Deprecated
+ public boolean isPageBufferingEnabled() {
+ return true;
+ }
+
+ /**
+ * Setter for property pageBuffering.
+ *
+ * @deprecated functionality is not needed in ajax rendering model
+ *
+ * @param pageBuffering
+ * the New value of property pageBuffering.
+ */
+ @Deprecated
+ public void setPageBufferingEnabled(boolean pageBuffering) {
+
+ }
+
+ /**
+ * Getter for property selectable.
+ *
+ * <p>
+ * The table is not selectable by default.
+ * </p>
+ *
+ * @return the Value of property selectable.
+ */
+ public boolean isSelectable() {
+ return selectable;
+ }
+
+ /**
+ * Setter for property selectable.
+ *
+ * <p>
+ * The table is not selectable by default.
+ * </p>
+ *
+ * @param selectable
+ * the New value of property selectable.
+ */
+ public void setSelectable(boolean selectable) {
+ if (this.selectable != selectable) {
+ this.selectable = selectable;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Getter for property columnHeaderMode.
+ *
+ * @return the Value of property columnHeaderMode.
+ */
+ public ColumnHeaderMode getColumnHeaderMode() {
+ return columnHeaderMode;
+ }
+
+ /**
+ * Setter for property columnHeaderMode.
+ *
+ * @param columnHeaderMode
+ * the New value of property columnHeaderMode.
+ */
+ public void setColumnHeaderMode(ColumnHeaderMode columnHeaderMode) {
+ if (columnHeaderMode == null) {
+ throw new IllegalArgumentException(
+ "Column header mode can not be null");
+ }
+ if (columnHeaderMode != this.columnHeaderMode) {
+ this.columnHeaderMode = columnHeaderMode;
+ requestRepaint();
+ }
+
+ }
+
+ /**
+ * Refreshes the rows in the internal cache. Only if
+ * {@link #resetPageBuffer()} is called before this then all values are
+ * guaranteed to be recreated.
+ */
+ protected void refreshRenderedCells() {
+ if (getParent() == null) {
+ return;
+ }
+
+ if (!isContentRefreshesEnabled) {
+ return;
+ }
+
+ // Collects the basic facts about the table page
+ final int pagelen = getPageLength();
+ int rows, totalRows;
+ rows = totalRows = size();
+ int firstIndex = Math
+ .min(getCurrentPageFirstItemIndex(), totalRows - 1);
+ if (rows > 0 && firstIndex >= 0) {
+ rows -= firstIndex;
+ }
+ if (pagelen > 0 && pagelen < rows) {
+ rows = pagelen;
+ }
+
+ // If "to be painted next" variables are set, use them
+ if (lastToBeRenderedInClient - firstToBeRenderedInClient > 0) {
+ rows = lastToBeRenderedInClient - firstToBeRenderedInClient + 1;
+ }
+ if (firstToBeRenderedInClient >= 0) {
+ if (firstToBeRenderedInClient < totalRows) {
+ firstIndex = firstToBeRenderedInClient;
+ } else {
+ firstIndex = totalRows - 1;
+ }
+ } else {
+ // initial load
+
+ // #8805 send one extra row in the beginning in case a partial
+ // row is shown on the UI
+ if (firstIndex > 0) {
+ firstIndex = firstIndex - 1;
+ rows = rows + 1;
+ }
+ firstToBeRenderedInClient = firstIndex;
+ }
+ if (totalRows > 0) {
+ if (rows + firstIndex > totalRows) {
+ rows = totalRows - firstIndex;
+ }
+ } else {
+ rows = 0;
+ }
+
+ // Saves the results to internal buffer
+ pageBuffer = getVisibleCellsNoCache(firstIndex, rows, true);
+
+ if (rows > 0) {
+ pageBufferFirstIndex = firstIndex;
+ }
+
+ setRowCacheInvalidated(true);
+ requestRepaint();
+ }
+
+ /**
+ * Requests that the Table should be repainted as soon as possible.
+ *
+ * Note that a {@code Table} does not necessarily repaint its contents when
+ * this method has been called. See {@link #refreshRowCache()} for forcing
+ * an update of the contents.
+ */
+
+ @Override
+ public void requestRepaint() {
+ // Overridden only for javadoc
+ super.requestRepaint();
+ }
+
+ @Override
+ public void requestRepaintAll() {
+ super.requestRepaintAll();
+
+ // Avoid sending a partial repaint (#8714)
+ refreshRowCache();
+ }
+
+ private void removeRowsFromCacheAndFillBottom(int firstIndex, int rows) {
+ int totalCachedRows = pageBuffer[CELL_ITEMID].length;
+ int totalRows = size();
+ int firstIndexInPageBuffer = firstIndex - pageBufferFirstIndex;
+
+ /*
+ * firstIndexInPageBuffer is the first row to be removed. "rows" rows
+ * after that should be removed. If the page buffer does not contain
+ * that many rows, we only remove the rows that actually are in the page
+ * buffer.
+ */
+ if (firstIndexInPageBuffer + rows > totalCachedRows) {
+ rows = totalCachedRows - firstIndexInPageBuffer;
+ }
+
+ /*
+ * Unregister components that will no longer be in the page buffer to
+ * make sure that no components leak.
+ */
+ unregisterComponentsAndPropertiesInRows(firstIndex, rows);
+
+ /*
+ * The number of rows that should be in the cache after this operation
+ * is done (pageBuffer currently contains the expanded items).
+ */
+ int newCachedRowCount = totalCachedRows;
+ if (newCachedRowCount + pageBufferFirstIndex > totalRows) {
+ newCachedRowCount = totalRows - pageBufferFirstIndex;
+ }
+
+ /*
+ * The index at which we should render the first row that does not come
+ * from the previous page buffer.
+ */
+ int firstAppendedRowInPageBuffer = totalCachedRows - rows;
+ int firstAppendedRow = firstAppendedRowInPageBuffer
+ + pageBufferFirstIndex;
+
+ /*
+ * Calculate the maximum number of new rows that we can add to the page
+ * buffer. Less than the rows we removed if the container does not
+ * contain that many items afterwards.
+ */
+ int maxRowsToRender = (totalRows - firstAppendedRow);
+ int rowsToAdd = rows;
+ if (rowsToAdd > maxRowsToRender) {
+ rowsToAdd = maxRowsToRender;
+ }
+
+ Object[][] cells = null;
+ if (rowsToAdd > 0) {
+ cells = getVisibleCellsNoCache(firstAppendedRow, rowsToAdd, false);
+ }
+ /*
+ * Create the new cache buffer by copying the first rows from the old
+ * buffer, moving the following rows upwards and appending more rows if
+ * applicable.
+ */
+ Object[][] newPageBuffer = new Object[pageBuffer.length][newCachedRowCount];
+
+ for (int i = 0; i < pageBuffer.length; i++) {
+ for (int row = 0; row < firstIndexInPageBuffer; row++) {
+ // Copy the first rows
+ newPageBuffer[i][row] = pageBuffer[i][row];
+ }
+ for (int row = firstIndexInPageBuffer; row < firstAppendedRowInPageBuffer; row++) {
+ // Move the rows that were after the expanded rows
+ newPageBuffer[i][row] = pageBuffer[i][row + rows];
+ }
+ for (int row = firstAppendedRowInPageBuffer; row < newCachedRowCount; row++) {
+ // Add the newly rendered rows. Only used if rowsToAdd > 0
+ // (cells != null)
+ newPageBuffer[i][row] = cells[i][row
+ - firstAppendedRowInPageBuffer];
+ }
+ }
+ pageBuffer = newPageBuffer;
+ }
+
+ private Object[][] getVisibleCellsUpdateCacheRows(int firstIndex, int rows) {
+ Object[][] cells = getVisibleCellsNoCache(firstIndex, rows, false);
+ int cacheIx = firstIndex - pageBufferFirstIndex;
+ // update the new rows in the cache.
+ int totalCachedRows = pageBuffer[CELL_ITEMID].length;
+ int end = Math.min(cacheIx + rows, totalCachedRows);
+ for (int ix = cacheIx; ix < end; ix++) {
+ for (int i = 0; i < pageBuffer.length; i++) {
+ pageBuffer[i][ix] = cells[i][ix - cacheIx];
+ }
+ }
+ return cells;
+ }
+
+ /**
+ * @param firstIndex
+ * The position where new rows should be inserted
+ * @param rows
+ * The maximum number of rows that should be inserted at position
+ * firstIndex. Less rows will be inserted if the page buffer is
+ * too small.
+ * @return
+ */
+ private Object[][] getVisibleCellsInsertIntoCache(int firstIndex, int rows) {
+ getLogger().finest(
+ "Insert " + rows + " rows at index " + firstIndex
+ + " to existing page buffer requested");
+
+ // Page buffer must not become larger than pageLength*cacheRate before
+ // or after the current page
+ int minPageBufferIndex = getCurrentPageFirstItemIndex()
+ - (int) (getPageLength() * getCacheRate());
+ if (minPageBufferIndex < 0) {
+ minPageBufferIndex = 0;
+ }
+
+ int maxPageBufferIndex = getCurrentPageFirstItemIndex()
+ + (int) (getPageLength() * (1 + getCacheRate()));
+ int maxBufferSize = maxPageBufferIndex - minPageBufferIndex;
+
+ if (getPageLength() == 0) {
+ // If pageLength == 0 then all rows should be rendered
+ maxBufferSize = pageBuffer[0].length + rows;
+ }
+ /*
+ * Number of rows that were previously cached. This is not necessarily
+ * the same as pageLength if we do not have enough rows in the
+ * container.
+ */
+ int currentlyCachedRowCount = pageBuffer[CELL_ITEMID].length;
+
+ /*
+ * firstIndexInPageBuffer is the offset in pageBuffer where the new rows
+ * will be inserted (firstIndex is the index in the whole table).
+ *
+ * E.g. scrolled down to row 1000: firstIndex==1010,
+ * pageBufferFirstIndex==1000 -> cacheIx==10
+ */
+ int firstIndexInPageBuffer = firstIndex - pageBufferFirstIndex;
+
+ /* If rows > size available in page buffer */
+ if (firstIndexInPageBuffer + rows > maxBufferSize) {
+ rows = maxBufferSize - firstIndexInPageBuffer;
+ }
+
+ /*
+ * "rows" rows will be inserted at firstIndex. Find out how many old
+ * rows fall outside the new buffer so we can unregister components in
+ * the cache.
+ */
+
+ /* All rows until the insertion point remain, always. */
+ int firstCacheRowToRemoveInPageBuffer = firstIndexInPageBuffer;
+
+ /*
+ * IF there is space remaining in the buffer after the rows have been
+ * inserted, we can keep more rows.
+ */
+ int numberOfOldRowsAfterInsertedRows = maxBufferSize
+ - firstIndexInPageBuffer - rows;
+ if (numberOfOldRowsAfterInsertedRows > 0) {
+ firstCacheRowToRemoveInPageBuffer += numberOfOldRowsAfterInsertedRows;
+ }
+
+ if (firstCacheRowToRemoveInPageBuffer <= currentlyCachedRowCount) {
+ /*
+ * Unregister all components that fall beyond the cache limits after
+ * inserting the new rows.
+ */
+ unregisterComponentsAndPropertiesInRows(
+ firstCacheRowToRemoveInPageBuffer + pageBufferFirstIndex,
+ currentlyCachedRowCount - firstCacheRowToRemoveInPageBuffer
+ + pageBufferFirstIndex);
+ }
+
+ // Calculate the new cache size
+ int newCachedRowCount = currentlyCachedRowCount;
+ if (maxBufferSize == 0 || currentlyCachedRowCount < maxBufferSize) {
+ newCachedRowCount = currentlyCachedRowCount + rows;
+ if (maxBufferSize > 0 && newCachedRowCount > maxBufferSize) {
+ newCachedRowCount = maxBufferSize;
+ }
+ }
+
+ /* Paint the new rows into a separate buffer */
+ Object[][] cells = getVisibleCellsNoCache(firstIndex, rows, false);
+
+ /*
+ * Create the new cache buffer and fill it with the data from the old
+ * buffer as well as the inserted rows.
+ */
+ Object[][] newPageBuffer = new Object[pageBuffer.length][newCachedRowCount];
+
+ for (int i = 0; i < pageBuffer.length; i++) {
+ for (int row = 0; row < firstIndexInPageBuffer; row++) {
+ // Copy the first rows
+ newPageBuffer[i][row] = pageBuffer[i][row];
+ }
+ for (int row = firstIndexInPageBuffer; row < firstIndexInPageBuffer
+ + rows; row++) {
+ // Copy the newly created rows
+ newPageBuffer[i][row] = cells[i][row - firstIndexInPageBuffer];
+ }
+ for (int row = firstIndexInPageBuffer + rows; row < newCachedRowCount; row++) {
+ // Move the old rows down below the newly inserted rows
+ newPageBuffer[i][row] = pageBuffer[i][row - rows];
+ }
+ }
+ pageBuffer = newPageBuffer;
+ getLogger().finest(
+ "Page Buffer now contains "
+ + pageBuffer[CELL_ITEMID].length
+ + " rows ("
+ + pageBufferFirstIndex
+ + "-"
+ + (pageBufferFirstIndex
+ + pageBuffer[CELL_ITEMID].length - 1) + ")");
+ return cells;
+ }
+
+ /**
+ * Render rows with index "firstIndex" to "firstIndex+rows-1" to a new
+ * buffer.
+ *
+ * Reuses values from the current page buffer if the rows are found there.
+ *
+ * @param firstIndex
+ * @param rows
+ * @param replaceListeners
+ * @return
+ */
+ private Object[][] getVisibleCellsNoCache(int firstIndex, int rows,
+ boolean replaceListeners) {
+ getLogger().finest(
+ "Render visible cells for rows " + firstIndex + "-"
+ + (firstIndex + rows - 1));
+ final Object[] colids = getVisibleColumns();
+ final int cols = colids.length;
+
+ HashSet<Property<?>> oldListenedProperties = listenedProperties;
+ HashSet<Component> oldVisibleComponents = visibleComponents;
+
+ if (replaceListeners) {
+ // initialize the listener collections, this should only be done if
+ // the entire cache is refreshed (through refreshRenderedCells)
+ listenedProperties = new HashSet<Property<?>>();
+ visibleComponents = new HashSet<Component>();
+ }
+
+ Object[][] cells = new Object[cols + CELL_FIRSTCOL][rows];
+ if (rows == 0) {
+ unregisterPropertiesAndComponents(oldListenedProperties,
+ oldVisibleComponents);
+ return cells;
+ }
+
+ // Gets the first item id
+ Object id;
+ if (items instanceof Container.Indexed) {
+ id = getIdByIndex(firstIndex);
+ } else {
+ id = firstItemId();
+ for (int i = 0; i < firstIndex; i++) {
+ id = nextItemId(id);
+ }
+ }
+
+ final RowHeaderMode headmode = getRowHeaderMode();
+ final boolean[] iscomponent = new boolean[cols];
+ for (int i = 0; i < cols; i++) {
+ iscomponent[i] = columnGenerators.containsKey(colids[i])
+ || Component.class.isAssignableFrom(getType(colids[i]));
+ }
+ int firstIndexNotInCache;
+ if (pageBuffer != null && pageBuffer[CELL_ITEMID].length > 0) {
+ firstIndexNotInCache = pageBufferFirstIndex
+ + pageBuffer[CELL_ITEMID].length;
+ } else {
+ firstIndexNotInCache = -1;
+ }
+
+ // Creates the page contents
+ int filledRows = 0;
+ for (int i = 0; i < rows && id != null; i++) {
+ cells[CELL_ITEMID][i] = id;
+ cells[CELL_KEY][i] = itemIdMapper.key(id);
+ if (headmode != ROW_HEADER_MODE_HIDDEN) {
+ switch (headmode) {
+ case INDEX:
+ cells[CELL_HEADER][i] = String.valueOf(i + firstIndex + 1);
+ break;
+ default:
+ cells[CELL_HEADER][i] = getItemCaption(id);
+ }
+ cells[CELL_ICON][i] = getItemIcon(id);
+ }
+
+ GeneratedRow generatedRow = rowGenerator != null ? rowGenerator
+ .generateRow(this, id) : null;
+ cells[CELL_GENERATED_ROW][i] = generatedRow;
+
+ for (int j = 0; j < cols; j++) {
+ if (isColumnCollapsed(colids[j])) {
+ continue;
+ }
+ Property<?> p = null;
+ Object value = "";
+ boolean isGeneratedRow = generatedRow != null;
+ boolean isGeneratedColumn = columnGenerators
+ .containsKey(colids[j]);
+ boolean isGenerated = isGeneratedRow || isGeneratedColumn;
+
+ if (!isGenerated) {
+ p = getContainerProperty(id, colids[j]);
+ }
+
+ if (isGeneratedRow) {
+ if (generatedRow.isSpanColumns() && j > 0) {
+ value = null;
+ } else if (generatedRow.isSpanColumns() && j == 0
+ && generatedRow.getValue() instanceof Component) {
+ value = generatedRow.getValue();
+ } else if (generatedRow.getText().length > j) {
+ value = generatedRow.getText()[j];
+ }
+ } else {
+ // check in current pageBuffer already has row
+ int index = firstIndex + i;
+ if (p != null || isGenerated) {
+ int indexInOldBuffer = index - pageBufferFirstIndex;
+ if (index < firstIndexNotInCache
+ && index >= pageBufferFirstIndex
+ && pageBuffer[CELL_GENERATED_ROW][indexInOldBuffer] == null
+ && id.equals(pageBuffer[CELL_ITEMID][indexInOldBuffer])) {
+ // we already have data in our cache,
+ // recycle it instead of fetching it via
+ // getValue/getPropertyValue
+ value = pageBuffer[CELL_FIRSTCOL + j][indexInOldBuffer];
+ if (!isGeneratedColumn && iscomponent[j]
+ || !(value instanceof Component)) {
+ listenProperty(p, oldListenedProperties);
+ }
+ } else {
+ if (isGeneratedColumn) {
+ ColumnGenerator cg = columnGenerators
+ .get(colids[j]);
+ value = cg.generateCell(this, id, colids[j]);
+ if (value != null
+ && !(value instanceof Component)
+ && !(value instanceof String)) {
+ // Avoid errors if a generator returns
+ // something
+ // other than a Component or a String
+ value = value.toString();
+ }
+ } else if (iscomponent[j]) {
+ value = p.getValue();
+ listenProperty(p, oldListenedProperties);
+ } else if (p != null) {
+ value = getPropertyValue(id, colids[j], p);
+ /*
+ * If returned value is Component (via
+ * fieldfactory or overridden getPropertyValue)
+ * we excpect it to listen property value
+ * changes. Otherwise if property emits value
+ * change events, table will start to listen
+ * them and refresh content when needed.
+ */
+ if (!(value instanceof Component)) {
+ listenProperty(p, oldListenedProperties);
+ }
+ } else {
+ value = getPropertyValue(id, colids[j], null);
+ }
+ }
+ }
+ }
+
+ if (value instanceof Component) {
+ registerComponent((Component) value);
+ }
+ cells[CELL_FIRSTCOL + j][i] = value;
+ }
+
+ // Gets the next item id
+ if (items instanceof Container.Indexed) {
+ int index = firstIndex + i + 1;
+ if (index < size()) {
+ id = getIdByIndex(index);
+ } else {
+ id = null;
+ }
+ } else {
+ id = nextItemId(id);
+ }
+
+ filledRows++;
+ }
+
+ // Assures that all the rows of the cell-buffer are valid
+ if (filledRows != cells[0].length) {
+ final Object[][] temp = new Object[cells.length][filledRows];
+ for (int i = 0; i < cells.length; i++) {
+ for (int j = 0; j < filledRows; j++) {
+ temp[i][j] = cells[i][j];
+ }
+ }
+ cells = temp;
+ }
+
+ unregisterPropertiesAndComponents(oldListenedProperties,
+ oldVisibleComponents);
+
+ return cells;
+ }
+
+ protected void registerComponent(Component component) {
+ getLogger().finest(
+ "Registered " + component.getClass().getSimpleName() + ": "
+ + component.getCaption());
+ if (component.getParent() != this) {
+ component.setParent(this);
+ }
+ visibleComponents.add(component);
+ }
+
+ private void listenProperty(Property<?> p,
+ HashSet<Property<?>> oldListenedProperties) {
+ if (p instanceof Property.ValueChangeNotifier) {
+ if (oldListenedProperties == null
+ || !oldListenedProperties.contains(p)) {
+ ((Property.ValueChangeNotifier) p).addListener(this);
+ }
+ /*
+ * register listened properties, so we can do proper cleanup to free
+ * memory. Essential if table has loads of data and it is used for a
+ * long time.
+ */
+ listenedProperties.add(p);
+
+ }
+ }
+
+ /**
+ * @param firstIx
+ * Index of the first row to process. Global index, not relative
+ * to page buffer.
+ * @param count
+ */
+ private void unregisterComponentsAndPropertiesInRows(int firstIx, int count) {
+ getLogger().finest(
+ "Unregistering components in rows " + firstIx + "-"
+ + (firstIx + count - 1));
+ Object[] colids = getVisibleColumns();
+ if (pageBuffer != null && pageBuffer[CELL_ITEMID].length > 0) {
+ int bufSize = pageBuffer[CELL_ITEMID].length;
+ int ix = firstIx - pageBufferFirstIndex;
+ ix = ix < 0 ? 0 : ix;
+ if (ix < bufSize) {
+ count = count > bufSize - ix ? bufSize - ix : count;
+ for (int i = 0; i < count; i++) {
+ for (int c = 0; c < colids.length; c++) {
+ Object cellVal = pageBuffer[CELL_FIRSTCOL + c][i + ix];
+ if (cellVal instanceof Component
+ && visibleComponents.contains(cellVal)) {
+ visibleComponents.remove(cellVal);
+ unregisterComponent((Component) cellVal);
+ } else {
+ Property<?> p = getContainerProperty(
+ pageBuffer[CELL_ITEMID][i + ix], colids[c]);
+ if (p instanceof ValueChangeNotifier
+ && listenedProperties.contains(p)) {
+ listenedProperties.remove(p);
+ ((ValueChangeNotifier) p).removeListener(this);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to remove listeners and maintain correct component
+ * hierarchy. Detaches properties and components if those are no more
+ * rendered in client.
+ *
+ * @param oldListenedProperties
+ * set of properties that where listened in last render
+ * @param oldVisibleComponents
+ * set of components that where attached in last render
+ */
+ private void unregisterPropertiesAndComponents(
+ HashSet<Property<?>> oldListenedProperties,
+ HashSet<Component> oldVisibleComponents) {
+ if (oldVisibleComponents != null) {
+ for (final Iterator<Component> i = oldVisibleComponents.iterator(); i
+ .hasNext();) {
+ Component c = i.next();
+ if (!visibleComponents.contains(c)) {
+ unregisterComponent(c);
+ }
+ }
+ }
+
+ if (oldListenedProperties != null) {
+ for (final Iterator<Property<?>> i = oldListenedProperties
+ .iterator(); i.hasNext();) {
+ Property.ValueChangeNotifier o = (ValueChangeNotifier) i.next();
+ if (!listenedProperties.contains(o)) {
+ o.removeListener(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method cleans up a Component that has been generated when Table is
+ * in editable mode. The component needs to be detached from its parent and
+ * if it is a field, it needs to be detached from its property data source
+ * in order to allow garbage collection to take care of removing the unused
+ * component from memory.
+ *
+ * Override this method and getPropertyValue(Object, Object, Property) with
+ * custom logic if you need to deal with buffered fields.
+ *
+ * @see #getPropertyValue(Object, Object, Property)
+ *
+ * @param oldVisibleComponents
+ * a set of components that should be unregistered.
+ */
+ protected void unregisterComponent(Component component) {
+ getLogger().finest(
+ "Unregistered " + component.getClass().getSimpleName() + ": "
+ + component.getCaption());
+ component.setParent(null);
+ /*
+ * Also remove property data sources to unregister listeners keeping the
+ * fields in memory.
+ */
+ if (component instanceof Field) {
+ Field<?> field = (Field<?>) component;
+ Property<?> associatedProperty = associatedProperties
+ .remove(component);
+ if (associatedProperty != null
+ && field.getPropertyDataSource() == associatedProperty) {
+ // Remove the property data source only if it's the one we
+ // added in getPropertyValue
+ field.setPropertyDataSource(null);
+ }
+ }
+ }
+
+ /**
+ * Refreshes the current page contents.
+ *
+ * @deprecated should not need to be used
+ */
+ @Deprecated
+ public void refreshCurrentPage() {
+
+ }
+
+ /**
+ * Sets the row header mode.
+ * <p>
+ * The mode can be one of the following ones:
+ * <ul>
+ * <li>{@link #ROW_HEADER_MODE_HIDDEN}: The row captions are hidden.</li>
+ * <li>{@link #ROW_HEADER_MODE_ID}: Items Id-objects <code>toString()</code>
+ * is used as row caption.
+ * <li>{@link #ROW_HEADER_MODE_ITEM}: Item-objects <code>toString()</code>
+ * is used as row caption.
+ * <li>{@link #ROW_HEADER_MODE_PROPERTY}: Property set with
+ * {@link #setItemCaptionPropertyId(Object)} is used as row header.
+ * <li>{@link #ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID}: Items Id-objects
+ * <code>toString()</code> is used as row header. If caption is explicitly
+ * specified, it overrides the id-caption.
+ * <li>{@link #ROW_HEADER_MODE_EXPLICIT}: The row headers must be explicitly
+ * specified.</li>
+ * <li>{@link #ROW_HEADER_MODE_INDEX}: The index of the item is used as row
+ * caption. The index mode can only be used with the containers implementing
+ * <code>Container.Indexed</code> interface.</li>
+ * </ul>
+ * The default value is {@link #ROW_HEADER_MODE_HIDDEN}
+ * </p>
+ *
+ * @param mode
+ * the One of the modes listed above.
+ */
+ public void setRowHeaderMode(RowHeaderMode mode) {
+ if (mode != null) {
+ rowHeaderMode = mode;
+ if (mode != RowHeaderMode.HIDDEN) {
+ setItemCaptionMode(mode.getItemCaptionMode());
+ }
+ // Assures the visual refresh. No need to reset the page buffer
+ // before
+ // as the content has not changed, only the alignments.
+ refreshRenderedCells();
+ }
+ }
+
+ /**
+ * Gets the row header mode.
+ *
+ * @return the Row header mode.
+ * @see #setRowHeaderMode(int)
+ */
+ public RowHeaderMode getRowHeaderMode() {
+ return rowHeaderMode;
+ }
+
+ /**
+ * Adds the new row to table and fill the visible cells (except generated
+ * columns) with given values.
+ *
+ * @param cells
+ * the Object array that is used for filling the visible cells
+ * new row. The types must be settable to visible column property
+ * types.
+ * @param itemId
+ * the Id the new row. If null, a new id is automatically
+ * assigned. If given, the table cant already have a item with
+ * given id.
+ * @return Returns item id for the new row. Returns null if operation fails.
+ */
+ public Object addItem(Object[] cells, Object itemId)
+ throws UnsupportedOperationException {
+
+ // remove generated columns from the list of columns being assigned
+ final LinkedList<Object> availableCols = new LinkedList<Object>();
+ for (Iterator<Object> it = visibleColumns.iterator(); it.hasNext();) {
+ Object id = it.next();
+ if (!columnGenerators.containsKey(id)) {
+ availableCols.add(id);
+ }
+ }
+ // Checks that a correct number of cells are given
+ if (cells.length != availableCols.size()) {
+ return null;
+ }
+
+ // Creates new item
+ Item item;
+ if (itemId == null) {
+ itemId = items.addItem();
+ if (itemId == null) {
+ return null;
+ }
+ item = items.getItem(itemId);
+ } else {
+ item = items.addItem(itemId);
+ }
+ if (item == null) {
+ return null;
+ }
+
+ // Fills the item properties
+ for (int i = 0; i < availableCols.size(); i++) {
+ item.getItemProperty(availableCols.get(i)).setValue(cells[i]);
+ }
+
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ refreshRowCache();
+ }
+
+ return itemId;
+ }
+
+ /**
+ * Discards and recreates the internal row cache. Call this if you make
+ * changes that affect the rows but the information about the changes are
+ * not automatically propagated to the Table.
+ * <p>
+ * Do not call this e.g. if you have updated the data model through a
+ * Property. These types of changes are automatically propagated to the
+ * Table.
+ * <p>
+ * A typical case when this is needed is if you update a generator (e.g.
+ * CellStyleGenerator) and want to ensure that the rows are redrawn with new
+ * styles.
+ * <p>
+ * <i>Note that calling this method is not cheap so avoid calling it
+ * unnecessarily.</i>
+ *
+ * @since 6.7.2
+ */
+ public void refreshRowCache() {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ @Override
+ public void setContainerDataSource(Container newDataSource) {
+
+ disableContentRefreshing();
+
+ if (newDataSource == null) {
+ newDataSource = new IndexedContainer();
+ }
+
+ // Assures that the data source is ordered by making unordered
+ // containers ordered by wrapping them
+ if (newDataSource instanceof Container.Ordered) {
+ super.setContainerDataSource(newDataSource);
+ } else {
+ super.setContainerDataSource(new ContainerOrderedWrapper(
+ newDataSource));
+ }
+
+ // Resets page position
+ currentPageFirstItemId = null;
+ currentPageFirstItemIndex = 0;
+
+ // Resets column properties
+ if (collapsedColumns != null) {
+ collapsedColumns.clear();
+ }
+
+ // columnGenerators 'override' properties, don't add the same id twice
+ Collection<Object> col = new LinkedList<Object>();
+ for (Iterator<?> it = getContainerPropertyIds().iterator(); it
+ .hasNext();) {
+ Object id = it.next();
+ if (columnGenerators == null || !columnGenerators.containsKey(id)) {
+ col.add(id);
+ }
+ }
+ // generators added last
+ if (columnGenerators != null && columnGenerators.size() > 0) {
+ col.addAll(columnGenerators.keySet());
+ }
+
+ setVisibleColumns(col.toArray());
+
+ // Assure visual refresh
+ resetPageBuffer();
+
+ enableContentRefreshing(true);
+ }
+
+ /**
+ * Gets items ids from a range of key values
+ *
+ * @param startRowKey
+ * The start key
+ * @param endRowKey
+ * The end key
+ * @return
+ */
+ private LinkedHashSet<Object> getItemIdsInRange(Object itemId,
+ final int length) {
+ LinkedHashSet<Object> ids = new LinkedHashSet<Object>();
+ for (int i = 0; i < length; i++) {
+ assert itemId != null; // should not be null unless client-server
+ // are out of sync
+ ids.add(itemId);
+ itemId = nextItemId(itemId);
+ }
+ return ids;
+ }
+
+ /**
+ * Handles selection if selection is a multiselection
+ *
+ * @param variables
+ * The variables
+ */
+ private void handleSelectedItems(Map<String, Object> variables) {
+ final String[] ka = (String[]) variables.get("selected");
+ final String[] ranges = (String[]) variables.get("selectedRanges");
+
+ Set<Object> renderedButNotSelectedItemIds = getCurrentlyRenderedItemIds();
+
+ @SuppressWarnings("unchecked")
+ HashSet<Object> newValue = new LinkedHashSet<Object>(
+ (Collection<Object>) getValue());
+
+ if (variables.containsKey("clearSelections")) {
+ // the client side has instructed to swipe all previous selections
+ newValue.clear();
+ }
+
+ /*
+ * Then add (possibly some of them back) rows that are currently
+ * selected on the client side (the ones that the client side is aware
+ * of).
+ */
+ for (int i = 0; i < ka.length; i++) {
+ // key to id
+ final Object id = itemIdMapper.get(ka[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ requestRepaint();
+ } else if (id != null && containsId(id)) {
+ newValue.add(id);
+ renderedButNotSelectedItemIds.remove(id);
+ }
+ }
+
+ /* Add range items aka shift clicked multiselection areas */
+ if (ranges != null) {
+ for (String range : ranges) {
+ String[] split = range.split("-");
+ Object startItemId = itemIdMapper.get(split[0]);
+ int length = Integer.valueOf(split[1]);
+ LinkedHashSet<Object> itemIdsInRange = getItemIdsInRange(
+ startItemId, length);
+ newValue.addAll(itemIdsInRange);
+ renderedButNotSelectedItemIds.removeAll(itemIdsInRange);
+ }
+ }
+ /*
+ * finally clear all currently rendered rows (the ones that the client
+ * side counterpart is aware of) that the client didn't send as selected
+ */
+ newValue.removeAll(renderedButNotSelectedItemIds);
+
+ if (!isNullSelectionAllowed() && newValue.isEmpty()) {
+ // empty selection not allowed, keep old value
+ requestRepaint();
+ return;
+ }
+
+ setValue(newValue, true);
+
+ }
+
+ private Set<Object> getCurrentlyRenderedItemIds() {
+ HashSet<Object> ids = new HashSet<Object>();
+ if (pageBuffer != null) {
+ for (int i = 0; i < pageBuffer[CELL_ITEMID].length; i++) {
+ ids.add(pageBuffer[CELL_ITEMID][i]);
+ }
+ }
+ return ids;
+ }
+
+ /* Component basics */
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @see com.vaadin.ui.Select#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+
+ boolean clientNeedsContentRefresh = false;
+
+ handleClickEvent(variables);
+
+ handleColumnResizeEvent(variables);
+
+ handleColumnWidthUpdates(variables);
+
+ disableContentRefreshing();
+
+ if (!isSelectable() && variables.containsKey("selected")) {
+ // Not-selectable is a special case, AbstractSelect does not support
+ // TODO could be optimized.
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
+ /*
+ * The AbstractSelect cannot handle the multiselection properly, instead
+ * we handle it ourself
+ */
+ else if (isSelectable() && isMultiSelect()
+ && variables.containsKey("selected")
+ && multiSelectMode == MultiSelectMode.DEFAULT) {
+ handleSelectedItems(variables);
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
+ super.changeVariables(source, variables);
+
+ // Client might update the pagelength if Table height is fixed
+ if (variables.containsKey("pagelength")) {
+ // Sets pageLength directly to avoid repaint that setter causes
+ pageLength = (Integer) variables.get("pagelength");
+ }
+
+ // Page start index
+ if (variables.containsKey("firstvisible")) {
+ final Integer value = (Integer) variables.get("firstvisible");
+ if (value != null) {
+ setCurrentPageFirstItemIndex(value.intValue(), false);
+ }
+ }
+
+ // Sets requested firstrow and rows for the next paint
+ if (variables.containsKey("reqfirstrow")
+ || variables.containsKey("reqrows")) {
+
+ try {
+ firstToBeRenderedInClient = ((Integer) variables
+ .get("firstToBeRendered")).intValue();
+ lastToBeRenderedInClient = ((Integer) variables
+ .get("lastToBeRendered")).intValue();
+ } catch (Exception e) {
+ // FIXME: Handle exception
+ getLogger().log(Level.FINER,
+ "Could not parse the first and/or last rows.", e);
+ }
+
+ // respect suggested rows only if table is not otherwise updated
+ // (row caches emptied by other event)
+ if (!containerChangeToBeRendered) {
+ Integer value = (Integer) variables.get("reqfirstrow");
+ if (value != null) {
+ reqFirstRowToPaint = value.intValue();
+ }
+ value = (Integer) variables.get("reqrows");
+ if (value != null) {
+ reqRowsToPaint = value.intValue();
+ // sanity check
+ if (reqFirstRowToPaint + reqRowsToPaint > size()) {
+ reqRowsToPaint = size() - reqFirstRowToPaint;
+ }
+ }
+ }
+ getLogger().finest(
+ "Client wants rows " + reqFirstRowToPaint + "-"
+ + (reqFirstRowToPaint + reqRowsToPaint - 1));
+ clientNeedsContentRefresh = true;
+ }
+
+ if (isSortEnabled()) {
+ // Sorting
+ boolean doSort = false;
+ if (variables.containsKey("sortcolumn")) {
+ final String colId = (String) variables.get("sortcolumn");
+ if (colId != null && !"".equals(colId) && !"null".equals(colId)) {
+ final Object id = columnIdMap.get(colId);
+ setSortContainerPropertyId(id, false);
+ doSort = true;
+ }
+ }
+ if (variables.containsKey("sortascending")) {
+ final boolean state = ((Boolean) variables.get("sortascending"))
+ .booleanValue();
+ if (state != sortAscending) {
+ setSortAscending(state, false);
+ doSort = true;
+ }
+ }
+ if (doSort) {
+ this.sort();
+ resetPageBuffer();
+ }
+ }
+
+ // Dynamic column hide/show and order
+ // Update visible columns
+ if (isColumnCollapsingAllowed()) {
+ if (variables.containsKey("collapsedcolumns")) {
+ try {
+ final Object[] ids = (Object[]) variables
+ .get("collapsedcolumns");
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext();) {
+ setColumnCollapsed(it.next(), false);
+ }
+ for (int i = 0; i < ids.length; i++) {
+ setColumnCollapsed(columnIdMap.get(ids[i].toString()),
+ true);
+ }
+ } catch (final Exception e) {
+ // FIXME: Handle exception
+ getLogger().log(Level.FINER,
+ "Could not determine column collapsing state", e);
+ }
+ clientNeedsContentRefresh = true;
+ }
+ }
+ if (isColumnReorderingAllowed()) {
+ if (variables.containsKey("columnorder")) {
+ try {
+ final Object[] ids = (Object[]) variables
+ .get("columnorder");
+ // need a real Object[], ids can be a String[]
+ final Object[] idsTemp = new Object[ids.length];
+ for (int i = 0; i < ids.length; i++) {
+ idsTemp[i] = columnIdMap.get(ids[i].toString());
+ }
+ setColumnOrder(idsTemp);
+ if (hasListeners(ColumnReorderEvent.class)) {
+ fireEvent(new ColumnReorderEvent(this));
+ }
+ } catch (final Exception e) {
+ // FIXME: Handle exception
+ getLogger().log(Level.FINER,
+ "Could not determine column reordering state", e);
+ }
+ clientNeedsContentRefresh = true;
+ }
+ }
+
+ enableContentRefreshing(clientNeedsContentRefresh);
+
+ // Actions
+ if (variables.containsKey("action")) {
+ final StringTokenizer st = new StringTokenizer(
+ (String) variables.get("action"), ",");
+ if (st.countTokens() == 2) {
+ final Object itemId = itemIdMapper.get(st.nextToken());
+ final Action action = actionMapper.get(st.nextToken());
+
+ if (action != null && (itemId == null || containsId(itemId))
+ && actionHandlers != null) {
+ for (Handler ah : actionHandlers) {
+ ah.handleAction(action, this, itemId);
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Handles click event
+ *
+ * @param variables
+ */
+ private void handleClickEvent(Map<String, Object> variables) {
+
+ // Item click event
+ if (variables.containsKey("clickEvent")) {
+ String key = (String) variables.get("clickedKey");
+ Object itemId = itemIdMapper.get(key);
+ Object propertyId = null;
+ String colkey = (String) variables.get("clickedColKey");
+ // click is not necessary on a property
+ if (colkey != null) {
+ propertyId = columnIdMap.get(colkey);
+ }
+ MouseEventDetails evt = MouseEventDetails
+ .deSerialize((String) variables.get("clickEvent"));
+ Item item = getItem(itemId);
+ if (item != null) {
+ fireEvent(new ItemClickEvent(this, item, itemId, propertyId,
+ evt));
+ }
+ }
+
+ // Header click event
+ else if (variables.containsKey("headerClickEvent")) {
+
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("headerClickEvent"));
+
+ Object cid = variables.get("headerClickCID");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+ }
+ fireEvent(new HeaderClickEvent(this, propertyId, details));
+ }
+
+ // Footer click event
+ else if (variables.containsKey("footerClickEvent")) {
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("footerClickEvent"));
+
+ Object cid = variables.get("footerClickCID");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+ }
+ fireEvent(new FooterClickEvent(this, propertyId, details));
+ }
+ }
+
+ /**
+ * Handles the column resize event sent by the client.
+ *
+ * @param variables
+ */
+ private void handleColumnResizeEvent(Map<String, Object> variables) {
+ if (variables.containsKey("columnResizeEventColumn")) {
+ Object cid = variables.get("columnResizeEventColumn");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+
+ Object prev = variables.get("columnResizeEventPrev");
+ int previousWidth = -1;
+ if (prev != null) {
+ previousWidth = Integer.valueOf(prev.toString());
+ }
+
+ Object curr = variables.get("columnResizeEventCurr");
+ int currentWidth = -1;
+ if (curr != null) {
+ currentWidth = Integer.valueOf(curr.toString());
+ }
+
+ fireColumnResizeEvent(propertyId, previousWidth, currentWidth);
+ }
+ }
+ }
+
+ private void fireColumnResizeEvent(Object propertyId, int previousWidth,
+ int currentWidth) {
+ /*
+ * Update the sizes on the server side. If a column previously had a
+ * expand ratio and the user resized the column then the expand ratio
+ * will be turned into a static pixel size.
+ */
+ setColumnWidth(propertyId, currentWidth);
+
+ fireEvent(new ColumnResizeEvent(this, propertyId, previousWidth,
+ currentWidth));
+ }
+
+ private void handleColumnWidthUpdates(Map<String, Object> variables) {
+ if (variables.containsKey("columnWidthUpdates")) {
+ String[] events = (String[]) variables.get("columnWidthUpdates");
+ for (String str : events) {
+ String[] eventDetails = str.split(":");
+ Object propertyId = columnIdMap.get(eventDetails[0]);
+ if (propertyId == null) {
+ propertyId = ROW_HEADER_FAKE_PROPERTY_ID;
+ }
+ int width = Integer.valueOf(eventDetails[1]);
+ setColumnWidth(propertyId, width);
+ }
+ }
+ }
+
+ /**
+ * Go to mode where content updates are not done. This is due we want to
+ * bypass expensive content for some reason (like when we know we may have
+ * other content changes on their way).
+ *
+ * @return true if content refresh flag was enabled prior this call
+ */
+ protected boolean disableContentRefreshing() {
+ boolean wasDisabled = isContentRefreshesEnabled;
+ isContentRefreshesEnabled = false;
+ return wasDisabled;
+ }
+
+ /**
+ * Go to mode where content content refreshing has effect.
+ *
+ * @param refreshContent
+ * true if content refresh needs to be done
+ */
+ protected void enableContentRefreshing(boolean refreshContent) {
+ isContentRefreshesEnabled = true;
+ if (refreshContent) {
+ refreshRenderedCells();
+ // Ensure that client gets a response
+ requestRepaint();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractSelect#paintContent(com.vaadin.
+ * terminal.PaintTarget)
+ */
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ /*
+ * Body actions - Actions which has the target null and can be invoked
+ * by right clicking on the table body.
+ */
+ final Set<Action> actionSet = findAndPaintBodyActions(target);
+
+ final Object[][] cells = getVisibleCells();
+ int rows = findNumRowsToPaint(target, cells);
+
+ int total = size();
+ if (shouldHideNullSelectionItem()) {
+ total--;
+ rows--;
+ }
+
+ // Table attributes
+ paintTableAttributes(target, rows, total);
+
+ paintVisibleColumnOrder(target);
+
+ // Rows
+ if (isPartialRowUpdate() && painted && !target.isFullRepaint()) {
+ paintPartialRowUpdate(target, actionSet);
+ /*
+ * Send the page buffer indexes to ensure that the client side stays
+ * in sync. Otherwise we _might_ have the situation where the client
+ * side discards too few or too many rows, causing out of sync
+ * issues.
+ *
+ * This could probably be done for full repaints also to simplify
+ * the client side.
+ */
+ int pageBufferLastIndex = pageBufferFirstIndex
+ + pageBuffer[CELL_ITEMID].length - 1;
+ target.addAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST,
+ pageBufferFirstIndex);
+ target.addAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_LAST,
+ pageBufferLastIndex);
+ } else if (target.isFullRepaint() || isRowCacheInvalidated()) {
+ paintRows(target, cells, actionSet);
+ setRowCacheInvalidated(false);
+ }
+
+ paintSorting(target);
+
+ resetVariablesAndPageBuffer(target);
+
+ // Actions
+ paintActions(target, actionSet);
+
+ paintColumnOrder(target);
+
+ // Available columns
+ paintAvailableColumns(target);
+
+ paintVisibleColumns(target);
+
+ if (keyMapperReset) {
+ keyMapperReset = false;
+ target.addAttribute(VScrollTable.ATTRIBUTE_KEY_MAPPER_RESET, true);
+ }
+
+ if (dropHandler != null) {
+ dropHandler.getAcceptCriterion().paint(target);
+ }
+
+ painted = true;
+ }
+
+ private void setRowCacheInvalidated(boolean invalidated) {
+ rowCacheInvalidated = invalidated;
+ }
+
+ protected boolean isRowCacheInvalidated() {
+ return rowCacheInvalidated;
+ }
+
+ private void paintPartialRowUpdate(PaintTarget target, Set<Action> actionSet)
+ throws PaintException {
+ paintPartialRowUpdates(target, actionSet);
+ paintPartialRowAdditions(target, actionSet);
+ }
+
+ private void paintPartialRowUpdates(PaintTarget target,
+ Set<Action> actionSet) throws PaintException {
+ final boolean[] iscomponent = findCellsWithComponents();
+
+ int firstIx = getFirstUpdatedItemIndex();
+ int count = getUpdatedRowCount();
+
+ target.startTag("urows");
+ target.addAttribute("firsturowix", firstIx);
+ target.addAttribute("numurows", count);
+
+ // Partial row updates bypass the normal caching mechanism.
+ Object[][] cells = getVisibleCellsUpdateCacheRows(firstIx, count);
+ for (int indexInRowbuffer = 0; indexInRowbuffer < count; indexInRowbuffer++) {
+ final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
+
+ if (shouldHideNullSelectionItem()) {
+ // Remove null selection item if null selection is not allowed
+ continue;
+ }
+
+ paintRow(target, cells, isEditable(), actionSet, iscomponent,
+ indexInRowbuffer, itemId);
+ }
+ target.endTag("urows");
+ }
+
+ private void paintPartialRowAdditions(PaintTarget target,
+ Set<Action> actionSet) throws PaintException {
+ final boolean[] iscomponent = findCellsWithComponents();
+
+ int firstIx = getFirstAddedItemIndex();
+ int count = getAddedRowCount();
+
+ target.startTag("prows");
+
+ if (!shouldHideAddedRows()) {
+ getLogger().finest(
+ "Paint rows for add. Index: " + firstIx + ", count: "
+ + count + ".");
+
+ // Partial row additions bypass the normal caching mechanism.
+ Object[][] cells = getVisibleCellsInsertIntoCache(firstIx, count);
+ if (cells[0].length < count) {
+ // delete the rows below, since they will fall beyond the cache
+ // page.
+ target.addAttribute("delbelow", true);
+ count = cells[0].length;
+ }
+
+ for (int indexInRowbuffer = 0; indexInRowbuffer < count; indexInRowbuffer++) {
+ final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
+ if (shouldHideNullSelectionItem()) {
+ // Remove null selection item if null selection is not
+ // allowed
+ continue;
+ }
+
+ paintRow(target, cells, isEditable(), actionSet, iscomponent,
+ indexInRowbuffer, itemId);
+ }
+ } else {
+ getLogger().finest(
+ "Paint rows for remove. Index: " + firstIx + ", count: "
+ + count + ".");
+ removeRowsFromCacheAndFillBottom(firstIx, count);
+ target.addAttribute("hide", true);
+ }
+
+ target.addAttribute("firstprowix", firstIx);
+ target.addAttribute("numprows", count);
+ target.endTag("prows");
+ }
+
+ /**
+ * Subclass and override this to enable partial row updates and additions,
+ * which bypass the normal caching mechanism. This is useful for e.g.
+ * TreeTable.
+ *
+ * @return true if this update is a partial row update, false if not. For
+ * plain Table it is always false.
+ */
+ protected boolean isPartialRowUpdate() {
+ return false;
+ }
+
+ /**
+ * Subclass and override this to enable partial row additions, bypassing the
+ * normal caching mechanism. This is useful for e.g. TreeTable, where
+ * expanding a node should only fetch and add the items inside of that node.
+ *
+ * @return The index of the first added item. For plain Table it is always
+ * 0.
+ */
+ protected int getFirstAddedItemIndex() {
+ return 0;
+ }
+
+ /**
+ * Subclass and override this to enable partial row additions, bypassing the
+ * normal caching mechanism. This is useful for e.g. TreeTable, where
+ * expanding a node should only fetch and add the items inside of that node.
+ *
+ * @return the number of rows to be added, starting at the index returned by
+ * {@link #getFirstAddedItemIndex()}. For plain Table it is always
+ * 0.
+ */
+ protected int getAddedRowCount() {
+ return 0;
+ }
+
+ /**
+ * Subclass and override this to enable removing of rows, bypassing the
+ * normal caching and lazy loading mechanism. This is useful for e.g.
+ * TreeTable, when you need to hide certain rows as a node is collapsed.
+ *
+ * This should return true if the rows pointed to by
+ * {@link #getFirstAddedItemIndex()} and {@link #getAddedRowCount()} should
+ * be hidden instead of added.
+ *
+ * @return whether the rows to add (see {@link #getFirstAddedItemIndex()}
+ * and {@link #getAddedRowCount()}) should be added or hidden. For
+ * plain Table it is always false.
+ */
+ protected boolean shouldHideAddedRows() {
+ return false;
+ }
+
+ /**
+ * Subclass and override this to enable partial row updates, bypassing the
+ * normal caching and lazy loading mechanism. This is useful for updating
+ * the state of certain rows, e.g. in the TreeTable the collapsed state of a
+ * single node is updated using this mechanism.
+ *
+ * @return the index of the first item to be updated. For plain Table it is
+ * always 0.
+ */
+ protected int getFirstUpdatedItemIndex() {
+ return 0;
+ }
+
+ /**
+ * Subclass and override this to enable partial row updates, bypassing the
+ * normal caching and lazy loading mechanism. This is useful for updating
+ * the state of certain rows, e.g. in the TreeTable the collapsed state of a
+ * single node is updated using this mechanism.
+ *
+ * @return the number of rows to update, starting at the index returned by
+ * {@link #getFirstUpdatedItemIndex()}. For plain table it is always
+ * 0.
+ */
+ protected int getUpdatedRowCount() {
+ return 0;
+ }
+
+ private void paintTableAttributes(PaintTarget target, int rows, int total)
+ throws PaintException {
+ paintTabIndex(target);
+ paintDragMode(target);
+ paintSelectMode(target);
+
+ if (cacheRate != CACHE_RATE_DEFAULT) {
+ target.addAttribute("cr", cacheRate);
+ }
+
+ target.addAttribute("cols", getVisibleColumns().length);
+ target.addAttribute("rows", rows);
+
+ target.addAttribute("firstrow",
+ (reqFirstRowToPaint >= 0 ? reqFirstRowToPaint
+ : firstToBeRenderedInClient));
+ target.addAttribute("totalrows", total);
+ if (getPageLength() != 0) {
+ target.addAttribute("pagelength", getPageLength());
+ }
+ if (areColumnHeadersEnabled()) {
+ target.addAttribute("colheaders", true);
+ }
+ if (rowHeadersAreEnabled()) {
+ target.addAttribute("rowheaders", true);
+ }
+
+ target.addAttribute("colfooters", columnFootersVisible);
+
+ // The cursors are only shown on pageable table
+ if (getCurrentPageFirstItemIndex() != 0 || getPageLength() > 0) {
+ target.addVariable(this, "firstvisible",
+ getCurrentPageFirstItemIndex());
+ }
+ }
+
+ /**
+ * Resets and paints "to be painted next" variables. Also reset pageBuffer
+ */
+ private void resetVariablesAndPageBuffer(PaintTarget target)
+ throws PaintException {
+ reqFirstRowToPaint = -1;
+ reqRowsToPaint = -1;
+ containerChangeToBeRendered = false;
+ target.addVariable(this, "reqrows", reqRowsToPaint);
+ target.addVariable(this, "reqfirstrow", reqFirstRowToPaint);
+ }
+
+ private boolean areColumnHeadersEnabled() {
+ return getColumnHeaderMode() != ColumnHeaderMode.HIDDEN;
+ }
+
+ private void paintVisibleColumns(PaintTarget target) throws PaintException {
+ target.startTag("visiblecolumns");
+ if (rowHeadersAreEnabled()) {
+ target.startTag("column");
+ target.addAttribute("cid", ROW_HEADER_COLUMN_KEY);
+ paintColumnWidth(target, ROW_HEADER_FAKE_PROPERTY_ID);
+ target.endTag("column");
+ }
+ final Collection<?> sortables = getSortableContainerPropertyIds();
+ for (Object colId : visibleColumns) {
+ if (colId != null) {
+ target.startTag("column");
+ target.addAttribute("cid", columnIdMap.key(colId));
+ final String head = getColumnHeader(colId);
+ target.addAttribute("caption", (head != null ? head : ""));
+ final String foot = getColumnFooter(colId);
+ target.addAttribute("fcaption", (foot != null ? foot : ""));
+ if (isColumnCollapsed(colId)) {
+ target.addAttribute("collapsed", true);
+ }
+ if (areColumnHeadersEnabled()) {
+ if (getColumnIcon(colId) != null) {
+ target.addAttribute("icon", getColumnIcon(colId));
+ }
+ if (sortables.contains(colId)) {
+ target.addAttribute("sortable", true);
+ }
+ }
+ if (!Align.LEFT.equals(getColumnAlignment(colId))) {
+ target.addAttribute("align", getColumnAlignment(colId)
+ .toString());
+ }
+ paintColumnWidth(target, colId);
+ target.endTag("column");
+ }
+ }
+ target.endTag("visiblecolumns");
+ }
+
+ private void paintAvailableColumns(PaintTarget target)
+ throws PaintException {
+ if (columnCollapsingAllowed) {
+ final HashSet<Object> collapsedCols = new HashSet<Object>();
+ for (Object colId : visibleColumns) {
+ if (isColumnCollapsed(colId)) {
+ collapsedCols.add(colId);
+ }
+ }
+ final String[] collapsedKeys = new String[collapsedCols.size()];
+ int nextColumn = 0;
+ for (Object colId : visibleColumns) {
+ if (isColumnCollapsed(colId)) {
+ collapsedKeys[nextColumn++] = columnIdMap.key(colId);
+ }
+ }
+ target.addVariable(this, "collapsedcolumns", collapsedKeys);
+
+ final String[] noncollapsibleKeys = new String[noncollapsibleColumns
+ .size()];
+ nextColumn = 0;
+ for (Object colId : noncollapsibleColumns) {
+ noncollapsibleKeys[nextColumn++] = columnIdMap.key(colId);
+ }
+ target.addVariable(this, "noncollapsiblecolumns",
+ noncollapsibleKeys);
+ }
+
+ }
+
+ private void paintActions(PaintTarget target, final Set<Action> actionSet)
+ throws PaintException {
+ if (!actionSet.isEmpty()) {
+ target.addVariable(this, "action", "");
+ target.startTag("actions");
+ for (Action a : actionSet) {
+ target.startTag("action");
+ if (a.getCaption() != null) {
+ target.addAttribute("caption", a.getCaption());
+ }
+ if (a.getIcon() != null) {
+ target.addAttribute("icon", a.getIcon());
+ }
+ target.addAttribute("key", actionMapper.key(a));
+ target.endTag("action");
+ }
+ target.endTag("actions");
+ }
+ }
+
+ private void paintColumnOrder(PaintTarget target) throws PaintException {
+ if (columnReorderingAllowed) {
+ final String[] colorder = new String[visibleColumns.size()];
+ int i = 0;
+ for (Object colId : visibleColumns) {
+ colorder[i++] = columnIdMap.key(colId);
+ }
+ target.addVariable(this, "columnorder", colorder);
+ }
+ }
+
+ private void paintSorting(PaintTarget target) throws PaintException {
+ // Sorting
+ if (getContainerDataSource() instanceof Container.Sortable) {
+ target.addVariable(this, "sortcolumn",
+ columnIdMap.key(sortContainerPropertyId));
+ target.addVariable(this, "sortascending", sortAscending);
+ }
+ }
+
+ private void paintRows(PaintTarget target, final Object[][] cells,
+ final Set<Action> actionSet) throws PaintException {
+ final boolean[] iscomponent = findCellsWithComponents();
+
+ target.startTag("rows");
+ // cells array contains all that are supposed to be visible on client,
+ // but we'll start from the one requested by client
+ int start = 0;
+ if (reqFirstRowToPaint != -1 && firstToBeRenderedInClient != -1) {
+ start = reqFirstRowToPaint - firstToBeRenderedInClient;
+ }
+ int end = cells[0].length;
+ if (reqRowsToPaint != -1) {
+ end = start + reqRowsToPaint;
+ }
+ // sanity check
+ if (lastToBeRenderedInClient != -1 && lastToBeRenderedInClient < end) {
+ end = lastToBeRenderedInClient + 1;
+ }
+ if (start > cells[CELL_ITEMID].length || start < 0) {
+ start = 0;
+ }
+ if (end > cells[CELL_ITEMID].length) {
+ end = cells[CELL_ITEMID].length;
+ }
+
+ for (int indexInRowbuffer = start; indexInRowbuffer < end; indexInRowbuffer++) {
+ final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
+
+ if (shouldHideNullSelectionItem()) {
+ // Remove null selection item if null selection is not allowed
+ continue;
+ }
+
+ paintRow(target, cells, isEditable(), actionSet, iscomponent,
+ indexInRowbuffer, itemId);
+ }
+ target.endTag("rows");
+ }
+
+ private boolean[] findCellsWithComponents() {
+ final boolean[] isComponent = new boolean[visibleColumns.size()];
+ int ix = 0;
+ for (Object columnId : visibleColumns) {
+ if (columnGenerators.containsKey(columnId)) {
+ isComponent[ix++] = true;
+ } else {
+ final Class<?> colType = getType(columnId);
+ isComponent[ix++] = colType != null
+ && Component.class.isAssignableFrom(colType);
+ }
+ }
+ return isComponent;
+ }
+
+ private void paintVisibleColumnOrder(PaintTarget target) {
+ // Visible column order
+ final ArrayList<String> visibleColOrder = new ArrayList<String>();
+ for (Object columnId : visibleColumns) {
+ if (!isColumnCollapsed(columnId)) {
+ visibleColOrder.add(columnIdMap.key(columnId));
+ }
+ }
+ target.addAttribute("vcolorder", visibleColOrder.toArray());
+ }
+
+ private Set<Action> findAndPaintBodyActions(PaintTarget target) {
+ Set<Action> actionSet = new LinkedHashSet<Action>();
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ for (Handler ah : actionHandlers) {
+ // Getting actions for the null item, which in this case means
+ // the body item
+ final Action[] actions = ah.getActions(null, this);
+ if (actions != null) {
+ for (Action action : actions) {
+ actionSet.add(action);
+ keys.add(actionMapper.key(action));
+ }
+ }
+ }
+ target.addAttribute("alb", keys.toArray());
+ }
+ return actionSet;
+ }
+
+ private boolean shouldHideNullSelectionItem() {
+ return !isNullSelectionAllowed() && getNullSelectionItemId() != null
+ && containsId(getNullSelectionItemId());
+ }
+
+ private int findNumRowsToPaint(PaintTarget target, final Object[][] cells)
+ throws PaintException {
+ int rows;
+ if (reqRowsToPaint >= 0) {
+ rows = reqRowsToPaint;
+ } else {
+ rows = cells[0].length;
+ if (alwaysRecalculateColumnWidths) {
+ // TODO experimental feature for now: tell the client to
+ // recalculate column widths.
+ // We'll only do this for paints that do not originate from
+ // table scroll/cache requests (i.e when reqRowsToPaint<0)
+ target.addAttribute("recalcWidths", true);
+ }
+ }
+ return rows;
+ }
+
+ private void paintSelectMode(PaintTarget target) throws PaintException {
+ if (multiSelectMode != MultiSelectMode.DEFAULT) {
+ target.addAttribute("multiselectmode", multiSelectMode.ordinal());
+ }
+ if (isSelectable()) {
+ target.addAttribute("selectmode", (isMultiSelect() ? "multi"
+ : "single"));
+ } else {
+ target.addAttribute("selectmode", "none");
+ }
+ if (!isNullSelectionAllowed()) {
+ target.addAttribute("nsa", false);
+ }
+
+ // selection support
+ // The select variable is only enabled if selectable
+ if (isSelectable()) {
+ target.addVariable(this, "selected", findSelectedKeys());
+ }
+ }
+
+ private String[] findSelectedKeys() {
+ LinkedList<String> selectedKeys = new LinkedList<String>();
+ if (isMultiSelect()) {
+ HashSet<?> sel = new HashSet<Object>((Set<?>) getValue());
+ Collection<?> vids = getVisibleItemIds();
+ for (Iterator<?> it = vids.iterator(); it.hasNext();) {
+ Object id = it.next();
+ if (sel.contains(id)) {
+ selectedKeys.add(itemIdMapper.key(id));
+ }
+ }
+ } else {
+ Object value = getValue();
+ if (value == null) {
+ value = getNullSelectionItemId();
+ }
+ if (value != null) {
+ selectedKeys.add(itemIdMapper.key(value));
+ }
+ }
+ return selectedKeys.toArray(new String[selectedKeys.size()]);
+ }
+
+ private void paintDragMode(PaintTarget target) throws PaintException {
+ if (dragMode != TableDragMode.NONE) {
+ target.addAttribute("dragmode", dragMode.ordinal());
+ }
+ }
+
+ private void paintTabIndex(PaintTarget target) throws PaintException {
+ // The tab ordering number
+ if (getTabIndex() > 0) {
+ target.addAttribute("tabindex", getTabIndex());
+ }
+ }
+
+ private void paintColumnWidth(PaintTarget target, final Object columnId)
+ throws PaintException {
+ if (columnWidths.containsKey(columnId)) {
+ if (getColumnWidth(columnId) > -1) {
+ target.addAttribute("width",
+ String.valueOf(getColumnWidth(columnId)));
+ } else {
+ target.addAttribute("er", getColumnExpandRatio(columnId));
+ }
+ }
+ }
+
+ private boolean rowHeadersAreEnabled() {
+ return getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN;
+ }
+
+ private void paintRow(PaintTarget target, final Object[][] cells,
+ final boolean iseditable, final Set<Action> actionSet,
+ final boolean[] iscomponent, int indexInRowbuffer,
+ final Object itemId) throws PaintException {
+ target.startTag("tr");
+
+ paintRowAttributes(target, cells, actionSet, indexInRowbuffer, itemId);
+
+ // cells
+ int currentColumn = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext(); currentColumn++) {
+ final Object columnId = it.next();
+ if (columnId == null || isColumnCollapsed(columnId)) {
+ continue;
+ }
+ /*
+ * For each cell, if a cellStyleGenerator is specified, get the
+ * specific style for the cell. If there is any, add it to the
+ * target.
+ */
+ if (cellStyleGenerator != null) {
+ String cellStyle = cellStyleGenerator
+ .getStyle(itemId, columnId);
+ if (cellStyle != null && !cellStyle.equals("")) {
+ target.addAttribute("style-" + columnIdMap.key(columnId),
+ cellStyle);
+ }
+ }
+
+ if ((iscomponent[currentColumn] || iseditable || cells[CELL_GENERATED_ROW][indexInRowbuffer] != null)
+ && Component.class.isInstance(cells[CELL_FIRSTCOL
+ + currentColumn][indexInRowbuffer])) {
+ final Component c = (Component) cells[CELL_FIRSTCOL
+ + currentColumn][indexInRowbuffer];
+ if (c == null) {
+ target.addText("");
+ paintCellTooltips(target, itemId, columnId);
+ } else {
+ LegacyPaint.paint(c, target);
+ }
+ } else {
+ target.addText((String) cells[CELL_FIRSTCOL + currentColumn][indexInRowbuffer]);
+ paintCellTooltips(target, itemId, columnId);
+ }
+ }
+
+ target.endTag("tr");
+ }
+
+ private void paintCellTooltips(PaintTarget target, Object itemId,
+ Object columnId) throws PaintException {
+ if (itemDescriptionGenerator != null) {
+ String itemDescription = itemDescriptionGenerator
+ .generateDescription(this, itemId, columnId);
+ if (itemDescription != null && !itemDescription.equals("")) {
+ target.addAttribute("descr-" + columnIdMap.key(columnId),
+ itemDescription);
+ }
+ }
+ }
+
+ private void paintRowTooltips(PaintTarget target, Object itemId)
+ throws PaintException {
+ if (itemDescriptionGenerator != null) {
+ String rowDescription = itemDescriptionGenerator
+ .generateDescription(this, itemId, null);
+ if (rowDescription != null && !rowDescription.equals("")) {
+ target.addAttribute("rowdescr", rowDescription);
+ }
+ }
+ }
+
+ private void paintRowAttributes(PaintTarget target, final Object[][] cells,
+ final Set<Action> actionSet, int indexInRowbuffer,
+ final Object itemId) throws PaintException {
+ // tr attributes
+
+ paintRowIcon(target, cells, indexInRowbuffer);
+ paintRowHeader(target, cells, indexInRowbuffer);
+ paintGeneratedRowInfo(target, cells, indexInRowbuffer);
+ target.addAttribute("key",
+ Integer.parseInt(cells[CELL_KEY][indexInRowbuffer].toString()));
+
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ }
+
+ // Actions
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ for (Handler ah : actionHandlers) {
+ final Action[] aa = ah.getActions(itemId, this);
+ if (aa != null) {
+ for (int ai = 0; ai < aa.length; ai++) {
+ final String key = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ keys.add(key);
+ }
+ }
+ }
+ target.addAttribute("al", keys.toArray());
+ }
+
+ /*
+ * For each row, if a cellStyleGenerator is specified, get the specific
+ * style for the cell, using null as propertyId. If there is any, add it
+ * to the target.
+ */
+ if (cellStyleGenerator != null) {
+ String rowStyle = cellStyleGenerator.getStyle(itemId, null);
+ if (rowStyle != null && !rowStyle.equals("")) {
+ target.addAttribute("rowstyle", rowStyle);
+ }
+ }
+
+ paintRowTooltips(target, itemId);
+
+ paintRowAttributes(target, itemId);
+ }
+
+ private void paintGeneratedRowInfo(PaintTarget target, Object[][] cells,
+ int indexInRowBuffer) throws PaintException {
+ GeneratedRow generatedRow = (GeneratedRow) cells[CELL_GENERATED_ROW][indexInRowBuffer];
+ if (generatedRow != null) {
+ target.addAttribute("gen_html", generatedRow.isHtmlContentAllowed());
+ target.addAttribute("gen_span", generatedRow.isSpanColumns());
+ target.addAttribute("gen_widget",
+ generatedRow.getValue() instanceof Component);
+ }
+ }
+
+ protected void paintRowHeader(PaintTarget target, Object[][] cells,
+ int indexInRowbuffer) throws PaintException {
+ if (rowHeadersAreEnabled()) {
+ if (cells[CELL_HEADER][indexInRowbuffer] != null) {
+ target.addAttribute("caption",
+ (String) cells[CELL_HEADER][indexInRowbuffer]);
+ }
+ }
+
+ }
+
+ protected void paintRowIcon(PaintTarget target, final Object[][] cells,
+ int indexInRowbuffer) throws PaintException {
+ if (rowHeadersAreEnabled()
+ && cells[CELL_ICON][indexInRowbuffer] != null) {
+ target.addAttribute("icon",
+ (Resource) cells[CELL_ICON][indexInRowbuffer]);
+ }
+ }
+
+ /**
+ * A method where extended Table implementations may add their custom
+ * attributes for rows.
+ *
+ * @param target
+ * @param itemId
+ */
+ protected void paintRowAttributes(PaintTarget target, Object itemId)
+ throws PaintException {
+
+ }
+
+ /**
+ * Gets the cached visible table contents.
+ *
+ * @return the cached visible table contents.
+ */
+ private Object[][] getVisibleCells() {
+ if (pageBuffer == null) {
+ refreshRenderedCells();
+ }
+ return pageBuffer;
+ }
+
+ /**
+ * Gets the value of property.
+ *
+ * By default if the table is editable the fieldFactory is used to create
+ * editors for table cells. Otherwise formatPropertyValue is used to format
+ * the value representation.
+ *
+ * @param rowId
+ * the Id of the row (same as item Id).
+ * @param colId
+ * the Id of the column.
+ * @param property
+ * the Property to be presented.
+ * @return Object Either formatted value or Component for field.
+ * @see #setTableFieldFactory(TableFieldFactory)
+ */
+ protected Object getPropertyValue(Object rowId, Object colId,
+ Property property) {
+ if (isEditable() && fieldFactory != null) {
+ final Field<?> f = fieldFactory.createField(
+ getContainerDataSource(), rowId, colId, this);
+ if (f != null) {
+ // Remember that we have made this association so we can remove
+ // it when the component is removed
+ associatedProperties.put(f, property);
+ bindPropertyToField(rowId, colId, property, f);
+ return f;
+ }
+ }
+
+ return formatPropertyValue(rowId, colId, property);
+ }
+
+ /**
+ * Binds an item property to a field generated by TableFieldFactory. The
+ * default behavior is to bind property straight to Field. If
+ * Property.Viewer type property (e.g. PropertyFormatter) is already set for
+ * field, the property is bound to that Property.Viewer.
+ *
+ * @param rowId
+ * @param colId
+ * @param property
+ * @param field
+ * @since 6.7.3
+ */
+ protected void bindPropertyToField(Object rowId, Object colId,
+ Property property, Field field) {
+ // check if field has a property that is Viewer set. In that case we
+ // expect developer has e.g. PropertyFormatter that he wishes to use and
+ // assign the property to the Viewer instead.
+ boolean hasFilterProperty = field.getPropertyDataSource() != null
+ && (field.getPropertyDataSource() instanceof Property.Viewer);
+ if (hasFilterProperty) {
+ ((Property.Viewer) field.getPropertyDataSource())
+ .setPropertyDataSource(property);
+ } else {
+ field.setPropertyDataSource(property);
+ }
+ }
+
+ /**
+ * Formats table cell property values. By default the property.toString()
+ * and return a empty string for null properties.
+ *
+ * @param rowId
+ * the Id of the row (same as item Id).
+ * @param colId
+ * the Id of the column.
+ * @param property
+ * the Property to be formatted.
+ * @return the String representation of property and its value.
+ * @since 3.1
+ */
+ protected String formatPropertyValue(Object rowId, Object colId,
+ Property<?> property) {
+ if (property == null) {
+ return "";
+ }
+ Converter<String, Object> converter = null;
+
+ if (hasConverter(colId)) {
+ converter = getConverter(colId);
+ } else {
+ ConverterUtil.getConverter(String.class, property.getType(),
+ getApplication());
+ }
+ Object value = property.getValue();
+ if (converter != null) {
+ return converter.convertToPresentation(value, getLocale());
+ }
+ return (null != value) ? value.toString() : "";
+ }
+
+ /* Action container */
+
+ /**
+ * Registers a new action handler for this container
+ *
+ * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler)
+ */
+
+ @Override
+ public void addActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList<Handler>();
+ actionMapper = new KeyMapper<Action>();
+ }
+
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the action
+ // handlers.
+ refreshRenderedCells();
+ }
+
+ }
+ }
+
+ /**
+ * Removes a previously registered action handler for the contents of this
+ * container.
+ *
+ * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
+ */
+
+ @Override
+ public void removeActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+
+ actionHandlers.remove(actionHandler);
+
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the action
+ // handlers.
+ refreshRenderedCells();
+ }
+ }
+
+ /**
+ * Removes all action handlers
+ */
+ public void removeAllActionHandlers() {
+ actionHandlers = null;
+ actionMapper = null;
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the action
+ // handlers.
+ refreshRenderedCells();
+ }
+
+ /* Property value change listening support */
+
+ /**
+ * Notifies this listener that the Property's value has changed.
+ *
+ * Also listens changes in rendered items to refresh content area.
+ *
+ * @see com.vaadin.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent)
+ */
+
+ @Override
+ public void valueChange(Property.ValueChangeEvent event) {
+ if (event.getProperty() == this
+ || event.getProperty() == getPropertyDataSource()) {
+ super.valueChange(event);
+ } else {
+ refreshRowCache();
+ containerChangeToBeRendered = true;
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Clears the current page buffer. Call this before
+ * {@link #refreshRenderedCells()} to ensure that all content is updated
+ * from the properties.
+ */
+ protected void resetPageBuffer() {
+ firstToBeRenderedInClient = -1;
+ lastToBeRenderedInClient = -1;
+ reqFirstRowToPaint = -1;
+ reqRowsToPaint = -1;
+ pageBuffer = null;
+ }
+
+ /**
+ * Notifies the component that it is connected to an application.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+
+ @Override
+ public void attach() {
+ super.attach();
+
+ refreshRenderedCells();
+ }
+
+ /**
+ * Notifies the component that it is detached from the application
+ *
+ * @see com.vaadin.ui.Component#detach()
+ */
+
+ @Override
+ public void detach() {
+ super.detach();
+ }
+
+ /**
+ * Removes all Items from the Container.
+ *
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+
+ @Override
+ public boolean removeAllItems() {
+ currentPageFirstItemId = null;
+ currentPageFirstItemIndex = 0;
+ return super.removeAllItems();
+ }
+
+ /**
+ * Removes the Item identified by <code>ItemId</code> from the Container.
+ *
+ * @see com.vaadin.data.Container#removeItem(Object)
+ */
+
+ @Override
+ public boolean removeItem(Object itemId) {
+ final Object nextItemId = nextItemId(itemId);
+ final boolean ret = super.removeItem(itemId);
+ if (ret && (itemId != null) && (itemId.equals(currentPageFirstItemId))) {
+ currentPageFirstItemId = nextItemId;
+ }
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return ret;
+ }
+
+ /**
+ * Removes a Property specified by the given Property ID from the Container.
+ *
+ * @see com.vaadin.data.Container#removeContainerProperty(Object)
+ */
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+
+ // If a visible property is removed, remove the corresponding column
+ visibleColumns.remove(propertyId);
+ columnAlignments.remove(propertyId);
+ columnIcons.remove(propertyId);
+ columnHeaders.remove(propertyId);
+ columnFooters.remove(propertyId);
+
+ return super.removeContainerProperty(propertyId);
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @param propertyId
+ * the Id of the proprty.
+ * @param type
+ * the class of the property.
+ * @param defaultValue
+ * the default value given for all existing items.
+ * @see com.vaadin.data.Container#addContainerProperty(Object, Class,
+ * Object)
+ */
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+
+ boolean visibleColAdded = false;
+ if (!visibleColumns.contains(propertyId)) {
+ visibleColumns.add(propertyId);
+ visibleColAdded = true;
+ }
+
+ if (!super.addContainerProperty(propertyId, type, defaultValue)) {
+ if (visibleColAdded) {
+ visibleColumns.remove(propertyId);
+ }
+ return false;
+ }
+ if (!(items instanceof Container.PropertySetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return true;
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @param propertyId
+ * the Id of the proprty
+ * @param type
+ * the class of the property
+ * @param defaultValue
+ * the default value given for all existing items
+ * @param columnHeader
+ * the Explicit header of the column. If explicit header is not
+ * needed, this should be set null.
+ * @param columnIcon
+ * the Icon of the column. If icon is not needed, this should be
+ * set null.
+ * @param columnAlignment
+ * the Alignment of the column. Null implies align left.
+ * @throws UnsupportedOperationException
+ * if the operation is not supported.
+ * @see com.vaadin.data.Container#addContainerProperty(Object, Class,
+ * Object)
+ */
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue, String columnHeader, Resource columnIcon,
+ Align columnAlignment) throws UnsupportedOperationException {
+ if (!this.addContainerProperty(propertyId, type, defaultValue)) {
+ return false;
+ }
+ setColumnAlignment(propertyId, columnAlignment);
+ setColumnHeader(propertyId, columnHeader);
+ setColumnIcon(propertyId, columnIcon);
+ return true;
+ }
+
+ /**
+ * Adds a generated column to the Table.
+ * <p>
+ * A generated column is a column that exists only in the Table, not as a
+ * property in the underlying Container. It shows up just as a regular
+ * column.
+ * </p>
+ * <p>
+ * A generated column will override a property with the same id, so that the
+ * generated column is shown instead of the column representing the
+ * property. Note that getContainerProperty() will still get the real
+ * property.
+ * </p>
+ * <p>
+ * Table will not listen to value change events from properties overridden
+ * by generated columns. If the content of your generated column depends on
+ * properties that are not directly visible in the table, attach value
+ * change listener to update the content on all depended properties.
+ * Otherwise your UI might not get updated as expected.
+ * </p>
+ * <p>
+ * Also note that getVisibleColumns() will return the generated columns,
+ * while getContainerPropertyIds() will not.
+ * </p>
+ *
+ * @param id
+ * the id of the column to be added
+ * @param generatedColumn
+ * the {@link ColumnGenerator} to use for this column
+ */
+ public void addGeneratedColumn(Object id, ColumnGenerator generatedColumn) {
+ if (generatedColumn == null) {
+ throw new IllegalArgumentException(
+ "Can not add null as a GeneratedColumn");
+ }
+ if (columnGenerators.containsKey(id)) {
+ throw new IllegalArgumentException(
+ "Can not add the same GeneratedColumn twice, id:" + id);
+ } else {
+ columnGenerators.put(id, generatedColumn);
+ /*
+ * add to visible column list unless already there (overriding
+ * column from DS)
+ */
+ if (!visibleColumns.contains(id)) {
+ visibleColumns.add(id);
+ }
+ refreshRowCache();
+ }
+ }
+
+ /**
+ * Returns the ColumnGenerator used to generate the given column.
+ *
+ * @param columnId
+ * The id of the generated column
+ * @return The ColumnGenerator used for the given columnId or null.
+ */
+ public ColumnGenerator getColumnGenerator(Object columnId)
+ throws IllegalArgumentException {
+ return columnGenerators.get(columnId);
+ }
+
+ /**
+ * Removes a generated column previously added with addGeneratedColumn.
+ *
+ * @param columnId
+ * id of the generated column to remove
+ * @return true if the column could be removed (existed in the Table)
+ */
+ public boolean removeGeneratedColumn(Object columnId) {
+ if (columnGenerators.containsKey(columnId)) {
+ columnGenerators.remove(columnId);
+ // remove column from visibleColumns list unless it exists in
+ // container (generator previously overrode this column)
+ if (!items.getContainerPropertyIds().contains(columnId)) {
+ visibleColumns.remove(columnId);
+ }
+ refreshRowCache();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns item identifiers of the items which are currently rendered on the
+ * client.
+ * <p>
+ * Note, that some due to historical reasons the name of the method is bit
+ * misleading. Some items may be partly or totally out of the viewport of
+ * the table's scrollable area. Actually detecting rows which can be
+ * actually seen by the end user may be problematic due to the client server
+ * architecture. Using {@link #getCurrentPageFirstItemId()} combined with
+ * {@link #getPageLength()} may produce good enough estimates in some
+ * situations.
+ *
+ * @see com.vaadin.ui.Select#getVisibleItemIds()
+ */
+
+ @Override
+ public Collection<?> getVisibleItemIds() {
+
+ final LinkedList<Object> visible = new LinkedList<Object>();
+
+ final Object[][] cells = getVisibleCells();
+ // may be null if the table has not been rendered yet (e.g. not attached
+ // to a layout)
+ if (null != cells) {
+ for (int i = 0; i < cells[CELL_ITEMID].length; i++) {
+ visible.add(cells[CELL_ITEMID][i]);
+ }
+ }
+
+ return visible;
+ }
+
+ /**
+ * Container datasource item set change. Table must flush its buffers on
+ * change.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange(com.vaadin.data.Container.ItemSetChangeEvent)
+ */
+
+ @Override
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ super.containerItemSetChange(event);
+
+ // super method clears the key map, must inform client about this to
+ // avoid getting invalid keys back (#8584)
+ keyMapperReset = true;
+
+ // ensure that page still has first item in page, ignore buffer refresh
+ // (forced in this method)
+ setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex(), false);
+ refreshRowCache();
+ }
+
+ /**
+ * Container datasource property set change. Table must flush its buffers on
+ * change.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeListener#containerPropertySetChange(com.vaadin.data.Container.PropertySetChangeEvent)
+ */
+
+ @Override
+ public void containerPropertySetChange(
+ Container.PropertySetChangeEvent event) {
+ disableContentRefreshing();
+ super.containerPropertySetChange(event);
+
+ // sanitetize visibleColumns. note that we are not adding previously
+ // non-existing properties as columns
+ Collection<?> containerPropertyIds = getContainerDataSource()
+ .getContainerPropertyIds();
+
+ LinkedList<Object> newVisibleColumns = new LinkedList<Object>(
+ visibleColumns);
+ for (Iterator<Object> iterator = newVisibleColumns.iterator(); iterator
+ .hasNext();) {
+ Object id = iterator.next();
+ if (!(containerPropertyIds.contains(id) || columnGenerators
+ .containsKey(id))) {
+ iterator.remove();
+ }
+ }
+ setVisibleColumns(newVisibleColumns.toArray());
+ // same for collapsed columns
+ for (Iterator<Object> iterator = collapsedColumns.iterator(); iterator
+ .hasNext();) {
+ Object id = iterator.next();
+ if (!(containerPropertyIds.contains(id) || columnGenerators
+ .containsKey(id))) {
+ iterator.remove();
+ }
+ }
+
+ resetPageBuffer();
+ enableContentRefreshing(true);
+ }
+
+ /**
+ * Adding new items is not supported.
+ *
+ * @throws UnsupportedOperationException
+ * if set to true.
+ * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean)
+ */
+
+ @Override
+ public void setNewItemsAllowed(boolean allowNewOptions)
+ throws UnsupportedOperationException {
+ if (allowNewOptions) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Gets the ID of the Item following the Item that corresponds to itemId.
+ *
+ * @see com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object)
+ */
+
+ @Override
+ public Object nextItemId(Object itemId) {
+ return ((Container.Ordered) items).nextItemId(itemId);
+ }
+
+ /**
+ * Gets the ID of the Item preceding the Item that corresponds to the
+ * itemId.
+ *
+ * @see com.vaadin.data.Container.Ordered#prevItemId(java.lang.Object)
+ */
+
+ @Override
+ public Object prevItemId(Object itemId) {
+ return ((Container.Ordered) items).prevItemId(itemId);
+ }
+
+ /**
+ * Gets the ID of the first Item in the Container.
+ *
+ * @see com.vaadin.data.Container.Ordered#firstItemId()
+ */
+
+ @Override
+ public Object firstItemId() {
+ return ((Container.Ordered) items).firstItemId();
+ }
+
+ /**
+ * Gets the ID of the last Item in the Container.
+ *
+ * @see com.vaadin.data.Container.Ordered#lastItemId()
+ */
+
+ @Override
+ public Object lastItemId() {
+ return ((Container.Ordered) items).lastItemId();
+ }
+
+ /**
+ * Tests if the Item corresponding to the given Item ID is the first Item in
+ * the Container.
+ *
+ * @see com.vaadin.data.Container.Ordered#isFirstId(java.lang.Object)
+ */
+
+ @Override
+ public boolean isFirstId(Object itemId) {
+ return ((Container.Ordered) items).isFirstId(itemId);
+ }
+
+ /**
+ * Tests if the Item corresponding to the given Item ID is the last Item in
+ * the Container.
+ *
+ * @see com.vaadin.data.Container.Ordered#isLastId(java.lang.Object)
+ */
+
+ @Override
+ public boolean isLastId(Object itemId) {
+ return ((Container.Ordered) items).isLastId(itemId);
+ }
+
+ /**
+ * Adds new item after the given item.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object)
+ */
+
+ @Override
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ Object itemId = ((Container.Ordered) items)
+ .addItemAfter(previousItemId);
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return itemId;
+ }
+
+ /**
+ * Adds new item after the given item.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
+ * java.lang.Object)
+ */
+
+ @Override
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ Item item = ((Container.Ordered) items).addItemAfter(previousItemId,
+ newItemId);
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return item;
+ }
+
+ /**
+ * Sets the TableFieldFactory that is used to create editor for table cells.
+ *
+ * The TableFieldFactory is only used if the Table is editable. By default
+ * the DefaultFieldFactory is used.
+ *
+ * @param fieldFactory
+ * the field factory to set.
+ * @see #isEditable
+ * @see DefaultFieldFactory
+ */
+ public void setTableFieldFactory(TableFieldFactory fieldFactory) {
+ this.fieldFactory = fieldFactory;
+
+ // Assure visual refresh
+ refreshRowCache();
+ }
+
+ /**
+ * Gets the TableFieldFactory that is used to create editor for table cells.
+ *
+ * The FieldFactory is only used if the Table is editable.
+ *
+ * @return TableFieldFactory used to create the Field instances.
+ * @see #isEditable
+ */
+ public TableFieldFactory getTableFieldFactory() {
+ return fieldFactory;
+ }
+
+ /**
+ * Is table editable.
+ *
+ * If table is editable a editor of type Field is created for each table
+ * cell. The assigned FieldFactory is used to create the instances.
+ *
+ * To provide custom editors for table cells create a class implementins the
+ * FieldFactory interface, and assign it to table, and set the editable
+ * property to true.
+ *
+ * @return true if table is editable, false oterwise.
+ * @see Field
+ * @see FieldFactory
+ *
+ */
+ public boolean isEditable() {
+ return editable;
+ }
+
+ /**
+ * Sets the editable property.
+ *
+ * If table is editable a editor of type Field is created for each table
+ * cell. The assigned FieldFactory is used to create the instances.
+ *
+ * To provide custom editors for table cells create a class implementins the
+ * FieldFactory interface, and assign it to table, and set the editable
+ * property to true.
+ *
+ * @param editable
+ * true if table should be editable by user.
+ * @see Field
+ * @see FieldFactory
+ *
+ */
+ public void setEditable(boolean editable) {
+ this.editable = editable;
+
+ // Assure visual refresh
+ refreshRowCache();
+ }
+
+ /**
+ * Sorts the table.
+ *
+ * @throws UnsupportedOperationException
+ * if the container data source does not implement
+ * Container.Sortable
+ * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ *
+ */
+
+ @Override
+ public void sort(Object[] propertyId, boolean[] ascending)
+ throws UnsupportedOperationException {
+ final Container c = getContainerDataSource();
+ if (c instanceof Container.Sortable) {
+ final int pageIndex = getCurrentPageFirstItemIndex();
+ ((Container.Sortable) c).sort(propertyId, ascending);
+ setCurrentPageFirstItemIndex(pageIndex);
+ refreshRowCache();
+
+ } else if (c != null) {
+ throw new UnsupportedOperationException(
+ "Underlying Data does not allow sorting");
+ }
+ }
+
+ /**
+ * Sorts the table by currently selected sorting column.
+ *
+ * @throws UnsupportedOperationException
+ * if the container data source does not implement
+ * Container.Sortable
+ */
+ public void sort() {
+ if (getSortContainerPropertyId() == null) {
+ return;
+ }
+ sort(new Object[] { sortContainerPropertyId },
+ new boolean[] { sortAscending });
+ }
+
+ /**
+ * Gets the container property IDs, which can be used to sort the item.
+ * <p>
+ * Note that the {@link #isSortEnabled()} state affects what this method
+ * returns. Disabling sorting causes this method to always return an empty
+ * collection.
+ * </p>
+ *
+ * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
+ */
+
+ @Override
+ public Collection<?> getSortableContainerPropertyIds() {
+ final Container c = getContainerDataSource();
+ if (c instanceof Container.Sortable && isSortEnabled()) {
+ return ((Container.Sortable) c).getSortableContainerPropertyIds();
+ } else {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Gets the currently sorted column property ID.
+ *
+ * @return the Container property id of the currently sorted column.
+ */
+ public Object getSortContainerPropertyId() {
+ return sortContainerPropertyId;
+ }
+
+ /**
+ * Sets the currently sorted column property id.
+ *
+ * @param propertyId
+ * the Container property id of the currently sorted column.
+ */
+ public void setSortContainerPropertyId(Object propertyId) {
+ setSortContainerPropertyId(propertyId, true);
+ }
+
+ /**
+ * Internal method to set currently sorted column property id. With doSort
+ * flag actual sorting may be bypassed.
+ *
+ * @param propertyId
+ * @param doSort
+ */
+ private void setSortContainerPropertyId(Object propertyId, boolean doSort) {
+ if ((sortContainerPropertyId != null && !sortContainerPropertyId
+ .equals(propertyId))
+ || (sortContainerPropertyId == null && propertyId != null)) {
+ sortContainerPropertyId = propertyId;
+
+ if (doSort) {
+ sort();
+ // Assures the visual refresh. This should not be necessary as
+ // sort() calls refreshRowCache
+ refreshRenderedCells();
+ }
+ }
+ }
+
+ /**
+ * Is the table currently sorted in ascending order.
+ *
+ * @return <code>true</code> if ascending, <code>false</code> if descending.
+ */
+ public boolean isSortAscending() {
+ return sortAscending;
+ }
+
+ /**
+ * Sets the table in ascending order.
+ *
+ * @param ascending
+ * <code>true</code> if ascending, <code>false</code> if
+ * descending.
+ */
+ public void setSortAscending(boolean ascending) {
+ setSortAscending(ascending, true);
+ }
+
+ /**
+ * Internal method to set sort ascending. With doSort flag actual sort can
+ * be bypassed.
+ *
+ * @param ascending
+ * @param doSort
+ */
+ private void setSortAscending(boolean ascending, boolean doSort) {
+ if (sortAscending != ascending) {
+ sortAscending = ascending;
+ if (doSort) {
+ sort();
+ // Assures the visual refresh. This should not be necessary as
+ // sort() calls refreshRowCache
+ refreshRenderedCells();
+ }
+ }
+ }
+
+ /**
+ * Is sorting disabled altogether.
+ *
+ * True iff no sortable columns are given even in the case where data source
+ * would support this.
+ *
+ * @return True iff sorting is disabled.
+ * @deprecated Use {@link #isSortEnabled()} instead
+ */
+ @Deprecated
+ public boolean isSortDisabled() {
+ return !isSortEnabled();
+ }
+
+ /**
+ * Checks if sorting is enabled.
+ *
+ * @return true if sorting by the user is allowed, false otherwise
+ */
+ public boolean isSortEnabled() {
+ return sortEnabled;
+ }
+
+ /**
+ * Disables the sorting by the user altogether.
+ *
+ * @param sortDisabled
+ * True iff sorting is disabled.
+ * @deprecated Use {@link #setSortEnabled(boolean)} instead
+ */
+ @Deprecated
+ public void setSortDisabled(boolean sortDisabled) {
+ setSortEnabled(!sortDisabled);
+ }
+
+ /**
+ * Enables or disables sorting.
+ * <p>
+ * Setting this to false disallows sorting by the user. It is still possible
+ * to call {@link #sort()}.
+ * </p>
+ *
+ * @param sortEnabled
+ * true to allow the user to sort the table, false to disallow it
+ */
+ public void setSortEnabled(boolean sortEnabled) {
+ if (this.sortEnabled != sortEnabled) {
+ this.sortEnabled = sortEnabled;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Used to create "generated columns"; columns that exist only in the Table,
+ * not in the underlying Container. Implement this interface and pass it to
+ * Table.addGeneratedColumn along with an id for the column to be generated.
+ *
+ */
+ public interface ColumnGenerator extends Serializable {
+
+ /**
+ * Called by Table when a cell in a generated column needs to be
+ * generated.
+ *
+ * @param source
+ * the source Table
+ * @param itemId
+ * the itemId (aka rowId) for the of the cell to be generated
+ * @param columnId
+ * the id for the generated column (as specified in
+ * addGeneratedColumn)
+ * @return A {@link Component} that should be rendered in the cell or a
+ * {@link String} that should be displayed in the cell. Other
+ * return values are not supported.
+ */
+ public abstract Object generateCell(Table source, Object itemId,
+ Object columnId);
+ }
+
+ /**
+ * Set cell style generator for Table.
+ *
+ * @param cellStyleGenerator
+ * New cell style generator or null to remove generator.
+ */
+ public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
+ this.cellStyleGenerator = cellStyleGenerator;
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the style generators
+ refreshRenderedCells();
+
+ }
+
+ /**
+ * Get the current cell style generator.
+ *
+ */
+ public CellStyleGenerator getCellStyleGenerator() {
+ return cellStyleGenerator;
+ }
+
+ /**
+ * Allow to define specific style on cells (and rows) contents. Implements
+ * this interface and pass it to Table.setCellStyleGenerator. Row styles are
+ * generated when porpertyId is null. The CSS class name that will be added
+ * to the cell content is <tt>v-table-cell-content-[style name]</tt>, and
+ * the row style will be <tt>v-table-row-[style name]</tt>.
+ */
+ public interface CellStyleGenerator extends Serializable {
+
+ /**
+ * Called by Table when a cell (and row) is painted.
+ *
+ * @param itemId
+ * The itemId of the painted cell
+ * @param propertyId
+ * The propertyId of the cell, null when getting row style
+ * @return The style name to add to this cell or row. (the CSS class
+ * name will be v-table-cell-content-[style name], or
+ * v-table-row-[style name] for rows)
+ */
+ public abstract String getStyle(Object itemId, Object propertyId);
+ }
+
+ @Override
+ public void addListener(ItemClickListener listener) {
+ addListener(VScrollTable.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
+ listener, ItemClickEvent.ITEM_CLICK_METHOD);
+ }
+
+ @Override
+ public void removeListener(ItemClickListener listener) {
+ removeListener(VScrollTable.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
+ listener);
+ }
+
+ // Identical to AbstractCompoenentContainer.setEnabled();
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (getParent() != null && !getParent().isEnabled()) {
+ // some ancestor still disabled, don't update children
+ return;
+ } else {
+ requestRepaintAll();
+ }
+ }
+
+ /**
+ * Sets the drag start mode of the Table. Drag start mode controls how Table
+ * behaves as a drag source.
+ *
+ * @param newDragMode
+ */
+ public void setDragMode(TableDragMode newDragMode) {
+ dragMode = newDragMode;
+ requestRepaint();
+ }
+
+ /**
+ * @return the current start mode of the Table. Drag start mode controls how
+ * Table behaves as a drag source.
+ */
+ public TableDragMode getDragMode() {
+ return dragMode;
+ }
+
+ /**
+ * Concrete implementation of {@link DataBoundTransferable} for data
+ * transferred from a table.
+ *
+ * @see {@link DataBoundTransferable}.
+ *
+ * @since 6.3
+ */
+ public class TableTransferable extends DataBoundTransferable {
+
+ protected TableTransferable(Map<String, Object> rawVariables) {
+ super(Table.this, rawVariables);
+ Object object = rawVariables.get("itemId");
+ if (object != null) {
+ setData("itemId", itemIdMapper.get((String) object));
+ }
+ object = rawVariables.get("propertyId");
+ if (object != null) {
+ setData("propertyId", columnIdMap.get((String) object));
+ }
+ }
+
+ @Override
+ public Object getItemId() {
+ return getData("itemId");
+ }
+
+ @Override
+ public Object getPropertyId() {
+ return getData("propertyId");
+ }
+
+ @Override
+ public Table getSourceComponent() {
+ return (Table) super.getSourceComponent();
+ }
+
+ }
+
+ @Override
+ public TableTransferable getTransferable(Map<String, Object> rawVariables) {
+ TableTransferable transferable = new TableTransferable(rawVariables);
+ return transferable;
+ }
+
+ @Override
+ public DropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ public void setDropHandler(DropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ }
+
+ @Override
+ public AbstractSelectTargetDetails translateDropTargetDetails(
+ Map<String, Object> clientVariables) {
+ return new AbstractSelectTargetDetails(clientVariables);
+ }
+
+ /**
+ * Sets the behavior of how the multi-select mode should behave when the
+ * table is both selectable and in multi-select mode.
+ * <p>
+ * Note, that on some clients the mode may not be respected. E.g. on touch
+ * based devices CTRL/SHIFT base selection method is invalid, so touch based
+ * browsers always use the {@link MultiSelectMode#SIMPLE}.
+ *
+ * @param mode
+ * The select mode of the table
+ */
+ public void setMultiSelectMode(MultiSelectMode mode) {
+ multiSelectMode = mode;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the select mode in which multi-select is used.
+ *
+ * @return The multi select mode
+ */
+ public MultiSelectMode getMultiSelectMode() {
+ return multiSelectMode;
+ }
+
+ /**
+ * Lazy loading accept criterion for Table. Accepted target rows are loaded
+ * from server once per drag and drop operation. Developer must override one
+ * method that decides on which rows the currently dragged data can be
+ * dropped.
+ *
+ * <p>
+ * Initially pretty much no data is sent to client. On first required
+ * criterion check (per drag request) the client side data structure is
+ * initialized from server and no subsequent requests requests are needed
+ * during that drag and drop operation.
+ */
+ public static abstract class TableDropCriterion extends ServerSideCriterion {
+
+ private Table table;
+
+ private Set<Object> allowedItemIds;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptcriteria.ServerSideCriterion#getIdentifier
+ * ()
+ */
+
+ @Override
+ protected String getIdentifier() {
+ return TableDropCriterion.class.getCanonicalName();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptcriteria.AcceptCriterion#accepts(com.vaadin
+ * .event.dd.DragAndDropEvent)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean accept(DragAndDropEvent dragEvent) {
+ AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
+ .getTargetDetails();
+ table = (Table) dragEvent.getTargetDetails().getTarget();
+ Collection<?> visibleItemIds = table.getVisibleItemIds();
+ allowedItemIds = getAllowedItemIds(dragEvent, table,
+ (Collection<Object>) visibleItemIds);
+
+ return allowedItemIds.contains(dropTargetData.getItemIdOver());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptcriteria.AcceptCriterion#paintResponse(
+ * com.vaadin.terminal.PaintTarget)
+ */
+
+ @Override
+ public void paintResponse(PaintTarget target) throws PaintException {
+ /*
+ * send allowed nodes to client so subsequent requests can be
+ * avoided
+ */
+ Object[] array = allowedItemIds.toArray();
+ for (int i = 0; i < array.length; i++) {
+ String key = table.itemIdMapper.key(array[i]);
+ array[i] = key;
+ }
+ target.addAttribute("allowedIds", array);
+ }
+
+ /**
+ * @param dragEvent
+ * @param table
+ * the table for which the allowed item identifiers are
+ * defined
+ * @param visibleItemIds
+ * the list of currently rendered item identifiers, accepted
+ * item id's need to be detected only for these visible items
+ * @return the set of identifiers for items on which the dragEvent will
+ * be accepted
+ */
+ protected abstract Set<Object> getAllowedItemIds(
+ DragAndDropEvent dragEvent, Table table,
+ Collection<Object> visibleItemIds);
+
+ }
+
+ /**
+ * Click event fired when clicking on the Table headers. The event includes
+ * a reference the the Table the event originated from, the property id of
+ * the column which header was pressed and details about the mouse event
+ * itself.
+ */
+ public static class HeaderClickEvent extends ClickEvent {
+ public static final Method HEADER_CLICK_METHOD;
+
+ static {
+ try {
+ // Set the header click method
+ HEADER_CLICK_METHOD = HeaderClickListener.class
+ .getDeclaredMethod("headerClick",
+ new Class[] { HeaderClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(e);
+ }
+ }
+
+ // The property id of the column which header was pressed
+ private final Object columnPropertyId;
+
+ public HeaderClickEvent(Component source, Object propertyId,
+ MouseEventDetails details) {
+ super(source, details);
+ columnPropertyId = propertyId;
+ }
+
+ /**
+ * Gets the property id of the column which header was pressed
+ *
+ * @return The column propety id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+ }
+
+ /**
+ * Click event fired when clicking on the Table footers. The event includes
+ * a reference the the Table the event originated from, the property id of
+ * the column which header was pressed and details about the mouse event
+ * itself.
+ */
+ public static class FooterClickEvent extends ClickEvent {
+ public static final Method FOOTER_CLICK_METHOD;
+
+ static {
+ try {
+ // Set the header click method
+ FOOTER_CLICK_METHOD = FooterClickListener.class
+ .getDeclaredMethod("footerClick",
+ new Class[] { FooterClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(e);
+ }
+ }
+
+ // The property id of the column which header was pressed
+ private final Object columnPropertyId;
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * The source of the component
+ * @param propertyId
+ * The propertyId of the column
+ * @param details
+ * The mouse details of the click
+ */
+ public FooterClickEvent(Component source, Object propertyId,
+ MouseEventDetails details) {
+ super(source, details);
+ columnPropertyId = propertyId;
+ }
+
+ /**
+ * Gets the property id of the column which header was pressed
+ *
+ * @return The column propety id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+ }
+
+ /**
+ * Interface for the listener for column header mouse click events. The
+ * headerClick method is called when the user presses a header column cell.
+ */
+ public interface HeaderClickListener extends Serializable {
+
+ /**
+ * Called when a user clicks a header column cell
+ *
+ * @param event
+ * The event which contains information about the column and
+ * the mouse click event
+ */
+ public void headerClick(HeaderClickEvent event);
+ }
+
+ /**
+ * Interface for the listener for column footer mouse click events. The
+ * footerClick method is called when the user presses a footer column cell.
+ */
+ public interface FooterClickListener extends Serializable {
+
+ /**
+ * Called when a user clicks a footer column cell
+ *
+ * @param event
+ * The event which contains information about the column and
+ * the mouse click event
+ */
+ public void footerClick(FooterClickEvent event);
+ }
+
+ /**
+ * Adds a header click listener which handles the click events when the user
+ * clicks on a column header cell in the Table.
+ * <p>
+ * The listener will receive events which contain information about which
+ * column was clicked and some details about the mouse event.
+ * </p>
+ *
+ * @param listener
+ * The handler which should handle the header click events.
+ */
+ public void addListener(HeaderClickListener listener) {
+ addListener(VScrollTable.HEADER_CLICK_EVENT_ID, HeaderClickEvent.class,
+ listener, HeaderClickEvent.HEADER_CLICK_METHOD);
+ }
+
+ /**
+ * Removes a header click listener
+ *
+ * @param listener
+ * The listener to remove.
+ */
+ public void removeListener(HeaderClickListener listener) {
+ removeListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, listener);
+ }
+
+ /**
+ * Adds a footer click listener which handles the click events when the user
+ * clicks on a column footer cell in the Table.
+ * <p>
+ * The listener will receive events which contain information about which
+ * column was clicked and some details about the mouse event.
+ * </p>
+ *
+ * @param listener
+ * The handler which should handle the footer click events.
+ */
+ public void addListener(FooterClickListener listener) {
+ addListener(VScrollTable.FOOTER_CLICK_EVENT_ID, FooterClickEvent.class,
+ listener, FooterClickEvent.FOOTER_CLICK_METHOD);
+ }
+
+ /**
+ * Removes a footer click listener
+ *
+ * @param listener
+ * The listener to remove.
+ */
+ public void removeListener(FooterClickListener listener) {
+ removeListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, listener);
+ }
+
+ /**
+ * Gets the footer caption beneath the rows
+ *
+ * @param propertyId
+ * The propertyId of the column *
+ * @return The caption of the footer or NULL if not set
+ */
+ public String getColumnFooter(Object propertyId) {
+ return columnFooters.get(propertyId);
+ }
+
+ /**
+ * Sets the column footer caption. The column footer caption is the text
+ * displayed beneath the column if footers have been set visible.
+ *
+ * @param propertyId
+ * The properyId of the column
+ *
+ * @param footer
+ * The caption of the footer
+ */
+ public void setColumnFooter(Object propertyId, String footer) {
+ if (footer == null) {
+ columnFooters.remove(propertyId);
+ } else {
+ columnFooters.put(propertyId, footer);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Sets the footer visible in the bottom of the table.
+ * <p>
+ * The footer can be used to add column related data like sums to the bottom
+ * of the Table using setColumnFooter(Object propertyId, String footer).
+ * </p>
+ *
+ * @param visible
+ * Should the footer be visible
+ */
+ public void setFooterVisible(boolean visible) {
+ if (visible != columnFootersVisible) {
+ columnFootersVisible = visible;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Is the footer currently visible?
+ *
+ * @return Returns true if visible else false
+ */
+ public boolean isFooterVisible() {
+ return columnFootersVisible;
+ }
+
+ /**
+ * This event is fired when a column is resized. The event contains the
+ * columns property id which was fired, the previous width of the column and
+ * the width of the column after the resize.
+ */
+ public static class ColumnResizeEvent extends Component.Event {
+ public static final Method COLUMN_RESIZE_METHOD;
+
+ static {
+ try {
+ COLUMN_RESIZE_METHOD = ColumnResizeListener.class
+ .getDeclaredMethod("columnResize",
+ new Class[] { ColumnResizeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(e);
+ }
+ }
+
+ private final int previousWidth;
+ private final int currentWidth;
+ private final Object columnPropertyId;
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * The source of the event
+ * @param propertyId
+ * The columns property id
+ * @param previous
+ * The width in pixels of the column before the resize event
+ * @param current
+ * The width in pixels of the column after the resize event
+ */
+ public ColumnResizeEvent(Component source, Object propertyId,
+ int previous, int current) {
+ super(source);
+ previousWidth = previous;
+ currentWidth = current;
+ columnPropertyId = propertyId;
+ }
+
+ /**
+ * Get the column property id of the column that was resized.
+ *
+ * @return The column property id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+
+ /**
+ * Get the width in pixels of the column before the resize event
+ *
+ * @return Width in pixels
+ */
+ public int getPreviousWidth() {
+ return previousWidth;
+ }
+
+ /**
+ * Get the width in pixels of the column after the resize event
+ *
+ * @return Width in pixels
+ */
+ public int getCurrentWidth() {
+ return currentWidth;
+ }
+ }
+
+ /**
+ * Interface for listening to column resize events.
+ */
+ public interface ColumnResizeListener extends Serializable {
+
+ /**
+ * This method is triggered when the column has been resized
+ *
+ * @param event
+ * The event which contains the column property id, the
+ * previous width of the column and the current width of the
+ * column
+ */
+ public void columnResize(ColumnResizeEvent event);
+ }
+
+ /**
+ * Adds a column resize listener to the Table. A column resize listener is
+ * called when a user resizes a columns width.
+ *
+ * @param listener
+ * The listener to attach to the Table
+ */
+ public void addListener(ColumnResizeListener listener) {
+ addListener(VScrollTable.COLUMN_RESIZE_EVENT_ID,
+ ColumnResizeEvent.class, listener,
+ ColumnResizeEvent.COLUMN_RESIZE_METHOD);
+ }
+
+ /**
+ * Removes a column resize listener from the Table.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeListener(ColumnResizeListener listener) {
+ removeListener(VScrollTable.COLUMN_RESIZE_EVENT_ID,
+ ColumnResizeEvent.class, listener);
+ }
+
+ /**
+ * This event is fired when a columns are reordered by the end user user.
+ */
+ public static class ColumnReorderEvent extends Component.Event {
+ public static final Method METHOD;
+
+ static {
+ try {
+ METHOD = ColumnReorderListener.class.getDeclaredMethod(
+ "columnReorder",
+ new Class[] { ColumnReorderEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(e);
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * The source of the event
+ */
+ public ColumnReorderEvent(Component source) {
+ super(source);
+ }
+
+ }
+
+ /**
+ * Interface for listening to column reorder events.
+ */
+ public interface ColumnReorderListener extends Serializable {
+
+ /**
+ * This method is triggered when the column has been reordered
+ *
+ * @param event
+ */
+ public void columnReorder(ColumnReorderEvent event);
+ }
+
+ /**
+ * Adds a column reorder listener to the Table. A column reorder listener is
+ * called when a user reorders columns.
+ *
+ * @param listener
+ * The listener to attach to the Table
+ */
+ public void addListener(ColumnReorderListener listener) {
+ addListener(VScrollTable.COLUMN_REORDER_EVENT_ID,
+ ColumnReorderEvent.class, listener, ColumnReorderEvent.METHOD);
+ }
+
+ /**
+ * Removes a column reorder listener from the Table.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeListener(ColumnReorderListener listener) {
+ removeListener(VScrollTable.COLUMN_REORDER_EVENT_ID,
+ ColumnReorderEvent.class, listener);
+ }
+
+ /**
+ * Set the item description generator which generates tooltips for cells and
+ * rows in the Table
+ *
+ * @param generator
+ * The generator to use or null to disable
+ */
+ public void setItemDescriptionGenerator(ItemDescriptionGenerator generator) {
+ if (generator != itemDescriptionGenerator) {
+ itemDescriptionGenerator = generator;
+ // Assures the visual refresh. No need to reset the page buffer
+ // before as the content has not changed, only the descriptions
+ refreshRenderedCells();
+ }
+ }
+
+ /**
+ * Get the item description generator which generates tooltips for cells and
+ * rows in the Table.
+ */
+ public ItemDescriptionGenerator getItemDescriptionGenerator() {
+ return itemDescriptionGenerator;
+ }
+
+ /**
+ * Row generators can be used to replace certain items in a table with a
+ * generated string. The generator is called each time the table is
+ * rendered, which means that new strings can be generated each time.
+ *
+ * Row generators can be used for e.g. summary rows or grouping of items.
+ */
+ public interface RowGenerator extends Serializable {
+ /**
+ * Called for every row that is painted in the Table. Returning a
+ * GeneratedRow object will cause the row to be painted based on the
+ * contents of the GeneratedRow. A generated row is by default styled
+ * similarly to a header or footer row.
+ * <p>
+ * The GeneratedRow data object contains the text that should be
+ * rendered in the row. The itemId in the container thus works only as a
+ * placeholder.
+ * <p>
+ * If GeneratedRow.setSpanColumns(true) is used, there will be one
+ * String spanning all columns (use setText("Spanning text")). Otherwise
+ * you can define one String per visible column.
+ * <p>
+ * If GeneratedRow.setRenderAsHtml(true) is used, the strings can
+ * contain HTML markup, otherwise all strings will be rendered as text
+ * (the default).
+ * <p>
+ * A "v-table-generated-row" CSS class is added to all generated rows.
+ * For custom styling of a generated row you can combine a RowGenerator
+ * with a CellStyleGenerator.
+ * <p>
+ *
+ * @param table
+ * The Table that is being painted
+ * @param itemId
+ * The itemId for the row
+ * @return A GeneratedRow describing how the row should be painted or
+ * null to paint the row with the contents from the container
+ */
+ public GeneratedRow generateRow(Table table, Object itemId);
+ }
+
+ public static class GeneratedRow implements Serializable {
+ private boolean htmlContentAllowed = false;
+ private boolean spanColumns = false;
+ private String[] text = null;
+
+ /**
+ * Creates a new generated row. If only one string is passed in, columns
+ * are automatically spanned.
+ *
+ * @param text
+ */
+ public GeneratedRow(String... text) {
+ setHtmlContentAllowed(false);
+ setSpanColumns(text == null || text.length == 1);
+ setText(text);
+ }
+
+ /**
+ * Pass one String if spanColumns is used, one String for each visible
+ * column otherwise
+ */
+ public void setText(String... text) {
+ if (text == null || (text.length == 1 && text[0] == null)) {
+ text = new String[] { "" };
+ }
+ this.text = text;
+ }
+
+ protected String[] getText() {
+ return text;
+ }
+
+ protected Object getValue() {
+ return getText();
+ }
+
+ protected boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+
+ /**
+ * If set to true, all strings passed to {@link #setText(String...)}
+ * will be rendered as HTML.
+ *
+ * @param htmlContentAllowed
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ }
+
+ protected boolean isSpanColumns() {
+ return spanColumns;
+ }
+
+ /**
+ * If set to true, only one string will be rendered, spanning the entire
+ * row.
+ *
+ * @param spanColumns
+ */
+ public void setSpanColumns(boolean spanColumns) {
+ this.spanColumns = spanColumns;
+ }
+ }
+
+ /**
+ * Assigns a row generator to the table. The row generator will be able to
+ * replace rows in the table when it is rendered.
+ *
+ * @param generator
+ * the new row generator
+ */
+ public void setRowGenerator(RowGenerator generator) {
+ rowGenerator = generator;
+ refreshRowCache();
+ }
+
+ /**
+ * @return the current row generator
+ */
+ public RowGenerator getRowGenerator() {
+ return rowGenerator;
+ }
+
+ /**
+ * Sets a converter for a property id.
+ * <p>
+ * The converter is used to format the the data for the given property id
+ * before displaying it in the table.
+ * </p>
+ *
+ * @param propertyId
+ * The propertyId to format using the converter
+ * @param converter
+ * The converter to use for the property id
+ */
+ public void setConverter(Object propertyId, Converter<String, ?> converter) {
+ if (!getContainerPropertyIds().contains(propertyId)) {
+ throw new IllegalArgumentException("PropertyId " + propertyId
+ + " must be in the container");
+ }
+ // FIXME: This check should be here but primitive types like Boolean
+ // formatter for boolean property must be handled
+
+ // if (!converter.getSourceType().isAssignableFrom(getType(propertyId)))
+ // {
+ // throw new IllegalArgumentException("Property type ("
+ // + getType(propertyId)
+ // + ") must match converter source type ("
+ // + converter.getSourceType() + ")");
+ // }
+ propertyValueConverters.put(propertyId,
+ (Converter<String, Object>) converter);
+ refreshRowCache();
+ }
+
+ /**
+ * Checks if there is a converter set explicitly for the given property id.
+ *
+ * @param propertyId
+ * The propertyId to check
+ * @return true if a converter has been set for the property id, false
+ * otherwise
+ */
+ protected boolean hasConverter(Object propertyId) {
+ return propertyValueConverters.containsKey(propertyId);
+ }
+
+ /**
+ * Returns the converter used to format the given propertyId.
+ *
+ * @param propertyId
+ * The propertyId to check
+ * @return The converter used to format the propertyId or null if no
+ * converter has been set
+ */
+ public Converter<String, Object> getConverter(Object propertyId) {
+ return propertyValueConverters.get(propertyId);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ // We need to ensure that the rows are sent to the client when the
+ // Table is made visible if it has been rendered as invisible.
+ setRowCacheInvalidated(true);
+ }
+ super.setVisible(visible);
+ }
+
+ @Override
+ public Iterator<Component> iterator() {
+ return getComponentIterator();
+ }
+
+ @Override
+ public Iterator<Component> getComponentIterator() {
+ if (visibleComponents == null) {
+ Collection<Component> empty = Collections.emptyList();
+ return empty.iterator();
+ }
+
+ return visibleComponents.iterator();
+ }
+
+ @Override
+ public boolean isComponentVisible(Component childComponent) {
+ return true;
+ }
+
+ private final Logger getLogger() {
+ if (logger == null) {
+ logger = Logger.getLogger(Table.class.getName());
+ }
+ return logger;
+ }
+}
diff --git a/server/src/com/vaadin/ui/TableFieldFactory.java b/server/src/com/vaadin/ui/TableFieldFactory.java
new file mode 100644
index 0000000000..6c9a641aa8
--- /dev/null
+++ b/server/src/com/vaadin/ui/TableFieldFactory.java
@@ -0,0 +1,45 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.data.Container;
+
+/**
+ * Factory interface for creating new Field-instances based on Container
+ * (datasource), item id, property id and uiContext (the component responsible
+ * for displaying fields). Currently this interface is used by {@link Table},
+ * but might later be used by some other components for {@link Field}
+ * generation.
+ *
+ * <p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 6.0
+ * @see FormFieldFactory
+ */
+public interface TableFieldFactory extends Serializable {
+ /**
+ * Creates a field based on the Container, item id, property id and the
+ * component responsible for displaying the field (most commonly
+ * {@link Table}).
+ *
+ * @param container
+ * the Container where the property belongs to.
+ * @param itemId
+ * the item Id.
+ * @param propertyId
+ * the Id of the property.
+ * @param uiContext
+ * the component where the field is presented.
+ * @return A field suitable for editing the specified data or null if the
+ * property should not be editable.
+ */
+ Field<?> createField(Container container, Object itemId, Object propertyId,
+ Component uiContext);
+
+}
diff --git a/server/src/com/vaadin/ui/TextArea.java b/server/src/com/vaadin/ui/TextArea.java
new file mode 100644
index 0000000000..d7837dd33f
--- /dev/null
+++ b/server/src/com/vaadin/ui/TextArea.java
@@ -0,0 +1,121 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.data.Property;
+import com.vaadin.shared.ui.textarea.TextAreaState;
+
+/**
+ * A text field that supports multi line editing.
+ */
+public class TextArea extends AbstractTextField {
+
+ /**
+ * Constructs an empty TextArea.
+ */
+ public TextArea() {
+ setValue("");
+ }
+
+ /**
+ * Constructs an empty TextArea with given caption.
+ *
+ * @param caption
+ * the caption for the field.
+ */
+ public TextArea(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a TextArea with given property data source.
+ *
+ * @param dataSource
+ * the data source for the field
+ */
+ public TextArea(Property dataSource) {
+ this();
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a TextArea with given caption and property data source.
+ *
+ * @param caption
+ * the caption for the field
+ * @param dataSource
+ * the data source for the field
+ */
+ public TextArea(String caption, Property dataSource) {
+ this(dataSource);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a TextArea with given caption and value.
+ *
+ * @param caption
+ * the caption for the field
+ * @param value
+ * the value for the field
+ */
+ public TextArea(String caption, String value) {
+ this(caption);
+ setValue(value);
+
+ }
+
+ @Override
+ public TextAreaState getState() {
+ return (TextAreaState) super.getState();
+ }
+
+ /**
+ * Sets the number of rows in the text area.
+ *
+ * @param rows
+ * the number of rows for this text area.
+ */
+ public void setRows(int rows) {
+ if (rows < 0) {
+ rows = 0;
+ }
+ getState().setRows(rows);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the number of rows in the text area.
+ *
+ * @return number of explicitly set rows.
+ */
+ public int getRows() {
+ return getState().getRows();
+ }
+
+ /**
+ * Sets the text area's word-wrap mode on or off.
+ *
+ * @param wordwrap
+ * the boolean value specifying if the text area should be in
+ * word-wrap mode.
+ */
+ public void setWordwrap(boolean wordwrap) {
+ getState().setWordwrap(wordwrap);
+ requestRepaint();
+ }
+
+ /**
+ * Tests if the text area is in word-wrap mode.
+ *
+ * @return <code>true</code> if the component is in word-wrap mode,
+ * <code>false</code> if not.
+ */
+ public boolean isWordwrap() {
+ return getState().isWordwrap();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/TextField.java b/server/src/com/vaadin/ui/TextField.java
new file mode 100644
index 0000000000..567e9c1c10
--- /dev/null
+++ b/server/src/com/vaadin/ui/TextField.java
@@ -0,0 +1,92 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.data.Property;
+
+/**
+ * <p>
+ * A text editor component that can be bound to any bindable Property. The text
+ * editor supports both multiline and single line modes, default is one-line
+ * mode.
+ * </p>
+ *
+ * <p>
+ * Since <code>TextField</code> extends <code>AbstractField</code> it implements
+ * the {@link com.vaadin.data.Buffered} interface. A <code>TextField</code> is
+ * in write-through mode by default, so
+ * {@link com.vaadin.ui.AbstractField#setWriteThrough(boolean)} must be called
+ * to enable buffering.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class TextField extends AbstractTextField {
+
+ /**
+ * Constructs an empty <code>TextField</code> with no caption.
+ */
+ public TextField() {
+ setValue("");
+ }
+
+ /**
+ * Constructs an empty <code>TextField</code> with given caption.
+ *
+ * @param caption
+ * the caption <code>String</code> for the editor.
+ */
+ public TextField(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new <code>TextField</code> that's bound to the specified
+ * <code>Property</code> and has no caption.
+ *
+ * @param dataSource
+ * the Property to be edited with this editor.
+ */
+ public TextField(Property dataSource) {
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a new <code>TextField</code> that's bound to the specified
+ * <code>Property</code> and has the given caption <code>String</code>.
+ *
+ * @param caption
+ * the caption <code>String</code> for the editor.
+ * @param dataSource
+ * the Property to be edited with this editor.
+ */
+ public TextField(String caption, Property dataSource) {
+ this(dataSource);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new <code>TextField</code> with the given caption and
+ * initial text contents. The editor constructed this way will not be bound
+ * to a Property unless
+ * {@link com.vaadin.data.Property.Viewer#setPropertyDataSource(Property)}
+ * is called to bind it.
+ *
+ * @param caption
+ * the caption <code>String</code> for the editor.
+ * @param value
+ * the initial text content of the editor.
+ */
+ public TextField(String caption, String value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Tree.java b/server/src/com/vaadin/ui/Tree.java
new file mode 100644
index 0000000000..c15975d879
--- /dev/null
+++ b/server/src/com/vaadin/ui/Tree.java
@@ -0,0 +1,1615 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.StringTokenizer;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.ContainerHierarchicalWrapper;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.DataBoundTransferable;
+import com.vaadin.event.ItemClickEvent;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.event.ItemClickEvent.ItemClickNotifier;
+import com.vaadin.event.Transferable;
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DragSource;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetails;
+import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
+import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion;
+import com.vaadin.event.dd.acceptcriteria.TargetDetailIs;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.terminal.KeyMapper;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.gwt.client.ui.tree.TreeConnector;
+import com.vaadin.terminal.gwt.client.ui.tree.VTree;
+import com.vaadin.tools.ReflectTools;
+
+/**
+ * Tree component. A Tree can be used to select an item (or multiple items) from
+ * a hierarchical set of items.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings({ "serial", "deprecation" })
+public class Tree extends AbstractSelect implements Container.Hierarchical,
+ Action.Container, ItemClickNotifier, DragSource, DropTarget {
+
+ /* Private members */
+
+ /**
+ * Set of expanded nodes.
+ */
+ private final HashSet<Object> expanded = new HashSet<Object>();
+
+ /**
+ * List of action handlers.
+ */
+ private LinkedList<Action.Handler> actionHandlers = null;
+
+ /**
+ * Action mapper.
+ */
+ private KeyMapper<Action> actionMapper = null;
+
+ /**
+ * Is the tree selectable on the client side.
+ */
+ private boolean selectable = true;
+
+ /**
+ * Flag to indicate sub-tree loading
+ */
+ private boolean partialUpdate = false;
+
+ /**
+ * Holds a itemId which was recently expanded
+ */
+ private Object expandedItemId;
+
+ /**
+ * a flag which indicates initial paint. After this flag set true partial
+ * updates are allowed.
+ */
+ private boolean initialPaint = true;
+
+ /**
+ * Item tooltip generator
+ */
+ private ItemDescriptionGenerator itemDescriptionGenerator;
+
+ /**
+ * Supported drag modes for Tree.
+ */
+ public enum TreeDragMode {
+ /**
+ * When drag mode is NONE, dragging from Tree is not supported. Browsers
+ * may still support selecting text/icons from Tree which can initiate
+ * HTML 5 style drag and drop operation.
+ */
+ NONE,
+ /**
+ * When drag mode is NODE, users can initiate drag from Tree nodes that
+ * represent {@link Item}s in from the backed {@link Container}.
+ */
+ NODE
+ // , SUBTREE
+ }
+
+ private TreeDragMode dragMode = TreeDragMode.NONE;
+
+ private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
+ /* Tree constructors */
+
+ /**
+ * Creates a new empty tree.
+ */
+ public Tree() {
+ }
+
+ /**
+ * Creates a new empty tree with caption.
+ *
+ * @param caption
+ */
+ public Tree(String caption) {
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new tree with caption and connect it to a Container.
+ *
+ * @param caption
+ * @param dataSource
+ */
+ public Tree(String caption, Container dataSource) {
+ setCaption(caption);
+ setContainerDataSource(dataSource);
+ }
+
+ /* Expanding and collapsing */
+
+ /**
+ * Check is an item is expanded
+ *
+ * @param itemId
+ * the item id.
+ * @return true iff the item is expanded.
+ */
+ public boolean isExpanded(Object itemId) {
+ return expanded.contains(itemId);
+ }
+
+ /**
+ * Expands an item.
+ *
+ * @param itemId
+ * the item id.
+ * @return True iff the expand operation succeeded
+ */
+ public boolean expandItem(Object itemId) {
+ boolean success = expandItem(itemId, true);
+ requestRepaint();
+ return success;
+ }
+
+ /**
+ * Expands an item.
+ *
+ * @param itemId
+ * the item id.
+ * @param sendChildTree
+ * flag to indicate if client needs subtree or not (may be
+ * cached)
+ * @return True iff the expand operation succeeded
+ */
+ private boolean expandItem(Object itemId, boolean sendChildTree) {
+
+ // Succeeds if the node is already expanded
+ if (isExpanded(itemId)) {
+ return true;
+ }
+
+ // Nodes that can not have children are not expandable
+ if (!areChildrenAllowed(itemId)) {
+ return false;
+ }
+
+ // Expands
+ expanded.add(itemId);
+
+ expandedItemId = itemId;
+ if (initialPaint) {
+ requestRepaint();
+ } else if (sendChildTree) {
+ requestPartialRepaint();
+ }
+ fireExpandEvent(itemId);
+
+ return true;
+ }
+
+ @Override
+ public void requestRepaint() {
+ super.requestRepaint();
+ partialUpdate = false;
+ }
+
+ private void requestPartialRepaint() {
+ super.requestRepaint();
+ partialUpdate = true;
+ }
+
+ /**
+ * Expands the items recursively
+ *
+ * Expands all the children recursively starting from an item. Operation
+ * succeeds only if all expandable items are expanded.
+ *
+ * @param startItemId
+ * @return True iff the expand operation succeeded
+ */
+ public boolean expandItemsRecursively(Object startItemId) {
+
+ boolean result = true;
+
+ // Initial stack
+ final Stack<Object> todo = new Stack<Object>();
+ todo.add(startItemId);
+
+ // Expands recursively
+ while (!todo.isEmpty()) {
+ final Object id = todo.pop();
+ if (areChildrenAllowed(id) && !expandItem(id, false)) {
+ result = false;
+ }
+ if (hasChildren(id)) {
+ todo.addAll(getChildren(id));
+ }
+ }
+ requestRepaint();
+ return result;
+ }
+
+ /**
+ * Collapses an item.
+ *
+ * @param itemId
+ * the item id.
+ * @return True iff the collapse operation succeeded
+ */
+ public boolean collapseItem(Object itemId) {
+
+ // Succeeds if the node is already collapsed
+ if (!isExpanded(itemId)) {
+ return true;
+ }
+
+ // Collapse
+ expanded.remove(itemId);
+ requestRepaint();
+ fireCollapseEvent(itemId);
+
+ return true;
+ }
+
+ /**
+ * Collapses the items recursively.
+ *
+ * Collapse all the children recursively starting from an item. Operation
+ * succeeds only if all expandable items are collapsed.
+ *
+ * @param startItemId
+ * @return True iff the collapse operation succeeded
+ */
+ public boolean collapseItemsRecursively(Object startItemId) {
+
+ boolean result = true;
+
+ // Initial stack
+ final Stack<Object> todo = new Stack<Object>();
+ todo.add(startItemId);
+
+ // Collapse recursively
+ while (!todo.isEmpty()) {
+ final Object id = todo.pop();
+ if (areChildrenAllowed(id) && !collapseItem(id)) {
+ result = false;
+ }
+ if (hasChildren(id)) {
+ todo.addAll(getChildren(id));
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the current selectable state. Selectable determines if the a node
+ * can be selected on the client side. Selectable does not affect
+ * {@link #setValue(Object)} or {@link #select(Object)}.
+ *
+ * <p>
+ * The tree is selectable by default.
+ * </p>
+ *
+ * @return the current selectable state.
+ */
+ public boolean isSelectable() {
+ return selectable;
+ }
+
+ /**
+ * Sets the selectable state. Selectable determines if the a node can be
+ * selected on the client side. Selectable does not affect
+ * {@link #setValue(Object)} or {@link #select(Object)}.
+ *
+ * <p>
+ * The tree is selectable by default.
+ * </p>
+ *
+ * @param selectable
+ * The new selectable state.
+ */
+ public void setSelectable(boolean selectable) {
+ if (this.selectable != selectable) {
+ this.selectable = selectable;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets the behavior of the multiselect mode
+ *
+ * @param mode
+ * The mode to set
+ */
+ public void setMultiselectMode(MultiSelectMode mode) {
+ if (multiSelectMode != mode && mode != null) {
+ multiSelectMode = mode;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Returns the mode the multiselect is in. The mode controls how
+ * multiselection can be done.
+ *
+ * @return The mode
+ */
+ public MultiSelectMode getMultiselectMode() {
+ return multiSelectMode;
+ }
+
+ /* Component API */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractSelect#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+
+ if (variables.containsKey("clickedKey")) {
+ String key = (String) variables.get("clickedKey");
+
+ Object id = itemIdMapper.get(key);
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("clickEvent"));
+ Item item = getItem(id);
+ if (item != null) {
+ fireEvent(new ItemClickEvent(this, item, id, null, details));
+ }
+ }
+
+ if (!isSelectable() && variables.containsKey("selected")) {
+ // Not-selectable is a special case, AbstractSelect does not support
+ // TODO could be optimized.
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
+ // Collapses the nodes
+ if (variables.containsKey("collapse")) {
+ final String[] keys = (String[]) variables.get("collapse");
+ for (int i = 0; i < keys.length; i++) {
+ final Object id = itemIdMapper.get(keys[i]);
+ if (id != null && isExpanded(id)) {
+ expanded.remove(id);
+ fireCollapseEvent(id);
+ }
+ }
+ }
+
+ // Expands the nodes
+ if (variables.containsKey("expand")) {
+ boolean sendChildTree = false;
+ if (variables.containsKey("requestChildTree")) {
+ sendChildTree = true;
+ }
+ final String[] keys = (String[]) variables.get("expand");
+ for (int i = 0; i < keys.length; i++) {
+ final Object id = itemIdMapper.get(keys[i]);
+ if (id != null) {
+ expandItem(id, sendChildTree);
+ }
+ }
+ }
+
+ // AbstractSelect cannot handle multiselection so we handle
+ // it ourself
+ if (variables.containsKey("selected") && isMultiSelect()
+ && multiSelectMode == MultiSelectMode.DEFAULT) {
+ handleSelectedItems(variables);
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
+ // Selections are handled by the select component
+ super.changeVariables(source, variables);
+
+ // Actions
+ if (variables.containsKey("action")) {
+ final StringTokenizer st = new StringTokenizer(
+ (String) variables.get("action"), ",");
+ if (st.countTokens() == 2) {
+ final Object itemId = itemIdMapper.get(st.nextToken());
+ final Action action = actionMapper.get(st.nextToken());
+ if (action != null && (itemId == null || containsId(itemId))
+ && actionHandlers != null) {
+ for (Handler ah : actionHandlers) {
+ ah.handleAction(action, this, itemId);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles the selection
+ *
+ * @param variables
+ * The variables sent to the server from the client
+ */
+ private void handleSelectedItems(Map<String, Object> variables) {
+ final String[] ka = (String[]) variables.get("selected");
+
+ // Converts the key-array to id-set
+ final LinkedList<Object> s = new LinkedList<Object>();
+ for (int i = 0; i < ka.length; i++) {
+ final Object id = itemIdMapper.get(ka[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ requestRepaint();
+ } else if (id != null && containsId(id)) {
+ s.add(id);
+ }
+ }
+
+ if (!isNullSelectionAllowed() && s.size() < 1) {
+ // empty selection not allowed, keep old value
+ requestRepaint();
+ return;
+ }
+
+ setValue(s, true);
+ }
+
+ /**
+ * Paints any needed component-specific things to the given UIDL stream.
+ *
+ * @see com.vaadin.ui.AbstractComponent#paintContent(PaintTarget)
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ initialPaint = false;
+
+ if (partialUpdate) {
+ target.addAttribute("partialUpdate", true);
+ target.addAttribute("rootKey", itemIdMapper.key(expandedItemId));
+ } else {
+ getCaptionChangeListener().clear();
+
+ // The tab ordering number
+ if (getTabIndex() > 0) {
+ target.addAttribute("tabindex", getTabIndex());
+ }
+
+ // Paint tree attributes
+ if (isSelectable()) {
+ target.addAttribute("selectmode", (isMultiSelect() ? "multi"
+ : "single"));
+ if (isMultiSelect()) {
+ target.addAttribute("multiselectmode",
+ multiSelectMode.ordinal());
+ }
+ } else {
+ target.addAttribute("selectmode", "none");
+ }
+ if (isNewItemsAllowed()) {
+ target.addAttribute("allownewitem", true);
+ }
+
+ if (isNullSelectionAllowed()) {
+ target.addAttribute("nullselect", true);
+ }
+
+ if (dragMode != TreeDragMode.NONE) {
+ target.addAttribute("dragMode", dragMode.ordinal());
+ }
+
+ }
+
+ // Initialize variables
+ final Set<Action> actionSet = new LinkedHashSet<Action>();
+
+ // rendered selectedKeys
+ LinkedList<String> selectedKeys = new LinkedList<String>();
+
+ final LinkedList<String> expandedKeys = new LinkedList<String>();
+
+ // Iterates through hierarchical tree using a stack of iterators
+ final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>();
+ Collection<?> ids;
+ if (partialUpdate) {
+ ids = getChildren(expandedItemId);
+ } else {
+ ids = rootItemIds();
+ }
+
+ if (ids != null) {
+ iteratorStack.push(ids.iterator());
+ }
+
+ /*
+ * Body actions - Actions which has the target null and can be invoked
+ * by right clicking on the Tree body
+ */
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ for (Handler ah : actionHandlers) {
+
+ // Getting action for the null item, which in this case
+ // means the body item
+ final Action[] aa = ah.getActions(null, this);
+ if (aa != null) {
+ for (int ai = 0; ai < aa.length; ai++) {
+ final String akey = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ keys.add(akey);
+ }
+ }
+ }
+ target.addAttribute("alb", keys.toArray());
+ }
+
+ while (!iteratorStack.isEmpty()) {
+
+ // Gets the iterator for current tree level
+ final Iterator<?> i = iteratorStack.peek();
+
+ // If the level is finished, back to previous tree level
+ if (!i.hasNext()) {
+
+ // Removes used iterator from the stack
+ iteratorStack.pop();
+
+ // Closes node
+ if (!iteratorStack.isEmpty()) {
+ target.endTag("node");
+ }
+ }
+
+ // Adds the item on current level
+ else {
+ final Object itemId = i.next();
+
+ // Starts the item / node
+ final boolean isNode = areChildrenAllowed(itemId);
+ if (isNode) {
+ target.startTag("node");
+ } else {
+ target.startTag("leaf");
+ }
+
+ if (itemStyleGenerator != null) {
+ String stylename = itemStyleGenerator.getStyle(itemId);
+ if (stylename != null) {
+ target.addAttribute(TreeConnector.ATTRIBUTE_NODE_STYLE,
+ stylename);
+ }
+ }
+
+ if (itemDescriptionGenerator != null) {
+ String description = itemDescriptionGenerator
+ .generateDescription(this, itemId, null);
+ if (description != null && !description.equals("")) {
+ target.addAttribute("descr", description);
+ }
+ }
+
+ // Adds the attributes
+ target.addAttribute(TreeConnector.ATTRIBUTE_NODE_CAPTION,
+ getItemCaption(itemId));
+ final Resource icon = getItemIcon(itemId);
+ if (icon != null) {
+ target.addAttribute(TreeConnector.ATTRIBUTE_NODE_ICON,
+ getItemIcon(itemId));
+ }
+ final String key = itemIdMapper.key(itemId);
+ target.addAttribute("key", key);
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ selectedKeys.add(key);
+ }
+ if (areChildrenAllowed(itemId) && isExpanded(itemId)) {
+ target.addAttribute("expanded", true);
+ expandedKeys.add(key);
+ }
+
+ // Add caption change listener
+ getCaptionChangeListener().addNotifierForItem(itemId);
+
+ // Actions
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ final Iterator<Action.Handler> ahi = actionHandlers
+ .iterator();
+ while (ahi.hasNext()) {
+ final Action[] aa = ahi.next().getActions(itemId, this);
+ if (aa != null) {
+ for (int ai = 0; ai < aa.length; ai++) {
+ final String akey = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ keys.add(akey);
+ }
+ }
+ }
+ target.addAttribute("al", keys.toArray());
+ }
+
+ // Adds the children if expanded, or close the tag
+ if (isExpanded(itemId) && hasChildren(itemId)
+ && areChildrenAllowed(itemId)) {
+ iteratorStack.push(getChildren(itemId).iterator());
+ } else {
+ if (isNode) {
+ target.endTag("node");
+ } else {
+ target.endTag("leaf");
+ }
+ }
+ }
+ }
+
+ // Actions
+ if (!actionSet.isEmpty()) {
+ target.addVariable(this, "action", "");
+ target.startTag("actions");
+ final Iterator<Action> i = actionSet.iterator();
+ while (i.hasNext()) {
+ final Action a = i.next();
+ target.startTag("action");
+ if (a.getCaption() != null) {
+ target.addAttribute(TreeConnector.ATTRIBUTE_ACTION_CAPTION,
+ a.getCaption());
+ }
+ if (a.getIcon() != null) {
+ target.addAttribute(TreeConnector.ATTRIBUTE_ACTION_ICON,
+ a.getIcon());
+ }
+ target.addAttribute("key", actionMapper.key(a));
+ target.endTag("action");
+ }
+ target.endTag("actions");
+ }
+
+ if (partialUpdate) {
+ partialUpdate = false;
+ } else {
+ // Selected
+ target.addVariable(this, "selected",
+ selectedKeys.toArray(new String[selectedKeys.size()]));
+
+ // Expand and collapse
+ target.addVariable(this, "expand", new String[] {});
+ target.addVariable(this, "collapse", new String[] {});
+
+ // New items
+ target.addVariable(this, "newitem", new String[] {});
+
+ if (dropHandler != null) {
+ dropHandler.getAcceptCriterion().paint(target);
+ }
+
+ }
+ }
+
+ /* Container.Hierarchical API */
+
+ /**
+ * Tests if the Item with given ID can have any children.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#areChildrenAllowed(Object)
+ */
+ @Override
+ public boolean areChildrenAllowed(Object itemId) {
+ return ((Container.Hierarchical) items).areChildrenAllowed(itemId);
+ }
+
+ /**
+ * Gets the IDs of all Items that are children of the specified Item.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#getChildren(Object)
+ */
+ @Override
+ public Collection<?> getChildren(Object itemId) {
+ return ((Container.Hierarchical) items).getChildren(itemId);
+ }
+
+ /**
+ * Gets the ID of the parent Item of the specified Item.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#getParent(Object)
+ */
+ @Override
+ public Object getParent(Object itemId) {
+ return ((Container.Hierarchical) items).getParent(itemId);
+ }
+
+ /**
+ * Tests if the Item specified with <code>itemId</code> has child Items.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#hasChildren(Object)
+ */
+ @Override
+ public boolean hasChildren(Object itemId) {
+ return ((Container.Hierarchical) items).hasChildren(itemId);
+ }
+
+ /**
+ * Tests if the Item specified with <code>itemId</code> is a root Item.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#isRoot(Object)
+ */
+ @Override
+ public boolean isRoot(Object itemId) {
+ return ((Container.Hierarchical) items).isRoot(itemId);
+ }
+
+ /**
+ * Gets the IDs of all Items in the container that don't have a parent.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#rootItemIds()
+ */
+ @Override
+ public Collection<?> rootItemIds() {
+ return ((Container.Hierarchical) items).rootItemIds();
+ }
+
+ /**
+ * Sets the given Item's capability to have children.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#setChildrenAllowed(Object,
+ * boolean)
+ */
+ @Override
+ public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) {
+ final boolean success = ((Container.Hierarchical) items)
+ .setChildrenAllowed(itemId, areChildrenAllowed);
+ if (success) {
+ requestRepaint();
+ }
+ return success;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Hierarchical#setParent(java.lang.Object ,
+ * java.lang.Object)
+ */
+ @Override
+ public boolean setParent(Object itemId, Object newParentId) {
+ final boolean success = ((Container.Hierarchical) items).setParent(
+ itemId, newParentId);
+ if (success) {
+ requestRepaint();
+ }
+ return success;
+ }
+
+ /* Overriding select behavior */
+
+ /**
+ * Sets the Container that serves as the data source of the viewer.
+ *
+ * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
+ */
+ @Override
+ public void setContainerDataSource(Container newDataSource) {
+ if (newDataSource == null) {
+ // Note: using wrapped IndexedContainer to match constructor (super
+ // creates an IndexedContainer, which is then wrapped).
+ newDataSource = new ContainerHierarchicalWrapper(
+ new IndexedContainer());
+ }
+
+ // Assure that the data source is ordered by making unordered
+ // containers ordered by wrapping them
+ if (Container.Hierarchical.class.isAssignableFrom(newDataSource
+ .getClass())) {
+ super.setContainerDataSource(newDataSource);
+ } else {
+ super.setContainerDataSource(new ContainerHierarchicalWrapper(
+ newDataSource));
+ }
+ }
+
+ /* Expand event and listener */
+
+ /**
+ * Event to fired when a node is expanded. ExapandEvent is fired when a node
+ * is to be expanded. it can me used to dynamically fill the sub-nodes of
+ * the node.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public static class ExpandEvent extends Component.Event {
+
+ private final Object expandedItemId;
+
+ /**
+ * New instance of options change event
+ *
+ * @param source
+ * the Source of the event.
+ * @param expandedItemId
+ */
+ public ExpandEvent(Component source, Object expandedItemId) {
+ super(source);
+ this.expandedItemId = expandedItemId;
+ }
+
+ /**
+ * Node where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Object getItemId() {
+ return expandedItemId;
+ }
+ }
+
+ /**
+ * Expand event listener.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ExpandListener extends Serializable {
+
+ public static final Method EXPAND_METHOD = ReflectTools.findMethod(
+ ExpandListener.class, "nodeExpand", ExpandEvent.class);
+
+ /**
+ * A node has been expanded.
+ *
+ * @param event
+ * the Expand event.
+ */
+ public void nodeExpand(ExpandEvent event);
+ }
+
+ /**
+ * Adds the expand listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(ExpandListener listener) {
+ addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD);
+ }
+
+ /**
+ * Removes the expand listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(ExpandListener listener) {
+ removeListener(ExpandEvent.class, listener,
+ ExpandListener.EXPAND_METHOD);
+ }
+
+ /**
+ * Emits the expand event.
+ *
+ * @param itemId
+ * the item id.
+ */
+ protected void fireExpandEvent(Object itemId) {
+ fireEvent(new ExpandEvent(this, itemId));
+ }
+
+ /* Collapse event */
+
+ /**
+ * Collapse event
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public static class CollapseEvent extends Component.Event {
+
+ private final Object collapsedItemId;
+
+ /**
+ * New instance of options change event.
+ *
+ * @param source
+ * the Source of the event.
+ * @param collapsedItemId
+ */
+ public CollapseEvent(Component source, Object collapsedItemId) {
+ super(source);
+ this.collapsedItemId = collapsedItemId;
+ }
+
+ /**
+ * Gets tge Collapsed Item id.
+ *
+ * @return the collapsed item id.
+ */
+ public Object getItemId() {
+ return collapsedItemId;
+ }
+ }
+
+ /**
+ * Collapse event listener.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface CollapseListener extends Serializable {
+
+ public static final Method COLLAPSE_METHOD = ReflectTools.findMethod(
+ CollapseListener.class, "nodeCollapse", CollapseEvent.class);
+
+ /**
+ * A node has been collapsed.
+ *
+ * @param event
+ * the Collapse event.
+ */
+ public void nodeCollapse(CollapseEvent event);
+ }
+
+ /**
+ * Adds the collapse listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(CollapseListener listener) {
+ addListener(CollapseEvent.class, listener,
+ CollapseListener.COLLAPSE_METHOD);
+ }
+
+ /**
+ * Removes the collapse listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(CollapseListener listener) {
+ removeListener(CollapseEvent.class, listener,
+ CollapseListener.COLLAPSE_METHOD);
+ }
+
+ /**
+ * Emits collapse event.
+ *
+ * @param itemId
+ * the item id.
+ */
+ protected void fireCollapseEvent(Object itemId) {
+ fireEvent(new CollapseEvent(this, itemId));
+ }
+
+ /* Action container */
+
+ /**
+ * Adds an action handler.
+ *
+ * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler)
+ */
+ @Override
+ public void addActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList<Action.Handler>();
+ actionMapper = new KeyMapper<Action>();
+ }
+
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ requestRepaint();
+ }
+ }
+ }
+
+ /**
+ * Removes an action handler.
+ *
+ * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
+ */
+ @Override
+ public void removeActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+
+ actionHandlers.remove(actionHandler);
+
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Removes all action handlers
+ */
+ public void removeAllActionHandlers() {
+ actionHandlers = null;
+ actionMapper = null;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the visible item ids.
+ *
+ * @see com.vaadin.ui.Select#getVisibleItemIds()
+ */
+ @Override
+ public Collection<?> getVisibleItemIds() {
+
+ final LinkedList<Object> visible = new LinkedList<Object>();
+
+ // Iterates trough hierarchical tree using a stack of iterators
+ final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>();
+ final Collection<?> ids = rootItemIds();
+ if (ids != null) {
+ iteratorStack.push(ids.iterator());
+ }
+ while (!iteratorStack.isEmpty()) {
+
+ // Gets the iterator for current tree level
+ final Iterator<?> i = iteratorStack.peek();
+
+ // If the level is finished, back to previous tree level
+ if (!i.hasNext()) {
+
+ // Removes used iterator from the stack
+ iteratorStack.pop();
+ }
+
+ // Adds the item on current level
+ else {
+ final Object itemId = i.next();
+
+ visible.add(itemId);
+
+ // Adds children if expanded, or close the tag
+ if (isExpanded(itemId) && hasChildren(itemId)) {
+ iteratorStack.push(getChildren(itemId).iterator());
+ }
+ }
+ }
+
+ return visible;
+ }
+
+ /**
+ * Tree does not support <code>setNullSelectionItemId</code>.
+ *
+ * @see com.vaadin.ui.AbstractSelect#setNullSelectionItemId(java.lang.Object)
+ */
+ @Override
+ public void setNullSelectionItemId(Object nullSelectionItemId)
+ throws UnsupportedOperationException {
+ if (nullSelectionItemId != null) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ /**
+ * Adding new items is not supported.
+ *
+ * @throws UnsupportedOperationException
+ * if set to true.
+ * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean)
+ */
+ @Override
+ public void setNewItemsAllowed(boolean allowNewOptions)
+ throws UnsupportedOperationException {
+ if (allowNewOptions) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Tree does not support lazy options loading mode. Setting this true will
+ * throw UnsupportedOperationException.
+ *
+ * @see com.vaadin.ui.Select#setLazyLoading(boolean)
+ */
+ public void setLazyLoading(boolean useLazyLoading) {
+ if (useLazyLoading) {
+ throw new UnsupportedOperationException(
+ "Lazy options loading is not supported by Tree.");
+ }
+ }
+
+ private ItemStyleGenerator itemStyleGenerator;
+
+ private DropHandler dropHandler;
+
+ @Override
+ public void addListener(ItemClickListener listener) {
+ addListener(VTree.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, listener,
+ ItemClickEvent.ITEM_CLICK_METHOD);
+ }
+
+ @Override
+ public void removeListener(ItemClickListener listener) {
+ removeListener(VTree.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
+ listener);
+ }
+
+ /**
+ * Sets the {@link ItemStyleGenerator} to be used with this tree.
+ *
+ * @param itemStyleGenerator
+ * item style generator or null to remove generator
+ */
+ public void setItemStyleGenerator(ItemStyleGenerator itemStyleGenerator) {
+ if (this.itemStyleGenerator != itemStyleGenerator) {
+ this.itemStyleGenerator = itemStyleGenerator;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * @return the current {@link ItemStyleGenerator} for this tree. Null if
+ * {@link ItemStyleGenerator} is not set.
+ */
+ public ItemStyleGenerator getItemStyleGenerator() {
+ return itemStyleGenerator;
+ }
+
+ /**
+ * ItemStyleGenerator can be used to add custom styles to tree items. The
+ * CSS class name that will be added to the cell content is
+ * <tt>v-tree-node-[style name]</tt>.
+ */
+ public interface ItemStyleGenerator extends Serializable {
+
+ /**
+ * Called by Tree when an item is painted.
+ *
+ * @param itemId
+ * The itemId of the item to be painted
+ * @return The style name to add to this item. (the CSS class name will
+ * be v-tree-node-[style name]
+ */
+ public abstract String getStyle(Object itemId);
+ }
+
+ // Overriden so javadoc comes from Container.Hierarchical
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ return super.removeItem(itemId);
+ }
+
+ @Override
+ public DropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ public void setDropHandler(DropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ }
+
+ /**
+ * A {@link TargetDetails} implementation with Tree specific api.
+ *
+ * @since 6.3
+ */
+ public class TreeTargetDetails extends AbstractSelectTargetDetails {
+
+ TreeTargetDetails(Map<String, Object> rawVariables) {
+ super(rawVariables);
+ }
+
+ @Override
+ public Tree getTarget() {
+ return (Tree) super.getTarget();
+ }
+
+ /**
+ * If the event is on a node that can not have children (see
+ * {@link Tree#areChildrenAllowed(Object)}), this method returns the
+ * parent item id of the target item (see {@link #getItemIdOver()} ).
+ * The identifier of the parent node is also returned if the cursor is
+ * on the top part of node. Else this method returns the same as
+ * {@link #getItemIdOver()}.
+ * <p>
+ * In other words this method returns the identifier of the "folder"
+ * into the drag operation is targeted.
+ * <p>
+ * If the method returns null, the current target is on a root node or
+ * on other undefined area over the tree component.
+ * <p>
+ * The default Tree implementation marks the targetted tree node with
+ * CSS classnames v-tree-node-dragfolder and
+ * v-tree-node-caption-dragfolder (for the caption element).
+ */
+ public Object getItemIdInto() {
+
+ Object itemIdOver = getItemIdOver();
+ if (areChildrenAllowed(itemIdOver)
+ && getDropLocation() == VerticalDropLocation.MIDDLE) {
+ return itemIdOver;
+ }
+ return getParent(itemIdOver);
+ }
+
+ /**
+ * If drop is targeted into "folder node" (see {@link #getItemIdInto()}
+ * ), this method returns the item id of the node after the drag was
+ * targeted. This method is useful when implementing drop into specific
+ * location (between specific nodes) in tree.
+ *
+ * @return the id of the item after the user targets the drop or null if
+ * "target" is a first item in node list (or the first in root
+ * node list)
+ */
+ public Object getItemIdAfter() {
+ Object itemIdOver = getItemIdOver();
+ Object itemIdInto2 = getItemIdInto();
+ if (itemIdOver.equals(itemIdInto2)) {
+ return null;
+ }
+ VerticalDropLocation dropLocation = getDropLocation();
+ if (VerticalDropLocation.TOP == dropLocation) {
+ // if on top of the caption area, add before
+ Collection<?> children;
+ Object itemIdInto = getItemIdInto();
+ if (itemIdInto != null) {
+ // seek the previous from child list
+ children = getChildren(itemIdInto);
+ } else {
+ children = rootItemIds();
+ }
+ Object ref = null;
+ for (Object object : children) {
+ if (object.equals(itemIdOver)) {
+ return ref;
+ }
+ ref = object;
+ }
+ }
+ return itemIdOver;
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.DropTarget#translateDropTargetDetails(java.util.Map)
+ */
+ @Override
+ public TreeTargetDetails translateDropTargetDetails(
+ Map<String, Object> clientVariables) {
+ return new TreeTargetDetails(clientVariables);
+ }
+
+ /**
+ * Helper API for {@link TreeDropCriterion}
+ *
+ * @param itemId
+ * @return
+ */
+ private String key(Object itemId) {
+ return itemIdMapper.key(itemId);
+ }
+
+ /**
+ * Sets the drag mode that controls how Tree behaves as a {@link DragSource}
+ * .
+ *
+ * @param dragMode
+ */
+ public void setDragMode(TreeDragMode dragMode) {
+ this.dragMode = dragMode;
+ requestRepaint();
+ }
+
+ /**
+ * @return the drag mode that controls how Tree behaves as a
+ * {@link DragSource}.
+ *
+ * @see TreeDragMode
+ */
+ public TreeDragMode getDragMode() {
+ return dragMode;
+ }
+
+ /**
+ * Concrete implementation of {@link DataBoundTransferable} for data
+ * transferred from a tree.
+ *
+ * @see {@link DataBoundTransferable}.
+ *
+ * @since 6.3
+ */
+ protected class TreeTransferable extends DataBoundTransferable {
+
+ public TreeTransferable(Component sourceComponent,
+ Map<String, Object> rawVariables) {
+ super(sourceComponent, rawVariables);
+ }
+
+ @Override
+ public Object getItemId() {
+ return getData("itemId");
+ }
+
+ @Override
+ public Object getPropertyId() {
+ return getItemCaptionPropertyId();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.event.dd.DragSource#getTransferable(java.util.Map)
+ */
+ @Override
+ public Transferable getTransferable(Map<String, Object> payload) {
+ TreeTransferable transferable = new TreeTransferable(this, payload);
+ // updating drag source variables
+ Object object = payload.get("itemId");
+ if (object != null) {
+ transferable.setData("itemId", itemIdMapper.get((String) object));
+ }
+
+ return transferable;
+ }
+
+ /**
+ * Lazy loading accept criterion for Tree. Accepted target nodes are loaded
+ * from server once per drag and drop operation. Developer must override one
+ * method that decides accepted tree nodes for the whole Tree.
+ *
+ * <p>
+ * Initially pretty much no data is sent to client. On first required
+ * criterion check (per drag request) the client side data structure is
+ * initialized from server and no subsequent requests requests are needed
+ * during that drag and drop operation.
+ */
+ public static abstract class TreeDropCriterion extends ServerSideCriterion {
+
+ private Tree tree;
+
+ private Set<Object> allowedItemIds;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptCriteria.ServerSideCriterion#getIdentifier
+ * ()
+ */
+ @Override
+ protected String getIdentifier() {
+ return TreeDropCriterion.class.getCanonicalName();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#accepts(com.vaadin
+ * .event.dd.DragAndDropEvent)
+ */
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
+ .getTargetDetails();
+ tree = (Tree) dragEvent.getTargetDetails().getTarget();
+ allowedItemIds = getAllowedItemIds(dragEvent, tree);
+
+ return allowedItemIds.contains(dropTargetData.getItemIdOver());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#paintResponse(
+ * com.vaadin.terminal.PaintTarget)
+ */
+ @Override
+ public void paintResponse(PaintTarget target) throws PaintException {
+ /*
+ * send allowed nodes to client so subsequent requests can be
+ * avoided
+ */
+ Object[] array = allowedItemIds.toArray();
+ for (int i = 0; i < array.length; i++) {
+ String key = tree.key(array[i]);
+ array[i] = key;
+ }
+ target.addAttribute("allowedIds", array);
+ }
+
+ protected abstract Set<Object> getAllowedItemIds(
+ DragAndDropEvent dragEvent, Tree tree);
+
+ }
+
+ /**
+ * A criterion that accepts {@link Transferable} only directly on a tree
+ * node that can have children.
+ * <p>
+ * Class is singleton, use {@link TargetItemAllowsChildren#get()} to get the
+ * instance.
+ *
+ * @see Tree#setChildrenAllowed(Object, boolean)
+ *
+ * @since 6.3
+ */
+ public static class TargetItemAllowsChildren extends TargetDetailIs {
+
+ private static TargetItemAllowsChildren instance = new TargetItemAllowsChildren();
+
+ public static TargetItemAllowsChildren get() {
+ return instance;
+ }
+
+ private TargetItemAllowsChildren() {
+ super("itemIdOverIsNode", Boolean.TRUE);
+ }
+
+ /*
+ * Uses enhanced server side check
+ */
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ try {
+ // must be over tree node and in the middle of it (not top or
+ // bottom
+ // part)
+ TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent
+ .getTargetDetails();
+
+ Object itemIdOver = eventDetails.getItemIdOver();
+ if (!eventDetails.getTarget().areChildrenAllowed(itemIdOver)) {
+ return false;
+ }
+ // return true if directly over
+ return eventDetails.getDropLocation() == VerticalDropLocation.MIDDLE;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ }
+
+ /**
+ * An accept criterion that checks the parent node (or parent hierarchy) for
+ * the item identifier given in constructor. If the parent is found, content
+ * is accepted. Criterion can be used to accepts drags on a specific sub
+ * tree only.
+ * <p>
+ * The root items is also consider to be valid target.
+ */
+ public class TargetInSubtree extends ClientSideCriterion {
+
+ private Object rootId;
+ private int depthToCheck = -1;
+
+ /**
+ * Constructs a criteria that accepts the drag if the targeted Item is a
+ * descendant of Item identified by given id
+ *
+ * @param parentItemId
+ * the item identifier of the parent node
+ */
+ public TargetInSubtree(Object parentItemId) {
+ rootId = parentItemId;
+ }
+
+ /**
+ * Constructs a criteria that accepts drops within given level below the
+ * subtree root identified by given id.
+ *
+ * @param rootId
+ * the item identifier to be sought for
+ * @param depthToCheck
+ * the depth that tree is traversed upwards to seek for the
+ * parent, -1 means that the whole structure should be
+ * checked
+ */
+ public TargetInSubtree(Object rootId, int depthToCheck) {
+ this.rootId = rootId;
+ this.depthToCheck = depthToCheck;
+ }
+
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ try {
+ TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent
+ .getTargetDetails();
+
+ if (eventDetails.getItemIdOver() != null) {
+ Object itemId = eventDetails.getItemIdOver();
+ int i = 0;
+ while (itemId != null
+ && (depthToCheck == -1 || i <= depthToCheck)) {
+ if (itemId.equals(rootId)) {
+ return true;
+ }
+ itemId = getParent(itemId);
+ i++;
+ }
+ }
+ return false;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ target.addAttribute("depth", depthToCheck);
+ target.addAttribute("key", key(rootId));
+ }
+
+ }
+
+ /**
+ * Set the item description generator which generates tooltips for the tree
+ * items
+ *
+ * @param generator
+ * The generator to use or null to disable
+ */
+ public void setItemDescriptionGenerator(ItemDescriptionGenerator generator) {
+ if (generator != itemDescriptionGenerator) {
+ itemDescriptionGenerator = generator;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Get the item description generator which generates tooltips for tree
+ * items
+ */
+ public ItemDescriptionGenerator getItemDescriptionGenerator() {
+ return itemDescriptionGenerator;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/TreeTable.java b/server/src/com/vaadin/ui/TreeTable.java
new file mode 100644
index 0000000000..6132b652f7
--- /dev/null
+++ b/server/src/com/vaadin/ui/TreeTable.java
@@ -0,0 +1,824 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Collapsible;
+import com.vaadin.data.Container;
+import com.vaadin.data.Container.Hierarchical;
+import com.vaadin.data.Container.ItemSetChangeEvent;
+import com.vaadin.data.util.ContainerHierarchicalWrapper;
+import com.vaadin.data.util.HierarchicalContainer;
+import com.vaadin.data.util.HierarchicalContainerOrderedWrapper;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.gwt.client.ui.treetable.TreeTableConnector;
+import com.vaadin.ui.Tree.CollapseEvent;
+import com.vaadin.ui.Tree.CollapseListener;
+import com.vaadin.ui.Tree.ExpandEvent;
+import com.vaadin.ui.Tree.ExpandListener;
+
+/**
+ * TreeTable extends the {@link Table} component so that it can also visualize a
+ * hierarchy of its Items in a similar manner that {@link Tree} does. The tree
+ * hierarchy is always displayed in the first actual column of the TreeTable.
+ * <p>
+ * The TreeTable supports the usual {@link Table} features like lazy loading, so
+ * it should be no problem to display lots of items at once. Only required rows
+ * and some cache rows are sent to the client.
+ * <p>
+ * TreeTable supports standard {@link Hierarchical} container interfaces, but
+ * also a more fine tuned version - {@link Collapsible}. A container
+ * implementing the {@link Collapsible} interface stores the collapsed/expanded
+ * state internally and can this way scale better on the server side than with
+ * standard Hierarchical implementations. Developer must however note that
+ * {@link Collapsible} containers can not be shared among several users as they
+ * share UI state in the container.
+ */
+@SuppressWarnings({ "serial" })
+public class TreeTable extends Table implements Hierarchical {
+
+ private interface ContainerStrategy extends Serializable {
+ public int size();
+
+ public boolean isNodeOpen(Object itemId);
+
+ public int getDepth(Object itemId);
+
+ public void toggleChildVisibility(Object itemId);
+
+ public Object getIdByIndex(int index);
+
+ public int indexOfId(Object id);
+
+ public Object nextItemId(Object itemId);
+
+ public Object lastItemId();
+
+ public Object prevItemId(Object itemId);
+
+ public boolean isLastId(Object itemId);
+
+ public Collection<?> getItemIds();
+
+ public void containerItemSetChange(ItemSetChangeEvent event);
+ }
+
+ private abstract class AbstractStrategy implements ContainerStrategy {
+
+ /**
+ * Consider adding getDepth to {@link Collapsible}, might help
+ * scalability with some container implementations.
+ */
+
+ @Override
+ public int getDepth(Object itemId) {
+ int depth = 0;
+ Hierarchical hierarchicalContainer = getContainerDataSource();
+ while (!hierarchicalContainer.isRoot(itemId)) {
+ depth++;
+ itemId = hierarchicalContainer.getParent(itemId);
+ }
+ return depth;
+ }
+
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ }
+
+ }
+
+ /**
+ * This strategy is used if current container implements {@link Collapsible}
+ * .
+ *
+ * open-collapsed logic diverted to container, otherwise use default
+ * implementations.
+ */
+ private class CollapsibleStrategy extends AbstractStrategy {
+
+ private Collapsible c() {
+ return (Collapsible) getContainerDataSource();
+ }
+
+ @Override
+ public void toggleChildVisibility(Object itemId) {
+ c().setCollapsed(itemId, !c().isCollapsed(itemId));
+ }
+
+ @Override
+ public boolean isNodeOpen(Object itemId) {
+ return !c().isCollapsed(itemId);
+ }
+
+ @Override
+ public int size() {
+ return TreeTable.super.size();
+ }
+
+ @Override
+ public Object getIdByIndex(int index) {
+ return TreeTable.super.getIdByIndex(index);
+ }
+
+ @Override
+ public int indexOfId(Object id) {
+ return TreeTable.super.indexOfId(id);
+ }
+
+ @Override
+ public boolean isLastId(Object itemId) {
+ // using the default impl
+ return TreeTable.super.isLastId(itemId);
+ }
+
+ @Override
+ public Object lastItemId() {
+ // using the default impl
+ return TreeTable.super.lastItemId();
+ }
+
+ @Override
+ public Object nextItemId(Object itemId) {
+ return TreeTable.super.nextItemId(itemId);
+ }
+
+ @Override
+ public Object prevItemId(Object itemId) {
+ return TreeTable.super.prevItemId(itemId);
+ }
+
+ @Override
+ public Collection<?> getItemIds() {
+ return TreeTable.super.getItemIds();
+ }
+
+ }
+
+ /**
+ * Strategy for Hierarchical but not Collapsible container like
+ * {@link HierarchicalContainer}.
+ *
+ * Store collapsed/open states internally, fool Table to use preorder when
+ * accessing items from container via Ordered/Indexed methods.
+ */
+ private class HierarchicalStrategy extends AbstractStrategy {
+
+ private final HashSet<Object> openItems = new HashSet<Object>();
+
+ @Override
+ public boolean isNodeOpen(Object itemId) {
+ return openItems.contains(itemId);
+ }
+
+ @Override
+ public int size() {
+ return getPreOrder().size();
+ }
+
+ @Override
+ public Collection<Object> getItemIds() {
+ return Collections.unmodifiableCollection(getPreOrder());
+ }
+
+ @Override
+ public boolean isLastId(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+
+ return itemId.equals(lastItemId());
+ }
+
+ @Override
+ public Object lastItemId() {
+ if (getPreOrder().size() > 0) {
+ return getPreOrder().get(getPreOrder().size() - 1);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Object nextItemId(Object itemId) {
+ int indexOf = getPreOrder().indexOf(itemId);
+ if (indexOf == -1) {
+ return null;
+ }
+ indexOf++;
+ if (indexOf == getPreOrder().size()) {
+ return null;
+ } else {
+ return getPreOrder().get(indexOf);
+ }
+ }
+
+ @Override
+ public Object prevItemId(Object itemId) {
+ int indexOf = getPreOrder().indexOf(itemId);
+ indexOf--;
+ if (indexOf < 0) {
+ return null;
+ } else {
+ return getPreOrder().get(indexOf);
+ }
+ }
+
+ @Override
+ public void toggleChildVisibility(Object itemId) {
+ boolean removed = openItems.remove(itemId);
+ if (!removed) {
+ openItems.add(itemId);
+ getLogger().finest("Item " + itemId + " is now expanded");
+ } else {
+ getLogger().finest("Item " + itemId + " is now collapsed");
+ }
+ clearPreorderCache();
+ }
+
+ private void clearPreorderCache() {
+ preOrder = null; // clear preorder cache
+ }
+
+ List<Object> preOrder;
+
+ /**
+ * Preorder of ids currently visible
+ *
+ * @return
+ */
+ private List<Object> getPreOrder() {
+ if (preOrder == null) {
+ preOrder = new ArrayList<Object>();
+ Collection<?> rootItemIds = getContainerDataSource()
+ .rootItemIds();
+ for (Object id : rootItemIds) {
+ preOrder.add(id);
+ addVisibleChildTree(id);
+ }
+ }
+ return preOrder;
+ }
+
+ private void addVisibleChildTree(Object id) {
+ if (isNodeOpen(id)) {
+ Collection<?> children = getContainerDataSource().getChildren(
+ id);
+ if (children != null) {
+ for (Object childId : children) {
+ preOrder.add(childId);
+ addVisibleChildTree(childId);
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public int indexOfId(Object id) {
+ return getPreOrder().indexOf(id);
+ }
+
+ @Override
+ public Object getIdByIndex(int index) {
+ return getPreOrder().get(index);
+ }
+
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ // preorder becomes invalid on sort, item additions etc.
+ clearPreorderCache();
+ super.containerItemSetChange(event);
+ }
+
+ }
+
+ /**
+ * Creates an empty TreeTable with a default container.
+ */
+ public TreeTable() {
+ super(null, new HierarchicalContainer());
+ }
+
+ /**
+ * Creates an empty TreeTable with a default container.
+ *
+ * @param caption
+ * the caption for the TreeTable
+ */
+ public TreeTable(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a TreeTable instance with given captions and data source.
+ *
+ * @param caption
+ * the caption for the component
+ * @param dataSource
+ * the dataSource that is used to list items in the component
+ */
+ public TreeTable(String caption, Container dataSource) {
+ super(caption, dataSource);
+ }
+
+ private ContainerStrategy cStrategy;
+ private Object focusedRowId = null;
+ private Object hierarchyColumnId;
+
+ /**
+ * The item id that was expanded or collapsed during this request. Reset at
+ * the end of paint and only used for determining if a partial or full paint
+ * should be done.
+ *
+ * Can safely be reset to null whenever a change occurs that would prevent a
+ * partial update from rendering the correct result, e.g. rows added or
+ * removed during an expand operation.
+ */
+ private Object toggledItemId;
+ private boolean animationsEnabled;
+ private boolean clearFocusedRowPending;
+
+ /**
+ * If the container does not send item set change events, always do a full
+ * repaint instead of a partial update when expanding/collapsing nodes.
+ */
+ private boolean containerSupportsPartialUpdates;
+
+ private ContainerStrategy getContainerStrategy() {
+ if (cStrategy == null) {
+ if (getContainerDataSource() instanceof Collapsible) {
+ cStrategy = new CollapsibleStrategy();
+ } else {
+ cStrategy = new HierarchicalStrategy();
+ }
+ }
+ return cStrategy;
+ }
+
+ @Override
+ protected void paintRowAttributes(PaintTarget target, Object itemId)
+ throws PaintException {
+ super.paintRowAttributes(target, itemId);
+ target.addAttribute("depth", getContainerStrategy().getDepth(itemId));
+ if (getContainerDataSource().areChildrenAllowed(itemId)) {
+ target.addAttribute("ca", true);
+ target.addAttribute("open",
+ getContainerStrategy().isNodeOpen(itemId));
+ }
+ }
+
+ @Override
+ protected void paintRowIcon(PaintTarget target, Object[][] cells,
+ int indexInRowbuffer) throws PaintException {
+ // always paint if present (in parent only if row headers visible)
+ if (getRowHeaderMode() == ROW_HEADER_MODE_HIDDEN) {
+ Resource itemIcon = getItemIcon(cells[CELL_ITEMID][indexInRowbuffer]);
+ if (itemIcon != null) {
+ target.addAttribute("icon", itemIcon);
+ }
+ } else if (cells[CELL_ICON][indexInRowbuffer] != null) {
+ target.addAttribute("icon",
+ (Resource) cells[CELL_ICON][indexInRowbuffer]);
+ }
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ super.changeVariables(source, variables);
+
+ if (variables.containsKey("toggleCollapsed")) {
+ String object = (String) variables.get("toggleCollapsed");
+ Object itemId = itemIdMapper.get(object);
+ toggledItemId = itemId;
+ toggleChildVisibility(itemId, false);
+ if (variables.containsKey("selectCollapsed")) {
+ // ensure collapsed is selected unless opened with selection
+ // head
+ if (isSelectable()) {
+ select(itemId);
+ }
+ }
+ } else if (variables.containsKey("focusParent")) {
+ String key = (String) variables.get("focusParent");
+ Object refId = itemIdMapper.get(key);
+ Object itemId = getParent(refId);
+ focusParent(itemId);
+ }
+ }
+
+ private void focusParent(Object itemId) {
+ boolean inView = false;
+ Object inPageId = getCurrentPageFirstItemId();
+ for (int i = 0; inPageId != null && i < getPageLength(); i++) {
+ if (inPageId.equals(itemId)) {
+ inView = true;
+ break;
+ }
+ inPageId = nextItemId(inPageId);
+ i++;
+ }
+ if (!inView) {
+ setCurrentPageFirstItemId(itemId);
+ }
+ // Select the row if it is selectable.
+ if (isSelectable()) {
+ if (isMultiSelect()) {
+ setValue(Collections.singleton(itemId));
+ } else {
+ setValue(itemId);
+ }
+ }
+ setFocusedRow(itemId);
+ }
+
+ private void setFocusedRow(Object itemId) {
+ focusedRowId = itemId;
+ if (focusedRowId == null) {
+ // Must still inform the client that the focusParent request has
+ // been processed
+ clearFocusedRowPending = true;
+ }
+ requestRepaint();
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (focusedRowId != null) {
+ target.addAttribute("focusedRow", itemIdMapper.key(focusedRowId));
+ focusedRowId = null;
+ } else if (clearFocusedRowPending) {
+ // Must still inform the client that the focusParent request has
+ // been processed
+ target.addAttribute("clearFocusPending", true);
+ clearFocusedRowPending = false;
+ }
+ target.addAttribute("animate", animationsEnabled);
+ if (hierarchyColumnId != null) {
+ Object[] visibleColumns2 = getVisibleColumns();
+ for (int i = 0; i < visibleColumns2.length; i++) {
+ Object object = visibleColumns2[i];
+ if (hierarchyColumnId.equals(object)) {
+ target.addAttribute(
+ TreeTableConnector.ATTRIBUTE_HIERARCHY_COLUMN_INDEX,
+ i);
+ break;
+ }
+ }
+ }
+ super.paintContent(target);
+ toggledItemId = null;
+ }
+
+ /*
+ * Override methods for partial row updates and additions when expanding /
+ * collapsing nodes.
+ */
+
+ @Override
+ protected boolean isPartialRowUpdate() {
+ return toggledItemId != null && containerSupportsPartialUpdates
+ && !isRowCacheInvalidated();
+ }
+
+ @Override
+ protected int getFirstAddedItemIndex() {
+ return indexOfId(toggledItemId) + 1;
+ }
+
+ @Override
+ protected int getAddedRowCount() {
+ return countSubNodesRecursively(getContainerDataSource(), toggledItemId);
+ }
+
+ private int countSubNodesRecursively(Hierarchical hc, Object itemId) {
+ int count = 0;
+ // we need the number of children for toggledItemId no matter if its
+ // collapsed or expanded. Other items' children are only counted if the
+ // item is expanded.
+ if (getContainerStrategy().isNodeOpen(itemId)
+ || itemId == toggledItemId) {
+ Collection<?> children = hc.getChildren(itemId);
+ if (children != null) {
+ count += children != null ? children.size() : 0;
+ for (Object id : children) {
+ count += countSubNodesRecursively(hc, id);
+ }
+ }
+ }
+ return count;
+ }
+
+ @Override
+ protected int getFirstUpdatedItemIndex() {
+ return indexOfId(toggledItemId);
+ }
+
+ @Override
+ protected int getUpdatedRowCount() {
+ return 1;
+ }
+
+ @Override
+ protected boolean shouldHideAddedRows() {
+ return !getContainerStrategy().isNodeOpen(toggledItemId);
+ }
+
+ private void toggleChildVisibility(Object itemId, boolean forceFullRefresh) {
+ getContainerStrategy().toggleChildVisibility(itemId);
+ // ensure that page still has first item in page, DON'T clear the
+ // caches.
+ setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex(), false);
+
+ if (isCollapsed(itemId)) {
+ fireCollapseEvent(itemId);
+ } else {
+ fireExpandEvent(itemId);
+ }
+
+ if (containerSupportsPartialUpdates && !forceFullRefresh) {
+ requestRepaint();
+ } else {
+ // For containers that do not send item set change events, always do
+ // full repaint instead of partial row update.
+ refreshRowCache();
+ }
+ }
+
+ @Override
+ public int size() {
+ return getContainerStrategy().size();
+ }
+
+ @Override
+ public Hierarchical getContainerDataSource() {
+ return (Hierarchical) super.getContainerDataSource();
+ }
+
+ @Override
+ public void setContainerDataSource(Container newDataSource) {
+ cStrategy = null;
+
+ // FIXME: This disables partial updates until TreeTable is fixed so it
+ // does not change component hierarchy during paint
+ containerSupportsPartialUpdates = (newDataSource instanceof ItemSetChangeNotifier) && false;
+
+ if (!(newDataSource instanceof Hierarchical)) {
+ newDataSource = new ContainerHierarchicalWrapper(newDataSource);
+ }
+
+ if (!(newDataSource instanceof Ordered)) {
+ newDataSource = new HierarchicalContainerOrderedWrapper(
+ (Hierarchical) newDataSource);
+ }
+
+ super.setContainerDataSource(newDataSource);
+ }
+
+ @Override
+ public void containerItemSetChange(
+ com.vaadin.data.Container.ItemSetChangeEvent event) {
+ // Can't do partial repaints if items are added or removed during the
+ // expand/collapse request
+ toggledItemId = null;
+ getContainerStrategy().containerItemSetChange(event);
+ super.containerItemSetChange(event);
+ }
+
+ @Override
+ protected Object getIdByIndex(int index) {
+ return getContainerStrategy().getIdByIndex(index);
+ }
+
+ @Override
+ protected int indexOfId(Object itemId) {
+ return getContainerStrategy().indexOfId(itemId);
+ }
+
+ @Override
+ public Object nextItemId(Object itemId) {
+ return getContainerStrategy().nextItemId(itemId);
+ }
+
+ @Override
+ public Object lastItemId() {
+ return getContainerStrategy().lastItemId();
+ }
+
+ @Override
+ public Object prevItemId(Object itemId) {
+ return getContainerStrategy().prevItemId(itemId);
+ }
+
+ @Override
+ public boolean isLastId(Object itemId) {
+ return getContainerStrategy().isLastId(itemId);
+ }
+
+ @Override
+ public Collection<?> getItemIds() {
+ return getContainerStrategy().getItemIds();
+ }
+
+ @Override
+ public boolean areChildrenAllowed(Object itemId) {
+ return getContainerDataSource().areChildrenAllowed(itemId);
+ }
+
+ @Override
+ public Collection<?> getChildren(Object itemId) {
+ return getContainerDataSource().getChildren(itemId);
+ }
+
+ @Override
+ public Object getParent(Object itemId) {
+ return getContainerDataSource().getParent(itemId);
+ }
+
+ @Override
+ public boolean hasChildren(Object itemId) {
+ return getContainerDataSource().hasChildren(itemId);
+ }
+
+ @Override
+ public boolean isRoot(Object itemId) {
+ return getContainerDataSource().isRoot(itemId);
+ }
+
+ @Override
+ public Collection<?> rootItemIds() {
+ return getContainerDataSource().rootItemIds();
+ }
+
+ @Override
+ public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed)
+ throws UnsupportedOperationException {
+ return getContainerDataSource().setChildrenAllowed(itemId,
+ areChildrenAllowed);
+ }
+
+ @Override
+ public boolean setParent(Object itemId, Object newParentId)
+ throws UnsupportedOperationException {
+ return getContainerDataSource().setParent(itemId, newParentId);
+ }
+
+ /**
+ * Sets the Item specified by given identifier as collapsed or expanded. If
+ * the Item is collapsed, its children are not displayed to the user.
+ *
+ * @param itemId
+ * the identifier of the Item
+ * @param collapsed
+ * true if the Item should be collapsed, false if expanded
+ */
+ public void setCollapsed(Object itemId, boolean collapsed) {
+ if (isCollapsed(itemId) != collapsed) {
+ if (null == toggledItemId && !isRowCacheInvalidated()
+ && getVisibleItemIds().contains(itemId)) {
+ // optimization: partial refresh if only one item is
+ // collapsed/expanded
+ toggledItemId = itemId;
+ toggleChildVisibility(itemId, false);
+ } else {
+ // make sure a full refresh takes place - otherwise neither
+ // partial nor full repaint of table content is performed
+ toggledItemId = null;
+ toggleChildVisibility(itemId, true);
+ }
+ }
+ }
+
+ /**
+ * Checks if Item with given identifier is collapsed in the UI.
+ *
+ * <p>
+ *
+ * @param itemId
+ * the identifier of the checked Item
+ * @return true if the Item with given id is collapsed
+ * @see Collapsible#isCollapsed(Object)
+ */
+ public boolean isCollapsed(Object itemId) {
+ return !getContainerStrategy().isNodeOpen(itemId);
+ }
+
+ /**
+ * Explicitly sets the column in which the TreeTable visualizes the
+ * hierarchy. If hierarchyColumnId is not set, the hierarchy is visualized
+ * in the first visible column.
+ *
+ * @param hierarchyColumnId
+ */
+ public void setHierarchyColumn(Object hierarchyColumnId) {
+ this.hierarchyColumnId = hierarchyColumnId;
+ }
+
+ /**
+ * @return the identifier of column into which the hierarchy will be
+ * visualized or null if the column is not explicitly defined.
+ */
+ public Object getHierarchyColumnId() {
+ return hierarchyColumnId;
+ }
+
+ /**
+ * Adds an expand listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(ExpandListener listener) {
+ addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD);
+ }
+
+ /**
+ * Removes an expand listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(ExpandListener listener) {
+ removeListener(ExpandEvent.class, listener,
+ ExpandListener.EXPAND_METHOD);
+ }
+
+ /**
+ * Emits an expand event.
+ *
+ * @param itemId
+ * the item id.
+ */
+ protected void fireExpandEvent(Object itemId) {
+ fireEvent(new ExpandEvent(this, itemId));
+ }
+
+ /**
+ * Adds a collapse listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(CollapseListener listener) {
+ addListener(CollapseEvent.class, listener,
+ CollapseListener.COLLAPSE_METHOD);
+ }
+
+ /**
+ * Removes a collapse listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(CollapseListener listener) {
+ removeListener(CollapseEvent.class, listener,
+ CollapseListener.COLLAPSE_METHOD);
+ }
+
+ /**
+ * Emits a collapse event.
+ *
+ * @param itemId
+ * the item id.
+ */
+ protected void fireCollapseEvent(Object itemId) {
+ fireEvent(new CollapseEvent(this, itemId));
+ }
+
+ /**
+ * @return true if animations are enabled
+ */
+ public boolean isAnimationsEnabled() {
+ return animationsEnabled;
+ }
+
+ /**
+ * Animations can be enabled by passing true to this method. Currently
+ * expanding rows slide in from the top and collapsing rows slide out the
+ * same way. NOTE! not supported in Internet Explorer 6 or 7.
+ *
+ * @param animationsEnabled
+ * true or false whether to enable animations or not.
+ */
+ public void setAnimationsEnabled(boolean animationsEnabled) {
+ this.animationsEnabled = animationsEnabled;
+ requestRepaint();
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(TreeTable.class.getName());
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/TwinColSelect.java b/server/src/com/vaadin/ui/TwinColSelect.java
new file mode 100644
index 0000000000..5539236f77
--- /dev/null
+++ b/server/src/com/vaadin/ui/TwinColSelect.java
@@ -0,0 +1,180 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.ui.twincolselect.VTwinColSelect;
+
+/**
+ * Multiselect component with two lists: left side for available items and right
+ * side for selected items.
+ */
+@SuppressWarnings("serial")
+public class TwinColSelect extends AbstractSelect {
+
+ private int columns = 0;
+ private int rows = 0;
+
+ private String leftColumnCaption;
+ private String rightColumnCaption;
+
+ /**
+ *
+ */
+ public TwinColSelect() {
+ super();
+ setMultiSelect(true);
+ }
+
+ /**
+ * @param caption
+ */
+ public TwinColSelect(String caption) {
+ super(caption);
+ setMultiSelect(true);
+ }
+
+ /**
+ * @param caption
+ * @param dataSource
+ */
+ public TwinColSelect(String caption, Container dataSource) {
+ super(caption, dataSource);
+ setMultiSelect(true);
+ }
+
+ /**
+ * Sets the number of columns in the editor. If the number of columns is set
+ * 0, the actual number of displayed columns is determined implicitly by the
+ * adapter.
+ * <p>
+ * The number of columns overrides the value set by setWidth. Only if
+ * columns are set to 0 (default) the width set using
+ * {@link #setWidth(float, int)} or {@link #setWidth(String)} is used.
+ *
+ * @param columns
+ * the number of columns to set.
+ */
+ public void setColumns(int columns) {
+ if (columns < 0) {
+ columns = 0;
+ }
+ if (this.columns != columns) {
+ this.columns = columns;
+ requestRepaint();
+ }
+ }
+
+ public int getColumns() {
+ return columns;
+ }
+
+ public int getRows() {
+ return rows;
+ }
+
+ /**
+ * Sets the number of rows in the editor. If the number of rows is set to 0,
+ * the actual number of displayed rows is determined implicitly by the
+ * adapter.
+ * <p>
+ * If a height if set (using {@link #setHeight(String)} or
+ * {@link #setHeight(float, int)}) it overrides the number of rows. Leave
+ * the height undefined to use this method. This is the opposite of how
+ * {@link #setColumns(int)} work.
+ *
+ *
+ * @param rows
+ * the number of rows to set.
+ */
+ public void setRows(int rows) {
+ if (rows < 0) {
+ rows = 0;
+ }
+ if (this.rows != rows) {
+ this.rows = rows;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * @param caption
+ * @param options
+ */
+ public TwinColSelect(String caption, Collection<?> options) {
+ super(caption, options);
+ setMultiSelect(true);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute("type", "twincol");
+ // Adds the number of columns
+ if (columns != 0) {
+ target.addAttribute("cols", columns);
+ }
+ // Adds the number of rows
+ if (rows != 0) {
+ target.addAttribute("rows", rows);
+ }
+
+ // Right and left column captions and/or icons (if set)
+ String lc = getLeftColumnCaption();
+ String rc = getRightColumnCaption();
+ if (lc != null) {
+ target.addAttribute(VTwinColSelect.ATTRIBUTE_LEFT_CAPTION, lc);
+ }
+ if (rc != null) {
+ target.addAttribute(VTwinColSelect.ATTRIBUTE_RIGHT_CAPTION, rc);
+ }
+
+ super.paintContent(target);
+ }
+
+ /**
+ * Sets the text shown above the right column.
+ *
+ * @param caption
+ * The text to show
+ */
+ public void setRightColumnCaption(String rightColumnCaption) {
+ this.rightColumnCaption = rightColumnCaption;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the text shown above the right column.
+ *
+ * @return The text shown or null if not set.
+ */
+ public String getRightColumnCaption() {
+ return rightColumnCaption;
+ }
+
+ /**
+ * Sets the text shown above the left column.
+ *
+ * @param caption
+ * The text to show
+ */
+ public void setLeftColumnCaption(String leftColumnCaption) {
+ this.leftColumnCaption = leftColumnCaption;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the text shown above the left column.
+ *
+ * @return The text shown or null if not set.
+ */
+ public String getLeftColumnCaption() {
+ return leftColumnCaption;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/UniqueSerializable.java b/server/src/com/vaadin/ui/UniqueSerializable.java
new file mode 100644
index 0000000000..828b285538
--- /dev/null
+++ b/server/src/com/vaadin/ui/UniqueSerializable.java
@@ -0,0 +1,30 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+/**
+ * A base class for generating an unique object that is serializable.
+ * <p>
+ * This class is abstract but has no abstract methods to force users to create
+ * an anonymous inner class. Otherwise each instance will not be unique.
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0
+ *
+ */
+public abstract class UniqueSerializable implements Serializable {
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return getClass() == obj.getClass();
+ }
+}
diff --git a/server/src/com/vaadin/ui/Upload.java b/server/src/com/vaadin/ui/Upload.java
new file mode 100644
index 0000000000..9d533b67f6
--- /dev/null
+++ b/server/src/com/vaadin/ui/Upload.java
@@ -0,0 +1,1055 @@
+/*
+ * @VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.StreamVariable.StreamingProgressEvent;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.server.NoInputStreamException;
+import com.vaadin.terminal.gwt.server.NoOutputStreamException;
+
+/**
+ * Component for uploading files from client to server.
+ *
+ * <p>
+ * The visible component consists of a file name input box and a browse button
+ * and an upload submit button to start uploading.
+ *
+ * <p>
+ * The Upload component needs a java.io.OutputStream to write the uploaded data.
+ * You need to implement the Upload.Receiver interface and return the output
+ * stream in the receiveUpload() method.
+ *
+ * <p>
+ * You can get an event regarding starting (StartedEvent), progress
+ * (ProgressEvent), and finishing (FinishedEvent) of upload by implementing
+ * StartedListener, ProgressListener, and FinishedListener, respectively. The
+ * FinishedListener is called for both failed and succeeded uploads. If you wish
+ * to separate between these two cases, you can use SucceededListener
+ * (SucceededEvenet) and FailedListener (FailedEvent).
+ *
+ * <p>
+ * The upload component does not itself show upload progress, but you can use
+ * the ProgressIndicator for providing progress feedback by implementing
+ * ProgressListener and updating the indicator in updateProgress().
+ *
+ * <p>
+ * Setting upload component immediate initiates the upload as soon as a file is
+ * selected, instead of the common pattern of file selection field and upload
+ * button.
+ *
+ * <p>
+ * Note! Because of browser dependent implementations of <input type="file">
+ * element, setting size for Upload component is not supported. For some
+ * browsers setting size may work to some extend.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Upload extends AbstractComponent implements Component.Focusable,
+ Vaadin6Component {
+
+ /**
+ * Should the field be focused on next repaint?
+ */
+ private final boolean focus = false;
+
+ /**
+ * The tab order number of this field.
+ */
+ private int tabIndex = 0;
+
+ /**
+ * The output of the upload is redirected to this receiver.
+ */
+ private Receiver receiver;
+
+ private boolean isUploading;
+
+ private long contentLength = -1;
+
+ private int totalBytes;
+
+ private String buttonCaption = "Upload";
+
+ /**
+ * ProgressListeners to which information about progress is sent during
+ * upload
+ */
+ private LinkedHashSet<ProgressListener> progressListeners;
+
+ private boolean interrupted = false;
+
+ private boolean notStarted;
+
+ private int nextid;
+
+ /**
+ * Flag to indicate that submitting file has been requested.
+ */
+ private boolean forceSubmit;
+
+ /**
+ * Creates a new instance of Upload.
+ *
+ * The receiver must be set before performing an upload.
+ */
+ public Upload() {
+ }
+
+ public Upload(String caption, Receiver uploadReceiver) {
+ setCaption(caption);
+ receiver = uploadReceiver;
+ }
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ if (variables.containsKey("pollForStart")) {
+ int id = (Integer) variables.get("pollForStart");
+ if (!isUploading && id == nextid) {
+ notStarted = true;
+ requestRepaint();
+ } else {
+ }
+ }
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * Target to paint the content on.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (notStarted) {
+ target.addAttribute("notStarted", true);
+ notStarted = false;
+ return;
+ }
+ if (forceSubmit) {
+ target.addAttribute("forceSubmit", true);
+ forceSubmit = true;
+ return;
+ }
+ // The field should be focused
+ if (focus) {
+ target.addAttribute("focus", true);
+ }
+
+ // The tab ordering number
+ if (tabIndex >= 0) {
+ target.addAttribute("tabindex", tabIndex);
+ }
+
+ target.addAttribute("state", isUploading);
+
+ if (buttonCaption != null) {
+ target.addAttribute("buttoncaption", buttonCaption);
+ }
+
+ target.addAttribute("nextid", nextid);
+
+ // Post file to this strean variable
+ target.addVariable(this, "action", getStreamVariable());
+
+ }
+
+ /**
+ * Interface that must be implemented by the upload receivers to provide the
+ * Upload component an output stream to write the uploaded data.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface Receiver extends Serializable {
+
+ /**
+ * Invoked when a new upload arrives.
+ *
+ * @param filename
+ * the desired filename of the upload, usually as specified
+ * by the client.
+ * @param mimeType
+ * the MIME type of the uploaded file.
+ * @return Stream to which the uploaded file should be written.
+ */
+ public OutputStream receiveUpload(String filename, String mimeType);
+
+ }
+
+ /* Upload events */
+
+ private static final Method UPLOAD_FINISHED_METHOD;
+
+ private static final Method UPLOAD_FAILED_METHOD;
+
+ private static final Method UPLOAD_SUCCEEDED_METHOD;
+
+ private static final Method UPLOAD_STARTED_METHOD;
+
+ static {
+ try {
+ UPLOAD_FINISHED_METHOD = FinishedListener.class.getDeclaredMethod(
+ "uploadFinished", new Class[] { FinishedEvent.class });
+ UPLOAD_FAILED_METHOD = FailedListener.class.getDeclaredMethod(
+ "uploadFailed", new Class[] { FailedEvent.class });
+ UPLOAD_STARTED_METHOD = StartedListener.class.getDeclaredMethod(
+ "uploadStarted", new Class[] { StartedEvent.class });
+ UPLOAD_SUCCEEDED_METHOD = SucceededListener.class
+ .getDeclaredMethod("uploadSucceeded",
+ new Class[] { SucceededEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in Upload");
+ }
+ }
+
+ /**
+ * Upload.FinishedEvent is sent when the upload receives a file, regardless
+ * of whether the reception was successful or failed. If you wish to
+ * distinguish between the two cases, use either SucceededEvent or
+ * FailedEvent, which are both subclasses of the FinishedEvent.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public static class FinishedEvent extends Component.Event {
+
+ /**
+ * Length of the received file.
+ */
+ private final long length;
+
+ /**
+ * MIME type of the received file.
+ */
+ private final String type;
+
+ /**
+ * Received file name.
+ */
+ private final String filename;
+
+ /**
+ *
+ * @param source
+ * the source of the file.
+ * @param filename
+ * the received file name.
+ * @param MIMEType
+ * the MIME type of the received file.
+ * @param length
+ * the length of the received file.
+ */
+ public FinishedEvent(Upload source, String filename, String MIMEType,
+ long length) {
+ super(source);
+ type = MIMEType;
+ this.filename = filename;
+ this.length = length;
+ }
+
+ /**
+ * Uploads where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Upload getUpload() {
+ return (Upload) getSource();
+ }
+
+ /**
+ * Gets the file name.
+ *
+ * @return the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Gets the MIME Type of the file.
+ *
+ * @return the MIME type.
+ */
+ public String getMIMEType() {
+ return type;
+ }
+
+ /**
+ * Gets the length of the file.
+ *
+ * @return the length.
+ */
+ public long getLength() {
+ return length;
+ }
+
+ }
+
+ /**
+ * Upload.FailedEvent event is sent when the upload is received, but the
+ * reception is interrupted for some reason.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public static class FailedEvent extends FinishedEvent {
+
+ private Exception reason = null;
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ * @param exception
+ */
+ public FailedEvent(Upload source, String filename, String MIMEType,
+ long length, Exception reason) {
+ this(source, filename, MIMEType, length);
+ this.reason = reason;
+ }
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ * @param exception
+ */
+ public FailedEvent(Upload source, String filename, String MIMEType,
+ long length) {
+ super(source, filename, MIMEType, length);
+ }
+
+ /**
+ * Gets the exception that caused the failure.
+ *
+ * @return the exception that caused the failure, null if n/a
+ */
+ public Exception getReason() {
+ return reason;
+ }
+
+ }
+
+ /**
+ * FailedEvent that indicates that an output stream could not be obtained.
+ */
+ public static class NoOutputStreamEvent extends FailedEvent {
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public NoOutputStreamEvent(Upload source, String filename,
+ String MIMEType, long length) {
+ super(source, filename, MIMEType, length);
+ }
+ }
+
+ /**
+ * FailedEvent that indicates that an input stream could not be obtained.
+ */
+ public static class NoInputStreamEvent extends FailedEvent {
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public NoInputStreamEvent(Upload source, String filename,
+ String MIMEType, long length) {
+ super(source, filename, MIMEType, length);
+ }
+
+ }
+
+ /**
+ * Upload.SucceededEvent event is sent when the upload is received
+ * successfully.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public static class SucceededEvent extends FinishedEvent {
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public SucceededEvent(Upload source, String filename, String MIMEType,
+ long length) {
+ super(source, filename, MIMEType, length);
+ }
+
+ }
+
+ /**
+ * Upload.StartedEvent event is sent when the upload is started to received.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+ public static class StartedEvent extends Component.Event {
+
+ private final String filename;
+ private final String type;
+ /**
+ * Length of the received file.
+ */
+ private final long length;
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public StartedEvent(Upload source, String filename, String MIMEType,
+ long contentLength) {
+ super(source);
+ this.filename = filename;
+ type = MIMEType;
+ length = contentLength;
+ }
+
+ /**
+ * Uploads where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Upload getUpload() {
+ return (Upload) getSource();
+ }
+
+ /**
+ * Gets the file name.
+ *
+ * @return the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Gets the MIME Type of the file.
+ *
+ * @return the MIME type.
+ */
+ public String getMIMEType() {
+ return type;
+ }
+
+ /**
+ * @return the length of the file that is being uploaded
+ */
+ public long getContentLength() {
+ return length;
+ }
+
+ }
+
+ /**
+ * Receives the events when the upload starts.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+ public interface StartedListener extends Serializable {
+
+ /**
+ * Upload has started.
+ *
+ * @param event
+ * the Upload started event.
+ */
+ public void uploadStarted(StartedEvent event);
+ }
+
+ /**
+ * Receives the events when the uploads are ready.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface FinishedListener extends Serializable {
+
+ /**
+ * Upload has finished.
+ *
+ * @param event
+ * the Upload finished event.
+ */
+ public void uploadFinished(FinishedEvent event);
+ }
+
+ /**
+ * Receives events when the uploads are finished, but unsuccessful.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface FailedListener extends Serializable {
+
+ /**
+ * Upload has finished unsuccessfully.
+ *
+ * @param event
+ * the Upload failed event.
+ */
+ public void uploadFailed(FailedEvent event);
+ }
+
+ /**
+ * Receives events when the uploads are successfully finished.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface SucceededListener extends Serializable {
+
+ /**
+ * Upload successfull..
+ *
+ * @param event
+ * the Upload successfull event.
+ */
+ public void uploadSucceeded(SucceededEvent event);
+ }
+
+ /**
+ * Adds the upload started event listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(StartedListener listener) {
+ addListener(StartedEvent.class, listener, UPLOAD_STARTED_METHOD);
+ }
+
+ /**
+ * Removes the upload started event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(StartedListener listener) {
+ removeListener(StartedEvent.class, listener, UPLOAD_STARTED_METHOD);
+ }
+
+ /**
+ * Adds the upload received event listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(FinishedListener listener) {
+ addListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD);
+ }
+
+ /**
+ * Removes the upload received event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(FinishedListener listener) {
+ removeListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD);
+ }
+
+ /**
+ * Adds the upload interrupted event listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(FailedListener listener) {
+ addListener(FailedEvent.class, listener, UPLOAD_FAILED_METHOD);
+ }
+
+ /**
+ * Removes the upload interrupted event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(FailedListener listener) {
+ removeListener(FailedEvent.class, listener, UPLOAD_FAILED_METHOD);
+ }
+
+ /**
+ * Adds the upload success event listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(SucceededListener listener) {
+ addListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD);
+ }
+
+ /**
+ * Removes the upload success event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(SucceededListener listener) {
+ removeListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD);
+ }
+
+ /**
+ * Adds the upload success event listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(ProgressListener listener) {
+ if (progressListeners == null) {
+ progressListeners = new LinkedHashSet<ProgressListener>();
+ }
+ progressListeners.add(listener);
+ }
+
+ /**
+ * Removes the upload success event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(ProgressListener listener) {
+ if (progressListeners != null) {
+ progressListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Emit upload received event.
+ *
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ protected void fireStarted(String filename, String MIMEType) {
+ fireEvent(new Upload.StartedEvent(this, filename, MIMEType,
+ contentLength));
+ }
+
+ /**
+ * Emits the upload failed event.
+ *
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ protected void fireUploadInterrupted(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.FailedEvent(this, filename, MIMEType, length));
+ }
+
+ protected void fireNoInputStream(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.NoInputStreamEvent(this, filename, MIMEType,
+ length));
+ }
+
+ protected void fireNoOutputStream(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.NoOutputStreamEvent(this, filename, MIMEType,
+ length));
+ }
+
+ protected void fireUploadInterrupted(String filename, String MIMEType,
+ long length, Exception e) {
+ fireEvent(new Upload.FailedEvent(this, filename, MIMEType, length, e));
+ }
+
+ /**
+ * Emits the upload success event.
+ *
+ * @param filename
+ * @param MIMEType
+ * @param length
+ *
+ */
+ protected void fireUploadSuccess(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.SucceededEvent(this, filename, MIMEType, length));
+ }
+
+ /**
+ * Emits the progress event.
+ *
+ * @param totalBytes
+ * bytes received so far
+ * @param contentLength
+ * actual size of the file being uploaded, if known
+ *
+ */
+ protected void fireUpdateProgress(long totalBytes, long contentLength) {
+ // this is implemented differently than other listeners to maintain
+ // backwards compatibility
+ if (progressListeners != null) {
+ for (Iterator<ProgressListener> it = progressListeners.iterator(); it
+ .hasNext();) {
+ ProgressListener l = it.next();
+ l.updateProgress(totalBytes, contentLength);
+ }
+ }
+ }
+
+ /**
+ * Returns the current receiver.
+ *
+ * @return the StreamVariable.
+ */
+ public Receiver getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Sets the receiver.
+ *
+ * @param receiver
+ * the receiver to set.
+ */
+ public void setReceiver(Receiver receiver) {
+ this.receiver = receiver;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void focus() {
+ super.focus();
+ }
+
+ /**
+ * Gets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#getTabIndex()
+ */
+ @Override
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ /**
+ * Sets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ @Override
+ public void setTabIndex(int tabIndex) {
+ this.tabIndex = tabIndex;
+ }
+
+ /**
+ * Go into upload state. This is to prevent double uploading on same
+ * component.
+ *
+ * Warning: this is an internal method used by the framework and should not
+ * be used by user of the Upload component. Using it results in the Upload
+ * component going in wrong state and not working. It is currently public
+ * because it is used by another class.
+ */
+ public void startUpload() {
+ if (isUploading) {
+ throw new IllegalStateException("uploading already started");
+ }
+ isUploading = true;
+ nextid++;
+ }
+
+ /**
+ * Interrupts the upload currently being received. The interruption will be
+ * done by the receiving tread so this method will return immediately and
+ * the actual interrupt will happen a bit later.
+ */
+ public void interruptUpload() {
+ if (isUploading) {
+ interrupted = true;
+ }
+ }
+
+ /**
+ * Go into state where new uploading can begin.
+ *
+ * Warning: this is an internal method used by the framework and should not
+ * be used by user of the Upload component.
+ */
+ private void endUpload() {
+ isUploading = false;
+ contentLength = -1;
+ interrupted = false;
+ requestRepaint();
+ }
+
+ public boolean isUploading() {
+ return isUploading;
+ }
+
+ /**
+ * Gets read bytes of the file currently being uploaded.
+ *
+ * @return bytes
+ */
+ public long getBytesRead() {
+ return totalBytes;
+ }
+
+ /**
+ * Returns size of file currently being uploaded. Value sane only during
+ * upload.
+ *
+ * @return size in bytes
+ */
+ public long getUploadSize() {
+ return contentLength;
+ }
+
+ /**
+ * This method is deprecated, use addListener(ProgressListener) instead.
+ *
+ * @deprecated Use addListener(ProgressListener) instead.
+ * @param progressListener
+ */
+ @Deprecated
+ public void setProgressListener(ProgressListener progressListener) {
+ addListener(progressListener);
+ }
+
+ /**
+ * This method is deprecated.
+ *
+ * @deprecated Replaced with addListener/removeListener
+ * @return listener
+ *
+ */
+ @Deprecated
+ public ProgressListener getProgressListener() {
+ if (progressListeners == null || progressListeners.isEmpty()) {
+ return null;
+ } else {
+ return progressListeners.iterator().next();
+ }
+ }
+
+ /**
+ * ProgressListener receives events to track progress of upload.
+ */
+ public interface ProgressListener extends Serializable {
+ /**
+ * Updates progress to listener
+ *
+ * @param readBytes
+ * bytes transferred
+ * @param contentLength
+ * total size of file currently being uploaded, -1 if unknown
+ */
+ public void updateProgress(long readBytes, long contentLength);
+ }
+
+ /**
+ * @return String to be rendered into button that fires uploading
+ */
+ public String getButtonCaption() {
+ return buttonCaption;
+ }
+
+ /**
+ * In addition to the actual file chooser, upload components have button
+ * that starts actual upload progress. This method is used to set text in
+ * that button.
+ * <p>
+ * In case the button text is set to null, the button is hidden. In this
+ * case developer must explicitly initiate the upload process with
+ * {@link #submitUpload()}.
+ * <p>
+ * In case the Upload is used in immediate mode using
+ * {@link #setImmediate(boolean)}, the file choose (html input with type
+ * "file") is hidden and only the button with this text is shown.
+ * <p>
+ *
+ * <p>
+ * <strong>Note</strong> the string given is set as is to the button. HTML
+ * formatting is not stripped. Be sure to properly validate your value
+ * according to your needs.
+ *
+ * @param buttonCaption
+ * text for upload components button.
+ */
+ public void setButtonCaption(String buttonCaption) {
+ this.buttonCaption = buttonCaption;
+ requestRepaint();
+ }
+
+ /**
+ * Forces the upload the send selected file to the server.
+ * <p>
+ * In case developer wants to use this feature, he/she will most probably
+ * want to hide the uploads internal submit button by setting its caption to
+ * null with {@link #setButtonCaption(String)} method.
+ * <p>
+ * Note, that the upload runs asynchronous. Developer should use normal
+ * upload listeners to trac the process of upload. If the field is empty
+ * uploaded the file name will be empty string and file length 0 in the
+ * upload finished event.
+ * <p>
+ * Also note, that the developer should not remove or modify the upload in
+ * the same user transaction where the upload submit is requested. The
+ * upload may safely be hidden or removed once the upload started event is
+ * fired.
+ */
+ public void submitUpload() {
+ requestRepaint();
+ forceSubmit = true;
+ }
+
+ @Override
+ public void requestRepaint() {
+ forceSubmit = false;
+ super.requestRepaint();
+ }
+
+ /*
+ * Handle to terminal via Upload monitors and controls the upload during it
+ * is being streamed.
+ */
+ private com.vaadin.terminal.StreamVariable streamVariable;
+
+ protected com.vaadin.terminal.StreamVariable getStreamVariable() {
+ if (streamVariable == null) {
+ streamVariable = new com.vaadin.terminal.StreamVariable() {
+ private StreamingStartEvent lastStartedEvent;
+
+ @Override
+ public boolean listenProgress() {
+ return (progressListeners != null && !progressListeners
+ .isEmpty());
+ }
+
+ @Override
+ public void onProgress(StreamingProgressEvent event) {
+ fireUpdateProgress(event.getBytesReceived(),
+ event.getContentLength());
+ }
+
+ @Override
+ public boolean isInterrupted() {
+ return interrupted;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ OutputStream receiveUpload = receiver.receiveUpload(
+ lastStartedEvent.getFileName(),
+ lastStartedEvent.getMimeType());
+ lastStartedEvent = null;
+ return receiveUpload;
+ }
+
+ @Override
+ public void streamingStarted(StreamingStartEvent event) {
+ startUpload();
+ contentLength = event.getContentLength();
+ fireStarted(event.getFileName(), event.getMimeType());
+ lastStartedEvent = event;
+ }
+
+ @Override
+ public void streamingFinished(StreamingEndEvent event) {
+ fireUploadSuccess(event.getFileName(), event.getMimeType(),
+ event.getContentLength());
+ endUpload();
+ requestRepaint();
+ }
+
+ @Override
+ public void streamingFailed(StreamingErrorEvent event) {
+ Exception exception = event.getException();
+ if (exception instanceof NoInputStreamException) {
+ fireNoInputStream(event.getFileName(),
+ event.getMimeType(), 0);
+ } else if (exception instanceof NoOutputStreamException) {
+ fireNoOutputStream(event.getFileName(),
+ event.getMimeType(), 0);
+ } else {
+ fireUploadInterrupted(event.getFileName(),
+ event.getMimeType(), 0, exception);
+ }
+ endUpload();
+ }
+ };
+ }
+ return streamVariable;
+ }
+
+ @Override
+ public java.util.Collection<?> getListeners(java.lang.Class<?> eventType) {
+ if (StreamingProgressEvent.class.isAssignableFrom(eventType)) {
+ if (progressListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections.unmodifiableCollection(progressListeners);
+ }
+
+ }
+ return super.getListeners(eventType);
+ };
+}
diff --git a/server/src/com/vaadin/ui/VerticalLayout.java b/server/src/com/vaadin/ui/VerticalLayout.java
new file mode 100644
index 0000000000..a04d052d98
--- /dev/null
+++ b/server/src/com/vaadin/ui/VerticalLayout.java
@@ -0,0 +1,25 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+/**
+ * Vertical layout
+ *
+ * <code>VerticalLayout</code> is a component container, which shows the
+ * subcomponents in the order of their addition (vertically). A vertical layout
+ * is by default 100% wide.
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.3
+ */
+@SuppressWarnings("serial")
+public class VerticalLayout extends AbstractOrderedLayout {
+
+ public VerticalLayout() {
+ setWidth("100%");
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/VerticalSplitPanel.java b/server/src/com/vaadin/ui/VerticalSplitPanel.java
new file mode 100644
index 0000000000..0630240e9c
--- /dev/null
+++ b/server/src/com/vaadin/ui/VerticalSplitPanel.java
@@ -0,0 +1,30 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui;
+
+/**
+ * A vertical split panel contains two components and lays them vertically. The
+ * first component is above the second component.
+ *
+ * <pre>
+ * +--------------------------+
+ * | |
+ * | The first component |
+ * | |
+ * +==========================+ <-- splitter
+ * | |
+ * | The second component |
+ * | |
+ * +--------------------------+
+ * </pre>
+ *
+ */
+public class VerticalSplitPanel extends AbstractSplitPanel {
+
+ public VerticalSplitPanel() {
+ super();
+ setSizeFull();
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Video.java b/server/src/com/vaadin/ui/Video.java
new file mode 100644
index 0000000000..d4f95a5be3
--- /dev/null
+++ b/server/src/com/vaadin/ui/Video.java
@@ -0,0 +1,81 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.shared.ui.video.VideoState;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.gwt.server.ResourceReference;
+
+/**
+ * The Video component translates into an HTML5 &lt;video&gt; element and as
+ * such is only supported in browsers that support HTML5 media markup. Browsers
+ * that do not support HTML5 display the text or HTML set by calling
+ * {@link #setAltText(String)}.
+ *
+ * A flash-player fallback can be implemented by setting HTML content allowed (
+ * {@link #setHtmlContentAllowed(boolean)} and calling
+ * {@link #setAltText(String)} with the flash player markup. An example of flash
+ * fallback can be found at the <a href=
+ * "https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Using_Flash"
+ * >Mozilla Developer Network</a>.
+ *
+ * Multiple sources can be specified. Which of the sources is used is selected
+ * by the browser depending on which file formats it supports. See <a
+ * href="http://en.wikipedia.org/wiki/HTML5_video#Table">wikipedia</a> for a
+ * table of formats supported by different browsers.
+ *
+ * @author Vaadin Ltd
+ * @since 6.7.0
+ */
+public class Video extends AbstractMedia {
+
+ @Override
+ public VideoState getState() {
+ return (VideoState) super.getState();
+ }
+
+ public Video() {
+ this("", null);
+ }
+
+ /**
+ * @param caption
+ * The caption for this video.
+ */
+ public Video(String caption) {
+ this(caption, null);
+ }
+
+ /**
+ * @param caption
+ * The caption for this video.
+ * @param source
+ * The Resource containing the video to play.
+ */
+ public Video(String caption, Resource source) {
+ setCaption(caption);
+ setSource(source);
+ setShowControls(true);
+ }
+
+ /**
+ * Sets the poster image, which is shown in place of the video before the
+ * user presses play.
+ *
+ * @param poster
+ */
+ public void setPoster(Resource poster) {
+ getState().setPoster(ResourceReference.create(poster));
+ requestRepaint();
+ }
+
+ /**
+ * @return The poster image.
+ */
+ public Resource getPoster() {
+ return ResourceReference.getResource(getState().getPoster());
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java
new file mode 100644
index 0000000000..e413d35e6d
--- /dev/null
+++ b/server/src/com/vaadin/ui/Window.java
@@ -0,0 +1,853 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.BlurNotifier;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.event.FieldEvents.FocusNotifier;
+import com.vaadin.event.MouseEvents.ClickEvent;
+import com.vaadin.event.ShortcutAction;
+import com.vaadin.event.ShortcutAction.KeyCode;
+import com.vaadin.event.ShortcutAction.ModifierKey;
+import com.vaadin.event.ShortcutListener;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.window.WindowServerRpc;
+import com.vaadin.shared.ui.window.WindowState;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Vaadin6Component;
+import com.vaadin.terminal.gwt.client.ui.root.VRoot;
+
+/**
+ * A component that represents a floating popup window that can be added to a
+ * {@link Root}. A window is added to a {@code Root} using
+ * {@link Root#addWindow(Window)}. </p>
+ * <p>
+ * The contents of a window is set using {@link #setContent(ComponentContainer)}
+ * or by using the {@link #Window(String, ComponentContainer)} constructor. The
+ * contents can in turn contain other components. By default, a
+ * {@link VerticalLayout} is used as content.
+ * </p>
+ * <p>
+ * A window can be positioned on the screen using absolute coordinates (pixels)
+ * or set to be centered using {@link #center()}
+ * </p>
+ * <p>
+ * The caption is displayed in the window header.
+ * </p>
+ * <p>
+ * In Vaadin versions prior to 7.0.0, Window was also used as application level
+ * windows. This function is now covered by the {@link Root} class.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Window extends Panel implements FocusNotifier, BlurNotifier,
+ Vaadin6Component {
+
+ private WindowServerRpc rpc = new WindowServerRpc() {
+
+ @Override
+ public void click(MouseEventDetails mouseDetails) {
+ fireEvent(new ClickEvent(Window.this, mouseDetails));
+ }
+ };
+
+ private int browserWindowWidth = -1;
+
+ private int browserWindowHeight = -1;
+
+ /**
+ * Creates a new unnamed window with a default layout.
+ */
+ public Window() {
+ this("", null);
+ }
+
+ /**
+ * Creates a new unnamed window with a default layout and given title.
+ *
+ * @param caption
+ * the title of the window.
+ */
+ public Window(String caption) {
+ this(caption, null);
+ }
+
+ /**
+ * Creates a new unnamed window with the given content and title.
+ *
+ * @param caption
+ * the title of the window.
+ * @param content
+ * the contents of the window
+ */
+ public Window(String caption, ComponentContainer content) {
+ super(caption, content);
+ registerRpc(rpc);
+ setSizeUndefined();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Panel#addComponent(com.vaadin.ui.Component)
+ */
+
+ @Override
+ public void addComponent(Component c) {
+ if (c instanceof Window) {
+ throw new IllegalArgumentException(
+ "Window cannot be added to another via addComponent. "
+ + "Use addWindow(Window) instead.");
+ }
+ super.addComponent(c);
+ }
+
+ /* ********************************************************************* */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Panel#paintContent(com.vaadin.terminal.PaintTarget)
+ */
+
+ @Override
+ public synchronized void paintContent(PaintTarget target)
+ throws PaintException {
+ if (bringToFront != null) {
+ target.addAttribute("bringToFront", bringToFront.intValue());
+ bringToFront = null;
+ }
+
+ // Contents of the window panel is painted
+ super.paintContent(target);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Panel#changeVariables(java.lang.Object, java.util.Map)
+ */
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+
+ // TODO Are these for top level windows or sub windows?
+ boolean sizeHasChanged = false;
+ // size is handled in super class, but resize events only in windows ->
+ // so detect if size change occurs before super.changeVariables()
+ if (variables.containsKey("height")
+ && (getHeightUnits() != Unit.PIXELS || (Integer) variables
+ .get("height") != getHeight())) {
+ sizeHasChanged = true;
+ }
+ if (variables.containsKey("width")
+ && (getWidthUnits() != Unit.PIXELS || (Integer) variables
+ .get("width") != getWidth())) {
+ sizeHasChanged = true;
+ }
+ Integer browserHeightVar = (Integer) variables
+ .get(VRoot.BROWSER_HEIGHT_VAR);
+ if (browserHeightVar != null
+ && browserHeightVar.intValue() != browserWindowHeight) {
+ browserWindowHeight = browserHeightVar.intValue();
+ sizeHasChanged = true;
+ }
+ Integer browserWidthVar = (Integer) variables
+ .get(VRoot.BROWSER_WIDTH_VAR);
+ if (browserWidthVar != null
+ && browserWidthVar.intValue() != browserWindowWidth) {
+ browserWindowWidth = browserWidthVar.intValue();
+ sizeHasChanged = true;
+ }
+
+ super.changeVariables(source, variables);
+
+ // Positioning
+ final Integer positionx = (Integer) variables.get("positionx");
+ if (positionx != null) {
+ final int x = positionx.intValue();
+ // This is information from the client so it is already using the
+ // position. No need to repaint.
+ setPositionX(x < 0 ? -1 : x, false);
+ }
+ final Integer positiony = (Integer) variables.get("positiony");
+ if (positiony != null) {
+ final int y = positiony.intValue();
+ // This is information from the client so it is already using the
+ // position. No need to repaint.
+ setPositionY(y < 0 ? -1 : y, false);
+ }
+
+ if (isClosable()) {
+ // Closing
+ final Boolean close = (Boolean) variables.get("close");
+ if (close != null && close.booleanValue()) {
+ close();
+ }
+ }
+
+ // fire event if size has really changed
+ if (sizeHasChanged) {
+ fireResize();
+ }
+
+ if (variables.containsKey(FocusEvent.EVENT_ID)) {
+ fireEvent(new FocusEvent(this));
+ } else if (variables.containsKey(BlurEvent.EVENT_ID)) {
+ fireEvent(new BlurEvent(this));
+ }
+
+ }
+
+ /**
+ * Method that handles window closing (from UI).
+ *
+ * <p>
+ * By default, sub-windows are removed from their respective parent windows
+ * and thus visually closed on browser-side. Browser-level windows also
+ * closed on the client-side, but they are not implicitly removed from the
+ * application.
+ * </p>
+ *
+ * <p>
+ * To explicitly close a sub-window, use {@link #removeWindow(Window)}. To
+ * react to a window being closed (after it is closed), register a
+ * {@link CloseListener}.
+ * </p>
+ */
+ public void close() {
+ Root root = getRoot();
+
+ // Don't do anything if not attached to a root
+ if (root != null) {
+ // focus is restored to the parent window
+ root.focus();
+ // subwindow is removed from the root
+ root.removeWindow(this);
+ }
+ }
+
+ /**
+ * Gets the distance of Window left border in pixels from left border of the
+ * containing (main window).
+ *
+ * @return the Distance of Window left border in pixels from left border of
+ * the containing (main window). or -1 if unspecified.
+ * @since 4.0.0
+ */
+ public int getPositionX() {
+ return getState().getPositionX();
+ }
+
+ /**
+ * Sets the distance of Window left border in pixels from left border of the
+ * containing (main window).
+ *
+ * @param positionX
+ * the Distance of Window left border in pixels from left border
+ * of the containing (main window). or -1 if unspecified.
+ * @since 4.0.0
+ */
+ public void setPositionX(int positionX) {
+ setPositionX(positionX, true);
+ }
+
+ /**
+ * Sets the distance of Window left border in pixels from left border of the
+ * containing (main window).
+ *
+ * @param positionX
+ * the Distance of Window left border in pixels from left border
+ * of the containing (main window). or -1 if unspecified.
+ * @param repaintRequired
+ * true if the window needs to be repainted, false otherwise
+ * @since 6.3.4
+ */
+ private void setPositionX(int positionX, boolean repaintRequired) {
+ getState().setPositionX(positionX);
+ getState().setCentered(false);
+ if (repaintRequired) {
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the distance of Window top border in pixels from top border of the
+ * containing (main window).
+ *
+ * @return Distance of Window top border in pixels from top border of the
+ * containing (main window). or -1 if unspecified .
+ *
+ * @since 4.0.0
+ */
+ public int getPositionY() {
+ return getState().getPositionY();
+ }
+
+ /**
+ * Sets the distance of Window top border in pixels from top border of the
+ * containing (main window).
+ *
+ * @param positionY
+ * the Distance of Window top border in pixels from top border of
+ * the containing (main window). or -1 if unspecified
+ *
+ * @since 4.0.0
+ */
+ public void setPositionY(int positionY) {
+ setPositionY(positionY, true);
+ }
+
+ /**
+ * Sets the distance of Window top border in pixels from top border of the
+ * containing (main window).
+ *
+ * @param positionY
+ * the Distance of Window top border in pixels from top border of
+ * the containing (main window). or -1 if unspecified
+ * @param repaintRequired
+ * true if the window needs to be repainted, false otherwise
+ *
+ * @since 6.3.4
+ */
+ private void setPositionY(int positionY, boolean repaintRequired) {
+ getState().setPositionY(positionY);
+ getState().setCentered(false);
+ if (repaintRequired) {
+ requestRepaint();
+ }
+ }
+
+ private static final Method WINDOW_CLOSE_METHOD;
+ static {
+ try {
+ WINDOW_CLOSE_METHOD = CloseListener.class.getDeclaredMethod(
+ "windowClose", new Class[] { CloseEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error, window close method not found");
+ }
+ }
+
+ public class CloseEvent extends Component.Event {
+
+ /**
+ *
+ * @param source
+ */
+ public CloseEvent(Component source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Window.
+ *
+ * @return the window.
+ */
+ public Window getWindow() {
+ return (Window) getSource();
+ }
+ }
+
+ /**
+ * An interface used for listening to Window close events. Add the
+ * CloseListener to a browser level window or a sub window and
+ * {@link CloseListener#windowClose(CloseEvent)} will be called whenever the
+ * user closes the window.
+ *
+ * <p>
+ * Since Vaadin 6.5, removing a window using {@link #removeWindow(Window)}
+ * fires the CloseListener.
+ * </p>
+ */
+ public interface CloseListener extends Serializable {
+ /**
+ * Called when the user closes a window. Use
+ * {@link CloseEvent#getWindow()} to get a reference to the
+ * {@link Window} that was closed.
+ *
+ * @param e
+ * Event containing
+ */
+ public void windowClose(CloseEvent e);
+ }
+
+ /**
+ * Adds a CloseListener to the window.
+ *
+ * For a sub window the CloseListener is fired when the user closes it
+ * (clicks on the close button).
+ *
+ * For a browser level window the CloseListener is fired when the browser
+ * level window is closed. Note that closing a browser level window does not
+ * mean it will be destroyed. Also note that Opera does not send events like
+ * all other browsers and therefore the close listener might not be called
+ * if Opera is used.
+ *
+ * <p>
+ * Since Vaadin 6.5, removing windows using {@link #removeWindow(Window)}
+ * does fire the CloseListener.
+ * </p>
+ *
+ * @param listener
+ * the CloseListener to add.
+ */
+ public void addListener(CloseListener listener) {
+ addListener(CloseEvent.class, listener, WINDOW_CLOSE_METHOD);
+ }
+
+ /**
+ * Removes the CloseListener from the window.
+ *
+ * <p>
+ * For more information on CloseListeners see {@link CloseListener}.
+ * </p>
+ *
+ * @param listener
+ * the CloseListener to remove.
+ */
+ public void removeListener(CloseListener listener) {
+ removeListener(CloseEvent.class, listener, WINDOW_CLOSE_METHOD);
+ }
+
+ protected void fireClose() {
+ fireEvent(new Window.CloseEvent(this));
+ }
+
+ /**
+ * Method for the resize event.
+ */
+ private static final Method WINDOW_RESIZE_METHOD;
+ static {
+ try {
+ WINDOW_RESIZE_METHOD = ResizeListener.class.getDeclaredMethod(
+ "windowResized", new Class[] { ResizeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error, window resized method not found");
+ }
+ }
+
+ /**
+ * Resize events are fired whenever the client-side fires a resize-event
+ * (e.g. the browser window is resized). The frequency may vary across
+ * browsers.
+ */
+ public class ResizeEvent extends Component.Event {
+
+ /**
+ *
+ * @param source
+ */
+ public ResizeEvent(Component source) {
+ super(source);
+ }
+
+ /**
+ * Get the window form which this event originated
+ *
+ * @return the window
+ */
+ public Window getWindow() {
+ return (Window) getSource();
+ }
+ }
+
+ /**
+ * Listener for window resize events.
+ *
+ * @see com.vaadin.ui.Window.ResizeEvent
+ */
+ public interface ResizeListener extends Serializable {
+ public void windowResized(ResizeEvent e);
+ }
+
+ /**
+ * Add a resize listener.
+ *
+ * @param listener
+ */
+ public void addListener(ResizeListener listener) {
+ addListener(ResizeEvent.class, listener, WINDOW_RESIZE_METHOD);
+ }
+
+ /**
+ * Remove a resize listener.
+ *
+ * @param listener
+ */
+ public void removeListener(ResizeListener listener) {
+ removeListener(ResizeEvent.class, listener);
+ }
+
+ /**
+ * Fire the resize event.
+ */
+ protected void fireResize() {
+ fireEvent(new ResizeEvent(this));
+ }
+
+ /**
+ * Used to keep the right order of windows if multiple windows are brought
+ * to front in a single changeset. If this is not used, the order is quite
+ * random (depends on the order getting to dirty list. e.g. which window got
+ * variable changes).
+ */
+ private Integer bringToFront = null;
+
+ /**
+ * If there are currently several windows visible, calling this method makes
+ * this window topmost.
+ * <p>
+ * This method can only be called if this window connected a root. Else an
+ * illegal state exception is thrown. Also if there are modal windows and
+ * this window is not modal, and illegal state exception is thrown.
+ * <p>
+ */
+ public void bringToFront() {
+ Root root = getRoot();
+ if (root == null) {
+ throw new IllegalStateException(
+ "Window must be attached to parent before calling bringToFront method.");
+ }
+ int maxBringToFront = -1;
+ for (Window w : root.getWindows()) {
+ if (!isModal() && w.isModal()) {
+ throw new IllegalStateException(
+ "The root contains modal windows, non-modal window cannot be brought to front.");
+ }
+ if (w.bringToFront != null) {
+ maxBringToFront = Math.max(maxBringToFront,
+ w.bringToFront.intValue());
+ }
+ }
+ bringToFront = Integer.valueOf(maxBringToFront + 1);
+ requestRepaint();
+ }
+
+ /**
+ * Sets sub-window modal, so that widgets behind it cannot be accessed.
+ * <b>Note:</b> affects sub-windows only.
+ *
+ * @param modal
+ * true if modality is to be turned on
+ */
+ public void setModal(boolean modal) {
+ getState().setModal(modal);
+ center();
+ requestRepaint();
+ }
+
+ /**
+ * @return true if this window is modal.
+ */
+ public boolean isModal() {
+ return getState().isModal();
+ }
+
+ /**
+ * Sets sub-window resizable. <b>Note:</b> affects sub-windows only.
+ *
+ * @param resizable
+ * true if resizability is to be turned on
+ */
+ public void setResizable(boolean resizable) {
+ getState().setResizable(resizable);
+ requestRepaint();
+ }
+
+ /**
+ *
+ * @return true if window is resizable by the end-user, otherwise false.
+ */
+ public boolean isResizable() {
+ return getState().isResizable();
+ }
+
+ /**
+ *
+ * @return true if a delay is used before recalculating sizes, false if
+ * sizes are recalculated immediately.
+ */
+ public boolean isResizeLazy() {
+ return getState().isResizeLazy();
+ }
+
+ /**
+ * Should resize operations be lazy, i.e. should there be a delay before
+ * layout sizes are recalculated. Speeds up resize operations in slow UIs
+ * with the penalty of slightly decreased usability.
+ *
+ * Note, some browser send false resize events for the browser window and
+ * are therefore always lazy.
+ *
+ * @param resizeLazy
+ * true to use a delay before recalculating sizes, false to
+ * calculate immediately.
+ */
+ public void setResizeLazy(boolean resizeLazy) {
+ getState().setResizeLazy(resizeLazy);
+ requestRepaint();
+ }
+
+ /**
+ * Sets this window to be centered relative to its parent window. Affects
+ * sub-windows only. If the window is resized as a result of the size of its
+ * content changing, it will keep itself centered as long as its position is
+ * not explicitly changed programmatically or by the user.
+ * <p>
+ * <b>NOTE:</b> This method has several issues as currently implemented.
+ * Please refer to http://dev.vaadin.com/ticket/8971 for details.
+ */
+ public void center() {
+ getState().setCentered(true);
+ requestRepaint();
+ }
+
+ /**
+ * Returns the closable status of the sub window. If a sub window is
+ * closable it typically shows an X in the upper right corner. Clicking on
+ * the X sends a close event to the server. Setting closable to false will
+ * remove the X from the sub window and prevent the user from closing the
+ * window.
+ *
+ * Note! For historical reasons readonly controls the closability of the sub
+ * window and therefore readonly and closable affect each other. Setting
+ * readonly to true will set closable to false and vice versa.
+ * <p/>
+ * Closable only applies to sub windows, not to browser level windows.
+ *
+ * @return true if the sub window can be closed by the user.
+ */
+ public boolean isClosable() {
+ return !isReadOnly();
+ }
+
+ /**
+ * Sets the closable status for the sub window. If a sub window is closable
+ * it typically shows an X in the upper right corner. Clicking on the X
+ * sends a close event to the server. Setting closable to false will remove
+ * the X from the sub window and prevent the user from closing the window.
+ *
+ * Note! For historical reasons readonly controls the closability of the sub
+ * window and therefore readonly and closable affect each other. Setting
+ * readonly to true will set closable to false and vice versa.
+ * <p/>
+ * Closable only applies to sub windows, not to browser level windows.
+ *
+ * @param closable
+ * determines if the sub window can be closed by the user.
+ */
+ public void setClosable(boolean closable) {
+ setReadOnly(!closable);
+ }
+
+ /**
+ * Indicates whether a sub window can be dragged or not. By default a sub
+ * window is draggable.
+ * <p/>
+ * Draggable only applies to sub windows, not to browser level windows.
+ *
+ * @param draggable
+ * true if the sub window can be dragged by the user
+ */
+ public boolean isDraggable() {
+ return getState().isDraggable();
+ }
+
+ /**
+ * Enables or disables that a sub window can be dragged (moved) by the user.
+ * By default a sub window is draggable.
+ * <p/>
+ * Draggable only applies to sub windows, not to browser level windows.
+ *
+ * @param draggable
+ * true if the sub window can be dragged by the user
+ */
+ public void setDraggable(boolean draggable) {
+ getState().setDraggable(draggable);
+ requestRepaint();
+ }
+
+ /*
+ * Actions
+ */
+ protected CloseShortcut closeShortcut;
+
+ /**
+ * Makes is possible to close the window by pressing the given
+ * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/>
+ * Note that this shortcut only reacts while the window has focus, closing
+ * itself - if you want to close a subwindow from a parent window, use
+ * {@link #addAction(com.vaadin.event.Action)} of the parent window instead.
+ *
+ * @param keyCode
+ * the keycode for invoking the shortcut
+ * @param modifiers
+ * the (optional) modifiers for invoking the shortcut, null for
+ * none
+ */
+ public void setCloseShortcut(int keyCode, int... modifiers) {
+ if (closeShortcut != null) {
+ removeAction(closeShortcut);
+ }
+ closeShortcut = new CloseShortcut(this, keyCode, modifiers);
+ addAction(closeShortcut);
+ }
+
+ /**
+ * Removes the keyboard shortcut previously set with
+ * {@link #setCloseShortcut(int, int...)}.
+ */
+ public void removeCloseShortcut() {
+ if (closeShortcut != null) {
+ removeAction(closeShortcut);
+ closeShortcut = null;
+ }
+ }
+
+ /**
+ * A {@link ShortcutListener} specifically made to define a keyboard
+ * shortcut that closes the window.
+ *
+ * <pre>
+ * <code>
+ * // within the window using helper
+ * subWindow.setCloseShortcut(KeyCode.ESCAPE, null);
+ *
+ * // or globally
+ * getWindow().addAction(new Window.CloseShortcut(subWindow, KeyCode.ESCAPE));
+ * </code>
+ * </pre>
+ *
+ */
+ public static class CloseShortcut extends ShortcutListener {
+ protected Window window;
+
+ /**
+ * Creates a keyboard shortcut for closing the given window using the
+ * shorthand notation defined in {@link ShortcutAction}.
+ *
+ * @param window
+ * to be closed when the shortcut is invoked
+ * @param shorthandCaption
+ * the caption with shortcut keycode and modifiers indicated
+ */
+ public CloseShortcut(Window window, String shorthandCaption) {
+ super(shorthandCaption);
+ this.window = window;
+ }
+
+ /**
+ * Creates a keyboard shortcut for closing the given window using the
+ * given {@link KeyCode} and {@link ModifierKey}s.
+ *
+ * @param window
+ * to be closed when the shortcut is invoked
+ * @param keyCode
+ * KeyCode to react to
+ * @param modifiers
+ * optional modifiers for shortcut
+ */
+ public CloseShortcut(Window window, int keyCode, int... modifiers) {
+ super(null, keyCode, modifiers);
+ this.window = window;
+ }
+
+ /**
+ * Creates a keyboard shortcut for closing the given window using the
+ * given {@link KeyCode}.
+ *
+ * @param window
+ * to be closed when the shortcut is invoked
+ * @param keyCode
+ * KeyCode to react to
+ */
+ public CloseShortcut(Window window, int keyCode) {
+ this(window, keyCode, null);
+ }
+
+ @Override
+ public void handleAction(Object sender, Object target) {
+ window.close();
+ }
+ }
+
+ /**
+ * Note, that focus/blur listeners in Window class are only supported by sub
+ * windows. Also note that Window is not considered focused if its contained
+ * component currently has focus.
+ *
+ * @see com.vaadin.event.FieldEvents.FocusNotifier#addListener(com.vaadin.event.FieldEvents.FocusListener)
+ */
+
+ @Override
+ public void addListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ /**
+ * Note, that focus/blur listeners in Window class are only supported by sub
+ * windows. Also note that Window is not considered focused if its contained
+ * component currently has focus.
+ *
+ * @see com.vaadin.event.FieldEvents.BlurNotifier#addListener(com.vaadin.event.FieldEvents.BlurListener)
+ */
+
+ @Override
+ public void addListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * If the window is a sub-window focusing will cause the sub-window to be
+ * brought on top of other sub-windows on gain keyboard focus.
+ */
+
+ @Override
+ public void focus() {
+ /*
+ * When focusing a sub-window it basically means it should be brought to
+ * the front. Instead of just moving the keyboard focus we focus the
+ * window and bring it top-most.
+ */
+ super.focus();
+ bringToFront();
+ }
+
+ @Override
+ public WindowState getState() {
+ return (WindowState) super.getState();
+ }
+}
diff --git a/server/src/com/vaadin/ui/doc-files/component_class_hierarchy.gif b/server/src/com/vaadin/ui/doc-files/component_class_hierarchy.gif
new file mode 100644
index 0000000000..936c220d11
--- /dev/null
+++ b/server/src/com/vaadin/ui/doc-files/component_class_hierarchy.gif
Binary files differ
diff --git a/server/src/com/vaadin/ui/doc-files/component_interfaces.gif b/server/src/com/vaadin/ui/doc-files/component_interfaces.gif
new file mode 100644
index 0000000000..44c99826bb
--- /dev/null
+++ b/server/src/com/vaadin/ui/doc-files/component_interfaces.gif
Binary files differ
diff --git a/server/src/com/vaadin/ui/package.html b/server/src/com/vaadin/ui/package.html
new file mode 100644
index 0000000000..6b19a28fe7
--- /dev/null
+++ b/server/src/com/vaadin/ui/package.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+
+<body bgcolor="white">
+
+<!-- Package summary here -->
+
+<p>Provides interfaces and classes in Vaadin.</p>
+
+<h2>Package Specification</h2>
+
+<p><strong>Interface hierarchy</strong></p>
+
+<p>The general interface hierarchy looks like this:</p>
+
+<p style="text-align: center;"><img
+ src="doc-files/component_interfaces.gif" /></p>
+
+<p><i>Note that the above picture includes only the main
+interfaces. This package includes several other lesser sub-interfaces
+which are not significant in this scope. The interfaces not appearing
+here are documented with the classes that define them.</i></p>
+
+<p>The {@link com.vaadin.ui.Component} interface is the top-level
+interface which must be implemented by all user interface components in
+Vaadin. It defines the common properties of the components and how the
+framework will handle them. Most simple components, such as {@link
+com.vaadin.ui.Button}, for example, do not need to implement the
+lower-level interfaces described below. Notice that also the classes and
+interfaces required by the component event framework are defined in
+{@link com.vaadin.ui.Component}.</p>
+
+<p>The next level in the component hierarchy are the classes
+implementing the {@link com.vaadin.ui.ComponentContainer} interface. It
+adds the capacity to contain other components to {@link
+com.vaadin.ui.Component} with a simple API.</p>
+
+<p>The third and last level is the {@link com.vaadin.ui.Layout},
+which adds the concept of location to the components contained in a
+{@link com.vaadin.ui.ComponentContainer}. It can be used to create
+containers which contents can be positioned.</p>
+
+<p><strong>Component class hierarchy</strong></p>
+
+<p>The actual component classes form a hierarchy like this:</p>
+
+<center><img src="doc-files/component_class_hierarchy.gif" /></center>
+<br />
+
+<center><i>Underlined classes are abstract.</i></center>
+
+<p>At the top level is {@link com.vaadin.ui.AbstractComponent} which
+implements the {@link com.vaadin.ui.Component} interface. As the name
+suggests it is abstract, but it does include a default implementation
+for all methods defined in <code>Component</code> so that a component is
+free to override only those functionalities it needs.</p>
+
+<p>As seen in the picture, <code>AbstractComponent</code> serves as
+the superclass for several "real" components, but it also has a some
+abstract extensions. {@link com.vaadin.ui.AbstractComponentContainer}
+serves as the root class for all components (for example, panels and
+windows) who can contain other components. {@link
+com.vaadin.ui.AbstractField}, on the other hand, implements several
+interfaces to provide a base class for components that are used for data
+display and manipulation.</p>
+
+
+<!-- Package spec here -->
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>
diff --git a/server/src/com/vaadin/ui/themes/BaseTheme.java b/server/src/com/vaadin/ui/themes/BaseTheme.java
new file mode 100644
index 0000000000..6f448746bf
--- /dev/null
+++ b/server/src/com/vaadin/ui/themes/BaseTheme.java
@@ -0,0 +1,59 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui.themes;
+
+/**
+ * <p>
+ * The Base theme is the foundation for all Vaadin themes. Although it is not
+ * necessary to use it as the starting point for all other themes, it is heavily
+ * encouraged, since it abstracts and hides away many necessary style properties
+ * that the Vaadin terminal expects and needs.
+ * </p>
+ * <p>
+ * When creating your own theme, either extend this class and specify the styles
+ * implemented in your theme here, or extend some other theme that has a class
+ * file specified (e.g. Reindeer or Runo).
+ * </p>
+ * <p>
+ * All theme class files should follow the convention of specifying the theme
+ * name as a string constant <code>THEME_NAME</code>.
+ *
+ * @since 6.3.0
+ *
+ */
+public class BaseTheme {
+
+ public static final String THEME_NAME = "base";
+
+ /**
+ * Creates a button that looks like a regular hypertext link but still acts
+ * like a normal button.
+ */
+ public static final String BUTTON_LINK = "link";
+
+ /**
+ * Removes extra decorations from the panel.
+ *
+ * @deprecated Base theme does not implement this style, but it is defined
+ * here since it has been a part of the framework before
+ * multiple themes were available. Use the constant provided by
+ * the theme you're using instead, e.g.
+ * {@link Reindeer#PANEL_LIGHT} or {@link Runo#PANEL_LIGHT}.
+ */
+ @Deprecated
+ public static final String PANEL_LIGHT = "light";
+
+ /**
+ * Adds the connector lines between a parent node and its child nodes to
+ * indicate the tree hierarchy better.
+ */
+ public static final String TREE_CONNECTORS = "connectors";
+
+ /**
+ * Clips the component so it will be constrained to its given size and not
+ * overflow.
+ */
+ public static final String CLIP = "v-clip";
+
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/ui/themes/ChameleonTheme.java b/server/src/com/vaadin/ui/themes/ChameleonTheme.java
new file mode 100644
index 0000000000..5ae8cd4e57
--- /dev/null
+++ b/server/src/com/vaadin/ui/themes/ChameleonTheme.java
@@ -0,0 +1,365 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui.themes;
+
+public class ChameleonTheme extends BaseTheme {
+
+ public static final String THEME_NAME = "chameleon";
+
+ /***************************************************************************
+ * Label styles
+ **************************************************************************/
+
+ /**
+ * Large font for main application headings
+ */
+ public static final String LABEL_H1 = "h1";
+
+ /**
+ * Large font for different sections in the application
+ */
+ public static final String LABEL_H2 = "h2";
+
+ /**
+ * Font for sub-section headers
+ */
+ public static final String LABEL_H3 = "h3";
+
+ /**
+ * Font for paragraphs headers
+ */
+ public static final String LABEL_H4 = "h4";
+
+ /**
+ * Big font for important or emphasized texts
+ */
+ public static final String LABEL_BIG = "big";
+
+ /**
+ * Small and a little lighter font
+ */
+ public static final String LABEL_SMALL = "small";
+
+ /**
+ * Very small and lighter font for things such as footnotes and component
+ * specific informations. Use carefully, since this style will usually
+ * reduce legibility.
+ */
+ public static final String LABEL_TINY = "tiny";
+
+ /**
+ * Adds color to the text (usually the alternate color of the theme)
+ */
+ public static final String LABEL_COLOR = "color";
+
+ /**
+ * Adds a warning icon on the left side and a yellow background to the label
+ */
+ public static final String LABEL_WARNING = "warning";
+
+ /**
+ * Adds an error icon on the left side and a red background to the label
+ */
+ public static final String LABEL_ERROR = "error";
+
+ /**
+ * Adds a spinner icon on the left side of the label
+ */
+ public static final String LABEL_LOADING = "loading";
+
+ /***************************************************************************
+ * Button styles
+ **************************************************************************/
+
+ /**
+ * Default action style for buttons (the button that gets activated when
+ * user presses 'enter' in a form). Use sparingly, only one default button
+ * per screen should be visible.
+ */
+ public static final String BUTTON_DEFAULT = "default";
+
+ /**
+ * Small sized button, use for context specific actions for example
+ */
+ public static final String BUTTON_SMALL = "small";
+
+ /**
+ * Big button, use to get more attention for the button action
+ */
+ public static final String BUTTON_BIG = "big";
+
+ /**
+ * Adds more padding on the sides of the button. Makes it easier for the
+ * user to hit the button.
+ */
+ public static final String BUTTON_WIDE = "wide";
+
+ /**
+ * Adds more padding on the top and on the bottom of the button. Makes it
+ * easier for the user to hit the button.
+ */
+ public static final String BUTTON_TALL = "tall";
+
+ /**
+ * Removes all graphics from the button, leaving only the caption and the
+ * icon visible. Useful for making icon-only buttons and toolbar buttons.
+ */
+ public static final String BUTTON_BORDERLESS = "borderless";
+
+ /**
+ * Places the button icon on top of the caption. By default the icon is on
+ * the left side of the button caption.
+ */
+ public static final String BUTTON_ICON_ON_TOP = "icon-on-top";
+
+ /**
+ * Places the button icon on the right side of the caption. By default the
+ * icon is on the left side of the button caption.
+ */
+ public static final String BUTTON_ICON_ON_RIGHT = "icon-on-right";
+
+ /**
+ * Removes the button caption and only shows its icon
+ */
+ public static final String BUTTON_ICON_ONLY = "icon-only";
+
+ /**
+ * Makes the button look like it is pressed down. Useful for creating a
+ * toggle button.
+ */
+ public static final String BUTTON_DOWN = "down";
+
+ /***************************************************************************
+ * TextField styles
+ **************************************************************************/
+
+ /**
+ * Small sized text field with small font
+ */
+ public static final String TEXTFIELD_SMALL = "small";
+
+ /**
+ * Large sized text field with big font
+ */
+ public static final String TEXTFIELD_BIG = "big";
+
+ /**
+ * Adds a magnifier icon on the left side of the fields text
+ */
+ public static final String TEXTFIELD_SEARCH = "search";
+
+ /***************************************************************************
+ * Select styles
+ **************************************************************************/
+
+ /**
+ * Small sized select with small font
+ */
+ public static final String SELECT_SMALL = "small";
+
+ /**
+ * Large sized select with big font
+ */
+ public static final String SELECT_BIG = "big";
+
+ /**
+ * Adds a magnifier icon on the left side of the fields text
+ */
+ public static final String COMBOBOX_SEARCH = "search";
+
+ /**
+ * Adds a magnifier icon on the left side of the fields text
+ */
+ public static final String COMBOBOX_SELECT_BUTTON = "select-button";
+
+ /***************************************************************************
+ * DateField styles
+ **************************************************************************/
+
+ /**
+ * Small sized date field with small font
+ */
+ public static final String DATEFIELD_SMALL = "small";
+
+ /**
+ * Large sized date field with big font
+ */
+ public static final String DATEFIELD_BIG = "big";
+
+ /***************************************************************************
+ * Panel styles
+ **************************************************************************/
+
+ /**
+ * Removes borders and background color from the panel
+ */
+ public static final String PANEL_BORDERLESS = "borderless";
+
+ /**
+ * Adds a more vibrant header for the panel, using the alternate color of
+ * the theme, and adds slight rounded corners (not supported in all
+ * browsers)
+ */
+ public static final String PANEL_BUBBLE = "bubble";
+
+ /**
+ * Removes borders and background color from the panel
+ */
+ public static final String PANEL_LIGHT = "light";
+
+ /***************************************************************************
+ * SplitPanel styles
+ **************************************************************************/
+
+ /**
+ * Reduces the split handle to a minimal size (1 pixel)
+ */
+ public static final String SPLITPANEL_SMALL = "small";
+
+ /***************************************************************************
+ * TabSheet styles
+ **************************************************************************/
+
+ /**
+ * Removes borders and background color from the tab sheet
+ */
+ public static final String TABSHEET_BORDERLESS = "borderless";
+
+ /***************************************************************************
+ * Accordion styles
+ **************************************************************************/
+
+ /**
+ * Makes the accordion background opaque (non-transparent)
+ */
+ public static final String ACCORDION_OPAQUE = "opaque";
+
+ /***************************************************************************
+ * Table styles
+ **************************************************************************/
+
+ /**
+ * Removes borders and background color from the table
+ */
+ public static final String TABLE_BORDERLESS = "borderless";
+
+ /**
+ * Makes the column header and content font size smaller inside the table
+ */
+ public static final String TABLE_SMALL = "small";
+
+ /**
+ * Makes the column header and content font size bigger inside the table
+ */
+ public static final String TABLE_BIG = "big";
+
+ /**
+ * Adds a light alternate background color to even rows in the table.
+ */
+ public static final String TABLE_STRIPED = "striped";
+
+ /***************************************************************************
+ * ProgressIndicator styles
+ **************************************************************************/
+
+ /**
+ * Reduces the height of the progress bar
+ */
+ public static final String PROGRESS_INDICATOR_SMALL = "small";
+
+ /**
+ * Increases the height of the progress bar. If the indicator is in
+ * indeterminate mode, shows a bigger spinner than the regular indeterminate
+ * indicator.
+ */
+ public static final String PROGRESS_INDICATOR_BIG = "big";
+
+ /**
+ * Displays an indeterminate progress indicator as a bar with animated
+ * background stripes. This style can be used in combination with the
+ * "small" and "big" styles.
+ */
+ public static final String PROGRESS_INDICATOR_INDETERMINATE_BAR = "bar";
+
+ /***************************************************************************
+ * Window styles
+ **************************************************************************/
+
+ /**
+ * Sub-window style that makes the window background opaque (i.e. not
+ * semi-transparent).
+ */
+ public static final String WINDOW_OPAQUE = "opaque";
+
+ /***************************************************************************
+ * Compound styles
+ **************************************************************************/
+
+ /**
+ * Creates a context for a segment button control. Place buttons inside the
+ * segment, and add "<code>first</code>" and "<code>last</code>" style names
+ * for the first and last button in the segment. Then use the
+ * {@link #BUTTON_DOWN} style to indicate button states.
+ *
+ * E.g.
+ *
+ * <pre>
+ * HorizontalLayout ("segment")
+ * + Button ("first down")
+ * + Button ("down")
+ * + Button
+ * ...
+ * + Button ("last")
+ * </pre>
+ *
+ * You can also use most of the different button styles for the contained
+ * buttons (e.g. {@link #BUTTON_BIG}, {@link #BUTTON_ICON_ONLY} etc.).
+ */
+ public static final String COMPOUND_HORIZONTAL_LAYOUT_SEGMENT = "segment";
+
+ /**
+ * Use this mixin-style in combination with the
+ * {@link #COMPOUND_HORIZONTAL_LAYOUT_SEGMENT} style to make buttons with
+ * the "down" style use the themes alternate color (e.g. blue instead of
+ * gray).
+ *
+ * E.g.
+ *
+ * <pre>
+ * HorizontalLayout ("segment segment-alternate")
+ * + Button ("first down")
+ * + Button ("down")
+ * + Button
+ * ...
+ * + Button ("last")
+ * </pre>
+ */
+ public static final String COMPOUND_HORIZONTAL_LAYOUT_SEGMENT_ALTERNATE = "segment-alternate";
+
+ /**
+ * Creates an iTunes-like menu from a CssLayout or a VerticalLayout. Place
+ * plain Labels and NativeButtons inside the layout, and you're all set.
+ *
+ * E.g.
+ *
+ * <pre>
+ * CssLayout ("sidebar-menu")
+ * + Label
+ * + NativeButton
+ * + NativeButton
+ * ...
+ * + Label
+ * + NativeButton
+ * </pre>
+ */
+ public static final String COMPOUND_LAYOUT_SIDEBAR_MENU = "sidebar-menu";
+
+ /**
+ * Adds a toolbar-like background for the layout, and aligns Buttons and
+ * Segments horizontally. Feel free to use different buttons styles inside
+ * the toolbar, like {@link #BUTTON_ICON_ON_TOP} and
+ * {@link #BUTTON_BORDERLESS}
+ */
+ public static final String COMPOUND_CSSLAYOUT_TOOLBAR = "toolbar";
+}
diff --git a/server/src/com/vaadin/ui/themes/LiferayTheme.java b/server/src/com/vaadin/ui/themes/LiferayTheme.java
new file mode 100644
index 0000000000..9b48306ac2
--- /dev/null
+++ b/server/src/com/vaadin/ui/themes/LiferayTheme.java
@@ -0,0 +1,31 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui.themes;
+
+public class LiferayTheme extends BaseTheme {
+
+ public static final String THEME_NAME = "liferay";
+
+ /***************************************************************************
+ *
+ * Panel styles
+ *
+ **************************************************************************/
+
+ /**
+ * Removes borders and background from the panel
+ */
+ public static final String PANEL_LIGHT = "light";
+
+ /***************************************************************************
+ *
+ * SplitPanel styles
+ *
+ **************************************************************************/
+
+ /**
+ * Reduces the split handle to a minimal size (1 pixel)
+ */
+ public static final String SPLITPANEL_SMALL = "small";
+}
diff --git a/server/src/com/vaadin/ui/themes/Reindeer.java b/server/src/com/vaadin/ui/themes/Reindeer.java
new file mode 100644
index 0000000000..7aaae8faa2
--- /dev/null
+++ b/server/src/com/vaadin/ui/themes/Reindeer.java
@@ -0,0 +1,217 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui.themes;
+
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.FormLayout;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.HorizontalSplitPanel;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.VerticalSplitPanel;
+
+public class Reindeer extends BaseTheme {
+
+ public static final String THEME_NAME = "reindeer";
+
+ /***************************************************************************
+ *
+ * Label styles
+ *
+ **************************************************************************/
+
+ /**
+ * Large font for main application headings
+ */
+ public static final String LABEL_H1 = "h1";
+
+ /**
+ * Large font for different sections in the application
+ */
+ public static final String LABEL_H2 = "h2";
+
+ /**
+ * Small and a little lighter font
+ */
+ public static final String LABEL_SMALL = "light";
+
+ /**
+ * @deprecated Use {@link #LABEL_SMALL} instead.
+ */
+ @Deprecated
+ public static final String LABEL_LIGHT = "small";
+
+ /***************************************************************************
+ *
+ * Button styles
+ *
+ **************************************************************************/
+
+ /**
+ * Default action style for buttons (the button that should get activated
+ * when the user presses 'enter' in a form). Use sparingly, only one default
+ * button per view should be visible.
+ */
+ public static final String BUTTON_DEFAULT = "primary";
+
+ /**
+ * @deprecated Use {@link #BUTTON_DEFAULT} instead
+ */
+ @Deprecated
+ public static final String BUTTON_PRIMARY = BUTTON_DEFAULT;
+
+ /**
+ * Small sized button, use for context specific actions for example
+ */
+ public static final String BUTTON_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * TextField styles
+ *
+ **************************************************************************/
+
+ /**
+ * Small sized text field with small font
+ */
+ public static final String TEXTFIELD_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * Panel styles
+ *
+ **************************************************************************/
+
+ /**
+ * Removes borders and background color from the panel
+ */
+ public static final String PANEL_LIGHT = "light";
+
+ /***************************************************************************
+ *
+ * SplitPanel styles
+ *
+ **************************************************************************/
+
+ /**
+ * Reduces the split handle to a minimal size (1 pixel)
+ */
+ public static final String SPLITPANEL_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * TabSheet styles
+ *
+ **************************************************************************/
+
+ /**
+ * Removes borders from the default tab sheet style.
+ */
+ public static final String TABSHEET_BORDERLESS = "borderless";
+
+ /**
+ * Removes borders and background color from the tab sheet, and shows the
+ * tabs as a small bar.
+ */
+ public static final String TABSHEET_SMALL = "bar";
+
+ /**
+ * @deprecated Use {@link #TABSHEET_SMALL} instead.
+ */
+ @Deprecated
+ public static final String TABSHEET_BAR = TABSHEET_SMALL;
+
+ /**
+ * Removes borders and background color from the tab sheet. The tabs are
+ * presented with minimal lines indicating the selected tab.
+ */
+ public static final String TABSHEET_MINIMAL = "minimal";
+
+ /**
+ * Makes the tab close buttons visible only when the user is hovering over
+ * the tab.
+ */
+ public static final String TABSHEET_HOVER_CLOSABLE = "hover-closable";
+
+ /**
+ * Makes the tab close buttons visible only when the tab is selected.
+ */
+ public static final String TABSHEET_SELECTED_CLOSABLE = "selected-closable";
+
+ /***************************************************************************
+ *
+ * Table styles
+ *
+ **************************************************************************/
+
+ /**
+ * Removes borders from the table
+ */
+ public static final String TABLE_BORDERLESS = "borderless";
+
+ /**
+ * Makes the table headers dark and more prominent.
+ */
+ public static final String TABLE_STRONG = "strong";
+
+ /***************************************************************************
+ *
+ * Layout styles
+ *
+ **************************************************************************/
+
+ /**
+ * Changes the background of a layout to white. Applies to
+ * {@link VerticalLayout}, {@link HorizontalLayout}, {@link GridLayout},
+ * {@link FormLayout}, {@link CssLayout}, {@link VerticalSplitPanel} and
+ * {@link HorizontalSplitPanel}.
+ * <p>
+ * <em>Does not revert any contained components back to normal if some
+ * parent layout has style {@link #LAYOUT_BLACK} applied.</em>
+ */
+ public static final String LAYOUT_WHITE = "white";
+
+ /**
+ * Changes the background of a layout to a shade of blue. Applies to
+ * {@link VerticalLayout}, {@link HorizontalLayout}, {@link GridLayout},
+ * {@link FormLayout}, {@link CssLayout}, {@link VerticalSplitPanel} and
+ * {@link HorizontalSplitPanel}.
+ * <p>
+ * <em>Does not revert any contained components back to normal if some
+ * parent layout has style {@link #LAYOUT_BLACK} applied.</em>
+ */
+ public static final String LAYOUT_BLUE = "blue";
+
+ /**
+ * <p>
+ * Changes the background of a layout to almost black, and at the same time
+ * transforms contained components to their black style correspondents when
+ * available. At least texts, buttons, text fields, selects, date fields,
+ * tables and a few other component styles should change.
+ * </p>
+ * <p>
+ * Applies to {@link VerticalLayout}, {@link HorizontalLayout},
+ * {@link GridLayout}, {@link FormLayout} and {@link CssLayout}.
+ * </p>
+ *
+ */
+ public static final String LAYOUT_BLACK = "black";
+
+ /***************************************************************************
+ *
+ * Window styles
+ *
+ **************************************************************************/
+
+ /**
+ * Makes the whole window white and increases the font size of the title.
+ */
+ public static final String WINDOW_LIGHT = "light";
+
+ /**
+ * Makes the whole window black, and changes contained components in the
+ * same way as {@link #LAYOUT_BLACK} does.
+ */
+ public static final String WINDOW_BLACK = "black";
+}
diff --git a/server/src/com/vaadin/ui/themes/Runo.java b/server/src/com/vaadin/ui/themes/Runo.java
new file mode 100644
index 0000000000..28a19e8dcd
--- /dev/null
+++ b/server/src/com/vaadin/ui/themes/Runo.java
@@ -0,0 +1,183 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.ui.themes;
+
+public class Runo extends BaseTheme {
+
+ public static final String THEME_NAME = "runo";
+
+ public static String themeName() {
+ return THEME_NAME.toLowerCase();
+ }
+
+ /***************************************************************************
+ *
+ * Button styles
+ *
+ **************************************************************************/
+
+ /**
+ * Small sized button, use for context specific actions for example
+ */
+ public static final String BUTTON_SMALL = "small";
+
+ /**
+ * Big sized button, use to gather much attention for some particular action
+ */
+ public static final String BUTTON_BIG = "big";
+
+ /**
+ * Default action style for buttons (the button that should get activated
+ * when the user presses 'enter' in a form). Use sparingly, only one default
+ * button per view should be visible.
+ */
+ public static final String BUTTON_DEFAULT = "default";
+
+ /***************************************************************************
+ *
+ * Panel styles
+ *
+ **************************************************************************/
+
+ /**
+ * Removes borders and background color from the panel
+ */
+ public static final String PANEL_LIGHT = "light";
+
+ /***************************************************************************
+ *
+ * TabSheet styles
+ *
+ **************************************************************************/
+
+ /**
+ * Smaller tabs, no border and background for content area
+ */
+ public static final String TABSHEET_SMALL = "light";
+
+ /***************************************************************************
+ *
+ * SplitPanel styles
+ *
+ **************************************************************************/
+
+ /**
+ * Reduces the width/height of the split handle. Useful when you don't want
+ * the split handle to touch the sides of the containing layout.
+ */
+ public static final String SPLITPANEL_REDUCED = "rounded";
+
+ /**
+ * Reduces the visual size of the split handle to one pixel (the active drag
+ * size is still larger).
+ */
+ public static final String SPLITPANEL_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * Label styles
+ *
+ **************************************************************************/
+
+ /**
+ * Largest title/header size. Use for main sections in your application.
+ */
+ public static final String LABEL_H1 = "h1";
+
+ /**
+ * Similar style as in panel captions. Useful for sub-sections within a
+ * view.
+ */
+ public static final String LABEL_H2 = "h2";
+
+ /**
+ * Small font size. Useful for contextual help texts and similar less
+ * frequently needed information. Use with modesty, since this style will be
+ * more harder to read due to its smaller size and contrast.
+ */
+ public static final String LABEL_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * Layout styles
+ *
+ **************************************************************************/
+
+ /**
+ * An alternative background color for layouts. Use on top of white
+ * background (e.g. inside Panels, TabSheets and sub-windows).
+ */
+ public static final String LAYOUT_DARKER = "darker";
+
+ /**
+ * Add a drop shadow around the layout and its contained components.
+ * Produces a rectangular shadow, even if the contained component would have
+ * a different shape.
+ * <p>
+ * Note: does not work in Internet Explorer 6
+ */
+ public static final String CSSLAYOUT_SHADOW = "box-shadow";
+
+ /**
+ * Adds necessary styles to the layout to make it look selectable (i.e.
+ * clickable). Add a click listener for the layout, and toggle the
+ * {@link #CSSLAYOUT_SELECTABLE_SELECTED} style for the same layout to make
+ * it look selected or not.
+ */
+ public static final String CSSLAYOUT_SELECTABLE = "selectable";
+ public static final String CSSLAYOUT_SELECTABLE_SELECTED = "selectable-selected";
+
+ /***************************************************************************
+ *
+ * TextField styles
+ *
+ **************************************************************************/
+
+ /**
+ * Small sized text field with small font
+ */
+ public static final String TEXTFIELD_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * Table styles
+ *
+ **************************************************************************/
+
+ /**
+ * Smaller header and item fonts.
+ */
+ public static final String TABLE_SMALL = "small";
+
+ /**
+ * Removes the border and background color from the table. Removes
+ * alternating row background colors as well.
+ */
+ public static final String TABLE_BORDERLESS = "borderless";
+
+ /***************************************************************************
+ *
+ * Accordion styles
+ *
+ **************************************************************************/
+
+ /**
+ * A detached looking accordion, providing space around its captions and
+ * content. Doesn't necessarily need a Panel or other container to wrap it
+ * in order to make it look right.
+ */
+ public static final String ACCORDION_LIGHT = "light";
+
+ /***************************************************************************
+ *
+ * Window styles
+ *
+ **************************************************************************/
+
+ /**
+ * Smaller header and a darker background color for the window. Useful for
+ * smaller dialog-like windows.
+ */
+ public static final String WINDOW_DIALOG = "dialog";
+}