TiddlyWiki5/core/modules/keyboard.js

388 lines
10 KiB
JavaScript

/*\
title: $:/core/modules/keyboard.js
type: application/javascript
module-type: global
Keyboard handling utilities
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var namedKeys = {
"cancel": 3,
"help": 6,
"backspace": 8,
"tab": 9,
"clear": 12,
"return": 13,
"enter": 13,
"pause": 19,
"escape": 27,
"space": 32,
"page_up": 33,
"page_down": 34,
"end": 35,
"home": 36,
"left": 37,
"up": 38,
"right": 39,
"down": 40,
"printscreen": 44,
"insert": 45,
"delete": 46,
"0": 48,
"1": 49,
"2": 50,
"3": 51,
"4": 52,
"5": 53,
"6": 54,
"7": 55,
"8": 56,
"9": 57,
"firefoxsemicolon": 59,
"firefoxequals": 61,
"a": 65,
"b": 66,
"c": 67,
"d": 68,
"e": 69,
"f": 70,
"g": 71,
"h": 72,
"i": 73,
"j": 74,
"k": 75,
"l": 76,
"m": 77,
"n": 78,
"o": 79,
"p": 80,
"q": 81,
"r": 82,
"s": 83,
"t": 84,
"u": 85,
"v": 86,
"w": 87,
"x": 88,
"y": 89,
"z": 90,
"numpad0": 96,
"numpad1": 97,
"numpad2": 98,
"numpad3": 99,
"numpad4": 100,
"numpad5": 101,
"numpad6": 102,
"numpad7": 103,
"numpad8": 104,
"numpad9": 105,
"multiply": 106,
"add": 107,
"separator": 108,
"subtract": 109,
"decimal": 110,
"divide": 111,
"f1": 112,
"f2": 113,
"f3": 114,
"f4": 115,
"f5": 116,
"f6": 117,
"f7": 118,
"f8": 119,
"f9": 120,
"f10": 121,
"f11": 122,
"f12": 123,
"f13": 124,
"f14": 125,
"f15": 126,
"f16": 127,
"f17": 128,
"f18": 129,
"f19": 130,
"f20": 131,
"f21": 132,
"f22": 133,
"f23": 134,
"f24": 135,
"firefoxminus": 173,
"semicolon": 186,
"equals": 187,
"comma": 188,
"dash": 189,
"period": 190,
"slash": 191,
"backquote": 192,
"openbracket": 219,
"backslash": 220,
"closebracket": 221,
"quote": 222
};
function KeyboardManager(options) {
var self = this;
options = options || "";
// Save the named key hashmap
this.namedKeys = namedKeys;
// Create a reverse mapping of code to keyname
this.keyNames = [];
$tw.utils.each(namedKeys,function(keyCode,name) {
self.keyNames[keyCode] = name.substr(0,1).toUpperCase() + name.substr(1);
});
// Save the platform-specific name of the "meta" key
this.metaKeyName = $tw.platform.isMac ? "cmd-" : "win-";
this.shortcutKeysList = [], // Stores the shortcut-key descriptors
this.shortcutActionList = [], // Stores the corresponding action strings
this.shortcutParsedList = []; // Stores the parsed key descriptors
this.shortcutPriorityList = []; // Stores the parsed shortcut priority
this.lookupNames = ["shortcuts"];
this.lookupNames.push($tw.platform.isMac ? "shortcuts-mac" : "shortcuts-not-mac")
this.lookupNames.push($tw.platform.isWindows ? "shortcuts-windows" : "shortcuts-not-windows");
this.lookupNames.push($tw.platform.isLinux ? "shortcuts-linux" : "shortcuts-not-linux");
this.updateShortcutLists(this.getShortcutTiddlerList());
$tw.wiki.addEventListener("change",function(changes) {
self.handleShortcutChanges(changes);
});
}
/*
Return an array of keycodes for the modifier keys ctrl, shift, alt, meta
*/
KeyboardManager.prototype.getModifierKeys = function() {
return [
16, // Shift
17, // Ctrl
18, // Alt
20, // CAPS LOCK
91, // Meta (left)
93, // Meta (right)
224 // Meta (Firefox)
]
};
/*
Parses a key descriptor into the structure:
{
keyCode: numeric keycode
shiftKey: boolean
altKey: boolean
ctrlKey: boolean
metaKey: boolean
}
Key descriptors have the following format:
ctrl+enter
ctrl+shift+alt+A
*/
KeyboardManager.prototype.parseKeyDescriptor = function(keyDescriptor,options) {
var components = keyDescriptor.split(/\+|\-/),
info = {
keyCode: 0,
shiftKey: false,
altKey: false,
ctrlKey: false,
metaKey: false
};
for(var t=0; t<components.length; t++) {
var s = components[t].toLowerCase(),
c = s.charCodeAt(0);
// Look for modifier keys
if(s === "ctrl") {
info.ctrlKey = true;
} else if(s === "shift") {
info.shiftKey = true;
} else if(s === "alt") {
info.altKey = true;
} else if(s === "meta" || s === "cmd" || s === "win") {
info.metaKey = true;
}
// Replace named keys with their code
if(this.namedKeys[s]) {
info.keyCode = this.namedKeys[s];
}
}
if(options.keyDescriptor) {
info.keyDescriptor = options.keyDescriptor;
}
if(info.keyCode) {
return info;
} else {
return null;
}
};
/*
Parse a list of key descriptors into an array of keyInfo objects. The key descriptors can be passed as an array of strings or a space separated string
*/
KeyboardManager.prototype.parseKeyDescriptors = function(keyDescriptors,options) {
var self = this;
options = options || {};
options.stack = options.stack || [];
var wiki = options.wiki || $tw.wiki;
if(typeof keyDescriptors === "string" && keyDescriptors === "") {
return [];
}
if(!$tw.utils.isArray(keyDescriptors)) {
keyDescriptors = keyDescriptors.split(" ");
}
var result = [];
$tw.utils.each(keyDescriptors,function(keyDescriptor) {
// Look for a named shortcut
if(keyDescriptor.substr(0,2) === "((" && keyDescriptor.substr(-2,2) === "))") {
if(options.stack.indexOf(keyDescriptor) === -1) {
options.stack.push(keyDescriptor);
var name = keyDescriptor.substring(2,keyDescriptor.length - 2),
lookupName = function(configName) {
var keyDescriptors = wiki.getTiddlerText("$:/config/" + configName + "/" + name);
if(keyDescriptors) {
options.keyDescriptor = keyDescriptor;
result.push.apply(result,self.parseKeyDescriptors(keyDescriptors,options));
}
};
$tw.utils.each(self.lookupNames,function(platformDescriptor) {
lookupName(platformDescriptor);
});
}
} else {
result.push(self.parseKeyDescriptor(keyDescriptor,options));
}
});
return result;
};
KeyboardManager.prototype.getPrintableShortcuts = function(keyInfoArray) {
var self = this,
result = [];
$tw.utils.each(keyInfoArray,function(keyInfo) {
if(keyInfo) {
result.push((keyInfo.ctrlKey ? "ctrl-" : "") +
(keyInfo.shiftKey ? "shift-" : "") +
(keyInfo.altKey ? "alt-" : "") +
(keyInfo.metaKey ? self.metaKeyName : "") +
(self.keyNames[keyInfo.keyCode]));
}
});
return result;
}
KeyboardManager.prototype.checkKeyDescriptor = function(event,keyInfo) {
return keyInfo &&
event.keyCode === keyInfo.keyCode &&
event.shiftKey === keyInfo.shiftKey &&
event.altKey === keyInfo.altKey &&
event.ctrlKey === keyInfo.ctrlKey &&
event.metaKey === keyInfo.metaKey;
};
KeyboardManager.prototype.checkKeyDescriptors = function(event,keyInfoArray) {
return (this.getMatchingKeyDescriptor(event,keyInfoArray) !== null);
};
KeyboardManager.prototype.getMatchingKeyDescriptor = function(event,keyInfoArray) {
for(var t=0; t<keyInfoArray.length; t++) {
if(this.checkKeyDescriptor(event,keyInfoArray[t])) {
return keyInfoArray[t];
}
}
return null;
};
KeyboardManager.prototype.getEventModifierKeyDescriptor = function(event) {
return event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey ? "ctrl" :
event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey ? "shift" :
event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey ? "ctrl-shift" :
event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey ? "alt" :
event.altKey && event.shiftKey && !event.ctrlKey && !event.metaKey ? "alt-shift" :
event.altKey && event.ctrlKey && !event.shiftKey && !event.metaKey ? "ctrl-alt" :
event.altKey && event.shiftKey && event.ctrlKey && !event.metaKey ? "ctrl-alt-shift" :
event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey ? "meta" :
event.metaKey && event.ctrlKey && !event.shiftKey && !event.altKey ? "meta-ctrl" :
event.metaKey && event.ctrlKey && event.shiftKey && !event.altKey ? "meta-ctrl-shift" :
event.metaKey && event.ctrlKey && event.shiftKey && event.altKey ? "meta-ctrl-alt-shift" : "normal";
};
KeyboardManager.prototype.getShortcutTiddlerList = function() {
return $tw.wiki.getTiddlersWithTag("$:/tags/KeyboardShortcut");
};
KeyboardManager.prototype.updateShortcutLists = function(tiddlerList) {
this.shortcutTiddlers = tiddlerList;
for(var i=0; i<tiddlerList.length; i++) {
var title = tiddlerList[i],
tiddlerFields = $tw.wiki.getTiddler(title).fields;
this.shortcutKeysList[i] = tiddlerFields.key !== undefined ? tiddlerFields.key : undefined;
this.shortcutActionList[i] = tiddlerFields.text;
this.shortcutParsedList[i] = this.shortcutKeysList[i] !== undefined ? this.parseKeyDescriptors(this.shortcutKeysList[i]) : undefined;
this.shortcutPriorityList[i] = tiddlerFields.priority === "yes" ? true : false;
}
};
/*
event: the keyboard event object
options:
onlyPriority: true if only priority global shortcuts should be invoked
*/
KeyboardManager.prototype.handleKeydownEvent = function(event, options) {
options = options || {};
var key, action;
for(var i=0; i<this.shortcutTiddlers.length; i++) {
if(options.onlyPriority && this.shortcutPriorityList[i] !== true) {
continue;
}
if(this.shortcutParsedList[i] !== undefined && this.checkKeyDescriptors(event,this.shortcutParsedList[i])) {
key = this.shortcutParsedList[i];
action = this.shortcutActionList[i];
}
}
if(key !== undefined) {
event.preventDefault();
event.stopPropagation();
$tw.rootWidget.invokeActionString(action,$tw.rootWidget,event);
return true;
}
return false;
};
KeyboardManager.prototype.detectNewShortcuts = function(changedTiddlers) {
var shortcutConfigTiddlers = [],
handled = false;
$tw.utils.each(this.lookupNames,function(platformDescriptor) {
var descriptorString = "$:/config/" + platformDescriptor + "/";
Object.keys(changedTiddlers).forEach(function(configTiddler) {
var configString = configTiddler.substr(0, configTiddler.lastIndexOf("/") + 1);
if(configString === descriptorString) {
shortcutConfigTiddlers.push(configTiddler);
handled = true;
}
});
});
if(handled) {
return $tw.utils.hopArray(changedTiddlers,shortcutConfigTiddlers);
} else {
return false;
}
};
KeyboardManager.prototype.handleShortcutChanges = function(changedTiddlers) {
var newList = this.getShortcutTiddlerList();
var hasChanged = $tw.utils.hopArray(changedTiddlers,this.shortcutTiddlers) ? true :
($tw.utils.hopArray(changedTiddlers,newList) ? true :
(this.detectNewShortcuts(changedTiddlers))
);
// Re-cache shortcuts if something changed
if(hasChanged) {
this.updateShortcutLists(newList);
}
};
exports.KeyboardManager = KeyboardManager;
})();