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 */ 9 WebInspector.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 19 WebInspector.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 */ 216 WebInspector.ShortcutRegistry.ForwardedShortcut = function() 217 { 218 } 219 220 WebInspector.ShortcutRegistry.ForwardedShortcut.instance = new WebInspector.ShortcutRegistry.ForwardedShortcut(); 221 222 /** @type {!WebInspector.ShortcutRegistry} */ 223 WebInspector.shortcutRegistry; 224