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.8KB

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