1/*
2 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/**
31 * @constructor
32 * @implements {WebInspector.ViewFactory}
33 * @param {!WebInspector.InspectorView} inspectorView
34 */
35WebInspector.Drawer = function(inspectorView)
36{
37    this._inspectorView = inspectorView;
38
39    this.element = this._inspectorView.devtoolsElement().createChild("div", "drawer");
40    this.element.style.flexBasis = 0;
41
42    this._savedHeight = 200; // Default.
43
44    this._drawerContentsElement = this.element.createChild("div");
45    this._drawerContentsElement.id = "drawer-contents";
46
47    this._toggleDrawerButton = new WebInspector.StatusBarButton(WebInspector.UIString("Show drawer."), "console-status-bar-item");
48    this._toggleDrawerButton.addEventListener("click", this.toggle, this);
49
50    this._viewFactories = [];
51    this._tabbedPane = new WebInspector.TabbedPane();
52    this._tabbedPane.closeableTabs = false;
53    this._tabbedPane.markAsRoot();
54
55    // Register console early for it to be the first in the list.
56    this.registerView("console", WebInspector.UIString("Console"), this);
57
58    this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._updateTabStrip, this);
59    this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
60    WebInspector.installDragHandle(this._tabbedPane.headerElement(), this._startStatusBarDragging.bind(this), this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), "row-resize");
61    this._tabbedPane.element.createChild("div", "drawer-resizer");
62    this._showDrawerOnLoadSetting = WebInspector.settings.createSetting("WebInspector.Drawer.showOnLoad", false);
63    this._lastSelectedViewSetting = WebInspector.settings.createSetting("WebInspector.Drawer.lastSelectedView", "console");
64}
65
66WebInspector.Drawer.prototype = {
67    /**
68     * @return {!Element}
69     */
70    toggleButtonElement: function()
71    {
72        return this._toggleDrawerButton.element;
73    },
74
75    _constrainHeight: function(height)
76    {
77        return Number.constrain(height, Preferences.minConsoleHeight, this._inspectorView.devtoolsElement().offsetHeight - Preferences.minConsoleHeight);
78    },
79
80    isHiding: function()
81    {
82        return this._isHiding;
83    },
84
85    /**
86     * @param {string} tabId
87     * @param {string} title
88     * @param {!WebInspector.View} view
89     */
90    _addView: function(tabId, title, view)
91    {
92        if (!this._tabbedPane.hasTab(tabId)) {
93            this._tabbedPane.appendTab(tabId, title, view,  undefined, false);
94        } else {
95            this._tabbedPane.changeTabTitle(tabId, title);
96            this._tabbedPane.changeTabView(tabId, view);
97        }
98    },
99
100    /**
101     * @param {string} id
102     * @param {string} title
103     * @param {!WebInspector.ViewFactory} factory
104     */
105    registerView: function(id, title, factory)
106    {
107        if (this._tabbedPane.hasTab(id))
108            this._tabbedPane.closeTab(id);
109        this._viewFactories[id] = factory;
110        this._tabbedPane.appendTab(id, title, new WebInspector.View());
111    },
112
113    /**
114     * @param {string} id
115     */
116    unregisterView: function(id)
117    {
118        if (this._tabbedPane.hasTab(id))
119            this._tabbedPane.closeTab(id);
120        delete this._viewFactories[id];
121    },
122
123    /**
124     * @param {string=} id
125     * @return {?WebInspector.View}
126     */
127    createView: function(id)
128    {
129        return WebInspector.panel("console").createView(id);
130    },
131
132    /**
133     * @param {string} id
134     */
135    closeView: function(id)
136    {
137        this._tabbedPane.closeTab(id);
138    },
139
140    /**
141     * @param {string} id
142     * @param {boolean=} immediately
143     */
144    showView: function(id, immediately)
145    {
146        if (!this._toggleDrawerButton.enabled())
147            return;
148        if (this._viewFactories[id])
149            this._tabbedPane.changeTabView(id, this._viewFactories[id].createView(id));
150        this._innerShow(immediately);
151        this._tabbedPane.selectTab(id, true);
152        this._updateTabStrip();
153    },
154
155    /**
156     * @param {string} id
157     * @param {string} title
158     * @param {!WebInspector.View} view
159     */
160    showCloseableView: function(id, title, view)
161    {
162        if (!this._toggleDrawerButton.enabled())
163            return;
164        if (!this._tabbedPane.hasTab(id)) {
165            this._tabbedPane.appendTab(id, title, view, undefined, false, true);
166        } else {
167            this._tabbedPane.changeTabView(id, view);
168            this._tabbedPane.changeTabTitle(id, title);
169        }
170        this._innerShow();
171        this._tabbedPane.selectTab(id, true);
172        this._updateTabStrip();
173    },
174
175    /**
176     * @param {boolean=} immediately
177     */
178    show: function(immediately)
179    {
180        this.showView(this._tabbedPane.selectedTabId, immediately);
181    },
182
183    showOnLoadIfNecessary: function()
184    {
185        if (this._showDrawerOnLoadSetting.get())
186            this.showView(this._lastSelectedViewSetting.get(), true);
187    },
188
189    /**
190     * @param {boolean=} immediately
191     */
192    _innerShow: function(immediately)
193    {
194        this._immediatelyFinishAnimation();
195
196        if (this._toggleDrawerButton.toggled)
197            return;
198        this._showDrawerOnLoadSetting.set(true);
199        this._toggleDrawerButton.toggled = true;
200        this._toggleDrawerButton.title = WebInspector.UIString("Hide drawer.");
201
202        document.body.classList.add("drawer-visible");
203        this._tabbedPane.show(this._drawerContentsElement);
204
205        var height = this._constrainHeight(this._savedHeight);
206        var animations = [
207            {element: this.element, start: {"flex-basis": 23}, end: {"flex-basis": height}},
208        ];
209
210        /**
211         * @param {boolean} finished
212         * @this {WebInspector.Drawer}
213         */
214        function animationCallback(finished)
215        {
216            if (this._inspectorView.currentPanel())
217                this._inspectorView.currentPanel().doResize();
218            if (!finished)
219                return;
220            this._updateTabStrip();
221            if (this._visibleView()) {
222                // Get console content back
223                this._tabbedPane.changeTabView(this._tabbedPane.selectedTabId, this._visibleView());
224                if (this._visibleView().afterShow)
225                    this._visibleView().afterShow();
226            }
227            delete this._currentAnimation;
228        }
229
230        this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(immediately), animationCallback.bind(this));
231
232        if (immediately)
233            this._currentAnimation.forceComplete();
234    },
235
236    /**
237     * @param {boolean=} immediately
238     */
239    hide: function(immediately)
240    {
241        this._immediatelyFinishAnimation();
242
243        if (!this._toggleDrawerButton.toggled)
244            return;
245        this._showDrawerOnLoadSetting.set(false);
246        this._toggleDrawerButton.toggled = false;
247        this._toggleDrawerButton.title = WebInspector.UIString("Show console.");
248
249        this._isHiding = true;
250        this._savedHeight = this.element.offsetHeight;
251
252        WebInspector.restoreFocusFromElement(this.element);
253
254        // Temporarily set properties and classes to mimic the post-animation values so panels
255        // like Elements in their updateStatusBarItems call will size things to fit the final location.
256        document.body.classList.remove("drawer-visible");
257        this._inspectorView.currentPanel().statusBarResized();
258        document.body.classList.add("drawer-visible");
259
260        var animations = [
261            {element: this.element, start: {"flex-basis": this.element.offsetHeight }, end: {"flex-basis": 23}},
262        ];
263
264        /**
265         * @param {boolean} finished
266         * @this {WebInspector.Drawer}
267         */
268        function animationCallback(finished)
269        {
270            var panel = this._inspectorView.currentPanel();
271            if (!finished) {
272                panel.doResize();
273                return;
274            }
275            this._tabbedPane.detach();
276            this._drawerContentsElement.removeChildren();
277            document.body.classList.remove("drawer-visible");
278            panel.doResize();
279            delete this._currentAnimation;
280            delete this._isHiding;
281        }
282
283        this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(immediately), animationCallback.bind(this));
284
285        if (immediately)
286            this._currentAnimation.forceComplete();
287    },
288
289    resize: function()
290    {
291        if (!this._toggleDrawerButton.toggled)
292            return;
293
294        this._visibleView().storeScrollPositions();
295        var height = this._constrainHeight(this.element.offsetHeight);
296        this.element.style.flexBasis = height + "px";
297        this._tabbedPane.doResize();
298    },
299
300    _immediatelyFinishAnimation: function()
301    {
302        if (this._currentAnimation)
303            this._currentAnimation.forceComplete();
304    },
305
306    /**
307     * @param {boolean=} immediately
308     * @return {number}
309     */
310    _animationDuration: function(immediately)
311    {
312        return immediately ? 0 : 50;
313    },
314
315    /**
316     * @return {boolean}
317     */
318    _startStatusBarDragging: function(event)
319    {
320        if (!this._toggleDrawerButton.toggled || event.target !== this._tabbedPane.headerElement())
321            return false;
322
323        this._visibleView().storeScrollPositions();
324        this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop();
325        return true;
326    },
327
328    _statusBarDragging: function(event)
329    {
330        var height = window.innerHeight - event.pageY + this._statusBarDragOffset;
331        height = Number.constrain(height, Preferences.minConsoleHeight, this._inspectorView.devtoolsElement().offsetHeight - Preferences.minConsoleHeight);
332
333        this.element.style.flexBasis = height + "px";
334        if (this._inspectorView.currentPanel())
335            this._inspectorView.currentPanel().doResize();
336        this._tabbedPane.doResize();
337
338        event.consume(true);
339    },
340
341    _endStatusBarDragging: function(event)
342    {
343        this._savedHeight = this.element.offsetHeight;
344        delete this._statusBarDragOffset;
345
346        event.consume();
347    },
348
349    /**
350     * @return {!WebInspector.View} view
351     */
352    _visibleView: function()
353    {
354        return this._tabbedPane.visibleView;
355    },
356
357    _updateTabStrip: function()
358    {
359        this._tabbedPane.onResize();
360        this._tabbedPane.doResize();
361    },
362
363    _tabSelected: function()
364    {
365        var tabId = this._tabbedPane.selectedTabId;
366        if (!this._tabbedPane.isTabCloseable(tabId))
367            this._lastSelectedViewSetting.set(tabId);
368        if (this._viewFactories[tabId])
369            this._tabbedPane.changeTabView(tabId, this._viewFactories[tabId].createView(tabId));
370    },
371
372    toggle: function()
373    {
374        if (this._toggleDrawerButton.toggled)
375            this.hide();
376        else
377            this.show();
378    },
379
380    /**
381     * @return {boolean}
382     */
383    visible: function()
384    {
385        return this._toggleDrawerButton.toggled;
386    },
387
388    /**
389     * @return {string}
390     */
391    selectedViewId: function()
392    {
393        return this._tabbedPane.selectedTabId;
394    }
395}
396