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() 30{ 31 WebInspector.View.call(this); 32 33 this.element.addStyleClass("panel"); 34} 35 36WebInspector.Panel.prototype = { 37 get toolbarItem() 38 { 39 if (this._toolbarItem) 40 return this._toolbarItem; 41 42 // Sample toolbar item as markup: 43 // <button class="toolbar-item resources toggleable"> 44 // <div class="toolbar-icon"></div> 45 // <div class="toolbar-label">Resources</div> 46 // </button> 47 48 this._toolbarItem = document.createElement("button"); 49 this._toolbarItem.className = "toolbar-item toggleable"; 50 this._toolbarItem.panel = this; 51 52 if ("toolbarItemClass" in this) 53 this._toolbarItem.addStyleClass(this.toolbarItemClass); 54 55 var iconElement = document.createElement("div"); 56 iconElement.className = "toolbar-icon"; 57 this._toolbarItem.appendChild(iconElement); 58 59 if ("toolbarItemLabel" in this) { 60 var labelElement = document.createElement("div"); 61 labelElement.className = "toolbar-label"; 62 labelElement.textContent = this.toolbarItemLabel; 63 this._toolbarItem.appendChild(labelElement); 64 } 65 66 return this._toolbarItem; 67 }, 68 69 show: function() 70 { 71 WebInspector.View.prototype.show.call(this); 72 73 var statusBarItems = this.statusBarItems; 74 if (statusBarItems) { 75 this._statusBarItemContainer = document.createElement("div"); 76 for (var i = 0; i < statusBarItems.length; ++i) 77 this._statusBarItemContainer.appendChild(statusBarItems[i]); 78 document.getElementById("main-status-bar").appendChild(this._statusBarItemContainer); 79 } 80 81 if ("_toolbarItem" in this) 82 this._toolbarItem.addStyleClass("toggled-on"); 83 84 WebInspector.currentFocusElement = this.defaultFocusedElement; 85 86 this.updateSidebarWidth(); 87 }, 88 89 hide: function() 90 { 91 WebInspector.View.prototype.hide.call(this); 92 93 if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode) 94 this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer); 95 delete this._statusBarItemContainer; 96 if ("_toolbarItem" in this) 97 this._toolbarItem.removeStyleClass("toggled-on"); 98 }, 99 100 get defaultFocusedElement() 101 { 102 return this.sidebarTreeElement || this.element; 103 }, 104 105 attach: function() 106 { 107 if (!this.element.parentNode) 108 document.getElementById("main-panels").appendChild(this.element); 109 }, 110 111 searchCanceled: function(startingNewSearch) 112 { 113 if (this._searchResults) { 114 for (var i = 0; i < this._searchResults.length; ++i) { 115 var view = this._searchResults[i]; 116 if (view.searchCanceled) 117 view.searchCanceled(); 118 delete view.currentQuery; 119 } 120 } 121 122 WebInspector.updateSearchMatchesCount(0, this); 123 124 if (this._currentSearchChunkIntervalIdentifier) { 125 clearInterval(this._currentSearchChunkIntervalIdentifier); 126 delete this._currentSearchChunkIntervalIdentifier; 127 } 128 129 this._totalSearchMatches = 0; 130 this._currentSearchResultIndex = 0; 131 this._searchResults = []; 132 }, 133 134 performSearch: function(query) 135 { 136 // Call searchCanceled since it will reset everything we need before doing a new search. 137 this.searchCanceled(true); 138 139 var searchableViews = this.searchableViews; 140 if (!searchableViews || !searchableViews.length) 141 return; 142 143 var parentElement = this.viewsContainerElement; 144 var visibleView = this.visibleView; 145 var sortFuction = this.searchResultsSortFunction; 146 147 var matchesCountUpdateTimeout = null; 148 149 function updateMatchesCount() 150 { 151 WebInspector.updateSearchMatchesCount(this._totalSearchMatches, this); 152 matchesCountUpdateTimeout = null; 153 } 154 155 function updateMatchesCountSoon() 156 { 157 if (matchesCountUpdateTimeout) 158 return; 159 // Update the matches count every half-second so it doesn't feel twitchy. 160 matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500); 161 } 162 163 function finishedCallback(view, searchMatches) 164 { 165 if (!searchMatches) 166 return; 167 168 this._totalSearchMatches += searchMatches; 169 this._searchResults.push(view); 170 171 if (sortFuction) 172 this._searchResults.sort(sortFuction); 173 174 if (this.searchMatchFound) 175 this.searchMatchFound(view, searchMatches); 176 177 updateMatchesCountSoon.call(this); 178 179 if (view === visibleView) 180 view.jumpToFirstSearchResult(); 181 } 182 183 var i = 0; 184 var panel = this; 185 var boundFinishedCallback = finishedCallback.bind(this); 186 var chunkIntervalIdentifier = null; 187 188 // Split up the work into chunks so we don't block the 189 // UI thread while processing. 190 191 function processChunk() 192 { 193 var view = searchableViews[i]; 194 195 if (++i >= searchableViews.length) { 196 if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier) 197 delete panel._currentSearchChunkIntervalIdentifier; 198 clearInterval(chunkIntervalIdentifier); 199 } 200 201 if (!view) 202 return; 203 204 if (view.element.parentNode !== parentElement && view.element.parentNode && parentElement) 205 view.detach(); 206 207 view.currentQuery = query; 208 view.performSearch(query, boundFinishedCallback); 209 } 210 211 processChunk(); 212 213 chunkIntervalIdentifier = setInterval(processChunk, 25); 214 this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; 215 }, 216 217 jumpToNextSearchResult: function() 218 { 219 if (!this.showView || !this._searchResults || !this._searchResults.length) 220 return; 221 222 var showFirstResult = false; 223 224 this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); 225 if (this._currentSearchResultIndex === -1) { 226 this._currentSearchResultIndex = 0; 227 showFirstResult = true; 228 } 229 230 var currentView = this._searchResults[this._currentSearchResultIndex]; 231 232 if (currentView.showingLastSearchResult()) { 233 if (++this._currentSearchResultIndex >= this._searchResults.length) 234 this._currentSearchResultIndex = 0; 235 currentView = this._searchResults[this._currentSearchResultIndex]; 236 showFirstResult = true; 237 } 238 239 if (currentView !== this.visibleView) { 240 currentView = this.visibleView; 241 this._currentSearchResultIndex = 0; 242 showFirstResult = true; 243 } 244 245 if (showFirstResult) 246 currentView.jumpToFirstSearchResult(); 247 else 248 currentView.jumpToNextSearchResult(); 249 }, 250 251 jumpToPreviousSearchResult: function() 252 { 253 if (!this.showView || !this._searchResults || !this._searchResults.length) 254 return; 255 256 var showLastResult = false; 257 258 this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); 259 if (this._currentSearchResultIndex === -1) { 260 this._currentSearchResultIndex = 0; 261 showLastResult = true; 262 } 263 264 var currentView = this._searchResults[this._currentSearchResultIndex]; 265 266 if (currentView.showingFirstSearchResult()) { 267 if (--this._currentSearchResultIndex < 0) 268 this._currentSearchResultIndex = (this._searchResults.length - 1); 269 currentView = this._searchResults[this._currentSearchResultIndex]; 270 showLastResult = true; 271 } 272 273 if (currentView !== this.visibleView) 274 this.showView(currentView); 275 276 if (showLastResult) 277 currentView.jumpToLastSearchResult(); 278 else 279 currentView.jumpToPreviousSearchResult(); 280 }, 281 282 createSidebar: function(parentElement, resizerParentElement) 283 { 284 if (this.hasSidebar) 285 return; 286 287 if (!parentElement) 288 parentElement = this.element; 289 290 if (!resizerParentElement) 291 resizerParentElement = parentElement; 292 293 this.hasSidebar = true; 294 295 this.sidebarElement = document.createElement("div"); 296 this.sidebarElement.className = "sidebar"; 297 parentElement.appendChild(this.sidebarElement); 298 299 this.sidebarResizeElement = document.createElement("div"); 300 this.sidebarResizeElement.className = "sidebar-resizer-vertical"; 301 this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); 302 resizerParentElement.appendChild(this.sidebarResizeElement); 303 304 this.sidebarTreeElement = document.createElement("ol"); 305 this.sidebarTreeElement.className = "sidebar-tree"; 306 this.sidebarElement.appendChild(this.sidebarTreeElement); 307 308 this.sidebarTree = new TreeOutline(this.sidebarTreeElement); 309 }, 310 311 _startSidebarDragging: function(event) 312 { 313 WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); 314 }, 315 316 _sidebarDragging: function(event) 317 { 318 this.updateSidebarWidth(event.pageX); 319 320 event.preventDefault(); 321 }, 322 323 _endSidebarDragging: function(event) 324 { 325 WebInspector.elementDragEnd(event); 326 }, 327 328 updateSidebarWidth: function(width) 329 { 330 if (!this.hasSidebar) 331 return; 332 333 if (this.sidebarElement.offsetWidth <= 0) { 334 // The stylesheet hasn't loaded yet or the window is closed, 335 // so we can't calculate what is need. Return early. 336 return; 337 } 338 339 if (!("_currentSidebarWidth" in this)) 340 this._currentSidebarWidth = this.sidebarElement.offsetWidth; 341 342 if (typeof width === "undefined") 343 width = this._currentSidebarWidth; 344 345 width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); 346 347 this._currentSidebarWidth = width; 348 this.setSidebarWidth(width); 349 350 this.updateMainViewWidth(width); 351 }, 352 353 setSidebarWidth: function(width) 354 { 355 this.sidebarElement.style.width = width + "px"; 356 this.sidebarResizeElement.style.left = (width - 3) + "px"; 357 }, 358 359 updateMainViewWidth: function(width) 360 { 361 // Should be implemented by ancestors. 362 }, 363 364 resize: function() 365 { 366 var visibleView = this.visibleView; 367 if (visibleView && "resize" in visibleView) 368 visibleView.resize(); 369 }, 370 371 canShowSourceLineForURL: function(url) 372 { 373 return false; 374 }, 375 376 showSourceLineForURL: function(url, line) 377 { 378 return false; 379 } 380} 381 382WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype; 383