1/* 2 * Copyright (C) 2007, 2008 Apple 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 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29WebInspector.Panel = function(name) 30{ 31 WebInspector.View.call(this); 32 33 this.element.addStyleClass("panel"); 34 this.element.addStyleClass(name); 35 this._panelName = name; 36 37 WebInspector.settings.installApplicationSetting(this._sidebarWidthSettingName(), undefined); 38} 39 40// Should by in sync with style declarations. 41WebInspector.Panel.counterRightMargin = 25; 42 43WebInspector.Panel.prototype = { 44 get toolbarItem() 45 { 46 if (this._toolbarItem) 47 return this._toolbarItem; 48 49 this._toolbarItem = WebInspector.Toolbar.createPanelToolbarItem(this); 50 return this._toolbarItem; 51 }, 52 53 get name() 54 { 55 return this._panelName; 56 }, 57 58 show: function() 59 { 60 WebInspector.View.prototype.show.call(this); 61 62 var statusBarItems = this.statusBarItems; 63 if (statusBarItems) { 64 this._statusBarItemContainer = document.createElement("div"); 65 for (var i = 0; i < statusBarItems.length; ++i) 66 this._statusBarItemContainer.appendChild(statusBarItems[i]); 67 document.getElementById("main-status-bar").appendChild(this._statusBarItemContainer); 68 } 69 70 if ("_toolbarItem" in this) 71 this._toolbarItem.addStyleClass("toggled-on"); 72 73 WebInspector.currentFocusElement = this.defaultFocusedElement; 74 75 this.restoreSidebarWidth(); 76 this._restoreScrollPositions(); 77 WebInspector.extensionServer.notifyPanelShown(this.name); 78 }, 79 80 hide: function() 81 { 82 this._storeScrollPositions(); 83 WebInspector.View.prototype.hide.call(this); 84 85 if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode) 86 this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer); 87 delete this._statusBarItemContainer; 88 if ("_toolbarItem" in this) 89 this._toolbarItem.removeStyleClass("toggled-on"); 90 WebInspector.extensionServer.notifyPanelHidden(this.name); 91 }, 92 93 reset: function() 94 { 95 this.searchCanceled(); 96 WebInspector.resetFocusElement(); 97 }, 98 99 get defaultFocusedElement() 100 { 101 return this.sidebarTreeElement || this.element; 102 }, 103 104 attach: function() 105 { 106 if (!this.element.parentNode) 107 document.getElementById("main-panels").appendChild(this.element); 108 }, 109 110 searchCanceled: function() 111 { 112 if (this._searchResults) { 113 for (var i = 0; i < this._searchResults.length; ++i) { 114 var view = this._searchResults[i]; 115 if (view.searchCanceled) 116 view.searchCanceled(); 117 delete view.currentQuery; 118 } 119 } 120 121 WebInspector.searchController.updateSearchMatchesCount(0, this); 122 123 if (this._currentSearchChunkIntervalIdentifier) { 124 clearInterval(this._currentSearchChunkIntervalIdentifier); 125 delete this._currentSearchChunkIntervalIdentifier; 126 } 127 128 this._totalSearchMatches = 0; 129 this._currentSearchResultIndex = 0; 130 this._searchResults = []; 131 }, 132 133 performSearch: function(query) 134 { 135 // Call searchCanceled since it will reset everything we need before doing a new search. 136 this.searchCanceled(true); 137 138 var searchableViews = this.searchableViews; 139 if (!searchableViews || !searchableViews.length) 140 return; 141 142 var parentElement = this.viewsContainerElement; 143 var visibleView = this.visibleView; 144 var sortFuction = this.searchResultsSortFunction; 145 146 var matchesCountUpdateTimeout = null; 147 148 function updateMatchesCount() 149 { 150 WebInspector.searchController.updateSearchMatchesCount(this._totalSearchMatches, this); 151 matchesCountUpdateTimeout = null; 152 } 153 154 function updateMatchesCountSoon() 155 { 156 if (matchesCountUpdateTimeout) 157 return; 158 // Update the matches count every half-second so it doesn't feel twitchy. 159 matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500); 160 } 161 162 function finishedCallback(view, searchMatches) 163 { 164 if (!searchMatches) 165 return; 166 167 this._totalSearchMatches += searchMatches; 168 this._searchResults.push(view); 169 170 if (sortFuction) 171 this._searchResults.sort(sortFuction); 172 173 if (this.searchMatchFound) 174 this.searchMatchFound(view, searchMatches); 175 176 updateMatchesCountSoon.call(this); 177 178 if (view === visibleView) 179 view.jumpToFirstSearchResult(); 180 } 181 182 var i = 0; 183 var panel = this; 184 var boundFinishedCallback = finishedCallback.bind(this); 185 var chunkIntervalIdentifier = null; 186 187 // Split up the work into chunks so we don't block the 188 // UI thread while processing. 189 190 function processChunk() 191 { 192 var view = searchableViews[i]; 193 194 if (++i >= searchableViews.length) { 195 if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier) 196 delete panel._currentSearchChunkIntervalIdentifier; 197 clearInterval(chunkIntervalIdentifier); 198 } 199 200 if (!view) 201 return; 202 203 if (view.element.parentNode !== parentElement && view.element.parentNode && parentElement) 204 view.detach(); 205 206 view.currentQuery = query; 207 view.performSearch(query, boundFinishedCallback); 208 } 209 210 processChunk(); 211 212 chunkIntervalIdentifier = setInterval(processChunk, 25); 213 this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; 214 }, 215 216 jumpToNextSearchResult: function() 217 { 218 if (!this.showView || !this._searchResults || !this._searchResults.length) 219 return; 220 221 var showFirstResult = false; 222 223 this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); 224 if (this._currentSearchResultIndex === -1) { 225 this._currentSearchResultIndex = 0; 226 showFirstResult = true; 227 } 228 229 var currentView = this._searchResults[this._currentSearchResultIndex]; 230 231 if (currentView.showingLastSearchResult()) { 232 if (++this._currentSearchResultIndex >= this._searchResults.length) 233 this._currentSearchResultIndex = 0; 234 currentView = this._searchResults[this._currentSearchResultIndex]; 235 showFirstResult = true; 236 } 237 238 if (currentView !== this.visibleView) { 239 this.showView(currentView); 240 WebInspector.searchController.focusSearchField(); 241 } 242 243 if (showFirstResult) 244 currentView.jumpToFirstSearchResult(); 245 else 246 currentView.jumpToNextSearchResult(); 247 }, 248 249 jumpToPreviousSearchResult: function() 250 { 251 if (!this.showView || !this._searchResults || !this._searchResults.length) 252 return; 253 254 var showLastResult = false; 255 256 this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); 257 if (this._currentSearchResultIndex === -1) { 258 this._currentSearchResultIndex = 0; 259 showLastResult = true; 260 } 261 262 var currentView = this._searchResults[this._currentSearchResultIndex]; 263 264 if (currentView.showingFirstSearchResult()) { 265 if (--this._currentSearchResultIndex < 0) 266 this._currentSearchResultIndex = (this._searchResults.length - 1); 267 currentView = this._searchResults[this._currentSearchResultIndex]; 268 showLastResult = true; 269 } 270 271 if (currentView !== this.visibleView) { 272 this.showView(currentView); 273 WebInspector.searchController.focusSearchField(); 274 } 275 276 if (showLastResult) 277 currentView.jumpToLastSearchResult(); 278 else 279 currentView.jumpToPreviousSearchResult(); 280 }, 281 282 createSidebar: function(parentElement, resizerParentElement) 283 { 284 if (this.sidebarElement) 285 return; 286 287 if (!parentElement) 288 parentElement = this.element; 289 290 if (!resizerParentElement) 291 resizerParentElement = parentElement; 292 293 this.sidebarElement = document.createElement("div"); 294 this.sidebarElement.className = "sidebar"; 295 parentElement.appendChild(this.sidebarElement); 296 297 this.sidebarResizeElement = document.createElement("div"); 298 this.sidebarResizeElement.className = "sidebar-resizer-vertical"; 299 this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); 300 resizerParentElement.appendChild(this.sidebarResizeElement); 301 302 this.sidebarTreeElement = document.createElement("ol"); 303 this.sidebarTreeElement.className = "sidebar-tree"; 304 this.sidebarElement.appendChild(this.sidebarTreeElement); 305 306 this.sidebarTree = new TreeOutline(this.sidebarTreeElement); 307 this.sidebarTree.panel = this; 308 }, 309 310 _sidebarWidthSettingName: function() 311 { 312 return this._panelName + "SidebarWidth"; 313 }, 314 315 _startSidebarDragging: function(event) 316 { 317 WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); 318 }, 319 320 _sidebarDragging: function(event) 321 { 322 this.updateSidebarWidth(event.pageX); 323 324 event.preventDefault(); 325 }, 326 327 _endSidebarDragging: function(event) 328 { 329 WebInspector.elementDragEnd(event); 330 this.saveSidebarWidth(); 331 }, 332 333 updateSidebarWidth: function(width) 334 { 335 if (!this.sidebarElement) 336 return; 337 338 if (this.sidebarElement.offsetWidth <= 0) { 339 // The stylesheet hasn't loaded yet or the window is closed, 340 // so we can't calculate what is need. Return early. 341 return; 342 } 343 344 if (!("_currentSidebarWidth" in this)) 345 this._currentSidebarWidth = this.sidebarElement.offsetWidth; 346 347 if (typeof width === "undefined") 348 width = this._currentSidebarWidth; 349 350 width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); 351 352 this._currentSidebarWidth = width; 353 this.setSidebarWidth(width); 354 355 this.updateMainViewWidth(width); 356 }, 357 358 setSidebarWidth: function(width) 359 { 360 this.sidebarElement.style.width = width + "px"; 361 this.sidebarResizeElement.style.left = (width - 3) + "px"; 362 }, 363 364 restoreSidebarWidth: function() 365 { 366 var sidebarWidth = WebInspector.settings[this._sidebarWidthSettingName()]; 367 this.updateSidebarWidth(sidebarWidth); 368 }, 369 370 saveSidebarWidth: function() 371 { 372 if (!this.sidebarElement) 373 return; 374 WebInspector.settings[this._sidebarWidthSettingName()] = this.sidebarElement.offsetWidth; 375 }, 376 377 updateMainViewWidth: function(width) 378 { 379 // Should be implemented by ancestors. 380 }, 381 382 resize: function() 383 { 384 var visibleView = this.visibleView; 385 if (visibleView && "resize" in visibleView) 386 visibleView.resize(); 387 }, 388 389 canShowAnchorLocation: function(anchor) 390 { 391 return false; 392 }, 393 394 showAnchorLocation: function(anchor) 395 { 396 return false; 397 }, 398 399 elementsToRestoreScrollPositionsFor: function() 400 { 401 return []; 402 }, 403 404 _storeScrollPositions: function() 405 { 406 var elements = this.elementsToRestoreScrollPositionsFor(); 407 for (var i = 0; i < elements.length; ++i) { 408 var container = elements[i]; 409 container._scrollTop = container.scrollTop; 410 } 411 }, 412 413 _restoreScrollPositions: function() 414 { 415 var elements = this.elementsToRestoreScrollPositionsFor(); 416 for (var i = 0; i < elements.length; ++i) { 417 var container = elements[i]; 418 if (container._scrollTop) 419 container.scrollTop = container._scrollTop; 420 } 421 } 422} 423 424WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype; 425