1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates an empty keyboard manager. Intended for use as a singleton. 26 * @constructor 27 * @class 28 * This class is responsible for managing focus and shortcuts via the keyboard. That includes dispatching 29 * keyboard events (shortcuts), as well as managing tab groups. It is at the heart of the 30 * Dwt keyboard navigation framework. 31 * <p> 32 * {@link DwtKeyboardMgr} intercepts key strokes and translates 33 * them into actions which it then dispatches to the component with focus. If the key 34 * stroke is a TAB (or Shift-TAB), then focus is moved based on the current tab group. 35 * </p><p> 36 * A {@link DwtShell} instantiates its own <i>DwtKeyboardMgr</i> at construction. 37 * The keyboard manager may then be retrieved via the shell's <code>getKeyboardMgr()</code> 38 * function. Once a handle to the shell's keyboard manager is retrieved, then the user is free 39 * to add tab groups, and to register keymaps and handlers with the keyboard manager. 40 * </p><p> 41 * Focus is managed among a stack of tab groups. The TAB button will move the focus within the 42 * current tab group. When a non-TAB is received, we first check if the control can handle it. 43 * In general, control key events simulate something the user could do with the mouse, and change 44 * the state/appearance of the control. For example, ENTER on a DwtButton simulates a button 45 * press. If the control does not handle the key event, the event is handed to the application, 46 * which handles it based on its current state. The application key event handler is in a sense 47 * global, since it does not matter which control received the event. 48 * </p><p> 49 * At any given time there is a default handler, which is responsible for determining what 50 * action is associated with a particular key sequence, and then taking it. A handler should support 51 * the following methods: 52 * 53 * <ul> 54 * <li><i>getKeyMapName()</i> -- returns the name of the map that defines shortcuts for this handler</li> 55 * <li><i>handleKeyAction()</i> -- performs the action associated with a shortcut</li> 56 * <li><i>handleKeyEvent()</i> -- optional override; handler solely responsible for handling event</li> 57 * </ul> 58 * </p> 59 * 60 * @author Ross Dargahi 61 * 62 * @param {DwtShell} shell the shell 63 * @see DwtShell 64 * @see DwtTabGroup 65 * @see DwtKeyMap 66 * @see DwtKeyMapMgr 67 * 68 * @private 69 */ 70 DwtKeyboardMgr = function(shell) { 71 72 DwtKeyboardMgr.__shell = shell; 73 74 this.__kbEventStatus = DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 75 this.__keyTimeout = DwtKeyboardMgr.SHORTCUT_TIMEOUT; 76 77 // focus 78 this.__tabGrpStack = []; 79 this.__currTabGroup = null; 80 this.__tabGroupChangeListenerObj = this.__tabGrpChangeListener.bind(this); 81 82 // shortcuts 83 this.__shortcutsEnabled = false; 84 this.__defaultHandlerStack = []; 85 this.__currDefaultHandler = null; 86 this.__killKeySeqTimedAction = new AjxTimedAction(this, this.__killKeySequenceAction); 87 this.__killKeySeqTimedActionId = -1; 88 this.__keySequence = []; 89 this._evtMgr = new AjxEventMgr(); 90 91 Dwt.setHandler(document, DwtEvent.ONKEYDOWN, DwtKeyboardMgr.__keyDownHdlr); 92 Dwt.setHandler(document, DwtEvent.ONKEYUP, DwtKeyboardMgr.__keyUpHdlr); 93 Dwt.setHandler(document, DwtEvent.ONKEYPRESS, DwtKeyboardMgr.__keyPressHdlr); 94 }; 95 96 DwtKeyboardMgr.prototype.isDwtKeyboardMgr = true; 97 DwtKeyboardMgr.prototype.toString = function() { return "DwtKeyboardMgr"; }; 98 99 DwtKeyboardMgr.SHORTCUT_TIMEOUT = 750; 100 101 DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED = "NOT HANDLED"; 102 DwtKeyboardMgr.__KEYSEQ_HANDLED = "HANDLED"; 103 DwtKeyboardMgr.__KEYSEQ_PENDING = "PENDING"; 104 105 /** 106 * Checks if the event may be a shortcut from within an input (text input or 107 * textarea). Since printable characters are echoed, the shortcut must be non-printable: 108 * 109 * <ul> 110 * <li>Alt or Ctrl or Meta plus another key</li> 111 * <li>Esc</li> 112 * </ul> 113 * 114 * @param {DwtKeyEvent} ev the key event 115 * @return {boolean} <code>true</code> if the event may be a shortcut 116 */ 117 118 // Enter and all four arrows can be used as shortcuts in an INPUT 119 DwtKeyboardMgr.IS_INPUT_SHORTCUT_KEY = AjxUtil.arrayAsHash([ 120 DwtKeyEvent.KEY_END_OF_TEXT, 121 DwtKeyEvent.KEY_RETURN, 122 DwtKeyEvent.KEY_ARROW_LEFT, 123 DwtKeyEvent.KEY_ARROW_UP, 124 DwtKeyEvent.KEY_ARROW_RIGHT, 125 DwtKeyEvent.KEY_ARROW_DOWN 126 ]); 127 128 // Returns true if the key event has a keycode that could be used in an input (INPUT or TEXTAREA) as a shortcut. That 129 // excludes printable characters. 130 DwtKeyboardMgr.isPossibleInputShortcut = function(ev) { 131 132 var target = DwtUiEvent.getTarget(ev); 133 return !DwtKeyMap.IS_MODIFIER[ev.keyCode] && (ev.keyCode === DwtKeyEvent.KEY_ESCAPE || DwtKeyMapMgr.hasModifier(ev) || 134 (target && target.nodeName.toLowerCase() == "input" && DwtKeyboardMgr.IS_INPUT_SHORTCUT_KEY[ev.keyCode])); 135 }; 136 137 /** 138 * Pushes the tab group onto the stack and makes it the active tab group. 139 * 140 * @param {DwtTabGroup} tabGroup the tab group to push onto the stack 141 * 142 * @see #popTabGroup 143 */ 144 DwtKeyboardMgr.prototype.pushTabGroup = function(tabGroup, preventFocus) { 145 146 if (!(tabGroup && tabGroup.isDwtTabGroup)) { 147 DBG.println(AjxDebug.DBG1, "pushTabGroup() called without a tab group: " + tabGroup); 148 return; 149 } 150 151 DBG.println(AjxDebug.FOCUS, "PUSH tab group " + tabGroup.getName()); 152 this.__tabGrpStack.push(tabGroup); 153 this.__currTabGroup = tabGroup; 154 var focusMember = tabGroup.getFocusMember(); 155 if (!focusMember) { 156 focusMember = tabGroup.resetFocusMember(true); 157 } 158 if (!focusMember) { 159 DBG.println(AjxDebug.FOCUS, "DwtKeyboardMgr.pushTabGroup: tab group " + tabGroup.__name + " has no members!"); 160 return; 161 } 162 tabGroup.addFocusChangeListener(this.__tabGroupChangeListenerObj); 163 if (!preventFocus) { 164 this.grabFocus(focusMember); 165 } 166 }; 167 168 /** 169 * Pops the current tab group off the top of the tab group stack. The previous 170 * tab group (if there is one) then becomes the current tab group. 171 * 172 * @param {DwtTabGroup} [tabGroup] the tab group to pop. If supplied, then the tab group 173 * stack is searched for the tab group and it is removed. If <code>null</code>, then the 174 * top tab group is popped. 175 * 176 * @return {DwtTabGroup} the popped tab group or <code>null</code> if there is one or less tab groups 177 */ 178 DwtKeyboardMgr.prototype.popTabGroup = function(tabGroup) { 179 180 if (!(tabGroup && tabGroup.isDwtTabGroup)) { 181 DBG.println(AjxDebug.DBG1, "popTabGroup() called without a tab group: " + tabGroup); 182 return null; 183 } 184 185 DBG.println(AjxDebug.FOCUS, "POP tab group " + tabGroup.getName()); 186 187 // we never want an empty stack 188 if (this.__tabGrpStack.length <= 1) { 189 return null; 190 } 191 192 // If we are popping a tab group that is not on the top of the stack then 193 // we need to find it and remove it. 194 if (tabGroup && this.__tabGrpStack[this.__tabGrpStack.length - 1] != tabGroup) { 195 var a = this.__tabGrpStack; 196 var len = a.length; 197 for (var i = len - 1; i >= 0; i--) { 198 if (tabGroup == a[i]) { 199 a[i].dump(AjxDebug.DBG1); 200 break; 201 } 202 } 203 204 /* If there is no match in the stack for tabGroup, then simply return null, 205 * else if the match is not the top item on the stack, then remove it from 206 * the stack. Else we are dealing with the topmost item on the stack so handle it 207 * as a simple pop. */ 208 if (i < 0) { // No match 209 return null; 210 } else if (i != len - 1) { // item is not on top 211 // Remove tabGroup 212 a.splice(i, 1); 213 return tabGroup; 214 } 215 } 216 217 var tabGroup = this.__tabGrpStack.pop(); 218 tabGroup.removeFocusChangeListener(this.__tabGroupChangeListenerObj); 219 220 var currTg = null; 221 if (this.__tabGrpStack.length > 0) { 222 currTg = this.__tabGrpStack[this.__tabGrpStack.length - 1]; 223 var focusMember = currTg.getFocusMember(); 224 if (!focusMember) { 225 focusMember = currTg.resetFocusMember(true); 226 } 227 if (focusMember) { 228 this.grabFocus(focusMember); 229 } 230 } 231 this.__currTabGroup = currTg; 232 233 return tabGroup; 234 }; 235 236 /** 237 * Replaces the current tab group with the given tab group. 238 * 239 * @param {DwtTabGroup} tabGroup the tab group to use 240 * @return {DwtTabGroup} the old tab group 241 */ 242 DwtKeyboardMgr.prototype.setTabGroup = function(tabGroup) { 243 244 var otg = this.popTabGroup(); 245 this.pushTabGroup(tabGroup); 246 247 return otg; 248 }; 249 250 /** 251 * Gets the current tab group 252 * 253 * @return {DwtTabGroup} current tab group 254 */ 255 DwtKeyboardMgr.prototype.getCurrentTabGroup = function() { 256 257 return this.__currTabGroup; 258 }; 259 260 /** 261 * Adds a default handler to the stack. A handler should define a 'handleKeyAction' method. 262 * 263 * @param {Object} handler default handler 264 */ 265 DwtKeyboardMgr.prototype.pushDefaultHandler = function(handler) { 266 267 if (!this.isEnabled() || !handler) { 268 return; 269 } 270 DBG.println(AjxDebug.FOCUS, "PUSH default handler: " + handler); 271 272 this.__defaultHandlerStack.push(handler); 273 this.__currDefaultHandler = handler; 274 }; 275 276 /** 277 * Removes a default handler from the stack. 278 * 279 * @return {Object} handler a default handler 280 */ 281 DwtKeyboardMgr.prototype.popDefaultHandler = function() { 282 283 DBG.println(AjxDebug.FOCUS, "POP default handler"); 284 // we never want an empty stack 285 if (this.__defaultHandlerStack.length <= 1) { 286 return null; 287 } 288 289 DBG.println(AjxDebug.FOCUS, "Default handler stack length: " + this.__defaultHandlerStack.length); 290 var handler = this.__defaultHandlerStack.pop(); 291 this.__currDefaultHandler = this.__defaultHandlerStack[this.__defaultHandlerStack.length - 1]; 292 DBG.println(AjxDebug.FOCUS, "Default handler is now: " + this.__currDefaultHandler); 293 294 return handler; 295 }; 296 297 /** 298 * Sets the focus to the given object. 299 * 300 * @param {HTMLInputElement|DwtControl|string} focusObj the object to which to set focus, or its ID 301 */ 302 DwtKeyboardMgr.prototype.grabFocus = function(focusObj) { 303 304 if (typeof focusObj === "string") { 305 focusObj = document.getElementById(focusObj); 306 } 307 else if (focusObj && focusObj.isDwtTabGroup) { 308 focusObj = focusObj.getFocusMember() || focusObj.getFirstMember(); 309 } 310 311 if (!focusObj) { 312 return; 313 } 314 315 // Make sure tab group knows what's currently focused 316 if (this.__currTabGroup) { 317 this.__currTabGroup.setFocusMember(focusObj, false, true); 318 } 319 320 this.__doGrabFocus(focusObj); 321 }; 322 323 /** 324 * Tells the keyboard manager that the given control now has focus. That control will handle shortcuts and become 325 * the reference point for tabbing. 326 * 327 * @param {DwtControl|Element} focusObj control (or element) that has focus 328 */ 329 DwtKeyboardMgr.prototype.updateFocus = function(focusObj, ev) { 330 331 if (!focusObj) { 332 return; 333 } 334 335 var ctg = this.__currTabGroup; 336 if (ctg) { 337 this.__currTabGroup.__showFocusedItem(focusObj, "updateFocus"); 338 } 339 var control = focusObj.isDwtControl ? focusObj : DwtControl.findControl(focusObj); 340 341 // Set the keyboard mgr's focus obj, which will be handed shortcuts. It must be a DwtControl. 342 if (control) { 343 this.__focusObj = control; 344 DBG.println(AjxDebug.FOCUS, "DwtKeyboardMgr UPDATEFOCUS kbMgr focus obj: " + control); 345 } 346 347 // Update the current (usually root) tab group's focus member to whichever of these it contains: the focus obj, 348 // its tab group member, or its control. 349 var tgm = this._findTabGroupMember(ev || focusObj); 350 if (tgm && ctg) { 351 ctg.setFocusMember(tgm, false, true); 352 } 353 }; 354 355 // Goes up the DOM looking for something (element or control) that is in the current tab group. 356 DwtKeyboardMgr.prototype._findTabGroupMember = function(obj) { 357 358 var ctg = this.__currTabGroup; 359 if (!obj || !ctg) { 360 return; 361 } 362 363 var htmlEl = (obj.isDwtControl && obj.getHtmlElement()) || DwtUiEvent.getTarget(obj, false) || obj; 364 365 try { 366 while (htmlEl) { 367 if (ctg.contains(htmlEl)) { 368 return htmlEl; 369 } 370 else { 371 var control = DwtControl.ALL_BY_ID[htmlEl.id]; 372 if (control && ctg.contains(control)) { 373 return control; 374 } 375 else { 376 var tgm = control && control.getTabGroupMember && control.getTabGroupMember(); 377 if (tgm && ctg.contains(tgm)) { 378 return tgm; 379 } 380 } 381 } 382 htmlEl = htmlEl.parentNode; 383 } 384 } catch(e) { 385 } 386 387 return null; 388 }; 389 390 /** 391 * Gets the object that has focus. 392 * 393 * @return {HTMLInputElement|DwtControl} focusObj the object with focus 394 */ 395 DwtKeyboardMgr.prototype.getFocusObj = function(focusObj) { 396 397 return this.__focusObj; 398 }; 399 400 /** 401 * This method is used to register an application key handler. If registered, this 402 * handler must support the following methods: 403 * <ul> 404 * <li><i>getKeyMapName</i>: This method returns a string representing the key map 405 * to be used for looking up actions 406 * <li><i>handleKeyAction</i>: This method should handle the key action and return 407 * true if it handled it else false. <i>handleKeyAction</i> has two formal parameters 408 * <ul> 409 * <li><i>actionCode</i>: The action code to be handled</li> 410 * <li><i>ev</i>: the {@link DwtKeyEvent} corresponding to the last key event in the sequence</li> 411 * </ul> 412 * </ul> 413 * 414 * @param {function} hdlr the handler function. This method should have the following 415 * signature <code>Boolean hdlr(Int actionCode DwtKeyEvent event);</code> 416 * 417 * @see DwtKeyEvent 418 */ 419 DwtKeyboardMgr.prototype.registerDefaultKeyActionHandler = function(hdlr) { 420 421 if (this.isEnabled()) { 422 this.__defaultKeyActionHdlr = hdlr; 423 } 424 }; 425 426 /** 427 * Registers a keymap with the shell. A keymap typically 428 * is a subclass of {@link DwtKeyMap} and defines the mappings from key sequences to 429 * actions. 430 * 431 * @param {DwtKeyMap} keyMap the key map to register 432 * 433 */ 434 DwtKeyboardMgr.prototype.registerKeyMap = function(keyMap) { 435 436 if (this.isEnabled()) { 437 this.__keyMapMgr = new DwtKeyMapMgr(keyMap); 438 } 439 }; 440 441 /** 442 * Sets the timeout (in milliseconds) between key presses for handling multi-keypress sequences. 443 * 444 * @param {number} timeout the timeout (in milliseconds) 445 */ 446 DwtKeyboardMgr.prototype.setKeyTimeout = function(timeout) { 447 this.__keyTimeout = timeout; 448 }; 449 450 /** 451 * Clears the key sequence. The next key event will begin a new one. 452 * 453 */ 454 DwtKeyboardMgr.prototype.clearKeySeq = function() { 455 456 this.__killKeySeqTimedActionId = -1; 457 this.__keySequence = []; 458 }; 459 460 /** 461 * Enables/disables keyboard nav (shortcuts). 462 * 463 * @param {boolean} enabled if <code>true</code>, enable keyboard nav 464 */ 465 DwtKeyboardMgr.prototype.enable = function(enabled) { 466 467 DBG.println(AjxDebug.DBG2, "keyboard shortcuts enabled: " + enabled); 468 this.__shortcutsEnabled = enabled; 469 }; 470 471 DwtKeyboardMgr.prototype.isEnabled = function() { 472 return this.__shortcutsEnabled; 473 }; 474 475 /** 476 * Adds a global key event listener. 477 * 478 * @param {constant} ev key event type 479 * @param {AjxListener} listener listener to notify 480 */ 481 DwtKeyboardMgr.prototype.addListener = function(ev, listener) { 482 this._evtMgr.addListener(ev, listener); 483 }; 484 485 /** 486 * Removes a global key event listener. 487 * 488 * @param {constant} ev key event type 489 * @param {AjxListener} listener listener to remove 490 */ 491 DwtKeyboardMgr.prototype.removeListener = function(ev, listener) { 492 this._evtMgr.removeListener(ev, listener); 493 }; 494 495 DwtKeyboardMgr.prototype.__doGrabFocus = function(focusObj) { 496 497 if (!focusObj) { 498 return; 499 } 500 501 var curFocusObj = this.getFocusObj(); 502 if (curFocusObj && curFocusObj.blur) { 503 DBG.println(AjxDebug.FOCUS, "DwtKeyboardMgr DOGRABFOCUS cur focus obj: " + [curFocusObj, curFocusObj._htmlElId || curFocusObj.id].join(' / ')); 504 curFocusObj.blur(); 505 } 506 507 DBG.println(AjxDebug.FOCUS, "DwtKeyboardMgr DOGRABFOCUS new focus obj: " + [focusObj, focusObj._htmlElId || focusObj.id].join(' / ')); 508 if (focusObj.focus) { 509 // focus handler should lead to focus update, but just in case ... 510 this.updateFocus(focusObj.focus() || focusObj); 511 } 512 }; 513 514 /** 515 * @private 516 */ 517 DwtKeyboardMgr.__keyUpHdlr = function(ev) { 518 519 ev = DwtUiEvent.getEvent(ev); 520 DBG.println(AjxDebug.KEYBOARD, "keyup: " + ev.keyCode); 521 522 var kbMgr = DwtKeyboardMgr.__shell.getKeyboardMgr(); 523 if (kbMgr._evtMgr.notifyListeners(DwtEvent.ONKEYUP, ev) === false) { 524 return false; 525 } 526 527 // clear saved Gecko key 528 if (AjxEnv.isMac && AjxEnv.isGeckoBased && ev.keyCode === 0) { 529 return DwtKeyboardMgr.__keyDownHdlr(ev); 530 } 531 else { 532 return DwtKeyboardMgr.__handleKeyEvent(ev); 533 } 534 }; 535 536 /** 537 * @private 538 */ 539 DwtKeyboardMgr.__keyPressHdlr = function(ev) { 540 541 ev = DwtUiEvent.getEvent(ev); 542 DBG.println(AjxDebug.KEYBOARD, "keypress: " + (ev.keyCode || ev.charCode)); 543 544 var kbMgr = DwtKeyboardMgr.__shell.getKeyboardMgr(); 545 if (kbMgr._evtMgr.notifyListeners(DwtEvent.ONKEYPRESS, ev) === false) { 546 return false; 547 } 548 549 DwtKeyEvent.geckoCheck(ev); 550 551 return DwtKeyboardMgr.__handleKeyEvent(ev); 552 }; 553 554 /** 555 * @private 556 */ 557 DwtKeyboardMgr.__handleKeyEvent = 558 function(ev) { 559 560 if (DwtKeyboardMgr.__shell._blockInput) { 561 return false; 562 } 563 564 ev = DwtUiEvent.getEvent(ev, this); 565 DBG.println(AjxDebug.KEYBOARD, [ev.type, ev.keyCode, ev.charCode, ev.which].join(" / ")); 566 var kbMgr = DwtKeyboardMgr.__shell.getKeyboardMgr(); 567 var kev = DwtShell.keyEvent; 568 kev.setFromDhtmlEvent(ev); 569 570 if (kbMgr.__kbEventStatus != DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED) { 571 return kbMgr.__processKeyEvent(ev, kev, false); 572 } 573 }; 574 575 /** 576 * @private 577 */ 578 DwtKeyboardMgr.__keyDownHdlr = function(ev) { 579 580 try { 581 582 ev = DwtUiEvent.getEvent(ev, this); 583 var kbMgr = DwtKeyboardMgr.__shell.getKeyboardMgr(); 584 ev.focusObj = null; 585 if (kbMgr._evtMgr.notifyListeners(DwtEvent.ONKEYDOWN, ev) === false) { 586 return false; 587 } 588 589 if (DwtKeyboardMgr.__shell._blockInput) { 590 return false; 591 } 592 DBG.println(AjxDebug.KEYBOARD, [ev.type, ev.keyCode, ev.charCode, ev.which].join(" / ")); 593 594 var kev = DwtShell.keyEvent; 595 kev.setFromDhtmlEvent(ev); 596 var keyCode = DwtKeyEvent.getCharCode(ev); 597 DBG.println(AjxDebug.KEYBOARD, "keydown: " + keyCode + " -------- " + ev.target); 598 599 // Popdown any tooltip 600 DwtKeyboardMgr.__shell.getToolTip().popdown(); 601 602 /********* FOCUS MANAGEMENT *********/ 603 604 /* The first thing we care about is the tab key since we want to manage 605 * focus based on the tab groups. 606 * 607 * If the tab hit happens in the currently 608 * focused obj, the go to the next/prev element in the tab group. 609 * 610 * If the tab happens in an element that is in the tab group hierarchy, but that 611 * element is not the currently focus element in the tab hierarchy (e.g. the user 612 * clicked in it and we didnt detect it) then sync the tab group's current focus 613 * element and handle the tab 614 * 615 * If the tab happens in an object not under the tab group hierarchy, then set 616 * focus to the current focus object in the tab hierarchy i.e. grab back control 617 */ 618 var ctg = kbMgr.__currTabGroup, 619 member; 620 621 if (keyCode == DwtKeyEvent.KEY_TAB) { 622 if (ctg && !DwtKeyMapMgr.hasModifier(kev)) { 623 DBG.println(AjxDebug.FOCUS, "Tab"); 624 // If the tab hit is in an element or if the current tab group has a focus member 625 if (ctg.getFocusMember()) { 626 member = kev.shiftKey ? ctg.getPrevFocusMember(true) : ctg.getNextFocusMember(true); 627 } 628 else { 629 DBG.println(AjxDebug.FOCUS, "DwtKeyboardMgr.__keyDownHdlr: no current focus member, resetting to first in tab group"); 630 // If there is no current focus member, then reset 631 member = ctg.resetFocusMember(true); 632 } 633 } 634 // If we did not handle the Tab, let the browser handle it 635 return kbMgr.__processKeyEvent(ev, kev, !member, member ? DwtKeyboardMgr.__KEYSEQ_HANDLED : DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED); 636 } 637 else if (ctg && AjxEnv.isGecko && kev.target instanceof HTMLHtmlElement) { 638 /* With FF we focus get set to the <html> element when tabbing in 639 * from the address or search fields. What we want to do is capture 640 * this here and reset the focus to the first element in the tabgroup 641 * 642 * TODO Verify this trick is needed/works with IE/Safari 643 */ 644 member = ctg.resetFocusMember(true); 645 } 646 647 // Allow key events to propagate when keyboard manager is disabled (to avoid taking over browser shortcuts). Bugzilla #45469. 648 if (!kbMgr.isEnabled()) { 649 return true; 650 } 651 652 653 /********* SHORTCUTS *********/ 654 655 // Filter out modifier keys. If we're in an input field, filter out legitimate input. 656 // (A shortcut from an input field must use a modifier key.) 657 if (DwtKeyMap.IS_MODIFIER[keyCode] || (kbMgr.__killKeySeqTimedActionId === -1 && 658 kev.target && DwtKeyMapMgr.isInputElement(kev.target) && !kev.target["data-hidden"] && !DwtKeyboardMgr.isPossibleInputShortcut(kev))) { 659 660 return kbMgr.__processKeyEvent(ev, kev, true, DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED); 661 } 662 663 /* Cancel any pending time action to kill the keysequence */ 664 if (kbMgr.__killKeySeqTimedActionId != -1) { 665 AjxTimedAction.cancelAction(kbMgr.__killKeySeqTimedActionId); 666 kbMgr.__killKeySeqTimedActionId = -1; 667 } 668 669 var parts = []; 670 if (kev.altKey) { parts.push(DwtKeyMap.ALT); } 671 if (kev.ctrlKey) { parts.push(DwtKeyMap.CTRL); } 672 if (kev.metaKey) { parts.push(DwtKeyMap.META); } 673 if (kev.shiftKey) { parts.push(DwtKeyMap.SHIFT); } 674 parts.push(keyCode); 675 kbMgr.__keySequence[kbMgr.__keySequence.length] = parts.join(DwtKeyMap.JOIN); 676 677 DBG.println(AjxDebug.KEYBOARD, "KEYCODE: " + keyCode + " - KEY SEQ: " + kbMgr.__keySequence.join("")); 678 679 var handled = DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 680 681 // First see if the control that currently has focus can handle the key event 682 var obj = ev.focusObj || kbMgr.__focusObj; 683 var hasFocus = obj && obj.hasFocus && obj.hasFocus(); 684 DBG.println(AjxDebug.KEYBOARD, "DwtKeyboardMgr::__keyDownHdlr - focus object " + obj + " has focus: " + hasFocus); 685 if (hasFocus && obj.handleKeyAction) { 686 handled = kbMgr.__dispatchKeyEvent(obj, kev); 687 while ((handled === DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED) && obj.parent) { 688 obj = obj.parent; 689 if (obj.getKeyMapName) { 690 handled = kbMgr.__dispatchKeyEvent(obj, kev); 691 } 692 } 693 } 694 695 // If the currently focused control didn't handle the event, hand it to the default key event handler 696 if (handled === DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED && kbMgr.__currDefaultHandler) { 697 handled = kbMgr.__dispatchKeyEvent(kbMgr.__currDefaultHandler, kev); 698 } 699 700 // see if we should let browser handle the event as well; note that we need to set the 'handled' var rather than 701 // just the 'propagate' one below, since the keyboard mgr isn't built for both it and the browser to handle the event. 702 if (kev.forcePropagate) { 703 handled = DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 704 kev.forcePropagate = false; 705 } 706 707 kbMgr.__kbEventStatus = handled; 708 var propagate = (handled == DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED); 709 710 if (handled != DwtKeyboardMgr.__KEYSEQ_PENDING) { 711 kbMgr.clearKeySeq(); 712 } 713 714 return kbMgr.__processKeyEvent(ev, kev, propagate); 715 716 } catch (ex) { 717 AjxException.reportScriptError(ex); 718 } 719 }; 720 721 /** 722 * Handles event dispatching 723 * 724 * @private 725 */ 726 DwtKeyboardMgr.prototype.__dispatchKeyEvent = function(hdlr, ev, forceActionCode) { 727 728 if (hdlr && hdlr.handleKeyEvent) { 729 var handled = hdlr.handleKeyEvent(ev); 730 return handled ? DwtKeyboardMgr.__KEYSEQ_HANDLED : DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 731 } 732 733 var mapName = (hdlr && hdlr.getKeyMapName) ? hdlr.getKeyMapName() : null; 734 if (!mapName) { 735 return DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 736 } 737 738 DBG.println(AjxDebug.KEYBOARD, "DwtKeyboardMgr.__dispatchKeyEvent: handler " + hdlr.toString() + " handling " + this.__keySequence + " for map: " + mapName); 739 var actionCode = this.__keyMapMgr.getActionCode(this.__keySequence, mapName, forceActionCode); 740 if (actionCode === DwtKeyMapMgr.NOT_A_TERMINAL) { 741 DBG.println(AjxDebug.KEYBOARD, "scheduling action to kill key sequence"); 742 /* setup a timed action to redispatch/kill the key sequence in the event 743 * the user does not press another key in the allotted time */ 744 this.__hdlr = hdlr; 745 this.__mapName = mapName; 746 this.__ev = ev; 747 this.__killKeySeqTimedActionId = AjxTimedAction.scheduleAction(this.__killKeySeqTimedAction, this.__keyTimeout); 748 return DwtKeyboardMgr.__KEYSEQ_PENDING; 749 } 750 else if (actionCode != null) { 751 /* It is possible that the component may not handle a valid action 752 * particulary actions defined in the default map */ 753 DBG.println(AjxDebug.KEYBOARD, "DwtKeyboardMgr.__dispatchKeyEvent: handling action: " + actionCode); 754 if (!hdlr.handleKeyAction) { 755 return DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 756 } 757 var result = hdlr.handleKeyAction(actionCode, ev); 758 return result ? DwtKeyboardMgr.__KEYSEQ_HANDLED : DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 759 } 760 else { 761 DBG.println(AjxDebug.KEYBOARD, "DwtKeyboardMgr.__dispatchKeyEvent: no action code for " + this.__keySequence); 762 return DwtKeyboardMgr.__KEYSEQ_NOT_HANDLED; 763 } 764 }; 765 766 /** 767 * This method will reattempt to handle the event in the case that the intermediate 768 * node in the keymap may have an action code associated with it. 769 * 770 * @private 771 */ 772 DwtKeyboardMgr.prototype.__killKeySequenceAction = function() { 773 774 DBG.println(AjxDebug.KEYBOARD, "DwtKeyboardMgr.__killKeySequenceAction: " + this.__mapName); 775 this.__dispatchKeyEvent(this.__hdlr, this.__ev, true); 776 this.clearKeySeq(); 777 }; 778 779 /** 780 * @private 781 */ 782 DwtKeyboardMgr.prototype.__tabGrpChangeListener = function(ev) { 783 this.__doGrabFocus(ev.newFocusMember); 784 }; 785 786 /** 787 * @private 788 */ 789 DwtKeyboardMgr.prototype.__processKeyEvent = function(ev, kev, propagate, status) { 790 791 if (status) { 792 this.__kbEventStatus = status; 793 } 794 kev._stopPropagation = !propagate; 795 kev._returnValue = propagate; 796 kev.setToDhtmlEvent(ev); 797 DBG.println(AjxDebug.KEYBOARD, "key event returning: " + propagate); 798 return propagate; 799 }; 800