| /* |
| * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). |
| * Copyright (C) 2009 Joseph Pecoraro |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @constructor |
| */ |
| WebInspector.SearchController = function() |
| { |
| this._element = document.createElement("table"); |
| this._element.className = "toolbar-search"; |
| this._element.cellSpacing = 0; |
| |
| this._firstRowElement = this._element.createChild("tr"); |
| this._secondRowElement = this._element.createChild("tr", "hidden"); |
| |
| // Column 1 |
| var searchControlElementColumn = this._firstRowElement.createChild("td"); |
| this._searchControlElement = searchControlElementColumn.createChild("span", "toolbar-search-control"); |
| this._searchInputElement = this._searchControlElement.createChild("input", "search-replace"); |
| this._searchInputElement.id = "search-input-field"; |
| |
| this._matchesElement = this._searchControlElement.createChild("label", "search-results-matches"); |
| this._matchesElement.setAttribute("for", "search-input-field"); |
| |
| this._searchNavigationElement = this._searchControlElement.createChild("div", "toolbar-search-navigation-controls"); |
| this._toggleFilterUI(false); |
| |
| this._searchNavigationPrevElement = this._searchNavigationElement.createChild("div", "toolbar-search-navigation toolbar-search-navigation-prev"); |
| this._searchNavigationPrevElement.addEventListener("click", this._onPrevButtonSearch.bind(this), false); |
| this._searchNavigationPrevElement.title = WebInspector.UIString("Search Previous"); |
| |
| this._searchNavigationNextElement = this._searchNavigationElement.createChild("div", "toolbar-search-navigation toolbar-search-navigation-next"); |
| this._searchNavigationNextElement.addEventListener("click", this._onNextButtonSearch.bind(this), false); |
| this._searchNavigationNextElement.title = WebInspector.UIString("Search Next"); |
| |
| this._searchInputElement.addEventListener("mousedown", this._onSearchFieldManualFocus.bind(this), false); // when the search field is manually selected |
| this._searchInputElement.addEventListener("keydown", this._onKeyDown.bind(this), true); |
| this._searchInputElement.addEventListener("input", this._onInput.bind(this), false); |
| |
| this._replaceInputElement = this._secondRowElement.createChild("td").createChild("input", "search-replace toolbar-replace-control"); |
| this._replaceInputElement.addEventListener("keydown", this._onKeyDown.bind(this), true); |
| this._replaceInputElement.placeholder = WebInspector.UIString("Replace"); |
| |
| // Column 2 |
| this._findButtonElement = this._firstRowElement.createChild("td").createChild("button", "hidden"); |
| this._findButtonElement.textContent = WebInspector.UIString("Find"); |
| this._findButtonElement.tabIndex = -1; |
| this._findButtonElement.addEventListener("click", this._onNextButtonSearch.bind(this), false); |
| |
| this._replaceButtonElement = this._secondRowElement.createChild("td").createChild("button"); |
| this._replaceButtonElement.textContent = WebInspector.UIString("Replace"); |
| this._replaceButtonElement.disabled = true; |
| this._replaceButtonElement.tabIndex = -1; |
| this._replaceButtonElement.addEventListener("click", this._replace.bind(this), false); |
| |
| // Column 3 |
| this._prevButtonElement = this._firstRowElement.createChild("td").createChild("button", "hidden"); |
| this._prevButtonElement.textContent = WebInspector.UIString("Previous"); |
| this._prevButtonElement.disabled = true; |
| this._prevButtonElement.tabIndex = -1; |
| this._prevButtonElement.addEventListener("click", this._onPrevButtonSearch.bind(this), false); |
| |
| this._replaceAllButtonElement = this._secondRowElement.createChild("td").createChild("button"); |
| this._replaceAllButtonElement.textContent = WebInspector.UIString("Replace All"); |
| this._replaceAllButtonElement.addEventListener("click", this._replaceAll.bind(this), false); |
| |
| // Column 4 |
| this._replaceElement = this._firstRowElement.createChild("td").createChild("span"); |
| |
| this._replaceCheckboxElement = this._replaceElement.createChild("input"); |
| this._replaceCheckboxElement.type = "checkbox"; |
| this._replaceCheckboxElement.id = "search-replace-trigger"; |
| this._replaceCheckboxElement.addEventListener("click", this._updateSecondRowVisibility.bind(this), false); |
| |
| this._replaceLabelElement = this._replaceElement.createChild("label"); |
| this._replaceLabelElement.textContent = WebInspector.UIString("Replace"); |
| this._replaceLabelElement.setAttribute("for", "search-replace-trigger"); |
| |
| // Column 5 |
| this._filterCheckboxContainer = this._firstRowElement.createChild("td").createChild("label"); |
| this._filterCheckboxContainer.setAttribute("for", "filter-trigger"); |
| |
| this._filterCheckboxElement = this._filterCheckboxContainer.createChild("input"); |
| this._filterCheckboxElement.type = "checkbox"; |
| this._filterCheckboxElement.id = "filter-trigger"; |
| this._filterCheckboxElement.addEventListener("click", this._filterCheckboxClick.bind(this), false); |
| |
| this._filterCheckboxContainer.createTextChild(WebInspector.UIString("Filter")); |
| |
| // Column 6 |
| var cancelButtonElement = this._firstRowElement.createChild("td").createChild("button"); |
| cancelButtonElement.textContent = WebInspector.UIString("Cancel"); |
| cancelButtonElement.tabIndex = -1; |
| cancelButtonElement.addEventListener("click", this.closeSearch.bind(this), false); |
| } |
| |
| WebInspector.SearchController.prototype = { |
| /** |
| * @param {number} matches |
| * @param {WebInspector.Searchable} provider |
| */ |
| updateSearchMatchesCount: function(matches, provider) |
| { |
| provider.currentSearchMatches = matches; |
| |
| if (provider === this._searchProvider) |
| this._updateSearchMatchesCountAndCurrentMatchIndex(provider.currentQuery ? matches : 0, -1); |
| }, |
| |
| /** |
| * @param {number} currentMatchIndex |
| * @param {WebInspector.Searchable} provider |
| */ |
| updateCurrentMatchIndex: function(currentMatchIndex, provider) |
| { |
| if (provider === this._searchProvider) |
| this._updateSearchMatchesCountAndCurrentMatchIndex(provider.currentSearchMatches, currentMatchIndex); |
| }, |
| |
| isSearchVisible: function() |
| { |
| return this._searchIsVisible; |
| }, |
| |
| closeSearch: function() |
| { |
| this.cancelSearch(); |
| WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement()); |
| }, |
| |
| cancelSearch: function() |
| { |
| if (!this._searchIsVisible) |
| return; |
| if (this._filterCheckboxElement.checked) { |
| this._filterCheckboxElement.checked = false; |
| this._toggleFilterUI(false); |
| this.resetFilter(); |
| } else |
| this.resetSearch(); |
| delete this._searchIsVisible; |
| this._searchHost.setFooterElement(null); |
| this.resetSearch(); |
| delete this._searchHost; |
| delete this._searchProvider; |
| }, |
| |
| resetSearch: function() |
| { |
| this._clearSearch(); |
| this._updateReplaceVisibility(); |
| this._matchesElement.textContent = ""; |
| }, |
| |
| /** |
| * @param {Event} event |
| * @return {boolean} |
| */ |
| handleShortcut: function(event) |
| { |
| var isMac = WebInspector.isMac(); |
| |
| switch (event.keyIdentifier) { |
| case "U+0046": // F key |
| if (isMac) |
| var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey; |
| else |
| var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey; |
| |
| if (isFindKey) { |
| this.showSearchField(); |
| event.consume(true); |
| return true; |
| } |
| break; |
| |
| case "F3": |
| if (!isMac) { |
| this.showSearchField(); |
| event.consume(true); |
| return true; |
| } |
| break; |
| |
| case "U+0047": // G key |
| if (isMac && event.metaKey && !event.ctrlKey && !event.altKey && this._searchHost) { |
| if (event.shiftKey) |
| this._searchProvider.jumpToPreviousSearchResult(); |
| else |
| this._searchProvider.jumpToNextSearchResult(); |
| event.consume(true); |
| return true; |
| } |
| break; |
| } |
| return false; |
| }, |
| |
| /** |
| * @param {boolean} enabled |
| */ |
| _updateSearchNavigationButtonState: function(enabled) |
| { |
| this._replaceButtonElement.disabled = !enabled; |
| this._prevButtonElement.disabled = !enabled; |
| if (enabled) { |
| this._searchNavigationPrevElement.addStyleClass("enabled"); |
| this._searchNavigationNextElement.addStyleClass("enabled"); |
| } else { |
| this._searchNavigationPrevElement.removeStyleClass("enabled"); |
| this._searchNavigationNextElement.removeStyleClass("enabled"); |
| } |
| }, |
| |
| /** |
| * @param {number} matches |
| * @param {number} currentMatchIndex |
| */ |
| _updateSearchMatchesCountAndCurrentMatchIndex: function(matches, currentMatchIndex) |
| { |
| if (!this._currentQuery) |
| this._matchesElement.textContent = ""; |
| else if (matches === 0 || currentMatchIndex >= 0) |
| this._matchesElement.textContent = WebInspector.UIString("%d of %d", currentMatchIndex + 1, matches); |
| else if (matches === 1) |
| this._matchesElement.textContent = WebInspector.UIString("1 match"); |
| else |
| this._matchesElement.textContent = WebInspector.UIString("%d matches", matches); |
| this._updateSearchNavigationButtonState(matches > 0); |
| }, |
| |
| showSearchField: function() |
| { |
| if (this._searchIsVisible) |
| this.cancelSearch(); |
| |
| if (WebInspector.drawer.element.isAncestor(document.activeElement) && WebInspector.drawer.getSearchProvider()) |
| this._searchHost = WebInspector.drawer; |
| else |
| this._searchHost = WebInspector.inspectorView; |
| |
| this._searchProvider = this._searchHost.getSearchProvider(); |
| this._searchHost.setFooterElement(this._element); |
| |
| this._updateReplaceVisibility(); |
| this._updateFilterVisibility(); |
| if (WebInspector.currentFocusElement() !== this._searchInputElement) { |
| var selection = window.getSelection(); |
| if (selection.rangeCount) { |
| var queryCandidate = selection.toString().replace(/\r?\n.*/, ""); |
| if (queryCandidate) |
| this._searchInputElement.value = queryCandidate; |
| } |
| } |
| this._performSearch(false, false); |
| this._searchInputElement.focus(); |
| this._searchInputElement.select(); |
| this._searchIsVisible = true; |
| }, |
| |
| /** |
| * @param {boolean} filter |
| */ |
| _toggleFilterUI: function(filter) |
| { |
| this._matchesElement.enableStyleClass("hidden", filter); |
| this._searchNavigationElement.enableStyleClass("hidden", filter); |
| this._searchInputElement.placeholder = filter ? WebInspector.UIString("Filter") : WebInspector.UIString("Find"); |
| }, |
| |
| _updateFilterVisibility: function() |
| { |
| if (this._searchProvider.canFilter()) |
| this._filterCheckboxContainer.removeStyleClass("hidden"); |
| else |
| this._filterCheckboxContainer.addStyleClass("hidden"); |
| }, |
| |
| _updateReplaceVisibility: function() |
| { |
| if (!this._searchProvider) |
| return; |
| |
| if (this._searchProvider.canSearchAndReplace()) |
| this._replaceElement.removeStyleClass("hidden"); |
| else { |
| this._replaceElement.addStyleClass("hidden"); |
| this._replaceCheckboxElement.checked = false; |
| this._updateSecondRowVisibility(); |
| } |
| }, |
| |
| /** |
| * @param {Event} event |
| */ |
| _onSearchFieldManualFocus: function(event) |
| { |
| WebInspector.setCurrentFocusElement(event.target); |
| }, |
| |
| /** |
| * @param {KeyboardEvent} event |
| */ |
| _onKeyDown: function(event) |
| { |
| if (isEnterKey(event)) { |
| if (event.target === this._searchInputElement) { |
| // FIXME: This won't start backwards search with Shift+Enter correctly. |
| if (!this._currentQuery) |
| this._performSearch(true, true); |
| else |
| this._jumpToNextSearchResult(event.shiftKey); |
| } else if (event.target === this._replaceInputElement) |
| this._replace(); |
| } |
| }, |
| |
| /** |
| * @param {boolean=} isBackwardSearch |
| */ |
| _jumpToNextSearchResult: function(isBackwardSearch) |
| { |
| if (!this._currentQuery || !this._searchNavigationPrevElement.hasStyleClass("enabled")) |
| return; |
| |
| if (isBackwardSearch) |
| this._searchProvider.jumpToPreviousSearchResult(); |
| else |
| this._searchProvider.jumpToNextSearchResult(); |
| }, |
| |
| _onNextButtonSearch: function(event) |
| { |
| if (!this._searchNavigationNextElement.hasStyleClass("enabled")) |
| return; |
| // Simulate next search on search-navigation-button click. |
| this._jumpToNextSearchResult(); |
| this._searchInputElement.focus(); |
| }, |
| |
| _onPrevButtonSearch: function(event) |
| { |
| if (!this._searchNavigationPrevElement.hasStyleClass("enabled")) |
| return; |
| // Simulate previous search on search-navigation-button click. |
| this._jumpToNextSearchResult(true); |
| this._searchInputElement.focus(); |
| }, |
| |
| _clearSearch: function() |
| { |
| delete this._currentQuery; |
| if (this._searchHost){ |
| var searchProvider = this._searchHost.getSearchProvider(); |
| if (searchProvider && !!searchProvider.currentQuery) { |
| delete searchProvider.currentQuery; |
| searchProvider.searchCanceled(); |
| } |
| } |
| this._updateSearchMatchesCountAndCurrentMatchIndex(0, -1); |
| }, |
| |
| /** |
| * @param {boolean} forceSearch |
| * @param {boolean} shouldJump |
| */ |
| _performSearch: function(forceSearch, shouldJump) |
| { |
| var query = this._searchInputElement.value; |
| var minimalSearchQuerySize = this._searchProvider.minimalSearchQuerySize(); |
| if (!query || !this._searchProvider || (!forceSearch && query.length < minimalSearchQuerySize && !this._currentQuery)) { |
| this._clearSearch(); |
| return; |
| } |
| |
| this._currentQuery = query; |
| this._searchProvider.currentQuery = query; |
| this._searchProvider.performSearch(query, shouldJump); |
| }, |
| |
| _updateSecondRowVisibility: function() |
| { |
| if (!this._searchIsVisible || !this._searchHost) |
| return; |
| if (this._replaceCheckboxElement.checked) { |
| this._element.addStyleClass("toolbar-search-replace"); |
| this._secondRowElement.removeStyleClass("hidden"); |
| this._prevButtonElement.removeStyleClass("hidden"); |
| this._findButtonElement.removeStyleClass("hidden"); |
| this._replaceCheckboxElement.tabIndex = -1; |
| this._replaceInputElement.focus(); |
| } else { |
| this._element.removeStyleClass("toolbar-search-replace"); |
| this._secondRowElement.addStyleClass("hidden"); |
| this._prevButtonElement.addStyleClass("hidden"); |
| this._findButtonElement.addStyleClass("hidden"); |
| this._replaceCheckboxElement.tabIndex = 0; |
| this._searchInputElement.focus(); |
| } |
| this._searchHost.setFooterElement(this._element); |
| }, |
| |
| _replace: function() |
| { |
| this._searchProvider.replaceSelectionWith(this._replaceInputElement.value); |
| delete this._currentQuery; |
| this._performSearch(true, true); |
| }, |
| |
| _replaceAll: function() |
| { |
| this._searchProvider.replaceAllWith(this._searchInputElement.value, this._replaceInputElement.value); |
| }, |
| |
| _filterCheckboxClick: function() |
| { |
| this._searchInputElement.focus(); |
| this._searchInputElement.select(); |
| |
| if (this._filterCheckboxElement.checked) { |
| this._toggleFilterUI(true); |
| this.resetSearch(); |
| this._performFilter(this._searchInputElement.value); |
| } else { |
| this._toggleFilterUI(false); |
| this.resetFilter(); |
| this._performSearch(false, false); |
| } |
| }, |
| |
| /** |
| * @param {string} query |
| */ |
| _performFilter: function(query) |
| { |
| this._searchProvider.setRegexFilter(query); |
| }, |
| |
| _onInput: function(event) |
| { |
| if (this._filterCheckboxElement.checked) |
| this._performFilter(event.target.value); |
| else |
| this._performSearch(false, true); |
| }, |
| |
| resetFilter: function() |
| { |
| this._performFilter(""); |
| } |
| } |
| |
| /** |
| * @type {?WebInspector.SearchController} |
| */ |
| WebInspector.searchController = null; |
| |
| /** |
| * @interface |
| */ |
| WebInspector.Searchable = function() |
| { |
| } |
| |
| WebInspector.Searchable.prototype = { |
| /** |
| * @return {boolean} |
| */ |
| canSearchAndReplace: function() { }, |
| |
| /** |
| * @return {boolean} |
| */ |
| canFilter: function() { }, |
| |
| searchCanceled: function() { }, |
| |
| /** |
| * @param {string} query |
| * @param {boolean} shouldJump |
| * @param {WebInspector.Searchable=} self |
| */ |
| performSearch: function(query, shouldJump, self) { }, |
| |
| /** |
| * @return {number} |
| */ |
| minimalSearchQuerySize: function() { }, |
| |
| /** |
| * @param {WebInspector.Searchable=} self |
| */ |
| jumpToNextSearchResult: function(self) { }, |
| |
| /** |
| * @param {WebInspector.Searchable=} self |
| */ |
| jumpToPreviousSearchResult: function(self) { }, |
| } |