1/*
2 * Copyright (C) 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/**
31 * @constructor
32 */
33WebInspector.KeyboardShortcut = function()
34{
35}
36
37/**
38 * Constants for encoding modifier key set as a bit mask.
39 * @see #_makeKeyFromCodeAndModifiers
40 */
41WebInspector.KeyboardShortcut.Modifiers = {
42    None: 0,   // Constant for empty modifiers set.
43    Shift: 1,
44    Ctrl: 2,
45    Alt: 4,
46    Meta: 8,   // Command key on Mac, Win key on other platforms.
47    get CtrlOrMeta()
48    {
49        // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms
50        return WebInspector.isMac() ? this.Meta : this.Ctrl;
51    },
52    get ShiftOrOption()
53    {
54        // Option on Mac, Shift on other platforms
55        return WebInspector.isMac() ? this.Alt : this.Shift;
56    }
57};
58
59/** @typedef {!{code: number, name: (string|!Object.<string, string>)}} */
60WebInspector.KeyboardShortcut.Key;
61
62/** @type {!Object.<string, !WebInspector.KeyboardShortcut.Key>} */
63WebInspector.KeyboardShortcut.Keys = {
64    Backspace: { code: 8, name: "\u21a4" },
65    Tab: { code: 9, name: { mac: "\u21e5", other: "Tab" } },
66    Enter: { code: 13, name: { mac: "\u21a9", other: "Enter" } },
67    Shift: { code: 16, name: { mac: "\u21e7", other: "Shift" } },
68    Ctrl: { code: 17, name: "Ctrl" },
69    Esc: { code: 27, name: { mac: "\u238b", other: "Esc" } },
70    Space: { code: 32, name: "Space" },
71    PageUp: { code: 33,  name: { mac: "\u21de", other: "PageUp" } },      // also NUM_NORTH_EAST
72    PageDown: { code: 34, name: { mac: "\u21df", other: "PageDown" } },   // also NUM_SOUTH_EAST
73    End: { code: 35, name: { mac: "\u2197", other: "End" } },             // also NUM_SOUTH_WEST
74    Home: { code: 36, name: { mac: "\u2196", other: "Home" } },           // also NUM_NORTH_WEST
75    Left: { code: 37, name: "\u2190" },           // also NUM_WEST
76    Up: { code: 38, name: "\u2191" },             // also NUM_NORTH
77    Right: { code: 39, name: "\u2192" },          // also NUM_EAST
78    Down: { code: 40, name: "\u2193" },           // also NUM_SOUTH
79    Delete: { code: 46, name: "Del" },
80    Zero: { code: 48, name: "0" },
81    H: { code: 72, name: "H" },
82    Meta: { code: 91, name: "Meta" },
83    F1: { code: 112, name: "F1" },
84    F2: { code: 113, name: "F2" },
85    F3: { code: 114, name: "F3" },
86    F4: { code: 115, name: "F4" },
87    F5: { code: 116, name: "F5" },
88    F6: { code: 117, name: "F6" },
89    F7: { code: 118, name: "F7" },
90    F8: { code: 119, name: "F8" },
91    F9: { code: 120, name: "F9" },
92    F10: { code: 121, name: "F10" },
93    F11: { code: 122, name: "F11" },
94    F12: { code: 123, name: "F12" },
95    Semicolon: { code: 186, name: ";" },
96    NumpadPlus: { code: 107, name: "Numpad +" },
97    NumpadMinus: { code: 109, name: "Numpad -" },
98    Numpad0: { code: 96, name: "Numpad 0" },
99    Plus: { code: 187, name: "+" },
100    Comma: { code: 188, name: "," },
101    Minus: { code: 189, name: "-" },
102    Period: { code: 190, name: "." },
103    Slash: { code: 191, name: "/" },
104    QuestionMark: { code: 191, name: "?" },
105    Apostrophe: { code: 192, name: "`" },
106    Tilde: { code: 192, name: "Tilde" },
107    Backslash: { code: 220, name: "\\" },
108    SingleQuote: { code: 222, name: "\'" },
109    get CtrlOrMeta()
110    {
111        // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms
112        return WebInspector.isMac() ? this.Meta : this.Ctrl;
113    },
114};
115
116WebInspector.KeyboardShortcut.KeyBindings = {};
117
118(function() {
119    for (var key in WebInspector.KeyboardShortcut.Keys) {
120        var descriptor = WebInspector.KeyboardShortcut.Keys[key];
121        if (typeof descriptor === "object" && descriptor["code"]) {
122            var name = typeof descriptor["name"] === "string" ? descriptor["name"] : key;
123            WebInspector.KeyboardShortcut.KeyBindings[name] = descriptor;
124        }
125    }
126})();
127
128/**
129 * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits.
130 * It is useful for matching pressed keys.
131 *
132 * @param {number|string} keyCode The code of the key, or a character "a-z" which is converted to a keyCode value.
133 * @param {number=} modifiers Optional list of modifiers passed as additional parameters.
134 * @return {number}
135 */
136WebInspector.KeyboardShortcut.makeKey = function(keyCode, modifiers)
137{
138    if (typeof keyCode === "string")
139        keyCode = keyCode.charCodeAt(0) - (/^[a-z]/.test(keyCode) ? 32 : 0);
140    modifiers = modifiers || WebInspector.KeyboardShortcut.Modifiers.None;
141    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers);
142}
143
144/**
145 * @param {?KeyboardEvent} keyboardEvent
146 * @return {number}
147 */
148WebInspector.KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent)
149{
150    var modifiers = WebInspector.KeyboardShortcut.Modifiers.None;
151    if (keyboardEvent.shiftKey)
152        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift;
153    if (keyboardEvent.ctrlKey)
154        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Ctrl;
155    if (keyboardEvent.altKey)
156        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Alt;
157    if (keyboardEvent.metaKey)
158        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Meta;
159
160    // Use either a real or a synthetic keyCode (for events originating from extensions).
161    var keyCode = keyboardEvent.keyCode || keyboardEvent["__keyCode"];
162    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers);
163}
164
165/**
166 * @param {?KeyboardEvent} keyboardEvent
167 * @return {number}
168 */
169WebInspector.KeyboardShortcut.makeKeyFromEventIgnoringModifiers = function(keyboardEvent)
170{
171    var keyCode = keyboardEvent.keyCode || keyboardEvent["__keyCode"];
172    return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, WebInspector.KeyboardShortcut.Modifiers.None);
173}
174
175/**
176 * @param {?KeyboardEvent} event
177 * @return {boolean}
178 */
179WebInspector.KeyboardShortcut.eventHasCtrlOrMeta = function(event)
180{
181    return WebInspector.isMac() ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
182}
183
184/**
185 * @param {!Event} event
186 * @return {boolean}
187 */
188WebInspector.KeyboardShortcut.hasNoModifiers = function(event)
189{
190    return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey;
191}
192
193/** @typedef {!{key: number, name: string}} */
194WebInspector.KeyboardShortcut.Descriptor;
195
196/**
197 * @param {string|!WebInspector.KeyboardShortcut.Key} key
198 * @param {number=} modifiers
199 * @return {!WebInspector.KeyboardShortcut.Descriptor}
200 */
201WebInspector.KeyboardShortcut.makeDescriptor = function(key, modifiers)
202{
203    return {
204        key: WebInspector.KeyboardShortcut.makeKey(typeof key === "string" ? key : key.code, modifiers),
205        name: WebInspector.KeyboardShortcut.shortcutToString(key, modifiers)
206    };
207}
208
209/**
210 * @param {string} shortcut
211 * @return {?WebInspector.KeyboardShortcut.Descriptor}
212 */
213WebInspector.KeyboardShortcut.makeDescriptorFromBindingShortcut = function(shortcut)
214{
215    var parts = shortcut.split(/\+(?!$)/);
216    var modifiers = 0;
217    var keyString;
218    for (var i = 0; i < parts.length; ++i) {
219        if (typeof WebInspector.KeyboardShortcut.Modifiers[parts[i]] !== "undefined") {
220            modifiers |= WebInspector.KeyboardShortcut.Modifiers[parts[i]];
221            continue;
222        }
223        console.assert(i === parts.length - 1, "Only one key other than modifier is allowed in shortcut <" + shortcut + ">");
224        keyString = parts[i];
225        break;
226    }
227    console.assert(keyString, "Modifiers-only shortcuts are not allowed (encountered <" + shortcut + ">)");
228    if (!keyString)
229        return null;
230
231    var key = WebInspector.KeyboardShortcut.Keys[keyString] || WebInspector.KeyboardShortcut.KeyBindings[keyString];
232    if (key && key.shiftKey)
233        modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift;
234    return WebInspector.KeyboardShortcut.makeDescriptor(key ? key : keyString, modifiers);
235}
236
237/**
238 * @param {string|!WebInspector.KeyboardShortcut.Key} key
239 * @param {number=} modifiers
240 * @return {string}
241 */
242WebInspector.KeyboardShortcut.shortcutToString = function(key, modifiers)
243{
244    return WebInspector.KeyboardShortcut._modifiersToString(modifiers) + WebInspector.KeyboardShortcut._keyName(key);
245}
246
247/**
248 * @param {string|!WebInspector.KeyboardShortcut.Key} key
249 * @return {string}
250 */
251WebInspector.KeyboardShortcut._keyName = function(key)
252{
253    if (typeof key === "string")
254        return key.toUpperCase();
255    if (typeof key.name === "string")
256        return key.name;
257    return key.name[WebInspector.platform()] || key.name.other || '';
258}
259
260/**
261 * @param {number} keyCode
262 * @param {?number} modifiers
263 * @return {number}
264 */
265WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers)
266{
267    return (keyCode & 255) | (modifiers << 8);
268};
269
270/**
271 * @param {number} key
272 * @return {!{keyCode: number, modifiers: number}}
273 */
274WebInspector.KeyboardShortcut.keyCodeAndModifiersFromKey = function(key)
275{
276    return { keyCode: key & 255, modifiers: key >> 8 };
277}
278
279/**
280 * @param {number|undefined} modifiers
281 * @return {string}
282 */
283WebInspector.KeyboardShortcut._modifiersToString = function(modifiers)
284{
285    const cmdKey = "\u2318";
286    const optKey = "\u2325";
287    const shiftKey = "\u21e7";
288    const ctrlKey = "\u2303";
289
290    var isMac = WebInspector.isMac();
291    var res = "";
292    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Ctrl)
293        res += isMac ? ctrlKey : "Ctrl + ";
294    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Alt)
295        res += isMac ? optKey : "Alt + ";
296    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Shift)
297        res += isMac ? shiftKey : "Shift + ";
298    if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Meta)
299        res += isMac ? cmdKey : "Win + ";
300
301    return res;
302};
303
304WebInspector.KeyboardShortcut.SelectAll = WebInspector.KeyboardShortcut.makeKey("a", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta);
305