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

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