keyboardshortcuthandler.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Generic keyboard shortcut handler.
  16. *
  17. * @author eae@google.com (Emil A Eklund)
  18. * @see ../demos/keyboardshortcuts.html
  19. */
  20. goog.provide('goog.ui.KeyboardShortcutEvent');
  21. goog.provide('goog.ui.KeyboardShortcutHandler');
  22. goog.provide('goog.ui.KeyboardShortcutHandler.EventType');
  23. goog.provide('goog.ui.KeyboardShortcutHandler.Modifiers');
  24. goog.require('goog.Timer');
  25. goog.require('goog.array');
  26. goog.require('goog.asserts');
  27. goog.require('goog.dom.TagName');
  28. goog.require('goog.events');
  29. goog.require('goog.events.Event');
  30. goog.require('goog.events.EventTarget');
  31. goog.require('goog.events.EventType');
  32. goog.require('goog.events.KeyCodes');
  33. goog.require('goog.events.KeyNames');
  34. goog.require('goog.events.Keys');
  35. goog.require('goog.object');
  36. goog.require('goog.userAgent');
  37. /**
  38. * Component for handling keyboard shortcuts. A shortcut is registered and bound
  39. * to a specific identifier. Once the shortcut is triggered an event is fired
  40. * with the identifier for the shortcut. This allows keyboard shortcuts to be
  41. * customized without modifying the code that listens for them.
  42. *
  43. * Supports keyboard shortcuts triggered by a single key, a stroke stroke (key
  44. * plus at least one modifier) and a sequence of keys or strokes.
  45. *
  46. * @param {goog.events.EventTarget|EventTarget} keyTarget Event target that the
  47. * key event listener is attached to, typically the applications root
  48. * container.
  49. * @constructor
  50. * @extends {goog.events.EventTarget}
  51. */
  52. goog.ui.KeyboardShortcutHandler = function(keyTarget) {
  53. goog.events.EventTarget.call(this);
  54. /**
  55. * Registered keyboard shortcuts tree. Stored as a map with the keyCode and
  56. * modifier(s) as the key and either a list of further strokes or the shortcut
  57. * task identifier as the value.
  58. * @type {!goog.ui.KeyboardShortcutHandler.SequenceTree_}
  59. * @see #makeStroke_
  60. * @private
  61. */
  62. this.shortcuts_ = {};
  63. /**
  64. * The currently active shortcut sequence tree, which represents the position
  65. * in the complete shortcuts_ tree reached by recent key strokes.
  66. * @type {!goog.ui.KeyboardShortcutHandler.SequenceTree_}
  67. * @private
  68. */
  69. this.currentTree_ = this.shortcuts_;
  70. /**
  71. * The time (in ms, epoch time) of the last keystroke which made progress in
  72. * the shortcut sequence tree (i.e. the time that currentTree_ was last set).
  73. * Used for timing out stroke sequences.
  74. * @type {number}
  75. * @private
  76. */
  77. this.lastStrokeTime_ = 0;
  78. /**
  79. * List of numeric key codes for keys that are safe to always regarded as
  80. * shortcuts, even if entered in a textarea or input field.
  81. * @type {Object}
  82. * @private
  83. */
  84. this.globalKeys_ = goog.object.createSet(
  85. goog.ui.KeyboardShortcutHandler.DEFAULT_GLOBAL_KEYS_);
  86. /**
  87. * List of input types that should only accept ENTER as a shortcut.
  88. * @type {Object}
  89. * @private
  90. */
  91. this.textInputs_ = goog.object.createSet(
  92. goog.ui.KeyboardShortcutHandler.DEFAULT_TEXT_INPUTS_);
  93. /**
  94. * Whether to always prevent the default action if a shortcut event is fired.
  95. * @type {boolean}
  96. * @private
  97. */
  98. this.alwaysPreventDefault_ = true;
  99. /**
  100. * Whether to always stop propagation if a shortcut event is fired.
  101. * @type {boolean}
  102. * @private
  103. */
  104. this.alwaysStopPropagation_ = false;
  105. /**
  106. * Whether to treat all shortcuts as if they had been passed
  107. * to setGlobalKeys().
  108. * @type {boolean}
  109. * @private
  110. */
  111. this.allShortcutsAreGlobal_ = false;
  112. /**
  113. * Whether to treat shortcuts with modifiers as if they had been passed
  114. * to setGlobalKeys(). Ignored if allShortcutsAreGlobal_ is true. Applies
  115. * only to form elements (not content-editable).
  116. * @type {boolean}
  117. * @private
  118. */
  119. this.modifierShortcutsAreGlobal_ = true;
  120. /**
  121. * Whether to treat space key as a shortcut when the focused element is a
  122. * checkbox, radiobutton or button.
  123. * @type {boolean}
  124. * @private
  125. */
  126. this.allowSpaceKeyOnButtons_ = false;
  127. /**
  128. * Tracks the currently pressed shortcut key, for Firefox.
  129. * @type {?number}
  130. * @private
  131. */
  132. this.activeShortcutKeyForGecko_ = null;
  133. this.initializeKeyListener(keyTarget);
  134. };
  135. goog.inherits(goog.ui.KeyboardShortcutHandler, goog.events.EventTarget);
  136. goog.tagUnsealableClass(goog.ui.KeyboardShortcutHandler);
  137. /**
  138. * A node in a keyboard shortcut sequence tree. A node is either:
  139. * 1. A terminal node with a non-nullable shortcut string which is the
  140. * identifier for the shortcut triggered by traversing the tree to that node.
  141. * 2. An internal node with a null shortcut string and a
  142. * {@code goog.ui.KeyboardShortcutHandler.SequenceTree_} representing the
  143. * continued stroke sequences from this node.
  144. * For clarity, the static factory methods for creating internal and terminal
  145. * nodes below should be used rather than using this constructor directly.
  146. * @param {string=} opt_shortcut The shortcut identifier, for terminal nodes.
  147. * @constructor
  148. * @struct
  149. * @private
  150. */
  151. goog.ui.KeyboardShortcutHandler.SequenceNode_ = function(opt_shortcut) {
  152. /** @const {?string} The shorcut action identifier, for terminal nodes. */
  153. this.shortcut = opt_shortcut || null;
  154. /** @const {goog.ui.KeyboardShortcutHandler.SequenceTree_} */
  155. this.next = opt_shortcut ? null : {};
  156. };
  157. /**
  158. * Creates a terminal shortcut sequence node for the given shortcut identifier.
  159. * @param {string} shortcut The shortcut identifier.
  160. * @return {!goog.ui.KeyboardShortcutHandler.SequenceNode_}
  161. * @private
  162. */
  163. goog.ui.KeyboardShortcutHandler.createTerminalNode_ = function(shortcut) {
  164. return new goog.ui.KeyboardShortcutHandler.SequenceNode_(shortcut);
  165. };
  166. /**
  167. * Creates an internal shortcut sequence node - a non-terminal part of a
  168. * keyboard sequence.
  169. * @return {!goog.ui.KeyboardShortcutHandler.SequenceNode_}
  170. * @private
  171. */
  172. goog.ui.KeyboardShortcutHandler.createInternalNode_ = function() {
  173. return new goog.ui.KeyboardShortcutHandler.SequenceNode_();
  174. };
  175. /**
  176. * A map of strokes (represented as strings) to the nodes reached by those
  177. * strokes.
  178. * @typedef {Object<string, goog.ui.KeyboardShortcutHandler.SequenceNode_>}
  179. * @private
  180. */
  181. goog.ui.KeyboardShortcutHandler.SequenceTree_;
  182. /**
  183. * Maximum allowed delay, in milliseconds, allowed between the first and second
  184. * key in a key sequence.
  185. * @type {number}
  186. */
  187. goog.ui.KeyboardShortcutHandler.MAX_KEY_SEQUENCE_DELAY = 1500; // 1.5 sec
  188. /**
  189. * Bit values for modifier keys.
  190. * @enum {number}
  191. */
  192. goog.ui.KeyboardShortcutHandler.Modifiers = {
  193. NONE: 0,
  194. SHIFT: 1,
  195. CTRL: 2,
  196. ALT: 4,
  197. META: 8
  198. };
  199. /**
  200. * Keys marked as global by default.
  201. * @type {Array<goog.events.KeyCodes>}
  202. * @private
  203. */
  204. goog.ui.KeyboardShortcutHandler.DEFAULT_GLOBAL_KEYS_ = [
  205. goog.events.KeyCodes.ESC, goog.events.KeyCodes.F1, goog.events.KeyCodes.F2,
  206. goog.events.KeyCodes.F3, goog.events.KeyCodes.F4, goog.events.KeyCodes.F5,
  207. goog.events.KeyCodes.F6, goog.events.KeyCodes.F7, goog.events.KeyCodes.F8,
  208. goog.events.KeyCodes.F9, goog.events.KeyCodes.F10, goog.events.KeyCodes.F11,
  209. goog.events.KeyCodes.F12, goog.events.KeyCodes.PAUSE
  210. ];
  211. /**
  212. * Text input types to allow only ENTER shortcuts.
  213. * Web Forms 2.0 for HTML5: Section 4.10.7 from 29 May 2012.
  214. * @type {Array<string>}
  215. * @private
  216. */
  217. goog.ui.KeyboardShortcutHandler.DEFAULT_TEXT_INPUTS_ = [
  218. 'color', 'date', 'datetime', 'datetime-local', 'email', 'month', 'number',
  219. 'password', 'search', 'tel', 'text', 'time', 'url', 'week'
  220. ];
  221. /**
  222. * Events.
  223. * @enum {string}
  224. */
  225. goog.ui.KeyboardShortcutHandler.EventType = {
  226. SHORTCUT_TRIGGERED: 'shortcut',
  227. SHORTCUT_PREFIX: 'shortcut_'
  228. };
  229. /**
  230. * Cache for name to key code lookup.
  231. * @type {Object<number>}
  232. * @private
  233. */
  234. goog.ui.KeyboardShortcutHandler.nameToKeyCodeCache_;
  235. /**
  236. * Target on which to listen for key events.
  237. * @type {goog.events.EventTarget|EventTarget}
  238. * @private
  239. */
  240. goog.ui.KeyboardShortcutHandler.prototype.keyTarget_;
  241. /**
  242. * Due to a bug in the way that Gecko on Mac handles cut/copy/paste key events
  243. * using the meta key, it is necessary to fake the keyDown for the action key
  244. * (C,V,X) by capturing it on keyUp.
  245. * Because users will often release the meta key a slight moment before they
  246. * release the action key, we need this variable that will store whether the
  247. * meta key has been released recently.
  248. * It will be cleared after a short delay in the key handling logic.
  249. * @type {boolean}
  250. * @private
  251. */
  252. goog.ui.KeyboardShortcutHandler.prototype.metaKeyRecentlyReleased_;
  253. /**
  254. * Whether a key event is a printable-key event. Windows uses ctrl+alt
  255. * (alt-graph) keys to type characters on European keyboards. For such keys, we
  256. * cannot identify whether these keys are used for typing characters when
  257. * receiving keydown events. Therefore, we set this flag when we receive their
  258. * respective keypress events and fire shortcut events only when we do not
  259. * receive them.
  260. * @type {boolean}
  261. * @private
  262. */
  263. goog.ui.KeyboardShortcutHandler.prototype.isPrintableKey_;
  264. /**
  265. * Static method for getting the key code for a given key.
  266. * @param {string} name Name of key.
  267. * @return {number} The key code.
  268. */
  269. goog.ui.KeyboardShortcutHandler.getKeyCode = function(name) {
  270. // Build reverse lookup object the first time this method is called.
  271. if (!goog.ui.KeyboardShortcutHandler.nameToKeyCodeCache_) {
  272. var map = {};
  273. for (var key in goog.events.KeyNames) {
  274. // Explicitly convert the stringified map keys to numbers and normalize.
  275. map[goog.events.KeyNames[key]] =
  276. goog.events.KeyCodes.normalizeKeyCode(parseInt(key, 10));
  277. }
  278. goog.ui.KeyboardShortcutHandler.nameToKeyCodeCache_ = map;
  279. }
  280. // Check if key is in cache.
  281. return goog.ui.KeyboardShortcutHandler.nameToKeyCodeCache_[name];
  282. };
  283. /**
  284. * Sets whether to always prevent the default action when a shortcut event is
  285. * fired. If false, the default action is prevented only if preventDefault is
  286. * called on either of the corresponding SHORTCUT_TRIGGERED or SHORTCUT_PREFIX
  287. * events. If true, the default action is prevented whenever a shortcut event
  288. * is fired. The default value is true.
  289. * @param {boolean} alwaysPreventDefault Whether to always call preventDefault.
  290. */
  291. goog.ui.KeyboardShortcutHandler.prototype.setAlwaysPreventDefault = function(
  292. alwaysPreventDefault) {
  293. this.alwaysPreventDefault_ = alwaysPreventDefault;
  294. };
  295. /**
  296. * Returns whether the default action will always be prevented when a shortcut
  297. * event is fired. The default value is true.
  298. * @see #setAlwaysPreventDefault
  299. * @return {boolean} Whether preventDefault will always be called.
  300. */
  301. goog.ui.KeyboardShortcutHandler.prototype.getAlwaysPreventDefault = function() {
  302. return this.alwaysPreventDefault_;
  303. };
  304. /**
  305. * Sets whether to always stop propagation for the event when fired. If false,
  306. * the propagation is stopped only if stopPropagation is called on either of the
  307. * corresponding SHORT_CUT_TRIGGERED or SHORTCUT_PREFIX events. If true, the
  308. * event is prevented from propagating beyond its target whenever it is fired.
  309. * The default value is false.
  310. * @param {boolean} alwaysStopPropagation Whether to always call
  311. * stopPropagation.
  312. */
  313. goog.ui.KeyboardShortcutHandler.prototype.setAlwaysStopPropagation = function(
  314. alwaysStopPropagation) {
  315. this.alwaysStopPropagation_ = alwaysStopPropagation;
  316. };
  317. /**
  318. * Returns whether the event will always be stopped from propagating beyond its
  319. * target when a shortcut event is fired. The default value is false.
  320. * @see #setAlwaysStopPropagation
  321. * @return {boolean} Whether stopPropagation will always be called.
  322. */
  323. goog.ui.KeyboardShortcutHandler.prototype.getAlwaysStopPropagation =
  324. function() {
  325. return this.alwaysStopPropagation_;
  326. };
  327. /**
  328. * Sets whether to treat all shortcuts (including modifier shortcuts) as if the
  329. * keys had been passed to the setGlobalKeys function.
  330. * @param {boolean} allShortcutsGlobal Whether to treat all shortcuts as global.
  331. */
  332. goog.ui.KeyboardShortcutHandler.prototype.setAllShortcutsAreGlobal = function(
  333. allShortcutsGlobal) {
  334. this.allShortcutsAreGlobal_ = allShortcutsGlobal;
  335. };
  336. /**
  337. * Returns whether all shortcuts (including modifier shortcuts) are treated as
  338. * if the keys had been passed to the setGlobalKeys function.
  339. * @see #setAllShortcutsAreGlobal
  340. * @return {boolean} Whether all shortcuts are treated as globals.
  341. */
  342. goog.ui.KeyboardShortcutHandler.prototype.getAllShortcutsAreGlobal =
  343. function() {
  344. return this.allShortcutsAreGlobal_;
  345. };
  346. /**
  347. * Sets whether to treat shortcuts with modifiers as if the keys had been
  348. * passed to the setGlobalKeys function. Ignored if you have called
  349. * setAllShortcutsAreGlobal(true). Applies only to form elements (not
  350. * content-editable).
  351. * @param {boolean} modifierShortcutsGlobal Whether to treat shortcuts with
  352. * modifiers as global.
  353. */
  354. goog.ui.KeyboardShortcutHandler.prototype.setModifierShortcutsAreGlobal =
  355. function(modifierShortcutsGlobal) {
  356. this.modifierShortcutsAreGlobal_ = modifierShortcutsGlobal;
  357. };
  358. /**
  359. * Returns whether shortcuts with modifiers are treated as if the keys had been
  360. * passed to the setGlobalKeys function. Ignored if you have called
  361. * setAllShortcutsAreGlobal(true). Applies only to form elements (not
  362. * content-editable).
  363. * @see #setModifierShortcutsAreGlobal
  364. * @return {boolean} Whether shortcuts with modifiers are treated as globals.
  365. */
  366. goog.ui.KeyboardShortcutHandler.prototype.getModifierShortcutsAreGlobal =
  367. function() {
  368. return this.modifierShortcutsAreGlobal_;
  369. };
  370. /**
  371. * Sets whether to treat space key as a shortcut when the focused element is a
  372. * checkbox, radiobutton or button.
  373. * @param {boolean} allowSpaceKeyOnButtons Whether to treat space key as a
  374. * shortcut when the focused element is a checkbox, radiobutton or button.
  375. */
  376. goog.ui.KeyboardShortcutHandler.prototype.setAllowSpaceKeyOnButtons = function(
  377. allowSpaceKeyOnButtons) {
  378. this.allowSpaceKeyOnButtons_ = allowSpaceKeyOnButtons;
  379. };
  380. /**
  381. * Registers a keyboard shortcut.
  382. * @param {string} identifier Identifier for the task performed by the keyboard
  383. * combination. Multiple shortcuts can be provided for the same
  384. * task by specifying the same identifier.
  385. * @param {...(number|string|Array<number>)} var_args See below.
  386. *
  387. * param {number} keyCode Numeric code for key
  388. * param {number=} opt_modifiers Bitmap indicating required modifier keys.
  389. * goog.ui.KeyboardShortcutHandler.Modifiers.SHIFT, CTRL, ALT,
  390. * or META.
  391. *
  392. * The last two parameters can be repeated any number of times to create a
  393. * shortcut using a sequence of strokes. Instead of varargs the second parameter
  394. * could also be an array where each element would be regarded as a parameter.
  395. *
  396. * A string representation of the shortcut can be supplied instead of the last
  397. * two parameters. In that case the method only takes two arguments, the
  398. * identifier and the string.
  399. *
  400. * Examples:
  401. * g registerShortcut(str, G_KEYCODE)
  402. * Ctrl+g registerShortcut(str, G_KEYCODE, CTRL)
  403. * Ctrl+Shift+g registerShortcut(str, G_KEYCODE, CTRL | SHIFT)
  404. * Ctrl+g a registerShortcut(str, G_KEYCODE, CTRL, A_KEYCODE)
  405. * Ctrl+g Shift+a registerShortcut(str, G_KEYCODE, CTRL, A_KEYCODE, SHIFT)
  406. * g a registerShortcut(str, G_KEYCODE, NONE, A_KEYCODE)
  407. *
  408. * Examples using string representation for shortcuts:
  409. * g registerShortcut(str, 'g')
  410. * Ctrl+g registerShortcut(str, 'ctrl+g')
  411. * Ctrl+Shift+g registerShortcut(str, 'ctrl+shift+g')
  412. * Ctrl+g a registerShortcut(str, 'ctrl+g a')
  413. * Ctrl+g Shift+a registerShortcut(str, 'ctrl+g shift+a')
  414. * g a registerShortcut(str, 'g a').
  415. */
  416. goog.ui.KeyboardShortcutHandler.prototype.registerShortcut = function(
  417. identifier, var_args) {
  418. // Add shortcut to shortcuts_ tree
  419. goog.ui.KeyboardShortcutHandler.setShortcut_(
  420. this.shortcuts_, this.interpretStrokes_(1, arguments), identifier);
  421. };
  422. /**
  423. * Unregisters a keyboard shortcut by keyCode and modifiers or string
  424. * representation of sequence.
  425. *
  426. * param {number} keyCode Numeric code for key
  427. * param {number=} opt_modifiers Bitmap indicating required modifier keys.
  428. * goog.ui.KeyboardShortcutHandler.Modifiers.SHIFT, CTRL, ALT,
  429. * or META.
  430. *
  431. * The two parameters can be repeated any number of times to create a shortcut
  432. * using a sequence of strokes.
  433. *
  434. * A string representation of the shortcut can be supplied instead see
  435. * {@link #registerShortcut} for syntax. In that case the method only takes one
  436. * argument.
  437. *
  438. * @param {...(number|string|Array<number>)} var_args String representation, or
  439. * array or list of alternating key codes and modifiers.
  440. */
  441. goog.ui.KeyboardShortcutHandler.prototype.unregisterShortcut = function(
  442. var_args) {
  443. // Remove shortcut from tree.
  444. goog.ui.KeyboardShortcutHandler.unsetShortcut_(
  445. this.shortcuts_, this.interpretStrokes_(0, arguments));
  446. };
  447. /**
  448. * Verifies if a particular keyboard shortcut is registered already. It has
  449. * the same interface as the unregistering of shortcuts.
  450. *
  451. * param {number} keyCode Numeric code for key
  452. * param {number=} opt_modifiers Bitmap indicating required modifier keys.
  453. * goog.ui.KeyboardShortcutHandler.Modifiers.SHIFT, CTRL, ALT,
  454. * or META.
  455. *
  456. * The two parameters can be repeated any number of times to create a shortcut
  457. * using a sequence of strokes.
  458. *
  459. * A string representation of the shortcut can be supplied instead see
  460. * {@link #registerShortcut} for syntax. In that case the method only takes one
  461. * argument.
  462. *
  463. * @param {...(number|string|Array<number>)} var_args String representation, or
  464. * array or list of alternating key codes and modifiers.
  465. * @return {boolean} Whether the specified keyboard shortcut is registered.
  466. */
  467. goog.ui.KeyboardShortcutHandler.prototype.isShortcutRegistered = function(
  468. var_args) {
  469. return this.checkShortcut_(
  470. this.shortcuts_, this.interpretStrokes_(0, arguments));
  471. };
  472. /**
  473. * Parses the variable arguments for registerShortcut and unregisterShortcut.
  474. * @param {number} initialIndex The first index of "args" to treat as
  475. * variable arguments.
  476. * @param {Object} args The "arguments" array passed
  477. * to registerShortcut or unregisterShortcut. Please see the comments in
  478. * registerShortcut for list of allowed forms.
  479. * @return {!Array<Array<string>>} The sequence of strokes,
  480. * represented as arrays of strings.
  481. * @private
  482. */
  483. goog.ui.KeyboardShortcutHandler.prototype.interpretStrokes_ = function(
  484. initialIndex, args) {
  485. var strokes;
  486. // Build strokes array from string.
  487. if (goog.isString(args[initialIndex])) {
  488. strokes = goog.array.map(
  489. goog.ui.KeyboardShortcutHandler.parseStringShortcut(args[initialIndex]),
  490. function(stroke) {
  491. goog.asserts.assertNumber(
  492. stroke.keyCode, 'A non-modifier key is needed in each stroke.');
  493. return goog.ui.KeyboardShortcutHandler.makeStroke_(
  494. stroke.key || '', stroke.keyCode, stroke.modifiers);
  495. });
  496. // Build strokes array from arguments list or from array.
  497. } else {
  498. var strokesArgs = args, i = initialIndex;
  499. if (goog.isArray(args[initialIndex])) {
  500. strokesArgs = args[initialIndex];
  501. i = 0;
  502. }
  503. strokes = [];
  504. for (; i < strokesArgs.length; i += 2) {
  505. // keyName == '' because this branch is only run on numbers
  506. // (corresponding to keyCodes).
  507. strokes.push(goog.ui.KeyboardShortcutHandler.makeStroke_(
  508. '', strokesArgs[i], strokesArgs[i + 1]));
  509. }
  510. }
  511. return strokes;
  512. };
  513. /**
  514. * Unregisters all keyboard shortcuts.
  515. */
  516. goog.ui.KeyboardShortcutHandler.prototype.unregisterAll = function() {
  517. this.shortcuts_ = {};
  518. };
  519. /**
  520. * Sets the global keys; keys that are safe to always regarded as shortcuts,
  521. * even if entered in a textarea or input field.
  522. * @param {Array<number>} keys List of keys.
  523. */
  524. goog.ui.KeyboardShortcutHandler.prototype.setGlobalKeys = function(keys) {
  525. this.globalKeys_ = goog.object.createSet(keys);
  526. };
  527. /**
  528. * @return {!Array<string>} The global keys, i.e. keys that are safe to always
  529. * regard as shortcuts, even if entered in a textarea or input field.
  530. */
  531. goog.ui.KeyboardShortcutHandler.prototype.getGlobalKeys = function() {
  532. return goog.object.getKeys(this.globalKeys_);
  533. };
  534. /** @override */
  535. goog.ui.KeyboardShortcutHandler.prototype.disposeInternal = function() {
  536. goog.ui.KeyboardShortcutHandler.superClass_.disposeInternal.call(this);
  537. this.unregisterAll();
  538. this.clearKeyListener();
  539. };
  540. /**
  541. * Returns event type for a specific shortcut.
  542. * @param {string} identifier Identifier for the shortcut task.
  543. * @return {string} The event type.
  544. */
  545. goog.ui.KeyboardShortcutHandler.prototype.getEventType = function(identifier) {
  546. return goog.ui.KeyboardShortcutHandler.EventType.SHORTCUT_PREFIX + identifier;
  547. };
  548. /**
  549. * Builds stroke array from string representation of shortcut.
  550. * @param {string} s String representation of shortcut.
  551. * @return {!Array<!{key: ?string, keyCode: ?number, modifiers: number}>} The
  552. * stroke array. A null keyCode means no non-modifier key was part of the
  553. * stroke.
  554. */
  555. goog.ui.KeyboardShortcutHandler.parseStringShortcut = function(s) {
  556. // Normalize whitespace and force to lower case.
  557. s = s.replace(/[ +]*\+[ +]*/g, '+').replace(/[ ]+/g, ' ').toLowerCase();
  558. // Build strokes array from string, space separates strokes, plus separates
  559. // individual keys.
  560. var groups = s.split(' ');
  561. var strokes = [];
  562. for (var group, i = 0; group = groups[i]; i++) {
  563. var keys = group.split('+');
  564. // Explicitly re-initialize key data (JS does not have block scoping).
  565. var keyName = null;
  566. var keyCode = null;
  567. var modifiers = goog.ui.KeyboardShortcutHandler.Modifiers.NONE;
  568. for (var key, j = 0; key = keys[j]; j++) {
  569. switch (key) {
  570. case 'shift':
  571. modifiers |= goog.ui.KeyboardShortcutHandler.Modifiers.SHIFT;
  572. continue;
  573. case 'ctrl':
  574. modifiers |= goog.ui.KeyboardShortcutHandler.Modifiers.CTRL;
  575. continue;
  576. case 'alt':
  577. modifiers |= goog.ui.KeyboardShortcutHandler.Modifiers.ALT;
  578. continue;
  579. case 'meta':
  580. modifiers |= goog.ui.KeyboardShortcutHandler.Modifiers.META;
  581. continue;
  582. }
  583. if (!goog.isNull(keyCode)) {
  584. goog.asserts.fail('At most one non-modifier key can be in a stroke.');
  585. }
  586. keyCode = goog.ui.KeyboardShortcutHandler.getKeyCode(key);
  587. goog.asserts.assertNumber(
  588. keyCode, 'Key name not found in goog.events.KeyNames: ' + key);
  589. keyName = key;
  590. break;
  591. }
  592. strokes.push({key: keyName, keyCode: keyCode, modifiers: modifiers});
  593. }
  594. return strokes;
  595. };
  596. /**
  597. * Adds a key event listener that triggers {@link #handleKeyDown_} when keys
  598. * are pressed.
  599. * @param {goog.events.EventTarget|EventTarget} keyTarget Event target that the
  600. * event listener should be attached to.
  601. * @protected
  602. */
  603. goog.ui.KeyboardShortcutHandler.prototype.initializeKeyListener = function(
  604. keyTarget) {
  605. this.keyTarget_ = keyTarget;
  606. goog.events.listen(
  607. this.keyTarget_, goog.events.EventType.KEYDOWN, this.handleKeyDown_,
  608. undefined /* opt_capture */, this);
  609. // Windows uses ctrl+alt keys (a.k.a. alt-graph keys) for typing characters
  610. // on European keyboards (e.g. ctrl+alt+e for an an euro sign.) Unfortunately,
  611. // Windows browsers do not have any methods except listening to keypress and
  612. // keyup events to identify if ctrl+alt keys are really used for inputting
  613. // characters. Therefore, we listen to these events and prevent firing
  614. // shortcut-key events if ctrl+alt keys are used for typing characters.
  615. if (goog.userAgent.WINDOWS) {
  616. goog.events.listen(
  617. this.keyTarget_, goog.events.EventType.KEYPRESS,
  618. this.handleWindowsKeyPress_, undefined /* opt_capture */, this);
  619. }
  620. goog.events.listen(
  621. this.keyTarget_, goog.events.EventType.KEYUP, this.handleKeyUp_,
  622. undefined /* opt_capture */, this);
  623. };
  624. /**
  625. * Handler for when a keyup event is fired. Currently only handled on Windows
  626. * (all browsers) or Gecko (all platforms).
  627. * @param {!goog.events.BrowserEvent} e The key event.
  628. * @private
  629. */
  630. goog.ui.KeyboardShortcutHandler.prototype.handleKeyUp_ = function(e) {
  631. if (goog.userAgent.GECKO) {
  632. this.handleGeckoKeyUp_(e);
  633. }
  634. if (goog.userAgent.WINDOWS) {
  635. this.handleWindowsKeyUp_(e);
  636. }
  637. };
  638. /**
  639. * Handler for when a keyup event is fired in Firefox (Gecko).
  640. * @param {!goog.events.BrowserEvent} e The key event.
  641. * @private
  642. */
  643. goog.ui.KeyboardShortcutHandler.prototype.handleGeckoKeyUp_ = function(e) {
  644. // Due to a bug in the way that Gecko on Mac handles cut/copy/paste key events
  645. // using the meta key, it is necessary to fake the keyDown for the action keys
  646. // (C,V,X) by capturing it on keyUp.
  647. // This is because the keyDown events themselves are not fired by the browser
  648. // in this case.
  649. // Because users will often release the meta key a slight moment before they
  650. // release the action key, we need to store whether the meta key has been
  651. // released recently to avoid "flaky" cutting/pasting behavior.
  652. if (goog.userAgent.MAC) {
  653. if (e.keyCode == goog.events.KeyCodes.MAC_FF_META) {
  654. this.metaKeyRecentlyReleased_ = true;
  655. goog.Timer.callOnce(function() {
  656. this.metaKeyRecentlyReleased_ = false;
  657. }, 400, this);
  658. return;
  659. }
  660. var metaKey = e.metaKey || this.metaKeyRecentlyReleased_;
  661. if ((e.keyCode == goog.events.KeyCodes.C ||
  662. e.keyCode == goog.events.KeyCodes.X ||
  663. e.keyCode == goog.events.KeyCodes.V) &&
  664. metaKey) {
  665. e.metaKey = metaKey;
  666. this.handleKeyDown_(e);
  667. }
  668. }
  669. // Firefox triggers buttons on space keyUp instead of keyDown. So if space
  670. // keyDown activated a shortcut, do NOT also trigger the focused button.
  671. if (goog.events.KeyCodes.SPACE == this.activeShortcutKeyForGecko_ &&
  672. goog.events.KeyCodes.SPACE == e.keyCode) {
  673. e.preventDefault();
  674. }
  675. this.activeShortcutKeyForGecko_ = null;
  676. };
  677. /**
  678. * Returns whether this event is possibly used for typing a printable character.
  679. * Windows uses ctrl+alt (a.k.a. alt-graph) keys for typing characters on
  680. * European keyboards. Since only Firefox provides a method that can identify
  681. * whether ctrl+alt keys are used for typing characters, we need to check
  682. * whether Windows sends a keypress event to prevent firing shortcut event if
  683. * this event is used for typing characters.
  684. * @param {!goog.events.BrowserEvent} e The key event.
  685. * @return {boolean} Whether this event is a possible printable-key event.
  686. * @private
  687. */
  688. goog.ui.KeyboardShortcutHandler.prototype.isPossiblePrintableKey_ = function(
  689. e) {
  690. return goog.userAgent.WINDOWS && e.ctrlKey && e.altKey;
  691. };
  692. /**
  693. * Handler for when a keypress event is fired on Windows.
  694. * @param {!goog.events.BrowserEvent} e The key event.
  695. * @private
  696. */
  697. goog.ui.KeyboardShortcutHandler.prototype.handleWindowsKeyPress_ = function(e) {
  698. // When this keypress event consists of a printable character, set the flag to
  699. // prevent firing shortcut key events when we receive the succeeding keyup
  700. // event. We accept all Unicode characters except control ones since this
  701. // keyCode may be a non-ASCII character.
  702. if (e.keyCode > 0x20 && this.isPossiblePrintableKey_(e)) {
  703. this.isPrintableKey_ = true;
  704. }
  705. };
  706. /**
  707. * Handler for when a keyup event is fired on Windows.
  708. * @param {!goog.events.BrowserEvent} e The key event.
  709. * @private
  710. */
  711. goog.ui.KeyboardShortcutHandler.prototype.handleWindowsKeyUp_ = function(e) {
  712. // For possible printable-key events, try firing a shortcut-key event only
  713. // when this event is not used for typing a character.
  714. if (!this.isPrintableKey_ && this.isPossiblePrintableKey_(e)) {
  715. this.handleKeyDown_(e);
  716. }
  717. };
  718. /**
  719. * Removes the listener that was added by link {@link #initializeKeyListener}.
  720. * @protected
  721. */
  722. goog.ui.KeyboardShortcutHandler.prototype.clearKeyListener = function() {
  723. goog.events.unlisten(
  724. this.keyTarget_, goog.events.EventType.KEYDOWN, this.handleKeyDown_,
  725. false, this);
  726. if (goog.userAgent.WINDOWS) {
  727. goog.events.unlisten(
  728. this.keyTarget_, goog.events.EventType.KEYPRESS,
  729. this.handleWindowsKeyPress_, false, this);
  730. }
  731. goog.events.unlisten(
  732. this.keyTarget_, goog.events.EventType.KEYUP, this.handleKeyUp_, false,
  733. this);
  734. this.keyTarget_ = null;
  735. };
  736. /**
  737. * Adds a shortcut stroke sequence to the given sequence tree. Recursive.
  738. * @param {!goog.ui.KeyboardShortcutHandler.SequenceTree_} tree The stroke
  739. * sequence tree to add to.
  740. * @param {Array<Array<string>>} strokes Array of strokes for shortcut.
  741. * @param {string} identifier Identifier for the task performed by shortcut.
  742. * @private
  743. */
  744. goog.ui.KeyboardShortcutHandler.setShortcut_ = function(
  745. tree, strokes, identifier) {
  746. var stroke = strokes.shift();
  747. goog.array.forEach(stroke, function(s) {
  748. var node = tree[s];
  749. if (node && (strokes.length == 0 || node.shortcut)) {
  750. // This new shortcut would override an existing shortcut or shortcut
  751. // prefix (since the new strokes end at an existing node), or an existing
  752. // shortcut would be triggered by the prefix to this new shortcut (since
  753. // there is already a terminal node on the path we are trying to create).
  754. throw Error('Keyboard shortcut conflicts with existing shortcut');
  755. }
  756. });
  757. if (strokes.length) {
  758. goog.array.forEach(stroke, function(s) {
  759. var node = goog.object.setIfUndefined(
  760. tree, s.toString(),
  761. goog.ui.KeyboardShortcutHandler.createInternalNode_());
  762. // setShortcut_ modifies strokes
  763. var strokesCopy = strokes.slice(0);
  764. goog.ui.KeyboardShortcutHandler.setShortcut_(
  765. goog.asserts.assert(
  766. node.next, 'An internal node must have a next map'),
  767. strokesCopy, identifier);
  768. });
  769. } else {
  770. goog.array.forEach(stroke, function(s) {
  771. // Add a terminal node.
  772. tree[s] = goog.ui.KeyboardShortcutHandler.createTerminalNode_(identifier);
  773. });
  774. }
  775. };
  776. /**
  777. * Removes a shortcut stroke sequence from the given sequence tree, pruning any
  778. * dead branches of the tree. Recursive.
  779. * @param {!goog.ui.KeyboardShortcutHandler.SequenceTree_} tree The stroke
  780. * sequence tree to remove from.
  781. * @param {Array<Array<string>>} strokes Array of strokes for shortcut to
  782. * remove.
  783. * @private
  784. */
  785. goog.ui.KeyboardShortcutHandler.unsetShortcut_ = function(tree, strokes) {
  786. var stroke = strokes.shift();
  787. goog.array.forEach(stroke, function(s) {
  788. var node = tree[s];
  789. if (!node) {
  790. // The given stroke sequence is not in the tree.
  791. return;
  792. }
  793. if (strokes.length == 0) {
  794. // Base case - the end of the stroke sequence.
  795. if (!node.shortcut) {
  796. // The given stroke sequence does not end at a terminal node.
  797. return;
  798. }
  799. delete tree[s];
  800. } else {
  801. if (!node.next) {
  802. // The given stroke sequence is not in the tree.
  803. return;
  804. }
  805. // Recursively remove the rest of the shortcut sequence from the node.next
  806. // subtree.
  807. // unsetShortcut_ modifies strokes
  808. var strokesCopy = strokes.slice(0);
  809. goog.ui.KeyboardShortcutHandler.unsetShortcut_(node.next, strokesCopy);
  810. if (goog.object.isEmpty(node.next)) {
  811. // The node.next subtree is now empty (the last stroke in it was just
  812. // removed), so prune this dead branch of the tree.
  813. delete tree[s];
  814. }
  815. }
  816. });
  817. };
  818. /**
  819. * Checks tree for a node matching one of stroke.
  820. * @param {!goog.ui.KeyboardShortcutHandler.SequenceTree_} tree The
  821. * stroke sequence tree to find the node in.
  822. * @param {Array<string>} stroke Stroke to find.
  823. * @return {goog.ui.KeyboardShortcutHandler.SequenceNode_|undefined} Node matching stroke.
  824. * @private
  825. */
  826. goog.ui.KeyboardShortcutHandler.prototype.getNode_ = function(tree, stroke) {
  827. for (var i = 0; i < stroke.length; i++) {
  828. var node = tree[stroke[i]];
  829. if (!node) {
  830. continue;
  831. }
  832. return node;
  833. }
  834. return undefined;
  835. };
  836. /**
  837. * Checks if a particular keyboard shortcut is registered.
  838. * @param {goog.ui.KeyboardShortcutHandler.SequenceTree_|null} tree The
  839. * stroke sequence tree to find the keyboard shortcut in.
  840. * @param {Array<Array<string>>} strokes Strokes array.
  841. * @return {boolean} True iff the keyboard shortcut is registred.
  842. * @private
  843. */
  844. goog.ui.KeyboardShortcutHandler.prototype.checkShortcut_ = function(
  845. tree, strokes) {
  846. while (strokes.length > 0 && tree) {
  847. var stroke = strokes.shift();
  848. var node = this.getNode_(tree, stroke);
  849. if (!node) {
  850. continue;
  851. }
  852. if (strokes.length == 0 && node.shortcut) {
  853. return true;
  854. }
  855. // checkShortcut_ modifies strokes
  856. var strokesCopy = strokes.slice(0);
  857. if (this.checkShortcut_(node.next, strokesCopy)) {
  858. return true;
  859. }
  860. }
  861. return false;
  862. };
  863. /**
  864. * Constructs key identification string from key name, key code and modifiers.
  865. *
  866. * @param {string} keyName Key name.
  867. * @param {number} keyCode Numeric key code.
  868. * @param {number} modifiers Required modifiers.
  869. * @return {Array<string>} An array of strings identifying the key/modifier
  870. * combinations.
  871. * @private
  872. */
  873. goog.ui.KeyboardShortcutHandler.makeStroke_ = function(
  874. keyName, keyCode, modifiers) {
  875. var mods = modifiers || 0;
  876. // entries must be usable as key in a map
  877. var strokes = ['c_' + keyCode + '_' + mods];
  878. if (keyName != '') {
  879. strokes.push('n_' + keyName + '_' + mods);
  880. }
  881. return strokes;
  882. };
  883. /**
  884. * Keypress handler.
  885. * @param {!goog.events.BrowserEvent} event Keypress event.
  886. * @private
  887. */
  888. goog.ui.KeyboardShortcutHandler.prototype.handleKeyDown_ = function(event) {
  889. if (!this.isValidShortcut_(event)) {
  890. return;
  891. }
  892. var keyCode = goog.events.KeyCodes.normalizeKeyCode(event.keyCode);
  893. var keyName = event.key;
  894. var modifiers =
  895. (event.shiftKey ? goog.ui.KeyboardShortcutHandler.Modifiers.SHIFT : 0) |
  896. (event.ctrlKey ? goog.ui.KeyboardShortcutHandler.Modifiers.CTRL : 0) |
  897. (event.altKey ? goog.ui.KeyboardShortcutHandler.Modifiers.ALT : 0) |
  898. (event.metaKey ? goog.ui.KeyboardShortcutHandler.Modifiers.META : 0);
  899. var stroke =
  900. goog.ui.KeyboardShortcutHandler.makeStroke_(keyName, keyCode, modifiers);
  901. var node = this.getNode_(this.currentTree_, stroke);
  902. if (!node || this.hasSequenceTimedOut_()) {
  903. // Either this stroke does not continue any active sequence, or the
  904. // currently active sequence has timed out. Reset shortcut tree progress.
  905. this.setCurrentTree_(this.shortcuts_);
  906. }
  907. node = this.getNode_(this.currentTree_, stroke);
  908. if (node && node.next) {
  909. // This stroke does not trigger a shortcut, but entered stroke(s) are a part
  910. // of a sequence. Progress in the sequence tree and record time to allow the
  911. // following stroke(s) to trigger the shortcut.
  912. this.setCurrentTree_(node.next);
  913. }
  914. // For possible printable-key events, we cannot identify whether the events
  915. // are used for typing characters until we receive respective keyup events.
  916. // Therefore, we handle this event when we receive a succeeding keyup event
  917. // to verify this event is not used for typing characters. preventDefault is
  918. // not called on the event to avoid disrupting a character input.
  919. if (event.type == 'keydown' && this.isPossiblePrintableKey_(event)) {
  920. this.isPrintableKey_ = false;
  921. return;
  922. } else if (!node) {
  923. // This stroke does not correspond to a shortcut or continued sequence.
  924. return;
  925. } else if (node.next) {
  926. // Prevent default action so that the rest of the stroke sequence can be
  927. // completed.
  928. event.preventDefault();
  929. return;
  930. }
  931. // This stroke triggers a shortcut. Any active sequence has been completed, so
  932. // reset the sequence tree.
  933. this.setCurrentTree_(this.shortcuts_);
  934. // Dispatch the triggered keyboard shortcut event. In addition to the generic
  935. // keyboard shortcut event a more specific fine grained one, specific for the
  936. // shortcut identifier, is fired.
  937. if (this.alwaysPreventDefault_) {
  938. event.preventDefault();
  939. }
  940. if (this.alwaysStopPropagation_) {
  941. event.stopPropagation();
  942. }
  943. var shortcut = goog.asserts.assertString(
  944. node.shortcut, 'A terminal node must have a string shortcut identifier.');
  945. // Dispatch SHORTCUT_TRIGGERED event
  946. var target = /** @type {Node} */ (event.target);
  947. var triggerEvent = new goog.ui.KeyboardShortcutEvent(
  948. goog.ui.KeyboardShortcutHandler.EventType.SHORTCUT_TRIGGERED, shortcut,
  949. target);
  950. var retVal = this.dispatchEvent(triggerEvent);
  951. // Dispatch SHORTCUT_PREFIX_<identifier> event
  952. var prefixEvent = new goog.ui.KeyboardShortcutEvent(
  953. goog.ui.KeyboardShortcutHandler.EventType.SHORTCUT_PREFIX + shortcut,
  954. shortcut, target);
  955. retVal &= this.dispatchEvent(prefixEvent);
  956. // The default action is prevented if 'preventDefault' was
  957. // called on either event, or if a listener returned false.
  958. if (!retVal) {
  959. event.preventDefault();
  960. }
  961. // For Firefox, track which shortcut key was pushed.
  962. if (goog.userAgent.GECKO) {
  963. this.activeShortcutKeyForGecko_ = keyCode;
  964. }
  965. };
  966. /**
  967. * Checks if a given keypress event may be treated as a shortcut.
  968. * @param {!goog.events.BrowserEvent} event Keypress event.
  969. * @return {boolean} Whether to attempt to process the event as a shortcut.
  970. * @private
  971. */
  972. goog.ui.KeyboardShortcutHandler.prototype.isValidShortcut_ = function(event) {
  973. // Ignore Ctrl, Shift and ALT
  974. var keyCode = event.keyCode;
  975. if (event.key != '') {
  976. var keyName = event.key;
  977. if (keyName == goog.events.Keys.CTRL || keyName == goog.events.Keys.SHIFT ||
  978. keyName == goog.events.Keys.ALT ||
  979. keyName == goog.events.Keys.ALTGRAPH) {
  980. return false;
  981. }
  982. } else {
  983. if (keyCode == goog.events.KeyCodes.SHIFT ||
  984. keyCode == goog.events.KeyCodes.CTRL ||
  985. keyCode == goog.events.KeyCodes.ALT) {
  986. return false;
  987. }
  988. }
  989. var el = /** @type {Element} */ (event.target);
  990. var isFormElement = el.tagName == goog.dom.TagName.TEXTAREA ||
  991. el.tagName == goog.dom.TagName.INPUT ||
  992. el.tagName == goog.dom.TagName.BUTTON ||
  993. el.tagName == goog.dom.TagName.SELECT;
  994. var isContentEditable = !isFormElement &&
  995. (el.isContentEditable ||
  996. (el.ownerDocument && el.ownerDocument.designMode == 'on'));
  997. if (!isFormElement && !isContentEditable) {
  998. return true;
  999. }
  1000. // Always allow keys registered as global to be used (typically Esc, the
  1001. // F-keys and other keys that are not typically used to manipulate text).
  1002. if (this.globalKeys_[keyCode] || this.allShortcutsAreGlobal_) {
  1003. return true;
  1004. }
  1005. if (isContentEditable) {
  1006. // For events originating from an element in editing mode we only let
  1007. // global key codes through.
  1008. return false;
  1009. }
  1010. // Event target is one of (TEXTAREA, INPUT, BUTTON, SELECT).
  1011. // Allow modifier shortcuts, unless we shouldn't.
  1012. if (this.modifierShortcutsAreGlobal_ &&
  1013. (event.altKey || event.ctrlKey || event.metaKey)) {
  1014. return true;
  1015. }
  1016. // Allow ENTER to be used as shortcut for text inputs.
  1017. if (el.tagName == goog.dom.TagName.INPUT && this.textInputs_[el.type]) {
  1018. return keyCode == goog.events.KeyCodes.ENTER;
  1019. }
  1020. // Checkboxes, radiobuttons and buttons. Allow all but SPACE as shortcut.
  1021. if (el.tagName == goog.dom.TagName.INPUT ||
  1022. el.tagName == goog.dom.TagName.BUTTON) {
  1023. // TODO(gboyer): If more flexibility is needed, create protected helper
  1024. // methods for each case (e.g. button, input, etc).
  1025. if (this.allowSpaceKeyOnButtons_) {
  1026. return true;
  1027. } else {
  1028. return keyCode != goog.events.KeyCodes.SPACE;
  1029. }
  1030. }
  1031. // Don't allow any additional shortcut keys for textareas or selects.
  1032. return false;
  1033. };
  1034. /**
  1035. * @return {boolean} True iff the current stroke sequence has timed out.
  1036. * @private
  1037. */
  1038. goog.ui.KeyboardShortcutHandler.prototype.hasSequenceTimedOut_ = function() {
  1039. return goog.now() - this.lastStrokeTime_ >=
  1040. goog.ui.KeyboardShortcutHandler.MAX_KEY_SEQUENCE_DELAY;
  1041. };
  1042. /**
  1043. * Sets the current keyboard shortcut sequence tree and updates the last stroke
  1044. * time.
  1045. * @param {!goog.ui.KeyboardShortcutHandler.SequenceTree_} tree
  1046. * @private
  1047. */
  1048. goog.ui.KeyboardShortcutHandler.prototype.setCurrentTree_ = function(tree) {
  1049. this.currentTree_ = tree;
  1050. this.lastStrokeTime_ = goog.now();
  1051. };
  1052. /**
  1053. * Object representing a keyboard shortcut event.
  1054. * @param {string} type Event type.
  1055. * @param {string} identifier Task identifier for the triggered shortcut.
  1056. * @param {Node|goog.events.EventTarget} target Target the original key press
  1057. * event originated from.
  1058. * @extends {goog.events.Event}
  1059. * @constructor
  1060. * @final
  1061. */
  1062. goog.ui.KeyboardShortcutEvent = function(type, identifier, target) {
  1063. goog.events.Event.call(this, type, target);
  1064. /**
  1065. * Task identifier for the triggered shortcut
  1066. * @type {string}
  1067. */
  1068. this.identifier = identifier;
  1069. };
  1070. goog.inherits(goog.ui.KeyboardShortcutEvent, goog.events.Event);