• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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