1 /* 2 * Copyright (C) 2009 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @param {!WebInspector.ContextMenu} topLevelMenu 34 * @param {string} type 35 * @param {string=} label 36 * @param {boolean=} disabled 37 * @param {boolean=} checked 38 */ 39 WebInspector.ContextMenuItem = function(topLevelMenu, type, label, disabled, checked) 40 { 41 this._type = type; 42 this._label = label; 43 this._disabled = disabled; 44 this._checked = checked; 45 this._contextMenu = topLevelMenu; 46 if (type === "item" || type === "checkbox") 47 this._id = topLevelMenu.nextId(); 48 } 49 50 WebInspector.ContextMenuItem.prototype = { 51 /** 52 * @return {number} 53 */ 54 id: function() 55 { 56 return this._id; 57 }, 58 59 /** 60 * @return {string} 61 */ 62 type: function() 63 { 64 return this._type; 65 }, 66 67 /** 68 * @return {boolean} 69 */ 70 isEnabled: function() 71 { 72 return !this._disabled; 73 }, 74 75 /** 76 * @param {boolean} enabled 77 */ 78 setEnabled: function(enabled) 79 { 80 this._disabled = !enabled; 81 }, 82 83 /** 84 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor} 85 */ 86 _buildDescriptor: function() 87 { 88 switch (this._type) { 89 case "item": 90 return { type: "item", id: this._id, label: this._label, enabled: !this._disabled }; 91 case "separator": 92 return { type: "separator" }; 93 case "checkbox": 94 return { type: "checkbox", id: this._id, label: this._label, checked: !!this._checked, enabled: !this._disabled }; 95 } 96 throw new Error("Invalid item type:" + this._type); 97 } 98 } 99 100 /** 101 * @constructor 102 * @extends {WebInspector.ContextMenuItem} 103 * @param {!WebInspector.ContextMenu} topLevelMenu 104 * @param {string=} label 105 * @param {boolean=} disabled 106 */ 107 WebInspector.ContextSubMenuItem = function(topLevelMenu, label, disabled) 108 { 109 WebInspector.ContextMenuItem.call(this, topLevelMenu, "subMenu", label, disabled); 110 /** @type {!Array.<!WebInspector.ContextMenuItem>} */ 111 this._items = []; 112 } 113 114 WebInspector.ContextSubMenuItem.prototype = { 115 /** 116 * @param {string} label 117 * @param {function(?)} handler 118 * @param {boolean=} disabled 119 * @return {!WebInspector.ContextMenuItem} 120 */ 121 appendItem: function(label, handler, disabled) 122 { 123 var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", label, disabled); 124 this._pushItem(item); 125 this._contextMenu._setHandler(item.id(), handler); 126 return item; 127 }, 128 129 /** 130 * @param {string} label 131 * @param {boolean=} disabled 132 * @return {!WebInspector.ContextSubMenuItem} 133 */ 134 appendSubMenuItem: function(label, disabled) 135 { 136 var item = new WebInspector.ContextSubMenuItem(this._contextMenu, label, disabled); 137 this._pushItem(item); 138 return item; 139 }, 140 141 /** 142 * @param {string} label 143 * @param {function()} handler 144 * @param {boolean=} checked 145 * @param {boolean=} disabled 146 * @return {!WebInspector.ContextMenuItem} 147 */ 148 appendCheckboxItem: function(label, handler, checked, disabled) 149 { 150 var item = new WebInspector.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked); 151 this._pushItem(item); 152 this._contextMenu._setHandler(item.id(), handler); 153 return item; 154 }, 155 156 appendSeparator: function() 157 { 158 if (this._items.length) 159 this._pendingSeparator = true; 160 }, 161 162 /** 163 * @param {!WebInspector.ContextMenuItem} item 164 */ 165 _pushItem: function(item) 166 { 167 if (this._pendingSeparator) { 168 this._items.push(new WebInspector.ContextMenuItem(this._contextMenu, "separator")); 169 delete this._pendingSeparator; 170 } 171 this._items.push(item); 172 }, 173 174 /** 175 * @return {boolean} 176 */ 177 isEmpty: function() 178 { 179 return !this._items.length; 180 }, 181 182 /** 183 * @return {!InspectorFrontendHostAPI.ContextMenuDescriptor} 184 */ 185 _buildDescriptor: function() 186 { 187 var result = { type: "subMenu", label: this._label, enabled: !this._disabled, subItems: [] }; 188 for (var i = 0; i < this._items.length; ++i) 189 result.subItems.push(this._items[i]._buildDescriptor()); 190 return result; 191 }, 192 193 __proto__: WebInspector.ContextMenuItem.prototype 194 } 195 196 /** 197 * @constructor 198 * @extends {WebInspector.ContextSubMenuItem} 199 * @param {!Event} event 200 */ 201 WebInspector.ContextMenu = function(event) 202 { 203 WebInspector.ContextSubMenuItem.call(this, this, ""); 204 this._event = event; 205 this._handlers = {}; 206 this._id = 0; 207 } 208 209 WebInspector.ContextMenu.initialize = function() 210 { 211 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.SetUseSoftMenu, setUseSoftMenu); 212 /** 213 * @param {!WebInspector.Event} event 214 */ 215 function setUseSoftMenu(event) 216 { 217 WebInspector.ContextMenu._useSoftMenu = /** @type {boolean} */ (event.data); 218 } 219 } 220 221 WebInspector.ContextMenu.prototype = { 222 /** 223 * @return {number} 224 */ 225 nextId: function() 226 { 227 return this._id++; 228 }, 229 230 show: function() 231 { 232 var menuObject = this._buildDescriptor(); 233 234 if (menuObject.length) { 235 WebInspector._contextMenu = this; 236 if (WebInspector.ContextMenu._useSoftMenu || InspectorFrontendHost.isHostedMode()) { 237 var softMenu = new WebInspector.SoftContextMenu(menuObject, this._itemSelected.bind(this)); 238 softMenu.show(this._event.x, this._event.y); 239 } else { 240 InspectorFrontendHost.showContextMenuAtPoint(this._event.x, this._event.y, menuObject); 241 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ContextMenuCleared, this._menuCleared, this); 242 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ContextMenuItemSelected, this._onItemSelected, this); 243 } 244 this._event.consume(true); 245 } 246 }, 247 248 /** 249 * @param {number} id 250 * @param {function(?)} handler 251 */ 252 _setHandler: function(id, handler) 253 { 254 if (handler) 255 this._handlers[id] = handler; 256 }, 257 258 /** 259 * @return {!Array.<!InspectorFrontendHostAPI.ContextMenuDescriptor>} 260 */ 261 _buildDescriptor: function() 262 { 263 var result = []; 264 for (var i = 0; i < this._items.length; ++i) 265 result.push(this._items[i]._buildDescriptor()); 266 return result; 267 }, 268 269 /** 270 * @param {!WebInspector.Event} event 271 */ 272 _onItemSelected: function(event) 273 { 274 this._itemSelected(/** @type {string} */ (event.data)); 275 }, 276 277 /** 278 * @param {string} id 279 */ 280 _itemSelected: function(id) 281 { 282 if (this._handlers[id]) 283 this._handlers[id].call(this); 284 this._menuCleared(); 285 }, 286 287 _menuCleared: function() 288 { 289 InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.ContextMenuCleared, this._menuCleared, this); 290 InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.ContextMenuItemSelected, this._onItemSelected, this); 291 }, 292 293 /** 294 * @param {!Object} target 295 */ 296 appendApplicableItems: function(target) 297 { 298 self.runtime.extensions(WebInspector.ContextMenu.Provider, target).forEach(processProviders.bind(this)); 299 300 /** 301 * @param {!Runtime.Extension} extension 302 * @this {WebInspector.ContextMenu} 303 */ 304 function processProviders(extension) 305 { 306 var provider = /** @type {!WebInspector.ContextMenu.Provider} */ (extension.instance()); 307 this.appendSeparator(); 308 provider.appendApplicableItems(this._event, this, target); 309 this.appendSeparator(); 310 } 311 }, 312 313 __proto__: WebInspector.ContextSubMenuItem.prototype 314 } 315 316 /** 317 * @interface 318 */ 319 WebInspector.ContextMenu.Provider = function() { 320 } 321 322 WebInspector.ContextMenu.Provider.prototype = { 323 /** 324 * @param {!Event} event 325 * @param {!WebInspector.ContextMenu} contextMenu 326 * @param {!Object} target 327 */ 328 appendApplicableItems: function(event, contextMenu, target) { } 329 } 330