2008-07-04 13:00:02 -07:00
|
|
|
|
// -*- Mode: js2; tab-width: 4; indent-tabs-mode: nil; js2-basic-offset: 4; js2-skip-preprocessor-directives: t; -*-
|
2008-07-04 07:29:24 -07:00
|
|
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
|
*
|
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
|
* 1.1 (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.mozilla.org/MPL/
|
|
|
|
|
*
|
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
|
* License.
|
|
|
|
|
*
|
|
|
|
|
* The Original Code is Mozilla Mobile Browser.
|
|
|
|
|
*
|
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
|
* Mozilla Corporation.
|
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2008
|
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
|
*
|
|
|
|
|
* Contributor(s):
|
|
|
|
|
* Daniel Brooks <db48x@yahoo.com>
|
|
|
|
|
*
|
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
|
*
|
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
2008-07-06 03:01:33 -07:00
|
|
|
|
// TODO: see about grouping the keys into categories
|
2008-07-17 15:55:26 -07:00
|
|
|
|
// TODO: move the shortcut editor to the prefs, if the prefs exist
|
2008-07-18 15:20:47 -07:00
|
|
|
|
// TODO: needs theme work, do we have someone who does that sort of thing?
|
2008-07-20 02:42:06 -07:00
|
|
|
|
// TODO: support multiple keys per command
|
2008-07-22 00:38:59 -07:00
|
|
|
|
// TODO: a single click should allow you to edit a shortcut, rather than the
|
|
|
|
|
// double click you have to use at the moment
|
2008-07-04 07:29:24 -07:00
|
|
|
|
|
2008-07-18 15:20:47 -07:00
|
|
|
|
var nsIJSON = Components.classes["@mozilla.org/dom/json;1"]
|
|
|
|
|
.createInstance(Components.interfaces.nsIJSON);
|
2008-07-05 17:06:24 -07:00
|
|
|
|
|
2008-07-04 07:29:24 -07:00
|
|
|
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|
|
|
|
|
|
|
|
|
function ShortcutEditor()
|
|
|
|
|
{
|
2008-07-05 17:06:24 -07:00
|
|
|
|
var prefsvc = Components.classes["@mozilla.org/preferences-service;1"]
|
|
|
|
|
.getService(Components.interfaces.nsIPrefService);
|
2008-07-04 13:00:02 -07:00
|
|
|
|
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
|
|
|
|
.getService(Components.interfaces.nsIPrefBranch2);
|
2008-07-05 17:06:24 -07:00
|
|
|
|
var keyPrefs = prefsvc.getBranch("shortcut.");
|
2008-07-06 03:01:33 -07:00
|
|
|
|
var keyCache;
|
2008-07-04 13:00:02 -07:00
|
|
|
|
|
2008-07-17 20:09:32 -07:00
|
|
|
|
var tree;
|
|
|
|
|
|
2008-07-04 07:29:24 -07:00
|
|
|
|
// first, we need to be able to manipulate the keys and commands themselves
|
|
|
|
|
function getCommandNames()
|
|
|
|
|
{
|
|
|
|
|
return Array.map(document.getElementsByTagNameNS(XUL_NS, "command"), function(c) { return c.getAttribute("id"); });
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-06 03:01:33 -07:00
|
|
|
|
function getKeys()
|
|
|
|
|
{
|
2008-07-06 04:12:57 -07:00
|
|
|
|
if (keyCache)
|
|
|
|
|
return keyCache;
|
2008-07-06 03:01:33 -07:00
|
|
|
|
|
|
|
|
|
keyCache = { };
|
|
|
|
|
Array.map(document.getElementsByTagNameNS(XUL_NS, "key"), function(k) { keyCache[k.getAttribute("command")] = k; });
|
|
|
|
|
return keyCache;
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-04 07:29:24 -07:00
|
|
|
|
function findKeyForCommand(command)
|
|
|
|
|
{
|
2008-07-06 03:01:33 -07:00
|
|
|
|
// returns the key the calls the named command, or null if there isn't one
|
2008-07-18 15:20:47 -07:00
|
|
|
|
var keys = getKeys();
|
|
|
|
|
return command in keys && keys[command];
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-05 17:06:24 -07:00
|
|
|
|
function findCommandForKey(keySpec)
|
2008-07-04 07:29:24 -07:00
|
|
|
|
{
|
|
|
|
|
// TODO: This is a bit simplistic as yet. For example, we should match
|
|
|
|
|
// a key with an optional modifier even if that modifier isn't
|
|
|
|
|
// specified in our arguments. Also, we need to differentiate
|
|
|
|
|
// between a key element with an attribute that is an empty string
|
|
|
|
|
// and one without that attribute at all.
|
|
|
|
|
var keys = document.getElementsByTagNameNS(XUL_NS, "key");
|
|
|
|
|
var l = keys.length;
|
|
|
|
|
for (var i = 0; i < l; i++)
|
2008-07-18 15:20:47 -07:00
|
|
|
|
{
|
2008-07-17 20:09:32 -07:00
|
|
|
|
if (keys[i].getAttribute("modifiers") == getModifiersFromFlags(keySpec.modifiers) &&
|
2008-07-05 17:06:24 -07:00
|
|
|
|
keys[i].getAttribute("key") == keySpec.key &&
|
|
|
|
|
keys[i].getAttribute("keycode") == keySpec.keycode)
|
2008-07-04 07:29:24 -07:00
|
|
|
|
return keys[i];
|
2008-07-18 15:20:47 -07:00
|
|
|
|
}
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-05 17:06:24 -07:00
|
|
|
|
function addKey(command, keySpec)
|
2008-07-04 07:29:24 -07:00
|
|
|
|
{
|
|
|
|
|
// generally adds a new key to the document that runs command,
|
|
|
|
|
// but if a key for command already exists it instead modifies
|
|
|
|
|
// that key. If a key already exists that matches the
|
|
|
|
|
// arguments, no modifications are made and null is
|
|
|
|
|
// returned. Otherwise, the new key is returned.
|
|
|
|
|
|
2008-07-22 00:38:59 -07:00
|
|
|
|
if (!keySpec)
|
|
|
|
|
return null;
|
|
|
|
|
|
2008-07-17 15:03:44 -07:00
|
|
|
|
var key = findKeyForCommand(command);
|
|
|
|
|
if (keySpec.exists)
|
2008-07-04 07:29:24 -07:00
|
|
|
|
{
|
2008-07-17 15:03:44 -07:00
|
|
|
|
if (findCommandForKey(keySpec))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
if (key)
|
|
|
|
|
{
|
2008-07-22 00:38:59 -07:00
|
|
|
|
keySpec.modifiers ? key.setAttribute("modifiers", getModifiersFromFlags(keySpec.modifiers))
|
|
|
|
|
: key.removeAttribute("modifiers");
|
|
|
|
|
keySpec.key ? key.setAttribute("key", keySpec.key)
|
|
|
|
|
: key.removeAttribute("key");
|
|
|
|
|
keySpec.keycode ? key.setAttribute("keycode", keySpec.keycode)
|
|
|
|
|
: key.removeAttribute("keycode");
|
2008-07-17 15:03:44 -07:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
key = document.createElementNS(XUL_NS, "key");
|
2008-07-22 00:38:59 -07:00
|
|
|
|
if (keySpec.modifiers)
|
|
|
|
|
key.setAttribute("modifiers", getModifiersFromFlags(keySpec.modifiers));
|
|
|
|
|
if (keySpec.key)
|
|
|
|
|
key.setAttribute("key", keySpec.key);
|
|
|
|
|
if (keySpec.keycode)
|
|
|
|
|
key.setAttribute("keycode", keySpec.keycode);
|
2008-07-17 20:09:32 -07:00
|
|
|
|
key.setAttribute("command", command);
|
2008-07-22 00:38:59 -07:00
|
|
|
|
document.getElementById("mainKeyset").appendChild(key);
|
2008-07-17 15:03:44 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-22 00:38:59 -07:00
|
|
|
|
keyCache[command] = key;
|
2008-07-17 20:09:32 -07:00
|
|
|
|
return key;
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 15:03:44 -07:00
|
|
|
|
if (key)
|
2008-07-22 00:38:59 -07:00
|
|
|
|
{
|
|
|
|
|
delete keyCache[command];
|
2008-07-17 15:03:44 -07:00
|
|
|
|
key.parentNode.removeChild(key);
|
2008-07-22 00:38:59 -07:00
|
|
|
|
}
|
2008-07-17 15:03:44 -07:00
|
|
|
|
return null;
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-05 17:06:24 -07:00
|
|
|
|
function makeKeySpec(modifiers, key, keycode)
|
|
|
|
|
{
|
2008-07-17 14:45:08 -07:00
|
|
|
|
// TODO: make this check more specific, once key elements implement a unique interface
|
2008-07-05 17:06:24 -07:00
|
|
|
|
if (modifiers instanceof Components.interfaces.nsIDOMElement)
|
2008-07-18 15:20:47 -07:00
|
|
|
|
{
|
2008-07-05 17:06:24 -07:00
|
|
|
|
return {
|
2008-07-17 15:03:44 -07:00
|
|
|
|
exists: true,
|
2008-07-17 14:45:08 -07:00
|
|
|
|
modifiers: getFlagsForModifiers(modifiers.getAttribute("modifiers")),
|
2008-07-17 20:09:32 -07:00
|
|
|
|
key: modifiers.getAttribute("key") || false,
|
|
|
|
|
keycode: modifiers.getAttribute("keycode") || false
|
2008-07-05 17:06:24 -07:00
|
|
|
|
};
|
2008-07-18 15:20:47 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 15:55:26 -07:00
|
|
|
|
if (modifiers instanceof Components.interfaces.nsIDOMKeyEvent)
|
2008-07-18 15:20:47 -07:00
|
|
|
|
{
|
2008-07-17 15:55:26 -07:00
|
|
|
|
return {
|
|
|
|
|
exists: true,
|
|
|
|
|
modifiers: getEventModifiers(modifiers),
|
2008-07-17 20:09:32 -07:00
|
|
|
|
key: getEventKey(modifiers) || false,
|
|
|
|
|
keycode: getEventKeyCode(modifiers) || false
|
2008-07-17 15:55:26 -07:00
|
|
|
|
};
|
2008-07-18 15:20:47 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-05 17:06:24 -07:00
|
|
|
|
return {
|
2008-07-20 02:42:06 -07:00
|
|
|
|
exists: !!(key || keycode),
|
2008-07-17 14:45:08 -07:00
|
|
|
|
modifiers: getFlagsForModifiers(modifiers),
|
2008-07-17 20:09:32 -07:00
|
|
|
|
key: key || false,
|
|
|
|
|
keycode: keycode || false
|
2008-07-05 17:06:24 -07:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 14:45:08 -07:00
|
|
|
|
var modifierFlags = { alt: 1, control: 2, meta: 4, shift: 8 };
|
|
|
|
|
function getFlagsForModifiers(modifiers)
|
|
|
|
|
{
|
|
|
|
|
if (!modifiers)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
var result;
|
|
|
|
|
for each (m in modifiers.split(" "))
|
|
|
|
|
result |= modifierFlags[m];
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 20:09:32 -07:00
|
|
|
|
function getModifiersFromFlags(flags)
|
|
|
|
|
{
|
|
|
|
|
var result = [], i = 1;
|
|
|
|
|
for each (m in ["alt", "control", "meta", "shift"])
|
|
|
|
|
{
|
|
|
|
|
if (flags & i)
|
|
|
|
|
result.push(m);
|
|
|
|
|
i += i;
|
|
|
|
|
}
|
|
|
|
|
return result.join(" ");
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 14:45:08 -07:00
|
|
|
|
function getEventModifiers(event)
|
|
|
|
|
{
|
|
|
|
|
var result, i = 1;
|
|
|
|
|
for each (m in [event.altKey, event.ctrlKey, event.metaKey, event.shiftKey])
|
|
|
|
|
{
|
|
|
|
|
result |= (m && i);
|
|
|
|
|
i += i;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getEventKey(event)
|
|
|
|
|
{
|
|
|
|
|
if (event.charCode)
|
2008-07-20 02:42:06 -07:00
|
|
|
|
return String.fromCharCode(event.charCode);
|
2008-07-17 14:45:08 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-18 15:20:47 -07:00
|
|
|
|
var keyCodeMap = { };
|
|
|
|
|
var nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_CANCEL] = "VK_CANCEL";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_HELP] = "VK_HELP";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_BACK_SPACE] = "VK_BACK";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_TAB] = "VK_TAB";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_CLEAR] = "VK_CLEAR";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_RETURN] = "VK_RETURN";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_ENTER] = "VK_ENTER";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_SHIFT] = "VK_SHIFT";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_CONTROL] = "VK_CONTROL";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_ALT] = "VK_ALT";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_PAUSE] = "VK_PAUSE";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_CAPS_LOCK] = "VK_CAPS_LOCK";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_ESCAPE] = "VK_ESCAPE";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_SPACE] = "VK_SPACE";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_PAGE_UP] = "VK_PAGE_UP";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_PAGE_DOWN] = "VK_PAGE_DOWN";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_END] = "VK_END";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_HOME] = "VK_HOME";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_LEFT] = "VK_LEFT";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_UP] = "VK_UP";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_RIGHT] = "VK_RIGHT";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_DOWN] = "VK_DOWN";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_PRINTSCREEN] = "VK_PRINTSCREEN";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_INSERT] = "VK_INSERT";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_DELETE] = "VK_DELETE";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_SEMICOLON] = "VK_SEMICOLON";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_EQUALS] = "VK_EQUALS";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_CONTEXT_MENU] = "VK_CONTEXT_MENU";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_MULTIPLY] = "VK_MULTIPLY";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_ADD] = "VK_ADD";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_SEPARATOR] = "VK_SEPARATOR";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_SUBTRACT] = "VK_SUBTRACT";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_DECIMAL] = "VK_DECIMAL";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_DIVIDE] = "VK_DIVIDE";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F1] = "VK_F1";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F2] = "VK_F2";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F3] = "VK_F3";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F4] = "VK_F4";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F5] = "VK_F5";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F6] = "VK_F6";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F7] = "VK_F7";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F8] = "VK_F8";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F9] = "VK_F9";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F10] = "VK_F10";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F11] = "VK_F11";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F12] = "VK_F12";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F13] = "VK_F13";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F14] = "VK_F14";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F15] = "VK_F15";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F16] = "VK_F16";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F17] = "VK_F17";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F18] = "VK_F18";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F19] = "VK_F19";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F20] = "VK_F20";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F21] = "VK_F21";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F22] = "VK_F22";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F23] = "VK_F23";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_F24] = "VK_F24";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_NUM_LOCK] = "VK_NUM_LOCK";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_SCROLL_LOCK] = "VK_SCROLL_LOCK";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_COMMA] = "VK_COMMA";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_PERIOD] = "VK_PERIOD";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_SLASH] = "VK_SLASH";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_BACK_QUOTE] = "VK_BACK_QUOTE";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET] = "VK_OPEN_BRACKET";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_BACK_SLASH] = "VK_BACK_SLASH";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET] = "VK_CLOSE_BRACKET";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_QUOTE] = "VK_QUOTE";
|
|
|
|
|
keyCodeMap[nsIDOMKeyEvent.DOM_VK_META] = "VK_META";
|
|
|
|
|
|
2008-07-17 14:45:08 -07:00
|
|
|
|
function getEventKeyCode(event)
|
|
|
|
|
{
|
|
|
|
|
return keyCodeMap[event.keyCode];
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-04 13:00:02 -07:00
|
|
|
|
// This code is all about converting key elements into human-readable
|
|
|
|
|
// descriptions of the keys they match. Copied essentially verbatim from
|
|
|
|
|
// nsMenuFrame::BuildAcceleratorText
|
|
|
|
|
// TODO: write some tests
|
|
|
|
|
|
|
|
|
|
// first, we need to look up the right names of the various modifier keys.
|
|
|
|
|
var platformBundle = document.getElementById("bundle-platformKeys");
|
|
|
|
|
var platformKeys = {
|
|
|
|
|
shift: platformBundle.getString("VK_SHIFT"),
|
|
|
|
|
meta: platformBundle.getString("VK_META"),
|
|
|
|
|
alt: platformBundle.getString("VK_ALT"),
|
|
|
|
|
control: platformBundle.getString("VK_CONTROL")
|
|
|
|
|
};
|
|
|
|
|
var modifierSeparator = platformBundle.getString("MODIFIER_SEPARATOR");
|
|
|
|
|
|
|
|
|
|
#ifdef XP_MACOSX
|
|
|
|
|
var accelKey = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
|
|
|
|
|
#else
|
|
|
|
|
var accelKey = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
accelKey = prefs.getCharPref("ui.key.accelKey");
|
|
|
|
|
} catch (e) { }
|
|
|
|
|
|
|
|
|
|
// convert from the accel keycode to the right string
|
|
|
|
|
var platformAccel = { };
|
|
|
|
|
platformAccel[Components.interfaces.nsIDOMKeyEvent.DOM_VK_META] = platformKeys.meta;
|
|
|
|
|
platformAccel[Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT] = platformKeys.alt;
|
|
|
|
|
platformAccel[Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL] = platformKeys.control;
|
2008-07-05 17:06:24 -07:00
|
|
|
|
platformKeys.accel = platformAccel[accelKey] || platformKeys.control;
|
2008-07-04 13:00:02 -07:00
|
|
|
|
|
2008-07-17 14:45:08 -07:00
|
|
|
|
function getKeyName(keySpec) {
|
2008-07-04 07:29:24 -07:00
|
|
|
|
// convert a key element into a string describing what keys to push.
|
|
|
|
|
// "Control-C" or "Control-Meta-Hyper-Shift-Q" or whatever
|
2008-07-17 14:45:08 -07:00
|
|
|
|
if (!keySpec)
|
2008-07-04 07:29:24 -07:00
|
|
|
|
return "";
|
2008-07-17 14:45:08 -07:00
|
|
|
|
if (keySpec instanceof Components.interfaces.nsIDOMElement)
|
|
|
|
|
keySpec = makeKeySpec(keySpec);
|
2008-07-20 02:42:06 -07:00
|
|
|
|
if (!keySpec.exists)
|
|
|
|
|
return "";
|
2008-07-04 07:29:24 -07:00
|
|
|
|
|
2008-07-04 13:00:02 -07:00
|
|
|
|
var accel = [];
|
|
|
|
|
var keybundle = document.getElementById("bundle-keys");
|
2008-07-17 14:45:08 -07:00
|
|
|
|
|
|
|
|
|
// this is sorta dumb, but whatever
|
|
|
|
|
var modifiers = [], i = 1;
|
2008-07-20 02:42:06 -07:00
|
|
|
|
for each (m in ["control", "alt", "meta", "shift"])
|
|
|
|
|
if (keySpec.modifiers & modifierFlags[m])
|
2008-07-17 14:45:08 -07:00
|
|
|
|
modifiers.push(m);
|
2008-07-04 13:00:02 -07:00
|
|
|
|
for each (m in modifiers)
|
|
|
|
|
if (m in platformKeys)
|
|
|
|
|
accel.push(platformKeys[m]);
|
2008-07-18 15:20:47 -07:00
|
|
|
|
|
2008-07-20 02:42:06 -07:00
|
|
|
|
var key = (keySpec.key && keySpec.key.toUpperCase());
|
|
|
|
|
|
2008-07-18 15:20:47 -07:00
|
|
|
|
var keyCode;
|
|
|
|
|
try
|
|
|
|
|
{
|
2008-07-20 02:42:06 -07:00
|
|
|
|
keyCode = keySpec.keycode && keybundle.getString(keySpec.keycode);
|
|
|
|
|
}
|
|
|
|
|
catch (ex)
|
|
|
|
|
{
|
|
|
|
|
var m = /VK_(\w+)/(keySpec.keycode);
|
2008-07-22 00:38:59 -07:00
|
|
|
|
if (m)
|
|
|
|
|
keyCode = m[1] || keySpec.keycode;
|
2008-07-20 02:42:06 -07:00
|
|
|
|
}
|
2008-07-18 15:20:47 -07:00
|
|
|
|
|
2008-07-20 02:42:06 -07:00
|
|
|
|
accel.push(key || keyCode || "");
|
2008-07-04 13:00:02 -07:00
|
|
|
|
return accel.join(modifierSeparator);
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 14:45:08 -07:00
|
|
|
|
// this listens to keyup events and converts them into the proper display name for the textbox
|
|
|
|
|
function keyListener(event)
|
|
|
|
|
{
|
|
|
|
|
if (!event instanceof Components.interfaces.nsIDOMKeyEvent)
|
|
|
|
|
return;
|
2008-07-17 20:09:32 -07:00
|
|
|
|
|
|
|
|
|
var keySpec = makeKeySpec(event);
|
|
|
|
|
this.value = getKeyName(keySpec);
|
2008-07-18 15:20:47 -07:00
|
|
|
|
tree.setAttribute("spec", nsIJSON.encode(keySpec));
|
2008-07-17 14:45:08 -07:00
|
|
|
|
event.preventDefault();
|
2008-07-22 00:38:59 -07:00
|
|
|
|
event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetListener(event)
|
|
|
|
|
{
|
|
|
|
|
tree.setAttribute("spec", nsIJSON.encode(makeKeySpec()));
|
2008-07-17 14:45:08 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 20:09:32 -07:00
|
|
|
|
function modificationListener(event)
|
|
|
|
|
{
|
|
|
|
|
if (event.attrName == "label" && event.newValue != event.prevValue)
|
|
|
|
|
{
|
|
|
|
|
var keySpec = tree.getAttribute("spec");
|
|
|
|
|
tree.removeAttribute("spec");
|
|
|
|
|
var cell = event.relatedNode.ownerElement;
|
|
|
|
|
cell.setAttribute("value", keySpec);
|
|
|
|
|
var command = cell.previousSibling.getAttribute("value");
|
2008-07-22 00:38:59 -07:00
|
|
|
|
keySpec = keySpec ? nsIJSON.decode(keySpec) : makeKeySpec();
|
2008-07-17 20:09:32 -07:00
|
|
|
|
addKey(command, keySpec);
|
|
|
|
|
save(command, keySpec);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-04 07:29:24 -07:00
|
|
|
|
// show the window
|
|
|
|
|
this.edit = function()
|
|
|
|
|
{
|
2008-07-17 20:09:32 -07:00
|
|
|
|
tree = document.getElementById("shortcuts");
|
|
|
|
|
|
2008-07-04 07:29:24 -07:00
|
|
|
|
var nodes = document.getElementById("ui-stack").childNodes;
|
2008-07-17 20:09:32 -07:00
|
|
|
|
Array.forEach(nodes, function(n) { if (n.getAttribute("id") != "browser-container") n.hidden = true; });
|
2008-07-04 07:29:24 -07:00
|
|
|
|
document.getElementById("shortcuts-container").hidden = false;
|
|
|
|
|
fillShortcutList();
|
2008-07-17 14:45:08 -07:00
|
|
|
|
|
2008-07-22 00:38:59 -07:00
|
|
|
|
var textbox = document.getAnonymousElementByAttribute(tree, "anonid", "input");
|
|
|
|
|
textbox.addEventListener("keypress", keyListener, true);
|
|
|
|
|
textbox.addEventListener("reset", resetListener, true);
|
2008-07-17 20:09:32 -07:00
|
|
|
|
tree.addEventListener("DOMAttrModified", modificationListener, true);
|
2008-07-04 07:29:24 -07:00
|
|
|
|
};
|
|
|
|
|
|
2008-07-17 15:03:44 -07:00
|
|
|
|
function hack()
|
|
|
|
|
{
|
|
|
|
|
// TODO: this is a hack, so I want to remove it. to do so, key elements
|
|
|
|
|
// will have to respond to direct dom manipulation.
|
|
|
|
|
Array.map(document.getElementsByTagNameNS(XUL_NS, "keyset"),
|
2008-07-17 20:09:32 -07:00
|
|
|
|
function(e) { return e.parentNode.removeChild(e); })
|
|
|
|
|
.forEach(function(e) { document.documentElement.appendChild(e); });
|
2008-07-22 00:38:59 -07:00
|
|
|
|
keyCache = undefined;
|
2008-07-17 15:03:44 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-04 07:29:24 -07:00
|
|
|
|
this.dismiss = function()
|
|
|
|
|
{
|
2008-07-17 20:09:32 -07:00
|
|
|
|
hack();
|
2008-07-22 00:38:59 -07:00
|
|
|
|
var textbox = document.getAnonymousElementByAttribute(tree, "anonid", "input");
|
|
|
|
|
textbox.removeEventListener("keypress", keyListener, true);
|
|
|
|
|
textbox.removeEventListener("reset", resetListener, true);
|
2008-07-04 07:29:24 -07:00
|
|
|
|
document.getElementById("shortcuts-container").hidden = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// also, updating the UI is helpful
|
|
|
|
|
function fillShortcutList()
|
|
|
|
|
{
|
|
|
|
|
var commands = getCommandNames();
|
2008-07-17 14:45:08 -07:00
|
|
|
|
var sb = document.getElementById("shortcut-bundles").childNodes;
|
2008-07-04 07:29:24 -07:00
|
|
|
|
|
2008-07-17 14:45:08 -07:00
|
|
|
|
function doAppend(command)
|
2008-07-04 07:29:24 -07:00
|
|
|
|
{
|
|
|
|
|
// TODO: alter the listbox xbl binding so that if appendItem is
|
|
|
|
|
// given more than 2 arguments, it interprets the additional
|
|
|
|
|
// arguments as labels for additional cells.
|
2008-07-22 00:38:59 -07:00
|
|
|
|
var key = findKeyForCommand(command);
|
2008-07-05 17:06:24 -07:00
|
|
|
|
var cell1 = document.createElementNS(XUL_NS, "treecell");
|
2008-07-17 14:45:08 -07:00
|
|
|
|
cell1.setAttribute("label", doGetString(command +".name") || command);
|
|
|
|
|
cell1.setAttribute("value", command);
|
2008-07-05 17:06:24 -07:00
|
|
|
|
var cell2 = document.createElementNS(XUL_NS, "treecell");
|
2008-07-17 14:45:08 -07:00
|
|
|
|
cell2.setAttribute("label", getKeyName(key));
|
|
|
|
|
cell2.setAttribute("value", makeKeySpec(key));
|
2008-07-05 17:06:24 -07:00
|
|
|
|
var row = document.createElementNS(XUL_NS, "treerow");
|
|
|
|
|
row.appendChild(cell1);
|
|
|
|
|
row.appendChild(cell2);
|
|
|
|
|
var item = document.createElementNS(XUL_NS, "treeitem");
|
|
|
|
|
item.appendChild(row);
|
2008-07-17 14:45:08 -07:00
|
|
|
|
children.appendChild(item);
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function doGetString(name)
|
|
|
|
|
{
|
2008-07-17 14:45:08 -07:00
|
|
|
|
var l = sb.length;
|
|
|
|
|
for (var i = 0; i < l; i++)
|
2008-07-18 15:20:47 -07:00
|
|
|
|
{
|
2008-07-17 14:45:08 -07:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return sb[i].getString(name);
|
|
|
|
|
}
|
|
|
|
|
catch (e) { }
|
2008-07-18 15:20:47 -07:00
|
|
|
|
}
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-05 17:06:24 -07:00
|
|
|
|
var children = document.getElementById("shortcuts-children");
|
|
|
|
|
tree.removeChild(children);
|
|
|
|
|
children = document.createElementNS(XUL_NS, "treechildren");
|
|
|
|
|
children.setAttribute("id", "shortcuts-children");
|
|
|
|
|
tree.appendChild(children);
|
2008-07-04 07:29:24 -07:00
|
|
|
|
|
2008-07-17 14:45:08 -07:00
|
|
|
|
commands.forEach(doAppend);
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
2008-07-05 17:06:24 -07:00
|
|
|
|
|
|
|
|
|
// saving and restoring a key assignment to the prefs
|
|
|
|
|
function save(command, keySpec)
|
|
|
|
|
{
|
2008-07-18 15:20:47 -07:00
|
|
|
|
var str = Components.classes["@mozilla.org/supports-string;1"]
|
|
|
|
|
.createInstance(Components.interfaces.nsISupportsString);
|
|
|
|
|
str.data = nsIJSON.encode(keySpec);
|
|
|
|
|
keyPrefs.setComplexValue(command, Components.interfaces.nsISupportsString, str);
|
2008-07-05 17:06:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 15:03:44 -07:00
|
|
|
|
function load(command)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2008-07-18 15:20:47 -07:00
|
|
|
|
return nsIJSON.decode(keyPrefs.getComplexValue(command, Components.interfaces.nsISupportsString).data);
|
2008-07-17 15:03:44 -07:00
|
|
|
|
}
|
|
|
|
|
catch (ex)
|
|
|
|
|
{
|
2008-07-22 00:38:59 -07:00
|
|
|
|
return "";
|
2008-07-17 15:03:44 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-07-17 20:09:32 -07:00
|
|
|
|
// and of course, none of this would be any use unless at some point we
|
|
|
|
|
// ensure that all the keys in the window match the user's choices
|
|
|
|
|
this.restore = function()
|
2008-07-05 17:06:24 -07:00
|
|
|
|
{
|
2008-07-17 20:09:32 -07:00
|
|
|
|
getCommandNames().forEach(function(cmd) { addKey(cmd, load(cmd)); });
|
2008-07-17 15:03:44 -07:00
|
|
|
|
hack();
|
2008-07-20 02:42:06 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// last but not least, we need to be able to test that everything is working
|
|
|
|
|
this.test = function()
|
|
|
|
|
{
|
|
|
|
|
// TODO: write a mochitest .xul file, and add it to the build
|
|
|
|
|
// TODO: test findCommandForKey() and findKeyForCommand()
|
|
|
|
|
function eq(a, b)
|
|
|
|
|
{
|
|
|
|
|
for (p in a)
|
|
|
|
|
if (a[p] != b[p])
|
|
|
|
|
return false;
|
|
|
|
|
for (p in b)
|
|
|
|
|
if (a[p] != b[p])
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ok(t, msg)
|
|
|
|
|
{
|
|
|
|
|
if (!t)
|
|
|
|
|
dump("ERROR FAILURE: "+ msg +"\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[[[undefined, "a"], {exists: true, modifiers: 0, key: "a", keycode: false}, "A"],
|
2008-07-20 04:11:28 -07:00
|
|
|
|
[["alt", "a"], {exists: true, modifiers: 1, key: "a", keycode: false}, "Alt+A"],
|
2008-07-20 02:42:06 -07:00
|
|
|
|
[["control", "a"], {exists: true, modifiers: 2, key: "a", keycode: false}, "Ctrl+A"],
|
|
|
|
|
[["meta", "a"], {exists: true, modifiers: 4, key: "a", keycode: false}, "Meta+A"],
|
|
|
|
|
[["shift", "a"], {exists: true, modifiers: 8, key: "a", keycode: false}, "Shift+A"],
|
|
|
|
|
[["control alt", "a"], {exists: true, modifiers: 3, key: "a", keycode: false}, "Ctrl+Alt+A"],
|
2008-07-20 04:11:28 -07:00
|
|
|
|
[["alt shift", "a"], {exists: true, modifiers: 9, key: "a", keycode: false}, "Alt+Shift+A"],
|
2008-07-20 02:42:06 -07:00
|
|
|
|
[["shift meta", "a"], {exists: true, modifiers: 12, key: "a", keycode: false}, "Meta+Shift+A"],
|
2008-07-20 04:11:28 -07:00
|
|
|
|
[["control alt shift", "a"], {exists: true, modifiers: 11, key: "a", keycode: false}, "Ctrl+Alt+Shift+A"],
|
|
|
|
|
[["alt shift meta", "a"], {exists: true, modifiers: 13, key: "a", keycode: false}, "Alt+Meta+Shift+A"],
|
2008-07-22 00:38:59 -07:00
|
|
|
|
[[undefined, undefined, "VK_BACK"], {exists: true, modifiers: 0, key: false, keycode: "VK_BACK"}, "Backspace"],
|
2008-07-20 02:42:06 -07:00
|
|
|
|
[["control", undefined, "VK_BACK"], {exists: true, modifiers: 2, key: false, keycode: "VK_BACK"}, "Ctrl+Backspace"],
|
|
|
|
|
[["control", undefined, "VK_A"], {exists: true, modifiers: 2, key: false, keycode: "VK_A"}, "Ctrl+A"],
|
|
|
|
|
[["meta shift alt control", undefined, "VK_A"], {exists: true, modifiers: 15, key: false, keycode: "VK_A"}, "Ctrl+Alt+Meta+Shift+A"],
|
|
|
|
|
[[], {exists: false, modifiers: 0, key: false, keycode: false}, ""],
|
|
|
|
|
[["control"], {exists: false, modifiers: 2, key: false, keycode: false}, ""],
|
|
|
|
|
[["alt", "α"], {exists: true, modifiers: 1, key: "α", keycode: false}, "Alt+Α"],
|
|
|
|
|
[["alt", "א"], {exists: true, modifiers: 1, key: "א", keycode: false}, "Alt+א"]
|
2008-07-22 00:38:59 -07:00
|
|
|
|
].forEach(function doTests(t)
|
2008-07-20 02:42:06 -07:00
|
|
|
|
{
|
|
|
|
|
var v, prefname;
|
|
|
|
|
ok(eq((v = makeKeySpec.apply(undefined, t[0])), t[1]),
|
|
|
|
|
"key spec for "+ t[0].toSource() +" should be "+ t[1].toSource() +", but was actually "+ v.toSource());
|
|
|
|
|
ok((v = getKeyName(makeKeySpec.apply(undefined, t[0]))) == t[2],
|
|
|
|
|
"key name for "+ t[0].toSource() +" should be '"+ t[2] +"', but was actually '"+ v +"'");
|
|
|
|
|
save((prefname = "test-" + t[2]), t[1]);
|
|
|
|
|
ok(eq((v = load(prefname)), t[1]),
|
|
|
|
|
"key spec for "+ t[2].toSource() +" should be "+ t[1].toSource() +", but was actually "+ v.toSource() +" after save+load");
|
|
|
|
|
keyPrefs.clearUserPref(prefname);
|
|
|
|
|
});
|
|
|
|
|
};
|
2008-07-04 07:29:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var Shortcuts = new ShortcutEditor();
|