You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ShortcutActionHandler.java 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.Iterator;
  7. import com.google.gwt.core.client.Scheduler;
  8. import com.google.gwt.user.client.Command;
  9. import com.google.gwt.user.client.DOM;
  10. import com.google.gwt.user.client.Element;
  11. import com.google.gwt.user.client.Event;
  12. import com.google.gwt.user.client.ui.HasWidgets;
  13. import com.google.gwt.user.client.ui.KeyboardListener;
  14. import com.google.gwt.user.client.ui.KeyboardListenerCollection;
  15. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  16. import com.vaadin.terminal.gwt.client.BrowserInfo;
  17. import com.vaadin.terminal.gwt.client.ComponentConnector;
  18. import com.vaadin.terminal.gwt.client.UIDL;
  19. import com.vaadin.terminal.gwt.client.Util;
  20. import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea;
  21. /**
  22. * A helper class to implement keyboard shorcut handling. Keeps a list of owners
  23. * actions and fires actions to server. User class needs to delegate keyboard
  24. * events to handleKeyboardEvents function.
  25. *
  26. * @author Vaadin Ltd
  27. */
  28. public class ShortcutActionHandler {
  29. /**
  30. * An interface implemented by those users of this helper class that want to
  31. * support special components like {@link VRichTextArea} that don't properly
  32. * propagate key down events. Those components can build support for
  33. * shortcut actions by traversing the closest
  34. * {@link ShortcutActionHandlerOwner} from the component hierarchy an
  35. * passing keydown events to {@link ShortcutActionHandler}.
  36. */
  37. public interface ShortcutActionHandlerOwner extends HasWidgets {
  38. /**
  39. * Returns the ShortCutActionHandler currently used or null if there is
  40. * currently no shortcutactionhandler
  41. */
  42. ShortcutActionHandler getShortcutActionHandler();
  43. }
  44. /**
  45. * A focusable {@link ComponentConnector} implementing this interface will
  46. * be notified before shortcut actions are handled if it will be the target
  47. * of the action (most commonly means it is the focused component during the
  48. * keyboard combination is triggered by the user).
  49. */
  50. public interface BeforeShortcutActionListener extends ComponentConnector {
  51. /**
  52. * This method is called by ShortcutActionHandler before firing the
  53. * shortcut if the Paintable is currently focused (aka the target of the
  54. * shortcut action). Eg. a field can update its possibly changed value
  55. * to the server before shortcut action is fired.
  56. *
  57. * @param e
  58. * the event that triggered the shortcut action
  59. */
  60. public void onBeforeShortcutAction(Event e);
  61. }
  62. private final ArrayList<ShortcutAction> actions = new ArrayList<ShortcutAction>();
  63. private ApplicationConnection client;
  64. private String paintableId;
  65. /**
  66. *
  67. * @param pid
  68. * Paintable id
  69. * @param c
  70. * reference to application connections
  71. */
  72. public ShortcutActionHandler(String pid, ApplicationConnection c) {
  73. paintableId = pid;
  74. client = c;
  75. }
  76. /**
  77. * Updates list of actions this handler listens to.
  78. *
  79. * @param c
  80. * UIDL snippet containing actions
  81. */
  82. public void updateActionMap(UIDL c) {
  83. actions.clear();
  84. final Iterator<?> it = c.getChildIterator();
  85. while (it.hasNext()) {
  86. final UIDL action = (UIDL) it.next();
  87. int[] modifiers = null;
  88. if (action.hasAttribute("mk")) {
  89. modifiers = action.getIntArrayAttribute("mk");
  90. }
  91. final ShortcutKeyCombination kc = new ShortcutKeyCombination(
  92. action.getIntAttribute("kc"), modifiers);
  93. final String key = action.getStringAttribute("key");
  94. final String caption = action.getStringAttribute("caption");
  95. actions.add(new ShortcutAction(key, kc, caption));
  96. }
  97. }
  98. public void handleKeyboardEvent(final Event event, ComponentConnector target) {
  99. final int modifiers = KeyboardListenerCollection
  100. .getKeyboardModifiers(event);
  101. final char keyCode = (char) DOM.eventGetKeyCode(event);
  102. final ShortcutKeyCombination kc = new ShortcutKeyCombination(keyCode,
  103. modifiers);
  104. final Iterator<ShortcutAction> it = actions.iterator();
  105. while (it.hasNext()) {
  106. final ShortcutAction a = it.next();
  107. if (a.getShortcutCombination().equals(kc)) {
  108. fireAction(event, a, target);
  109. break;
  110. }
  111. }
  112. }
  113. public void handleKeyboardEvent(final Event event) {
  114. handleKeyboardEvent(event, null);
  115. }
  116. private void fireAction(final Event event, final ShortcutAction a,
  117. ComponentConnector target) {
  118. final Element et = DOM.eventGetTarget(event);
  119. if (target == null) {
  120. target = Util.findPaintable(client, et);
  121. }
  122. final ComponentConnector finalTarget = target;
  123. event.preventDefault();
  124. /*
  125. * The target component might have unpublished changes, try to
  126. * synchronize them before firing shortcut action.
  127. */
  128. if (finalTarget instanceof BeforeShortcutActionListener) {
  129. ((BeforeShortcutActionListener) finalTarget)
  130. .onBeforeShortcutAction(event);
  131. } else {
  132. shakeTarget(et);
  133. Scheduler.get().scheduleDeferred(new Command() {
  134. public void execute() {
  135. shakeTarget(et);
  136. }
  137. });
  138. }
  139. Scheduler.get().scheduleDeferred(new Command() {
  140. public void execute() {
  141. if (finalTarget != null) {
  142. client.updateVariable(paintableId, "actiontarget",
  143. finalTarget, false);
  144. }
  145. client.updateVariable(paintableId, "action", a.getKey(), true);
  146. }
  147. });
  148. }
  149. /**
  150. * We try to fire value change in the component the key combination was
  151. * typed. Eg. textfield may contain newly typed text that is expected to be
  152. * sent to server. This is done by removing focus and then returning it
  153. * immediately back to target element.
  154. * <p>
  155. * This is practically a hack and should be replaced with an interface
  156. * {@link BeforeShortcutActionListener} via widgets could be notified when
  157. * they should fire value change. Big task for TextFields, DateFields and
  158. * various selects.
  159. *
  160. * <p>
  161. * TODO separate opera impl with generator
  162. */
  163. private static void shakeTarget(final Element e) {
  164. blur(e);
  165. if (BrowserInfo.get().isOpera()) {
  166. // will mess up with focus and blur event if the focus is not
  167. // deferred. Will cause a small flickering, so not doing it for all
  168. // browsers.
  169. Scheduler.get().scheduleDeferred(new Command() {
  170. public void execute() {
  171. focus(e);
  172. }
  173. });
  174. } else {
  175. focus(e);
  176. }
  177. }
  178. private static native void blur(Element e)
  179. /*-{
  180. if(e.blur) {
  181. e.blur();
  182. }
  183. }-*/;
  184. private static native void focus(Element e)
  185. /*-{
  186. if(e.blur) {
  187. e.focus();
  188. }
  189. }-*/;
  190. }
  191. class ShortcutKeyCombination {
  192. public static final int SHIFT = 16;
  193. public static final int CTRL = 17;
  194. public static final int ALT = 18;
  195. public static final int META = 91;
  196. char keyCode = 0;
  197. private int modifiersMask;
  198. public ShortcutKeyCombination() {
  199. }
  200. ShortcutKeyCombination(char kc, int modifierMask) {
  201. keyCode = kc;
  202. modifiersMask = modifierMask;
  203. }
  204. ShortcutKeyCombination(int kc, int[] modifiers) {
  205. keyCode = (char) kc;
  206. modifiersMask = 0;
  207. if (modifiers != null) {
  208. for (int i = 0; i < modifiers.length; i++) {
  209. switch (modifiers[i]) {
  210. case ALT:
  211. modifiersMask = modifiersMask
  212. | KeyboardListener.MODIFIER_ALT;
  213. break;
  214. case CTRL:
  215. modifiersMask = modifiersMask
  216. | KeyboardListener.MODIFIER_CTRL;
  217. break;
  218. case SHIFT:
  219. modifiersMask = modifiersMask
  220. | KeyboardListener.MODIFIER_SHIFT;
  221. break;
  222. case META:
  223. modifiersMask = modifiersMask
  224. | KeyboardListener.MODIFIER_META;
  225. break;
  226. default:
  227. break;
  228. }
  229. }
  230. }
  231. }
  232. public boolean equals(ShortcutKeyCombination other) {
  233. if (keyCode == other.keyCode && modifiersMask == other.modifiersMask) {
  234. return true;
  235. }
  236. return false;
  237. }
  238. }
  239. class ShortcutAction {
  240. private final ShortcutKeyCombination sc;
  241. private final String caption;
  242. private final String key;
  243. public ShortcutAction(String key, ShortcutKeyCombination sc, String caption) {
  244. this.sc = sc;
  245. this.key = key;
  246. this.caption = caption;
  247. }
  248. public ShortcutKeyCombination getShortcutCombination() {
  249. return sc;
  250. }
  251. public String getCaption() {
  252. return caption;
  253. }
  254. public String getKey() {
  255. return key;
  256. }
  257. }