257 lines
9.0 KiB
JavaScript
257 lines
9.0 KiB
JavaScript
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Distributed under the BSD license:
|
||
|
*
|
||
|
* Copyright (c) 2010, Ajax.org B.V.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
* * Neither the name of Ajax.org B.V. nor the
|
||
|
* names of its contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
define(function(require, exports, module) {
|
||
|
"use strict";
|
||
|
|
||
|
var keyUtil = require("../lib/keys");
|
||
|
var useragent = require("../lib/useragent");
|
||
|
var KEY_MODS = keyUtil.KEY_MODS;
|
||
|
|
||
|
function HashHandler(config, platform) {
|
||
|
this.platform = platform || (useragent.isMac ? "mac" : "win");
|
||
|
this.commands = {};
|
||
|
this.commandKeyBinding = {};
|
||
|
this.addCommands(config);
|
||
|
this.$singleCommand = true;
|
||
|
}
|
||
|
|
||
|
function MultiHashHandler(config, platform) {
|
||
|
HashHandler.call(this, config, platform);
|
||
|
this.$singleCommand = false;
|
||
|
}
|
||
|
|
||
|
MultiHashHandler.prototype = HashHandler.prototype;
|
||
|
|
||
|
(function() {
|
||
|
|
||
|
|
||
|
this.addCommand = function(command) {
|
||
|
if (this.commands[command.name])
|
||
|
this.removeCommand(command);
|
||
|
|
||
|
this.commands[command.name] = command;
|
||
|
|
||
|
if (command.bindKey)
|
||
|
this._buildKeyHash(command);
|
||
|
};
|
||
|
|
||
|
this.removeCommand = function(command, keepCommand) {
|
||
|
var name = command && (typeof command === 'string' ? command : command.name);
|
||
|
command = this.commands[name];
|
||
|
if (!keepCommand)
|
||
|
delete this.commands[name];
|
||
|
|
||
|
// exhaustive search is brute force but since removeCommand is
|
||
|
// not a performance critical operation this should be OK
|
||
|
var ckb = this.commandKeyBinding;
|
||
|
for (var keyId in ckb) {
|
||
|
var cmdGroup = ckb[keyId];
|
||
|
if (cmdGroup == command) {
|
||
|
delete ckb[keyId];
|
||
|
} else if (Array.isArray(cmdGroup)) {
|
||
|
var i = cmdGroup.indexOf(command);
|
||
|
if (i != -1) {
|
||
|
cmdGroup.splice(i, 1);
|
||
|
if (cmdGroup.length == 1)
|
||
|
ckb[keyId] = cmdGroup[0];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.bindKey = function(key, command, position) {
|
||
|
if (typeof key == "object" && key) {
|
||
|
if (position == undefined)
|
||
|
position = key.position;
|
||
|
key = key[this.platform];
|
||
|
}
|
||
|
if (!key)
|
||
|
return;
|
||
|
if (typeof command == "function")
|
||
|
return this.addCommand({exec: command, bindKey: key, name: command.name || key});
|
||
|
|
||
|
key.split("|").forEach(function(keyPart) {
|
||
|
var chain = "";
|
||
|
if (keyPart.indexOf(" ") != -1) {
|
||
|
var parts = keyPart.split(/\s+/);
|
||
|
keyPart = parts.pop();
|
||
|
parts.forEach(function(keyPart) {
|
||
|
var binding = this.parseKeys(keyPart);
|
||
|
var id = KEY_MODS[binding.hashId] + binding.key;
|
||
|
chain += (chain ? " " : "") + id;
|
||
|
this._addCommandToBinding(chain, "chainKeys");
|
||
|
}, this);
|
||
|
chain += " ";
|
||
|
}
|
||
|
var binding = this.parseKeys(keyPart);
|
||
|
var id = KEY_MODS[binding.hashId] + binding.key;
|
||
|
this._addCommandToBinding(chain + id, command, position);
|
||
|
}, this);
|
||
|
};
|
||
|
|
||
|
function getPosition(command) {
|
||
|
return typeof command == "object" && command.bindKey
|
||
|
&& command.bindKey.position
|
||
|
|| (command.isDefault ? -100 : 0);
|
||
|
}
|
||
|
this._addCommandToBinding = function(keyId, command, position) {
|
||
|
var ckb = this.commandKeyBinding, i;
|
||
|
if (!command) {
|
||
|
delete ckb[keyId];
|
||
|
} else if (!ckb[keyId] || this.$singleCommand) {
|
||
|
ckb[keyId] = command;
|
||
|
} else {
|
||
|
if (!Array.isArray(ckb[keyId])) {
|
||
|
ckb[keyId] = [ckb[keyId]];
|
||
|
} else if ((i = ckb[keyId].indexOf(command)) != -1) {
|
||
|
ckb[keyId].splice(i, 1);
|
||
|
}
|
||
|
|
||
|
if (typeof position != "number") {
|
||
|
position = getPosition(command);
|
||
|
}
|
||
|
|
||
|
var commands = ckb[keyId];
|
||
|
for (i = 0; i < commands.length; i++) {
|
||
|
var other = commands[i];
|
||
|
var otherPos = getPosition(other);
|
||
|
if (otherPos > position)
|
||
|
break;
|
||
|
}
|
||
|
commands.splice(i, 0, command);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.addCommands = function(commands) {
|
||
|
commands && Object.keys(commands).forEach(function(name) {
|
||
|
var command = commands[name];
|
||
|
if (!command)
|
||
|
return;
|
||
|
|
||
|
if (typeof command === "string")
|
||
|
return this.bindKey(command, name);
|
||
|
|
||
|
if (typeof command === "function")
|
||
|
command = { exec: command };
|
||
|
|
||
|
if (typeof command !== "object")
|
||
|
return;
|
||
|
|
||
|
if (!command.name)
|
||
|
command.name = name;
|
||
|
|
||
|
this.addCommand(command);
|
||
|
}, this);
|
||
|
};
|
||
|
|
||
|
this.removeCommands = function(commands) {
|
||
|
Object.keys(commands).forEach(function(name) {
|
||
|
this.removeCommand(commands[name]);
|
||
|
}, this);
|
||
|
};
|
||
|
|
||
|
this.bindKeys = function(keyList) {
|
||
|
Object.keys(keyList).forEach(function(key) {
|
||
|
this.bindKey(key, keyList[key]);
|
||
|
}, this);
|
||
|
};
|
||
|
|
||
|
this._buildKeyHash = function(command) {
|
||
|
this.bindKey(command.bindKey, command);
|
||
|
};
|
||
|
|
||
|
// accepts keys in the form ctrl+Enter or ctrl-Enter
|
||
|
// keys without modifiers or shift only
|
||
|
this.parseKeys = function(keys) {
|
||
|
var parts = keys.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(x){return x;});
|
||
|
var key = parts.pop();
|
||
|
|
||
|
var keyCode = keyUtil[key];
|
||
|
if (keyUtil.FUNCTION_KEYS[keyCode])
|
||
|
key = keyUtil.FUNCTION_KEYS[keyCode].toLowerCase();
|
||
|
else if (!parts.length)
|
||
|
return {key: key, hashId: -1};
|
||
|
else if (parts.length == 1 && parts[0] == "shift")
|
||
|
return {key: key.toUpperCase(), hashId: -1};
|
||
|
|
||
|
var hashId = 0;
|
||
|
for (var i = parts.length; i--;) {
|
||
|
var modifier = keyUtil.KEY_MODS[parts[i]];
|
||
|
if (modifier == null) {
|
||
|
if (typeof console != "undefined")
|
||
|
console.error("invalid modifier " + parts[i] + " in " + keys);
|
||
|
return false;
|
||
|
}
|
||
|
hashId |= modifier;
|
||
|
}
|
||
|
return {key: key, hashId: hashId};
|
||
|
};
|
||
|
|
||
|
this.findKeyCommand = function findKeyCommand(hashId, keyString) {
|
||
|
var key = KEY_MODS[hashId] + keyString;
|
||
|
return this.commandKeyBinding[key];
|
||
|
};
|
||
|
|
||
|
this.handleKeyboard = function(data, hashId, keyString, keyCode) {
|
||
|
if (keyCode < 0) return;
|
||
|
var key = KEY_MODS[hashId] + keyString;
|
||
|
var command = this.commandKeyBinding[key];
|
||
|
if (data.$keyChain) {
|
||
|
data.$keyChain += " " + key;
|
||
|
command = this.commandKeyBinding[data.$keyChain] || command;
|
||
|
}
|
||
|
|
||
|
if (command) {
|
||
|
if (command == "chainKeys" || command[command.length - 1] == "chainKeys") {
|
||
|
data.$keyChain = data.$keyChain || key;
|
||
|
return {command: "null"};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (data.$keyChain) {
|
||
|
if ((!hashId || hashId == 4) && keyString.length == 1)
|
||
|
data.$keyChain = data.$keyChain.slice(0, -key.length - 1); // wait for input
|
||
|
else if (hashId == -1 || keyCode > 0)
|
||
|
data.$keyChain = ""; // reset keyChain
|
||
|
}
|
||
|
return {command: command};
|
||
|
};
|
||
|
|
||
|
this.getStatusText = function(editor, data) {
|
||
|
return data.$keyChain || "";
|
||
|
};
|
||
|
|
||
|
}).call(HashHandler.prototype);
|
||
|
|
||
|
exports.HashHandler = HashHandler;
|
||
|
exports.MultiHashHandler = MultiHashHandler;
|
||
|
});
|