initial import
[vuplus_webkit] / Source / WebCore / inspector / front-end / SoftContextMenu.js
1 /*
2  * Copyright (C) 2011 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 if (!InspectorFrontendHost.showContextMenu) {
27
28 /**
29  * @constructor
30  */
31 WebInspector.SoftContextMenu = function(items)
32 {
33     this._items = items;
34 }
35
36 WebInspector.SoftContextMenu.prototype = {
37     show: function(event)
38     {
39         this._x = event.x;
40         this._y = event.y;
41         this._time = new Date().getTime();
42
43         // Absolutely position menu for iframes.
44         var absoluteX = event.pageX;
45         var absoluteY = event.pageY;
46         var targetElement = event.target;
47         while (targetElement && window !== targetElement.ownerDocument.defaultView) {
48             var frameElement = targetElement.ownerDocument.defaultView.frameElement;
49             absoluteY += frameElement.totalOffsetTop();
50             absoluteX += frameElement.totalOffsetLeft();
51             targetElement = frameElement;
52         }
53
54         // Install glass pane capturing events.
55         this._glassPaneElement = document.createElement("div");
56         this._glassPaneElement.className = "soft-context-menu-glass-pane";
57         this._glassPaneElement.tabIndex = 0;
58         this._glassPaneElement.addEventListener("mouseup", this._glassPaneMouseUp.bind(this), false);
59
60         // Create context menu.
61         this._contextMenuElement = document.createElement("div");
62         this._contextMenuElement.className = "soft-context-menu";
63         this._contextMenuElement.tabIndex = 0;
64         this._contextMenuElement.style.top = absoluteY + "px";
65         this._contextMenuElement.style.left = absoluteX + "px";
66
67         this._contextMenuElement.addEventListener("mousedown", this._discardMenu.bind(this), false);
68         this._contextMenuElement.addEventListener("keydown", this._menuKeyDown.bind(this), false);
69         this._contextMenuElement.addEventListener("blur", this._discardMenu.bind(this), false);
70
71         for (var i = 0; i < this._items.length; ++i)
72             this._contextMenuElement.appendChild(this._createMenuItem(this._items[i]));
73
74         this._glassPaneElement.appendChild(this._contextMenuElement);
75         document.body.appendChild(this._glassPaneElement);
76         this._contextMenuElement.focus();
77
78         // Re-position menu in case it does not fit.
79         if (document.body.offsetWidth <  this._contextMenuElement.offsetLeft + this._contextMenuElement.offsetWidth)
80             this._contextMenuElement.style.left = (absoluteX - this._contextMenuElement.offsetWidth) + "px";
81         if (document.body.offsetHeight < this._contextMenuElement.offsetTop + this._contextMenuElement.offsetHeight)
82             this._contextMenuElement.style.top = (document.body.offsetHeight - this._contextMenuElement.offsetHeight) + "px";
83
84         event.stopPropagation();
85         event.preventDefault();
86     },
87
88     _createMenuItem: function(item)
89     {
90         if (item.type === "separator")
91             return this._createSeparator();
92
93         var menuItemElement = document.createElement("div");
94         menuItemElement.className = "soft-context-menu-item";
95
96         var checkMarkElement = document.createElement("span");
97         checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol
98         checkMarkElement.style.pointerEvents = "none";
99         if (!item.checked)
100             checkMarkElement.style.opacity = "0";
101
102         menuItemElement.appendChild(checkMarkElement);
103         menuItemElement.appendChild(document.createTextNode(item.label));
104
105         menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false);
106         menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false);
107
108         // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation.
109         menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false);
110         menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false);
111
112         menuItemElement._actionId = item.id;
113         return menuItemElement;
114     },
115
116     _createSeparator: function()
117     {
118         var separatorElement = document.createElement("div");
119         separatorElement.className = "soft-context-menu-separator";
120         separatorElement._isSeparator = true;
121         return separatorElement;
122     },
123
124     _menuItemMouseDown: function(event)
125     {
126         // Do not let separator's mouse down hit menu's handler - we need to receive mouse up!
127         event.stopPropagation();
128         event.preventDefault();
129     },
130
131     _menuItemMouseUp: function(event)
132     {
133         this._triggerAction(event.target, event);
134     },
135
136     _triggerAction: function(menuItemElement, event)
137     {
138         this._discardMenu(event);
139         if (typeof menuItemElement._actionId !== "undefined") {
140             WebInspector.contextMenuItemSelected(menuItemElement._actionId);
141             delete menuItemElement._actionId;
142         }
143     },
144
145     _menuItemMouseOver: function(event)
146     {
147         this._highlightMenuItem(event.target);
148     },
149
150     _menuItemMouseOut: function(event)
151     {
152         this._highlightMenuItem(null);
153     },
154
155     _highlightMenuItem: function(menuItemElement)
156     {
157         if (this._highlightedMenuItemElement)
158             this._highlightedMenuItemElement.removeStyleClass("soft-context-menu-item-mouse-over");
159         this._highlightedMenuItemElement = menuItemElement;
160         if (this._highlightedMenuItemElement)
161             this._highlightedMenuItemElement.addStyleClass("soft-context-menu-item-mouse-over");
162     },
163
164     _highlightPrevious: function()
165     {
166         var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.previousSibling : this._contextMenuElement.lastChild;
167         while (menuItemElement && menuItemElement._isSeparator)
168             menuItemElement = menuItemElement.previousSibling;
169         if (menuItemElement)
170             this._highlightMenuItem(menuItemElement);
171     },
172
173     _highlightNext: function()
174     {
175         var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.nextSibling : this._contextMenuElement.firstChild;
176         while (menuItemElement && menuItemElement._isSeparator)
177             menuItemElement = menuItemElement.nextSibling;
178         if (menuItemElement)
179             this._highlightMenuItem(menuItemElement);
180     },
181
182     _menuKeyDown: function(event)
183     {
184         switch (event.keyIdentifier) {
185         case "Up":
186             this._highlightPrevious(); break;
187         case "Down":
188             this._highlightNext(); break;
189         case "U+001B": // Escape
190             this._discardMenu(event); break;
191         case "Enter":
192             if (!isEnterKey(event))
193                 break;
194             // Fall through
195         case "U+0020": // Space
196             if (this._highlightedMenuItemElement)
197                 this._triggerAction(this._highlightedMenuItemElement, event);
198             break;
199         }
200         event.stopPropagation();
201         event.preventDefault();
202     },
203
204     _glassPaneMouseUp: function(event)
205     {
206         // Return if this is simple 'click', since dispatched on glass pane, can't use 'click' event.
207         if (event.x === this._x && event.y === this._y && new Date().getTime() - this._time < 300)
208             return;
209         this._discardMenu(event);
210     },
211
212     _discardMenu: function(event)
213     {
214         if (this._glassPaneElement) {
215             var glassPane = this._glassPaneElement;
216             delete this._glassPaneElement;
217             // This can re-enter discardMenu due to blur.
218             document.body.removeChild(glassPane);
219
220             event.stopPropagation();
221             event.preventDefault();
222         }
223     }
224 }
225
226 InspectorFrontendHost.showContextMenu = function(event, items)
227 {
228     new WebInspector.SoftContextMenu(items).show(event);
229 }
230
231 }