summaryrefslogtreecommitdiffstats
path: root/src/com/vaadin
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/vaadin')
-rw-r--r--src/com/vaadin/Application.java12
-rw-r--r--src/com/vaadin/annotations/package.html12
-rw-r--r--src/com/vaadin/data/Validator.java145
-rw-r--r--src/com/vaadin/data/doc-files/Container_full.gifbin9870 -> 0 bytes
-rw-r--r--src/com/vaadin/data/doc-files/Container_simple.gifbin7448 -> 0 bytes
-rw-r--r--src/com/vaadin/data/doc-files/Item.gifbin2694 -> 0 bytes
-rw-r--r--src/com/vaadin/data/doc-files/Property.gifbin1415 -> 0 bytes
-rw-r--r--src/com/vaadin/data/doc-files/datalayer.gifbin7595 -> 0 bytes
-rw-r--r--src/com/vaadin/data/package.html95
-rw-r--r--src/com/vaadin/data/util/BeanItemContainer.java292
-rw-r--r--src/com/vaadin/data/util/DefaultItemSorter.java5
-rw-r--r--src/com/vaadin/data/util/HierarchicalContainer.java45
-rw-r--r--src/com/vaadin/data/util/package.html31
-rw-r--r--src/com/vaadin/data/validator/AbstractStringValidator.java122
-rw-r--r--src/com/vaadin/data/validator/AbstractValidator.java144
-rw-r--r--src/com/vaadin/data/validator/package.html13
-rw-r--r--src/com/vaadin/launcher/DemoLauncher.java4
-rw-r--r--src/com/vaadin/launcher/DevelopmentServerLauncher.java56
-rw-r--r--src/com/vaadin/package.html39
-rw-r--r--src/com/vaadin/service/FileTypeResolver.java34
-rw-r--r--src/com/vaadin/terminal/ParameterHandler.java36
-rw-r--r--src/com/vaadin/terminal/URIHandler.java27
-rwxr-xr-xsrc/com/vaadin/terminal/gwt/client/ApplicationConnection.java23
-rw-r--r--src/com/vaadin/terminal/gwt/client/CSSRule.java101
-rw-r--r--src/com/vaadin/terminal/gwt/client/SynchronousXHR.java21
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java69
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/SimpleFocusablePanel.java61
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java366
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java15
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java1914
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VSlider.java126
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java8
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VTree.java1068
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java192
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java14
-rw-r--r--src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java55
-rw-r--r--src/com/vaadin/ui/AbstractOrderedLayout.java24
-rw-r--r--src/com/vaadin/ui/AbstractSelect.java15
-rw-r--r--src/com/vaadin/ui/Component.java19
-rw-r--r--src/com/vaadin/ui/DateField.java90
-rw-r--r--src/com/vaadin/ui/Table.java931
-rw-r--r--src/com/vaadin/ui/Tree.java71
-rw-r--r--src/com/vaadin/ui/Window.java184
-rw-r--r--src/com/vaadin/ui/themes/Runo.java140
44 files changed, 5761 insertions, 858 deletions
diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java
index 9a0c180a1d..61df7c9f9a 100644
--- a/src/com/vaadin/Application.java
+++ b/src/com/vaadin/Application.java
@@ -17,7 +17,6 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Properties;
-import java.util.Random;
import com.vaadin.service.ApplicationContext;
import com.vaadin.terminal.ApplicationResource;
@@ -92,9 +91,10 @@ public abstract class Application implements URIHandler,
Terminal.ErrorListener, Serializable {
/**
- * Random window name generator.
+ * Id use for the next window that is opened. Access to this must be
+ * synchronized.
*/
- private static Random nameGenerator = new Random();
+ private int nextWindowId = 1;
/**
* Application context the application is running in.
@@ -327,7 +327,11 @@ public abstract class Application implements URIHandler,
while (!accepted) {
// Try another name
- name = String.valueOf(Math.abs(nameGenerator.nextInt()));
+ synchronized (this) {
+ name = String.valueOf(nextWindowId);
+ nextWindowId++;
+ }
+
if (!windows.containsKey(name)) {
accepted = true;
}
diff --git a/src/com/vaadin/annotations/package.html b/src/com/vaadin/annotations/package.html
new file mode 100644
index 0000000000..d789e9b5df
--- /dev/null
+++ b/src/com/vaadin/annotations/package.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+
+<body bgcolor="white">
+
+<p>Contains annotations used in Vaadin. Note that some annotations
+are also found in other packages e.g., {@link com.vaadin.ui.ClientWidget}.</p>
+
+</body>
+</html>
diff --git a/src/com/vaadin/data/Validator.java b/src/com/vaadin/data/Validator.java
index 665fec4156..21a9da9f97 100644
--- a/src/com/vaadin/data/Validator.java
+++ b/src/com/vaadin/data/Validator.java
@@ -11,16 +11,23 @@ import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
/**
- * Object validator interface. Implementors of this class can be added to any
- * {@link com.vaadin.data.Validatable} object to verify its value. The
- * <code>Validatable#isValid(Object)</code> iterates all registered
- * <code>Validator</code>s, calling their {@link #validate(Object)} methods.
- * <code>validate(Object)</code> should throw the
- * {@link Validator.InvalidValueException} if the given value is not valid by
- * its standards.
- *
- * Validators should not have side effects on other objects as they can be
- * called from Paintable.paint().
+ * Interface that implements a method for validating if an {@link Object} is
+ * valid or not.
+ * <p>
+ * Implementors of this class can be added to any
+ * {@link com.vaadin.data.Validatable Validatable} implementor to verify its
+ * value.
+ * </p>
+ * <p>
+ * {@link #isValid(Object)} and {@link #validate(Object)} can be used to check
+ * if a value is valid. {@link #isValid(Object)} and {@link #validate(Object)}
+ * must use the same validation logic so that iff {@link #isValid(Object)}
+ * returns false, {@link #validate(Object)} throws an
+ * {@link InvalidValueException}.
+ * </p>
+ * <p>
+ * Validators must not have any side effects.
+ * </p>
*
* @author IT Mill Ltd.
* @version
@@ -30,29 +37,31 @@ import com.vaadin.terminal.PaintTarget;
public interface Validator extends Serializable {
/**
- * Checks the given value against this validator. If the value is valid this
- * method should do nothing, and if it's not valid, it should throw
- * <code>Validator.InvalidValueException</code>
+ * Checks the given value against this validator. If the value is valid the
+ * method does nothing. If the value is invalid, an
+ * {@link InvalidValueException} is thrown.
*
* @param value
* the value to check
* @throws Validator.InvalidValueException
- * if the value is not valid
+ * if the value is invalid
*/
public void validate(Object value) throws Validator.InvalidValueException;
/**
- * Tests if the given value is valid.
+ * Tests if the given value is valid. This method must be symmetric with
+ * {@link #validate(Object)} so that {@link #validate(Object)} throws an
+ * error iff this method returns false.
*
* @param value
* the value to check
- * @return <code>true</code> for valid value, otherwise <code>false</code>.
+ * @return <code>true</code> if the value is valid, <code>false</code>
+ * otherwise.
*/
public boolean isValid(Object value);
/**
- * Invalid value exception can be thrown by {@link Validator} when a given
- * value is not valid.
+ * Exception that is thrown by a {@link Validator} when a value is invalid.
*
* @author IT Mill Ltd.
* @version
@@ -63,12 +72,15 @@ public interface Validator extends Serializable {
public class InvalidValueException extends RuntimeException implements
ErrorMessage {
- /** Array of validation errors that are causing the problem. */
+ /**
+ * Array of one or more validation errors that are causing this
+ * validation error.
+ */
private InvalidValueException[] causes = null;
/**
- * Constructs a new <code>InvalidValueException</code> with the
- * specified detail message.
+ * Constructs a new {@code InvalidValueException} with the specified
+ * message.
*
* @param message
* The detail message of the problem.
@@ -78,16 +90,15 @@ public interface Validator extends Serializable {
}
/**
- * Constructs a new <code>InvalidValueException</code> with a set of
- * causing validation exceptions. The error message contains first the
- * given message and then a list of validation errors in the given
- * validatables.
+ * Constructs a new {@code InvalidValueException} with a set of causing
+ * validation exceptions. The causing validation exceptions are included
+ * when the exception is painted to the client.
*
* @param message
* The detail message of the problem.
* @param causes
- * Array of validatables whos invalidities are possiblity
- * causing the invalidity.
+ * One or more {@code InvalidValueException}s that caused
+ * this exception.
*/
public InvalidValueException(String message,
InvalidValueException[] causes) {
@@ -101,9 +112,12 @@ public interface Validator extends Serializable {
}
/**
- * See if the error message doesn't paint anything visible.
+ * Check if the error message should be hidden.
+ *
+ * An empty (null or "") message is invisible unless it contains nested
+ * exceptions that are visible.
*
- * @return True iff the paint method does not paint anything visible.
+ * @return true if the error message should be hidden, false otherwise
*/
public boolean isInvisible() {
String msg = getMessage();
@@ -120,10 +134,21 @@ public interface Validator extends Serializable {
return true;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.ErrorMessage#getErrorLevel()
+ */
public final int getErrorLevel() {
return ErrorMessage.ERROR;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.Paintable#paint(com.vaadin.terminal.PaintTarget)
+ */
public void paint(PaintTarget target) throws PaintException {
target.startTag("error");
target.addAttribute("level", "error");
@@ -142,33 +167,85 @@ public interface Validator extends Serializable {
target.endTag("error");
}
- /* Documented in super interface */
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.ErrorMessage#addListener(com.vaadin.terminal.
+ * Paintable.RepaintRequestListener)
+ */
public void addListener(RepaintRequestListener listener) {
}
- /* Documented in super interface */
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.ErrorMessage#removeListener(com.vaadin.terminal
+ * .Paintable.RepaintRequestListener)
+ */
public void removeListener(RepaintRequestListener listener) {
}
- /* Documented in super interface */
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.ErrorMessage#requestRepaint()
+ */
public void requestRepaint() {
}
- /* Documented in super interface */
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Paintable#requestRepaintRequests()
+ */
public void requestRepaintRequests() {
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Paintable#getDebugId()
+ */
public String getDebugId() {
return null;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.Paintable#setDebugId(java.lang.String)
+ */
public void setDebugId(String id) {
throw new UnsupportedOperationException(
- "Setting testing id for this Paintable is not implemented");
+ "InvalidValueException cannot have a debug id");
+ }
+
+ /**
+ * Returns the {@code InvalidValueExceptions} that caused this
+ * exception.
+ *
+ * @return An array containing the {@code InvalidValueExceptions} that
+ * caused this exception. Returns an empty array if this
+ * exception was not caused by other exceptions.
+ */
+ public InvalidValueException[] getCauses() {
+ return causes;
}
}
+ /**
+ * A specific type of {@link InvalidValueException} that indicates that
+ * validation failed because the value was empty. What empty means is up to
+ * the thrower.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.3.0
+ */
@SuppressWarnings("serial")
public class EmptyValueException extends Validator.InvalidValueException {
diff --git a/src/com/vaadin/data/doc-files/Container_full.gif b/src/com/vaadin/data/doc-files/Container_full.gif
deleted file mode 100644
index 28ee720e32..0000000000
--- a/src/com/vaadin/data/doc-files/Container_full.gif
+++ /dev/null
Binary files differ
diff --git a/src/com/vaadin/data/doc-files/Container_simple.gif b/src/com/vaadin/data/doc-files/Container_simple.gif
deleted file mode 100644
index 6db32581b8..0000000000
--- a/src/com/vaadin/data/doc-files/Container_simple.gif
+++ /dev/null
Binary files differ
diff --git a/src/com/vaadin/data/doc-files/Item.gif b/src/com/vaadin/data/doc-files/Item.gif
deleted file mode 100644
index 4cd202ceba..0000000000
--- a/src/com/vaadin/data/doc-files/Item.gif
+++ /dev/null
Binary files differ
diff --git a/src/com/vaadin/data/doc-files/Property.gif b/src/com/vaadin/data/doc-files/Property.gif
deleted file mode 100644
index 4aff19db1c..0000000000
--- a/src/com/vaadin/data/doc-files/Property.gif
+++ /dev/null
Binary files differ
diff --git a/src/com/vaadin/data/doc-files/datalayer.gif b/src/com/vaadin/data/doc-files/datalayer.gif
deleted file mode 100644
index b3d25639e0..0000000000
--- a/src/com/vaadin/data/doc-files/datalayer.gif
+++ /dev/null
Binary files differ
diff --git a/src/com/vaadin/data/package.html b/src/com/vaadin/data/package.html
index f6e0a2d645..a14ea1ac88 100644
--- a/src/com/vaadin/data/package.html
+++ b/src/com/vaadin/data/package.html
@@ -5,82 +5,45 @@
<body bgcolor="white">
-<p>Provides interfaces for the data layer which contains classes
-for typed data values, data collections, and handlers. A
-{@link com.vaadin.data.Property Property} is a simple typed data
-value; an {@link com.vaadin.data.Item Item} is a collection of
-Properties, each corresponding to a unique identifier; a
-{@link com.vaadin.data.Container Container} is a collection of
-identified Items with special constraints; a
-{@link com.vaadin.data.Buffered Buffered} class is able to track
-its changes and to commit or discard them later.</p>
+<p>Contains interfaces for the data layer, mainly for binding typed
+data and data collections to components, and for validating data.</p>
-<h2>Package Specification</h2>
+<h2>Data binding</h2>
-<p>The package contains a three-tiered structure for typed data objects and
-collections of them:</p>
-
-<center>
-<p><a href=Property.html target="classFrame"><img src=doc-files/Property.gif></a></p>
-<p><a href=Item.html target="classFrame"><img src=doc-files/Item.gif></a></p>
-<p><a href=Container.html target="classFrame"><img src=doc-files/Container_simple.gif></a></p>
-</center>
+<p>The package contains a three-tiered structure for typed data
+objects and collections of them:</p>
<ul>
- <li>The simplest of these is the
- {@link com.vaadin.data.Property Property} which represents a
- single typed data value. A Property may be read-only in which
- case attempts to modify its contents will throw an exception.
-
- <li>The second level of the data layer is represented by the
- {@link com.vaadin.data.Item Item} which embodies a set of
- <i>Properties</i>. Each Property in an Item corresponds to a locally
- unique(that is, inside the Item) identifier.
-
- <li>The third level is called the
- {@link com.vaadin.data.Container Container} which can be
- visualized as a set of Items, each corresponding to a locally unique
- identifier. Note that the Container imposes a few restrictions on the
- data stored in it, see further documentation in the
- <a href=Container.html target="classFrame">class specification</a>.
+ <li>A {@link com.vaadin.data.Property Property} represents a
+ single, typed data value.
+
+ <li>An {@link com.vaadin.data.Item Item} embodies a set of <i>Properties</i>.
+ A locally unique (inside the {@link com.vaadin.data.Item Item})
+ Property identifier corresponds to each Property inside the Item.</li>
+ <li>A {@link com.vaadin.data.Container Container} contains a set
+ of Items, each corresponding to a locally unique Item identifier. Note
+ that Container imposes a few restrictions on the data stored in it, see
+ {@link com.vaadin.data.Container Container} for further information.</li>
</ul>
-<p>In addition to these interfaces the package contains the
-{@link com.vaadin.data.Buffered Buffered} interface, which defines
-the methods to make an object buffered, that is, track the changes to an
-object and allow committing or discarding them at a later time.</p>
-
-<p>Provides interfaces for the validation framework. The framework
-defines two interfaces; one for classes that need to support external
-validation, and another one for the validators themselves.</p>
+<p>For more information on the data model, see the <a
+ href="http://vaadin.com/book/-/page/datamodel.html">Data model
+chapter</a> in Book of Vaadin.</p>
-<h2>Validation</h2>
-
-<p>The most important method defined by the
-{@link com.vaadin.data.Validatable Validatable} interface is
-{@link com.vaadin.data.Validatable#isValid() isValid()}. It
-asks all registered validators to verify if the object's value is valid
-or not. Note that it depends on the validators registered for a object which
-values are valid and which are not. For example, a <code>null</code> value
-can be valid value for one validator but invalid for another.<p>
+<h2>Buffering</h2>
-<p>In addition to <code>isValid()</code>, <code>Validatable</code> defines
-methods to add, remove and list validators of a validatable object.</p>
+<p>A {@link com.vaadin.data.Buffered Buffered} implementor is able
+to track and buffer changes and commit or discard them later.</p>
-<p>{@link com.vaadin.data.Validator Validator} defines the
-interface for an external validator. These validators may be added to
-any <code>Validatable</code> object, and their task is to check, when
-requested, that the object which they are attached to contains a valid
-value. The actual validation logic is hidden in the
-{@link com.vaadin.data.Validator#validate(Object) validate(Object)}
-method.</p>
+<h2>Validation</h2>
-<p>In addition to <code>check(Object)</code>, <code>Validator</code> defines
-the <code>InvalidValueException</code> which is used to signal that a
-checked value is invalid, and the
-{@link com.vaadin.data.Validator.Suggestive Suggestive}
-subinterface which includes functionality to suggest a valid value for
-the validated object.</p><!-- Put @see and @since tags down here. -->
+<p>{@link com.vaadin.data.Validator Validator} implementations are
+used to validate data, typically the value of a {@link
+com.vaadin.ui.Field Field}. One or more {@link com.vaadin.data.Validator
+Validators} can be added to a {@link com.vaadin.data.Validatable
+Validatable} implementor and then used to validate the value of the
+Validatable. </p>
+<!-- Put @see and @since tags down here. -->
</body>
</html>
diff --git a/src/com/vaadin/data/util/BeanItemContainer.java b/src/com/vaadin/data/util/BeanItemContainer.java
index e99f219dbb..68ac574df2 100644
--- a/src/com/vaadin/data/util/BeanItemContainer.java
+++ b/src/com/vaadin/data/util/BeanItemContainer.java
@@ -5,7 +5,7 @@ package com.vaadin.data.util;
import java.beans.PropertyDescriptor;
import java.io.IOException;
-import java.util.ArrayList;
+import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -28,13 +28,30 @@ import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Property.ValueChangeNotifier;
/**
- * An {@link ArrayList} backed container for {@link BeanItem}s.
+ * An in-memory container for JavaBeans.
+ *
+ * <p>
+ * The properties of the container are determined automatically by introspecting
+ * the used JavaBean class. Only beans of the same type can be added to the
+ * container.
+ * </p>
+ *
+ * <p>
+ * BeanItemContainer uses the beans themselves as identifiers. The
+ * {@link Object#hashCode()} of a bean is used when storing and looking up beans
+ * so it must not change during the lifetime of the bean (it should not depend
+ * on any part of the bean that can be modified). Typically this restricts the
+ * implementation of {@link Object#equals(Object)} as well in order for it to
+ * fulfill the contract between {@code equals()} and {@code hashCode()}.
+ * </p>
+ *
* <p>
- * Bean objects act as identifiers. For this reason, they should implement
- * Object.equals(Object) and Object.hashCode().
+ * It is not possible to add additional properties to the container and nested
+ * bean properties are not supported.
* </p>
*
* @param <BT>
+ * The type of the Bean
*
* @since 5.4
*/
@@ -55,17 +72,33 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
private ListSet<BT> allItems = new ListSet<BT>();
/**
- * Maps all pojos (item ids) in the container (including filtered) to their
- * corresponding BeanItem.
+ * Maps all beans (item ids) in the container (including filtered) to their
+ * corresponding BeanItem. This requires the beans to implement
+ * {@link Object#equals(Object)} and {@link Object#hashCode()} so it is not
+ * affected by the contents of the bean.
*/
private final Map<BT, BeanItem<BT>> beanToItem = new HashMap<BT, BeanItem<BT>>();
- // internal data model to obtain property IDs etc.
+ /**
+ * The type of the beans in the container.
+ */
private final Class<? extends BT> type;
+
+ /**
+ * A description of the properties found in beans of type {@link #type}.
+ * Determines the property ids that are present in the container.
+ */
private transient LinkedHashMap<String, PropertyDescriptor> model;
+ /**
+ * Collection of listeners interested in
+ * {@link Container.ItemSetChangeEvent ItemSetChangeEvent} events.
+ */
private List<ItemSetChangeListener> itemSetChangeListeners;
+ /**
+ * Filters currently applied to the container.
+ */
private Set<Filter> filters = new HashSet<Filter>();
/**
@@ -73,7 +106,10 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
*/
private ItemSorter itemSorter = new DefaultItemSorter();
- /* Special serialization to handle method references */
+ /**
+ * A special deserialization method that resolves {@link #model} is needed
+ * as PropertyDescriptor is not {@link Serializable}.
+ */
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
@@ -81,12 +117,12 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
/**
- * Constructs BeanItemContainer for beans of a given type.
+ * Constructs a {@code BeanItemContainer} for beans of the given type.
*
* @param type
- * the class of beans to be used with this containers.
+ * the type of the beans that will be added to the container.
* @throws IllegalArgumentException
- * If the type is null
+ * If {@code type} is null
*/
public BeanItemContainer(Class<? extends BT> type) {
if (type == null) {
@@ -98,11 +134,13 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
/**
- * Constructs BeanItemContainer with given collection of beans in it. The
- * collection must not be empty or an IllegalArgument is thrown.
+ * Constructs a {@code BeanItemContainer} and adds the given beans to it.
+ * The collection must not be empty.
+ * {@link BeanItemContainer#BeanItemContainer(Class)} can be used for
+ * creating an initially empty {@code BeanItemContainer}.
*
* @param collection
- * non empty {@link Collection} of beans.
+ * a non empty {@link Collection} of beans.
* @throws IllegalArgumentException
* If the collection is null or empty.
*/
@@ -119,6 +157,13 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
addAll(collection);
}
+ /**
+ * Adds all the beans in {@code collection} in one go. More efficient than
+ * adding them one by one.
+ *
+ * @param collection
+ * The collection of beans to add. Must not be null.
+ */
private void addAll(Collection<BT> collection) {
// Pre-allocate space for the collection
allItems.ensureCapacity(allItems.size() + collection.size());
@@ -135,20 +180,23 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
/**
- * Unsupported operation.
- *
- * @see com.vaadin.data.Container.Indexed#addItemAt(int)
+ * Unsupported operation. Beans should be added through
+ * {@link #addItemAt(int, Object)}.
*/
public Object addItemAt(int index) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
- * Adds new item at given index.
+ * Adds a new bean at the given index.
*
* The bean is used both as the item contents and as the item identifier.
*
- * @see com.vaadin.data.Container.Indexed#addItemAt(int, Object)
+ * @param index
+ * Index at which the bean should be added.
+ * @param newItemId
+ * The bean to add to the container.
+ * @return Returns the new BeanItem or null if the operation fails.
*/
public BeanItem<BT> addItemAt(int index, Object newItemId)
throws UnsupportedOperationException {
@@ -164,7 +212,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
/**
- * Adds new item at given index of the internal (unfiltered) list.
+ * Adds a bean at the given index of the internal (unfiltered) list.
* <p>
* The item is also added in the visible part of the list if it passes the
* filters.
@@ -188,8 +236,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
/**
* Adds the bean to all internal data structures at the given position.
* Fails if the bean is already in the container or is not assignable to the
- * correct type. Returns the new BeanItem if the bean was added
- * successfully.
+ * correct type. Returns a new BeanItem if the bean was added successfully.
*
* <p>
* Caller should call {@link #filterAll()} after calling this method to
@@ -229,19 +276,26 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
return beanItem;
}
- @SuppressWarnings("unchecked")
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#getIdByIndex(int)
+ */
public BT getIdByIndex(int index) {
return filteredItems.get(index);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#indexOfId(java.lang.Object)
+ */
public int indexOfId(Object itemId) {
return filteredItems.indexOf(itemId);
}
/**
- * Unsupported operation.
- *
- * @see com.vaadin.data.Container.Ordered#addItemAfter(Object)
+ * Unsupported operation. Use {@link #addItemAfter(Object, Object)}.
*/
public Object addItemAfter(Object previousItemId)
throws UnsupportedOperationException {
@@ -249,7 +303,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
/**
- * Adds new item after the given item.
+ * Adds the bean after the given bean.
*
* The bean is used both as the item contents and as the item identifier.
*
@@ -268,6 +322,11 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#firstItemId()
+ */
public BT firstItemId() {
if (size() > 0) {
return getIdByIndex(0);
@@ -276,14 +335,29 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#isFirstId(java.lang.Object)
+ */
public boolean isFirstId(Object itemId) {
return firstItemId() == itemId;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#isLastId(java.lang.Object)
+ */
public boolean isLastId(Object itemId) {
return lastItemId() == itemId;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#lastItemId()
+ */
public BT lastItemId() {
if (size() > 0) {
return getIdByIndex(size() - 1);
@@ -292,6 +366,11 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object)
+ */
public BT nextItemId(Object itemId) {
int index = indexOfId(itemId);
if (index >= 0 && index < size() - 1) {
@@ -302,6 +381,11 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#prevItemId(java.lang.Object)
+ */
public BT prevItemId(Object itemId) {
int index = indexOfId(itemId);
if (index > 0) {
@@ -312,13 +396,17 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /**
+ * Unsupported operation. Properties are determined by the introspecting the
+ * bean class.
+ */
public boolean addContainerProperty(Object propertyId, Class<?> type,
Object defaultValue) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
- * Unsupported operation.
+ * Unsupported operation. Use {@link #addBean(Object)}.
*
* @see com.vaadin.data.Container#addItem()
*/
@@ -327,7 +415,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
/**
- * Creates a new Item with the bean into the Container.
+ * Adds the bean to the Container.
*
* The bean is used both as the item contents and as the item identifier.
*
@@ -338,7 +426,7 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
/**
- * Creates a new Item with the bean into the Container.
+ * Adds the bean to the Container.
*
* The bean is used both as the item contents and as the item identifier.
*
@@ -355,32 +443,68 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#containsId(java.lang.Object)
+ */
public boolean containsId(Object itemId) {
// only look at visible items after filtering
return filteredItems.contains(itemId);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object,
+ * java.lang.Object)
+ */
public Property getContainerProperty(Object itemId, Object propertyId) {
return getItem(itemId).getItemProperty(propertyId);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerPropertyIds()
+ */
public Collection<String> getContainerPropertyIds() {
return model.keySet();
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItem(java.lang.Object)
+ */
public BeanItem<BT> getItem(Object itemId) {
return beanToItem.get(itemId);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItemIds()
+ */
@SuppressWarnings("unchecked")
public Collection<BT> getItemIds() {
return (Collection<BT>) filteredItems.clone();
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getType(java.lang.Object)
+ */
public Class<?> getType(Object propertyId) {
return model.get(propertyId).getPropertyType();
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
public boolean removeAllItems() throws UnsupportedOperationException {
allItems.clear();
filteredItems.clear();
@@ -393,11 +517,20 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
return true;
}
+ /**
+ * Unsupported operation. Properties are determined by the introspecting the
+ * bean class.
+ */
public boolean removeContainerProperty(Object propertyId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
public boolean removeItem(Object itemId)
throws UnsupportedOperationException {
if (!allItems.remove(itemId)) {
@@ -412,6 +545,15 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
return true;
}
+ /**
+ * Make this container listen to the given property provided it notifies
+ * when its value changes.
+ *
+ * @param beanItem
+ * The BeanItem that contains the property
+ * @param propertyId
+ * The id of the property
+ */
private void addValueChangeListener(BeanItem<BT> beanItem, Object propertyId) {
Property property = beanItem.getItemProperty(propertyId);
if (property instanceof ValueChangeNotifier) {
@@ -423,6 +565,14 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /**
+ * Remove this container as a listener for the given property.
+ *
+ * @param item
+ * The BeanItem that contains the property
+ * @param propertyId
+ * The id of the property
+ */
private void removeValueChangeListener(BeanItem<BT> item, Object propertyId) {
Property property = item.getItemProperty(propertyId);
if (property instanceof ValueChangeNotifier) {
@@ -430,16 +580,33 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /**
+ * Remove this contains as a listener for all the properties in the given
+ * BeanItem.
+ *
+ * @param item
+ * The BeanItem that contains the properties
+ */
private void removeAllValueChangeListeners(BeanItem<BT> item) {
for (Object propertyId : item.getItemPropertyIds()) {
removeValueChangeListener(item, propertyId);
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#size()
+ */
public int size() {
return filteredItems.size();
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
+ */
public Collection<Object> getSortableContainerPropertyIds() {
LinkedList<Object> sortables = new LinkedList<Object>();
for (Object propertyId : getContainerPropertyIds()) {
@@ -479,6 +646,13 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
Collections.sort(allItems, getItemSorter());
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com.vaadin
+ * .data.Container.ItemSetChangeListener)
+ */
public void addListener(ItemSetChangeListener listener) {
if (itemSetChangeListeners == null) {
itemSetChangeListeners = new LinkedList<ItemSetChangeListener>();
@@ -486,12 +660,22 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
itemSetChangeListeners.add(listener);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin
+ * .data.Container.ItemSetChangeListener)
+ */
public void removeListener(ItemSetChangeListener listener) {
if (itemSetChangeListeners != null) {
itemSetChangeListeners.remove(listener);
}
}
+ /**
+ * Send an ItemSetChange event to all listeners.
+ */
private void fireItemSetChange() {
if (itemSetChangeListeners != null) {
final Container.ItemSetChangeEvent event = new Container.ItemSetChangeEvent() {
@@ -505,6 +689,13 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Filterable#addContainerFilter(java.lang.Object,
+ * java.lang.String, boolean, boolean)
+ */
public void addContainerFilter(Object propertyId, String filterString,
boolean ignoreCase, boolean onlyMatchPrefix) {
if (filters.isEmpty()) {
@@ -542,6 +733,13 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /**
+ * Remove (from the filtered list) any items that do not match the given
+ * filter.
+ *
+ * @param f
+ * The filter used to determine if items should be removed
+ */
protected void filter(Filter f) {
Iterator<BT> iterator = filteredItems.iterator();
while (iterator.hasNext()) {
@@ -552,6 +750,11 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Filterable#removeAllContainerFilters()
+ */
public void removeAllContainerFilters() {
if (!filters.isEmpty()) {
filters = new HashSet<Filter>();
@@ -563,6 +766,13 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Filterable#removeContainerFilters(java.lang
+ * .Object)
+ */
public void removeContainerFilters(Object propertyId) {
if (!filters.isEmpty()) {
for (Iterator<Filter> iterator = filters.iterator(); iterator
@@ -580,15 +790,37 @@ public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Property.ValueChangeListener#valueChange(com.vaadin.data
+ * .Property.ValueChangeEvent)
+ */
public void valueChange(ValueChangeEvent event) {
// if a property that is used in a filter is changed, refresh filtering
filterAll();
}
+ /**
+ * Returns the ItemSorter that is used for sorting the container.
+ *
+ * @see #setItemSorter(ItemSorter)
+ *
+ * @return The ItemSorter that is used for sorting the container
+ */
public ItemSorter getItemSorter() {
return itemSorter;
}
+ /**
+ * Sets the ItemSorter that is used for sorting the container. The
+ * {@link ItemSorter#compare(Object, Object)} method is called to compare
+ * two beans (item ids).
+ *
+ * @param itemSorter
+ * The ItemSorter to use when sorting the container
+ */
public void setItemSorter(ItemSorter itemSorter) {
this.itemSorter = itemSorter;
}
diff --git a/src/com/vaadin/data/util/DefaultItemSorter.java b/src/com/vaadin/data/util/DefaultItemSorter.java
index 1e83715ace..a2ecd38ef8 100644
--- a/src/com/vaadin/data/util/DefaultItemSorter.java
+++ b/src/com/vaadin/data/util/DefaultItemSorter.java
@@ -113,7 +113,10 @@ public class DefaultItemSorter implements ItemSorter {
* The first item to compare.
* @param item2
* The second item to compare.
- * @return
+ * @return a negative, zero, or positive integer if the property value in
+ * the first item is less than, equal to, or greater than the
+ * property value in the second item. Negated if {@code
+ * sortDirection} is false.
*/
protected int compareProperty(Object propertyId, boolean sortDirection,
Item item1, Item item2) {
diff --git a/src/com/vaadin/data/util/HierarchicalContainer.java b/src/com/vaadin/data/util/HierarchicalContainer.java
index 121d1cc936..108c544198 100644
--- a/src/com/vaadin/data/util/HierarchicalContainer.java
+++ b/src/com/vaadin/data/util/HierarchicalContainer.java
@@ -69,6 +69,10 @@ public class HierarchicalContainer extends IndexedContainer implements
*/
private boolean includeParentsWhenFiltering = true;
+ private boolean contentChangedEventsDisabled = false;
+
+ private boolean contentsChangedEventPending;
+
/*
* Can the specified Item have any children? Don't add a JavaDoc comment
* here, we use the default documentation from implemented interface.
@@ -378,6 +382,7 @@ public class HierarchicalContainer extends IndexedContainer implements
*/
@Override
public Object addItem() {
+ disableContentsChangeEvents();
final Object itemId = super.addItem();
if (itemId == null) {
return null;
@@ -391,10 +396,35 @@ public class HierarchicalContainer extends IndexedContainer implements
}
}
}
-
+ enableAndFireContentsChangeEvents();
return itemId;
}
+ @Override
+ protected void fireContentsChange(int addedItemIndex) {
+ if (contentsChangeEventsOn()) {
+ super.fireContentsChange(addedItemIndex);
+ } else {
+ contentsChangedEventPending = true;
+ }
+ }
+
+ private boolean contentsChangeEventsOn() {
+ return !contentChangedEventsDisabled;
+ }
+
+ private void disableContentsChangeEvents() {
+ contentChangedEventsDisabled = true;
+ }
+
+ private void enableAndFireContentsChangeEvents() {
+ contentChangedEventsDisabled = false;
+ if (contentsChangedEventPending) {
+ fireContentsChange(-1);
+ }
+ contentsChangedEventPending = false;
+ }
+
/*
* (non-Javadoc)
*
@@ -402,6 +432,7 @@ public class HierarchicalContainer extends IndexedContainer implements
*/
@Override
public Item addItem(Object itemId) {
+ disableContentsChangeEvents();
final Item item = super.addItem(itemId);
if (item == null) {
return null;
@@ -414,7 +445,7 @@ public class HierarchicalContainer extends IndexedContainer implements
filteredRoots.add(itemId);
}
}
-
+ enableAndFireContentsChangeEvents();
return item;
}
@@ -425,6 +456,7 @@ public class HierarchicalContainer extends IndexedContainer implements
*/
@Override
public boolean removeAllItems() {
+ disableContentsChangeEvents();
final boolean success = super.removeAllItems();
if (success) {
@@ -439,6 +471,7 @@ public class HierarchicalContainer extends IndexedContainer implements
filteredChildren = null;
}
}
+ enableAndFireContentsChangeEvents();
return success;
}
@@ -449,6 +482,7 @@ public class HierarchicalContainer extends IndexedContainer implements
*/
@Override
public boolean removeItem(Object itemId) {
+ disableContentsChangeEvents();
final boolean success = super.removeItem(itemId);
if (success) {
@@ -496,6 +530,8 @@ public class HierarchicalContainer extends IndexedContainer implements
noChildrenAllowed.remove(itemId);
}
+ enableAndFireContentsChangeEvents();
+
return success;
}
@@ -508,7 +544,10 @@ public class HierarchicalContainer extends IndexedContainer implements
* @return true if the operation succeeded
*/
public boolean removeItemRecursively(Object itemId) {
- return removeItemRecursively(this, itemId);
+ disableContentsChangeEvents();
+ boolean removeItemRecursively = removeItemRecursively(this, itemId);
+ enableAndFireContentsChangeEvents();
+ return removeItemRecursively;
}
/**
diff --git a/src/com/vaadin/data/util/package.html b/src/com/vaadin/data/util/package.html
index 332d8f8143..07e3acde9e 100644
--- a/src/com/vaadin/data/util/package.html
+++ b/src/com/vaadin/data/util/package.html
@@ -6,32 +6,13 @@
<body bgcolor="white">
-<p>Provides various utility classes that implement the data layer
-functionality.</p>
+<p>Provides implementations of Property, Item and Container
+interfaces, and utilities for the data layer.</p>
-<p>The first {@link com.vaadin.data.Property Property} class,
-{@link com.vaadin.data.util.ObjectProperty ObjectProperty}, provides
-a simple class containing a typed data value. The second,
-{@link com.vaadin.data.util.MethodProperty MethodProperty}, provides
-a way to bind a field of an object to the Property interface using the
-accessor methods for the field.</p>
-
-<p>The next level of the data layer, the
-{@link com.vaadin.data.Item Item}, is implemented by
-{@link com.vaadin.data.util.BeanItem BeanItem}, though it is only a
-simple wrapper to the former to provide the Item interface for any regular
-Java Bean.</p>
-
-<p>The third level, the {@link com.vaadin.data.Container Container},
-has several implementations in the {@link com.vaadin.data.util}
-package.</p>
-
-<!-- <h2>Package Specification</h2> -->
-
-<!-- Package spec here -->
-
-
-<!-- Put @see and @since tags down here. -->
+<p>Various Property, Item and Container implementations are provided
+in this package. Each implementation can have its own sets of
+constraints on the data it encapsulates and on how the implementation
+can be used. See the class javadocs for more information.</p>
</body>
</html>
diff --git a/src/com/vaadin/data/validator/AbstractStringValidator.java b/src/com/vaadin/data/validator/AbstractStringValidator.java
index 42dc5f25d9..359d83ef10 100644
--- a/src/com/vaadin/data/validator/AbstractStringValidator.java
+++ b/src/com/vaadin/data/validator/AbstractStringValidator.java
@@ -1,57 +1,71 @@
/*
@ITMillApache2LicenseForJavaFiles@
*/
-package com.vaadin.data.validator;
-
-/**
- * Validator base class for validating strings. See
- * {@link com.vaadin.data.validator.AbstractValidator} for more information.
- *
- * <p>
- * If the validation fails, the exception thrown contains the error message with
- * its argument 0 replaced with the string being validated.
- * </p>
- *
- * @author IT Mill Ltd.
- * @version
- * @VERSION@
- * @since 5.4
- */
-@SuppressWarnings("serial")
-public abstract class AbstractStringValidator extends AbstractValidator {
-
- /**
- * Constructs a validator for strings.
- * <p>
- * Null and empty string values are always accepted. To disallow empty
- * values, set the field being validated as required.
- * </p>
- *
- * @param errorMessage
- * the message included in the exception (with its parameter {0}
- * replaced by the string to be validated) in case the validation
- * fails
- */
- public AbstractStringValidator(String errorMessage) {
- super(errorMessage);
- }
-
- public boolean isValid(Object value) {
- if (value == null) {
- return true;
- }
- if (!(value instanceof String)) {
- return false;
- }
- return isValidString((String) value);
- }
-
- /**
- * Checks if the given string is valid.
- *
- * @param value
- * String to check. Can never be null.
- * @return true if the string is valid, false otherwise
- */
- protected abstract boolean isValidString(String value);
-}
+package com.vaadin.data.validator;
+
+/**
+ * Validator base class for validating strings. See
+ * {@link com.vaadin.data.validator.AbstractValidator} for more information.
+ *
+ * <p>
+ * To include the value that failed validation in the exception message you can
+ * use "{0}" in the error message. This will be replaced with the failed value
+ * (converted to string using {@link #toString()}) or "null" if the value is
+ * null.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.4
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractStringValidator extends AbstractValidator {
+
+ /**
+ * Constructs a validator for strings.
+ *
+ * <p>
+ * Null and empty string values are always accepted. To reject empty values,
+ * set the field being validated as required.
+ * </p>
+ *
+ * @param errorMessage
+ * the message to be included in an {@link InvalidValueException}
+ * (with "{0}" replaced by the value that failed validation).
+ * */
+ public AbstractStringValidator(String errorMessage) {
+ super(errorMessage);
+ }
+
+ /**
+ * Tests if the given value is a valid string.
+ * <p>
+ * Null values are always accepted. Values that are not {@link String}s are
+ * always rejected. Uses {@link #isValidString(String)} to validate the
+ * value.
+ * </p>
+ *
+ * @param value
+ * the value to check
+ * @return true if the value is a valid string, false otherwise
+ */
+ public boolean isValid(Object value) {
+ if (value == null) {
+ return true;
+ }
+ if (!(value instanceof String)) {
+ return false;
+ }
+ return isValidString((String) value);
+ }
+
+ /**
+ * Checks if the given string is valid.
+ *
+ * @param value
+ * String to check. Can never be null.
+ * @return true if the string is valid, false otherwise
+ */
+ protected abstract boolean isValidString(String value);
+}
diff --git a/src/com/vaadin/data/validator/AbstractValidator.java b/src/com/vaadin/data/validator/AbstractValidator.java
index af78faa442..84cf379f31 100644
--- a/src/com/vaadin/data/validator/AbstractValidator.java
+++ b/src/com/vaadin/data/validator/AbstractValidator.java
@@ -1,71 +1,79 @@
/*
@ITMillApache2LicenseForJavaFiles@
*/
-package com.vaadin.data.validator;
-
-import com.vaadin.data.Validator;
-
-/**
- * Default Validator base class. See {@link com.vaadin.data.validator.Validator}
- * for more information.
- * <p>
- * If the validation fails, the exception thrown contains the error message with
- * its argument 0 replaced with the toString() of the object being validated.
- * </p>
- *
- * @author IT Mill Ltd.
- * @version
- * @VERSION@
- * @since 5.4
- */
-@SuppressWarnings("serial")
-public abstract class AbstractValidator implements Validator {
-
- /**
- * Error message.
- */
- private String errorMessage;
-
- /**
- * Constructs a validator with an error message.
- *
- * @param errorMessage
- * the message included in the exception (with its parameter {0}
- * replaced by toString() of the object to be validated) in case
- * the validation fails
- */
- public AbstractValidator(String errorMessage) {
- this.errorMessage = errorMessage;
- }
-
- public void validate(Object value) throws InvalidValueException {
- if (!isValid(value)) {
- String message;
- if (value == null) {
- message = errorMessage.replace("{0}", "null");
- } else {
- message = errorMessage.replace("{0}", value.toString());
- }
- throw new InvalidValueException(message);
- }
- }
-
- /**
- * Gets the message to be displayed in case the value does not validate.
- *
- * @return the Error Message.
- */
- public String getErrorMessage() {
- return errorMessage;
- }
-
- /**
- * Sets the message to be displayed in case the value does not validate.
- *
- * @param errorMessage
- * the Error Message to set.
- */
- public void setErrorMessage(String errorMessage) {
- this.errorMessage = errorMessage;
- }
-}
+package com.vaadin.data.validator;
+
+import com.vaadin.data.Validator;
+
+/**
+ * Abstract {@link com.vaadin.data.validator.Validator Validator} implementation
+ * that provides a basic Validator implementation except the
+ * {@link #isValid(Object)} method. Sub-classes need to implement the
+ * {@link #isValid(Object)} method.
+ * <p>
+ * To include the value that failed validation in the exception message you can
+ * use "{0}" in the error message. This will be replaced with the failed value
+ * (converted to string using {@link #toString()}) or "null" if the value is
+ * null.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.4
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractValidator implements Validator {
+
+ /**
+ * Error message that is included in an {@link InvalidValueException} if
+ * such is thrown.
+ */
+ private String errorMessage;
+
+ /**
+ * Constructs a validator with the given error message.
+ *
+ * @param errorMessage
+ * the message to be included in an {@link InvalidValueException}
+ * (with "{0}" replaced by the value that failed validation).
+ */
+ public AbstractValidator(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ public void validate(Object value) throws InvalidValueException {
+ if (!isValid(value)) {
+ String message;
+ if (value == null) {
+ message = errorMessage.replace("{0}", "null");
+ } else {
+ message = errorMessage.replace("{0}", value.toString());
+ }
+ throw new InvalidValueException(message);
+ }
+ }
+
+ /**
+ * Returns the message to be included in the exception in case the value
+ * does not validate.
+ *
+ * @return the error message provided in the constructor or using
+ * {@link #setErrorMessage(String)}.
+ */
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ /**
+ * Sets the message to be included in the exception in case the value does
+ * not validate. The exception message is typically shown to the end user.
+ *
+ * @param errorMessage
+ * the error message. "{0}" is automatically replaced by the
+ * value that did not validate.
+ */
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+}
diff --git a/src/com/vaadin/data/validator/package.html b/src/com/vaadin/data/validator/package.html
index 8697eb6313..c991bfc82a 100644
--- a/src/com/vaadin/data/validator/package.html
+++ b/src/com/vaadin/data/validator/package.html
@@ -8,13 +8,16 @@
<!-- Package summary here -->
-<p>Provides validators for data contained in data-bound objects..</p>
+<p>Provides various {@link com.vaadin.data.Validator}
+implementations.</p>
-<!-- <h2>Package Specification</h2> -->
+<p>{@link com.vaadin.data.validator.AbstractValidator
+AbstractValidator} provides an abstract implementation of the {@link
+com.vaadin.data.Validator} interface and can be extended for custom
+validation needs. {@link
+com.vaadin.data.validator.AbstractStringValidator
+AbstractStringValidator} can also be extended if the value is a String.</p>
-<!-- Package spec here -->
-
-<!-- Put @see and @since tags down here. -->
</body>
</html>
diff --git a/src/com/vaadin/launcher/DemoLauncher.java b/src/com/vaadin/launcher/DemoLauncher.java
index d52df1f8ae..38b6060a27 100644
--- a/src/com/vaadin/launcher/DemoLauncher.java
+++ b/src/com/vaadin/launcher/DemoLauncher.java
@@ -34,7 +34,7 @@ import com.vaadin.launcher.util.BrowserLauncher;
*/
public class DemoLauncher {
- public static void main(String[] args) {
+ public static void main(String[] args) throws Exception {
final Map serverArgs = DevelopmentServerLauncher.parseArguments(args);
boolean deployed = false;
@@ -59,7 +59,7 @@ public class DemoLauncher {
}
}
- // Start the Winstone servlet container
+ // Start the Jetty servlet container
final String url = DevelopmentServerLauncher.runServer(serverArgs,
"Demo Server");
diff --git a/src/com/vaadin/launcher/DevelopmentServerLauncher.java b/src/com/vaadin/launcher/DevelopmentServerLauncher.java
index b7caa881b1..8e6a71b2a6 100644
--- a/src/com/vaadin/launcher/DevelopmentServerLauncher.java
+++ b/src/com/vaadin/launcher/DevelopmentServerLauncher.java
@@ -30,6 +30,7 @@ public class DevelopmentServerLauncher {
* for options.
*
* @param args
+ * @throws Exception
*/
public static void main(String[] args) {
@@ -38,16 +39,19 @@ public class DevelopmentServerLauncher {
// Start Jetty
System.out.println("Starting Jetty servlet container.");
- final String url = runServer(serverArgs, "Development Server Mode");
-
- // Start Browser
- if (!serverArgs.containsKey("nogui") && url != null) {
- System.out.println("Starting Web Browser.");
+ String url;
+ try {
+ url = runServer(serverArgs, "Development Server Mode");
+ // Start Browser
+ if (!serverArgs.containsKey("nogui") && url != null) {
+ System.out.println("Starting Web Browser.");
- // Open browser into application URL
- BrowserLauncher.openBrowser(url);
+ // Open browser into application URL
+ BrowserLauncher.openBrowser(url);
+ }
+ } catch (Exception e) {
+ // NOP exception already on console by jetty
}
-
}
/**
@@ -55,9 +59,11 @@ public class DevelopmentServerLauncher {
*
* @param serverArgs
* @return
+ * @throws Exception
+ * @throws Exception
*/
protected static String runServer(Map<String, String> serverArgs,
- String mode) {
+ String mode) throws Exception {
// Assign default values for some arguments
assignDefault(serverArgs, "webroot", "WebContent");
@@ -81,27 +87,27 @@ public class DevelopmentServerLauncher {
+ serverPort
+ "\n-------------------------------------------------\n");
- try {
- final Server server = new Server();
+ final Server server = new Server();
- final Connector connector = new SelectChannelConnector();
+ final Connector connector = new SelectChannelConnector();
- connector.setPort(port);
- server.setConnectors(new Connector[] { connector });
+ connector.setPort(port);
+ server.setConnectors(new Connector[] { connector });
- final WebAppContext webappcontext = new WebAppContext();
- String path = DevelopmentServerLauncher.class.getPackage()
- .getName().replace(".", File.separator);
- webappcontext.setDefaultsDescriptor(path + File.separator
- + "jetty-webdefault.xml");
- webappcontext.setContextPath(serverArgs.get("context"));
- webappcontext.setWar(serverArgs.get("webroot"));
- server.setHandler(webappcontext);
+ final WebAppContext webappcontext = new WebAppContext();
+ String path = DevelopmentServerLauncher.class.getPackage().getName()
+ .replace(".", File.separator);
+ webappcontext.setDefaultsDescriptor(path + File.separator
+ + "jetty-webdefault.xml");
+ webappcontext.setContextPath(serverArgs.get("context"));
+ webappcontext.setWar(serverArgs.get("webroot"));
+ server.setHandler(webappcontext);
+ try {
server.start();
- } catch (final Exception e) {
- e.printStackTrace();
- return null;
+ } catch (Exception e) {
+ server.stop();
+ throw e;
}
return "http://localhost:" + port + serverArgs.get("context");
diff --git a/src/com/vaadin/package.html b/src/com/vaadin/package.html
index 5a2defa80b..6142fcb8de 100644
--- a/src/com/vaadin/package.html
+++ b/src/com/vaadin/package.html
@@ -5,34 +5,23 @@
<body bgcolor="white">
-<p>This package is the base of Vaadin. The base package
-contains the Application class, the starting point of any aplication that uses Vaadin.
-The sub-packages from this point contain application components and utilities.
-</p>
+<p>The Vaadin base package. Contains the Application class, the
+starting point of any application that uses Vaadin.</p>
-<h2>Package Specification</h2>
+<p>Contains all Vaadin core classes. A Vaadin application is based
+on the {@link com.vaadin.Application} class and deployed as a servlet
+using {@link com.vaadin.terminal.gwt.server.ApplicationServlet} or
+{@link com.vaadin.terminal.gwt.server.GAEApplicationServlet} (for Google
+App Engine).</p>
- <b>Vaadin is composed of the following packages:</b>
- <dl>
- <dt>com.vaadin.data</dt>
- <dd>A library of interfaces for transparently binding UI components to datasources.</dd>
- <dt>com.vaadin.data.util</dt>
- <dd>Basic implementations of data-intfaces and utidtties for working with data-bound components.</dd>
- <dt>com.vaadin.data.validator</dt>
- <dd>Classes providing data-validation for Properties.</dd>
- <dt>com.vaadin.event</dt>
- <dd>Interfaces defining how to send and recieve events.</dd>
- <dt>com.vaadin.service</dt>
- <dd>Classes provding miscelaneous utidtty services.</dd>
- <dt>com.vaadin.terminal</dt>
- <dd>Classes and interfaces for implementing the terminal specific adapters.</dd>
- <dt>com.vaadin.terminal.web</dt>
- <dd>Classes that implement support both for AJAX-based and more limited web browsers.</dd>
- <dt>com.vaadin.ui</dt>
- <dd>UI components.</dd>
- </dl>
+<p>Vaadin applications can also be deployed as portlets using {@link
+com.vaadin.terminal.gwt.server.ApplicationPortlet] (JSR-168) or {@link
+com.vaadin.terminal.gwt.server.ApplicationPortlet2} (JSR-286).</p>
+
+<p>All classes in Vaadin are serializable unless otherwise noted.
+This allows Vaadin applications to run in cluster and cloud
+environments.</p>
-<!-- Put @see and @since tags down here. -->
</body>
</html>
diff --git a/src/com/vaadin/service/FileTypeResolver.java b/src/com/vaadin/service/FileTypeResolver.java
index 566f299376..5603a722d6 100644
--- a/src/com/vaadin/service/FileTypeResolver.java
+++ b/src/com/vaadin/service/FileTypeResolver.java
@@ -33,7 +33,7 @@ public class FileTypeResolver implements Serializable {
* Default icon given if no icon is specified for a mime-type.
*/
static public Resource DEFAULT_ICON = new ThemeResource(
- "icon/files/file.gif");
+ "../runo/icons/16/document.png");
/**
* Default mime-type.
@@ -195,12 +195,12 @@ public class FileTypeResolver implements Serializable {
/**
* File extension to MIME type mapping.
*/
- static private Hashtable extToMIMEMap = new Hashtable();
+ static private Hashtable<String, String> extToMIMEMap = new Hashtable<String, String>();
/**
* MIME type to Icon mapping.
*/
- static private Hashtable MIMEToIconMap = new Hashtable();
+ static private Hashtable<String, Resource> MIMEToIconMap = new Hashtable<String, Resource>();
static {
@@ -218,8 +218,9 @@ public class FileTypeResolver implements Serializable {
}
// Initialize Icons
- addIcon("inode/drive", new ThemeResource("icon/files/drive.gif"));
- addIcon("inode/directory", new ThemeResource("icon/files/folder.gif"));
+ ThemeResource folder = new ThemeResource("../runo/icons/16/folder.png");
+ addIcon("inode/drive", folder);
+ addIcon("inode/directory", folder);
}
/**
@@ -254,7 +255,7 @@ public class FileTypeResolver implements Serializable {
}
// Return type from extension map, if found
- final String type = (String) extToMIMEMap.get(ext);
+ final String type = extToMIMEMap.get(ext);
if (type != null) {
return type;
}
@@ -274,9 +275,11 @@ public class FileTypeResolver implements Serializable {
* @return the icon corresponding to the given file
*/
public static Resource getIcon(String fileName) {
+ return getIconByMineType(getMIMEType(fileName));
+ }
- final String mimeType = getMIMEType(fileName);
- final Resource icon = (Resource) MIMEToIconMap.get(mimeType);
+ private static Resource getIconByMineType(String mimeType) {
+ final Resource icon = MIMEToIconMap.get(mimeType);
if (icon != null) {
return icon;
}
@@ -297,16 +300,7 @@ public class FileTypeResolver implements Serializable {
* @return the icon corresponding to the given file
*/
public static Resource getIcon(File file) {
-
- final String mimeType = getMIMEType(file);
- final Resource icon = (Resource) MIMEToIconMap.get(mimeType);
- if (icon != null) {
- return icon;
- }
-
- // If nothing is known about the file-type, general file
- // icon is used
- return DEFAULT_ICON;
+ return getIconByMineType(getMIMEType(file));
}
/**
@@ -371,7 +365,7 @@ public class FileTypeResolver implements Serializable {
* @return unmodifiable map containing the current file extension to
* mime-type mapping
*/
- public static Map getExtensionToMIMETypeMapping() {
+ public static Map<String, String> getExtensionToMIMETypeMapping() {
return Collections.unmodifiableMap(extToMIMEMap);
}
@@ -380,7 +374,7 @@ public class FileTypeResolver implements Serializable {
*
* @return unmodifiable map containing the current mime-type to icon mapping
*/
- public static Map getMIMETypeToIconMapping() {
+ public static Map<String, Resource> getMIMETypeToIconMapping() {
return Collections.unmodifiableMap(MIMEToIconMap);
}
}
diff --git a/src/com/vaadin/terminal/ParameterHandler.java b/src/com/vaadin/terminal/ParameterHandler.java
index 501dbcd992..be124d4bbd 100644
--- a/src/com/vaadin/terminal/ParameterHandler.java
+++ b/src/com/vaadin/terminal/ParameterHandler.java
@@ -7,17 +7,21 @@ package com.vaadin.terminal;
import java.io.Serializable;
import java.util.Map;
+import com.vaadin.ui.Window;
+
/**
- * Interface implemented by all the classes capable of handling external
- * parameters.
+ * {@code ParameterHandler} is implemented by classes capable of handling
+ * external parameters.
*
* <p>
- * Some terminals can provide external parameters for application. For example
- * GET and POST parameters are passed to application as external parameters on
- * Web Adapter. The parameters can be received at any time during the
- * application lifecycle. All the parameter handlers implementing this interface
- * and registered to {@link com.vaadin.ui.Window} receive all the parameters got
- * from the terminal in the given window.
+ * What parameters are provided depend on what the {@link Terminal} provides and
+ * if the application is deployed as a servlet or portlet. URL GET parameters
+ * are typically provided to the {@link #handleParameters(Map)} method.
+ * </p>
+ * <p>
+ * A {@code ParameterHandler} must be registered to a {@code Window} using
+ * {@link Window#addParameterHandler(ParameterHandler)} to be called when
+ * parameters are available.
* </p>
*
* @author IT Mill Ltd.
@@ -28,27 +32,25 @@ import java.util.Map;
public interface ParameterHandler extends Serializable {
/**
- * <p>
- * Handles the given parameters. The parameters are given as inmodifieable
- * name to value map. All parameters names are of type:
- * {@link java.lang.String}. All the parameter values are arrays of strings.
- * </p>
+ * Handles the given parameters. All parameters names are of type
+ * {@link String} and the values are {@link String} arrays.
*
* @param parameters
- * the Inmodifiable name to value[] mapping.
+ * an unmodifiable map which contains the parameter names and
+ * values
*
*/
public void handleParameters(Map<String, String[]> parameters);
/**
- * ParameterHandler error event.
+ * An ErrorEvent implementation for ParameterHandler.
*/
public interface ErrorEvent extends Terminal.ErrorEvent {
/**
- * Gets the source ParameterHandler.
+ * Gets the ParameterHandler that caused the error.
*
- * @return the source Parameter Handler.
+ * @return the ParameterHandler that caused the error
*/
public ParameterHandler getParameterHandler();
diff --git a/src/com/vaadin/terminal/URIHandler.java b/src/com/vaadin/terminal/URIHandler.java
index fe1746201e..2d5facb28e 100644
--- a/src/com/vaadin/terminal/URIHandler.java
+++ b/src/com/vaadin/terminal/URIHandler.java
@@ -8,12 +8,10 @@ import java.io.Serializable;
import java.net.URL;
/**
- * Interface implemented by all the classes capable of handling URI:s.
- *
- * <p>
- * <code>URIHandler</code> can provide <code>DownloadStream</code> for
- * transferring data for client.
- * </p>
+ * A URIHandler is used for handling URI:s requested by the user and can
+ * optionally provide a {@link DownloadStream}. If a {@link DownloadStream} is
+ * returned by {@link #handleURI(URL, String)}, the stream is sent to the
+ * client.
*
* @author IT Mill Ltd.
* @version
@@ -23,27 +21,26 @@ import java.net.URL;
public interface URIHandler extends Serializable {
/**
- * Handles a given relative URI. If the URI handling wants to emit a
- * downloadable stream it can return download stream object. If no emitting
- * stream is necessary, null should be returned instead.
+ * Handles a given URI. If the URI handler to emit a downloadable stream it
+ * should return a {@code DownloadStream} object.
*
* @param context
- * the URl.
+ * the base URL
* @param relativeUri
- * the relative uri.
- * @return the download stream object.
+ * a URI relative to {@code context}
+ * @return A downloadable stream or null if no stream is provided
*/
public DownloadStream handleURI(URL context, String relativeUri);
/**
- * URIHandler error event.
+ * An {@code ErrorEvent} implementation for URIHandler.
*/
public interface ErrorEvent extends Terminal.ErrorEvent {
/**
- * Gets the source URIHandler.
+ * Gets the URIHandler that caused this error.
*
- * @return the URIHandler.
+ * @return the URIHandler that caused the error
*/
public URIHandler getURIHandler();
diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
index 619404a065..9d649bcd99 100755
--- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
+++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
@@ -12,7 +12,6 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Set;
-import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
@@ -29,7 +28,6 @@ import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.impl.HTTPRequestImpl;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
@@ -483,11 +481,9 @@ public class ApplicationConnection {
endRequest();
}
} else {
- // Synchronized call, discarded response
-
- syncSendForce(((HTTPRequestImpl) GWT.create(HTTPRequestImpl.class))
- .createXmlHTTPRequest(), uri + "&" + PARAM_UNLOADBURST
- + "=1", rd);
+ // Synchronized call, discarded response (leaving the page)
+ SynchronousXHR syncXHR = (SynchronousXHR) SynchronousXHR.create();
+ syncXHR.synchronousPost(uri + "&" + PARAM_UNLOADBURST + "=1", rd);
}
}
@@ -521,19 +517,6 @@ public class ApplicationConnection {
}
}
- private native void syncSendForce(JavaScriptObject xmlHttpRequest,
- String uri, String requestData)
- /*-{
- try {
- xmlHttpRequest.open("POST", uri, false);
- xmlHttpRequest.setRequestHeader("Content-Type", "text/plain;charset=utf-8");
- xmlHttpRequest.send(requestData);
- } catch (e) {
- // No errors are managed as this is synchronous forceful send that can just fail
- }
- this.@com.vaadin.terminal.gwt.client.ApplicationConnection::endRequest()();
- }-*/;
-
private void startRequest() {
activeRequests++;
requestStartTime = new Date();
diff --git a/src/com/vaadin/terminal/gwt/client/CSSRule.java b/src/com/vaadin/terminal/gwt/client/CSSRule.java
index d50f7ce21b..0931c23d86 100644
--- a/src/com/vaadin/terminal/gwt/client/CSSRule.java
+++ b/src/com/vaadin/terminal/gwt/client/CSSRule.java
@@ -13,16 +13,30 @@ public class CSSRule {
private final String selector;
private JavaScriptObject rules = null;
- public CSSRule(String selector) {
+ /**
+ *
+ * @param selector
+ * the CSS selector to search for in the stylesheets
+ * @param deep
+ * should the search follow any @import statements?
+ */
+ public CSSRule(final String selector, final boolean deep) {
this.selector = selector;
- fetchRule(selector);
+ fetchRule(selector, deep);
}
// TODO how to find the right LINK-element? We should probably give the
// stylesheet a name.
- private native void fetchRule(final String selector)
- /*-{
- this.@com.vaadin.terminal.gwt.client.CSSRule::rules = @com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)($doc.styleSheets[1], selector);
+ private native void fetchRule(final String selector, final boolean deep) /*-{
+ var sheets = $doc.styleSheets;
+ for(var i = 0; i < sheets.length; i++) {
+ var sheet = sheets[i];
+ if(sheet.href && sheet.href.indexOf("VAADIN/themes")>-1) {
+ this.@com.vaadin.terminal.gwt.client.CSSRule::rules = @com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet, selector, deep);
+ return;
+ }
+ }
+ this.@com.vaadin.terminal.gwt.client.CSSRule::rules = [];
}-*/;
/*
@@ -30,38 +44,48 @@ public class CSSRule {
* 'rules' array. The array is reverse ordered (last one found is first).
*/
private static native JavaScriptObject searchForRule(
- JavaScriptObject sheet, final String selector)
- /*-{
- if(!$doc.styleSheets)
+ final JavaScriptObject sheet, final String selector,
+ final boolean deep) /*-{
+ if(!$doc.styleSheets)
return null;
-
- selector = selector.toLowerCase();
-
- var allMatches = [];
-
- var theRules = new Array();
- if (sheet.cssRules)
+
+ selector = selector.toLowerCase();
+
+ var allMatches = [];
+
+ // IE handles imported sheet differently
+ if(deep && sheet.imports && sheet.imports.length > 0) {
+ for(var i=0; i < sheet.imports.length; i++) {
+ var imports = @com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet.imports[i], selector, deep);
+ allMatches.concat(imports);
+ }
+ }
+
+ var theRules = new Array();
+ if (sheet.cssRules)
theRules = sheet.cssRules
- else if (sheet.rules)
+ else if (sheet.rules)
theRules = sheet.rules
-
+
var j = theRules.length;
for(var i=0; i<j; i++) {
- var r = theRules[i];
- if(r.type == 3) {
- allMatches.unshift(@com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)(r.styleSheet, selector));
- } else if(r.type == 1) {
- var selectors = r.selectorText.toLowerCase().split(",");
- var n = selectors.length;
- for(var m=0; m<n; m++) {
- if(selectors[m].replace(/^\s+|\s+$/g, "") == selector) {
- allMatches.unshift(r);
- break; // No need to loop other selectors for this rule
- }
- }
- }
+ var r = theRules[i];
+ if(r.type == 1 || sheet.imports) {
+ var selectors = r.selectorText.toLowerCase().split(",");
+ var n = selectors.length;
+ for(var m=0; m<n; m++) {
+ if(selectors[m].replace(/^\s+|\s+$/g, "") == selector) {
+ allMatches.unshift(r);
+ break; // No need to loop other selectors for this rule
+ }
}
-
+ } else if(deep && r.type == 3) {
+ // Search @import stylesheet
+ var imports = @com.vaadin.terminal.gwt.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(r.styleSheet, selector, deep);
+ allMatches = allMatches.concat(imports);
+ }
+ }
+
return allMatches;
}-*/;
@@ -69,14 +93,15 @@ public class CSSRule {
* Returns a specific property value from this CSS rule.
*
* @param propertyName
- * @return
+ * camelCase CSS property name
+ * @return the value of the property as a String
*/
- public native String getPropertyValue(final String propertyName)
- /*-{
- for(var i=0; i<this.@com.vaadin.terminal.gwt.client.CSSRule::rules.length; i++){
- var value = this.@com.vaadin.terminal.gwt.client.CSSRule::rules[i].style[propertyName];
- if(value)
- return value;
+ public native String getPropertyValue(final String propertyName) /*-{
+ var j = this.@com.vaadin.terminal.gwt.client.CSSRule::rules.length;
+ for(var i=0; i<j; i++) {
+ var value = this.@com.vaadin.terminal.gwt.client.CSSRule::rules[i].style[propertyName];
+ if(value)
+ return value;
}
return null;
}-*/;
diff --git a/src/com/vaadin/terminal/gwt/client/SynchronousXHR.java b/src/com/vaadin/terminal/gwt/client/SynchronousXHR.java
new file mode 100644
index 0000000000..991b256794
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/SynchronousXHR.java
@@ -0,0 +1,21 @@
+package com.vaadin.terminal.gwt.client;
+
+import com.google.gwt.xhr.client.XMLHttpRequest;
+
+public class SynchronousXHR extends XMLHttpRequest {
+
+ protected SynchronousXHR() {
+ }
+
+ public native final void synchronousPost(String uri, String requestData)
+ /*-{
+ try {
+ this.open("POST", uri, false);
+ this.setRequestHeader("Content-Type", "text/plain;charset=utf-8");
+ this.send(requestData);
+ } catch (e) {
+ // No errors are managed as this is synchronous forceful send that can just fail
+ }
+ }-*/;
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java
new file mode 100644
index 0000000000..cebdf1063f
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java
@@ -0,0 +1,69 @@
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.event.dom.client.HasScrollHandlers;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.ScrollPanel;
+
+/**
+ * A scrollhandlers similar to {@link ScrollPanel}.
+ *
+ */
+public class FocusableScrollPanel extends SimpleFocusablePanel implements
+ HasScrollHandlers {
+
+ public FocusableScrollPanel() {
+ // Prevent IE standard mode bug when a AbsolutePanel is contained.
+ Style style = getElement().getStyle();
+ style.setOverflow(Overflow.AUTO);
+ style.setProperty("zoom", "1");
+ style.setPosition(Position.RELATIVE);
+ }
+
+ public HandlerRegistration addScrollHandler(ScrollHandler handler) {
+ return addDomHandler(handler, ScrollEvent.getType());
+ }
+
+ /**
+ * Gets the horizontal scroll position.
+ *
+ * @return the horizontal scroll position, in pixels
+ */
+ public int getHorizontalScrollPosition() {
+ return getElement().getScrollLeft();
+ }
+
+ /**
+ * Gets the vertical scroll position.
+ *
+ * @return the vertical scroll position, in pixels
+ */
+ public int getScrollPosition() {
+ return getElement().getScrollTop();
+ }
+
+ /**
+ * Sets the horizontal scroll position.
+ *
+ * @param position
+ * the new horizontal scroll position, in pixels
+ */
+ public void setHorizontalScrollPosition(int position) {
+ getElement().setScrollLeft(position);
+ }
+
+ /**
+ * Sets the vertical scroll position.
+ *
+ * @param position
+ * the new vertical scroll position, in pixels
+ */
+ public void setScrollPosition(int position) {
+ getElement().setScrollTop(position);
+ }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/SimpleFocusablePanel.java b/src/com/vaadin/terminal/gwt/client/ui/SimpleFocusablePanel.java
new file mode 100644
index 0000000000..b534e3faba
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/client/ui/SimpleFocusablePanel.java
@@ -0,0 +1,61 @@
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.HasKeyPressHandlers;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.terminal.gwt.client.Focusable;
+
+/**
+ * Compared to FocusPanel in GWT this panel does not support eg. accesskeys, but
+ * is simpler by its dom hierarchy nor supports focusing via java api.
+ */
+public class SimpleFocusablePanel extends SimplePanel implements
+ HasFocusHandlers, HasBlurHandlers, HasKeyDownHandlers,
+ HasKeyPressHandlers, Focusable {
+
+ public SimpleFocusablePanel() {
+ // make focusable, as we don't need access key magic we don't need to
+ // use FocusImpl.createFocusable
+ getElement().setTabIndex(0);
+ }
+
+ public HandlerRegistration addFocusHandler(FocusHandler handler) {
+ return addDomHandler(handler, FocusEvent.getType());
+ }
+
+ public HandlerRegistration addBlurHandler(BlurHandler handler) {
+ return addDomHandler(handler, BlurEvent.getType());
+ }
+
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
+ return addDomHandler(handler, KeyPressEvent.getType());
+ }
+
+ public void setFocus(boolean focus) {
+ if (focus) {
+ FocusImpl.getFocusImplForPanel().focus(getElement());
+ } else {
+ FocusImpl.getFocusImplForPanel().blur(getElement());
+ }
+ }
+
+ public void focus() {
+ setFocus(true);
+ }
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java
index 5c359f70d4..f80edc4bc4 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java
@@ -9,6 +9,15 @@ import java.util.List;
import java.util.Stack;
import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Command;
@@ -21,7 +30,6 @@ import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.ContainerResizedListener;
@@ -29,8 +37,9 @@ import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.Util;
-public class VMenuBar extends Widget implements Paintable,
- CloseHandler<PopupPanel>, ContainerResizedListener {
+public class VMenuBar extends SimpleFocusablePanel implements Paintable,
+ CloseHandler<PopupPanel>, ContainerResizedListener, KeyPressHandler,
+ KeyDownHandler, BlurHandler, FocusHandler {
/** Set the CSS class name to allow styling. */
public static final String CLASSNAME = "v-menubar";
@@ -65,11 +74,25 @@ public class VMenuBar extends Widget implements Paintable,
public VMenuBar() {
// Create an empty horizontal menubar
this(false);
+
+ // Navigation is only handled by the root bar
+ addFocusHandler(this);
+ addBlurHandler(this);
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
}
public VMenuBar(boolean subMenu) {
super();
- setElement(DOM.createDiv());
items = new ArrayList<CustomMenuItem>();
popup = null;
@@ -884,4 +907,339 @@ public class VMenuBar extends Widget implements Paintable,
}
return w;
}
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent)
+ */
+ public void onKeyPress(KeyPressEvent event) {
+ if (handleNavigation(event.getNativeEvent().getKeyCode(), event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt.event.dom.client.KeyDownEvent)
+ */
+ public void onKeyDown(KeyDownEvent event) {
+ if (handleNavigation(event.getNativeEvent().getKeyCode(), event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Get the key that moves the selection upwards. By default it is the
+ * up arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that moves the selection downwards. By default it is the
+ * down arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that moves the selection left. By default it is the
+ * left arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that moves the selection right. By default it is the
+ * right arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * Get the key that selects a menu item. By default it is the Enter key but
+ * by overriding this you can change the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return KeyCodes.KEY_ENTER;
+ }
+
+ /**
+ * Get the key that closes the menu. By default it is the escape key but by
+ * overriding this yoy can change the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getCloseMenuKey() {
+ return KeyCodes.KEY_ESCAPE;
+ }
+
+ /**
+ * Handles the keyboard events handled by the MenuBar
+ *
+ * @param event
+ * The keyboard event received
+ * @return true iff the navigation event was handled
+ */
+ public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+ if (keycode == KeyCodes.KEY_TAB || ctrl || shift || !isEnabled()) {
+ // Do not handle tab key, nor ctrl keys
+ return false;
+ }
+
+ if (keycode == getNavigationLeftKey()) {
+ if (getSelected() == null) {
+ // If nothing is selected then select the last item
+ setSelected(items.get(items.size() - 1));
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu == null && getParentMenu() == null) {
+ // If this is the root menu then move to the right
+ int idx = items.indexOf(getSelected());
+ if (idx > 0) {
+ setSelected(items.get(idx - 1));
+ } else {
+ setSelected(items.get(items.size() - 1));
+ }
+
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+
+ } else if (getParentMenu().getParentMenu() == null) {
+
+ // Get the root menu
+ VMenuBar root = getParentMenu();
+
+ root.getSelected().getSubMenu().setSelected(null);
+ root.hideChildren();
+
+ // Get the root menus items and select the previous one
+ int idx = root.getItems().indexOf(root.getSelected());
+ idx = idx > 0 ? idx : root.getItems().size();
+ CustomMenuItem selected = root.getItems().get(--idx);
+
+ while (selected.isSeparator() || !selected.isEnabled()) {
+ idx = idx > 0 ? idx : root.getItems().size();
+ selected = root.getItems().get(--idx);
+ }
+
+ root.setSelected(selected);
+ root.showChildMenu(selected);
+ VMenuBar submenu = selected.getSubMenu();
+
+ // Select the first item in the newly open submenu
+ submenu.setSelected(submenu.getItems().get(0));
+
+ } else {
+ getParentMenu().getSelected().getSubMenu().setSelected(null);
+ getParentMenu().hideChildren();
+ }
+
+ return true;
+
+ } else if (keycode == getNavigationRightKey()) {
+
+ if (getSelected() == null) {
+ // If nothing is selected then select the first item
+ setSelected(items.get(0));
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu == null && getParentMenu() == null) {
+ // If this is the root menu then move to the right
+ int idx = items.indexOf(getSelected());
+
+ if (idx < items.size() - 1) {
+ setSelected(items.get(idx + 1));
+ } else {
+ setSelected(items.get(0));
+ }
+
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu == null
+ && getSelected().getSubMenu() != null) {
+ // If the item has a submenu then show it and move the selection
+ // there
+ showChildMenu(getSelected());
+ menuVisible = true;
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else if (visibleChildMenu == null) {
+
+ // Get the root menu
+ VMenuBar root = getParentMenu();
+ while(root.getParentMenu() != null){
+ root = root.getParentMenu();
+ }
+
+ // Hide the submenu
+ root.hideChildren();
+
+ // Get the root menus items and select the next one
+ int idx = root.getItems().indexOf(root.getSelected());
+ idx = idx < root.getItems().size()-1 ? idx : -1;
+ CustomMenuItem selected = root.getItems().get(++idx);
+
+ while (selected.isSeparator() || !selected.isEnabled()) {
+ idx = idx < root.getItems().size() - 1 ? idx : -1;
+ selected = root.getItems().get(++idx);
+ }
+
+ root.setSelected(selected);
+ root.showChildMenu(selected);
+ VMenuBar submenu = selected.getSubMenu();
+
+ // Select the first item in the newly open submenu
+ submenu.setSelected(submenu.getItems().get(0));
+
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ }
+
+ return true;
+
+ } else if (keycode == getNavigationUpKey()) {
+
+ if (getSelected() == null) {
+ // If nothing is selected then select the last item
+ setSelected(items.get(items.size() - 1));
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else {
+ // Select the previous item if possible or loop to the last item
+ int idx = items.indexOf(getSelected());
+ if (idx > 0) {
+ setSelected(items.get(idx - 1));
+ } else {
+ setSelected(items.get(items.size() - 1));
+ }
+
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ }
+
+ return true;
+
+ } else if (keycode == getNavigationDownKey()) {
+
+ if (getSelected() == null) {
+ // If nothing is selected then select the first item
+ setSelected(items.get(0));
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu == null && getParentMenu() == null) {
+ // If this is the root menu the show the child menu with arrow
+ // down
+ showChildMenu(getSelected());
+ menuVisible = true;
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else {
+ // Select the next item if possible or loop to the first item
+ int idx = items.indexOf(getSelected());
+ if (idx < items.size() - 1) {
+ setSelected(items.get(idx + 1));
+ } else {
+ setSelected(items.get(0));
+ }
+
+ if (getSelected().isSeparator() || !getSelected().isEnabled()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ }
+ return true;
+
+ } else if (keycode == getCloseMenuKey()) {
+ setSelected(null);
+ hideChildren();
+ menuVisible = false;
+
+ } else if (keycode == getNavigationSelectKey()) {
+ if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ menuVisible = false;
+ } else if (visibleChildMenu == null
+ && getSelected().getSubMenu() != null) {
+ // If the item has a submenu then show it and move the selection
+ // there
+ showChildMenu(getSelected());
+ menuVisible = true;
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else {
+ Command command = getSelected().getCommand();
+ if (command != null) {
+ command.execute();
+ }
+
+ hideParents(true);
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+ * .dom.client.BlurEvent)
+ */
+ public void onBlur(BlurEvent event) {
+ setSelected(null);
+ hideChildren();
+ menuVisible = false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+ public void onFocus(FocusEvent event) {
+ if (getSelected() == null) {
+ // If nothing is selected then select the first item
+ setSelected(items.get(0));
+ }
+ }
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java
index 8d5c21c512..b8a004e699 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java
@@ -27,6 +27,7 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
private final VOverlay popup;
private boolean open = false;
+ private boolean parsable = true;
public VPopupCalendar() {
super();
@@ -51,7 +52,10 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
boolean lastReadOnlyState = readonly;
+ parsable = uidl.getBooleanAttribute("parsable");
+
super.updateFromUIDL(uidl, client);
+
popup.setStyleName(VDateField.CLASSNAME + "-popup "
+ VDateField.CLASSNAME + "-"
+ resolutionToString(currentResolution));
@@ -174,4 +178,15 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field,
return fieldExtraWidth;
}
+ @Override
+ protected void buildDate() {
+ // Save previous value
+ String previousValue = getText();
+ super.buildDate();
+
+ // Restore previous value if the input could not be parsed
+ if (!parsable) {
+ setText(previousValue);
+ }
+ }
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
index 83bf043d98..e8cf37c1ff 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
@@ -12,6 +12,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.NodeList;
@@ -23,6 +24,15 @@ import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.user.client.Command;
@@ -35,12 +45,12 @@ import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.Container;
+import com.vaadin.terminal.gwt.client.Focusable;
import com.vaadin.terminal.gwt.client.MouseEventDetails;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.RenderSpace;
@@ -80,14 +90,27 @@ import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
* TODO implement unregistering for child components in Cells
*/
public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
- VHasDropHandler {
+ VHasDropHandler, KeyPressHandler, KeyDownHandler, FocusHandler,
+ BlurHandler, Focusable {
public static final String CLASSNAME = "v-table";
+ public static final String CLASSNAME_SELECTION_FOCUS = CLASSNAME + "-focus";
+
public static final String ITEM_CLICK_EVENT_ID = "itemClick";
+ public static final String HEADER_CLICK_EVENT_ID = "handleHeaderClick";
+ public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick";
+ public static final String COLUMN_RESIZE_EVENT_ID = "columnResize";
private static final double CACHE_RATE_DEFAULT = 2;
/**
+ * The default multi select mode where simple left clicks only selects one
+ * item, CTRL+left click selects multiple items and SHIFT-left click selects
+ * a range of items.
+ */
+ private static final int MULTISELECT_MODE_DEFAULT = 0;
+
+ /**
* multiple of pagelength which component will cache when requesting more
* rows
*/
@@ -104,19 +127,103 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private int pageLength = 15;
private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
- private boolean showRowHeaders = false;
+ protected boolean showRowHeaders = false;
private String[] columnOrder;
- private ApplicationConnection client;
- private String paintableId;
+ protected ApplicationConnection client;
+ protected String paintableId;
private boolean immediate;
+ private boolean nullSelectionAllowed = true;
private int selectMode = Table.SELECT_MODE_NONE;
private final HashSet<String> selectedRowKeys = new HashSet<String>();
+ /*
+ * These are used when jumping between pages when pressing Home and End
+ */
+ private boolean selectLastItemInNextRender = false;
+ private boolean selectFirstItemInNextRender = false;
+ private boolean focusFirstItemInNextRender = false;
+ private boolean focusLastItemInNextRender = false;
+
+ /*
+ * The currently focused row
+ */
+ private VScrollTableRow focusedRow;
+
+ /*
+ * Helper to store selection range start in when using the keyboard
+ */
+ private VScrollTableRow selectionRangeStart;
+
+ /*
+ * Flag for notifying when the selection has changed and should be sent to
+ * the server
+ */
+ private boolean selectionChanged = false;
+
+ /*
+ * The speed (in pixels) which the scrolling scrolls vertically/horizontally
+ */
+ private int scrollingVelocity = 10;
+
+ private Timer scrollingVelocityTimer = null;;
+
+ /**
+ * Represents a select range of rows
+ */
+ private class SelectionRange {
+ /**
+ * The starting key of the range
+ */
+ private int startRowKey;
+
+ /**
+ * The ending key of the range
+ */
+ private int endRowKey;
+
+ /**
+ * Constuctor.
+ *
+ * @param startRowKey
+ * The range start. Must be less than endRowKey
+ * @param endRowKey
+ * The range end. Must be bigger than startRowKey
+ */
+ public SelectionRange(int startRowKey, int endRowKey) {
+ this.startRowKey = startRowKey;
+ this.endRowKey = endRowKey;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return startRowKey + "-" + endRowKey;
+ }
+
+ public boolean inRange(int key) {
+ return key >= startRowKey && key <= endRowKey;
+ }
+
+ public int getStartKey() {
+ return startRowKey;
+ }
+
+ public int getEndKey() {
+ return endRowKey;
+ }
+ };
+
+ private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
+
private boolean initializedAndAttached = false;
/**
@@ -126,7 +233,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private final TableHead tHead = new TableHead();
- private final ScrollPanel bodyContainer = new ScrollPanel();
+ private final TableFooter tFoot = new TableFooter();
+
+ private final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel();
private int totalRows;
@@ -149,6 +258,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private Element scrollPositionElement;
private boolean enabled;
private boolean showColHeaders;
+ private boolean showColFooters;
/** flag to indicate that table body has changed */
private boolean isNewBody = true;
@@ -166,21 +276,342 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private boolean rendering = false;
private int dragmode;
+ private int multiselectmode;
+
public VScrollTable() {
- bodyContainer.addScrollHandler(this);
- bodyContainer.setStyleName(CLASSNAME + "-body");
+ scrollBodyPanel.setStyleName(CLASSNAME + "-body-wrapper");
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ scrollBodyPanel.addKeyPressHandler(this);
+ } else {
+ scrollBodyPanel.addKeyDownHandler(this);
+ }
+
+ scrollBodyPanel.addFocusHandler(this);
+ scrollBodyPanel.addBlurHandler(this);
+
+ scrollBodyPanel.addScrollHandler(this);
+ scrollBodyPanel.setStyleName(CLASSNAME + "-body");
setStyleName(CLASSNAME);
+
add(tHead);
- add(bodyContainer);
+ add(scrollBodyPanel);
+ add(tFoot);
rowRequestHandler = new RowRequestHandler();
+ /*
+ * We need to use the sinkEvents method to catch the keyUp events so we
+ * can cache a single shift. KeyUpHandler cannot do this.
+ */
+ sinkEvents(Event.ONKEYUP);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
+ * .client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (event.getTypeInt() == Event.ONKEYUP) {
+ if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
+ sendSelectedRows();
+ selectionRangeStart = null;
+ } else if ((event.getKeyCode() == getNavigationUpKey()
+ || event.getKeyCode() == getNavigationDownKey()
+ || event.getKeyCode() == getNavigationPageUpKey() || event
+ .getKeyCode() == getNavigationPageDownKey())
+ && !event.getShiftKey()) {
+ sendSelectedRows();
+
+ if (scrollingVelocityTimer != null) {
+ scrollingVelocityTimer.cancel();
+ scrollingVelocityTimer = null;
+ scrollingVelocity = 10;
+ }
+ }
+ }
+ }
+
+ /**
+ * Fires a column resize event which sends the resize information to the
+ * server.
+ *
+ * @param columnId
+ * The columnId of the column which was resized
+ * @param originalWidth
+ * The width in pixels of the column before the resize event
+ * @param newWidth
+ * The width in pixels of the column after the resize event
+ */
+ private void fireColumnResizeEvent(String columnId, int originalWidth,
+ int newWidth) {
+ client.updateVariable(paintableId, "columnResizeEventColumn",
+ columnId, false);
+ client.updateVariable(paintableId, "columnResizeEventPrev",
+ originalWidth, false);
+ client.updateVariable(paintableId, "columnResizeEventCurr",
+ newWidth, immediate);
+
+ }
+
+ /**
+ * Moves the focus one step down
+ *
+ * @return Returns true if succeeded
+ */
+ private boolean moveFocusDown() {
+ return moveFocusDown(0);
+ }
+
+ /**
+ * Moves the focus down by 1+offset rows
+ *
+ * @return Returns true if succeeded, else false if the selection could not
+ * be move downwards
+ */
+ private boolean moveFocusDown(int offset) {
+ if (selectMode > VScrollTable.SELECT_MODE_NONE) {
+ if (focusedRow == null && scrollBody.iterator().hasNext()) {
+ return setRowFocus((VScrollTableRow) scrollBody.iterator()
+ .next());
+ } else {
+ VScrollTableRow next = getNextRow(focusedRow, offset);
+ if (next != null) {
+ return setRowFocus(next);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Moves the selection one step up
+ *
+ * @return Returns true if succeeded
+ */
+ private boolean moveFocusUp() {
+ return moveFocusUp(0);
+ }
+
+ /**
+ * Moves the focus row upwards
+ *
+ * @return Returns true if succeeded, else false if the selection could not
+ * be move upwards
+ *
+ */
+ private boolean moveFocusUp(int offset) {
+ if (selectMode > VScrollTable.SELECT_MODE_NONE) {
+ if (focusedRow == null && scrollBody.iterator().hasNext()) {
+ return setRowFocus((VScrollTableRow) scrollBody.iterator()
+ .next());
+ } else {
+ VScrollTableRow prev = getPreviousRow(focusedRow, offset);
+ if (prev != null) {
+ return setRowFocus(prev);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Selects a row where the current selection head is
+ *
+ * @param ctrlSelect
+ * Is the selection a ctrl+selection
+ * @param shiftSelect
+ * Is the selection a shift+selection
+ * @return Returns truw
+ */
+ private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
+ if (focusedRow != null) {
+ // Arrows moves the selection and clears previous selections
+ if (selectMode > SELECT_MODE_NONE && !ctrlSelect && !shiftSelect) {
+ deselectAll();
+ focusedRow.toggleSelection(!ctrlSelect);
+ selectionRangeStart = focusedRow;
+ }
+
+ // Ctrl+arrows moves selection head
+ else if (selectMode > SELECT_MODE_NONE && ctrlSelect
+ && !shiftSelect) {
+ selectionRangeStart = focusedRow;
+ // No selection, only selection head is moved
+ }
+
+ // Shift+arrows selection selects a range
+ else if (selectMode == SELECT_MODE_MULTI && !ctrlSelect
+ && shiftSelect) {
+ focusedRow.toggleShiftSelection(shiftSelect);
+ }
+ }
+ }
+
+ /**
+ * Sends the selection to the server
+ */
+ private void sendSelectedRows() {
+ // Don't send anything if selection has not changed
+ if (!selectionChanged) {
+ return;
+ }
+
+ // Reset selection changed flag
+ selectionChanged = false;
+
+ // Note: changing the immediateness of this
+ // might
+ // require changes to "clickEvent" immediateness
+ // also.
+ if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ // Convert ranges to a set of strings
+ Set<String> ranges = new HashSet<String>();
+ for (SelectionRange range : selectedRowRanges) {
+ ranges.add(range.toString());
+ }
+
+ // Send the selected row ranges
+ client.updateVariable(paintableId, "selectedRanges", ranges
+ .toArray(new String[selectedRowRanges.size()]), false);
+ }
+
+ // Send the selected rows
+ client.updateVariable(paintableId, "selected", selectedRowKeys
+ .toArray(new String[selectedRowKeys.size()]), immediate);
+
+ }
+
+ /**
+ * Get the key that moves the selection head upwards. By default it is the
+ * up arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that moves the selection head downwards. By default it is the
+ * down arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that scrolls to the left in the table. By default it is the
+ * left arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that scroll to the right on the table. By default it is the
+ * right arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * Get the key that selects an item in the table. By default it is the space
+ * bar key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return 32;
+ }
+
+ /**
+ * Get the key the moves the selection one page up in the table. By default
+ * this is the Page Up key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageUpKey() {
+ return KeyCodes.KEY_PAGEUP;
}
+ /**
+ * Get the key the moves the selection one page down in the table. By
+ * default this is the Page Down key but by overriding this you can change
+ * the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageDownKey() {
+ return KeyCodes.KEY_PAGEDOWN;
+ }
+
+ /**
+ * Get the key the moves the selection to the beginning of the table. By
+ * default this is the Home key but by overriding this you can change the
+ * key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationStartKey() {
+ return KeyCodes.KEY_HOME;
+ }
+
+ /**
+ * Get the key the moves the selection to the end of the table. By default
+ * this is the End key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationEndKey() {
+ return KeyCodes.KEY_END;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal
+ * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection)
+ */
@SuppressWarnings("unchecked")
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
rendering = true;
+
+ /*
+ * We need to do this before updateComponent since updateComponent calls
+ * this.setHeight() which will calculate a new body height depending on
+ * the space available.
+ */
+ showColFooters = uidl.getBooleanAttribute("colfooters");
+ tFoot.setVisible(showColFooters);
+
if (client.updateComponent(this, uidl, true)) {
rendering = false;
return;
@@ -199,6 +630,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (scrollBody != null) {
if (totalRows == 0) {
tHead.clear();
+ tFoot.clear();
}
initializedAndAttached = false;
initialContentReceived = false;
@@ -210,12 +642,16 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
dragmode = uidl.hasAttribute("dragmode") ? uidl
.getIntAttribute("dragmode") : 0;
+ multiselectmode = uidl.hasAttribute("multiselectmode") ? uidl
+ .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT;
+
setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
: CACHE_RATE_DEFAULT);
recalcWidths = uidl.hasAttribute("recalcWidths");
if (recalcWidths) {
tHead.clear();
+ tFoot.clear();
}
if (uidl.hasAttribute("pagelength")) {
@@ -229,12 +665,15 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
// received 'surprising' firstvisible from server: scroll there
firstRowInViewPort = firstvisible;
- bodyContainer.setScrollPosition((int) (firstvisible * scrollBody
+ scrollBodyPanel.setScrollPosition((int) (firstvisible * scrollBody
.getRowHeight()));
}
showRowHeaders = uidl.getBooleanAttribute("rowheaders");
showColHeaders = uidl.getBooleanAttribute("colheaders");
+
+ nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
+ .getBooleanAttribute("nsa") : true;
if (uidl.hasVariable("sortascending")) {
sortAscending = uidl.getBooleanVariable("sortascending");
@@ -244,9 +683,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (uidl.hasVariable("selected")) {
final Set<String> selectedKeys = uidl
.getStringArrayVariableAsSet("selected");
- selectedRowKeys.clear();
for (String string : selectedKeys) {
- selectedRowKeys.add(string);
+ VScrollTableRow row = getRenderedRowByKey(string);
+ if (row != null && !row.isSelected()) {
+ row.toggleSelection(false);
+ }
}
}
@@ -285,6 +726,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
updateActionMap(c);
} else if (c.getTag().equals("visiblecolumns")) {
tHead.updateCellsFromUIDL(c);
+ tFoot.updateCellsFromUIDL(c);
} else if (c.getTag().equals("-ac")) {
ac = c;
}
@@ -302,6 +744,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
updateHeader(uidl.getStringArrayAttribute("vcolorder"));
+ updateFooter(uidl.getStringArrayAttribute("vcolorder"));
+
if (!recalcWidths && initializedAndAttached) {
updateBody(rowData, uidl.getIntAttribute("firstrow"), uidl
.getIntAttribute("rows"));
@@ -313,7 +757,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// run overflow fix for scrollable area
DeferredCommand.addCommand(new Command() {
public void execute() {
- Util.runWebkitOverflowAutoFix(bodyContainer
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel
.getElement());
}
});
@@ -323,11 +767,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
scrollBody.removeFromParent();
lazyUnregistryBag.add(scrollBody);
}
- scrollBody = new VScrollTableBody();
+ scrollBody = createScrollBody();
scrollBody.renderInitialRows(rowData, uidl
.getIntAttribute("firstrow"), uidl.getIntAttribute("rows"));
- bodyContainer.add(scrollBody);
+ scrollBodyPanel.add(scrollBody);
initialContentReceived = true;
if (isAttached()) {
sizeInit();
@@ -343,10 +787,88 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
hideScrollPositionAnnotation();
purgeUnregistryBag();
+
+ // This is called when the Home button has been pressed and the pages
+ // changes
+ if (selectFirstItemInNextRender) {
+ selectFirstRenderedRow(false);
+ selectFirstItemInNextRender = false;
+ }
+
+ if (focusFirstItemInNextRender) {
+ selectFirstRenderedRow(true);
+ focusFirstItemInNextRender = false;
+ }
+
+ // This is called when the End button has been pressed and the pages
+ // changes
+ if (selectLastItemInNextRender) {
+ selectLastRenderedRow(false);
+ selectLastItemInNextRender = false;
+ }
+
+ if (focusLastItemInNextRender) {
+ selectLastRenderedRow(true);
+ focusLastItemInNextRender = false;
+ }
+
+ if (focusedRow != null) {
+ setRowFocus(getRenderedRowByKey(focusedRow.getKey()));
+ }
+
+ if (!isFocusable()) {
+ scrollBodyPanel.getElement().setTabIndex(-1);
+ } else {
+ scrollBodyPanel.getElement().setTabIndex(0);
+ }
+
rendering = false;
headerChangedDuringUpdate = false;
}
+ protected VScrollTableBody createScrollBody() {
+ return new VScrollTableBody();
+ }
+
+ /**
+ * Selects the last rendered row in the table
+ *
+ * @param focusOnly
+ * Should the focus only be moved to the last row
+ */
+ private void selectLastRenderedRow(boolean focusOnly) {
+ VScrollTableRow row = null;
+ Iterator<Widget> it = scrollBody.iterator();
+ while (it.hasNext()) {
+ row = (VScrollTableRow) it.next();
+ }
+ if (row != null) {
+ setRowFocus(row);
+ if (!focusOnly) {
+ deselectAll();
+ selectFocusedRow(false, false);
+ sendSelectedRows();
+ }
+ }
+
+ }
+
+ /**
+ * Selects the first rendered row
+ *
+ * @param focusOnly
+ * Should the focus only be moved to the first row
+ */
+ private void selectFirstRenderedRow(boolean focusOnly) {
+ setRowFocus((VScrollTableRow) scrollBody.iterator().next());
+ if (!focusOnly) {
+ deselectAll();
+ selectFocusedRow(false, false);
+ sendSelectedRows();
+ }
+
+ }
+
private void setCacheRate(double d) {
if (cache_rate != d) {
cache_rate = d;
@@ -423,6 +945,38 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
/**
+ * Updates footers.
+ * <p>
+ * Update headers whould be called before this method is called!
+ * </p>
+ *
+ * @param strings
+ */
+ private void updateFooter(String[] strings) {
+ if (strings == null) {
+ return;
+ }
+
+ // Add dummy column if row headers are present
+ int colIndex = 0;
+ if (showRowHeaders) {
+ tFoot.enableColumn("0", colIndex);
+ colIndex++;
+ } else {
+ tFoot.removeCell("0");
+ }
+
+ int i;
+ for (i = 0; i < strings.length; i++) {
+ final String cid = strings[i];
+ tFoot.enableColumn(cid, colIndex);
+ colIndex++;
+ }
+
+ tFoot.setVisible(showColFooters);
+ }
+
+ /**
* @param uidl
* which contains row data
* @param firstRow
@@ -498,28 +1052,98 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
- final HeaderCell cell = tHead.getHeaderCell(colIndex);
- cell.setWidth(w, isDefinedWidth);
+ // Set header column width
+ final HeaderCell hcell = tHead.getHeaderCell(colIndex);
+ hcell.setWidth(w, isDefinedWidth);
+
+ // Set body column width
scrollBody.setColWidth(colIndex, w);
+
+ // Set footer column width
+ final FooterCell fcell = tFoot.getFooterCell(colIndex);
+ if (fcell != null) {
+ fcell.setWidth(w, isDefinedWidth);
+ }
}
private int getColWidth(String colKey) {
return tHead.getHeaderCell(colKey).getWidth();
}
+ /**
+ * Get a rendered row by its key
+ *
+ * @param key
+ * The key to search with
+ * @return
+ */
private VScrollTableRow getRenderedRowByKey(String key) {
+ if (scrollBody != null) {
+ final Iterator<Widget> it = scrollBody.iterator();
+ VScrollTableRow r = null;
+ while (it.hasNext()) {
+ r = (VScrollTableRow) it.next();
+ if (r.getKey().equals(key)) {
+ return r;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the next row to the given row
+ *
+ * @param row
+ * The row to calculate from
+ *
+ * @return The next row or null if no row exists
+ */
+ private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
final Iterator<Widget> it = scrollBody.iterator();
VScrollTableRow r = null;
while (it.hasNext()) {
r = (VScrollTableRow) it.next();
- if (r.getKey().equals(key)) {
+ if (r == row) {
+ r = null;
+ while (offset >= 0 && it.hasNext()) {
+ r = (VScrollTableRow) it.next();
+ offset--;
+ }
return r;
}
}
+
+ return null;
+ }
+
+ /**
+ * Returns the previous row from the given row
+ *
+ * @param row
+ * The row to calculate from
+ * @return The previous row or null if no row exists
+ */
+ private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
+ final Iterator<Widget> it = scrollBody.iterator();
+ final Iterator<Widget> offsetIt = scrollBody.iterator();
+ VScrollTableRow r = null;
+ VScrollTableRow prev = null;
+ while (it.hasNext()) {
+ r = (VScrollTableRow) it.next();
+ if (offset < 0) {
+ prev = (VScrollTableRow) offsetIt.next();
+ }
+ if (r == row) {
+ return prev;
+ }
+ offset--;
+ }
+
return null;
}
- private void reOrderColumn(String columnKey, int newIndex) {
+ protected void reOrderColumn(String columnKey, int newIndex) {
final int oldIndex = getColIndexByKey(columnKey);
@@ -529,6 +1153,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// Change body order
scrollBody.moveCol(oldIndex, newIndex);
+ // Change footer order
+ tFoot.moveCell(oldIndex, newIndex);
+
/*
* Build new columnOrder and update it to server Note that columnOrder
* also contains collapsed columns so we cannot directly build it from
@@ -622,6 +1249,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
final int[] widths = new int[tHead.visibleCells.size()];
tHead.enableBrowserIntelligence();
+ tFoot.enableBrowserIntelligence();
+
// first loop: collect natural widths
while (headCells.hasNext()) {
final HeaderCell hCell = (HeaderCell) headCells.next();
@@ -647,6 +1276,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
tHead.disableBrowserIntelligence();
+ tFoot.disableBrowserIntelligence();
boolean willHaveScrollbarz = willHaveScrollbars();
@@ -766,8 +1396,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (needsSpaceForHorizontalSrollbar) {
bodyHeight += Util.getNativeScrollbarSize();
}
- bodyContainer.setHeight(bodyHeight + "px");
- Util.runWebkitOverflowAutoFix(bodyContainer.getElement());
+ scrollBodyPanel.setHeight(bodyHeight + "px");
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
}
isNewBody = false;
@@ -777,7 +1407,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// without
DeferredCommand.addCommand(new Command() {
public void execute() {
- bodyContainer
+ scrollBodyPanel
.setScrollPosition((int) (firstvisible * scrollBody
.getRowHeight()));
firstRowInViewPort = firstvisible;
@@ -820,7 +1450,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
} else {
int fakeheight = (int) Math.round(scrollBody.getRowHeight()
* totalRows);
- int availableHeight = bodyContainer.getElement().getPropertyInt(
+ int availableHeight = scrollBodyPanel.getElement().getPropertyInt(
"clientHeight");
if (fakeheight > availableHeight) {
return true;
@@ -840,7 +1470,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
Style style = scrollPositionElement.getStyle();
style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
- style.setMarginTop(-bodyContainer.getOffsetHeight(), Unit.PX);
+ style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
// indexes go from 1-totalRows, as rowheaders in index-mode indicate
int last = (firstRowInViewPort + pageLength);
@@ -1130,10 +1760,14 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
@Override
public void onBrowserEvent(Event event) {
if (enabled && event != null) {
- if (isResizing || event.getTarget() == colResizeWidget) {
+ if (isResizing
+ || event.getEventTarget().cast() == colResizeWidget) {
onResizeEvent(event);
} else {
handleCaptionEvent(event);
+ if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+ scrollBodyPanel.setFocus(true);
+ }
}
}
}
@@ -1167,6 +1801,24 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
floatingCopyOfHeaderCell = null;
}
+ /**
+ * Fires a header click event after the user has clicked a column header
+ * cell
+ *
+ * @param event
+ * The click event
+ */
+ private void fireHeaderClickedEvent(Event event) {
+ if (client.hasEventListeners(VScrollTable.this,
+ HEADER_CLICK_EVENT_ID)) {
+ MouseEventDetails details = new MouseEventDetails(event);
+ client.updateVariable(paintableId, "headerClickEvent", details
+ .toString(), false);
+ client.updateVariable(paintableId, "headerClickCID", cid,
+ immediate);
+ }
+ }
+
protected void handleCaptionEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONMOUSEDOWN:
@@ -1210,15 +1862,17 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
cid, false);
}
// get also cache columns at the same request
- bodyContainer.setScrollPosition(0);
+ scrollBodyPanel.setScrollPosition(0);
firstvisible = 0;
rowRequestHandler.setReqFirstRow(0);
rowRequestHandler.setReqRows((int) (2 * pageLength
* cache_rate + pageLength));
rowRequestHandler.deferRowFetch();
}
+ fireHeaderClickedEvent(event);
break;
}
+ fireHeaderClickedEvent(event);
break;
case Event.ONMOUSEMOVE:
if (dragging) {
@@ -1274,6 +1928,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// readjust undefined width columns
lazyAdjustColumnWidths.cancel();
lazyAdjustColumnWidths.schedule(1);
+ fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
break;
case Event.ONMOUSEMOVE:
if (isResizing) {
@@ -1503,8 +2158,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
headerChangedDuringUpdate = true;
}
}
-
}
+
// check for orphaned header cells
for (Iterator<String> cit = availableCells.keySet().iterator(); cit
.hasNext();) {
@@ -1514,7 +2169,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
cit.remove();
}
}
-
}
public void enableColumn(String cid, int index) {
@@ -1662,7 +2316,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
@Override
public void onBrowserEvent(Event event) {
if (enabled) {
- if (event.getTarget() == columnSelector) {
+ if (event.getEventTarget().cast() == columnSelector) {
final int left = DOM.getAbsoluteLeft(columnSelector);
final int top = DOM.getAbsoluteTop(columnSelector)
+ DOM.getElementPropertyInt(columnSelector,
@@ -1796,6 +2450,529 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
/**
+ * A cell in the footer
+ */
+ public class FooterCell extends Widget {
+ private Element td = DOM.createTD();
+ private Element captionContainer = DOM.createDiv();
+ private char align = ALIGN_LEFT;
+ private int width = -1;
+ private float expandRatio = 0;
+ private String cid;
+
+ public FooterCell(String colId, String headerText) {
+ cid = colId;
+
+ setText(headerText);
+
+ DOM.setElementProperty(captionContainer, "className", CLASSNAME
+ + "-footer-container");
+
+ // ensure no clipping initially (problem on column additions)
+ DOM.setStyleAttribute(captionContainer, "overflow", "visible");
+
+ DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
+
+ DOM.appendChild(td, captionContainer);
+
+ DOM.sinkEvents(td, Event.MOUSEEVENTS);
+
+ setElement(td);
+ }
+
+ /**
+ * Sets the text of the footer
+ *
+ * @param footerText
+ * The text in the footer
+ */
+ public void setText(String footerText) {
+ DOM.setInnerHTML(captionContainer, footerText);
+ }
+
+ /**
+ * Set alignment of the text in the cell
+ *
+ * @param c
+ * The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
+ * ALIGN_RIGHT
+ */
+ public void setAlign(char c) {
+ if (align != c) {
+ switch (c) {
+ case ALIGN_CENTER:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "center");
+ break;
+ case ALIGN_RIGHT:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "right");
+ break;
+ default:
+ DOM.setStyleAttribute(captionContainer, "textAlign", "");
+ break;
+ }
+ }
+ align = c;
+ }
+
+ /**
+ * Get the alignment of the text int the cell
+ *
+ * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
+ */
+ public char getAlign() {
+ return align;
+ }
+
+ /**
+ * Sets the width of the cell
+ *
+ * @param w
+ * The width of the cell
+ * @param ensureDefinedWidth
+ * Ensures the the given width is not recalculated
+ */
+ public void setWidth(int w, boolean ensureDefinedWidth) {
+
+ if (ensureDefinedWidth) {
+ // on column resize expand ratio becomes zero
+ expandRatio = 0;
+ }
+ if (width == w) {
+ return;
+ }
+ width = w;
+ if (width <= 0) {
+ // go to default mode, clip content if necessary
+ DOM.setStyleAttribute(captionContainer, "overflow", "");
+ }
+ if (w == -1) {
+ DOM.setStyleAttribute(captionContainer, "width", "");
+ setWidth("");
+ } else {
+
+ /*
+ * Reduce width with one pixel for the right border since the
+ * footers does not have any spacers between them.
+ */
+ int borderWidths = 1;
+
+ // Set the container width (check for negative value)
+ if (w - borderWidths >= 0) {
+ captionContainer.getStyle().setPropertyPx("width",
+ w - borderWidths);
+ } else {
+ captionContainer.getStyle().setPropertyPx("width", 0);
+ }
+
+ /*
+ * if we already have tBody, set the header width properly, if
+ * not defer it. IE will fail with complex float in table header
+ * unless TD width is not explicitly set.
+ */
+ if (scrollBody != null) {
+ /*
+ * Reduce with one since footer does not have any spacers,
+ * instead a 1 pixel border.
+ */
+ int tdWidth = width + scrollBody.getCellExtraWidth()
+ - borderWidths;
+ setWidth(tdWidth + "px");
+ } else {
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ int borderWidths = 1;
+ int tdWidth = width
+ + scrollBody.getCellExtraWidth()
+ - borderWidths;
+ setWidth(tdWidth + "px");
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Sets the width to undefined
+ */
+ public void setUndefinedWidth() {
+ setWidth(-1, false);
+ }
+
+ /**
+ * Sets the expand ratio of the cell
+ *
+ * @param floatAttribute
+ * The expand ratio
+ */
+ public void setExpandRatio(float floatAttribute) {
+ expandRatio = floatAttribute;
+ }
+
+ /**
+ * Returns the expand ration of the cell
+ *
+ * @return The expand ratio
+ */
+ public float getExpandRatio() {
+ return expandRatio;
+ }
+
+ /**
+ * Is the cell enabled?
+ *
+ * @return True if enabled else False
+ */
+ public boolean isEnabled() {
+ return getParent() != null;
+ }
+
+ /**
+ * Handle column clicking
+ */
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (enabled && event != null) {
+ handleCaptionEvent(event);
+
+ if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+ scrollBodyPanel.setFocus(true);
+ }
+ }
+ }
+
+ /**
+ * Handles a event on the captions
+ *
+ * @param event
+ * The event to handle
+ */
+ protected void handleCaptionEvent(Event event) {
+ if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+ fireFooterClickedEvent(event);
+ }
+ }
+
+ /**
+ * Fires a footer click event after the user has clicked a column footer
+ * cell
+ *
+ * @param event
+ * The click event
+ */
+ private void fireFooterClickedEvent(Event event) {
+ if (client.hasEventListeners(VScrollTable.this,
+ FOOTER_CLICK_EVENT_ID)) {
+ MouseEventDetails details = new MouseEventDetails(event);
+ client.updateVariable(paintableId, "footerClickEvent", details
+ .toString(), false);
+ client.updateVariable(paintableId, "footerClickCID", cid,
+ immediate);
+ }
+ }
+
+ /**
+ * Returns the column key of the column
+ *
+ * @return The column key
+ */
+ public String getColKey() {
+ return cid;
+ }
+ }
+
+ /**
+ * HeaderCell that is header cell for row headers.
+ *
+ * Reordering disabled and clicking on it resets sorting.
+ */
+ public class RowHeadersFooterCell extends FooterCell {
+
+ RowHeadersFooterCell() {
+ super("0", "");
+ }
+
+ @Override
+ protected void handleCaptionEvent(Event event) {
+ // NOP: RowHeaders cannot be reordered
+ // TODO It'd be nice to reset sorting here
+ }
+ }
+
+ /**
+ * The footer of the table which can be seen in the bottom of the Table.
+ */
+ public class TableFooter extends Panel {
+
+ private static final int WRAPPER_WIDTH = 9000;
+
+ ArrayList<Widget> visibleCells = new ArrayList<Widget>();
+ HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
+
+ Element div = DOM.createDiv();
+ Element hTableWrapper = DOM.createDiv();
+ Element hTableContainer = DOM.createDiv();
+ Element table = DOM.createTable();
+ Element headerTableBody = DOM.createTBody();
+ Element tr = DOM.createTR();
+
+ public TableFooter() {
+
+ DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
+ DOM.setElementProperty(hTableWrapper, "className", CLASSNAME
+ + "-footer");
+
+ DOM.appendChild(table, headerTableBody);
+ DOM.appendChild(headerTableBody, tr);
+ DOM.appendChild(hTableContainer, table);
+ DOM.appendChild(hTableWrapper, hTableContainer);
+ DOM.appendChild(div, hTableWrapper);
+ setElement(div);
+
+ setStyleName(CLASSNAME + "-footer-wrap");
+
+ availableCells.put("0", new RowHeadersFooterCell());
+ }
+
+ @Override
+ public void clear() {
+ for (String cid : availableCells.keySet()) {
+ removeCell(cid);
+ }
+ availableCells.clear();
+ availableCells.put("0", new RowHeadersFooterCell());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
+ * .ui.Widget)
+ */
+ @Override
+ public boolean remove(Widget w) {
+ if (visibleCells.contains(w)) {
+ visibleCells.remove(w);
+ orphan(w);
+ DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
+ */
+ public Iterator<Widget> iterator() {
+ return visibleCells.iterator();
+ }
+
+ /**
+ * Gets a footer cell which represents the given columnId
+ *
+ * @param cid
+ * The columnId
+ *
+ * @return The cell
+ */
+ public FooterCell getFooterCell(String cid) {
+ return availableCells.get(cid);
+ }
+
+ /**
+ * Gets a footer cell by using a column index
+ *
+ * @param index
+ * The index of the column
+ * @return The Cell
+ */
+ public FooterCell getFooterCell(int index) {
+ if (index < visibleCells.size()) {
+ return (FooterCell) visibleCells.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Updates the cells contents when updateUIDL request is received
+ *
+ * @param uidl
+ * The UIDL
+ */
+ public void updateCellsFromUIDL(UIDL uidl) {
+ Iterator<?> columnIterator = uidl.getChildIterator();
+ HashSet<String> updated = new HashSet<String>();
+ updated.add("0");
+ while (columnIterator.hasNext()) {
+ final UIDL col = (UIDL) columnIterator.next();
+ final String cid = col.getStringAttribute("cid");
+ updated.add(cid);
+
+ String caption = col.getStringAttribute("fcaption");
+ FooterCell c = getFooterCell(cid);
+ if (c == null) {
+ c = new FooterCell(cid, caption);
+ availableCells.put(cid, c);
+ if (initializedAndAttached) {
+ // we will need a column width recalculation
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ } else {
+ c.setText(caption);
+ }
+
+ if (col.hasAttribute("align")) {
+ c.setAlign(col.getStringAttribute("align").charAt(0));
+ }
+ if (col.hasAttribute("width")) {
+ final String width = col.getStringAttribute("width");
+ c.setWidth(Integer.parseInt(width), true);
+ } else if (recalcWidths) {
+ c.setUndefinedWidth();
+ }
+ if (col.hasAttribute("er")) {
+ c.setExpandRatio(col.getFloatAttribute("er"));
+ }
+ if (col.hasAttribute("collapsed")) {
+ // ensure header is properly removed from parent (case when
+ // collapsing happens via servers side api)
+ if (c.isAttached()) {
+ c.removeFromParent();
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ // check for orphaned header cells
+ for (Iterator<String> cit = availableCells.keySet().iterator(); cit
+ .hasNext();) {
+ String cid = cit.next();
+ if (!updated.contains(cid)) {
+ removeCell(cid);
+ cit.remove();
+ }
+ }
+ }
+
+ /**
+ * Set a footer cell for a specified column index
+ *
+ * @param index
+ * The index
+ * @param cell
+ * The footer cell
+ */
+ public void setFooterCell(int index, FooterCell cell) {
+ if (cell.isEnabled()) {
+ // we're moving the cell
+ DOM.removeChild(tr, cell.getElement());
+ orphan(cell);
+ }
+ if (index < visibleCells.size()) {
+ // insert to right slot
+ DOM.insertChild(tr, cell.getElement(), index);
+ adopt(cell);
+ visibleCells.add(index, cell);
+ } else if (index == visibleCells.size()) {
+ // simply append
+ DOM.appendChild(tr, cell.getElement());
+ adopt(cell);
+ visibleCells.add(cell);
+ } else {
+ throw new RuntimeException(
+ "Header cells must be appended in order");
+ }
+ }
+
+ /**
+ * Remove a cell by using the columnId
+ *
+ * @param colKey
+ * The columnId to remove
+ */
+ public void removeCell(String colKey) {
+ final FooterCell c = getFooterCell(colKey);
+ remove(c);
+ }
+
+ /**
+ * Enable a column (Sets the footer cell)
+ *
+ * @param cid
+ * The columnId
+ * @param index
+ * The index of the column
+ */
+ public void enableColumn(String cid, int index) {
+ final FooterCell c = getFooterCell(cid);
+ if (!c.isEnabled() || getFooterCell(index) != c) {
+ setFooterCell(index, c);
+ if (initializedAndAttached) {
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ /**
+ * Disable browser measurement of the table width
+ */
+ public void disableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
+ + "px");
+ }
+
+ /**
+ * Enable browser measurement of the table width
+ */
+ public void enableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", "");
+ }
+
+ /**
+ * Set the horizontal position in the cell in the footer. This is done
+ * when a horizontal scrollbar is present.
+ *
+ * @param scrollLeft
+ * The value of the leftScroll
+ */
+ public void setHorizontalScrollPosition(int scrollLeft) {
+ if (BrowserInfo.get().isIE6()) {
+ hTableWrapper.getStyle().setProperty("position", "relative");
+ hTableWrapper.getStyle().setPropertyPx("left", -scrollLeft);
+ } else {
+ hTableWrapper.setScrollLeft(scrollLeft);
+ }
+ }
+
+ /**
+ * Swap cells when the column are dragged
+ *
+ * @param oldIndex
+ * The old index of the cell
+ * @param newIndex
+ * The new index of the cell
+ */
+ public void moveCell(int oldIndex, int newIndex) {
+ final FooterCell hCell = getFooterCell(oldIndex);
+ final Element cell = hCell.getElement();
+
+ visibleCells.remove(oldIndex);
+ DOM.removeChild(tr, cell);
+
+ DOM.insertChild(tr, cell, newIndex);
+ visibleCells.add(newIndex, hCell);
+ }
+ }
+
+ /**
* This Panel can only contain VScrollTableRow type of widgets. This
* "simulates" very large table, keeping spacers which take room of
* unrendered rows.
@@ -1830,9 +3007,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
private char[] aligns;
- VScrollTableBody() {
+ protected VScrollTableBody() {
constructDOM();
-
setElement(container);
}
@@ -1862,7 +3038,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
public int getAvailableWidth() {
- int availW = bodyContainer.getOffsetWidth() - getBorderWidth();
+ int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
return availW;
}
@@ -1872,8 +3048,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
final Iterator<?> it = rowData.getChildIterator();
aligns = tHead.getColumnAlignments();
while (it.hasNext()) {
- final VScrollTableRow row = new VScrollTableRow((UIDL) it
- .next(), aligns);
+ final VScrollTableRow row = createRow((UIDL) it.next(), aligns);
addRow(row);
}
if (isAttached()) {
@@ -1887,7 +3062,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
final Iterator<?> it = rowData.getChildIterator();
if (firstIndex == lastRendered + 1) {
while (it.hasNext()) {
- final VScrollTableRow row = createRow((UIDL) it.next());
+ final VScrollTableRow row = prepareRow((UIDL) it.next());
addRow(row);
lastRendered++;
}
@@ -1897,7 +3072,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
int i = rows;
while (it.hasNext()) {
i--;
- rowArray[i] = createRow((UIDL) it.next());
+ rowArray[i] = prepareRow((UIDL) it.next());
}
for (i = 0; i < rows; i++) {
addRowBeforeFirstRendered(rowArray[i]);
@@ -1908,7 +3083,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
while (lastRendered + 1 > firstRendered) {
unlinkRow(false);
}
- final VScrollTableRow row = createRow((UIDL) it.next());
+ final VScrollTableRow row = prepareRow((UIDL) it.next());
firstRendered = firstIndex;
lastRendered = firstIndex - 1;
addRow(row);
@@ -1916,7 +3091,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
setContainerHeight();
fixSpacers();
while (it.hasNext()) {
- addRow(createRow((UIDL) it.next()));
+ addRow(prepareRow((UIDL) it.next()));
lastRendered++;
}
fixSpacers();
@@ -1962,8 +3137,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
*
* @param uidl
*/
- private VScrollTableRow createRow(UIDL uidl) {
- final VScrollTableRow row = new VScrollTableRow(uidl, aligns);
+ private VScrollTableRow prepareRow(UIDL uidl) {
+ final VScrollTableRow row = createRow(uidl, aligns);
final int cells = DOM.getChildCount(row.getElement());
for (int i = 0; i < cells; i++) {
final Element cell = DOM.getChild(row.getElement(), i);
@@ -1978,6 +3153,10 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
return row;
}
+ protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
+ return new VScrollTableRow(uidl, aligns);
+ }
+
private void addRowBeforeFirstRendered(VScrollTableRow row) {
VScrollTableRow first = null;
if (renderedRows.size() > 0) {
@@ -2194,7 +3373,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// for measuring
noCells = true;
VScrollTableRow next = (VScrollTableRow) iterator().next();
- next.addCell("", ALIGN_LEFT, "", true);
+ next.addCell(null, "", ALIGN_LEFT, "", true);
firstTD = item.getCells().getItem(0);
}
com.google.gwt.dom.client.Element wrapper = firstTD
@@ -2254,9 +3433,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
Container {
private static final int DRAGMODE_MULTIROW = 2;
- ArrayList<Widget> childWidgets = new ArrayList<Widget>();
+ protected ArrayList<Widget> childWidgets = new ArrayList<Widget>();
private boolean selected = false;
- private final int rowKey;
+ protected final int rowKey;
private List<UIDL> pendingComponentPaints;
private String[] actionKeys = null;
@@ -2268,7 +3447,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
rowElement = Document.get().createTRElement();
setElement(rowElement);
DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
- | Event.ONDBLCLICK | Event.ONCONTEXTMENU);
+ | Event.ONDBLCLICK | Event.ONCONTEXTMENU
+ | Event.ONKEYDOWN);
}
private void paintComponent(Paintable p, UIDL uidl) {
@@ -2323,8 +3503,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// row header
if (showRowHeaders) {
- addCell(buildCaptionHtmlSnippet(uidl), aligns[col++], "",
- true);
+ addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
+ "", true);
}
if (uidl.hasAttribute("al")) {
@@ -2344,17 +3524,19 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
if (cell instanceof String) {
- addCell(cell.toString(), aligns[col++], style, false);
+ addCell(uidl, cell.toString(), aligns[col++], style,
+ false);
} else {
final Paintable cellContent = client
.getPaintable((UIDL) cell);
- addCell((Widget) cellContent, aligns[col++], style);
+ addCell(uidl, (Widget) cellContent, aligns[col++],
+ style);
paintComponent(cellContent, (UIDL) cell);
}
}
if (uidl.hasAttribute("selected") && !isSelected()) {
- toggleSelection();
+ toggleSelection(true);
}
}
@@ -2364,11 +3546,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
public VScrollTableRow() {
this(0);
addStyleName(CLASSNAME + "-row");
- addCell("_", 'b', "", true);
+ addCell(null, "_", 'b', "", true);
}
- public void addCell(String text, char align, String style,
- boolean textIsHTML) {
+ public void addCell(UIDL rowUidl, String text, char align,
+ String style, boolean textIsHTML) {
// String only content is optimized by not using Label widget
final Element td = DOM.createTD();
final Element container = DOM.createDiv();
@@ -2398,7 +3580,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
getElement().appendChild(td);
}
- public void addCell(Widget w, char align, String style) {
+ public void addCell(UIDL rowUidl, Widget w, char align, String style) {
final Element td = DOM.createTD();
final Element container = DOM.createDiv();
String className = CLASSNAME + "-cell-content";
@@ -2485,6 +3667,19 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
}
+ /**
+ * Add this to the element mouse down event by using
+ * element.setPropertyJSO
+ * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it
+ * then again when the mouse is depressed in the mouse up event.
+ *
+ * @return Returns the JSO preventing text selection
+ */
+ private native JavaScriptObject applyDisableTextSelectionIEHack()
+ /*-{
+ return function(){ return false; };
+ }-*/;
+
/*
* React on click that occur on content cells only
*/
@@ -2500,21 +3695,65 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
case Event.ONMOUSEUP:
mDown = false;
handleClickEvent(event, targetTdOrTr);
+ scrollBodyPanel.setFocus(true);
if (event.getButton() == Event.BUTTON_LEFT
&& selectMode > Table.SELECT_MODE_NONE) {
- toggleSelection();
- // Note: changing the immediateness of this
- // might
- // require changes to "clickEvent" immediateness
- // also.
- client
- .updateVariable(
- paintableId,
- "selected",
- selectedRowKeys
- .toArray(new String[selectedRowKeys
- .size()]),
- immediate);
+
+ // Ctrl+Shift click
+ if ((event.getCtrlKey() || event.getMetaKey())
+ && event.getShiftKey()
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ toggleShiftSelection(false);
+ setRowFocus(this);
+
+ // Ctrl click
+ } else if ((event.getCtrlKey() || event
+ .getMetaKey())
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ toggleSelection(true);
+ setRowFocus(this);
+
+ // Ctrl click (Single selection)
+ } else if ((event.getCtrlKey() || event
+ .getMetaKey()
+ && selectMode == SELECT_MODE_SINGLE)) {
+ if (!isSelected()
+ || (isSelected() && nullSelectionAllowed)) {
+
+ if (!isSelected()) {
+ deselectAll();
+ }
+
+ toggleSelection(true);
+ setRowFocus(this);
+ }
+
+ // Shift click
+ } else if (event.getShiftKey()
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ toggleShiftSelection(true);
+
+ // click
+ } else {
+ if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ deselectAll();
+ }
+
+ selectionRangeStart = this;
+ toggleSelection(multiselectmode == MULTISELECT_MODE_DEFAULT);
+ setRowFocus(this);
+ }
+
+ // Remove IE text selection hack
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast())
+ .setPropertyJSO("onselectstart",
+ null);
+ }
+ sendSelectedRows();
}
break;
case Event.ONCONTEXTMENU:
@@ -2568,7 +3807,31 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
event.preventDefault();
event.stopPropagation();
+ } else if (event.getCtrlKey()
+ || event.getShiftKey()
+ || event.getMetaKey()
+ && selectMode == SELECT_MODE_MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT) {
+ // Prevent default text selection in Firefox
+ event.preventDefault();
+
+ // Prevent default text selection in IE
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast())
+ .setPropertyJSO(
+ "onselectstart",
+ applyDisableTextSelectionIEHack());
+ }
+
+ event.stopPropagation();
}
+
+ if (!isFocusable()) {
+ scrollBodyPanel.getElement().setTabIndex(-1);
+ } else {
+ scrollBodyPanel.getElement().setTabIndex(0);
+ }
+
break;
case Event.ONMOUSEOUT:
mDown = false;
@@ -2643,25 +3906,112 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
left += Window.getScrollLeft();
client.getContextMenu().showAt(this, left, top);
}
- event.cancelBubble(true);
+ event.stopPropagation();
event.preventDefault();
}
+ /**
+ * Has the row been selected?
+ *
+ * @return Returns true if selected, else false
+ */
public boolean isSelected() {
return selected;
}
- private void toggleSelection() {
+ /**
+ * Toggle the selection of the row
+ */
+ public void toggleSelection(boolean ctrlSelect) {
selected = !selected;
+ selectionChanged = true;
if (selected) {
- if (selectMode == Table.SELECT_MODE_SINGLE) {
- deselectAll();
- }
selectedRowKeys.add(String.valueOf(rowKey));
addStyleName("v-selected");
} else {
- selectedRowKeys.remove(String.valueOf(rowKey));
removeStyleName("v-selected");
+ selectedRowKeys.remove(String.valueOf(rowKey));
+ }
+ removeKeyFromSelectedRange(rowKey);
+ }
+
+ /**
+ * Is called when a user clicks an item when holding SHIFT key down.
+ * This will select a new range from the last cell clicked
+ *
+ * @param deselectPrevious
+ * Should the previous selected range be deselected
+ */
+ private void toggleShiftSelection(boolean deselectPrevious) {
+
+ /*
+ * Ensures that we are in multiselect mode and that we have a
+ * previous selection which was not a deselection
+ */
+ if (selectMode == SELECT_MODE_SINGLE) {
+ // No previous selection found
+ deselectAll();
+ toggleSelection(true);
+ return;
+ }
+
+ // Set the selectable range
+ int startKey;
+ if (selectionRangeStart != null) {
+ startKey = Integer.valueOf(selectionRangeStart.getKey());
+ } else {
+ startKey = Integer.valueOf(focusedRow.getKey());
+ }
+ int endKey = rowKey;
+ if (endKey < startKey) {
+ // Swap keys if in the wrong order
+ startKey ^= endKey;
+ endKey ^= startKey;
+ startKey ^= endKey;
+ }
+
+ // Deselect previous items if so desired
+ if (deselectPrevious) {
+ deselectAll();
+ }
+
+ // Select the range (not including this row)
+ VScrollTableRow startRow = getRenderedRowByKey(String
+ .valueOf(startKey));
+ VScrollTableRow endRow = getRenderedRowByKey(String
+ .valueOf(endKey));
+
+ // If start row is null then we have a multipage selection from
+ // above
+ if (startRow == null) {
+ startRow = (VScrollTableRow) scrollBody.iterator().next();
+ setRowFocus(endRow);
+ }
+
+ if (endRow == null) {
+ setRowFocus(startRow);
+ }
+
+ Iterator<Widget> rows = scrollBody.iterator();
+ boolean startSelection = false;
+ while (rows.hasNext()) {
+ VScrollTableRow row = (VScrollTableRow) rows.next();
+ if (row == startRow || startSelection) {
+ startSelection = true;
+ if (!row.isSelected()) {
+ row.toggleSelection(false);
+ }
+ selectedRowKeys.add(row.getKey());
+ }
+
+ if (row == endRow && row != null) {
+ startSelection = false;
+ }
+ }
+
+ // Add range
+ if (startRow != endRow) {
+ selectedRowRanges.add(new SelectionRange(startKey, endKey));
}
}
@@ -2764,17 +4114,21 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
}
+ /**
+ * Deselects all items
+ */
public void deselectAll() {
final Object[] keys = selectedRowKeys.toArray();
for (int i = 0; i < keys.length; i++) {
final VScrollTableRow row = getRenderedRowByKey((String) keys[i]);
if (row != null && row.isSelected()) {
- row.toggleSelection();
+ row.toggleSelection(false);
+ removeKeyFromSelectedRange(Integer.parseInt(row.getKey()));
}
}
// still ensure all selects are removed from (not necessary rendered)
selectedRowKeys.clear();
-
+ selectedRowRanges.clear();
}
/**
@@ -2790,7 +4144,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
int rowHeight = (int) scrollBody.getRowHeight();
- int bodyH = bodyContainer.getOffsetHeight();
+ int bodyH = scrollBodyPanel.getOffsetHeight();
int rowsAtOnce = bodyH / rowHeight;
boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
if (anotherPartlyVisible) {
@@ -2806,8 +4160,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (currentlyVisible < pageLength
&& currentlyVisible < totalRows) {
// shake scrollpanel to fill empty space
- bodyContainer.setScrollPosition(scrollTop + 1);
- bodyContainer.setScrollPosition(scrollTop - 1);
+ scrollBodyPanel.setScrollPosition(scrollTop + 1);
+ scrollBodyPanel.setScrollPosition(scrollTop - 1);
}
}
}
@@ -2839,6 +4193,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
super.setWidth("");
}
+ if (!isFocusable()) {
+ scrollBodyPanel.getElement().setTabIndex(-1);
+ } else {
+ scrollBodyPanel.getElement().setTabIndex(0);
+ }
}
private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
@@ -2922,7 +4281,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
bodyHeight += Util.getNativeScrollbarSize();
}
int heightBefore = getOffsetHeight();
- bodyContainer.setHeight(bodyHeight + "px");
+ scrollBodyPanel.setHeight(bodyHeight + "px");
if (heightBefore != getOffsetHeight()) {
Util.notifyParentOfSizeChange(VScrollTable.this, false);
}
@@ -2930,7 +4289,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
scrollBody.reLayoutComponents();
DeferredCommand.addCommand(new Command() {
public void execute() {
- Util.runWebkitOverflowAutoFix(bodyContainer.getElement());
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
}
});
}
@@ -2943,7 +4302,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
*/
private void setContentWidth(int pixels) {
tHead.setWidth(pixels + "px");
- bodyContainer.setWidth(pixels + "px");
+ scrollBodyPanel.setWidth(pixels + "px");
+ tFoot.setWidth(pixels + "px");
}
private int borderWidth = -1;
@@ -2953,8 +4313,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
*/
private int getBorderWidth() {
if (borderWidth < 0) {
- borderWidth = Util.measureHorizontalPaddingAndBorder(bodyContainer
- .getElement(), 2);
+ borderWidth = Util.measureHorizontalPaddingAndBorder(
+ scrollBodyPanel.getElement(), 2);
if (borderWidth < 0) {
borderWidth = 0;
}
@@ -2970,11 +4330,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (height != null && !"".equals(height)) {
int contentH = getOffsetHeight();
contentH -= showColHeaders ? tHead.getOffsetHeight() : 0;
+ contentH -= tFoot.getOffsetHeight();
contentH -= getContentAreaBorderHeight();
if (contentH < 0) {
contentH = 0;
}
- bodyContainer.setHeight(contentH + "px");
+ scrollBodyPanel.setHeight(contentH + "px");
}
}
@@ -2990,15 +4351,15 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (contentAreaBorderHeight < 0) {
if (BrowserInfo.get().isIE7() || BrowserInfo.get().isIE6()) {
contentAreaBorderHeight = Util
- .measureVerticalBorder(bodyContainer.getElement());
+ .measureVerticalBorder(scrollBodyPanel.getElement());
} else {
- DOM.setStyleAttribute(bodyContainer.getElement(), "overflow",
+ DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
"hidden");
- int oh = bodyContainer.getOffsetHeight();
- int ch = bodyContainer.getElement().getPropertyInt(
+ int oh = scrollBodyPanel.getOffsetHeight();
+ int ch = scrollBodyPanel.getElement().getPropertyInt(
"clientHeight");
contentAreaBorderHeight = oh - ch;
- DOM.setStyleAttribute(bodyContainer.getElement(), "overflow",
+ DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
"auto");
}
}
@@ -3017,7 +4378,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// Webkit may sometimes get an odd rendering bug (white space
// between header and body), see bug #3875. Running
// overflow hack here to shake body element a bit.
- Util.runWebkitOverflowAutoFix(bodyContainer.getElement());
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+ }
+
+ if (!isFocusable()) {
+ scrollBodyPanel.getElement().setTabIndex(-1);
+ } else {
+ scrollBodyPanel.getElement().setTabIndex(0);
}
}
@@ -3034,7 +4401,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
if (visible) {
DeferredCommand.addCommand(new Command() {
public void execute() {
- bodyContainer
+ scrollBodyPanel
.setScrollPosition((int) (firstRowInViewPort * scrollBody
.getRowHeight()));
}
@@ -3051,7 +4418,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
* possibly with values caption and icon
* @return html snippet containing possibly an icon + caption text
*/
- private String buildCaptionHtmlSnippet(UIDL uidl) {
+ protected String buildCaptionHtmlSnippet(UIDL uidl) {
String s = uidl.getStringAttribute("caption");
if (uidl.hasAttribute("icon")) {
s = "<img src=\""
@@ -3067,13 +4434,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
* user scrolls
*/
public void onScroll(ScrollEvent event) {
- scrollLeft = bodyContainer.getElement().getScrollLeft();
- scrollTop = bodyContainer.getScrollPosition();
+ scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
+ scrollTop = scrollBodyPanel.getScrollPosition();
if (!initializedAndAttached) {
return;
}
if (!enabled) {
- bodyContainer
+ scrollBodyPanel
.setScrollPosition((int) (firstRowInViewPort * scrollBody
.getRowHeight()));
return;
@@ -3097,6 +4464,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
// fix headers horizontal scrolling
tHead.setHorizontalScrollPosition(scrollLeft);
+ // fix footers horizontal scrolling
+ tFoot.setHorizontalScrollPosition(scrollLeft);
+
firstRowInViewPort = (int) Math.ceil(scrollTop
/ scrollBody.getRowHeight());
if (firstRowInViewPort > totalRows - pageLength) {
@@ -3321,4 +4691,360 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
}
+ protected VScrollTableRow getFocusedRow() {
+ return focusedRow;
+ }
+
+ /**
+ * Moves the selection head to a specific row
+ *
+ * @param row
+ * The row to where the selection head should move
+ * @return Returns true if focus was moved successfully, else false
+ */
+ private boolean setRowFocus(VScrollTableRow row) {
+
+ if (selectMode == SELECT_MODE_NONE) {
+ return false;
+ }
+
+ // Remove previous selection
+ if (focusedRow != null && focusedRow != row) {
+ focusedRow.removeStyleName(CLASSNAME_SELECTION_FOCUS);
+ }
+
+ if (row != null) {
+
+ // Trying to set focus on already focused row
+ if (row == focusedRow) {
+ return false;
+ }
+
+ // Set new focused row
+ focusedRow = row;
+
+ // Apply focus style to new selection
+ focusedRow.addStyleName(CLASSNAME_SELECTION_FOCUS);
+
+ // Scroll up or down if needed
+ int rowTop = focusedRow.getElement().getAbsoluteTop();
+ int scrollTop = scrollBodyPanel.getElement().getAbsoluteTop();
+ int scrollBottom = scrollTop
+ + scrollBodyPanel.getElement().getOffsetHeight();
+ if (rowTop > scrollBottom - focusedRow.getOffsetHeight()) {
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition()
+ + focusedRow.getOffsetHeight());
+ } else if (rowTop < scrollTop) {
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition()
+ - focusedRow.getOffsetHeight());
+ }
+
+ return true;
+ } else {
+ focusedRow = null;
+ }
+
+ return false;
+ }
+
+ /**
+ * Handles the keyboard events handled by the table
+ *
+ * @param event
+ * The keyboard event received
+ * @return true iff the navigation event was handled
+ */
+ protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+ if (keycode == KeyCodes.KEY_TAB) {
+ // Do not handle tab key
+ return false;
+ }
+
+ // Down navigation
+ if (selectMode == SELECT_MODE_NONE && keycode == getNavigationDownKey()) {
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition()
+ + scrollingVelocity);
+ return true;
+ } else if (keycode == getNavigationDownKey()) {
+ if (selectMode == SELECT_MODE_MULTI && moveFocusDown()) {
+ selectFocusedRow(ctrl, shift);
+
+ } else if (selectMode == SELECT_MODE_SINGLE && !shift
+ && moveFocusDown()) {
+ selectFocusedRow(ctrl, shift);
+ }
+ return true;
+ }
+
+ // Up navigation
+ if (selectMode == SELECT_MODE_NONE && keycode == getNavigationUpKey()) {
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition()
+ - scrollingVelocity);
+ return true;
+ } else if (keycode == getNavigationUpKey()) {
+ if (selectMode == SELECT_MODE_MULTI && moveFocusUp()) {
+ selectFocusedRow(ctrl, shift);
+ } else if (selectMode == SELECT_MODE_SINGLE && !shift
+ && moveFocusUp()) {
+ selectFocusedRow(ctrl, shift);
+ }
+ return true;
+ }
+
+ if (keycode == getNavigationLeftKey()) {
+ // Left navigation
+ scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
+ .getHorizontalScrollPosition()
+ - scrollingVelocity);
+ return true;
+
+ } else if (keycode == getNavigationRightKey()) {
+ // Right navigation
+ scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
+ .getHorizontalScrollPosition()
+ + scrollingVelocity);
+ }
+
+ // Select navigation
+ if (selectMode > SELECT_MODE_NONE
+ && keycode == getNavigationSelectKey()) {
+ if (selectMode == SELECT_MODE_SINGLE) {
+ boolean wasSelected = focusedRow.isSelected();
+ deselectAll();
+ if (!wasSelected || !nullSelectionAllowed ) {
+ focusedRow.toggleSelection(true);
+ }
+
+ } else {
+ focusedRow.toggleSelection(true);
+ }
+
+ sendSelectedRows();
+ return true;
+ }
+
+ // Page Down navigation
+ if (keycode == getNavigationPageDownKey()) {
+ int rowHeight = (int) scrollBody.getRowHeight();
+ int offset = pageLength * rowHeight - rowHeight;
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition()
+ + offset);
+ if (selectMode > SELECT_MODE_NONE) {
+ if (!moveFocusDown(pageLength - 2)) {
+ final int lastRendered = scrollBody.getLastRendered();
+ if (lastRendered == totalRows - 1) {
+ selectLastRenderedRow(false);
+ } else {
+ selectLastItemInNextRender = true;
+ }
+ } else {
+ selectFocusedRow(false, false);
+ sendSelectedRows();
+ }
+ }
+ return true;
+ }
+
+ // Page Up navigation
+ if (keycode == getNavigationPageUpKey()) {
+ int rowHeight = (int) scrollBody.getRowHeight();
+ int offset = pageLength * rowHeight - rowHeight;
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition()
+ - offset);
+ if (selectMode > SELECT_MODE_NONE) {
+ if (!moveFocusUp(pageLength - 2)) {
+ final int firstRendered = scrollBody.getFirstRendered();
+ if (firstRendered == 0) {
+ selectFirstRenderedRow(false);
+ } else {
+ selectFirstItemInNextRender = true;
+ }
+ } else {
+ selectFocusedRow(false, false);
+ sendSelectedRows();
+ }
+ }
+ return true;
+ }
+
+ // Goto start navigation
+ if (keycode == getNavigationStartKey()) {
+ if (selectMode > SELECT_MODE_NONE) {
+ final int firstRendered = scrollBody.getFirstRendered();
+ boolean focusOnly = ctrl;
+ if (firstRendered == 0) {
+ selectFirstRenderedRow(focusOnly);
+ } else if (focusOnly) {
+ focusFirstItemInNextRender = true;
+ } else {
+ selectFirstItemInNextRender = true;
+ }
+ }
+ scrollBodyPanel.setScrollPosition(0);
+ return true;
+ }
+
+ // Goto end navigation
+ if (keycode == getNavigationEndKey()) {
+ if (selectMode > SELECT_MODE_NONE) {
+ final int lastRendered = scrollBody.getLastRendered();
+ boolean focusOnly = ctrl;
+ if (lastRendered == totalRows - 1) {
+ selectLastRenderedRow(focusOnly);
+ } else if (focusOnly) {
+ focusLastItemInNextRender = true;
+ } else {
+ selectLastItemInNextRender = true;
+ }
+ }
+ scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+ * .gwt.event.dom.client.KeyPressEvent)
+ */
+ public void onKeyPress(KeyPressEvent event) {
+ if (handleNavigation(event.getNativeEvent().getKeyCode(), event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
+ }
+
+ // Start the velocityTimer
+ if (scrollingVelocityTimer == null) {
+ scrollingVelocityTimer = new Timer() {
+ @Override
+ public void run() {
+ scrollingVelocity++;
+ }
+ };
+ scrollingVelocityTimer.scheduleRepeating(100);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ public void onKeyDown(KeyDownEvent event) {
+ if (handleNavigation(event.getNativeEvent().getKeyCode(), event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
+ }
+
+ // Start the velocityTimer
+ if (scrollingVelocityTimer == null) {
+ scrollingVelocityTimer = new Timer() {
+ @Override
+ public void run() {
+ scrollingVelocity++;
+ }
+ };
+ scrollingVelocityTimer.scheduleRepeating(100);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+ public void onFocus(FocusEvent event) {
+ if (isFocusable()) {
+ scrollBodyPanel.addStyleName("focused");
+
+ // Focus a row if no row is in focus
+ if (focusedRow == null) {
+ setRowFocus((VScrollTableRow) scrollBody.iterator().next());
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+ * .dom.client.BlurEvent)
+ */
+ public void onBlur(BlurEvent event) {
+ scrollBodyPanel.removeStyleName("focused");
+
+ // Unfocus any row
+ setRowFocus(null);
+ }
+
+ /**
+ * Removes a key from a range if the key is found in a selected range
+ *
+ * @param key
+ * The key to remove
+ */
+ private void removeKeyFromSelectedRange(int key) {
+ for (SelectionRange range : selectedRowRanges) {
+ if (range.inRange(key)) {
+ int start = range.getStartKey();
+ int end = range.getEndKey();
+
+ if (start < key && end > key) {
+ selectedRowRanges.add(new SelectionRange(start, key - 1));
+ selectedRowRanges.add(new SelectionRange(key + 1, end));
+ } else if (start == key && start < end) {
+ selectedRowRanges.add(new SelectionRange(start + 1, end));
+ } else if (end == key && start < end) {
+ selectedRowRanges.add(new SelectionRange(start, end - 1));
+ }
+
+ selectedRowRanges.remove(range);
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Can the Table be focused?
+ *
+ * @return True if the table can be focused, else false
+ */
+ public boolean isFocusable() {
+ if (scrollBody != null) {
+ boolean hasVerticalScrollbars = scrollBody.getOffsetHeight() > scrollBodyPanel
+ .getOffsetHeight();
+ boolean hasHorizontalScrollbars = scrollBody.getOffsetWidth() > scrollBodyPanel
+ .getOffsetWidth();
+ return !(!hasHorizontalScrollbars && !hasVerticalScrollbars && selectMode == SELECT_MODE_NONE);
+ }
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.Focusable#focus()
+ */
+ public void focus() {
+ scrollBodyPanel.focus();
+ }
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java b/src/com/vaadin/terminal/gwt/client/ui/VSlider.java
index 81fb898011..d30c2b2f20 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VSlider.java
@@ -4,6 +4,7 @@
//
package com.vaadin.terminal.gwt.client.ui;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
@@ -12,14 +13,13 @@ import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.ContainerResizedListener;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
-public class VSlider extends Widget implements Paintable, Field,
+public class VSlider extends SimpleFocusablePanel implements Paintable, Field,
ContainerResizedListener {
public static final String CLASSNAME = "v-slider";
@@ -39,6 +39,7 @@ public class VSlider extends Widget implements Paintable, Field,
private boolean readonly;
private boolean scrollbarStyle;
+ private int acceleration = 1;
private int handleSize;
private double min;
private double max;
@@ -76,7 +77,6 @@ public class VSlider extends Widget implements Paintable, Field,
public VSlider() {
super();
- setElement(DOM.createDiv());
base = DOM.createDiv();
handle = DOM.createDiv();
smaller = DOM.createDiv();
@@ -98,18 +98,19 @@ public class VSlider extends Widget implements Paintable, Field,
DOM.setStyleAttribute(bigger, "display", "none");
DOM.setStyleAttribute(handle, "visibility", "hidden");
- DOM.sinkEvents(getElement(), Event.MOUSEEVENTS | Event.ONMOUSEWHEEL);
+ DOM.sinkEvents(getElement(), Event.MOUSEEVENTS | Event.ONMOUSEWHEEL
+ | Event.KEYEVENTS | Event.FOCUSEVENTS);
DOM.sinkEvents(base, Event.ONCLICK);
DOM.sinkEvents(handle, Event.MOUSEEVENTS);
DOM.sinkEvents(smaller, Event.ONMOUSEDOWN | Event.ONMOUSEUP
| Event.ONMOUSEOUT);
DOM.sinkEvents(bigger, Event.ONMOUSEDOWN | Event.ONMOUSEUP
| Event.ONMOUSEOUT);
-
+
feedbackPopup.addStyleName(CLASSNAME + "-feedback");
feedbackPopup.setWidget(feedback);
}
-
+
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
this.client = client;
@@ -332,8 +333,35 @@ public class VSlider extends Widget implements Paintable, Field,
decreaseValue(true);
} else if (targ == bigger) {
increaseValue(true);
- } else {
+ } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) {
processBaseEvent(event);
+ } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS)
+ || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) {
+
+ if (handleNavigation(event.getKeyCode(), event.getCtrlKey(), event
+ .getShiftKey())) {
+
+ feedbackPopup.show();
+
+ if (scrollTimer != null) {
+ scrollTimer.cancel();
+ }
+ scrollTimer = new Timer() {
+ @Override
+ public void run() {
+ updateValueToServer();
+ acceleration = 1;
+ }
+ };
+ scrollTimer.schedule(100);
+
+ DOM.eventPreventDefault(event);
+ DOM.eventCancelBubble(event, true);
+ }
+ } else if (DOM.eventGetType(event) == Event.ONFOCUS) {
+ feedbackPopup.show();
+ } else if (DOM.eventGetType(event) == Event.ONBLUR) {
+ feedbackPopup.hide();
}
}
@@ -478,4 +506,88 @@ public class VSlider extends Widget implements Paintable, Field,
client.updateVariable(id, "value", value.doubleValue(), immediate);
}
+ /**
+ * Handles the keyboard events handled by the Slider
+ *
+ * @param event
+ * The keyboard event received
+ * @return true iff the navigation event was handled
+ */
+ public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+
+ // No support for ctrl moving
+ if (ctrl) {
+ return false;
+ }
+
+ if ((keycode == getNavigationUpKey() && vertical)
+ || (keycode == getNavigationRightKey() && !vertical)) {
+ if (shift) {
+ for (int a = 0; a < acceleration; a++) {
+ increaseValue(false);
+ }
+ acceleration++;
+ } else {
+ increaseValue(false);
+ }
+ return true;
+ } else if (keycode == getNavigationDownKey() && vertical
+ || (keycode == getNavigationLeftKey() && !vertical)) {
+ if (shift) {
+ for (int a = 0; a < acceleration; a++) {
+ decreaseValue(false);
+ }
+ acceleration++;
+ } else {
+ decreaseValue(false);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the key that increases the vertical slider. By default it is the up
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that decreases the vertical slider. By default it is the down
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that decreases the horizontal slider. By default it is the
+ * left arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that increases the horizontal slider. By default it is the
+ * right arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java b/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java
index 510676b3a4..209c5cabde 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java
@@ -356,4 +356,12 @@ public class VTextualDate extends VDateField implements Paintable, Field,
text.setFocus(true);
}
+ protected String getText() {
+ return text.getText();
+ }
+
+ protected void setText(String text) {
+ this.text.setText(text);
+ }
+
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTree.java b/src/com/vaadin/terminal/gwt/client/ui/VTree.java
index 42421b80de..9f5202cb68 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VTree.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VTree.java
@@ -4,12 +4,25 @@
package com.vaadin.terminal.gwt.client.ui;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Set;
+import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
@@ -37,18 +50,28 @@ import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
/**
*
*/
-public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
+public class VTree extends SimpleFocusablePanel implements Paintable,
+ VHasDropHandler, FocusHandler, BlurHandler, KeyPressHandler,
+ KeyDownHandler {
public static final String CLASSNAME = "v-tree";
public static final String ITEM_CLICK_EVENT_ID = "itemClick";
+ public static final int MULTISELECT_MODE_DEFAULT = 0;
+ public static final int MULTISELECT_MODE_SIMPLE = 1;
+
+ private final FlowPanel body = new FlowPanel();
+
private Set<String> selectedIds = new HashSet<String>();
private ApplicationConnection client;
private String paintableId;
private boolean selectable;
private boolean isMultiselect;
private String currentMouseOverKey;
+ private TreeNode lastSelection;
+ private TreeNode focusedNode;
+ private int multiSelectMode = MULTISELECT_MODE_DEFAULT;
private final HashMap<String, TreeNode> keyToNode = new HashMap<String, TreeNode>();
@@ -72,9 +95,80 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
private int dragMode;
+ private boolean selectionHasChanged = false;
+
public VTree() {
super();
setStyleName(CLASSNAME);
+ add(body);
+
+ addFocusHandler(this);
+ addBlurHandler(this);
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
+
+ /*
+ * We need to use the sinkEvents method to catch the keyUp events so we
+ * can cache a single shift. KeyUpHandler cannot do this. At the same
+ * time we catch the mouse down and up events so we can apply the text
+ * selection patch in IE
+ */
+ sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
+ * .client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONMOUSEDOWN) {
+ // Prevent default text selection in IE
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast()).setPropertyJSO(
+ "onselectstart", applyDisableTextSelectionIEHack());
+ } else {
+ setFocus(true);
+ }
+
+ } else if (event.getTypeInt() == Event.ONMOUSEUP) {
+ // Remove IE text selection hack
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast()).setPropertyJSO(
+ "onselectstart", null);
+ }
+ } else if (event.getTypeInt() == Event.ONKEYUP) {
+ if (selectionHasChanged) {
+ if (event.getKeyCode() == getNavigationDownKey()
+ && !event.getShiftKey()) {
+ sendSelectionToServer();
+ event.preventDefault();
+ } else if (event.getKeyCode() == getNavigationUpKey()
+ && !event.getShiftKey()) {
+ sendSelectionToServer();
+ event.preventDefault();
+ } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
+ sendSelectionToServer();
+ event.preventDefault();
+ } else if (event.getKeyCode() == getNavigationSelectKey()) {
+ sendSelectionToServer();
+ event.preventDefault();
+ }
+ }
+ }
}
private void updateActionMap(UIDL c) {
@@ -129,7 +223,7 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect");
- clear();
+ body.clear();
for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {
final UIDL childUidl = (UIDL) i.next();
if ("actions".equals(childUidl.getTag())) {
@@ -141,17 +235,21 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
}
final TreeNode childTree = new TreeNode();
if (childTree.ie6compatnode != null) {
- this.add(childTree);
+ body.add(childTree);
}
childTree.updateFromUIDL(childUidl, client);
if (childTree.ie6compatnode == null) {
- this.add(childTree);
+ body.add(childTree);
}
}
final String selectMode = uidl.getStringAttribute("selectmode");
selectable = !"none".equals(selectMode);
isMultiselect = "multi".equals(selectMode);
+ if (isMultiselect) {
+ multiSelectMode = uidl.getIntAttribute("multiselectmode");
+ }
+
selectedIds = uidl.getStringArrayVariableAsSet("selected");
rendering = false;
@@ -314,10 +412,25 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
treeNode.setSelected(false);
}
+ sendSelectionToServer();
+ }
+
+ /**
+ * Sends the selection to the server
+ */
+ private void sendSelectionToServer() {
client.updateVariable(paintableId, "selected", selectedIds
.toArray(new String[selectedIds.size()]), immediate);
+ selectionHasChanged = false;
}
+ /**
+ * Is a node selected in the tree
+ *
+ * @param treeNode
+ * The node to check
+ * @return
+ */
public boolean isSelected(TreeNode treeNode) {
return selectedIds.contains(treeNode.key);
}
@@ -325,6 +438,7 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
public class TreeNode extends SimplePanel implements ActionOwner {
public static final String CLASSNAME = "v-tree-node";
+ public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused";
public String key;
@@ -348,6 +462,8 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
private int cachedHeight = -1;
+ private boolean focused = false;
+
public TreeNode() {
constructDom();
sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS
@@ -430,6 +546,57 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
}
}
+ /**
+ * Handles mouse selection
+ *
+ * @param ctrl
+ * Was the ctrl-key pressed
+ * @param shift
+ * Was the shift-key pressed
+ * @return Returns true if event was handled, else false
+ */
+ private boolean handleClickSelection(boolean ctrl, boolean shift) {
+
+ if (multiSelectMode == MULTISELECT_MODE_SIMPLE || !isMultiselect) {
+ toggleSelection();
+ setFocusedNode(this);
+ lastSelection = this;
+ } else if (multiSelectMode == MULTISELECT_MODE_DEFAULT) {
+ // Handle ctrl+click
+ if (isMultiselect && ctrl && !shift) {
+ toggleSelection();
+ setFocusedNode(this);
+ lastSelection = this;
+
+ // Handle shift+click
+ } else if (isMultiselect && !ctrl && shift) {
+ deselectAll();
+ selectNodeRange(lastSelection.key, key);
+ sendSelectionToServer();
+
+ // Handle ctrl+shift click
+ } else if (isMultiselect && ctrl && shift) {
+ selectNodeRange(lastSelection.key, key);
+
+ // Handle click
+ } else {
+ deselectAll();
+ toggleSelection();
+ setFocusedNode(this);
+ lastSelection = this;
+ }
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
+ * .user.client.Event)
+ */
@Override
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
@@ -451,15 +618,22 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
if (getElement() == target || ie6compatnode == target) {
// state change
toggleState();
- } else if (!readonly && inCaption) {
+ } else if (!readonly && inCaption && selectable) {
// caption click = selection change && possible click event
- toggleSelection();
+ if (handleClickSelection(event.getCtrlKey()
+ || event.getMetaKey(), event.getShiftKey())) {
+ event.preventDefault();
+ }
}
DOM.eventCancelBubble(event, true);
} else if (type == Event.ONCONTEXTMENU) {
showContextMenu(event);
}
+ if (type == Event.ONMOUSEDOWN) {
+ event.preventDefault();
+ }
+
if (dragMode != 0 || dropHandler != null) {
if (type == Event.ONMOUSEDOWN) {
if (nodeCaptionDiv.isOrHasChild(event.getTarget())) {
@@ -674,6 +848,24 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
return childrenLoaded;
}
+ /**
+ * Returns the children of the node
+ *
+ * @return A set of tree nodes
+ */
+ public List<TreeNode> getChildren() {
+ List<TreeNode> nodes = new LinkedList<TreeNode>();
+
+ if (!isLeaf() && isChildrenLoaded()) {
+ Iterator<Widget> iter = childNodeContainer.iterator();
+ while (iter.hasNext()) {
+ TreeNode node = (TreeNode) iter.next();
+ nodes.add(node);
+ }
+ }
+ return nodes;
+ }
+
public Action[] getActions() {
if (actionKeys == null) {
return new Action[] {};
@@ -713,6 +905,30 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
return VTree.this.isSelected(this);
}
+ /**
+ * Travels up the hierarchy looking for this node
+ *
+ * @param child
+ * The child which grandparent this is or is not
+ * @return True if this is a grandparent of the child node
+ */
+ public boolean isGrandParentOf(TreeNode child) {
+ TreeNode currentNode = child;
+ boolean isGrandParent = false;
+ while (currentNode != null) {
+ currentNode = currentNode.getParentNode();
+ if (currentNode == this) {
+ isGrandParent = true;
+ break;
+ }
+ }
+ return isGrandParent;
+ }
+
+ public boolean isSibling(TreeNode node) {
+ return node.getParentNode() == getParentNode();
+ }
+
public void showContextMenu(Event event) {
if (!readonly && !disabled) {
if (actionKeys != null) {
@@ -740,6 +956,11 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
setWidth(captionWidth + "px");
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.Widget#onAttach()
+ */
@Override
public void onAttach() {
super.onAttach();
@@ -748,11 +969,49 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.Widget#onDetach()
+ */
@Override
protected void onDetach() {
super.onDetach();
client.getContextMenu().ensureHidden(this);
}
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.UIObject#toString()
+ */
+ @Override
+ public String toString() {
+ return nodeCaptionSpan.getInnerText();
+ }
+
+ /**
+ * Is the node focused?
+ *
+ * @param focused
+ * True if focused, false if not
+ */
+ public void setFocused(boolean focused) {
+ if (!this.focused && focused) {
+ nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED);
+ if (BrowserInfo.get().isIE6()) {
+ ie6compatnode.addClassName(CLASSNAME_FOCUSED);
+ }
+ this.focused = focused;
+ } else if (this.focused && !focused) {
+ nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED);
+ if (BrowserInfo.get().isIE6()) {
+ ie6compatnode.removeClassName(CLASSNAME_FOCUSED);
+ }
+ this.focused = focused;
+ }
+ }
+
}
public VDropHandler getDropHandler() {
@@ -762,4 +1021,801 @@ public class VTree extends FlowPanel implements Paintable, VHasDropHandler {
public TreeNode getNodeByKey(String key) {
return keyToNode.get(key);
}
+
+ /**
+ * Deselects all items in the tree
+ */
+ public void deselectAll() {
+ for (String key : selectedIds) {
+ TreeNode node = keyToNode.get(key);
+ if (node != null) {
+ node.setSelected(false);
+ }
+ }
+ selectedIds.clear();
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects a range of nodes
+ *
+ * @param startNodeKey
+ * The start node key
+ * @param endNodeKey
+ * The end node key
+ */
+ private void selectNodeRange(String startNodeKey, String endNodeKey) {
+
+ TreeNode startNode = keyToNode.get(startNodeKey);
+ TreeNode endNode = keyToNode.get(endNodeKey);
+
+ // The nodes have the same parent
+ if (startNode.getParent() == endNode.getParent()) {
+ doSiblingSelection(startNode, endNode);
+
+ // The start node is a grandparent of the end node
+ } else if (startNode.isGrandParentOf(endNode)) {
+ doRelationSelection(startNode, endNode);
+
+ // The end node is a grandparent of the start node
+ } else if (endNode.isGrandParentOf(startNode)) {
+ doRelationSelection(endNode, startNode);
+
+ } else {
+ doNoRelationSelection(startNode, endNode);
+ }
+ }
+
+ /**
+ * Selects a node and deselect all other nodes
+ *
+ * @param node
+ * The node to select
+ */
+ private void selectNode(TreeNode node, boolean deselectPrevious) {
+ if (deselectPrevious) {
+ deselectAll();
+ }
+
+ if (node != null) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ lastSelection = node;
+ }
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Deselects a node
+ *
+ * @param node
+ * The node to deselect
+ */
+ private void deselectNode(TreeNode node) {
+ node.setSelected(false);
+ selectedIds.remove(node.key);
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects all the open children to a node
+ *
+ * @param node
+ * The parent node
+ */
+ private void selectAllChildren(TreeNode node, boolean includeRootNode) {
+ if (includeRootNode) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ }
+
+ for (TreeNode child : node.getChildren()) {
+ if (!child.isLeaf() && child.getState()) {
+ selectAllChildren(child, true);
+ } else {
+ child.setSelected(true);
+ selectedIds.add(child.key);
+ }
+ }
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects all children until a stop child is reached
+ *
+ * @param root
+ * The root not to start from
+ * @param stopNode
+ * The node to finish with
+ * @param includeRootNode
+ * Should the root node be selected
+ * @param includeStopNode
+ * Should the stop node be selected
+ *
+ * @return Returns false if the stop child was found, else true if all
+ * children was selected
+ */
+ private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode,
+ boolean includeRootNode, boolean includeStopNode) {
+ if (includeRootNode) {
+ root.setSelected(true);
+ selectedIds.add(root.key);
+ }
+ if (root.getState() && root != stopNode) {
+ for (TreeNode child : root.getChildren()) {
+ if (!child.isLeaf() && child.getState() && child != stopNode) {
+ if (!selectAllChildrenUntil(child, stopNode, true,
+ includeStopNode)) {
+ return false;
+ }
+ } else if (child == stopNode) {
+ if (includeStopNode) {
+ child.setSelected(true);
+ selectedIds.add(child.key);
+ }
+ return false;
+ } else if (child.isLeaf()) {
+ child.setSelected(true);
+ selectedIds.add(child.key);
+ }
+ }
+ }
+ selectionHasChanged = true;
+
+ return true;
+ }
+
+ /**
+ * Select a range between two nodes which have no relation to each other
+ *
+ * @param startNode
+ * The start node to start the selection from
+ * @param endNode
+ * The end node to end the selection to
+ */
+ private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) {
+
+ TreeNode commonParent = getCommonGrandParent(startNode, endNode);
+ TreeNode startBranch = null, endBranch = null;
+
+ // Find the children of the common parent
+ List<TreeNode> children;
+ if (commonParent != null) {
+ children = commonParent.getChildren();
+ } else {
+ children = new LinkedList<TreeNode>();
+ for (int w = 0; w < body.getWidgetCount(); w++) {
+ children.add((TreeNode) body.getWidget(w));
+ }
+ }
+
+ // Find the start and end branches
+ for (TreeNode node : children) {
+ if (nodeIsInBranch(startNode, node)) {
+ startBranch = node;
+ }
+ if (nodeIsInBranch(endNode, node)) {
+ endBranch = node;
+ }
+ }
+
+ // Swap nodes if necessary
+ if (children.indexOf(startBranch) > children.indexOf(endBranch)) {
+ TreeNode temp = startBranch;
+ startBranch = endBranch;
+ endBranch = temp;
+
+ temp = startNode;
+ startNode = endNode;
+ endNode = temp;
+ }
+
+ // Select all children under the start node
+ selectAllChildren(startNode, true);
+ TreeNode startParent = startNode.getParentNode();
+ TreeNode currentNode = startNode;
+ while (startParent != null && startParent != commonParent) {
+ List<TreeNode> startChildren = startParent.getChildren();
+ for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren
+ .size(); i++) {
+ selectAllChildren(startChildren.get(i), true);
+ }
+
+ currentNode = startParent;
+ startParent = startParent.getParentNode();
+ }
+
+ // Select nodes until the end node is reached
+ for (int i = children.indexOf(startBranch) + 1; i <= children
+ .indexOf(endBranch); i++) {
+ selectAllChildrenUntil(children.get(i), endNode, true, true);
+ }
+
+ // Ensure end node was selected
+ endNode.setSelected(true);
+ selectedIds.add(endNode.key);
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Examines the children of the branch node and returns true if a node is in
+ * that branch
+ *
+ * @param node
+ * The node to search for
+ * @param branch
+ * The branch to search in
+ * @return True if found, false if not found
+ */
+ private boolean nodeIsInBranch(TreeNode node, TreeNode branch) {
+ if (node == branch) {
+ return true;
+ }
+ for (TreeNode child : branch.getChildren()) {
+ if (child == node) {
+ return true;
+ }
+ if (!child.isLeaf() && child.getState()) {
+ if (nodeIsInBranch(node, child)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Selects a range of items which are in direct relation with each other.<br/>
+ * NOTE: The start node <b>MUST</b> be before the end node!
+ *
+ * @param startNode
+ *
+ * @param endNode
+ */
+ private void doRelationSelection(TreeNode startNode, TreeNode endNode) {
+ TreeNode currentNode = endNode;
+ while (currentNode != startNode) {
+ currentNode.setSelected(true);
+ selectedIds.add(currentNode.key);
+
+ // Traverse children above the selection
+ List<TreeNode> subChildren = currentNode.getParentNode()
+ .getChildren();
+ if (subChildren.size() > 1) {
+ selectNodeRange(subChildren.iterator().next().key,
+ currentNode.key);
+ } else if (subChildren.size() == 1) {
+ TreeNode n = subChildren.get(0);
+ n.setSelected(true);
+ selectedIds.add(n.key);
+ }
+
+ currentNode = currentNode.getParentNode();
+ }
+ startNode.setSelected(true);
+ selectedIds.add(startNode.key);
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects a range of items which have the same parent.
+ *
+ * @param startNode
+ * The start node
+ * @param endNode
+ * The end node
+ */
+ private void doSiblingSelection(TreeNode startNode, TreeNode endNode) {
+ TreeNode parent = startNode.getParentNode();
+
+ List<TreeNode> children = new LinkedList<TreeNode>();
+ if (parent == null) {
+ // Topmost parent
+ for (int w = 0; w < body.getWidgetCount(); w++) {
+ children.add((TreeNode) body.getWidget(w));
+ }
+ } else {
+ children = parent.getChildren();
+ }
+
+ // Swap start and end point if needed
+ if (children.indexOf(startNode) > children.indexOf(endNode)) {
+ TreeNode temp = startNode;
+ startNode = endNode;
+ endNode = temp;
+ }
+
+ Iterator<TreeNode> childIter = children.iterator();
+ boolean startFound = false;
+ while (childIter.hasNext()) {
+ TreeNode node = childIter.next();
+ if (node == startNode) {
+ startFound = true;
+ }
+
+ if (startFound && node != endNode && node.getState()) {
+ selectAllChildren(node, true);
+ } else if (startFound && node != endNode) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ }
+
+ if (node == endNode) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ break;
+ }
+ }
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Returns the first common parent of two nodes
+ *
+ * @param node1
+ * The first node
+ * @param node2
+ * The second node
+ * @return The common parent or null
+ */
+ public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) {
+ // If either one does not have a grand parent then return null
+ if (node1.getParentNode() == null || node2.getParentNode() == null) {
+ return null;
+ }
+
+ // If the nodes are parents of each other then return null
+ if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) {
+ return null;
+ }
+
+ // Get parents of node1
+ List<TreeNode> parents1 = new ArrayList<TreeNode>();
+ TreeNode parent1 = node1.getParentNode();
+ while (parent1 != null) {
+ parents1.add(parent1);
+ parent1 = parent1.getParentNode();
+ }
+
+ // Get parents of node2
+ List<TreeNode> parents2 = new ArrayList<TreeNode>();
+ TreeNode parent2 = node2.getParentNode();
+ while (parent2 != null) {
+ parents2.add(parent2);
+ parent2 = parent2.getParentNode();
+ }
+
+ // Search the parents for the first common parent
+ for (int i = 0; i < parents1.size(); i++) {
+ parent1 = parents1.get(i);
+ for (int j = 0; j < parents2.size(); j++) {
+ parent2 = parents2.get(j);
+ if (parent1 == parent2) {
+ return parent1;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the node currently in focus
+ *
+ * @param node
+ * The node to focus or null to remove the focus completely
+ */
+ public void setFocusedNode(TreeNode node) {
+ // Unfocus previously focused node
+ if (focusedNode != null) {
+ focusedNode.setFocused(false);
+ }
+
+ if (node != null) {
+ node.setFocused(true);
+ }
+
+ focusedNode = node;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+ public void onFocus(FocusEvent event) {
+ // If no node has focus, focus the first item in the tree
+ if (focusedNode == null && selectable) {
+ setFocusedNode((TreeNode) body.getWidget(0));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+ * .dom.client.BlurEvent)
+ */
+ public void onBlur(BlurEvent event) {
+ setFocusedNode(null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+ * .gwt.event.dom.client.KeyPressEvent)
+ */
+ public void onKeyPress(KeyPressEvent event) {
+ if (handleKeyNavigation(event.getNativeEvent().getKeyCode(), event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ public void onKeyDown(KeyDownEvent event) {
+ if (handleKeyNavigation(event.getNativeEvent().getKeyCode(), event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ /**
+ * Handles the keyboard navigation
+ *
+ * @param keycode
+ * The keycode of the pressed key
+ * @param ctrl
+ * Was ctrl pressed
+ * @param shift
+ * Was shift pressed
+ * @return Returns true if the key was handled, else false
+ */
+ protected boolean handleKeyNavigation(int keycode, boolean ctrl,
+ boolean shift) {
+
+ // Navigate down
+ if (keycode == getNavigationDownKey()) {
+ TreeNode node = null;
+ // If node is open and has children then move in to the children
+ if (!focusedNode.isLeaf() && focusedNode.getState()
+ && focusedNode.getChildren().size() > 0) {
+ node = focusedNode.getChildren().get(0);
+ }
+
+ // Else move down to the next sibling
+ else {
+ node = getNextSibling(focusedNode);
+ if (node == null) {
+ // Else jump to the parent and try to select the next
+ // sibling there
+ TreeNode current = focusedNode;
+ while (node == null && current.getParentNode() != null) {
+ node = getNextSibling(current.getParentNode());
+ current = current.getParentNode();
+ }
+ }
+ }
+
+ if (node != null && selectable) {
+ setFocusedNode(node);
+ if (!ctrl && !shift) {
+ selectNode(node, true);
+ } else if (shift && isMultiselect) {
+ deselectAll();
+ selectNodeRange(lastSelection.key, node.key);
+ } else if (shift) {
+ selectNode(node, true);
+ }
+ }
+ return true;
+ }
+
+ // Navigate up
+ if (keycode == getNavigationUpKey()) {
+ TreeNode prev = getPreviousSibling(focusedNode);
+ TreeNode node = null;
+ if (prev != null) {
+ node = getLastVisibleChildInTree(prev);
+ } else if (focusedNode.getParentNode() != null) {
+ node = focusedNode.getParentNode();
+ }
+ if (node != null && selectable) {
+ setFocusedNode(node);
+
+ if (!ctrl && !shift) {
+ selectNode(node, true);
+ } else if (shift && isMultiselect) {
+ deselectAll();
+ selectNodeRange(lastSelection.key, node.key);
+ } else if (shift) {
+ selectNode(node, true);
+ }
+ }
+ return true;
+ }
+
+ // Navigate left (close branch)
+ if (keycode == getNavigationLeftKey()) {
+ if (!focusedNode.isLeaf() && focusedNode.getState()) {
+ focusedNode.setState(false, true);
+ } else if (focusedNode.getParentNode() != null
+ && (focusedNode.isLeaf() || !focusedNode.getState())) {
+
+ if (ctrl) {
+ setFocusedNode(focusedNode.getParentNode());
+ } else if (shift) {
+ doRelationSelection(focusedNode.getParentNode(),
+ focusedNode);
+ setFocusedNode(focusedNode.getParentNode());
+ } else {
+ setSelected(focusedNode, false);
+ setFocusedNode(focusedNode.getParentNode());
+ setSelected(focusedNode, true);
+ }
+ }
+ return true;
+ }
+
+ // Navigate right (open branch)
+ if (keycode == getNavigationRightKey()) {
+ if (!focusedNode.isLeaf() && !focusedNode.getState()) {
+ focusedNode.setState(true, true);
+ } else if (!focusedNode.isLeaf()) {
+ if (ctrl) {
+ setFocusedNode(focusedNode.getChildren().get(0));
+ } else if (shift) {
+ setSelected(focusedNode, true);
+ setFocusedNode(focusedNode.getChildren().get(0));
+ setSelected(focusedNode, true);
+ } else {
+ setSelected(focusedNode, false);
+ setFocusedNode(focusedNode.getChildren().get(0));
+ setSelected(focusedNode, true);
+ }
+ }
+ return true;
+ }
+
+ // Selection
+ if (keycode == getNavigationSelectKey()) {
+ if (!focusedNode.isSelected()) {
+ selectNode(focusedNode, !isMultiselect
+ || multiSelectMode == MULTISELECT_MODE_SIMPLE);
+ } else {
+ deselectNode(focusedNode);
+ }
+ return true;
+ }
+
+ // Home selection
+ if (keycode == getNavigationStartKey()) {
+ TreeNode node = (TreeNode) body.getWidget(0);
+ if (!ctrl && !shift) {
+ selectNode(node, true);
+ } else if (ctrl) {
+ setFocusedNode(node);
+ } else if (shift) {
+ deselectAll();
+ selectNodeRange(focusedNode.key, node.key);
+ }
+ sendSelectionToServer();
+ return true;
+ }
+
+ // End selection
+ if (keycode == getNavigationEndKey()) {
+ TreeNode lastNode = (TreeNode) body
+ .getWidget(body.getWidgetCount() - 1);
+ TreeNode node = getLastVisibleChildInTree(lastNode);
+ if (!ctrl && !shift) {
+ selectNode(node, true);
+ } else if (ctrl) {
+ setFocusedNode(node);
+ } else if (shift) {
+ deselectAll();
+ selectNodeRange(focusedNode.key, node.key);
+ }
+ sendSelectionToServer();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Traverses the tree to the bottom most child
+ *
+ * @param root
+ * The root of the tree
+ * @return The bottom most child
+ */
+ private TreeNode getLastVisibleChildInTree(TreeNode root) {
+ if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) {
+ return root;
+ }
+ List<TreeNode> children = root.getChildren();
+ return getLastVisibleChildInTree(children.get(children.size() - 1));
+ }
+
+ /**
+ * Gets the next sibling in the tree
+ *
+ * @param node
+ * The node to get the sibling for
+ * @return The sibling node or null if the node is the last sibling
+ */
+ private TreeNode getNextSibling(TreeNode node) {
+ TreeNode parent = node.getParentNode();
+ List<TreeNode> children;
+ if (parent == null) {
+ children = new LinkedList<TreeNode>();
+ for (int w = 0; w < body.getWidgetCount(); w++) {
+ children.add((TreeNode) body.getWidget(w));
+ }
+ } else {
+ children = parent.getChildren();
+ }
+
+ int idx = children.indexOf(node);
+ if (idx < children.size() - 1) {
+ return children.get(idx + 1);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the previous sibling in the tree
+ *
+ * @param node
+ * The node to get the sibling for
+ * @return The sibling node or null if the node is the first sibling
+ */
+ private TreeNode getPreviousSibling(TreeNode node) {
+ TreeNode parent = node.getParentNode();
+ List<TreeNode> children;
+ if (parent == null) {
+ children = new LinkedList<TreeNode>();
+ for (int w = 0; w < body.getWidgetCount(); w++) {
+ children.add((TreeNode) body.getWidget(w));
+ }
+ } else {
+ children = parent.getChildren();
+ }
+
+ int idx = children.indexOf(node);
+ if (idx > 0) {
+ return children.get(idx - 1);
+ }
+
+ return null;
+ }
+
+ /**
+ * Add this to the element mouse down event by using element.setPropertyJSO
+ * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
+ * when the mouse is depressed in the mouse up event.
+ *
+ * @return Returns the JSO preventing text selection
+ */
+ private native JavaScriptObject applyDisableTextSelectionIEHack()
+ /*-{
+ return function(){ return false; };
+ }-*/;
+
+ /**
+ * Get the key that moves the selection head upwards. By default it is the
+ * up arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that moves the selection head downwards. By default it is the
+ * down arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that scrolls to the left in the table. By default it is the
+ * left arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that scroll to the right on the table. By default it is the
+ * right arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * Get the key that selects an item in the table. By default it is the space
+ * bar key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return 32;
+ }
+
+ /**
+ * Get the key the moves the selection one page up in the table. By default
+ * this is the Page Up key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageUpKey() {
+ return KeyCodes.KEY_PAGEUP;
+ }
+
+ /**
+ * Get the key the moves the selection one page down in the table. By
+ * default this is the Page Down key but by overriding this you can change
+ * the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageDownKey() {
+ return KeyCodes.KEY_PAGEDOWN;
+ }
+
+ /**
+ * Get the key the moves the selection to the beginning of the table. By
+ * default this is the Home key but by overriding this you can change the
+ * key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationStartKey() {
+ return KeyCodes.KEY_HOME;
+ }
+
+ /**
+ * Get the key the moves the selection to the end of the table. By default
+ * this is the End key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationEndKey() {
+ return KeyCodes.KEY_END;
+ }
+
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java
index a4d53ed837..089c61080f 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java
@@ -8,6 +8,9 @@ import java.util.ArrayList;
import java.util.Iterator;
import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.user.client.DOM;
@@ -16,9 +19,8 @@ import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.vaadin.terminal.gwt.client.UIDL;
-
-public class VTwinColSelect extends VOptionGroupBase implements
- MouseDownHandler {
+
+public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, MouseDownHandler {
private static final String CLASSNAME = "v-select-twincol";
@@ -67,9 +69,12 @@ public class VTwinColSelect extends VOptionGroupBase implements
buttons.add(remove);
panel.add(buttons);
panel.add(selections);
-
+
+ options.addKeyDownHandler(this);
options.addMouseDownHandler(this);
+
selections.addMouseDownHandler(this);
+ selections.addKeyDownHandler(this);
}
@Override
@@ -148,47 +153,81 @@ public class VTwinColSelect extends VOptionGroupBase implements
return selectedIndexes;
}
+ private void addItem() {
+ final boolean[] sel = getItemsToAdd();
+ for (int i = 0; i < sel.length; i++) {
+ if (sel[i]) {
+ final int optionIndex = i
+ - (sel.length - options.getItemCount());
+ selectedKeys.add(options.getValue(optionIndex));
+
+ // Move selection to another column
+ final String text = options.getItemText(optionIndex);
+ final String value = options.getValue(optionIndex);
+ selections.addItem(text, value);
+ selections.setItemSelected(selections.getItemCount() - 1, true);
+ options.removeItem(optionIndex);
+
+ if (options.getItemCount() > 0) {
+ options.setItemSelected(optionIndex > 0 ? optionIndex - 1
+ : 0, true);
+ }
+ }
+ }
+
+ // If no items are left move the focus to the selections
+ if (options.getItemCount() == 0) {
+ selections.setFocus(true);
+ } else {
+ options.setFocus(true);
+ }
+
+ client.updateVariable(id, "selected", selectedKeys
+ .toArray(new String[selectedKeys.size()]), isImmediate());
+ }
+
+ private void removeItem() {
+ final boolean[] sel = getItemsToRemove();
+ for (int i = 0; i < sel.length; i++) {
+ if (sel[i]) {
+ final int selectionIndex = i
+ - (sel.length - selections.getItemCount());
+ selectedKeys.remove(selections.getValue(selectionIndex));
+
+ // Move selection to another column
+ final String text = selections.getItemText(selectionIndex);
+ final String value = selections.getValue(selectionIndex);
+ options.addItem(text, value);
+ options.setItemSelected(options.getItemCount() - 1, true);
+ selections.removeItem(selectionIndex);
+
+ if (selections.getItemCount() > 0) {
+ selections.setItemSelected(
+ selectionIndex > 0 ? selectionIndex - 1 : 0, true);
+ }
+ }
+ }
+
+ // If no items are left move the focus to the selections
+ if (selections.getItemCount() == 0) {
+ options.setFocus(true);
+ } else {
+ selections.setFocus(true);
+ }
+
+ client.updateVariable(id, "selected", selectedKeys
+ .toArray(new String[selectedKeys.size()]), isImmediate());
+ }
+
@Override
public void onClick(ClickEvent event) {
super.onClick(event);
if (event.getSource() == add) {
- final boolean[] sel = getItemsToAdd();
- for (int i = 0; i < sel.length; i++) {
- if (sel[i]) {
- final int optionIndex = i
- - (sel.length - options.getItemCount());
- selectedKeys.add(options.getValue(optionIndex));
-
- // Move selection to another column
- final String text = options.getItemText(optionIndex);
- final String value = options.getValue(optionIndex);
- selections.addItem(text, value);
- selections.setItemSelected(selections.getItemCount() - 1,
- true);
- options.removeItem(optionIndex);
- }
- }
- client.updateVariable(id, "selected", selectedKeys
- .toArray(new String[selectedKeys.size()]), isImmediate());
+ addItem();
} else if (event.getSource() == remove) {
- final boolean[] sel = getItemsToRemove();
- for (int i = 0; i < sel.length; i++) {
- if (sel[i]) {
- final int selectionIndex = i
- - (sel.length - selections.getItemCount());
- selectedKeys.remove(selections.getValue(selectionIndex));
-
- // Move selection to another column
- final String text = selections.getItemText(selectionIndex);
- final String value = selections.getValue(selectionIndex);
- options.addItem(text, value);
- options.setItemSelected(options.getItemCount() - 1, true);
- selections.removeItem(selectionIndex);
- }
- }
- client.updateVariable(id, "selected", selectedKeys
- .toArray(new String[selectedKeys.size()]), isImmediate());
+ removeItem();
+
} else if (event.getSource() == options) {
// unselect all in other list, to avoid mistakes (i.e wrong button)
final int c = selections.getItemCount();
@@ -246,6 +285,79 @@ public class VTwinColSelect extends VOptionGroupBase implements
public void focus() {
options.setFocus(true);
+ }
+
+ /**
+ * Get the key that selects an item in the table. By default it is the Enter
+ * key but by overriding this you can change the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return KeyCodes.KEY_ENTER;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeKeyCode();
+
+ // Catch tab and move between select:s
+ if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) {
+ // Prevent default behavior
+ event.preventDefault();
+
+ // Remove current selections
+ for (int i = 0; i < options.getItemCount(); i++) {
+ options.setItemSelected(i, false);
+ }
+
+ // Focus selections
+ selections.setFocus(true);
+ }
+
+ if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown()
+ && event.getSource() == selections) {
+ // Prevent default behavior
+ event.preventDefault();
+
+ // Remove current selections
+ for (int i = 0; i < selections.getItemCount(); i++) {
+ selections.setItemSelected(i, false);
+ }
+
+ // Focus options
+ options.setFocus(true);
+ }
+
+ if (keycode == getNavigationSelectKey()) {
+ // Prevent default behavior
+ event.preventDefault();
+
+ // Decide which select the selection was made in
+ if (event.getSource() == options) {
+ // Prevents the selection to become a single selection when
+ // using Enter key
+ // as the selection key (default)
+ options.setFocus(false);
+
+ addItem();
+
+ } else if (event.getSource() == selections) {
+ // Prevents the selection to become a single selection when
+ // using Enter key
+ // as the selection key (default)
+ selections.setFocus(false);
+
+ removeItem();
+ }
+ }
+
}
/*
@@ -268,5 +380,5 @@ public class VTwinColSelect extends VOptionGroupBase implements
}
}
- }
+ }
}
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
index 98b95b2a7c..d8664e216c 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
@@ -34,6 +34,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
+import java.util.UUID;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
@@ -319,6 +320,8 @@ public abstract class AbstractCommunicationManager implements
private DragAndDropService dragAndDropService;
+ private static int nextUnusedWindowSuffix = 1;
+
/**
* TODO New constructor - document me!
*
@@ -670,7 +673,7 @@ public abstract class AbstractCommunicationManager implements
String seckey = (String) request.getSession().getAttribute(
ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
if (seckey == null) {
- seckey = "" + (int) (Math.random() * 1000000);
+ seckey = UUID.randomUUID().toString();
request.getSession().setAttribute(
ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey);
}
@@ -1543,9 +1546,12 @@ public abstract class AbstractCommunicationManager implements
// If the requested window is already open, resolve conflict
if (currentlyOpenWindowsInClient.containsKey(window.getName())) {
String newWindowName = window.getName();
- while (currentlyOpenWindowsInClient.containsKey(newWindowName)) {
- newWindowName = window.getName() + "_"
- + ((int) (Math.random() * 100000000));
+
+ synchronized (AbstractCommunicationManager.class) {
+ while (currentlyOpenWindowsInClient.containsKey(newWindowName)) {
+ newWindowName = window.getName() + "_"
+ + nextUnusedWindowSuffix++;
+ }
}
window = application.getWindow(newWindowName);
diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java
index abb50a05eb..aa65d80d96 100644
--- a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java
+++ b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java
@@ -68,7 +68,7 @@ public class ClassPathExplorer {
};
private static List<String> rawClasspathEntries = getRawClasspathEntries();
- private static Map<URL, String> classpathLocations = getClasspathLocations(rawClasspathEntries);
+ private static Map<String, URL> classpathLocations = getClasspathLocations(rawClasspathEntries);
private ClassPathExplorer() {
}
@@ -79,9 +79,9 @@ public class ClassPathExplorer {
public static Collection<Class<? extends Paintable>> getPaintablesHavingWidgetAnnotation() {
Collection<Class<? extends Paintable>> paintables = new HashSet<Class<? extends Paintable>>();
- Set<URL> keySet = classpathLocations.keySet();
- for (URL url : keySet) {
- searchForPaintables(url, classpathLocations.get(url), paintables);
+ Set<String> keySet = classpathLocations.keySet();
+ for (String url : keySet) {
+ searchForPaintables(classpathLocations.get(url), url, paintables);
}
return paintables;
@@ -103,9 +103,9 @@ public class ClassPathExplorer {
*/
public static Map<String, URL> getAvailableWidgetSets() {
Map<String, URL> widgetsets = new HashMap<String, URL>();
- Set<URL> keySet = classpathLocations.keySet();
- for (URL url : keySet) {
- searchForWidgetSets(url, widgetsets);
+ Set<String> keySet = classpathLocations.keySet();
+ for (String location : keySet) {
+ searchForWidgetSets(location, widgetsets);
}
StringBuilder sb = new StringBuilder();
sb.append("Widgetsets found from classpath:\n");
@@ -120,9 +120,10 @@ public class ClassPathExplorer {
return widgetsets;
}
- private static void searchForWidgetSets(URL location,
+ private static void searchForWidgetSets(String locationString,
Map<String, URL> widgetsets) {
+ URL location = classpathLocations.get(locationString);
File directory = new File(location.getFile());
if (directory.exists() && !directory.isHidden()) {
@@ -134,10 +135,10 @@ public class ClassPathExplorer {
// remove the extension
String classname = files[i].substring(0,
files[i].length() - 8);
- classname = classpathLocations.get(location) + "."
- + classname;
+ String packageName = locationString
+ .substring(locationString.lastIndexOf("/") + 1);
+ classname = packageName + "." + classname;
if (!widgetsets.containsKey(classname)) {
- String packageName = classpathLocations.get(location);
String packagePath = packageName.replaceAll("\\.", "/");
String basePath = location.getFile().replaceAll(
"/" + packagePath + "$", "");
@@ -225,10 +226,10 @@ public class ClassPathExplorer {
* Determine every URL location defined by the current classpath, and it's
* associated package name.
*/
- private final static Map<URL, String> getClasspathLocations(
+ private final static Map<String, URL> getClasspathLocations(
List<String> rawClasspathEntries) {
// try to keep the order of the classpath
- Map<URL, String> locations = new LinkedHashMap<URL, String>();
+ Map<String, URL> locations = new LinkedHashMap<String, URL>();
for (String classpathEntry : rawClasspathEntries) {
File file = new File(classpathEntry);
include(null, file, locations);
@@ -285,7 +286,7 @@ public class ClassPathExplorer {
* @param locations
*/
private final static void include(String name, File file,
- Map<URL, String> locations) {
+ Map<String, URL> locations) {
if (!file.exists()) {
return;
}
@@ -312,9 +313,10 @@ public class ClassPathExplorer {
// add the present directory
if (!dirs[i].isHidden()
&& !dirs[i].getPath().contains(File.separator + ".")) {
- locations.put(new URL("file://"
- + dirs[i].getCanonicalPath()), name
- + dirs[i].getName());
+ String key = dirs[i].getCanonicalPath() + "/" + name
+ + dirs[i].getName();
+ locations.put(key, new URL("file://"
+ + dirs[i].getCanonicalPath()));
}
} catch (Exception ioe) {
return;
@@ -323,14 +325,15 @@ public class ClassPathExplorer {
}
}
- private static void includeJar(File file, Map<URL, String> locations) {
+ private static void includeJar(File file, Map<String, URL> locations) {
try {
URL url = new URL("file:" + file.getCanonicalPath());
url = new URL("jar:" + url.toExternalForm() + "!/");
JarURLConnection conn = (JarURLConnection) url.openConnection();
JarFile jarFile = conn.getJarFile();
if (jarFile != null) {
- locations.put(url, "");
+ // the key does not matter here as long as it is unique
+ locations.put(url.toString(), url);
}
} catch (Exception e) {
// e.printStackTrace();
@@ -340,7 +343,7 @@ public class ClassPathExplorer {
}
private final static void searchForPaintables(URL location,
- String packageName,
+ String locationString,
Collection<Class<? extends Paintable>> paintables) {
// Get a File object for the package
@@ -355,6 +358,8 @@ public class ClassPathExplorer {
// remove the .class extension
String classname = files[i].substring(0,
files[i].length() - 6);
+ String packageName = locationString
+ .substring(locationString.lastIndexOf("/") + 1);
classname = packageName + "." + classname;
tryToAdd(classname, paintables);
}
@@ -447,11 +452,11 @@ public class ClassPathExplorer {
*/
public static URL getDefaultSourceDirectory() {
if (logger.isLoggable(Level.FINE)) {
- logger.fine("classpathLocations keys:");
- ArrayList<URL> locations = new ArrayList<URL>(classpathLocations
- .keySet());
- for (URL location : locations) {
- logger.fine(location.toString());
+ logger.fine("classpathLocations values:");
+ ArrayList<String> locations = new ArrayList<String>(
+ classpathLocations.keySet());
+ for (String location : locations) {
+ logger.fine(String.valueOf(classpathLocations.get(location)));
}
}
diff --git a/src/com/vaadin/ui/AbstractOrderedLayout.java b/src/com/vaadin/ui/AbstractOrderedLayout.java
index 123c5a8dc0..89b72b0f88 100644
--- a/src/com/vaadin/ui/AbstractOrderedLayout.java
+++ b/src/com/vaadin/ui/AbstractOrderedLayout.java
@@ -360,4 +360,28 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
removeListener(CLICK_EVENT, LayoutClickEvent.class, listener);
}
+ /**
+ * Returns the index of the given component.
+ *
+ * @param component
+ * The component to look up.
+ * @return The index of the component or -1 if the component is not a child.
+ */
+ public int getComponentIndex(Component component) {
+ return components.indexOf(component);
+ }
+
+ /**
+ * Returns the component at the given position.
+ *
+ * @param index
+ * The position of the component.
+ * @return The component at the given index.
+ * @throws IndexOutOfBoundsException
+ * If the index is out of range.
+ */
+ public Component getComponent(int index) throws IndexOutOfBoundsException {
+ return components.get(index);
+ }
+
}
diff --git a/src/com/vaadin/ui/AbstractSelect.java b/src/com/vaadin/ui/AbstractSelect.java
index 9667de32d6..a8451e7ef6 100644
--- a/src/com/vaadin/ui/AbstractSelect.java
+++ b/src/com/vaadin/ui/AbstractSelect.java
@@ -127,6 +127,21 @@ public abstract class AbstractSelect extends AbstractField implements
}
/**
+ * Multi select modes that controls how multi select behaves.
+ */
+ public enum MultiSelectMode {
+ /**
+ * The default behavior of the multi select mode
+ */
+ DEFAULT,
+
+ /**
+ * The previous more simple behavior of the multselect
+ */
+ SIMPLE
+ }
+
+ /**
* Is the select in multiselect mode?
*/
private boolean multiSelect = false;
diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java
index 251a415e83..cfc648d9b0 100644
--- a/src/com/vaadin/ui/Component.java
+++ b/src/com/vaadin/ui/Component.java
@@ -441,7 +441,8 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* <pre>
* RichTextArea area = new RichTextArea();
* area.setCaption(&quot;You can edit stuff here&quot;);
- * area.setValue(&quot;&lt;h1&gt;Helpful Heading&lt;/h1&gt;&quot; + &quot;&lt;p&gt;All this is for you to edit.&lt;/p&gt;&quot;);
+ * area.setValue(&quot;&lt;h1&gt;Helpful Heading&lt;/h1&gt;&quot;
+ * + &quot;&lt;p&gt;All this is for you to edit.&lt;/p&gt;&quot;);
* </pre>
*
* <p>
@@ -573,7 +574,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* Gets the application object to which the component is attached.
*
* <p>
- * The method will return {@code null} if the component has not yet been
+ * The method will return {@code null} if the component is not currently
* attached to an application. This is often a problem in constructors of
* regular components and in the initializers of custom composite
* components. A standard workaround is to move the problematic
@@ -716,7 +717,7 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* as an empty collection.
*/
public void childRequestedRepaint(
- Collection<RepaintRequestListener> alreadyNotified);
+ Collection<RepaintRequestListener> alreadyNotified);
/* Component event framework */
@@ -819,11 +820,13 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
*
* public void componentEvent(Event event) {
* // Act according to the source of the event
- * if (event.getSource() == ok &amp;&amp; event.getClass() == Button.ClickEvent.class)
+ * if (event.getSource() == ok
+ * &amp;&amp; event.getClass() == Button.ClickEvent.class)
* getWindow().showNotification(&quot;Click!&quot;);
*
* // Display source component and event class names
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName() + &quot;: &quot; + event.getClass().getName());
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
* }
* }
*
@@ -851,7 +854,8 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* getWindow().showNotification(&quot;Click!&quot;);
*
* // Display source component and event class names
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName() + &quot;: &quot; + event.getClass().getName());
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
* }
* </pre>
*
@@ -898,7 +902,8 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
* if (event.getSource() == ok)
* getWindow().showNotification(&quot;Click!&quot;);
*
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName() + &quot;: &quot; + event.getClass().getName());
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * + &quot;: &quot; + event.getClass().getName());
* }
* }
*
diff --git a/src/com/vaadin/ui/DateField.java b/src/com/vaadin/ui/DateField.java
index 45fd45716d..52f6d60a9d 100644
--- a/src/com/vaadin/ui/DateField.java
+++ b/src/com/vaadin/ui/DateField.java
@@ -12,6 +12,7 @@ import java.util.Locale;
import java.util.Map;
import com.vaadin.data.Property;
+import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.event.FieldEvents;
import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener;
@@ -19,6 +20,7 @@ import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.UserError;
import com.vaadin.terminal.gwt.client.ui.VDateField;
import com.vaadin.terminal.gwt.client.ui.VPopupCalendar;
@@ -119,6 +121,13 @@ public class DateField extends AbstractField implements
private boolean lenient = false;
+ private String dateString = null;
+
+ /**
+ * Was the last entered string parsable?
+ */
+ private boolean parsingSucceeded = true;
+
/**
* Determines if week numbers are shown in the date selector.
*/
@@ -130,6 +139,7 @@ public class DateField extends AbstractField implements
* Constructs an empty <code>DateField</code> with no caption.
*/
public DateField() {
+ setInvalidAllowed(false);
}
/**
@@ -140,6 +150,7 @@ public class DateField extends AbstractField implements
*/
public DateField(String caption) {
setCaption(caption);
+ setInvalidAllowed(false);
}
/**
@@ -164,6 +175,7 @@ public class DateField extends AbstractField implements
* the Property to be edited with this editor.
*/
public DateField(Property dataSource) throws IllegalArgumentException {
+ setInvalidAllowed(false);
if (!Date.class.isAssignableFrom(dataSource.getType())) {
throw new IllegalArgumentException("Can't use "
+ dataSource.getType().getName()
@@ -186,6 +198,7 @@ public class DateField extends AbstractField implements
* the Date value.
*/
public DateField(String caption, Date value) {
+ setInvalidAllowed(false);
setValue(value);
setCaption(caption);
}
@@ -216,6 +229,7 @@ public class DateField extends AbstractField implements
target.addAttribute("type", type);
target.addAttribute(VDateField.WEEK_NUMBERS, isShowISOWeekNumbers());
+ target.addAttribute("parsable", parsingSucceeded);
// Gets the calendar
final Calendar calendar = getCalendar();
@@ -264,6 +278,7 @@ public class DateField extends AbstractField implements
@Override
public void changeVariables(Object source, Map variables) {
super.changeVariables(source, variables);
+ setComponentError(null);
if (!isReadOnly()
&& (variables.containsKey("year")
@@ -281,7 +296,7 @@ public class DateField extends AbstractField implements
// this enables analyzing invalid input on the server
Object o = variables.get("dateString");
- String dateString = null;
+ dateString = null;
if (o != null) {
dateString = o.toString();
}
@@ -345,7 +360,10 @@ public class DateField extends AbstractField implements
if (newDate == null && dateString != null && !"".equals(dateString)) {
try {
- setValue(handleUnparsableDateString(dateString));
+ Date parsedDate = handleUnparsableDateString(dateString);
+ parsingSucceeded = true;
+ setValue(parsedDate, true);
+
/*
* Ensure the value is sent to the client if the value is
* set to the same as the previous (#4304). Does not repaint
@@ -354,13 +372,25 @@ public class DateField extends AbstractField implements
*/
requestRepaint();
} catch (ConversionException e) {
- // FIXME: Should not throw the exception but set an error
- // message for the field. And should retain the entered
- // value.
- throw e;
+ /*
+ * Sets the component error to the Conversion Exceptions
+ * message. This can be overriden in
+ * handleUnparsableDateString.
+ */
+ setComponentError(new UserError(e.getLocalizedMessage()));
+
+ /*
+ * The value of the DateField should be null if an invalid
+ * value has been given. Not using setValue() since we do
+ * not want to cause the client side value to change.
+ */
+ parsingSucceeded = false;
+ setInternalValue(null);
+ fireValueChange(true);
}
} else if (newDate != oldDate
&& (newDate == null || !newDate.equals(oldDate))) {
+ parsingSucceeded = true;
setValue(newDate, true); // Don't require a repaint, client
// updates itself
}
@@ -392,7 +422,7 @@ public class DateField extends AbstractField implements
*/
protected Date handleUnparsableDateString(String dateString)
throws Property.ConversionException {
- throw new Property.ConversionException();
+ throw new Property.ConversionException("Date format not recognized");
}
/* Property features */
@@ -430,13 +460,25 @@ public class DateField extends AbstractField implements
setValue(newValue, false);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object, boolean)
+ */
@Override
public void setValue(Object newValue, boolean repaintIsNotNeeded)
throws Property.ReadOnlyException, Property.ConversionException {
// Allows setting dates directly
if (newValue == null || newValue instanceof Date) {
- super.setValue(newValue, repaintIsNotNeeded);
+ try {
+ super.setValue(newValue, repaintIsNotNeeded);
+ parsingSucceeded = true;
+ } catch (InvalidValueException ive) {
+ // Thrown if validator fails
+ parsingSucceeded = false;
+ throw ive;
+ }
} else {
// Try to parse as string
@@ -444,8 +486,11 @@ public class DateField extends AbstractField implements
final SimpleDateFormat parser = new SimpleDateFormat();
final Date val = parser.parse(newValue.toString());
super.setValue(val, repaintIsNotNeeded);
+ parsingSucceeded = true;
} catch (final ParseException e) {
- throw new Property.ConversionException(e.getMessage());
+ parsingSucceeded = false;
+ throw new Property.ConversionException(
+ "Date format not recognized");
}
}
}
@@ -611,4 +656,31 @@ public class DateField extends AbstractField implements
requestRepaint();
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractField#isEmpty()
+ */
+ @Override
+ protected boolean isEmpty() {
+ /*
+ * Logically isEmpty() should return false also in the case that the
+ * entered value is invalid.
+ */
+ return dateString == null || dateString.equals("");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractField#isValid()
+ */
+ @Override
+ public boolean isValid() {
+ /*
+ * For the DateField to be valid it has to be parsable also
+ */
+ boolean parsable = isEmpty() || (!isEmpty() && getValue() != null);
+ return parsable && super.isValid();
+ }
}
diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java
index ad9a0c2f3f..8c9a0afc3d 100644
--- a/src/com/vaadin/ui/Table.java
+++ b/src/com/vaadin/ui/Table.java
@@ -5,6 +5,7 @@
package com.vaadin.ui;
import java.io.Serializable;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -93,15 +94,15 @@ public class Table extends AbstractSelect implements Action.Container,
MULTIROW
}
- private static final int CELL_KEY = 0;
+ protected static final int CELL_KEY = 0;
- private static final int CELL_HEADER = 1;
+ protected static final int CELL_HEADER = 1;
- private static final int CELL_ICON = 2;
+ protected static final int CELL_ICON = 2;
- private static final int CELL_ITEMID = 3;
+ protected static final int CELL_ITEMID = 3;
- private static final int CELL_FIRSTCOL = 4;
+ protected static final int CELL_FIRSTCOL = 4;
/**
* Left column alignment. <b>This is the default behaviour. </b>
@@ -225,6 +226,11 @@ public class Table extends AbstractSelect implements Action.Container,
private final HashMap<Object, String> columnHeaders = new HashMap<Object, String>();
/**
+ * Holds footers for visible columns (by propertyId).
+ */
+ private final HashMap<Object, String> columnFooters = new HashMap<Object, String>();
+
+ /**
* Holds icons for visible columns (by propertyId).
*/
private final HashMap<Object, Resource> columnIcons = new HashMap<Object, Resource>();
@@ -271,6 +277,11 @@ public class Table extends AbstractSelect implements Action.Container,
private int columnHeaderMode = COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID;
/**
+ * Should the Table footer be visible?
+ */
+ private boolean columnFootersVisible = false;
+
+ /**
* True iff the row captions are hidden.
*/
private boolean rowCaptionsAreHidden = true;
@@ -368,6 +379,12 @@ public class Table extends AbstractSelect implements Action.Container,
private DropHandler dropHandler;
+ private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
+ private HeaderClickHandler headerClickHandler;
+
+ private FooterClickHandler footerClickHandler;
+
/* Table constructors */
/**
@@ -872,7 +889,7 @@ public class Table extends AbstractSelect implements Action.Container,
final int index = getCurrentPageFirstItemIndex();
Object id = null;
if (index >= 0 && index < size()) {
- id = ((Container.Indexed) items).getIdByIndex(index);
+ id = getIdByIndex(index);
}
if (id != null && !id.equals(currentPageFirstItemId)) {
currentPageFirstItemId = id;
@@ -881,12 +898,16 @@ public class Table extends AbstractSelect implements Action.Container,
// If there is no item id at all, use the first one
if (currentPageFirstItemId == null) {
- currentPageFirstItemId = ((Container.Ordered) items).firstItemId();
+ currentPageFirstItemId = firstItemId();
}
return currentPageFirstItemId;
}
+ protected Object getIdByIndex(int index) {
+ return ((Container.Indexed) items).getIdByIndex(index);
+ }
+
/**
* Setter for property currentPageFirstItemId.
*
@@ -898,15 +919,14 @@ public class Table extends AbstractSelect implements Action.Container,
// Gets the corresponding index
int index = -1;
if (items instanceof Container.Indexed) {
- index = ((Container.Indexed) items)
- .indexOfId(currentPageFirstItemId);
+ index = indexOfId(currentPageFirstItemId);
} else {
// If the table item container does not have index, we have to
// calculates the index by hand
- Object id = ((Container.Ordered) items).firstItemId();
+ Object id = firstItemId();
while (id != null && !id.equals(currentPageFirstItemId)) {
index++;
- id = ((Container.Ordered) items).nextItemId(id);
+ id = nextItemId(id);
}
if (id == null) {
index = -1;
@@ -940,6 +960,10 @@ public class Table extends AbstractSelect implements Action.Container,
}
+ protected int indexOfId(Object itemId) {
+ return ((Container.Indexed) items).indexOfId(itemId);
+ }
+
/**
* Gets the icon Resource for the specified column.
*
@@ -1224,8 +1248,7 @@ public class Table extends AbstractSelect implements Action.Container,
// Refresh first item id
if (items instanceof Container.Indexed) {
try {
- currentPageFirstItemId = ((Container.Indexed) items)
- .getIdByIndex(newIndex);
+ currentPageFirstItemId = getIdByIndex(newIndex);
} catch (final IndexOutOfBoundsException e) {
currentPageFirstItemId = null;
}
@@ -1236,48 +1259,42 @@ public class Table extends AbstractSelect implements Action.Container,
// container forwards / backwards
// next available item forward or backward
- currentPageFirstItemId = ((Container.Ordered) items).firstItemId();
+ currentPageFirstItemId = firstItemId();
// Go forwards in the middle of the list (respect borders)
while (currentPageFirstItemIndex < newIndex
- && !((Container.Ordered) items)
- .isLastId(currentPageFirstItemId)) {
+ && !isLastId(currentPageFirstItemId)) {
currentPageFirstItemIndex++;
- currentPageFirstItemId = ((Container.Ordered) items)
- .nextItemId(currentPageFirstItemId);
+ currentPageFirstItemId = nextItemId(currentPageFirstItemId);
}
// If we did hit the border
- if (((Container.Ordered) items).isLastId(currentPageFirstItemId)) {
+ if (isLastId(currentPageFirstItemId)) {
currentPageFirstItemIndex = size - 1;
}
// Go backwards in the middle of the list (respect borders)
while (currentPageFirstItemIndex > newIndex
- && !((Container.Ordered) items)
- .isFirstId(currentPageFirstItemId)) {
+ && !isFirstId(currentPageFirstItemId)) {
currentPageFirstItemIndex--;
- currentPageFirstItemId = ((Container.Ordered) items)
- .prevItemId(currentPageFirstItemId);
+ currentPageFirstItemId = prevItemId(currentPageFirstItemId);
}
// If we did hit the border
- if (((Container.Ordered) items).isFirstId(currentPageFirstItemId)) {
+ if (isFirstId(currentPageFirstItemId)) {
currentPageFirstItemIndex = 0;
}
// Go forwards once more
while (currentPageFirstItemIndex < newIndex
- && !((Container.Ordered) items)
- .isLastId(currentPageFirstItemId)) {
+ && !isLastId(currentPageFirstItemId)) {
currentPageFirstItemIndex++;
- currentPageFirstItemId = ((Container.Ordered) items)
- .nextItemId(currentPageFirstItemId);
+ currentPageFirstItemId = nextItemId(currentPageFirstItemId);
}
// If for some reason we do hit border again, override
// the user index request
- if (((Container.Ordered) items).isLastId(currentPageFirstItemId)) {
+ if (isLastId(currentPageFirstItemId)) {
newIndex = currentPageFirstItemIndex = size - 1;
}
}
@@ -1381,7 +1398,7 @@ public class Table extends AbstractSelect implements Action.Container,
/**
* Refreshes rendered rows
*/
- private void refreshRenderedCells() {
+ protected void refreshRenderedCells() {
if (getParent() == null) {
return;
}
@@ -1442,11 +1459,11 @@ public class Table extends AbstractSelect implements Action.Container,
// Gets the first item id
if (items instanceof Container.Indexed) {
- id = ((Container.Indexed) items).getIdByIndex(firstIndex);
+ id = getIdByIndex(firstIndex);
} else {
- id = ((Container.Ordered) items).firstItemId();
+ id = firstItemId();
for (int i = 0; i < firstIndex; i++) {
- id = ((Container.Ordered) items).nextItemId(id);
+ id = nextItemId(id);
}
}
@@ -1554,15 +1571,14 @@ public class Table extends AbstractSelect implements Action.Container,
// Gets the next item id
if (items instanceof Container.Indexed) {
- Container.Indexed indexed = (Container.Indexed) items;
int index = firstIndex + i + 1;
- if (index < indexed.size()) {
- id = indexed.getIdByIndex(index);
+ if (index < totalRows) {
+ id = getIdByIndex(index);
} else {
id = null;
}
} else {
- id = ((Container.Ordered) items).nextItemId(id);
+ id = nextItemId(id);
}
filledRows++;
@@ -1830,6 +1846,79 @@ public class Table extends AbstractSelect implements Action.Container,
resetPageBuffer();
enableContentRefreshing(true);
+ }
+
+ /**
+ * Gets items ids from a range of key values
+ *
+ * @param startRowKey
+ * The start key
+ * @param endRowKey
+ * The end key
+ * @return
+ */
+ private Set<Object> getItemIdsInRange(int startRowKey, int endRowKey) {
+ HashSet<Object> ids = new HashSet<Object>();
+
+ Object startItemId = itemIdMapper.get(String.valueOf(startRowKey));
+ ids.add(startItemId);
+
+ Object endItemId = itemIdMapper.get(String.valueOf(endRowKey));
+ ids.add(endItemId);
+
+ Object currentItemId = startItemId;
+
+ Container.Ordered ordered = (Container.Ordered) items;
+ while (currentItemId != endItemId) {
+ currentItemId = ordered.nextItemId(currentItemId);
+ if (currentItemId != null) {
+ ids.add(currentItemId);
+ }
+ }
+
+ return ids;
+ }
+
+ /**
+ * Handles selection if selection is a multiselection
+ *
+ * @param variables
+ * The variables
+ */
+ private void handleSelectedItems(Map<String, Object> variables) {
+ final String[] ka = (String[]) variables.get("selected");
+ final String[] ranges = (String[]) variables.get("selectedRanges");
+
+ // Converts the key-array to id-set
+ final LinkedList s = new LinkedList();
+ for (int i = 0; i < ka.length; i++) {
+ final Object id = itemIdMapper.get(ka[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ requestRepaint();
+ } else if (id != null && containsId(id)) {
+ s.add(id);
+ }
+ }
+
+ if (!isNullSelectionAllowed() && s.size() < 1) {
+ // empty selection not allowed, keep old value
+ requestRepaint();
+ return;
+ }
+
+ // Add range items
+ if (ranges != null) {
+ for (String range : ranges) {
+ String[] limits = range.split("-");
+ int start = Integer.valueOf(limits[0]);
+ int end = Integer.valueOf(limits[1]);
+ s.addAll(getItemIdsInRange(start, end));
+ }
+ }
+
+ setValue(s, true);
}
@@ -1848,6 +1937,8 @@ public class Table extends AbstractSelect implements Action.Container,
handleClickEvent(variables);
+ handleColumnResizeEvent(variables);
+
disableContentRefreshing();
if (!isSelectable() && variables.containsKey("selected")) {
@@ -1857,6 +1948,18 @@ public class Table extends AbstractSelect implements Action.Container,
variables.remove("selected");
}
+ /*
+ * The AbstractSelect cannot handle the multiselection properly, instead
+ * we handle it ourself
+ */
+ else if (isSelectable() && isMultiSelect()
+ && variables.containsKey("selected")
+ && multiSelectMode == MultiSelectMode.DEFAULT) {
+ handleSelectedItems(variables);
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
super.changeVariables(source, variables);
// Client might update the pagelength if Table height is fixed
@@ -2000,6 +2103,8 @@ public class Table extends AbstractSelect implements Action.Container,
* @param variables
*/
private void handleClickEvent(Map<String, Object> variables) {
+
+ // Item click event
if (variables.containsKey("clickEvent")) {
String key = (String) variables.get("clickedKey");
Object itemId = itemIdMapper.get(key);
@@ -2017,6 +2122,70 @@ public class Table extends AbstractSelect implements Action.Container,
evt));
}
}
+
+ // Header click event
+ else if (variables.containsKey("headerClickEvent")) {
+
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("headerClickEvent"));
+
+ Object cid = variables.get("headerClickCID");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+ }
+ fireEvent(new HeaderClickEvent(this, propertyId, details));
+ }
+
+ // Footer click event
+ else if (variables.containsKey("footerClickEvent")) {
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("footerClickEvent"));
+
+ Object cid = variables.get("footerClickCID");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+ }
+ fireEvent(new FooterClickEvent(this, propertyId, details));
+ }
+ }
+
+ /**
+ * Handles the column resize event sent by the client.
+ *
+ * @param variables
+ */
+ private void handleColumnResizeEvent(Map<String, Object> variables) {
+ if (variables.containsKey("columnResizeEventColumn")) {
+ Object cid = variables.get("columnResizeEventColumn");
+ Object propertyId = null;
+ if (cid != null) {
+ propertyId = columnIdMap.get(cid.toString());
+ }
+
+ Object prev = variables.get("columnResizeEventPrev");
+ int previousWidth = -1;
+ if (prev != null) {
+ previousWidth = Integer.valueOf(prev.toString());
+ }
+
+ Object curr = variables.get("columnResizeEventCurr");
+ int currentWidth = -1;
+ if (curr != null) {
+ currentWidth = Integer.valueOf(curr.toString());
+ }
+
+ /*
+ * Update the sizes on the server side. If a column previously had a
+ * expand ratio and the user resized the column then the expand
+ * ratio will be turned into a static pixel size.
+ */
+ setColumnWidth(propertyId, currentWidth);
+
+ fireEvent(new ColumnResizeEvent(this, propertyId, previousWidth,
+ currentWidth));
+ }
}
/**
@@ -2065,6 +2234,10 @@ public class Table extends AbstractSelect implements Action.Container,
target.addAttribute("dragmode", dragMode.ordinal());
}
+ if (multiSelectMode != MultiSelectMode.DEFAULT) {
+ target.addAttribute("multiselectmode", multiSelectMode.ordinal());
+ }
+
// Initialize temps
final Object[] colids = getVisibleColumns();
final int cols = colids.length;
@@ -2073,7 +2246,6 @@ public class Table extends AbstractSelect implements Action.Container,
final int pagelen = getPageLength();
final int colHeadMode = getColumnHeaderMode();
final boolean colheads = colHeadMode != COLUMN_HEADER_MODE_HIDDEN;
- final boolean rowheads = getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN;
final Object[][] cells = getVisibleCells();
final boolean iseditable = isEditable();
int rows;
@@ -2099,7 +2271,6 @@ public class Table extends AbstractSelect implements Action.Container,
// selection support
LinkedList<String> selectedKeys = new LinkedList<String>();
if (isMultiSelect()) {
- // only paint selections that are currently visible in the client
HashSet sel = new HashSet((Set) getValue());
Collection vids = getVisibleItemIds();
for (Iterator it = vids.iterator(); it.hasNext();) {
@@ -2133,6 +2304,10 @@ public class Table extends AbstractSelect implements Action.Container,
target.addAttribute("cols", cols);
target.addAttribute("rows", rows);
+ if (!isNullSelectionAllowed()) {
+ target.addAttribute("nsa", false);
+ }
+
target.addAttribute("firstrow",
(reqFirstRowToPaint >= 0 ? reqFirstRowToPaint
: firstToBeRenderedInClient));
@@ -2143,9 +2318,12 @@ public class Table extends AbstractSelect implements Action.Container,
if (colheads) {
target.addAttribute("colheaders", true);
}
- if (rowheads) {
+ if (getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN) {
target.addAttribute("rowheaders", true);
}
+ if (columnFootersVisible) {
+ target.addAttribute("colfooters", true);
+ }
// Visible column order
final Collection sortables = getSortableContainerPropertyIds();
@@ -2195,8 +2373,8 @@ public class Table extends AbstractSelect implements Action.Container,
start = 0;
}
- for (int i = start; i < end; i++) {
- final Object itemId = cells[CELL_ITEMID][i];
+ for (int indexInRowbuffer = start; indexInRowbuffer < end; indexInRowbuffer++) {
+ final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
if (!isNullSelectionAllowed() && getNullSelectionItemId() != null
&& itemId == getNullSelectionItemId()) {
@@ -2204,93 +2382,8 @@ public class Table extends AbstractSelect implements Action.Container,
continue;
}
- target.startTag("tr");
-
- // tr attributes
- if (rowheads) {
- if (cells[CELL_ICON][i] != null) {
- target.addAttribute("icon", (Resource) cells[CELL_ICON][i]);
- }
- if (cells[CELL_HEADER][i] != null) {
- target.addAttribute("caption",
- (String) cells[CELL_HEADER][i]);
- }
- }
- target.addAttribute("key", Integer.parseInt(cells[CELL_KEY][i]
- .toString()));
-
- if (isSelected(itemId)) {
- target.addAttribute("selected", true);
- }
-
- // Actions
- if (actionHandlers != null) {
- final ArrayList<String> keys = new ArrayList<String>();
- for (final Iterator<Handler> ahi = actionHandlers.iterator(); ahi
- .hasNext();) {
- final Action[] aa = (ahi.next()).getActions(itemId, this);
- if (aa != null) {
- for (int ai = 0; ai < aa.length; ai++) {
- final String key = actionMapper.key(aa[ai]);
- actionSet.add(aa[ai]);
- keys.add(key);
- }
- }
- }
- target.addAttribute("al", keys.toArray());
- }
-
- /*
- * For each row, if a cellStyleGenerator is specified, get the
- * specific style for the cell, using null as propertyId. If there
- * is any, add it to the target.
- */
- if (cellStyleGenerator != null) {
- String rowStyle = cellStyleGenerator.getStyle(itemId, null);
- if (rowStyle != null && !rowStyle.equals("")) {
- target.addAttribute("rowstyle", rowStyle);
- }
- }
-
- // cells
- int currentColumn = 0;
- for (final Iterator<Object> it = visibleColumns.iterator(); it
- .hasNext(); currentColumn++) {
- final Object columnId = it.next();
- if (columnId == null || isColumnCollapsed(columnId)) {
- continue;
- }
- /*
- * For each cell, if a cellStyleGenerator is specified, get the
- * specific style for the cell. If there is any, add it to the
- * target.
- */
- if (cellStyleGenerator != null) {
- String cellStyle = cellStyleGenerator.getStyle(itemId,
- columnId);
- if (cellStyle != null && !cellStyle.equals("")) {
- target.addAttribute("style-"
- + columnIdMap.key(columnId), cellStyle);
- }
- }
- if ((iscomponent[currentColumn] || iseditable)
- && Component.class.isInstance(cells[CELL_FIRSTCOL
- + currentColumn][i])) {
- final Component c = (Component) cells[CELL_FIRSTCOL
- + currentColumn][i];
- if (c == null) {
- target.addText("");
- } else {
- c.paint(target);
- }
- } else {
- target
- .addText((String) cells[CELL_FIRSTCOL
- + currentColumn][i]);
- }
- }
-
- target.endTag("tr");
+ paintRow(target, cells, iseditable, actionSet, iscomponent,
+ indexInRowbuffer, itemId);
}
target.endTag("rows");
@@ -2380,6 +2473,8 @@ public class Table extends AbstractSelect implements Action.Container,
target.addAttribute("cid", columnIdMap.key(columnId));
final String head = getColumnHeader(columnId);
target.addAttribute("caption", (head != null ? head : ""));
+ final String foot = getColumnFooter(columnId);
+ target.addAttribute("fcaption", (foot != null ? foot : ""));
if (isColumnCollapsed(columnId)) {
target.addAttribute("collapsed", true);
}
@@ -2413,6 +2508,131 @@ public class Table extends AbstractSelect implements Action.Container,
}
}
+ private void paintRow(PaintTarget target, final Object[][] cells,
+ final boolean iseditable, final Set<Action> actionSet,
+ final boolean[] iscomponent, int indexInRowbuffer,
+ final Object itemId) throws PaintException {
+ target.startTag("tr");
+
+ paintRowAttributes(target, cells, actionSet, indexInRowbuffer, itemId);
+
+ // cells
+ int currentColumn = 0;
+ for (final Iterator<Object> it = visibleColumns.iterator(); it
+ .hasNext(); currentColumn++) {
+ final Object columnId = it.next();
+ if (columnId == null || isColumnCollapsed(columnId)) {
+ continue;
+ }
+ /*
+ * For each cell, if a cellStyleGenerator is specified, get the
+ * specific style for the cell. If there is any, add it to the
+ * target.
+ */
+ if (cellStyleGenerator != null) {
+ String cellStyle = cellStyleGenerator
+ .getStyle(itemId, columnId);
+ if (cellStyle != null && !cellStyle.equals("")) {
+ target.addAttribute("style-" + columnIdMap.key(columnId),
+ cellStyle);
+ }
+ }
+ if ((iscomponent[currentColumn] || iseditable)
+ && Component.class.isInstance(cells[CELL_FIRSTCOL
+ + currentColumn][indexInRowbuffer])) {
+ final Component c = (Component) cells[CELL_FIRSTCOL
+ + currentColumn][indexInRowbuffer];
+ if (c == null) {
+ target.addText("");
+ } else {
+ c.paint(target);
+ }
+ } else {
+ target
+ .addText((String) cells[CELL_FIRSTCOL + currentColumn][indexInRowbuffer]);
+ }
+ }
+
+ target.endTag("tr");
+ }
+
+ private void paintRowAttributes(PaintTarget target, final Object[][] cells,
+ final Set<Action> actionSet, int indexInRowbuffer,
+ final Object itemId) throws PaintException {
+ // tr attributes
+
+ paintRowIcon(target, cells, indexInRowbuffer);
+ paintRowHeader(target, cells, indexInRowbuffer);
+ target.addAttribute("key", Integer
+ .parseInt(cells[CELL_KEY][indexInRowbuffer].toString()));
+
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ }
+
+ // Actions
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ for (final Iterator<Handler> ahi = actionHandlers.iterator(); ahi
+ .hasNext();) {
+ final Action[] aa = (ahi.next()).getActions(itemId, this);
+ if (aa != null) {
+ for (int ai = 0; ai < aa.length; ai++) {
+ final String key = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ keys.add(key);
+ }
+ }
+ }
+ target.addAttribute("al", keys.toArray());
+ }
+
+ /*
+ * For each row, if a cellStyleGenerator is specified, get the specific
+ * style for the cell, using null as propertyId. If there is any, add it
+ * to the target.
+ */
+ if (cellStyleGenerator != null) {
+ String rowStyle = cellStyleGenerator.getStyle(itemId, null);
+ if (rowStyle != null && !rowStyle.equals("")) {
+ target.addAttribute("rowstyle", rowStyle);
+ }
+ }
+ paintRowAttributes(target, itemId);
+ }
+
+ protected void paintRowHeader(PaintTarget target, Object[][] cells,
+ int indexInRowbuffer) throws PaintException {
+ if (getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN) {
+ if (cells[CELL_HEADER][indexInRowbuffer] != null) {
+ target.addAttribute("caption",
+ (String) cells[CELL_HEADER][indexInRowbuffer]);
+ }
+ }
+
+ }
+
+ protected void paintRowIcon(PaintTarget target, final Object[][] cells,
+ int indexInRowbuffer) throws PaintException {
+ if (getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN
+ && cells[CELL_ICON][indexInRowbuffer] != null) {
+ target.addAttribute("icon",
+ (Resource) cells[CELL_ICON][indexInRowbuffer]);
+ }
+ }
+
+ /**
+ * A method where extended Table implementations may add their custom
+ * attributes for rows.
+ *
+ * @param target
+ * @param itemId
+ */
+ protected void paintRowAttributes(PaintTarget target, Object itemId)
+ throws PaintException {
+
+ }
+
/**
* Gets the cached visible table contents.
*
@@ -2551,7 +2771,7 @@ public class Table extends AbstractSelect implements Action.Container,
requestRepaint();
}
- private void resetPageBuffer() {
+ protected void resetPageBuffer() {
firstToBeRenderedInClient = -1;
lastToBeRenderedInClient = -1;
reqFirstRowToPaint = -1;
@@ -2614,8 +2834,7 @@ public class Table extends AbstractSelect implements Action.Container,
*/
@Override
public boolean removeItem(Object itemId) {
- final Object nextItemId = ((Container.Ordered) items)
- .nextItemId(itemId);
+ final Object nextItemId = nextItemId(itemId);
final boolean ret = super.removeItem(itemId);
if (ret && (itemId != null) && (itemId.equals(currentPageFirstItemId))) {
currentPageFirstItemId = nextItemId;
@@ -2641,6 +2860,7 @@ public class Table extends AbstractSelect implements Action.Container,
columnAlignments.remove(propertyId);
columnIcons.remove(propertyId);
columnHeaders.remove(propertyId);
+ columnFooters.remove(propertyId);
return super.removeContainerProperty(propertyId);
}
@@ -2860,18 +3080,6 @@ public class Table extends AbstractSelect implements Action.Container,
}
/**
- * Focusing to this component is not supported.
- *
- * @throws UnsupportedOperationException
- * if invoked.
- * @see com.vaadin.ui.AbstractField#focus()
- */
- @Override
- public void focus() throws UnsupportedOperationException {
- throw new UnsupportedOperationException();
- }
-
- /**
* Gets the ID of the Item following the Item that corresponds to itemId.
*
* @see com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object)
@@ -3433,6 +3641,27 @@ public class Table extends AbstractSelect implements Action.Container,
}
/**
+ * Sets the behavior of how the multi-select mode should behave when the
+ * table is both selectable and in multi-select mode.
+ *
+ * @param mode
+ * The select mode of the table
+ */
+ public void setMultiSelectMode(MultiSelectMode mode) {
+ multiSelectMode = mode;
+ requestRepaint();
+ }
+
+ /**
+ * Returns the select mode in which multi-select is used.
+ *
+ * @return The multi select mode
+ */
+ public MultiSelectMode getMultiSelectMode() {
+ return multiSelectMode;
+ }
+
+ /**
* Lazy loading accept criterion for Table. Accepted target rows are loaded
* from server once per drag and drop operation. Developer must override one
* method that decides on which rows the currently dragged data can be
@@ -3525,4 +3754,418 @@ public class Table extends AbstractSelect implements Action.Container,
}
+ /**
+ * Click event fired when clicking on the Table headers. The event includes
+ * a reference the the Table the event originated from, the property id of
+ * the column which header was pressed and details about the mouse event
+ * itself.
+ */
+ public static class HeaderClickEvent extends Component.Event {
+ public static final Method HEADER_CLICK_METHOD;
+
+ static {
+ try {
+ // Set the header click method
+ HEADER_CLICK_METHOD = HeaderClickHandler.class
+ .getDeclaredMethod("handleHeaderClick",
+ new Class[] { HeaderClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ // The property id of the column which header was pressed
+ private Object columnPropertyId;
+
+ // The mouse details
+ private MouseEventDetails details;
+
+ public HeaderClickEvent(Component source, Object propertyId,
+ MouseEventDetails details) {
+ super(source);
+ this.details = details;
+ columnPropertyId = propertyId;
+ }
+
+ /**
+ * Gets the property id of the column which header was pressed
+ *
+ * @return The column propety id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+
+ /**
+ * Returns the details of the mouse event like the mouse coordinates,
+ * button pressed etc.
+ *
+ * @return The mouse details
+ */
+ public MouseEventDetails getEventDetails() {
+ return details;
+ }
+ }
+
+ /**
+ * Click event fired when clicking on the Table footers. The event includes
+ * a reference the the Table the event originated from, the property id of
+ * the column which header was pressed and details about the mouse event
+ * itself.
+ */
+ public static class FooterClickEvent extends Component.Event {
+ public static final Method FOOTER_CLICK_METHOD;
+
+ static {
+ try {
+ // Set the header click method
+ FOOTER_CLICK_METHOD = FooterClickHandler.class
+ .getDeclaredMethod("handleFooterClick",
+ new Class[] { FooterClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ // The property id of the column which header was pressed
+ private Object columnPropertyId;
+
+ // The mouse details
+ private MouseEventDetails details;
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * The source of the component
+ * @param propertyId
+ * The propertyId of the column
+ * @param details
+ * The mouse details of the click
+ */
+ public FooterClickEvent(Component source, Object propertyId,
+ MouseEventDetails details) {
+ super(source);
+ columnPropertyId = propertyId;
+ this.details = details;
+ }
+
+ /**
+ * Gets the property id of the column which header was pressed
+ *
+ * @return The column propety id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+
+ /**
+ * Returns the details of the mouse event like the mouse coordinates,
+ * button pressed etc.
+ *
+ * @return The mouse details
+ */
+ public MouseEventDetails getEventDetails() {
+ return details;
+ }
+ }
+
+ /**
+ * Interface for the handler listening to column header mouse click events.
+ * The handleHeaderClick method is called when the user presses a header
+ * column cell.
+ */
+ public interface HeaderClickHandler {
+
+ /**
+ * Called when a user clicks a header column cell
+ *
+ * @param event
+ * The event which contains information about the column and
+ * the mouse click event
+ */
+ public void handleHeaderClick(HeaderClickEvent event);
+ }
+
+ /**
+ * Interface for the handler listening to column footer mouse click events.
+ * The handleHeaderClick method is called when the user presses a footer
+ * column cell.
+ */
+ public interface FooterClickHandler {
+
+ /**
+ * Called when a user clicks a footer column cell
+ *
+ * @param event
+ * The event which contains information about the column and
+ * the mouse click event
+ */
+ public void handleFooterClick(FooterClickEvent event);
+ }
+
+ /**
+ * Sets the header click handler which handles the click events when the
+ * user clicks on a column header cell in the Table.
+ * <p>
+ * The handler will receive events which contains information about which
+ * column was clicked and some details about the mouse event.
+ * </p>
+ *
+ * @param handler
+ * The handler which should handle the header click events.
+ */
+ public void setHeaderClickHandler(HeaderClickHandler handler) {
+ if (headerClickHandler != handler) {
+ if (handler == null && headerClickHandler != null) {
+ // Remove header click handler
+ removeListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler);
+
+ headerClickHandler = handler;
+ } else if (headerClickHandler != null) {
+ // Replace header click handler
+ removeListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler);
+
+ headerClickHandler = handler;
+
+ addListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler,
+ HeaderClickEvent.HEADER_CLICK_METHOD);
+ } else if (handler != null) {
+ // Set a new header click handler
+ headerClickHandler = handler;
+ addListener(VScrollTable.HEADER_CLICK_EVENT_ID,
+ HeaderClickEvent.class, headerClickHandler,
+ HeaderClickEvent.HEADER_CLICK_METHOD);
+ }
+ }
+ }
+
+ /**
+ * Sets the footer click handler which handles the click events when the
+ * user clicks on a column footer cell in the Table.
+ * <p>
+ * The handler will recieve events which contains information about which
+ * column was clicked and some details about the mouse event.
+ * </p>
+ *
+ * @param handler
+ * The handler which should handle the footer click events
+ */
+ public void setFooterClickHandler(FooterClickHandler handler) {
+ if (footerClickHandler != handler) {
+ if (handler == null && footerClickHandler != null) {
+ // Remove header click handler
+ removeListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler);
+ footerClickHandler = handler;
+ } else if (footerClickHandler != null) {
+ // Replace footer click handler
+ removeListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler);
+ footerClickHandler = handler;
+ addListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler,
+ FooterClickEvent.FOOTER_CLICK_METHOD);
+ } else if (handler != null) {
+ // Set a new footer click handler
+ footerClickHandler = handler;
+ addListener(VScrollTable.FOOTER_CLICK_EVENT_ID,
+ FooterClickEvent.class, footerClickHandler,
+ FooterClickEvent.FOOTER_CLICK_METHOD);
+ }
+ }
+ }
+
+ /**
+ * Returns the header click handler which receives click events from the
+ * columns header cells when they are clicked on.
+ *
+ * @return
+ */
+ public HeaderClickHandler getHeaderClickHandler() {
+ return headerClickHandler;
+ }
+
+ /**
+ * Returns the footer click handler which recieves click events from the
+ * columns footer cells when they are clicked on.
+ *
+ * @return
+ */
+ public FooterClickHandler getFooterClickHandler() {
+ return footerClickHandler;
+ }
+
+ /**
+ * Gets the footer caption beneath the rows
+ *
+ * @param propertyId
+ * The propertyId of the column *
+ * @return The caption of the footer or NULL if not set
+ */
+ public String getColumnFooter(Object propertyId) {
+ return columnFooters.get(propertyId);
+ }
+
+ /**
+ * Sets the column footer caption. The column footer caption is the text
+ * displayed beneath the column if footers have been set visible.
+ *
+ * @param propertyId
+ * The properyId of the column
+ *
+ * @param footer
+ * The caption of the footer
+ */
+ public void setColumnFooter(Object propertyId, String footer) {
+ if (footer == null) {
+ columnFooters.remove(propertyId);
+ return;
+ }
+ columnFooters.put(propertyId, footer);
+
+ requestRepaint();
+ }
+
+ /**
+ * Sets the footer visible in the bottom of the table.
+ * <p>
+ * The footer can be used to add column related data like sums to the bottom
+ * of the Table using setColumnFooter(Object propertyId, String footer).
+ * </p>
+ *
+ * @param visible
+ * Should the footer be visible
+ */
+ public void setFooterVisible(boolean visible) {
+ columnFootersVisible = visible;
+
+ // Assures the visual refresh
+ refreshRenderedCells();
+ }
+
+ /**
+ * Is the footer currently visible?
+ *
+ * @return Returns true if visible else false
+ */
+ public boolean isFooterVisible() {
+ return columnFootersVisible;
+ }
+
+ /**
+ * This event is fired when a column is resized. The event contains the
+ * columns property id which was fired, the previous width of the column and
+ * the width of the column after the resize.
+ */
+ public static class ColumnResizeEvent extends Component.Event{
+ public static final Method COLUMN_RESIZE_METHOD;
+
+ static {
+ try {
+ COLUMN_RESIZE_METHOD = ColumnResizeListener.class
+ .getDeclaredMethod("columnResize",
+ new Class[] { ColumnResizeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ private final int previousWidth;
+ private final int currentWidth;
+ private final Object columnPropertyId;
+
+ /**
+ * Constructor
+ *
+ * @param source
+ * The source of the event
+ * @param propertyId
+ * The columns property id
+ * @param previous
+ * The width in pixels of the column before the resize event
+ * @param current
+ * The width in pixels of the column after the resize event
+ */
+ public ColumnResizeEvent(Component source, Object propertyId,
+ int previous, int current) {
+ super(source);
+ previousWidth = previous;
+ currentWidth = current;
+ columnPropertyId = propertyId;
+ }
+
+ /**
+ * Get the column property id of the column that was resized.
+ *
+ * @return The column property id
+ */
+ public Object getPropertyId() {
+ return columnPropertyId;
+ }
+
+ /**
+ * Get the width in pixels of the column before the resize event
+ *
+ * @return Width in pixels
+ */
+ public int getPreviousWidth() {
+ return previousWidth;
+ }
+
+ /**
+ * Get the width in pixels of the column after the resize event
+ *
+ * @return Width in pixels
+ */
+ public int getCurrentWidth() {
+ return currentWidth;
+ }
+ }
+
+ /**
+ * Interface for listening to column resize events.
+ */
+ public interface ColumnResizeListener{
+
+ /**
+ * This method is triggered when the column has been resized
+ *
+ * @param event
+ * The event which contains the column property id, the
+ * previous width of the column and the current width of the
+ * column
+ */
+ public void columnResize(ColumnResizeEvent event);
+ }
+
+ /**
+ * Adds a column resize listener to the Table. A column resize listener is
+ * called when a user resizes a columns width.
+ *
+ * @param listener
+ * The listener to attach to the Table
+ */
+ public void addListener(ColumnResizeListener listener) {
+ addListener(VScrollTable.COLUMN_RESIZE_EVENT_ID,
+ ColumnResizeEvent.class, listener,
+ ColumnResizeEvent.COLUMN_RESIZE_METHOD);
+ }
+
+ /**
+ * Removes a column resize listener from the Table.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ public void removeListener(ColumnResizeListener listener) {
+ removeListener(VScrollTable.COLUMN_RESIZE_EVENT_ID,
+ ColumnResizeEvent.class, listener);
+ }
}
diff --git a/src/com/vaadin/ui/Tree.java b/src/com/vaadin/ui/Tree.java
index 2b38d5a6eb..11b049d803 100644
--- a/src/com/vaadin/ui/Tree.java
+++ b/src/com/vaadin/ui/Tree.java
@@ -119,7 +119,9 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
}
private TreeDragMode dragMode = TreeDragMode.NONE;
-
+
+ private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
/* Tree constructors */
/**
@@ -338,6 +340,29 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
requestRepaint();
}
}
+
+ /**
+ * Sets the behavior of the multiselect mode
+ *
+ * @param mode
+ * The mode to set
+ */
+ public void setMultiselectMode(MultiSelectMode mode) {
+ if (multiSelectMode != mode && mode != null) {
+ multiSelectMode = mode;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * Returns the mode the multiselect is in. The mode controls how
+ * multiselection can be done.
+ *
+ * @return The mode
+ */
+ public MultiSelectMode getMultiselectMode() {
+ return multiSelectMode;
+ }
/* Component API */
@@ -396,6 +421,15 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
}
}
+ // AbstractSelect cannot handle multiselection so we handle
+ // it ourself
+ if (variables.containsKey("selected") && isMultiSelect()
+ && multiSelectMode == MultiSelectMode.DEFAULT) {
+ handleSelectedItems(variables);
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
// Selections are handled by the select component
super.changeVariables(source, variables);
@@ -419,6 +453,37 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
}
/**
+ * Handles the selection
+ *
+ * @param variables
+ * The variables sent to the server from the client
+ */
+ private void handleSelectedItems(Map<String, Object> variables) {
+ final String[] ka = (String[]) variables.get("selected");
+
+ // Converts the key-array to id-set
+ final LinkedList s = new LinkedList();
+ for (int i = 0; i < ka.length; i++) {
+ final Object id = itemIdMapper.get(ka[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ requestRepaint();
+ } else if (id != null && containsId(id)) {
+ s.add(id);
+ }
+ }
+
+ if (!isNullSelectionAllowed() && s.size() < 1) {
+ // empty selection not allowed, keep old value
+ requestRepaint();
+ return;
+ }
+
+ setValue(s, true);
+ }
+
+ /**
* Paints any needed component-specific things to the given UIDL stream.
*
* @see com.vaadin.ui.AbstractComponent#paintContent(PaintTarget)
@@ -442,6 +507,10 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
if (isSelectable()) {
target.addAttribute("selectmode", (isMultiSelect() ? "multi"
: "single"));
+ if (isMultiSelect()) {
+ target.addAttribute("multiselectmode", multiSelectMode
+ .ordinal());
+ }
} else {
target.addAttribute("selectmode", "none");
}
diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java
index ea3d583c4e..266cafa484 100644
--- a/src/com/vaadin/ui/Window.java
+++ b/src/com/vaadin/ui/Window.java
@@ -32,7 +32,44 @@ import com.vaadin.terminal.URIHandler;
import com.vaadin.terminal.gwt.client.ui.VView;
/**
- * Application window component.
+ * A component that represents an application (browser native) window or a sub
+ * window.
+ * <p>
+ * If the window is a application window or a sub window depends on how it is
+ * added to the application. Adding a {@code Window} to a {@code Window} using
+ * {@link Window#addWindow(Window)} makes it a sub window and adding a {@code
+ * Window} to the {@code Application} using
+ * {@link Application#addWindow(Window)} makes it an application window.
+ * </p>
+ * <p>
+ * An application window is the base of any view in a Vaadin application. All
+ * applications contain a main application window (set using
+ * {@link Application#setMainWindow(Window)} which is what is initially shown to
+ * the user. The contents of a window is set using
+ * {@link #setContent(ComponentContainer)}. The contents can in turn contain
+ * other components. For multi-tab applications there is one window instance per
+ * opened tab.
+ * </p>
+ * <p>
+ * A sub window is floating popup style window that can be added to an
+ * application window. Like the application window its content is set using
+ * {@link #setContent(ComponentContainer)}. A sub window can be positioned on
+ * the screen using absolute coordinates (pixels). The default content of the
+ * Window is set to be suitable for application windows. For sub windows it
+ * might be necessary to set the size of the content to work as expected.
+ * </p>
+ * <p>
+ * Window caption is displayed in the browser title bar for application level
+ * windows and in the window header for sub windows.
+ * </p>
+ * <p>
+ * Certain methods in this class are only meaningful for sub windows and other
+ * parts only for application windows. These are marked using <b>Sub window
+ * only</b> and <b>Application window only</b> respectively in the javadoc.
+ * </p>
+ * <p>
+ * Sub window is to be split into a separate component in Vaadin 7.
+ * </p>
*
* @author IT Mill Ltd.
* @version
@@ -44,148 +81,159 @@ import com.vaadin.terminal.gwt.client.ui.VView;
public class Window extends Panel implements URIHandler, ParameterHandler {
/**
- * Window with no border.
+ * <b>Application window only</b>. A border style used for opening resources
+ * in a window without a border.
*/
public static final int BORDER_NONE = 0;
/**
- * Window with only minimal border.
+ * <b>Application window only</b>. A border style used for opening resources
+ * in a window with a minimal border.
*/
public static final int BORDER_MINIMAL = 1;
/**
- * Window with default borders.
+ * <b>Application window only</b>. A border style that indicates that the
+ * default border style should be used when opening resources.
*/
public static final int BORDER_DEFAULT = 2;
/**
- * The terminal this window is attached to.
+ * <b>Application window only</b>. The terminal this window is attached to.
*/
private Terminal terminal = null;
/**
- * The application this window is attached to.
+ * <b>Application window only</b>. The application this window is attached
+ * to or null.
*/
private Application application = null;
/**
- * List of URI handlers for this window.
+ * <b>Application window only</b>. List of URI handlers for this window.
*/
private LinkedList<URIHandler> uriHandlerList = null;
/**
- * List of parameter handlers for this window.
+ * <b>Application window only</b>. List of parameter handlers for this
+ * window.
*/
private LinkedList<ParameterHandler> parameterHandlerList = null;
- /** Set of subwindows */
+ /**
+ * <b>Application window only</b>. List of sub windows in this window. A sub
+ * window cannot have other sub windows.
+ */
private final LinkedHashSet<Window> subwindows = new LinkedHashSet<Window>();
/**
- * Explicitly specified theme of this window. If null, application theme is
- * used.
+ * <b>Application window only</b>. Explicitly specified theme of this window
+ * or null if the application theme should be used.
*/
private String theme = null;
/**
- * Resources to be opened automatically on next repaint.
+ * <b>Application window only</b>. Resources to be opened automatically on
+ * next repaint. The list is automatically cleared when it has been sent to
+ * the client.
*/
private final LinkedList<OpenResource> openList = new LinkedList<OpenResource>();
/**
- * The name of the window.
+ * <b>Application window only</b>. Unique name of the window used to
+ * identify it.
*/
private String name = null;
/**
- * Window border mode.
+ * <b>Application window only.</b> Border mode of the Window.
*/
private int border = BORDER_DEFAULT;
/**
- * Distance of Window top border in pixels from top border of the containing
- * (main window) or -1 if unspecified.
+ * <b>Sub window only</b>. Top offset in pixels for the sub window (relative
+ * to the parent application window) or -1 if unspecified.
*/
private int positionY = -1;
/**
- * Distance of Window left border in pixels from left border of the
- * containing (main window) or -1 if unspecified .
+ * <b>Sub window only</b>. Left offset in pixels for the sub window
+ * (relative to the parent application window) or -1 if unspecified.
*/
private int positionX = -1;
+ /**
+ * <b>Application window only</b>. A list of notifications that are waiting
+ * to be sent to the client. Cleared (set to null) when the notifications
+ * have been sent.
+ */
private LinkedList<Notification> notifications;
+ /**
+ * <b>Sub window only</b>. Modality flag for sub window.
+ */
private boolean modal = false;
+ /**
+ * <b>Sub window only</b>. Controls if the end user can resize the window.
+ */
private boolean resizable = true;
+ /**
+ * <b>Sub window only</b>. Controls if the end user can move the window by
+ * dragging.
+ */
private boolean draggable = true;
+ /**
+ * <b>Sub window only</b>. Flag which is true if the window is centered on
+ * the screen.
+ */
private boolean centerRequested = false;
+ /**
+ * Component that should be focused after the next repaint. Null if no focus
+ * change should take place.
+ */
private Focusable pendingFocus;
+ /**
+ * <b>Application window only</b>. A list of javascript commands that are
+ * waiting to be sent to the client. Cleared (set to null) when the commands
+ * have been sent.
+ */
private ArrayList<String> jsExecQueue = null;
+ /**
+ * The component that should be scrolled into view after the next repaint.
+ * Null if nothing should be scrolled into view.
+ */
private Component scrollIntoView;
- /* ********************************************************************* */
-
/**
- * Creates a new empty unnamed window with default layout.
- *
- * <p>
- * To show the window in application, it must be added to application with
- * <code>Application.addWindow</code> method.
- * </p>
- *
- * <p>
- * The windows are scrollable by default.
- * </p>
- *
- * @param caption
- * the Title of the window.
+ * Creates a new unnamed window with a default layout.
*/
public Window() {
this("", null);
}
/**
- * Creates a new empty window with default layout.
- *
- * <p>
- * To show the window in application, it must be added to application with
- * <code>Application.addWindow</code> method.
- * </p>
- *
- * <p>
- * The windows are scrollable by default.
- * </p>
+ * Creates a new unnamed window with a default layout and given title.
*
* @param caption
- * the Title of the window.
+ * the title of the window.
*/
public Window(String caption) {
this(caption, null);
}
/**
- * Creates a new window.
- *
- * <p>
- * To show the window in application, it must be added to application with
- * <code>Application.addWindow</code> method.
- * </p>
- *
- * <p>
- * The windows are scrollable by default.
- * </p>
+ * Creates a new unnamed window with the given content and title.
*
* @param caption
- * the Title of the window.
- * @param layout
- * the Layout of the window.
+ * the title of the window.
+ * @param content
+ * the contents of the window
*/
public Window(String caption, ComponentContainer content) {
super(caption, content);
@@ -461,14 +509,13 @@ public class Window extends Panel implements URIHandler, ParameterHandler {
}
/**
- * Sets the theme for this window.
- *
- * Setting theme for subwindows is not supported.
+ * <b>Application window only</b>. Sets the theme for this window.
*
* The terminal will reload its host page on theme changes.
*
* @param theme
- * the New theme for this window. Null implies the default theme.
+ * the new theme for this window or null to use the application
+ * theme.
*/
public void setTheme(String theme) {
if (getParent() != null) {
@@ -480,7 +527,7 @@ public class Window extends Panel implements URIHandler, ParameterHandler {
}
/**
- * Paints the content of this component.
+ * Paints the contents of this component.
*
* @param event
* the Paint Event.
@@ -750,16 +797,23 @@ public class Window extends Panel implements URIHandler, ParameterHandler {
}
/**
- * Returns the border.
+ * Returns the border style of the window.
*
- * @return the border.
+ * @see #setBorder(int)
+ * @return the border style for the window
*/
public int getBorder() {
return border;
}
/**
- * Sets the border.
+ * Sets the border style for this window. Valid values are
+ * {@link Window#BORDER_NONE}, {@link Window#BORDER_MINIMAL},
+ * {@link Window#BORDER_DEFAULT}.
+ * <p>
+ * <b>Note!</b> Setting this seems to currently have no effect whatsoever on
+ * the window.
+ * </p>
*
* @param border
* the border to set.
diff --git a/src/com/vaadin/ui/themes/Runo.java b/src/com/vaadin/ui/themes/Runo.java
index 29cc7089a6..88011addc4 100644
--- a/src/com/vaadin/ui/themes/Runo.java
+++ b/src/com/vaadin/ui/themes/Runo.java
@@ -7,6 +7,10 @@ public class Runo extends BaseTheme {
public static final String THEME_NAME = "Runo";
+ public static String themeName() {
+ return THEME_NAME.toLowerCase();
+ }
+
/***************************************************************************
*
* Button styles
@@ -18,6 +22,18 @@ public class Runo extends BaseTheme {
*/
public static final String BUTTON_SMALL = "small";
+ /**
+ * Big sized button, use to gather much attention for some particular action
+ */
+ public static final String BUTTON_BIG = "big";
+
+ /**
+ * Default action style for buttons (the button that should get activated
+ * when the user presses 'enter' in a form). Use sparingly, only one default
+ * button per view should be visible.
+ */
+ public static final String BUTTON_DEFAULT = "default";
+
/***************************************************************************
*
* Panel styles
@@ -40,4 +56,128 @@ public class Runo extends BaseTheme {
*/
public static final String TABSHEET_SMALL = "light";
+ /***************************************************************************
+ *
+ * SplitPanel styles
+ *
+ **************************************************************************/
+
+ /**
+ * Reduces the width/height of the split handle. Useful when you don't want
+ * the split handle to touch the sides of the containing layout.
+ */
+ public static final String SPLITPANEL_REDUCED = "rounded";
+
+ /**
+ * Reduces the visual size of the split handle to one pixel (the active drag
+ * size is still larger).
+ */
+ public static final String SPLITPANEL_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * Label styles
+ *
+ **************************************************************************/
+
+ /**
+ * Largest title/header size. Use for main sections in your application.
+ */
+ public static final String LABEL_H1 = "h1";
+
+ /**
+ * Similar style as in panel captions. Useful for sub-sections within a
+ * view.
+ */
+ public static final String LABEL_H2 = "h2";
+
+ /**
+ * Small font size. Useful for contextual help texts and similar less
+ * frequently needed information. Use with modesty, since this style will be
+ * more harder to read due to its smaller size and contrast.
+ */
+ public static final String LABEL_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * Layout styles
+ *
+ **************************************************************************/
+
+ /**
+ * An alternative background color for layouts. Use on top of white
+ * background (e.g. inside Panels, TabSheets and sub-windows).
+ */
+ public static final String LAYOUT_DARKER = "darker";
+
+ /**
+ * Add a drop shadow around the layout and its contained components.
+ * Produces a rectangular shadow, even if the contained component would have
+ * a different shape.
+ * <p>
+ * Note: does not work in Internet Explorer 6
+ */
+ public static final String CSSLAYOUT_SHADOW = "box-shadow";
+
+ /**
+ * Adds necessary styles to the layout to make it look selectable (i.e.
+ * clickable). Add a click listener for the layout, and toggle the
+ * {@link #CSSLAYOUT_SELECTABLE_SELECTED} style for the same layout to make
+ * it look selected or not.
+ */
+ public static final String CSSLAYOUT_SELECTABLE = "selectable";
+ public static final String CSSLAYOUT_SELECTABLE_SELECTED = "selectable-selected";
+
+ /***************************************************************************
+ *
+ * TextField styles
+ *
+ **************************************************************************/
+
+ /**
+ * Small sized text field with small font
+ */
+ public static final String TEXTFIELD_SMALL = "small";
+
+ /***************************************************************************
+ *
+ * Table styles
+ *
+ **************************************************************************/
+
+ /**
+ * Smaller header and item fonts.
+ */
+ public static final String TABLE_SMALL = "small";
+
+ /**
+ * Removes the border and background color from the table. Removes
+ * alternating row background colors as well.
+ */
+ public static final String TABLE_BORDERLESS = "borderless";
+
+ /***************************************************************************
+ *
+ * Accordion styles
+ *
+ **************************************************************************/
+
+ /**
+ * A detached looking accordion, providing space around its captions and
+ * content. Doesn't necessarily need a Panel or other container to wrap it
+ * in order to make it look right.
+ */
+ public static final String ACCORDION_LIGHT = "light";
+
+ /***************************************************************************
+ *
+ * Window styles
+ *
+ **************************************************************************/
+
+ /**
+ * Smaller header and a darker background color for the window. Useful for
+ * smaller dialog-like windows.
+ */
+ public static final String WINDOW_DIALOG = "dialog";
}