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.ContextSubMenuItem} 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    id: function()
52    {
53        return this._id;
54    },
55
56    type: function()
57    {
58        return this._type;
59    },
60
61    /**
62     * @return {boolean}
63     */
64    isEnabled: function()
65    {
66        return !this._disabled;
67    },
68
69    /**
70     * @param {boolean} enabled
71     */
72    setEnabled: function(enabled)
73    {
74        this._disabled = !enabled;
75    },
76
77    _buildDescriptor: function()
78    {
79        switch (this._type) {
80        case "item":
81            return { type: "item", id: this._id, label: this._label, enabled: !this._disabled };
82        case "separator":
83            return { type: "separator" };
84        case "checkbox":
85            return { type: "checkbox", id: this._id, label: this._label, checked: !!this._checked, enabled: !this._disabled };
86        }
87    }
88}
89
90/**
91 * @constructor
92 * @extends {WebInspector.ContextMenuItem}
93 * @param topLevelMenu
94 * @param {string=} label
95 * @param {boolean=} disabled
96 */
97WebInspector.ContextSubMenuItem = function(topLevelMenu, label, disabled)
98{
99    WebInspector.ContextMenuItem.call(this, topLevelMenu, "subMenu", label, disabled);
100    /** @type {!Array.<!WebInspector.ContextMenuItem>} */
101    this._items = [];
102}
103
104WebInspector.ContextSubMenuItem.prototype = {
105    /**
106     * @param {string} label
107     * @param {function(?)} handler
108     * @param {boolean=} disabled
109     * @return {!WebInspector.ContextMenuItem}
110     */
111    appendItem: function(label, handler, disabled)
112    {
113        var item = new WebInspector.ContextMenuItem(this._contextMenu, "item", label, disabled);
114        this._pushItem(item);
115        this._contextMenu._setHandler(item.id(), handler);
116        return item;
117    },
118
119    /**
120     * @param {string} label
121     * @param {boolean=} disabled
122     * @return {!WebInspector.ContextMenuItem}
123     */
124    appendSubMenuItem: function(label, disabled)
125    {
126        var item = new WebInspector.ContextSubMenuItem(this._contextMenu, label, disabled);
127        this._pushItem(item);
128        return item;
129    },
130
131    /**
132     * @param {boolean=} disabled
133     */
134    appendCheckboxItem: function(label, handler, checked, disabled)
135    {
136        var item = new WebInspector.ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked);
137        this._pushItem(item);
138        this._contextMenu._setHandler(item.id(), handler);
139        return item;
140    },
141
142    appendSeparator: function()
143    {
144        if (this._items.length)
145            this._pendingSeparator = true;
146    },
147
148    /**
149     * @param {!WebInspector.ContextMenuItem} item
150     */
151    _pushItem: function(item)
152    {
153        if (this._pendingSeparator) {
154            this._items.push(new WebInspector.ContextMenuItem(this._contextMenu, "separator"));
155            delete this._pendingSeparator;
156        }
157        this._items.push(item);
158    },
159
160    /**
161     * @return {boolean}
162     */
163    isEmpty: function()
164    {
165        return !this._items.length;
166    },
167
168    _buildDescriptor: function()
169    {
170        var result = { type: "subMenu", label: this._label, enabled: !this._disabled, subItems: [] };
171        for (var i = 0; i < this._items.length; ++i)
172            result.subItems.push(this._items[i]._buildDescriptor());
173        return result;
174    },
175
176    __proto__: WebInspector.ContextMenuItem.prototype
177}
178
179/**
180 * @constructor
181 * @extends {WebInspector.ContextSubMenuItem}
182 */
183WebInspector.ContextMenu = function(event) {
184    WebInspector.ContextSubMenuItem.call(this, this, "");
185    this._event = event;
186    this._handlers = {};
187    this._id = 0;
188}
189
190/**
191 * @param {boolean} useSoftMenu
192 */
193WebInspector.ContextMenu.setUseSoftMenu = function(useSoftMenu)
194{
195    WebInspector.ContextMenu._useSoftMenu = useSoftMenu;
196}
197
198WebInspector.ContextMenu.prototype = {
199    nextId: function()
200    {
201        return this._id++;
202    },
203
204    show: function()
205    {
206        var menuObject = this._buildDescriptor();
207
208        if (menuObject.length) {
209            WebInspector._contextMenu = this;
210            if (WebInspector.ContextMenu._useSoftMenu) {
211                var softMenu = new WebInspector.SoftContextMenu(menuObject);
212                softMenu.show(this._event);
213            } else {
214                InspectorFrontendHost.showContextMenu(this._event, menuObject);
215            }
216            this._event.consume();
217        }
218    },
219
220    _setHandler: function(id, handler)
221    {
222        if (handler)
223            this._handlers[id] = handler;
224    },
225
226    _buildDescriptor: function()
227    {
228        var result = [];
229        for (var i = 0; i < this._items.length; ++i)
230            result.push(this._items[i]._buildDescriptor());
231        return result;
232    },
233
234    _itemSelected: function(id)
235    {
236        if (this._handlers[id])
237            this._handlers[id].call(this);
238    },
239
240    /**
241     * @param {!Object} target
242     */
243    appendApplicableItems: function(target)
244    {
245        for (var i = 0; i < WebInspector.ContextMenu._providers.length; ++i) {
246            var provider = WebInspector.ContextMenu._providers[i];
247            this.appendSeparator();
248            provider.appendApplicableItems(this._event, this, target);
249            this.appendSeparator();
250        }
251    },
252
253    __proto__: WebInspector.ContextSubMenuItem.prototype
254}
255
256/**
257 * @interface
258 */
259WebInspector.ContextMenu.Provider = function() {
260}
261
262WebInspector.ContextMenu.Provider.prototype = {
263    /**
264     * @param {!WebInspector.ContextMenu} contextMenu
265     * @param {!Object} target
266     */
267    appendApplicableItems: function(event, contextMenu, target) { }
268}
269
270/**
271 * @param {!WebInspector.ContextMenu.Provider} provider
272 */
273WebInspector.ContextMenu.registerProvider = function(provider)
274{
275    WebInspector.ContextMenu._providers.push(provider);
276}
277
278WebInspector.ContextMenu._providers = [];
279
280WebInspector.contextMenuItemSelected = function(id)
281{
282    if (WebInspector._contextMenu)
283        WebInspector._contextMenu._itemSelected(id);
284}
285
286WebInspector.contextMenuCleared = function()
287{
288    // FIXME: Unfortunately, contextMenuCleared is invoked between show and item selected
289    // so we can't delete last menu object from WebInspector. Fix the contract.
290}
291