mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-28 10:53:39 +01:00
f430b66d3b
classes are now uppercase and in their own files. lowercase classes are deprecated.
Interfaces are now actual interfaces that should be implemented instead of creating and returning an ai Object every time
(cherry picked from commit 5e3c67a5cf
)
308 lines
8.7 KiB
TypeScript
Executable File
308 lines
8.7 KiB
TypeScript
Executable File
/**
|
|
* EGroupware egw_action framework - Shortcut/Keyboard input manager
|
|
*
|
|
* @link https://www.egroupware.org
|
|
* @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
|
|
*/
|
|
|
|
/*egw:uses
|
|
vendor.bower-asset.jquery.dist.jquery;
|
|
egw_action;
|
|
*/
|
|
|
|
import {egw_getAppObjectManager, egw_globalObjectManager} from "./egw_action";
|
|
import {_egw_active_menu} from "./egw_menu";
|
|
import {
|
|
EGW_AO_FLAG_DEFAULT_FOCUS,
|
|
EGW_AO_EXEC_SELECTED,
|
|
EGW_VALID_KEYS,
|
|
EGW_KEY_MENU,
|
|
EGW_KEY_F1, EGW_KEY_F12
|
|
} from "./egw_action_constants";
|
|
import {egwBitIsSet} from "./egw_action_common";
|
|
import type {EgwActionObject} from "./EgwActionObject";
|
|
import type {EgwActionObjectManager} from "./EgwActionObjectManager";
|
|
|
|
/**
|
|
* The translation function converts the given native key code into one of the
|
|
* egw key constants as listed above. This key codes were chosen to match the
|
|
* key codes of IE and FF.
|
|
*/
|
|
export var egw_keycode_translation_function = function (_nativeKeyCode) {
|
|
// 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.
|
|
*/
|
|
export function egw_keycode_makeValid(_keyCode)
|
|
{
|
|
const idx = EGW_VALID_KEYS.indexOf(_keyCode);
|
|
if (idx >= 0)
|
|
{
|
|
return _keyCode;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
function _egw_nodeIsInInput(_node)
|
|
{
|
|
if ((_node != null) && (_node != document))
|
|
{
|
|
const tagName = _node.tagName.toLowerCase();
|
|
if (tagName == "input" || tagName == "select" || tagName == 'textarea' || tagName == 'button' ||
|
|
['et2-textbox', 'et2-number', 'et2-searchbox', 'et2-select', 'et2-textarea', 'et2-button'].indexOf(tagName) != -1)
|
|
{
|
|
return true;
|
|
} else
|
|
{
|
|
return _egw_nodeIsInInput(_node.parentNode);
|
|
}
|
|
} else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* execute
|
|
* @param fn after DOM is ready
|
|
* replacement for jQuery.ready()
|
|
*/
|
|
function ready(fn)
|
|
{
|
|
if (document.readyState !== 'loading')
|
|
{
|
|
fn();
|
|
} else
|
|
{
|
|
document.addEventListener('DOMContentLoaded', fn);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the onkeypress handler on the document
|
|
*/
|
|
ready(() => {//waits for DOM ready
|
|
|
|
// Fetch the key down event and translate it into browser-independent and
|
|
// easy to use key codes and shift states
|
|
document.addEventListener("keydown", (keyboardEvent: KeyboardEvent) => {
|
|
|
|
// Translate the given key code and make it valid
|
|
// 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;
|
|
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)
|
|
{
|
|
// Check whether the event came from the sidebox - if yes, ignore
|
|
//if(jQuery(keyboardEvent.target).parents("#egw_fw_sidemenu").length > 0) return;
|
|
//TODO check replacement with ralf or Nathan
|
|
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;
|
|
}
|
|
|
|
// If a context menu is open, give the keyboard to it
|
|
if (typeof _egw_active_menu !== undefined && _egw_active_menu &&
|
|
_egw_active_menu.keyHandler(keyCode, keyboardEvent.shiftKey, keyboardEvent.ctrlKey || keyboardEvent.metaKey, keyboardEvent.altKey))
|
|
{
|
|
keyboardEvent.preventDefault();
|
|
return;
|
|
}
|
|
// Check whether the event came from an input field - if yes, only
|
|
// allow function keys (like F1) to be captured by our code
|
|
const inInput = _egw_nodeIsInInput(keyboardEvent.target);
|
|
if (!inInput || (keyCode >= EGW_KEY_F1 && keyCode <= EGW_KEY_F12))
|
|
{
|
|
if (egw_keyHandler(keyCode, keyboardEvent.shiftKey, keyboardEvent.ctrlKey || keyboardEvent.metaKey, keyboardEvent.altKey))
|
|
{
|
|
// If the key handler successfully passed the key event to some
|
|
// subcomponent, prevent the default action
|
|
keyboardEvent.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
/**
|
|
* Required to catch the context menu
|
|
*/
|
|
jQuery(window).on("contextmenu",document, function(event) {
|
|
// Check for actual key press
|
|
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))
|
|
{
|
|
// If the key handler successfully passed the key event to some
|
|
// subcomponent, prevent the default action
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
return true;
|
|
})
|
|
|
|
|
|
/**
|
|
* Creates a unique key for the given shortcut
|
|
* TODO those ids exist already
|
|
*/
|
|
export function egw_shortcutIdx(_keyCode: number, _shift: boolean, _ctrl: boolean, _alt: boolean):string
|
|
{
|
|
return "_" + _keyCode + "_" +
|
|
(_shift ? "S" : "") +
|
|
(_ctrl ? "C" : "") +
|
|
(_alt ? "A" : "");
|
|
}
|
|
type Shortcut= {"handler":()=> boolean,
|
|
"context": any,
|
|
"shortcut":{
|
|
"keyCode": number,
|
|
"shift": boolean,
|
|
"ctrl": boolean,
|
|
"alt":boolean
|
|
}}
|
|
|
|
export var egw_registeredShortcuts = {}
|
|
|
|
/**
|
|
* Registers a global shortcut. If the shortcut already exists, it is overwritten.
|
|
* @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
|
|
* is evoked. An object containing the shortcut data will be passed as first
|
|
* parameter.
|
|
* @param {any} _context is the context in which the function will be executed
|
|
*/
|
|
export function egw_registerGlobalShortcut(_keyCode: number, _shift: boolean, _ctrl: boolean, _alt: boolean, _handler: () => boolean, _context: any)
|
|
{
|
|
// Generate the hash map index for the shortcut
|
|
const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt);
|
|
|
|
// Register the shortcut
|
|
egw_registeredShortcuts[idx] = {
|
|
"handler": _handler,
|
|
"context": _context,
|
|
"shortcut": {
|
|
"keyCode": _keyCode,
|
|
"shift": _shift,
|
|
"ctrl": _ctrl,
|
|
"alt": _alt
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unregisters the given shortcut.
|
|
*/
|
|
export function egw_unregisterGlobalShortcut(_keyCode, _shift, _ctrl, _alt)
|
|
{
|
|
// Generate the hash map index for the shortcut
|
|
const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt);
|
|
|
|
// 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).
|
|
*/
|
|
export function egw_keyHandler(_keyCode, _shift, _ctrl, _alt)
|
|
{
|
|
|
|
// Check whether there is a global shortcut waiting for the keypress event
|
|
const idx = egw_shortcutIdx(_keyCode, _shift, _ctrl, _alt);
|
|
if (typeof egw_registeredShortcuts[idx] != "undefined")
|
|
{
|
|
const shortcut:Shortcut = egw_registeredShortcuts[idx];
|
|
|
|
// Call the registered shortcut function and return its result, if it handled it
|
|
const result = shortcut.handler.call(shortcut.context, shortcut.shortcut);
|
|
if (result) return result;
|
|
}
|
|
|
|
// Pass the keypress to the currently focused action object
|
|
|
|
// Get the object manager and fetch the container of the currently
|
|
// focused object
|
|
let focusedObject: EgwActionObject = egw_globalObjectManager ? egw_globalObjectManager.getFocusedObject() : null;
|
|
const appMgr: EgwActionObjectManager = egw_getAppObjectManager(false);
|
|
if (appMgr && !focusedObject)
|
|
{
|
|
focusedObject = appMgr.getFocusedObject();
|
|
|
|
if (!focusedObject)
|
|
{
|
|
// 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.
|
|
let egwActionObject:EgwActionObject = null;
|
|
for (const child of appMgr.children)
|
|
{
|
|
if (egwBitIsSet(EGW_AO_FLAG_DEFAULT_FOCUS, child.flags))
|
|
{
|
|
egwActionObject = child;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get the first child of the found container and focus the first
|
|
// object
|
|
if (egwActionObject && egwActionObject.children.length > 0)
|
|
{
|
|
egwActionObject.children[0].setFocused(true);
|
|
focusedObject = egwActionObject.children[0];
|
|
}
|
|
}
|
|
}
|
|
if (focusedObject)
|
|
{
|
|
// Handle the default keys (arrow_up, down etc.)
|
|
let egwActionObject = focusedObject.getContainerRoot();
|
|
let handled = false;
|
|
|
|
if (egwActionObject)
|
|
{
|
|
handled = egwActionObject.handleKeyPress(_keyCode, _shift, _ctrl, _alt);
|
|
}
|
|
|
|
// Execute the egw_popup key handler of the focused object
|
|
if (!handled)
|
|
{
|
|
return focusedObject.executeActionImplementation(
|
|
{
|
|
"keyEvent": {
|
|
"keyCode": _keyCode,
|
|
"shift": _shift,
|
|
"ctrl": _ctrl,
|
|
"alt": _alt
|
|
}
|
|
}, "popup", EGW_AO_EXEC_SELECTED);
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|