diff options
author | Henri Sara <henri.sara@itmill.com> | 2009-05-11 09:19:03 +0000 |
---|---|---|
committer | Henri Sara <henri.sara@itmill.com> | 2009-05-11 09:19:03 +0000 |
commit | adc8c0ad3573272c236040c3a76005b9e73a5737 (patch) | |
tree | a3860704dbd5b82dc6af38684b80f8ef79a32722 /src/com/vaadin/event | |
parent | 5abc870dda584d0c2fc47fd5eec4ae3de3fa240e (diff) | |
download | vaadin-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.java | 166 | ||||
-rw-r--r-- | src/com/vaadin/event/EventRouter.java | 150 | ||||
-rw-r--r-- | src/com/vaadin/event/ItemClickEvent.java | 155 | ||||
-rw-r--r-- | src/com/vaadin/event/ListenerMethod.java | 641 | ||||
-rw-r--r-- | src/com/vaadin/event/MethodEventSource.java | 162 | ||||
-rw-r--r-- | src/com/vaadin/event/ShortcutAction.java | 188 | ||||
-rw-r--r-- | src/com/vaadin/event/package.html | 58 |
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> |