2011-06-14 12:13:47 +02:00
|
|
|
/**
|
2021-06-12 17:03:22 +02:00
|
|
|
* EGroupware egw_action framework - Shortcut/Keyboard input manager
|
2011-06-14 12:13:47 +02:00
|
|
|
*
|
2021-06-12 17:03:22 +02:00
|
|
|
* @link https://www.egroupware.org
|
2011-06-14 12:13:47 +02:00
|
|
|
* @author Andreas Stöckel <as@stylite.de>
|
|
|
|
* @copyright 2011 by Andreas Stöckel
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @package egw_action
|
|
|
|
*/
|
|
|
|
|
2011-08-03 16:04:30 +02:00
|
|
|
/*egw:uses
|
2016-06-06 17:38:20 +02:00
|
|
|
vendor.bower-asset.jquery.dist.jquery;
|
2011-08-03 16:04:30 +02:00
|
|
|
egw_action;
|
2011-06-14 12:13:47 +02:00
|
|
|
*/
|
|
|
|
|
2023-07-10 16:02:30 +02:00
|
|
|
import {egw_getAppObjectManager, egw_globalObjectManager} from "./egw_action";
|
|
|
|
import {_egw_active_menu} from "./egw_menu";
|
2021-06-14 14:57:42 +02:00
|
|
|
import {
|
|
|
|
EGW_AO_FLAG_DEFAULT_FOCUS,
|
|
|
|
EGW_AO_EXEC_SELECTED,
|
|
|
|
EGW_VALID_KEYS,
|
|
|
|
EGW_KEY_MENU,
|
|
|
|
EGW_KEY_F1, EGW_KEY_F12
|
2023-07-10 16:02:30 +02:00
|
|
|
} from "./egw_action_constants";
|
|
|
|
import {egwBitIsSet} from "./egw_action_common";
|
|
|
|
import type {EgwActionObject} from "./EgwActionObject";
|
|
|
|
import type {EgwActionObjectManager} from "./EgwActionObjectManager";
|
2011-06-14 12:13:47 +02:00
|
|
|
|
|
|
|
/**
|
2023-07-10 16:02:30 +02:00
|
|
|
* The translation function converts the given native key code into one of the
|
2011-06-14 12:13:47 +02:00
|
|
|
* egw key constants as listed above. This key codes were chosen to match the
|
|
|
|
* key codes of IE and FF.
|
|
|
|
*/
|
2023-07-10 16:02:30 +02:00
|
|
|
export var egw_keycode_translation_function = function (_nativeKeyCode) {
|
2011-06-14 12:13:47 +02:00
|
|
|
// Map the numpad to the 0..9 keys
|
|
|
|
if (_nativeKeyCode >= 96 && _nativeKeyCode <= 105)
|
|
|
|
{
|
|
|
|
_nativeKeyCode -= 48
|
|
|
|
}
|
|
|
|
|
|
|
|
return _nativeKeyCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether the given keycode is in the list of valid key codes. If not,
|
|
|
|
* returns -1.
|
|
|
|
*/
|
2023-07-10 16:02:30 +02:00
|
|
|
export function egw_keycode_makeValid(_keyCode)
|
|
|
|
{
|
|
|
|
const idx = EGW_VALID_KEYS.indexOf(_keyCode);
|
|
|
|
if (idx >= 0)
|
|
|
|
{
|
2011-06-14 12:13:47 +02:00
|
|
|
return _keyCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-06-15 22:54:58 +02:00
|
|
|
function _egw_nodeIsInInput(_node)
|
|
|
|
{
|
|
|
|
if ((_node != null) && (_node != document))
|
|
|
|
{
|
2023-07-10 16:02:30 +02:00
|
|
|
const tagName = _node.tagName.toLowerCase();
|
2022-07-26 22:23:55 +02:00
|
|
|
if (tagName == "input" || tagName == "select" || tagName == 'textarea' || tagName == 'button' ||
|
|
|
|
['et2-textbox', 'et2-number', 'et2-searchbox', 'et2-select', 'et2-textarea', 'et2-button'].indexOf(tagName) != -1)
|
2011-06-15 22:54:58 +02:00
|
|
|
{
|
|
|
|
return true;
|
2023-07-10 16:02:30 +02:00
|
|
|
} else
|
2011-06-15 22:54:58 +02:00
|
|
|
{
|
|
|
|
return _egw_nodeIsInInput(_node.parentNode);
|
|
|
|
}
|
2023-07-10 16:02:30 +02:00
|
|
|
} else
|
2011-06-15 22:54:58 +02:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-10 16:02:30 +02:00
|
|
|
/**
|
|
|
|
* execute
|
|
|
|
* @param fn after DOM is ready
|
|
|
|
* replacement for jQuery.ready()
|
|
|
|
*/
|
|
|
|
function ready(fn)
|
|
|
|
{
|
|
|
|
if (document.readyState !== 'loading')
|
|
|
|
{
|
|
|
|
fn();
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
document.addEventListener('DOMContentLoaded', fn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-14 12:13:47 +02:00
|
|
|
/**
|
2016-06-06 17:38:20 +02:00
|
|
|
* Register the onkeypress handler on the document
|
2011-06-14 12:13:47 +02:00
|
|
|
*/
|
2023-07-10 16:02:30 +02:00
|
|
|
ready(() => {//waits for DOM ready
|
2011-06-14 12:13:47 +02:00
|
|
|
|
|
|
|
// Fetch the key down event and translate it into browser-independent and
|
|
|
|
// easy to use key codes and shift states
|
2023-07-10 16:02:30 +02:00
|
|
|
document.addEventListener("keydown", (keyboardEvent: KeyboardEvent) => {
|
2011-06-14 12:13:47 +02:00
|
|
|
|
|
|
|
// Translate the given key code and make it valid
|
2023-07-10 16:02:30 +02:00
|
|
|
// TODO there are actual string key codes now so it would be nice to use those standardized ones instead of using our own
|
|
|
|
let keyCode = keyboardEvent.keyCode;
|
2011-06-14 12:13:47 +02:00
|
|
|
keyCode = egw_keycode_translation_function(keyCode);
|
|
|
|
keyCode = egw_keycode_makeValid(keyCode);
|
|
|
|
|
|
|
|
// Only go on if this is a valid key code - call the key handler
|
|
|
|
if (keyCode != -1)
|
|
|
|
{
|
2021-05-13 18:01:38 +02:00
|
|
|
// Check whether the event came from the sidebox - if yes, ignore
|
2023-07-10 16:02:30 +02:00
|
|
|
//if(jQuery(keyboardEvent.target).parents("#egw_fw_sidemenu").length > 0) return;
|
|
|
|
let target: any = keyboardEvent.target // this is some kind of element
|
|
|
|
while ((target = target.parentNode) && target !== document)
|
|
|
|
{
|
|
|
|
if (!"#egw_fw_sidemenu" || target.matches("#egw_fw_sidemenu")) return;
|
|
|
|
}
|
2021-05-13 18:01:38 +02:00
|
|
|
|
2021-05-13 23:09:53 +02:00
|
|
|
// If a context menu is open, give the keyboard to it
|
2021-06-11 09:06:25 +02:00
|
|
|
if (typeof _egw_active_menu !== undefined && _egw_active_menu &&
|
2023-07-10 16:02:30 +02:00
|
|
|
_egw_active_menu.keyHandler(keyCode, keyboardEvent.shiftKey, keyboardEvent.ctrlKey || keyboardEvent.metaKey, keyboardEvent.altKey))
|
2021-05-13 23:09:53 +02:00
|
|
|
{
|
2023-07-10 16:02:30 +02:00
|
|
|
keyboardEvent.preventDefault();
|
2021-05-13 23:09:53 +02:00
|
|
|
return;
|
|
|
|
}
|
2011-06-15 22:54:58 +02:00
|
|
|
// Check whether the event came from an input field - if yes, only
|
|
|
|
// allow function keys (like F1) to be captured by our code
|
2023-07-10 16:02:30 +02:00
|
|
|
const inInput = _egw_nodeIsInInput(keyboardEvent.target);
|
2011-06-15 22:54:58 +02:00
|
|
|
if (!inInput || (keyCode >= EGW_KEY_F1 && keyCode <= EGW_KEY_F12))
|
2011-06-14 12:13:47 +02:00
|
|
|
{
|
2023-07-10 16:02:30 +02:00
|
|
|
if (egw_keyHandler(keyCode, keyboardEvent.shiftKey, keyboardEvent.ctrlKey || keyboardEvent.metaKey, keyboardEvent.altKey))
|
2011-06-15 22:54:58 +02:00
|
|
|
{
|
|
|
|
// If the key handler successfully passed the key event to some
|
2023-07-10 16:02:30 +02:00
|
|
|
// subcomponent, prevent the default action
|
|
|
|
keyboardEvent.preventDefault();
|
2011-06-15 22:54:58 +02:00
|
|
|
}
|
2011-06-14 12:13:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2013-04-09 18:13:10 +02:00
|
|
|
/**
|
|
|
|
* Required to catch the context menu
|
|
|
|
*/
|
2016-06-02 16:51:15 +02:00
|
|
|
jQuery(window).on("contextmenu",document, function(event) {
|
2014-01-11 16:20:11 +01:00
|
|
|
// Check for actual key press
|
2023-07-10 16:02:30 +02:00
|
|
|
if (!(event.originalEvent.x == 1 && event.originalEvent.y == 1)) return true;
|
|
|
|
if (!event.ctrlKey && egw_keyHandler(EGW_KEY_MENU, event.shiftKey, event.ctrlKey || event.metaKey, event.altKey))
|
2013-04-09 18:13:10 +02:00
|
|
|
{
|
|
|
|
// If the key handler successfully passed the key event to some
|
2023-07-10 16:02:30 +02:00
|
|
|
// subcomponent, prevent the default action
|
2013-04-09 18:13:10 +02:00
|
|
|
event.preventDefault();
|
2013-04-10 11:03:08 +02:00
|
|
|
return false;
|
2013-04-09 18:13:10 +02:00
|
|
|
}
|
2013-04-10 11:03:08 +02:00
|
|
|
return true;
|
2023-07-10 16:02:30 +02:00
|
|
|
})
|
2013-04-09 18:13:10 +02:00
|
|
|
|
2011-06-19 12:48:51 +02:00
|
|
|
|
|
|
|
/**
|
2023-07-10 16:02:30 +02:00
|
|
|
* Creates a unique key for the given shortcut
|
|
|
|
* TODO those ids exist already
|
2011-06-14 12:13:47 +02:00
|
|
|
*/
|
2023-07-10 16:02:30 +02:00
|
|
|
export function egw_shortcutIdx(_keyCode: number, _shift: boolean, _ctrl: boolean, _alt: boolean):string
|
2011-06-14 12:13:47 +02:00
|
|
|
{
|
2016-06-06 17:38:20 +02:00
|
|
|
return "_" + _keyCode + "_" +
|
2011-06-14 12:13:47 +02:00
|
|
|
(_shift ? "S" : "") +
|
|
|
|
(_ctrl ? "C" : "") +
|
|
|
|
(_alt ? "A" : "");
|
|
|
|
}
|
2023-07-10 16:02:30 +02:00
|
|
|
type Shortcut= {"handler":()=> boolean,
|
|
|
|
"context": any,
|
|
|
|
"shortcut":{
|
|
|
|
"keyCode": number,
|
|
|
|
"shift": boolean,
|
|
|
|
"ctrl": boolean,
|
|
|
|
"alt":boolean
|
|
|
|
}}
|
2011-06-14 12:13:47 +02:00
|
|
|
|
2023-07-10 16:02:30 +02:00
|
|
|
export var egw_registeredShortcuts = {}
|
2011-06-14 12:13:47 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a global shortcut. If the shortcut already exists, it is overwritten.
|
2023-07-10 16:02:30 +02:00
|
|
|
* @param {int} _keyCode is one of the keycode constants
|
|
|
|
* @param {bool} _shift whether shift has to be set
|
|
|
|
* @param {bool} _ctrl whether ctrl has to be set
|
|
|
|
* @param {bool} _alt whether alt has to be set
|
|
|
|
* @param {function} _handler the function which will be called when the shortcut
|
2011-06-14 12:13:47 +02:00
|
|
|
* is evoked. An object containing the shortcut data will be passed as first
|
|
|
|
* parameter.
|
2023-07-10 16:02:30 +02:00
|
|
|
* @param {any} _context is the context in which the function will be executed
|
2011-06-14 12:13:47 +02:00
|
|
|
*/
|
2023-07-10 16:02:30 +02:00
|
|
|
export function egw_registerGlobalShortcut(_keyCode: number, _shift: boolean, _ctrl: boolean, _alt: boolean, _handler: () => boolean, _context: any)
|
2011-06-14 12:13:47 +02:00
|
|
|
{
|
|
|
|
// Generate the hash map index for the shortcut
|
2023-07-10 16:02:30 +02:00
|
|
|
const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt);
|
2011-06-14 12:13:47 +02:00
|
|
|
|
|
|
|
// Register the shortcut
|
|
|
|
egw_registeredShortcuts[idx] = {
|
|
|
|
"handler": _handler,
|
|
|
|
"context": _context,
|
|
|
|
"shortcut": {
|
|
|
|
"keyCode": _keyCode,
|
|
|
|
"shift": _shift,
|
|
|
|
"ctrl": _ctrl,
|
|
|
|
"alt": _alt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregisters the given shortcut.
|
|
|
|
*/
|
2021-06-11 11:31:06 +02:00
|
|
|
export function egw_unregisterGlobalShortcut(_keyCode, _shift, _ctrl, _alt)
|
|
|
|
{
|
2011-06-14 12:13:47 +02:00
|
|
|
// Generate the hash map index for the shortcut
|
2023-07-10 16:02:30 +02:00
|
|
|
const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt);
|
2011-06-14 12:13:47 +02:00
|
|
|
|
|
|
|
// Delete the entry from the hash map
|
|
|
|
delete egw_registeredShortcuts[idx];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the egw_keyHandler function handles various key presses. The boolean
|
|
|
|
* _shift, _ctrl, _alt values have been translated into platform independent
|
|
|
|
* values (for apple devices).
|
|
|
|
*/
|
2023-07-10 16:02:30 +02:00
|
|
|
export function egw_keyHandler(_keyCode, _shift, _ctrl, _alt)
|
|
|
|
{
|
2011-06-14 12:13:47 +02:00
|
|
|
|
|
|
|
// Check whether there is a global shortcut waiting for the keypress event
|
2023-07-10 16:02:30 +02:00
|
|
|
const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt);
|
2011-06-14 12:13:47 +02:00
|
|
|
if (typeof egw_registeredShortcuts[idx] != "undefined")
|
|
|
|
{
|
2023-07-10 16:02:30 +02:00
|
|
|
const shortcut:Shortcut = egw_registeredShortcuts[idx];
|
2011-06-14 12:13:47 +02:00
|
|
|
|
2015-10-27 17:45:37 +01:00
|
|
|
// Call the registered shortcut function and return its result, if it handled it
|
2023-07-10 16:02:30 +02:00
|
|
|
const result = shortcut.handler.call(shortcut.context, shortcut.shortcut);
|
|
|
|
if (result) return result;
|
2011-06-14 12:13:47 +02:00
|
|
|
}
|
2016-06-06 17:38:20 +02:00
|
|
|
|
2015-10-27 17:45:37 +01:00
|
|
|
// Pass the keypress to the currently focused action object
|
|
|
|
|
|
|
|
// Get the object manager and fetch the container of the currently
|
|
|
|
// focused object
|
2023-07-10 16:02:30 +02:00
|
|
|
let focusedObject: EgwActionObject = egw_globalObjectManager ? egw_globalObjectManager.getFocusedObject() : null;
|
|
|
|
const appMgr: EgwActionObjectManager = egw_getAppObjectManager(false);
|
2020-05-20 18:31:32 +02:00
|
|
|
if (appMgr && !focusedObject)
|
2011-06-14 12:13:47 +02:00
|
|
|
{
|
2020-05-20 18:31:32 +02:00
|
|
|
focusedObject = appMgr.getFocusedObject();
|
2011-06-14 12:13:47 +02:00
|
|
|
|
2015-10-27 17:45:37 +01:00
|
|
|
if (!focusedObject)
|
2011-06-14 12:13:47 +02:00
|
|
|
{
|
2015-10-27 17:45:37 +01:00
|
|
|
// If the current application doesn't have a focused object,
|
|
|
|
// check whether the application object manager contains an object
|
|
|
|
// with the EGW_AO_FLAG_DEFAULT_FOCUS flag set.
|
2023-07-10 16:02:30 +02:00
|
|
|
let egwActionObject:EgwActionObject = null;
|
|
|
|
for (const child of appMgr.children)
|
2011-06-15 22:54:58 +02:00
|
|
|
{
|
2015-10-27 17:45:37 +01:00
|
|
|
if (egwBitIsSet(EGW_AO_FLAG_DEFAULT_FOCUS, child.flags))
|
2011-06-15 22:54:58 +02:00
|
|
|
{
|
2023-07-10 16:02:30 +02:00
|
|
|
egwActionObject = child;
|
2015-10-27 17:45:37 +01:00
|
|
|
break;
|
2011-06-15 22:54:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-27 17:45:37 +01:00
|
|
|
// Get the first child of the found container and focus the first
|
|
|
|
// object
|
2023-07-10 16:02:30 +02:00
|
|
|
if (egwActionObject && egwActionObject.children.length > 0)
|
2011-06-14 12:13:47 +02:00
|
|
|
{
|
2023-07-10 16:02:30 +02:00
|
|
|
egwActionObject.children[0].setFocused(true);
|
|
|
|
focusedObject = egwActionObject.children[0];
|
2015-10-27 17:45:37 +01:00
|
|
|
}
|
|
|
|
}
|
2020-05-20 18:31:32 +02:00
|
|
|
}
|
|
|
|
if (focusedObject)
|
|
|
|
{
|
|
|
|
// Handle the default keys (arrow_up, down etc.)
|
2023-07-10 16:02:30 +02:00
|
|
|
let egwActionObject = focusedObject.getContainerRoot();
|
|
|
|
let handled = false;
|
2011-06-16 15:43:46 +02:00
|
|
|
|
2023-07-10 16:02:30 +02:00
|
|
|
if (egwActionObject)
|
2015-10-27 17:45:37 +01:00
|
|
|
{
|
2023-07-10 16:02:30 +02:00
|
|
|
handled = egwActionObject.handleKeyPress(_keyCode, _shift, _ctrl, _alt);
|
2020-05-20 18:31:32 +02:00
|
|
|
}
|
2011-06-16 15:43:46 +02:00
|
|
|
|
2020-05-20 18:31:32 +02:00
|
|
|
// Execute the egw_popup key handler of the focused object
|
|
|
|
if (!handled)
|
|
|
|
{
|
|
|
|
return focusedObject.executeActionImplementation(
|
2023-07-10 16:02:30 +02:00
|
|
|
{
|
|
|
|
"keyEvent": {
|
|
|
|
"keyCode": _keyCode,
|
|
|
|
"shift": _shift,
|
|
|
|
"ctrl": _ctrl,
|
|
|
|
"alt": _alt
|
|
|
|
}
|
|
|
|
}, "popup", EGW_AO_EXEC_SELECTED);
|
2015-10-27 17:45:37 +01:00
|
|
|
}
|
2020-05-20 18:31:32 +02:00
|
|
|
|
|
|
|
return handled;
|
2011-06-14 12:13:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|