/* * Copyright (C) 2009 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "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 THE COPYRIGHT * OWNER 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. */ WebInspector.Popover = function(contentElement) { this.element = document.createElement("div"); this.element.className = "popover"; this._popupArrowElement = document.createElement("div"); this._popupArrowElement.className = "arrow"; this.element.appendChild(this._popupArrowElement); this.contentElement = contentElement; this._contentDiv = document.createElement("div"); this._contentDiv.className = "content"; this._visible = false; } WebInspector.Popover.prototype = { show: function(anchor, preferredWidth, preferredHeight) { // This should not happen, but we hide previous popup to be on the safe side. if (WebInspector.Popover._popoverElement) document.body.removeChild(WebInspector.Popover._popoverElement); WebInspector.Popover._popoverElement = this.element; // Temporarily attach in order to measure preferred dimensions. this.contentElement.positionAt(0, 0); document.body.appendChild(this.contentElement); var preferredWidth = preferredWidth || this.contentElement.offsetWidth; var preferredHeight = preferredHeight || this.contentElement.offsetHeight; this._contentDiv.appendChild(this.contentElement); this.element.appendChild(this._contentDiv); document.body.appendChild(this.element); this._positionElement(anchor, preferredWidth, preferredHeight); this._visible = true; }, hide: function() { if (WebInspector.Popover._popoverElement) { delete WebInspector.Popover._popoverElement; document.body.removeChild(this.element); } this._visible = false; }, get visible() { return this._visible; }, _positionElement: function(anchorElement, preferredWidth, preferredHeight) { const borderWidth = 25; const scrollerWidth = 11; const arrowHeight = 15; const arrowOffset = 10; const borderRadius = 10; // Skinny tooltips are not pretty, their arrow location is not nice. preferredWidth = Math.max(preferredWidth, 50); const totalWidth = window.innerWidth; const totalHeight = window.innerHeight; var anchorBox = {x: anchorElement.totalOffsetLeft, y: anchorElement.totalOffsetTop, width: anchorElement.offsetWidth, height: anchorElement.offsetHeight}; while (anchorElement !== document.body) { if (anchorElement.scrollLeft) anchorBox.x -= anchorElement.scrollLeft; if (anchorElement.scrollTop) anchorBox.y -= anchorElement.scrollTop; anchorElement = anchorElement.parentElement; } var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerWidth, height: preferredHeight }; var verticalAlignment; var roomAbove = anchorBox.y; var roomBelow = totalHeight - anchorBox.y - anchorBox.height; if (roomAbove > roomBelow) { // Positioning above the anchor. if (anchorBox.y > newElementPosition.height + arrowHeight + borderRadius) newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHeight; else { newElementPosition.y = borderRadius * 2; newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight; } verticalAlignment = "bottom"; } else { // Positioning below the anchor. newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight; if (newElementPosition.y + newElementPosition.height + arrowHeight - borderWidth >= totalHeight) newElementPosition.height = totalHeight - anchorBox.y - anchorBox.height - borderRadius * 2 - arrowHeight; // Align arrow. verticalAlignment = "top"; } var horizontalAlignment; if (anchorBox.x + newElementPosition.width < totalWidth) { newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius - arrowOffset); horizontalAlignment = "left"; } else if (newElementPosition.width + borderRadius * 2 < totalWidth) { newElementPosition.x = totalWidth - newElementPosition.width - borderRadius; horizontalAlignment = "right"; // Position arrow accurately. var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.width - borderRadius - arrowOffset); arrowRightPosition += anchorBox.width / 2; this._popupArrowElement.style.right = arrowRightPosition + "px"; } else { newElementPosition.x = borderRadius; newElementPosition.width = totalWidth - borderRadius * 2; newElementPosition.height += scrollerWidth; horizontalAlignment = "left"; if (verticalAlignment === "bottom") newElementPosition.y -= scrollerWidth; // Position arrow accurately. this._popupArrowElement.style.left = Math.max(0, anchorBox.x - borderRadius * 2 - arrowOffset) + "px"; this._popupArrowElement.style.left += anchorBox.width / 2; } this.element.className = "popover " + verticalAlignment + "-" + horizontalAlignment + "-arrow"; this.element.positionAt(newElementPosition.x - borderWidth, newElementPosition.y - borderWidth); this.element.style.width = newElementPosition.width + borderWidth * 2 + "px"; this.element.style.height = newElementPosition.height + borderWidth * 2 + "px"; } } WebInspector.PopoverHelper = function(panelElement, getAnchor, showPopup, showOnClick, onHide) { this._panelElement = panelElement; this._getAnchor = getAnchor; this._showPopup = showPopup; this._showOnClick = showOnClick; this._onHide = onHide; panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false); panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false); this.setTimeout(1000); } WebInspector.PopoverHelper.prototype = { setTimeout: function(timeout) { this._timeout = timeout; }, _mouseDown: function(event) { this._killHidePopupTimer(); this._handleMouseAction(event, true); }, _mouseMove: function(event) { // Pretend that nothing has happened. if (this._hoverElement === event.target || (this._hoverElement && this._hoverElement.isAncestor(event.target))) return; // User has 500ms (this._timeout / 2) to reach the popup. if (this._popup && !this._hidePopupTimer) { var self = this; function doHide() { self._hidePopup(); delete self._hidePopupTimer; } this._hidePopupTimer = setTimeout(doHide, this._timeout / 2); } this._handleMouseAction(event); }, _handleMouseAction: function(event, isMouseDown) { this._resetHoverTimer(); this._hoverElement = this._getAnchor(event.target); if (!this._hoverElement) return; const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.6 : this._timeout); this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay); }, _resetHoverTimer: function() { if (this._hoverTimer) { clearTimeout(this._hoverTimer); delete this._hoverTimer; } }, hidePopup: function() { this._resetHoverTimer(); this._hidePopup(); }, _hidePopup: function() { if (!this._popup) return; if (this._onHide) this._onHide(); this._popup.hide(); delete this._popup; }, _mouseHover: function(element) { delete this._hoverTimer; this._popup = this._showPopup(element); if (this._popup) this._popup.contentElement.addEventListener("mousemove", this._killHidePopupTimer.bind(this), true); }, _killHidePopupTimer: function() { if (this._hidePopupTimer) { clearTimeout(this._hidePopupTimer); delete this._hidePopupTimer; // We know that we reached the popup, but we might have moved over other elements. // Discard pending command. this._resetHoverTimer(); } } }