2011-02-25 Andrey Kosyakov <caseq@chromium.org>
[WebKit-https.git] / Source / WebCore / inspector / front-end / Panel.js
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
29 WebInspector.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.
41 WebInspector.Panel.counterRightMargin = 25;
42
43 WebInspector.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     },
78
79     hide: function()
80     {
81         this._storeScrollPositions();
82         WebInspector.View.prototype.hide.call(this);
83
84         if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode)
85             this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer);
86         delete this._statusBarItemContainer;
87         if ("_toolbarItem" in this)
88             this._toolbarItem.removeStyleClass("toggled-on");
89     },
90
91     get defaultFocusedElement()
92     {
93         return this.sidebarTreeElement || this.element;
94     },
95
96     attach: function()
97     {
98         if (!this.element.parentNode)
99             document.getElementById("main-panels").appendChild(this.element);
100     },
101
102     searchCanceled: function()
103     {
104         if (this._searchResults) {
105             for (var i = 0; i < this._searchResults.length; ++i) {
106                 var view = this._searchResults[i];
107                 if (view.searchCanceled)
108                     view.searchCanceled();
109                 delete view.currentQuery;
110             }
111         }
112
113         WebInspector.searchController.updateSearchMatchesCount(0, this);
114
115         if (this._currentSearchChunkIntervalIdentifier) {
116             clearInterval(this._currentSearchChunkIntervalIdentifier);
117             delete this._currentSearchChunkIntervalIdentifier;
118         }
119
120         this._totalSearchMatches = 0;
121         this._currentSearchResultIndex = 0;
122         this._searchResults = [];
123     },
124
125     performSearch: function(query)
126     {
127         // Call searchCanceled since it will reset everything we need before doing a new search.
128         this.searchCanceled(true);
129
130         var searchableViews = this.searchableViews;
131         if (!searchableViews || !searchableViews.length)
132             return;
133
134         var parentElement = this.viewsContainerElement;
135         var visibleView = this.visibleView;
136         var sortFuction = this.searchResultsSortFunction;
137
138         var matchesCountUpdateTimeout = null;
139
140         function updateMatchesCount()
141         {
142             WebInspector.searchController.updateSearchMatchesCount(this._totalSearchMatches, this);
143             matchesCountUpdateTimeout = null;
144         }
145
146         function updateMatchesCountSoon()
147         {
148             if (matchesCountUpdateTimeout)
149                 return;
150             // Update the matches count every half-second so it doesn't feel twitchy.
151             matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500);
152         }
153
154         function finishedCallback(view, searchMatches)
155         {
156             if (!searchMatches)
157                 return;
158
159             this._totalSearchMatches += searchMatches;
160             this._searchResults.push(view);
161
162             if (sortFuction)
163                 this._searchResults.sort(sortFuction);
164
165             if (this.searchMatchFound)
166                 this.searchMatchFound(view, searchMatches);
167
168             updateMatchesCountSoon.call(this);
169
170             if (view === visibleView)
171                 view.jumpToFirstSearchResult();
172         }
173
174         var i = 0;
175         var panel = this;
176         var boundFinishedCallback = finishedCallback.bind(this);
177         var chunkIntervalIdentifier = null;
178
179         // Split up the work into chunks so we don't block the
180         // UI thread while processing.
181
182         function processChunk()
183         {
184             var view = searchableViews[i];
185
186             if (++i >= searchableViews.length) {
187                 if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
188                     delete panel._currentSearchChunkIntervalIdentifier;
189                 clearInterval(chunkIntervalIdentifier);
190             }
191
192             if (!view)
193                 return;
194
195             if (view.element.parentNode !== parentElement && view.element.parentNode && parentElement)
196                 view.detach();
197
198             view.currentQuery = query;
199             view.performSearch(query, boundFinishedCallback);
200         }
201
202         processChunk();
203
204         chunkIntervalIdentifier = setInterval(processChunk, 25);
205         this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
206     },
207
208     jumpToNextSearchResult: function()
209     {
210         if (!this.showView || !this._searchResults || !this._searchResults.length)
211             return;
212
213         var showFirstResult = false;
214
215         this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
216         if (this._currentSearchResultIndex === -1) {
217             this._currentSearchResultIndex = 0;
218             showFirstResult = true;
219         }
220
221         var currentView = this._searchResults[this._currentSearchResultIndex];
222
223         if (currentView.showingLastSearchResult()) {
224             if (++this._currentSearchResultIndex >= this._searchResults.length)
225                 this._currentSearchResultIndex = 0;
226             currentView = this._searchResults[this._currentSearchResultIndex];
227             showFirstResult = true;
228         }
229
230         if (currentView !== this.visibleView) {
231             this.showView(currentView);
232             WebInspector.focusSearchField();
233         }
234
235         if (showFirstResult)
236             currentView.jumpToFirstSearchResult();
237         else
238             currentView.jumpToNextSearchResult();
239     },
240
241     jumpToPreviousSearchResult: function()
242     {
243         if (!this.showView || !this._searchResults || !this._searchResults.length)
244             return;
245
246         var showLastResult = false;
247
248         this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
249         if (this._currentSearchResultIndex === -1) {
250             this._currentSearchResultIndex = 0;
251             showLastResult = true;
252         }
253
254         var currentView = this._searchResults[this._currentSearchResultIndex];
255
256         if (currentView.showingFirstSearchResult()) {
257             if (--this._currentSearchResultIndex < 0)
258                 this._currentSearchResultIndex = (this._searchResults.length - 1);
259             currentView = this._searchResults[this._currentSearchResultIndex];
260             showLastResult = true;
261         }
262
263         if (currentView !== this.visibleView) {
264             this.showView(currentView);
265             WebInspector.focusSearchField();
266         }
267
268         if (showLastResult)
269             currentView.jumpToLastSearchResult();
270         else
271             currentView.jumpToPreviousSearchResult();
272     },
273
274     createSidebar: function(parentElement, resizerParentElement)
275     {
276         if (this.sidebarElement)
277             return;
278
279         if (!parentElement)
280             parentElement = this.element;
281
282         if (!resizerParentElement)
283             resizerParentElement = parentElement;
284
285         this.sidebarElement = document.createElement("div");
286         this.sidebarElement.className = "sidebar";
287         parentElement.appendChild(this.sidebarElement);
288
289         this.sidebarResizeElement = document.createElement("div");
290         this.sidebarResizeElement.className = "sidebar-resizer-vertical";
291         this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
292         resizerParentElement.appendChild(this.sidebarResizeElement);
293
294         this.sidebarTreeElement = document.createElement("ol");
295         this.sidebarTreeElement.className = "sidebar-tree";
296         this.sidebarElement.appendChild(this.sidebarTreeElement);
297
298         this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
299         this.sidebarTree.panel = this;
300     },
301
302     _sidebarWidthSettingName: function()
303     {
304         return this._panelName + "SidebarWidth";
305     },
306
307     _startSidebarDragging: function(event)
308     {
309         WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
310     },
311
312     _sidebarDragging: function(event)
313     {
314         this.updateSidebarWidth(event.pageX);
315
316         event.preventDefault();
317     },
318
319     _endSidebarDragging: function(event)
320     {
321         WebInspector.elementDragEnd(event);
322         this.saveSidebarWidth();
323     },
324
325     updateSidebarWidth: function(width)
326     {
327         if (!this.sidebarElement)
328             return;
329
330         if (this.sidebarElement.offsetWidth <= 0) {
331             // The stylesheet hasn't loaded yet or the window is closed,
332             // so we can't calculate what is need. Return early.
333             return;
334         }
335
336         if (!("_currentSidebarWidth" in this))
337             this._currentSidebarWidth = this.sidebarElement.offsetWidth;
338
339         if (typeof width === "undefined")
340             width = this._currentSidebarWidth;
341
342         width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
343
344         this._currentSidebarWidth = width;
345         this.setSidebarWidth(width);
346
347         this.updateMainViewWidth(width);
348     },
349
350     setSidebarWidth: function(width)
351     {
352         this.sidebarElement.style.width = width + "px";
353         this.sidebarResizeElement.style.left = (width - 3) + "px";
354     },
355
356     restoreSidebarWidth: function()
357     {
358         var sidebarWidth = WebInspector.settings[this._sidebarWidthSettingName()];
359         this.updateSidebarWidth(sidebarWidth);
360     },
361
362     saveSidebarWidth: function()
363     {
364         if (!this.sidebarElement)
365             return;
366         WebInspector.settings[this._sidebarWidthSettingName()] = this.sidebarElement.offsetWidth;
367     },
368
369     updateMainViewWidth: function(width)
370     {
371         // Should be implemented by ancestors.
372     },
373
374     resize: function()
375     {
376         var visibleView = this.visibleView;
377         if (visibleView && "resize" in visibleView)
378             visibleView.resize();
379     },
380
381     canShowSourceLine: function(url, line)
382     {
383         return false;
384     },
385
386     showSourceLine: function(url, line)
387     {
388         return false;
389     },
390
391     elementsToRestoreScrollPositionsFor: function()
392     {
393         return [];
394     },
395
396     _storeScrollPositions: function()
397     {
398         var elements = this.elementsToRestoreScrollPositionsFor();
399         for (var i = 0; i < elements.length; ++i) {
400             var container = elements[i];
401             container._scrollTop = container.scrollTop;
402         }
403     },
404
405     _restoreScrollPositions: function()
406     {
407         var elements = this.elementsToRestoreScrollPositionsFor();
408         for (var i = 0; i < elements.length; ++i) {
409             var container = elements[i];
410             if (container._scrollTop)
411                 container.scrollTop = container._scrollTop;
412         }
413     }
414 }
415
416 WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype;