]> source.dussan.org Git - vaadin-framework.git/commitdiff
Easier keyboard shortcuts for #875 - includes shorthand notation for ShortcutAction...
authorMarc Englund <marc.englund@itmill.com>
Tue, 23 Mar 2010 08:31:20 +0000 (08:31 +0000)
committerMarc Englund <marc.englund@itmill.com>
Tue, 23 Mar 2010 08:31:20 +0000 (08:31 +0000)
svn changeset:12026/svn branch:6.3

src/com/vaadin/event/Action.java
src/com/vaadin/event/ActionManager.java [new file with mode: 0644]
src/com/vaadin/event/ShortcutAction.java
src/com/vaadin/event/ShortcutListener.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/ShortcutActionHandler.java
src/com/vaadin/terminal/gwt/client/ui/VForm.java
src/com/vaadin/ui/AbstractField.java
src/com/vaadin/ui/Button.java
src/com/vaadin/ui/Form.java
src/com/vaadin/ui/Panel.java
src/com/vaadin/ui/Window.java

index c3329e6d5606b313ff713003e0ea72acc466aafc..247def9ee68062fca3e14bb8d6f4bc0711b018da 100644 (file)
@@ -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 (file)
index 0000000..4ff8a36
--- /dev/null
@@ -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);
+        }
+    }
+
+}
index 5b82f51448ca05d251d7e18ab10bafd7ec40b5f8..d3531ad7fa5ea7b55818307ce0e262154bd8d26a 100644 (file)
@@ -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 (file)
index 0000000..7e041de
--- /dev/null
@@ -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);
+}
index 3acdeab171dcb9ed883649b3b335afb8999c81a2..093534b9cce5eb92065dd1ebc5061236d8d64917 100644 (file)
@@ -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);
                     }
index 01e6caae0d3b02b2d18a5a8368152ff7eef4cae9..9c4e6b87f8919c85caa4bc7b18ff244a10142776 100644 (file)
@@ -6,8 +6,11 @@ package com.vaadin.terminal.gwt.client.ui;
 \r
 import java.util.Set;\r
 \r
+import com.google.gwt.event.dom.client.KeyDownEvent;\r
+import com.google.gwt.event.dom.client.KeyDownHandler;\r
 import com.google.gwt.user.client.DOM;\r
 import com.google.gwt.user.client.Element;\r
+import com.google.gwt.user.client.Event;\r
 import com.google.gwt.user.client.ui.ComplexPanel;\r
 import com.google.gwt.user.client.ui.Widget;\r
 import com.vaadin.terminal.gwt.client.ApplicationConnection;\r
@@ -22,6 +25,8 @@ import com.vaadin.terminal.gwt.client.VErrorMessage;
 \r
 public class VForm extends ComplexPanel implements Container {\r
 \r
+    protected String id;\r
+\r
     private String height = "";\r
 \r
     private String width = "";\r
@@ -52,6 +57,8 @@ public class VForm extends ComplexPanel implements Container {
 \r
     private boolean rendering = false;\r
 \r
+    ShortcutActionHandler shortcutHandler;\r
+\r
     public VForm() {\r
         setElement(DOM.createDiv());\r
         DOM.appendChild(getElement(), fieldSet);\r
@@ -69,11 +76,20 @@ public class VForm extends ComplexPanel implements Container {
         errorMessage.setStyleName(CLASSNAME + "-errormessage");\r
         DOM.appendChild(fieldSet, errorMessage.getElement());\r
         DOM.appendChild(fieldSet, footerContainer);\r
+\r
+        addDomHandler(new KeyDownHandler() {\r
+            public void onKeyDown(KeyDownEvent event) {\r
+                shortcutHandler.handleKeyboardEvent(Event.as(event\r
+                        .getNativeEvent()));\r
+                return;\r
+            }\r
+        }, KeyDownEvent.getType());\r
     }\r
 \r
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
         rendering = true;\r
         this.client = client;\r
+        id = uidl.getId();\r
 \r
         if (client.updateComponent(this, uidl, false)) {\r
             rendering = false;\r
@@ -126,7 +142,8 @@ public class VForm extends ComplexPanel implements Container {
 \r
         // first render footer so it will be easier to handle relative height of\r
         // main layout\r
-        if (uidl.getChildCount() > 1) {\r
+        if (uidl.getChildCount() > 1\r
+                && !uidl.getChildUIDL(1).getTag().equals("actions")) {\r
             // render footer\r
             Container newFooter = (Container) client.getPaintable(uidl\r
                     .getChildUIDL(1));\r
@@ -162,6 +179,20 @@ public class VForm extends ComplexPanel implements Container {
         }\r
         lo.updateFromUIDL(layoutUidl, client);\r
 \r
+        // We may have actions attached to this panel\r
+        if (uidl.getChildCount() > 1) {\r
+            final int cnt = uidl.getChildCount();\r
+            for (int i = 1; i < cnt; i++) {\r
+                UIDL childUidl = uidl.getChildUIDL(i);\r
+                if (childUidl.getTag().equals("actions")) {\r
+                    if (shortcutHandler == null) {\r
+                        shortcutHandler = new ShortcutActionHandler(id, client);\r
+                    }\r
+                    shortcutHandler.updateActionMap(childUidl);\r
+                }\r
+            }\r
+        }\r
+\r
         rendering = false;\r
     }\r
 \r
index c3d5066600f134a49bdee76a3dc7155cebf33665..413916f298bb4268b04313db95f09db6dd88f333 100644 (file)
@@ -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
index 63b90c2c7c8362b18f54bd41d8a179ad1d91b124..156c9429014f45207eb8be032dcd97cb29f950b5 100644 (file)
@@ -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();
+        }
+    }
+
 }
index 4f417c4b945c828874b929dc2a055a66fd954d79..7a4c8159ac968931af2d9af5e493d8036e1cd61a 100644 (file)
@@ -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;
 
@@ -132,6 +136,12 @@ public class Form extends AbstractField implements Item.Editor, Buffered, Item,
     private int gridlayoutCursorX = -1;
     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.
      * 
@@ -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();
+        }
+    }
+
 }
index f4ce352687b1c4d49d9f013ac9403088392ffc06..ee39a1797edaff7f7c71f2c4e3a58a45cadd899f 100644 (file)
@@ -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();
+        }
     }
 
     /**
index f4657d81f6af9fc59b0a99f006c02fcd88954408..bc037c18fb66aeef80a448e881b8b295ee654fe4 100644 (file)
@@ -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();
+        }
+    }
 }