4d508adb03ce6df7cbd168cf1f44d28f8844b80e
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / Drawer.js
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  * @param {!WebInspector.InspectorView} inspectorView
33  */
34 WebInspector.Drawer = function(inspectorView)
35 {
36     this._inspectorView = inspectorView;
37
38     this.element = this._inspectorView.devtoolsElement().createChild("div", "drawer");
39     this.element.style.flexBasis = 0;
40
41     this._savedHeight = 200; // Default.
42
43     this._drawerContentsElement = this.element.createChild("div");
44     this._drawerContentsElement.id = "drawer-contents";
45
46     this._toggleDrawerButton = new WebInspector.StatusBarButton(WebInspector.UIString("Show drawer."), "console-status-bar-item");
47     this._toggleDrawerButton.addEventListener("click", this.toggle, this);
48
49     this._tabbedPane = new WebInspector.TabbedPane();
50     this._tabbedPane.closeableTabs = false;
51     this._tabbedPane.markAsRoot();
52     this._tabbedPane.setRetainTabOrder(true, WebInspector.moduleManager.orderComparator(WebInspector.Drawer.ViewFactory, "name", "order"));
53
54     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._updateTabStrip, this);
55     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
56     WebInspector.installDragHandle(this._tabbedPane.headerElement(), this._startStatusBarDragging.bind(this), this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), "ns-resize");
57     this._tabbedPane.element.createChild("div", "drawer-resizer");
58     this._showDrawerOnLoadSetting = WebInspector.settings.createSetting("WebInspector.Drawer.showOnLoad", false);
59     this._lastSelectedViewSetting = WebInspector.settings.createSetting("WebInspector.Drawer.lastSelectedView", "console");
60     this._initialize();
61 }
62
63 WebInspector.Drawer.prototype = {
64     _initialize: function()
65     {
66         this._viewFactories = {};
67         var extensions = WebInspector.moduleManager.extensions(WebInspector.Drawer.ViewFactory);
68
69         for (var i = 0; i < extensions.length; ++i) {
70             var descriptor = extensions[i].descriptor();
71             var id = descriptor["name"];
72             var title = WebInspector.UIString(descriptor["title"]);
73             var settingName = descriptor["setting"];
74             var setting = settingName ? /** @type {!WebInspector.Setting|undefined} */ (WebInspector.settings[settingName]) : null;
75
76             this._viewFactories[id] = extensions[i];
77
78             if (setting) {
79                 setting.addChangeListener(this._toggleSettingBasedView.bind(this, id, title, setting));
80                 if (setting.get())
81                     this._tabbedPane.appendTab(id, title, new WebInspector.View());
82             } else {
83                 this._tabbedPane.appendTab(id, title, new WebInspector.View());
84             }
85         }
86     },
87
88     /**
89      * @param {string} id
90      * @param {string} title
91      * @param {!WebInspector.Setting} setting
92      */
93     _toggleSettingBasedView: function(id, title, setting)
94     {
95         this._tabbedPane.closeTab(id);
96         if (setting.get())
97             this._tabbedPane.appendTab(id, title, new WebInspector.View());
98     },
99
100     /**
101      * @return {!Element}
102      */
103     toggleButtonElement: function()
104     {
105         return this._toggleDrawerButton.element;
106     },
107
108     _constrainHeight: function(height)
109     {
110         return Number.constrain(height, Preferences.minConsoleHeight, this._inspectorView.devtoolsElement().offsetHeight - Preferences.minConsoleHeight);
111     },
112
113     /**
114      * @return {boolean}
115      */
116     isHiding: function()
117     {
118         return this._isHiding;
119     },
120
121     /**
122      * @param {string} tabId
123      * @param {string} title
124      * @param {!WebInspector.View} view
125      */
126     _addView: function(tabId, title, view)
127     {
128         if (!this._tabbedPane.hasTab(tabId)) {
129             this._tabbedPane.appendTab(tabId, title, view,  undefined, false);
130         } else {
131             this._tabbedPane.changeTabTitle(tabId, title);
132             this._tabbedPane.changeTabView(tabId, view);
133         }
134     },
135
136     /**
137      * @param {string} id
138      */
139     closeView: function(id)
140     {
141         this._tabbedPane.closeTab(id);
142     },
143
144     /**
145      * @param {string} id
146      * @param {boolean=} immediately
147      */
148     showView: function(id, immediately)
149     {
150         if (!this._toggleDrawerButton.enabled())
151             return;
152         var viewFactory = this._viewFactory(id);
153         if (viewFactory)
154             this._tabbedPane.changeTabView(id, viewFactory.createView());
155         this._innerShow(immediately);
156         this._tabbedPane.selectTab(id, true);
157         // In case this id is already selected, anyways persist it as the last saved value.
158         this._lastSelectedViewSetting.set(id);
159         this._updateTabStrip();
160     },
161
162     /**
163      * @param {string} id
164      * @param {string} title
165      * @param {!WebInspector.View} view
166      */
167     showCloseableView: function(id, title, view)
168     {
169         if (!this._toggleDrawerButton.enabled())
170             return;
171         if (!this._tabbedPane.hasTab(id)) {
172             this._tabbedPane.appendTab(id, title, view, undefined, false, true);
173         } else {
174             this._tabbedPane.changeTabView(id, view);
175             this._tabbedPane.changeTabTitle(id, title);
176         }
177         this._innerShow();
178         this._tabbedPane.selectTab(id, true);
179         this._updateTabStrip();
180     },
181
182     /**
183      * @param {boolean=} immediately
184      */
185     show: function(immediately)
186     {
187         this.showView(this._lastSelectedViewSetting.get(), immediately);
188     },
189
190     showOnLoadIfNecessary: function()
191     {
192         if (this._showDrawerOnLoadSetting.get())
193             this.showView(this._lastSelectedViewSetting.get(), true);
194     },
195
196     /**
197      * @param {boolean=} immediately
198      */
199     _innerShow: function(immediately)
200     {
201         this._immediatelyFinishAnimation();
202
203         if (this._toggleDrawerButton.toggled)
204             return;
205         this._showDrawerOnLoadSetting.set(true);
206         this._toggleDrawerButton.toggled = true;
207         this._toggleDrawerButton.title = WebInspector.UIString("Hide drawer.");
208
209         document.body.classList.add("drawer-visible");
210         this._tabbedPane.show(this._drawerContentsElement);
211
212         var height = this._constrainHeight(this._savedHeight);
213         // While loading, window may be zero height. Do not corrupt desired drawer height in this case.
214         // FIXME: making Drawer a view and placing it inside SplitView eliminates the need for this.
215         if (window.innerHeight == 0)
216             height = this._savedHeight;
217         var animations = [
218             {element: this.element, start: {"flex-basis": 23}, end: {"flex-basis": height}},
219         ];
220
221         /**
222          * @param {boolean} finished
223          * @this {WebInspector.Drawer}
224          */
225         function animationCallback(finished)
226         {
227             if (this._inspectorView.currentPanel())
228                 this._inspectorView.currentPanel().doResize();
229             if (!finished)
230                 return;
231             this._updateTabStrip();
232             if (this._visibleView()) {
233                 // Get console content back
234                 this._tabbedPane.changeTabView(this._tabbedPane.selectedTabId, this._visibleView());
235                 this._visibleView().focus();
236             }
237             delete this._currentAnimation;
238         }
239
240         this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(immediately), animationCallback.bind(this));
241
242         if (immediately)
243             this._currentAnimation.forceComplete();
244     },
245
246     /**
247      * @param {boolean=} immediately
248      */
249     hide: function(immediately)
250     {
251         this._immediatelyFinishAnimation();
252
253         if (!this._toggleDrawerButton.toggled)
254             return;
255         this._showDrawerOnLoadSetting.set(false);
256         this._toggleDrawerButton.toggled = false;
257         this._toggleDrawerButton.title = WebInspector.UIString("Show console.");
258
259         this._isHiding = true;
260         this._savedHeight = this.element.offsetHeight;
261
262         WebInspector.restoreFocusFromElement(this.element);
263
264         // Temporarily set properties and classes to mimic the post-animation values so panels
265         // like Elements in their updateStatusBarItems call will size things to fit the final location.
266         document.body.classList.remove("drawer-visible");
267         this._inspectorView.currentPanel().statusBarResized();
268         document.body.classList.add("drawer-visible");
269
270         var animations = [
271             {element: this.element, start: {"flex-basis": this.element.offsetHeight }, end: {"flex-basis": 23}},
272         ];
273
274         /**
275          * @param {boolean} finished
276          * @this {WebInspector.Drawer}
277          */
278         function animationCallback(finished)
279         {
280             var panel = this._inspectorView.currentPanel();
281             if (!finished) {
282                 panel.doResize();
283                 return;
284             }
285             this._tabbedPane.detach();
286             this._drawerContentsElement.removeChildren();
287             document.body.classList.remove("drawer-visible");
288             panel.doResize();
289             delete this._currentAnimation;
290             delete this._isHiding;
291         }
292
293         this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(immediately), animationCallback.bind(this));
294
295         if (immediately)
296             this._currentAnimation.forceComplete();
297     },
298
299     resize: function()
300     {
301         if (!this._toggleDrawerButton.toggled)
302             return;
303
304         this._visibleView().storeScrollPositions();
305         var height = this._constrainHeight(this.element.offsetHeight);
306         this.element.style.flexBasis = height + "px";
307         this._tabbedPane.doResize();
308     },
309
310     _immediatelyFinishAnimation: function()
311     {
312         if (this._currentAnimation)
313             this._currentAnimation.forceComplete();
314     },
315
316     /**
317      * @param {boolean=} immediately
318      * @return {number}
319      */
320     _animationDuration: function(immediately)
321     {
322         return immediately ? 0 : 50;
323     },
324
325     /**
326      * @return {boolean}
327      */
328     _startStatusBarDragging: function(event)
329     {
330         if (!this._toggleDrawerButton.toggled || event.target !== this._tabbedPane.headerElement())
331             return false;
332
333         this._visibleView().storeScrollPositions();
334         this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop();
335         return true;
336     },
337
338     _statusBarDragging: function(event)
339     {
340         var height = window.innerHeight - event.pageY + this._statusBarDragOffset;
341         height = Number.constrain(height, Preferences.minConsoleHeight, this._inspectorView.devtoolsElement().offsetHeight - Preferences.minConsoleHeight);
342
343         this.element.style.flexBasis = height + "px";
344         if (this._inspectorView.currentPanel())
345             this._inspectorView.currentPanel().doResize();
346         this._tabbedPane.doResize();
347
348         event.consume(true);
349     },
350
351     _endStatusBarDragging: function(event)
352     {
353         this._savedHeight = this.element.offsetHeight;
354         delete this._statusBarDragOffset;
355
356         event.consume();
357     },
358
359     /**
360      * @return {!WebInspector.View} view
361      */
362     _visibleView: function()
363     {
364         return this._tabbedPane.visibleView;
365     },
366
367     _updateTabStrip: function()
368     {
369         this._tabbedPane.onResize();
370         this._tabbedPane.doResize();
371     },
372
373     /**
374      * @param {!WebInspector.Event} event
375      */
376     _tabSelected: function(event)
377     {
378         var tabId = this._tabbedPane.selectedTabId;
379         if (event.data["isUserGesture"] && !this._tabbedPane.isTabCloseable(tabId))
380             this._lastSelectedViewSetting.set(tabId);
381         var viewFactory = this._viewFactory(tabId);
382         if (viewFactory)
383             this._tabbedPane.changeTabView(tabId, viewFactory.createView());
384     },
385
386     toggle: function()
387     {
388         if (this._toggleDrawerButton.toggled)
389             this.hide();
390         else
391             this.show();
392     },
393
394     /**
395      * @return {boolean}
396      */
397     visible: function()
398     {
399         return this._toggleDrawerButton.toggled;
400     },
401
402     /**
403      * @return {string}
404      */
405     selectedViewId: function()
406     {
407         return this._tabbedPane.selectedTabId;
408     },
409
410     /**
411      * @return {?WebInspector.Drawer.ViewFactory}
412      */
413     _viewFactory: function(id)
414     {
415         return this._viewFactories[id] ? /** @type {!WebInspector.Drawer.ViewFactory} */ (this._viewFactories[id].instance()) : null;
416     }
417 }
418
419 /**
420  * @interface
421  */
422 WebInspector.Drawer.ViewFactory = function()
423 {
424 }
425
426 WebInspector.Drawer.ViewFactory.prototype = {
427     /**
428      * @return {!WebInspector.View}
429      */
430     createView: function() {}
431 }
432
433 /**
434  * @constructor
435  * @implements {WebInspector.Drawer.ViewFactory}
436  * @param {function(new:T)} constructor
437  * @template T
438  */
439 WebInspector.Drawer.SingletonViewFactory = function(constructor)
440 {
441     this._constructor = constructor;
442 }
443
444 WebInspector.Drawer.SingletonViewFactory.prototype = {
445     /**
446      * @return {!WebInspector.View}
447      */
448     createView: function()
449     {
450         if (!this._instance)
451             this._instance = /** @type {!WebInspector.View} */(new this._constructor());
452         return this._instance;
453     }
454 }