2013-09-05 16:02:48 +02:00
|
|
|
/**
|
|
|
|
* Copyright 2013 IBM Corp.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
**/
|
2014-08-08 01:01:35 +02:00
|
|
|
RED.keyboard = (function() {
|
2014-05-14 15:17:26 +02:00
|
|
|
|
2017-01-06 00:33:19 +01:00
|
|
|
var isMac = /Mac/i.test(window.navigator.platform);
|
|
|
|
|
2013-09-05 16:02:48 +02:00
|
|
|
var handlers = {};
|
2016-12-04 23:59:43 +01:00
|
|
|
var partialState;
|
|
|
|
|
|
|
|
var keyMap = {
|
|
|
|
"left":37,
|
|
|
|
"up":38,
|
|
|
|
"right":39,
|
|
|
|
"down":40,
|
|
|
|
"escape":27,
|
|
|
|
"enter": 13,
|
|
|
|
"backspace": 8,
|
|
|
|
"delete": 46,
|
|
|
|
"space": 32,
|
|
|
|
";":186,
|
|
|
|
"=":187,
|
|
|
|
",":188,
|
|
|
|
"-":189,
|
|
|
|
".":190,
|
|
|
|
"/":191,
|
|
|
|
"\\":220,
|
|
|
|
"'":222,
|
|
|
|
"?":191 // <- QWERTY specific
|
|
|
|
}
|
|
|
|
var metaKeyCodes = {
|
|
|
|
16:true,
|
|
|
|
17:true,
|
|
|
|
18: true,
|
|
|
|
91:true,
|
|
|
|
93: true
|
|
|
|
}
|
|
|
|
var actionToKeyMap = {}
|
|
|
|
|
|
|
|
// FF generates some different keycodes because reasons.
|
|
|
|
var firefoxKeyCodeMap = {
|
|
|
|
59:186,
|
|
|
|
61:187,
|
|
|
|
173:189
|
|
|
|
}
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
$.getJSON("red/keymap.json",function(data) {
|
|
|
|
for (var scope in data) {
|
|
|
|
if (data.hasOwnProperty(scope)) {
|
|
|
|
var keys = data[scope];
|
|
|
|
for (var key in keys) {
|
|
|
|
if (keys.hasOwnProperty(key)) {
|
|
|
|
addHandler(scope,key,keys[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
RED.actions.add("core:show-help", showKeyboardHelp);
|
|
|
|
|
|
|
|
}
|
|
|
|
function parseKeySpecifier(key) {
|
|
|
|
var parts = key.toLowerCase().split("-");
|
|
|
|
var modifiers = {};
|
|
|
|
var keycode;
|
|
|
|
var blank = 0;
|
|
|
|
for (var i=0;i<parts.length;i++) {
|
|
|
|
switch(parts[i]) {
|
|
|
|
case "ctrl":
|
|
|
|
case "cmd":
|
|
|
|
modifiers.ctrl = true;
|
|
|
|
modifiers.meta = true;
|
|
|
|
break;
|
|
|
|
case "alt":
|
|
|
|
modifiers.alt = true;
|
|
|
|
break;
|
|
|
|
case "shift":
|
|
|
|
modifiers.shift = true;
|
|
|
|
break;
|
|
|
|
case "":
|
|
|
|
blank++;
|
|
|
|
keycode = keyMap["-"];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (keyMap.hasOwnProperty(parts[i])) {
|
|
|
|
keycode = keyMap[parts[i]];
|
|
|
|
} else if (parts[i].length > 1) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
keycode = parts[i].toUpperCase().charCodeAt(0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return [keycode,modifiers];
|
|
|
|
}
|
2014-05-14 15:17:26 +02:00
|
|
|
|
2016-05-08 23:50:55 +02:00
|
|
|
function resolveKeyEvent(evt) {
|
2016-12-04 23:59:43 +01:00
|
|
|
var slot = partialState||handlers;
|
2016-05-08 23:50:55 +02:00
|
|
|
if (evt.ctrlKey || evt.metaKey) {
|
|
|
|
slot = slot.ctrl;
|
|
|
|
}
|
|
|
|
if (slot && evt.shiftKey) {
|
|
|
|
slot = slot.shift;
|
|
|
|
}
|
|
|
|
if (slot && evt.altKey) {
|
|
|
|
slot = slot.alt;
|
|
|
|
}
|
2016-12-04 23:59:43 +01:00
|
|
|
var keyCode = firefoxKeyCodeMap[evt.keyCode] || evt.keyCode;
|
|
|
|
if (slot && slot[keyCode]) {
|
|
|
|
var handler = slot[keyCode];
|
|
|
|
if (!handler.scope) {
|
|
|
|
if (partialState) {
|
|
|
|
partialState = null;
|
|
|
|
return resolveKeyEvent(evt);
|
|
|
|
} else {
|
|
|
|
partialState = handler;
|
|
|
|
evt.preventDefault();
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else if (handler.scope && handler.scope !== "*") {
|
2016-05-08 23:50:55 +02:00
|
|
|
var target = evt.target;
|
|
|
|
while (target.nodeName !== 'BODY' && target.id !== handler.scope) {
|
|
|
|
target = target.parentElement;
|
|
|
|
}
|
|
|
|
if (target.nodeName === 'BODY') {
|
|
|
|
handler = null;
|
|
|
|
}
|
|
|
|
}
|
2016-12-04 23:59:43 +01:00
|
|
|
partialState = null;
|
2016-05-08 23:50:55 +02:00
|
|
|
return handler;
|
2016-12-04 23:59:43 +01:00
|
|
|
} else if (partialState) {
|
|
|
|
partialState = null;
|
|
|
|
return resolveKeyEvent(evt);
|
2016-05-08 23:50:55 +02:00
|
|
|
}
|
|
|
|
}
|
2013-09-05 16:02:48 +02:00
|
|
|
d3.select(window).on("keydown",function() {
|
2016-12-04 23:59:43 +01:00
|
|
|
if (metaKeyCodes[d3.event.keyCode]) {
|
|
|
|
return;
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
2016-05-08 23:50:55 +02:00
|
|
|
var handler = resolveKeyEvent(d3.event);
|
2016-12-04 23:59:43 +01:00
|
|
|
if (handler && handler.ondown) {
|
|
|
|
if (typeof handler.ondown === "string") {
|
|
|
|
RED.actions.invoke(handler.ondown);
|
|
|
|
} else {
|
|
|
|
handler.ondown();
|
|
|
|
}
|
|
|
|
d3.event.preventDefault();
|
2014-04-16 14:39:16 +02:00
|
|
|
}
|
|
|
|
});
|
2016-05-08 23:50:55 +02:00
|
|
|
|
2016-12-04 23:59:43 +01:00
|
|
|
function addHandler(scope,key,modifiers,ondown) {
|
2013-09-05 16:02:48 +02:00
|
|
|
var mod = modifiers;
|
2014-04-16 14:39:16 +02:00
|
|
|
var cbdown = ondown;
|
2016-12-04 23:59:43 +01:00
|
|
|
if (typeof modifiers == "function" || typeof modifiers === "string") {
|
2013-09-05 16:02:48 +02:00
|
|
|
mod = {};
|
2014-04-16 14:39:16 +02:00
|
|
|
cbdown = modifiers;
|
2016-05-08 23:50:55 +02:00
|
|
|
}
|
2016-12-04 23:59:43 +01:00
|
|
|
var keys = [];
|
|
|
|
var i=0;
|
|
|
|
if (typeof key === 'string') {
|
|
|
|
if (typeof cbdown === 'string') {
|
|
|
|
actionToKeyMap[cbdown] = {scope:scope,key:key};
|
|
|
|
}
|
|
|
|
var parts = key.split(" ");
|
|
|
|
for (i=0;i<parts.length;i++) {
|
|
|
|
var parsedKey = parseKeySpecifier(parts[i]);
|
|
|
|
if (parsedKey) {
|
|
|
|
keys.push(parsedKey);
|
|
|
|
} else {
|
|
|
|
console.log("Unrecognised key specifier:",key)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
keys.push([key,mod])
|
2016-05-08 23:50:55 +02:00
|
|
|
}
|
2016-12-04 23:59:43 +01:00
|
|
|
var slot = handlers;
|
|
|
|
for (i=0;i<keys.length;i++) {
|
|
|
|
key = keys[i][0];
|
|
|
|
mod = keys[i][1];
|
|
|
|
if (mod.ctrl) {
|
|
|
|
slot.ctrl = slot.ctrl||{};
|
|
|
|
slot = slot.ctrl;
|
|
|
|
}
|
|
|
|
if (mod.shift) {
|
|
|
|
slot.shift = slot.shift||{};
|
|
|
|
slot = slot.shift;
|
|
|
|
}
|
|
|
|
if (mod.alt) {
|
|
|
|
slot.alt = slot.alt||{};
|
|
|
|
slot = slot.alt;
|
|
|
|
}
|
|
|
|
slot[key] = slot[key] || {};
|
|
|
|
slot = slot[key];
|
|
|
|
//slot[key] = {scope: scope, ondown:cbdown};
|
2016-05-08 23:50:55 +02:00
|
|
|
}
|
2016-12-04 23:59:43 +01:00
|
|
|
slot.scope = scope;
|
|
|
|
slot.ondown = cbdown;
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
2016-05-08 23:50:55 +02:00
|
|
|
|
|
|
|
function removeHandler(key,modifiers) {
|
|
|
|
var mod = modifiers || {};
|
2016-12-04 23:59:43 +01:00
|
|
|
var keys = [];
|
|
|
|
var i=0;
|
|
|
|
if (typeof key === 'string') {
|
|
|
|
delete actionToKeyMap[key];
|
|
|
|
var parts = key.split(" ");
|
|
|
|
for (i=0;i<parts.length;i++) {
|
|
|
|
var parsedKey = parseKeySpecifier(parts[i]);
|
|
|
|
if (parsedKey) {
|
|
|
|
keys.push(parsedKey);
|
|
|
|
} else {
|
|
|
|
console.log("Unrecognised key specifier:",key)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
keys.push([key,mod])
|
2016-05-08 23:50:55 +02:00
|
|
|
}
|
2016-12-04 23:59:43 +01:00
|
|
|
var slot = handlers;
|
|
|
|
for (i=0;i<keys.length;i++) {
|
|
|
|
key = keys[i][0];
|
|
|
|
mod = keys[i][1];
|
|
|
|
if (mod.ctrl) {
|
|
|
|
slot = slot.ctrl;
|
|
|
|
}
|
|
|
|
if (slot && mod.shift) {
|
|
|
|
slot = slot.shift;
|
|
|
|
}
|
|
|
|
if (slot && mod.alt) {
|
|
|
|
slot = slot.alt;
|
|
|
|
}
|
|
|
|
if (!slot[key]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
slot = slot[key];
|
2016-05-08 23:50:55 +02:00
|
|
|
}
|
2016-12-04 23:59:43 +01:00
|
|
|
delete slot.scope;
|
|
|
|
delete slot.ondown;
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
2016-05-08 23:50:55 +02:00
|
|
|
|
2017-01-11 12:35:48 +01:00
|
|
|
var shortcutDialog = null;
|
2016-05-08 23:50:55 +02:00
|
|
|
|
2016-12-04 23:59:43 +01:00
|
|
|
var cmdCtrlKey = '<span class="help-key">'+(isMac?'⌘':'Ctrl')+'</span>';
|
|
|
|
|
2015-03-14 23:16:07 +01:00
|
|
|
function showKeyboardHelp() {
|
2015-04-13 17:48:38 +02:00
|
|
|
if (!RED.settings.theme("menu.menu-item-keyboard-shortcuts",true)) {
|
2015-04-13 14:55:17 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-01-11 12:35:48 +01:00
|
|
|
if (!shortcutDialog) {
|
|
|
|
shortcutDialog = $('<div id="keyboard-help-dialog" class="hide">'+
|
|
|
|
'<ol id="keyboard-shortcut-list"></ol>'+
|
2015-04-13 14:55:17 +02:00
|
|
|
'</div>')
|
2017-01-11 12:35:48 +01:00
|
|
|
.appendTo("body");
|
|
|
|
|
|
|
|
var shortcutList = $('#keyboard-shortcut-list').editableList({
|
|
|
|
addButton: false,
|
|
|
|
scrollOnAdd: false,
|
|
|
|
addItem: function(container,i,object) {
|
|
|
|
var item = $('<div class="keyboard-shortcut-entry">').appendTo(container);
|
2017-01-11 12:41:25 +01:00
|
|
|
|
2017-01-11 12:35:48 +01:00
|
|
|
var key = $('<div class="keyboard-shortcut-entry-key">').appendTo(item);
|
2017-01-11 12:41:25 +01:00
|
|
|
if (object.key) {
|
|
|
|
key.append(formatKey(object.key));
|
|
|
|
} else {
|
|
|
|
item.addClass("keyboard-shortcut-entry-unassigned");
|
|
|
|
key.html(RED._('keyboard.unassigned'));
|
|
|
|
}
|
2017-01-11 12:35:48 +01:00
|
|
|
|
2017-01-11 13:18:52 +01:00
|
|
|
var text = object.id.replace(/(^.+:([a-z]))|(-([a-z]))/g,function() {
|
|
|
|
if (arguments[5] === 0) {
|
|
|
|
return arguments[2].toUpperCase();
|
2017-01-11 12:35:48 +01:00
|
|
|
} else {
|
2017-01-11 13:18:52 +01:00
|
|
|
return " "+arguments[4].toUpperCase();
|
2017-01-11 12:35:48 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
var label = $('<div>').html(text).appendTo(item);
|
2017-01-11 12:41:25 +01:00
|
|
|
if (object.scope) {
|
|
|
|
$('<div class="keyboard-shortcut-entry-scope">').html(object.scope).appendTo(item);
|
|
|
|
}
|
2017-01-11 12:35:48 +01:00
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
});
|
|
|
|
var shortcuts = RED.actions.list();
|
|
|
|
shortcuts.sort(function(A,B) {
|
|
|
|
return A.id.localeCompare(B.id);
|
|
|
|
});
|
|
|
|
shortcuts.forEach(function(s) {
|
2017-01-11 12:41:25 +01:00
|
|
|
shortcutList.editableList('addItem',s);
|
2017-01-11 12:35:48 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
shortcutDialog.dialog({
|
2015-04-13 14:55:17 +02:00
|
|
|
modal: true,
|
|
|
|
autoOpen: false,
|
|
|
|
width: "800",
|
2017-01-11 12:35:48 +01:00
|
|
|
height: "400",
|
|
|
|
title:RED._("keyboard.title"),
|
2016-05-08 23:50:55 +02:00
|
|
|
resizable: false
|
2015-04-13 14:55:17 +02:00
|
|
|
});
|
|
|
|
}
|
2016-05-08 23:50:55 +02:00
|
|
|
|
2017-01-11 12:35:48 +01:00
|
|
|
shortcutDialog.dialog("open");
|
2015-03-14 23:16:07 +01:00
|
|
|
}
|
2017-01-06 00:33:19 +01:00
|
|
|
function formatKey(key) {
|
|
|
|
var formattedKey = isMac?key.replace(/ctrl-?/,"⌘"):key;
|
2017-01-11 12:35:48 +01:00
|
|
|
formattedKey = isMac?formattedKey.replace(/alt-?/,"⌥"):key;
|
2017-01-06 00:33:19 +01:00
|
|
|
formattedKey = formattedKey.replace(/shift-?/,"⇧")
|
|
|
|
formattedKey = formattedKey.replace(/left/,"←")
|
|
|
|
formattedKey = formattedKey.replace(/up/,"↑")
|
|
|
|
formattedKey = formattedKey.replace(/right/,"→")
|
|
|
|
formattedKey = formattedKey.replace(/down/,"↓")
|
|
|
|
return '<span class="help-key-block"><span class="help-key">'+formattedKey.split(" ").join('</span> <span class="help-key">')+'</span></span>';
|
|
|
|
}
|
2016-05-08 23:50:55 +02:00
|
|
|
|
2013-09-05 16:02:48 +02:00
|
|
|
return {
|
2016-12-04 23:59:43 +01:00
|
|
|
init: init,
|
2013-09-05 16:02:48 +02:00
|
|
|
add: addHandler,
|
|
|
|
remove: removeHandler,
|
2016-12-04 23:59:43 +01:00
|
|
|
getShortcut: function(actionName) {
|
|
|
|
return actionToKeyMap[actionName];
|
2017-01-06 00:33:19 +01:00
|
|
|
},
|
|
|
|
formatKey: formatKey
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
|
|
|
|
2014-08-08 01:01:35 +02:00
|
|
|
})();
|