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 */
39WebInspector.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
50WebInspector.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 */
107WebInspector.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
114WebInspector.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 */
201WebInspector.ContextMenu = function(event)
202{
203    WebInspector.ContextSubMenuItem.call(this, this, "");
204    this._event = event;
205    this._handlers = {};
206    this._id = 0;
207}
208
209WebInspector.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
221WebInspector.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 */
319WebInspector.ContextMenu.Provider = function() {
320}
321
322WebInspector.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