diff options
Diffstat (limited to 'chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js')
-rw-r--r-- | chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js new file mode 100644 index 00000000000..a4626e36b1b --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/ui/SoftContextMenu.js @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2011 Google Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {!Array.<!WebInspector.ContextMenuItem>} items + * @param {!WebInspector.SoftContextMenu=} parentMenu + */ +WebInspector.SoftContextMenu = function(items, parentMenu) +{ + this._items = items; + this._parentMenu = parentMenu; +} + +WebInspector.SoftContextMenu.prototype = { + /** + * @param {!Event} event + */ + show: function(event) + { + this._x = event.x; + this._y = event.y; + this._time = new Date().getTime(); + + // Absolutely position menu for iframes. + var absoluteX = event.pageX; + var absoluteY = event.pageY; + var targetElement = event.target; + while (targetElement && window !== targetElement.ownerDocument.defaultView) { + var frameElement = targetElement.ownerDocument.defaultView.frameElement; + absoluteY += frameElement.totalOffsetTop(); + absoluteX += frameElement.totalOffsetLeft(); + targetElement = frameElement; + } + + // Create context menu. + var targetRect; + this._contextMenuElement = document.createElement("div"); + this._contextMenuElement.className = "soft-context-menu"; + this._contextMenuElement.tabIndex = 0; + this._contextMenuElement.style.top = absoluteY + "px"; + this._contextMenuElement.style.left = absoluteX + "px"; + + this._contextMenuElement.addEventListener("mouseup", consumeEvent, false); + this._contextMenuElement.addEventListener("keydown", this._menuKeyDown.bind(this), false); + + for (var i = 0; i < this._items.length; ++i) + this._contextMenuElement.appendChild(this._createMenuItem(this._items[i])); + + // Install glass pane capturing events. + if (!this._parentMenu) { + this._glassPaneElement = document.createElement("div"); + this._glassPaneElement.className = "soft-context-menu-glass-pane"; + this._glassPaneElement.tabIndex = 0; + this._glassPaneElement.addEventListener("mouseup", this._glassPaneMouseUp.bind(this), false); + this._glassPaneElement.appendChild(this._contextMenuElement); + document.body.appendChild(this._glassPaneElement); + this._focus(); + } else + this._parentMenu._parentGlassPaneElement().appendChild(this._contextMenuElement); + + // Re-position menu in case it does not fit. + if (document.body.offsetWidth < this._contextMenuElement.offsetLeft + this._contextMenuElement.offsetWidth) + this._contextMenuElement.style.left = (absoluteX - this._contextMenuElement.offsetWidth) + "px"; + if (document.body.offsetHeight < this._contextMenuElement.offsetTop + this._contextMenuElement.offsetHeight) + this._contextMenuElement.style.top = (document.body.offsetHeight - this._contextMenuElement.offsetHeight) + "px"; + + event.consume(true); + }, + + _parentGlassPaneElement: function() + { + if (this._glassPaneElement) + return this._glassPaneElement; + if (this._parentMenu) + return this._parentMenu._parentGlassPaneElement(); + return null; + }, + + _createMenuItem: function(item) + { + if (item.type === "separator") + return this._createSeparator(); + + if (item.type === "subMenu") + return this._createSubMenu(item); + + var menuItemElement = document.createElement("div"); + menuItemElement.className = "soft-context-menu-item"; + + var checkMarkElement = document.createElement("span"); + checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol + checkMarkElement.className = "soft-context-menu-item-checkmark"; + if (!item.checked) + checkMarkElement.style.opacity = "0"; + + menuItemElement.appendChild(checkMarkElement); + menuItemElement.appendChild(document.createTextNode(item.label)); + + menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false); + menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false); + + // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation. + menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false); + menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false); + + menuItemElement._actionId = item.id; + return menuItemElement; + }, + + _createSubMenu: function(item) + { + var menuItemElement = document.createElement("div"); + menuItemElement.className = "soft-context-menu-item"; + menuItemElement._subItems = item.subItems; + + // Occupy the same space on the left in all items. + var checkMarkElement = document.createElement("span"); + checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol + checkMarkElement.className = "soft-context-menu-item-checkmark"; + checkMarkElement.style.opacity = "0"; + menuItemElement.appendChild(checkMarkElement); + + var subMenuArrowElement = document.createElement("span"); + subMenuArrowElement.textContent = "\u25B6"; // BLACK RIGHT-POINTING TRIANGLE + subMenuArrowElement.className = "soft-context-menu-item-submenu-arrow"; + + menuItemElement.appendChild(document.createTextNode(item.label)); + menuItemElement.appendChild(subMenuArrowElement); + + menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false); + menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false); + + // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation. + menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false); + menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false); + + return menuItemElement; + }, + + _createSeparator: function() + { + var separatorElement = document.createElement("div"); + separatorElement.className = "soft-context-menu-separator"; + separatorElement._isSeparator = true; + separatorElement.addEventListener("mouseover", this._hideSubMenu.bind(this), false); + separatorElement.createChild("div", "separator-line"); + return separatorElement; + }, + + _menuItemMouseDown: function(event) + { + // Do not let separator's mouse down hit menu's handler - we need to receive mouse up! + event.consume(true); + }, + + _menuItemMouseUp: function(event) + { + this._triggerAction(event.target, event); + event.consume(); + }, + + _focus: function() + { + this._contextMenuElement.focus(); + }, + + _triggerAction: function(menuItemElement, event) + { + if (!menuItemElement._subItems) { + this._discardMenu(true, event); + if (typeof menuItemElement._actionId !== "undefined") { + WebInspector.contextMenuItemSelected(menuItemElement._actionId); + delete menuItemElement._actionId; + } + return; + } + + this._showSubMenu(menuItemElement, event); + event.consume(); + }, + + _showSubMenu: function(menuItemElement, event) + { + if (menuItemElement._subMenuTimer) { + clearTimeout(menuItemElement._subMenuTimer); + delete menuItemElement._subMenuTimer; + } + if (this._subMenu) + return; + + this._subMenu = new WebInspector.SoftContextMenu(menuItemElement._subItems, this); + this._subMenu.show(this._buildMouseEventForSubMenu(menuItemElement)); + }, + + _buildMouseEventForSubMenu: function(subMenuItemElement) + { + var subMenuOffset = { x: subMenuItemElement.offsetWidth - 3, y: subMenuItemElement.offsetTop - 1 }; + var targetX = this._x + subMenuOffset.x; + var targetY = this._y + subMenuOffset.y; + var targetPageX = parseInt(this._contextMenuElement.style.left, 10) + subMenuOffset.x; + var targetPageY = parseInt(this._contextMenuElement.style.top, 10) + subMenuOffset.y; + return { x: targetX, y: targetY, pageX: targetPageX, pageY: targetPageY, consume: function() {} }; + }, + + _hideSubMenu: function() + { + if (!this._subMenu) + return; + this._subMenu._discardSubMenus(); + this._focus(); + }, + + _menuItemMouseOver: function(event) + { + this._highlightMenuItem(event.target); + }, + + _menuItemMouseOut: function(event) + { + if (!this._subMenu || !event.relatedTarget) { + this._highlightMenuItem(null); + return; + } + + var relatedTarget = event.relatedTarget; + if (this._contextMenuElement.isSelfOrAncestor(relatedTarget) || relatedTarget.classList.contains("soft-context-menu-glass-pane")) + this._highlightMenuItem(null); + }, + + _highlightMenuItem: function(menuItemElement) + { + if (this._highlightedMenuItemElement === menuItemElement) + return; + + this._hideSubMenu(); + if (this._highlightedMenuItemElement) { + this._highlightedMenuItemElement.classList.remove("soft-context-menu-item-mouse-over"); + if (this._highlightedMenuItemElement._subItems && this._highlightedMenuItemElement._subMenuTimer) { + clearTimeout(this._highlightedMenuItemElement._subMenuTimer); + delete this._highlightedMenuItemElement._subMenuTimer; + } + } + this._highlightedMenuItemElement = menuItemElement; + if (this._highlightedMenuItemElement) { + this._highlightedMenuItemElement.classList.add("soft-context-menu-item-mouse-over"); + this._contextMenuElement.focus(); + if (this._highlightedMenuItemElement._subItems && !this._highlightedMenuItemElement._subMenuTimer) + this._highlightedMenuItemElement._subMenuTimer = setTimeout(this._showSubMenu.bind(this, this._highlightedMenuItemElement, this._buildMouseEventForSubMenu(this._highlightedMenuItemElement)), 150); + } + }, + + _highlightPrevious: function() + { + var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.previousSibling : this._contextMenuElement.lastChild; + while (menuItemElement && menuItemElement._isSeparator) + menuItemElement = menuItemElement.previousSibling; + if (menuItemElement) + this._highlightMenuItem(menuItemElement); + }, + + _highlightNext: function() + { + var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.nextSibling : this._contextMenuElement.firstChild; + while (menuItemElement && menuItemElement._isSeparator) + menuItemElement = menuItemElement.nextSibling; + if (menuItemElement) + this._highlightMenuItem(menuItemElement); + }, + + _menuKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Up": + this._highlightPrevious(); break; + case "Down": + this._highlightNext(); break; + case "Left": + if (this._parentMenu) { + this._highlightMenuItem(null); + this._parentMenu._focus(); + } + break; + case "Right": + if (!this._highlightedMenuItemElement) + break; + if (this._highlightedMenuItemElement._subItems) { + this._showSubMenu(this._highlightedMenuItemElement, this._buildMouseEventForSubMenu(this._highlightedMenuItemElement)); + this._subMenu._focus(); + this._subMenu._highlightNext(); + } + break; + case "U+001B": // Escape + this._discardMenu(true, event); break; + case "Enter": + if (!isEnterKey(event)) + break; + // Fall through + case "U+0020": // Space + if (this._highlightedMenuItemElement) + this._triggerAction(this._highlightedMenuItemElement, event); + break; + } + event.consume(true); + }, + + _glassPaneMouseUp: function(event) + { + // Return if this is simple 'click', since dispatched on glass pane, can't use 'click' event. + if (event.x === this._x && event.y === this._y && new Date().getTime() - this._time < 300) + return; + this._discardMenu(true, event); + event.consume(); + }, + + /** + * @param {boolean} closeParentMenus + * @param {!Event=} event + */ + _discardMenu: function(closeParentMenus, event) + { + if (this._subMenu && !closeParentMenus) + return; + if (this._glassPaneElement) { + var glassPane = this._glassPaneElement; + delete this._glassPaneElement; + // This can re-enter discardMenu due to blur. + document.body.removeChild(glassPane); + if (this._parentMenu) { + delete this._parentMenu._subMenu; + if (closeParentMenus) + this._parentMenu._discardMenu(closeParentMenus, event); + } + + if (event) + event.consume(true); + } else if (this._parentMenu && this._contextMenuElement.parentElement) { + this._discardSubMenus(); + if (closeParentMenus) + this._parentMenu._discardMenu(closeParentMenus, event); + + if (event) + event.consume(true); + } + }, + + _discardSubMenus: function() + { + if (this._subMenu) + this._subMenu._discardSubMenus(); + this._contextMenuElement.remove(); + if (this._parentMenu) + delete this._parentMenu._subMenu; + } +} + +if (!InspectorFrontendHost.showContextMenu) { + +InspectorFrontendHost.showContextMenu = function(event, items) +{ + new WebInspector.SoftContextMenu(items).show(event); +} + +} |