• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2009 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 are
6 * met:
7 *
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
13 * distribution.
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.
17 *
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.
29 */
30
31/**
32 * This class provides a popup that can be shown relative to an anchor element
33 * or at an arbitrary absolute position.
34 * Points are Objects: {x: xValue, y: yValue}.
35 * Rectangles are Objects: {x: xValue, y: yValue, width: widthValue, height: heightValue}.
36 *
37 * element is an optional unparented visible element (style.display != "none" AND style.visibility != "hidden").
38 * If the element is absent/undefined, it must have been set with the element(x) setter before the show() method invocation.
39 */
40WebInspector.Popup = function(element)
41{
42    if (element)
43        this.element = element;
44    this._keyHandler = this._keyEventHandler.bind(this);
45    this._mouseDownHandler = this._mouseDownEventHandler.bind(this);
46    this._visible = false;
47    this._autoHide = true;
48}
49
50WebInspector.Popup.prototype = {
51    show: function()
52    {
53        if (this.visible)
54            return;
55        var ownerDocument = this._contentElement.ownerDocument;
56        if (!ownerDocument)
57            return;
58
59        this._glasspaneElement = ownerDocument.createElement("div");
60        this._glasspaneElement.className = "popup-glasspane";
61        ownerDocument.body.appendChild(this._glasspaneElement);
62
63        this._contentElement.positionAt(0, 0);
64        this._contentElement.removeStyleClass("hidden");
65        ownerDocument.body.appendChild(this._contentElement);
66
67        this.positionElement();
68        this._visible = true;
69        ownerDocument.addEventListener("keydown", this._keyHandler, false);
70        ownerDocument.addEventListener("mousedown", this._mouseDownHandler, false);
71    },
72
73    hide: function()
74    {
75        if (this.visible) {
76            this._visible = false;
77            this._contentElement.ownerDocument.removeEventListener("keydown", this._keyHandler, false);
78            this._contentElement.ownerDocument.removeEventListener("mousedown", this._mouseDownHandler, false);
79            this._glasspaneElement.parentElement.removeChild(this._glasspaneElement);
80            this._contentElement.parentElement.removeChild(this._contentElement);
81        }
82    },
83
84    get visible()
85    {
86        return this._visible;
87    },
88
89    set element(x)
90    {
91        this._checkNotVisible();
92        this._contentElement = x;
93        this._contentElement.addStyleClass("hidden");
94    },
95
96    get element()
97    {
98        return this._contentElement;
99    },
100
101    positionElement: function()
102    {
103        var element = this._contentElement;
104        var anchorElement = this._anchorElement;
105
106        var targetDocument = element.ownerDocument;
107        var targetDocumentBody = targetDocument.body;
108        var targetDocumentElement = targetDocument.documentElement;
109        var clippingBox = {x: 0, y: 0, width: targetDocumentElement.clientWidth, height: targetDocumentElement.clientHeight};
110        var parentElement = element.offsetParent || element.parentElement;
111
112        var anchorPosition = {x: anchorElement.totalOffsetLeft, y: anchorElement.totalOffsetTop};
113
114        // FIXME(apavlov@chromium.org): Translate anchorPosition to the element.ownerDocument frame when https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed.
115        var anchorBox = {x: anchorPosition.x, y: anchorPosition.y, width: anchorElement.offsetWidth, height: anchorElement.offsetHeight};
116        var elementBox = {x: element.totalOffsetLeft, y: element.totalOffsetTop, width: element.offsetWidth, height: element.offsetHeight};
117        var newElementPosition = {x: 0, y: 0};
118
119        if (anchorBox.y - elementBox.height >= clippingBox.y)
120            newElementPosition.y = anchorBox.y - elementBox.height;
121        else
122            newElementPosition.y = Math.min(anchorBox.y + anchorBox.height, Math.max(clippingBox.y, clippingBox.y + clippingBox.height - elementBox.height));
123
124        if (anchorBox.x + elementBox.height <= clippingBox.x + clippingBox.height)
125            newElementPosition.x = anchorBox.x;
126        else
127            newElementPosition.x = Math.max(clippingBox.x, clippingBox.x + clippingBox.height - elementBox.height);
128        element.positionAt(newElementPosition.x, newElementPosition.y);
129    },
130
131    set anchor(x)
132    {
133        this._checkNotVisible();
134        this._anchorElement = x;
135    },
136
137    get anchor()
138    {
139        return this._anchorElement;
140    },
141
142    set autoHide(x)
143    {
144        this._autoHide = x;
145    },
146
147    _checkNotVisible: function()
148    {
149        if (this.visible)
150            throw new Error("The popup must not be visible.");
151    },
152
153    _keyEventHandler: function(event)
154    {
155        // Escape hides the popup.
156        if (event.keyIdentifier == "U+001B") {
157            this.hide();
158            event.preventDefault();
159            event.handled = true;
160        }
161    },
162
163    _mouseDownEventHandler: function(event)
164    {
165        if (this._autoHide && event.originalTarget === this._glasspaneElement)
166            this.hide();
167    }
168}
169