2 * Copyright (C) 2009 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @param {WebInspector.PopoverHelper=} popoverHelper
35 WebInspector.Popover = function(popoverHelper)
37 this.element = document.createElement("div");
38 this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll";
40 this._popupArrowElement = document.createElement("div");
41 this._popupArrowElement.className = "arrow";
42 this.element.appendChild(this._popupArrowElement);
44 this._contentDiv = document.createElement("div");
45 this._contentDiv.className = "content";
46 this._visible = false;
47 this._popoverHelper = popoverHelper;
50 WebInspector.Popover.prototype = {
51 show: function(contentElement, anchor, preferredWidth, preferredHeight)
55 this.contentElement = contentElement;
57 // This should not happen, but we hide previous popup to be on the safe side.
58 if (WebInspector.Popover._popoverElement)
59 document.body.removeChild(WebInspector.Popover._popoverElement);
60 WebInspector.Popover._popoverElement = this.element;
62 // Temporarily attach in order to measure preferred dimensions.
63 this.contentElement.positionAt(0, 0);
64 document.body.appendChild(this.contentElement);
65 preferredWidth = preferredWidth || this.contentElement.offsetWidth;
66 preferredHeight = preferredHeight || this.contentElement.offsetHeight;
68 this._contentDiv.appendChild(this.contentElement);
69 this.element.appendChild(this._contentDiv);
70 document.body.appendChild(this.element);
71 this._positionElement(anchor, preferredWidth, preferredHeight);
73 if (this._popoverHelper)
74 contentElement.addEventListener("mousemove", this._popoverHelper._killHidePopoverTimer.bind(this._popoverHelper), true);
79 if (WebInspector.Popover._popoverElement) {
80 delete WebInspector.Popover._popoverElement;
81 document.body.removeChild(this.element);
83 this._visible = false;
93 return this._disposed;
100 this._disposed = true;
103 _positionElement: function(anchorElement, preferredWidth, preferredHeight)
105 const borderWidth = 25;
106 const scrollerWidth = 11;
107 const arrowHeight = 15;
108 const arrowOffset = 10;
109 const borderRadius = 10;
111 // Skinny tooltips are not pretty, their arrow location is not nice.
112 preferredWidth = Math.max(preferredWidth, 50);
113 const totalWidth = window.innerWidth;
114 const totalHeight = window.innerHeight;
116 var anchorBox = anchorElement.boxInWindow(window);
117 var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerWidth, height: preferredHeight };
119 var verticalAlignment;
120 var roomAbove = anchorBox.y;
121 var roomBelow = totalHeight - anchorBox.y - anchorBox.height;
123 if (roomAbove > roomBelow) {
124 // Positioning above the anchor.
125 if (anchorBox.y > newElementPosition.height + arrowHeight + borderRadius)
126 newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHeight;
128 newElementPosition.y = borderRadius * 2;
129 newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight;
131 verticalAlignment = "bottom";
133 // Positioning below the anchor.
134 newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight;
135 if (newElementPosition.y + newElementPosition.height + arrowHeight - borderWidth >= totalHeight)
136 newElementPosition.height = totalHeight - anchorBox.y - anchorBox.height - borderRadius * 2 - arrowHeight;
138 verticalAlignment = "top";
141 var horizontalAlignment;
142 if (anchorBox.x + newElementPosition.width < totalWidth) {
143 newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius - arrowOffset);
144 horizontalAlignment = "left";
145 } else if (newElementPosition.width + borderRadius * 2 < totalWidth) {
146 newElementPosition.x = totalWidth - newElementPosition.width - borderRadius;
147 horizontalAlignment = "right";
148 // Position arrow accurately.
149 var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.width - borderRadius - arrowOffset);
150 arrowRightPosition += anchorBox.width / 2;
151 this._popupArrowElement.style.right = arrowRightPosition + "px";
153 newElementPosition.x = borderRadius;
154 newElementPosition.width = totalWidth - borderRadius * 2;
155 newElementPosition.height += scrollerWidth;
156 horizontalAlignment = "left";
157 if (verticalAlignment === "bottom")
158 newElementPosition.y -= scrollerWidth;
159 // Position arrow accurately.
160 this._popupArrowElement.style.left = Math.max(0, anchorBox.x - borderRadius * 2 - arrowOffset) + "px";
161 this._popupArrowElement.style.left += anchorBox.width / 2;
164 this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll " + verticalAlignment + "-" + horizontalAlignment + "-arrow";
165 this.element.positionAt(newElementPosition.x - borderWidth, newElementPosition.y - borderWidth);
166 this.element.style.width = newElementPosition.width + borderWidth * 2 + "px";
167 this.element.style.height = newElementPosition.height + borderWidth * 2 + "px";
173 * @param {function()=} onHide
174 * @param {boolean=} disableOnClick
176 WebInspector.PopoverHelper = function(panelElement, getAnchor, showPopover, onHide, disableOnClick)
178 this._panelElement = panelElement;
179 this._getAnchor = getAnchor;
180 this._showPopover = showPopover;
181 this._onHide = onHide;
182 this._disableOnClick = !!disableOnClick;
183 panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false);
184 panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false);
185 this.setTimeout(1000);
188 WebInspector.PopoverHelper.prototype = {
189 setTimeout: function(timeout)
191 this._timeout = timeout;
194 _mouseDown: function(event)
196 if (this._disableOnClick)
199 this._killHidePopoverTimer();
200 this._handleMouseAction(event, true);
204 _mouseMove: function(event)
206 // Pretend that nothing has happened.
207 if (this._hoverElement === event.target || (this._hoverElement && this._hoverElement.isAncestor(event.target)))
210 // User has 500ms (this._timeout / 2) to reach the popup.
211 if (this._popover && !this._hidePopoverTimer) {
216 delete self._hidePopoverTimer;
218 this._hidePopoverTimer = setTimeout(doHide, this._timeout / 2);
221 this._handleMouseAction(event, false);
224 _handleMouseAction: function(event, isMouseDown)
226 this._resetHoverTimer();
227 if (event.which && this._disableOnClick)
229 this._hoverElement = this._getAnchor(event.target);
230 if (!this._hoverElement)
232 const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.6 : this._timeout);
233 this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay);
236 _resetHoverTimer: function()
238 if (this._hoverTimer) {
239 clearTimeout(this._hoverTimer);
240 delete this._hoverTimer;
244 hidePopover: function()
246 this._resetHoverTimer();
250 _hidePopover: function()
258 this._popover.dispose();
259 delete this._popover;
262 _mouseHover: function(element)
264 delete this._hoverTimer;
267 this._popover = new WebInspector.Popover(this);
268 this._showPopover(element, this._popover);
271 _killHidePopoverTimer: function()
273 if (this._hidePopoverTimer) {
274 clearTimeout(this._hidePopoverTimer);
275 delete this._hidePopoverTimer;
277 // We know that we reached the popup, but we might have moved over other elements.
278 // Discard pending command.
279 this._resetHoverTimer();