summaryrefslogtreecommitdiffstats
path: root/src/com/vaadin/ui
diff options
context:
space:
mode:
authorHenri Sara <henri.sara@itmill.com>2009-05-11 09:19:03 +0000
committerHenri Sara <henri.sara@itmill.com>2009-05-11 09:19:03 +0000
commitadc8c0ad3573272c236040c3a76005b9e73a5737 (patch)
treea3860704dbd5b82dc6af38684b80f8ef79a32722 /src/com/vaadin/ui
parent5abc870dda584d0c2fc47fd5eec4ae3de3fa240e (diff)
downloadvaadin-framework-adc8c0ad3573272c236040c3a76005b9e73a5737.tar.gz
vaadin-framework-adc8c0ad3573272c236040c3a76005b9e73a5737.zip
#2904: initial bulk rename "com.itmill.toolkit" -> "com.vaadin"
- com.itmill.toolkit.external not yet fully renamed svn changeset:7715/svn branch:6.0
Diffstat (limited to 'src/com/vaadin/ui')
-rw-r--r--src/com/vaadin/ui/AbsoluteLayout.java357
-rw-r--r--src/com/vaadin/ui/AbstractComponent.java1296
-rw-r--r--src/com/vaadin/ui/AbstractComponentContainer.java274
-rw-r--r--src/com/vaadin/ui/AbstractField.java1157
-rw-r--r--src/com/vaadin/ui/AbstractLayout.java92
-rw-r--r--src/com/vaadin/ui/AbstractOrderedLayout.java366
-rw-r--r--src/com/vaadin/ui/AbstractSelect.java1679
-rw-r--r--src/com/vaadin/ui/Accordion.java11
-rw-r--r--src/com/vaadin/ui/Alignment.java155
-rw-r--r--src/com/vaadin/ui/AlignmentUtils.java145
-rw-r--r--src/com/vaadin/ui/BaseFieldFactory.java151
-rw-r--r--src/com/vaadin/ui/Button.java350
-rw-r--r--src/com/vaadin/ui/CheckBox.java103
-rw-r--r--src/com/vaadin/ui/ComboBox.java86
-rw-r--r--src/com/vaadin/ui/Component.java437
-rw-r--r--src/com/vaadin/ui/ComponentContainer.java232
-rw-r--r--src/com/vaadin/ui/CustomComponent.java225
-rw-r--r--src/com/vaadin/ui/CustomLayout.java310
-rw-r--r--src/com/vaadin/ui/DateField.java527
-rw-r--r--src/com/vaadin/ui/Embedded.java425
-rw-r--r--src/com/vaadin/ui/ExpandLayout.java101
-rw-r--r--src/com/vaadin/ui/Field.java105
-rw-r--r--src/com/vaadin/ui/FieldFactory.java76
-rw-r--r--src/com/vaadin/ui/Form.java1179
-rw-r--r--src/com/vaadin/ui/FormLayout.java36
-rw-r--r--src/com/vaadin/ui/GridLayout.java1313
-rw-r--r--src/com/vaadin/ui/HorizontalLayout.java26
-rw-r--r--src/com/vaadin/ui/InlineDateField.java52
-rw-r--r--src/com/vaadin/ui/Label.java546
-rw-r--r--src/com/vaadin/ui/Layout.java238
-rw-r--r--src/com/vaadin/ui/Link.java243
-rw-r--r--src/com/vaadin/ui/ListSelect.java96
-rw-r--r--src/com/vaadin/ui/LoginForm.java260
-rw-r--r--src/com/vaadin/ui/MenuBar.java617
-rw-r--r--src/com/vaadin/ui/NativeSelect.java91
-rw-r--r--src/com/vaadin/ui/OptionGroup.java41
-rw-r--r--src/com/vaadin/ui/OrderedLayout.java122
-rw-r--r--src/com/vaadin/ui/Panel.java555
-rw-r--r--src/com/vaadin/ui/PopupDateField.java52
-rw-r--r--src/com/vaadin/ui/PopupView.java432
-rw-r--r--src/com/vaadin/ui/ProgressIndicator.java267
-rw-r--r--src/com/vaadin/ui/RichTextArea.java35
-rw-r--r--src/com/vaadin/ui/Select.java476
-rw-r--r--src/com/vaadin/ui/Slider.java503
-rw-r--r--src/com/vaadin/ui/SplitPanel.java330
-rw-r--r--src/com/vaadin/ui/TabSheet.java790
-rw-r--r--src/com/vaadin/ui/Table.java3222
-rw-r--r--src/com/vaadin/ui/TextField.java556
-rw-r--r--src/com/vaadin/ui/Tree.java1048
-rw-r--r--src/com/vaadin/ui/TwinColSelect.java115
-rw-r--r--src/com/vaadin/ui/Upload.java985
-rw-r--r--src/com/vaadin/ui/UriFragmentUtility.java153
-rw-r--r--src/com/vaadin/ui/VerticalLayout.java27
-rw-r--r--src/com/vaadin/ui/Window.java1627
-rw-r--r--src/com/vaadin/ui/doc-files/component_class_hierarchy.gifbin0 -> 11077 bytes
-rw-r--r--src/com/vaadin/ui/doc-files/component_interfaces.gifbin0 -> 2272 bytes
-rw-r--r--src/com/vaadin/ui/package.html72
57 files changed, 24765 insertions, 0 deletions
diff --git a/src/com/vaadin/ui/AbsoluteLayout.java b/src/com/vaadin/ui/AbsoluteLayout.java
new file mode 100644
index 0000000000..cf3f62eee4
--- /dev/null
+++ b/src/com/vaadin/ui/AbsoluteLayout.java
@@ -0,0 +1,357 @@
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+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.gwt.client.ui.IAbsoluteLayout;
+
+/**
+ * AbsoluteLayout is a layout implementation that mimics html absolute
+ * positioning.
+ *
+ */
+@SuppressWarnings("serial")
+public class AbsoluteLayout extends AbstractLayout {
+
+ private Collection<Component> components = new LinkedHashSet<Component>();
+ private Map<Component, ComponentPosition> componentToCoordinates = new HashMap<Component, ComponentPosition>();
+
+ public AbsoluteLayout() {
+ setSizeFull();
+ }
+
+ @Override
+ public String getTag() {
+ return IAbsoluteLayout.TAGNAME;
+ }
+
+ public Iterator<Component> getComponentIterator() {
+ return components.iterator();
+ }
+
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ ComponentPosition position = getPosition(oldComponent);
+ removeComponent(oldComponent);
+ addComponent(newComponent);
+ componentToCoordinates.put(newComponent, position);
+ }
+
+ @Override
+ public void addComponent(Component c) {
+ components.add(c);
+ super.addComponent(c);
+ }
+
+ @Override
+ public void removeComponent(Component c) {
+ components.remove(c);
+ super.removeComponent(c);
+ requestRepaint();
+ }
+
+ public void addComponent(Component c, String cssPosition) {
+ addComponent(c);
+ getPosition(c).setCSSString(cssPosition);
+ }
+
+ public ComponentPosition getPosition(Component component) {
+ if (componentToCoordinates.containsKey(component)) {
+ return componentToCoordinates.get(component);
+ } else {
+ ComponentPosition coords = new ComponentPosition();
+ componentToCoordinates.put(component, coords);
+ return coords;
+ }
+ }
+
+ /**
+ * TODO symmetric getters and setters for fields to make this simpler to use
+ * in generic java tools
+ *
+ */
+ public class ComponentPosition implements Serializable {
+
+ private int zIndex = -1;
+ private float topValue = -1;
+ private float rightValue = -1;
+ private float bottomValue = -1;
+ private float leftValue = -1;
+
+ private int topUnits;
+ private int rightUnits;
+ private int bottomUnits;
+ private int leftUnits;
+
+ /**
+ * Sets the position attributes using CSS syntax. Example usage:
+ *
+ * <code><pre>
+ * setCSSString("top:10px;left:20%;z-index:16;");
+ * </pre></code>
+ *
+ * @param css
+ */
+ public void setCSSString(String css) {
+ 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]);
+ } else {
+ String value;
+ if (keyValuePair.length > 1) {
+ value = keyValuePair[1].trim();
+ } else {
+ value = "";
+ }
+ String unit = value.replaceAll("[0-9\\.]+", "");
+ if (!unit.equals("")) {
+ value = value.substring(0, value.indexOf(unit)).trim();
+ }
+ float v = Float.parseFloat(value);
+ int unitInt = parseCssUnit(unit);
+ if (key.equals("top")) {
+ topValue = v;
+ topUnits = unitInt;
+ } else if (key.equals("right")) {
+ rightValue = v;
+ rightUnits = unitInt;
+ } else if (key.equals("bottom")) {
+ bottomValue = v;
+ bottomUnits = unitInt;
+ } else if (key.equals("left")) {
+ leftValue = v;
+ leftUnits = unitInt;
+ }
+ }
+ }
+ requestRepaint();
+ }
+
+ private int parseCssUnit(String string) {
+ for (int i = 0; i < UNIT_SYMBOLS.length; i++) {
+ if (UNIT_SYMBOLS[i].equals(string)) {
+ return i;
+ }
+ }
+ return 0; // defaults to px (eg. top:0;)
+ }
+
+ public String getCSSString() {
+ String s = "";
+ if (topValue >= 0) {
+ s += "top:" + topValue + UNIT_SYMBOLS[topUnits] + ";";
+ }
+ if (rightValue >= 0) {
+ s += "right:" + rightValue + UNIT_SYMBOLS[rightUnits] + ";";
+ }
+ if (bottomValue >= 0) {
+ s += "bottom:" + bottomValue + UNIT_SYMBOLS[bottomUnits] + ";";
+ }
+ if (leftValue >= 0) {
+ s += "left:" + leftValue + UNIT_SYMBOLS[leftUnits] + ";";
+ }
+ if (zIndex >= 0) {
+ s += "z-index:" + zIndex + ";";
+ }
+ return s;
+ }
+
+ public void setTop(float topValue, int topUnits) {
+ validateLength(topValue, topUnits);
+ this.topValue = topValue;
+ this.topUnits = topUnits;
+ requestRepaint();
+ }
+
+ public void setRight(float rightValue, int rightUnits) {
+ validateLength(rightValue, rightUnits);
+ this.rightValue = rightValue;
+ this.rightUnits = rightUnits;
+ requestRepaint();
+ }
+
+ public void setBottom(float bottomValue, int units) {
+ validateLength(bottomValue, units);
+ this.bottomValue = bottomValue;
+ bottomUnits = units;
+ requestRepaint();
+ }
+
+ public void setLeft(float leftValue, int units) {
+ validateLength(leftValue, units);
+ this.leftValue = leftValue;
+ leftUnits = units;
+ requestRepaint();
+ }
+
+ public void setZIndex(int zIndex) {
+ this.zIndex = zIndex;
+ requestRepaint();
+ }
+
+ public void setTopValue(float topValue) {
+ validateLength(topValue, topUnits);
+ this.topValue = topValue;
+ requestRepaint();
+ }
+
+ public float getTopValue() {
+ return topValue;
+ }
+
+ /**
+ * @return the rightValue
+ */
+ public float getRightValue() {
+ return rightValue;
+ }
+
+ /**
+ * @param rightValue
+ * the rightValue to set
+ */
+ public void setRightValue(float rightValue) {
+ validateLength(rightValue, rightUnits);
+ this.rightValue = rightValue;
+ requestRepaint();
+ }
+
+ /**
+ * @return the bottomValue
+ */
+ public float getBottomValue() {
+ return bottomValue;
+ }
+
+ /**
+ * @param bottomValue
+ * the bottomValue to set
+ */
+ public void setBottomValue(float bottomValue) {
+ validateLength(bottomValue, bottomUnits);
+ this.bottomValue = bottomValue;
+ requestRepaint();
+ }
+
+ /**
+ * @return the leftValue
+ */
+ public float getLeftValue() {
+ return leftValue;
+ }
+
+ /**
+ * @param leftValue
+ * the leftValue to set
+ */
+ public void setLeftValue(float leftValue) {
+ validateLength(leftValue, leftUnits);
+ this.leftValue = leftValue;
+ requestRepaint();
+ }
+
+ /**
+ * @return the topUnits
+ */
+ public int getTopUnits() {
+ return topUnits;
+ }
+
+ /**
+ * @param topUnits
+ * the topUnits to set
+ */
+ public void setTopUnits(int topUnits) {
+ validateLength(topValue, topUnits);
+ this.topUnits = topUnits;
+ requestRepaint();
+ }
+
+ /**
+ * @return the rightUnits
+ */
+ public int getRightUnits() {
+ return rightUnits;
+ }
+
+ /**
+ * @param rightUnits
+ * the rightUnits to set
+ */
+ public void setRightUnits(int rightUnits) {
+ validateLength(rightValue, rightUnits);
+ this.rightUnits = rightUnits;
+ requestRepaint();
+ }
+
+ /**
+ * @return the bottomUnits
+ */
+ public int getBottomUnits() {
+ return bottomUnits;
+ }
+
+ /**
+ * @param bottomUnits
+ * the bottomUnits to set
+ */
+ public void setBottomUnits(int bottomUnits) {
+ validateLength(bottomValue, bottomUnits);
+ this.bottomUnits = bottomUnits;
+ requestRepaint();
+ }
+
+ /**
+ * @return the leftUnits
+ */
+ public int getLeftUnits() {
+ return leftUnits;
+ }
+
+ /**
+ * @param leftUnits
+ * the leftUnits to set
+ */
+ public void setLeftUnits(int leftUnits) {
+ validateLength(leftValue, leftUnits);
+ this.leftUnits = leftUnits;
+ requestRepaint();
+ }
+
+ /**
+ * @return the zIndex
+ */
+ public int getZIndex() {
+ return zIndex;
+ }
+
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ for (Component component : components) {
+ target.startTag("cc");
+ target.addAttribute("css", getPosition(component).getCSSString());
+ component.paint(target);
+ target.endTag("cc");
+ }
+ }
+
+ private static void validateLength(float topValue, int topUnits2) {
+ // TODO throw on invalid value
+
+ }
+
+}
diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java
new file mode 100644
index 0000000000..b26278b016
--- /dev/null
+++ b/src/com/vaadin/ui/AbstractComponent.java
@@ -0,0 +1,1296 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.vaadin.Application;
+import com.vaadin.event.EventRouter;
+import com.vaadin.event.MethodEventSource;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Terminal;
+import com.vaadin.terminal.gwt.server.ComponentSizeValidator;
+
+/**
+ * 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 a IT Mill
+ * Toolkit component. Most components in the toolkit do just that.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractComponent implements Component, MethodEventSource {
+
+ /* Private members */
+
+ /**
+ * Style names.
+ */
+ private ArrayList styles;
+
+ /**
+ * Caption text.
+ */
+ private String caption;
+
+ /**
+ * Application specific data object. The component does not use or modify
+ * this.
+ */
+ private Object applicationData;
+
+ /**
+ * Icon to be shown together with caption.
+ */
+ private Resource icon;
+
+ /**
+ * Is the component enabled (its normal usage is allowed).
+ */
+ private boolean enabled = true;
+
+ /**
+ * Is the component visible (it is rendered).
+ */
+ private boolean visible = true;
+
+ /**
+ * Is the component read-only ?
+ */
+ private boolean readOnly = false;
+
+ /**
+ * Description of the usage (XML).
+ */
+ private String description = null;
+
+ /**
+ * The container this component resides in.
+ */
+ private Component parent = null;
+
+ /**
+ * The EventRouter used for the event model.
+ */
+ private EventRouter eventRouter = null;
+
+ /**
+ * The internal error message of the component.
+ */
+ private ErrorMessage componentError = null;
+
+ /**
+ * Immediate mode: if true, all variable changes are required to be sent
+ * from the terminal immediately.
+ */
+ private boolean immediate = false;
+
+ /**
+ * Locale of this component.
+ */
+ private Locale locale;
+
+ /**
+ * List of repaint request listeners or null if not listened at all.
+ */
+ private LinkedList repaintRequestListeners = null;
+
+ /**
+ * Are all the repaint listeners notified about recent changes ?
+ */
+ private boolean repaintRequestListenersNotified = false;
+
+ private String testingId;
+
+ /* Sizeable fields */
+
+ private float width = SIZE_UNDEFINED;
+ private float height = SIZE_UNDEFINED;
+ private int widthUnit = UNITS_PIXELS;
+ private int heightUnit = UNITS_PIXELS;
+ private static final Pattern sizePattern = Pattern
+ .compile("^(-?\\d+(\\.\\d+)?)(%|px|em|ex|in|cm|mm|pt|pc)?$");
+
+ private ComponentErrorHandler errorHandler = null;
+
+ /* Constructor */
+
+ /**
+ * Constructs a new Component.
+ */
+ public AbstractComponent() {
+ // ComponentSizeValidator.setCreationLocation(this);
+ }
+
+ /* Get/Set component properties */
+
+ /**
+ * Gets the UIDL tag corresponding to the component.
+ *
+ * @return the component's UIDL tag as <code>String</code>
+ */
+ public abstract String getTag();
+
+ public void setDebugId(String id) {
+ testingId = id;
+ }
+
+ public String getDebugId() {
+ return testingId;
+ }
+
+ /**
+ * 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 com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * 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.
+ */
+ public String getStyleName() {
+ String s = "";
+ if (styles != null) {
+ for (final Iterator it = styles.iterator(); it.hasNext();) {
+ s += (String) 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.
+ */
+ public void setStyleName(String style) {
+ if (style == null || "".equals(style)) {
+ styles = null;
+ requestRepaint();
+ return;
+ }
+ if (styles == null) {
+ styles = new ArrayList();
+ }
+ styles.clear();
+ styles.add(style);
+ requestRepaint();
+ }
+
+ public void addStyleName(String style) {
+ if (style == null || "".equals(style)) {
+ return;
+ }
+ if (styles == null) {
+ styles = new ArrayList();
+ }
+ if (!styles.contains(style)) {
+ styles.add(style);
+ requestRepaint();
+ }
+ }
+
+ public void removeStyleName(String style) {
+ if (styles != null) {
+ styles.remove(style);
+ requestRepaint();
+ }
+ }
+
+ /*
+ * Get's the component's caption. Don't add a JavaDoc comment here, we use
+ * the default documentation from implemented interface.
+ */
+ public String getCaption() {
+ return caption;
+ }
+
+ /**
+ * Sets the component's caption <code>String</code>. Caption is the visible
+ * name of the component. This method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param caption
+ * the new caption <code>String</code> for the component.
+ */
+ public void setCaption(String caption) {
+ this.caption = caption;
+ requestRepaint();
+ }
+
+ /*
+ * Don't add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ public Locale getLocale() {
+ if (locale != null) {
+ return locale;
+ }
+ if (parent != null) {
+ return parent.getLocale();
+ }
+ final Application app = getApplication();
+ if (app != null) {
+ return app.getLocale();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the locale of this component.
+ *
+ * @param locale
+ * the locale to become this component's locale.
+ */
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ /*
+ * Gets the component's icon resource. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ public Resource getIcon() {
+ return icon;
+ }
+
+ /**
+ * Sets the component's icon. This method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param icon
+ * the icon to be shown with the component's caption.
+ */
+ public void setIcon(Resource icon) {
+ this.icon = icon;
+ requestRepaint();
+ }
+
+ /*
+ * Tests if the component is enabled or not. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ public boolean isEnabled() {
+ return enabled && (parent == null || parent.isEnabled()) && isVisible();
+ }
+
+ /*
+ * Enables or disables the component. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ public void setEnabled(boolean enabled) {
+ if (this.enabled != enabled) {
+ boolean wasEnabled = isEnabled();
+ this.enabled = enabled;
+ // don't repaint if ancestor is disabled
+ if (wasEnabled != isEnabled()) {
+ requestRepaint();
+ }
+ }
+ }
+
+ /*
+ * 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 immediate;
+ }
+
+ /**
+ * Sets the component's immediate mode to the specified status. This method
+ * will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * 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) {
+ this.immediate = immediate;
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#isVisible()
+ */
+ public boolean isVisible() {
+ return visible && (getParent() == null || getParent().isVisible());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component#setVisible(boolean)
+ */
+ public void setVisible(boolean visible) {
+
+ if (this.visible != visible) {
+ this.visible = visible;
+ // Instead of requesting repaint normally we
+ // fire the event directly to assure that the
+ // event goes through event in the component might
+ // now be invisible
+ fireRequestRepaintEvent(null);
+ }
+ }
+
+ /**
+ * <p>
+ * Gets the component's description. 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 description;
+ }
+
+ /**
+ * Sets the component's description. See {@link #getDescription()} for more
+ * information on what the description is. This method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param description
+ * the new description string for the component.
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ requestRepaint();
+ }
+
+ /*
+ * Gets the component's parent component. Don't add a JavaDoc comment here,
+ * we use the default documentation from implemented interface.
+ */
+ public Component getParent() {
+ return parent;
+ }
+
+ /*
+ * Sets the parent component. Don't add a JavaDoc comment here, we use the
+ * default documentation from implemented interface.
+ */
+ public void setParent(Component parent) {
+
+ // If the parent is not changed, don't do anything
+ if (parent == this.parent) {
+ return;
+ }
+
+ if (parent != null && this.parent != null) {
+ throw new IllegalStateException("Component already has a parent.");
+ }
+
+ // Send detach event if the component have been connected to a window
+ if (getApplication() != null) {
+ detach();
+ }
+
+ // Connect to new parent
+ this.parent = parent;
+
+ // Send attach event if connected to a window
+ if (getApplication() != null) {
+ attach();
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ /*
+ * Sets the component's read-only mode. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ requestRepaint();
+ }
+
+ /*
+ * Gets the parent window of the component. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ public Window getWindow() {
+ if (parent == null) {
+ return null;
+ } else {
+ return parent.getWindow();
+ }
+ }
+
+ /*
+ * 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.
+ */
+ public void attach() {
+ requestRepaint();
+ }
+
+ /*
+ * Detach the component from application. Don't add a JavaDoc comment here,
+ * we use the default documentation from implemented interface.
+ */
+ public void detach() {
+ }
+
+ /*
+ * Gets the parent application of the component. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ public Application getApplication() {
+ if (parent == null) {
+ return null;
+ } else {
+ return parent.getApplication();
+ }
+ }
+
+ /* Component painting */
+
+ /* Documented in super interface */
+ public void requestRepaintRequests() {
+ repaintRequestListenersNotified = false;
+ }
+
+ /*
+ * Paints the component into a UIDL stream. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ public final void paint(PaintTarget target) throws PaintException {
+ if (!target.startTag(this, getTag()) || repaintRequestListenersNotified) {
+
+ // Paint the contents of the component
+
+ // Only paint content of visible components.
+ if (isVisible()) {
+ if (getHeight() >= 0
+ && (getHeightUnits() != UNITS_PERCENTAGE || ComponentSizeValidator
+ .parentCanDefineHeight(this))) {
+ target.addAttribute("height", "" + getCSSHeight());
+ }
+
+ if (getWidth() >= 0
+ && (getWidthUnits() != UNITS_PERCENTAGE || ComponentSizeValidator
+ .parentCanDefineWidth(this))) {
+ target.addAttribute("width", "" + getCSSWidth());
+ }
+ if (styles != null && styles.size() > 0) {
+ target.addAttribute("style", getStyle());
+ }
+ if (isReadOnly()) {
+ target.addAttribute("readonly", true);
+ }
+
+ if (isImmediate()) {
+ target.addAttribute("immediate", true);
+ }
+ if (!isEnabled()) {
+ target.addAttribute("disabled", true);
+ }
+ if (getCaption() != null) {
+ target.addAttribute("caption", getCaption());
+ }
+ if (getIcon() != null) {
+ target.addAttribute("icon", getIcon());
+ }
+
+ if (getDescription() != null && getDescription().length() > 0) {
+ target.addAttribute("description", getDescription());
+ }
+
+ paintContent(target);
+
+ final ErrorMessage error = getErrorMessage();
+ if (error != null) {
+ error.paint(target);
+ }
+ } else {
+ target.addAttribute("invisible", true);
+ }
+ } else {
+
+ // Contents have not changed, only cached presentation can be used
+ target.addAttribute("cached", true);
+ }
+ target.endTag(getTag());
+
+ repaintRequestListenersNotified = false;
+ }
+
+ /**
+ * Build CSS compatible string representation of height.
+ *
+ * @return CSS height
+ */
+ private String getCSSHeight() {
+ if (getHeightUnits() == UNITS_PIXELS) {
+ return ((int) getHeight()) + UNIT_SYMBOLS[getHeightUnits()];
+ } else {
+ return getHeight() + UNIT_SYMBOLS[getHeightUnits()];
+ }
+ }
+
+ /**
+ * Build CSS compatible string representation of width.
+ *
+ * @return CSS width
+ */
+ private String getCSSWidth() {
+ if (getWidthUnits() == UNITS_PIXELS) {
+ return ((int) getWidth()) + UNIT_SYMBOLS[getWidthUnits()];
+ } else {
+ return getWidth() + UNIT_SYMBOLS[getWidthUnits()];
+ }
+ }
+
+ /**
+ * Paints any needed component-specific things to the given UIDL stream. The
+ * more general {@link #paint(PaintTarget)} method handles all general
+ * attributes common to all components, and it calls this method to paint
+ * any component-specific attributes to the UIDL stream.
+ *
+ * @param target
+ * the target UIDL stream where the component should paint itself
+ * to
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ }
+
+ /* Documentation copied from interface */
+ public void requestRepaint() {
+
+ // The effect of the repaint request is identical to case where a
+ // child requests repaint
+ childRequestedRepaint(null);
+ }
+
+ /* Documentation copied from interface */
+ public void childRequestedRepaint(Collection alreadyNotified) {
+ // Invisible components (by flag in this particular component) do not
+ // need repaints
+ if (!visible) {
+ return;
+ }
+
+ fireRequestRepaintEvent(alreadyNotified);
+ }
+
+ /**
+ * Fires the repaint request event.
+ *
+ * @param alreadyNotified
+ */
+ private void fireRequestRepaintEvent(Collection alreadyNotified) {
+ // Notify listeners only once
+ if (!repaintRequestListenersNotified) {
+ // Notify the listeners
+ if (repaintRequestListeners != null
+ && !repaintRequestListeners.isEmpty()) {
+ final Object[] listeners = repaintRequestListeners.toArray();
+ final RepaintRequestEvent event = new RepaintRequestEvent(this);
+ for (int i = 0; i < listeners.length; i++) {
+ if (alreadyNotified == null) {
+ alreadyNotified = new LinkedList();
+ }
+ if (!alreadyNotified.contains(listeners[i])) {
+ ((RepaintRequestListener) listeners[i])
+ .repaintRequested(event);
+ alreadyNotified.add(listeners[i]);
+ repaintRequestListenersNotified = true;
+ }
+ }
+ }
+
+ // Notify the parent
+ final Component parent = getParent();
+ if (parent != null) {
+ parent.childRequestedRepaint(alreadyNotified);
+ }
+ }
+ }
+
+ /* Documentation copied from interface */
+ public void addListener(RepaintRequestListener listener) {
+ if (repaintRequestListeners == null) {
+ repaintRequestListeners = new LinkedList();
+ }
+ if (!repaintRequestListeners.contains(listener)) {
+ repaintRequestListeners.add(listener);
+ }
+ }
+
+ /* Documentation copied from interface */
+ public void removeListener(RepaintRequestListener listener) {
+ if (repaintRequestListeners != null) {
+ repaintRequestListeners.remove(listener);
+ if (repaintRequestListeners.isEmpty()) {
+ repaintRequestListeners = null;
+ }
+ }
+ }
+
+ /* Component variable changes */
+
+ /*
+ * Invoked when the value of a variable has changed. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public void changeVariables(Object source, Map variables) {
+ }
+
+ /* General event framework */
+
+ private static final Method COMPONENT_EVENT_METHOD;
+
+ static {
+ try {
+ COMPONENT_EVENT_METHOD = Component.Listener.class
+ .getDeclaredMethod("componentEvent",
+ new Class[] { Component.Event.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in AbstractComponent");
+ }
+ }
+
+ /**
+ * <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 object
+ * the object instance who owns the activation method.
+ * @param method
+ * the activation method.
+ */
+ public void addListener(Class eventType, Object object, Method method) {
+ if (eventRouter == null) {
+ eventRouter = new EventRouter();
+ }
+ eventRouter.addListener(eventType, object, 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 object
+ * the object instance who owns the activation method.
+ * @param methodName
+ * the name of the activation method.
+ */
+ public void addListener(Class eventType, Object object, String methodName) {
+ if (eventRouter == null) {
+ eventRouter = new EventRouter();
+ }
+ eventRouter.addListener(eventType, object, 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.
+ */
+ 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>.
+ */
+ 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>.
+ */
+ public void removeListener(Class eventType, Object target, String methodName) {
+ if (eventRouter != null) {
+ eventRouter.removeListener(eventType, target, methodName);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void addListener(Component.Listener listener) {
+ if (eventRouter == null) {
+ eventRouter = new EventRouter();
+ }
+
+ eventRouter.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.
+ */
+ public void removeListener(Component.Listener listener) {
+ if (eventRouter != null) {
+ eventRouter.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()
+ */
+ public float getHeight() {
+ return height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#getHeightUnits()
+ */
+ public int getHeightUnits() {
+ return heightUnit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#getWidth()
+ */
+ public float getWidth() {
+ return width;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#getWidthUnits()
+ */
+ public int getWidthUnits() {
+ return widthUnit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setHeight(float)
+ */
+ @Deprecated
+ public void setHeight(float height) {
+ setHeight(height, getHeightUnits());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setHeightUnits(int)
+ */
+ @Deprecated
+ public void setHeightUnits(int unit) {
+ setHeight(getHeight(), unit);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setHeight(float, int)
+ */
+ public void setHeight(float height, int unit) {
+ this.height = height;
+ heightUnit = unit;
+ requestRepaint();
+ // ComponentSizeValidator.setHeightLocation(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setSizeFull()
+ */
+ public void setSizeFull() {
+ setWidth(100, UNITS_PERCENTAGE);
+ setHeight(100, UNITS_PERCENTAGE);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setSizeUndefined()
+ */
+ public void setSizeUndefined() {
+ setWidth(-1, UNITS_PIXELS);
+ setHeight(-1, UNITS_PIXELS);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setWidth(float)
+ */
+ @Deprecated
+ public void setWidth(float width) {
+ setWidth(width, getWidthUnits());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setWidthUnits(int)
+ */
+ @Deprecated
+ public void setWidthUnits(int unit) {
+ setWidth(getWidth(), unit);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setWidth(float, int)
+ */
+ public void setWidth(float width, int unit) {
+ this.width = width;
+ widthUnit = unit;
+ requestRepaint();
+ // ComponentSizeValidator.setWidthLocation(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setWidth(java.lang.String)
+ */
+ public void setWidth(String width) {
+ float[] p = parseStringSize(width);
+ setWidth(p[0], (int) p[1]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Sizeable#setHeight(java.lang.String)
+ */
+ public void setHeight(String height) {
+ float[] p = parseStringSize(height);
+ setHeight(p[0], (int) p[1]);
+ }
+
+ /*
+ * Returns array with size in index 0 unit in index 1. Null or empty string
+ * will produce {-1,UNITS_PIXELS}
+ */
+ private static float[] parseStringSize(String s) {
+ float[] values = { -1, UNITS_PIXELS };
+ if (s == null) {
+ return values;
+ }
+ s = s.trim();
+ if ("".equals(s)) {
+ return values;
+ }
+
+ Matcher matcher = sizePattern.matcher(s);
+ if (matcher.find()) {
+ values[0] = Float.parseFloat(matcher.group(1));
+ if (values[0] < 0) {
+ values[0] = -1;
+ } else {
+ String unit = matcher.group(3);
+ if (unit == null) {
+ values[1] = UNITS_PIXELS;
+ } else if (unit.equals("px")) {
+ values[1] = UNITS_PIXELS;
+ } else if (unit.equals("%")) {
+ values[1] = UNITS_PERCENTAGE;
+ } else if (unit.equals("em")) {
+ values[1] = UNITS_EM;
+ } else if (unit.equals("ex")) {
+ values[1] = UNITS_EX;
+ } else if (unit.equals("in")) {
+ values[1] = UNITS_INCH;
+ } else if (unit.equals("cm")) {
+ values[1] = UNITS_CM;
+ } else if (unit.equals("mm")) {
+ values[1] = UNITS_MM;
+ } else if (unit.equals("pt")) {
+ values[1] = UNITS_POINTS;
+ } else if (unit.equals("pc")) {
+ values[1] = UNITS_PICAS;
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Invalid size argument: \"" + s
+ + "\" (should match " + sizePattern.pattern() + ")");
+ }
+ return values;
+ }
+
+ 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;
+
+ }
+
+} \ No newline at end of file
diff --git a/src/com/vaadin/ui/AbstractComponentContainer.java b/src/com/vaadin/ui/AbstractComponentContainer.java
new file mode 100644
index 0000000000..517dff14ac
--- /dev/null
+++ b/src/com/vaadin/ui/AbstractComponentContainer.java
@@ -0,0 +1,274 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * 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 IT Mill 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.
+ */
+ public void removeAllComponents() {
+ final LinkedList l = new LinkedList();
+
+ // Adds all components
+ for (final Iterator i = getComponentIterator(); i.hasNext();) {
+ l.add(i.next());
+ }
+
+ // Removes all component
+ for (final Iterator i = l.iterator(); i.hasNext();) {
+ removeComponent((Component) 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.
+ */
+ public void moveComponentsFrom(ComponentContainer source) {
+ final LinkedList components = new LinkedList();
+ for (final Iterator i = source.getComponentIterator(); i.hasNext();) {
+ components.add(i.next());
+ }
+
+ for (final Iterator i = components.iterator(); i.hasNext();) {
+ final Component c = (Component) i.next();
+ source.removeComponent(c);
+ addComponent(c);
+ }
+ }
+
+ /**
+ * Notifies all contained components that the container is attached to a
+ * window.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ super.attach();
+
+ for (final Iterator i = getComponentIterator(); i.hasNext();) {
+ ((Component) i.next()).attach();
+ }
+ }
+
+ /**
+ * Notifies all contained components that the container is detached from a
+ * window.
+ *
+ * @see com.vaadin.ui.Component#detach()
+ */
+ @Override
+ public void detach() {
+ super.detach();
+
+ for (final Iterator i = getComponentIterator(); i.hasNext();) {
+ ((Component) i.next()).detach();
+ }
+ }
+
+ /* 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 */
+ public void addListener(ComponentAttachListener listener) {
+ addListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ /* documented in interface */
+ public void addListener(ComponentDetachListener listener) {
+ addListener(ComponentContainer.ComponentDetachEvent.class, listener,
+ COMPONENT_DETACHED_METHOD);
+ }
+
+ /* documented in interface */
+ public void removeListener(ComponentAttachListener listener) {
+ removeListener(ComponentContainer.ComponentAttachEvent.class, listener,
+ COMPONENT_ATTACHED_METHOD);
+ }
+
+ /* documented in interface */
+ 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)
+ */
+ 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)
+ */
+ public void removeComponent(Component c) {
+ if (c.getParent() == this) {
+ c.setParent(null);
+ fireComponentDetachEvent(c);
+ }
+ }
+
+ @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();
+ }
+ }
+
+ @Override
+ public void setWidth(float width, int unit) {
+ if (getWidth() < 0 && width >= 0) {
+ // width becoming defined -> relative width children currently
+ // painted undefined may become defined
+ // TODO could be optimized(subtree of only those components
+ // which have undefined height due this component), currently just
+ // repaints whole subtree
+ requestRepaintAll();
+ } else if (getWidth() >= 0 && width < 0) {
+ requestRepaintAll();
+ }
+ super.setWidth(width, unit);
+ }
+
+ @Override
+ public void setHeight(float height, int unit) {
+ float currentHeight = getHeight();
+ if (currentHeight < 0.0f && height >= 0.0f) {
+ // height becoming defined -> relative height childs currently
+ // painted undefined may become defined
+ // TODO this could be optimized (subtree of only those components
+ // which have undefined width due this component), currently just
+ // repaints whole
+ // subtree
+ requestRepaintAll();
+ } else if (currentHeight >= 0 && height < 0) {
+ requestRepaintAll();
+ }
+ super.setHeight(height, unit);
+ }
+
+ public void requestRepaintAll() {
+ requestRepaint();
+ for (Iterator childIterator = getComponentIterator(); childIterator
+ .hasNext();) {
+ Component c = (Component) childIterator.next();
+ if (c instanceof Form) {
+ // Form has children in layout, but is not ComponentContainer
+ c.requestRepaint();
+ ((Form) c).getLayout().requestRepaintAll();
+ } else if (c instanceof Table) {
+ ((Table) c).requestRepaintAll();
+ } else if (c instanceof ComponentContainer) {
+ ((ComponentContainer) c).requestRepaintAll();
+ } else {
+ c.requestRepaint();
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java
new file mode 100644
index 0000000000..9d1420a78d
--- /dev/null
+++ b/src/com/vaadin/ui/AbstractField.java
@@ -0,0 +1,1157 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+import com.vaadin.Application;
+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.terminal.CompositeErrorMessage;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * <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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractField extends AbstractComponent implements Field,
+ Property.ReadOnlyStatusChangeNotifier {
+
+ /* Private members */
+
+ private boolean delayedFocus;
+
+ /**
+ * Value of the abstract field.
+ */
+ private Object value;
+
+ /**
+ * Connected data-source.
+ */
+ private Property dataSource = null;
+
+ /**
+ * The list of validators.
+ */
+ private LinkedList validators = null;
+
+ /**
+ * Auto commit mode.
+ */
+ private boolean writeTroughMode = true;
+
+ /**
+ * Reads the value from data-source, when it is not modified.
+ */
+ private boolean readTroughMode = true;
+
+ /**
+ * Is the field modified but not committed.
+ */
+ private boolean modified = 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 tab order number of this field.
+ */
+ private int tabIndex = 0;
+
+ /**
+ * Required field.
+ */
+ private boolean required = false;
+
+ /**
+ * The error message for the exception that is thrown when the field is
+ * required but empty.
+ */
+ private String requiredError = "";
+
+ /**
+ * Is automatic validation enabled.
+ */
+ private boolean validationVisible = true;
+
+ /* Component basics */
+
+ /*
+ * Paints the field. Don't add a JavaDoc comment here, we use the default
+ * documentation from the implemented interface.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // The tab ordering number
+ if (tabIndex != 0) {
+ target.addAttribute("tabindex", tabIndex);
+ }
+
+ // If the field is modified, but not committed, set modified attribute
+ if (isModified()) {
+ target.addAttribute("modified", true);
+ }
+
+ // Adds the required attribute
+ if (!isReadOnly() && isRequired()) {
+ target.addAttribute("required", true);
+ }
+
+ // Hide the error indicator if needed
+ if (isRequired() && isEmpty() && getComponentError() == null
+ && getErrorMessage() != null) {
+ target.addAttribute("hideErrors", true);
+ }
+ }
+
+ /*
+ * Gets the field type Don't add a JavaDoc comment here, we use the default
+ * documentation from the implemented interface.
+ */
+ public abstract Class 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()
+ */
+ public boolean isInvalidCommitted() {
+ return invalidCommitted;
+ }
+
+ /**
+ * Sets if the invalid data should be committed to datasource.
+ *
+ * @see com.vaadin.data.BufferedValidatable#setInvalidCommitted(boolean)
+ */
+ 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.
+ */
+ public void commit() throws Buffered.SourceException, InvalidValueException {
+ if (dataSource != null && !dataSource.isReadOnly()) {
+ if ((isInvalidCommitted() || isValid())) {
+ final Object newValue = getValue();
+ try {
+
+ // Commits the value to datasource.
+ dataSource.setValue(newValue);
+
+ } catch (final Throwable e) {
+
+ // Sets the buffering state.
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throws the source exception.
+ throw currentBufferedSourceException;
+ }
+ } else {
+ /* An invalid value and we don't allow them, throw the exception */
+ validate();
+ }
+ }
+
+ boolean repaintNeeded = false;
+
+ // The abstract field is not modified anymore
+ if (modified) {
+ modified = false;
+ repaintNeeded = true;
+ }
+
+ // If successful, remove set the buffering state to be ok
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ repaintNeeded = true;
+ }
+
+ if (repaintNeeded) {
+ requestRepaint();
+ }
+ }
+
+ /*
+ * Updates the value from the data source. Don't add a JavaDoc comment here,
+ * we use the default documentation from the implemented interface.
+ */
+ public void discard() throws Buffered.SourceException {
+ if (dataSource != null) {
+
+ // Gets the correct value from datasource
+ Object newValue;
+ try {
+
+ // Discards buffer by overwriting from datasource
+ newValue = String.class == getType() ? dataSource.toString()
+ : dataSource.getValue();
+
+ // If successful, remove set the buffering state to be ok
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ requestRepaint();
+ }
+ } catch (final Throwable e) {
+
+ // Sets the buffering state
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throws the source exception
+ throw currentBufferedSourceException;
+ }
+
+ final boolean wasModified = isModified();
+ modified = false;
+
+ // If the new value differs from the previous one
+ if ((newValue == null && value != null)
+ || (newValue != null && !newValue.equals(value))) {
+ setInternalValue(newValue);
+ fireValueChange(false);
+ }
+
+ // If the value did not change, but the modification status did
+ else if (wasModified) {
+ requestRepaint();
+ }
+ }
+ }
+
+ /*
+ * 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.
+ */
+ public boolean isModified() {
+ return modified;
+ }
+
+ /*
+ * 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.
+ */
+ public boolean isWriteThrough() {
+ return writeTroughMode;
+ }
+
+ /*
+ * Sets the field's write-through mode to the specified status Don't add a
+ * JavaDoc comment here, we use the default documentation from the
+ * implemented interface.
+ */
+ public void setWriteThrough(boolean writeTrough)
+ throws Buffered.SourceException, InvalidValueException {
+ if (writeTroughMode == writeTrough) {
+ return;
+ }
+ writeTroughMode = writeTrough;
+ if (writeTroughMode) {
+ 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.
+ */
+ public boolean isReadThrough() {
+ return readTroughMode;
+ }
+
+ /*
+ * Sets the field's read-through mode to the specified status Don't add a
+ * JavaDoc comment here, we use the default documentation from the
+ * implemented interface.
+ */
+ public void setReadThrough(boolean readTrough)
+ throws Buffered.SourceException {
+ if (readTroughMode == readTrough) {
+ return;
+ }
+ readTroughMode = readTrough;
+ if (!isModified() && readTroughMode && dataSource != null) {
+ setInternalValue(String.class == getType() ? dataSource.toString()
+ : dataSource.getValue());
+ fireValueChange(false);
+ }
+ }
+
+ /* Property interface implementation */
+
+ /**
+ * Returns the value of the Property in human readable textual format.
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ final Object value = getValue();
+ if (value == null) {
+ return null;
+ }
+ return getValue().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. In the read-through mode, the abstract buffer is
+ * also updated and validation is performed.
+ * </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 datasources
+ * native type, use getPropertyDatasource().getValue() instead.
+ * </p>
+ *
+ * <p>
+ * Note that when you extend AbstractField, you must reimplement this method
+ * if datasource.getValue() is not assignable to class returned by getType()
+ * AND getType() is not String. In case of Strings, getValue() calls
+ * datasource.toString() instead of datasource.getValue().
+ * </p>
+ *
+ * @return the current value of the field.
+ */
+ public Object getValue() {
+
+ // Give the value from abstract buffers if the field if possible
+ if (dataSource == null || !isReadThrough() || isModified()) {
+ return value;
+ }
+
+ Object newValue = String.class == getType() ? dataSource.toString()
+ : dataSource.getValue();
+ if ((newValue == null && value != null)
+ || (newValue != null && !newValue.equals(value))) {
+ setInternalValue(newValue);
+ fireValueChange(false);
+ }
+
+ return newValue;
+ }
+
+ /**
+ * Sets the value of the field.
+ *
+ * @param newValue
+ * the New value of the field.
+ * @throws Property.ReadOnlyException
+ * @throws Property.ConversionException
+ */
+ public void setValue(Object newValue) throws Property.ReadOnlyException,
+ Property.ConversionException {
+ setValue(newValue, false);
+ }
+
+ /**
+ * Sets the value of the field.
+ *
+ * @param newValue
+ * the New value of the field.
+ * @param repaintIsNotNeeded
+ * True iff caller is sure that repaint is not needed.
+ * @throws Property.ReadOnlyException
+ * @throws Property.ConversionException
+ */
+ protected void setValue(Object newValue, boolean repaintIsNotNeeded)
+ throws Property.ReadOnlyException, Property.ConversionException {
+
+ if ((newValue == null && value != null)
+ || (newValue != null && !newValue.equals(value))) {
+
+ // 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)) {
+ repaintIsNotNeeded = false;
+ }
+
+ // If invalid values are not allowed, the value must be checked
+ if (!isInvalidAllowed()) {
+ final Collection v = getValidators();
+ if (v != null) {
+ for (final Iterator i = v.iterator(); i.hasNext();) {
+ ((Validator) i.next()).validate(newValue);
+ }
+ }
+ }
+
+ // Changes the value
+ setInternalValue(newValue);
+ modified = dataSource != null;
+
+ // In write trough mode , try to commit
+ if (isWriteThrough() && dataSource != null
+ && (isInvalidCommitted() || isValid())) {
+ try {
+
+ // Commits the value to datasource
+ dataSource.setValue(newValue);
+
+ // The buffer is now unmodified
+ modified = false;
+
+ } catch (final Throwable e) {
+
+ // Sets the buffering state
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throws the source exception
+ throw currentBufferedSourceException;
+ }
+ }
+
+ // If successful, remove set the buffering state to be ok
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ requestRepaint();
+ }
+
+ // Fires the value change
+ fireValueChange(repaintIsNotNeeded);
+ }
+ }
+
+ /* 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.
+ */
+ public Property getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * <p>
+ * Sets the specified Property as the data source for the field. All
+ * uncommitted changes to the field are discarded and the value is refreshed
+ * 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>
+ *
+ * @param newDataSource
+ * the new data source Property.
+ */
+ public void setPropertyDataSource(Property newDataSource) {
+
+ // Saves the old value
+ final Object oldValue = value;
+
+ // Discards all changes to old datasource
+ try {
+ discard();
+ } catch (final Buffered.SourceException ignored) {
+ }
+
+ // 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;
+
+ // Gets the value from source
+ try {
+ if (dataSource != null) {
+ setInternalValue(String.class == getType() ? dataSource
+ .toString() : dataSource.getValue());
+ }
+ modified = false;
+ } catch (final Throwable e) {
+ currentBufferedSourceException = new Buffered.SourceException(this,
+ e);
+ modified = true;
+ }
+
+ // Listens the new data source if possible
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+
+ // Copy the validators from the data source
+ if (dataSource instanceof Validatable) {
+ final Collection validators = ((Validatable) dataSource)
+ .getValidators();
+ if (validators != null) {
+ for (final Iterator i = validators.iterator(); i.hasNext();) {
+ addValidator((Validator) i.next());
+ }
+ }
+ }
+
+ // Fires value change if the value has changed
+ if ((value != oldValue)
+ && ((value != null && !value.equals(oldValue)) || value == null)) {
+ fireValueChange(false);
+ }
+ }
+
+ /* 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.
+ */
+ public void addValidator(Validator validator) {
+ if (validators == null) {
+ validators = new LinkedList();
+ }
+ validators.add(validator);
+ requestRepaint();
+ }
+
+ /**
+ * Gets the validators of the field.
+ *
+ * @return the Unmodifiable collection that holds all validators for the
+ * field.
+ */
+ public Collection getValidators() {
+ if (validators == null || validators.isEmpty()) {
+ return null;
+ }
+ return Collections.unmodifiableCollection(validators);
+ }
+
+ /**
+ * Removes the validator from the field.
+ *
+ * @param validator
+ * the validator to remove.
+ */
+ public void removeValidator(Validator validator) {
+ if (validators != null) {
+ validators.remove(validator);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Tests the current value against all registered validators.
+ *
+ * @return <code>true</code> if all registered validators claim that the
+ * current value is valid, <code>false</code> otherwise.
+ */
+ public boolean isValid() {
+
+ if (isEmpty()) {
+ if (isRequired()) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ if (validators == null) {
+ return true;
+ }
+
+ final Object value = getValue();
+ for (final Iterator i = validators.iterator(); i.hasNext();) {
+ if (!((Validator) i.next()).isValid(value)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks the validity of the Validatable by validating the field with all
+ * attached validators.
+ *
+ * The "required" validation is a built-in validation feature. If the field
+ * is required, but empty, validation will throw an EmptyValueException with
+ * the error message set with setRequiredError().
+ *
+ * @see com.vaadin.data.Validatable#validate()
+ */
+ public void validate() throws Validator.InvalidValueException {
+
+ if (isEmpty()) {
+ if (isRequired()) {
+ throw new Validator.EmptyValueException(requiredError);
+ } else {
+ return;
+ }
+ }
+
+ // If there is no validator, there can not be any errors
+ if (validators == null) {
+ return;
+ }
+
+ // Initialize temps
+ Validator.InvalidValueException firstError = null;
+ LinkedList errors = null;
+ final Object value = getValue();
+
+ // Gets all the validation errors
+ for (final Iterator i = validators.iterator(); i.hasNext();) {
+ try {
+ ((Validator) i.next()).validate(value);
+ } catch (final Validator.InvalidValueException e) {
+ if (firstError == null) {
+ firstError = e;
+ } else {
+ if (errors == null) {
+ errors = new LinkedList();
+ errors.add(firstError);
+ }
+ errors.add(e);
+ }
+ }
+ }
+
+ // If there were no error
+ if (firstError == null) {
+ return;
+ }
+
+ // If only one error occurred, throw it forwards
+ if (errors == null) {
+ throw firstError;
+ }
+
+ // Creates composite validator
+ final Validator.InvalidValueException[] exceptions = new Validator.InvalidValueException[errors
+ .size()];
+ int index = 0;
+ for (final Iterator i = errors.iterator(); i.hasNext();) {
+ exceptions[index++] = (Validator.InvalidValueException) i.next();
+ }
+
+ throw new Validator.InvalidValueException(null, exceptions);
+ }
+
+ /**
+ * 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()
+ */
+ 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)
+ */
+ 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.
+ */
+ ErrorMessage 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
+ && currentBufferedSourceException == null) {
+ return null;
+ }
+
+ // Throw combination of the error types
+ return new CompositeErrorMessage(new ErrorMessage[] { superError,
+ validationError, currentBufferedSourceException });
+
+ }
+
+ /* 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.
+ */
+ 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.
+ */
+ 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");
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying the Property whose read-only
+ * status has changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ *
+ * @param event
+ * the value change event telling the data source contents have
+ * changed.
+ */
+ public void valueChange(Property.ValueChangeEvent event) {
+ if (isReadThrough() || !isModified()) {
+ fireValueChange(false);
+ }
+ }
+
+ @Override
+ public void changeVariables(Object source, Map variables) {
+ super.changeVariables(source, variables);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#focus()
+ */
+ public void focus() {
+ final Application app = getApplication();
+ if (app != null) {
+ getWindow().setFocusedComponent(this);
+ delayedFocus = false;
+ } else {
+ delayedFocus = true;
+ }
+ }
+
+ /**
+ * Creates abstract field by the type of the property.
+ *
+ * <p>
+ * This returns most suitable field type for editing property of given type.
+ * </p>
+ *
+ * @param propertyType
+ * the Type of the property, that needs to be edited.
+ */
+ public static AbstractField constructField(Class propertyType) {
+
+ // Null typed properties can not be edited
+ if (propertyType == null) {
+ return null;
+ }
+
+ // Date field
+ if (Date.class.isAssignableFrom(propertyType)) {
+ return new DateField();
+ }
+
+ // Boolean field
+ if (Boolean.class.isAssignableFrom(propertyType)) {
+ final Button button = new Button("");
+ button.setSwitchMode(true);
+ button.setImmediate(false);
+ return button;
+ }
+
+ // Text field is used by default
+ return new TextField();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#getTabIndex()
+ */
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ public void setTabIndex(int tabIndex) {
+ this.tabIndex = tabIndex;
+ }
+
+ /**
+ * 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 overriden by the inheriting classes to update all dependent
+ * variables.
+ *
+ * @param newValue
+ * the new value to be set.
+ */
+ protected void setInternalValue(Object 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 (delayedFocus) {
+ focus();
+ }
+ }
+
+ /**
+ * 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>.
+ */
+ public boolean isRequired() {
+ return required;
+ }
+
+ /**
+ * 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.
+ */
+ public void setRequired(boolean required) {
+ this.required = 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.
+ */
+ public void setRequiredError(String requiredMessage) {
+ requiredError = requiredMessage;
+ requestRepaint();
+ }
+
+ public String getRequiredError() {
+ return requiredError;
+ }
+
+ /**
+ * 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 (getValue() == 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();
+ }
+
+} \ No newline at end of file
diff --git a/src/com/vaadin/ui/AbstractLayout.java b/src/com/vaadin/ui/AbstractLayout.java
new file mode 100644
index 0000000000..3604e5ea1d
--- /dev/null
+++ b/src/com/vaadin/ui/AbstractLayout.java
@@ -0,0 +1,92 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.ui.Layout.MarginHandler;
+
+/**
+ * An abstract class that defines default implementation for the {@link Layout}
+ * interface.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractLayout extends AbstractComponentContainer
+ implements Layout, MarginHandler {
+
+ protected MarginInfo margins = new MarginInfo(false);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractComponent#getTag()
+ */
+ @Override
+ public abstract String getTag();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout#setMargin(boolean)
+ */
+ public void setMargin(boolean enabled) {
+ margins.setMargins(enabled);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.MarginHandler#getMargin()
+ */
+ public MarginInfo getMargin() {
+ return margins;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.MarginHandler#setMargin(MarginInfo)
+ */
+ public void setMargin(MarginInfo marginInfo) {
+ margins.setMargins(marginInfo);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout#setMargin(boolean, boolean, boolean,
+ * boolean)
+ */
+ public void setMargin(boolean topEnabled, boolean rightEnabled,
+ boolean bottomEnabled, boolean leftEnabled) {
+ margins
+ .setMargins(topEnabled, rightEnabled, bottomEnabled,
+ leftEnabled);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.AbstractComponent#paintContent(com.vaadin
+ * .terminal.PaintTarget)
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Add margin info. Defaults to false.
+ target.addAttribute("margins", margins.getBitMask());
+
+ }
+
+}
diff --git a/src/com/vaadin/ui/AbstractOrderedLayout.java b/src/com/vaadin/ui/AbstractOrderedLayout.java
new file mode 100644
index 0000000000..e83a71eeaf
--- /dev/null
+++ b/src/com/vaadin/ui/AbstractOrderedLayout.java
@@ -0,0 +1,366 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Sizeable;
+
+@SuppressWarnings("serial")
+public abstract class AbstractOrderedLayout extends AbstractLayout implements
+ Layout.AlignmentHandler, Layout.SpacingHandler {
+
+ private 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).
+ */
+ private final Map<Component, Alignment> componentToAlignment = new HashMap<Component, Alignment>();
+
+ private final Map<Component, Float> componentToExpandRatio = new HashMap<Component, Float>();
+
+ /**
+ * Is spacing between contained components enabled. Defaults to false.
+ */
+ private boolean spacing = false;
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "orderedlayout";
+ }
+
+ /**
+ * 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) {
+ super.addComponent(c);
+ components.add(c);
+ requestRepaint();
+ }
+
+ /**
+ * 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) {
+ super.addComponent(c);
+ components.addFirst(c);
+ requestRepaint();
+ }
+
+ /**
+ * 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) {
+ super.addComponent(c);
+ components.add(index, c);
+ requestRepaint();
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * the component to be removed.
+ */
+ @Override
+ public void removeComponent(Component c) {
+ super.removeComponent(c);
+ components.remove(c);
+ componentToAlignment.remove(c);
+ componentToExpandRatio.remove(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.
+ */
+ public Iterator getComponentIterator() {
+ return components.iterator();
+ }
+
+ /**
+ * 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 {
+ super.paintContent(target);
+
+ // Add spacing attribute (omitted if false)
+ if (spacing) {
+ target.addAttribute("spacing", spacing);
+ }
+
+ final String[] alignmentsArray = new String[components.size()];
+ final Integer[] expandRatioArray = new Integer[components.size()];
+ float sum = getExpandRatioSum();
+ boolean equallyDivided = false;
+ int realSum = 0;
+ if (sum == 0 && components.size() > 0) {
+ // no component has been expanded, all components have same expand
+ // rate
+ equallyDivided = true;
+ float equalSize = 1 / (float) components.size();
+ int myRatio = Math.round(equalSize * 1000);
+ for (int i = 0; i < expandRatioArray.length; i++) {
+ expandRatioArray[i] = myRatio;
+ }
+ realSum = myRatio * components.size();
+ }
+
+ // Adds all items in all the locations
+ int index = 0;
+ for (final Iterator i = components.iterator(); i.hasNext();) {
+ final Component c = (Component) i.next();
+ if (c != null) {
+ // Paint child component UIDL
+ c.paint(target);
+ alignmentsArray[index] = String
+ .valueOf(getComponentAlignment(c).getBitMask());
+ if (!equallyDivided) {
+ int myRatio = Math.round((getExpandRatio(c) / sum) * 1000);
+ expandRatioArray[index] = myRatio;
+ realSum += myRatio;
+ }
+ index++;
+ }
+ }
+
+ // correct possible rounding error
+ if (expandRatioArray.length > 0) {
+ expandRatioArray[0] -= realSum - 1000;
+ }
+
+ // Add child component alignment info to layout tag
+ target.addAttribute("alignments", alignmentsArray);
+ target.addAttribute("expandRatios", expandRatioArray);
+ }
+
+ private float getExpandRatioSum() {
+ float sum = 0;
+ for (Iterator<Entry<Component, Float>> iterator = componentToExpandRatio
+ .entrySet().iterator(); iterator.hasNext();) {
+ sum += iterator.next().getValue();
+ }
+ return sum;
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ // Gets the locations
+ int oldLocation = -1;
+ int newLocation = -1;
+ int location = 0;
+ for (final Iterator i = components.iterator(); i.hasNext();) {
+ final Component 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);
+ componentToAlignment.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ } else {
+ components.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ components.remove(oldComponent);
+ componentToAlignment.remove(oldComponent);
+ components.add(newLocation, oldComponent);
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.Layout.AlignmentHandler#setComponentAlignment(com
+ * .itmill.toolkit.ui.Component, int, int)
+ */
+ public void setComponentAlignment(Component childComponent,
+ int horizontalAlignment, int verticalAlignment) {
+ if (components.contains(childComponent)) {
+ // Alignments are bit masks
+ componentToAlignment.put(childComponent, new Alignment(
+ horizontalAlignment + verticalAlignment));
+ requestRepaint();
+ } else {
+ throw new IllegalArgumentException(
+ "Component must be added to layout before using setComponentAlignment()");
+ }
+ }
+
+ public void setComponentAlignment(Component childComponent,
+ Alignment alignment) {
+ if (components.contains(childComponent)) {
+ componentToAlignment.put(childComponent, alignment);
+ requestRepaint();
+ } else {
+ throw new IllegalArgumentException(
+ "Component must be added to layout before using setComponentAlignment()");
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
+ * .itmill.toolkit.ui.Component)
+ */
+ public Alignment getComponentAlignment(Component childComponent) {
+ Alignment alignment = componentToAlignment.get(childComponent);
+ if (alignment == null) {
+ return ALIGNMENT_DEFAULT;
+ } else {
+ return alignment;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
+ */
+ public void setSpacing(boolean enabled) {
+ spacing = enabled;
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
+ */
+ @Deprecated
+ public boolean isSpacingEnabled() {
+ return spacing;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
+ */
+ public boolean isSpacing() {
+ return spacing;
+ }
+
+ /**
+ * <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) {
+ if (components.contains(component)) {
+ componentToExpandRatio.put(component, ratio);
+ requestRepaint();
+ } else {
+ throw new IllegalArgumentException(
+ "Component must be added to layout before using setExpandRatio()");
+ }
+ };
+
+ /**
+ * 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) {
+ Float ratio = componentToExpandRatio.get(component);
+ return (ratio == null) ? 0 : ratio.floatValue();
+ }
+
+ public void setComponentAlignment(Component component, String alignment) {
+ AlignmentUtils.setComponentAlignment(this, component, alignment);
+ }
+
+}
diff --git a/src/com/vaadin/ui/AbstractSelect.java b/src/com/vaadin/ui/AbstractSelect.java
new file mode 100644
index 0000000000..4719a98371
--- /dev/null
+++ b/src/com/vaadin/ui/AbstractSelect.java
@@ -0,0 +1,1679 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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.terminal.KeyMapper;
+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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractSelect extends AbstractField implements
+ Container, Container.Viewer, Container.PropertySetChangeListener,
+ Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier,
+ Container.ItemSetChangeListener {
+
+ /**
+ * Item caption mode: Item's ID's <code>String</code> representation is used
+ * as caption.
+ */
+ public static final int ITEM_CAPTION_MODE_ID = 0;
+ /**
+ * Item caption mode: Item's <code>String</code> representation is used as
+ * caption.
+ */
+ public static final int ITEM_CAPTION_MODE_ITEM = 1;
+ /**
+ * 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.
+ */
+ public static final int ITEM_CAPTION_MODE_INDEX = 2;
+ /**
+ * 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>.
+ */
+ public static final int ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = 3;
+ /**
+ * Item caption mode: Captions must be explicitly specified.
+ */
+ public static final int ITEM_CAPTION_MODE_EXPLICIT = 4;
+ /**
+ * Item caption mode: Only icons are shown, captions are hidden.
+ */
+ public static final int ITEM_CAPTION_MODE_ICON_ONLY = 5;
+ /**
+ * Item caption mode: Item captions are read from property specified with
+ * <code>setItemCaptionPropertyId</code>.
+ */
+ public static final int ITEM_CAPTION_MODE_PROPERTY = 6;
+
+ /**
+ * 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();
+
+ }
+
+ /**
+ * 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 itemIdMapper = new KeyMapper();
+
+ /**
+ * Item icons.
+ */
+ private final HashMap itemIcons = new HashMap();
+
+ /**
+ * Item captions.
+ */
+ private final HashMap itemCaptions = new HashMap();
+
+ /**
+ * Item caption mode.
+ */
+ private int itemCaptionMode = ITEM_CAPTION_MODE_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 propertySetEventListeners = null;
+
+ /**
+ * List of item set change event listeners.
+ */
+ private Set 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 field properties
+ super.paintContent(target);
+
+ // 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())) {
+ // Gets the option attribute values
+ final Object id = getNullSelectionItemId();
+ final String key = itemIdMapper.key(id);
+ final String caption = getItemCaption(id);
+ final Resource icon = getItemIcon(id);
+ // Paints option
+ target.startTag("so");
+ if (icon != null) {
+ target.addAttribute("icon", icon);
+ }
+ target.addAttribute("caption", caption);
+ target.addAttribute("nullselection", true);
+ target.addAttribute("key", key);
+ if (isSelected(id)) {
+ target.addAttribute("selected", true);
+ selectedKeys[keyIndex++] = key;
+ }
+ 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);
+ final String caption = getItemCaption(id);
+ // add listener for each item, to cause repaint if an item changes
+ getCaptionChangeListener().addNotifierForItem(id);
+ final Resource icon = getItemIcon(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");
+ // ==
+
+ // Paint variables
+ target.addVariable(this, "selected", selectedKeys);
+ if (isNewItemsAllowed()) {
+ target.addVariable(this, "newitem", "");
+ }
+
+ }
+
+ /**
+ * 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 variables) {
+ super.changeVariables(source, variables);
+
+ // New option entered (and it is allowed)
+ final String newitem = (String) variables.get("newitem");
+ if (newitem != null && newitem.length() > 0) {
+ getNewItemHandler().addNewItem(newitem);
+ }
+
+ // Selection change
+ if (variables.containsKey("selected")) {
+ final String[] ka = (String[]) variables.get("selected");
+
+ // Multiselect mode
+ if (isMultiSelect()) {
+
+ // TODO Optimize by adding repaintNotNeeded when applicable
+
+ // Converts the key-array to id-set
+ final LinkedList s = new LinkedList();
+ 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;
+ }
+
+ // Limits the deselection to the set of visible items
+ // (non-visible items can not be deselected)
+ final Collection visible = getVisibleItemIds();
+ if (visible != null) {
+ Set newsel = (Set) getValue();
+ if (newsel == null) {
+ newsel = new HashSet();
+ } else {
+ newsel = new HashSet(newsel);
+ }
+ newsel.removeAll(visible);
+ newsel.addAll(s);
+ setValue(newsel, true);
+ }
+ } else {
+ // Single select mode
+ if (!isNullSelectionAllowed()
+ && (ka.length == 0 || ka[0] == null || ka[0] == getNullSelectionItemId())) {
+ requestRepaint();
+ return;
+ }
+ 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 (!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 {
+ 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) {
+ try {
+ getContainerProperty(newItemCaption,
+ getItemCaptionPropertyId()).setValue(
+ newItemCaption);
+ } catch (final Property.ConversionException ignored) {
+ /*
+ * The conversion exception is safely ignored, the
+ * caption is just missing
+ */
+ }
+ }
+ if (isMultiSelect()) {
+ Set values = new HashSet((Collection) getValue());
+ values.add(newItemCaption);
+ setValue(values);
+ } else {
+ setValue(newItemCaption);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "select";
+ }
+
+ /**
+ * 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() {
+ if (isVisible()) {
+ return getItemIds();
+ }
+ return null;
+ }
+
+ /* 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();
+ }
+ if (retValue instanceof Set) {
+ return Collections.unmodifiableSet((Set) retValue);
+ } else if (retValue instanceof Collection) {
+ return new HashSet((Collection) retValue);
+ } else {
+ final Set s = new HashSet();
+ 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,
+ Property.ConversionException {
+ if (newValue == null) {
+ newValue = getNullSelectionItemId();
+ }
+
+ 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, Property.ConversionException {
+
+ if (isMultiSelect()) {
+ if (newValue == null) {
+ super.setValue(new HashSet(), repaintIsNotNeeded);
+ } else if (Collection.class.isAssignableFrom(newValue.getClass())) {
+ super.setValue(new HashSet((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.
+ */
+ public Item getItem(Object itemId) {
+ return items.getItem(itemId);
+ }
+
+ /**
+ * Gets the item Id collection from the container.
+ *
+ * @return the Collection of item ids.
+ */
+ public Collection getItemIds() {
+ return items.getItemIds();
+ }
+
+ /**
+ * Gets the property Id collection from the container.
+ *
+ * @return the Collection of property ids.
+ */
+ 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)
+ */
+ 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()
+ */
+ 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.
+ */
+ 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)
+ */
+ 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)
+ */
+ 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()
+ */
+ 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()
+ */
+ 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)
+ */
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+
+ final Item retval = items.addItem(itemId);
+ if (retval != null
+ && !(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Removes the item identified by Id from the container. This functionality
+ * is optional. If the function is not implemented, the functions allways
+ * returns false.
+ *
+ * @return True if the operation succeeded.
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+ 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)
+ */
+ 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 as data-source for viewing.
+ *
+ * @param newDataSource
+ * the new data source.
+ */
+ 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);
+ }
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the viewing data-source container.
+ *
+ * @see com.vaadin.data.Container.Viewer#getContainerDataSource()
+ */
+ 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 loose
+ * selection information: if selected items set contains one or more
+ * selected items, only one of the selected items is kept as selected.
+ *
+ * @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 s = new HashSet();
+ 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 ITEM_CAPTION_MODE_ID:
+ caption = itemId.toString();
+ break;
+
+ case ITEM_CAPTION_MODE_INDEX:
+ if (items instanceof Container.Indexed) {
+ caption = String.valueOf(((Container.Indexed) items)
+ .indexOfId(itemId));
+ } else {
+ caption = "ERROR: Container is not indexed";
+ }
+ break;
+
+ case ITEM_CAPTION_MODE_ITEM:
+ final Item i = getItem(itemId);
+ if (i != null) {
+ caption = i.toString();
+ }
+ break;
+
+ case ITEM_CAPTION_MODE_EXPLICIT:
+ caption = (String) itemCaptions.get(itemId);
+ break;
+
+ case ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID:
+ caption = (String) itemCaptions.get(itemId);
+ if (caption == null) {
+ caption = itemId.toString();
+ }
+ break;
+
+ case ITEM_CAPTION_MODE_PROPERTY:
+ final Property p = getContainerProperty(itemId,
+ getItemCaptionPropertyId());
+ if (p != null) {
+ caption = p.toString();
+ }
+ break;
+ }
+
+ // All items must have some captions
+ return caption != null ? caption : "";
+ }
+
+ /**
+ * Sets the icon for an item.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @param icon
+ * the New icon.
+ */
+ 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 = (Resource) 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(int mode) {
+ if (ITEM_CAPTION_MODE_ID <= mode && mode <= ITEM_CAPTION_MODE_PROPERTY) {
+ 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 int 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>
+ * 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 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>
+ * .
+ *
+ * @param propertyId
+ * the Id of the property that specifies icons for items.
+ */
+ public void setItemIconPropertyId(Object propertyId) {
+ if ((propertyId != null)
+ && Resource.class.isAssignableFrom(getType(propertyId))) {
+ itemIconPropertyId = propertyId;
+ } else {
+ itemIconPropertyId = null;
+ }
+ 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 tem 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 s = new HashSet((Set) getValue());
+ s.add(itemId);
+ setValue(s);
+ }
+ }
+
+ /**
+ * Unselects an item.
+ *
+ * @param itemId
+ * the Item to be unselected.
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ *
+ */
+ public void unselect(Object itemId) {
+ if (isSelected(itemId)) {
+ if (isMultiSelect()) {
+ final Set s = new HashSet((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)
+ */
+ 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)
+ */
+ public void addListener(Container.PropertySetChangeListener listener) {
+ if (propertySetEventListeners == null) {
+ propertySetEventListeners = new LinkedHashSet();
+ }
+ propertySetEventListeners.add(listener);
+ }
+
+ /**
+ * Removes a previously registered Property set change listener.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeNotifier#removeListener(com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ 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)
+ */
+ public void addListener(Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners == null) {
+ itemSetEventListeners = new LinkedHashSet();
+ }
+ itemSetEventListeners.add(listener);
+ }
+
+ /**
+ * Removes the Item set change listener from the object.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners != null) {
+ itemSetEventListeners.remove(listener);
+ if (itemSetEventListeners.isEmpty()) {
+ itemSetEventListeners = null;
+ }
+ }
+ }
+
+ /**
+ * Lets the listener know a Containers Item set has changed.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange(com.vaadin.data.Container.ItemSetChangeEvent)
+ */
+ 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()
+ */
+ 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()
+ */
+ public Container getContainer() {
+ return AbstractSelect.this;
+ }
+
+ }
+
+ /**
+ * Allow of disallow empty selection. 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.
+ *
+ * @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 final 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
+ * idetified 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 {
+
+ HashSet captionChangeNotifiers = new HashSet();
+
+ public void addNotifierForItem(Object itemId) {
+ switch (getItemCaptionMode()) {
+ case ITEM_CAPTION_MODE_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 ITEM_CAPTION_MODE_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 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());
+ }
+ }
+ }
+
+ public void valueChange(
+ com.vaadin.data.Property.ValueChangeEvent event) {
+ requestRepaint();
+ }
+
+ public void itemPropertySetChange(
+ com.vaadin.data.Item.PropertySetChangeEvent event) {
+ requestRepaint();
+ }
+
+ }
+
+}
diff --git a/src/com/vaadin/ui/Accordion.java b/src/com/vaadin/ui/Accordion.java
new file mode 100644
index 0000000000..73b4c03743
--- /dev/null
+++ b/src/com/vaadin/ui/Accordion.java
@@ -0,0 +1,11 @@
+package com.vaadin.ui;
+
+@SuppressWarnings("serial")
+public class Accordion extends TabSheet {
+
+ @Override
+ public String getTag() {
+ return "accordion";
+ }
+
+}
diff --git a/src/com/vaadin/ui/Alignment.java b/src/com/vaadin/ui/Alignment.java
new file mode 100644
index 0000000000..12d4c00110
--- /dev/null
+++ b/src/com/vaadin/ui/Alignment.java
@@ -0,0 +1,155 @@
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.gwt.client.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/src/com/vaadin/ui/AlignmentUtils.java b/src/com/vaadin/ui/AlignmentUtils.java
new file mode 100644
index 0000000000..28076596df
--- /dev/null
+++ b/src/com/vaadin/ui/AlignmentUtils.java
@@ -0,0 +1,145 @@
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.ui.Layout.AlignmentHandler;
+
+/**
+ * Helper class for setting alignments using a short notation.
+ *
+ * Supported notation is:
+ *
+ * t,top for top alignment
+ *
+ * m,middle for vertical center alignment
+ *
+ * b,bottom for bottom alignment
+ *
+ * l,left for left alignment
+ *
+ * c,center for horizontal center alignment
+ *
+ * r,right for right alignment
+ *
+ */
+@SuppressWarnings("serial")
+public class AlignmentUtils implements Serializable {
+
+ private static int horizontalMask = AlignmentHandler.ALIGNMENT_LEFT
+ | AlignmentHandler.ALIGNMENT_HORIZONTAL_CENTER
+ | AlignmentHandler.ALIGNMENT_RIGHT;
+ private static int verticalMask = AlignmentHandler.ALIGNMENT_TOP
+ | AlignmentHandler.ALIGNMENT_VERTICAL_CENTER
+ | AlignmentHandler.ALIGNMENT_BOTTOM;
+
+ private static Map<String, Integer> alignmentStrings = new HashMap();
+
+ private static void addMapping(int alignment, String... values) {
+ for (String s : values) {
+ alignmentStrings.put(s, alignment);
+ }
+ }
+
+ static {
+ addMapping(AlignmentHandler.ALIGNMENT_TOP, "t", "top");
+ addMapping(AlignmentHandler.ALIGNMENT_BOTTOM, "b", "bottom");
+ addMapping(AlignmentHandler.ALIGNMENT_VERTICAL_CENTER, "m", "middle");
+
+ addMapping(AlignmentHandler.ALIGNMENT_LEFT, "l", "left");
+ addMapping(AlignmentHandler.ALIGNMENT_RIGHT, "r", "right");
+ addMapping(AlignmentHandler.ALIGNMENT_HORIZONTAL_CENTER, "c", "center");
+ }
+
+ /**
+ * Set the alignment for the component using short notation
+ *
+ * @param parent
+ * @param component
+ * @param alignment
+ * String containing one or two alignment strings. If short
+ * notation "r","t",etc is used valid strings include
+ * "r","rt","tr","t". If the longer notation is used the
+ * alignments should be separated by a space e.g.
+ * "right","right top","top right","top". It is valid to mix
+ * short and long notation but they must be separated by a space
+ * e.g. "r top".
+ * @throws IllegalArgumentException
+ */
+ public static void setComponentAlignment(AlignmentHandler parent,
+ Component component, String alignment)
+ throws IllegalArgumentException {
+ if (alignment == null || alignment.length() == 0) {
+ throw new IllegalArgumentException(
+ "alignment for setComponentAlignment() cannot be null or empty");
+ }
+
+ Integer currentAlignment = parent.getComponentAlignment(component)
+ .getBitMask();
+
+ if (alignment.length() == 1) {
+ // Use short form "t","l",...
+ currentAlignment = parseAlignment(alignment.substring(0, 1),
+ currentAlignment);
+ } else if (alignment.length() == 2) {
+ // Use short form "tr","lb",...
+ currentAlignment = parseAlignment(alignment.substring(0, 1),
+ currentAlignment);
+ currentAlignment = parseAlignment(alignment.substring(1, 2),
+ currentAlignment);
+ } else {
+ // Alignments are separated by space
+ String[] strings = alignment.split(" ");
+ if (strings.length > 2) {
+ throw new IllegalArgumentException(
+ "alignment for setComponentAlignment() should not contain more than 2 alignments");
+ }
+ for (String alignmentString : strings) {
+ currentAlignment = parseAlignment(alignmentString,
+ currentAlignment);
+ }
+ }
+
+ int horizontalAlignment = currentAlignment & horizontalMask;
+ int verticalAlignment = currentAlignment & verticalMask;
+ parent.setComponentAlignment(component, new Alignment(
+ horizontalAlignment + verticalAlignment));
+ }
+
+ /**
+ * Parse alignmentString which contains one alignment (horizontal or
+ * vertical) and return and updated version of the passed alignment where
+ * the alignment in one direction has been changed. If the passed
+ * alignmentString is unknown an exception is thrown
+ *
+ * @param alignmentString
+ * @param alignment
+ * @return
+ * @throws IllegalArgumentException
+ */
+ private static int parseAlignment(String alignmentString, int alignment)
+ throws IllegalArgumentException {
+ Integer parsed = alignmentStrings.get(alignmentString.toLowerCase());
+
+ if (parsed == null) {
+ throw new IllegalArgumentException(
+ "Could not parse alignment string '" + alignmentString
+ + "'");
+ }
+
+ if ((parsed & horizontalMask) != 0) {
+ // Get the vertical alignment from the current alignment
+ int vertical = (alignment & verticalMask);
+ // Add the parsed horizontal alignment
+ alignment = (vertical | parsed);
+ } else {
+ // Get the horizontal alignment from the current alignment
+ int horizontal = (alignment & horizontalMask);
+ // Add the parsed vertical alignment
+ alignment = (horizontal | parsed);
+ }
+
+ return alignment;
+ }
+}
diff --git a/src/com/vaadin/ui/BaseFieldFactory.java b/src/com/vaadin/ui/BaseFieldFactory.java
new file mode 100644
index 0000000000..66a153a287
--- /dev/null
+++ b/src/com/vaadin/ui/BaseFieldFactory.java
@@ -0,0 +1,151 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Date;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * Default implementation of the the following Field types are used by default:
+ * <p>
+ * <b>Boolean</b>: Button(switchMode:true).<br/>
+ * <b>Date</b>: DateField(resolution: day).<br/>
+ * <b>Item</b>: Form. <br/>
+ * <b>default field type</b>: TextField.
+ * <p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.1
+ */
+
+@SuppressWarnings("serial")
+public class BaseFieldFactory implements FieldFactory {
+
+ /**
+ * Creates the field based on type of data.
+ *
+ *
+ * @param type
+ * the type of data presented in field.
+ * @param uiContext
+ * the context where the Field is presented.
+ *
+ * @see com.vaadin.ui.FieldFactory#createField(Class, Component)
+ */
+ public Field createField(Class type, Component uiContext) {
+ // 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)) {
+ final Button button = new Button();
+ button.setSwitchMode(true);
+ button.setImmediate(false);
+ return button;
+ }
+
+ // Nested form is used by default
+ return new TextField();
+ }
+
+ /**
+ * Creates the field based on the datasource property.
+ *
+ * @see com.vaadin.ui.FieldFactory#createField(Property, Component)
+ */
+ public Field createField(Property property, Component uiContext) {
+ if (property != null) {
+ return createField(property.getType(), uiContext);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Creates the field based on the item and property id.
+ *
+ * @see com.vaadin.ui.FieldFactory#createField(Item, Object,
+ * Component)
+ */
+ public Field createField(Item item, Object propertyId, Component uiContext) {
+ if (item != null && propertyId != null) {
+ final Field f = createField(item.getItemProperty(propertyId),
+ uiContext);
+ if (f instanceof AbstractComponent) {
+ String name = propertyId.toString();
+ if (name.length() > 0) {
+
+ // If name follows method naming conventions, convert the
+ // name to spaced uppercased text. For example, convert
+ // "firstName" to "First Name"
+ 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();
+ }
+
+ ((AbstractComponent) f).setCaption(name);
+ }
+ }
+ return f;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @see com.vaadin.ui.FieldFactory#createField(com.vaadin.data.Container,
+ * java.lang.Object, java.lang.Object, com.vaadin.ui.Component)
+ */
+ public Field createField(Container container, Object itemId,
+ Object propertyId, Component uiContext) {
+ return createField(container.getContainerProperty(itemId, propertyId),
+ uiContext);
+ }
+
+}
diff --git a/src/com/vaadin/ui/Button.java b/src/com/vaadin/ui/Button.java
new file mode 100644
index 0000000000..872285f8fc
--- /dev/null
+++ b/src/com/vaadin/ui/Button.java
@@ -0,0 +1,350 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import com.vaadin.data.Property;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * A generic button component.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Button extends AbstractField {
+
+ /* Private members */
+
+ boolean switchMode = false;
+
+ /**
+ * Creates a new push button. The value of the push button is false and it
+ * is immediate by default.
+ *
+ */
+ public Button() {
+ setValue(new Boolean(false));
+ setSwitchMode(false);
+ }
+
+ /**
+ * Creates a new push button.
+ *
+ * The value of the push button is false and it is immediate by default.
+ *
+ * @param caption
+ * the Button caption.
+ */
+ public Button(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new push button with click listener.
+ *
+ * @param caption
+ * the Button caption.
+ * @param listener
+ * the Button click listener.
+ */
+ public Button(String caption, ClickListener listener) {
+ this(caption);
+ addListener(listener);
+ }
+
+ /**
+ * Creates a new push button with a method listening button clicks. Using
+ * this method is discouraged because it cannot be checked during
+ * compilation. Use
+ * {@link #Button(String, com.vaadin.ui.Button.ClickListener)}
+ * instead. The method must have either no parameters, or only one parameter
+ * of Button.ClickEvent type.
+ *
+ * @param caption
+ * the Button caption.
+ * @param target
+ * the Object having the method for listening button clicks.
+ * @param methodName
+ * the name of the method in target object, that receives button
+ * click events.
+ */
+ public Button(String caption, Object target, String methodName) {
+ this(caption);
+ addListener(ClickEvent.class, target, methodName);
+ }
+
+ /**
+ * Creates a new switch button with initial value.
+ *
+ * @param state
+ * the Initial state of the switch-button.
+ * @param initialState
+ */
+ public Button(String caption, boolean initialState) {
+ setCaption(caption);
+ setValue(new Boolean(initialState));
+ setSwitchMode(true);
+ }
+
+ /**
+ * Creates a new switch button that is connected to a boolean property.
+ *
+ * @param state
+ * the Initial state of the switch-button.
+ * @param dataSource
+ */
+ public Button(String caption, Property dataSource) {
+ setCaption(caption);
+ setSwitchMode(true);
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Gets component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "button";
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param event
+ * the PaintEvent.
+ * @throws IOException
+ * if the writing failed due to input/output error.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ if (isSwitchMode()) {
+ target.addAttribute("type", "switch");
+ }
+ target.addVariable(this, "state", booleanValue());
+ }
+
+ /**
+ * Invoked when the value of a variable has changed. Button listeners are
+ * notified if the button is clicked.
+ *
+ * @param source
+ * @param variables
+ */
+ @Override
+ public void changeVariables(Object source, Map variables) {
+ super.changeVariables(source, variables);
+
+ if (!isReadOnly() && variables.containsKey("state")) {
+ // Gets the new and old button states
+ final Boolean newValue = (Boolean) variables.get("state");
+ final Boolean oldValue = (Boolean) getValue();
+
+ if (isSwitchMode()) {
+
+ // For switch button, the event is only sent if the
+ // switch state is changed
+ if (newValue != null && !newValue.equals(oldValue)
+ && !isReadOnly()) {
+ setValue(newValue);
+ fireClick();
+ }
+ } else {
+
+ // Only send click event if the button is pushed
+ if (newValue.booleanValue()) {
+ fireClick();
+ }
+
+ // If the button is true for some reason, release it
+ if (oldValue.booleanValue()) {
+ setValue(new Boolean(false));
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if it is switchMode.
+ *
+ * @return <code>true</code> if it is in Switch Mode, otherwise
+ * <code>false</code>.
+ */
+ public boolean isSwitchMode() {
+ return switchMode;
+ }
+
+ /**
+ * Sets the switchMode.
+ *
+ * @param switchMode
+ * The switchMode to set.
+ */
+ public void setSwitchMode(boolean switchMode) {
+ this.switchMode = switchMode;
+ if (!switchMode) {
+ setImmediate(true);
+ if (booleanValue()) {
+ setValue(new Boolean(false));
+ }
+ }
+ }
+
+ /**
+ * Get the boolean value of the button state.
+ *
+ * @return True iff the button is pressed down or checked.
+ */
+ public boolean booleanValue() {
+ boolean state;
+ try {
+ state = ((Boolean) getValue()).booleanValue();
+ } catch (final NullPointerException e) {
+ state = false;
+ }
+ return state;
+ }
+
+ /**
+ * Sets immediate mode. Push buttons can not be set in non-immediate mode.
+ *
+ * @see com.vaadin.ui.AbstractComponent#setImmediate(boolean)
+ */
+ @Override
+ public void setImmediate(boolean immediate) {
+ // Push buttons are always immediate
+ super.setImmediate(!isSwitchMode() || immediate);
+ }
+
+ /**
+ * The type of the button as a property.
+ *
+ * @see com.vaadin.data.Property#getType()
+ */
+ @Override
+ public Class getType() {
+ return Boolean.class;
+ }
+
+ /* Click event */
+
+ private static final Method BUTTON_CLICK_METHOD;
+
+ /* Button style with no decorations. Looks like a link, acts like a button */
+ public static final String STYLE_LINK = "link";
+
+ static {
+ try {
+ BUTTON_CLICK_METHOD = ClickListener.class.getDeclaredMethod(
+ "buttonClick", new Class[] { ClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in Button");
+ }
+ }
+
+ /**
+ * Click event. This event is thrown, when the button is clicked.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class ClickEvent extends Component.Event {
+
+ /**
+ * New instance of text change event.
+ *
+ * @param source
+ * the Source of the event.
+ */
+ public ClickEvent(Component source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Button where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Button getButton() {
+ return (Button) getSource();
+ }
+ }
+
+ /**
+ * Button click listener
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ClickListener extends Serializable {
+
+ /**
+ * Button has been pressed.
+ *
+ * @param event
+ * Button click event.
+ */
+ 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, BUTTON_CLICK_METHOD);
+ }
+
+ /**
+ * Removes the button click listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(ClickListener listener) {
+ removeListener(ClickEvent.class, listener, BUTTON_CLICK_METHOD);
+ }
+
+ /**
+ * Emits the options change event.
+ */
+ protected void fireClick() {
+ fireEvent(new Button.ClickEvent(this));
+ }
+
+ @Override
+ protected void setInternalValue(Object newValue) {
+ // Make sure only booleans get through
+ if (!(newValue instanceof Boolean)) {
+ throw new IllegalArgumentException(getClass().getSimpleName()
+ + " only accepts Boolean values");
+ }
+ super.setInternalValue(newValue);
+ }
+
+}
diff --git a/src/com/vaadin/ui/CheckBox.java b/src/com/vaadin/ui/CheckBox.java
new file mode 100644
index 0000000000..32b47412a3
--- /dev/null
+++ b/src/com/vaadin/ui/CheckBox.java
@@ -0,0 +1,103 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.data.Property;
+
+@SuppressWarnings("serial")
+public class CheckBox extends Button {
+ /**
+ * Creates a new switch button.
+ */
+ public CheckBox() {
+ setSwitchMode(true);
+ }
+
+ /**
+ * Creates a new switch button with a caption and a set initial state.
+ *
+ * @param caption
+ * the caption of the switch button
+ * @param initialState
+ * the initial state of the switch button
+ */
+ public CheckBox(String caption, boolean initialState) {
+ super(caption, initialState);
+ }
+
+ /**
+ * Creates a new switch button with a caption and a click listener.
+ *
+ * @param caption
+ * the caption of the switch button
+ * @param listener
+ * the click listener
+ */
+ public CheckBox(String caption, ClickListener listener) {
+ super(caption, listener);
+ setSwitchMode(true);
+ }
+
+ /**
+ * Convenience method for creating a new switch button with a method
+ * listening button clicks. 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.
+ * The method must have either no parameters, or only one parameter of
+ * Button.ClickEvent type.
+ *
+ * @param caption
+ * the Button caption.
+ * @param target
+ * the Object having the method for listening button clicks.
+ * @param methodName
+ * the name of the method in target object, that receives button
+ * click events.
+ */
+ public CheckBox(String caption, Object target, String methodName) {
+ super(caption, target, methodName);
+ setSwitchMode(true);
+ }
+
+ /**
+ * Creates a new switch button that is connected to a boolean property.
+ *
+ * @param state
+ * the Initial state of the switch-button.
+ * @param dataSource
+ */
+ public CheckBox(String caption, Property dataSource) {
+ super(caption, dataSource);
+ setSwitchMode(true);
+ }
+
+ /**
+ * Creates a new push button with a set caption.
+ *
+ * The value of the push button is always false and they are immediate by
+ * default.
+ *
+ * @param caption
+ * the Button caption.
+ */
+
+ public CheckBox(String caption) {
+ super(caption, false);
+ }
+
+ @Override
+ public void setSwitchMode(boolean switchMode)
+ throws UnsupportedOperationException {
+ if (this.switchMode && !switchMode) {
+ throw new UnsupportedOperationException(
+ "CheckBox is always in switch mode (consider using a Button)");
+ }
+ super.setSwitchMode(true);
+ }
+
+}
diff --git a/src/com/vaadin/ui/ComboBox.java b/src/com/vaadin/ui/ComboBox.java
new file mode 100644
index 0000000000..68400986da
--- /dev/null
+++ b/src/com/vaadin/ui/ComboBox.java
@@ -0,0 +1,86 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * 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;
+
+ public ComboBox() {
+ setMultiSelect(false);
+ setNewItemsAllowed(false);
+ }
+
+ public ComboBox(String caption, Collection options) {
+ super(caption, options);
+ setMultiSelect(false);
+ setNewItemsAllowed(false);
+ }
+
+ public ComboBox(String caption, Container dataSource) {
+ super(caption, dataSource);
+ setMultiSelect(false);
+ setNewItemsAllowed(false);
+ }
+
+ public ComboBox(String caption) {
+ super(caption);
+ setMultiSelect(false);
+ setNewItemsAllowed(false);
+ }
+
+ @Override
+ public void setMultiSelect(boolean multiSelect) {
+ if (multiSelect && !isMultiSelect()) {
+ throw new UnsupportedOperationException("Multiselect not supported");
+ }
+ super.setMultiSelect(multiSelect);
+ }
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (inputPrompt != null) {
+ target.addAttribute("prompt", inputPrompt);
+ }
+ super.paintContent(target);
+ }
+
+}
diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java
new file mode 100644
index 0000000000..6b5ead80d2
--- /dev/null
+++ b/src/com/vaadin/ui/Component.java
@@ -0,0 +1,437 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.Locale;
+
+import com.vaadin.Application;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.Paintable;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Sizeable;
+import com.vaadin.terminal.VariableOwner;
+
+/**
+ * The top-level component interface which must be implemented by all UI
+ * components that use IT Mill Toolkit.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface Component extends Paintable, VariableOwner, Sizeable,
+ Serializable {
+
+ /**
+ * Gets style for component. Multiple styles are joined with spaces.
+ *
+ * @return the component's styleValue of property style.
+ */
+ public String getStyleName();
+
+ /**
+ * Sets and replaces all previous style names of the component. This method
+ * will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param style
+ * the new style of the component.
+ */
+ public void setStyleName(String style);
+
+ /**
+ * Adds style name to component. Handling additional style names is terminal
+ * specific, but in web browser environment they will most likely become CSS
+ * classes as given on server side.
+ *
+ * This method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param style
+ * the new style to be added to the component
+ */
+ public void addStyleName(String style);
+
+ /**
+ * Removes given style name from component.
+ *
+ * @param style
+ * the style to be removed
+ */
+ public void removeStyleName(String style);
+
+ /**
+ * <p>
+ * Tests if the component is enabled or not. All the variable change events
+ * are blocked from disabled components. Also the component should visually
+ * indicate that it is disabled (by shading the component for example). All
+ * hidden (isVisible() == false) components must return false.
+ * </p>
+ *
+ * <p>
+ * <b>Note</b> The component is considered disabled if it's parent is
+ * disabled.
+ * </p>
+ *
+ * <p>
+ * Components should be enabled by default.
+ * </p>
+ *
+ * @return <code>true</code> if the component, and it's parent, is enabled
+ * <code>false</code> otherwise.
+ * @see VariableOwner#isEnabled()
+ */
+ public boolean isEnabled();
+
+ /**
+ * Enables or disables the component. Being enabled means that the component
+ * can be edited. This method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * <p>
+ * <b>Note</b> that after enabling a component, {@link #isEnabled()} might
+ * still return false if the parent is disabled.
+ * </p>
+ *
+ * <p>
+ * <b>Also note</b> that if the component contains child-components, it
+ * should recursively call requestRepaint() for all descendant components.
+ * </p>
+ *
+ * @param enabled
+ * the boolean value specifying if the component should be
+ * enabled after the call or not
+ */
+ public void setEnabled(boolean enabled);
+
+ /**
+ * Tests the components visibility. Visibility defines if the component is
+ * drawn when updating UI. Default is <code>true</code>.
+ *
+ * <p>
+ * <b>Note</b> that to return true, this component and all its parents must
+ * be visible.
+ *
+ * <p>
+ * <b>Also note</b> that this method does not check if component is attached
+ * and shown to user. Component and all its parents may be visible, but not
+ * necessary attached to application. To test if component will be drawn,
+ * check its visibility and that {@link Component#getApplication()} does not
+ * return <code>null</code>.
+ *
+ * @return <code>true</code> if the component is visible in the UI,
+ * <code>false</code> if not
+ */
+ public boolean isVisible();
+
+ /**
+ * Sets this components visibility status. Visibility defines if the
+ * component is shown in the UI or not.
+ * <p>
+ * <b>Note</b> that to be shown in UI this component and all its parents
+ * must be visible.
+ *
+ * @param visible
+ * the boolean value specifying if the component should be
+ * visible after the call or not.
+ */
+ public void setVisible(boolean visible);
+
+ /**
+ * Gets the visual parent of the component. The components can be nested but
+ * one component can have only one parent.
+ *
+ * @return the parent component.
+ */
+ public Component getParent();
+
+ /**
+ * Sets the component's parent component.
+ *
+ * <p>
+ * This method calls automatically {@link #attach()} if the parent is
+ * attached to a window (or is itself a window}, and {@link #detach()} if
+ * <code>parent</code> is set <code>null</code>, but the component was in
+ * the application.
+ * </p>
+ *
+ * <p>
+ * This method is rarely called directly. Instead the
+ * {@link ComponentContainer#addComponent(Component)} method is used to add
+ * components to container, which call this method implicitly.
+ *
+ * @param parent
+ * the new parent component.
+ */
+ public void setParent(Component parent);
+
+ /**
+ * Tests if the component is in read-only mode.
+ *
+ * @return <code>true</code> if the component is in read-only mode,
+ * <code>false</code> if not.
+ */
+ public boolean isReadOnly();
+
+ /**
+ * Sets the component's to read-only mode to the specified state. This
+ * method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param readOnly
+ * the boolean value specifying if the component should be in
+ * read-only mode after the call or not.
+ */
+ public void setReadOnly(boolean readOnly);
+
+ /**
+ * Gets the caption of the component. Caption is the visible name of the
+ * component.
+ *
+ * @return the component's caption <code>String</code>.
+ */
+ public String getCaption();
+
+ /**
+ * Sets the component's caption <code>String</code>. Caption is the visible
+ * name of the component. This method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param caption
+ * the new caption <code>String</code> for the component.
+ */
+ public void setCaption(String caption);
+
+ /**
+ * Gets the component's icon. A component may have a graphical icon
+ * associated with it, this method retrieves it if it is defined.
+ *
+ * @return the component's icon or <code>null</code> if it not defined.
+ */
+ public Resource getIcon();
+
+ /**
+ * Sets the component's icon. This method will trigger a
+ * {@link com.vaadin.terminal.Paintable.RepaintRequestEvent
+ * RepaintRequestEvent}.
+ *
+ * @param icon
+ * the icon to be shown with the component's caption.
+ */
+ public void setIcon(Resource icon);
+
+ /**
+ * Gets the component's parent window. If the component does not yet belong
+ * to a window <code>null</code> is returned.
+ *
+ * @return the parent window of the component or <code>null</code>.
+ */
+ public Window getWindow();
+
+ /**
+ * Gets the component's parent application. If the component does not yet
+ * belong to a application <code>null</code> is returned.
+ *
+ * @return the parent application of the component or <code>null</code>.
+ */
+ public Application getApplication();
+
+ /**
+ * <p>
+ * Notifies the component that it is connected to an application. This
+ * method is always called before the component is first time painted and is
+ * suitable to be extended. The <code>getApplication</code> and
+ * <code>getWindow</code> methods might return <code>null</code> before this
+ * method is called.
+ * </p>
+ *
+ * <p>
+ * The caller of this method is {@link #setParent(Component)} if the parent
+ * is already in the application. If the parent is not in the application,
+ * it must call the {@link #attach()} for all its children when it will be
+ * added to the application.
+ * </p>
+ */
+ public void attach();
+
+ /**
+ * Notifies the component that it is detached from the application.
+ * <p>
+ * The {@link #getApplication()} and {@link #getWindow()} methods might
+ * return <code>null</code> after this method is called.
+ * </p>
+ *
+ * <p>
+ * The caller of this method is {@link #setParent(Component)} if the parent
+ * is in the application. When the parent is detached from the application
+ * it is its response to call {@link #detach()} for all the children and to
+ * detach itself from the terminal.
+ * </p>
+ */
+ public void detach();
+
+ /**
+ * Gets the locale of this component.
+ *
+ * @return This component's locale. If this component does not have a
+ * locale, the locale of its parent is returned. Eventually locale
+ * of application is returned. If application does not have its own
+ * locale the locale is determined by
+ * <code>Locale.getDefautlt</code>. Returns null if the component
+ * does not have its own locale and has not yet been added to a
+ * containment hierarchy such that the locale can be determined from
+ * the containing parent.
+ */
+ public Locale getLocale();
+
+ /**
+ * The children must call this method when they need repainting. The call
+ * must be made event in the case the children sent the repaint request
+ * themselves.
+ *
+ * @param alreadyNotified
+ * the collection of repaint request listeners that have been
+ * already notified by the child. This component should not
+ * renotify the listed listeners again. The container given as
+ * parameter must be modifiable as the component might modify it
+ * and pass it forwards. Null parameter is interpreted as empty
+ * collection.
+ */
+ public void childRequestedRepaint(Collection alreadyNotified);
+
+ /* Component event framework */
+
+ /**
+ * Superclass of all component originated <code>Event</code>s.
+ */
+ public class Event extends EventObject {
+
+ /**
+ * Constructs a new event with a specified source component.
+ *
+ * @param source
+ * the source component of the event.
+ */
+ public Event(Component source) {
+ super(source);
+ }
+ }
+
+ /**
+ * Listener interface for receiving <code>Component.Event</code>s.
+ */
+ public interface Listener extends EventListener, Serializable {
+
+ /**
+ * Notifies the listener of a component event.
+ *
+ * @param event
+ * the event that has occured.
+ */
+ public void componentEvent(Component.Event event);
+ }
+
+ /**
+ * Registers a new component event listener for this component.
+ *
+ * @param listener
+ * the new Listener to be registered.
+ */
+ public void addListener(Component.Listener listener);
+
+ /**
+ * Removes a previously registered component event listener from this
+ * component.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ public void removeListener(Component.Listener listener);
+
+ /**
+ * Class of all component originated <code>ErrorEvent</code>s.
+ */
+ @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);
+ }
+
+ /**
+ * Interface implemented by components which can obtain input focus.
+ */
+ public interface Focusable extends Component {
+
+ /**
+ * Sets the focus to this component.
+ */
+ public void focus();
+
+ /**
+ * Gets the Tabulator index of this Focusable component.
+ *
+ * @return tab index set for this Focusable component
+ */
+ public int getTabIndex();
+
+ /**
+ * Sets the tab index of this field. The tab index property is used to
+ * specify the natural tab order of fields.
+ *
+ * @param tabIndex
+ * the tab order of this component. Indexes usually start
+ * from 1. Negative value means that field is not wanted to
+ * tabbing sequence.
+ */
+ public void setTabIndex(int tabIndex);
+
+ }
+}
diff --git a/src/com/vaadin/ui/ComponentContainer.java b/src/com/vaadin/ui/ComponentContainer.java
new file mode 100644
index 0000000000..aa591c9742
--- /dev/null
+++ b/src/com/vaadin/ui/ComponentContainer.java
@@ -0,0 +1,232 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+/**
+ * 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface ComponentContainer extends Component {
+
+ /**
+ * 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 added.
+ */
+ 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 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.
+ */
+ public Iterator getComponentIterator();
+
+ /**
+ * Causes a repaint of this component, and all components below it.
+ *
+ * This should only be used in special cases, e.g when the state of a
+ * descendant depends on the state of a ancestor.
+ *
+ */
+ public void requestRepaintAll();
+
+ /**
+ * 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/src/com/vaadin/ui/CustomComponent.java b/src/com/vaadin/ui/CustomComponent.java
new file mode 100644
index 0000000000..19fb081047
--- /dev/null
+++ b/src/com/vaadin/ui/CustomComponent.java
@@ -0,0 +1,225 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class CustomComponent extends AbstractComponentContainer {
+
+ /**
+ * The root component implementing the custom component.
+ */
+ private Component root = null;
+
+ /**
+ * Type of the component.
+ */
+ private String componentType = 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 final 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 final 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 ------------------------------------------ */
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (root == null) {
+ throw new IllegalStateException("Composition root must be set to"
+ + " non-null value before the " + getClass().getName()
+ + " can be painted");
+ }
+
+ if (getComponentType() != null) {
+ target.addAttribute("type", getComponentType());
+ }
+ root.paint(target);
+ }
+
+ /**
+ * Gets the component type.
+ *
+ * The component type is textual type of the component. This is included in
+ * the UIDL as component tag attribute.
+ *
+ * @return the component type.
+ */
+ public String getComponentType() {
+ return componentType;
+ }
+
+ /**
+ * Sets the component type.
+ *
+ * The component type is textual type of the component. This is included in
+ * the UIDL as component tag attribute.
+ *
+ * @param componentType
+ * the componentType to set.
+ */
+ public void setComponentType(String componentType) {
+ this.componentType = componentType;
+ }
+
+ @Override
+ public String getTag() {
+ return "customcomponent";
+ }
+
+ private class ComponentIterator implements Iterator, Serializable {
+ boolean first = getCompositionRoot() != null;
+
+ public boolean hasNext() {
+ return first;
+ }
+
+ public Object next() {
+ first = false;
+ return root;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public Iterator getComponentIterator() {
+ return new ComponentIterator();
+ }
+
+ /**
+ * This method is not supported by CustomComponent.
+ *
+ * @see com.vaadin.ui.ComponentContainer#replaceComponent(com.vaadin.ui.Component,
+ * com.vaadin.ui.Component)
+ */
+ 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/src/com/vaadin/ui/CustomLayout.java b/src/com/vaadin/ui/CustomLayout.java
new file mode 100644
index 0000000000..6ec7986a29
--- /dev/null
+++ b/src/com/vaadin/ui/CustomLayout.java
@@ -0,0 +1,310 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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 com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * <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 Toolkit 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class CustomLayout extends AbstractLayout {
+
+ private static final int BUFFER_SIZE = 10000;
+
+ /**
+ * Custom layout slots containing the components.
+ */
+ private final HashMap slots = new HashMap();
+
+ private String templateContents = null;
+
+ private String templateName = null;
+
+ /**
+ * 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 {
+
+ InputStreamReader reader = new InputStreamReader(templateStream,
+ "UTF-8");
+ StringBuffer b = new StringBuffer(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;
+ }
+ }
+
+ templateContents = b.toString();
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Constructor for custom layout with given template name. Template file is
+ * fetched from "<theme>/layout/<templateName>".
+ */
+ public CustomLayout(String template) {
+ templateName = template;
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "customlayout";
+ }
+
+ /**
+ * 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 = (Component) slots.get(location);
+ if (old != null) {
+ removeComponent(old);
+ }
+ slots.put(location, c);
+ 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);
+ c.setParent(null);
+ fireComponentDetachEvent(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((Component) 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.
+ */
+ public Iterator getComponentIterator() {
+ return slots.values().iterator();
+ }
+
+ /**
+ * 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 (Component) slots.get(location);
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ if (templateName != null) {
+ target.addAttribute("template", templateName);
+ } else {
+ target.addAttribute("templateContents", templateContents);
+ }
+ // Adds all items in all the locations
+ for (final Iterator i = slots.keySet().iterator(); i.hasNext();) {
+ // Gets the (location,component)
+ final String location = (String) i.next();
+ final Component c = (Component) slots.get(location);
+ if (c != null) {
+ // Writes the item
+ target.startTag("location");
+ target.addAttribute("name", location);
+ c.paint(target);
+ target.endTag("location");
+ }
+ }
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ // Gets the locations
+ String oldLocation = null;
+ String newLocation = null;
+ for (final Iterator i = slots.keySet().iterator(); i.hasNext();) {
+ final String location = (String) i.next();
+ final Component 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);
+ requestRepaint();
+ }
+ }
+
+ /**
+ * CustomLayout's template selecting was previously implemented with
+ * setStyle. Overriding to improve backwards compatibility.
+ *
+ * @param name
+ * template name
+ */
+ @Override
+ public void setStyle(String name) {
+ setTemplateName(name);
+ }
+
+ /** Get the name of the template */
+ public String getTemplateName() {
+ return templateName;
+ }
+
+ /**
+ * Set the name of the template used to draw custom layout.
+ *
+ * With GWT-adapter, the template with name 'templatename' is loaded from
+ * ITMILL/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) {
+ this.templateName = templateName;
+ templateContents = 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.");
+ }
+
+}
diff --git a/src/com/vaadin/ui/DateField.java b/src/com/vaadin/ui/DateField.java
new file mode 100644
index 0000000000..fc88187c06
--- /dev/null
+++ b/src/com/vaadin/ui/DateField.java
@@ -0,0 +1,527 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+import com.vaadin.data.Property;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * <p>
+ * A date editor component that can be bound to any bindable 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. 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class DateField extends AbstractField {
+
+ /* Private members */
+
+ /**
+ * Resolution identifier: milliseconds.
+ */
+ public static final int RESOLUTION_MSEC = 0;
+
+ /**
+ * Resolution identifier: seconds.
+ */
+ public static final int RESOLUTION_SEC = 1;
+
+ /**
+ * Resolution identifier: minutes.
+ */
+ public static final int RESOLUTION_MIN = 2;
+
+ /**
+ * Resolution identifier: hours.
+ */
+ public static final int RESOLUTION_HOUR = 3;
+
+ /**
+ * Resolution identifier: days.
+ */
+ public static final int RESOLUTION_DAY = 4;
+
+ /**
+ * Resolution identifier: months.
+ */
+ public static final int RESOLUTION_MONTH = 5;
+
+ /**
+ * Resolution identifier: years.
+ */
+ public static final int RESOLUTION_YEAR = 6;
+
+ /**
+ * Popup date selector (calendar).
+ */
+ protected static final String TYPE_POPUP = "popup";
+
+ /**
+ * Inline date selector (calendar).
+ */
+ protected static final String TYPE_INLINE = "inline";
+
+ /**
+ * Specified widget type.
+ */
+ protected String type = TYPE_POPUP;
+
+ /**
+ * Specified smallest modifiable unit.
+ */
+ private int resolution = RESOLUTION_MSEC;
+
+ /**
+ * Specified largest modifiable unit.
+ */
+ private static final int largestModifiable = RESOLUTION_YEAR;
+
+ /**
+ * The internal calendar to be used in java.utl.Date conversions.
+ */
+ private Calendar calendar;
+
+ /**
+ * Overridden format string
+ */
+ private String dateFormat;
+
+ /**
+ * Read-only content of an ITextualDate field - null for other types of date
+ * fields.
+ */
+ private String dateString;
+
+ /* 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 {
+ super.paintContent(target);
+
+ // Adds the locale as attribute
+ final Locale l = getLocale();
+ if (l != null) {
+ target.addAttribute("locale", l.toString());
+ }
+
+ if (getDateFormat() != null) {
+ target.addAttribute("format", dateFormat);
+ }
+
+ target.addAttribute("type", type);
+
+ // Gets the calendar
+ final Calendar calendar = getCalendar();
+ final Date currentDate = (Date) getValue();
+
+ for (int r = resolution; r <= largestModifiable; r++) {
+ switch (r) {
+ case RESOLUTION_MSEC:
+ target.addVariable(this, "msec", currentDate != null ? calendar
+ .get(Calendar.MILLISECOND) : -1);
+ break;
+ case RESOLUTION_SEC:
+ target.addVariable(this, "sec", currentDate != null ? calendar
+ .get(Calendar.SECOND) : -1);
+ break;
+ case RESOLUTION_MIN:
+ target.addVariable(this, "min", currentDate != null ? calendar
+ .get(Calendar.MINUTE) : -1);
+ break;
+ case RESOLUTION_HOUR:
+ target.addVariable(this, "hour", currentDate != null ? calendar
+ .get(Calendar.HOUR_OF_DAY) : -1);
+ break;
+ case RESOLUTION_DAY:
+ target.addVariable(this, "day", currentDate != null ? calendar
+ .get(Calendar.DAY_OF_MONTH) : -1);
+ break;
+ case RESOLUTION_MONTH:
+ target.addVariable(this, "month",
+ currentDate != null ? calendar.get(Calendar.MONTH) + 1
+ : -1);
+ break;
+ case RESOLUTION_YEAR:
+ target.addVariable(this, "year", currentDate != null ? calendar
+ .get(Calendar.YEAR) : -1);
+ break;
+ }
+ }
+ }
+
+ /*
+ * Gets the components UIDL tag string. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ @Override
+ public String getTag() {
+ return "datefield";
+ }
+
+ /*
+ * 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 variables) {
+ super.changeVariables(source, 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 = (Date) getValue();
+ final String oldDateString = dateString;
+ Date newDate = null;
+
+ // this enables analyzing invalid input on the server
+ Object o = variables.get("dateString");
+ if (o != null) {
+ dateString = o.toString();
+ } else {
+ dateString = null;
+ }
+
+ // Gets the new date in parts
+ // Null values are converted to negative values.
+ int year = variables.containsKey("year") ? (variables.get("year") == null ? -1
+ : ((Integer) variables.get("year")).intValue())
+ : -1;
+ int month = variables.containsKey("month") ? (variables
+ .get("month") == null ? -1 : ((Integer) variables
+ .get("month")).intValue() - 1) : -1;
+ int day = variables.containsKey("day") ? (variables.get("day") == null ? -1
+ : ((Integer) variables.get("day")).intValue())
+ : -1;
+ int hour = variables.containsKey("hour") ? (variables.get("hour") == null ? -1
+ : ((Integer) variables.get("hour")).intValue())
+ : -1;
+ int min = variables.containsKey("min") ? (variables.get("min") == null ? -1
+ : ((Integer) variables.get("min")).intValue())
+ : -1;
+ int sec = variables.containsKey("sec") ? (variables.get("sec") == null ? -1
+ : ((Integer) variables.get("sec")).intValue())
+ : -1;
+ int msec = variables.containsKey("msec") ? (variables.get("msec") == null ? -1
+ : ((Integer) variables.get("msec")).intValue())
+ : -1;
+
+ // If all of the components is < 0 use the previous value
+ if (year < 0 && month < 0 && day < 0 && hour < 0 && min < 0
+ && sec < 0 && msec < 0) {
+ newDate = null;
+ } else {
+
+ // Clone the calendar for date operation
+ final Calendar cal = getCalendar();
+
+ // Make sure that meaningful values exists
+ // Use the previous value if some of the variables
+ // have not been changed.
+ year = year < 0 ? cal.get(Calendar.YEAR) : year;
+ month = month < 0 ? cal.get(Calendar.MONTH) : month;
+ day = day < 0 ? cal.get(Calendar.DAY_OF_MONTH) : day;
+ hour = hour < 0 ? cal.get(Calendar.HOUR_OF_DAY) : hour;
+ min = min < 0 ? cal.get(Calendar.MINUTE) : min;
+ sec = sec < 0 ? cal.get(Calendar.SECOND) : sec;
+ msec = msec < 0 ? cal.get(Calendar.MILLISECOND) : msec;
+
+ // Sets the calendar fields
+ cal.set(Calendar.YEAR, year);
+ cal.set(Calendar.MONTH, month);
+ cal.set(Calendar.DAY_OF_MONTH, day);
+ cal.set(Calendar.HOUR_OF_DAY, hour);
+ cal.set(Calendar.MINUTE, min);
+ cal.set(Calendar.SECOND, sec);
+ cal.set(Calendar.MILLISECOND, msec);
+
+ // Assigns the date
+ newDate = cal.getTime();
+ }
+
+ if (newDate != oldDate
+ && (newDate == null || !newDate.equals(oldDate))) {
+ setValue(newDate, true); // Don't require a repaint, client
+ // updates itself
+ } else if (dateString != null && !"".equals(dateString)
+ && !dateString.equals(oldDateString)) {
+ setValue(handleUnparsableDateString(dateString));
+ }
+ }
+ }
+
+ /**
+ * This method is called to handle the date string from the client if the
+ * client could not parse it as a Date.
+ *
+ * By default, null is returned. If an exception is thrown, the current
+ * value will not be modified.
+ *
+ * This can be overridden to handle conversions or to throw an exception, or
+ * to fire an event.
+ *
+ * The default behavior is likely to change in the next major version of the
+ * toolkit - a Property.ConversionException will be thrown.
+ *
+ * @param dateString
+ * @return parsed Date
+ * @throws Property.ConversionException
+ * to keep the old value and indicate an error
+ */
+ protected Date handleUnparsableDateString(String dateString)
+ throws Property.ConversionException {
+ // TODO in the next major version, this should throw an exception to be
+ // consistent with other fields
+ // throw new Property.ConversionException();
+ return null;
+ }
+
+ /* 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 getType() {
+ return Date.class;
+ }
+
+ /*
+ * Returns the value of the property in human readable textual format. Don't
+ * add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public String toString() {
+ final Date value = (Date) getValue();
+ if (value != null) {
+ return value.toString();
+ }
+ return null;
+ }
+
+ /*
+ * Sets the value of the property. Don't add a JavaDoc comment here, we use
+ * the default documentation from implemented interface.
+ */
+ @Override
+ public void setValue(Object newValue) throws Property.ReadOnlyException,
+ Property.ConversionException {
+ setValue(newValue, false);
+ }
+
+ @Override
+ public void setValue(Object newValue, boolean repaintIsNotNeeded)
+ throws Property.ReadOnlyException, Property.ConversionException {
+
+ // Allows setting dates directly
+ if (newValue == null || newValue instanceof Date) {
+ super.setValue(newValue, repaintIsNotNeeded);
+ } else {
+
+ // Try to parse as string
+ try {
+ final SimpleDateFormat parser = new SimpleDateFormat();
+ final Date val = parser.parse(newValue.toString());
+ super.setValue(val, repaintIsNotNeeded);
+ } catch (final ParseException e) {
+ throw new Property.ConversionException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Sets the DateField datasource. Datasource type must assignable to Date.
+ *
+ * @see com.vaadin.data.Property.Viewer#setPropertyDataSource(Property)
+ */
+ @Override
+ public void setPropertyDataSource(Property newDataSource) {
+ if (newDataSource == null
+ || Date.class.isAssignableFrom(newDataSource.getType())) {
+ super.setPropertyDataSource(newDataSource);
+ } else {
+ throw new IllegalArgumentException(
+ "DateField only supports Date properties");
+ }
+ }
+
+ /**
+ * Gets the resolution.
+ *
+ * @return int
+ */
+ public int getResolution() {
+ return resolution;
+ }
+
+ /**
+ * Sets the resolution of the DateField.
+ *
+ * @param resolution
+ * the resolution to set.
+ */
+ public void setResolution(int resolution) {
+ this.resolution = resolution;
+ }
+
+ /**
+ * 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();
+ }
+
+ // Clone the instance
+ final Calendar newCal = (Calendar) calendar.clone();
+
+ // Assigns the current time tom calendar.
+ final Date currentDate = (Date) getValue();
+ if (currentDate != null) {
+ newCal.setTime(currentDate);
+ }
+
+ 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 Toolkit issue #2200.
+ *
+ * @param dateFormat
+ * the dateFormat to set
+ *
+ * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
+ */
+ public void setDateFormat(String dateFormat) {
+ this.dateFormat = dateFormat;
+ }
+
+ /**
+ * Reterns 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;
+ }
+
+}
diff --git a/src/com/vaadin/ui/Embedded.java b/src/com/vaadin/ui/Embedded.java
new file mode 100644
index 0000000000..3b0acfee5e
--- /dev/null
+++ b/src/com/vaadin/ui/Embedded.java
@@ -0,0 +1,425 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+
+/**
+ * Component for embedding external objects.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Embedded extends AbstractComponent {
+
+ /**
+ * 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 parameteres.
+ */
+ private final Hashtable parameters = new Hashtable();
+
+ /**
+ * Applet or other client side runnable properties.
+ */
+ private String codebase = null;
+
+ private String codetype = null;
+
+ private String classId = null;
+
+ private String archive = null;
+
+ /**
+ * Creates a new empty Embedded object.
+ */
+ public Embedded() {
+ }
+
+ /**
+ * Creates a new empty Embedded object with caption.
+ *
+ * @param caption
+ */
+ public Embedded(String caption) {
+ 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) {
+ setCaption(caption);
+ setSource(source);
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "embedded";
+ }
+
+ /**
+ * 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);
+ }
+
+ // Params
+ for (final Iterator i = getParameterNames(); i.hasNext();) {
+ target.startTag("embeddedparam");
+ final String key = (String) i.next();
+ target.addAttribute("name", key);
+ target.addAttribute("value", getParameter(key));
+ target.endTag("embeddedparam");
+ }
+ }
+
+ /**
+ * 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 (String) 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 getParameterNames() {
+ return parameters.keySet().iterator();
+ }
+
+ /**
+ * Gets the codebase, the root-path used to access resources with relative
+ * paths.
+ *
+ * @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;
+ }
+
+ /**
+ * Gets the standby text displayed when the object is loading.
+ *
+ * @return the standby text.
+ */
+ public String getStandby() {
+ return standby;
+ }
+
+ /**
+ * Sets the codebase, the root-path used to access resources with relative
+ * paths.
+ *
+ * @param codebase
+ * the codebase to set.
+ */
+ public void setCodebase(String codebase) {
+ if (codebase != this.codebase
+ || (codebase != null && !codebase.equals(this.codebase))) {
+ this.codebase = codebase;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets the codetype, the MIME-Type of the code.
+ *
+ * @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;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets the standby, the text to display while loading the object.
+ *
+ * @param standby
+ * the standby to set.
+ */
+ public void setStandby(String standby) {
+ if (standby != this.standby
+ || (standby != null && !standby.equals(this.standby))) {
+ this.standby = standby;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the classId attribute.
+ *
+ * @return the class id.
+ */
+ public String getClassId() {
+ return classId;
+ }
+
+ /**
+ * Sets the classId attribute.
+ *
+ * @param classId
+ * the classId to set.
+ */
+ public void setClassId(String classId) {
+ if (classId != this.classId
+ || (classId != null && !classId.equals(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
+ * </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();
+ }
+ }
+
+ /**
+ * Gets the archive attribute.
+ *
+ * @return the archive attribute.
+ */
+ public String getArchive() {
+ return archive;
+ }
+
+ /**
+ * Sets the archive attribute.
+ *
+ * @param archive
+ * the archive string to set.
+ */
+ public void setArchive(String archive) {
+ if (archive != this.archive
+ || (archive != null && !archive.equals(this.archive))) {
+ this.archive = archive;
+ requestRepaint();
+ }
+ }
+
+}
diff --git a/src/com/vaadin/ui/ExpandLayout.java b/src/com/vaadin/ui/ExpandLayout.java
new file mode 100644
index 0000000000..7f988a47c2
--- /dev/null
+++ b/src/com/vaadin/ui/ExpandLayout.java
@@ -0,0 +1,101 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+/**
+ * A layout that will give one of it's components as much space as possible,
+ * while still showing the other components in the layout. The other components
+ * will in effect be given a fixed sized space, while the space given to the
+ * expanded component will grow/shrink to fill the rest of the space available -
+ * for instance when re-sizing the window.
+ *
+ * Note that this layout is 100% in both directions by default ({link
+ * {@link #setSizeFull()}). Remember to set the units if you want to specify a
+ * fixed size. If the layout fails to show up, check that the parent layout is
+ * actually giving some space.
+ *
+ * @deprecated Deprecated in favor of the new OrderedLayout
+ */
+@SuppressWarnings("serial")
+@Deprecated
+public class ExpandLayout extends OrderedLayout {
+
+ private Component expanded = null;
+
+ public ExpandLayout() {
+ this(ORIENTATION_VERTICAL);
+ }
+
+ public ExpandLayout(int orientation) {
+ super(orientation);
+
+ setSizeFull();
+ }
+
+ /**
+ * @param c
+ * Component which container will be maximized
+ */
+ public void expand(Component c) {
+ if (expanded != null) {
+ try {
+ setExpandRatio(expanded, 0.0f);
+ } catch (IllegalArgumentException e) {
+ // Ignore error if component has been removed
+ }
+ }
+
+ expanded = c;
+ if (expanded != null) {
+ setExpandRatio(expanded, 1.0f);
+ }
+
+ requestRepaint();
+ }
+
+ @Override
+ public void addComponent(Component c, int index) {
+ super.addComponent(c, index);
+ if (expanded == null) {
+ expand(c);
+ }
+ }
+
+ @Override
+ public void addComponent(Component c) {
+ super.addComponent(c);
+ if (expanded == null) {
+ expand(c);
+ }
+ }
+
+ @Override
+ public void addComponentAsFirst(Component c) {
+ super.addComponentAsFirst(c);
+ if (expanded == null) {
+ expand(c);
+ }
+ }
+
+ @Override
+ public void removeComponent(Component c) {
+ super.removeComponent(c);
+ if (c == expanded) {
+ if (getComponentIterator().hasNext()) {
+ expand((Component) getComponentIterator().next());
+ } else {
+ expand(null);
+ }
+ }
+ }
+
+ @Override
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ super.replaceComponent(oldComponent, newComponent);
+ if (oldComponent == expanded) {
+ expand(newComponent);
+ }
+ }
+}
diff --git a/src/com/vaadin/ui/Field.java b/src/com/vaadin/ui/Field.java
new file mode 100644
index 0000000000..72dc3130c3
--- /dev/null
+++ b/src/com/vaadin/ui/Field.java
@@ -0,0 +1,105 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.data.BufferedValidatable;
+import com.vaadin.data.Property;
+import com.vaadin.ui.Component.Focusable;
+
+/**
+ * @author IT Mill Ltd.
+ *
+ */
+public interface Field extends Component, BufferedValidatable, Property,
+ Property.ValueChangeNotifier, Property.ValueChangeListener,
+ Property.Editor, Focusable {
+
+ /**
+ * Sets the Caption.
+ *
+ * @param caption
+ */
+ void setCaption(String caption);
+
+ String getDescription();
+
+ /**
+ * Sets the Description.
+ *
+ * @param caption
+ */
+ void setDescription(String caption);
+
+ /**
+ * 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public 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.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+}
diff --git a/src/com/vaadin/ui/FieldFactory.java b/src/com/vaadin/ui/FieldFactory.java
new file mode 100644
index 0000000000..f0f96386e3
--- /dev/null
+++ b/src/com/vaadin/ui/FieldFactory.java
@@ -0,0 +1,76 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * Factory for creating new Field-instances based on type, datasource and/or
+ * context.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.1
+ */
+public interface FieldFactory extends Serializable {
+
+ /**
+ * Creates a field based on type of data.
+ *
+ * @param type
+ * the type of data presented in field.
+ * @param uiContext
+ * the component where the field is presented.
+ * @return Field the field suitable for editing the specified data.
+ *
+ */
+ Field createField(Class type, Component uiContext);
+
+ /**
+ * Creates a field based on the property datasource.
+ *
+ * @param property
+ * the property datasource.
+ * @param uiContext
+ * the component where the field is presented.
+ * @return Field the field suitable for editing the specified data.
+ */
+ Field createField(Property property, Component uiContext);
+
+ /**
+ * Creates a field based on the item and property id.
+ *
+ * @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.
+ * @return Field the field suitable for editing the specified data.
+ */
+ Field createField(Item item, Object propertyId, Component uiContext);
+
+ /**
+ * Creates a field based on the container item id and property id.
+ *
+ * @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 Field the field suitable for editing the specified data.
+ */
+ Field createField(Container container, Object itemId, Object propertyId,
+ Component uiContext);
+
+} \ No newline at end of file
diff --git a/src/com/vaadin/ui/Form.java b/src/com/vaadin/ui/Form.java
new file mode 100644
index 0000000000..3922028af0
--- /dev/null
+++ b/src/com/vaadin/ui/Form.java
@@ -0,0 +1,1179 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+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.util.BeanItem;
+import com.vaadin.terminal.CompositeErrorMessage;
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Form extends AbstractField implements Item.Editor, Buffered, Item,
+ Validatable {
+
+ private Object propertyValue;
+
+ /**
+ * Layout of the form.
+ */
+ private Layout layout;
+
+ /**
+ * Item connected to this form as datasource.
+ */
+ private Item itemDatasource;
+
+ /**
+ * Ordered list of property ids in this editor.
+ */
+ private final LinkedList propertyIds = new LinkedList();
+
+ /**
+ * 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 fields = new HashMap();
+
+ /**
+ * Field factory for this form.
+ */
+ private FieldFactory fieldFactory;
+
+ /**
+ * Visible item properties.
+ */
+ private Collection visibleItemProperties;
+
+ /**
+ * Form needs to repaint itself if child fields value changes due possible
+ * change in form validity.
+ */
+ private final ValueChangeListener fieldValueChangeListener = new ValueChangeListener() {
+ public void valueChange(
+ com.vaadin.data.Property.ValueChangeEvent event) {
+ requestRepaint();
+ }
+ };
+
+ private Layout formFooter;
+
+ /**
+ * 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;
+
+ /**
+ * Contructs a new form with default layout.
+ *
+ * <p>
+ * By default the form uses <code>OrderedLayout</code> with
+ * <code>form</code>-style.
+ * </p>
+ *
+ * @param formLayout
+ * the layout of the form.
+ */
+ public Form() {
+ this(null);
+ setValidationVisible(false);
+ }
+
+ /**
+ * Contructs a new form with given layout.
+ *
+ * @param formLayout
+ * the layout of the form.
+ */
+ public Form(Layout formLayout) {
+ this(formLayout, new BaseFieldFactory());
+ }
+
+ /**
+ * Contructs a new form with given layout and FieldFactory.
+ *
+ * @param formLayout
+ * the layout of the form.
+ * @param fieldFactory
+ * the FieldFactory of the form.
+ */
+ public Form(Layout formLayout, FieldFactory fieldFactory) {
+ super();
+ setLayout(formLayout);
+ setFieldFactory(fieldFactory);
+ setValidationVisible(false);
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /* Documented in interface */
+ @Override
+ public String getTag() {
+ return "form";
+ }
+
+ /* Documented in interface */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ layout.paint(target);
+ if (formFooter != null) {
+ formFooter.paint(target);
+ }
+ }
+
+ /**
+ * 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 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 Validator.InvalidValueException(
+ 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 Validator.InvalidValueException(
+ 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,
+ 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 {
+
+ LinkedList 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 i = propertyIds.iterator(); i.hasNext();) {
+ try {
+ final Field f = ((Field) 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();
+ }
+ 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 i = problems.iterator(); i.hasNext();) {
+ causes[index++] = (Throwable) 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 problems = null;
+
+ // Try to discard all changes
+ for (final Iterator i = propertyIds.iterator(); i.hasNext();) {
+ try {
+ ((Field) fields.get(i.next())).discard();
+ } catch (final Buffered.SourceException e) {
+ if (problems == null) {
+ problems = new LinkedList();
+ }
+ 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 i = problems.iterator(); i.hasNext();) {
+ causes[index++] = (Throwable) 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 i = propertyIds.iterator(); i.hasNext();) {
+ final Field f = (Field) 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
+ 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
+ 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 i = propertyIds.iterator(); i.hasNext();) {
+ ((Field) 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 i = propertyIds.iterator(); i.hasNext();) {
+ ((Field) fields.get(i.next())).setWriteThrough(writeThrough);
+ }
+ }
+ }
+
+ /**
+ * Adds a new property to form and create corresponding field.
+ *
+ * @see com.vaadin.data.Item#addItemProperty(Object, Property)
+ */
+ 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;
+ }
+
+ // Gets suitable field
+ final Field field = fieldFactory.createField(property, this);
+ if (field == null) {
+ return false;
+ }
+
+ // Configures the field
+ try {
+ field.setPropertyDataSource(property);
+ String caption = id.toString();
+ if (caption.length() > 50) {
+ caption = caption.substring(0, 47) + "...";
+ }
+ if (caption.length() > 0) {
+ caption = "" + Character.toUpperCase(caption.charAt(0))
+ + caption.substring(1, caption.length());
+ }
+ field.setCaption(caption);
+ } catch (final Throwable ignored) {
+ return false;
+ }
+
+ addField(id, field);
+
+ return true;
+ }
+
+ /**
+ * Adds the field to form.
+ *
+ * <p>
+ * The property id must not be already used in the form.
+ * </p>
+ *
+ * <p>
+ * This field is added to the form layout in the default position (the
+ * position used by {@link Layout#addComponent(Component)} method. In the
+ * special case that the underlying layout is a custom layout, string
+ * representation of the property id is used instead of the default
+ * location.
+ * </p>
+ *
+ * @param propertyId
+ * the Property id the the field.
+ * @param field
+ * the New field added to the form.
+ */
+ public void addField(Object propertyId, Field field) {
+
+ if (propertyId != null && field != null) {
+ fields.put(propertyId, field);
+ field.addListener(fieldValueChangeListener);
+ propertyIds.addLast(propertyId);
+ field.setReadThrough(readThrough);
+ field.setWriteThrough(writeThrough);
+ if (isImmediate() && field instanceof AbstractComponent) {
+ ((AbstractComponent) field).setImmediate(true);
+ }
+ if (layout instanceof CustomLayout) {
+ ((CustomLayout) layout).addComponent(field, propertyId
+ .toString());
+ } else {
+ layout.addComponent(field);
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /**
+ * 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)
+ */
+ public Property getItemProperty(Object id) {
+ final Field field = (Field) fields.get(id);
+ if (field == null) {
+ return null;
+ }
+ 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 (Field) fields.get(propertyId);
+ }
+
+ /* Documented in interface */
+ public Collection getItemPropertyIds() {
+ return Collections.unmodifiableCollection(propertyIds);
+ }
+
+ /**
+ * Removes the property and corresponding field from the form.
+ *
+ * @see com.vaadin.data.Item#removeItemProperty(Object)
+ */
+ public boolean removeItemProperty(Object id) {
+
+ final Field field = (Field) fields.get(id);
+
+ if (field != null) {
+ propertyIds.remove(id);
+ fields.remove(id);
+ layout.removeComponent(field);
+ field.removeListener(fieldValueChangeListener);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 */
+ 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)
+ */
+ 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 (layout instanceof GridLayout) {
+ GridLayout gl = (GridLayout) layout;
+ 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) {
+ 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) {
+ f.setPropertyDataSource(property);
+ addField(id, f);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Sets the layout of the form.
+ *
+ * <p>
+ * By default form uses <code>OrderedLayout</code> with <code>form</code>
+ * -style.
+ * </p>
+ *
+ * @param newLayout
+ * the Layout of the form.
+ */
+ public void setLayout(Layout newLayout) {
+
+ // Use orderedlayout by default
+ if (newLayout == null) {
+ newLayout = new FormLayout();
+ }
+
+ // reset cursor memory
+ gridlayoutCursorX = -1;
+ gridlayoutCursorY = -1;
+
+ // Move fields from previous layout
+ if (layout != null) {
+ final Object[] properties = propertyIds.toArray();
+ for (int i = 0; i < properties.length; i++) {
+ Field f = getField(properties[i]);
+ layout.removeComponent(f);
+ if (newLayout instanceof CustomLayout) {
+ ((CustomLayout) newLayout).addComponent(f, properties[i]
+ .toString());
+ } else {
+ newLayout.addComponent(f);
+ }
+ }
+
+ layout.setParent(null);
+ }
+
+ // Replace the previous layout
+ newLayout.setParent(this);
+ layout = newLayout;
+
+ }
+
+ /**
+ * 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>
+ *
+ * @param propertyId
+ * the id of the property.
+ * @param values
+ * @param descriptions
+ * @return the select property generated
+ */
+ public Select 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 = (Field) 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 Select newField = new Select();
+ if (isMultiselect) {
+ newField.setMultiSelect(true);
+ }
+ 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];
+ if (id == null) {
+ id = new Object();
+ newField.setNullSelectionItemId(id);
+ }
+ final Item 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
+ layout.replaceComponent(oldField, newField);
+ fields.put(propertyId, newField);
+ newField.addListener(fieldValueChangeListener);
+ oldField.removeListener(fieldValueChangeListener);
+
+ return newField;
+ }
+
+ /**
+ * Notifies the component that it is connected to an application
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ super.attach();
+ layout.attach();
+ }
+
+ /**
+ * Notifies the component that it is detached from the application.
+ *
+ * @see com.vaadin.ui.Component#detach()
+ */
+ @Override
+ public void detach() {
+ super.detach();
+ layout.detach();
+ }
+
+ /**
+ * Tests the current value of the object against all registered validators
+ *
+ * @see com.vaadin.data.Validatable#isValid()
+ */
+ @Override
+ public boolean isValid() {
+ boolean valid = true;
+ for (final Iterator i = propertyIds.iterator(); i.hasNext();) {
+ valid &= ((Field) fields.get(i.next())).isValid();
+ }
+ return valid && super.isValid();
+ }
+
+ /**
+ * Checks the validity of the validatable.
+ *
+ * @see com.vaadin.data.Validatable#validate()
+ */
+ @Override
+ public void validate() throws InvalidValueException {
+ super.validate();
+ for (final Iterator i = propertyIds.iterator(); i.hasNext();) {
+ ((Field) 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();) {
+ ((Field) fields.get(i.next())).setReadOnly(readOnly);
+ }
+ }
+
+ /**
+ * Sets the field factory of Form.
+ *
+ * <code>FieldFactory</code> is used to create fields for form properties.
+ * By default the form uses BaseFieldFactory to create Field instances.
+ *
+ * @param fieldFactory
+ * the New factory used to create the fields.
+ * @see Field
+ * @see FieldFactory
+ */
+ public void setFieldFactory(FieldFactory fieldFactory) {
+ this.fieldFactory = fieldFactory;
+ }
+
+ /**
+ * Get the field factory of the form.
+ *
+ * @return the FieldFactory Factory used to create the fields.
+ */
+ public FieldFactory getFieldFactory() {
+ 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 field in form.
+ *
+ * @return the Field.
+ */
+ private Field getFirstField() {
+ Object id = null;
+ if (getItemPropertyIds() != null) {
+ 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(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 v = new LinkedList();
+ 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 = getFirstField();
+ 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 i = fields.values().iterator(); i.hasNext();) {
+ Field f = (Field) 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 i = fields.values().iterator(); i.hasNext();) {
+ Field f = (Field) 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() {
+ if (formFooter == null) {
+ formFooter = new OrderedLayout(OrderedLayout.ORIENTATION_HORIZONTAL);
+ formFooter.setParent(this);
+ }
+ return formFooter;
+ }
+
+ /**
+ * Sets the layout that is rendered below normal form contens.
+ *
+ * @param newFormFooter
+ * the new Layout
+ */
+ public void setFooter(Layout newFormFooter) {
+ if (formFooter != null) {
+ formFooter.setParent(null);
+ }
+ formFooter = newFormFooter;
+ formFooter.setParent(this);
+ }
+
+ @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();
+ }
+ }
+
+}
diff --git a/src/com/vaadin/ui/FormLayout.java b/src/com/vaadin/ui/FormLayout.java
new file mode 100644
index 0000000000..1fbfcf2ee4
--- /dev/null
+++ b/src/com/vaadin/ui/FormLayout.java
@@ -0,0 +1,36 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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.
+ *
+ */
+@SuppressWarnings( { "deprecation", "serial" })
+public class FormLayout extends OrderedLayout {
+
+ public FormLayout() {
+ super();
+ setSpacing(true);
+ setMargin(true, false, true, false);
+ }
+
+ @Override
+ public String getTag() {
+ return "formlayout";
+ }
+
+}
diff --git a/src/com/vaadin/ui/GridLayout.java b/src/com/vaadin/ui/GridLayout.java
new file mode 100644
index 0000000000..fbbc0e3ffa
--- /dev/null
+++ b/src/com/vaadin/ui/GridLayout.java
@@ -0,0 +1,1313 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * <p>
+ * A container that consists of components with certain coordinates (cell
+ * position) on a grid. It also maintains cursor for adding component in left to
+ * right, top to bottom order.
+ * </p>
+ *
+ * <p>
+ * Each component in a <code>GridLayout</code> uses a certain
+ * {@link GridLayout.Area area} (column1,row1,column2,row2) from the grid. One
+ * should not add components that would overlap with the existing components
+ * because in such case an {@link OverlapsException} is thrown. Adding component
+ * with cursor automatically extends the grid by increasing the grid height.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class GridLayout extends AbstractLayout implements
+ Layout.AlignmentHandler, Layout.SpacingHandler {
+
+ /**
+ * Initial grid columns.
+ */
+ private int cols = 0;
+
+ /**
+ * Initial grid rows.
+ */
+ private int rows = 0;
+
+ /**
+ * 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 areas = new LinkedList();
+
+ /**
+ * Mapping from components to their respective areas.
+ */
+ private final LinkedList components = new LinkedList();
+
+ /**
+ * Mapping from components to alignments (horizontal + vertical).
+ */
+ private Map<Component, Alignment> componentToAlignment = new HashMap<Component, Alignment>();
+
+ /**
+ * Is spacing between contained components enabled. Defaults to false.
+ */
+ private boolean spacing = false;
+
+ 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 grid of given size (number of cells). Note that grid's
+ * final size depends on the items that are added into the grid. Grid grows
+ * if you add components outside the grid's 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);
+ }
+
+ /**
+ * Constructs an empty grid layout that is extended as needed.
+ */
+ public GridLayout() {
+ this(1, 1);
+ }
+
+ /**
+ * <p>
+ * Adds a component with a specified area to the grid. The area the new
+ * component should take is defined by specifying the upper left corner
+ * (column1, row1) and the lower right corner (column2, row2) of the area.
+ * </p>
+ *
+ * <p>
+ * If the new component overlaps with any of the existing components already
+ * present in the grid the operation will fail and an
+ * {@link OverlapsException} is thrown.
+ * </p>
+ *
+ * @param c
+ * the component to be added.
+ * @param column1
+ * the column of the upper left corner of the area <code>c</code>
+ * is supposed to occupy.
+ * @param row1
+ * the row of the upper left corner of the area <code>c</code> is
+ * supposed to occupy.
+ * @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 >= cols || row2 >= rows) {
+ throw new OutOfBoundsException(area);
+ }
+
+ // Checks that newItem does not overlap with existing items
+ checkExistingOverlaps(area);
+
+ // first attemt to add to super
+ super.addComponent(component);
+
+ // Inserts the component to right place at the list
+ // Respect top-down, left-right ordering
+ // component.setParent(this);
+ final Iterator i = areas.iterator();
+ int index = 0;
+ boolean done = false;
+ while (!done && i.hasNext()) {
+ final Area existingArea = (Area) 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);
+ }
+
+ // 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 >= cols) {
+ // 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 i = areas.iterator(); i.hasNext();) {
+ final Area existingArea = (Area) i.next();
+ if (existingArea.overlaps(area)) {
+ // Component not added, overlaps with existing component
+ throw new OverlapsException(existingArea);
+ }
+ }
+ }
+
+ /**
+ * Adds the component into this container to cells column1,row1 (NortWest
+ * corner of the area.) End coordinates (SouthEast corner of the area) are
+ * the same as column1,row1. Component width and height is 1.
+ *
+ * @param c
+ * the component to be added.
+ * @param column
+ * the column index.
+ * @param row
+ * the row index.
+ * @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 c, int column, int row)
+ throws OverlapsException, OutOfBoundsException {
+ this.addComponent(c, column, row, column, row);
+ }
+
+ /**
+ * Force the next component to be added to the beginning of the next line.
+ * By calling this function user can ensure that no more components are
+ * added to the right of the previous component.
+ *
+ * @see #space()
+ */
+ public void newLine() {
+ cursorX = 0;
+ cursorY++;
+ }
+
+ /**
+ * Moves the cursor forwards by one. If the cursor goes out of the right
+ * grid border, move it to next line.
+ *
+ * @see #newLine()
+ */
+ public void space() {
+ cursorX++;
+ if (cursorX >= cols) {
+ 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 c
+ * 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
+ cols = cursorX >= cols ? cursorX + 1 : cols;
+ rows = cursorY >= rows ? cursorY + 1 : rows;
+
+ addComponent(component, cursorX, cursorY);
+ }
+
+ /**
+ * Removes the given component from this container.
+ *
+ * @param c
+ * 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;
+ }
+
+ super.removeComponent(component);
+
+ Area area = null;
+ for (final Iterator i = areas.iterator(); area == null && i.hasNext();) {
+ final Area a = (Area) i.next();
+ if (a.getComponent() == component) {
+ area = a;
+ }
+ }
+
+ components.remove(component);
+ if (area != null) {
+ areas.remove(area);
+ }
+
+ componentToAlignment.remove(component);
+
+ requestRepaint();
+ }
+
+ /**
+ * Removes the component specified with it's cell index.
+ *
+ * @param column
+ * the Component's column.
+ * @param row
+ * the Component's row.
+ */
+ public void removeComponent(int column, int row) {
+
+ // Finds the area
+ for (final Iterator i = areas.iterator(); i.hasNext();) {
+ final Area area = (Area) i.next();
+ if (area.getColumn1() == column && area.getRow1() == row) {
+ removeComponent(area.getComponent());
+ return;
+ }
+ }
+ }
+
+ /**
+ * Gets an Iterator to the component container contents. Using the Iterator
+ * it's possible to step through the contents of the container.
+ *
+ * @return the Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return Collections.unmodifiableCollection(components).iterator();
+ }
+
+ /**
+ * 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 {
+
+ super.paintContent(target);
+
+ // TODO refactor attribute names in future release.
+ target.addAttribute("h", rows);
+ target.addAttribute("w", cols);
+
+ target.addAttribute("structuralChange", structuralChange);
+ structuralChange = false;
+
+ if (spacing) {
+ target.addAttribute("spacing", spacing);
+ }
+
+ // Area iterator
+ final Iterator 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 cellUsed = new HashMap();
+
+ // Empty cell collector
+ int emptyCells = 0;
+
+ final String[] alignmentsArray = new String[components.size()];
+ final Integer[] columnExpandRatioArray = new Integer[cols];
+ final Integer[] rowExpandRatioArray = new Integer[rows];
+
+ 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) cols;
+ int myRatio = Math.round(equalSize * 1000);
+ for (int i = 0; i < cols; i++) {
+ columnExpandRatioArray[i] = myRatio;
+ }
+ realColExpandRatioSum = myRatio * cols;
+ } else {
+ for (int i = 0; i < cols; 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) rows;
+ int myRatio = Math.round(equalSize * 1000);
+ for (int i = 0; i < rows; i++) {
+ rowExpandRatioArray[i] = myRatio;
+ }
+ realRowExpandRatioSum = myRatio * rows;
+ }
+
+ int index = 0;
+
+ // Iterates every applicable row
+ for (int cury = 0; cury < rows; 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 < cols; 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);
+ }
+ area.getComponent().paint(target);
+
+ alignmentsArray[index++] = String
+ .valueOf(getComponentAlignment(area.getComponent())
+ .getBitMask());
+
+ target.endTag("gc");
+
+ // Fetch next item
+ if (areaiterator.hasNext()) {
+ area = (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 = ((Integer) 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(new Integer(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", cols - 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
+ * .itmill.toolkit.ui.Component)
+ */
+ public Alignment getComponentAlignment(Component childComponent) {
+ Alignment alignment = componentToAlignment.get(childComponent);
+ if (alignment == null) {
+ return ALIGNMENT_DEFAULT;
+ } else {
+ return alignment;
+ }
+ }
+
+ /**
+ * Gets the components UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ * @see com.vaadin.ui.AbstractComponent#getTag()
+ */
+ @Override
+ public String getTag() {
+ return "gridlayout";
+ }
+
+ /**
+ * This class defines an area on a grid. An Area is defined by the cells of
+ * its upper left corner (column1,row1) and lower right corner
+ * (column2,row2).
+ *
+ * @author IT Mill 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 on 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
+ * <code>c</code> is supposed to occupy.
+ * @param row1
+ * The row of the upper left corner cell of the area
+ * <code>c</code> is supposed to occupy.
+ * @param column2
+ * The column of the lower right corner cell of the area
+ * <code>c</code> is supposed to occupy.
+ * @param row2
+ * The row of the lower right corner cell 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
+ */
+ 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 the given Area overlaps with an another Area.
+ *
+ * @param other
+ * the Another Area that's to be tested for overlap with this
+ * area.
+ * @return <code>true</code> if <code>other</code> overlaps with this
+ * area, <code>false</code> if it doesn't.
+ */
+ 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 datastructure 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 getColumn1() instead.
+ *
+ * @see com.vaadin.ui.GridLayout#getColumn1()
+ */
+ @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 getColumn2() instead.
+ *
+ * @see com.vaadin.ui.GridLayout#getColumn2()
+ */
+ @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 getRow1() instead.
+ *
+ * @see com.vaadin.ui.GridLayout#getRow1()
+ */
+ @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 getRow2() instead.
+ *
+ * @see com.vaadin.ui.GridLayout#getRow2()
+ */
+ @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;
+ }
+
+ }
+
+ /**
+ * An <code>Exception</code> object which is thrown when two Items occupy
+ * the same space on a grid.
+ *
+ * @author IT Mill 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;
+ }
+
+ /**
+ * 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 IT Mill 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 (cols == columns) {
+ return;
+ }
+
+ // Checks for overlaps
+ if (cols > columns) {
+ for (final Iterator i = areas.iterator(); i.hasNext();) {
+ final Area area = (Area) i.next();
+ if (area.column2 >= columns) {
+ throw new OutOfBoundsException(area);
+ }
+ }
+ }
+
+ cols = columns;
+
+ requestRepaint();
+ }
+
+ /**
+ * Get the number of columns in the grid.
+ *
+ * @return the number of columns in the grid.
+ */
+ public final int getColumns() {
+ return cols;
+ }
+
+ /**
+ * 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 (this.rows == rows) {
+ return;
+ }
+
+ // Checks for overlaps
+ if (this.rows > rows) {
+ for (final Iterator i = areas.iterator(); i.hasNext();) {
+ final Area area = (Area) i.next();
+ if (area.row2 >= rows) {
+ throw new OutOfBoundsException(area);
+ }
+ }
+ }
+
+ this.rows = rows;
+
+ requestRepaint();
+ }
+
+ /**
+ * Get the number of rows in the grid.
+ *
+ * @return the number of rows in the grid.
+ */
+ public final int getRows() {
+ return rows;
+ }
+
+ /**
+ * Gets the current cursor x-position. 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.
+ *
+ * @return the grid column the Cursor is on.
+ */
+ 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 cursor y-position. 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.
+ *
+ * @return the grid row the Cursor is on.
+ */
+ public int getCursorY() {
+ return cursorY;
+ }
+
+ /**
+ * Sets the current cursor y-position. This is usually handled automatically
+ * by GridLayout.
+ *
+ * @param cursorY
+ */
+ public void setCursorY(int cursorY) {
+ this.cursorY = cursorY;
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ // Gets the locations
+ Area oldLocation = null;
+ Area newLocation = null;
+ for (final Iterator i = areas.iterator(); i.hasNext();) {
+ final Area location = (Area) 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();
+ cursorX = 0;
+ cursorY = 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.Layout.AlignmentHandler#setComponentAlignment(com
+ * .itmill.toolkit.ui.Component, int, int)
+ */
+ public void setComponentAlignment(Component childComponent,
+ int horizontalAlignment, int verticalAlignment) {
+ componentToAlignment.put(childComponent, new Alignment(
+ horizontalAlignment + verticalAlignment));
+ requestRepaint();
+ }
+
+ public void setComponentAlignment(Component childComponent,
+ Alignment alignment) {
+ componentToAlignment.put(childComponent, alignment);
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
+ */
+ public void setSpacing(boolean enabled) {
+ spacing = enabled;
+ requestRepaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
+ */
+ @Deprecated
+ public boolean isSpacingEnabled() {
+ return spacing;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
+ */
+ public boolean isSpacing() {
+ return spacing;
+ }
+
+ /**
+ * Inserts an empty row at the chosen position in the grid.
+ *
+ * @param row
+ * Number of the row the new row will be inserted before
+ */
+ public void insertRow(int row) {
+ if (row > rows) {
+ throw new IllegalArgumentException("Cannot insert row at " + row
+ + " in a gridlayout with height " + rows);
+ }
+
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area existingArea = (Area) 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(rows + 1);
+ structuralChange = true;
+ requestRepaint();
+ }
+
+ /**
+ * Removes row and all components in the row. Components which span over
+ * several rows are removed if the selected row is the component's first
+ * row.
+ *
+ * @param row
+ * The row number to remove
+ */
+ public void removeRow(int row) {
+ if (row >= rows) {
+ throw new IllegalArgumentException("Cannot delete row " + row
+ + " from a gridlayout with height " + rows);
+ }
+
+ // 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 i = areas.iterator(); i.hasNext();) {
+ Area existingArea = (Area) i.next();
+ if (existingArea.row2 >= row) {
+ existingArea.row2--;
+
+ if (existingArea.row1 > row) {
+ existingArea.row1--;
+ }
+ }
+ }
+
+ setRows(rows - 1);
+ if (cursorY > row) {
+ cursorY--;
+ }
+
+ structuralChange = true;
+ requestRepaint();
+
+ }
+
+ /**
+ * Sets the expand ratio of given column. Expand ratio defines how excess
+ * space is distributed among columns. Excess space means the space not
+ * consumed by non relatively sized components.
+ *
+ * <p>
+ * By default excess space is distributed evenly.
+ *
+ * <p>
+ * Note, that width needs to be defined for this method to have any effect.
+ *
+ * @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. Expand ratio defines how excess space
+ * is distributed among rows. Excess space means the space not consumed by
+ * non relatively sized components.
+ *
+ * <p>
+ * By default excess space is distributed evenly.
+ *
+ * <p>
+ * Note, that height needs to be defined for this method to have any effect.
+ *
+ * @see #setHeight(float, int)
+ *
+ * @param rowIndex
+ * @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
+ * @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
+ * x-index
+ * @param y
+ * y-index
+ * @return Component in given cell or null if empty
+ */
+ public Component getComponent(int x, int y) {
+ for (final Iterator iterator = areas.iterator(); iterator.hasNext();) {
+ final Area 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 layed in the
+ * GridLayout.
+ *
+ * @param component
+ * the component whose area information is requested.
+ * @return an Area object that contains information how component is layed
+ * in the grid
+ */
+ public Area getComponentArea(Component component) {
+ for (final Iterator iterator = areas.iterator(); iterator.hasNext();) {
+ final Area area = (Area) iterator.next();
+ if (area.getComponent() == component) {
+ return area;
+ }
+ }
+ return null;
+ }
+
+ public void setComponentAlignment(Component component, String alignment) {
+ AlignmentUtils.setComponentAlignment(this, component, alignment);
+ }
+
+}
diff --git a/src/com/vaadin/ui/HorizontalLayout.java b/src/com/vaadin/ui/HorizontalLayout.java
new file mode 100644
index 0000000000..820b0c29de
--- /dev/null
+++ b/src/com/vaadin/ui/HorizontalLayout.java
@@ -0,0 +1,26 @@
+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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.3
+ */
+@SuppressWarnings("serial")
+public class HorizontalLayout extends AbstractOrderedLayout {
+
+ public HorizontalLayout() {
+
+ }
+
+ @Override
+ public String getTag() {
+ return "horizontallayout";
+ }
+
+}
diff --git a/src/com/vaadin/ui/InlineDateField.java b/src/com/vaadin/ui/InlineDateField.java
new file mode 100644
index 0000000000..88f0c3e0b4
--- /dev/null
+++ b/src/com/vaadin/ui/InlineDateField.java
@@ -0,0 +1,52 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+public class InlineDateField extends DateField {
+
+ public InlineDateField() {
+ super();
+ type = TYPE_INLINE;
+ }
+
+ public InlineDateField(Property dataSource) throws IllegalArgumentException {
+ super(dataSource);
+ type = TYPE_INLINE;
+ }
+
+ public InlineDateField(String caption, Date value) {
+ super(caption, value);
+ type = TYPE_INLINE;
+ }
+
+ public InlineDateField(String caption, Property dataSource) {
+ super(caption, dataSource);
+ type = TYPE_INLINE;
+ }
+
+ public InlineDateField(String caption) {
+ super(caption);
+ type = TYPE_INLINE;
+ }
+
+}
diff --git a/src/com/vaadin/ui/Label.java b/src/com/vaadin/ui/Label.java
new file mode 100644
index 0000000000..3b24a07930
--- /dev/null
+++ b/src/com/vaadin/ui/Label.java
@@ -0,0 +1,546 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.data.Property;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * Label component for showing non-editable short texts.
+ *
+ * The label content can be set to the modes specified by the final members
+ * CONTENT_*
+ *
+ * <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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Label extends AbstractComponent implements Property,
+ Property.Viewer, Property.ValueChangeListener,
+ Property.ValueChangeNotifier, Comparable {
+
+ /**
+ * 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;
+
+ /**
+ * Formatted content mode, where the contents is XML restricted to the UIDL
+ * 1.0 formatting markups.
+ *
+ * @deprecated Use CONTENT_XML instead.
+ */
+ @Deprecated
+ public static final int CONTENT_UIDL = 2;
+
+ /**
+ * Content mode, where the label contains XHTML. Contents is then enclosed
+ * in DIV elements having namespace of
+ * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".
+ */
+ public static final int CONTENT_XHTML = 3;
+
+ /**
+ * Content mode, where the label contains well-formed or well-balanced XML.
+ * Each of the root elements must have their default namespace specified.
+ */
+ public static final int CONTENT_XML = 4;
+
+ /**
+ * Content mode, where the label contains RAW output. Output is not required
+ * to comply to with XML. In Web Adapter output is inserted inside the
+ * resulting HTML document as-is. This is useful for some specific purposes
+ * where possibly broken HTML content needs to be shown, but in most cases
+ * XHTML mode should be preferred.
+ */
+ public static final int CONTENT_RAW = 5;
+
+ /**
+ * The default content mode is plain text.
+ */
+ public static final int CONTENT_DEFAULT = CONTENT_TEXT;
+
+ /** Array of content mode names that are rendered in UIDL as mode attribute. */
+ private static final String[] CONTENT_MODE_NAME = { "text", "pre", "uidl",
+ "xhtml", "xml", "raw" };
+
+ private static final String DATASOURCE_MUST_BE_SET = "Datasource must be set";
+
+ private Property dataSource;
+
+ private int contentMode = CONTENT_DEFAULT;
+
+ /**
+ * Creates an empty Label.
+ */
+ public Label() {
+ this("");
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents.
+ *
+ * @param content
+ */
+ public Label(String content) {
+ this(content, CONTENT_DEFAULT);
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents read from given
+ * datasource.
+ *
+ * @param contentSource
+ */
+ public Label(Property contentSource) {
+ this(contentSource, CONTENT_DEFAULT);
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents.
+ *
+ * @param content
+ * @param contentMode
+ */
+ public Label(String content, int contentMode) {
+ this(new ObjectProperty(content, String.class), contentMode);
+ }
+
+ /**
+ * Creates a new instance of Label with text-contents read from given
+ * datasource.
+ *
+ * @param contentSource
+ * @param contentMode
+ */
+ public Label(Property contentSource, int contentMode) {
+ setPropertyDataSource(contentSource);
+ if (contentMode != CONTENT_DEFAULT) {
+ setContentMode(contentMode);
+ }
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Get the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "label";
+ }
+
+ /**
+ * Set the component to read-only. Readonly is not used in label.
+ *
+ * @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_SET);
+ }
+ dataSource.setReadOnly(readOnly);
+ }
+
+ /**
+ * Is the component read-only ? Readonly is not used in label - this returns
+ * allways false.
+ *
+ * @return <code>true</code> if the component is in read only mode.
+ */
+ @Override
+ public boolean isReadOnly() {
+ if (dataSource == null) {
+ throw new IllegalStateException(DATASOURCE_MUST_BE_SET);
+ }
+ 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 {
+ if (contentMode != CONTENT_TEXT) {
+ target.addAttribute("mode", CONTENT_MODE_NAME[contentMode]);
+ }
+ if (contentMode == CONTENT_TEXT) {
+ target.addText(toString());
+ } else if (contentMode == CONTENT_UIDL) {
+ target.addUIDL(toString());
+ } else if (contentMode == CONTENT_XHTML) {
+ target.startTag("data");
+ target.addXMLSection("div", toString(),
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
+ target.endTag("data");
+ } else if (contentMode == CONTENT_PREFORMATTED) {
+ target.startTag("pre");
+ target.addText(toString());
+ target.endTag("pre");
+ } else if (contentMode == CONTENT_XML) {
+ target.addXMLSection("data", toString(), null);
+ } else if (contentMode == CONTENT_RAW) {
+ target.startTag("data");
+ target.addAttribute("escape", false);
+ target.addText(toString());
+ target.endTag("data");
+ }
+
+ }
+
+ /**
+ * Gets the value of the label. Value of the label is the XML contents of
+ * the label.
+ *
+ * @return the Value of the label.
+ */
+ public Object getValue() {
+ if (dataSource == null) {
+ throw new IllegalStateException(DATASOURCE_MUST_BE_SET);
+ }
+ return dataSource.getValue();
+ }
+
+ /**
+ * Set the value of the label. Value of the label is the XML contents of the
+ * label.
+ *
+ * @param newValue
+ * the New value of the label.
+ */
+ public void setValue(Object newValue) {
+ if (dataSource == null) {
+ throw new IllegalStateException(DATASOURCE_MUST_BE_SET);
+ }
+ dataSource.setValue(newValue);
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ if (dataSource == null) {
+ throw new IllegalStateException(DATASOURCE_MUST_BE_SET);
+ }
+ return dataSource.toString();
+ }
+
+ /**
+ * Gets the type of the Property.
+ *
+ * @see com.vaadin.data.Property#getType()
+ */
+ public Class getType() {
+ if (dataSource == null) {
+ throw new IllegalStateException(DATASOURCE_MUST_BE_SET);
+ }
+ return dataSource.getType();
+ }
+
+ /**
+ * Gets the viewing data-source property.
+ *
+ * @return the data source property.
+ * @see com.vaadin.data.Property.Viewer#getPropertyDataSource()
+ */
+ 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)
+ */
+ 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 content mode of the Label.
+ *
+ * <p>
+ * Possible content modes include:
+ * <ul>
+ * <li><b>CONTENT_TEXT</b> Content mode, where the label contains only plain
+ * text. The getValue() result is coded to XML when painting.</li>
+ * <li><b>CONTENT_PREFORMATTED</b> Content mode, where the label contains
+ * preformatted text.</li>
+ * <li><b>CONTENT_UIDL</b> Formatted content mode, where the contents is XML
+ * restricted to the UIDL 1.0 formatting markups.</li>
+ * <li><b>CONTENT_XHTML</b> Content mode, where the label contains XHTML.
+ * Contents is then enclosed in DIV elements having namespace of
+ * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".</li>
+ * <li><b>CONTENT_XML</b> Content mode, where the label contains well-formed
+ * or well-balanced XML. Each of the root elements must have their default
+ * namespace specified.</li>
+ * <li><b>CONTENT_RAW</b> Content mode, where the label contains RAW output.
+ * Output is not required to comply to with XML. In Web Adapter output is
+ * inserted inside the resulting HTML document as-is. This is useful for
+ * some specific purposes where possibly broken HTML content needs to be
+ * shown, but in most cases XHTML mode should be preferred.</li>
+ * </ul>
+ * </p>
+ *
+ * @return the Content mode of the label.
+ */
+ public int getContentMode() {
+ return contentMode;
+ }
+
+ /**
+ * Sets the content mode of the Label.
+ *
+ * <p>
+ * Possible content modes include:
+ * <ul>
+ * <li><b>CONTENT_TEXT</b> Content mode, where the label contains only plain
+ * text. The getValue() result is coded to XML when painting.</li>
+ * <li><b>CONTENT_PREFORMATTED</b> Content mode, where the label contains
+ * preformatted text.</li>
+ * <li><b>CONTENT_UIDL</b> Formatted content mode, where the contents is XML
+ * restricted to the UIDL 1.0 formatting markups.</li>
+ * <li><b>CONTENT_XHTML</b> Content mode, where the label contains XHTML.
+ * Contents is then enclosed in DIV elements having namespace of
+ * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".</li>
+ * <li><b>CONTENT_XML</b> Content mode, where the label contains well-formed
+ * or well-balanced XML. Each of the root elements must have their default
+ * namespace specified.</li>
+ * <li><b>CONTENT_RAW</b> Content mode, where the label contains RAW output.
+ * Output is not required to comply to with XML. In Web Adapter output is
+ * inserted inside the resulting HTML document as-is. This is useful for
+ * some specific purposes where possibly broken HTML content needs to be
+ * shown, but in most cases XHTML mode should be preferred.</li>
+ * </ul>
+ * </p>
+ *
+ * @param contentMode
+ * the New content mode of the label.
+ */
+ public void setContentMode(int contentMode) {
+ if (contentMode != this.contentMode && contentMode >= CONTENT_TEXT
+ && contentMode <= CONTENT_RAW) {
+ this.contentMode = 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public 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()
+ */
+ 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)
+ */
+ 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)
+ */
+ 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));
+ requestRepaint();
+ }
+
+ /**
+ * Listens the value change events from data source.
+ *
+ * @see com.vaadin.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent)
+ */
+ public void valueChange(Property.ValueChangeEvent event) {
+ fireValueChange();
+ }
+
+ /**
+ * 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)
+ */
+ public int compareTo(Object other) {
+
+ String thisValue;
+ String otherValue;
+
+ if (contentMode == CONTENT_XML || contentMode == CONTENT_UIDL
+ || contentMode == CONTENT_XHTML) {
+ thisValue = stripTags(toString());
+ } else {
+ thisValue = toString();
+ }
+
+ if (other instanceof Label
+ && (((Label) other).getContentMode() == CONTENT_XML
+ || ((Label) other).getContentMode() == CONTENT_UIDL || ((Label) other)
+ .getContentMode() == CONTENT_XHTML)) {
+ otherValue = stripTags(other.toString());
+ } else {
+ otherValue = other.toString();
+ }
+
+ 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();
+ }
+
+}
diff --git a/src/com/vaadin/ui/Layout.java b/src/com/vaadin/ui/Layout.java
new file mode 100644
index 0000000000..935ae363b5
--- /dev/null
+++ b/src/com/vaadin/ui/Layout.java
@@ -0,0 +1,238 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.gwt.client.ui.IMarginInfo;
+import com.vaadin.terminal.gwt.client.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 IT Mill 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
+ * @deprecated Use {@link #isSpacing()} instead.
+ */
+ @Deprecated
+ public boolean isSpacingEnabled();
+
+ /**
+ *
+ * @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 IMarginInfo 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/src/com/vaadin/ui/Link.java b/src/com/vaadin/ui/Link.java
new file mode 100644
index 0000000000..6deb05a99a
--- /dev/null
+++ b/src/com/vaadin/ui/Link.java
@@ -0,0 +1,243 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+
+/**
+ * Link is used to create external or internal URL links.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Link extends AbstractComponent {
+
+ /* Target window border type constant: No window border */
+ public static final int TARGET_BORDER_NONE = Window.BORDER_NONE;
+
+ /* Target window border type constant: Minimal window border */
+ public static final int TARGET_BORDER_MINIMAL = Window.BORDER_MINIMAL;
+
+ /* Target window border type constant: Default window border */
+ public static final int TARGET_BORDER_DEFAULT = Window.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);
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "link";
+ }
+
+ /**
+ * 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();
+ }
+}
diff --git a/src/com/vaadin/ui/ListSelect.java b/src/com/vaadin/ui/ListSelect.java
new file mode 100644
index 0000000000..7860474433
--- /dev/null
+++ b/src/com/vaadin/ui/ListSelect.java
@@ -0,0 +1,96 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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/src/com/vaadin/ui/LoginForm.java b/src/com/vaadin/ui/LoginForm.java
new file mode 100644
index 0000000000..3d3aeaf8b7
--- /dev/null
+++ b/src/com/vaadin/ui/LoginForm.java
@@ -0,0 +1,260 @@
+package com.vaadin.ui;
+
+import java.io.ByteArrayInputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.net.URL;
+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.ParameterHandler;
+import com.vaadin.terminal.URIHandler;
+
+/**
+ * LoginForm is a Toolkit component to handle common problem among Ajax
+ * applications: browsers password managers don't fill dynamically created forms
+ * like all those UI elements created by IT Mill Toolkit.
+ * <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.
+ * <p>
+ * Note, this is a new Ajax terminal specific component and is likely to change.
+ *
+ * @since 5.3
+ */
+@SuppressWarnings("serial")
+public class LoginForm extends CustomComponent {
+
+ private Embedded iframe = new Embedded();
+
+ private ApplicationResource loginPage = new ApplicationResource() {
+
+ public Application getApplication() {
+ return LoginForm.this.getApplication();
+ }
+
+ public int getBufferSize() {
+ return getLoginHTML().length;
+ }
+
+ public long getCacheTime() {
+ return -1;
+ }
+
+ public String getFilename() {
+ return "login";
+ }
+
+ public DownloadStream getStream() {
+ return new DownloadStream(new ByteArrayInputStream(getLoginHTML()),
+ getMIMEType(), getFilename());
+ }
+
+ public String getMIMEType() {
+ return "text/html";
+ }
+ };
+
+ private ParameterHandler paramHandler = new ParameterHandler() {
+
+ public void handleParameters(Map parameters) {
+ if (parameters.containsKey("username")) {
+ getWindow().addURIHandler(uriHandler);
+
+ HashMap params = new HashMap();
+ // expecting single params
+ for (Iterator it = parameters.keySet().iterator(); it.hasNext();) {
+ String key = (String) it.next();
+ String value = ((String[]) parameters.get(key))[0];
+ params.put(key, value);
+ }
+ LoginEvent event = new LoginEvent(params);
+ fireEvent(event);
+ }
+ }
+ };
+
+ private URIHandler uriHandler = new URIHandler() {
+ private final String responce = "<html><body>Login form handeled."
+ + "<script type='text/javascript'>top.itmill.forceSync();"
+ + "</script></body></html>";
+
+ public DownloadStream handleURI(URL context, String relativeUri) {
+ if (relativeUri != null && relativeUri.contains("loginHandler")) {
+ if (window != null) {
+ window.removeURIHandler(this);
+ }
+ DownloadStream downloadStream = new DownloadStream(
+ new ByteArrayInputStream(responce.getBytes()),
+ "text/html", "loginSuccesfull");
+ downloadStream.setCacheTime(-1);
+ return downloadStream;
+ } else {
+ return null;
+ }
+ }
+ };
+
+ private Window window;
+
+ public LoginForm() {
+ iframe.setType(Embedded.TYPE_BROWSER);
+ iframe.setSizeFull();
+ setSizeFull();
+ setCompositionRoot(iframe);
+ }
+
+ /**
+ * 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 theme = getApplication().getMainWindow().getTheme();
+ String guessedThemeUri = getApplication().getURL() + "ITMILL/themes/"
+ + (theme == null ? "default" : theme) + "/styles.css";
+ String guessedThemeUri2 = getApplication().getURL()
+ + "../ITMILL/themes/" + (theme == null ? "default" : theme)
+ + "/styles.css";
+
+ String appUri = getApplication().getURL().toString();
+
+ 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();};"
+ + "</script>"
+ + "<link rel='stylesheet' href='"
+ + guessedThemeUri
+ + "'/>"
+ + "<link rel='stylesheet' href='"
+ + guessedThemeUri2
+ + "'/>"
+ + "</head><body onload='setTarget();' style='margin:0;padding:0;'>"
+ + "<div class='i-app i-app-loginpage'>"
+ + "<iframe name='logintarget' style='width:0;height:0;"
+ + "border:0;margin:0;padding:0;'></iframe>"
+ + "<form id='loginf' target='logintarget'>"
+ + "<div>Username</div><div >"
+ + "<input class='i-textfield' style='display:block;' type='text' name='username'></div>"
+ + "<div>Password</div>"
+ + "<div><input class='i-textfield' style='display:block;' type='password' name='password'></div>"
+ + "<div><input class='i-button' type='submit' value='Login'></div></form></div>" + "</body></html>")
+ .getBytes();
+ }
+
+ @Override
+ public void attach() {
+ super.attach();
+ getApplication().addResource(loginPage);
+ getWindow().addParameterHandler(paramHandler);
+ iframe.setSource(loginPage);
+ }
+
+ @Override
+ public void detach() {
+ getApplication().removeResource(loginPage);
+ getWindow().removeParameterHandler(paramHandler);
+ // store window temporary to properly remove uri handler once
+ // response is handled. (May happen if login handler removes login
+ // form
+ window = getWindow();
+ if (window.getParent() != null) {
+ window = (Window) window.getParent();
+ }
+ super.detach();
+ }
+
+ /**
+ * This event is sent when login form is submitted.
+ */
+ public class LoginEvent extends Event {
+
+ private Map params;
+
+ private LoginEvent(Map 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 (String) 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;
+
+ 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);
+ }
+
+}
diff --git a/src/com/vaadin/ui/MenuBar.java b/src/com/vaadin/ui/MenuBar.java
new file mode 100644
index 0000000000..0ce6e725d9
--- /dev/null
+++ b/src/com/vaadin/ui/MenuBar.java
@@ -0,0 +1,617 @@
+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;
+
+/**
+ * <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 {
+
+ // Items of the top-level menu
+ private final List<MenuItem> menuItems;
+
+ // Number of items in this menu
+ private static int numberOfItems = 0;
+
+ private boolean collapseItems;
+ private Resource submenuIcon;
+ private MenuItem moreItem;
+
+ /** Tag is the UIDL element name for client-server communications. */
+ @Override
+ public java.lang.String getTag() {
+ return "menubar";
+ }
+
+ /** Paint (serialise) the component for the client. */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Superclass writes any common attributes in the paint target.
+ super.paintContent(target);
+
+ // Stack for list iterators
+ Stack<Iterator<MenuItem>> iteratorStack = new Stack<Iterator<MenuItem>>();
+
+ target.startTag("options");
+
+ if (submenuIcon != null) {
+ target.addAttribute("submenuIcon", submenuIcon);
+ }
+
+ target.addAttribute("collapseItems", collapseItems);
+
+ if (collapseItems) {
+ 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");
+
+ Iterator<MenuItem> itr = menuItems.iterator();
+
+ // This generates the tree from the contents of the menu
+ while (itr.hasNext()) {
+
+ MenuItem item = itr.next();
+
+ target.startTag("item");
+
+ target.addAttribute("text", item.getText());
+ target.addAttribute("id", item.getId());
+
+ Command command = item.getCommand();
+ if (command != null) {
+ target.addAttribute("command", true);
+ } else {
+ target.addAttribute("command", false);
+ }
+
+ Resource icon = item.getIcon();
+ if (icon != null) {
+ target.addAttribute("icon", icon);
+ }
+
+ if (item.hasChildren()) {
+ iteratorStack.push(itr); // For later use
+
+ // Go through the children
+ itr = item.getChildren().iterator();
+ } else {
+ target.endTag("item"); // Item had no children, end description
+ }
+
+ // The end submenu. More than one submenu may end at once.
+ while (!itr.hasNext() && !iteratorStack.empty()) {
+ itr = iteratorStack.pop();
+ target.endTag("item");
+ }
+
+ }
+
+ target.endTag("items");
+ }
+
+ /** Deserialize changes received from client. */
+ @Override
+ public void changeVariables(Object source, Map 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.getCommand().menuSelected(tmpItem);
+ }
+ }// if
+ }// changeVariables
+
+ /**
+ * Constructs an empty, horizontal menu
+ */
+ public MenuBar() {
+ menuItems = new ArrayList<MenuItem>();
+ setCollapse(true);
+ 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 icon to be used if a sub-menu has children. Defaults to null;
+ *
+ * @param icon
+ */
+ public void setSubmenuIcon(Resource icon) {
+ submenuIcon = icon;
+ requestRepaint();
+ }
+
+ /**
+ * Get the icon used for sub-menus. Returns null if no icon is set.
+ *
+ * @return
+ */
+ public Resource getSubmenuIcon() {
+ return submenuIcon;
+ }
+
+ /**
+ * Enable or disable collapsing top-level items. Top-level items will
+ * collapse to if there is not enough room for them. Items that don't fit
+ * will be placed under the "More" menu item.
+ *
+ * Collapsing is enabled by default.
+ *
+ * @param collapse
+ */
+ public void setCollapse(boolean collapse) {
+ collapseItems = collapse;
+ requestRepaint();
+ }
+
+ /**
+ * Collapsing is enabled by default.
+ *
+ * @return true if the top-level items will be collapsed
+ */
+ public boolean getCollapse() {
+ return collapseItems;
+ }
+
+ /**
+ * 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 the "More" text is be
+ * used.
+ *
+ * @param item
+ */
+ public void setMoreMenuItem(MenuItem item) {
+ if (item != null) {
+ moreItem = item;
+ } else {
+ moreItem = new MenuItem("More", null, null);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Get the MenuItem used as the collapse menu item.
+ *
+ * @return
+ */
+ public MenuItem getMoreMenuItem() {
+ return moreItem;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * 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 itsChildren != null;
+ }
+
+ /**
+ * 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
+ */
+ public MenuBar.MenuItem addItem(String caption, Resource icon,
+ MenuBar.Command command) {
+ 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
+ *
+ */
+ public MenuBar.MenuItem addItemBefore(String caption, Resource icon,
+ MenuBar.Command command, MenuBar.MenuItem itemToAddBefore) {
+
+ 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() {
+ return itsChildren.size();
+ }
+
+ /**
+ * 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;
+ }
+
+ }// class MenuItem
+
+}// class MenuBar
diff --git a/src/com/vaadin/ui/NativeSelect.java b/src/com/vaadin/ui/NativeSelect.java
new file mode 100644
index 0000000000..e178a39397
--- /dev/null
+++ b/src/com/vaadin/ui/NativeSelect.java
@@ -0,0 +1,91 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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/src/com/vaadin/ui/OptionGroup.java b/src/com/vaadin/ui/OptionGroup.java
new file mode 100644
index 0000000000..861b57f5c6
--- /dev/null
+++ b/src/com/vaadin/ui/OptionGroup.java
@@ -0,0 +1,41 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * Configures select to be used as an option group.
+ */
+@SuppressWarnings("serial")
+public class OptionGroup extends AbstractSelect {
+
+ 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");
+ super.paintContent(target);
+ }
+
+}
diff --git a/src/com/vaadin/ui/OrderedLayout.java b/src/com/vaadin/ui/OrderedLayout.java
new file mode 100644
index 0000000000..572d621919
--- /dev/null
+++ b/src/com/vaadin/ui/OrderedLayout.java
@@ -0,0 +1,122 @@
+package com.vaadin.ui;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * Ordered layout.
+ *
+ * <code>OrderedLayout</code> is a component container, which shows the
+ * subcomponents in the order of their addition in specified orientation.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ * @deprecated Replaced by VerticalLayout/HorizontalLayout. For type checking
+ * please not that VerticalLayout/HorizontalLayout do not extend
+ * OrderedLayout but AbstractOrderedLayout (which also OrderedLayout
+ * extends).
+ */
+@SuppressWarnings("serial")
+@Deprecated
+public class OrderedLayout extends AbstractOrderedLayout {
+ /* Predefined orientations */
+
+ /**
+ * Components are to be laid out vertically.
+ */
+ public static final int ORIENTATION_VERTICAL = 0;
+
+ /**
+ * Components are to be laid out horizontally.
+ */
+ public static final int ORIENTATION_HORIZONTAL = 1;
+
+ /**
+ * Orientation of the layout.
+ */
+ private int orientation;
+
+ /**
+ * Creates a new ordered layout. The order of the layout is
+ * <code>ORIENTATION_VERTICAL</code>.
+ *
+ * @deprecated Use VerticalLayout instead.
+ */
+ @Deprecated
+ public OrderedLayout() {
+ this(ORIENTATION_VERTICAL);
+ }
+
+ /**
+ * Create a new ordered layout. The orientation of the layout is given as
+ * parameters.
+ *
+ * @param orientation
+ * the Orientation of the layout.
+ *
+ * @deprecated Use VerticalLayout/HorizontalLayout instead.
+ */
+ @Deprecated
+ public OrderedLayout(int orientation) {
+ this.orientation = orientation;
+ if (orientation == ORIENTATION_VERTICAL) {
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+ }
+
+ /**
+ * Gets the orientation of the container.
+ *
+ * @return the Value of property orientation.
+ */
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Sets the orientation of this OrderedLayout. This method should only be
+ * used before initial paint.
+ *
+ * @param orientation
+ * the New value of property orientation.
+ * @deprecated Use VerticalLayout/HorizontalLayout or define orientation in
+ * constructor instead
+ */
+ @Deprecated
+ public void setOrientation(int orientation) {
+ setOrientation(orientation, true);
+ }
+
+ /**
+ * Internal method to change orientation of layout. This method should only
+ * be used before initial paint.
+ *
+ * @param orientation
+ */
+ protected void setOrientation(int orientation, boolean needsRepaint) {
+ // Checks the validity of the argument
+ if (orientation < ORIENTATION_VERTICAL
+ || orientation > ORIENTATION_HORIZONTAL) {
+ throw new IllegalArgumentException();
+ }
+
+ this.orientation = orientation;
+ if (needsRepaint) {
+ requestRepaint();
+ }
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ // Adds the orientation attributes (the default is vertical)
+ if (orientation == ORIENTATION_HORIZONTAL) {
+ target.addAttribute("orientation", "horizontal");
+ }
+
+ }
+
+}
diff --git a/src/com/vaadin/ui/Panel.java b/src/com/vaadin/ui/Panel.java
new file mode 100644
index 0000000000..e50713846c
--- /dev/null
+++ b/src/com/vaadin/ui/Panel.java
@@ -0,0 +1,555 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+import com.vaadin.event.Action;
+import com.vaadin.event.ShortcutAction;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.terminal.KeyMapper;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Scrollable;
+
+/**
+ * Panel - a simple single component container.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Panel extends AbstractComponentContainer implements Scrollable,
+ ComponentContainer.ComponentAttachListener,
+ ComponentContainer.ComponentDetachListener, Action.Container {
+
+ public static final String STYLE_LIGHT = "light";
+
+ /**
+ * Content of the panel.
+ */
+ private ComponentContainer content;
+
+ /**
+ * Scroll X position.
+ */
+ private int scrollOffsetX = 0;
+
+ /**
+ * Scroll Y position.
+ */
+ private int scrollOffsetY = 0;
+
+ /**
+ * Scrolling mode.
+ */
+ private boolean scrollable = false;
+
+ /** List of action handlers */
+ private LinkedList actionHandlers = null;
+
+ /** Action mapper */
+ private KeyMapper actionMapper = null;
+
+ /**
+ * 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) {
+ setContent(content);
+ setWidth(100, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Creates a new empty panel with caption. Default layout is used.
+ *
+ * @param caption
+ * the caption used in the panel.
+ */
+ 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.
+ * @param content
+ * the content used in the panel.
+ */
+ public Panel(String caption, ComponentContainer content) {
+ this(content);
+ setCaption(caption);
+ }
+
+ /**
+ * Gets the current layout of the panel.
+ *
+ * @return the Current layout of the panel.
+ * @deprecated A Panel can now contain a ComponentContainer which is not
+ * necessarily a Layout. Use {@link #getContent()} instead.
+ */
+ @Deprecated
+ public Layout getLayout() {
+ if (content instanceof Layout) {
+ return (Layout) content;
+ } else if (content == null) {
+ return null;
+ }
+
+ throw new IllegalStateException(
+ "Panel does not contain a Layout. Use getContent() instead of getLayout().");
+ }
+
+ /**
+ * Sets the layout of the panel.
+ *
+ * If given layout is null, a VerticalLayout with margins set is used as a
+ * default.
+ *
+ * Components from old layout are not moved to new layout by default
+ * (changed in 5.2.2). Use function in Layout interface manually.
+ *
+ * @param newLayout
+ * the New layout of the panel.
+ * @deprecated A Panel can now contain a ComponentContainer which is not
+ * necessarily a Layout. Use
+ * {@link #setContent(ComponentContainer)} instead.
+ */
+ @Deprecated
+ public void setLayout(Layout newLayout) {
+ setContent(newLayout);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 {
+ content.paint(target);
+
+ if (isScrollable()) {
+ target.addVariable(this, "scrollLeft", getScrollLeft());
+ target.addVariable(this, "scrollTop", getScrollTop());
+ }
+
+ if (actionHandlers != null && !actionHandlers.isEmpty()) {
+ target.addVariable(this, "action", "");
+ target.startTag("actions");
+
+ for (final Iterator ahi = actionHandlers.iterator(); ahi.hasNext();) {
+ final Action[] aa = ((Action.Handler) ahi.next()).getActions(
+ null, this);
+ if (aa != null) {
+ for (int ai = 0; ai < aa.length; ai++) {
+ final Action a = aa[ai];
+ target.startTag("action");
+ final String akey = actionMapper.key(aa[ai]);
+ target.addAttribute("key", akey);
+ if (a.getCaption() != null) {
+ target.addAttribute("caption", a.getCaption());
+ }
+ if (a.getIcon() != null) {
+ target.addAttribute("icon", a.getIcon());
+ }
+ if (a instanceof ShortcutAction) {
+ final ShortcutAction sa = (ShortcutAction) a;
+ target.addAttribute("kc", sa.getKeyCode());
+ final int[] modifiers = sa.getModifiers();
+ if (modifiers != null) {
+ final String[] smodifiers = new String[modifiers.length];
+ for (int i = 0; i < modifiers.length; i++) {
+ smodifiers[i] = String
+ .valueOf(modifiers[i]);
+ }
+ target.addAttribute("mk", smodifiers);
+ }
+ }
+ target.endTag("action");
+ }
+ }
+ }
+ target.endTag("actions");
+ }
+ }
+
+ @Override
+ public void requestRepaintAll() {
+ // Panel has odd structure, delegate to layout
+ requestRepaint();
+ if (getContent() != null) {
+ getContent().requestRepaintAll();
+ }
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "panel";
+ }
+
+ /**
+ * 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 added.
+ * @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 trough all the components
+ * in the container.
+ *
+ * @return the Iterator of the components inside the container.
+ * @see com.vaadin.ui.ComponentContainer#getComponentIterator()
+ */
+ public Iterator getComponentIterator() {
+ return content.getComponentIterator();
+ }
+
+ /**
+ * Called when one or more variables handled by the implementing class are
+ * changed.
+ *
+ * @see com.vaadin.terminal.VariableOwner#changeVariables(Object,
+ * Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map variables) {
+ super.changeVariables(source, 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
+ scrollOffsetX = newScrollX.intValue();
+ }
+ if (newScrollY != null && newScrollY.intValue() != getScrollTop()) {
+ // set internally, not to fire request repaint
+ scrollOffsetY = newScrollY.intValue();
+ }
+
+ // Actions
+ if (variables.containsKey("action")) {
+ final String key = (String) variables.get("action");
+ final Action action = (Action) actionMapper.get(key);
+ if (action != null && actionHandlers != null) {
+ Object[] array = actionHandlers.toArray();
+ for (int i = 0; i < array.length; i++) {
+ ((Action.Handler) array[i])
+ .handleAction(action, this, this);
+ }
+ }
+ }
+
+ }
+
+ /* Scrolling functionality */
+
+ /* Documented in interface */
+ public int getScrollLeft() {
+ return scrollOffsetX;
+ }
+
+ /**
+ * @deprecated use getScrollLeft() instead
+ */
+ @Deprecated
+ public int getScrollOffsetX() {
+ return getScrollLeft();
+ }
+
+ /* Documented in interface */
+ public int getScrollTop() {
+ return scrollOffsetY;
+ }
+
+ /**
+ * @deprecated use getScrollTop() instead
+ */
+ @Deprecated
+ public int getScrollOffsetY() {
+ return getScrollTop();
+ }
+
+ /* Documented in interface */
+ public boolean isScrollable() {
+ return scrollable;
+ }
+
+ /* Documented in interface */
+ public void setScrollable(boolean isScrollingEnabled) {
+ if (scrollable != isScrollingEnabled) {
+ scrollable = isScrollingEnabled;
+ requestRepaint();
+ }
+ }
+
+ /* Documented in interface */
+ public void setScrollLeft(int pixelsScrolled) {
+ if (pixelsScrolled < 0) {
+ throw new IllegalArgumentException(
+ "Scroll offset must be at least 0");
+ }
+ if (scrollOffsetX != pixelsScrolled) {
+ scrollOffsetX = pixelsScrolled;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * @deprecated use setScrollLeft() method instead
+ */
+ @Deprecated
+ public void setScrollOffsetX(int pixels) {
+ setScrollLeft(pixels);
+ }
+
+ /* Documented in interface */
+ public void setScrollTop(int pixelsScrolledDown) {
+ if (pixelsScrolledDown < 0) {
+ throw new IllegalArgumentException(
+ "Scroll offset must be at least 0");
+ }
+ if (scrollOffsetY != pixelsScrolledDown) {
+ scrollOffsetY = pixelsScrolledDown;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * @deprecated use setScrollTop() method instead
+ */
+ @Deprecated
+ public void setScrollOffsetY(int pixels) {
+ setScrollTop(pixels);
+ }
+
+ /* Documented in superclass */
+ 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)
+ */
+ 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)
+ */
+ public void componentDetachedFromContainer(ComponentDetachEvent event) {
+ if (event.getContainer() == content) {
+ fireComponentDetachEvent(event.getDetachedComponent());
+ }
+ }
+
+ /**
+ * Notifies the component that it is connected to an application.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ // can't call parent here as this is Panels hierarchy is a hack
+ requestRepaint();
+ if (content != null) {
+ content.attach();
+ }
+ }
+
+ /**
+ * Notifies the component that it is detached from the application.
+ *
+ * @see com.vaadin.ui.Component#detach()
+ */
+ @Override
+ public void detach() {
+ // can't call parent here as this is Panels hierarchy is a hack
+ if (content != null) {
+ content.detach();
+ }
+ }
+
+ /**
+ * Removes all components from this container.
+ *
+ * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
+ */
+ @Override
+ public void removeAllComponents() {
+ content.removeAllComponents();
+ }
+
+ public void addActionHandler(Handler actionHandler) {
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList();
+ actionMapper = new KeyMapper();
+ }
+
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ requestRepaint();
+ }
+ }
+
+ }
+
+ /**
+ * Removes an action handler.
+ *
+ * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
+ */
+ public void removeActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+
+ actionHandlers.remove(actionHandler);
+
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+
+ requestRepaint();
+ }
+ }
+}
diff --git a/src/com/vaadin/ui/PopupDateField.java b/src/com/vaadin/ui/PopupDateField.java
new file mode 100644
index 0000000000..5164601f0d
--- /dev/null
+++ b/src/com/vaadin/ui/PopupDateField.java
@@ -0,0 +1,52 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Date;
+
+import com.vaadin.data.Property;
+
+/**
+ * <p>
+ * A date entry component, which displays the actual date selector as a popup.
+ *
+ * </p>
+ *
+ * @see DateField
+ * @see InlineDateField
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+public class PopupDateField extends DateField {
+
+ public PopupDateField() {
+ super();
+ type = TYPE_POPUP;
+ }
+
+ public PopupDateField(Property dataSource) throws IllegalArgumentException {
+ super(dataSource);
+ type = TYPE_POPUP;
+ }
+
+ public PopupDateField(String caption, Date value) {
+ super(caption, value);
+ type = TYPE_POPUP;
+ }
+
+ public PopupDateField(String caption, Property dataSource) {
+ super(caption, dataSource);
+ type = TYPE_POPUP;
+ }
+
+ public PopupDateField(String caption) {
+ super(caption);
+ type = TYPE_POPUP;
+ }
+
+}
diff --git a/src/com/vaadin/ui/PopupView.java b/src/com/vaadin/ui/PopupView.java
new file mode 100644
index 0000000000..4fa89a7eb5
--- /dev/null
+++ b/src/com/vaadin/ui/PopupView.java
@@ -0,0 +1,432 @@
+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.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ *
+ * 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 IT Mill Ltd.
+ */
+@SuppressWarnings("serial")
+public class PopupView extends AbstractComponentContainer {
+
+ 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");
+ }
+ }
+
+ /* 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() {
+ public java.lang.String getMinimizedValueAsHTML() {
+ return small;
+ }
+
+ 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 || newContent.getMinimizedValueAsHTML() == null
+ || newContent.getPopupComponent() == null) {
+ throw new IllegalArgumentException(
+ "Content object is or contains 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 automaticly 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()
+ */
+ public Iterator<Component> getComponentIterator() {
+ return new Iterator<Component>() {
+
+ private boolean first = visibleComponent == null;
+
+ public boolean hasNext() {
+ return !first;
+ }
+
+ public Component next() {
+ if (!first) {
+ first = true;
+ return visibleComponent;
+ } else {
+ return null;
+ }
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ }
+
+ /**
+ * 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
+ */
+ 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.
+ */
+
+ /**
+ * @see com.vaadin.ui.AbstractComponent#getTag()
+ */
+ @Override
+ public java.lang.String getTag() {
+ return "popupview";
+ }
+
+ /**
+ * 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 {
+ // Superclass writes any common attributes in the paint target.
+ super.paintContent(target);
+
+ String html = content.getMinimizedValueAsHTML();
+ if (html == null) {
+ throw new PaintException(
+ "Recieved null when trying to paint minimized value.");
+ }
+ target.addAttribute("html", content.getMinimizedValueAsHTML());
+ target.addAttribute("hideOnMouseOut", hideOnMouseOut);
+
+ // Only paint component to client if we know that the popup is showing
+ if (isPopupVisible()) {
+ target.startTag("popupComponent");
+ visibleComponent.paint(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 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/src/com/vaadin/ui/ProgressIndicator.java b/src/com/vaadin/ui/ProgressIndicator.java
new file mode 100644
index 0000000000..1ad5848dfb
--- /dev/null
+++ b/src/com/vaadin/ui/ProgressIndicator.java
@@ -0,0 +1,267 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.data.Property;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * <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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 4
+ */
+@SuppressWarnings("serial")
+public class ProgressIndicator extends AbstractField implements Property,
+ Property.Viewer, Property.ValueChangeListener {
+
+ /**
+ * 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(new Float(0), Float.class));
+ }
+
+ /**
+ * Creates a new instance of ProgressIndicator with given state.
+ *
+ * @param value
+ */
+ public ProgressIndicator(Float value) {
+ setPropertyDataSource(new ObjectProperty(value, Float.class));
+ }
+
+ /**
+ * Creates a new instance of ProgressIndicator with stae read from given
+ * datasource.
+ *
+ * @param contentSource
+ */
+ public ProgressIndicator(Property contentSource) {
+ setPropertyDataSource(contentSource);
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "progressindicator";
+ }
+
+ /**
+ * 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 Object getValue() {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be set");
+ }
+ return 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(java.lang.Object)
+ */
+ @Override
+ public void setValue(Object newValue) {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be set");
+ }
+ dataSource.setValue(newValue);
+ }
+
+ /**
+ * @see com.vaadin.ui.AbstractField#toString()
+ */
+ @Override
+ public String toString() {
+ if (dataSource == null) {
+ throw new IllegalStateException("Datasource must be set");
+ }
+ return dataSource.toString();
+ }
+
+ /**
+ * @see com.vaadin.ui.AbstractField#getType()
+ */
+ @Override
+ public Class 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;
+ }
+
+}
diff --git a/src/com/vaadin/ui/RichTextArea.java b/src/com/vaadin/ui/RichTextArea.java
new file mode 100644
index 0000000000..4c1c281811
--- /dev/null
+++ b/src/com/vaadin/ui/RichTextArea.java
@@ -0,0 +1,35 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * 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.
+ */
+@SuppressWarnings("serial")
+public class RichTextArea extends TextField {
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ target.addAttribute("richtext", true);
+ super.paintContent(target);
+ }
+
+ /**
+ * RichTextArea does not support input prompt.
+ */
+ @Override
+ public void setInputPrompt(String inputPrompt) {
+ throw new UnsupportedOperationException(
+ "RichTextArea does not support inputPrompt");
+ }
+
+}
diff --git a/src/com/vaadin/ui/Select.java b/src/com/vaadin/ui/Select.java
new file mode 100644
index 0000000000..ce3fb7e687
--- /dev/null
+++ b/src/com/vaadin/ui/Select.java
@@ -0,0 +1,476 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+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.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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Select extends AbstractSelect implements AbstractSelect.Filtering {
+
+ /**
+ * 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;
+ private List filteredOptions;
+
+ /**
+ * Flag to indicate that request repaint is called by filter request only
+ */
+ private boolean optionRequest;
+
+ /* 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);
+ 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);
+ }
+
+ // Adds the required attribute
+ if (isRequired()) {
+ target.addAttribute("required", 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("filteringmode", getFilteringMode());
+
+ // Paints the options and create array of selected id keys
+ // TODO Also use conventional rendering if lazy loading is not supported
+ // by terminal
+ int keyIndex = 0;
+
+ target.startTag("options");
+
+ if (currentPage < 0) {
+ optionRequest = false;
+ currentPage = 0;
+ filterstring = "";
+ }
+
+ List options = getFilteredOptions();
+ options = sanitetizeList(options, needNullSelectOption);
+
+ final boolean paintNullSelection = needNullSelectOption
+ && (currentPage == 0 && (filterstring == null || filterstring
+ .equals("")));
+
+ 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 (filteredOptions != null) {
+ target.addAttribute("totalMatches", filteredOptions.size()
+ + (needNullSelectOption ? 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;
+ }
+
+ /**
+ * 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.
+ *
+ * @param options
+ * @param needNullSelectOption
+ * flag to indicate if nullselect option needs to be taken into
+ * consideration
+ */
+ private List sanitetizeList(List options, boolean needNullSelectOption) {
+
+ if (options.size() > pageLength) {
+ int first = currentPage * pageLength;
+ int last = first + pageLength;
+ if (needNullSelectOption) {
+ if (currentPage > 0) {
+ first--;
+ }
+ last--;
+ }
+ if (options.size() < last) {
+ last = options.size();
+ }
+ if (!optionRequest) {
+ // TODO ensure proper page
+ if (!isMultiSelect()) {
+ Object selection = getValue();
+ if (selection != null) {
+ int index = options.indexOf(selection);
+ if (index != -1 && (index < first || index >= last)) {
+ int newPage = (index + (needNullSelectOption ? 1
+ : 0))
+ / pageLength;
+ currentPage = newPage;
+ return sanitetizeList(options, needNullSelectOption);
+ }
+ }
+ }
+ }
+
+ return options.subList(first, last);
+ } else {
+ return options;
+ }
+ }
+
+ protected List getFilteredOptions() {
+ if (filterstring == null || filterstring.equals("")
+ || filteringMode == FILTERINGMODE_OFF) {
+ prevfilterstring = null;
+ filteredOptions = new LinkedList(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();
+ 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 variables) {
+
+ // 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 s = new LinkedList();
+ 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) {
+ Set newsel = (Set) getValue();
+ if (newsel == null) {
+ newsel = new HashSet();
+ } else {
+ newsel = new HashSet(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();
+ return;
+ }
+
+ // Try to set the property value
+
+ // 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;
+ }
+
+ }
+
+ @Override
+ public void requestRepaint() {
+ super.requestRepaint();
+ optionRequest = false;
+ prevfilterstring = filterstring;
+ filterstring = null;
+ }
+
+ private void optionRepaint() {
+ super.requestRepaint();
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "select";
+ }
+
+ public void setFilteringMode(int filteringMode) {
+ this.filteringMode = filteringMode;
+ }
+
+ 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;
+ }
+
+}
diff --git a/src/com/vaadin/ui/Slider.java b/src/com/vaadin/ui/Slider.java
new file mode 100644
index 0000000000..ec9fc2c90b
--- /dev/null
+++ b/src/com/vaadin/ui/Slider.java
@@ -0,0 +1,503 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Map;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * 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 IT Mill Ltd.
+ */
+@SuppressWarnings("serial")
+public class Slider extends AbstractField {
+
+ public static final int ORIENTATION_HORIZONTAL = 0;
+
+ public static final int ORIENTATION_VERTICAL = 1;
+
+ /**
+ * Style constant representing a scrollbar styled slider. Use this with
+ * {@link #addStyleName(String)}. Default styling usually represents a
+ * common slider found e.g. in Adobe Photoshop. The client side
+ * implementation dictates how different styles will look.
+ */
+ @Deprecated
+ public static final String STYLE_SCROLLBAR = "scrollbar";
+
+ /** Minimum value of slider */
+ private double min = 0;
+
+ /** Maximum value of slider */
+ private double max = 100;
+
+ /**
+ * Resolution, how many digits are considered relevant after desimal point.
+ * Must be a non-negative value
+ */
+ private int resolution = 0;
+
+ /**
+ * Slider orientation (horizontal/vertical), defaults .
+ */
+ private int orientation = ORIENTATION_HORIZONTAL;
+
+ /**
+ * Slider size in pixels. In horizontal mode, if set to -1, allow 100% width
+ * of container. In vertical mode, if set to -1, default height is
+ * determined by the client-side implementation.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ private int size = -1;
+
+ /**
+ * Handle (draggable control element) size in percents relative to base
+ * size. Must be a value between 1-99. Other values are converted to nearest
+ * bound. A negative value sets the width to auto (client-side
+ * implementation calculates).
+ *
+ * @deprecated The size is dictated by the current theme.
+ */
+ @Deprecated
+ private int handleSize = -1;
+
+ /**
+ * Show arrows that can be pressed to slide the handle in some increments
+ * (client-side implementation decides the increment, usually somewhere
+ * between 5-10% of slide range).
+ */
+ @Deprecated
+ private final boolean arrows = false;
+
+ /**
+ * 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. All slider
+ * values set to defaults.
+ *
+ * @param caption
+ * The caption for this Slider (e.g. "Volume").
+ */
+ public Slider(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Create a new slider with given range and resolution
+ *
+ * @param min
+ * @param max
+ * @param resolution
+ */
+ public Slider(double min, double max, int resolution) {
+ this();
+ setMin(min);
+ setMax(max);
+ setResolution(resolution);
+ }
+
+ /**
+ * Create a new slider with given range
+ *
+ * @param min
+ * @param max
+ */
+ public Slider(int min, int max) {
+ this();
+ setMin(min);
+ setMax(max);
+ setResolution(0);
+ }
+
+ /**
+ * Create a new slider with given caption and range
+ *
+ * @param caption
+ * @param min
+ * @param max
+ */
+ public Slider(String caption, int min, int max) {
+ this(min, max);
+ setCaption(caption);
+ }
+
+ /**
+ * Gets the biggest possible value in Sliders range.
+ *
+ * @return the biggest value slider can have
+ */
+ public double getMax() {
+ return max;
+ }
+
+ /**
+ * Set the maximum value of the Slider. If the current value of the Slider
+ * is out of new bounds, the value is set to new minimum.
+ *
+ * @param max
+ * New maximum value of the Slider.
+ */
+ public void setMax(double max) {
+ this.max = max;
+ try {
+ if ((new Double(getValue().toString())).doubleValue() > max) {
+ super.setValue(new Double(max));
+ }
+ } catch (final ClassCastException e) {
+ // FIXME: Handle exception
+ /*
+ * Where does ClassCastException come from? Can't see any casts
+ * above
+ */
+ super.setValue(new Double(max));
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Gets the minimum value in Sliders range.
+ *
+ * @return the smalles value slider can have
+ */
+ public double getMin() {
+ return min;
+ }
+
+ /**
+ * Set the minimum value of the Slider. If the current value of the Slider
+ * is out of new bounds, the value is set to new minimum.
+ *
+ * @param min
+ * New minimum value of the Slider.
+ */
+ public void setMin(double min) {
+ this.min = min;
+ try {
+ if ((new Double(getValue().toString())).doubleValue() < min) {
+ super.setValue(new Double(min));
+ }
+ } catch (final ClassCastException e) {
+ // FIXME: Handle exception
+ /*
+ * Where does ClassCastException come from? Can't see any casts
+ * above
+ */
+ super.setValue(new Double(min));
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Get the current orientation of the Slider (horizontal or vertical).
+ *
+ * @return orientation
+ */
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Set the orientation of the Slider.
+ *
+ * @param int new orientation
+ */
+ public void setOrientation(int orientation) {
+ this.orientation = orientation;
+ requestRepaint();
+ }
+
+ /**
+ * Get the current resolution of the Slider.
+ *
+ * @return resolution
+ */
+ public int getResolution() {
+ return resolution;
+ }
+
+ /**
+ * Set a new resolution for the Slider.
+ *
+ * @param resolution
+ */
+ public void setResolution(int resolution) {
+ if (resolution < 0) {
+ return;
+ }
+ this.resolution = resolution;
+ requestRepaint();
+ }
+
+ /**
+ * Set the value of this Slider.
+ *
+ * @param value
+ * New value of Slider. Must be within Sliders range (min - max),
+ * otherwise throws an exception.
+ * @param repaintIsNotNeeded
+ * If true, client-side is not requested to repaint itself.
+ * @throws ValueOutOfBoundsException
+ */
+ public void setValue(Double value, boolean repaintIsNotNeeded)
+ throws ValueOutOfBoundsException {
+ 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(new Double(newValue), repaintIsNotNeeded);
+ }
+
+ /**
+ * Set the value of this Slider.
+ *
+ * @param value
+ * New value of Slider. Must be within Sliders range (min - max),
+ * otherwise throws an exception.
+ * @throws ValueOutOfBoundsException
+ */
+ public void setValue(Double value) throws ValueOutOfBoundsException {
+ setValue(value, false);
+ }
+
+ /**
+ * Set the value of this Slider.
+ *
+ * @param value
+ * New value of Slider. Must be within Sliders range (min - max),
+ * otherwise throws an exception.
+ * @throws ValueOutOfBoundsException
+ */
+ public void setValue(double value) throws ValueOutOfBoundsException {
+ setValue(new Double(value), false);
+ }
+
+ /**
+ * Get the current Slider size.
+ *
+ * @return size in pixels or -1 for auto sizing.
+ * @deprecated use standard getWidth/getHeight instead
+ */
+ @Deprecated
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Set the size for this Slider.
+ *
+ * @param size
+ * in pixels, or -1 auto sizing.
+ * @deprecated use standard setWidth/setHeight instead
+ */
+ @Deprecated
+ public void setSize(int size) {
+ this.size = size;
+ switch (orientation) {
+ case ORIENTATION_HORIZONTAL:
+ setWidth(size, UNITS_PIXELS);
+ break;
+ default:
+ setHeight(size, UNITS_PIXELS);
+ break;
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Get the handle size of this Slider.
+ *
+ * @return handle size in percentages.
+ * @deprecated The size is dictated by the current theme.
+ */
+ @Deprecated
+ public int getHandleSize() {
+ return handleSize;
+ }
+
+ /**
+ * Set the handle size of this Slider.
+ *
+ * @param handleSize
+ * in percentages relative to slider base size.
+ * @deprecated The size is dictated by the current theme.
+ */
+ @Deprecated
+ public void setHandleSize(int handleSize) {
+ if (handleSize < 0) {
+ this.handleSize = -1;
+ } else if (handleSize > 99) {
+ this.handleSize = 99;
+ } else if (handleSize < 1) {
+ this.handleSize = 1;
+ } else {
+ this.handleSize = handleSize;
+ }
+ requestRepaint();
+ }
+
+ @Override
+ public String getTag() {
+ return "slider";
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ 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", ((Double) getValue())
+ .doubleValue());
+ } else {
+ target.addVariable(this, "value", ((Double) getValue()).intValue());
+ }
+
+ if (orientation == ORIENTATION_VERTICAL) {
+ target.addAttribute("vertical", true);
+ }
+
+ if (arrows) {
+ target.addAttribute("arrows", true);
+ }
+
+ if (size > -1) {
+ target.addAttribute("size", size);
+ }
+
+ if (min != max && min < max) {
+ target.addAttribute("hsize", handleSize);
+ } else {
+ target.addAttribute("hsize", 100);
+ }
+
+ }
+
+ /**
+ * 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 variables) {
+ super.changeVariables(source, 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);
+ }
+ }
+ }
+ }
+
+ /**
+ * ValueOutOfBoundsException
+ *
+ * @author IT Mill Ltd.
+ *
+ */
+ public class ValueOutOfBoundsException extends Exception {
+
+ private final Double value;
+
+ /**
+ * Constructs an <code>ValueOutOfBoundsException</code> with the
+ * specified detail message.
+ *
+ * @param valueOutOfBounds
+ */
+ public ValueOutOfBoundsException(Double valueOutOfBounds) {
+ value = valueOutOfBounds;
+ }
+
+ public Double getValue() {
+ return value;
+ }
+
+ }
+
+ @Override
+ public Class getType() {
+ return Double.class;
+ }
+
+}
diff --git a/src/com/vaadin/ui/SplitPanel.java b/src/com/vaadin/ui/SplitPanel.java
new file mode 100644
index 0000000000..bad238d888
--- /dev/null
+++ b/src/com/vaadin/ui/SplitPanel.java
@@ -0,0 +1,330 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.RenderInformation.Size;
+
+/**
+ * SplitPanel.
+ *
+ * <code>SplitPanel</code> is a component container, that can contain two
+ * components (possibly containers) which are split by divider element.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+public class SplitPanel extends AbstractLayout {
+
+ /* Predefined orientations */
+
+ /**
+ * Components are to be layed out vertically.
+ */
+ public static final int ORIENTATION_VERTICAL = 0;
+
+ /**
+ * Components are to be layed out horizontally.
+ */
+ public static final int ORIENTATION_HORIZONTAL = 1;
+
+ private Component firstComponent;
+
+ private Component secondComponent;
+
+ /**
+ * Orientation of the layout.
+ */
+ private int orientation;
+
+ private int pos = 50;
+
+ private int posUnit = UNITS_PERCENTAGE;
+
+ private boolean locked = false;
+
+ /**
+ * Creates a new split panel. The orientation of the panels is
+ * <code>ORIENTATION_VERTICAL</code>.
+ */
+ public SplitPanel() {
+ orientation = ORIENTATION_VERTICAL;
+ setSizeFull();
+ }
+
+ /**
+ * Create a new split panels. The orientation of the panel is given as
+ * parameters.
+ *
+ * @param orientation
+ * the Orientation of the layout.
+ */
+ public SplitPanel(int orientation) {
+ this();
+ setOrientation(orientation);
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ if (orientation == ORIENTATION_HORIZONTAL) {
+ return "hsplitpanel";
+ } else {
+ return "vsplitpanel";
+ }
+ }
+
+ /**
+ * 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 (firstComponent == null) {
+ firstComponent = c;
+ } else if (secondComponent == null) {
+ secondComponent = c;
+ } else {
+ throw new UnsupportedOperationException(
+ "Split panel can contain only two components");
+ }
+ super.addComponent(c);
+ requestRepaint();
+ }
+
+ public void setFirstComponent(Component c) {
+ if (firstComponent != null) {
+ // detach old
+ removeComponent(firstComponent);
+ }
+ firstComponent = c;
+ super.addComponent(c);
+ }
+
+ public void setSecondComponent(Component c) {
+ if (secondComponent != null) {
+ // detach old
+ removeComponent(secondComponent);
+ }
+ secondComponent = c;
+ super.addComponent(c);
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @param c
+ * the component to be removed.
+ */
+ @Override
+ public void removeComponent(Component c) {
+ super.removeComponent(c);
+ if (c == firstComponent) {
+ firstComponent = null;
+ } else {
+ secondComponent = null;
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Gets the component container iterator for going trough all the components
+ * in the container.
+ *
+ * @return the Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return new Iterator() {
+ int i = 0;
+
+ public boolean hasNext() {
+ if (i < (firstComponent == null ? 0 : 1)
+ + (secondComponent == null ? 0 : 1)) {
+ return true;
+ }
+ return false;
+ }
+
+ public Object next() {
+ if (!hasNext()) {
+ return null;
+ }
+ i++;
+ if (i == 1) {
+ return firstComponent == null ? secondComponent
+ : firstComponent;
+ } else if (i == 2) {
+ return secondComponent;
+ }
+ return null;
+ }
+
+ public void remove() {
+ if (i == 1) {
+ if (firstComponent != null) {
+ setFirstComponent(null);
+ i = 0;
+ } else {
+ setSecondComponent(null);
+ }
+ } else if (i == 2) {
+ setSecondComponent(null);
+ }
+ }
+ };
+ }
+
+ /**
+ * 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 {
+ super.paintContent(target);
+
+ final String position = pos + UNIT_SYMBOLS[posUnit];
+
+ target.addAttribute("position", position);
+
+ if (isLocked()) {
+ target.addAttribute("locked", true);
+ }
+
+ if (firstComponent != null) {
+ firstComponent.paint(target);
+ } else {
+ VerticalLayout temporaryComponent = new VerticalLayout();
+ temporaryComponent.setParent(this);
+ temporaryComponent.paint(target);
+ }
+ if (secondComponent != null) {
+ secondComponent.paint(target);
+ } else {
+ VerticalLayout temporaryComponent = new VerticalLayout();
+ temporaryComponent.setParent(this);
+ temporaryComponent.paint(target);
+ }
+ }
+
+ /**
+ * Gets the orientation of the container.
+ *
+ * @return the Value of property orientation.
+ */
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Set the orientation of the container.
+ *
+ * @param orientation
+ * the New value of property orientation.
+ */
+ public void setOrientation(int orientation) {
+
+ // Checks the validity of the argument
+ if (orientation < ORIENTATION_VERTICAL
+ || orientation > ORIENTATION_HORIZONTAL) {
+ throw new IllegalArgumentException();
+ }
+
+ this.orientation = orientation;
+ requestRepaint();
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+ if (oldComponent == firstComponent) {
+ setFirstComponent(newComponent);
+ } else if (oldComponent == secondComponent) {
+ setSecondComponent(newComponent);
+ }
+ requestRepaint();
+ }
+
+ /**
+ * Moves the position of the splitter.
+ *
+ * @param pos
+ * the new size of the first region in persentage
+ */
+ public void setSplitPosition(int pos) {
+ setSplitPosition(pos, UNITS_PERCENTAGE);
+ }
+
+ /**
+ * Moves the position of the splitter with given position and unit.
+ *
+ * @param pos
+ * size of the first region
+ * @param unit
+ * the unit (from {@link Size}) in which the size is given.
+ */
+ public void setSplitPosition(int pos, int unit) {
+ this.pos = pos;
+ posUnit = unit;
+ 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) {
+ this.locked = 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 locked;
+ }
+
+ /*
+ * 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 variables) {
+
+ super.changeVariables(source, variables);
+
+ if (variables.containsKey("position") && !isLocked()) {
+ Integer newPos = (Integer) variables.get("position");
+ // Client always sends pixel values
+ setSplitPosition(newPos, UNITS_PIXELS);
+ }
+
+ }
+
+}
diff --git a/src/com/vaadin/ui/TabSheet.java b/src/com/vaadin/ui/TabSheet.java
new file mode 100644
index 0000000000..b0dac11767
--- /dev/null
+++ b/src/com/vaadin/ui/TabSheet.java
@@ -0,0 +1,790 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+import com.vaadin.terminal.ErrorMessage;
+import com.vaadin.terminal.KeyMapper;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Paintable.RepaintRequestListener;
+
+/**
+ * Tabsheet component.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class TabSheet extends AbstractComponentContainer implements
+ RepaintRequestListener {
+
+ /**
+ * Linked list of component tabs.
+ */
+ private final LinkedList components = new LinkedList();
+
+ /**
+ * Map containing information related to the tabs (caption, icon etc).
+ */
+ private final HashMap<Component, Tab> tabs = new HashMap<Component, Tab>();
+
+ /**
+ * Selected tab.
+ */
+ private Component selected = null;
+
+ private final KeyMapper keyMapper = new KeyMapper();
+
+ /**
+ * Holds the value of property tabsHIdden.
+ */
+ private boolean tabsHidden;
+
+ private LinkedList paintedTabs = new LinkedList();
+
+ /**
+ * Constructs a new Tabsheet. Tabsheet is immediate by default.
+ */
+ public TabSheet() {
+ super();
+ // expand horizontally by default
+ setWidth(100, UNITS_PERCENTAGE);
+ setImmediate(true);
+ }
+
+ /**
+ * Gets the component container iterator for going trough all the components
+ * in the container.
+ *
+ * @return the Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return java.util.Collections.unmodifiableList(components).iterator();
+ }
+
+ /**
+ * Removes the component from this container.
+ *
+ * @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()) {
+ selected = null;
+ } else {
+ selected = (Component) components.getFirst();
+ fireSelectedTabChange();
+ }
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Adds a new tab into TabSheet. Components caption and icon are rendered
+ * into tab.
+ *
+ * @param c
+ * the component to be added.
+ */
+ @Override
+ public void addComponent(Component c) {
+ addTab(c);
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * @param c
+ * the component to be added onto tab.
+ * @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 tab
+ */
+ public Tab addTab(Component c, String caption, Resource icon) {
+ if (c != null) {
+ components.addLast(c);
+ Tab tab = new TabSheetTabImpl(caption, icon);
+
+ tabs.put(c, tab);
+ if (selected == null) {
+ selected = c;
+ fireSelectedTabChange();
+ }
+ super.addComponent(c);
+ requestRepaint();
+ return tab;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Adds a new tab into TabSheet. Components caption and icon are rendered
+ * into tab.
+ *
+ * @param c
+ * the component to be added onto tab.
+ * @return the created tab
+ */
+ public Tab addTab(Component c) {
+ if (c != null) {
+ return addTab(c, c.getCaption(), c.getIcon());
+ }
+ return null;
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "tabsheet";
+ }
+
+ /**
+ * Moves all components from another container to this container. The
+ * components are removed from the other container.
+ *
+ * @param source
+ * the container components are removed from.
+ */
+ @Override
+ public void moveComponentsFrom(ComponentContainer source) {
+ for (final Iterator i = source.getComponentIterator(); i.hasNext();) {
+ final Component c = (Component) 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 event
+ * the Paint Event.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (areTabsHidden()) {
+ target.addAttribute("hidetabs", true);
+ }
+
+ target.startTag("tabs");
+
+ for (final Iterator i = getComponentIterator(); i.hasNext();) {
+ final Component 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()) {
+ selected = component;
+ } else {
+ /*
+ * The current selection is not valid but this tab cannot be
+ * selected either.
+ */
+ selected = null;
+ }
+ }
+ target.startTag("tab");
+ if (!tab.isEnabled() && tab.isVisible()) {
+ target.addAttribute("disabled", true);
+ }
+
+ if (!tab.isVisible()) {
+ target.addAttribute("hidden", true);
+ }
+
+ final Resource icon = tab.getIcon();
+ if (icon != null) {
+ target.addAttribute("icon", icon);
+ }
+ final String caption = tab.getCaption();
+ if (caption != null && caption.length() > 0) {
+ target.addAttribute("caption", caption);
+ }
+
+ final String description = tab.getDescription();
+ if (description != null) {
+ target.addAttribute("description", description);
+ }
+
+ final ErrorMessage componentError = tab.getComponentError();
+ if (componentError != null) {
+ componentError.paint(target);
+ }
+
+ target.addAttribute("key", keyMapper.key(component));
+ if (component.equals(selected)) {
+ target.addAttribute("selected", true);
+ component.paint(target);
+ paintedTabs.add(component);
+ } else if (paintedTabs.contains(component)) {
+ component.paint(target);
+ } else {
+ component.requestRepaintRequests();
+ }
+ target.endTag("tab");
+ }
+
+ target.endTag("tabs");
+
+ if (selected != null) {
+ target.addVariable(this, "selected", keyMapper.key(selected));
+ }
+ }
+
+ /**
+ * Are tabs hidden.
+ *
+ * @return the Property visibility.
+ */
+ public boolean areTabsHidden() {
+ return tabsHidden;
+ }
+
+ /**
+ * Setter for property tabsHidden.
+ *
+ * @param tabsHidden
+ * True if the tabs should be hidden.
+ */
+ public void hideTabs(boolean tabsHidden) {
+ this.tabsHidden = tabsHidden;
+ requestRepaint();
+ }
+
+ /**
+ * Gets the caption for a component.
+ *
+ * @param c
+ * the component.
+ * @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 tabs captions.
+ *
+ * @param c
+ * the component.
+ * @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 component.
+ *
+ * @param c
+ * the component.
+ * @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.
+ *
+ * Normally TabSheet uses icon from component
+ *
+ * @param c
+ * the component
+ * @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 Tab for the component. The Tab object can be used for setting
+ * caption,icon, etc for the tab.
+ *
+ * @param c
+ * the component
+ * @return
+ */
+ public Tab getTab(Component c) {
+ return tabs.get(c);
+ }
+
+ /**
+ * Sets the selected tab.
+ *
+ * @param c
+ */
+ public void setSelectedTab(Component c) {
+ if (c != null && components.contains(c) && !selected.equals(c)) {
+ selected = c;
+ fireSelectedTabChange();
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the selected tab.
+ *
+ * @return the selected tab.
+ */
+ public Component getSelectedTab() {
+ return selected;
+ }
+
+ /**
+ * 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 variables) {
+ if (variables.containsKey("selected")) {
+ setSelectedTab((Component) keyMapper.get((String) variables
+ .get("selected")));
+ }
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(Component oldComponent, Component newComponent) {
+
+ if (selected == oldComponent) {
+ // keep selection w/o selectedTabChange event
+ selected = newComponent;
+ }
+
+ Tab newTab = tabs.get(newComponent);
+ Tab oldTab = tabs.get(oldComponent);
+
+ // Gets the captions
+ String oldCaption = null;
+ Resource oldIcon = null;
+ String newCaption = null;
+ Resource newIcon = null;
+
+ if (oldTab != null) {
+ oldCaption = oldTab.getCaption();
+ oldIcon = oldTab.getIcon();
+ }
+
+ if (newTab != null) {
+ newCaption = newTab.getCaption();
+ newIcon = newTab.getIcon();
+ } else {
+ newCaption = newComponent.getCaption();
+ newIcon = newComponent.getIcon();
+ }
+
+ // Gets the locations
+ int oldLocation = -1;
+ int newLocation = -1;
+ int location = 0;
+ for (final Iterator i = components.iterator(); i.hasNext();) {
+ final Component 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);
+ keyMapper.remove(oldComponent);
+ newTab = addTab(newComponent);
+ components.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ newTab.setCaption(oldCaption);
+ newTab.setIcon(oldIcon);
+ } 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);
+ }
+
+ if (newTab != null) {
+ // This should always be true
+ newTab.setCaption(oldCaption);
+ newTab.setIcon(oldIcon);
+ }
+ if (oldTab != null) {
+ // This should always be true
+ oldTab.setCaption(newCaption);
+ oldTab.setIcon(newIcon);
+ }
+
+ 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 thrown, when the selected tab in
+ * the tab sheet is changed.
+ *
+ * @author IT Mill 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
+ *
+ * @author IT Mill Ltd.
+ *
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface SelectedTabChangeListener extends Serializable {
+
+ /**
+ * Visible tab in tab sheet has has been changed.
+ *
+ * @param event
+ * the Selected tab change event.
+ */
+ public void selectedTabChange(SelectedTabChangeEvent event);
+ }
+
+ /**
+ * Adds the selected tab change listener
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addListener(SelectedTabChangeListener listener) {
+ addListener(SelectedTabChangeEvent.class, listener,
+ SELECTED_TAB_CHANGE_METHOD);
+ }
+
+ /**
+ * Removes the selected tab change listener
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(SelectedTabChangeListener listener) {
+ removeListener(SelectedTabChangeEvent.class, listener,
+ SELECTED_TAB_CHANGE_METHOD);
+ }
+
+ /**
+ * Emits the options change event.
+ */
+ protected void fireSelectedTabChange() {
+ fireEvent(new SelectedTabChangeEvent(this));
+ }
+
+ /*
+ * If child is not rendered on the client we need to repaint on child
+ * repaint due the way captions and icons are handled.
+ */
+ public void repaintRequested(RepaintRequestEvent event) {
+ if (!paintedTabs.contains(event.getPaintable())) {
+ requestRepaint();
+ }
+ }
+
+ @Override
+ public void detach() {
+ super.detach();
+ paintedTabs.clear();
+ }
+
+ /**
+ *
+ */
+ public interface Tab extends Serializable {
+ /**
+ * Returns the visible status for the tab.
+ *
+ * @return true for visible, false for hidden
+ */
+ public boolean isVisible();
+
+ /**
+ * Sets the visible status for the tab.
+ *
+ * @param visible
+ * true for visible, false for hidden
+ */
+ public void setVisible(boolean visible);
+
+ /**
+ * Returns the enabled status for the tab.
+ *
+ * @return true for enabled, false for disabled
+ */
+ public boolean isEnabled();
+
+ /**
+ * Sets the enabled status for the tab.
+ *
+ * @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.
+ *
+ * @return the description for the tab
+ */
+ public String getDescription();
+
+ /**
+ * Sets the description for the tab.
+ *
+ * @param description
+ * the new description string for the tab.
+ */
+ public void setDescription(String description);
+
+ public void setComponentError(ErrorMessage componentError);
+
+ public ErrorMessage getComponentError();
+
+ }
+
+ /**
+ * TabSheet's implementation of Tab
+ *
+ */
+ public class TabSheetTabImpl implements Tab {
+
+ private String caption = "";
+ private Resource icon = null;
+ private boolean enabled = true;
+ private boolean visible = true;
+ private String description = null;
+ private ErrorMessage componentError = null;
+
+ public TabSheetTabImpl(String caption, Resource icon) {
+ if (caption == null) {
+ caption = "";
+ }
+ this.caption = caption;
+ this.icon = icon;
+ }
+
+ /**
+ * Returns the tab caption. Can never be null.
+ */
+ public String getCaption() {
+ return caption;
+ }
+
+ public void setCaption(String caption) {
+ this.caption = caption;
+ requestRepaint();
+ }
+
+ public Resource getIcon() {
+ return icon;
+ }
+
+ public void setIcon(Resource icon) {
+ this.icon = icon;
+ requestRepaint();
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ requestRepaint();
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ requestRepaint();
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ requestRepaint();
+ }
+
+ public ErrorMessage getComponentError() {
+ return componentError;
+ }
+
+ public void setComponentError(ErrorMessage componentError) {
+ this.componentError = componentError;
+ requestRepaint();
+ }
+
+ }
+}
diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java
new file mode 100644
index 0000000000..8df31ace01
--- /dev/null
+++ b/src/com/vaadin/ui/Table.java
@@ -0,0 +1,3222 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+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 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.event.Action;
+import com.vaadin.event.ItemClickEvent;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.event.ItemClickEvent.ItemClickSource;
+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.MouseEventDetails;
+
+/**
+ * <p>
+ * <code>TableComponent</code> is used for representing data or components in
+ * pageable and selectable table.
+ * </p>
+ *
+ * <p>
+ * Note! Since version 5, components in Table will not have their caption nor
+ * icon rendered. In order to workaround this limitation, wrap your component in
+ * a Layout.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Table extends AbstractSelect implements Action.Container,
+ Container.Ordered, Container.Sortable, ItemClickSource {
+
+ private static final int CELL_KEY = 0;
+
+ private static final int CELL_HEADER = 1;
+
+ private static final int CELL_ICON = 2;
+
+ private static final int CELL_ITEMID = 3;
+
+ private static final int CELL_FIRSTCOL = 4;
+
+ /**
+ * Left column alignment. <b>This is the default behaviour. </b>
+ */
+ public static final String ALIGN_LEFT = "b";
+
+ /**
+ * Center column alignment.
+ */
+ public static final String ALIGN_CENTER = "c";
+
+ /**
+ * Right column alignment.
+ */
+ public static final String ALIGN_RIGHT = "e";
+
+ /**
+ * Column header mode: Column headers are hidden. <b>This is the default
+ * behavior. </b>
+ */
+ public static final int COLUMN_HEADER_MODE_HIDDEN = -1;
+
+ /**
+ * Column header mode: Property ID:s are used as column headers.
+ */
+ public static final int COLUMN_HEADER_MODE_ID = 0;
+
+ /**
+ * Column header mode: Column headers are explicitly specified with
+ * <code>setColumnHeaders</code>.
+ */
+ public static final int COLUMN_HEADER_MODE_EXPLICIT = 1;
+
+ /**
+ * Column header mode: Column headers are explicitly specified with
+ * <code>setColumnHeaders</code>
+ */
+ public static final int COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = 2;
+
+ /**
+ * Row caption mode: The row headers are hidden. <b>This is the default
+ * mode. </b>
+ */
+ public static final int ROW_HEADER_MODE_HIDDEN = -1;
+
+ /**
+ * Row caption mode: Items Id-objects toString is used as row caption.
+ */
+ public static final int ROW_HEADER_MODE_ID = AbstractSelect.ITEM_CAPTION_MODE_ID;
+
+ /**
+ * Row caption mode: Item-objects toString is used as row caption.
+ */
+ public static final int ROW_HEADER_MODE_ITEM = AbstractSelect.ITEM_CAPTION_MODE_ITEM;
+
+ /**
+ * Row caption mode: Index of the item is used as item caption. The index
+ * mode can only be used with the containers implementing Container.Indexed
+ * interface.
+ */
+ public static final int ROW_HEADER_MODE_INDEX = AbstractSelect.ITEM_CAPTION_MODE_INDEX;
+
+ /**
+ * Row caption mode: Item captions are explicitly specified.
+ */
+ public static final int ROW_HEADER_MODE_EXPLICIT = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT;
+
+ /**
+ * Row caption mode: Item captions are read from property specified with
+ * <code>setItemCaptionPropertyId</code>.
+ */
+ public static final int ROW_HEADER_MODE_PROPERTY = AbstractSelect.ITEM_CAPTION_MODE_PROPERTY;
+
+ /**
+ * Row caption mode: Only icons are shown, the captions are hidden.
+ */
+ public static final int ROW_HEADER_MODE_ICON_ONLY = AbstractSelect.ITEM_CAPTION_MODE_ICON_ONLY;
+
+ /**
+ * Row caption mode: Item captions are explicitly specified, but if the
+ * caption is missing, the item id objects <code>toString()</code> is used
+ * instead.
+ */
+ public static final int ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID;
+
+ /* 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 columnIdMap = new KeyMapper();
+
+ /**
+ * Holds visible column propertyIds - in order.
+ */
+ private LinkedList<Object> visibleColumns = new LinkedList<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 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, String> columnAlignments = new HashMap<Object, String>();
+
+ /**
+ * 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 int columnHeaderMode = COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * True iff the row captions are hidden.
+ */
+ private boolean rowCaptionsAreHidden = true;
+
+ /**
+ * Page contents buffer used in buffered mode.
+ */
+ private Object[][] pageBuffer = null;
+
+ /**
+ * Set of properties listened - the list is kept to release the listeners
+ * later.
+ *
+ * Note: This should be set or list. IdentityHashMap used due very heavy
+ * hashCode in indexed container
+ */
+ 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 actionMapper = null;
+
+ /**
+ * Table cell editor factory.
+ */
+ private FieldFactory fieldFactory = new BaseFieldFactory();
+
+ /**
+ * 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 disabled alltogether; even if some of the properties
+ * would be sortable.
+ */
+ private boolean sortDisabled = false;
+
+ /**
+ * 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;
+
+ private int clickListenerCount;
+
+ /*
+ * 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;
+
+ /* 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");
+ }
+
+ // 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, null);
+ setColumnIcon(col, null);
+ }
+ }
+ } finally {
+ if (disabledHere) {
+ enableContentRefreshing(false);
+ }
+ }
+ }
+
+ this.visibleColumns = newVC;
+
+ // Assures visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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
+ * <code>COLUMN_HEADER_MODE_EXPLICIT</code> or
+ * <code>COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
+ * headers. In the defaults mode any nulls in the headers array are replaced
+ * with id.toString() outputs when rendering.
+ * </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] = columnHeaders.get(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
+ * <code>COLUMN_HEADER_MODE_EXPLICIT</code> or
+ * <code>COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> 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
+ * <code>getVisibleColumns</code> 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]);
+ }
+
+ // Assures the visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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
+ * <code>COLUMN_HEADER_MODE_EXPLICIT</code> or
+ * <code>COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
+ * headers with icons.
+ * </p>
+ *
+ * @return the Array of icons that match the <code>getVisibleColumns</code>.
+ */
+ 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
+ * <code>COLUMN_HEADER_MODE_EXPLICIT</code> or
+ * <code>COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
+ * headers with icons.
+ * </p>
+ *
+ * @param columnIcons
+ * the Array of icons that match the
+ * <code>getVisibleColumns</code>.
+ */
+ 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]);
+ }
+
+ // Assure visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * Gets the array of column alignments.
+ *
+ * <p>
+ * The items in the array must match the properties identified by
+ * <code>getVisibleColumns()</code>. The possible values for the alignments
+ * include:
+ * <ul>
+ * <li><code>ALIGN_LEFT</code>: Left alignment</li>
+ * <li><code>ALIGN_CENTER</code>: Centered</li>
+ * <li><code>ALIGN_RIGHT</code>: Right alignment</li>
+ * </ul>
+ * The alignments default to <code>ALIGN_LEFT</code>: any null values are
+ * rendered as align lefts.
+ * </p>
+ *
+ * @return the Column alignments array.
+ */
+ public String[] getColumnAlignments() {
+ if (columnAlignments == null) {
+ return null;
+ }
+ final String[] alignments = new String[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 items in the array must match the properties identified by
+ * <code>getVisibleColumns()</code>. The possible values for the alignments
+ * include:
+ * <ul>
+ * <li><code>ALIGN_LEFT</code>: Left alignment</li>
+ * <li><code>ALIGN_CENTER</code>: Centered</li>
+ * <li><code>ALIGN_RIGHT</code>: Right alignment</li>
+ * </ul>
+ * The alignments default to <code>ALIGN_LEFT</code>
+ * </p>
+ *
+ * @param columnAlignments
+ * the Column alignments array.
+ */
+ public void setColumnAlignments(String[] columnAlignments) {
+
+ if (columnAlignments.length != visibleColumns.size()) {
+ throw new IllegalArgumentException(
+ "The length of the alignments array must match the number of visible columns");
+ }
+
+ // Checks all alignments
+ for (int i = 0; i < columnAlignments.length; i++) {
+ final String a = columnAlignments[i];
+ if (a != null && !a.equals(ALIGN_LEFT) && !a.equals(ALIGN_CENTER)
+ && !a.equals(ALIGN_RIGHT)) {
+ throw new IllegalArgumentException("Column " + i
+ + " aligment '" + a + "' is invalid");
+ }
+ }
+
+ // Resets the alignments
+ final HashMap<Object, String> newCA = new HashMap<Object, String>();
+ 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
+ resetPageBuffer();
+ 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 columnId
+ * colunmns property id
+ * @param width
+ * width to be reserved for colunmns content
+ * @since 4.0.3
+ */
+ public void setColumnWidth(Object columnId, int width) {
+ if (width < 0) {
+ columnWidths.remove(columnId);
+ } else {
+ columnWidths.put(columnId, new Integer(width));
+ }
+ }
+
+ /**
+ * 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-sizeable columns the column
+ * becomes fixed width column if users resizes the column.
+ *
+ * @param columnId
+ * colunmns property id
+ * @param expandRatio
+ * the expandRatio used to divide excess space for this column
+ */
+ public void setColumnExpandRatio(Object columnId, float expandRatio) {
+ if (expandRatio < 0) {
+ columnWidths.remove(columnId);
+ } else {
+ columnWidths.put(columnId, 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 colun or -1 when value not set
+ */
+ public int getColumnWidth(Object propertyId) {
+ 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>
+ *
+ * @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
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+ }
+
+ /**
+ * 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 = ((Container.Indexed) items).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 = ((Container.Ordered) items).firstItemId();
+ }
+
+ return currentPageFirstItemId;
+ }
+
+ /**
+ * 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 = ((Container.Indexed) items)
+ .indexOfId(currentPageFirstItemId);
+ } else {
+ // If the table item container does not have index, we have to
+ // calculates the index by hand
+ Object id = ((Container.Ordered) items).firstItemId();
+ while (id != null && !id.equals(currentPageFirstItemId)) {
+ index++;
+ id = ((Container.Ordered) items).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) {
+ setCurrentPageFirstItemIndex(maxIndex);
+ return;
+ }
+
+ this.currentPageFirstItemId = currentPageFirstItemId;
+ currentPageFirstItemIndex = index;
+ }
+
+ // Assures the visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+
+ }
+
+ /**
+ * 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);
+ }
+
+ // Assures the visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * Gets the header for the specified column.
+ *
+ * @param propertyId
+ * the propertyId indentifying the column.
+ * @return the header for the specifed column if it has one.
+ */
+ public String getColumnHeader(Object propertyId) {
+ if (getColumnHeaderMode() == COLUMN_HEADER_MODE_HIDDEN) {
+ return null;
+ }
+
+ String header = columnHeaders.get(propertyId);
+ if ((header == null && getColumnHeaderMode() == COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID)
+ || getColumnHeaderMode() == COLUMN_HEADER_MODE_ID) {
+ header = propertyId.toString();
+ }
+
+ return header;
+ }
+
+ /**
+ * Sets the column header for the specified column;
+ *
+ * @param propertyId
+ * the propertyId indentifying the column.
+ * @param header
+ * the header to set.
+ */
+ public void setColumnHeader(Object propertyId, String header) {
+
+ if (header == null) {
+ columnHeaders.remove(propertyId);
+ return;
+ }
+ columnHeaders.put(propertyId, header);
+
+ // Assures the visual refresh
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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 String getColumnAlignment(Object propertyId) {
+ final String 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: ALIGN_LEFT, ALIGN_CENTER or ALIGN_RIGHT
+ * </p>
+ *
+ * @param propertyId
+ * the propertyID identifying the column.
+ * @param alignment
+ * the desired alignment.
+ */
+ public void setColumnAlignment(Object propertyId, String alignment) {
+
+ // Checks for valid alignments
+ if (alignment != null && !alignment.equals(ALIGN_LEFT)
+ && !alignment.equals(ALIGN_CENTER)
+ && !alignment.equals(ALIGN_RIGHT)) {
+ throw new IllegalArgumentException("Column alignment '" + alignment
+ + "' is not supported.");
+ }
+
+ if (alignment == null || alignment.equals(ALIGN_LEFT)) {
+ columnAlignments.remove(propertyId);
+ return;
+ }
+
+ columnAlignments.put(propertyId, alignment);
+
+ // Assures the visual refresh
+ 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 IllegalAccessException
+ */
+ public void setColumnCollapsed(Object propertyId, boolean collapsed)
+ throws IllegalAccessException {
+ if (!isColumnCollapsingAllowed()) {
+ throw new IllegalAccessException("Column collapsing not allowed!");
+ }
+
+ if (collapsed) {
+ collapsedColumns.add(propertyId);
+ } else {
+ collapsedColumns.remove(propertyId);
+ }
+
+ // Assures the visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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 reorderingAllowed
+ * specifies whether column reordering is allowed.
+ */
+ public void setColumnReorderingAllowed(boolean reorderingAllowed) {
+ columnReorderingAllowed = reorderingAllowed;
+
+ // Assures the visual refresh
+ refreshRenderedCells();
+ }
+
+ /*
+ * 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
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * Getter for property currentPageFirstItem.
+ *
+ * @return the Value of property currentPageFirstItem.
+ */
+ public int getCurrentPageFirstItemIndex() {
+ return currentPageFirstItemIndex;
+ }
+
+ private 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;
+ }
+
+ // Ensures that the new value is valid
+ if (newIndex > maxIndex) {
+ newIndex = maxIndex;
+ }
+
+ // Refresh first item id
+ if (items instanceof Container.Indexed) {
+ try {
+ currentPageFirstItemId = ((Container.Indexed) items)
+ .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 = ((Container.Ordered) items).firstItemId();
+
+ // Go forwards in the middle of the list (respect borders)
+ while (currentPageFirstItemIndex < newIndex
+ && !((Container.Ordered) items)
+ .isLastId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex++;
+ currentPageFirstItemId = ((Container.Ordered) items)
+ .nextItemId(currentPageFirstItemId);
+ }
+
+ // If we did hit the border
+ if (((Container.Ordered) items).isLastId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex = size - 1;
+ }
+
+ // Go backwards in the middle of the list (respect borders)
+ while (currentPageFirstItemIndex > newIndex
+ && !((Container.Ordered) items)
+ .isFirstId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex--;
+ currentPageFirstItemId = ((Container.Ordered) items)
+ .prevItemId(currentPageFirstItemId);
+ }
+
+ // If we did hit the border
+ if (((Container.Ordered) items).isFirstId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex = 0;
+ }
+
+ // Go forwards once more
+ while (currentPageFirstItemIndex < newIndex
+ && !((Container.Ordered) items)
+ .isLastId(currentPageFirstItemId)) {
+ currentPageFirstItemIndex++;
+ currentPageFirstItemId = ((Container.Ordered) items)
+ .nextItemId(currentPageFirstItemId);
+ }
+
+ // If for some reason we do hit border again, override
+ // the user index request
+ if (((Container.Ordered) items).isLastId(currentPageFirstItemId)) {
+ newIndex = currentPageFirstItemIndex = size - 1;
+ }
+ }
+ if (needsPageBufferReset) {
+ // Assures the visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+ }
+
+ /**
+ * 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 int getColumnHeaderMode() {
+ return columnHeaderMode;
+ }
+
+ /**
+ * Setter for property columnHeaderMode.
+ *
+ * @param columnHeaderMode
+ * the New value of property columnHeaderMode.
+ */
+ public void setColumnHeaderMode(int columnHeaderMode) {
+ if (columnHeaderMode >= COLUMN_HEADER_MODE_HIDDEN
+ && columnHeaderMode <= COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID) {
+ this.columnHeaderMode = columnHeaderMode;
+ }
+
+ // Assures the visual refresh
+ refreshRenderedCells();
+ }
+
+ /**
+ * Refreshes rendered rows
+ */
+ private void refreshRenderedCells() {
+ if (getParent() == null) {
+ return;
+ }
+
+ if (isContentRefreshesEnabled) {
+
+ HashSet<Property> oldListenedProperties = listenedProperties;
+ HashSet<Component> oldVisibleComponents = visibleComponents;
+
+ // initialize the listener collections
+ listenedProperties = new HashSet<Property>();
+ visibleComponents = new HashSet<Component>();
+
+ // Collects the basic facts about the table page
+ final Object[] colids = getVisibleColumns();
+ final int cols = colids.length;
+ final int pagelen = getPageLength();
+ int firstIndex = getCurrentPageFirstItemIndex();
+ int rows, totalRows;
+ rows = totalRows = size();
+ 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;
+ }
+ Object id;
+ if (firstToBeRenderedInClient >= 0) {
+ if (firstToBeRenderedInClient < totalRows) {
+ firstIndex = firstToBeRenderedInClient;
+ } else {
+ firstIndex = totalRows - 1;
+ }
+ } else {
+ // initial load
+ firstToBeRenderedInClient = firstIndex;
+ }
+ if (totalRows > 0) {
+ if (rows + firstIndex > totalRows) {
+ rows = totalRows - firstIndex;
+ }
+ } else {
+ rows = 0;
+ }
+
+ Object[][] cells = new Object[cols + CELL_FIRSTCOL][rows];
+ if (rows == 0) {
+ pageBuffer = cells;
+ unregisterPropertiesAndComponents(oldListenedProperties,
+ oldVisibleComponents);
+ return;
+ }
+
+ // Gets the first item id
+ if (items instanceof Container.Indexed) {
+ id = ((Container.Indexed) items).getIdByIndex(firstIndex);
+ } else {
+ id = ((Container.Ordered) items).firstItemId();
+ for (int i = 0; i < firstIndex; i++) {
+ id = ((Container.Ordered) items).nextItemId(id);
+ }
+ }
+
+ final int 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 ROW_HEADER_MODE_INDEX:
+ cells[CELL_HEADER][i] = String.valueOf(i + firstIndex
+ + 1);
+ break;
+ default:
+ cells[CELL_HEADER][i] = getItemCaption(id);
+ }
+ cells[CELL_ICON][i] = getItemIcon(id);
+ }
+
+ if (cols > 0) {
+ for (int j = 0; j < cols; j++) {
+ if (isColumnCollapsed(colids[j])) {
+ continue;
+ }
+ Property p = null;
+ Object value = "";
+ boolean isGenerated = columnGenerators
+ .containsKey(colids[j]);
+
+ if (!isGenerated) {
+ p = getContainerProperty(id, colids[j]);
+ }
+
+ // check in current pageBuffer already has row
+ int index = firstIndex + i;
+ if (p != null || isGenerated) {
+ if (index < firstIndexNotInCache
+ && index >= pageBufferFirstIndex) {
+ // we have data already in our cache,
+ // recycle it instead of fetching it via
+ // getValue/getPropertyValue
+ int indexInOldBuffer = index
+ - pageBufferFirstIndex;
+ value = pageBuffer[CELL_FIRSTCOL + j][indexInOldBuffer];
+ } else {
+ if (isGenerated) {
+ ColumnGenerator cg = columnGenerators
+ .get(colids[j]);
+ value = cg
+ .generateCell(this, id, colids[j]);
+
+ } else if (iscomponent[j]) {
+ value = p.getValue();
+ } 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)
+ && p instanceof Property.ValueChangeNotifier) {
+ // only add listener to property once
+ 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);
+ }
+ } else {
+ value = getPropertyValue(id, colids[j],
+ null);
+ }
+ }
+ }
+
+ if (value instanceof Component) {
+ if (oldVisibleComponents == null
+ || !oldVisibleComponents.contains(value)) {
+ ((Component) value).setParent(this);
+ }
+ visibleComponents.add((Component) value);
+ }
+ cells[CELL_FIRSTCOL + j][i] = value;
+ }
+ }
+
+ id = ((Container.Ordered) items).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;
+ }
+
+ pageBufferFirstIndex = firstIndex;
+
+ // Saves the results to internal buffer
+ pageBuffer = cells;
+
+ unregisterPropertiesAndComponents(oldListenedProperties,
+ oldVisibleComponents);
+
+ requestRepaint();
+ }
+
+ }
+
+ /**
+ * 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)) {
+ c.setParent(null);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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><code>ROW_HEADER_MODE_HIDDEN</code>: The row captions are hidden.</li>
+ * <li><code>ROW_HEADER_MODE_ID</code>: Items Id-objects
+ * <code>toString()</code> is used as row caption.
+ * <li><code>ROW_HEADER_MODE_ITEM</code>: Item-objects
+ * <code>toString()</code> is used as row caption.
+ * <li><code>ROW_HEADER_MODE_PROPERTY</code>: Property set with
+ * <code>setItemCaptionPropertyId()</code> is used as row header.
+ * <li><code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code>: Items Id-objects
+ * <code>toString()</code> is used as row header. If caption is explicitly
+ * specified, it overrides the id-caption.
+ * <li><code>ROW_HEADER_MODE_EXPLICIT</code>: The row headers must be
+ * explicitly specified.</li>
+ * <li><code>ROW_HEADER_MODE_INDEX</code>: 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 <code>ROW_HEADER_MODE_HIDDEN</code>
+ * </p>
+ *
+ * @param mode
+ * the One of the modes listed above.
+ */
+ public void setRowHeaderMode(int mode) {
+ if (ROW_HEADER_MODE_HIDDEN == mode) {
+ rowCaptionsAreHidden = true;
+ } else {
+ rowCaptionsAreHidden = false;
+ setItemCaptionMode(mode);
+ }
+
+ // Assure visual refresh
+ refreshRenderedCells();
+ }
+
+ /**
+ * Gets the row header mode.
+ *
+ * @return the Row header mode.
+ * @see #setRowHeaderMode(int)
+ */
+ public int getRowHeaderMode() {
+ return rowCaptionsAreHidden ? ROW_HEADER_MODE_HIDDEN
+ : getItemCaptionMode();
+ }
+
+ /**
+ * 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)) {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ return itemId;
+ }
+
+ /* Overriding select behavior */
+
+ @Override
+ public void setValue(Object newValue) throws ReadOnlyException,
+ ConversionException {
+ // external selection change, need to truncate pageBuffer
+ resetPageBuffer();
+ refreshRenderedCells();
+ super.setValue(newValue);
+ }
+
+ /**
+ * Sets the Container that serves as the data source of the viewer.
+ *
+ * As a side-effect Table's value (selection) is set to null due old
+ * selection not necessary exists in new Container.
+ *
+ * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
+ */
+ @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());
+
+ // null value as we may not be sure that currently selected identifier
+ // exits in new ds
+ setValue(null);
+
+ // Assure visual refresh
+ resetPageBuffer();
+
+ enableContentRefreshing(true);
+
+ }
+
+ /* 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 variables) {
+
+ boolean clientNeedsContentRefresh = false;
+
+ handleClickEvent(variables);
+
+ disableContentRefreshing();
+
+ if (!isSelectable() && variables.containsKey("selected")) {
+ // Not-selectable is a special case, AbstractSelect does not support
+ // TODO could be optimized.
+ variables = new HashMap(variables);
+ variables.remove("selected");
+ }
+
+ super.changeVariables(source, variables);
+
+ // 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
+ e.printStackTrace();
+ }
+
+ // 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;
+ }
+ }
+ }
+ clientNeedsContentRefresh = true;
+ }
+
+ // 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 = (Action) actionMapper.get(st.nextToken());
+ if (action != null && containsId(itemId)
+ && actionHandlers != null) {
+ for (final Iterator<Handler> i = actionHandlers.iterator(); i
+ .hasNext();) {
+ (i.next()).handleAction(action, this, itemId);
+ }
+ }
+ }
+ }
+
+ if (!sortDisabled) {
+ // 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
+ e.printStackTrace();
+ }
+ clientNeedsContentRefresh = true;
+ }
+ }
+ if (isColumnReorderingAllowed()) {
+ if (variables.containsKey("columnorder")) {
+ try {
+ final Object[] ids = (Object[]) variables
+ .get("columnorder");
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = columnIdMap.get(ids[i].toString());
+ }
+ setColumnOrder(ids);
+ } catch (final Exception e) {
+ // FIXME: Handle exception
+ e.printStackTrace();
+
+ }
+ clientNeedsContentRefresh = true;
+ }
+ }
+
+ enableContentRefreshing(clientNeedsContentRefresh);
+ }
+
+ /**
+ * Handles click event
+ *
+ * @param variables
+ */
+ private void handleClickEvent(Map variables) {
+ if (clickListenerCount > 0) {
+ 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));
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 {
+
+ // The tab ordering number
+ if (getTabIndex() > 0) {
+ target.addAttribute("tabindex", getTabIndex());
+ }
+
+ // Initialize temps
+ final Object[] colids = getVisibleColumns();
+ final int cols = colids.length;
+ final int first = getCurrentPageFirstItemIndex();
+ int total = size();
+ final int pagelen = getPageLength();
+ final int colHeadMode = getColumnHeaderMode();
+ final boolean colheads = colHeadMode != COLUMN_HEADER_MODE_HIDDEN;
+ final boolean rowheads = getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN;
+ final Object[][] cells = getVisibleCells();
+ final boolean iseditable = isEditable();
+ 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);
+ }
+ }
+
+ if (!isNullSelectionAllowed() && getNullSelectionItemId() != null
+ && containsId(getNullSelectionItemId())) {
+ total--;
+ rows--;
+ }
+
+ // selection support
+ LinkedList<String> selectedKeys = new LinkedList<String>();
+ if (isMultiSelect()) {
+ // only paint selections that are currently visible in the client
+ HashSet sel = new HashSet((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));
+ }
+ }
+
+ // Table attributes
+ if (isSelectable()) {
+ target.addAttribute("selectmode", (isMultiSelect() ? "multi"
+ : "single"));
+ } else {
+ target.addAttribute("selectmode", "none");
+ }
+
+ if (clickListenerCount > 0) {
+ target.addAttribute("listenClicks", true);
+ }
+
+ target.addAttribute("cols", cols);
+ target.addAttribute("rows", rows);
+
+ target.addAttribute("firstrow",
+ (reqFirstRowToPaint >= 0 ? reqFirstRowToPaint
+ : firstToBeRenderedInClient));
+ target.addAttribute("totalrows", total);
+ if (pagelen != 0) {
+ target.addAttribute("pagelength", pagelen);
+ }
+ if (colheads) {
+ target.addAttribute("colheaders", true);
+ }
+ if (rowheads) {
+ target.addAttribute("rowheaders", true);
+ }
+
+ // Visible column order
+ final Collection sortables = getSortableContainerPropertyIds();
+ final ArrayList<String> visibleColOrder = new ArrayList<String>();
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext();) {
+ final Object columnId = it.next();
+ if (!isColumnCollapsed(columnId)) {
+ visibleColOrder.add(columnIdMap.key(columnId));
+ }
+ }
+ target.addAttribute("vcolorder", visibleColOrder.toArray());
+
+ // Rows
+ final Set<Action> actionSet = new LinkedHashSet<Action>();
+ final boolean selectable = isSelectable();
+ final boolean[] iscomponent = new boolean[visibleColumns.size()];
+ int iscomponentIndex = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext()
+ && iscomponentIndex < iscomponent.length;) {
+ final Object columnId = it.next();
+ if (columnGenerators.containsKey(columnId)) {
+ iscomponent[iscomponentIndex++] = true;
+ } else {
+ final Class colType = getType(columnId);
+ iscomponent[iscomponentIndex++] = colType != null
+ && Component.class.isAssignableFrom(colType);
+ }
+ }
+ 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;
+ }
+
+ for (int i = start; i < end; i++) {
+ final Object itemId = cells[CELL_ITEMID][i];
+
+ if (!isNullSelectionAllowed() && getNullSelectionItemId() != null
+ && itemId == getNullSelectionItemId()) {
+ // Remove null selection item if null selection is not allowed
+ continue;
+ }
+
+ target.startTag("tr");
+
+ // tr attributes
+ if (rowheads) {
+ if (cells[CELL_ICON][i] != null) {
+ target.addAttribute("icon", (Resource) cells[CELL_ICON][i]);
+ }
+ if (cells[CELL_HEADER][i] != null) {
+ target.addAttribute("caption",
+ (String) cells[CELL_HEADER][i]);
+ }
+ }
+ target.addAttribute("key", Integer.parseInt(cells[CELL_KEY][i]
+ .toString()));
+ if (actionHandlers != null || isSelectable()) {
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ }
+ }
+
+ // Actions
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ for (final Iterator<Handler> ahi = actionHandlers.iterator(); ahi
+ .hasNext();) {
+ final Action[] aa = (ahi.next()).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);
+ }
+ }
+
+ // 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)
+ && Component.class.isInstance(cells[CELL_FIRSTCOL
+ + currentColumn][i])) {
+ final Component c = (Component) cells[CELL_FIRSTCOL
+ + currentColumn][i];
+ if (c == null) {
+ target.addText("");
+ } else {
+ c.paint(target);
+ }
+ } else {
+ target
+ .addText((String) cells[CELL_FIRSTCOL
+ + currentColumn][i]);
+ }
+ }
+
+ target.endTag("tr");
+ }
+ target.endTag("rows");
+
+ // The select variable is only enabled if selectable
+ if (selectable && selectedKeys.size() > 0) {
+ target.addVariable(this, "selected", selectedKeys
+ .toArray(new String[selectedKeys.size()]));
+ }
+
+ // The cursors are only shown on pageable table
+ if (first != 0 || getPageLength() > 0) {
+ target.addVariable(this, "firstvisible", first);
+ }
+
+ // Sorting
+ if (getContainerDataSource() instanceof Container.Sortable) {
+ target.addVariable(this, "sortcolumn", columnIdMap
+ .key(sortContainerPropertyId));
+ target.addVariable(this, "sortascending", sortAscending);
+ }
+
+ // Resets and paints "to be painted next" variables. Also reset
+ // pageBuffer
+ reqFirstRowToPaint = -1;
+ reqRowsToPaint = -1;
+ containerChangeToBeRendered = false;
+ target.addVariable(this, "reqrows", reqRowsToPaint);
+ target.addVariable(this, "reqfirstrow", reqFirstRowToPaint);
+
+ // Actions
+ if (!actionSet.isEmpty()) {
+ target.addVariable(this, "action", "");
+ target.startTag("actions");
+ for (final Iterator<Action> it = actionSet.iterator(); it.hasNext();) {
+ final Action a = it.next();
+ 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");
+ }
+ if (columnReorderingAllowed) {
+ final String[] colorder = new String[visibleColumns.size()];
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext()
+ && i < colorder.length;) {
+ colorder[i++] = columnIdMap.key(it.next());
+ }
+ target.addVariable(this, "columnorder", colorder);
+ }
+ // Available columns
+ if (columnCollapsingAllowed) {
+ final HashSet<Object> ccs = new HashSet<Object>();
+ for (final Iterator<Object> i = visibleColumns.iterator(); i
+ .hasNext();) {
+ final Object o = i.next();
+ if (isColumnCollapsed(o)) {
+ ccs.add(o);
+ }
+ }
+ final String[] collapsedkeys = new String[ccs.size()];
+ int nextColumn = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext()
+ && nextColumn < collapsedkeys.length;) {
+ final Object columnId = it.next();
+ if (isColumnCollapsed(columnId)) {
+ collapsedkeys[nextColumn++] = columnIdMap.key(columnId);
+ }
+ }
+ target.addVariable(this, "collapsedcolumns", collapsedkeys);
+ }
+ target.startTag("visiblecolumns");
+ int i = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext(); i++) {
+ final Object columnId = it.next();
+ if (columnId != null) {
+ target.startTag("column");
+ target.addAttribute("cid", columnIdMap.key(columnId));
+ final String head = getColumnHeader(columnId);
+ target.addAttribute("caption", (head != null ? head : ""));
+ if (isColumnCollapsed(columnId)) {
+ target.addAttribute("collapsed", true);
+ }
+ if (colheads) {
+ if (getColumnIcon(columnId) != null) {
+ target.addAttribute("icon", getColumnIcon(columnId));
+ }
+ if (sortables.contains(columnId)) {
+ target.addAttribute("sortable", true);
+ }
+ }
+ if (!ALIGN_LEFT.equals(getColumnAlignment(columnId))) {
+ target.addAttribute("align", getColumnAlignment(columnId));
+ }
+ if (columnWidths.containsKey(columnId)) {
+ if (getColumnWidth(columnId) > -1) {
+ target.addAttribute("width", String
+ .valueOf(getColumnWidth(columnId)));
+ } else {
+ target.addAttribute("er",
+ getColumnExpandRatio(columnId));
+ }
+ }
+ target.endTag("column");
+ }
+ }
+ target.endTag("visiblecolumns");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractSelect#getTag()
+ */
+ @Override
+ public String getTag() {
+ return "table";
+ }
+
+ /**
+ * 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 #setFieldFactory(FieldFactory)
+ */
+ 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) {
+ f.setPropertyDataSource(property);
+ return f;
+ }
+ }
+
+ return formatPropertyValue(rowId, colId, 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 "";
+ }
+ return property.toString();
+ }
+
+ /* Action container */
+
+ /**
+ * Registers a new action handler for this container
+ *
+ * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler)
+ */
+ public void addActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList<Handler>();
+ actionMapper = new KeyMapper();
+ }
+
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ requestRepaint();
+ }
+
+ }
+ }
+
+ /**
+ * Removes a previously registered action handler for the contents of this
+ * container.
+ *
+ * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
+ */
+ public void removeActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+
+ actionHandlers.remove(actionHandler);
+
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /* 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) {
+ super.valueChange(event);
+ } else {
+ resetPageBuffer();
+ refreshRenderedCells();
+ containerChangeToBeRendered = true;
+ }
+ requestRepaint();
+ }
+
+ private 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();
+
+ if (visibleComponents != null) {
+ for (final Iterator<Component> i = visibleComponents.iterator(); i
+ .hasNext();) {
+ i.next().attach();
+ }
+ }
+ }
+
+ /**
+ * Notifies the component that it is detached from the application
+ *
+ * @see com.vaadin.ui.Component#detach()
+ */
+ @Override
+ public void detach() {
+ super.detach();
+
+ if (visibleComponents != null) {
+ for (final Iterator<Component> i = visibleComponents.iterator(); i
+ .hasNext();) {
+ i.next().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 = ((Container.Ordered) items)
+ .nextItemId(itemId);
+ final boolean ret = super.removeItem(itemId);
+ if (ret && (itemId != null) && (itemId.equals(currentPageFirstItemId))) {
+ currentPageFirstItemId = nextItemId;
+ }
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+ 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);
+
+ 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)) {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+ 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,
+ String 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>
+ * 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);
+ }
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+ }
+
+ /**
+ * 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);
+ }
+ resetPageBuffer();
+ refreshRenderedCells();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the list of items on the current page
+ *
+ * @see com.vaadin.ui.Select#getVisibleItemIds()
+ */
+ @Override
+ public Collection getVisibleItemIds() {
+
+ final LinkedList<Object> visible = new LinkedList<Object>();
+
+ final Object[][] cells = getVisibleCells();
+ 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);
+ if (event instanceof IndexedContainer.ItemSetChangeEvent) {
+ IndexedContainer.ItemSetChangeEvent evt = (IndexedContainer.ItemSetChangeEvent) event;
+ // if the event is not a global one and the added item is outside
+ // the visible/buffered area, no need to do anything
+ if (evt.getAddedItemIndex() != -1
+ && (firstToBeRenderedInClient >= 0)
+ && (lastToBeRenderedInClient >= 0)
+ && (firstToBeRenderedInClient > evt.getAddedItemIndex() || lastToBeRenderedInClient < evt
+ .getAddedItemIndex())) {
+ return;
+ }
+ }
+ // ensure that page still has first item in page
+ setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex());
+
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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) {
+ super.containerPropertySetChange(event);
+
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * Focusing to this component is not supported.
+ *
+ * @throws UnsupportedOperationException
+ * if invoked.
+ * @see com.vaadin.ui.AbstractField#focus()
+ */
+ @Override
+ public void focus() throws UnsupportedOperationException {
+ 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)
+ */
+ 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)
+ */
+ 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()
+ */
+ 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()
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ Object itemId = ((Container.Ordered) items)
+ .addItemAfter(previousItemId);
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+ return itemId;
+ }
+
+ /**
+ * Adds new item after the given item.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
+ * java.lang.Object)
+ */
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ Item item = ((Container.Ordered) items).addItemAfter(previousItemId,
+ newItemId);
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+ return item;
+ }
+
+ /**
+ * Gets the FieldFactory that is used to create editor for table cells.
+ *
+ * The FieldFactory is only used if the Table is editable.
+ *
+ * @return FieldFactory used to create the Field instances.
+ * @see #isEditable
+ */
+ public FieldFactory getFieldFactory() {
+ return fieldFactory;
+ }
+
+ /**
+ * Sets the FieldFactory that is used to create editor for table cells.
+ *
+ * The FieldFactory is only used if the Table is editable. By default the
+ * BaseFieldFactory is used.
+ *
+ * @param fieldFactory
+ * the field factory to set.
+ * @see #isEditable
+ * @see BaseFieldFactory
+ *
+ */
+ public void setFieldFactory(FieldFactory fieldFactory) {
+ this.fieldFactory = fieldFactory;
+
+ // Assure visual refresh
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * 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[])
+ *
+ */
+ 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);
+ resetPageBuffer();
+ refreshRenderedCells();
+
+ } 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.
+ *
+ * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
+ */
+ public Collection getSortableContainerPropertyIds() {
+ final Container c = getContainerDataSource();
+ if (c instanceof Container.Sortable && !isSortDisabled()) {
+ return ((Container.Sortable) c).getSortableContainerPropertyIds();
+ } else {
+ return new LinkedList();
+ }
+ }
+
+ /**
+ * 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
+ 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
+ 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.
+ */
+ public boolean isSortDisabled() {
+ return sortDisabled;
+ }
+
+ /**
+ * Disables the sorting altogether.
+ *
+ * To disable sorting altogether, set to true. In this case no sortable
+ * columns are given even in the case where datasource would support this.
+ *
+ * @param sortDisabled
+ * True iff sorting is disabled.
+ */
+ public void setSortDisabled(boolean sortDisabled) {
+ if (this.sortDisabled != sortDisabled) {
+ this.sortDisabled = sortDisabled;
+ refreshRenderedCells();
+ }
+ }
+
+ /**
+ * Table 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 Table.");
+ }
+ }
+
+ /*
+ * Override abstract fields to string method to avoid non-informative null's
+ * in debugger
+ */
+ @Override
+ public String toString() {
+ return "Table:" + getContainerPropertyIds() + ", rows "
+ + getContainerDataSource().size() + " ,value:"
+ + super.toString();
+ }
+
+ /**
+ * 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
+ */
+ public abstract Component 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;
+ requestRepaint();
+ }
+
+ /**
+ * 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>i-table-cell-content-[style name]</tt>, and
+ * the row style will be <tt>i-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 i-table-cell-content-[style name], or
+ * i-table-row-[style name] for rows)
+ */
+ public abstract String getStyle(Object itemId, Object propertyId);
+ }
+
+ public void addListener(ItemClickListener listener) {
+ addListener(ItemClickEvent.class, listener,
+ ItemClickEvent.ITEM_CLICK_METHOD);
+ clickListenerCount++;
+ // repaint needed only if click listening became necessary
+ if (clickListenerCount == 1) {
+ requestRepaint();
+ }
+
+ }
+
+ public void removeListener(ItemClickListener listener) {
+ removeListener(ItemClickEvent.class, listener,
+ ItemClickEvent.ITEM_CLICK_METHOD);
+ clickListenerCount--;
+ // repaint needed only if click listening is not needed in client
+ // anymore
+ if (clickListenerCount == 0) {
+ requestRepaint();
+ }
+ }
+
+ // 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();
+ }
+ }
+
+ // Virtually identical to AbstractCompoenentContainer.setEnabled();
+ public void requestRepaintAll() {
+ requestRepaint();
+ if (visibleComponents != null) {
+ for (Iterator<Component> childIterator = visibleComponents
+ .iterator(); childIterator.hasNext();) {
+ Component c = childIterator.next();
+ if (c instanceof Form) {
+ // Form has children in layout, but is not
+ // ComponentContainer
+ c.requestRepaint();
+ ((Form) c).getLayout().requestRepaintAll();
+ } else if (c instanceof Table) {
+ ((Table) c).requestRepaintAll();
+ } else if (c instanceof ComponentContainer) {
+ ((ComponentContainer) c).requestRepaintAll();
+ } else {
+ c.requestRepaint();
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/vaadin/ui/TextField.java b/src/com/vaadin/ui/TextField.java
new file mode 100644
index 0000000000..626d340922
--- /dev/null
+++ b/src/com/vaadin/ui/TextField.java
@@ -0,0 +1,556 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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;
+
+/**
+ * <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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class TextField extends AbstractField {
+
+ /* Private members */
+
+ /**
+ * Value formatter used to format the string contents.
+ */
+ private Format format;
+
+ /**
+ * Number of visible columns in the TextField.
+ */
+ private int columns = 0;
+
+ /**
+ * Number of visible rows in a multiline TextField. Value 0 implies a
+ * single-line text-editor.
+ */
+ private int rows = 0;
+
+ /**
+ * Tells if word-wrapping should be used in multiline mode.
+ */
+ private boolean wordwrap = true;
+
+ /**
+ * Tells if input is used to enter sensitive information that is not echoed
+ * to display. Typically passwords.
+ */
+ private boolean secret = false;
+
+ /**
+ * Null representation.
+ */
+ private String nullRepresentation = "null";
+
+ /**
+ * Is setting to null from non-null value allowed by setting with null
+ * representation .
+ */
+ private boolean nullSettingAllowed = false;
+
+ private String inputPrompt = null;
+
+ /**
+ * Maximum character count in text field.
+ */
+ private int maxLength = -1;
+
+ /* Constructors */
+
+ /**
+ * 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) {
+ setValue("");
+ 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 text
+ * the initial text content of the editor.
+ */
+ public TextField(String caption, String 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 {
+ super.paintContent(target);
+
+ // Sets the secret attribute
+ if (isSecret()) {
+ target.addAttribute("secret", true);
+ }
+
+ if (getMaxLength() >= 0) {
+ target.addAttribute("maxLength", getMaxLength());
+ }
+
+ if (inputPrompt != null) {
+ target.addAttribute("prompt", inputPrompt);
+ }
+
+ // Adds the number of column and rows
+ final int c = getColumns();
+ final int r = getRows();
+ if (c != 0) {
+ target.addAttribute("cols", String.valueOf(c));
+ }
+ if (r != 0) {
+ target.addAttribute("rows", String.valueOf(r));
+ target.addAttribute("multiline", true);
+ if (!wordwrap) {
+ target.addAttribute("wordwrap", 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);
+ }
+
+ /**
+ * 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();
+ }
+
+ /*
+ * Gets the value of the field, but uses formatter is given. Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Object getValue() {
+ Object v = super.getValue();
+ if (format == null || v == null) {
+ return v;
+ }
+ try {
+ return format.format(v);
+ } catch (final IllegalArgumentException e) {
+ return v;
+ }
+ }
+
+ /*
+ * Gets the components UIDL tag string. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ @Override
+ public String getTag() {
+ return "textfield";
+ }
+
+ /*
+ * 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 variables) {
+
+ super.changeVariables(source, 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 = 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();
+ }
+ }
+ }
+
+ }
+
+ /* Text field configuration */
+
+ /**
+ * 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 columns;
+ }
+
+ /**
+ * 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;
+ }
+ this.columns = columns;
+ requestRepaint();
+ }
+
+ /**
+ * Gets 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.
+ *
+ * @return number of explicitly set rows.
+ */
+ 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.
+ *
+ * @param rows
+ * the number of rows for this editor.
+ */
+ public void setRows(int rows) {
+ if (rows < 0) {
+ rows = 0;
+ }
+ this.rows = rows;
+ requestRepaint();
+ }
+
+ /**
+ * Tests if the editor is in word-wrap mode.
+ *
+ * @return <code>true</code> if the component is in the word-wrap mode,
+ * <code>false</code> if not.
+ */
+ public boolean isWordwrap() {
+ return wordwrap;
+ }
+
+ /**
+ * Sets the editor's word-wrap mode on or off.
+ *
+ * @param wordwrap
+ * the boolean value specifying if the editor should be in
+ * word-wrap mode after the call or not.
+ */
+ public void setWordwrap(boolean wordwrap) {
+ this.wordwrap = wordwrap;
+ }
+
+ /* 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 getType() {
+ return String.class;
+ }
+
+ /**
+ * Gets the secret property on and off. If a field is used to enter
+ * secretinformation the information is not echoed to display.
+ *
+ * @return <code>true</code> if the field is used to enter secret
+ * information, <code>false</code> otherwise.
+ */
+ public boolean isSecret() {
+ return secret;
+ }
+
+ /**
+ * Sets the secret property on and off. If a field is used to enter
+ * secretinformation the information is not echoed to display.
+ *
+ * @param secret
+ * the value specifying if the field is used to enter secret
+ * information.
+ */
+ public void setSecret(boolean secret) {
+ this.secret = secret;
+ requestRepaint();
+ }
+
+ /**
+ * 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 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;
+ }
+
+ /**
+ * Gets the value formatter of TextField.
+ *
+ * @return the Format used to format the value.
+ * @deprecated
+ */
+ @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
+ */
+ @Deprecated
+ public void setFormat(Format format) {
+ this.format = format;
+ requestRepaint();
+ }
+
+ @Override
+ protected boolean isEmpty() {
+ return super.isEmpty() || toString().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 maxLength;
+ }
+
+ /**
+ * 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) {
+ this.maxLength = maxLength;
+ requestRepaint();
+ }
+
+}
diff --git a/src/com/vaadin/ui/Tree.java b/src/com/vaadin/ui/Tree.java
new file mode 100644
index 0000000000..0d56384994
--- /dev/null
+++ b/src/com/vaadin/ui/Tree.java
@@ -0,0 +1,1048 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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.ItemClickEvent;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.event.ItemClickEvent.ItemClickSource;
+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.MouseEventDetails;
+
+/**
+ * Tree component. A Tree can be used to select an item (or multiple items) from
+ * a hierarchical set of items.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Tree extends AbstractSelect implements Container.Hierarchical,
+ Action.Container, ItemClickSource {
+
+ private static final Method EXPAND_METHOD;
+
+ private static final Method COLLAPSE_METHOD;
+
+ static {
+ try {
+ EXPAND_METHOD = ExpandListener.class.getDeclaredMethod(
+ "nodeExpand", new Class[] { ExpandEvent.class });
+ COLLAPSE_METHOD = CollapseListener.class.getDeclaredMethod(
+ "nodeCollapse", new Class[] { CollapseEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in Tree");
+ }
+ }
+
+ /* Private members */
+
+ /**
+ * Set of expanded nodes.
+ */
+ private final HashSet expanded = new HashSet();
+
+ /**
+ * List of action handlers.
+ */
+ private LinkedList<Action.Handler> actionHandlers = null;
+
+ /**
+ * Action mapper.
+ */
+ private KeyMapper actionMapper = null;
+
+ /**
+ * Is the tree selectable .
+ */
+ 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;
+
+ /* 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 todo = new Stack();
+ 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 todo = new Stack();
+ 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;
+ }
+
+ /**
+ * Getter for property selectable.
+ *
+ * <p>
+ * The tree is selectable by default.
+ * </p>
+ *
+ * @return the Value of property selectable.
+ */
+ public boolean isSelectable() {
+ return selectable;
+ }
+
+ /**
+ * Setter for property selectable.
+ *
+ * <p>
+ * The tree is 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();
+ }
+ }
+
+ /* Component API */
+
+ /**
+ * Gets the UIDL tag corresponding to the component.
+ *
+ * @see com.vaadin.ui.AbstractComponent#getTag()
+ */
+ @Override
+ public String getTag() {
+ return "tree";
+ }
+
+ /**
+ * Called when one or more variables handled by the implementing class are
+ * changed.
+ *
+ * @see com.vaadin.terminal.VariableOwner#changeVariables(Object
+ * source, Map variables)
+ */
+ @Override
+ public void changeVariables(Object source, Map variables) {
+
+ if (clickListenerCount > 0 && 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(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);
+ }
+ }
+ }
+
+ // 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 = (Action) actionMapper.get(st.nextToken());
+ if (action != null && containsId(itemId)
+ && actionHandlers != null) {
+ for (final Iterator<Action.Handler> i = actionHandlers
+ .iterator(); i.hasNext();) {
+ i.next().handleAction(action, this, itemId);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 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"));
+ } else {
+ target.addAttribute("selectmode", "none");
+ }
+ if (isNewItemsAllowed()) {
+ target.addAttribute("allownewitem", true);
+ }
+
+ if (isNullSelectionAllowed()) {
+ target.addAttribute("nullselect", true);
+ }
+
+ if (clickListenerCount > 0) {
+ target.addAttribute("listenClicks", true);
+ }
+
+ }
+
+ // Initialize variables
+ final Set<Action> actionSet = new LinkedHashSet<Action>();
+ String[] selectedKeys;
+ if (isMultiSelect()) {
+ selectedKeys = new String[((Set) getValue()).size()];
+ } else {
+ selectedKeys = new String[(getValue() == null ? 0 : 1)];
+ }
+ int keyIndex = 0;
+ final LinkedList expandedKeys = new LinkedList();
+
+ // 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());
+ }
+
+ 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");
+ }
+
+ // Adds the attributes
+ target.addAttribute("caption", getItemCaption(itemId));
+ final Resource icon = getItemIcon(itemId);
+ if (icon != null) {
+ target.addAttribute("icon", getItemIcon(itemId));
+ }
+ final String key = itemIdMapper.key(itemId);
+ target.addAttribute("key", key);
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ try {
+ selectedKeys[keyIndex++] = key;
+ } catch (Exception e) {
+ // TODO Fix, see TreeExample (featurebrowser)
+ e.printStackTrace();
+ }
+ }
+ 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("caption", a.getCaption());
+ }
+ if (a.getIcon() != null) {
+ target.addAttribute("icon", a.getIcon());
+ }
+ target.addAttribute("key", actionMapper.key(a));
+ target.endTag("action");
+ }
+ target.endTag("actions");
+ }
+
+ if (partialUpdate) {
+ // update tree-level selection information in case some selected
+ // node(s) were collapsed
+ target.addVariable(this, "selected", selectedKeys);
+
+ partialUpdate = false;
+ } else {
+ // Selected
+ target.addVariable(this, "selected", selectedKeys);
+
+ // Expand and collapse
+ target.addVariable(this, "expand", new String[] {});
+ target.addVariable(this, "collapse", new String[] {});
+
+ // New items
+ target.addVariable(this, "newitem", new String[] {});
+ }
+ }
+
+ /* Container.Hierarchical API */
+
+ /**
+ * Tests if the Item with given ID can have any children.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#areChildrenAllowed(Object)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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)
+ */
+ 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()
+ */
+ 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)
+ */
+ public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) {
+ final boolean success = ((Container.Hierarchical) items)
+ .setChildrenAllowed(itemId, areChildrenAllowed);
+ if (success) {
+ fireValueChange(false);
+ }
+ return success;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Hierarchical#setParent(java.lang.Object
+ * , java.lang.Object)
+ */
+ 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface ExpandListener extends Serializable {
+
+ /**
+ * 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, EXPAND_METHOD);
+ }
+
+ /**
+ * Removes the expand listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(ExpandListener listener) {
+ removeListener(ExpandEvent.class, listener, 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface CollapseListener extends Serializable {
+
+ /**
+ * 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, COLLAPSE_METHOD);
+ }
+
+ /**
+ * Removes the collapse listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(CollapseListener listener) {
+ removeListener(CollapseEvent.class, listener, 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)
+ */
+ public void addActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList<Action.Handler>();
+ actionMapper = new KeyMapper();
+ }
+
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ requestRepaint();
+ }
+ }
+ }
+
+ /**
+ * Removes an action handler.
+ *
+ * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
+ */
+ public void removeActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+
+ actionHandlers.remove(actionHandler);
+
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Gets the visible item ids.
+ *
+ * @see com.vaadin.ui.Select#getVisibleItemIds()
+ */
+ @Override
+ public Collection getVisibleItemIds() {
+
+ final LinkedList visible = new LinkedList();
+
+ // 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();
+ }
+ }
+
+ /**
+ * Focusing to this component is not supported.
+ *
+ * @throws UnsupportedOperationException
+ * if invoked.
+ * @see com.vaadin.ui.AbstractField#focus()
+ */
+ @Override
+ public void focus() throws UnsupportedOperationException {
+ 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 int clickListenerCount = 0;
+
+ public void addListener(ItemClickListener listener) {
+ addListener(ItemClickEvent.class, listener,
+ ItemClickEvent.ITEM_CLICK_METHOD);
+ clickListenerCount++;
+ // repaint needed only if click listening became necessary
+ if (clickListenerCount == 1) {
+ requestRepaint();
+ }
+ }
+
+ public void removeListener(ItemClickListener listener) {
+ removeListener(ItemClickEvent.class, listener,
+ ItemClickEvent.ITEM_CLICK_METHOD);
+ clickListenerCount++;
+ // repaint needed only if click listening is not needed in client
+ // anymore
+ if (clickListenerCount == 0) {
+ requestRepaint();
+ }
+ }
+
+}
diff --git a/src/com/vaadin/ui/TwinColSelect.java b/src/com/vaadin/ui/TwinColSelect.java
new file mode 100644
index 0000000000..a619a5a0a9
--- /dev/null
+++ b/src/com/vaadin/ui/TwinColSelect.java
@@ -0,0 +1,115 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * 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;
+
+ /**
+ *
+ */
+ 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.
+ *
+ * @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();
+ }
+ }
+
+ /**
+ * @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);
+ }
+ super.paintContent(target);
+ }
+
+}
diff --git a/src/com/vaadin/ui/Upload.java b/src/com/vaadin/ui/Upload.java
new file mode 100644
index 0000000000..238217a4c2
--- /dev/null
+++ b/src/com/vaadin/ui/Upload.java
@@ -0,0 +1,985 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
+import com.vaadin.Application;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.UploadStream;
+
+/**
+ * Component for uploading files from client to server.
+ *
+ * The visible component consists of a file name input box and a browse button
+ * and an upload submit button to start uploading.
+ *
+ * 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.
+ *
+ * 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).
+ *
+ * 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().
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Upload extends AbstractComponent implements Component.Focusable {
+
+ private boolean delayedFocus;
+
+ /**
+ * Upload buffer size.
+ */
+ private static final int BUFFER_SIZE = 64 * 1024; // 64k
+
+ /**
+ * 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";
+
+ /**
+ * ProgressListener to which information about progress is sent during
+ * upload
+ */
+ private ProgressListener progressListener;
+ private LinkedHashSet progressListeners;
+
+ /* TODO: Add a default constructor, receive to temp file. */
+
+ /**
+ * Creates a new instance of Upload that redirects the uploaded data to
+ * stream given by the Receiver.
+ *
+ * @param caption
+ * Normal component caption. You can set the caption of the
+ * upload submit button with setButtonCaption().
+ * @param uploadReceiver
+ * Receiver to call to retrieve output stream when upload starts.
+ */
+ public Upload(String caption, Receiver uploadReceiver) {
+ setCaption(caption);
+ receiver = uploadReceiver;
+ }
+
+ /**
+ * Gets the component type.
+ *
+ * @return Component type as string.
+ */
+ @Override
+ public String getTag() {
+ return "upload";
+ }
+
+ /**
+ * This method is called by terminal when upload is received.
+ *
+ * Note, this method is called outside synchronized (Application) block, so
+ * overriding this may be dangerous.
+ *
+ * @param upload
+ */
+ public void receiveUpload(UploadStream upload) {
+ if (!isUploading) {
+ throw new IllegalStateException("uploading not started");
+ }
+
+ // Gets file properties
+ final String filename = upload.getContentName();
+ final String type = upload.getContentType();
+
+ final Application application = getApplication();
+
+ synchronized (application) {
+ fireStarted(filename, type);
+ }
+
+ // Gets the output target stream
+ final OutputStream out = receiver.receiveUpload(filename, type);
+ if (out == null) {
+ synchronized (application) {
+ fireNoOutputStream(filename, type, 0);
+ endUpload();
+ }
+ return;
+ }
+
+ final InputStream in = upload.getStream();
+
+ if (null == in) {
+ // No file, for instance non-existent filename in html upload
+ synchronized (application) {
+ fireNoInputStream(filename, type, 0);
+ endUpload();
+ }
+ return;
+ }
+
+ final byte buffer[] = new byte[BUFFER_SIZE];
+ int bytesRead = 0;
+ totalBytes = 0;
+ try {
+ while ((bytesRead = in.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ totalBytes += bytesRead;
+ if (progressListener != null && contentLength > 0) {
+ // update progress if listener set and contentLength
+ // received
+ synchronized (application) {
+ fireUpdateProgress(totalBytes, contentLength);
+ }
+ }
+ }
+
+ // upload successful
+ out.close();
+ synchronized (application) {
+ fireUploadSuccess(filename, type, totalBytes);
+ endUpload();
+ requestRepaint();
+ }
+
+ } catch (final Exception e) {
+ synchronized (application) {
+ // Download interrupted
+ fireUploadInterrupted(filename, type, totalBytes, e);
+ endUpload();
+ }
+ }
+ }
+
+ /**
+ * 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 variables) {
+ // NOP
+
+ }
+
+ /**
+ * 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 {
+ // 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);
+
+ target.addAttribute("buttoncaption", buttonCaption);
+
+ target.addVariable(this, "fake", true);
+
+ target.addUploadStreamVariable(this, "stream");
+ }
+
+ /**
+ * Interface that must be implemented by the upload receivers to provide the
+ * Upload component an output stream to write the uploaded data.
+ *
+ * @author IT Mill 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.Received event 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public 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.Interrupted event is sent when the upload is received, but the
+ * reception is interrupted for some reason.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public 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 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 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.Success event is sent when the upload is received successfully.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public 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.Started event is sent when the upload is started to received.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.0
+ */
+ public class StartedEvent extends Component.Event {
+
+ private final String filename;
+ private final String type;
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public StartedEvent(Upload source, String filename, String MIMEType) {
+ super(source);
+ this.filename = filename;
+ type = MIMEType;
+ }
+
+ /**
+ * 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;
+ }
+
+ }
+
+ /**
+ * Receives the events when the upload starts.
+ *
+ * @author IT Mill 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 IT Mill 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 IT Mill 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 IT Mill 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();
+ }
+ 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));
+ }
+
+ /**
+ * Emit upload finished event.
+ *
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ protected void fireUploadReceived(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.FinishedEvent(this, filename, MIMEType, length));
+ }
+
+ /**
+ * 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 it = progressListeners.iterator(); it.hasNext();) {
+ ProgressListener l = (ProgressListener) it.next();
+ l.updateProgress(totalBytes, contentLength);
+ }
+ }
+ // deprecated:
+ if (progressListener != null) {
+ progressListener.updateProgress(totalBytes, contentLength);
+ }
+ }
+
+ /**
+ * Returns the current receiver.
+ *
+ * @return the Receiver.
+ */
+ public Receiver getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Sets the receiver.
+ *
+ * @param receiver
+ * the receiver to set.
+ */
+ public void setReceiver(Receiver receiver) {
+ this.receiver = receiver;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Focusable#focus()
+ */
+ public void focus() {
+ final Application app = getApplication();
+ if (app != null) {
+ getWindow().setFocusedComponent(this);
+ delayedFocus = false;
+ } else {
+ delayedFocus = true;
+ }
+ }
+
+ /**
+ * Gets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#getTabIndex()
+ */
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ /**
+ * Sets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ public void setTabIndex(int tabIndex) {
+ this.tabIndex = tabIndex;
+ }
+
+ /**
+ * Sets the size of the file currently being uploaded.
+ *
+ * @param contentLength
+ */
+ public void setUploadSize(long contentLength) {
+ this.contentLength = contentLength;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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.
+ */
+ public void endUpload() {
+ isUploading = false;
+ contentLength = -1;
+ }
+
+ 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) {
+ this.progressListener = progressListener;
+ }
+
+ /**
+ * This method is deprecated.
+ *
+ * @deprecated Replaced with addListener/removeListener
+ * @return listener
+ *
+ */
+ @Deprecated
+ public ProgressListener getProgressListener() {
+ return progressListener;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * File uploads usually have button that starts actual upload progress. This
+ * method is used to set text in that button.
+ *
+ * @param buttonCaption
+ * text for uploads button.
+ */
+ public void setButtonCaption(String buttonCaption) {
+ this.buttonCaption = buttonCaption;
+ }
+
+ /**
+ * Notifies the component that it is connected to an application.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ super.attach();
+ if (delayedFocus) {
+ focus();
+ }
+ }
+
+}
diff --git a/src/com/vaadin/ui/UriFragmentUtility.java b/src/com/vaadin/ui/UriFragmentUtility.java
new file mode 100644
index 0000000000..ad00386369
--- /dev/null
+++ b/src/com/vaadin/ui/UriFragmentUtility.java
@@ -0,0 +1,153 @@
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import com.vaadin.service.ApplicationContext.TransactionListener;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+
+/**
+ * Experimental web browser dependent component for URI fragment (part after
+ * hash mark "#") reading and writing.
+ *
+ * Component can be used to workaround common ajax web applications pitfalls:
+ * bookmarking a program state and back button.
+ *
+ */
+@SuppressWarnings("serial")
+public class UriFragmentUtility extends AbstractComponent {
+
+ /**
+ * Listener that listens changes in URI fragment.
+ */
+ public interface FragmentChangedListener extends Serializable {
+
+ public void fragmentChanged(FragmentChangedEvent source);
+
+ }
+
+ /**
+ * Event fired when uri fragment changes.
+ */
+ public class FragmentChangedEvent extends Component.Event {
+
+ /**
+ * Creates a new instance of UriFragmentReader change event.
+ *
+ * @param source
+ * the Source of the event.
+ */
+ public FragmentChangedEvent(Component source) {
+ super(source);
+ }
+
+ /**
+ * Gets the UriFragmentReader where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public UriFragmentUtility getUriFragmentUtility() {
+ return (UriFragmentUtility) getSource();
+ }
+ }
+
+ private static final Method FRAGMENT_CHANGED_METHOD;
+
+ static {
+ try {
+ FRAGMENT_CHANGED_METHOD = FragmentChangedListener.class
+ .getDeclaredMethod("fragmentChanged",
+ new Class[] { FragmentChangedEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in FragmentChangedListener");
+ }
+ }
+
+ public void addListener(FragmentChangedListener listener) {
+ addListener(FragmentChangedEvent.class, listener,
+ FRAGMENT_CHANGED_METHOD);
+ }
+
+ public void removeListener(FragmentChangedListener listener) {
+ removeListener(FragmentChangedEvent.class, listener,
+ FRAGMENT_CHANGED_METHOD);
+ }
+
+ private String fragment;
+
+ public UriFragmentUtility() {
+ // immediate by default
+ setImmediate(true);
+ }
+
+ @Override
+ public String getTag() {
+ return "urifragment";
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ target.addVariable(this, "fragment", fragment);
+ }
+
+ @Override
+ public void changeVariables(Object source, Map variables) {
+ super.changeVariables(source, variables);
+ fragment = (String) variables.get("fragment");
+ fireEvent(new FragmentChangedEvent(this));
+ }
+
+ /**
+ * Gets currently set URI fragment.
+ *
+ * Note that initial URI fragment that user used to enter the application
+ * will be read after application init. If you absolutely need that you must
+ * hook to {@link TransactionListener}
+ *
+ * To listen changes in fragment, hook a {@link FragmentChangedListener}.
+ *
+ * @return the current fragment in browser uri or null if not known
+ */
+ public String getFragment() {
+ return fragment;
+ }
+
+ /**
+ * Sets URI fragment. Optionally fires a {@link FragmentChangedEvent}
+ *
+ * @param newFragment
+ * id of the new fragment
+ * @param fireEvent
+ * true to fire event
+ * @see FragmentChangedEvent
+ * @see FragmentChangedListener
+ */
+ public void setFragment(String newFragment, boolean fireEvent) {
+ if ((newFragment == null && fragment != null)
+ || (newFragment != null && !newFragment.equals(fragment))) {
+ fragment = newFragment;
+ if (fireEvent) {
+ fireEvent(new FragmentChangedEvent(this));
+ }
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Sets URI fragment. This method fires a {@link FragmentChangedEvent}
+ *
+ * @param newFragment
+ * id of the new fragment
+ * @see FragmentChangedEvent
+ * @see FragmentChangedListener
+ */
+ public void setFragment(String newFragment) {
+ setFragment(newFragment, true);
+ }
+
+}
diff --git a/src/com/vaadin/ui/VerticalLayout.java b/src/com/vaadin/ui/VerticalLayout.java
new file mode 100644
index 0000000000..55f70a740f
--- /dev/null
+++ b/src/com/vaadin/ui/VerticalLayout.java
@@ -0,0 +1,27 @@
+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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.3
+ */
+@SuppressWarnings("serial")
+public class VerticalLayout extends AbstractOrderedLayout {
+
+ public VerticalLayout() {
+ setWidth("100%");
+ }
+
+ @Override
+ public String getTag() {
+ return "verticallayout";
+ }
+
+}
diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java
new file mode 100644
index 0000000000..90b761652c
--- /dev/null
+++ b/src/com/vaadin/ui/Window.java
@@ -0,0 +1,1627 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.Application;
+import com.vaadin.terminal.DownloadStream;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.ParameterHandler;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.Sizeable;
+import com.vaadin.terminal.Terminal;
+import com.vaadin.terminal.URIHandler;
+
+/**
+ * Application window component.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Window extends Panel implements URIHandler, ParameterHandler {
+
+ /**
+ * Window with no border.
+ */
+ public static final int BORDER_NONE = 0;
+
+ /**
+ * Window with only minimal border.
+ */
+ public static final int BORDER_MINIMAL = 1;
+
+ /**
+ * Window with default borders.
+ */
+ public static final int BORDER_DEFAULT = 2;
+
+ /**
+ * The terminal this window is attached to.
+ */
+ private Terminal terminal = null;
+
+ /**
+ * The application this window is attached to.
+ */
+ private Application application = null;
+
+ /**
+ * List of URI handlers for this window.
+ */
+ private LinkedList uriHandlerList = null;
+
+ /**
+ * List of parameter handlers for this window.
+ */
+ private LinkedList parameterHandlerList = null;
+
+ /** Set of subwindows */
+ private final HashSet subwindows = new HashSet();
+
+ /**
+ * Explicitly specified theme of this window. If null, application theme is
+ * used.
+ */
+ private String theme = null;
+
+ /**
+ * Resources to be opened automatically on next repaint.
+ */
+ private final LinkedList openList = new LinkedList();
+
+ /**
+ * The name of the window.
+ */
+ private String name = null;
+
+ /**
+ * Window border mode.
+ */
+ private int border = BORDER_DEFAULT;
+
+ /**
+ * Distance of Window top border in pixels from top border of the containing
+ * (main window) or -1 if unspecified.
+ */
+ private int positionY = -1;
+
+ /**
+ * Distance of Window left border in pixels from left border of the
+ * containing (main window) or -1 if unspecified .
+ */
+ private int positionX = -1;
+
+ private LinkedList notifications;
+
+ private boolean modal = false;
+
+ private boolean resizable = true;
+
+ private boolean centerRequested = false;
+
+ private Focusable pendingFocus;
+
+ /* ********************************************************************* */
+
+ /**
+ * Creates a new empty unnamed window with default layout.
+ *
+ * <p>
+ * To show the window in application, it must be added to application with
+ * <code>Application.addWindow</code> method.
+ * </p>
+ *
+ * <p>
+ * The windows are scrollable by default.
+ * </p>
+ *
+ * @param caption
+ * the Title of the window.
+ */
+ public Window() {
+ this("", null);
+ }
+
+ /**
+ * Creates a new empty window with default layout.
+ *
+ * <p>
+ * To show the window in application, it must be added to application with
+ * <code>Application.addWindow</code> method.
+ * </p>
+ *
+ * <p>
+ * The windows are scrollable by default.
+ * </p>
+ *
+ * @param caption
+ * the Title of the window.
+ */
+ public Window(String caption) {
+ this(caption, null);
+ }
+
+ /**
+ * Creates a new window.
+ *
+ * <p>
+ * To show the window in application, it must be added to application with
+ * <code>Application.addWindow</code> method.
+ * </p>
+ *
+ * <p>
+ * The windows are scrollable by default.
+ * </p>
+ *
+ * @param caption
+ * the Title of the window.
+ * @param layout
+ * the Layout of the window.
+ */
+ public Window(String caption, ComponentContainer content) {
+ super(caption, content);
+ setScrollable(true);
+ setSizeUndefined();
+ }
+
+ /**
+ * Gets the terminal type.
+ *
+ * @return the Value of property terminal.
+ */
+ public Terminal getTerminal() {
+ return terminal;
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Gets the window of the component. Returns the window where this component
+ * belongs to. If the component does not yet belong to a window the returns
+ * null.
+ *
+ * @return the parent window of the component.
+ */
+ @Override
+ public final Window getWindow() {
+ return this;
+ }
+
+ /**
+ * Gets the application instance of the component. Returns the application
+ * where this component belongs to. If the component does not yet belong to
+ * a application the returns null.
+ *
+ * @return the parent application of the component.
+ */
+ @Override
+ public final Application getApplication() {
+ if (getParent() == null) {
+ return application;
+ }
+ return ((Window) getParent()).getApplication();
+ }
+
+ /**
+ * Getter for property parent.
+ *
+ * <p>
+ * Parent is the visual parent of a component. Each component can belong to
+ * only one ComponentContainer at time.
+ * </p>
+ *
+ * <p>
+ * For windows attached directly to the application, parent is
+ * <code>null</code>. For windows inside other windows, parent is the window
+ * containing this window.
+ * </p>
+ *
+ * @return the Value of property parent.
+ */
+ @Override
+ public final Component getParent() {
+ return super.getParent();
+ }
+
+ /**
+ * Setter for property parent.
+ *
+ * <p>
+ * Parent is the visual parent of a component. This is mostly called by
+ * containers add method and should not be called directly
+ * </p>
+ *
+ * @param parent
+ * the New value of property parent.
+ */
+ @Override
+ public void setParent(Component parent) {
+ super.setParent(parent);
+ }
+
+ /**
+ * Gets the component UIDL tag.
+ *
+ * @return the Component UIDL tag as string.
+ */
+ @Override
+ public String getTag() {
+ return "window";
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Adds the new URI handler to this window. For sub-windows, URI handlers
+ * are attached to root level window.
+ *
+ * @param handler
+ * the URI handler to add.
+ */
+ public void addURIHandler(URIHandler handler) {
+ if (getParent() != null) {
+ // this is subwindow, attach to main level instead
+ // TODO hold internal list also and remove on detach
+ Window mainWindow = (Window) getParent();
+ mainWindow.addURIHandler(handler);
+ } else {
+ if (uriHandlerList == null) {
+ uriHandlerList = new LinkedList();
+ }
+ synchronized (uriHandlerList) {
+ if (!uriHandlerList.contains(handler)) {
+ uriHandlerList.addLast(handler);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes the given URI handler from this window.
+ *
+ * @param handler
+ * the URI handler to remove.
+ */
+ public void removeURIHandler(URIHandler handler) {
+ if (getParent() != null) {
+ // this is subwindow
+ Window mainWindow = (Window) getParent();
+ mainWindow.removeURIHandler(handler);
+ } else {
+ if (handler == null || uriHandlerList == null) {
+ return;
+ }
+ synchronized (uriHandlerList) {
+ uriHandlerList.remove(handler);
+ if (uriHandlerList.isEmpty()) {
+ uriHandlerList = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles uri recursively. Windows uri handler passes uri to all
+ * {@link URIHandler}s added to it.
+ * <p>
+ * Note, that instead of overriding this method developer should consider
+ * using {@link Window#addURIHandler(URIHandler)} to add uri handler to
+ * Window.
+ *
+ * @param context
+ * @param relativeUri
+ */
+ public DownloadStream handleURI(URL context, String relativeUri) {
+
+ DownloadStream result = null;
+ if (uriHandlerList != null) {
+ Object[] handlers;
+ synchronized (uriHandlerList) {
+ handlers = uriHandlerList.toArray();
+ }
+ for (int i = 0; i < handlers.length; i++) {
+ final DownloadStream ds = ((URIHandler) handlers[i]).handleURI(
+ context, relativeUri);
+ if (ds != null) {
+ if (result != null) {
+ throw new RuntimeException("handleURI for " + context
+ + " uri: '" + relativeUri
+ + "' returns ambigious result.");
+ }
+ result = ds;
+ }
+ }
+ }
+ return result;
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Adds the new parameter handler to this window. For sub windows, parameter
+ * handlers are attached to parent windows.
+ *
+ * @param handler
+ * the parameter handler to add.
+ */
+ public void addParameterHandler(ParameterHandler handler) {
+ if (getParent() != null) {
+ // this is subwindow
+ // TODO hold internal list also and remove on detach
+ Window mainWindow = (Window) getParent();
+ mainWindow.addParameterHandler(handler);
+ } else {
+ if (parameterHandlerList == null) {
+ parameterHandlerList = new LinkedList();
+ }
+ synchronized (parameterHandlerList) {
+ if (!parameterHandlerList.contains(handler)) {
+ parameterHandlerList.addLast(handler);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Removes the given URI handler from this window.
+ *
+ * @param handler
+ * the parameter handler to remove.
+ */
+ public void removeParameterHandler(ParameterHandler handler) {
+ if (getParent() != null) {
+ // this is subwindow
+ Window mainWindow = (Window) getParent();
+ mainWindow.addParameterHandler(handler);
+ } else {
+ if (handler == null || parameterHandlerList == null) {
+ return;
+ }
+ synchronized (parameterHandlerList) {
+ parameterHandlerList.remove(handler);
+ if (parameterHandlerList.isEmpty()) {
+ parameterHandlerList = null;
+ }
+ }
+ }
+ }
+
+ /* Documented by the interface */
+ public void handleParameters(Map parameters) {
+ if (parameterHandlerList != null) {
+ Object[] handlers;
+ synchronized (parameterHandlerList) {
+ handlers = parameterHandlerList.toArray();
+ }
+ for (int i = 0; i < handlers.length; i++) {
+ ((ParameterHandler) handlers[i]).handleParameters(parameters);
+ }
+ }
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Gets the theme for this window.
+ *
+ * <p>
+ * Subwindows do not support themes and thus return theme used by the parent
+ * </p>
+ *
+ * @return the Name of the theme used in window. If the theme for this
+ * individual window is not explicitly set, the application theme is
+ * used instead. If application is not assigned the
+ * terminal.getDefaultTheme is used. If terminal is not set, null is
+ * returned
+ */
+ public String getTheme() {
+ if (getParent() != null) {
+ return ((Window) getParent()).getTheme();
+ }
+ if (theme != null) {
+ return theme;
+ }
+ if ((application != null) && (application.getTheme() != null)) {
+ return application.getTheme();
+ }
+ if (terminal != null) {
+ return terminal.getDefaultTheme();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the theme for this window.
+ *
+ * Setting theme for subwindows is not supported.
+ *
+ * In Toolkit 5 terminal will reload its host page on theme changes.
+ *
+ * @param theme
+ * the New theme for this window. Null implies the default theme.
+ */
+ public void setTheme(String theme) {
+ if (getParent() != null) {
+ throw new UnsupportedOperationException(
+ "Setting theme for sub-windows is not supported.");
+ }
+ this.theme = theme;
+ requestRepaint();
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param event
+ * the Paint Event.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public synchronized void paintContent(PaintTarget target)
+ throws PaintException {
+
+ // Sets the window name
+ final String name = getName();
+ target.addAttribute("name", name == null ? "" : name);
+
+ // Sets the window theme
+ final String theme = getTheme();
+ target.addAttribute("theme", theme == null ? "" : theme);
+
+ if (modal) {
+ target.addAttribute("modal", true);
+ }
+
+ if (resizable) {
+ target.addAttribute("resizable", true);
+ }
+
+ if (centerRequested) {
+ target.addAttribute("center", true);
+ centerRequested = false;
+ }
+
+ // Marks the main window
+ if (getApplication() != null
+ && this == getApplication().getMainWindow()) {
+ target.addAttribute("main", true);
+ }
+
+ if (getContent() != null) {
+ if (getContent().getHeightUnits() == Sizeable.UNITS_PERCENTAGE) {
+ target.addAttribute("layoutRelativeHeight", true);
+ }
+ if (getContent().getWidthUnits() == Sizeable.UNITS_PERCENTAGE) {
+ target.addAttribute("layoutRelativeWidth", true);
+ }
+ }
+
+ // Open requested resource
+ synchronized (openList) {
+ if (!openList.isEmpty()) {
+ for (final Iterator i = openList.iterator(); i.hasNext();) {
+ ((OpenResource) i.next()).paintContent(target);
+ }
+ openList.clear();
+ }
+ }
+
+ // Contents of the window panel is painted
+ super.paintContent(target);
+
+ // Window position
+ target.addVariable(this, "positionx", getPositionX());
+ target.addVariable(this, "positiony", getPositionY());
+
+ // Window closing
+ target.addVariable(this, "close", false);
+
+ // Paint subwindows
+ for (final Iterator i = subwindows.iterator(); i.hasNext();) {
+ final Window w = (Window) i.next();
+ w.paint(target);
+ }
+
+ // Paint notifications
+ if (notifications != null) {
+ target.startTag("notifications");
+ for (final Iterator it = notifications.iterator(); it.hasNext();) {
+ final Notification n = (Notification) it.next();
+ target.startTag("notification");
+ if (n.getCaption() != null) {
+ target.addAttribute("caption", n.getCaption());
+ }
+ if (n.getMessage() != null) {
+ target.addAttribute("message", n.getMessage());
+ }
+ if (n.getIcon() != null) {
+ target.addAttribute("icon", n.getIcon());
+ }
+ target.addAttribute("position", n.getPosition());
+ target.addAttribute("delay", n.getDelayMsec());
+ if (n.getStyleName() != null) {
+ target.addAttribute("style", n.getStyleName());
+ }
+ target.endTag("notification");
+ }
+ target.endTag("notifications");
+ notifications = null;
+ }
+
+ if (pendingFocus != null) {
+ // ensure focused component is still attached to this main window
+ if (pendingFocus.getWindow() == this
+ || (pendingFocus.getWindow() != null && pendingFocus
+ .getWindow().getParent() == this)) {
+ target.paintReference(pendingFocus, "focused");
+ }
+ pendingFocus = null;
+ }
+
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Opens the given resource in this window.
+ *
+ * @param resource
+ */
+ public void open(Resource resource) {
+ synchronized (openList) {
+ if (!openList.contains(resource)) {
+ openList.add(new OpenResource(resource, null, -1, -1,
+ BORDER_DEFAULT));
+ }
+ }
+ requestRepaint();
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Opens the given resource in named terminal window. Empty or
+ * <code>null</code> window name results the resource to be opened in this
+ * window.
+ *
+ * @param resource
+ * the resource.
+ * @param windowName
+ * the name of the window.
+ */
+ public void open(Resource resource, String windowName) {
+ synchronized (openList) {
+ if (!openList.contains(resource)) {
+ openList.add(new OpenResource(resource, windowName, -1, -1,
+ BORDER_DEFAULT));
+ }
+ }
+ requestRepaint();
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Opens the given resource in named terminal window with given size and
+ * border properties. Empty or <code>null</code> window name results the
+ * resource to be opened in this window.
+ *
+ * @param resource
+ * @param windowName
+ * @param width
+ * @param height
+ * @param border
+ */
+ public void open(Resource resource, String windowName, int width,
+ int height, int border) {
+ synchronized (openList) {
+ if (!openList.contains(resource)) {
+ openList.add(new OpenResource(resource, windowName, width,
+ height, border));
+ }
+ }
+ requestRepaint();
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Returns the full url of the window, this returns window specific url even
+ * for the main window.
+ *
+ * @return the URL of the window.
+ */
+ public URL getURL() {
+
+ if (application == null) {
+ return null;
+ }
+
+ try {
+ return new URL(application.getURL(), getName() + "/");
+ } catch (final MalformedURLException e) {
+ throw new RuntimeException(
+ "Internal problem getting window URL, please report");
+ }
+ }
+
+ /**
+ * Gets the unique name of the window that indentifies it on the terminal.
+ *
+ * <p>
+ * Name identifies the URL used to access application-level windows, but is
+ * not used for windows inside other windows. all application-level windows
+ * can be accessed by their names in url
+ * <code>http://host:port/foo/bar/</code> where
+ * <code>http://host:port/foo/</code> is the application url as returned by
+ * getURL() and <code>bar</code> is the name of the window. Also note that
+ * not all windows should be added to application - one can also add windows
+ * inside other windows - these windows show as smaller windows inside those
+ * windows.
+ * </p>
+ *
+ * @return the Name of the Window.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the border.
+ *
+ * @return the border.
+ */
+ public int getBorder() {
+ return border;
+ }
+
+ /**
+ * Sets the border.
+ *
+ * @param border
+ * the border to set.
+ */
+ public void setBorder(int border) {
+ this.border = border;
+ }
+
+ /**
+ * Sets the application this window is connected to.
+ *
+ * <p>
+ * This method should not be invoked directly. Instead the
+ * {@link com.vaadin.Application#addWindow(Window)} method should be
+ * used to add the window to an application and
+ * {@link com.vaadin.Application#removeWindow(Window)} method for
+ * removing the window from the applicion. These methods call this method
+ * implicitly.
+ * </p>
+ *
+ * <p>
+ * The method invokes {@link Component#attach()} and
+ * {@link Component#detach()} methods when necessary.
+ * <p>
+ *
+ * @param application
+ * the application to set.
+ */
+ public void setApplication(Application application) {
+
+ // If the application is not changed, dont do nothing
+ if (application == this.application) {
+ return;
+ }
+
+ // Sends detach event if the window is connected to application
+ if (this.application != null) {
+ detach();
+ }
+
+ // Connects to new parent
+ this.application = application;
+
+ // Sends the attach event if connected to a window
+ if (application != null) {
+ attach();
+ }
+ }
+
+ /**
+ * Sets the name.
+ * <p>
+ * The name of the window must be unique inside the application.
+ * </p>
+ *
+ * <p>
+ * If the name is null, the the window is given name automatically when it
+ * is added to an application.
+ * </p>
+ *
+ * @param name
+ * the name to set.
+ */
+ public void setName(String 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");
+ }
+
+ this.name = name;
+ }
+
+ /**
+ * Sets the terminal type. The terminal type is set by the the terminal
+ * adapter and may change from time to time.
+ *
+ * @param type
+ * the terminal type to set.
+ */
+ public void setTerminal(Terminal type) {
+ terminal = type;
+ }
+
+ /**
+ * Private data structure for storing opening window properties.
+ */
+ private class OpenResource implements Serializable {
+
+ private final Resource resource;
+
+ private final String name;
+
+ private final int width;
+
+ private final int height;
+
+ private final int border;
+
+ /**
+ * Creates a new open resource.
+ *
+ * @param resource
+ * @param name
+ * @param width
+ * @param height
+ * @param border
+ */
+ private OpenResource(Resource resource, String name, int width,
+ int height, int border) {
+ this.resource = resource;
+ this.name = name;
+ this.width = width;
+ this.height = height;
+ this.border = border;
+ }
+
+ /**
+ * Paints the open-tag inside the window.
+ *
+ * @param target
+ * the Paint Event.
+ * @throws PaintException
+ * if the Paint Operation fails.
+ */
+ private void paintContent(PaintTarget target) throws PaintException {
+ target.startTag("open");
+ target.addAttribute("src", resource);
+ if (name != null && name.length() > 0) {
+ target.addAttribute("name", name);
+ }
+ if (width >= 0) {
+ target.addAttribute("width", width);
+ }
+ if (height >= 0) {
+ target.addAttribute("height", height);
+ }
+ switch (border) {
+ case Window.BORDER_MINIMAL:
+ target.addAttribute("border", "minimal");
+ break;
+ case Window.BORDER_NONE:
+ target.addAttribute("border", "none");
+ break;
+ }
+
+ target.endTag("open");
+ }
+ }
+
+ /**
+ * Called when one or more variables handled by the implementing class are
+ * changed.
+ *
+ * @see com.vaadin.terminal.VariableOwner#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map variables) {
+
+ 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() != UNITS_PIXELS || (Integer) variables
+ .get("height") != getHeight())) {
+ sizeHasChanged = true;
+ }
+ if (variables.containsKey("width")
+ && (getWidthUnits() != UNITS_PIXELS || (Integer) variables
+ .get("width") != getWidth())) {
+ sizeHasChanged = true;
+ }
+
+ super.changeVariables(source, variables);
+
+ // Positioning
+ final Integer positionx = (Integer) variables.get("positionx");
+ if (positionx != null) {
+ final int x = positionx.intValue();
+ setPositionX(x < 0 ? -1 : x);
+ }
+ final Integer positiony = (Integer) variables.get("positiony");
+ if (positiony != null) {
+ final int y = positiony.intValue();
+ setPositionY(y < 0 ? -1 : y);
+ }
+
+ if (!isReadOnly()) {
+ // Closing
+ final Boolean close = (Boolean) variables.get("close");
+ if (close != null && close.booleanValue()) {
+ close();
+ }
+ }
+
+ // fire event if size has really changed
+ if (sizeHasChanged) {
+ fireResize();
+ }
+
+ }
+
+ /**
+ * 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>
+ * If one wants change the default behavior, register a window close
+ * listenter and do something else. For example, you could re-open the
+ * browser-level window with mainWindow.open(), re-add the removed
+ * sub-window back to its parent or remove browser-level window
+ * automatically from the application.
+ * </p>
+ */
+ protected void close() {
+ Window parent = (Window) getParent();
+ if (parent == null) {
+ fireClose();
+ } else {
+ // subwindow is removed from parent
+ parent.removeWindow(this);
+ fireClose();
+ }
+ }
+
+ /**
+ * 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 positionX;
+ }
+
+ /**
+ * 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) {
+ this.positionX = positionX;
+ 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 positionY;
+ }
+
+ /**
+ * 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) {
+ this.positionY = positionY;
+ 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();
+ }
+ }
+
+ public interface CloseListener extends Serializable {
+ public void windowClose(CloseEvent e);
+ }
+
+ /**
+ * Adds the listener.
+ *
+ * @param listener
+ * the listener to add.
+ */
+ public void addListener(CloseListener listener) {
+ addListener(CloseEvent.class, listener, WINDOW_CLOSE_METHOD);
+ }
+
+ /**
+ * Removes the listener.
+ *
+ * @param listener
+ * the listener to remove.
+ */
+ public void removeListener(CloseListener listener) {
+ addListener(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, this);
+ }
+
+ /**
+ * Fire the resize event.
+ */
+ protected void fireResize() {
+ fireEvent(new ResizeEvent(this));
+ }
+
+ private void attachWindow(Window w) {
+ subwindows.add(w);
+ w.setParent(this);
+ requestRepaint();
+ }
+
+ /**
+ * Adds a window inside another window.
+ *
+ * <p>
+ * Adding windows inside another window creates "subwindows". These windows
+ * should not be added to application directly and are not accessible
+ * directly with any url. Addding windows implicitly sets their parents.
+ * </p>
+ *
+ * <p>
+ * Only one level of subwindows are supported. Thus you can add windows
+ * inside such windows whose parent is <code>null</code>.
+ * </p>
+ *
+ * @param window
+ * @throws IllegalArgumentException
+ * if a window is added inside non-application level window.
+ * @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 was already added to application"
+ + " - it can not be added to another window also.");
+ } else if (getParent() != null) {
+ throw new IllegalArgumentException(
+ "You can only add windows inside application-level windows.");
+ } else if (window.subwindows.size() > 0) {
+ throw new IllegalArgumentException(
+ "Only one level of subwindows are supported.");
+ }
+
+ attachWindow(window);
+ }
+
+ /**
+ * Remove the given subwindow from this window.
+ *
+ * @param window
+ * Window to be removed.
+ */
+ public void removeWindow(Window window) {
+ subwindows.remove(window);
+ window.setParent(null);
+ requestRepaint();
+
+ }
+
+ /**
+ * Get the set of all child windows.
+ *
+ * @return Set of child windows.
+ */
+ public Set getChildWindows() {
+ return Collections.unmodifiableSet(subwindows);
+ }
+
+ /**
+ * Sets sub-window modal, so that widgets behind it cannot be accessed.
+ * <b>Note:</b> affects sub-windows only.
+ *
+ * @param modality
+ * true if modality is to be turned on
+ */
+ public void setModal(boolean modality) {
+ modal = modality;
+ center();
+ requestRepaint();
+ }
+
+ /**
+ * @return true if this window is modal.
+ */
+ public boolean isModal() {
+ return modal;
+ }
+
+ /**
+ * 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 resizeability) {
+ resizable = resizeability;
+ requestRepaint();
+ }
+
+ /**
+ *
+ * @return true if window is resizable by the end-user, otherwise false.
+ */
+ public boolean isResizable() {
+ return resizable;
+ }
+
+ /**
+ * Request to center this window on the screen. <b>Note:</b> affects
+ * sub-windows only.
+ */
+ public void center() {
+ centerRequested = true;
+ requestRepaint();
+ }
+
+ /**
+ * Shows a notification message on the middle of the window. The message
+ * automatically disappears ("humanized message").
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message
+ */
+ public void showNotification(String caption) {
+ addNotification(new Notification(caption));
+ }
+
+ /**
+ * Shows a notification message the window. 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.
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The message
+ * @param type
+ * The message type
+ */
+ public void showNotification(String caption, int type) {
+ addNotification(new Notification(caption, type));
+ }
+
+ /**
+ * Shows a notification consisting of a bigger caption and a smaller
+ * description on the middle of the window. The message automatically
+ * disappears ("humanized message").
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The caption of the message
+ * @param description
+ * The message description
+ *
+ */
+ public void showNotification(String caption, String description) {
+ addNotification(new Notification(caption, description));
+ }
+
+ /**
+ * 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.
+ *
+ * @see #showNotification(com.vaadin.ui.Window.Notification)
+ * @see Notification
+ *
+ * @param caption
+ * The caption of the message
+ * @param description
+ * The message description
+ * @param type
+ * The message type
+ */
+ public void showNotification(String caption, String description, int type) {
+ addNotification(new Notification(caption, description, type));
+ }
+
+ /**
+ * 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
+ */
+ public void showNotification(Notification notification) {
+ addNotification(notification);
+ }
+
+ private void addNotification(Notification notification) {
+ if (notifications == null) {
+ notifications = new LinkedList();
+ }
+ notifications.add(notification);
+ requestRepaint();
+ }
+
+ /**
+ * 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 Toolkit application, use Focusable.focus(). See
+ * {@link Focusable}.
+ *
+ * @param focusable
+ * to be focused on next paint
+ */
+ void setFocusedComponent(Focusable focusable) {
+ if (getParent() != null) {
+ // focus is handled by main windows
+ ((Window) getParent()).setFocusedComponent(focusable);
+ } else {
+ pendingFocus = focusable;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * 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 static 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;
+
+ /**
+ * Creates a "humanized" notification message.
+ *
+ * @param caption
+ * The message to show
+ */
+ public Notification(String caption) {
+ this(caption, null, TYPE_HUMANIZED_MESSAGE);
+ }
+
+ /**
+ * Creates a notification message of the specified type.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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 = caption;
+ this.description = description;
+ 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;
+ }
+
+ /**
+ * @deprecated Use {@link #getDescription()} instead.
+ * @return
+ */
+ @Deprecated
+ public String getMessage() {
+ return description;
+ }
+
+ /**
+ * @deprecated Use {@link #setDescription(String)} instead.
+ * @param description
+ */
+ @Deprecated
+ public void setMessage(String description) {
+ this.description = description;
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+}
diff --git a/src/com/vaadin/ui/doc-files/component_class_hierarchy.gif b/src/com/vaadin/ui/doc-files/component_class_hierarchy.gif
new file mode 100644
index 0000000000..936c220d11
--- /dev/null
+++ b/src/com/vaadin/ui/doc-files/component_class_hierarchy.gif
Binary files differ
diff --git a/src/com/vaadin/ui/doc-files/component_interfaces.gif b/src/com/vaadin/ui/doc-files/component_interfaces.gif
new file mode 100644
index 0000000000..44c99826bb
--- /dev/null
+++ b/src/com/vaadin/ui/doc-files/component_interfaces.gif
Binary files differ
diff --git a/src/com/vaadin/ui/package.html b/src/com/vaadin/ui/package.html
new file mode 100644
index 0000000000..2a26453a1f
--- /dev/null
+++ b/src/com/vaadin/ui/package.html
@@ -0,0 +1,72 @@
+<!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 the IT Mill Toolkit.</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 subinterfaces 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 UI components. It defines the
+common properties of the components and how the framework will handle
+them. Most simple components (like {@link com.vaadin.ui.Button} for
+example} won't need to implement the lower level interfaces described
+below. Note that 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 whose contents can be positioned arbitrarily.</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>