summaryrefslogtreecommitdiffstats
path: root/src/com/vaadin/event
diff options
context:
space:
mode:
authorHenri Sara <henri.sara@itmill.com>2009-05-11 09:19:03 +0000
committerHenri Sara <henri.sara@itmill.com>2009-05-11 09:19:03 +0000
commitadc8c0ad3573272c236040c3a76005b9e73a5737 (patch)
treea3860704dbd5b82dc6af38684b80f8ef79a32722 /src/com/vaadin/event
parent5abc870dda584d0c2fc47fd5eec4ae3de3fa240e (diff)
downloadvaadin-framework-adc8c0ad3573272c236040c3a76005b9e73a5737.tar.gz
vaadin-framework-adc8c0ad3573272c236040c3a76005b9e73a5737.zip
#2904: initial bulk rename "com.itmill.toolkit" -> "com.vaadin"
- com.itmill.toolkit.external not yet fully renamed svn changeset:7715/svn branch:6.0
Diffstat (limited to 'src/com/vaadin/event')
-rw-r--r--src/com/vaadin/event/Action.java166
-rw-r--r--src/com/vaadin/event/EventRouter.java150
-rw-r--r--src/com/vaadin/event/ItemClickEvent.java155
-rw-r--r--src/com/vaadin/event/ListenerMethod.java641
-rw-r--r--src/com/vaadin/event/MethodEventSource.java162
-rw-r--r--src/com/vaadin/event/ShortcutAction.java188
-rw-r--r--src/com/vaadin/event/package.html58
7 files changed, 1520 insertions, 0 deletions
diff --git a/src/com/vaadin/event/Action.java b/src/com/vaadin/event/Action.java
new file mode 100644
index 0000000000..c3329e6d56
--- /dev/null
+++ b/src/com/vaadin/event/Action.java
@@ -0,0 +1,166 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.event;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.Resource;
+
+/**
+ * Implements the action framework. This class contains subinterfaces for action
+ * handling and listing, and for action handler registrations and
+ * unregistration.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class Action implements Serializable {
+
+ /**
+ * Action title.
+ */
+ private String caption;
+
+ /**
+ * Action icon.
+ */
+ private Resource icon = null;
+
+ /**
+ * Constructs a new action with the given caption.
+ *
+ * @param caption
+ * the caption for the new action.
+ */
+ public Action(String caption) {
+ this.caption = caption;
+ }
+
+ /**
+ * Constructs a new action with the given caption string and icon.
+ *
+ * @param caption
+ * the caption for the new action.
+ * @param icon
+ * the icon for the new action.
+ */
+ public Action(String caption, Resource icon) {
+ this.caption = caption;
+ this.icon = icon;
+ }
+
+ /**
+ * Returns the action's caption.
+ *
+ * @return the action's caption as a <code>String</code>.
+ */
+ public String getCaption() {
+ return caption;
+ }
+
+ /**
+ * Returns the action's icon.
+ *
+ * @return the action's Icon.
+ */
+ public Resource getIcon() {
+ return icon;
+ }
+
+ /**
+ * Interface implemented by classes who wish to handle actions.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface Handler extends Serializable {
+
+ /**
+ * Gets the list of actions applicable to this handler.
+ *
+ * @param target
+ * the target handler to list actions for. For item
+ * containers this is the item id.
+ * @param sender
+ * the party that would be sending the actions. Most of this
+ * is the action container.
+ * @return the list of Action
+ */
+ public Action[] getActions(Object target, Object sender);
+
+ /**
+ * Handles an action for the given target. The handler method may just
+ * discard the action if it's not suitable.
+ *
+ * @param action
+ * the action to be handled.
+ * @param sender
+ * the sender of the action. This is most often the action
+ * container.
+ * @param target
+ * the target of the action. For item containers this is the
+ * item id.
+ */
+ public void handleAction(Action action, Object sender, Object target);
+ }
+
+ /**
+ * Interface implemented by all components where actions can be registered.
+ * This means that the components lets others to register as action handlers
+ * to it. When the component receives an action targeting its contents it
+ * should loop all action handlers registered to it and let them handle the
+ * action.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public interface Container extends Serializable {
+
+ /**
+ * Registers a new action handler for this container
+ *
+ * @param actionHandler
+ * the new handler to be added.
+ */
+ public void addActionHandler(Action.Handler actionHandler);
+
+ /**
+ * Removes a previously registered action handler for the contents of
+ * this container.
+ *
+ * @param actionHandler
+ * the handler to be removed.
+ */
+ public void removeActionHandler(Action.Handler actionHandler);
+ }
+
+ /**
+ * Sets the caption.
+ *
+ * @param caption
+ * the caption to set.
+ */
+ public void setCaption(String caption) {
+ this.caption = caption;
+ }
+
+ /**
+ * Sets the icon.
+ *
+ * @param icon
+ * the icon to set.
+ */
+ public void setIcon(Resource icon) {
+ this.icon = icon;
+ }
+
+}
diff --git a/src/com/vaadin/event/EventRouter.java b/src/com/vaadin/event/EventRouter.java
new file mode 100644
index 0000000000..ca98c5e799
--- /dev/null
+++ b/src/com/vaadin/event/EventRouter.java
@@ -0,0 +1,150 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.event;
+
+import java.lang.reflect.Method;
+import java.util.EventObject;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * <code>EventRouter</code> class implementing the inheritable event listening
+ * model. For more information on the event model see the
+ * {@link com.vaadin.event package documentation}.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class EventRouter implements MethodEventSource {
+
+ /**
+ * List of registered listeners.
+ */
+ private Set listenerList = null;
+
+ /*
+ * Registers a new listener with the specified activation method to listen
+ * events generated by this component. Don't add a JavaDoc comment here, we
+ * use the default documentation from implemented interface.
+ */
+ public void addListener(Class eventType, Object object, Method method) {
+ if (listenerList == null) {
+ listenerList = new LinkedHashSet();
+ }
+ listenerList.add(new ListenerMethod(eventType, object, method));
+ }
+
+ /*
+ * Registers a new listener with the specified named activation method to
+ * listen events generated by this component. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ public void addListener(Class eventType, Object object, String methodName) {
+ if (listenerList == null) {
+ listenerList = new LinkedHashSet();
+ }
+ listenerList.add(new ListenerMethod(eventType, object, methodName));
+ }
+
+ /*
+ * Removes all registered listeners matching the given parameters. Don't add
+ * a JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public void removeListener(Class eventType, Object target) {
+ if (listenerList != null) {
+ final Iterator i = listenerList.iterator();
+ while (i.hasNext()) {
+ final ListenerMethod lm = (ListenerMethod) i.next();
+ if (lm.matches(eventType, target)) {
+ i.remove();
+ return;
+ }
+ }
+ }
+ }
+
+ /*
+ * Removes the event listener methods matching the given given paramaters.
+ * Don't add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ public void removeListener(Class eventType, Object target, Method method) {
+ if (listenerList != null) {
+ final Iterator i = listenerList.iterator();
+ while (i.hasNext()) {
+ final ListenerMethod lm = (ListenerMethod) i.next();
+ if (lm.matches(eventType, target, method)) {
+ i.remove();
+ return;
+ }
+ }
+ }
+ }
+
+ /*
+ * Removes the event listener method matching the given given parameters.
+ * Don't add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ public void removeListener(Class eventType, Object target, String methodName) {
+
+ // Find the correct method
+ final Method[] methods = target.getClass().getMethods();
+ Method method = null;
+ for (int i = 0; i < methods.length; i++) {
+ if (methods[i].getName().equals(methodName)) {
+ method = methods[i];
+ }
+ }
+ if (method == null) {
+ throw new IllegalArgumentException();
+ }
+
+ // Remove the listeners
+ if (listenerList != null) {
+ final Iterator i = listenerList.iterator();
+ while (i.hasNext()) {
+ final ListenerMethod lm = (ListenerMethod) i.next();
+ if (lm.matches(eventType, target, method)) {
+ i.remove();
+ return;
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Removes all listeners from event router.
+ */
+ public void removeAllListeners() {
+ listenerList = null;
+ }
+
+ /**
+ * Sends an event to all registered listeners. The listeners will decide if
+ * the activation method should be called or not.
+ *
+ * @param event
+ * the Event to be sent to all listeners.
+ */
+ public void fireEvent(EventObject event) {
+ // It is not necessary to send any events if there are no listeners
+ if (listenerList != null) {
+ // Send the event to all listeners. The listeners themselves
+ // will filter out unwanted events.
+
+ final Iterator i = listenerList.iterator();
+ while (i.hasNext()) {
+ ((ListenerMethod) i.next()).receiveEvent(event);
+ }
+ }
+ }
+}
diff --git a/src/com/vaadin/event/ItemClickEvent.java b/src/com/vaadin/event/ItemClickEvent.java
new file mode 100644
index 0000000000..b69e8e4c92
--- /dev/null
+++ b/src/com/vaadin/event/ItemClickEvent.java
@@ -0,0 +1,155 @@
+package com.vaadin.event;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.terminal.gwt.client.MouseEventDetails;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Component.Event;
+
+/**
+ *
+ * Click event fired by a {@link Component} implementing
+ * {@link com.vaadin.data.Container} interface. ItemClickEvents happens
+ * on an {@link Item} rendered somehow on terminal. Event may also contain a
+ * specific {@link Property} on which the click event happened.
+ *
+ * ClickEvents are rather terminal dependent events. Correct values in event
+ * details cannot be guaranteed.
+ *
+ * EXPERIMENTAL FEATURE, user input is welcome
+ *
+ * @since 5.3
+ *
+ * TODO extract generic super class/interfaces if we implement some other
+ * click events.
+ */
+@SuppressWarnings("serial")
+public class ItemClickEvent extends Event implements Serializable {
+ public static final int BUTTON_LEFT = MouseEventDetails.BUTTON_LEFT;
+ public static final int BUTTON_MIDDLE = MouseEventDetails.BUTTON_MIDDLE;
+ public static final int BUTTON_RIGHT = MouseEventDetails.BUTTON_RIGHT;
+
+ private MouseEventDetails details;
+ private Item item;
+ private Object itemId;
+ private Object propertyId;
+
+ public ItemClickEvent(Component source, Item item, Object itemId,
+ Object propertyId, MouseEventDetails details) {
+ super(source);
+ this.details = details;
+ this.item = item;
+ this.itemId = itemId;
+ this.propertyId = propertyId;
+ }
+
+ /**
+ * Gets the item on which the click event occurred.
+ *
+ * @return item which was clicked
+ */
+ public Item getItem() {
+ return item;
+ }
+
+ /**
+ * Gets a possible identifier in source for clicked Item
+ *
+ * @return
+ */
+ public Object getItemId() {
+ return itemId;
+ }
+
+ /**
+ * Returns property on which click event occurred. Returns null if source
+ * cannot be resolved at property leve. For example if clicked a cell in
+ * table, the "column id" is returned.
+ *
+ * @return a property id of clicked property or null if click didn't occur
+ * on any distinct property.
+ */
+ public Object getPropertyId() {
+ return propertyId;
+ }
+
+ public int getButton() {
+ return details.getButton();
+ }
+
+ public int getClientX() {
+ return details.getClientX();
+ }
+
+ public int getClientY() {
+ return details.getClientY();
+ }
+
+ public boolean isDoubleClick() {
+ return details.isDoubleClick();
+ }
+
+ public boolean isAltKey() {
+ return details.isAltKey();
+ }
+
+ public boolean isCtrlKey() {
+ return details.isCtrlKey();
+ }
+
+ public boolean isMetaKey() {
+ return details.isMetaKey();
+ }
+
+ public boolean isShiftKey() {
+ return details.isShiftKey();
+ }
+
+ public static final Method ITEM_CLICK_METHOD;
+
+ static {
+ try {
+ ITEM_CLICK_METHOD = ItemClickListener.class.getDeclaredMethod(
+ "itemClick", new Class[] { ItemClickEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException();
+ }
+ }
+
+ public interface ItemClickListener extends Serializable {
+ public void itemClick(ItemClickEvent event);
+ }
+
+ /**
+ * Components implementing
+ *
+ * @link {@link Container} interface may support emitting
+ * {@link ItemClickEvent}s.
+ */
+ public interface ItemClickSource extends Serializable {
+ /**
+ * Register listener to handle ItemClickEvents.
+ *
+ * Note! Click listeners are rather terminal dependent features.
+ *
+ * This feature is EXPERIMENTAL
+ *
+ * @param listener
+ * ItemClickListener to be registered
+ */
+ public void addListener(ItemClickListener listener);
+
+ /**
+ * Removes ItemClickListener.
+ *
+ * @param listener
+ */
+ public void removeListener(ItemClickListener listener);
+ }
+
+}
diff --git a/src/com/vaadin/event/ListenerMethod.java b/src/com/vaadin/event/ListenerMethod.java
new file mode 100644
index 0000000000..3efd4c70c7
--- /dev/null
+++ b/src/com/vaadin/event/ListenerMethod.java
@@ -0,0 +1,641 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.event;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.EventListener;
+import java.util.EventObject;
+
+/**
+ * <p>
+ * One registered event listener. This class contains the listener object
+ * reference, listened event type, the trigger method to call when the event
+ * fires, and the optional argument list to pass to the method and the index of
+ * the argument to replace with the event object.
+ * </p>
+ *
+ * <p>
+ * This Class provides several constructors that allow omission of the optional
+ * arguments, and giving the listener method directly, or having the constructor
+ * to reflect it using merely the name of the method.
+ * </p>
+ *
+ * <p>
+ * It should be pointed out that the method
+ * {@link #receiveEvent(EventObject event)} is the one that filters out the
+ * events that do not match with the given event type and thus do not result in
+ * calling of the trigger method.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class ListenerMethod implements EventListener, Serializable {
+
+ /**
+ * Type of the event that should trigger this listener. Also the subclasses
+ * of this class are accepted to trigger the listener.
+ */
+ private final Class<?> eventType;
+
+ /**
+ * The object containing the trigger method.
+ */
+ private Object object;
+
+ /**
+ * The trigger method to call when an event passing the given criteria
+ * fires.
+ */
+ private transient Method method;
+
+ /**
+ * Optional argument set to pass to the trigger method.
+ */
+ private Object[] arguments;
+
+ /**
+ * Optional index to <code>arguments</code> that point out which one should
+ * be replaced with the triggering event object and thus be passed to the
+ * trigger method.
+ */
+ private int eventArgumentIndex;
+
+ /* Special serialization to handle method references */
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ try {
+ out.defaultWriteObject();
+ String name = method.getName();
+ Class<?>[] paramTypes = method.getParameterTypes();
+ out.writeObject(name);
+ out.writeObject(paramTypes);
+ } catch (NotSerializableException e) {
+ System.err
+ .println("Fatal error in serialization of the application: Class "
+ + object.getClass().getName()
+ + " must implement serialization.");
+ throw e;
+ }
+
+ };
+
+ /* Special serialization to handle method references */
+ private void readObject(java.io.ObjectInputStream in) throws IOException,
+ ClassNotFoundException {
+ in.defaultReadObject();
+ try {
+ String name = (String) in.readObject();
+ Class<?>[] paramTypes = (Class<?>[]) in.readObject();
+ // We can not use getMethod directly as we want to support anonymous
+ // inner classes
+ method = findHighestMethod(object.getClass(), name, paramTypes);
+ } catch (SecurityException e) {
+ System.err.println("Internal deserialization error");
+ e.printStackTrace();
+ }
+ };
+
+ private static Method findHighestMethod(Class<?> cls, String method,
+ Class<?>[] paramTypes) {
+ Class<?>[] ifaces = cls.getInterfaces();
+ for (int i = 0; i < ifaces.length; i++) {
+ Method ifaceMethod = findHighestMethod(ifaces[i], method,
+ paramTypes);
+ if (ifaceMethod != null) {
+ return ifaceMethod;
+ }
+ }
+ if (cls.getSuperclass() != null) {
+ Method parentMethod = findHighestMethod(cls.getSuperclass(),
+ method, paramTypes);
+ if (parentMethod != null) {
+ return parentMethod;
+ }
+ }
+ Method[] methods = cls.getMethods();
+ for (int i = 0; i < methods.length; i++) {
+ // we ignore parameter types for now - you need to add this
+ if (methods[i].getName().equals(method)) {
+ return methods[i];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * <p>
+ * Constructs a new event listener from a trigger method, it's arguments and
+ * the argument index specifying which one is replaced with the event object
+ * when the trigger method is called.
+ * </p>
+ *
+ * <p>
+ * This constructor gets the trigger method as a parameter so it does not
+ * need to reflect to find it out.
+ * </p>
+ *
+ * @param eventType
+ * the event type that is listener listens to. All events of this
+ * kind (or its subclasses) result in calling the trigger method.
+ * @param object
+ * the object instance that contains the trigger method
+ * @param method
+ * the trigger method
+ * @param arguments
+ * the arguments to be passed to the trigger method
+ * @param eventArgumentIndex
+ * An index to the argument list. This index points out the
+ * argument that is replaced with the event object before the
+ * argument set is passed to the trigger method. If the
+ * eventArgumentIndex is negative, the triggering event object
+ * will not be passed to the trigger method, though it is still
+ * called.
+ * @throws java.lang.IllegalArgumentException
+ * if <code>method</code> is not a member of <code>object</code>
+ * .
+ */
+ public ListenerMethod(Class<?> eventType, Object object, Method method,
+ Object[] arguments, int eventArgumentIndex)
+ throws java.lang.IllegalArgumentException {
+
+ // Checks that the object is of correct type
+ if (!method.getDeclaringClass().isAssignableFrom(object.getClass())) {
+ throw new java.lang.IllegalArgumentException();
+ }
+
+ // Checks that the event argument is null
+ if (eventArgumentIndex >= 0 && arguments[eventArgumentIndex] != null) {
+ throw new java.lang.IllegalArgumentException();
+ }
+
+ // Checks the event type is supported by the method
+ if (eventArgumentIndex >= 0
+ && !method.getParameterTypes()[eventArgumentIndex]
+ .isAssignableFrom(eventType)) {
+ throw new java.lang.IllegalArgumentException();
+ }
+
+ this.eventType = eventType;
+ this.object = object;
+ this.method = method;
+ this.arguments = arguments;
+ this.eventArgumentIndex = eventArgumentIndex;
+ }
+
+ /**
+ * <p>
+ * Constructs a new event listener from a trigger method name, it's
+ * arguments and the argument index specifying which one is replaced with
+ * the event object. The actual trigger method is reflected from
+ * <code>object</code>, and <code>java.lang.IllegalArgumentException</code>
+ * is thrown unless exactly one match is found.
+ * </p>
+ *
+ * @param eventType
+ * the event type that is listener listens to. All events of this
+ * kind (or its subclasses) result in calling the trigger method.
+ * @param object
+ * the object instance that contains the trigger method.
+ * @param methodName
+ * the name of the trigger method. If the object does not contain
+ * the method or it contains more than one matching methods
+ * <code>java.lang.IllegalArgumentException</code> is thrown.
+ * @param arguments
+ * the arguments to be passed to the trigger method.
+ * @param eventArgumentIndex
+ * An index to the argument list. This index points out the
+ * argument that is replaced with the event object before the
+ * argument set is passed to the trigger method. If the
+ * eventArgumentIndex is negative, the triggering event object
+ * will not be passed to the trigger method, though it is still
+ * called.
+ * @throws java.lang.IllegalArgumentException
+ * unless exactly one match <code>methodName</code> is found in
+ * <code>object</code>.
+ */
+ public ListenerMethod(Class<?> eventType, Object object, String methodName,
+ Object[] arguments, int eventArgumentIndex)
+ throws java.lang.IllegalArgumentException {
+
+ // Finds the correct method
+ final Method[] methods = object.getClass().getMethods();
+ Method method = null;
+ for (int i = 0; i < methods.length; i++) {
+ if (methods[i].getName().equals(methodName)) {
+ method = methods[i];
+ }
+ }
+ if (method == null) {
+ throw new IllegalArgumentException();
+ }
+
+ // Checks that the event argument is null
+ if (eventArgumentIndex >= 0 && arguments[eventArgumentIndex] != null) {
+ throw new java.lang.IllegalArgumentException();
+ }
+
+ // Checks the event type is supported by the method
+ if (eventArgumentIndex >= 0
+ && !method.getParameterTypes()[eventArgumentIndex]
+ .isAssignableFrom(eventType)) {
+ throw new java.lang.IllegalArgumentException();
+ }
+
+ this.eventType = eventType;
+ this.object = object;
+ this.method = method;
+ this.arguments = arguments;
+ this.eventArgumentIndex = eventArgumentIndex;
+ }
+
+ /**
+ * <p>
+ * Constructs a new event listener from the trigger method and it's
+ * arguments. Since the the index to the replaced parameter is not specified
+ * the event triggering this listener will not be passed to the trigger
+ * method.
+ * </p>
+ *
+ * <p>
+ * This constructor gets the trigger method as a parameter so it does not
+ * need to reflect to find it out.
+ * </p>
+ *
+ * @param eventType
+ * the event type that is listener listens to. All events of this
+ * kind (or its subclasses) result in calling the trigger method.
+ * @param object
+ * the object instance that contains the trigger method.
+ * @param method
+ * the trigger method.
+ * @param arguments
+ * the arguments to be passed to the trigger method.
+ * @throws java.lang.IllegalArgumentException
+ * if <code>method</code> is not a member of <code>object</code>
+ * .
+ */
+ public ListenerMethod(Class<?> eventType, Object object, Method method,
+ Object[] arguments) throws java.lang.IllegalArgumentException {
+
+ // Check that the object is of correct type
+ if (!method.getDeclaringClass().isAssignableFrom(object.getClass())) {
+ throw new java.lang.IllegalArgumentException();
+ }
+
+ this.eventType = eventType;
+ this.object = object;
+ this.method = method;
+ this.arguments = arguments;
+ eventArgumentIndex = -1;
+ }
+
+ /**
+ * <p>
+ * Constructs a new event listener from a trigger method name and it's
+ * arguments. Since the the index to the replaced parameter is not specified
+ * the event triggering this listener will not be passed to the trigger
+ * method.
+ * </p>
+ *
+ * <p>
+ * The actual trigger method is reflected from <code>object</code>, and
+ * <code>java.lang.IllegalArgumentException</code> is thrown unless exactly
+ * one match is found.
+ * </p>
+ *
+ * @param eventType
+ * the event type that is listener listens to. All events of this
+ * kind (or its subclasses) result in calling the trigger method.
+ * @param object
+ * the object instance that contains the trigger method.
+ * @param methodName
+ * the name of the trigger method. If the object does not contain
+ * the method or it contains more than one matching methods
+ * <code>java.lang.IllegalArgumentException</code> is thrown.
+ * @param arguments
+ * the arguments to be passed to the trigger method.
+ * @throws java.lang.IllegalArgumentException
+ * unless exactly one match <code>methodName</code> is found in
+ * <code>object</code>.
+ */
+ public ListenerMethod(Class<?> eventType, Object object, String methodName,
+ Object[] arguments) throws java.lang.IllegalArgumentException {
+
+ // Find the correct method
+ final Method[] methods = object.getClass().getMethods();
+ Method method = null;
+ for (int i = 0; i < methods.length; i++) {
+ if (methods[i].getName().equals(methodName)) {
+ method = methods[i];
+ }
+ }
+ if (method == null) {
+ throw new IllegalArgumentException();
+ }
+
+ this.eventType = eventType;
+ this.object = object;
+ this.method = method;
+ this.arguments = arguments;
+ eventArgumentIndex = -1;
+ }
+
+ /**
+ * <p>
+ * Constructs a new event listener from a trigger method. Since the argument
+ * list is unspecified no parameters are passed to the trigger method when
+ * the listener is triggered.
+ * </p>
+ *
+ * <p>
+ * This constructor gets the trigger method as a parameter so it does not
+ * need to reflect to find it out.
+ * </p>
+ *
+ * @param eventType
+ * the event type that is listener listens to. All events of this
+ * kind (or its subclasses) result in calling the trigger method.
+ * @param object
+ * the object instance that contains the trigger method.
+ * @param method
+ * the trigger method.
+ * @throws java.lang.IllegalArgumentException
+ * if <code>method</code> is not a member of <code>object</code>
+ * .
+ */
+ public ListenerMethod(Class<?> eventType, Object object, Method method)
+ throws java.lang.IllegalArgumentException {
+
+ // Checks that the object is of correct type
+ if (!method.getDeclaringClass().isAssignableFrom(object.getClass())) {
+ throw new java.lang.IllegalArgumentException();
+ }
+
+ this.eventType = eventType;
+ this.object = object;
+ this.method = method;
+ eventArgumentIndex = -1;
+
+ final Class<?>[] params = method.getParameterTypes();
+
+ if (params.length == 0) {
+ arguments = new Object[0];
+ } else if (params.length == 1 && params[0].isAssignableFrom(eventType)) {
+ arguments = new Object[] { null };
+ eventArgumentIndex = 0;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * <p>
+ * Constructs a new event listener from a trigger method name. Since the
+ * argument list is unspecified no parameters are passed to the trigger
+ * method when the listener is triggered.
+ * </p>
+ *
+ * <p>
+ * The actual trigger method is reflected from <code>object</code>, and
+ * <code>java.lang.IllegalArgumentException</code> is thrown unless exactly
+ * one match is found.
+ * </p>
+ *
+ * @param eventType
+ * the event type that is listener listens to. All events of this
+ * kind (or its subclasses) result in calling the trigger method.
+ * @param object
+ * the object instance that contains the trigger method.
+ * @param methodName
+ * the name of the trigger method. If the object does not contain
+ * the method or it contains more than one matching methods
+ * <code>java.lang.IllegalArgumentException</code> is thrown.
+ * @throws java.lang.IllegalArgumentException
+ * unless exactly one match <code>methodName</code> is found in
+ * <code>object</code>.
+ */
+ public ListenerMethod(Class<?> eventType, Object object, String methodName)
+ throws java.lang.IllegalArgumentException {
+
+ // Finds the correct method
+ final Method[] methods = object.getClass().getMethods();
+ Method method = null;
+ for (int i = 0; i < methods.length; i++) {
+ if (methods[i].getName().equals(methodName)) {
+ method = methods[i];
+ }
+ }
+ if (method == null) {
+ throw new IllegalArgumentException();
+ }
+
+ this.eventType = eventType;
+ this.object = object;
+ this.method = method;
+ eventArgumentIndex = -1;
+
+ final Class<?>[] params = method.getParameterTypes();
+
+ if (params.length == 0) {
+ arguments = new Object[0];
+ } else if (params.length == 1 && params[0].isAssignableFrom(eventType)) {
+ arguments = new Object[] { null };
+ eventArgumentIndex = 0;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Receives one event from the <code>EventRouter</code> and calls the
+ * trigger method if it matches with the criteria defined for the listener.
+ * Only the events of the same or subclass of the specified event class
+ * result in the trigger method to be called.
+ *
+ * @param event
+ * the fired event. Unless the trigger method's argument list and
+ * the index to the to be replaced argument is specified, this
+ * event will not be passed to the trigger method.
+ */
+ public void receiveEvent(EventObject event) {
+ // Only send events supported by the method
+ if (eventType.isAssignableFrom(event.getClass())) {
+ try {
+ if (eventArgumentIndex >= 0) {
+ if (eventArgumentIndex == 0 && arguments.length == 1) {
+ method.invoke(object, new Object[] { event });
+ } else {
+ final Object[] arg = new Object[arguments.length];
+ for (int i = 0; i < arg.length; i++) {
+ arg[i] = arguments[i];
+ }
+ arg[eventArgumentIndex] = event;
+ method.invoke(object, arg);
+ }
+ } else {
+ method.invoke(object, arguments);
+ }
+
+ } catch (final java.lang.IllegalAccessException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error - please report", e);
+ } catch (final java.lang.reflect.InvocationTargetException e) {
+ // An exception was thrown by the invocation target. Throw it
+ // forwards.
+ throw new MethodException("Invocation of method " + method
+ + " failed.", e.getTargetException());
+ }
+ }
+ }
+
+ /**
+ * Checks if the given object and event match with the ones stored in this
+ * listener.
+ *
+ * @param target
+ * the object to be matched against the object stored by this
+ * listener.
+ * @param eventType
+ * the type to be tested for equality against the type stored by
+ * this listener.
+ * @return <code>true</code> if <code>target</code> is the same object as
+ * the one stored in this object and <code>eventType</code> equals
+ * the event type stored in this object. *
+ */
+ public boolean matches(Class<?> eventType, Object target) {
+ return (target == object) && (eventType.equals(this.eventType));
+ }
+
+ /**
+ * Checks if the given object, event and method match with the ones stored
+ * in this listener.
+ *
+ * @param target
+ * the object to be matched against the object stored by this
+ * listener.
+ * @param eventType
+ * the type to be tested for equality against the type stored by
+ * this listener.
+ * @param method
+ * the method to be tested for equality against the method stored
+ * by this listener.
+ * @return <code>true</code> if <code>target</code> is the same object as
+ * the one stored in this object, <code>eventType</code> equals with
+ * the event type stored in this object and <code>method</code>
+ * equals with the method stored in this object
+ */
+ public boolean matches(Class<?> eventType, Object target, Method method) {
+ return (target == object)
+ && (eventType.equals(this.eventType) && method
+ .equals(this.method));
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+
+ hash = 31 * hash + eventArgumentIndex;
+ hash = 31 * hash + (eventType == null ? 0 : eventType.hashCode());
+ hash = 31 * hash + (object == null ? 0 : object.hashCode());
+ hash = 31 * hash + (method == null ? 0 : method.hashCode());
+
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ // return false if obj is a subclass (do not use instanceof check)
+ if ((obj == null) || (obj.getClass() != getClass())) {
+ return false;
+ }
+
+ // obj is of same class, test it further
+ ListenerMethod t = (ListenerMethod) obj;
+
+ return eventArgumentIndex == t.eventArgumentIndex
+ && (eventType == t.eventType || (eventType != null && eventType
+ .equals(t.eventType)))
+ && (object == t.object || (object != null && object
+ .equals(t.object)))
+ && (method == t.method || (method != null && method
+ .equals(t.method)))
+ && (arguments == t.arguments || (Arrays.equals(arguments,
+ t.arguments)));
+ }
+
+ /**
+ * Exception that wraps an exception thrown by an invoked method. When
+ * <code>ListenerMethod</code> invokes the target method, it may throw
+ * arbitrary exception. The original exception is wrapped into
+ * MethodException instance and rethrown by the <code>ListenerMethod</code>.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class MethodException extends RuntimeException implements
+ Serializable {
+
+ private final Throwable cause;
+
+ private String message;
+
+ private MethodException(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ /**
+ * Retrieves the cause of this throwable or <code>null</code> if the
+ * cause does not exist or not known.
+ *
+ * @return the cause of this throwable or <code>null</code> if the cause
+ * is nonexistent or unknown.
+ * @see java.lang.Throwable#getCause()
+ */
+ @Override
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * Returns the error message string of this throwable object.
+ *
+ * @return the error message.
+ * @see java.lang.Throwable#getMessage()
+ */
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * @see java.lang.Throwable#toString()
+ */
+ @Override
+ public String toString() {
+ String msg = super.toString();
+ if (cause != null) {
+ msg += "\nCause: " + cause.toString();
+ }
+ return msg;
+ }
+
+ }
+}
diff --git a/src/com/vaadin/event/MethodEventSource.java b/src/com/vaadin/event/MethodEventSource.java
new file mode 100644
index 0000000000..3fd8ef68be
--- /dev/null
+++ b/src/com/vaadin/event/MethodEventSource.java
@@ -0,0 +1,162 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.event;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+/**
+ * <p>
+ * Interface for classes supporting registration of methods as event receivers.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package
+ * documentation}.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+public interface MethodEventSource extends Serializable {
+
+ /**
+ * <p>
+ * Registers a new event listener with the specified activation method to
+ * listen events generated by this component. If the activation method does
+ * not have any arguments the event object will not be passed to it when
+ * it's called.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package
+ * documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the type of the listened event. Events of this type or its
+ * subclasses activate the listener.
+ * @param object
+ * the object instance who owns the activation method.
+ * @param method
+ * the activation method.
+ * @throws java.lang.IllegalArgumentException
+ * unless <code>method</code> has exactly one match in
+ * <code>object</code>
+ */
+ public void addListener(Class eventType, Object object, Method method);
+
+ /**
+ * <p>
+ * Registers a new listener with the specified activation method to listen
+ * events generated by this component. If the activation method does not
+ * have any arguments the event object will not be passed to it when it's
+ * called.
+ * </p>
+ *
+ * <p>
+ * This version of <code>addListener</code> gets the name of the activation
+ * method as a parameter. The actual method is reflected from
+ * <code>object</code>, and unless exactly one match is found,
+ * <code>java.lang.IllegalArgumentException</code> is thrown.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package
+ * documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the type of the listened event. Events of this type or its
+ * subclasses activate the listener.
+ * @param object
+ * the object instance who owns the activation method.
+ * @param methodName
+ * the name of the activation method.
+ * @throws java.lang.IllegalArgumentException
+ * unless <code>method</code> has exactly one match in
+ * <code>object</code>
+ */
+ public void addListener(Class eventType, Object object, String methodName);
+
+ /**
+ * Removes all registered listeners matching the given parameters. Since
+ * this method receives the event type and the listener object as
+ * parameters, it will unregister all <code>object</code>'s methods that are
+ * registered to listen to events of type <code>eventType</code> generated
+ * by this component.
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package
+ * documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the exact event type the <code>object</code> listens to.
+ * @param target
+ * the target object that has registered to listen to events of
+ * type <code>eventType</code> with one or more methods.
+ */
+ public void removeListener(Class eventType, Object target);
+
+ /**
+ * Removes one registered listener method. The given method owned by the
+ * given object will no longer be called when the specified events are
+ * generated by this component.
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package
+ * documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the exact event type the <code>object</code> listens to.
+ * @param target
+ * the target object that has registered to listen to events of
+ * type eventType with one or more methods.
+ * @param method
+ * the method owned by the target that's registered to listen to
+ * events of type eventType.
+ */
+ public void removeListener(Class eventType, Object target, Method method);
+
+ /**
+ * <p>
+ * Removes one registered listener method. The given method owned by the
+ * given object will no longer be called when the specified events are
+ * generated by this component.
+ * </p>
+ *
+ * <p>
+ * This version of <code>removeListener</code> gets the name of the
+ * activation method as a parameter. The actual method is reflected from the
+ * target, and unless exactly one match is found,
+ * <code>java.lang.IllegalArgumentException</code> is thrown.
+ * </p>
+ *
+ * <p>
+ * For more information on the inheritable event mechanism see the
+ * {@link com.vaadin.event com.vaadin.event package
+ * documentation}.
+ * </p>
+ *
+ * @param eventType
+ * the exact event type the <code>object</code> listens to.
+ * @param target
+ * the target object that has registered to listen to events of
+ * type <code>eventType</code> with one or more methods.
+ * @param methodName
+ * the name of the method owned by <code>target</code> that's
+ * registered to listen to events of type <code>eventType</code>.
+ */
+ public void removeListener(Class eventType, Object target, String methodName);
+}
diff --git a/src/com/vaadin/event/ShortcutAction.java b/src/com/vaadin/event/ShortcutAction.java
new file mode 100644
index 0000000000..5b82f51448
--- /dev/null
+++ b/src/com/vaadin/event/ShortcutAction.java
@@ -0,0 +1,188 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.event;
+
+import java.io.Serializable;
+
+import com.vaadin.terminal.Resource;
+
+/**
+ * Extends Action class with keyboard bindings. TODO: fix documentation.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @since 4.0.1
+ */
+@SuppressWarnings("serial")
+public class ShortcutAction extends Action {
+
+ private final int keyCode;
+
+ private final int[] modifiers;
+
+ public ShortcutAction(String caption, int kc, int[] m) {
+ super(caption);
+ keyCode = kc;
+ modifiers = m;
+ }
+
+ public ShortcutAction(String caption, Resource icon, int kc, int[] m) {
+ super(caption, icon);
+ keyCode = kc;
+ modifiers = m;
+ }
+
+ public int getKeyCode() {
+ return keyCode;
+ }
+
+ public int[] getModifiers() {
+ return modifiers;
+ }
+
+ /**
+ * Key codes that can be used for shortcuts
+ *
+ */
+ public interface KeyCode extends Serializable {
+ public static final int ENTER = 13;
+
+ public static final int ESCAPE = 27;
+
+ public static final int PAGE_UP = 33;
+
+ public static final int PAGE_DOWN = 34;
+
+ public static final int TAB = 9;
+
+ public static final int ARROW_LEFT = 37;
+
+ public static final int ARROW_UP = 38;
+
+ public static final int ARROW_RIGHT = 39;
+
+ public static final int ARROW_DOWN = 40;
+
+ public static final int BACKSPACE = 8;
+
+ public static final int DELETE = 46;
+
+ public static final int INSERT = 45;
+
+ public static final int END = 35;
+
+ public static final int HOME = 36;
+
+ public static final int F1 = 112;
+
+ public static final int F2 = 113;
+
+ public static final int F3 = 114;
+
+ public static final int F4 = 115;
+
+ public static final int F5 = 116;
+
+ public static final int F6 = 117;
+
+ public static final int F7 = 118;
+
+ public static final int F8 = 119;
+
+ public static final int F9 = 120;
+
+ public static final int F10 = 121;
+
+ public static final int F11 = 122;
+
+ public static final int F12 = 123;
+
+ public static final int A = 65;
+
+ public static final int B = 66;
+
+ public static final int C = 67;
+
+ public static final int D = 68;
+
+ public static final int E = 69;
+
+ public static final int F = 70;
+
+ public static final int G = 71;
+
+ public static final int H = 72;
+
+ public static final int I = 73;
+
+ public static final int J = 74;
+
+ public static final int K = 75;
+
+ public static final int L = 76;
+
+ public static final int M = 77;
+
+ public static final int N = 78;
+
+ public static final int O = 79;
+
+ public static final int P = 80;
+
+ public static final int Q = 81;
+
+ public static final int R = 82;
+
+ public static final int S = 83;
+
+ public static final int T = 84;
+
+ public static final int U = 85;
+
+ public static final int V = 86;
+
+ public static final int W = 87;
+
+ public static final int X = 88;
+
+ public static final int Y = 89;
+
+ public static final int Z = 90;
+
+ public static final int NUM0 = 48;
+
+ public static final int NUM1 = 49;
+
+ public static final int NUM2 = 50;
+
+ public static final int NUM3 = 51;
+
+ public static final int NUM4 = 52;
+
+ public static final int NUM5 = 53;
+
+ public static final int NUM6 = 54;
+
+ public static final int NUM7 = 55;
+
+ public static final int NUM8 = 56;
+
+ public static final int NUM9 = 57;
+
+ public static final int SPACEBAR = 32;
+ }
+
+ /**
+ * Modifier key constants
+ *
+ */
+ public interface ModifierKey extends Serializable {
+ public static final int SHIFT = 16;
+
+ public static final int CTRL = 17;
+
+ public static final int ALT = 18;
+ }
+}
diff --git a/src/com/vaadin/event/package.html b/src/com/vaadin/event/package.html
new file mode 100644
index 0000000000..b44626125d
--- /dev/null
+++ b/src/com/vaadin/event/package.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+
+<body bgcolor="white">
+
+<!-- Package summary here -->
+
+<p>Provides classes and interfaces for the inheritable event
+model. The model supports inheritable events and a flexible way of
+registering and unregistering event listeners. It's a fundamental building
+block of the IT Mill Toolkit, and as it is included in
+{@link com.vaadin.ui.AbstractComponent}, all UI components
+automatically support it.</p>
+
+<h2>Package Specification</h2>
+
+<p>The core of the event model is the inheritable event class
+hierarchy, and the {@link com.vaadin.event.EventRouter EventRouter}
+which provide a simple, ubiquitous mechanism to transport events to all
+interested parties.</p>
+
+<p>The power of the event inheritance arises from the possibility of
+receiving not only the events of the registered type, <i>but also the
+ones which are inherited from it</i>. For example, let's assume that there
+are the events <code>GeneralEvent</code> and <code>SpecializedEvent</code>
+so that the latter inherits the former. Furthermore we have an object
+<code>A</code> which registers to receive <code>GeneralEvent</code> type
+events from the object <code>B</code>. <code>A</code> would of course
+receive all <code>GeneralEvent</code>s generated by <code>B</code>, but in
+addition to this, <code>A</code> would also receive all
+<code>SpecializedEvent</code>s generated by <code>B</code>. However, if
+<code>B</code> generates some other events that do not have
+<code>GeneralEvent</code> as an ancestor, <code>A</code> would not receive
+them unless it registers to listen for them, too.</p>
+
+<p>The interface to attaching and detaching listeners to and from an object
+works with methods. One specifies the event that should trigger the listener,
+the trigger method that should be called when a suitable event occurs and the
+object owning the method. From these a new listener is constructed and added
+to the event router of the specified component.</p>
+
+<p>The interface is defined in
+{@link com.vaadin.event.MethodEventSource MethodEventSource}, and a
+straightforward implementation of it is defined in
+{@link com.vaadin.event.EventRouter EventRouter} which also includes
+a method to actually fire the events.</p>
+
+<p>All fired events are passed to all registered listeners, which are of
+type {@link com.vaadin.event.ListenerMethod ListenerMethod}. The
+listener then checks if the event type matches with the specified event
+type and calls the specified trigger method if it does.</p>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>