aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/itmill/toolkit/ui
diff options
context:
space:
mode:
authorJoonas Lehtinen <joonas.lehtinen@itmill.com>2006-11-01 09:11:32 +0000
committerJoonas Lehtinen <joonas.lehtinen@itmill.com>2006-11-01 09:11:32 +0000
commit13af8cba414fbb6f02ef458a86c5afcad70c5275 (patch)
tree959ccae1696d9c208124ec3982f166bca6c28f0a /src/com/itmill/toolkit/ui
parentde5565e87dc08be0a577c663bb2e009d0838c872 (diff)
downloadvaadin-framework-13af8cba414fbb6f02ef458a86c5afcad70c5275.tar.gz
vaadin-framework-13af8cba414fbb6f02ef458a86c5afcad70c5275.zip
Refactoring: Enably -> IT Mill Toolkit
svn changeset:92/svn branch:toolkit
Diffstat (limited to 'src/com/itmill/toolkit/ui')
-rw-r--r--src/com/itmill/toolkit/ui/AbstractComponent.java817
-rw-r--r--src/com/itmill/toolkit/ui/AbstractComponentContainer.java189
-rw-r--r--src/com/itmill/toolkit/ui/AbstractField.java922
-rw-r--r--src/com/itmill/toolkit/ui/BaseFieldFactory.java130
-rw-r--r--src/com/itmill/toolkit/ui/Button.java286
-rw-r--r--src/com/itmill/toolkit/ui/Component.java324
-rw-r--r--src/com/itmill/toolkit/ui/ComponentContainer.java173
-rw-r--r--src/com/itmill/toolkit/ui/CustomComponent.java387
-rw-r--r--src/com/itmill/toolkit/ui/CustomLayout.java183
-rw-r--r--src/com/itmill/toolkit/ui/DateField.java407
-rw-r--r--src/com/itmill/toolkit/ui/Embedded.java433
-rw-r--r--src/com/itmill/toolkit/ui/Field.java104
-rw-r--r--src/com/itmill/toolkit/ui/FieldFactory.java82
-rw-r--r--src/com/itmill/toolkit/ui/Form.java822
-rw-r--r--src/com/itmill/toolkit/ui/FrameWindow.java469
-rw-r--r--src/com/itmill/toolkit/ui/GridLayout.java757
-rw-r--r--src/com/itmill/toolkit/ui/Label.java455
-rw-r--r--src/com/itmill/toolkit/ui/Layout.java43
-rw-r--r--src/com/itmill/toolkit/ui/Link.java256
-rw-r--r--src/com/itmill/toolkit/ui/OrderedLayout.java215
-rw-r--r--src/com/itmill/toolkit/ui/Panel.java375
-rw-r--r--src/com/itmill/toolkit/ui/Select.java1202
-rw-r--r--src/com/itmill/toolkit/ui/TabSheet.java398
-rw-r--r--src/com/itmill/toolkit/ui/Table.java2114
-rw-r--r--src/com/itmill/toolkit/ui/TextField.java394
-rw-r--r--src/com/itmill/toolkit/ui/Tree.java763
-rw-r--r--src/com/itmill/toolkit/ui/Upload.java446
-rw-r--r--src/com/itmill/toolkit/ui/Window.java695
-rw-r--r--src/com/itmill/toolkit/ui/doc-files/component_class_hierarchy.gifbin0 -> 11077 bytes
-rw-r--r--src/com/itmill/toolkit/ui/doc-files/component_interfaces.gifbin0 -> 2272 bytes
-rw-r--r--src/com/itmill/toolkit/ui/package.html72
31 files changed, 13913 insertions, 0 deletions
diff --git a/src/com/itmill/toolkit/ui/AbstractComponent.java b/src/com/itmill/toolkit/ui/AbstractComponent.java
new file mode 100644
index 0000000000..4c08b3811f
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/AbstractComponent.java
@@ -0,0 +1,817 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.event.EventRouter;
+import com.itmill.toolkit.event.MethodEventSource;
+import com.itmill.toolkit.terminal.*;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
+import java.lang.reflect.Method;
+
+/** 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
+ * MillStone component. Most components in the MillStone base UI package do
+ * just that.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public abstract class AbstractComponent
+ implements Component, MethodEventSource {
+
+ /* Private members ************************************************* */
+
+ /** Look-and-feel style of the component. */
+ private String style;
+
+ /** Caption text. */
+ private String caption;
+
+ /** Application specific data object. */
+ private Object applicationData;
+
+ /** Icon to be shown together with caption. */
+ private Resource icon;
+
+ /** Is the component enable (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 MillStone event model. */
+ private EventRouter eventRouter = null;
+
+ /** The internal error message of the component. */
+ private ErrorMessage componentError = null;
+
+ /** List of event variable change event handling dependencies */
+ private Set dependencies = null;
+
+ /** Immediate mode: if true, all variable changes are required to be sent
+ * from the terminal immediately
+ */
+ private boolean immediate = false;
+
+ /** Debug mode: if true, the component may output visual debug information
+ */
+ private boolean debug = 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;
+
+ /* Constructor ***************************************************** */
+
+ /** Constructs a new Component */
+ public AbstractComponent() {
+ }
+
+ /* Get/Set component properties ************************************ */
+
+ /** Gets the UIDL tag corresponding to the component.
+ *
+ * @return component's UIDL tag as <code>String</code>
+ */
+ public abstract String getTag();
+
+ /* Gets the component's style.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public String getStyle() {
+ return this.style;
+ }
+
+ /* Sets the component's style.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public void setStyle(String style) {
+ this.style = 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 this.caption;
+ }
+
+ /** Sets the component's caption <code>String</code>. Caption is the
+ * visible name of the component. This method will trigger a
+ * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}.
+ *
+ * @param caption 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 (this.locale != null)
+ return this.locale;
+ if (this.parent != null)
+ return parent.getLocale();
+ Application app = this.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 this.icon;
+ }
+
+ /** Sets the component's icon. This method will trigger a
+ * {@link com.itmill.toolkit.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 this.enabled && 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) {
+ this.enabled = enabled;
+ 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.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}.
+ *
+ * @param immediate 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();
+ }
+
+ /* Tests if the component is visible.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public boolean isVisible() {
+ return this.visible;
+ }
+
+ /* Sets the components visibility.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ 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 this.description;
+ }
+
+ /** Sets the component's description. See {@link #getDescription()} for
+ * more information on what the description is. This method will trigger
+ * a {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}.
+ *
+ * @param description 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 this.parent;
+ }
+
+ /* Set 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, dont do nothing
+ if (parent == this.parent)
+ return;
+
+ // Send detach event if the component have been connected to a window
+ if (getApplication() != null) {
+ detach();
+ this.parent = null;
+ }
+
+ // Connect to new parent
+ this.parent = parent;
+
+ // Send attach event if connected to a window
+ if (getApplication() != null)
+ attach();
+ }
+
+ /** Get 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 this.componentError;
+ }
+
+ /** Gets the component's error message.
+ * @link Terminal.ErrorMessage#ErrorMessage(String, int)
+ *
+ * @return component's error message
+ */
+ public ErrorMessage getComponentError() {
+ return this.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 errorMessage 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;
+ }
+
+ /* Set 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();
+ }
+
+ /* Get 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() {
+ }
+
+ /* Detach the component from application.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public void detach() {
+ }
+
+ /* Get 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, this.getTag())) {
+ if (getStyle() != null && getStyle().length() > 0)
+ target.addAttribute("style", getStyle());
+ if (isReadOnly())
+ target.addAttribute("readonly", true);
+ if (!isVisible())
+ target.addAttribute("invisible", 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());
+
+ // Only paint content of visible components.
+ if (isVisible()) {
+ paintContent(target);
+
+ String desc = getDescription();
+ if (desc != null && description.length() > 0) {
+ target.startTag("description");
+ target.addUIDL(getDescription());
+ target.endTag("description");
+ }
+
+ ErrorMessage error = getErrorMessage();
+ if (error != null)
+ error.paint(target);
+ }
+ }
+ target.endTag(this.getTag());
+
+ repaintRequestListenersNotified = false;
+ }
+
+ /** 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 target UIDL stream where the component should paint
+ * itself to
+ * @throws PaintException if the 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 do not need repaints
+ if (!isVisible())
+ return;
+
+ fireRequestRepaintEvent(alreadyNotified);
+ }
+
+ /** Fire repaint request event */
+ private void fireRequestRepaintEvent(Collection alreadyNotified) {
+
+ // Notify listeners only once
+ if (!repaintRequestListenersNotified) {
+
+ // Notify the listeners
+ if (repaintRequestListeners != null
+ && !repaintRequestListeners.isEmpty()) {
+ Object[] listeners = repaintRequestListeners.toArray();
+ 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
+ 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) {
+
+ }
+
+ /* Adds a variable-change dependency to this component.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public void dependsOn(VariableOwner depended) {
+
+ // Assure that the list exists
+ if (dependencies == null)
+ dependencies = new HashSet();
+
+ // Add to the list of dependencies
+ if (depended != null)
+ dependencies.add(depended);
+ }
+
+ /* Removes a dependency from the component.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public void removeDirectDependency(VariableOwner depended) {
+
+ // Remove the listener if necessary
+ if (dependencies != null && depended != null)
+ dependencies.remove(depended);
+ }
+
+ /* Gets the set of depended components.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public Set getDirectDependencies() {
+ return dependencies;
+ }
+
+ /* 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 (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ e.printStackTrace();
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ /** <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 MillStone inheritable event mechanism
+ * see the
+ * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.</p>
+ *
+ * @param eventType 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
+ * @throws java.lang.IllegalArgumentException unless <code>method</code>
+ * has exactly one match in <code>object</code>
+ */
+ public void addListener(Class eventType, Object object, Method method) {
+ if (eventRouter == null)
+ eventRouter = new EventRouter();
+ eventRouter.addListener(eventType, object, method);
+ }
+
+ /** <p>Registers a new listener with the specified activation method to
+ * listen events generated by this component. If the activation method
+ * does not have any arguments the event object will not be passed to it
+ * when it's called.</p>
+ *
+ * <p>This 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 MillStone inheritable event mechanism
+ * see the
+ * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.</p>
+ *
+ * @param eventType 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
+ * @throws java.lang.IllegalArgumentException unless <code>method</code>
+ * has exactly one match in <code>object</code>
+ */
+ 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 MillStone inheritable event mechanism
+ * see the
+ * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.</p>
+ *
+ * @param eventType 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
+ */
+ 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 MillStone inheritable event mechanism
+ * see the
+ * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.</p>
+ *
+ * @param eventType 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 MillStone inheritable event mechanism
+ * see the
+ * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.</p>
+ *
+ * @param eventType 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 methodName 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);
+ }
+
+ /** Send event to all listeners
+ * @param event 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 a component event. It is transmitted to all registered
+ * listeners interested in such events.
+ */
+ protected void fireComponentEvent() {
+ fireEvent(new Component.Event(this));
+ }
+
+ /** Emits a component error event. It is transmitted to all registered
+ * listeners interested in such events.
+ */
+ protected void fireComponentErrorEvent() {
+ fireEvent(new Component.ErrorEvent(this.getComponentError(),this));
+ }
+
+ /** Sets application specific data object.
+ *
+ * @param data Application specific data.
+ * @since 3.1
+ */
+ public void setData(Object data) {
+ this.applicationData = data;
+ }
+
+ /** Gets application specific data.
+ *
+ * @return Application specific data set with setData function.
+ * @since 3.1
+ */
+ public Object getData() {
+ return this.applicationData;
+ }
+} \ No newline at end of file
diff --git a/src/com/itmill/toolkit/ui/AbstractComponentContainer.java b/src/com/itmill/toolkit/ui/AbstractComponentContainer.java
new file mode 100644
index 0000000000..e7b142f828
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/AbstractComponentContainer.java
@@ -0,0 +1,189 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.Iterator;
+
+/** 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 MillStone component container.
+ *
+ * @author IT Mill Ltd
+ * @version @VERSION@
+ * @since 3.0
+ */
+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
+ * reimplemented in extending classes for a more powerfu
+ * implementation.
+ */
+ public void removeAllComponents() {
+ LinkedList l = new LinkedList();
+
+ // Add all components
+ for (Iterator i = getComponentIterator(); i.hasNext();)
+ l.add(i.next());
+
+ // Remove all component
+ for (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) {
+ LinkedList components = new LinkedList();
+ for (Iterator i = source.getComponentIterator(); i.hasNext();)
+ components.add(i.next());
+
+ for (Iterator i = components.iterator(); i.hasNext();) {
+ Component c = (Component) i.next();
+ source.removeComponent(c);
+ addComponent(c);
+ }
+ }
+
+ /** Notifies all contained components that the container is attached to
+ * a window.
+ *
+ * @see com.itmill.toolkit.ui.Component#attach()
+ */
+ public void attach() {
+ super.attach();
+
+ for (Iterator i = getComponentIterator(); i.hasNext();)
+ ((Component)i.next()).attach();
+ }
+
+ /** Notifies all contained components that the container is detached
+ * from a window.
+ *
+ * @see com.itmill.toolkit.ui.Component#detach()
+ */
+ public void detach() {
+ super.detach();
+
+ for (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 (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ /* 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);
+ }
+
+ /** Fire 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));
+ }
+
+ /** Fire 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 ComponentAttachEvent(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.itmill.toolkit.ui.ComponentContainer#addComponent(Component)
+ */
+ public void addComponent(Component c) {
+ c.setParent(this);
+ if (getApplication() != null)
+ c.attach();
+ 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.itmill.toolkit.ui.ComponentContainer#removeComponent(Component)
+ */
+ public void removeComponent(Component c) {
+ if (getApplication() != null)
+ c.detach();
+ c.setParent(null);
+ fireComponentDetachEvent(c);
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/AbstractField.java b/src/com/itmill/toolkit/ui/AbstractField.java
new file mode 100644
index 0000000000..13a20ad23d
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/AbstractField.java
@@ -0,0 +1,922 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+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 com.itmill.toolkit.data.Buffered;
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.data.Validatable;
+import com.itmill.toolkit.data.Validator;
+import com.itmill.toolkit.terminal.ErrorMessage;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.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.itmill.toolkit.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.itmill.toolkit.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.itmill.toolkit.data.Validator validators}
+ * to make sure the value contained in the field is valid.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public abstract class AbstractField extends AbstractComponent implements Field,
+ Property.ReadOnlyStatusChangeNotifier {
+
+ /* Private members ************************************************* */
+
+ private boolean delayedFocus;
+
+ /** Value of the datafield */
+ 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;
+
+ /** Read 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 alloved 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;
+
+ /** Unique focusable id */
+ private long focusableId = -1;
+
+ /** Required field */
+ private boolean required = false;
+
+ /* Component basics ************************************************ */
+
+ public AbstractField() {
+ this.focusableId = Window.getNewFocusableId(this);
+ }
+
+ /*
+ * Paint the field. Don't add a JavaDoc comment here, we use the default
+ * documentation from the implemented interface.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Focus control id
+ if (this.focusableId > 0) {
+ target.addAttribute("focusid", this.focusableId);
+ }
+
+ // The tab ordering number
+ if (this.tabIndex > 0)
+ target.addAttribute("tabindex", this.tabIndex);
+
+ // If the field is modified, but not committed, set modified attribute
+ if (isModified())
+ target.addAttribute("modified", true);
+
+ // Add required attribute
+ if (isRequired())
+ target.addAttribute("required", 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 readonly
+ * mode.
+ */
+ public boolean isReadOnly() {
+ return super.isReadOnly()
+ || (dataSource != null && dataSource.isReadOnly());
+ }
+
+ /**
+ * Change the readonly state and throw read-only status change events.
+ *
+ * @see com.itmill.toolkit.ui.Component#setReadOnly(boolean)
+ */
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ fireReadOnlyStatusChange();
+ }
+
+ /* Buffering ******************************************************* */
+
+ public boolean isInvalidCommitted() {
+ return invalidCommitted;
+ }
+
+ public void setInvalidCommitted(boolean isCommitted) {
+ this.invalidCommitted = isCommitted;
+ }
+
+ /*
+ * Save 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 {
+ if (dataSource != null && (isInvalidCommitted() || isValid())
+ && !dataSource.isReadOnly()) {
+ Object newValue = getValue();
+ try {
+
+ // Commit the value to datasource
+ dataSource.setValue(newValue);
+
+ } catch (Throwable e) {
+
+ // Set the buffering state
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throw the source exception
+ throw currentBufferedSourceException;
+ }
+ }
+
+ 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();
+ }
+
+ /*
+ * Update 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) {
+
+ // Get the correct value from datasource
+ Object newValue;
+ try {
+
+ // Discard buffer by overwriting from datasource
+ newValue = dataSource.getValue();
+
+ // If successful, remove set the buffering state to be ok
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ requestRepaint();
+ }
+ } catch (Throwable e) {
+
+ // Set the buffering state
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throw the source exception
+ throw currentBufferedSourceException;
+ }
+
+ 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();
+ }
+
+ // 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;
+ }
+
+ /*
+ * Test 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;
+ }
+
+ /*
+ * Set 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 {
+ if (writeTroughMode == writeTrough)
+ return;
+ writeTroughMode = writeTrough;
+ if (writeTroughMode)
+ commit();
+ }
+
+ /*
+ * Test 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;
+ }
+
+ /*
+ * Set 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(dataSource.getValue());
+ fireValueChange();
+ }
+ }
+
+ /* Property interface implementation ******************************* */
+
+ /**
+ * Returns the value of the Property in human readable textual format.
+ *
+ * @return <code>String</code> representation of the value stored in the
+ * Property
+ */
+ public String toString() {
+ Object value = getValue();
+ if (value == null)
+ return null;
+ return getValue().toString();
+ }
+
+ /**
+ * Gets the current value of the field. 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.
+ *
+ * @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 = dataSource.getValue();
+ if ((newValue == null && value != null)
+ || (newValue != null && !newValue.equals(value))) {
+ setInternalValue(newValue);
+ fireValueChange();
+ }
+
+ return newValue;
+ }
+
+ /**
+ * Set the value of the field.
+ *
+ * @param newValue
+ * New value of the field.
+ */
+ public void setValue(Object newValue) 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();
+
+ // If invalid values are not allowed, the value must be checked
+ if (!isInvalidAllowed()) {
+ Collection v = getValidators();
+ if (v != null)
+ for (Iterator i = v.iterator(); i.hasNext();)
+ ((Validator) i.next()).validate(newValue);
+ }
+
+ // Change the value
+ setInternalValue(newValue);
+ modified = dataSource != null;
+
+ // In write trough mode , try to commit
+ if (isWriteThrough() && dataSource != null
+ && (isInvalidCommitted() || isValid())) {
+ try {
+
+ // Commit the value to datasource
+ dataSource.setValue(newValue);
+
+ // The buffer is now unmodified
+ modified = false;
+
+ } catch (Throwable e) {
+
+ // Set the buffering state
+ currentBufferedSourceException = new Buffered.SourceException(
+ this, e);
+ requestRepaint();
+
+ // Throw the source exception
+ throw currentBufferedSourceException;
+ }
+ }
+
+ // If successful, remove set the buffering state to be ok
+ if (currentBufferedSourceException != null) {
+ currentBufferedSourceException = null;
+ requestRepaint();
+ }
+
+ // Fire value change
+ fireValueChange();
+ }
+ }
+
+ /* 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) {
+
+ // Save the old value
+ Object oldValue = value;
+
+ // Discard all changes to old datasource
+ try {
+ discard();
+ } catch (Buffered.SourceException ignored) {
+ }
+
+ // Stop listening the old data source changes
+ if (dataSource != null
+ && Property.ValueChangeNotifier.class
+ .isAssignableFrom(dataSource.getClass()))
+ ((Property.ValueChangeNotifier) dataSource).removeListener(this);
+
+ // Set the new data source
+ dataSource = newDataSource;
+
+ // Get the value from source
+ try {
+ if (dataSource != null)
+ setInternalValue(dataSource.getValue());
+ modified = false;
+ } catch (Throwable e) {
+ currentBufferedSourceException = new Buffered.SourceException(this,
+ e);
+ modified = true;
+ }
+
+ // Listen 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) {
+ Collection validators = ((Validatable) dataSource).getValidators();
+ if (validators != null)
+ for (Iterator i = validators.iterator(); i.hasNext();)
+ addValidator((Validator) i.next());
+ }
+
+ // Fire value change if the value has changed
+ if ((value != oldValue)
+ && ((value != null && !value.equals(oldValue)) || value == null))
+ fireValueChange();
+ }
+
+ /* 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);
+ }
+
+ /**
+ * Gets the validators of the field.
+ *
+ * @return Unmodifiable collection that holds all validators for the field.
+ */
+ public Collection getValidators() {
+ if (validators == null || validators.isEmpty())
+ return null;
+ return Collections.unmodifiableCollection(validators);
+ }
+
+ /**
+ * Removes a validator from the field.
+ *
+ * @param validator
+ * the validator to remove
+ */
+ public void removeValidator(Validator validator) {
+ if (validators != null)
+ validators.remove(validator);
+ }
+
+ /**
+ * 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 (validators == null)
+ return true;
+
+ Object value = getValue();
+ for (Iterator i = validators.iterator(); i.hasNext();)
+ if (!((Validator) i.next()).isValid(value))
+ return false;
+
+ return true;
+ }
+
+ public void validate() throws Validator.InvalidValueException {
+
+ // If there is no validator, there can not be any errors
+ if (validators == null)
+ return;
+
+ // Initialize temps
+ Validator.InvalidValueException firstError = null;
+ LinkedList errors = null;
+ Object value = getValue();
+
+ // Get all the validation errors
+ for (Iterator i = validators.iterator(); i.hasNext();)
+ try {
+ ((Validator) i.next()).validate(value);
+ } catch (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;
+
+ // Create composite validator
+ Validator.InvalidValueException[] exceptions = new Validator.InvalidValueException[errors
+ .size()];
+ int index = 0;
+ for (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.
+ *
+ * @see com.itmill.toolkit.data.Validatable#isInvalidAllowed()
+ *
+ * @return true iff the invalid values are allowed.
+ */
+ 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.
+ * 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.
+ *
+ * @see com.itmill.toolkit.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.itmill.toolkit.ui.AbstractComponent#getErrorMessage()
+ */
+ public ErrorMessage getErrorMessage() {
+ ErrorMessage superError = super.getErrorMessage();
+ return superError;
+ /*
+ * TODO: Check the logic of this ErrorMessage validationError = null;
+ * try { validate(); } catch (Validator.InvalidValueException e) {
+ * validationError = e; }
+ *
+ * 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 (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ /*
+ * Add 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);
+ }
+
+ /*
+ * Remove 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);
+ }
+
+ /**
+ * Emit a value change event. The value contained in the field is validated
+ * before the event is created.
+ */
+ protected void fireValueChange() {
+ fireEvent(new AbstractField.ValueChangeEvent(this));
+ 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 (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ /**
+ * 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3258688823264161846L;
+
+ /**
+ * New instance of text change event
+ *
+ * @param source
+ * Source of the event.
+ */
+ public ReadOnlyStatusChangeEvent(AbstractField source) {
+ super(source);
+ }
+
+ /**
+ * Property where the event occurred
+ *
+ * @return Source of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /*
+ * Add 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);
+ }
+
+ /**
+ * Emit a 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();
+ }
+
+ /** Ask the terminal to place the cursor to this field. */
+ public void focus() {
+ Window w = getWindow();
+ if (w != null) {
+ w.setFocusedComponent(this);
+ } else {
+ this.delayedFocus = true;
+ }
+ }
+
+ /**
+ * Create abstract field by the type of the property.
+ *
+ * <p>
+ * This returns most suitable field type for editing property of given type
+ * </p>
+ *
+ * @param propertyType
+ * 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)) {
+ Button button = new Button("");
+ button.setSwitchMode(true);
+ button.setImmediate(false);
+ return button;
+ }
+
+ // Text field is used by default
+ return new TextField();
+ }
+
+ /**
+ * Get the tab index of this field. The tab index property is used to
+ * specify the natural tab ordering of fields.
+ *
+ * @return Tab index of this field. Negative value means unspecified.
+ */
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ /**
+ * Get the tab index of this field. The tab index property is used to
+ * specify the natural tab ordering of fields.
+ *
+ * @param tabIndex
+ * The tab order of this component. Negative value means
+ * unspecified.
+ */
+ public void setTabIndex(int tabIndex) {
+ this.tabIndex = tabIndex;
+ }
+
+ /**
+ * Set the internal field value. This is purely used by AbstractField to
+ * change the internal Field value. It does not trigger any 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) {
+ this.value = newValue;
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component.Focusable#getFocusableId()
+ */
+ public long getFocusableId() {
+ return this.focusableId;
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component#attach()
+ */
+ public void attach() {
+ super.attach();
+ if (this.delayedFocus) {
+ this.delayedFocus = false;
+ this.focus();
+ }
+ }
+
+ /**
+ * Is this field required.
+ *
+ * Required fields must filled by the user.
+ *
+ * @return true if the
+ */
+ public boolean isRequired() {
+ return required;
+ }
+
+ /**
+ * Set the field required. Required fields must filled by the user.
+ *
+ * @param required
+ * Is the field required
+ */
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ /**
+ * Free used resources.
+ */
+ public void finalize() throws Throwable {
+ if (focusableId > -1) {
+ Window.removeFocusableId(focusableId);
+ }
+ super.finalize();
+ }
+} \ No newline at end of file
diff --git a/src/com/itmill/toolkit/ui/BaseFieldFactory.java b/src/com/itmill/toolkit/ui/BaseFieldFactory.java
new file mode 100644
index 0000000000..9ea6d3efc8
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/BaseFieldFactory.java
@@ -0,0 +1,130 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Date;
+
+import com.itmill.toolkit.data.Container;
+import com.itmill.toolkit.data.Item;
+import com.itmill.toolkit.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)
+ * <b>Item</b>: Form<br/>
+ * <b>default field type</b>: TextField
+ * <p>
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.1
+ */
+
+public class BaseFieldFactory implements FieldFactory {
+
+ /** Creates 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.itmill.toolkit.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)) {
+ DateField df = new DateField();
+ df.setResolution(DateField.RESOLUTION_DAY);
+ return df;
+ }
+
+ // Boolean field
+ if (Boolean.class.isAssignableFrom(type)) {
+ Button button = new Button();
+ button.setSwitchMode(true);
+ button.setImmediate(false);
+ return button;
+ }
+
+ // Nested form is used by default
+ return new TextField();
+ }
+
+ /** Create field based on the datasource property.
+ *
+ * @see com.itmill.toolkit.ui.FieldFactory#createField(Property, Component)
+ */
+ public Field createField(Property property, Component uiContext) {
+ if (property != null)
+ return createField(property.getType(),uiContext);
+ else
+ return null;
+ }
+
+ /** Creates field based on the item and property id.
+ *
+ * @see com.itmill.toolkit.ui.FieldFactory#createField(Item, Object, Component)
+ */
+ public Field createField(Item item, Object propertyId, Component uiContext) {
+ if (item != null && propertyId != null){
+ Field f= createField(item.getItemProperty(propertyId),uiContext);
+ if (f instanceof AbstractComponent)
+ ((AbstractComponent)f).setCaption(propertyId.toString());
+ return f;
+ }
+ else
+ return null;
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.FieldFactory#createField(com.itmill.toolkit.data.Container, java.lang.Object, java.lang.Object, com.itmill.toolkit.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/itmill/toolkit/ui/Button.java b/src/com/itmill/toolkit/ui/Button.java
new file mode 100644
index 0000000000..1b1d22d3d9
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Button.java
@@ -0,0 +1,286 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Map;
+import java.lang.reflect.Method;
+
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/** A generic button component.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class Button extends AbstractField {
+
+ /* Final members *************************************************** */
+
+ /** strings to be catched at adatapter (transformer) */
+ private static final String BUTTON_VAR_NAME = "clicked";
+
+ /* Private members ************************************************* */
+
+ boolean switchMode = false;
+
+ /** Creates a new push button.
+ *
+ * The value of the push button is allways false and they are
+ * immediate by default.
+ *
+ */
+ public Button() {
+ setSwitchMode(false);
+ }
+
+ /** Creates a new push button.
+ *
+ * The value of the push button is allways false and they are
+ * immediate by default.
+ *
+ * @param caption Button caption
+ */
+ public Button(String caption) {
+ setCaption(caption);
+ setSwitchMode(false);
+ }
+
+ /** Creates a new push button with click listener.
+ * @param caption Button caption
+ * @param listener Button click listener
+ */
+ public Button(String caption, ClickListener listener) {
+ this(caption);
+ addListener(listener);
+ }
+
+ /** Creates a new push button with a method listening button clicks.
+ * The method must have either no parameters, or only one parameter of
+ * Button.ClickEvent type.
+ * @param caption Button caption
+ * @param target 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 new switch button with initial value.
+ * @param state Initial state of the switch-button.
+ */
+ public Button(String caption, boolean initialState) {
+ setCaption(caption);
+ setValue(new Boolean(initialState));
+ setSwitchMode(true);
+ }
+
+ /** Creates new switch button that is connected to a boolean property.
+ * @param state Initial state of the switch-button.
+ */
+ public Button(String caption, Property dataSource) {
+ setCaption(caption);
+ setSwitchMode(true);
+ setPropertyDataSource(dataSource);
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "button";
+ }
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ * @throws IOException Passed from the UIDLStream.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ if (isSwitchMode())
+ target.addAttribute("type", "switch");
+ boolean state;
+ try {
+ state = ((Boolean) getValue()).booleanValue();
+ } catch (NullPointerException e) {
+ state = false;
+ }
+ target.addVariable(this, "state", state);
+ }
+
+ /** Invoked when the value of a variable has changed. Button
+ * listeners are notified if the button is clicked.
+ * @param event Variable change event.
+ */
+ public void changeVariables(Object source, Map variables) {
+ if (variables.containsKey("state")) {
+ // Get the new and old button states
+ Boolean newValue = (Boolean) variables.get("state");
+ 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));
+ }
+ }
+ }
+
+ /**
+ * Returns the switchMode.
+ * @return boolean
+ */
+ 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);
+ setValue(new Boolean(false));
+ }
+ }
+
+ /** Set immediate mode.
+ * @see com.itmill.toolkit.ui.AbstractComponent#setImmediate(boolean)
+ *
+ * Push buttons can not be set in non-immediate mode.
+ */
+ public void setImmediate(boolean immediate) {
+ // Push buttons are allways immediate
+ super.setImmediate(!isSwitchMode() || immediate);
+ }
+
+ /** The type of the button as a property.
+ * @see com.itmill.toolkit.data.Property#getType()
+ */
+ public Class getType() {
+ return Boolean.class;
+ }
+
+ /* Click event ************************************************ */
+
+ private static final Method BUTTON_CLICK_METHOD;
+ static {
+ try {
+ BUTTON_CLICK_METHOD =
+ ClickListener.class.getDeclaredMethod(
+ "buttonClick",
+ new Class[] { ClickEvent.class });
+ } catch (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ /** 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3546647602931118393L;
+
+ /** New instance of text change event
+ * @param source Source of the event.
+ */
+ public ClickEvent(Component source) {
+ super(source);
+ }
+
+ /** Button where the event occurred
+ * @return 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 {
+
+ /** Button has been pressed.
+ * @param event Button click event.
+ */
+ public void buttonClick(ClickEvent event);
+ }
+
+ /** Add button click listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(ClickListener listener) {
+ addListener(ClickEvent.class, listener, BUTTON_CLICK_METHOD);
+ }
+
+ /** Remove button click listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(ClickListener listener) {
+ removeListener(ClickEvent.class, listener, BUTTON_CLICK_METHOD);
+ }
+
+ /** Emit options change event. */
+ protected void fireClick() {
+ fireEvent(new Button.ClickEvent(this));
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/Component.java b/src/com/itmill/toolkit/ui/Component.java
new file mode 100644
index 0000000000..d1e6bb8039
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Component.java
@@ -0,0 +1,324 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.terminal.ErrorMessage;
+import com.itmill.toolkit.terminal.Paintable;
+import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.terminal.VariableOwner;
+
+import java.util.Collection;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.Locale;
+
+/** The top-level component interface which must be implemented by all
+ * MillStone UI components. It contains the methods the MillStone framework
+ * needs to handle the components in a user interface.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public interface Component extends Paintable, VariableOwner {
+
+ /** Gets the look-and-feel style of the component.
+ *
+ * @return component's styleValue of property style.
+ */
+ public String getStyle();
+
+ /** Sets the look-and-feel style of the component. This method will
+ * trigger a {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}.
+ * .
+ * @param style new style of the component
+ */
+ public void setStyle(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>Components should be enabled by default.</p>
+ *
+ * @return <code>true</code> if the component is enabled,
+ * <code>false</code> if not
+ * @see VariableOwner#isEnabled()
+ */
+ public boolean isEnabled();
+
+ /** Enable or disable the component. Being enabled means that the
+ * component can be edited. This method will trigger a
+ * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}.
+ *
+ * @param enabled boolean value specifying if the component should be
+ * enabled after the call or not
+ */
+ public void setEnabled(boolean enabled);
+
+ /** Tests if the component is visible or not. Visibility defines if the
+ * component is shown in the UI or not. Default is <code>true</code>.
+ *
+ * @return <code>true</code> if the component is visible in the UI,
+ * <code>false</code> if not
+ */
+ public boolean isVisible();
+
+ /** Sets the components visibility status. Visibility defines if the
+ * component is shown in the UI or not.
+ *
+ * @param visible 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.itmill.toolkit.terminal.Paintable.RepaintRequestEvent RepaintRequestEvent}.
+ *
+ * @param readOnly 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 component's caption <code>String</code>
+ */
+ public String getCaption();
+
+ /** 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();
+
+ /** Gets the component's parent window. If the component does not yet
+ * belong to a window <code>null</code> is returned.
+ *
+ * @return 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 parent application of the component or <code>null</code>
+ */
+ public Application getApplication();
+
+ /** 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> functions 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 Locale.getDefautl(). 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 A 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 4048791277653274933L;
+
+ /** Constructs a new event with a specified source component.
+ *
+ * @param source 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 {
+
+ /** 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. */
+ public class ErrorEvent extends Event {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 4051323457293857333L;
+
+ private ErrorMessage message;
+ private Throwable throwable;
+
+ /** Constructs a new event with a specified source component.
+ *
+ * @param source source component of the event
+ */
+ public ErrorEvent(
+ ErrorMessage message,
+ Component component) {
+ super(component);
+ this.message = message;
+ }
+
+ /** Return the error message */
+ public ErrorMessage getErrorMessage() {
+ return this.message;
+ }
+ }
+
+ /** Listener interface for receiving <code>Component.Errors</code>s */
+ public interface ErrorListener extends EventListener {
+
+ /** 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 {
+
+ /** Set focus to this component.*/
+ public void focus();
+
+ /** Get Tabulator index of this Focusable component.
+ * @return Positive tab order of this focusable. Negative of zero means
+ * unspecified tab order.
+ */
+ public int getTabIndex();
+
+ /** Set Tabulator index of this Focusable component.
+ * @param tabIndex Positive tab order of this focusable. Negative of
+ * zero means unspecified tab order.
+ */
+ public void setTabIndex(int tabIndex);
+
+ /** Get unique ID of focusable.
+ * This will be used to move input focus directly to this
+ * component.
+ * @return Unique id of focusable.
+ */
+ public long getFocusableId();
+
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/ComponentContainer.java b/src/com/itmill/toolkit/ui/ComponentContainer.java
new file mode 100644
index 0000000000..0ea1b9b9c4
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/ComponentContainer.java
@@ -0,0 +1,173 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+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 a component into this container.
+ *
+ * @param c the component to be added
+ */
+ public void addComponent(Component c);
+
+ /** Removes a 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();
+
+ /** Replace a 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 component iterator
+ */
+ public Iterator getComponentIterator();
+
+ /** 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);
+
+ /** Listen component attach events */
+ public void addListener(ComponentAttachListener listener);
+
+ /** Stop listening component attach events */
+ public void removeListener(ComponentAttachListener listener);
+
+ /** Listen component detach events */
+ public void addListener(ComponentDetachListener listener);
+
+ /** Stop listening component detach events */
+ public void removeListener(ComponentDetachListener listener);
+
+ /** Component attach listener interface. */
+ public interface ComponentAttachListener {
+
+ /** A new component is attached to container */
+ public void componentAttachedToContainer(ComponentAttachEvent event);
+ }
+
+ /** Component detach listener interface. */
+ public interface ComponentDetachListener {
+
+ /** A component has been detached from container */
+ public void componentDetachedFromContainer(ComponentDetachEvent event);
+ }
+
+ /** Component attach event sent when a component is attached to container */
+ public class ComponentAttachEvent extends Component.Event {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3257285812184692019L;
+
+ private Component component;
+
+ /** Create 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);
+ this.component = attachedComponent;
+ }
+
+ /** Get the component container */
+ public ComponentContainer getContainer() {
+ return (ComponentContainer) getSource();
+ }
+
+ /** Get the attached component */
+ public Component getAttachedComponent() {
+ return component;
+ }
+ }
+
+ /** Component detach event sent when a component is detached from container */
+ public class ComponentDetachEvent extends Component.Event {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3618140052337930290L;
+
+ private Component component;
+
+ /** Create 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);
+ this.component = detachedComponent;
+ }
+
+ /** Get the component container */
+ public ComponentContainer getContainer() {
+ return (ComponentContainer) getSource();
+ }
+
+ /** Get the detached component */
+ public Component getDetachedComponent() {
+ return component;
+ }
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/CustomComponent.java b/src/com/itmill/toolkit/ui/CustomComponent.java
new file mode 100644
index 0000000000..1df4abc134
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/CustomComponent.java
@@ -0,0 +1,387 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.terminal.VariableOwner;
+
+/** 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
+ */
+public class CustomComponent implements Component {
+
+ /** The root component implementing the custom component */
+ private Component root = null;
+
+ /** The visibility of the component */
+ private boolean visible = true;
+
+ /** The parent of the component */
+ private Component parent = null;
+
+ /** Dependencies of the component, or null */
+ private HashSet dependencies = null;
+
+ /** Type of the component */
+ private String componentType = null;
+
+ /** 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;
+
+ /** Construct 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() {
+ }
+
+ /** Construct 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) {
+ setCompositionRoot(compositionRoot);
+ }
+
+ /** Returns the composition root.
+ * @return 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 && root != null)
+ root.setParent(null);
+ this.root = compositionRoot;
+ if (root != null)
+ root.setParent(this);
+ }
+
+ /* Basic component features ------------------------------------------ */
+
+ public void attach() {
+ if (root != null)
+ root.attach();
+ }
+
+ public void detach() {
+ if (root != null)
+ root.detach();
+ }
+
+ public Application getApplication() {
+ if (parent == null)
+ return null;
+ return parent.getApplication();
+ }
+
+ /** The caption of the custom component is by default the the
+ * caption of the root component, or null if the root is not set
+ */
+ public String getCaption() {
+ if (root == null)
+ return null;
+ return root.getCaption();
+ }
+
+ /** The icon of the custom component is by default the the
+ * icon of the root component, or null if the root is not set
+ */
+ public Resource getIcon() {
+ if (root == null)
+ return null;
+ return root.getIcon();
+ }
+
+ /** The icon of the custom component is by default the the
+ * locale of the parent or null if the parent is not set.
+ */
+ public Locale getLocale() {
+ if (parent == null)
+ return null;
+ return parent.getLocale();
+ }
+
+ public Component getParent() {
+ return parent;
+ }
+
+ /** Custom component does not implement custom styles by default and
+ * this function returns null.
+ */
+ public String getStyle() {
+ return null;
+ }
+
+ public Window getWindow() {
+ if (parent == null)
+ return null;
+ return parent.getWindow();
+ }
+
+ /** Custom component is allways enabled by default */
+ public boolean isEnabled() {
+ return true;
+ }
+
+ /** Custom component is by default in the non-immediate mode. The immediateness
+ * of the custom component is defined by the components it is composed of.
+ */
+ public boolean isImmediate() {
+ return false;
+ }
+
+ /** The custom components are not readonly by default. */
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ /* 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) {
+
+ // Notify listeners only once
+ if (!repaintRequestListenersNotified) {
+
+ // Notify the listeners
+ if (repaintRequestListeners != null
+ && !repaintRequestListeners.isEmpty()) {
+ Object[] listeners = repaintRequestListeners.toArray();
+ 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
+ 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;
+ }
+ }
+
+ /** The custom component is allways enabled by default. */
+ public void setEnabled(boolean enabled) {
+ }
+
+ public void setParent(Component parent) {
+
+ // If the parent is not changed, dont do nothing
+ if (parent == this.parent)
+ return;
+
+ // Send detach event if the component have been connected to a window
+ if (getApplication() != null) {
+ detach();
+ this.parent = null;
+ }
+
+ // Connect to new parent
+ this.parent = parent;
+
+ // Send attach event if connected to a window
+ if (getApplication() != null)
+ attach();
+ }
+
+ /** Changing the read-only mode of the component is not supported by default.
+ */
+ public void setReadOnly(boolean readOnly) {
+ }
+
+ /** Changing the style of the component is not supported by default.
+ */
+ public void setStyle(String style) {
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ }
+
+ /* Documented in super interface */
+ public void requestRepaintRequests() {
+ repaintRequestListenersNotified = false;
+ }
+
+ /* Documented in super interface */
+ public void paint(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 (isVisible()) {
+ String type = getComponentType();
+ if (type != null) {
+ if (!target.startTag(this, "component")) {
+ target.addAttribute("type", type);
+ root.paint(target);
+ }
+ target.endTag("component");
+ } else
+ root.paint(target);
+ }
+ repaintRequestListenersNotified = false;
+ }
+
+ /** The custom component does not have any variables by default */
+ public void changeVariables(Object source, Map variables) {
+ }
+
+ public void dependsOn(VariableOwner depended) {
+ if (depended == null)
+ return;
+ if (dependencies == null)
+ dependencies = new HashSet();
+ dependencies.add(depended);
+ }
+
+ public Set getDirectDependencies() {
+ return dependencies;
+ }
+
+ public void removeDirectDependency(VariableOwner depended) {
+ if (dependencies == null)
+ return;
+ dependencies.remove(depended);
+ if (dependencies.isEmpty())
+ dependencies = null;
+ }
+
+ /* Event functions are not implemented by default -------------------- */
+
+ /** Custom component does not implement any component events by default */
+ public void addListener(Component.Listener listener) {
+ }
+
+ /** Custom component does not implement any component events by default */
+ public void removeListener(Component.Listener listener) {
+ }
+
+ /** Gets the component type.
+ *
+ * The component type is textual type of the component. This is included
+ * in the UIDL as component tag attribute. If the component type is null
+ * (default), the component tag is not included in the UIDL at all.
+ *
+ * Returns the componentType.
+ * @return String
+ */
+ 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. If the component type is null
+ * (default), the component tag is not included in the UIDL at all.
+ *
+ * @param componentType The componentType to set
+ */
+ public void setComponentType(String componentType) {
+ this.componentType = componentType;
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/CustomLayout.java b/src/com/itmill/toolkit/ui/CustomLayout.java
new file mode 100644
index 0000000000..c3ced267d4
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/CustomLayout.java
@@ -0,0 +1,183 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+import java.util.Iterator;
+import java.util.HashMap;
+
+/** <p>A container component with freely designed layout and style. The
+ * container consists of items with textually represented locations. Each
+ * item contains one sub-component. The adapter and theme are resposible for
+ * rendering the layout with given style by placing the items on the screen
+ * in defined locations.</p>
+ *
+ * <p>The definition of locations is not fixed - the each style can define
+ * its locations in a way that is suitable for it. One typical example would
+ * be to create visual design for a website as a custom layout: the visual
+ * design could define locations for "menu", "body" and "title" for example.
+ * The layout would then be implemented as XLS-template with for given
+ * style.</p>
+ *
+ * <p>The default theme handles the styles that are not defined by just
+ * drawing the subcomponents with flowlayout.</p>
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class CustomLayout extends AbstractComponentContainer implements Layout {
+
+ /** Custom layout slots containing the components */
+ private HashMap slots = new HashMap();
+
+ /** Constructor for custom layout with given style */
+ public CustomLayout(String style) {
+ setStyle(style);
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "customlayout";
+ }
+
+ /** Add a component into this container to given location.
+ * @param c The component to be added.
+ * @param location The location of the component
+ */
+ public void addComponent(Component c, String location) {
+ Component old = (Component)slots.get(location);
+ if (old != null) {
+ removeComponent(old);
+ }
+ slots.put(location, c);
+ c.setParent(this);
+ fireComponentAttachEvent(c);
+ requestRepaint();
+ }
+
+ /** Add a 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.
+ */
+ public void addComponent(Component c) {
+ this.addComponent(c, "");
+ }
+
+ /** Remove a component from this container.
+ * @param c The component to be removed.
+ */
+ public void removeComponent(Component c) {
+ if (c == null) return;
+ slots.values().remove(c);
+ c.setParent(null);
+ fireComponentDetachEvent(c);
+ requestRepaint();
+ }
+
+ /** Remove a component from this container from given location.
+ * @param location Location identifier of the component
+ */
+ public void removeComponent(String location) {
+ this.removeComponent((Component) slots.get(location));
+ }
+
+ /** Get component container iterator for going trough all the components in
+ * the container.
+ * @return Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return slots.values().iterator();
+ }
+
+ /** Get child-component by its location.
+ *
+ * @param location The name of the location where the requested
+ * component resides
+ * @return Component in the given location or null if not found.
+ */
+ public Component getComponent(String location) {
+ return (Component) slots.get(location);
+ }
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Add all items in all the locations
+ for (Iterator i = slots.keySet().iterator(); i.hasNext();) {
+
+ // Get the (location,component)
+ String location = (String) i.next();
+ Component c = (Component) slots.get(location);
+
+ // Write 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) {
+
+ // Get the locations
+ String oldLocation = null;
+ String newLocation = null;
+ for (Iterator i=slots.keySet().iterator(); i.hasNext();) {
+ String location = (String) i.next();
+ 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();
+ }
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/DateField.java b/src/com/itmill/toolkit/ui/DateField.java
new file mode 100644
index 0000000000..facb8d3f30
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/DateField.java
@@ -0,0 +1,407 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.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.itmill.toolkit.data.Property;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/**
+ * <p>
+ * A date editor component that can be bound to any bindable Property. that is
+ * compatible with java.util.Date.
+ *
+ * <p>
+ * Since <code>DateField</code> extends <code>AbstractField</code> it
+ * implements the {@link com.itmill.toolkit.data.Buffered}interface. A
+ * <code>DateField</code> is in write-through mode by default, so
+ * {@link com.itmill.toolkit.ui.AbstractField#setWriteThrough(boolean)}must be
+ * called to enable buffering.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+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;
+
+ /** 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;
+
+ /* 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
+ * 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.itmill.toolkit.data.Property.Viewer#setPropertyDataSource(Property)}
+ * is called to bind it.
+ *
+ * @param caption
+ * caption <code>String</code> for the editor
+ * @param text
+ * initial text content of the editor
+ */
+ public DateField(String caption, Date value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+ /* Component basic features ********************************************* */
+
+ /*
+ * Paint this component. Don't add a JavaDoc comment here, we use the
+ * default documentation from implemented interface.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ // Add locale as attribute
+ Locale l = getLocale();
+ if (l != null) {
+ target.addAttribute("locale",l.toString());
+ }
+
+ // Get the calendar
+ Calendar calendar = getCalendar();
+ 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.
+ */
+ 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.
+ */
+ public void changeVariables(Object source, Map variables) {
+
+ if (!isReadOnly()
+ && (variables.containsKey("year")
+ || variables.containsKey("month")
+ || variables.containsKey("day")
+ || variables.containsKey("hour")
+ || variables.containsKey("min")
+ || variables.containsKey("sec") || variables
+ .containsKey("msec"))) {
+
+ // Old and new dates
+ Date oldDate = (Date) getValue();
+ Date newDate = null;
+
+ // Get 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
+ Calendar cal = (Calendar) 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;
+
+ // Set 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);
+
+ // Assign the date
+ newDate = cal.getTime();
+ }
+
+ if (newDate != oldDate
+ && (newDate == null || !newDate.equals(oldDate)))
+ setValue(newDate);
+ }
+ }
+
+ /* Property features **************************************************** */
+
+ /*
+ * Gets the edited property's type. Don't add a JavaDoc comment here, we use
+ * the default documentation from implemented interface.
+ */
+ public Class getType() {
+ return Date.class;
+ }
+
+ /*
+ * Return 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.
+ */
+ public String toString() {
+ Date value = (Date) getValue();
+ if (value != null)
+ return value.toString();
+ return null;
+ }
+
+ /*
+ * Set the value of the property. Don't add a JavaDoc comment here, we use
+ * the default documentation from implemented interface.
+ */
+ public void setValue(Object newValue) throws Property.ReadOnlyException,
+ Property.ConversionException {
+
+ // Allow setting dates directly
+ if (newValue == null || newValue instanceof Date)
+ super.setValue(newValue);
+ else {
+
+ // Try to parse as string
+ try {
+ SimpleDateFormat parser = new SimpleDateFormat();
+ Date val = parser.parse(newValue.toString());
+ super.setValue(val);
+ } catch (ParseException e) {
+ throw new Property.ConversionException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Set DateField datasource. Datasource type must assignable to Date.
+ *
+ * @see com.itmill.toolkit.data.Property.Viewer#setPropertyDataSource(Property)
+ */
+ 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");
+ }
+
+ /**
+ * Returns 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 Calendar.getInstance() is used.
+ *
+ * @see #setCalendar(Calendar)
+ * @return Calendar
+ */
+ private Calendar getCalendar() {
+
+ // Make sure we have an calendar instance
+ if (this.calendar == null) {
+ this.calendar = Calendar.getInstance();
+ }
+
+ // Clone the instance
+ Calendar newCal = (Calendar) this.calendar.clone();
+
+ // Assign the current time tom calendar.
+ Date currentDate = (Date) getValue();
+ if (currentDate != null)
+ newCal.setTime(currentDate);
+
+ return newCal;
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/Embedded.java b/src/com/itmill/toolkit/ui/Embedded.java
new file mode 100644
index 0000000000..9422077c14
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Embedded.java
@@ -0,0 +1,433 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.terminal.Sizeable;
+
+/** Component for embedding external objects.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class Embedded extends AbstractComponent implements Sizeable {
+
+ /** General object type */
+ public static final int TYPE_OBJECT = 0;
+
+ /** Image types */
+ public static final int TYPE_IMAGE = 1;
+
+ /** Type of the object */
+ private int type = TYPE_OBJECT;
+
+ /** Source of the embedded object */
+ private Resource source = null;
+
+ /** Dimensions of the object. */
+ private int width = -1;
+ private int height = -1;
+ private int widthUnits = Sizeable.UNITS_PIXELS;
+ private int heightUnits = Sizeable.UNITS_PIXELS;
+
+ /** Generic object attributes */
+ private String mimeType = null;
+ private String standby = null;
+
+ /** Hash of object parameteres. */
+ private 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.
+ */
+ 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.
+ */
+ public Embedded(String caption, Resource source) {
+ setCaption(caption);
+ setSource(source);
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "embedded";
+ }
+
+ /** Invoked when the component state should be painted */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (type == TYPE_IMAGE) {
+ target.addAttribute("type", "image");
+ }
+
+ if (source != null)
+ target.addAttribute("src", source);
+
+ // Dimensions
+ if (width > 0)
+ target.addAttribute(
+ "width",
+ "" + width + Sizeable.UNIT_SYMBOLS[this.widthUnits]);
+ if (height > 0)
+ target.addAttribute(
+ "height",
+ "" + height + Sizeable.UNIT_SYMBOLS[this.heightUnits]);
+ 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 (Iterator i = this.getParameterNames(); i.hasNext();) {
+ target.startTag("embeddedparam");
+ String key = (String) i.next();
+ target.addAttribute("name", key);
+ target.addAttribute("value", (String) getParameter(key));
+ target.endTag("embeddedparam");
+ }
+ }
+
+ /** Set 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();
+ }
+
+ /** Get 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 Value of parameter or null if not found.
+ */
+ public String getParameter(String name) {
+ return (String) parameters.get(name);
+ }
+
+ /** Remove an object parameter from the list.
+ * @param name - The name of the parameter to remove.
+ */
+ public void removeParameter(String name) {
+ parameters.remove(name);
+ requestRepaint();
+ }
+
+ /** Get embedded object parameter names.
+ * @return Iterator of parameters names.
+ */
+ public Iterator getParameterNames() {
+ return parameters.keySet().iterator();
+ }
+
+ /**
+ * Returns the codebase, the root-path used to access resources with relative paths.
+ * @return String
+ */
+ public String getCodebase() {
+ return codebase;
+ }
+
+ /**
+ * Returns the MIME-Type of the code.
+ * @return String
+ */
+ public String getCodetype() {
+ return codetype;
+ }
+
+ /**
+ * Returns the MIME-Type of the object
+ * @return String
+ */
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ /**
+ * Returns the standby text displayed when
+ * the object is loading.
+ * @return String
+ */
+ 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();
+ }
+ }
+
+ /**
+ * Returns the visual height of the object.
+ * Default height is -1, which is interpreted as "unspecified".
+ * @return The height in units specified by heightUnits property.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns the visual width of the object.
+ * Default width is -1, which is interpreted as "unspecified".
+ * @return The width in units specified by widthUnits property.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /** Sets the visual height of the object.
+ * Default height is -1, which is interpreted as "unspecified".
+ * @param height The height in units specified by heightUnits property.
+ */
+ public void setHeight(int height) {
+ if (this.height != height) {
+ this.height = height;
+ requestRepaint();
+ }
+ }
+
+ /** Sets the visual width of the object.
+ * Default width is -1, which is interpreted as "unspecified".
+ * @param width The width in units specified by widthUnits property.
+ */
+ public void setWidth(int width) {
+ if (this.width != width) {
+ this.width = width;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Returns the classId attribute.
+ * @return String
+ */
+ 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();
+ }
+ }
+
+ /** Get the resource contained in the embedded object.
+ * @return Resource
+ */
+ public Resource getSource() {
+ return source;
+ }
+
+ /** Get 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 int
+ */
+ public int getType() {
+ return type;
+ }
+
+ /** Set 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;
+ String mt = source.getMIMEType();
+ if ((mt.substring(0, mt.indexOf("/")).equalsIgnoreCase("image"))) {
+ type = TYPE_IMAGE;
+ } else {
+ type = TYPE_OBJECT;
+ }
+ 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)
+ throw new IllegalArgumentException("Unsupported type");
+ if (type != this.type) {
+ this.type = type;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Returns the archive attribute.
+ * @return String
+ */
+ 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();
+ }
+ }
+
+ /**Get height property units.
+ * Default units are <code>Sizeable.UNITS_PIXELS</code>.
+ * @see com.itmill.toolkit.terminal.Sizeable#getHeightUnits()
+ */
+ public int getHeightUnits() {
+ return this.heightUnits;
+ }
+
+ /**Get width property units.
+ * Default units are <code>Sizeable.UNITS_PIXELS</code>.
+ * @see com.itmill.toolkit.terminal.Sizeable#getWidthUnits()
+ */
+ public int getWidthUnits() {
+ return this.widthUnits;
+ }
+
+ /**Set height property units.
+ * @see com.itmill.toolkit.terminal.Sizeable#setHeightUnits(int)
+ */
+ public void setHeightUnits(int units) {
+ if (units >= 0 && units <= Sizeable.UNITS_PERCENTAGE && this.heightUnits != units) {
+ this.heightUnits = units;
+ requestRepaint();
+ }
+ }
+
+ /**Set width property units.
+ * @see com.itmill.toolkit.terminal.Sizeable#setWidthUnits(int)
+ */
+ public void setWidthUnits(int units) {
+ if (units >= 0 && units <= Sizeable.UNITS_PERCENTAGE && this.widthUnits != units) {
+ this.widthUnits = units;
+ requestRepaint();
+ }
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/Field.java b/src/com/itmill/toolkit/ui/Field.java
new file mode 100644
index 0000000000..837162bbde
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Field.java
@@ -0,0 +1,104 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.data.BufferedValidatable;
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.ui.Component.Focusable;
+
+/**
+ * @author Sami Ekblad
+ *
+ */
+public interface Field
+ extends
+ Component,
+ BufferedValidatable,
+ Property,
+ Property.ValueChangeNotifier,
+ Property.ValueChangeListener,
+ Property.Editor,
+ Focusable {
+
+ void setCaption(String caption);
+
+ String getDescription();
+
+ void setDescription(String caption);
+
+ /** Is this field required.
+ *
+ * Required fields must filled by the user.
+ *
+ * @return true if the
+ * @since 3.1
+ */
+ public boolean isRequired();
+
+ /** Set the field required.
+ * Required fields must filled by the user.
+ *
+ * @param required Is the field required
+ * @since 3.1
+ */
+ public void setRequired(boolean required);
+
+ /** An <code>Event</code> object specifying the Field whose value
+ * has been changed.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public class ValueChangeEvent
+ extends Component.Event
+ implements Property.ValueChangeEvent {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3545803169444672816L;
+
+ /** 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 Source Property of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/FieldFactory.java b/src/com/itmill/toolkit/ui/FieldFactory.java
new file mode 100644
index 0000000000..041b2761a8
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/FieldFactory.java
@@ -0,0 +1,82 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.data.Container;
+import com.itmill.toolkit.data.Item;
+import com.itmill.toolkit.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 {
+
+
+ /** Creates 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 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 field based on the item and property id.
+ *
+ * @param item The item where the property belongs to.
+ * @param propertyId 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 field based on the container item id and property id.
+ *
+ * @param container Container where the property belongs to.
+ * @param itemId The item Id.
+ * @param propertyId 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/itmill/toolkit/ui/Form.java b/src/com/itmill/toolkit/ui/Form.java
new file mode 100644
index 0000000000..b80159683d
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Form.java
@@ -0,0 +1,822 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.itmill.toolkit.data.*;
+import com.itmill.toolkit.data.Validator.InvalidValueException;
+import com.itmill.toolkit.data.util.BeanItem;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/** Form component provides easy way of creating and managing sets fields.
+ *
+ * <p>Form 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>Form provides customizable editor for classes implementing
+ * {@link com.itmill.toolkit.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.itmill.toolkit.data.Item} interface, most properties of any
+ * class following bean pattern, can be accessed trough
+ * {@link com.itmill.toolkit.data.util.BeanItem}.</p>
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+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 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 HashMap fields = new HashMap();
+
+ /** Field factory for this form */
+ private FieldFactory fieldFactory;
+
+ /** Registered Validators */
+ private LinkedList validators;
+
+ /** Visible item properties */
+ private Collection visibleItemProperties;
+
+ /** Contruct a new form with default layout.
+ *
+ * <p>By default the form uses <code>OrderedLayout</code>
+ * with <code>form</code>-style.
+ *
+ * @param formLayout The layout of the form.
+ */
+ public Form() {
+ this(null);
+ }
+
+ /** Contruct a new form with given layout.
+ *
+ * @param formLayout The layout of the form.
+ */
+ public Form(Layout formLayout) {
+ this(formLayout, new BaseFieldFactory());
+ }
+
+ /** Contruct a new form with given layout and FieldFactory.
+ *
+ * @param formLayout The layout of the form.
+ * @param fieldFactory FieldFactory of the form
+ */
+ public Form(Layout formLayout, FieldFactory fieldFactory) {
+ super();
+ setLayout(formLayout);
+ setFieldFactory(fieldFactory);
+ }
+
+ /* Documented in interface */
+ public String getTag() {
+ return "component";
+ }
+
+ /* Documented in interface */
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ layout.paint(target);
+
+ }
+
+ /* Commit changes to the data source
+ * Don't add a JavaDoc comment here, we use the default one from the
+ * interface.
+ */
+ public void commit() throws Buffered.SourceException {
+
+ LinkedList problems = null;
+
+ // Try to commit all
+ for (Iterator i = propertyIds.iterator(); i.hasNext();)
+ try {
+ Field f = ((Field) fields.get(i.next()));
+ //Commit only non-readonly fields.
+ if (!f.isReadOnly()) {
+ f.commit();
+ }
+ } catch (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
+ Throwable[] causes = new Throwable[problems.size()];
+ int index = 0;
+ for (Iterator i = problems.iterator(); i.hasNext();)
+ causes[index++] = (Throwable) i.next();
+ Buffered.SourceException e = new Buffered.SourceException(this, causes);
+ currentBufferedSourceException = e;
+ requestRepaint();
+ throw e;
+ }
+
+ /* Discard local changes and refresh values from the data source
+ * Don't add a JavaDoc comment here, we use the default one from the
+ * interface.
+ */
+ public void discard() throws Buffered.SourceException {
+
+ LinkedList problems = null;
+
+ // Try to discard all changes
+ for (Iterator i = propertyIds.iterator(); i.hasNext();)
+ try {
+ ((Field) fields.get(i.next())).discard();
+ } catch (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;
+ }
+
+ // Discard problems occurred
+ Throwable[] causes = new Throwable[problems.size()];
+ int index = 0;
+ for (Iterator i = problems.iterator(); i.hasNext();)
+ causes[index++] = (Throwable) i.next();
+ 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.
+ */
+ public boolean isModified() {
+ for (Iterator i = propertyIds.iterator(); i.hasNext();) {
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public void setReadThrough(boolean readThrough) {
+ if (readThrough != this.readThrough) {
+ this.readThrough = readThrough;
+ for (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.
+ */
+ public void setWriteThrough(boolean writeThrough) {
+ if (writeThrough != this.writeThrough) {
+ this.writeThrough = writeThrough;
+ for (Iterator i = propertyIds.iterator(); i.hasNext();)
+ ((Field) fields.get(i.next())).setWriteThrough(writeThrough);
+ }
+ }
+
+ /** Add a new property to form and create corresponding field.
+ *
+ * @see com.itmill.toolkit.data.Item#addItemProperty(Object, Property)
+ */
+ public boolean addItemProperty(Object id, Property property) {
+
+ // Check inputs
+ if (id == null || property == null)
+ throw new NullPointerException("Id and property must be non-null");
+
+ // Check that the property id is not reserved
+ if (propertyIds.contains(id))
+ return false;
+
+ // Get suitable field
+ Field field = this.fieldFactory.createField(property, this);
+ if (field == null)
+ return false;
+
+ // Configure 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 (Throwable ignored) {
+ return false;
+ }
+
+ addField(id, field);
+
+ return true;
+ }
+
+ /** Add 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 Property id the the field.
+ * @param field New field added to the form.
+ */
+ public void addField(Object propertyId, Field field) {
+
+ if (propertyId != null && field != null) {
+ this.dependsOn(field);
+ field.dependsOn(this);
+ fields.put(propertyId, field);
+ propertyIds.addLast(propertyId);
+ field.setReadThrough(readThrough);
+ field.setWriteThrough(writeThrough);
+
+ 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.itmill.toolkit.data.Item#getItemProperty(Object)
+ */
+ public Property getItemProperty(Object id) {
+ Field field = (Field) fields.get(id);
+ if (field == null)
+ return null;
+ Property property = field.getPropertyDataSource();
+
+ if (property != null)
+ return property;
+ else
+ return field;
+ }
+
+ /** Get the field identified by the propertyid */
+ 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.itmill.toolkit.data.Item#removeItemProperty(Object)
+ */
+ public boolean removeItemProperty(Object id) {
+
+ Field field = (Field) fields.get(id);
+
+ if (field != null) {
+ propertyIds.remove(id);
+ fields.remove(id);
+ this.removeDirectDependency(field);
+ field.removeDirectDependency(this);
+ layout.removeComponent(field);
+ return true;
+ }
+
+ return false;
+ }
+
+ /** Removes all properties and fields from the form.
+ *
+ * @return Success of the operation. Removal of all fields succeeded
+ * if (and only if) the return value is true.
+ */
+ public boolean removeAllProperties() {
+ 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;
+ }
+
+ /** Set 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.itmill.toolkit.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.itmill.toolkit.data.Item.Viewer#setItemDataSource(Item)
+ */
+ public void setItemDataSource(Item newDataSource, Collection propertyIds) {
+
+ // Remove all fields first from the form
+ removeAllProperties();
+
+ // Set the datasource
+ itemDatasource = newDataSource;
+
+ //If the new datasource is null, just set null datasource
+ if (itemDatasource == null)
+ return;
+
+ // Add all the properties to this form
+ for (Iterator i = propertyIds.iterator(); i.hasNext();) {
+ Object id = i.next();
+ Property property = itemDatasource.getItemProperty(id);
+ if (id != null && property != null) {
+ Field f =
+ this.fieldFactory.createField(itemDatasource, id, this);
+ if (f != null) {
+ f.setPropertyDataSource(property);
+ addField(id, f);
+ }
+ }
+ }
+ }
+
+ /** Get the layout of the form.
+ *
+ * <p>By default form uses <code>OrderedLayout</code> with <code>form</code>-style.</p>
+ *
+ * @return Layout of the form.
+ */
+ public Layout getLayout() {
+ return layout;
+ }
+
+ /** Set the layout of the form.
+ *
+ * <p>By default form uses <code>OrderedLayout</code> with <code>form</code>-style.</p>
+ *
+ * @param layout Layout of the form.
+ */
+ public void setLayout(Layout newLayout) {
+
+ // Use orderedlayout by default
+ if (newLayout == null) {
+ newLayout = new OrderedLayout();
+ newLayout.setStyle("form");
+ }
+
+ // Move components from previous layout
+ if (this.layout != null) {
+ newLayout.moveComponentsFrom(this.layout);
+ this.layout.setParent(null);
+ }
+
+ // Replace the previous layout
+ newLayout.setParent(this);
+ this.layout = newLayout;
+ }
+
+ /** Set a 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>
+ *
+ * @return The select property generated
+ */
+ public Select replaceWithSelect(
+ Object propertyId,
+ Object[] values,
+ Object[] descriptions) {
+
+ // Check 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");
+
+ // Get the old field
+ Field oldField = (Field) fields.get(propertyId);
+ if (oldField == null)
+ throw new IllegalArgumentException(
+ "Field with given propertyid '"
+ + propertyId.toString()
+ + "' can not be found.");
+ Object value = oldField.getValue();
+
+ // Check 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 (Iterator it = ((Collection) value).iterator();
+ it.hasNext();
+ ) {
+ 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");
+ }
+
+ // Create new field matching to old field parameters
+ 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());
+
+ // Create 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);
+ }
+ Item item = newField.addItem(id);
+ if (item != null)
+ item.getItemProperty("desc").setValue(
+ descriptions[i].toString());
+ }
+
+ // Set the property data source
+ Property property = oldField.getPropertyDataSource();
+ oldField.setPropertyDataSource(null);
+ newField.setPropertyDataSource(property);
+
+ // Replace the old field with new one
+ layout.replaceComponent(oldField, newField);
+ fields.put(propertyId, newField);
+ this.removeDirectDependency(oldField);
+ oldField.removeDirectDependency(this);
+ this.dependsOn(newField);
+ newField.dependsOn(this);
+
+ return newField;
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component#attach()
+ */
+ public void attach() {
+ super.attach();
+ layout.attach();
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component#detach()
+ */
+ public void detach() {
+ super.detach();
+ layout.detach();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Validatable#addValidator(com.itmill.toolkit.data.Validator)
+ */
+ public void addValidator(Validator validator) {
+
+ if (this.validators == null) {
+ this.validators = new LinkedList();
+ }
+ this.validators.add(validator);
+ }
+ /**
+ * @see com.itmill.toolkit.data.Validatable#removeValidator(com.itmill.toolkit.data.Validator)
+ */
+ public void removeValidator(Validator validator) {
+ if (this.validators != null) {
+ this.validators.remove(validator);
+ }
+ }
+ /**
+ * @see com.itmill.toolkit.data.Validatable#getValidators()
+ */
+ public Collection getValidators() {
+ if (this.validators == null) {
+ this.validators = new LinkedList();
+ }
+ return null;
+ }
+ /**
+ * @see com.itmill.toolkit.data.Validatable#isValid()
+ */
+ public boolean isValid() {
+ boolean valid = true;
+ for (Iterator i = propertyIds.iterator(); i.hasNext();)
+ valid &= ((Field) fields.get(i.next())).isValid();
+ return valid;
+ }
+ /**
+ * @see com.itmill.toolkit.data.Validatable#validate()
+ */
+ public void validate() throws InvalidValueException {
+ for (Iterator i = propertyIds.iterator(); i.hasNext();)
+ ((Field) fields.get(i.next())).validate();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Validatable#isInvalidAllowed()
+ */
+ public boolean isInvalidAllowed() {
+ return true;
+ }
+ /**
+ * @see com.itmill.toolkit.data.Validatable#setInvalidAllowed(boolean)
+ */
+ public void setInvalidAllowed(boolean invalidValueAllowed)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+ /**
+ * @see com.itmill.toolkit.ui.Component#setReadOnly(boolean)
+ */
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ for (Iterator i = propertyIds.iterator(); i.hasNext();)
+ ((Field) fields.get(i.next())).setReadOnly(readOnly);
+ }
+
+ /** Set the field factory of Form.
+ *
+ * FieldFacroty is used to create fields for form properties.
+ * By default the form uses BaseFieldFactory to create Field instances.
+ *
+ * @param fieldFactory 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 FieldFactory Factory used to create the fields
+ */
+ public FieldFactory getFieldFactory() {
+ return this.fieldFactory;
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.AbstractField#getType()
+ */
+ public Class getType() {
+ if (getPropertyDataSource() != null)
+ return getPropertyDataSource().getType();
+ return Object.class;
+ }
+
+ /** Set the internal value.
+ *
+ * This is relevant when the Form is used as Field.
+ * @see com.itmill.toolkit.ui.AbstractField#setInternalValue(java.lang.Object)
+ */
+ protected void setInternalValue(Object newValue) {
+ // Store old value
+ Object oldValue = this.propertyValue;
+
+ // Set the current Value
+ super.setInternalValue(newValue);
+ this.propertyValue = newValue;
+
+ // Ignore form updating if data object has not changed.
+ if (oldValue != newValue) {
+ setFormDataSource(newValue, getVisibleItemProperties());
+ }
+ }
+
+ /**Get first field in form.
+ * @return Field
+ */
+ private Field getFirstField() {
+ Object id = null;
+ if (this.getItemPropertyIds() != null) {
+ id = this.getItemPropertyIds().iterator().next();
+ }
+ if (id != null)
+ return this.getField(id);
+ return null;
+ }
+
+ /** Update the internal form datasource.
+ *
+ * Method setFormDataSource.
+ * @param value
+ */
+ 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);
+ }
+
+ // Set the datasource to form
+ if (item != null && properties != null) {
+ // Show only given properties
+ this.setItemDataSource(item, properties);
+ } else {
+ // Show all properties
+ this.setItemDataSource(item);
+ }
+ }
+
+ /**
+ * Returns the visibleProperties.
+ * @return Collection
+ */
+ public Collection getVisibleItemProperties() {
+ return visibleItemProperties;
+ }
+
+ /**
+ * Sets the visibleProperties.
+ * @param visibleProperties The visibleProperties to set
+ */
+ public void setVisibleItemProperties(Collection visibleProperties) {
+ this.visibleItemProperties = visibleProperties;
+ Object value = getValue();
+ setFormDataSource(value, getVisibleItemProperties());
+ }
+
+ /** Focuses the first field in the form.
+ * @see com.itmill.toolkit.ui.Component.Focusable#focus()
+ */
+ public void focus() {
+ Field f = getFirstField();
+ if (f != null) {
+ f.focus();
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component.Focusable#setTabIndex(int)
+ */
+ public void setTabIndex(int tabIndex) {
+ super.setTabIndex(tabIndex);
+ for (Iterator i = this.getItemPropertyIds().iterator(); i.hasNext();)
+ (this.getField(i.next())).setTabIndex(tabIndex);
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/FrameWindow.java b/src/com/itmill/toolkit/ui/FrameWindow.java
new file mode 100644
index 0000000000..4588f02e17
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/FrameWindow.java
@@ -0,0 +1,469 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.net.URL;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Resource;
+
+/** <p>An application frame window component. This component implements a
+ * window that contains a hierarchical set of frames. Each frame can contain
+ * a web-page, window or a set of frames that divides the space horizontally
+ * or vertically.</p>
+ *
+ * <p>A <code>FrameWindow</code> can't contain any components directly (as
+ * it contains only a set of frames) and thus the container interface
+ * methods do nothing.</p>
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class FrameWindow extends Window {
+
+ private Frameset frameset = new Frameset();
+
+ /** <p>Constructs a new frame window.
+ *
+ */
+ public FrameWindow() {
+ }
+
+
+ /** <p>Constructs a new frame window.
+ *
+ * @param caption title of the window
+ */
+ public FrameWindow(String caption) {
+ super(caption);
+ }
+
+ /** Gets the window's UIDL tag.
+ *
+ * @return window's UIDL tag as <code>String</code>
+ */
+ public String getTag() {
+ return "framewindow";
+ }
+
+ /** Gets the main frameset of the window. This set contains all the
+ * top-level frames of the window. New contents are added by adding
+ * frames to this frameset.
+ *
+ * @return top-level frame set of this frame window
+ */
+ public Frameset getFrameset() {
+ return frameset;
+ }
+
+ /** Paints the window contents.
+ *
+ * @param target A paint target event
+ * @throws PaintException if the paint operation fails
+ *
+ * @see com.itmill.toolkit.ui.AbstractComponent#paintContent(PaintTarget)
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ super.paintContent(target);
+
+ // Paint frameset
+ getFrameset().paint(target);
+ }
+
+ /** An individual frame that contains either a window or the contents of the
+ * url set to frame.
+ *
+ * <p>The frames can be only created to framesets using the
+ * <code>newFrame()</code> functions of the frameset.</p>
+ */
+ public class Frame {
+
+ /** URL of the frame contents */
+ private URL url;
+
+ /** Name of the frame */
+ private String name;
+
+ /** Window connected to frame or null */
+ private Window window;
+
+ /** Window connected to frame or null */
+ private Resource resource;
+
+ /** String representation of the width */
+ private String width = "*";
+
+ /** Parent frameset */
+ protected Frameset parentFrameset;
+
+ /** URL of the frame */
+ public URL getURL() {
+ return window == null ? url : window.getURL();
+ }
+
+ /** Get the parent frameset */
+ public Frameset getParentFrameset() {
+ return parentFrameset;
+ }
+
+ /** Name of the freame */
+ public String getName() {
+ return window == null ? name : window.getName();
+ }
+
+ /** Window connected to frame */
+ public Window getWindow() {
+ return window;
+ }
+
+ /** Resource connected to frame */
+ public Resource getResource() {
+ return resource;
+ }
+
+ /** Absolute width/height of the frame in pixels */
+ public void setAbsoluteSize(int widthInPixels) {
+ width = String.valueOf(widthInPixels);
+ requestRepaint();
+ }
+
+ /** Set the frame size to be freely specified by the terminal */
+ public void setFreeSize() {
+ width = "*";
+ requestRepaint();
+ }
+
+ /** Set the frame width/height as a percentage of the containing
+ * frameset size */
+ public void setRelativeSize(int widthInPercents) {
+ if (widthInPercents < 0 || widthInPercents > 100)
+ throw new IllegalArgumentException(
+ "Relative width must " + "be between 0% and 100%");
+ width = String.valueOf(widthInPercents) + "%";
+ requestRepaint();
+ }
+
+ /** Paint the frame */
+ private void paint(PaintTarget target) throws PaintException {
+ target.startTag("frame");
+ if (getResource() != null)
+ target.addAttribute("src", getResource());
+ else
+ target.addAttribute("src", getURL().toString());
+ target.addAttribute("name", getName());
+ target.endTag("frame");
+ }
+ }
+
+ /** Vertical or horizontal set of frames */
+ public class Frameset extends Frame {
+
+ /** List of frames ordered from left to right or from top to bottom */
+ private LinkedList frames = new LinkedList();
+
+ /** True iff the frames are on top of each other. If false the frames
+ * are side by side.
+ */
+ private boolean vertical = false;
+
+ /** Get a list of frames.
+ *
+ * @return unmodifiable list of frames.
+ */
+ public List getFrames() {
+ return Collections.unmodifiableList(frames);
+ }
+
+ /** Create new frame containing a window.
+ *
+ * <p>The new frame will be in the end of the frames list.</p>
+ */
+ public Frame newFrame(Window window) {
+ return newFrame(window, size());
+ }
+
+ /** Create new frame containing a window.
+ *
+ * <p>The new frame will be put before the frame identified
+ * by the given index. The indexes of the frame previously in the
+ * given position and all the positions after it are incremented
+ * by one.</p>
+ */
+ public Frame newFrame(Window window, int index) {
+ Frame f = new Frame();
+ f.window = window;
+ f.parentFrameset = this;
+ frames.add(index, f);
+ if (getApplication() != null)
+ getApplication().addWindow(window);
+ requestRepaint();
+ return f;
+ }
+
+ /** Create new frame containing a url.
+ *
+ * <p>The new frame will be put in the end of the frames list..</p>
+ */
+ public Frame newFrame(URL url, String name) {
+ return newFrame(url, name, size());
+ }
+
+ /** Create new frame containing a resource.
+ *
+ * <p>The new frame will be put in the end of the frames list..</p>
+ */
+ public Frame newFrame(Resource resource, String name) {
+ return newFrame(resource, name, size());
+ }
+
+ /** Create new frame containing a url.
+ *
+ * <p>The new frame will be put before the frame identified
+ * by the given index. The indexes of the frame previously in the
+ * given position and all the positions after it are incremented
+ * by one.</p>
+ */
+ public Frame newFrame(URL url, String name, int index) {
+ Frame f = new Frame();
+ f.url = url;
+ f.name = name;
+ f.parentFrameset = this;
+ frames.add(index, f);
+ requestRepaint();
+ return f;
+ }
+
+ /** Create new frame containing a resource.
+ *
+ * <p>The new frame will be put before the frame identified
+ * by the given index. The indexes of the frame previously in the
+ * given position and all the positions after it are incremented
+ * by one.</p>
+ */
+ public Frame newFrame(Resource resource, String name, int index) {
+ Frame f = new Frame();
+ f.resource = resource;
+ f.name = name;
+ f.parentFrameset = this;
+ frames.add(index, f);
+ requestRepaint();
+ return f;
+ }
+
+ /** Create new frameset.
+ *
+ * <p>The new frame will be put before the frame identified
+ * by the given index. The indexes of the frame previously in the
+ * given position and all the positions after it are incremented
+ * by one.</p>
+ */
+ public Frameset newFrameset(boolean isVertical, int index) {
+ Frameset f = new Frameset();
+ f.setVertical(isVertical);
+ f.parentFrameset = this;
+ frames.add(index, f);
+ requestRepaint();
+ return f;
+ }
+
+ /** Remove a frame from this frameset */
+ public void removeFrame(Frame frame) {
+ frames.remove(frame);
+ frame.parentFrameset = null;
+ requestRepaint();
+ }
+
+ /** Remove all frames from this frameset */
+ public void removeAllFrames() {
+ for (Iterator i = frames.iterator(); i.hasNext();)
+ ((Frame) i.next()).parentFrameset = null;
+ frames.clear();
+ requestRepaint();
+ }
+
+ /** Number of frames in this frameset */
+ public int size() {
+ return frames.size();
+ }
+
+ /** Set the framaset to be vertical.
+ *
+ * <p>By setting this true, the frames will be ordered on top
+ * of each other from top to bottom. Setting this false, the
+ * frames will be ordered side by side from left to right.</p>
+ */
+ public void setVertical(boolean isVertical) {
+ this.vertical = isVertical;
+ requestRepaint();
+ }
+
+ /** Check if the frameset is vertical.
+ *
+ * <p>If this is true, the frames will be ordered on top
+ * of each other from top to bottom, otherwise the
+ * frames will be ordered side by side from left to right.</p>
+ */
+ public boolean isVertical() {
+ return vertical;
+ }
+
+ /** Get frame by name.
+ * @return Frame having the given name or null if the frame is
+ * not found
+ */
+ public Frame getFrame(String name) {
+ if (name == null)
+ return null;
+ for (Iterator i = frames.iterator(); i.hasNext();) {
+ Frame f = (Frame) i.next();
+ if (name.equals(f.getName()))
+ return f;
+ }
+ return null;
+ }
+
+ /** Get frame by index.
+ * @return Frame having the given index or null if the frame is
+ * not found
+ */
+ public Frame getFrame(int index) {
+ if (index >= 0 && index < frames.size())
+ return (Frame) frames.get(index);
+ return null;
+ }
+
+ /** Paint the frameset */
+ private void paint(PaintTarget target) throws PaintException {
+ target.startTag("frameset");
+ if (!frames.isEmpty()) {
+ StringBuffer widths = null;
+ for (Iterator i = frames.iterator(); i.hasNext();) {
+ Frame f = (Frame) i.next();
+ if (widths == null)
+ widths = new StringBuffer();
+ else
+ widths.append(',');
+ widths.append(f.width);
+ }
+ if (vertical)
+ target.addAttribute("rows", widths.toString());
+ else
+ target.addAttribute("cols", widths.toString());
+ for (Iterator i = frames.iterator(); i.hasNext();) {
+ Frame f = (Frame) i.next();
+ if (Frameset.class.isAssignableFrom(f.getClass()))
+ ((Frameset) f).paint(target);
+ else
+ f.paint(target);
+ }
+ }
+ target.endTag("frameset");
+ }
+
+ /** Set the application for all the frames in this frameset */
+ private void setApplication(
+ Application fromApplication,
+ Application toApplication) {
+ for (Iterator i = frames.iterator(); i.hasNext();) {
+ Frame f = (Frame) i.next();
+ if (f instanceof Frameset)
+ ((Frameset) f).setApplication(
+ fromApplication,
+ toApplication);
+ else if (f.window != null) {
+ if (toApplication == null) {
+ fromApplication.removeWindow(f.window);
+ } else
+ toApplication.addWindow(f.window);
+ }
+ }
+ }
+ }
+
+ /** Setting the application for frame window also sets the application
+ * for all the frames.
+ * @see com.itmill.toolkit.ui.Window#setApplication(Application)
+ */
+ public void setApplication(Application application) {
+ Application fromApplication = getApplication();
+ super.setApplication(application);
+ Frameset fs = getFrameset();
+ if (fs != null)
+ fs.setApplication(fromApplication, application);
+ }
+
+ /** Frame windows does not support scrolling.
+ */
+ public boolean isScrollable() {
+ return false;
+ }
+
+ /** Frame windows does not support scrolling.
+ *
+ * @see com.itmill.toolkit.terminal.Scrollable#setScrollable(boolean)
+ */
+ public void setScrollable(boolean isScrollingEnabled) {
+ }
+
+ /** Frame windows does not support scrolling.
+ *
+ * @see com.itmill.toolkit.terminal.Scrollable#setScrollOffsetX(int)
+ */
+ public void setScrollOffsetX(int pixelsScrolledLeft) {
+ }
+
+ /** Frame windows does not support scrolling.
+ *
+ * @see com.itmill.toolkit.terminal.Scrollable#setScrollOffsetY(int)
+ */
+ public void setScrollOffsetY(int pixelsScrolledDown) {
+ }
+
+ /** Frame window does not support adding components directly.
+ *
+ * <p>To add component to frame window, normal window must be
+ * first created and then attached to frame window as a frame.</p>
+ *
+ * @see com.itmill.toolkit.ui.ComponentContainer#addComponent(Component)
+ * @throws UnsupportedOperationException if invoked.
+ */
+ public void addComponent(Component c)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/GridLayout.java b/src/com/itmill/toolkit/ui/GridLayout.java
new file mode 100644
index 0000000000..3af8f34ac7
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/GridLayout.java
@@ -0,0 +1,757 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/** <p>A container that consists of components with certain coordinates 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} (x1,y1,x2,y2) 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
+ */
+public class GridLayout extends AbstractComponentContainer implements Layout {
+
+ /** Initial grid x size */
+ private int width = 0;
+
+ /** Initial grid y size */
+ private int height = 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 LinkedList areas = new LinkedList();
+
+ /** Mapping from components to threir respective areas. */
+ private LinkedList components = new LinkedList();
+
+ /** Constructor for grid of given size.
+ * 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 width Width of the grid.
+ * @param height Height of the grid.
+ */
+ public GridLayout(int width, int height) {
+ setWidth(width);
+ setHeight(height);
+ }
+
+ /** 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 (x1, y1) and the lower right corner (x2, y2) 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 x1 The X-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y1 The Y-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param x2 The X-coordinate of the lower right corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y2 The Y-coordinate 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 coordinates are outside of the
+ * grid area.
+ */
+ public void addComponent(
+ Component component,
+ int x1,
+ int y1,
+ int x2,
+ int y2)
+ throws OverlapsException, OutOfBoundsException {
+
+ if (component == null)
+ throw new NullPointerException("Component must not be null");
+
+ // Check that the component does not already exist in the container
+ if (components.contains(component))
+ throw new IllegalArgumentException("Component is already in the container");
+
+ // Create area
+ Area area = new Area(component, x1, y1, x2, y2);
+
+ // Check the validity of the coordinates
+ if (x2 < x1 || y2 < y2)
+ throw new IllegalArgumentException("Illegal coordinates for the component");
+ if (x1 < 0 || y1 < 0 || x2 >= width || y2 >= height)
+ throw new OutOfBoundsException(area);
+
+ // Check that newItem does not overlap with existing items
+ checkExistingOverlaps(area);
+
+ // Insert the component to right place at the list
+ // Respect top-down, left-right ordering
+ component.setParent(this);
+ Iterator i = areas.iterator();
+ int index = 0;
+ boolean done = false;
+ while (!done && i.hasNext()) {
+ Area existingArea = (Area) i.next();
+ if ((existingArea.y1 >= y1 && existingArea.x1 > x1)
+ || existingArea.y1 > y1) {
+ areas.add(index, area);
+ components.add(index, component);
+ done = true;
+ }
+ index++;
+ }
+ if (!done) {
+ areas.addLast(area);
+ components.addLast(component);
+ }
+
+ super.addComponent(component);
+ requestRepaint();
+ }
+
+ /** Tests if the given area overlaps with any of the items already on
+ * the grid.
+ *
+ * @param area 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 (Iterator i = areas.iterator(); i.hasNext();) {
+ Area existingArea = (Area) i.next();
+ if (existingArea.overlaps(area))
+
+ // Component not added, overlaps with existing component
+ throw new OverlapsException(existingArea);
+ }
+ }
+
+ /** Add component into this container to coordinates x1,y1 (NortWest corner of the area.)
+ * End coordinates (SouthEast corner of the area) are the same as x1,y1. Component width
+ * and height is 1.
+ * @param c The component to be added.
+ * @param x X-coordinate
+ * @param y Y-coordinate
+ */
+ public void addComponent(Component c, int x, int y) {
+ this.addComponent(c, x, y, x, y);
+ }
+
+ /** 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++;
+ }
+
+ /** Move 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 >= width) {
+ cursorX = 0;
+ cursorY++;
+ }
+ }
+
+ /** Add a 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 automaticly extended.
+ * @param c The component to be added.
+ */
+ public void addComponent(Component component) {
+
+ // Find 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 (OverlapsException ignored) {
+ space();
+ }
+
+ // Extend the grid if needed
+ width = cursorX >= width ? cursorX + 1 : width;
+ height = cursorY >= height ? cursorY + 1 : height;
+
+ addComponent(component, cursorX, cursorY);
+ }
+
+ /** Removes the given component from this
+ * container.
+ *
+ * @param c The component to be removed.
+ */
+ 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 (Iterator i = areas.iterator(); area == null && i.hasNext();) {
+ Area a = (Area) i.next();
+ if (a.getComponent() == component)
+ area = a;
+ }
+
+ components.remove(component);
+ if (area != null)
+ areas.remove(area);
+
+ requestRepaint();
+ }
+
+ /** Removes a component specified with it's top-left corner coordinates
+ * from this grid.
+ *
+ * @param x Component's top-left corner's X-coordinate
+ * @param y Component's top-left corner's Y-coordinate
+ */
+ public void removeComponent(int x, int y) {
+
+ // Find area
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area area = (Area) i.next();
+ if (area.getX1() == x && area.getY1() == y) {
+ 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 Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return Collections.unmodifiableCollection(components).iterator();
+ }
+
+ /** Paints the contents of this component.
+ *
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ target.addAttribute("h", height);
+ target.addAttribute("w", width);
+
+ // Area iterator
+ Iterator areaiterator = areas.iterator();
+
+ // Current item to be processed (fetch first item)
+ Area area = areaiterator.hasNext() ? (Area) areaiterator.next() : null;
+
+ // Collect rowspan related information here
+ HashMap cellUsed = new HashMap();
+
+ // Empty cell collector
+ int emptyCells = 0;
+
+ // Iterate every applicable row
+ for (int cury = 0; cury < height; cury++) {
+ target.startTag("gr");
+
+ // Iterate every applicable column
+ for (int curx = 0; curx < width; curx++) {
+
+ // Check if current item is located at curx,cury
+ if (area != null && (area.y1 == cury) && (area.x1 == 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
+ int cols = (area.x2 - area.x1) + 1;
+ int rows = (area.y2 - area.y1) + 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);
+
+ target.endTag("gc");
+
+ // Fetch next item
+ if (areaiterator.hasNext()) {
+ area = (Area) areaiterator.next();
+ } else {
+ area = null;
+ }
+
+ // Update 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++;
+ }
+ }
+
+ // Skip current item's spanned columns
+ if (cols > 1) {
+ curx += cols - 1;
+ }
+
+ } else {
+
+ // Check 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
+ 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++;
+
+ // Remove cellUsed key as it has become obsolete
+ cellUsed.remove(new Integer(curx));
+ }
+ } else {
+
+ // empty cell is needed
+ emptyCells++;
+ }
+ }
+
+ } // iterate every column
+
+ // Last column handled of current row
+
+ // Check if empty cell needs to be rendered
+ if (emptyCells > 0) {
+ target.startTag("gc");
+ target.addAttribute("x", width - emptyCells);
+ target.addAttribute("y", cury);
+ if (emptyCells > 1) {
+ target.addAttribute("w", emptyCells);
+ }
+ target.endTag("gc");
+
+ emptyCells = 0;
+ }
+
+ target.endTag("gr");
+ } // iterate every row
+
+ // Last row handled
+ }
+
+ /** Gets the components UIDL tag.
+ *
+ * @return Component UIDL tag as string.
+ * @see com.itmill.toolkit.ui.AbstractComponent#getTag()
+ */
+ public String getTag() {
+ return "gridlayout";
+ }
+
+ /** This class defines an area on a grid. An Area is defined by the
+ * coordinates of its upper left corner (x1,y1) and lower right corner
+ * (x2,y2)
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public class Area {
+
+ /** X-coordinate of the upper left corner of the area */
+ private int x1;
+
+ /** Y-coordinate of the upper left corner of the area */
+ private int y1;
+
+ /** X-coordinate of the lower right corner of the area */
+ private int x2;
+
+ /** Y-coordinate of the lower right corner of the area */
+ private int y2;
+
+ /** Component painted on the area */
+ private Component component;
+
+ /** <p>Construct a new area on a grid.
+ *
+ * @param x1 The X-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y1 The Y-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param x2 The X-coordinate of the lower right corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y2 The Y-coordinate 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
+ */
+ public Area(Component component, int x1, int y1, int x2, int y2) {
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+ this.component = component;
+ }
+
+ /** Tests if the given Area overlaps with an another Area.
+ *
+ * @param other 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 x1 <= other.getX2()
+ && y1 <= other.getY2()
+ && x2 >= other.getX1()
+ && y2 >= other.getY1();
+
+ }
+
+ /** Returns the component connected to the area.
+ * @return 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;
+ }
+
+ /** Returns the top-left corner x-coordinate.
+ * @return int
+ */
+ public int getX1() {
+ return x1;
+ }
+
+ /**
+ * Returns the bottom-right corner x-coordinate.
+ * @return int
+ */
+ public int getX2() {
+ return x2;
+ }
+
+ /**
+ * Returns the top-left corner y-coordinate.
+ * @return int
+ */
+ public int getY1() {
+ return y1;
+ }
+
+ /**
+ * Returns the bottom-right corner y-coordinate.
+ * @return int
+ */
+ public int getY2() {
+ return y2;
+ }
+
+ }
+
+ /** 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3978144339870101561L;
+
+ private Area existingArea;
+
+ /** Constructs an <code>OverlapsException</code>.
+ * @param msg the detail message.
+ */
+ public OverlapsException(Area existingArea) {
+ this.existingArea = existingArea;
+ }
+
+ /** Get the 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3618985589664592694L;
+
+ private Area areaOutOfBounds;
+
+ /** Constructs an <code>OoutOfBoundsException</code> with the specified
+ * detail message.
+ *
+ * @param msg the detail message.
+ */
+ public OutOfBoundsException(Area areaOutOfBounds) {
+ this.areaOutOfBounds = areaOutOfBounds;
+ }
+
+ /** Get the area that is out of bounds */
+ public Area getArea() {
+ return areaOutOfBounds;
+ }
+ }
+
+ /** Set the width of the grid. The width can not be reduced if there are
+ * any areas that would be outside of the shrunk grid.
+ * @param width New width of the grid.
+ * @throws OutOfBoundsException if the one of the areas would exceed the
+ * bounds of the grid after the modification of the grid size.
+ */
+ public void setWidth(int width) {
+
+ // The the param
+ if (width < 1)
+ throw new IllegalArgumentException("The grid width and height must be at least 1");
+
+ // In case of no change
+ if (this.width == width)
+ return;
+
+ // Check for overlaps
+ if (this.width > width)
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area area = (Area) i.next();
+ if (area.x2 >= width)
+ throw new OutOfBoundsException(area);
+ }
+
+ this.width = width;
+
+ requestRepaint();
+ }
+
+ /** Get the width of the grids.
+ * @return The width of the grid
+ */
+ public final int getWidth() {
+ return this.width;
+ }
+
+ /** Set the height of the grid. The width can not be reduced if there are
+ * any areas that would be outside of the shrunk grid.
+ * @param Height of the grid
+ */
+ public void setHeight(int height) {
+
+ // The the param
+ if (height < 1)
+ throw new IllegalArgumentException("The grid width and height must be at least 1");
+
+ // In case of no change
+ if (this.height == height)
+ return;
+
+ // Check for overlaps
+ if (this.height > height)
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area area = (Area) i.next();
+ if (area.y2 >= height)
+ throw new OutOfBoundsException(area);
+ }
+
+ this.height = height;
+
+ requestRepaint();
+ }
+
+ /** Get the height of the grid.
+ * @return int - how many cells high the grid is
+ */
+ public final int getHeight() {
+ return this.height;
+ }
+
+ /** Get the current cursor x-position.
+ * The cursor position points the position for the next component
+ * that is added without specifying its coordinates. When the
+ * cursor position is occupied, the next component will be added
+ * to first free position after the cursor.
+ * @return Cursor x-coordinate.
+ */
+ public int getCursorX() {
+ return cursorX;
+ }
+
+ /** Get the current cursor y-position.
+ * The cursor position points the position for the next component
+ * that is added without specifying its coordinates. When the
+ * cursor position is occupied, the next component will be added
+ * to first free position after the cursor.
+ * @return Cursor y-coordinate.
+ */
+ public int getCursorY() {
+ return cursorY;
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(
+ Component oldComponent,
+ Component newComponent) {
+
+ // Get the locations
+ Area oldLocation = null;
+ Area newLocation = null;
+ for (Iterator i=areas.iterator(); i.hasNext();) {
+ Area location = (Area) i.next();
+ Component 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.getX1(),oldLocation.getY1(),oldLocation.getX2(),oldLocation.getY2());
+ } else {
+ oldLocation.setComponent(newComponent);
+ newLocation.setComponent(oldComponent);
+ requestRepaint();
+ }
+ }
+
+ /*
+ * @see com.itmill.toolkit.ui.ComponentContainer#removeAllComponents()
+ */
+ public void removeAllComponents() {
+ super.removeAllComponents();
+ this.cursorX = 0;
+ this.cursorY = 0;
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/Label.java b/src/com/itmill/toolkit/ui/Label.java
new file mode 100644
index 0000000000..c2897a5652
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Label.java
@@ -0,0 +1,455 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.lang.reflect.Method;
+
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.data.util.ObjectProperty;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.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
+ */
+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.
+ */
+ 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;
+
+ private Property dataSource;
+ private int contentMode = CONTENT_DEFAULT;
+
+ /** Creates an empty Label. */
+ public Label() {
+ setPropertyDataSource(new ObjectProperty("", String.class));
+ }
+
+ /** Creates a new instance of Label with text-contents. */
+ public Label(String content) {
+ setPropertyDataSource(new ObjectProperty(content, String.class));
+ }
+
+ /** Creates a new instance of Label with text-contents read from given datasource. */
+ public Label(Property contentSource) {
+ setPropertyDataSource(contentSource);
+ }
+
+ /** Creates a new instance of Label with text-contents. */
+ public Label(String content, int contentMode) {
+ setPropertyDataSource(new ObjectProperty(content, String.class));
+ setContentMode(contentMode);
+ }
+
+ /** Creates a new instance of Label with text-contents read from given datasource. */
+ public Label(Property contentSource, int contentMode) {
+ setPropertyDataSource(contentSource);
+ setContentMode(contentMode);
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ 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
+ */
+ 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 label - this returns allways false.
+ * @return True iff the component is in read only mode
+ */
+ public boolean isReadOnly() {
+ if (dataSource == null)
+ throw new IllegalStateException("Datasource must be se");
+ return dataSource.isReadOnly();
+ }
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+ 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.addCharacterData(toString());
+ target.endTag("data");
+ }
+
+ }
+
+ /** Get the value of the label.
+ * Value of the label is the XML contents of the label.
+ * @return Value of the label
+ */
+ public Object getValue() {
+ if (dataSource == null)
+ throw new IllegalStateException("Datasource must be se");
+ return dataSource.getValue();
+ }
+
+ /** Set the value of the label.
+ * Value of the label is the XML contents of the label.
+ * @param newValue New value of the label
+ */
+ public void setValue(Object newValue) {
+ if (dataSource == null)
+ throw new IllegalStateException("Datasource must be se");
+ this.dataSource.setValue(newValue);
+ }
+
+ public String toString() {
+ if (dataSource == null)
+ throw new IllegalStateException("Datasource must be se");
+ return dataSource.toString();
+ }
+
+ public Class getType() {
+ if (dataSource == null)
+ throw new IllegalStateException("Datasource must be se");
+ return dataSource.getType();
+ }
+
+ /** Get viewing data-source property. */
+ public Property getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /** Set the property as data-source for viewing. */
+ public void setPropertyDataSource(Property newDataSource) {
+ // Stop listening the old data source changes
+ if (dataSource != null
+ && Property.ValueChangeNotifier.class.isAssignableFrom(
+ dataSource.getClass()))
+ ((Property.ValueChangeNotifier) dataSource).removeListener(this);
+
+ // Set the new data source
+ dataSource = newDataSource;
+
+ // Listen the new data source if possible
+ if (dataSource != null
+ && Property.ValueChangeNotifier.class.isAssignableFrom(
+ dataSource.getClass()))
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+
+ /** Get 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 Content mode of the label.
+ */
+ public int getContentMode() {
+ return contentMode;
+ }
+
+ /** Set 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 New content mode of the label.
+ */
+ public void setContentMode(int contentMode) {
+ if (contentMode >= CONTENT_TEXT && contentMode <= CONTENT_RAW)
+ this.contentMode = contentMode;
+ }
+
+ /* 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 (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ /** Value change event
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public class ValueChangeEvent
+ extends Component.Event
+ implements Property.ValueChangeEvent {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3906084563938586935L;
+
+ /** New instance of text change event
+ * @param source Source of the event.
+ */
+ public ValueChangeEvent(Label source) {
+ super(source);
+ }
+
+ /** Value where the event occurred
+ * @return Source of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /** Add value change listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(Property.ValueChangeListener listener) {
+ addListener(
+ Label.ValueChangeEvent.class,
+ listener,
+ VALUE_CHANGE_METHOD);
+ }
+
+ /** Remove value change listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(Property.ValueChangeListener listener) {
+ removeListener(
+ Label.ValueChangeEvent.class,
+ listener,
+ VALUE_CHANGE_METHOD);
+ }
+
+ /** Emit options change event. */
+ protected void fireValueChange() {
+ // Set the error message
+ fireEvent(new Label.ValueChangeEvent(this));
+ requestRepaint();
+ }
+
+ /** Listen value change events from data source.
+ * @see com.itmill.toolkit.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent)
+ */
+ public void valueChange(Property.ValueChangeEvent event) {
+ fireValueChange();
+ }
+
+ /** Compare 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>
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ * @param other 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.
+ */
+ 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);
+ }
+
+ /** Strip tags from the XML.
+ *
+ * @param xml String containing a XML snippet
+ * @return The original XML without tags
+ */
+ private String stripTags(String xml) {
+
+ StringBuffer res = new StringBuffer();
+
+ int processed = 0;
+ 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/itmill/toolkit/ui/Layout.java b/src/com/itmill/toolkit/ui/Layout.java
new file mode 100644
index 0000000000..e3805f6dd1
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Layout.java
@@ -0,0 +1,43 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+/** 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. This interface is empty in
+ * MillStone 3.0, but will be extended in MillStone 3.1.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public interface Layout extends ComponentContainer {
+
+}
diff --git a/src/com/itmill/toolkit/ui/Link.java b/src/com/itmill/toolkit/ui/Link.java
new file mode 100644
index 0000000000..c09859dcda
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Link.java
@@ -0,0 +1,256 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Resource;
+
+/** Link component.
+ * Link is used to create external or internal URL links.
+ *
+ * Internal links can be used to create action items, which
+ * change the state to application to one of the predefined states.
+ * For example, a link can be created for existing MenuTree items.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+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 Window window = 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 link to a window. */
+ public Link(Window window) {
+
+ // Set the link caption to match window caption
+ setCaption(window.getCaption());
+
+ // Set the target
+ setTargetName(window.getName());
+
+ setTargetName(window.getName());
+ setTargetWidth(window.getWidth());
+ setTargetHeight(window.getHeight());
+ setTargetBorder(window.getBorder());
+ }
+
+ /** Creates a new instance of Link */
+ public Link(String caption, Resource resource) {
+ setCaption(caption);
+ this.resource = resource;
+ }
+
+ /** Creates a new instance of Link that opens a new window.
+ *
+ *
+ * @param caption 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 Width of the target window.
+ * @param height Height of the target window.
+ * @param border Borget 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);
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "link";
+ }
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (window != null)
+ target.addAttribute("src", window.getURL().toString());
+ else if (resource != null)
+ target.addAttribute("src", resource);
+ else
+ return;
+
+ // Target window name
+ String name = getTargetName();
+ if (name != null && name.length()>0)
+ target.addAttribute("name", name);
+
+ // Target window size
+ if (getTargetWidth() >= 0)
+ target.addAttribute("width", getTargetWidth());
+ if (getTargetHeight() >= 0)
+ target.addAttribute("height", 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 int
+ */
+ public int getTargetBorder() {
+ return targetBorder;
+ }
+
+ /** Returns the target window height or -1 if not set.
+ * @return int
+ */
+ 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 String
+ */
+ public String getTargetName() {
+ return targetName;
+ }
+
+ /** Returns the target window width or -1 if not set.
+ * @return int
+ */
+ 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 Resource
+ */
+ public Resource getResource() {
+ return resource;
+ }
+
+ /** Returns the window this link opens.
+ * @return Window
+ */
+ public Window getWindow() {
+ return window;
+ }
+
+ /** Sets the resource this link opens.
+ * @param resource The resource to set
+ */
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ if (resource != null) {
+ window = null;
+ }
+ requestRepaint();
+ }
+
+ /** Sets the window this link opens.
+ * @param window The window to set
+ */
+ public void setWindow(Window window) {
+ this.window = window;
+ if (window != null) {
+ this.resource = null;
+ }
+ requestRepaint();
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/OrderedLayout.java b/src/com/itmill/toolkit/ui/OrderedLayout.java
new file mode 100644
index 0000000000..eacdbac926
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/OrderedLayout.java
@@ -0,0 +1,215 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/** Ordered layout.
+ *
+ * Ordered layout 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
+ */
+public class OrderedLayout
+ extends AbstractComponentContainer
+ implements Layout {
+
+ /* Predefined orientations ***************************************** */
+
+ /** Components are to be layed out vertically. */
+ public static int ORIENTATION_VERTICAL = 0;
+ /** Components are to be layed out horizontally. */
+ public static int ORIENTATION_HORIZONTAL = 1;
+
+ /** Custom layout slots containing the components */
+ private LinkedList components = new LinkedList();
+
+ /** Orientation of the layout. */
+ private int orientation;
+
+ /** Create a new ordered layout.
+ * The order of the layout is ORIENTATION_VERTICAL.
+ */
+ public OrderedLayout() {
+ orientation = ORIENTATION_VERTICAL;
+ }
+
+ /** Create a new ordered layout.
+ * The orientation of the layout is given as parameters.
+ *
+ * @param orientation Orientation of the layout.
+ */
+ public OrderedLayout(int orientation) {
+ this.orientation = orientation;
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ 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.
+ */
+ public void addComponent(Component c) {
+ components.add(c);
+ super.addComponent(c);
+ requestRepaint();
+ }
+
+ /** Add 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) {
+ components.addFirst(c);
+ super.addComponent(c);
+ requestRepaint();
+ }
+
+ /** Add a component into indexed position in this container.
+ * @param c The component to be added.
+ * @param index Index of the component position.
+ * The components currently in and after the position are shifted forwards.
+ */
+ public void addComponent(Component c, int index) {
+ components.add(index, c);
+ super.addComponent(c);
+ requestRepaint();
+ }
+
+ /** Remove a component from this container.
+ * @param c The component to be removed.
+ */
+ public void removeComponent(Component c) {
+ super.removeComponent(c);
+ components.remove(c);
+ requestRepaint();
+ }
+
+ /** Get component container iterator for going trough all the components in
+ * the container.
+ * @return Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return components.iterator();
+ }
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Add the attributes: orientation
+ // note that the default values (b/vertival) are omitted
+ if (orientation == ORIENTATION_HORIZONTAL)
+ target.addAttribute("orientation", "horizontal");
+
+ // Add all items in all the locations
+ for (Iterator i = components.iterator(); i.hasNext();) {
+ Component c = (Component) i.next();
+ if (c != null) {
+ c.paint(target);
+ }
+ }
+ }
+
+ /** Get the orientation of the container.
+ * @return Value of property orientation.
+ */
+ public int getOrientation() {
+ return this.orientation;
+ }
+
+ /** Set the orientation of the container.
+ * @param orientation New value of property orientation.
+ */
+ public void setOrientation(int orientation) {
+
+ // Check the validity of the argument
+ if (orientation < ORIENTATION_VERTICAL
+ || orientation > ORIENTATION_HORIZONTAL)
+ throw new IllegalArgumentException();
+
+ this.orientation = orientation;
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(
+ Component oldComponent,
+ Component newComponent) {
+
+ // Get the locations
+ int oldLocation = -1;
+ int newLocation = -1;
+ int location = 0;
+ for (Iterator i = components.iterator(); i.hasNext();) {
+ 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);
+ components.add(oldLocation, newComponent);
+ } else {
+ components.remove(newComponent);
+ components.add(oldLocation, newComponent);
+ components.remove(oldComponent);
+ components.add(newLocation, oldComponent);
+ }
+
+ requestRepaint();
+ }
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/Panel.java b/src/com/itmill/toolkit/ui/Panel.java
new file mode 100644
index 0000000000..08d6482d43
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Panel.java
@@ -0,0 +1,375 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Scrollable;
+import com.itmill.toolkit.terminal.Sizeable;
+
+/** Panel - a simple single component container.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class Panel
+ extends AbstractComponentContainer
+ implements
+ Sizeable,
+ Scrollable,
+ ComponentContainer.ComponentAttachListener,
+ ComponentContainer.ComponentDetachListener {
+
+ /** Layout of the panel */
+ private Layout layout;
+
+ /** Width of the panel or -1 if unspecified */
+ private int width = -1;
+
+ /** Height of the panel or -1 if unspecified */
+ private int height = -1;
+
+ /** Width unit */
+ private int widthUnit = Sizeable.UNITS_PIXELS;
+
+ /** Height unit */
+ private int heightUnit = Sizeable.UNITS_PIXELS;
+
+ /** Scroll X position */
+ private int scrollOffsetX = 0;
+
+ /** Scroll Y position */
+ private int scrollOffsetY = 0;
+
+ /** Scrolling mode */
+ private boolean scrollable = false;
+
+ /** Create new empty panel.
+ * Ordered layout is used.
+ */
+ public Panel() {
+ this(new OrderedLayout());
+ }
+
+ /** Create new empty panel with given layout.
+ * Layout must be non-null.
+ *
+ * @param layout The layout used in the panel.
+ */
+ public Panel(Layout layout) {
+ setLayout(layout);
+ }
+
+ /** Create new empty panel with caption.
+ * Ordered layout is used.
+ *
+ * @param caption The caption used in the panel.
+ */
+ public Panel(String caption) {
+ this(caption, new OrderedLayout());
+ }
+
+ /** Create new empty panel with caption.
+ *
+ * @param caption The caption of the panel.
+ * @param layout The layout used in the panel.
+ */
+ public Panel(String caption, Layout layout) {
+ this(layout);
+ setCaption(caption);
+ }
+
+ /** Get the current layout of the panel.
+ * @return Current layout of the panel.
+ */
+ public Layout getLayout() {
+ return this.layout;
+ }
+
+ /** Set the layout of the panel.
+ * All the components are moved to new layout.
+ *
+ * @param layout New layout of the panel.
+ */
+ public void setLayout(Layout layout) {
+
+ // Only allow non-null layouts
+ if (layout == null)
+ layout = new OrderedLayout();
+
+ // Set the panel to be parent for the layout
+ layout.setParent(this);
+ dependsOn(layout);
+
+ // If panel already contains a layout, move the contents to new one
+ // and detach old layout from the panel
+ if (this.layout != null) {
+ layout.moveComponentsFrom(this.layout);
+ removeDirectDependency(this.layout);
+ this.layout.setParent(null);
+ }
+
+ // Remove the event listeners from the old layout
+ if (this.layout != null) {
+ this.layout.removeListener((ComponentContainer.ComponentAttachListener) this);
+ this.layout.removeListener((ComponentContainer.ComponentDetachListener) this);
+ }
+
+ // Set the new layout
+ this.layout = layout;
+
+ // Add event listeners for new layout
+ layout.addListener((ComponentContainer.ComponentAttachListener) this);
+ layout.addListener((ComponentContainer.ComponentDetachListener) this);
+ }
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+ layout.paint(target);
+ target.addVariable(this, "height", getHeight());
+ target.addVariable(this, "width", getWidth());
+ if (isScrollable()) {
+ target.addVariable(this, "scrollleft", getScrollOffsetX());
+ target.addVariable(this, "scrolldown", getScrollOffsetY());
+ }
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "panel";
+ }
+
+ /** Add a component into this container.
+ * @param c The component to be added.
+ */
+ public void addComponent(Component c) {
+ layout.addComponent(c);
+ // No repaint request is made as we except the underlaying container to
+ // request repaints
+ }
+
+ /** Remove a component from this container.
+ * @param c The component to be added.
+ */
+ public void removeComponent(Component c) {
+ layout.removeComponent(c);
+ // No repaint request is made as we except the underlaying container to
+ // request repaints
+ }
+
+ /** Get component container iterator for going trough all the components in the container.
+ * @return Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return layout.getComponentIterator();
+ }
+
+ /**
+ * @return The height in pixels or negative value if not assigned.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * @return The width in pixels or negative value if not assigned.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /** Sets the height in pixels.
+ * Use negative value to let the client decide the height.
+ * @param height The height to set
+ */
+ public void setHeight(int height) {
+ this.height = height;
+ requestRepaint();
+ }
+
+ /** Sets the width in pixels.
+ * Use negative value to allow the client decide the width.
+ * @param width The width to set
+ */
+ public void setWidth(int width) {
+ this.width = width;
+ requestRepaint();
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.VariableOwner#changeVariables(Object, Map)
+ */
+ public void changeVariables(Object source, Map variables) {
+ super.changeVariables(source, variables);
+
+ // Get new size
+ Integer newWidth = (Integer) variables.get("width");
+ Integer newHeight = (Integer) variables.get("height");
+ if (newWidth != null && newWidth.intValue() != getWidth())
+ setWidth(newWidth.intValue());
+ if (newHeight != null && newHeight.intValue() != getHeight())
+ setHeight(newHeight.intValue());
+
+ // Scrolling
+ Integer newScrollX = (Integer) variables.get("scrollleft");
+ Integer newScrollY = (Integer) variables.get("scrolldown");
+ if (newScrollX != null && newScrollX.intValue() != getScrollOffsetX())
+ setScrollOffsetX(newScrollX.intValue());
+ if (newScrollY != null && newScrollY.intValue() != getScrollOffsetY())
+ setScrollOffsetY(newScrollY.intValue());
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Sizeable#getHeightUnits()
+ */
+ public int getHeightUnits() {
+ return heightUnit;
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.Sizeable#getWidthUnits()
+ */
+ public int getWidthUnits() {
+ return widthUnit;
+ }
+
+ /** Set height units.
+ * Panel supports only Sizeable.UNITS_PIXELS and this is ignored.
+ * @see com.itmill.toolkit.terminal.Sizeable#setHeightUnits(int)
+ */
+ public void setHeightUnits(int units) {
+ // Ignored
+ }
+
+ /** Set width units.
+ * Panel supports only Sizeable.UNITS_PIXELS, and this is ignored.
+ * @see com.itmill.toolkit.terminal.Sizeable#setWidthUnits(int)
+ */
+ public void setWidthUnits(int units) {
+ // Ignored
+ }
+
+ /* Scrolling functionality */
+
+ /* Documented in interface */
+ public int getScrollOffsetX() {
+ return scrollOffsetX;
+ }
+
+ /* Documented in interface */
+ public int getScrollOffsetY() {
+ return scrollOffsetY;
+ }
+
+ /* 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 setScrollOffsetX(int pixelsScrolledLeft) {
+ if (pixelsScrolledLeft < 0)
+ throw new IllegalArgumentException("Scroll offset must be at least 0");
+ if (this.scrollOffsetX != pixelsScrolledLeft) {
+ scrollOffsetX = pixelsScrolledLeft;
+ requestRepaint();
+ }
+ }
+
+ /* Documented in interface */
+ public void setScrollOffsetY(int pixelsScrolledDown) {
+ if (pixelsScrolledDown < 0)
+ throw new IllegalArgumentException("Scroll offset must be at least 0");
+ if (this.scrollOffsetY != pixelsScrolledDown) {
+ scrollOffsetY = pixelsScrolledDown;
+ requestRepaint();
+ }
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(
+ Component oldComponent,
+ Component newComponent) {
+
+ layout.replaceComponent(oldComponent, newComponent);
+ }
+
+ /** Pass the events from underlying layout forwards.
+ * @see com.itmill.toolkit.ui.ComponentContainer.ComponentAttachListener#componentAttachedToContainer(com.itmill.toolkit.ui.ComponentContainer.ComponentAttachEvent)
+ */
+ public void componentAttachedToContainer(ComponentAttachEvent event) {
+ if (event.getContainer() == layout)
+ fireComponentAttachEvent(event.getAttachedComponent());
+ }
+
+ /** Pass the events from underlying layout forwards.
+ * @see com.itmill.toolkit.ui.ComponentContainer.ComponentDetachListener#componentDetachedFromContainer(com.itmill.toolkit.ui.ComponentContainer.ComponentDetachEvent)
+ */
+ public void componentDetachedFromContainer(ComponentDetachEvent event) {
+ if (event.getContainer() == layout)
+ fireComponentDetachEvent(event.getDetachedComponent());
+ }
+
+ /*
+ * @see com.itmill.toolkit.ui.Component#attach()
+ */
+ public void attach() {
+ if (layout != null) layout.attach();
+ }
+
+ /*
+ * @see com.itmill.toolkit.ui.Component#detach()
+ */
+ public void detach() {
+ if (layout != null) layout.detach();
+ }
+ /*
+ * @see com.itmill.toolkit.ui.ComponentContainer#removeAllComponents()
+ */
+ public void removeAllComponents() {
+ layout.removeAllComponents();
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/Select.java b/src/com/itmill/toolkit/ui/Select.java
new file mode 100644
index 0000000000..be73c31420
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Select.java
@@ -0,0 +1,1202 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+import com.itmill.toolkit.data.Container;
+import com.itmill.toolkit.data.Item;
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.data.util.IndexedContainer;
+import com.itmill.toolkit.terminal.KeyMapper;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.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.itmill.toolkit.data.Item}s in a
+ * {@link com.itmill.toolkit.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
+ */
+public class Select
+ 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.itmill.toolkit.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;
+
+ /** 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 HashMap itemIcons = new HashMap();
+
+ /** Item captions */
+ private 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 LinkedList propertySetEventListeners = null;
+
+ /** List of item set change event listeners */
+ private LinkedList itemSetEventListeners = null;
+
+ /** Item id that represents null selection 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>
+ */
+ private Object nullSelectionItemId = null;
+
+ /* Constructors ********************************************************* */
+
+ /** Creates an empty Select.
+ * The caption is not used.
+ */
+ public Select() {
+ setContainerDataSource(new IndexedContainer());
+ }
+
+ /** Creates an empty Select with caption.
+ */
+ public Select(String caption) {
+ setContainerDataSource(new IndexedContainer());
+ setCaption(caption);
+ }
+
+ /** Creates a new select wthat is connected to a data-source.
+ * @param dataSource Container datasource to be selected from by this select.
+ * @param caption Caption of the component.
+ * @param selected Selected item id or null, if none selected.
+ */
+ public Select(String caption, Container dataSource) {
+ setCaption(caption);
+ setContainerDataSource(dataSource);
+ }
+
+ /** Creates a new select that is filled from a collection of option values.
+ * @param caption Caption of this field.
+ * @param options Collection containing the options.
+ * @param selected Selected option or null, if none selected.
+ */
+ public Select(String caption, Collection options) {
+
+ // Create options container and add given options to it
+ Container c = new IndexedContainer();
+ if (options != null)
+ for (Iterator i = options.iterator(); i.hasNext();)
+ c.addItem(i.next());
+
+ setCaption(caption);
+ setContainerDataSource((Container) c);
+ }
+
+ /* Component methods **************************************************** */
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Paint field properties
+ super.paintContent(target);
+
+ // Paint select attributes
+ if (isMultiSelect())
+ target.addAttribute("selectmode", "multi");
+ if (isNewItemsAllowed())
+ target.addAttribute("allownewitem", true);
+
+ // Paint options and create array of selected id keys
+ String[] selectedKeys;
+ if (isMultiSelect())
+ selectedKeys = new String[((Set) getValue()).size()];
+ else
+ selectedKeys =
+ new String[(
+ getValue() == null
+ && getNullSelectionItemId() == null ? 0 : 1)];
+ int keyIndex = 0;
+ target.startTag("options");
+
+ // Support for external null selection item id
+ Collection ids = getItemIds();
+ if (getNullSelectionItemId() != null
+ && (!ids.contains(getNullSelectionItemId()))) {
+
+ // Get the option attribute values
+ Object id = getNullSelectionItemId();
+ String key = itemIdMapper.key(id);
+ String caption = getItemCaption(id);
+ Resource icon = getItemIcon(id);
+
+ // Paint 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");
+ }
+
+ // Paint available selection options from data source
+ for (Iterator i = getItemIds().iterator(); i.hasNext();) {
+
+ // Get the option attribute values
+ Object id = i.next();
+ String key = itemIdMapper.key(id);
+ String caption = getItemCaption(id);
+ Resource icon = getItemIcon(id);
+
+ // Paint 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.
+ * @param event Variable change event containing the information about
+ * the changed variable.
+ */
+ public void changeVariables(Object source, Map variables) {
+
+ // Try to set the property value
+
+ // New option entered (and it is allowed)
+ String newitem = (String) variables.get("newitem");
+ if (newitem != null && newitem.length() > 0) {
+
+ // Check for readonly
+ if (isReadOnly())
+ throw new Property.ReadOnlyException();
+
+ // Add new option
+ if (addItem(newitem) != null) {
+
+ // Set the caption property, if used
+ if (getItemCaptionPropertyId() != null)
+ try {
+ getContainerProperty(
+ newitem,
+ getItemCaptionPropertyId()).setValue(
+ newitem);
+ } catch (Property.ConversionException ignored) {
+ // The conversion exception is safely ignored, the caption is
+ // just missing
+ }
+ }
+ }
+
+ // Selection change
+ if (variables.containsKey("selected")) {
+ String[] ka = (String[]) variables.get("selected");
+
+ // Multiselect mode
+ if (isMultiSelect()) {
+
+ // Convert the key-array to id-set
+ LinkedList s = new LinkedList();
+ for (int i = 0; i < ka.length; i++) {
+ Object id = itemIdMapper.get(ka[i]);
+ if (id != null && containsId(id))
+ s.add(id);
+ else if (
+ itemIdMapper.isNewIdKey(ka[i])
+ && newitem != null
+ && newitem.length() > 0)
+ s.add(newitem);
+ }
+
+ // Limit the deselection to the set of visible items
+ // (non-visible items can not be deselected)
+ 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);
+ super.setValue(newsel);
+ }
+ }
+
+ // Single select mode
+ else {
+ if (ka.length == 0) {
+
+ // Allow deselection only if the deselected item is visible
+ Object current = getValue();
+ Collection visible = getVisibleItemIds();
+ if (visible != null && visible.contains(current))
+ setValue(null);
+ } else {
+ Object id = itemIdMapper.get(ka[0]);
+ if (id != null && id.equals(getNullSelectionItemId()))
+ setValue(null);
+ else if (itemIdMapper.isNewIdKey(ka[0]))
+ setValue(newitem);
+ else
+ setValue(id);
+ }
+ }
+ }
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "select";
+ }
+
+ /** Get 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 ***************************************************** */
+
+ /** Return the type of the property.
+ * getValue and setValue functions must be compatible with this type:
+ * one can safely cast getValue() to given type and pass any variable
+ * assignable to this type as a parameter to setValue().
+ * @return type Type of the property.
+ */
+ public Class getType() {
+ if (isMultiSelect())
+ return Set.class;
+ else
+ return Object.class;
+ }
+
+ /** Get the selected item id or in multiselect mode a set of selected ids.
+ */
+ public Object getValue() {
+ 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 {
+ Set s = new HashSet();
+ if (items.containsId(retValue))
+ s.add(retValue);
+ return s;
+ }
+
+ } else
+ return retValue;
+ }
+
+ /** Set 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 New selected item or collection of selected items.
+ */
+ public void setValue(Object newValue)
+ throws Property.ReadOnlyException, Property.ConversionException {
+
+ if (isMultiSelect()) {
+ if (newValue == null)
+ super.setValue(new HashSet());
+ else if (Collection.class.isAssignableFrom(newValue.getClass()))
+ super.setValue(new HashSet((Collection) newValue));
+ } else if (newValue == null || items.containsId(newValue))
+ super.setValue(newValue);
+ }
+
+ /* Container methods **************************************************** */
+
+ /** Get the item from the container with given id.
+ * If the container does not contain the requested item, null is returned.
+ */
+ public Item getItem(Object itemId) {
+ return items.getItem(itemId);
+ }
+
+ /** Get item Id collection from the container.
+ * @return Collection of item ids.
+ */
+ public Collection getItemIds() {
+ return items.getItemIds();
+ }
+
+ /** Get property Id collection from the container.
+ * @return Collection of property ids.
+ */
+ public Collection getContainerPropertyIds() {
+ return items.getContainerPropertyIds();
+ }
+
+ /** Get property type.
+ * @param id Id identifying the of the property.
+ */
+ public Class getType(Object propertyId) {
+ return items.getType(propertyId);
+ }
+
+ /** Get the number of items in the container.
+ * @return Number of items in the container.
+ */
+ public int size() {
+ return items.size();
+ }
+
+ /** Test, if the collection contains an item with given id.
+ * @param itemId Id the of item to be tested.
+ */
+ public boolean containsId(Object itemId) {
+ if (itemId != null)
+ return items.containsId(itemId);
+ else
+ return false;
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container#getContainerProperty(Object, Object)
+ */
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ return items.getContainerProperty(itemId, propertyId);
+ }
+
+ /* Container.Managed methods ******************************************** */
+
+ /** Add 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 iff the operation succeeded.
+ */
+ public boolean addContainerProperty(
+ Object propertyId,
+ Class type,
+ Object defaultValue)
+ throws UnsupportedOperationException {
+
+ boolean retval =
+ items.addContainerProperty(propertyId, type, defaultValue);
+ if (retval
+ && !(items instanceof Container.PropertySetChangeNotifier)) {
+ firePropertySetChange();
+ }
+ return retval;
+ }
+
+ /** Remove all items from the container.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns false.
+ *
+ * @return True iff the operation succeeded.
+ */
+ public boolean removeAllItems() throws UnsupportedOperationException {
+
+ boolean retval = items.removeAllItems();
+ this.itemIdMapper.removeAll();
+ if (retval) {
+ setValue(null);
+ if (!(items instanceof Container.ItemSetChangeNotifier))
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /** Create 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 Id of the created item or null in case of failure.
+ */
+ public Object addItem() throws UnsupportedOperationException {
+
+ 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 Identification of the item to be created.
+ * @return Created item with the given id, or null in case of failure.
+ */
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+
+ Item retval = items.addItem(itemId);
+ if (retval != null
+ && !(items instanceof Container.ItemSetChangeNotifier))
+ fireItemSetChange();
+ return retval;
+ }
+
+ /** Remove item identified by Id from the container.
+ * This functionality is optional. If the function is not implemented,
+ * the functions allways returns false.
+ *
+ * @return True iff the operation succeeded.
+ */
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+
+ unselect(itemId);
+ boolean retval = items.removeItem(itemId);
+ this.itemIdMapper.remove(itemId);
+ if (retval && !(items instanceof Container.ItemSetChangeNotifier))
+ fireItemSetChange();
+ return retval;
+ }
+
+ /** Remove 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 iff the operation succeeded.
+ */
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+
+ boolean retval = items.removeContainerProperty(propertyId);
+ if (retval && !(items instanceof Container.PropertySetChangeNotifier))
+ firePropertySetChange();
+ return retval;
+ }
+
+ /* Container.Viewer methods ********************************************* */
+
+ /** Set the container as data-source for viewing. */
+ public void setContainerDataSource(Container newDataSource) {
+ if (newDataSource == null)
+ newDataSource = new IndexedContainer();
+
+ if (items != newDataSource) {
+
+ // Remove listeners from the old datasource
+ if (items != null) {
+ try {
+ ((Container.ItemSetChangeNotifier) items).removeListener(
+ (Container.ItemSetChangeListener) this);
+ } catch (ClassCastException ignored) {
+ // Ignored
+ }
+ try {
+ (
+ (
+ Container
+ .PropertySetChangeNotifier) items)
+ .removeListener(
+ (Container.PropertySetChangeListener) this);
+ } catch (ClassCastException ignored) {
+ // Ignored
+ }
+ }
+
+ // Assign new data source
+ items = newDataSource;
+
+ // Clear itemIdMapper also
+ this.itemIdMapper.removeAll();
+
+ // Add listeners
+ if (items != null) {
+ try {
+ ((Container.ItemSetChangeNotifier) items).addListener(
+ (Container.ItemSetChangeListener) this);
+ } catch (ClassCastException ignored) {
+ // Ignored
+ }
+ try {
+ ((Container.PropertySetChangeNotifier) items).addListener(
+ (Container.PropertySetChangeListener) this);
+ } catch (ClassCastException ignored) {
+ // Ignored
+ }
+ }
+ //TODO: This should be conditional
+ fireValueChange();
+ }
+ }
+
+ /** Get viewing data-source container. */
+ public Container getContainerDataSource() {
+ return items;
+ }
+
+ /* Select attributes **************************************************** */
+
+ /** Is the select in multiselect mode? In multiselect mode
+ * @return Value of property multiSelect.
+ */
+ public boolean isMultiSelect() {
+ return this.multiSelect;
+ }
+
+ /** Set 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 New value of property multiSelect.
+ */
+ public void setMultiSelect(boolean multiSelect) {
+
+ if (multiSelect != this.multiSelect) {
+
+ // Selection before mode change
+ Object oldValue = getValue();
+
+ this.multiSelect = multiSelect;
+
+ // Convert the value type
+ if (multiSelect) {
+ Set s = new HashSet();
+ if (oldValue != null)
+ s.add(oldValue);
+ setValue(s);
+ } else {
+ 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. No that data-source must allow adding new
+ * items (it must implement Container.Managed).
+ * @return True iff additions are allowed.
+ */
+ public boolean isNewItemsAllowed() {
+
+ return this.allowNewOptions;
+ }
+
+ /** Enable or disable possibility to add new options by the user.
+ * @param allowNewOptions 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 New caption.
+ */
+ public void setItemCaption(Object itemId, String caption) {
+ if (itemId != null) {
+ itemCaptions.put(itemId, caption);
+ requestRepaint();
+ }
+ }
+
+ /** Get 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 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 :
+ try {
+ caption =
+ String.valueOf(
+ ((Container.Indexed) items).indexOfId(itemId));
+ } catch (ClassCastException ignored) {
+ }
+ break;
+
+ case ITEM_CAPTION_MODE_ITEM :
+ 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 :
+ Property p =
+ getContainerProperty(itemId, getItemCaptionPropertyId());
+ if (p != null)
+ caption = p.toString();
+ break;
+ }
+
+ // All items must have some captions
+ return caption != null ? caption : "";
+ }
+
+ /** Set icon for an item.
+ *
+ * @param itemId The id of the item to be assigned an icon.
+ * @param icon New icon.
+ */
+ public void setItemIcon(Object itemId, Resource icon) {
+ if (itemId != null) {
+ if (icon == null)
+ itemIcons.remove(itemId);
+ else
+ itemIcons.put(itemId, icon);
+ requestRepaint();
+ }
+ }
+
+ /** Get the item icon.
+ *
+ * @param itemId The id of the item to be assigned an icon.
+ * @return Icon for the item or null, if not specified.
+ */
+ public Resource getItemIcon(Object itemId) {
+ Resource explicit = (Resource) itemIcons.get(itemId);
+ if (explicit != null)
+ return explicit;
+
+ if (getItemIconPropertyId() == null)
+ return null;
+
+ Property ip = getContainerProperty(itemId, getItemIconPropertyId());
+ if (ip == null)
+ return null;
+ Object icon = ip.getValue();
+ if (icon instanceof Resource)
+ return (Resource) icon;
+
+ return null;
+ }
+
+ /** Set 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 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();
+ }
+ }
+
+ /** Get 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 One of the modes listed above.
+ */
+ public int getItemCaptionMode() {
+ return itemCaptionMode;
+ }
+
+ /** Set 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>.
+ *
+ */
+ 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();
+ }
+ }
+
+ /** Get the item caption property.
+ *
+ * @return Id of the property used as item caption source.
+ */
+ public Object getItemCaptionPropertyId() {
+ return itemCaptionPropertyId;
+ }
+
+ /** Set 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 that 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 Id of the property that specifies icons for
+ * items.
+ */
+ public void setItemIconPropertyId(Object propertyId) {
+ if ((propertyId != null)
+ && Resource.class.isAssignableFrom(getType(propertyId))) {
+ itemIconPropertyId = propertyId;
+ requestRepaint();
+ } else
+ itemIconPropertyId = null;
+ }
+
+ /** Get 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 that 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 Id of the property containing the item icons.
+ */
+ public Object getItemIconPropertyId() {
+ return itemIconPropertyId;
+ }
+
+ /** Test 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>
+ *
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ * @param itemId Id the of the item to be tested
+ */
+ public boolean isSelected(Object itemId) {
+ if (itemId == null)
+ return false;
+ if (isMultiSelect())
+ return ((Set) getValue()).contains(itemId);
+ else {
+ Object value = getValue();
+ return itemId.equals(
+ value == null ? getNullSelectionItemId() : value);
+ }
+ }
+
+ /** Select an item.
+ *
+ * <p>In single select mode selecting item identified
+ * by {@link #getNullSelectionItemId()} sets the value of the
+ * property to null.</p>
+ *
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ * @param itemId Item to be selected.
+ */
+ public void select(Object itemId) {
+ if (!isSelected(itemId) && items.containsId(itemId)) {
+ if (isMultiSelect()) {
+ Set s = new HashSet((Set) getValue());
+ s.add(itemId);
+ setValue(s);
+ } else if (itemId.equals(getNullSelectionItemId()))
+ setValue(null);
+ else
+ setValue(itemId);
+ }
+ }
+
+ /** Unselect an item.
+ *
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ * @param itemId Item to be unselected.
+ */
+ public void unselect(Object itemId) {
+ if (isSelected(itemId)) {
+ if (isMultiSelect()) {
+ Set s = new HashSet((Set) getValue());
+ s.remove(itemId);
+ setValue(s);
+ } else
+ setValue(null);
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.PropertySetChangeListener#containerPropertySetChange(com.itmill.toolkit.data.Container.PropertySetChangeEvent)
+ */
+ public void containerPropertySetChange(
+ Container.PropertySetChangeEvent event) {
+ firePropertySetChange();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.PropertySetChangeNotifier#addListener(com.itmill.toolkit.data.Container.PropertySetChangeListener)
+ */
+ public void addListener(Container.PropertySetChangeListener listener) {
+ if (propertySetEventListeners == null)
+ propertySetEventListeners = new LinkedList();
+ propertySetEventListeners.add(listener);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.PropertySetChangeNotifier#removeListener(com.itmill.toolkit.data.Container.PropertySetChangeListener)
+ */
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ if (propertySetEventListeners != null) {
+ propertySetEventListeners.remove(listener);
+ if (propertySetEventListeners.isEmpty())
+ propertySetEventListeners = null;
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.ItemSetChangeNotifier#addListener(com.itmill.toolkit.data.Container.ItemSetChangeListener)
+ */
+ public void addListener(Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners == null)
+ itemSetEventListeners = new LinkedList();
+ itemSetEventListeners.add(listener);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.ItemSetChangeNotifier#removeListener(com.itmill.toolkit.data.Container.ItemSetChangeListener)
+ */
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners != null) {
+ itemSetEventListeners.remove(listener);
+ if (itemSetEventListeners.isEmpty())
+ itemSetEventListeners = null;
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.ItemSetChangeListener#containerItemSetChange(com.itmill.toolkit.data.Container.ItemSetChangeEvent)
+ */
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ // Clear item id mapping table
+ this.itemIdMapper.removeAll();
+
+ // Notify all listeners
+ fireItemSetChange();
+ }
+
+ /** Fire property set change event */
+ protected void firePropertySetChange() {
+ if (propertySetEventListeners != null
+ && !propertySetEventListeners.isEmpty()) {
+ Container.PropertySetChangeEvent event =
+ new PropertySetChangeEvent();
+ Object[] listeners = propertySetEventListeners.toArray();
+ for (int i = 0; i < listeners.length; i++)
+ (
+ (
+ Container
+ .PropertySetChangeListener) listeners[i])
+ .containerPropertySetChange(
+ event);
+ }
+ requestRepaint();
+ }
+
+ /** Fire item set change event */
+ protected void fireItemSetChange() {
+ if (itemSetEventListeners != null
+ && !itemSetEventListeners.isEmpty()) {
+ Container.ItemSetChangeEvent event = new ItemSetChangeEvent();
+ 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 Container.ItemSetChangeEvent {
+
+ /**
+ * @see com.itmill.toolkit.data.Container.ItemSetChangeEvent#getContainer()
+ */
+ public Container getContainer() {
+ return Select.this;
+ }
+
+ }
+
+ /** Implementation of property set change event */
+ private class PropertySetChangeEvent
+ implements Container.PropertySetChangeEvent {
+
+ /**
+ * @see com.itmill.toolkit.data.Container.PropertySetChangeEvent#getContainer()
+ */
+ public Container getContainer() {
+ return Select.this;
+ }
+
+ }
+ /** 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 idetified
+ * by this id is the same as selecting no items at all. This setting only affects the
+ * single select mode.</p>
+
+ * @return 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 nullPropertyValueContainerItemId The nullPropertyValueContainerItemId to set
+ * @see #getNullSelectionItemId()
+ * @see #isSelected(Object)
+ * @see #select(Object)
+ */
+ public void setNullSelectionItemId(Object nullSelectionItemId) {
+ this.nullSelectionItemId = nullSelectionItemId;
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/TabSheet.java b/src/com/itmill/toolkit/ui/TabSheet.java
new file mode 100644
index 0000000000..adadc476e6
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/TabSheet.java
@@ -0,0 +1,398 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.itmill.toolkit.terminal.*;
+
+/** Tabsheet component.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class TabSheet extends AbstractComponentContainer {
+
+ /** Linked list of component tabs */
+ private LinkedList tabs = new LinkedList();
+
+ /** Tab -> caption mapping */
+ private Hashtable tabCaptions = new Hashtable();
+
+ /** Tab -> icon mapping */
+ private Hashtable tabIcons = new Hashtable();
+
+ /** Selected tab */
+ private Component selected = null;
+ private KeyMapper keyMapper = new KeyMapper();
+
+ /** Holds value of property tabsHIdden. */
+ private boolean tabsHidden;
+
+ /** Construct new Tabsheet.
+ * Tabsheet is immediate by default.
+ */
+ public TabSheet() {
+ super();
+ setImmediate(true);
+ }
+
+ /** Get component container iterator for going trough all the components in the container.
+ * @return Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return java.util.Collections.unmodifiableList(tabs).iterator();
+ }
+
+ /** Remove a component from this container.
+ * @param c The component to be removed.
+ */
+ public void removeComponent(Component c) {
+ if (c != null && tabs.contains(c)) {
+ super.removeComponent(c);
+ keyMapper.remove(c);
+ tabs.remove(c);
+ tabCaptions.remove(c);
+ if (c.equals(selected)) {
+ if (tabs.isEmpty())
+ selected = null;
+ else {
+ selected = (Component) tabs.getFirst();
+ fireSelectedTabChange();
+ }
+ }
+ requestRepaint();
+ }
+ }
+
+ /** Add a component into this container.
+ * The component is added as a tab where its default tab-caption is
+ * the caption of the component.
+ * @param c The component to be added.
+ */
+ public void addComponent(Component c) {
+ addTab(c, c.getCaption(), getIcon());
+ }
+
+ /** Add a new tab into TabSheet.
+ * @param c The component to be added onto tab.
+ * @param caption The caption of the tab.
+ * @param icon Set the icon of the tab.
+ */
+ public void addTab(Component c, String caption, Resource icon) {
+ if (c != null) {
+ tabs.addLast(c);
+ tabCaptions.put(c, caption != null ? caption : "");
+ if (icon != null)
+ tabIcons.put(c, icon);
+ if (selected == null) {
+ selected = c;
+ fireSelectedTabChange();
+ }
+ super.addComponent(c);
+ requestRepaint();
+ }
+ }
+
+ /** Get component UIDL tag.
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "tabsheet";
+ }
+
+ /** Move all components from another container to this container.
+ * The components are removed from the other container.
+ * @param source The container components are removed from.
+ */
+ public void moveComponentsFrom(ComponentContainer source) {
+ for (Iterator i = source.getComponentIterator(); i.hasNext();) {
+ 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);
+
+ }
+ }
+
+ /** Paint the content of this component.
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ if (areTabsHidden())
+ target.addAttribute("hidetabs", true);
+
+ target.startTag("tabs");
+
+ for (Iterator i = getComponentIterator(); i.hasNext();) {
+ Component c = (Component) i.next();
+ if (!c.isVisible())
+ continue;
+ target.startTag("tab");
+ Resource icon = getTabIcon(c);
+ if (icon != null)
+ target.addAttribute("icon", icon);
+ String caption = getTabCaption(c);
+ if (!c.isEnabled()) {
+ target.addAttribute("disabled", true);
+ }
+
+ if (caption != null && caption.length() > 0)
+ target.addAttribute("caption", caption);
+ target.addAttribute("key", keyMapper.key(c));
+ if (c.equals(selected)) {
+ target.addAttribute("selected", true);
+ c.paint(target);
+ }
+ target.endTag("tab");
+ }
+
+ target.endTag("tabs");
+
+ if (selected != null)
+ target.addVariable(this, "selected", keyMapper.key(selected));
+ }
+
+ /** Are tabs hidden.
+ * @return Property visibility
+ */
+ public boolean areTabsHidden() {
+ return this.tabsHidden;
+ }
+
+ /** Setter for property tabsHidden.
+ * @param tabsHidden True if the tabs should be hidden.
+ */
+ public void hideTabs(boolean tabsHidden) {
+ this.tabsHidden = tabsHidden;
+ requestRepaint();
+ }
+
+ /** Get the caption for a component */
+ public String getTabCaption(Component c) {
+ String caption = (String) tabCaptions.get(c);
+ if (caption == null)
+ caption = "";
+ return caption;
+ }
+
+ /** Set the caption for a component */
+ public void setTabCaption(Component c, String caption) {
+ tabCaptions.put(c, caption);
+ requestRepaint();
+ }
+
+ /** Get the icon for a component */
+ public Resource getTabIcon(Component c) {
+ return (Resource) tabIcons.get(c);
+ }
+
+ /** Set the icon for a component */
+ public void setTabIcon(Component c, Resource icon) {
+ if (icon == null)
+ tabIcons.remove(c);
+ else
+ tabIcons.put(c, icon);
+ requestRepaint();
+ }
+
+ /** Set the selected tab */
+ public void setSelectedTab(Component c) {
+ if (c != null && tabs.contains(c) && !selected.equals(c)) {
+ selected = c;
+ fireSelectedTabChange();
+ requestRepaint();
+ }
+ }
+
+ /** Get the selected tab */
+ public Component getSelectedTab() {
+ return selected;
+ }
+
+ /** Invoked when the value of a variable has changed.
+ * @param event Variable change event containing the information about
+ * the changed variable.
+ */
+ 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) {
+
+ // Get the captions
+ String oldCaption = getTabCaption(oldComponent);
+ Resource oldIcon = getTabIcon(oldComponent);
+ String newCaption = getTabCaption(newComponent);
+ Resource newIcon = getTabIcon(newComponent);
+
+ // Get the locations
+ int oldLocation = -1;
+ int newLocation = -1;
+ int location = 0;
+ for (Iterator i = tabs.iterator(); i.hasNext();) {
+ 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);
+ addComponent(newComponent);
+ tabs.remove(newComponent);
+ tabs.add(oldLocation, newComponent);
+ setTabCaption(newComponent, oldCaption);
+ setTabIcon(newComponent, oldIcon);
+ } else {
+ if (oldLocation > newLocation) {
+ tabs.remove(oldComponent);
+ tabs.add(newLocation, oldComponent);
+ tabs.remove(newComponent);
+ tabs.add(oldLocation, newComponent);
+ } else {
+ tabs.remove(newComponent);
+ tabs.add(oldLocation, newComponent);
+ tabs.remove(oldComponent);
+ tabs.add(newLocation, oldComponent);
+ }
+ setTabCaption(newComponent, oldCaption);
+ setTabIcon(newComponent, oldIcon);
+ setTabCaption(oldComponent, newCaption);
+ setTabIcon(oldComponent, 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 (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ /** 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3258129141914940469L;
+
+ /** New instance of selected tab change event
+ * @param source Source of the event.
+ */
+ public SelectedTabChangeEvent(Component source) {
+ super(source);
+ }
+
+ /** Select where the event occurred
+ * @return Source of the event.
+ */
+ public Select getSelect() {
+ return (Select) getSource();
+ }
+ }
+
+ /** Selected Tab Change Event listener
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public interface SelectedTabChangeListener {
+
+ /** Visible tab in tab sheet has has been changed.
+ * @param event Selected tab change event.
+ */
+ public void selectedTabChange(SelectedTabChangeEvent event);
+ }
+
+ /** Add selected tab change listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(SelectedTabChangeListener listener) {
+ addListener(
+ SelectedTabChangeEvent.class,
+ listener,
+ SELECTED_TAB_CHANGE_METHOD);
+ }
+
+ /** Remove selected tab change listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(SelectedTabChangeListener listener) {
+ removeListener(
+ SelectedTabChangeEvent.class,
+ listener,
+ SELECTED_TAB_CHANGE_METHOD);
+ }
+
+ /** Emit options change event. */
+ protected void fireSelectedTabChange() {
+ fireEvent(new SelectedTabChangeEvent(this));
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/Table.java b/src/com/itmill/toolkit/ui/Table.java
new file mode 100644
index 0000000000..08f4be3cf1
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Table.java
@@ -0,0 +1,2114 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+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.StringTokenizer;
+
+import com.itmill.toolkit.data.Container;
+import com.itmill.toolkit.data.Item;
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.data.util.ContainerOrderedWrapper;
+import com.itmill.toolkit.data.util.IndexedContainer;
+import com.itmill.toolkit.event.Action;
+import com.itmill.toolkit.terminal.KeyMapper;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Resource;
+
+/**
+ * Table component is used for representing data or components in pageable and
+ * selectable table.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@ @since 3.0
+ */
+public class Table extends Select implements Action.Container,
+ Container.Ordered, Container.Sortable {
+
+ 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
+ * behaviour. </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 = Select.ITEM_CAPTION_MODE_ID;
+
+ /**
+ * Row caption mode: Item-objects toString() is used as row caption.
+ */
+ public static final int ROW_HEADER_MODE_ITEM = Select.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 = Select.ITEM_CAPTION_MODE_INDEX;
+
+ /**
+ * Row caption mode: Item captions are explicitly specified.
+ */
+ public static final int ROW_HEADER_MODE_EXPLICIT = Select.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 = Select.ITEM_CAPTION_MODE_PROPERTY;
+
+ /**
+ * Row caption mode: Only icons are shown, the captions are hidden.
+ */
+ public static final int ROW_HEADER_MODE_ICON_ONLY = Select.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 = Select.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 KeyMapper columnIdMap = new KeyMapper();
+
+ /** Holds visible column propertyIds - in order */
+ private LinkedList visibleColumns = new LinkedList();
+
+ /** Holds propertyIds of currently collapsed columns */
+ private HashSet collapsedColumns = new HashSet();
+
+ /** Holds headers for visible columns (by propertyId) */
+ private HashMap columnHeaders = new HashMap();
+
+ /** Holds icons for visible columns (by propertyId) */
+ private HashMap columnIcons = new HashMap();
+
+ /** Holds alignments for visible columns (by propertyId) */
+ private HashMap columnAlignments = new HashMap();
+
+ /** Holds value of property pageLength. 0 disables paging. */
+ private int pageLength = 0;
+
+ /** 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 pageBuffering. */
+ private boolean pageBuffering = false;
+
+ /** Holds value of property selectable. */
+ private boolean selectable = false;
+
+ /** Holds value of property columnHeaderMode. */
+ private int columnHeaderMode = COLUMN_HEADER_MODE_HIDDEN;
+
+ /** True iff the row captions are hidden. */
+ private boolean rowCaptionsAreHidden = true;
+
+ /** Page contents buffer used in buffered mode */
+ private Object[][] pageBuffer = null;
+
+ /**
+ * List of properties listened - the list is kept to release the listeners
+ * later.
+ */
+ private LinkedList listenedProperties = null;
+
+ /** List of visible components - the is used for needsRepaint calculation. */
+ private LinkedList visibleComponents = null;
+
+ /** List of action handlers */
+ private LinkedList 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;
+
+ /* Table constructors *************************************************** */
+
+ /** Create new empty table */
+ public Table() {
+ setRowHeaderMode(ROW_HEADER_MODE_HIDDEN);
+ }
+
+ /** Create new empty table with caption. */
+ public Table(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /** Create new table with caption and connect it to a Container. */
+ public Table(String caption, Container dataSource) {
+ this();
+ setCaption(caption);
+ setContainerDataSource(dataSource);
+ }
+
+ /* Table functionality ************************************************** */
+
+ /**
+ * Get the array of visible column property id:s.
+ *
+ * <p>
+ * The columns are show in the order of their appearance in this array
+ * </p>
+ *
+ * @return Value of property availableColumns.
+ */
+ public Object[] getVisibleColumns() {
+ if (this.visibleColumns == null) {
+ return null;
+ }
+ return this.visibleColumns.toArray();
+ }
+
+ /**
+ * Set the array of visible column property id:s.
+ *
+ * <p>
+ * The columns are show in the order of their appearance in this array
+ * </p>
+ *
+ * @param availableColumns
+ * 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");
+
+ // Check that the new visible columns contains no nulls and properties
+ // exist
+ Collection properties = getContainerPropertyIds();
+ for (int i = 0; i < visibleColumns.length; i++) {
+ if (visibleColumns[i] == null)
+ throw new NullPointerException("Properties must be non-nulls");
+ else if (!properties.contains(visibleColumns[i]))
+ throw new IllegalArgumentException(
+ "Properties must exist in the Container, missing property: "
+ + visibleColumns[i]);
+ }
+
+ // If this is called befor the constructor is finished, it might be
+ // uninitialized
+ LinkedList newVC = new LinkedList();
+ for (int i = 0; i < visibleColumns.length; i++) {
+ newVC.add(visibleColumns[i]);
+ }
+
+ // Remove alignments, icons and headers from hidden columns
+ if (this.visibleColumns != null)
+ for (Iterator i = this.visibleColumns.iterator(); i.hasNext();) {
+ Object col = i.next();
+ if (!newVC.contains(col)) {
+ setColumnHeader(col, null);
+ setColumnAlignment(col, null);
+ setColumnIcon(col, null);
+ }
+ }
+
+ this.visibleColumns = newVC;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Get 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>ROW_HEADER_MODE_EXPLICIT</code> or
+ * <code>ROW_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 Array of column headers.
+ */
+ public String[] getColumnHeaders() {
+ if (this.columnHeaders == null) {
+ return null;
+ }
+ String[] headers = new String[this.visibleColumns.size()];
+ int i = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext(); i++) {
+ headers[i] = (String) this.columnHeaders.get(it.next());
+ }
+ return headers;
+ }
+
+ /**
+ * Set 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>ROW_HEADER_MODE_EXPLICIT</code> or
+ * <code>ROW_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
+ * Array of column headers that match the
+ * <code>getVisibleColumns()</code>.
+ */
+ public void setColumnHeaders(String[] columnHeaders) {
+
+ if (columnHeaders.length != this.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 (Iterator it = this.visibleColumns.iterator(); it.hasNext()
+ && i < columnHeaders.length; i++) {
+ this.columnHeaders.put(it.next(), columnHeaders[i]);
+ }
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Get 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>ROW_HEADER_MODE_EXPLICIT</code> or
+ * <code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
+ * headers with icons.
+ * </p>
+ *
+ * @return Array of icons that match the <code>getVisibleColumns()</code>.
+ */
+ public Resource[] getColumnIcons() {
+ if (this.columnIcons == null) {
+ return null;
+ }
+ Resource[] icons = new Resource[this.visibleColumns.size()];
+ int i = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext(); i++) {
+ icons[i] = (Resource) this.columnIcons.get(it.next());
+ }
+
+ return icons;
+ }
+
+ /**
+ * Set 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>ROW_HEADER_MODE_EXPLICIT</code> or
+ * <code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
+ * headers with icons.
+ * </p>
+ *
+ * @param columnIcons
+ * Array of icons that match the <code>getVisibleColumns()</code>.
+ */
+ public void setColumnIcons(Resource[] columnIcons) {
+
+ if (columnIcons.length != this.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 (Iterator it = this.visibleColumns.iterator(); it.hasNext()
+ && i < columnIcons.length; i++) {
+ this.columnIcons.put(it.next(), columnIcons[i]);
+ }
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Get 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 Column alignments array.
+ */
+ public String[] getColumnAlignments() {
+ if (this.columnAlignments == null) {
+ return null;
+ }
+ String[] alignments = new String[this.visibleColumns.size()];
+ int i = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext(); i++) {
+ alignments[i++] = getColumnAlignment(it.next());
+ }
+
+ return alignments;
+ }
+
+ /**
+ * Set 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
+ * Column alignments array.
+ */
+ public void setColumnAlignments(String[] columnAlignments) {
+
+ if (columnAlignments.length != this.visibleColumns.size())
+ throw new IllegalArgumentException(
+ "The length of the alignments array must match the number of visible columns");
+
+ // Check all alignments
+ for (int i = 0; i < columnAlignments.length; i++) {
+ 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");
+ }
+
+ // Reset alignments
+ HashMap newCA = new HashMap();
+ int i = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext()
+ && i < columnAlignments.length; i++) {
+ newCA.put(it.next(), columnAlignments[i]);
+ }
+ this.columnAlignments = newCA;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Get the page length.
+ *
+ * <p>
+ * Setting page length 0 disables paging.
+ * </p>
+ *
+ * @return Lenght of one page.
+ */
+ public int getPageLength() {
+ return this.pageLength;
+ }
+
+ /**
+ * Set the page length.
+ *
+ * <p>
+ * Setting page length 0 disables paging. The page length defaults to 0 (no
+ * paging).
+ * </p>
+ *
+ * @param Lenght
+ * of one page.
+ */
+ public void setPageLength(int pageLength) {
+ if (pageLength >= 0 && this.pageLength != pageLength) {
+ this.pageLength = pageLength;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+ }
+
+ /**
+ * Getter for property currentPageFirstItem.
+ *
+ * @return Value of property currentPageFirstItem.
+ */
+ public Object getCurrentPageFirstItemId() {
+
+ // Priorise index over id if indexes are supported
+ if (items instanceof Container.Indexed) {
+ 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 currentPageFirstItem.
+ *
+ * @param currentPageFirstItem
+ * New value of property currentPageFirstItem.
+ */
+ public void setCurrentPageFirstItemId(Object currentPageFirstItemId) {
+
+ // Get the corresponding index
+ int index = -1;
+ try {
+ index = ((Container.Indexed) items)
+ .indexOfId(currentPageFirstItemId);
+ } catch (ClassCastException e) {
+
+ // If the table item container does not have index, we have to
+ // calculate 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 successfull
+ if (index >= 0) {
+ this.currentPageFirstItemId = currentPageFirstItemId;
+ this.currentPageFirstItemIndex = index;
+ }
+
+ // Assure visual refresh
+ refreshCurrentPage();
+
+ }
+
+ /**
+ * 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 (Resource) this.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)
+ this.columnIcons.remove(propertyId);
+ else
+ this.columnIcons.put(propertyId, icon);
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * 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 = (String) this.columnHeaders.get(propertyId);
+ if ((header == null && this.getColumnHeaderMode() == COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID)
+ || this.getColumnHeaderMode() == COLUMN_HEADER_MODE_ID) {
+ header = propertyId.toString();
+ }
+
+ return header;
+ }
+
+ /**
+ * Sets 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) {
+ this.columnHeaders.remove(propertyId);
+ return;
+ }
+ this.columnHeaders.put(propertyId, header);
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * 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) {
+ String a = (String) this.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) {
+
+ // Check 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)) {
+ this.columnAlignments.remove(propertyId);
+ return;
+ }
+
+ this.columnAlignments.put(propertyId, alignment);
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * 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.
+ */
+ public void setColumnCollapsed(Object propertyId, boolean collapsed)
+ throws IllegalAccessException {
+ if (!this.isColumnCollapsingAllowed()) {
+ throw new IllegalAccessException("Column collapsing not allowed!");
+ }
+
+ if (collapsed)
+ this.collapsedColumns.add(propertyId);
+ else
+ this.collapsedColumns.remove(propertyId);
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Check if column collapsing is allowed.
+ *
+ * @return true if columns can be collapsed; false otherwise.
+ */
+ public boolean isColumnCollapsingAllowed() {
+ return this.columnCollapsingAllowed;
+ }
+
+ /**
+ * Sets whether column collapsing is allowed or not.
+ *
+ * @param collapsingAllowed
+ * specifies whether column collapsing is allowed.
+ */
+ public void setColumnCollapsingAllowed(boolean collapsingAllowed) {
+ this.columnCollapsingAllowed = collapsingAllowed;
+
+ if (!collapsingAllowed)
+ collapsedColumns.clear();
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Check if column reordering is allowed.
+ *
+ * @return true if columns can be reordered; false otherwise.
+ */
+ public boolean isColumnReorderingAllowed() {
+ return this.columnReorderingAllowed;
+ }
+
+ /**
+ * Sets whether column reordering is allowed or not.
+ *
+ * @param reorderingAllowed
+ * specifies whether column reordering is allowed.
+ */
+ public void setColumnReorderingAllowed(boolean reorderingAllowed) {
+ this.columnReorderingAllowed = reorderingAllowed;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /*
+ * 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 || !this.isColumnReorderingAllowed()) {
+ return;
+ }
+ LinkedList newOrder = new LinkedList();
+ for (int i = 0; i < columnOrder.length; i++) {
+ if (columnOrder[i] != null
+ && this.visibleColumns.contains(columnOrder[i])) {
+ this.visibleColumns.remove(columnOrder[i]);
+ newOrder.add(columnOrder[i]);
+ }
+ }
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext();) {
+ Object columnId = it.next();
+ if (!newOrder.contains(columnId))
+ newOrder.add(columnId);
+ }
+ this.visibleColumns = newOrder;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Getter for property currentPageFirstItem.
+ *
+ * @return Value of property currentPageFirstItem.
+ */
+ public int getCurrentPageFirstItemIndex() {
+ return this.currentPageFirstItemIndex;
+ }
+
+ /**
+ * Setter for property currentPageFirstItem.
+ *
+ * @param newIndex
+ * New value of property currentPageFirstItem.
+ */
+ public void setCurrentPageFirstItemIndex(int newIndex) {
+
+ // Ensure that the new value is valid
+ if (newIndex < 0)
+ newIndex = 0;
+ if (newIndex >= size())
+ newIndex = size() - 1;
+
+ // Refresh first item id
+ if (items instanceof Container.Indexed) {
+ try {
+ currentPageFirstItemId = ((Container.Indexed) items)
+ .getIdByIndex(newIndex);
+ } catch (IndexOutOfBoundsException e) {
+ currentPageFirstItemId = null;
+ }
+ this.currentPageFirstItemIndex = newIndex;
+ } else {
+
+ // For containers not supporting indexes, we must iterate the
+ // container forwards / backwards
+ // next available item forward or backward
+
+ this.currentPageFirstItemId = ((Container.Ordered) items).firstItemId();
+
+ // Go forwards in the middle of the list (respect borders)
+ while (this.currentPageFirstItemIndex < newIndex
+ && !((Container.Ordered) items)
+ .isLastId(currentPageFirstItemId)) {
+ this.currentPageFirstItemIndex++;
+ currentPageFirstItemId = ((Container.Ordered) items)
+ .nextItemId(currentPageFirstItemId);
+ }
+
+ // If we did hit the border
+ if (((Container.Ordered) items).isLastId(currentPageFirstItemId)) {
+ this.currentPageFirstItemIndex = size() - 1;
+ }
+
+ // Go backwards in the middle of the list (respect borders)
+ while (this.currentPageFirstItemIndex > newIndex
+ && !((Container.Ordered) items)
+ .isFirstId(currentPageFirstItemId)) {
+ this.currentPageFirstItemIndex--;
+ currentPageFirstItemId = ((Container.Ordered) items)
+ .prevItemId(currentPageFirstItemId);
+ }
+
+ // If we did hit the border
+ if (((Container.Ordered) items).isFirstId(currentPageFirstItemId)) {
+ this.currentPageFirstItemIndex = 0;
+ }
+
+ // Go forwards once more
+ while (this.currentPageFirstItemIndex < newIndex
+ && !((Container.Ordered) items)
+ .isLastId(currentPageFirstItemId)) {
+ this.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 = this.currentPageFirstItemIndex = size() - 1;
+ }
+ }
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Getter for property pageBuffering.
+ *
+ * @return Value of property pageBuffering.
+ */
+ public boolean isPageBufferingEnabled() {
+ return this.pageBuffering;
+ }
+
+ /**
+ * Setter for property pageBuffering.
+ *
+ * @param pageBuffering
+ * New value of property pageBuffering.
+ */
+ public void setPageBufferingEnabled(boolean pageBuffering) {
+
+ this.pageBuffering = pageBuffering;
+
+ // If page buffering is disabled, clear the buffer
+ if (!pageBuffering)
+ pageBuffer = null;
+ }
+
+ /**
+ * Getter for property selectable.
+ *
+ * <p>
+ * The table is not selectable by default.
+ * </p>
+ *
+ * @return Value of property selectable.
+ */
+ public boolean isSelectable() {
+ return this.selectable;
+ }
+
+ /**
+ * Setter for property selectable.
+ *
+ * <p>
+ * The table is not selectable by default.
+ * </p>
+ *
+ * @param selectable
+ * New value of property selectable.
+ */
+ public void setSelectable(boolean selectable) {
+ if (this.selectable != selectable) {
+ this.selectable = selectable;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Getter for property columnHeaderMode.
+ *
+ * @return Value of property columnHeaderMode.
+ */
+ public int getColumnHeaderMode() {
+ return this.columnHeaderMode;
+ }
+
+ /**
+ * Setter for property columnHeaderMode.
+ *
+ * @param columnHeaderMode
+ * 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;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Refresh the current page contents. If the page buffering is turned off,
+ * it is not necessary to call this explicitely.
+ */
+ public void refreshCurrentPage() {
+
+ // Clear page buffer and notify about the change
+ pageBuffer = null;
+ requestRepaint();
+ }
+
+ /**
+ * Set 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
+ * 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
+ refreshCurrentPage();
+ }
+
+ /**
+ * Get the row header mode.
+ *
+ * @return Row header mode.
+ * @see #setRowHeaderMode(int)
+ */
+ public int getRowHeaderMode() {
+ return rowCaptionsAreHidden ? ROW_HEADER_MODE_HIDDEN
+ : getItemCaptionMode();
+ }
+
+ /**
+ * Add new row to table and fill the visible cells with given values.
+ *
+ * @param cells
+ * Object array that is used for filling the visible cells new
+ * row. The types must be settable to visible column property
+ * types.
+ * @param itemId
+ * 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 {
+
+ Object[] cols = getVisibleColumns();
+
+ // Check that a correct number of cells are given
+ if (cells.length != cols.length)
+ return null;
+
+ // Create 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;
+
+ // Fill the item properties
+ for (int i = 0; i < cols.length; i++)
+ item.getItemProperty(cols[i]).setValue(cells[i]);
+
+ return itemId;
+ }
+
+ /* Overriding select behavior******************************************** */
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Viewer#setContainerDataSource(Container)
+ */
+ public void setContainerDataSource(Container newDataSource) {
+
+ if (newDataSource == null)
+ newDataSource = new IndexedContainer();
+
+ // Assure 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));
+
+ // Reset page position
+ currentPageFirstItemId = null;
+ currentPageFirstItemIndex = 0;
+
+ // Reset column properties
+ if (this.collapsedColumns != null)
+ this.collapsedColumns.clear();
+ setVisibleColumns(getContainerPropertyIds().toArray());
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /* Component basics ***************************************************** */
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @param event
+ * Variable change event containing the information about the
+ * changed variable.
+ */
+ public void changeVariables(Object source, Map variables) {
+
+ super.changeVariables(source, variables);
+
+ // Page start index
+ if (variables.containsKey("firstvisible")) {
+ Integer value = (Integer) variables.get("firstvisible");
+ if (value != null)
+ setCurrentPageFirstItemIndex(value.intValue() - 1);
+ }
+
+ // Actions
+ if (variables.containsKey("action")) {
+ StringTokenizer st = new StringTokenizer((String) variables
+ .get("action"), ",");
+ if (st.countTokens() == 2) {
+ Object itemId = itemIdMapper.get(st.nextToken());
+ Action action = (Action) actionMapper.get(st.nextToken());
+ if (action != null && containsId(itemId)
+ && actionHandlers != null)
+ for (Iterator i = actionHandlers.iterator(); i.hasNext();)
+ ((Action.Handler) i.next()).handleAction(action, this,
+ itemId);
+ }
+ }
+
+ // Sorting
+ boolean doSort = false;
+ if (variables.containsKey("sortcolumn")) {
+ String colId = (String) variables.get("sortcolumn");
+ if (colId != null && !"".equals(colId) && !"null".equals(colId)) {
+ Object id = this.columnIdMap.get(colId);
+ setSortContainerPropertyId(id);
+ doSort = true;
+ }
+ }
+ if (variables.containsKey("sortascending")) {
+ boolean state = ((Boolean) variables.get("sortascending"))
+ .booleanValue();
+ if (state != this.sortAscending) {
+ setSortAscending(state);
+ doSort = true;
+ }
+ }
+ if (doSort)
+ this.sort();
+
+ // Dynamic column hide/show and order
+ // Update visible columns
+ if (this.isColumnCollapsingAllowed()) {
+ if (variables.containsKey("collapsedcolumns")) {
+ try {
+ Object[] ids = (Object[]) variables.get("collapsedcolumns");
+ for (Iterator it = this.visibleColumns.iterator(); it
+ .hasNext();) {
+ this.setColumnCollapsed(it.next(), false);
+ }
+ for (int i = 0; i < ids.length; i++) {
+ this.setColumnCollapsed(columnIdMap.get(ids[i]
+ .toString()), true);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ if (this.isColumnReorderingAllowed()) {
+ if (variables.containsKey("columnorder")) {
+ try {
+ Object[] ids = (Object[]) variables.get("columnorder");
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = columnIdMap.get(ids[i].toString());
+ }
+ this.setColumnOrder(ids);
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Paint the content of this component.
+ *
+ * @param target
+ * Paint target.
+ * @throws PaintException
+ * The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Focus control id
+ if (this.getFocusableId() > 0) {
+ target.addAttribute("focusid", this.getFocusableId());
+ }
+
+ // The tab ordering number
+ if (this.getTabIndex() > 0)
+ target.addAttribute("tabindex", this.getTabIndex());
+
+ // Initialize temps
+ Object[] colids = getVisibleColumns();
+ int cols = colids.length;
+ int first = getCurrentPageFirstItemIndex();
+ int total = size();
+ int pagelen = getPageLength();
+ int colHeadMode = getColumnHeaderMode();
+ boolean colheads = colHeadMode != COLUMN_HEADER_MODE_HIDDEN;
+ boolean rowheads = getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN;
+ Object[][] cells = getVisibleCells();
+ boolean iseditable = this.isEditable();
+
+ // selection support
+ String[] selectedKeys;
+ if (isMultiSelect())
+ selectedKeys = new String[((Set) getValue()).size()];
+ else
+ selectedKeys = new String[(getValue() == null
+ && getNullSelectionItemId() == null ? 0 : 1)];
+ int keyIndex = 0;
+
+ // Table attributes
+ if (isSelectable())
+ target.addAttribute("selectmode", (isMultiSelect() ? "multi"
+ : "single"));
+ else
+ target.addAttribute("selectmode", "none");
+ target.addAttribute("cols", cols);
+ target.addAttribute("rows", cells[0].length);
+ target.addAttribute("totalrows", total);
+ if (pagelen != 0)
+ target.addAttribute("pagelength", pagelen);
+ if (colheads)
+ target.addAttribute("colheaders", true);
+ if (rowheads)
+ target.addAttribute("rowheaders", true);
+
+ // Columns
+ target.startTag("cols");
+ Collection sortables = getSortableContainerPropertyIds();
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext();) {
+ Object columnId = it.next();
+ if (!isColumnCollapsed(columnId)) {
+ target.startTag("ch");
+ if (colheads) {
+ if (this.getColumnIcon(columnId) != null)
+ target.addAttribute("icon", this
+ .getColumnIcon(columnId));
+ if (sortables.contains(columnId))
+ target.addAttribute("sortable", true);
+ String header = (String) this.getColumnHeader(columnId);
+ target.addAttribute("caption", (header != null ? header
+ : ""));
+ }
+ target.addAttribute("cid", this.columnIdMap.key(columnId));
+ if (!ALIGN_LEFT.equals(this.getColumnAlignment(columnId)))
+ target.addAttribute("align", this
+ .getColumnAlignment(columnId));
+ target.endTag("ch");
+ }
+ }
+ target.endTag("cols");
+
+ // Rows
+ Set actionSet = new LinkedHashSet();
+ boolean selectable = isSelectable();
+ boolean[] iscomponent = new boolean[this.visibleColumns.size()];
+ int iscomponentIndex = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext()
+ && iscomponentIndex < iscomponent.length;) {
+ Object columnId = it.next();
+ Class colType = getType(columnId);
+ iscomponent[iscomponentIndex++] = colType != null
+ && Component.class.isAssignableFrom(colType);
+ }
+ target.startTag("rows");
+ for (int i = 0; i < cells[0].length; i++) {
+ target.startTag("tr");
+ Object itemId = cells[CELL_ITEMID][i];
+
+ // 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]);
+ }
+ if (actionHandlers != null || isSelectable()) {
+ target.addAttribute("key", (String) cells[CELL_KEY][i]);
+ if (isSelected(itemId) && keyIndex < selectedKeys.length) {
+ target.addAttribute("selected", true);
+ selectedKeys[keyIndex++] = (String) cells[CELL_KEY][i];
+ }
+ }
+
+ // Actions
+ if (actionHandlers != null) {
+ target.startTag("al");
+ for (Iterator ahi = actionHandlers.iterator(); ahi.hasNext();) {
+ Action[] aa = ((Action.Handler) ahi.next()).getActions(
+ itemId, this);
+ if (aa != null)
+ for (int ai = 0; ai < aa.length; ai++) {
+ String key = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ target.addSection("ak", key);
+ }
+ }
+ target.endTag("al");
+ }
+
+ // cells
+ int currentColumn = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext(); currentColumn++) {
+ Object columnId = it.next();
+ if (columnId == null || this.isColumnCollapsed(columnId))
+ continue;
+ if ((iscomponent[currentColumn] || iseditable)
+ && Component.class.isInstance(cells[CELL_FIRSTCOL
+ + currentColumn][i])) {
+ Component c = (Component) cells[CELL_FIRSTCOL
+ + currentColumn][i];
+ if (c == null)
+ target.addSection("label", "");
+ else
+ c.paint(target);
+ } else
+ target.addSection("label", (String) cells[CELL_FIRSTCOL
+ + currentColumn][i]);
+ }
+
+ target.endTag("tr");
+ }
+ target.endTag("rows");
+
+ // The select variable is only enabled if selectable
+ if (selectable)
+ target.addVariable(this, "selected", selectedKeys);
+
+ // The cursors are only shown on pageable table
+ if (first != 0 || getPageLength() > 0)
+ target.addVariable(this, "firstvisible", first + 1);
+
+ // Sorting
+ if (getContainerDataSource() instanceof Container.Sortable) {
+ target.addVariable(this, "sortcolumn", this.columnIdMap.key(this.sortContainerPropertyId));
+ target.addVariable(this, "sortascending", this.sortAscending);
+ }
+
+ // Actions
+ if (!actionSet.isEmpty()) {
+ target.startTag("actions");
+ target.addVariable(this, "action", "");
+ for (Iterator it = actionSet.iterator(); it.hasNext();) {
+ Action a = (Action) 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 (this.columnReorderingAllowed) {
+ String[] colorder = new String[this.visibleColumns.size()];
+ int i = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext()
+ && i < colorder.length;) {
+ colorder[i++] = this.columnIdMap.key(it.next());
+ }
+ target.addVariable(this, "columnorder", colorder);
+ }
+ // Available columns
+ if (this.columnCollapsingAllowed) {
+ HashSet ccs = new HashSet();
+ for (Iterator i = visibleColumns.iterator(); i.hasNext();) {
+ Object o = i.next();
+ if (isColumnCollapsed(o))
+ ccs.add(o);
+ }
+ String[] collapsedkeys = new String[ccs.size()];
+ int nextColumn = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext()
+ && nextColumn < collapsedkeys.length;) {
+ Object columnId = it.next();
+ if (this.isColumnCollapsed(columnId)) {
+ collapsedkeys[nextColumn++] = this.columnIdMap
+ .key(columnId);
+ }
+ }
+ target.addVariable(this, "collapsedcolumns", collapsedkeys);
+ target.startTag("visiblecolumns");
+ int i = 0;
+ for (Iterator it = this.visibleColumns.iterator(); it.hasNext(); i++) {
+ Object columnId = it.next();
+ if (columnId != null) {
+ target.startTag("column");
+ target.addAttribute("cid", this.columnIdMap.key(columnId));
+ String head = getColumnHeader(columnId);
+ target.addAttribute("caption", (head != null ? head : ""));
+ if (this.isColumnCollapsed(columnId)) {
+ target.addAttribute("collapsed", true);
+ }
+ target.endTag("column");
+ }
+ }
+ target.endTag("visiblecolumns");
+ }
+ }
+
+ /**
+ * Get UIDL tag corresponding to component.
+ *
+ * @return UIDL tag as string.
+ */
+ public String getTag() {
+ return "table";
+ }
+
+ /** Return cached visible table contents */
+ private Object[][] getVisibleCells() {
+
+ // Return a buffered value if possible
+ if (pageBuffer != null && isPageBufferingEnabled())
+ return pageBuffer;
+
+ // Stop listening the old properties and initialise the list
+ if (listenedProperties == null)
+ listenedProperties = new LinkedList();
+ else
+ for (Iterator i = listenedProperties.iterator(); i.hasNext();) {
+ ((Property.ValueChangeNotifier) i.next()).removeListener(this);
+ }
+
+ // Detach old visible component from the table
+ if (visibleComponents == null)
+ visibleComponents = new LinkedList();
+ else
+ for (Iterator i = visibleComponents.iterator(); i.hasNext();) {
+ ((Component) i.next()).setParent(null);
+ }
+
+ // Collect basic facts about the table page
+ Object[] colids = getVisibleColumns();
+ int cols = colids.length;
+ int pagelen = getPageLength();
+ int firstIndex = getCurrentPageFirstItemIndex();
+ int rows = size();
+ if (rows > 0 && firstIndex >= 0)
+ rows -= firstIndex;
+
+ if (pagelen > 0 && pagelen < rows)
+ rows = pagelen;
+ Object[][] cells = new Object[cols + CELL_FIRSTCOL][rows];
+ if (rows == 0)
+ return cells;
+ Object id = getCurrentPageFirstItemId();
+ int headmode = getRowHeaderMode();
+ boolean[] iscomponent = new boolean[cols];
+ for (int i = 0; i < cols; i++)
+ iscomponent[i] = Component.class
+ .isAssignableFrom(getType(colids[i]));
+
+ // Create 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++) {
+ Object value = null;
+ Property p = getContainerProperty(id, colids[j]);
+ if (p != null) {
+ if (p instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) p)
+ .addListener(this);
+ listenedProperties.add(p);
+ }
+ if (iscomponent[j]) {
+ value = p.getValue();
+ } else if (p != null) {
+ value = getPropertyValue(id, colids[j], p);
+ } else {
+ value = getPropertyValue(id, colids[j], null);
+ }
+ } else {
+ value = "";
+ }
+
+ if (value instanceof Component) {
+ ((Component) value).setParent(this);
+ visibleComponents.add((Component) value);
+ }
+ cells[CELL_FIRSTCOL + j][i] = value;
+
+ }
+ }
+ id = ((Container.Ordered) items).nextItemId(id);
+
+ filledRows++;
+ }
+
+ // Assure that all the rows of the cell-buffer are valid
+ if (filledRows != cells[0].length) {
+ 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;
+ }
+
+ // Save the results to internal buffer iff in buffering mode
+ // to possible conserve memory from large non-buffered pages
+ if (isPageBufferingEnabled())
+ pageBuffer = cells;
+
+ return cells;
+ }
+
+ /**
+ * Get 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.
+ *
+ * @see #setFieldFactory(FieldFactory)
+ * @param rowId
+ * Id of the row (same as item Id)
+ * @param colId
+ * Id of the column
+ * @param property
+ * Property to be presented
+ * @return Object Either formatted value or Component for field.
+ */
+ protected Object getPropertyValue(Object rowId, Object colId,
+ Property property) {
+ if (this.isEditable() && this.fieldFactory != null) {
+ Field f = this.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 itemId
+ * @param property
+ * Property to be formatted
+ * @return 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 *************************************************** */
+
+ /**
+ * @see com.itmill.toolkit.event.Action.Container#addActionHandler(Action.Handler)
+ */
+ public void addActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList();
+ actionMapper = new KeyMapper();
+ }
+
+ if(!actionHandlers.contains(actionHandler)){
+ actionHandlers.add(actionHandler);
+ requestRepaint();
+ }
+
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.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 **************************** */
+
+ /**
+ * @see com.itmill.toolkit.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent)
+ */
+ public void valueChange(Property.ValueChangeEvent event) {
+ super.valueChange(event);
+ requestRepaint();
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component#attach()
+ */
+ public void attach() {
+ super.attach();
+
+ if (visibleComponents != null)
+ for (Iterator i = visibleComponents.iterator(); i.hasNext();)
+ ((Component) i.next()).attach();
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component#attach()
+ */
+ public void detach() {
+ super.detach();
+
+ if (visibleComponents != null)
+ for (Iterator i = visibleComponents.iterator(); i.hasNext();)
+ ((Component) i.next()).detach();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container#removeAllItems()
+ */
+ public boolean removeAllItems() {
+ this.currentPageFirstItemId = null;
+ this.currentPageFirstItemIndex = 0;
+ return super.removeAllItems();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container#removeItem(Object)
+ */
+ public boolean removeItem(Object itemId) {
+ Object nextItemId = ((Container.Ordered) items).nextItemId(itemId);
+ boolean ret = super.removeItem(itemId);
+ if (ret && (itemId != null)
+ && (itemId.equals(this.currentPageFirstItemId))) {
+ this.currentPageFirstItemId = nextItemId;
+ }
+ return ret;
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container#removeContainerProperty(Object)
+ */
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+
+ // If a visible property is removed, remove the corresponding column
+ this.visibleColumns.remove(propertyId);
+ this.columnAlignments.remove(propertyId);
+ this.columnIcons.remove(propertyId);
+ this.columnHeaders.remove(propertyId);
+
+ return super.removeContainerProperty(propertyId);
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @see com.itmill.toolkit.data.Container#addContainerProperty(Object,
+ * Class, Object)
+ *
+ * @param propertyId
+ * Id of the proprty
+ * @param type
+ * The class of the property
+ * @param defaultValue
+ * The default value given for all existing items
+ */
+ public boolean addContainerProperty(Object propertyId, Class type,
+ Object defaultValue) throws UnsupportedOperationException {
+ if (!super.addContainerProperty(propertyId, type, defaultValue))
+ return false;
+ if (!this.visibleColumns.contains(propertyId))
+ this.visibleColumns.add(propertyId);
+ return true;
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @see com.itmill.toolkit.data.Container#addContainerProperty(Object,
+ * Class, Object)
+ *
+ * @param propertyId
+ * Id of the proprty
+ * @param type
+ * The class of the property
+ * @param defaultValue
+ * The default value given for all existing items
+ * @param columnHeader
+ * Explicit header of the column. If explicit header is not
+ * needed, this should be set null.
+ * @param columnIcon
+ * Icon of the column. If icon is not needed, this should be set
+ * null.
+ * @param columnAlignment
+ * Alignment of the column. Null implies align left.
+ */
+ 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;
+ this.setColumnAlignment(propertyId, columnAlignment);
+ this.setColumnHeader(propertyId, columnHeader);
+ this.setColumnIcon(propertyId, columnIcon);
+ return true;
+ }
+
+ /**
+ * Return list of items on the current page
+ *
+ * @see com.itmill.toolkit.ui.Select#getVisibleItemIds()
+ */
+ public Collection getVisibleItemIds() {
+
+ LinkedList visible = new LinkedList();
+
+ 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.itmill.toolkit.data.Container.ItemSetChangeListener#containerItemSetChange(com.itmill.toolkit.data.Container.ItemSetChangeEvent)
+ */
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ pageBuffer = null;
+ super.containerItemSetChange(event);
+ setCurrentPageFirstItemIndex(this.getCurrentPageFirstItemIndex());
+ }
+
+ /**
+ * Container datasource property set change. Table must flush its buffers on
+ * change.
+ *
+ * @see com.itmill.toolkit.data.Container.PropertySetChangeListener#containerPropertySetChange(com.itmill.toolkit.data.Container.PropertySetChangeEvent)
+ */
+ public void containerPropertySetChange(
+ Container.PropertySetChangeEvent event) {
+ pageBuffer = null;
+ super.containerPropertySetChange(event);
+ }
+
+ /**
+ * Adding new items is not supported.
+ *
+ * @see com.itmill.toolkit.ui.Select#setNewItemsAllowed(boolean)
+ * @throws UnsupportedOperationException
+ * if set to true.
+ */
+ public void setNewItemsAllowed(boolean allowNewOptions)
+ throws UnsupportedOperationException {
+ if (allowNewOptions)
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Focusing to this component is not supported.
+ *
+ * @see com.itmill.toolkit.ui.AbstractField#focus()
+ * @throws UnsupportedOperationException
+ * if invoked.
+ */
+ public void focus() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#nextItemId(java.lang.Object)
+ */
+ public Object nextItemId(Object itemId) {
+ return ((Container.Ordered) items).nextItemId(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#prevItemId(java.lang.Object)
+ */
+ public Object prevItemId(Object itemId) {
+ return ((Container.Ordered) items).prevItemId(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#firstItemId()
+ */
+ public Object firstItemId() {
+ return ((Container.Ordered) items).firstItemId();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#lastItemId()
+ */
+ public Object lastItemId() {
+ return ((Container.Ordered) items).lastItemId();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#isFirstId(java.lang.Object)
+ */
+ public boolean isFirstId(Object itemId) {
+ return ((Container.Ordered) items).isFirstId(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#isLastId(java.lang.Object)
+ */
+ public boolean isLastId(Object itemId) {
+ return ((Container.Ordered) items).isLastId(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#addItemAfter(java.lang.Object)
+ */
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ return ((Container.Ordered) items).addItemAfter(previousItemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Ordered#addItemAfter(java.lang.Object,
+ * java.lang.Object)
+ */
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ return ((Container.Ordered) items).addItemAfter(previousItemId,
+ newItemId);
+ }
+
+ /**
+ * Get the FieldFactory that is used to create editor for table cells.
+ *
+ * The FieldFactory is only used if the Table is editable.
+ *
+ * @see #isEditable
+ * @return FieldFactory used to create the Field instances.
+ */
+ public FieldFactory getFieldFactory() {
+ return fieldFactory;
+ }
+
+ /**
+ * Set 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.
+ *
+ * @see #isEditable
+ * @see BaseFieldFactory
+ * @param fieldFactory
+ * The field factory to set
+ */
+ public void setFieldFactory(FieldFactory fieldFactory) {
+ this.fieldFactory = fieldFactory;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * 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.
+ *
+ * @see Field
+ * @see FieldFactory
+ * @return true if table is editable, false oterwise.
+ */
+ public boolean isEditable() {
+ return editable;
+ }
+
+ /**
+ * Set 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.
+ *
+ * @see Field
+ * @see FieldFactory
+ * @param editable
+ * true if table should be editable by user.
+ */
+ public void setEditable(boolean editable) {
+ this.editable = editable;
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Sort table.
+ *
+ * @see com.itmill.toolkit.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ *
+ * @throws UnsupportedOperationException
+ * if the container data source does not implement
+ * Container.Sortable
+ */
+ public void sort(Object[] propertyId, boolean[] ascending)
+ throws UnsupportedOperationException {
+ Container c = getContainerDataSource();
+ if (c instanceof Container.Sortable) {
+ int pageIndex = this.getCurrentPageFirstItemIndex();
+ ((Container.Sortable) c).sort(propertyId, ascending);
+ setCurrentPageFirstItemIndex(pageIndex);
+ } else if (c != null) {
+ throw new UnsupportedOperationException(
+ "Underlying Data does not allow sorting");
+ }
+ }
+
+ /**
+ * Sort 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[] { this.sortContainerPropertyId },
+ new boolean[] { this.sortAscending });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.data.Container.Sortable#getSortableContainerPropertyIds()
+ */
+ public Collection getSortableContainerPropertyIds() {
+ Container c = getContainerDataSource();
+ if (c instanceof Container.Sortable && !isSortDisabled()) {
+ return ((Container.Sortable) c).getSortableContainerPropertyIds();
+ } else {
+ return new LinkedList();
+ }
+ }
+
+ /**
+ * Get the currently sorted column property ID.
+ *
+ * @return Container property id of the currently sorted column.
+ */
+ public Object getSortContainerPropertyId() {
+ return this.sortContainerPropertyId;
+ }
+
+ /**
+ * Set the currently sorted column property id.
+ *
+ * @param propertyId
+ * Container property id of the currently sorted column.
+ */
+ public void setSortContainerPropertyId(Object propertyId) {
+ if ((this.sortContainerPropertyId != null && !this.sortContainerPropertyId
+ .equals(propertyId))
+ || (this.sortContainerPropertyId == null && propertyId != null)) {
+ this.sortContainerPropertyId = propertyId;
+ sort();
+ }
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /**
+ * Is the table currently sorted in ascending order.
+ *
+ * @return <code>true</code> if ascending, <code>false</code> if
+ * descending
+ */
+ public boolean isSortAscending() {
+ return this.sortAscending;
+ }
+
+ /**
+ * Set the table in ascending order.
+ *
+ * @param ascending
+ * <code>true</code> if ascending, <code>false</code> if
+ * descending
+ */
+ public void setSortAscending(boolean ascending) {
+ if (this.sortAscending != ascending) {
+ this.sortAscending = ascending;
+ sort();
+ }
+
+ // Assure visual refresh
+ refreshCurrentPage();
+ }
+
+ /** Is sorting disabled alltogether.
+ *
+ * True iff no sortable columns are given even in the case where datasource would support this.
+ *
+ * @return True iff sorting is disabled.
+ */
+ public boolean isSortDisabled() {
+ return sortDisabled;
+ }
+
+
+ /** Disable sorting alltogether.
+ *
+ * To disable sorting alltogether, 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;
+ refreshCurrentPage();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/itmill/toolkit/ui/TextField.java b/src/com/itmill/toolkit/ui/TextField.java
new file mode 100644
index 0000000000..86e3f14d50
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/TextField.java
@@ -0,0 +1,394 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.text.Format;
+import java.util.Map;
+
+import com.itmill.toolkit.data.Property;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.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.itmill.toolkit.data.Buffered} interface. A
+ * <code>TextField</code> is in write-through mode by default, so
+ * {@link com.itmill.toolkit.ui.AbstractField#setWriteThrough(boolean)}
+ * must be called to enable buffering.</p>
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+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;
+
+ /* Constructors **************************************************** */
+
+ /** Constructs an empty <code>TextField</code> with no caption. */
+ public TextField() {
+ setValue("");
+ }
+
+ /** Constructs an empty <code>TextField</code> with given caption. */
+ 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 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.itmill.toolkit.data.Property.Viewer#setPropertyDataSource(Property)}
+ * is called to bind it.
+ *
+ * @param caption caption <code>String</code> for the editor
+ * @param text initial text content of the editor
+ */
+ public TextField(String caption, String value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+ /* Component basic features ********************************************* */
+
+ /* Paint this component.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+
+ // Set secret attribute
+ if (this.isSecret())
+ target.addAttribute("secret", true);
+
+ // Add the number of column and rows
+ int c = getColumns();
+ 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);
+ }
+
+ // Add 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);
+ }
+
+ /** Get the formatted dtring value.
+ * Sets the field value by using the assigned Format.
+ * @param value to be formatted
+ * @return Formatted value
+ * @see #setFormat(Format)
+ * @see Format
+ */
+ protected String getFormattedValue() {
+ Object value = getValue();
+ if (this.format != null && value != null)
+ try {
+ return this.format.format(value);
+ } catch (IllegalArgumentException ignored) {
+ // Ignored exception
+ }
+ if (value != null)
+ return value.toString();
+ return null;
+ }
+
+ /* Gets the components UIDL tag string.
+ * Don't add a JavaDoc comment here, we use the default documentation
+ * from implemented interface.
+ */
+ 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.
+ */
+ public void changeVariables(Object source, Map variables) {
+
+ // Set 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");
+ String oldValue = getFormattedValue();
+ if (newValue != null
+ && (oldValue == null || isNullSettingAllowed())
+ && newValue.equals(getNullRepresentation()))
+ newValue = null;
+ if (newValue != oldValue
+ && (newValue == null || !newValue.equals(oldValue)))
+ setValue(newValue);
+ }
+
+ }
+
+ /* 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.
+ *
+ * @param the number of columns for this editor
+ */
+ public int getColumns() {
+ return this.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.
+ *
+ * @return number of explicitly set columns
+ */
+ 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 this.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 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 this.wordwrap;
+ }
+
+ /** Sets the editor's word-wrap mode on or off.
+ *
+ * @param wordwrap 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.
+ */
+ public Class getType() {
+ return String.class;
+ }
+ /** Get the secret property on and off.
+ * If a field is used to enter secretinformation
+ * the information is not echoed to display.
+ * @return true if the field is used to enter secret information, false otherwise.
+ */
+ public boolean isSecret() {
+ return secret;
+ }
+
+ /** Set the secret property on and off.
+ * If a field is used to enter secretinformation
+ * the information is not echoed to display.
+ * @param secret value specifying if the field is used to enter secret information.
+ */
+ public void setSecret(boolean secret) {
+ this.secret = secret;
+ }
+
+ /** Get 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>
+ *
+ * @see TextField#isNullSettingAllowed()
+ * @return String Textual representation for null strings.
+ */
+ 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 allways 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 allways
+ * 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>
+ *
+ * @see TextField#setNullSettingAllowed(boolean)
+ * @param nullRepresentation Textual representation for null strings.
+ */
+ public void setNullRepresentation(String nullRepresentation) {
+ this.nullRepresentation = nullRepresentation;
+ }
+
+ /** Set the null conversion mode.
+ *
+ * <p>If this property is true, writing null-representation string to text
+ * field allways 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 allways
+ * converted to null-values.
+ * @see TextField#getNullRepresentation()
+ */
+ public void setNullSettingAllowed(boolean nullSettingAllowed) {
+ this.nullSettingAllowed = nullSettingAllowed;
+ }
+
+ /** Get the value formatter of TextField.
+ *
+ *
+ * @return The Format used to format the value.
+ */
+ public Format getFormat() {
+ return format;
+ }
+
+ /** Get the value formatter of TextField.
+ *
+ * @param The Format used to format the value. Null disables the formatting.
+ */
+ public void setFormat(Format format) {
+ this.format = format;
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/Tree.java b/src/com/itmill/toolkit/ui/Tree.java
new file mode 100644
index 0000000000..b3371b1aa7
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Tree.java
@@ -0,0 +1,763 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+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.itmill.toolkit.data.Container;
+import com.itmill.toolkit.data.util.ContainerHierarchicalWrapper;
+import com.itmill.toolkit.event.Action;
+import com.itmill.toolkit.terminal.KeyMapper;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Resource;
+
+/** MenuTree component.
+ * MenuTree 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
+ */
+public class Tree extends Select implements Container.Hierarchical, Action.Container {
+
+ /* Static members ***************************************************** */
+
+ 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 (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ e.printStackTrace();
+ throw new java.lang.RuntimeException(
+ "Internal error, please report");
+ }
+ }
+
+ /* Private members **************************************************** */
+
+ /** Set of expanded nodes */
+ private HashSet expanded = new HashSet();
+
+ /** List of action handlers */
+ private LinkedList actionHandlers = null;
+
+ /** Action mapper */
+ private KeyMapper actionMapper = null;
+
+ /** Is the tree selectable */
+ private boolean selectable = true;
+
+ /* Tree constructors ************************************************** */
+
+ /** Create new empty tree */
+ public Tree() {
+ }
+
+ /** Create new empty tree with caption. */
+ public Tree(String caption) {
+ setCaption(caption);
+ }
+
+ /** Create new tree with caption and connect it to a Container. */
+ public Tree(String caption, Container dataSource) {
+ setCaption(caption);
+ setContainerDataSource(dataSource);
+ }
+
+ /* Expanding and collapsing ******************************************* */
+
+ /** Check is an item is expanded
+ * @return true iff the item is expanded
+ */
+ public boolean isExpanded(Object itemId) {
+ return expanded.contains(itemId);
+ }
+
+ /** Expand an item.
+ *
+ * @return True iff the expand operation succeeded
+ */
+ public boolean expandItem(Object itemId) {
+
+ // 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;
+
+ // Expand
+ expanded.add(itemId);
+ requestRepaint();
+ fireExpandEvent(itemId);
+
+ return true;
+ }
+
+ /** Expand items recursively
+ *
+ * Expands all the children recursively starting from an item.
+ * Operation succeeds only if all expandable items are expanded.
+ * @return True iff the expand operation succeeded
+ */
+ public boolean expandItemsRecursively(Object startItemId) {
+
+ boolean result = true;
+
+ // Initial stack
+ Stack todo = new Stack();
+ todo.add(startItemId);
+
+ // Expand recursively
+ while (!todo.isEmpty()) {
+ Object id = todo.pop();
+ if (areChildrenAllowed(id) && !expandItem(id)) {
+ result = false;
+ }
+ if (hasChildren(id)) {
+ todo.addAll(getChildren(id));
+ }
+ }
+
+ return result;
+ }
+
+ /** Collapse an item.
+ *
+ * @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;
+ }
+
+ /** Collapse items recursively
+ *
+ * Collapse all the children recursively starting from an item.
+ * Operation succeeds only if all expandable items are collapsed.
+ * @return True iff the collapse operation succeeded
+ */
+ public boolean collapseItemsRecursively(Object startItemId) {
+
+ boolean result = true;
+
+ // Initial stack
+ Stack todo = new Stack();
+ todo.add(startItemId);
+
+ // Collapse recursively
+ while (!todo.isEmpty()) {
+ 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 Value of property selectable.
+ */
+ public boolean isSelectable() {
+ return this.selectable;
+ }
+
+ /** Setter for property selectable.
+ *
+ * <p>The tree is selectable by default.</p>
+ *
+ * @param selectable New value of property selectable.
+ */
+ public void setSelectable(boolean selectable) {
+ if (this.selectable != selectable) {
+ this.selectable = selectable;
+ requestRepaint();
+ }
+ }
+
+ /* Component API ****************************************************** */
+
+ /**
+ * @see com.itmill.toolkit.ui.AbstractComponent#getTag()
+ */
+ public String getTag() {
+ return "tree";
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.VariableOwner#changeVariables(Object source, Map variables)
+ */
+ public void changeVariables(Object source, Map variables) {
+
+ // Collapse nodes
+ if (variables.containsKey("collapse")) {
+ String[] keys = (String[]) variables.get("collapse");
+ for (int i = 0; i < keys.length; i++) {
+ Object id = itemIdMapper.get(keys[i]);
+ if (id != null)
+ collapseItem(id);
+ }
+ }
+
+ // Expand nodes
+ if (variables.containsKey("expand")) {
+ String[] keys = (String[]) variables.get("expand");
+ for (int i = 0; i < keys.length; i++) {
+ Object id = itemIdMapper.get(keys[i]);
+ if (id != null)
+ expandItem(id);
+ }
+ }
+
+ // Selections are handled by the select component
+ super.changeVariables(source, variables);
+
+ // Actions
+ if (variables.containsKey("action")) {
+
+ StringTokenizer st =
+ new StringTokenizer((String) variables.get("action"), ",");
+ if (st.countTokens() == 2) {
+ Object itemId = itemIdMapper.get(st.nextToken());
+ Action action = (Action) actionMapper.get(st.nextToken());
+ if (action != null
+ && containsId(itemId)
+ && actionHandlers != null)
+ for (Iterator i = actionHandlers.iterator();
+ i.hasNext();
+ )
+ ((Action.Handler) i.next()).handleAction(
+ action,
+ this,
+ itemId);
+ }
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.AbstractComponent#paintContent(PaintTarget)
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Focus control id
+ if (this.getFocusableId() > 0) {
+ target.addAttribute("focusid", this.getFocusableId());
+ }
+
+ // The tab ordering number
+ if (this.getTabIndex() > 0)
+ target.addAttribute("tabindex", this.getTabIndex());
+
+
+ // Paint tree attributes
+ if (isSelectable())
+ target.addAttribute(
+ "selectmode",
+ (isMultiSelect() ? "multi" : "single"));
+ else
+ target.addAttribute("selectmode", "none");
+ if (isNewItemsAllowed())
+ target.addAttribute("allownewitem", true);
+
+ // Initialize variables
+ Set actionSet = new LinkedHashSet();
+ String[] selectedKeys;
+ if (isMultiSelect())
+ selectedKeys = new String[((Set) getValue()).size()];
+ else
+ selectedKeys = new String[(getValue() == null ? 0 : 1)];
+ int keyIndex = 0;
+ LinkedList expandedKeys = new LinkedList();
+
+ // Iterate trough hierarchical tree using a stack of iterators
+ Stack iteratorStack = new Stack();
+ Collection ids = rootItemIds();
+ if (ids != null)
+ iteratorStack.push(ids.iterator());
+ while (!iteratorStack.isEmpty()) {
+
+ // Get the iterator for current tree level
+ Iterator i = (Iterator) iteratorStack.peek();
+
+ // If the level is finished, back to previous tree level
+ if (!i.hasNext()) {
+
+ // Remove used iterator from the stack
+ iteratorStack.pop();
+
+ // Close node
+ if (!iteratorStack.isEmpty())
+ target.endTag("node");
+ }
+
+ // Add the item on current level
+ else {
+ Object itemId = i.next();
+
+ // Start the item / node
+ boolean isNode = areChildrenAllowed(itemId);
+ if (isNode)
+ target.startTag("node");
+ else
+ target.startTag("leaf");
+
+ // Add attributes
+ target.addAttribute("caption", getItemCaption(itemId));
+ Resource icon = getItemIcon(itemId);
+ if (icon != null)
+ target.addAttribute("icon", getItemIcon(itemId));
+ String key = itemIdMapper.key(itemId);
+ target.addAttribute("key", key);
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ selectedKeys[keyIndex++] = key;
+ }
+ if (areChildrenAllowed(itemId) && isExpanded(itemId)) {
+ target.addAttribute("expanded", true);
+ expandedKeys.add(key);
+ }
+
+ // Actions
+ if (actionHandlers != null) {
+ target.startTag("al");
+ for (Iterator ahi = actionHandlers.iterator();
+ ahi.hasNext();
+ ) {
+ Action[] aa =
+ ((Action.Handler) ahi.next()).getActions(
+ itemId,
+ this);
+ if (aa != null)
+ for (int ai = 0; ai < aa.length; ai++) {
+ String akey = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ target.addSection("ak", akey);
+ }
+ }
+ target.endTag("al");
+ }
+
+ // Add 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.startTag("actions");
+ target.addVariable(this, "action", "");
+ for (Iterator i = actionSet.iterator(); i.hasNext();) {
+ Action a = (Action) 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");
+ }
+
+ // 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 ***************************************** */
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#areChildrenAllowed(Object)
+ */
+ public boolean areChildrenAllowed(Object itemId) {
+ return ((Container.Hierarchical) items).areChildrenAllowed(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#getChildren(Object)
+ */
+ public Collection getChildren(Object itemId) {
+ return ((Container.Hierarchical) items).getChildren(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#getParent(Object)
+ */
+ public Object getParent(Object itemId) {
+ return ((Container.Hierarchical) items).getParent(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#hasChildren(Object)
+ */
+ public boolean hasChildren(Object itemId) {
+ return ((Container.Hierarchical) items).hasChildren(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#isRoot(Object)
+ */
+ public boolean isRoot(Object itemId) {
+ return ((Container.Hierarchical) items).isRoot(itemId);
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#rootItemIds()
+ */
+ public Collection rootItemIds() {
+ return ((Container.Hierarchical) items).rootItemIds();
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#setChildrenAllowed(Object, boolean)
+ */
+ public boolean setChildrenAllowed(
+ Object itemId,
+ boolean areChildrenAllowed) {
+ boolean success =
+ ((Container.Hierarchical) items).setChildrenAllowed(
+ itemId,
+ areChildrenAllowed);
+ if (success)
+ fireValueChange();
+ return success;
+ }
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Hierarchical#setParent(Object, Object)
+ */
+ public boolean setParent(Object itemId, Object newParentId) {
+ boolean success =
+ ((Container.Hierarchical) items).setParent(itemId, newParentId);
+ if (success)
+ requestRepaint();
+ return success;
+ }
+
+ /* Overriding select behavior******************************************** */
+
+ /**
+ * @see com.itmill.toolkit.data.Container.Viewer#setContainerDataSource(Container)
+ */
+ public void setContainerDataSource(Container newDataSource) {
+
+ // 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3832624001804481075L;
+ private Object expandedItemId;
+ /** New instance of options change event
+ * @param source Source of the event.
+ */
+ public ExpandEvent(Component source, Object expandedItemId) {
+ super(source);
+ this.expandedItemId = expandedItemId;
+ }
+
+ /** Node where the event occurred
+ * @return Source of the event.
+ */
+ public Object getItemId() {
+ return this.expandedItemId;
+ }
+ }
+
+ /** Expand event listener
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public interface ExpandListener {
+
+ /** A node has been expanded.
+ * @param event Expand event.
+ */
+ public void nodeExpand(ExpandEvent event);
+ }
+
+ /** Add expand listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(ExpandListener listener) {
+ addListener(ExpandEvent.class, listener, EXPAND_METHOD);
+ }
+
+ /** Remove expand listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(ExpandListener listener) {
+ removeListener(ExpandEvent.class, listener, EXPAND_METHOD);
+ }
+
+ /** Emit expand event. */
+ 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3257009834783290160L;
+
+ private Object collapsedItemId;
+
+ /** New instance of options change event
+ * @param source Source of the event.
+ */
+ public CollapseEvent(Component source, Object collapsedItemId) {
+ super(source);
+ this.collapsedItemId = collapsedItemId;
+ }
+
+ /** Node where the event occurred
+ * @return Source of the event.
+ */
+ public Object getItemId() {
+ return collapsedItemId;
+ }
+ }
+
+ /** Collapse event listener
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public interface CollapseListener {
+
+ /** A node has been collapsed.
+ * @param event Collapse event.
+ */
+ public void nodeCollapse(CollapseEvent event);
+ }
+
+ /** Add collapse listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(CollapseListener listener) {
+ addListener(CollapseEvent.class, listener, COLLAPSE_METHOD);
+ }
+
+ /** Remove collapse listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(CollapseListener listener) {
+ removeListener(CollapseEvent.class, listener, COLLAPSE_METHOD);
+ }
+
+ /** Emit collapse event. */
+ protected void fireCollapseEvent(Object itemId) {
+ fireEvent(new CollapseEvent(this, itemId));
+ }
+
+ /* Action container *************************************************** */
+
+ /** Adds an action handler.
+ * @see com.itmill.toolkit.event.Action.Container#addActionHandler(Action.Handler)
+ */
+ public void addActionHandler(Action.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.itmill.toolkit.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();
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Select#getVisibleItemIds()
+ */
+ public Collection getVisibleItemIds() {
+
+ LinkedList visible = new LinkedList();
+
+ // Iterate trough hierarchical tree using a stack of iterators
+ Stack iteratorStack = new Stack();
+ Collection ids = rootItemIds();
+ if (ids != null)
+ iteratorStack.push(ids.iterator());
+ while (!iteratorStack.isEmpty()) {
+
+ // Get the iterator for current tree level
+ Iterator i = (Iterator) iteratorStack.peek();
+
+ // If the level is finished, back to previous tree level
+ if (!i.hasNext()) {
+
+ // Remove used iterator from the stack
+ iteratorStack.pop();
+ }
+
+ // Add the item on current level
+ else {
+ Object itemId = i.next();
+
+ visible.add(itemId);
+
+ // Add children if expanded, or close the tag
+ if (isExpanded(itemId) && hasChildren(itemId)) {
+ iteratorStack.push(getChildren(itemId).iterator());
+ }
+ }
+ }
+
+ return visible;
+ }
+
+ /** Adding new items is not supported.
+ * @see com.itmill.toolkit.ui.Select#setNewItemsAllowed(boolean)
+ * @throws UnsupportedOperationException if set to true.
+ */
+ public void setNewItemsAllowed(boolean allowNewOptions)
+ throws UnsupportedOperationException {
+ if (allowNewOptions)
+ throw new UnsupportedOperationException();
+ }
+
+ /** Focusing to this component is not supported.
+ * @see com.itmill.toolkit.ui.AbstractField#focus()
+ * @throws UnsupportedOperationException if invoked.
+ */
+ public void focus() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/Upload.java b/src/com/itmill/toolkit/ui/Upload.java
new file mode 100644
index 0000000000..3bcb998352
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Upload.java
@@ -0,0 +1,446 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.UploadStream;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/** Component for client file uploading.
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class Upload extends AbstractComponent implements Component.Focusable {
+
+ /** Upload buffer size. */
+ private static final int BUFFER_SIZE = 64 * 1024; // 64k
+
+ /** Should the field be focused on next repaint */
+ private 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 long focusableId = -1;
+
+ /* TODO: Add a default constructor, receive to temp file. */
+
+ /** Creates a new instance of Upload that redirects the
+ * uploaded data to given stream.
+ *
+ */
+ public Upload(String caption, Receiver uploadReceiver) {
+ this.focusableId = Window.getNewFocusableId(this);
+ setCaption(caption);
+ receiver = uploadReceiver;
+ }
+
+ /** Get component type.
+ * @return Component type as string.
+ */
+ public String getTag() {
+ return "upload";
+ }
+
+ /** Invoked when the value of a variable has changed. */
+ public void changeVariables(Object source, Map variables) {
+
+ // Check the variable name
+ if (!variables.containsKey("stream"))
+ return;
+
+ // Get the upload stream
+ UploadStream upload = (UploadStream) variables.get("stream");
+
+ // Get file properties
+ String filename = upload.getContentName();
+ String type = upload.getContentType();
+
+ // Get the output target stream
+ OutputStream out = receiver.receiveUpload(filename, type);
+ if (out == null)
+ throw new RuntimeException("Error getting outputstream from upload receiver");
+
+ InputStream in = upload.getStream();
+ if (null==in) {
+ // No file, for instance non-existent filename in html upload
+ fireUploadInterrupted(filename, type, 0);
+ return;
+ }
+ byte buffer[] = new byte[BUFFER_SIZE];
+ int bytesRead = 0;
+ long totalBytes = 0;
+ try {
+ while ((bytesRead = in.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ totalBytes += bytesRead;
+ }
+
+ // Download successfull
+ out.close();
+ fireUploadSuccess(filename, type, totalBytes);
+ requestRepaint();
+
+ } catch (IOException e) {
+
+ // Download interrupted
+ fireUploadInterrupted(filename, type, totalBytes);
+ }
+ }
+
+ /** Paint the content of this component.
+ * @param target Target to paint the content on.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+ // The field should be focused
+ if (focus)
+ target.addAttribute("focus", true);
+
+ // The tab ordering number
+ if (this.tabIndex >= 0)
+ target.addAttribute("tabindex", this.tabIndex);
+
+ target.addUploadStreamVariable(this, "stream");
+ }
+
+ /** Notify all upload listeners */
+ private void notifyListeners() {
+
+ }
+
+ /** Interface that must be implemented by the upload receivers.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public interface Receiver {
+
+ /** 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;
+
+ 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_SUCCEEDED_METHOD =
+ SucceededListener.class.getDeclaredMethod(
+ "uploadSucceeded",
+ new Class[] { SucceededEvent.class });
+ } catch (java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException("Internal error");
+ }
+ }
+
+ /** Upload.Received event is sent when the upload receives a file,
+ * regardless if the receival was successfull.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public class FinishedEvent extends Component.Event {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3257288015385670969L;
+
+ /** Length of the received file. */
+ private long length;
+
+ /** MIME type of the received file. */
+ private String type;
+
+ /** Received file name */
+ private String filename;
+
+ public FinishedEvent(
+ Upload source,
+ String filename,
+ String MIMEType,
+ long length) {
+ super(source);
+ this.type = MIMEType;
+ this.filename = filename;
+ this.length = length;
+ }
+
+ /** Upload where the event occurred
+ * @return Source of the event.
+ */
+ public Upload getUpload() {
+ return (Upload) getSource();
+ }
+ /**
+ * Returns the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Returns the length.
+ */
+ public long getLength() {
+ return length;
+ }
+
+ /**
+ * Returns the type.
+ */
+ public String getMIMEType() {
+ return type;
+ }
+
+ }
+
+ /** 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3833746590157386293L;
+
+ public FailedEvent(
+ 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 {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3256445798169524023L;
+
+ public SucceededEvent(
+ Upload source,
+ String filename,
+ String MIMEType,
+ long length) {
+ super(source, filename, MIMEType, length);
+ }
+
+ }
+
+ /** Receives events when the uploads are ready.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public interface FinishedListener {
+
+ /** Upload has finished.
+ * @param event Upload finished event.
+ */
+ public void uploadFinished(FinishedEvent event);
+ }
+
+ /** Receives events when the uploads are finished, but unsuccessfull.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public interface FailedListener {
+
+ /** Upload has finished unsuccessfully.
+ * @param event 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 {
+
+ /** Upload successfull..
+ * @param event Upload successfull event.
+ */
+ public void uploadSucceeded(SucceededEvent event);
+ }
+
+ /** Add upload received event listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(FinishedListener listener) {
+ addListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD);
+ }
+
+ /** Remove upload received event listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(FinishedListener listener) {
+ removeListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD);
+ }
+
+ /** Add upload interrupted event listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(FailedListener listener) {
+ addListener(FailedEvent.class, listener, UPLOAD_FAILED_METHOD);
+ }
+
+ /** Remove upload interrupted event listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(FailedListener listener) {
+ removeListener(FinishedEvent.class, listener, UPLOAD_FAILED_METHOD);
+ }
+
+ /** Add upload success event listener
+ * @param listener Listener to be added.
+ */
+ public void addListener(SucceededListener listener) {
+ addListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD);
+ }
+
+ /** Remove upload success event listener
+ * @param listener Listener to be removed.
+ */
+ public void removeListener(SucceededListener listener) {
+ removeListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD);
+ }
+
+ /** Emit upload received event. */
+ protected void fireUploadReceived(
+ String filename,
+ String MIMEType,
+ long length) {
+ fireEvent(new Upload.FinishedEvent(this, filename, MIMEType, length));
+ }
+
+ /** Emit upload interrupted event. */
+ protected void fireUploadInterrupted(
+ String filename,
+ String MIMEType,
+ long length) {
+ fireEvent(new Upload.FailedEvent(this, filename, MIMEType, length));
+ }
+
+ /** Emit upload success event. */
+ protected void fireUploadSuccess(
+ String filename,
+ String MIMEType,
+ long length) {
+ fireEvent(new Upload.SucceededEvent(this, filename, MIMEType, length));
+ }
+ /** Returns the current receiver.
+ * @return Receiver
+ */
+ public Receiver getReceiver() {
+ return receiver;
+ }
+
+ /** Sets the receiver.
+ * @param receiver The receiver to set
+ */
+ public void setReceiver(Receiver receiver) {
+ this.receiver = receiver;
+ }
+ /**
+ * @see com.itmill.toolkit.ui.Component.Focusable#focus()
+ */
+ public void focus() {
+ Window w = getWindow();
+ if (w != null) {
+ w.setFocusedComponent(this);
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component.Focusable#getTabIndex()
+ */
+ public int getTabIndex() {
+ return this.tabIndex;
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component.Focusable#setTabIndex(int)
+ */
+ public void setTabIndex(int tabIndex) {
+ this.tabIndex = tabIndex;
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.Component.Focusable#getFocusableId()
+ */
+ public long getFocusableId() {
+ return this.focusableId;
+ }
+
+}
diff --git a/src/com/itmill/toolkit/ui/Window.java b/src/com/itmill/toolkit/ui/Window.java
new file mode 100644
index 0000000000..f74ff710a8
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/Window.java
@@ -0,0 +1,695 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.terminal.DownloadStream;
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.ParameterHandler;
+import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.terminal.Sizeable;
+import com.itmill.toolkit.terminal.Terminal;
+import com.itmill.toolkit.terminal.URIHandler;
+
+import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Iterator;
+
+/**
+ * Application window component.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+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 applicaiton 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;
+
+ /**
+ * 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 LinkedList openList = new LinkedList();
+
+ /** The name of the window */
+ private String name = null;
+
+ /** Window border mode */
+ private int border = BORDER_DEFAULT;
+
+ /** Focused component */
+ private Focusable focusedComponent;
+
+ /* ********************************************************************* */
+
+ /**
+ * Create 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
+ * Title of the window
+ */
+ public Window() {
+ this("", null);
+ }
+
+ /**
+ * Create 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
+ * Title of the window
+ */
+ public Window(String caption) {
+ this(caption, null);
+ }
+
+ /**
+ * Create 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
+ * Title of the window
+ * @param layout
+ * Layout of the window
+ */
+ public Window(String caption, Layout layout) {
+ super(caption, layout);
+ setScrollable(true);
+ }
+
+ /**
+ * Get terminal type.
+ *
+ * @return Value of property terminal.
+ */
+ public Terminal getTerminal() {
+ return this.terminal;
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Get 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 parent window of the component.
+ */
+ public final Window getWindow() {
+ return this;
+ }
+
+ /**
+ * Get 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 parent application of the component.
+ */
+ public final Application getApplication() {
+ return this.application;
+ }
+
+ /**
+ * Getter for property parent. Parent is the visual parent of a component.
+ * Each component can belong to only one ComponentContainer at time.
+ *
+ * @return Value of property parent.
+ */
+ public final Component getParent() {
+ return null;
+ }
+
+ /**
+ * Setter for property parent. Parent is the visual parent of a component.
+ * This is mostly called by containers add method. Setting parent is not
+ * allowed for the window, and thus this call should newer be called.
+ *
+ * @param parent
+ * New value of property parent.
+ */
+ public void setParent(Component parent) {
+ throw new RuntimeException("Setting parent for Window is not allowed");
+ }
+
+ /**
+ * Get component UIDL tag.
+ *
+ * @return Component UIDL tag as string.
+ */
+ public String getTag() {
+ return "window";
+ }
+
+ /* ********************************************************************* */
+
+ /** Add new URI handler to this window */
+ public void addURIHandler(URIHandler handler) {
+ if (uriHandlerList == null)
+ uriHandlerList = new LinkedList();
+ synchronized (uriHandlerList) {
+ uriHandlerList.addLast(handler);
+ }
+ }
+
+ /** Remove given URI handler from this window */
+ public void removeURIHandler(URIHandler handler) {
+ if (handler == null || uriHandlerList == null)
+ return;
+ synchronized (uriHandlerList) {
+ uriHandlerList.remove(handler);
+ if (uriHandlerList.isEmpty())
+ uriHandlerList = null;
+ }
+ }
+
+ /**
+ * Handle uri recursively.
+ */
+ 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++) {
+ 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;
+ }
+
+ /* ********************************************************************* */
+
+ /** Add new parameter handler to this window. */
+ public void addParameterHandler(ParameterHandler handler) {
+ if (parameterHandlerList == null)
+ parameterHandlerList = new LinkedList();
+ synchronized (parameterHandlerList) {
+ parameterHandlerList.addLast(handler);
+ }
+ }
+
+ /** Remove given URI handler from this window. */
+ public void removeParameterHandler(ParameterHandler handler) {
+ 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);
+ }
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Get theme for this window.
+ *
+ * @return 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 (theme != null)
+ return theme;
+ if ((application != null) && (application.getTheme() != null))
+ return application.getTheme();
+ if (terminal != null)
+ return terminal.getDefaultTheme();
+ return null;
+ }
+
+ /**
+ * Set theme for this window.
+ *
+ * @param theme
+ * New theme for this window. Null implies the default theme.
+ */
+ public void setTheme(String theme) {
+ this.theme = theme;
+ requestRepaint();
+ }
+
+ /**
+ * Paint the content of this component.
+ *
+ * @param event
+ * PaintEvent.
+ * @throws PaintException
+ * The paint operation failed.
+ */
+ public synchronized void paintContent(PaintTarget target)
+ throws PaintException {
+
+ // Set the window name
+ target.addAttribute("name", getName());
+
+ // Mark main window
+ if (getApplication() != null
+ && this == getApplication().getMainWindow())
+ target.addAttribute("main", true);
+
+ // Open requested resource
+ synchronized (openList) {
+ if (!openList.isEmpty()) {
+ for (Iterator i = openList.iterator(); i.hasNext();)
+ ((OpenResource) i.next()).paintContent(target);
+ openList.clear();
+ }
+ }
+
+ // Contents of the window panel is painted
+ super.paintContent(target);
+
+ // Set focused component
+ if (this.focusedComponent != null)
+ target.addVariable(this, "focused", ""
+ + this.focusedComponent.getFocusableId());
+ else
+ target.addVariable(this, "focused", "");
+
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Open the given resource in this window.
+ */
+ public void open(Resource resource) {
+ synchronized (openList) {
+ openList.add(new OpenResource(resource, null, -1, -1,
+ BORDER_DEFAULT));
+ }
+ requestRepaint();
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Open the given resource in named terminal window. Empty or
+ * <code>null</code> window name results the resource to be opened in this
+ * window.
+ */
+ public void open(Resource resource, String windowName) {
+ synchronized (openList) {
+ openList.add(new OpenResource(resource, windowName, -1, -1,
+ BORDER_DEFAULT));
+ }
+ requestRepaint();
+ }
+
+ /* ********************************************************************* */
+
+ /**
+ * Open 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.
+ */
+ public void open(Resource resource, String windowName, int width,
+ int height, int border) {
+ synchronized (openList) {
+ 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 String
+ */
+ public URL getURL() {
+
+ if (application == null)
+ return null;
+
+ try {
+ return new URL(application.getURL(), getName() + "/");
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Internal problem, please report");
+ }
+ }
+
+ /**
+ * Get the unique name of the window that indentifies it on the terminal.
+ *
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the border.
+ *
+ * @return int
+ */
+ 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.itmill.toolkit.Application#addWindow(Window)} method should be
+ * used to add the window to an application and
+ * {@link com.itmill.toolkit.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;
+
+ // Send detach event if the window is connected to application
+ if (this.application != null) {
+ detach();
+ }
+
+ // Connect to new parent
+ this.application = application;
+
+ // Send 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. Also the
+ * name may only contain the following characters: a-z, A-Z and 0-9.
+ * </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");
+
+ // Check the name format
+ if (name != null)
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')))
+ throw new IllegalArgumentException(
+ "Window name can contain "
+ + "only a-z, A-Z and 0-9 characters: '"
+ + name + "' given.");
+ }
+
+ this.name = name;
+ }
+
+ /**
+ * Set terminal type. The terminal type is set by the the terminal adapter
+ * and may change from time to time.
+ *
+ * @param type
+ * terminal type to set
+ */
+ public void setTerminal(Terminal type) {
+ this.terminal = type;
+ }
+
+ /**
+ * Window only supports pixels as unit.
+ *
+ * @see com.itmill.toolkit.terminal.Sizeable#getHeightUnits()
+ */
+ public void setHeightUnits(int units) {
+ if (units != Sizeable.UNITS_PIXELS)
+ throw new IllegalArgumentException("Only pixels are supported");
+ }
+
+ /**
+ * Window only supports pixels as unit.
+ *
+ * @see com.itmill.toolkit.terminal.Sizeable#getWidthUnits()
+ */
+ public void setWidthUnits(int units) {
+ if (units != Sizeable.UNITS_PIXELS)
+ throw new IllegalArgumentException("Only pixels are supported");
+ }
+
+ /** Private data structure for storing opening window properties */
+ private class OpenResource {
+
+ private Resource resource;
+
+ private String name;
+
+ private int width;
+
+ private int height;
+
+ private int border;
+
+ /** Create new open resource */
+ 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;
+ }
+
+ /** Paint the open-tag inside the window. */
+ 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");
+ }
+ }
+
+ /**
+ * @see com.itmill.toolkit.terminal.VariableOwner#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ public void changeVariables(Object source, Map variables) {
+ super.changeVariables(source, variables);
+
+ // Get focused component
+ String focusedId = (String) variables.get("focused");
+ if (focusedId != null) {
+ try {
+ long id = Long.parseLong(focusedId);
+ this.focusedComponent = Window.getFocusableById(id);
+ } catch (NumberFormatException ignored) {
+ // We ignore invalid focusable ids
+ }
+ }
+
+ }
+
+ /**
+ * Get currently focused component in this window.
+ *
+ * @return Focused component or null if none is focused.
+ */
+ public Component.Focusable getFocusedComponent() {
+ return this.focusedComponent;
+ }
+
+ /**
+ * Set currently focused component in this window.
+ *
+ * @param focusable
+ * Focused component or null if none is focused.
+ */
+ public void setFocusedComponent(Component.Focusable focusable) {
+ this.focusedComponent = focusable;
+ }
+
+ /* Focusable id generator ****************************************** */
+
+ private static long lastUsedFocusableId = 0;
+
+ private static Map focusableComponents = new HashMap();
+
+ /** Get an id for focusable component. */
+ public static long getNewFocusableId(Component.Focusable focusable) {
+ long newId = ++lastUsedFocusableId;
+ WeakReference ref = new WeakReference(focusable);
+ focusableComponents.put(new Long(newId), ref);
+ return newId;
+ }
+
+ /** Map focusable id back to focusable component. */
+ public static Component.Focusable getFocusableById(long focusableId) {
+ WeakReference ref = (WeakReference) focusableComponents.get(new Long(
+ focusableId));
+ if (ref != null) {
+ Object o = ref.get();
+ if (o != null) {
+ return (Component.Focusable) o;
+ }
+ }
+ return null;
+ }
+
+ /** Release focusable component id when not used anymore. */
+ public static void removeFocusableId(long focusableId) {
+ Long id = new Long(focusableId);
+ WeakReference ref = (WeakReference) focusableComponents.get(id);
+ ref.clear();
+ focusableComponents.remove(id);
+ }
+}
diff --git a/src/com/itmill/toolkit/ui/doc-files/component_class_hierarchy.gif b/src/com/itmill/toolkit/ui/doc-files/component_class_hierarchy.gif
new file mode 100644
index 0000000000..936c220d11
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/doc-files/component_class_hierarchy.gif
Binary files differ
diff --git a/src/com/itmill/toolkit/ui/doc-files/component_interfaces.gif b/src/com/itmill/toolkit/ui/doc-files/component_interfaces.gif
new file mode 100644
index 0000000000..44c99826bb
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/doc-files/component_interfaces.gif
Binary files differ
diff --git a/src/com/itmill/toolkit/ui/package.html b/src/com/itmill/toolkit/ui/package.html
new file mode 100644
index 0000000000..b6b17b43d0
--- /dev/null
+++ b/src/com/itmill/toolkit/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 the MillStone UI component library.</p>
+
+<h2>Package Specification</h2>
+
+<p><strong>Interface hierarchy</strong></p>
+
+<p>The general interface hierarchy looks like this:</p>
+
+<p><center><img src="doc-files/component_interfaces.gif"/> </center></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 org.millstone.base.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 org.millstone.base.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 org.millstone.base.ui.Component}.</p>
+
+<p>The next level in the component hierarchy are the classes implementing
+the {@link org.millstone.base.ui.ComponentContainer} interface. It adds the
+capacity to contain other components to
+{@link org.millstone.base.ui.Component} with a simple API.</p>
+
+<p>The third and last level is the {@link org.millstone.base.ui.Layout},
+which adds the concept of location to the components contained in a
+{@link org.millstone.base.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>
+
+<p><center><img src="doc-files/component_class_hierarchy.gif"/></center></p>
+
+<p><center><i>Underlined classes are abstract.</i></center></p>
+
+<p>At the top level is {@link org.millstone.base.ui.AbstractComponent}
+which implements the {@link org.millstone.base.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 org.millstone.base.ui.AbstractComponentContainer} serves
+as the root class for all components (for example, panels and windows) who
+can contain other components. {@link org.millstone.base.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>