diff options
author | Marc Englund <marc.englund@itmill.com> | 2010-03-23 08:31:20 +0000 |
---|---|---|
committer | Marc Englund <marc.englund@itmill.com> | 2010-03-23 08:31:20 +0000 |
commit | cc6fa776179274fb514b76cbfabddcac8d8469b9 (patch) | |
tree | d292b2b65805931accdc588170560af173e53cbf /src | |
parent | 12a9faf7aa438715805d08d3a42b63b0f18965a5 (diff) | |
download | vaadin-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.java | 18 | ||||
-rw-r--r-- | src/com/vaadin/event/ActionManager.java | 224 | ||||
-rw-r--r-- | src/com/vaadin/event/ShortcutAction.java | 121 | ||||
-rw-r--r-- | src/com/vaadin/event/ShortcutListener.java | 29 | ||||
-rw-r--r-- | src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java | 9 | ||||
-rw-r--r-- | src/com/vaadin/terminal/gwt/client/ui/VForm.java | 33 | ||||
-rw-r--r-- | src/com/vaadin/ui/AbstractField.java | 73 | ||||
-rw-r--r-- | src/com/vaadin/ui/Button.java | 47 | ||||
-rw-r--r-- | src/com/vaadin/ui/Form.java | 58 | ||||
-rw-r--r-- | src/com/vaadin/ui/Panel.java | 126 | ||||
-rw-r--r-- | src/com/vaadin/ui/Window.java | 45 |
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(); + } + } } |