summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMarc Englund <marc.englund@itmill.com>2010-03-23 08:31:20 +0000
committerMarc Englund <marc.englund@itmill.com>2010-03-23 08:31:20 +0000
commitcc6fa776179274fb514b76cbfabddcac8d8469b9 (patch)
treed292b2b65805931accdc588170560af173e53cbf /src
parent12a9faf7aa438715805d08d3a42b63b0f18965a5 (diff)
downloadvaadin-framework-cc6fa776179274fb514b76cbfabddcac8d8469b9.tar.gz
vaadin-framework-cc6fa776179274fb514b76cbfabddcac8d8469b9.zip
Easier keyboard shortcuts for #875 - includes shorthand notation for ShortcutAction, Form is now handler in addition to Panel, AbstractField.addAction() delegates handling to containing Window. Javadoc and examples TBD
svn changeset:12026/svn branch:6.3
Diffstat (limited to 'src')
-rw-r--r--src/com/vaadin/event/Action.java18
-rw-r--r--src/com/vaadin/event/ActionManager.java224
-rw-r--r--src/com/vaadin/event/ShortcutAction.java121
-rw-r--r--src/com/vaadin/event/ShortcutListener.java29
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java9
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VForm.java33
-rw-r--r--src/com/vaadin/ui/AbstractField.java73
-rw-r--r--src/com/vaadin/ui/Button.java47
-rw-r--r--src/com/vaadin/ui/Form.java58
-rw-r--r--src/com/vaadin/ui/Panel.java126
-rw-r--r--src/com/vaadin/ui/Window.java45
11 files changed, 693 insertions, 90 deletions
diff --git a/src/com/vaadin/event/Action.java b/src/com/vaadin/event/Action.java
index c3329e6d56..247def9ee6 100644
--- a/src/com/vaadin/event/Action.java
+++ b/src/com/vaadin/event/Action.java
@@ -72,6 +72,24 @@ public class Action implements Serializable {
return icon;
}
+ public interface Listener {
+ public void handleAction(Object sender, Object target);
+ }
+
+ public interface Notifier extends Container {
+ public <T extends Action & Action.Listener> boolean addAction(T action);
+
+ public <T extends Action & Action.Listener> boolean removeAction(
+ T action);
+ }
+
+ public interface NotifierProxy {
+ public <T extends Action & Action.Listener> boolean addAction(T action);
+
+ public <T extends Action & Action.Listener> boolean removeAction(
+ T action);
+ }
+
/**
* Interface implemented by classes who wish to handle actions.
*
diff --git a/src/com/vaadin/event/ActionManager.java b/src/com/vaadin/event/ActionManager.java
new file mode 100644
index 0000000000..4ff8a36121
--- /dev/null
+++ b/src/com/vaadin/event/ActionManager.java
@@ -0,0 +1,224 @@
+package com.vaadin.event;
+
+import java.util.HashSet;
+import java.util.Map;
+
+import com.vaadin.event.Action.Container;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.terminal.KeyMapper;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.ui.Component;
+
+/**
+ * Javadoc TODO
+ *
+ * Notes:
+ * <p>
+ * Empties the keymapper for each repaint to avoid leaks; can cause problems in
+ * the future if the client assumes key don't change. (if lazyloading, one must
+ * not cache results)
+ * </p>
+ *
+ *
+ */
+public class ActionManager implements Action.Container, Action.Handler,
+ Action.Notifier {
+
+ private static final long serialVersionUID = 1641868163608066491L;
+
+ /** List of action handlers */
+ protected HashSet<Action> ownActions = null;
+
+ /** List of action handlers */
+ protected HashSet<Handler> actionHandlers = null;
+
+ /** Action mapper */
+ protected KeyMapper actionMapper = null;
+
+ protected Component viewer;
+
+ public ActionManager() {
+
+ }
+
+ public <T extends Component & Container> ActionManager(T viewer) {
+ this.viewer = viewer;
+ }
+
+ private void requestRepaint() {
+ if (viewer != null) {
+ viewer.requestRepaint();
+ }
+ }
+
+ public <T extends Component & Container> void setViewer(T viewer) {
+ if (viewer == this.viewer) {
+ return;
+ }
+ if (this.viewer != null) {
+ ((Container) this.viewer).removeActionHandler(this);
+ }
+ requestRepaint(); // this goes to the old viewer
+ if (viewer != null) {
+ viewer.addActionHandler(this);
+ }
+ this.viewer = viewer;
+ requestRepaint(); // this goes to the new viewer
+ }
+
+ public <T extends Action & Action.Listener> boolean addAction(T action) {
+ if (ownActions == null) {
+ ownActions = new HashSet<Action>();
+ }
+ if (ownActions.add(action)) {
+ requestRepaint();
+ return true;
+ }
+ return false;
+ }
+
+ public <T extends Action & Action.Listener> boolean removeAction(T action) {
+ if (ownActions != null) {
+ if (ownActions.remove(action)) {
+ requestRepaint();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void addActionHandler(Handler actionHandler) {
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new HashSet<Handler>();
+ }
+
+ if (actionHandlers.add(actionHandler)) {
+ requestRepaint();
+ }
+ }
+ }
+
+ public void removeActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+
+ if (actionHandlers.remove(actionHandler)) {
+ requestRepaint();
+ }
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ }
+
+ }
+ }
+
+ public void removeAllActionHandlers() {
+ if (actionHandlers != null) {
+ actionHandlers = null;
+ requestRepaint();
+ }
+ }
+
+ public void paintActions(Object actionTarget, PaintTarget paintTarget)
+ throws PaintException {
+
+ actionMapper = null;
+
+ HashSet<Action> actions = new HashSet<Action>();
+ if (actionHandlers != null) {
+ for (Action.Handler handler : actionHandlers) {
+ Action[] as = handler.getActions(actionTarget, this.viewer);
+ if (as != null) {
+ for (Action action : as) {
+ actions.add(action);
+ }
+ }
+ }
+ }
+ if (ownActions != null) {
+ actions.addAll(ownActions);
+ }
+
+ if (!actions.isEmpty()) {
+ actionMapper = new KeyMapper();
+
+ paintTarget.addVariable(this.viewer, "action", "");
+ paintTarget.startTag("actions");
+
+ for (final Action a : actions) {
+ paintTarget.startTag("action");
+ final String akey = actionMapper.key(a);
+ paintTarget.addAttribute("key", akey);
+ if (a.getCaption() != null) {
+ paintTarget.addAttribute("caption", a.getCaption());
+ }
+ if (a.getIcon() != null) {
+ paintTarget.addAttribute("icon", a.getIcon());
+ }
+ if (a instanceof ShortcutAction) {
+ final ShortcutAction sa = (ShortcutAction) a;
+ paintTarget.addAttribute("kc", sa.getKeyCode());
+ final int[] modifiers = sa.getModifiers();
+ if (modifiers != null) {
+ final String[] smodifiers = new String[modifiers.length];
+ for (int i = 0; i < modifiers.length; i++) {
+ smodifiers[i] = String.valueOf(modifiers[i]);
+ }
+ paintTarget.addAttribute("mk", smodifiers);
+ }
+ }
+ paintTarget.endTag("action");
+ }
+
+ paintTarget.endTag("actions");
+ }
+
+ }
+
+ public void handleActions(Map variables, Container sender) {
+ if (variables.containsKey("action") && actionMapper != null) {
+ final String key = (String) variables.get("action");
+ final Action action = (Action) actionMapper.get(key);
+ final Object target = variables.get("actiontarget");
+ if (action != null) {
+ handleAction(action, sender, target);
+ }
+ }
+ }
+
+ public Action[] getActions(Object target, Object sender) {
+ HashSet<Action> actions = new HashSet<Action>();
+ if (ownActions != null) {
+ for (Action a : ownActions) {
+ actions.add(a);
+ }
+ }
+ if (actionHandlers != null) {
+ for (Action.Handler h : actionHandlers) {
+ Action[] as = h.getActions(target, sender);
+ if (as != null) {
+ for (Action a : as) {
+ actions.add(a);
+ }
+ }
+ }
+ }
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ public void handleAction(Action action, Object sender, Object target) {
+ if (actionHandlers != null) {
+ for (Handler h : actionHandlers) {
+ h.handleAction(action, sender, target);
+ }
+ }
+ if (ownActions != null && ownActions.contains(action)
+ && action instanceof Action.Listener) {
+ ((Action.Listener) action).handleAction(sender, target);
+ }
+ }
+
+}
diff --git a/src/com/vaadin/event/ShortcutAction.java b/src/com/vaadin/event/ShortcutAction.java
index 5b82f51448..d3531ad7fa 100644
--- a/src/com/vaadin/event/ShortcutAction.java
+++ b/src/com/vaadin/event/ShortcutAction.java
@@ -5,6 +5,8 @@
package com.vaadin.event;
import java.io.Serializable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import com.vaadin.terminal.Resource;
@@ -34,6 +36,125 @@ public class ShortcutAction extends Action {
modifiers = m;
}
+ /**
+ * Used in the caption shorthand notation to indicate the ALT modifier.
+ */
+ public static final char MNEMONIC_CHAR_ALT = '&';
+ /**
+ * Used in the caption shorthand notation to indicate the SHIFT modifier.
+ */
+ public static final char MNEMONIC_CHAR_SHIFT = '%';
+ /**
+ * Used in the caption shorthand notation to indicate the CTRL modifier.
+ */
+ public static final char MNEMONIC_CHAR_CTRL = '^';
+
+ // regex-quote (escape) the characters
+ private static final String MNEMONIC_ALT = Pattern.quote(Character
+ .toString(MNEMONIC_CHAR_ALT));
+ private static final String MNEMONIC_SHIFT = Pattern.quote(Character
+ .toString(MNEMONIC_CHAR_SHIFT));
+ private static final String MNEMONIC_CTRL = Pattern.quote(Character
+ .toString(MNEMONIC_CHAR_CTRL));
+ // Used for replacing escaped chars, e.g && with &
+ private static final Pattern MNEMONICS_ESCAPE = Pattern.compile("("
+ + MNEMONIC_ALT + "?)" + MNEMONIC_ALT + "|(" + MNEMONIC_SHIFT + "?)"
+ + MNEMONIC_SHIFT + "|(" + MNEMONIC_CTRL + "?)" + MNEMONIC_CTRL);
+ // Used for removing escaped chars, only leaving real mnemonics
+ private static final Pattern MNEMONICS_REMOVE = Pattern.compile("(["
+ + MNEMONIC_ALT + "|" + MNEMONIC_SHIFT + "|" + MNEMONIC_CTRL
+ + "])\\1");
+ // Mnemonic char, optionally followed by another, and optionally a third
+ private static final Pattern MNEMONICS = Pattern.compile("(" + MNEMONIC_ALT
+ + "|" + MNEMONIC_SHIFT + "|" + MNEMONIC_CTRL + ")(?!\\1)(?:("
+ + MNEMONIC_ALT + "|" + MNEMONIC_SHIFT + "|" + MNEMONIC_CTRL
+ + ")(?!\\1|\\2))?(?:(" + MNEMONIC_ALT + "|" + MNEMONIC_SHIFT + "|"
+ + MNEMONIC_CTRL + ")(?!\\1|\\2|\\3))?.");
+
+ /**
+ * Constructs a ShortcutAction using a shorthand notation to encode the
+ * keycode and modifiers in the caption.
+ * <p>
+ * Insert one or more modifier characters before the character to use as
+ * keycode. E.g <code>"&Save"</code> will make a shortcut responding to
+ * ALT-S, <code>"E^xit"</code> will respond to CTRL-X.<br/>
+ * Multiple modifiers can be used, e.g <code>"&^Delete"</code> will respond
+ * to CTRL-ALT-D (the order of the modifier characters is not important).
+ * </p>
+ * <p>
+ * The modifier characters will be removed from the caption. The modifier
+ * character is be escaped by itself: two consecutive characters are turned
+ * into the original character w/o the special meaning. E.g
+ * <code>"Save&&&close"</code> will respond to ALT-C, and the caption will
+ * say "Save&close".
+ * </p>
+ *
+ * @param shorthandCaption
+ * the caption in modifier shorthand
+ */
+ public ShortcutAction(String shorthandCaption) {
+ this(shorthandCaption, null);
+ }
+
+ /**
+ * Constructs a ShortcutAction using a shorthand notation to encode the
+ * keycode a in the caption.
+ * <p>
+ * This works the same way as {@link #ShortcutAction(String)}, with the
+ * exception that the modifiers given override those indicated in the
+ * caption. I.e use any of the modifier characters in the caption to
+ * indicate the keycode, but the modifier will be the given set.<br/>
+ * E.g
+ * <code>new ShortcutAction("Do &stuff", new int[]{ShortcutAction.ModifierKey.CTRL}));</code>
+ * will respond to CTRL-S.
+ * </p>
+ *
+ * @param shorthandCaption
+ * @param modifierKeys
+ */
+ public ShortcutAction(String shorthandCaption, int[] modifierKeys) {
+ // && -> & etc
+ super(MNEMONICS_ESCAPE.matcher(shorthandCaption).replaceAll("$1$2$3"));
+ // replace escaped chars with something that won't accidentally match
+ shorthandCaption = MNEMONICS_REMOVE.matcher(shorthandCaption)
+ .replaceAll("\u001A");
+ Matcher matcher = MNEMONICS.matcher(shorthandCaption);
+ if (matcher.find()) {
+ String match = matcher.group();
+
+ // KeyCode from last char in match, lowercase
+ keyCode = Character.toLowerCase(matcher.group().charAt(
+ match.length() - 1));
+
+ // Given modifiers override this indicated in the caption
+ if (modifierKeys != null) {
+ modifiers = modifierKeys;
+ } else {
+ // Read modifiers from caption
+ int[] mod = new int[match.length() - 1];
+ for (int i = 0; i < mod.length; i++) {
+ int kc = match.charAt(i);
+ switch (kc) {
+ case MNEMONIC_CHAR_ALT:
+ mod[i] = ModifierKey.ALT;
+ break;
+ case MNEMONIC_CHAR_CTRL:
+ mod[i] = ModifierKey.CTRL;
+ break;
+ case MNEMONIC_CHAR_SHIFT:
+ mod[i] = ModifierKey.SHIFT;
+ break;
+ }
+ }
+ modifiers = mod;
+ }
+
+ } else {
+ keyCode = -1;
+ modifiers = modifierKeys;
+ }
+ }
+
public int getKeyCode() {
return keyCode;
}
diff --git a/src/com/vaadin/event/ShortcutListener.java b/src/com/vaadin/event/ShortcutListener.java
new file mode 100644
index 0000000000..7e041de47a
--- /dev/null
+++ b/src/com/vaadin/event/ShortcutListener.java
@@ -0,0 +1,29 @@
+package com.vaadin.event;
+
+import com.vaadin.event.Action.Listener;
+import com.vaadin.terminal.Resource;
+
+public abstract class ShortcutListener extends ShortcutAction implements
+ Listener {
+
+ private static final long serialVersionUID = 1L;
+
+ public ShortcutListener(String caption, int keyCode, int... modifierKeys) {
+ super(caption, keyCode, modifierKeys);
+ }
+
+ public ShortcutListener(String shorthandCaption, int... modifierKeys) {
+ super(shorthandCaption, modifierKeys);
+ }
+
+ public ShortcutListener(String caption, Resource icon, int keyCode,
+ int... modifierKeys) {
+ super(caption, icon, keyCode, modifierKeys);
+ }
+
+ public ShortcutListener(String shorthandCaption) {
+ super(shorthandCaption);
+ }
+
+ abstract public void handleAction(Object sender, Object target);
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java b/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java
index 3acdeab171..093534b9cc 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java
@@ -15,6 +15,7 @@ import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
/**
@@ -76,10 +77,16 @@ public class ShortcutActionHandler {
while (it.hasNext()) {
final ShortcutAction a = (ShortcutAction) it.next();
if (a.getShortcutCombination().equals(kc)) {
+ Element et = DOM.eventGetTarget(event);
+ final Paintable target = client.getPaintable(et);
DOM.eventPreventDefault(event);
- shakeTarget(DOM.eventGetTarget(event));
+ shakeTarget(et);
DeferredCommand.addCommand(new Command() {
public void execute() {
+ if (target != null) {
+ client.updateVariable(paintableId, "actiontarget",
+ target, false);
+ }
client.updateVariable(paintableId, "action",
a.getKey(), true);
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/VForm.java
index 01e6caae0d..9c4e6b87f8 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VForm.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VForm.java
@@ -6,8 +6,11 @@ package com.vaadin.terminal.gwt.client.ui;
import java.util.Set;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
@@ -22,6 +25,8 @@ import com.vaadin.terminal.gwt.client.VErrorMessage;
public class VForm extends ComplexPanel implements Container {
+ protected String id;
+
private String height = "";
private String width = "";
@@ -52,6 +57,8 @@ public class VForm extends ComplexPanel implements Container {
private boolean rendering = false;
+ ShortcutActionHandler shortcutHandler;
+
public VForm() {
setElement(DOM.createDiv());
DOM.appendChild(getElement(), fieldSet);
@@ -69,11 +76,20 @@ public class VForm extends ComplexPanel implements Container {
errorMessage.setStyleName(CLASSNAME + "-errormessage");
DOM.appendChild(fieldSet, errorMessage.getElement());
DOM.appendChild(fieldSet, footerContainer);
+
+ addDomHandler(new KeyDownHandler() {
+ public void onKeyDown(KeyDownEvent event) {
+ shortcutHandler.handleKeyboardEvent(Event.as(event
+ .getNativeEvent()));
+ return;
+ }
+ }, KeyDownEvent.getType());
}
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
rendering = true;
this.client = client;
+ id = uidl.getId();
if (client.updateComponent(this, uidl, false)) {
rendering = false;
@@ -126,7 +142,8 @@ public class VForm extends ComplexPanel implements Container {
// first render footer so it will be easier to handle relative height of
// main layout
- if (uidl.getChildCount() > 1) {
+ if (uidl.getChildCount() > 1
+ && !uidl.getChildUIDL(1).getTag().equals("actions")) {
// render footer
Container newFooter = (Container) client.getPaintable(uidl
.getChildUIDL(1));
@@ -162,6 +179,20 @@ public class VForm extends ComplexPanel implements Container {
}
lo.updateFromUIDL(layoutUidl, client);
+ // We may have actions attached to this panel
+ if (uidl.getChildCount() > 1) {
+ final int cnt = uidl.getChildCount();
+ for (int i = 1; i < cnt; i++) {
+ UIDL childUidl = uidl.getChildUIDL(i);
+ if (childUidl.getTag().equals("actions")) {
+ if (shortcutHandler == null) {
+ shortcutHandler = new ShortcutActionHandler(id, client);
+ }
+ shortcutHandler.updateActionMap(childUidl);
+ }
+ }
+ }
+
rendering = false;
}
diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java
index c3d5066600..413916f298 100644
--- a/src/com/vaadin/ui/AbstractField.java
+++ b/src/com/vaadin/ui/AbstractField.java
@@ -19,6 +19,9 @@ import com.vaadin.data.Property;
import com.vaadin.data.Validatable;
import com.vaadin.data.Validator;
import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.event.Action;
+import com.vaadin.event.ActionManager;
+import com.vaadin.event.ShortcutListener;
import com.vaadin.terminal.CompositeErrorMessage;
import com.vaadin.terminal.ErrorMessage;
import com.vaadin.terminal.PaintException;
@@ -52,7 +55,7 @@ import com.vaadin.terminal.PaintTarget;
*/
@SuppressWarnings("serial")
public abstract class AbstractField extends AbstractComponent implements Field,
- Property.ReadOnlyStatusChangeNotifier {
+ Property.ReadOnlyStatusChangeNotifier, Action.NotifierProxy {
/* Private members */
@@ -124,6 +127,12 @@ public abstract class AbstractField extends AbstractComponent implements Field,
*/
private boolean validationVisible = true;
+ /**
+ * Keeps track of the Actions added to this component; the actual
+ * handling/notifying is delegated, usually to the containing window.
+ */
+ protected ActionManager actionManager;
+
/* Component basics */
/*
@@ -1046,6 +1055,21 @@ public abstract class AbstractField extends AbstractComponent implements Field,
if (delayedFocus) {
focus();
}
+ if (actionManager != null && !(this instanceof Action.Container)) {
+ // Only for non Action.Containers because those want to paint
+ // actions themselves - e.g Form
+ actionManager.setViewer(getWindow());
+ }
+ }
+
+ @Override
+ public void detach() {
+ super.detach();
+ if (actionManager != null && !(this instanceof Action.Container)) {
+ // Only for non Action.Containers because those want to paint
+ // actions themselves - e.g Form
+ actionManager.setViewer((Window) null);
+ }
}
/**
@@ -1161,4 +1185,51 @@ public abstract class AbstractField extends AbstractComponent implements Field,
requestRepaint();
}
+ /*
+ * Actions
+ */
+
+ protected ActionManager getActionManager() {
+ if (actionManager == null) {
+ actionManager = new ActionManager();
+ if (getWindow() != null) {
+ actionManager.setViewer(getWindow());
+ }
+ }
+ return actionManager;
+ }
+
+ public <T extends Action & Action.Listener> boolean addAction(T action) {
+ return getActionManager().addAction(action);
+ }
+
+ public <T extends Action & Action.Listener> boolean removeAction(T action) {
+ if (actionManager == null) {
+ return actionManager.removeAction(action);
+ }
+ return false;
+ }
+
+ public static class FocusShortcut extends ShortcutListener {
+ protected Focusable focusable;
+
+ public FocusShortcut(Focusable focusable, String shorthandCaption) {
+ super(shorthandCaption);
+ this.focusable = focusable;
+ }
+
+ public FocusShortcut(Focusable focusable, int keyCode, int... modifiers) {
+ super(null, keyCode, modifiers);
+ this.focusable = focusable;
+ }
+
+ public FocusShortcut(Focusable focusable, int keyCode) {
+ this(focusable, keyCode, null);
+ }
+
+ @Override
+ public void handleAction(Object sender, Object target) {
+ this.focusable.focus();
+ }
+ }
} \ No newline at end of file
diff --git a/src/com/vaadin/ui/Button.java b/src/com/vaadin/ui/Button.java
index 63b90c2c7c..156c942901 100644
--- a/src/com/vaadin/ui/Button.java
+++ b/src/com/vaadin/ui/Button.java
@@ -10,6 +10,7 @@ import java.lang.reflect.Method;
import java.util.Map;
import com.vaadin.data.Property;
+import com.vaadin.event.ShortcutListener;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.gwt.client.ui.VButton;
@@ -347,4 +348,50 @@ public class Button extends AbstractField {
super.setInternalValue(newValue);
}
+ /*
+ * Actions
+ */
+
+ protected ClickShortcut clickMnemonic;
+
+ public ClickShortcut setClickMnemonic(int keyCode, int... modifiers) {
+ ClickShortcut old = clickMnemonic;
+ if (old != null) {
+ removeAction(old);
+ }
+ clickMnemonic = new ClickShortcut(this, keyCode, modifiers);
+ addAction(clickMnemonic);
+ return old;
+ }
+
+ public void removeClickMnemonic() {
+ if (clickMnemonic != null) {
+ removeAction(clickMnemonic);
+ clickMnemonic = null;
+ }
+ }
+
+ public static class ClickShortcut extends ShortcutListener {
+ protected Button button;
+
+ public ClickShortcut(Button button, String shorthandCaption) {
+ super(shorthandCaption);
+ this.button = button;
+ }
+
+ public ClickShortcut(Button button, int keyCode, int... modifiers) {
+ super(null, keyCode, modifiers);
+ this.button = button;
+ }
+
+ public ClickShortcut(Button button, int keyCode) {
+ this(button, keyCode, null);
+ }
+
+ @Override
+ public void handleAction(Object sender, Object target) {
+ this.button.fireClick();
+ }
+ }
+
}
diff --git a/src/com/vaadin/ui/Form.java b/src/com/vaadin/ui/Form.java
index 4f417c4b94..7a4c8159ac 100644
--- a/src/com/vaadin/ui/Form.java
+++ b/src/com/vaadin/ui/Form.java
@@ -9,6 +9,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.Map;
import com.vaadin.data.Buffered;
import com.vaadin.data.Item;
@@ -17,6 +18,9 @@ import com.vaadin.data.Validatable;
import com.vaadin.data.Validator;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.util.BeanItem;
+import com.vaadin.event.Action;
+import com.vaadin.event.ActionManager;
+import com.vaadin.event.Action.Handler;
import com.vaadin.terminal.CompositeErrorMessage;
import com.vaadin.terminal.ErrorMessage;
import com.vaadin.terminal.PaintException;
@@ -57,7 +61,7 @@ import com.vaadin.terminal.gwt.client.ui.VForm;
@SuppressWarnings("serial")
@ClientWidget(VForm.class)
public class Form extends AbstractField implements Item.Editor, Buffered, Item,
- Validatable {
+ Validatable, Action.Container {
private Object propertyValue;
@@ -133,6 +137,12 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item,
private int gridlayoutCursorY = -1;
/**
+ * Keeps track of the Actions added to this component, and manages the
+ * painting and handling as well.
+ */
+ ActionManager actionManager = new ActionManager(this);
+
+ /**
* Contructs a new form with default layout.
*
* <p>
@@ -179,10 +189,25 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item,
@Override
public void paintContent(PaintTarget target) throws PaintException {
super.paintContent(target);
+
layout.paint(target);
if (formFooter != null) {
formFooter.paint(target);
}
+
+ if (actionManager != null) {
+ actionManager.paintActions(null, target);
+ }
+ }
+
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ super.changeVariables(source, variables);
+
+ // Actions
+ if (actionManager != null) {
+ actionManager.handleActions(variables, this);
+ }
}
/**
@@ -1254,4 +1279,35 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item,
}
}
+ /*
+ * ACTIONS
+ */
+
+ protected ActionManager getActionManager() {
+ if (actionManager == null) {
+ actionManager = new ActionManager();
+ actionManager.setViewer(this);
+ }
+ return actionManager;
+ }
+
+ public void addActionHandler(Handler actionHandler) {
+ getActionManager().addActionHandler(actionHandler);
+ }
+
+ public void removeActionHandler(Handler actionHandler) {
+ if (actionManager != null) {
+ actionManager.removeActionHandler(actionHandler);
+ }
+ }
+
+ /**
+ * Removes all action handlers
+ */
+ public void removeAllActionHandlers() {
+ if (actionManager != null) {
+ actionManager.removeAllActionHandlers();
+ }
+ }
+
}
diff --git a/src/com/vaadin/ui/Panel.java b/src/com/vaadin/ui/Panel.java
index f4ce352687..ee39a1797e 100644
--- a/src/com/vaadin/ui/Panel.java
+++ b/src/com/vaadin/ui/Panel.java
@@ -5,15 +5,13 @@
package com.vaadin.ui;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.Map;
import com.vaadin.event.Action;
-import com.vaadin.event.ShortcutAction;
+import com.vaadin.event.ActionManager;
import com.vaadin.event.Action.Handler;
import com.vaadin.event.MouseEvents.ClickEvent;
import com.vaadin.event.MouseEvents.ClickListener;
-import com.vaadin.terminal.KeyMapper;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.Scrollable;
@@ -34,7 +32,8 @@ import com.vaadin.ui.themes.Runo;
@ClientWidget(VPanel.class)
public class Panel extends AbstractComponentContainer implements Scrollable,
ComponentContainer.ComponentAttachListener,
- ComponentContainer.ComponentDetachListener, Action.Container {
+ ComponentContainer.ComponentDetachListener, Action.Container,
+ Action.Notifier {
private static final String CLICK_EVENT = VPanel.CLICK_EVENT_IDENTIFIER;
@@ -70,11 +69,11 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
*/
private boolean scrollable = false;
- /** List of action handlers */
- private LinkedList actionHandlers = null;
-
- /** Action mapper */
- private KeyMapper actionMapper = null;
+ /**
+ * Keeps track of the Actions added to this component, and manages the
+ * painting and handling as well.
+ */
+ protected ActionManager actionManager;
/**
* Creates a new empty panel. A VerticalLayout is used as content.
@@ -244,44 +243,9 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
target.addVariable(this, "scrollTop", getScrollTop());
}
- target.addVariable(this, "action", "");
- target.startTag("actions");
-
- if (actionHandlers != null && !actionHandlers.isEmpty()) {
- for (final Iterator ahi = actionHandlers.iterator(); ahi.hasNext();) {
- final Action[] aa = ((Action.Handler) ahi.next()).getActions(
- null, this);
- if (aa != null) {
- for (int ai = 0; ai < aa.length; ai++) {
- final Action a = aa[ai];
- target.startTag("action");
- final String akey = actionMapper.key(aa[ai]);
- target.addAttribute("key", akey);
- if (a.getCaption() != null) {
- target.addAttribute("caption", a.getCaption());
- }
- if (a.getIcon() != null) {
- target.addAttribute("icon", a.getIcon());
- }
- if (a instanceof ShortcutAction) {
- final ShortcutAction sa = (ShortcutAction) a;
- target.addAttribute("kc", sa.getKeyCode());
- final int[] modifiers = sa.getModifiers();
- if (modifiers != null) {
- final String[] smodifiers = new String[modifiers.length];
- for (int i = 0; i < modifiers.length; i++) {
- smodifiers[i] = String
- .valueOf(modifiers[i]);
- }
- target.addAttribute("mk", smodifiers);
- }
- }
- target.endTag("action");
- }
- }
- }
+ if (actionManager != null) {
+ actionManager.paintActions(null, target);
}
- target.endTag("actions");
}
@Override
@@ -369,16 +333,8 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
}
// Actions
- if (variables.containsKey("action")) {
- final String key = (String) variables.get("action");
- final Action action = (Action) actionMapper.get(key);
- if (action != null && actionHandlers != null) {
- Object[] array = actionHandlers.toArray();
- for (int i = 0; i < array.length; i++) {
- ((Action.Handler) array[i])
- .handleAction(action, this, this);
- }
- }
+ if (actionManager != null) {
+ actionManager.handleActions(variables, this);
}
}
@@ -529,39 +485,37 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
content.removeAllComponents();
}
- public void addActionHandler(Handler actionHandler) {
- if (actionHandler != null) {
-
- if (actionHandlers == null) {
- actionHandlers = new LinkedList();
- actionMapper = new KeyMapper();
- }
-
- if (!actionHandlers.contains(actionHandler)) {
- actionHandlers.add(actionHandler);
- requestRepaint();
- }
+ /*
+ * ACTIONS
+ */
+ protected ActionManager getActionManager() {
+ if (actionManager == null) {
+ actionManager = new ActionManager();
+ actionManager.setViewer(this);
}
-
+ return actionManager;
}
- /**
- * Removes an action handler.
- *
- * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
- */
- public void removeActionHandler(Action.Handler actionHandler) {
-
- if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+ public <T extends Action & com.vaadin.event.Action.Listener> boolean addAction(
+ T action) {
+ return getActionManager().addAction(action);
+ }
- actionHandlers.remove(actionHandler);
+ public <T extends Action & com.vaadin.event.Action.Listener> boolean removeAction(
+ T action) {
+ if (actionManager == null) {
+ return actionManager.removeAction(action);
+ }
+ return false;
+ }
- if (actionHandlers.isEmpty()) {
- actionHandlers = null;
- actionMapper = null;
- }
+ public void addActionHandler(Handler actionHandler) {
+ getActionManager().addActionHandler(actionHandler);
+ }
- requestRepaint();
+ public void removeActionHandler(Handler actionHandler) {
+ if (actionManager != null) {
+ actionManager.removeActionHandler(actionHandler);
}
}
@@ -569,9 +523,9 @@ public class Panel extends AbstractComponentContainer implements Scrollable,
* Removes all action handlers
*/
public void removeAllActionHandlers() {
- actionHandlers = null;
- actionMapper = null;
- requestRepaint();
+ if (actionManager != null) {
+ actionManager.removeAllActionHandlers();
+ }
}
/**
diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java
index f4657d81f6..bc037c18fb 100644
--- a/src/com/vaadin/ui/Window.java
+++ b/src/com/vaadin/ui/Window.java
@@ -17,6 +17,7 @@ import java.util.Map;
import java.util.Set;
import com.vaadin.Application;
+import com.vaadin.event.ShortcutListener;
import com.vaadin.terminal.DownloadStream;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
@@ -1798,4 +1799,48 @@ public class Window extends Panel implements URIHandler, ParameterHandler {
requestRepaint();
}
+ /*
+ * Actions
+ */
+ protected CloseShortcut closeMnemonic;
+
+ public CloseShortcut setCloseMnemonic(int keyCode, int... modifiers) {
+ CloseShortcut old = closeMnemonic;
+ if (old != null) {
+ removeAction(old);
+ }
+ closeMnemonic = new CloseShortcut(this, keyCode, modifiers);
+ addAction(closeMnemonic);
+ return old;
+ }
+
+ public void removeCloseMnemonic() {
+ if (closeMnemonic != null) {
+ removeAction(closeMnemonic);
+ closeMnemonic = null;
+ }
+ }
+
+ public static class CloseShortcut extends ShortcutListener {
+ protected Window window;
+
+ public CloseShortcut(Window window, String shorthandCaption) {
+ super(shorthandCaption);
+ this.window = window;
+ }
+
+ public CloseShortcut(Window window, int keyCode, int... modifiers) {
+ super(null, keyCode, modifiers);
+ this.window = window;
+ }
+
+ public CloseShortcut(Window window, int keyCode) {
+ this(window, keyCode, null);
+ }
+
+ @Override
+ public void handleAction(Object sender, Object target) {
+ this.window.close();
+ }
+ }
}