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