1/*
2 * Copyright (C) 2012 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 * @constructor
33 * @param {!Element} relativeToElement
34 * @param {!WebInspector.DialogDelegate} delegate
35 */
36WebInspector.Dialog = function(relativeToElement, delegate)
37{
38    this._delegate = delegate;
39    this._relativeToElement = relativeToElement;
40
41    this._glassPane = new WebInspector.GlassPane();
42    WebInspector.GlassPane.DefaultFocusedViewStack.push(this);
43
44    // Install glass pane capturing events.
45    this._glassPane.element.tabIndex = 0;
46    this._glassPane.element.addEventListener("focus", this._onGlassPaneFocus.bind(this), false);
47
48    this._element = this._glassPane.element.createChild("div");
49    this._element.tabIndex = 0;
50    this._element.addEventListener("focus", this._onFocus.bind(this), false);
51    this._element.addEventListener("keydown", this._onKeyDown.bind(this), false);
52    this._closeKeys = [
53        WebInspector.KeyboardShortcut.Keys.Enter.code,
54        WebInspector.KeyboardShortcut.Keys.Esc.code,
55    ];
56
57    delegate.show(this._element);
58
59    this._position();
60    this._delegate.focus();
61}
62
63/**
64 * @return {?WebInspector.Dialog}
65 */
66WebInspector.Dialog.currentInstance = function()
67{
68    return WebInspector.Dialog._instance;
69}
70
71/**
72 * @param {!Element} relativeToElement
73 * @param {!WebInspector.DialogDelegate} delegate
74 */
75WebInspector.Dialog.show = function(relativeToElement, delegate)
76{
77    if (WebInspector.Dialog._instance)
78        return;
79    WebInspector.Dialog._instance = new WebInspector.Dialog(relativeToElement, delegate);
80}
81
82WebInspector.Dialog.hide = function()
83{
84    if (!WebInspector.Dialog._instance)
85        return;
86    WebInspector.Dialog._instance._hide();
87}
88
89WebInspector.Dialog.prototype = {
90    focus: function()
91    {
92        this._element.focus();
93    },
94
95    _hide: function()
96    {
97        if (this._isHiding)
98            return;
99        this._isHiding = true;
100
101        this._delegate.willHide();
102
103        delete WebInspector.Dialog._instance;
104        WebInspector.GlassPane.DefaultFocusedViewStack.pop();
105        this._glassPane.dispose();
106    },
107
108    _onGlassPaneFocus: function(event)
109    {
110        this._hide();
111    },
112
113    _onFocus: function(event)
114    {
115        this._delegate.focus();
116    },
117
118    _position: function()
119    {
120        this._delegate.position(this._element, this._relativeToElement);
121    },
122
123    _onKeyDown: function(event)
124    {
125        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
126            event.preventDefault();
127            return;
128        }
129
130        if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Enter.code)
131            this._delegate.onEnter(event);
132
133        if (!event.handled && this._closeKeys.indexOf(event.keyCode) >= 0) {
134            this._hide();
135            event.consume(true);
136        }
137    }
138};
139
140/**
141 * @constructor
142 * @extends {WebInspector.Object}
143 */
144WebInspector.DialogDelegate = function()
145{
146    /** @type {!Element} */
147    this.element;
148}
149
150WebInspector.DialogDelegate.prototype = {
151    /**
152     * @param {!Element} element
153     */
154    show: function(element)
155    {
156        element.appendChild(this.element);
157        this.element.classList.add("dialog-contents");
158        element.classList.add("dialog", "toolbar-colors");
159    },
160
161    /**
162     * @param {!Element} element
163     * @param {!Element} relativeToElement
164     */
165    position: function(element, relativeToElement)
166    {
167        var container = WebInspector.Dialog._modalHostView.element;
168        var box = relativeToElement.boxInWindow(window).relativeToElement(container);
169
170        var positionX = box.x + (relativeToElement.offsetWidth - element.offsetWidth) / 2;
171        positionX = Number.constrain(positionX, 0, container.offsetWidth - element.offsetWidth);
172
173        var positionY = box.y + (relativeToElement.offsetHeight - element.offsetHeight) / 2;
174        positionY = Number.constrain(positionY, 0, container.offsetHeight - element.offsetHeight);
175
176        element.style.position = "absolute";
177        element.positionAt(positionX, positionY, container);
178    },
179
180    focus: function() { },
181
182    onEnter: function(event) { },
183
184    willHide: function() { },
185
186    __proto__: WebInspector.Object.prototype
187}
188
189/** @type {?WebInspector.View} */
190WebInspector.Dialog._modalHostView = null;
191
192/**
193 * @param {!WebInspector.View} view
194 */
195WebInspector.Dialog.setModalHostView = function(view)
196{
197    WebInspector.Dialog._modalHostView = view;
198};
199
200/**
201 * FIXME: make utility method in Dialog, so clients use it instead of this getter.
202 * Method should be like Dialog.showModalElement(position params, reposition callback).
203 * @return {?WebInspector.View}
204 */
205WebInspector.Dialog.modalHostView = function()
206{
207    return WebInspector.Dialog._modalHostView;
208};
209
210WebInspector.Dialog.modalHostRepositioned = function()
211{
212    if (WebInspector.Dialog._instance)
213        WebInspector.Dialog._instance._position();
214};
215
216