123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- /*
- * 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 {WebInspector.SoftContextMenu=} parentMenu
- */
- WebInspector.SoftContextMenu = function(items, parentMenu)
- {
- this._items = items;
- this._parentMenu = parentMenu;
- }
- WebInspector.SoftContextMenu.prototype = {
- /**
- * @param {boolean=} alignToCurrentTarget
- */
- show: function(event, alignToCurrentTarget)
- {
- 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;
- if (alignToCurrentTarget) {
- targetRect = event.currentTarget.getBoundingClientRect();
- // Align with bottom left of currentTarget by default.
- absoluteX = targetRect.left;
- absoluteY = targetRect.bottom;
- }
- 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) {
- if (alignToCurrentTarget)
- this._contextMenuElement.style.left = Math.max(0, targetRect.right - this._contextMenuElement.offsetWidth) + "px";
- else
- this._contextMenuElement.style.left = (absoluteX - this._contextMenuElement.offsetWidth) + "px";
- }
- if (document.body.offsetHeight < this._contextMenuElement.offsetTop + this._contextMenuElement.offsetHeight) {
- if (alignToCurrentTarget)
- this._contextMenuElement.style.top = Math.max(0, targetRect.top - this._contextMenuElement.offsetHeight) + "px";
- else
- 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.hasStyleClass("soft-context-menu-glass-pane"))
- this._highlightMenuItem(null);
- },
- _highlightMenuItem: function(menuItemElement)
- {
- if (this._highlightedMenuItemElement === menuItemElement)
- return;
- this._hideSubMenu();
- if (this._highlightedMenuItemElement) {
- this._highlightedMenuItemElement.removeStyleClass("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.addStyleClass("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();
- if (this._contextMenuElement.parentElement)
- this._contextMenuElement.parentElement.removeChild(this._contextMenuElement);
- if (this._parentMenu)
- delete this._parentMenu._subMenu;
- }
- }
- if (!InspectorFrontendHost.showContextMenu) {
- InspectorFrontendHost.showContextMenu = function(event, items)
- {
- new WebInspector.SoftContextMenu(items).show(event);
- }
- }
|