From 13af8cba414fbb6f02ef458a86c5afcad70c5275 Mon Sep 17 00:00:00 2001
From: Joonas Lehtinen 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:String
+ */
+ 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 String
. 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 String
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);
+ }
+ }
+
+ /**
+ *
+ * Tag
+ * Description
+ * Example
+ *
+ * <b>
+ * bold
+ * bold text
+ *
+ * <i>
+ * italic
+ * italic text
+ *
+ * <u>
+ * underlined
+ * underlined text
+ *
+ * <br>
+ * linebreak
+ * N/A
+ *
+ * <ul>
+ *
<li>item1
<li>item1
</ul>item list
+ *
+ *
These tags may be nested.
+ * + * @return component's descriptionString
+ */
+ 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 ErrorMessage
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();
+ }
+ }
+
+ /** 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.
+ * + *For more information on the MillStone inheritable event mechanism + * see the + * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.
+ * + * @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 unlessmethod
+ * has exactly one match in object
+ */
+ public void addListener(Class eventType, Object object, Method method) {
+ if (eventRouter == null)
+ eventRouter = new EventRouter();
+ eventRouter.addListener(eventType, object, method);
+ }
+
+ /** 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.
+ * + *This version of addListener
gets the name of the
+ * activation method as a parameter. The actual method is reflected from
+ * object
, and unless exactly one match is found,
+ * java.lang.IllegalArgumentException
is thrown.
For more information on the MillStone inheritable event mechanism + * see the + * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.
+ * + * @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 unlessmethod
+ * has exactly one match in object
+ */
+ 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 object
's methods that
+ * are registered to listen to events of type eventType
+ * generated by this component.
+ *
+ * For more information on the MillStone inheritable event mechanism + * see the + * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.
+ * + * @param eventType exact event type theobject
listens to
+ * @param target target object that has registered to listen to events
+ * of type eventType
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.
+ *
+ * For more information on the MillStone inheritable event mechanism + * see the + * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.
+ * + * @param eventType exact event type theobject
listens to
+ * @param target target object that has registered to listen to events
+ * of type eventType
with one or more methods
+ * @param method the method owned by target
that's
+ * registered to listen to events of type eventType
+ */
+ public void removeListener(Class eventType, Object target, Method method) {
+ if (eventRouter != null)
+ eventRouter.removeListener(eventType, target, method);
+ }
+
+ /** 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.
+ * + *This version of removeListener
gets the name of the
+ * activation method as a parameter. The actual method is reflected from
+ * target
, and unless exactly one match is found,
+ * java.lang.IllegalArgumentException
is thrown.
For more information on the MillStone inheritable event mechanism + * see the + * {@link com.itmill.toolkit.event com.itmill.toolkit.event package documentation}.
+ * + * @param eventType exact event type theobject
listens to
+ * @param target target object that has registered to listen to events
+ * of type eventType
with one or more methods
+ * @param methodName name of the method owned by target
+ * that's registered to listen to events of type eventType
+ */
+ 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;
+
+/**
+ *
+ * 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.
+ * AbstractField
implements that interface itself, too, so
+ * accessing the Property value represented by it is straightforward.
+ *
+ * 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. + *
+ * + *+ * The class also supports {@link com.itmill.toolkit.data.Validator validators} + * to make sure the value contained in the field is valid. + *
+ * + * @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. + * + * @returnString
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 null
if
+ * none defined.
+ */
+ public Property getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * + * 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. + *
+ * + *+ * 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. + *
+ * + * @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. + * + * @returntrue
if all registered validators claim that the
+ * current value is valid, false
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 Event
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.
+ *
+ * + * This returns most suitable field type for editing property of given type + *
+ * + * @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: + *
+ * Boolean: Button(switchMode:true)
+ * Date: DateField(resolution: day)
+ * Item: Form
+ * default field type: TextField
+ *
+ * @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); + + /**
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.
+ * + *Components should be enabled by default.
+ * + * @returntrue
if the component is enabled,
+ * false
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 true
.
+ *
+ * @return true
if the component is visible in the UI,
+ * false
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.
+ *
+ * This method calls
+ * automatically {@link #attach()} if the parent is attached to a
+ * window (or is itself a window}, and {@link #detach()} if
+ * parent
is set null
, but the component
+ * was in the application.
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 true
if the component is in read-only mode,
+ * false
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 String
+ */
+ 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 null
if it not defined.
+ */
+ public Resource getIcon();
+
+ /** Gets the component's parent window. If the component does not yet
+ * belong to a window null
is returned.
+ *
+ * @return parent window of the component or null
+ */
+ public Window getWindow();
+
+ /** Gets the component's parent application. If the component does not
+ * yet belong to a application null
is returned.
+ *
+ * @return parent application of the component or null
+ */
+ 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 getApplication()
and
+ * getWindow()
functions might return null
+ * before this method is called.
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.
+ */ + public void attach(); + + /** Notifies the component that it is detached from the application. + *The {@link #getApplication()} and {@link #getWindow()}
+ * methods might return null
after this method is
+ * called.
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.
+ */ + 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 originatedEvent
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 Component.Event
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 ErrorEvent
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 Component.Errors
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.
+ *
+ * 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.
+ * + * @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 fromsource
.
+ *
+ * @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.
+ * 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.
+ * + * @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. + * + *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.
+ */ + public CustomComponent() { + } + + /** Construct new custom component. + * + *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.
+ * + * @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. + *The composition root must be set to non-null value before the + * component can be used. The composition root can only be set once.
+ * @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; + +/**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.
+ * + *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.
+ * + *The default theme handles the styles that are not defined by just + * drawing the subcomponents with flowlayout.
+ * + * @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; + +/** + *+ * A date editor component that can be bound to any bindable Property. that is + * compatible with java.util.Date. + * + *
+ * Since DateField
extends AbstractField
it
+ * implements the {@link com.itmill.toolkit.data.Buffered}interface. A
+ * DateField
is in write-through mode by default, so
+ * {@link com.itmill.toolkit.ui.AbstractField#setWriteThrough(boolean)}must be
+ * called to enable buffering.
+ *
DateField
with no caption. */
+ public DateField() {
+ }
+
+ /**
+ * Constructs an empty DateField
with caption.
+ *
+ * @param caption
+ * The caption of the datefield.
+ */
+ public DateField(String caption) {
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new DateField
that's bound to the specified
+ * Property
and has the given caption String
.
+ *
+ * @param caption
+ * caption String
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 DateField
that's bound to the specified
+ * Property
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 DateField
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 String
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.
+ * This can be one of the following:
This can be one of the following:
Sizeable.UNITS_PIXELS
.
+ * @see com.itmill.toolkit.terminal.Sizeable#getHeightUnits()
+ */
+ public int getHeightUnits() {
+ return this.heightUnits;
+ }
+
+ /**Get width property units.
+ * Default units are Sizeable.UNITS_PIXELS
.
+ * @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 Event
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.
+ *
+ * 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. + *
+ * + *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}.
+ * + * @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. + * + *By default the form uses OrderedLayout
+ * with form
-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.
+ *
+ *
The property id must not be already used in the form. + *
+ * + *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.
+ * + * @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. + * + *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.
+ * + * @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. + * + *Setting item datasource clears any fields, the form might contain + * and adds all the properties as fields to the form.
+ * + * @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. + * + *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.
+ * + * @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. + * + *By default form uses OrderedLayout
with form
-style.
By default form uses OrderedLayout
with form
-style.
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.
+ * + * @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; + +/**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.
+ * + *A FrameWindow
can't contain any components directly (as
+ * it contains only a set of frames) and thus the container interface
+ * methods do nothing.
Constructs a new frame window. + * + */ + public FrameWindow() { + } + + + /**
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 String
+ */
+ 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.
+ *
+ *
The frames can be only created to framesets using the
+ * newFrame()
functions of the frameset.
The new frame will be in the end of the frames list.
+ */ + public Frame newFrame(Window window) { + return newFrame(window, size()); + } + + /** Create new frame containing a window. + * + *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.
+ */ + 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. + * + *The new frame will be put in the end of the frames list..
+ */ + public Frame newFrame(URL url, String name) { + return newFrame(url, name, size()); + } + + /** Create new frame containing a resource. + * + *The new frame will be put in the end of the frames list..
+ */ + public Frame newFrame(Resource resource, String name) { + return newFrame(resource, name, size()); + } + + /** Create new frame containing a url. + * + *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.
+ */ + 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. + * + *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.
+ */ + 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. + * + *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.
+ */ + 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. + * + *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.
+ */ + public void setVertical(boolean isVertical) { + this.vertical = isVertical; + requestRepaint(); + } + + /** Check if the frameset is vertical. + * + *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.
+ */ + 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. + * + *To add component to frame window, normal window must be + * first created and then attached to frame window as a frame.
+ * + * @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; + +/**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.
+ * + *Each component in a GridLayout
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.
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.
+ * + *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.
+ * + * @param c The component to be added. + * @param x1 The X-coordinate of the upper left corner of the area + *c
is supposed to occupy
+ * @param y1 The Y-coordinate of the upper left corner of the area
+ * c
is supposed to occupy
+ * @param x2 The X-coordinate of the lower right corner of the area
+ * c
is supposed to occupy
+ * @param y2 The Y-coordinate of the lower right corner of the area
+ * c
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 area
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;
+
+ /** Construct a new area on a grid.
+ *
+ * @param x1 The X-coordinate of the upper left corner of the area
+ * c
is supposed to occupy
+ * @param y1 The Y-coordinate of the upper left corner of the area
+ * c
is supposed to occupy
+ * @param x2 The X-coordinate of the lower right corner of the area
+ * c
is supposed to occupy
+ * @param y2 The Y-coordinate of the lower right corner of the area
+ * c
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 true
if other
overlaps with
+ * this area, false
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.
+ *
+ *
This function only sets the value in the datastructure and does not + * send any events or set parents
+ * + * @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; + } + + } + + /** AnException
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 OverlapsException
.
+ * @param msg the detail message.
+ */
+ public OverlapsException(Area existingArea) {
+ this.existingArea = existingArea;
+ }
+
+ /** Get the area */
+ public Area getArea() {
+ return existingArea;
+ }
+ }
+
+ /** An Exception
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 OoutOfBoundsException
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_*
+ *
+ * The contents of the label may contain simple + * formatting: + *
Possible content modes include: + *
Possible content modes include: + *
Labels can be compared to other labels for sorting label contents. + * This is especially handy for sorting table columns.
+ * + *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.
+ * + * @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; + +/**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}.
+ * + *A Select
component may be in single- or multiselect mode.
+ * Multiselect mode means that more than one item can be selected
+ * simultaneously.
String
representation
+ * is used as caption.
+ */
+ public static final int ITEM_CAPTION_MODE_ID = 0;
+
+ /** Item caption mode: Item's String
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 String
representation is used as caption.
+ * This is the default.
+ */
+ 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 setItemCaptionPropertyId
.
+ */
+ 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.
+ *
+ * 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.
+ */ + 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. + * + *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.
+ * + * @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 + *setItemCaptionMode()
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.
+ *
+ * The mode can be one of the following ones: + *
ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID
: Items
+ * Id-objects toString()
is used as item caption. If
+ * caption is explicitly specified, it overrides the id-caption.
+ * ITEM_CAPTION_MODE_ID
: Items Id-objects
+ * toString()
is used as item caption.ITEM_CAPTION_MODE_ITEM
: Item-objects
+ * toString()
is used as item caption.ITEM_CAPTION_MODE_INDEX
: The index of the item is
+ * used as item caption. The index mode can
+ * only be used with the containers implementing
+ * Container.Indexed
interface.ITEM_CAPTION_MODE_EXPLICIT
: The item captions
+ * must be explicitly specified.ITEM_CAPTION_MODE_PROPERTY
: The item captions
+ * are read from property, that must be specified with
+ * setItemCaptionPropertyId()
.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID
is the default
+ * mode.
+ *
+ *
+ * @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.
+ *
+ * The mode can be one of the following ones: + *
ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID
: Items
+ * Id-objects toString()
is used as item caption. If
+ * caption is explicitly specified, it overrides the id-caption.
+ * ITEM_CAPTION_MODE_ID
: Items Id-objects
+ * toString()
is used as item caption.ITEM_CAPTION_MODE_ITEM
: Item-objects
+ * toString()
is used as item caption.ITEM_CAPTION_MODE_INDEX
: The index of the item is
+ * used as item caption. The index mode can
+ * only be used with the containers implementing
+ * Container.Indexed
interface.ITEM_CAPTION_MODE_EXPLICIT
: The item captions
+ * must be explicitly specified.ITEM_CAPTION_MODE_PROPERTY
: The item captions
+ * are read from property, that must be specified with
+ * setItemCaptionPropertyId()
.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID
is the default
+ * mode.
+ *
+ *
+ * @return One of the modes listed above.
+ */
+ public int getItemCaptionMode() {
+ return itemCaptionMode;
+ }
+
+ /** Set the item caption property.
+ *
+ * Setting the id to a existing property implicitly sets
+ * the item caption mode to ITEM_CAPTION_MODE_PROPERTY
.
+ * If the object is in ITEM_CAPTION_MODE_PROPERTY
+ * mode, setting caption property id null resets the
+ * item caption mode to ITEM_CAPTION_EXPLICIT_DEFAULTS_ID
.
Setting the property id to null disables this feature. The + * id is null by default
. + * + */ + 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. + * + *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.
+ * + *Note that the icons set with setItemIcon
+ * function override the icons from the property.
Setting the property id to null disables this feature. The + * id is null by default
. + * + * @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. + * + *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.
+ * + *Note that the icons set with setItemIcon
+ * function override the icons from the property.
Setting the property id to null disables this feature. The + * id is null by default
. + * + * @return Id of the property containing the item icons. + */ + public Object getItemIconPropertyId() { + return itemIconPropertyId; + } + + /** Test if an item is selected + * + *In single select mode testing selection status of the item identified + * by {@link #getNullSelectionItemId()} returns true if the value of the + * property is null.
+ * + * @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. + * + *In single select mode selecting item identified + * by {@link #getNullSelectionItemId()} sets the value of the + * property to null.
+ * + * @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. + * + *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.
+ + * @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. + * + *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.
+ * + * @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. This is the default behaviour. + */ + 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. This is the default + * behaviour. + */ + 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 + *setColumnHeaders()
+ */
+ public static final int COLUMN_HEADER_MODE_EXPLICIT = 1;
+
+ /**
+ * Column header mode: Column headers are explicitly specified with
+ * setColumnHeaders()
+ */
+ public static final int COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = 2;
+
+ /**
+ * Row caption mode: The row headers are hidden. This is the default
+ * mode.
+ */
+ 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
+ * setItemCaptionPropertyId
.
+ */
+ 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 toString()
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.
+ *
+ * + * The columns are show in the order of their appearance in this array + *
+ * + * @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. + * + *+ * The columns are show in the order of their appearance in this array + *
+ * + * @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. + * + *
+ * The headers match the property id:s given my the set visible column
+ * headers. The table must be set in either
+ * ROW_HEADER_MODE_EXPLICIT
or
+ * ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID
mode to show the
+ * headers. In the defaults mode any nulls in the headers array are replaced
+ * with id.toString() outputs when rendering.
+ *
+ * The headers match the property id:s given my the set visible column
+ * headers. The table must be set in either
+ * ROW_HEADER_MODE_EXPLICIT
or
+ * ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID
mode to show the
+ * headers. In the defaults mode any nulls in the headers array are replaced
+ * with id.toString() outputs when rendering.
+ *
getVisibleColumns()
.
+ */
+ 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.
+ *
+ *
+ * The icons in headers match the property id:s given my the set visible
+ * column headers. The table must be set in either
+ * ROW_HEADER_MODE_EXPLICIT
or
+ * ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID
mode to show the
+ * headers with icons.
+ *
getVisibleColumns()
.
+ */
+ 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.
+ *
+ *
+ * The icons in headers match the property id:s given my the set visible
+ * column headers. The table must be set in either
+ * ROW_HEADER_MODE_EXPLICIT
or
+ * ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID
mode to show the
+ * headers with icons.
+ *
getVisibleColumns()
.
+ */
+ 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.
+ *
+ *
+ * The items in the array must match the properties identified by
+ * getVisibleColumns()
. The possible values for the
+ * alignments include:
+ *
ALIGN_LEFT
: Left alignmentALIGN_CENTER
: CenteredALIGN_RIGHT
: Right alignmentALIGN_LEFT
: any null values are
+ * rendered as align lefts.
+ *
+ *
+ * @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.
+ *
+ *
+ * The items in the array must match the properties identified by
+ * getVisibleColumns()
. The possible values for the
+ * alignments include:
+ *
ALIGN_LEFT
: Left alignmentALIGN_CENTER
: CenteredALIGN_RIGHT
: Right alignmentALIGN_LEFT
+ *
+ *
+ * @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.
+ *
+ * + * Setting page length 0 disables paging. + *
+ * + * @return Lenght of one page. + */ + public int getPageLength() { + return this.pageLength; + } + + /** + * Set the page length. + * + *+ * Setting page length 0 disables paging. The page length defaults to 0 (no + * paging). + *
+ * + * @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. + *+ * Throws IllegalArgumentException if the specified column is not visible. + *
+ * + * @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. + * + *+ * Throws IllegalArgumentException if the alignment is not one of the + * following: ALIGN_LEFT, ALIGN_CENTER or ALIGN_RIGHT + *
+ * + * @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. + * + *+ * The table is not selectable by default. + *
+ * + * @return Value of property selectable. + */ + public boolean isSelectable() { + return this.selectable; + } + + /** + * Setter for property selectable. + * + *+ * The table is not selectable by default. + *
+ * + * @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. + *+ * The mode can be one of the following ones: + *
ROW_HEADER_MODE_HIDDEN
: The row captions are hidden.
+ * ROW_HEADER_MODE_ID
: Items Id-objects
+ * toString()
is used as row caption.
+ * ROW_HEADER_MODE_ITEM
: Item-objects
+ * toString()
is used as row caption.
+ * ROW_HEADER_MODE_PROPERTY
: Property set with
+ * setItemCaptionPropertyId()
is used as row header.
+ * ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID
: Items
+ * Id-objects toString()
is used as row header. If caption is
+ * explicitly specified, it overrides the id-caption.
+ * ROW_HEADER_MODE_EXPLICIT
: The row headers must be
+ * explicitly specified.ROW_HEADER_MODE_INDEX
: The index of the item is used
+ * as row caption. The index mode can only be used with the containers
+ * implementing Container.Indexed
interface.ROW_HEADER_MODE_HIDDEN
+ *
+ *
+ * @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 true
if ascending, false
if
+ * descending
+ */
+ public boolean isSortAscending() {
+ return this.sortAscending;
+ }
+
+ /**
+ * Set the table in ascending order.
+ *
+ * @param ascending
+ * true
if ascending, false
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;
+
+/** 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.
+ * + *Since TextField
extends AbstractField
it
+ * implements the {@link com.itmill.toolkit.data.Buffered} interface. A
+ * TextField
is in write-through mode by default, so
+ * {@link com.itmill.toolkit.ui.AbstractField#setWriteThrough(boolean)}
+ * must be called to enable buffering.
TextField
with no caption. */
+ public TextField() {
+ setValue("");
+ }
+
+ /** Constructs an empty TextField
with given caption. */
+ public TextField(String caption) {
+ setValue("");
+ setCaption(caption);
+ }
+
+ /** Constructs a new TextField
that's bound to the
+ * specified Property
and has no caption.
+ *
+ * @param dataSource the Property to be edited with this editor
+ */
+ public TextField(Property dataSource) {
+ setPropertyDataSource(dataSource);
+ }
+
+ /** Constructs a new TextField
that's bound to the
+ * specified Property
and has the given caption
+ * String
.
+ *
+ * @param caption caption String
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 TextField
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 String
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 true
if the component is in the word-wrap mode,
+ * false
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.
+ *
+ * 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.
+ * + *The default value is string 'null'
+ * + * @see TextField#isNullSettingAllowed() + * @return String Textual representation for null strings. + */ + public String getNullRepresentation() { + return nullRepresentation; + } + + /** Is setting nulls with null-string representation allowed. + * + *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.
+ * + *By default this setting is false
+ * + * @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. + * + *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.
+ * + *The default value is string 'null'
+ * + * @see TextField#setNullSettingAllowed(boolean) + * @param nullRepresentation Textual representation for null strings. + */ + public void setNullRepresentation(String nullRepresentation) { + this.nullRepresentation = nullRepresentation; + } + + /** Set the null conversion mode. + * + *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.
+ * + *By default this setting is false
+ * + * @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. + * + *The tree is selectable by default.
+ * + * @return Value of property selectable. + */ + public boolean isSelectable() { + return this.selectable; + } + + /** Setter for property selectable. + * + *The tree is selectable by default.
+ * + * @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. + * + *
+ * To show the window in application, it must be added to application with
+ * Application.addWindow()
method.
+ *
+ * The windows are scrollable by default. + *
+ * + * @param caption + * Title of the window + */ + public Window() { + this("", null); + } + + /** + * Create new empty window with default layout. + * + *
+ * To show the window in application, it must be added to application with
+ * Application.addWindow()
method.
+ *
+ * The windows are scrollable by default. + *
+ * + * @param caption + * Title of the window + */ + public Window(String caption) { + this(caption, null); + } + + /** + * Create new window. + * + *
+ * To show the window in application, it must be added to application with
+ * Application.addWindow()
method.
+ *
+ * The windows are scrollable by default. + *
+ * + * @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 + *null
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 null
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.
+ *
+ * + * 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. + *
+ * + *+ * The method invokes {@link Component#attach()} and + * {@link Component#detach()} methods when necessary. + *
+ * + * @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. + *
+ * 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. + *
+ * + *+ * If the name is null, the the window is given name automatically when it + * is added to an application. + *
+ * + * @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 Binary files /dev/null and b/src/com/itmill/toolkit/ui/doc-files/component_class_hierarchy.gif 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 Binary files /dev/null and b/src/com/itmill/toolkit/ui/doc-files/component_interfaces.gif 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 @@ + + + + + + + + + + +Provides interfaces and classes in the the MillStone UI component library.
+ +Interface hierarchy
+ +The general interface hierarchy looks like this:
+ +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.
+ +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}.
+ +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.
+ +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.
+ +Component class hierarchy
+ +The actual component classes form a hierarchy like this:
+ +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 Component
so that
+a component is free to override only those functionalities it needs.
As seen in the picture, AbstractComponent
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.