1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @constructor
7 * @param {!WebInspector.ActionRegistry} actionRegistry
8 */
9WebInspector.ShortcutRegistry = function(actionRegistry)
10{
11    this._actionRegistry = actionRegistry;
12    /** @type {!StringMultimap.<string>} */
13    this._defaultKeyToActions = new StringMultimap();
14    /** @type {!StringMultimap.<!WebInspector.KeyboardShortcut.Descriptor>} */
15    this._defaultActionToShortcut = new StringMultimap();
16    this._registerBindings();
17}
18
19WebInspector.ShortcutRegistry.prototype = {
20    /**
21     * @param {number} key
22     * @return {!Array.<string>}
23     */
24    applicableActions: function(key)
25    {
26        return this._actionRegistry.applicableActions(this._actionIdsForKey(key), WebInspector.context);
27    },
28
29    /**
30     * @param {number} key
31     * @return {!Array.<string>}
32     */
33    _actionIdsForKey: function(key)
34    {
35        var result = new StringSet();
36        var defaults = this._defaultActionsForKey(key);
37        defaults.values().forEach(function(actionId) {
38            result.add(actionId);
39        }, this);
40
41        return result.values();
42    },
43
44    /**
45     * @param {number} key
46     * @return {!Set.<string>}
47     */
48    _defaultActionsForKey: function(key)
49    {
50        return this._defaultKeyToActions.get(String(key));
51    },
52
53    /**
54     * @param {string} actionId
55     * @return {!Array.<!WebInspector.KeyboardShortcut.Descriptor>}
56     */
57    shortcutDescriptorsForAction: function(actionId)
58    {
59        return this._defaultActionToShortcut.get(actionId).values();
60    },
61
62    /**
63     * @param {!Array.<string>} actionIds
64     * @return {!Array.<number>}
65     */
66    keysForActions: function(actionIds)
67    {
68        var result = [];
69        for (var i = 0; i < actionIds.length; ++i) {
70            var descriptors = this.shortcutDescriptorsForAction(actionIds[i]);
71            for (var j = 0; j < descriptors.length; ++j)
72                result.push(descriptors[j].key);
73        }
74        return result;
75    },
76
77    /**
78     * @param {!KeyboardEvent} event
79     */
80    handleShortcut: function(event)
81    {
82        this.handleKey(WebInspector.KeyboardShortcut.makeKeyFromEvent(event), event.keyIdentifier, event);
83    },
84
85    /**
86     * @param {number} key
87     * @param {string} keyIdentifier
88     * @param {!KeyboardEvent=} event
89     */
90    handleKey: function(key, keyIdentifier, event)
91    {
92        var keyModifiers = key >> 8;
93        var actionIds = this.applicableActions(key);
94        if (WebInspector.GlassPane.DefaultFocusedViewStack.length > 1) {
95            if (actionIds.length && !isPossiblyInputKey())
96                event.consume(true);
97            return;
98        }
99
100        for (var i = 0; i < actionIds.length; ++i) {
101            if (!isPossiblyInputKey()) {
102                if (handler.call(this, actionIds[i]))
103                    break;
104            } else {
105                this._pendingActionTimer = setTimeout(handler.bind(this, actionIds[i]), 0);
106                break;
107            }
108        }
109
110        /**
111         * @return {boolean}
112         */
113        function isPossiblyInputKey()
114        {
115            if (!event || !WebInspector.isBeingEdited(/** @type {!Node} */ (event.target)) || /^F\d+|Control|Shift|Alt|Meta|Win|U\+001B$/.test(keyIdentifier))
116                return false;
117
118            if (!keyModifiers)
119                return true;
120
121            var modifiers = WebInspector.KeyboardShortcut.Modifiers;
122            if ((keyModifiers & (modifiers.Ctrl | modifiers.Alt)) === (modifiers.Ctrl | modifiers.Alt))
123                return WebInspector.isWin();
124
125            return !hasModifier(modifiers.Ctrl) && !hasModifier(modifiers.Alt) && !hasModifier(modifiers.Meta);
126        }
127
128        /**
129         * @param {number} mod
130         * @return {boolean}
131         */
132        function hasModifier(mod)
133        {
134            return !!(keyModifiers & mod);
135        }
136
137        /**
138         * @param {string} actionId
139         * @return {boolean}
140         * @this {WebInspector.ShortcutRegistry}
141         */
142        function handler(actionId)
143        {
144            var result = this._actionRegistry.execute(actionId);
145            if (result && event)
146                event.consume(true);
147            delete this._pendingActionTimer;
148            return result;
149        }
150    },
151
152    /**
153     * @param {string} actionId
154     * @param {string} shortcut
155     */
156    registerShortcut: function(actionId, shortcut)
157    {
158        var descriptor = WebInspector.KeyboardShortcut.makeDescriptorFromBindingShortcut(shortcut);
159        if (!descriptor)
160            return;
161        this._defaultActionToShortcut.set(actionId, descriptor);
162        this._defaultKeyToActions.set(String(descriptor.key), actionId);
163    },
164
165    dismissPendingShortcutAction: function()
166    {
167        if (this._pendingActionTimer) {
168            clearTimeout(this._pendingActionTimer);
169            delete this._pendingActionTimer;
170        }
171    },
172
173    _registerBindings: function()
174    {
175        document.addEventListener("input", this.dismissPendingShortcutAction.bind(this), true);
176        var extensions = self.runtime.extensions(WebInspector.ActionDelegate);
177        extensions.forEach(registerExtension, this);
178
179        /**
180         * @param {!Runtime.Extension} extension
181         * @this {WebInspector.ShortcutRegistry}
182         */
183        function registerExtension(extension)
184        {
185            var descriptor = extension.descriptor();
186            var bindings = descriptor["bindings"];
187            for (var i = 0; bindings && i < bindings.length; ++i) {
188                if (!platformMatches(bindings[i].platform))
189                    continue;
190                var shortcuts = bindings[i]["shortcut"].split(/\s+/);
191                shortcuts.forEach(this.registerShortcut.bind(this, descriptor["actionId"]));
192            }
193        }
194
195        /**
196         * @param {string=} platformsString
197         * @return {boolean}
198         */
199        function platformMatches(platformsString)
200        {
201            if (!platformsString)
202                return true;
203            var platforms = platformsString.split(",");
204            var isMatch = false;
205            var currentPlatform = WebInspector.platform();
206            for (var i = 0; !isMatch && i < platforms.length; ++i)
207                isMatch = platforms[i] === currentPlatform;
208            return isMatch;
209        }
210    }
211}
212
213/**
214 * @constructor
215 */
216WebInspector.ShortcutRegistry.ForwardedShortcut = function()
217{
218}
219
220WebInspector.ShortcutRegistry.ForwardedShortcut.instance = new WebInspector.ShortcutRegistry.ForwardedShortcut();
221
222/** @type {!WebInspector.ShortcutRegistry} */
223WebInspector.shortcutRegistry;
224