1 /* 2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Matt Lilek (pewtermoose (at) gmail.com). 4 * Copyright (C) 2009 Joseph Pecoraro 5 * Copyright (C) 2011 Google Inc. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 17 * its contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 24 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 WebInspector.SearchController = function() 33 { 34 this.element = document.getElementById("search"); 35 this._matchesElement = document.getElementById("search-results-matches"); 36 this._toolbarLabelElement = document.getElementById("search-toolbar-label"); 37 38 this.element.addEventListener("search", this._onSearch.bind(this), false); // when the search is emptied 39 this.element.addEventListener("mousedown", this._onSearchFieldManualFocus.bind(this), false); // when the search field is manually selected 40 this.element.addEventListener("keydown", this._onKeyDown.bind(this), true); 41 } 42 43 WebInspector.SearchController.prototype = { 44 updateSearchMatchesCount: function(matches, panel) 45 { 46 if (!panel) 47 panel = WebInspector.currentPanel; 48 49 panel.currentSearchMatches = matches; 50 51 if (panel === WebInspector.currentPanel) 52 this._updateSearchMatchesCount(WebInspector.currentPanel.currentQuery && matches); 53 }, 54 55 updateSearchLabel: function() 56 { 57 var panelName = WebInspector.currentPanel && WebInspector.currentPanel.toolbarItemLabel; 58 if (!panelName) 59 return; 60 var newLabel = WebInspector.UIString("Search %s", panelName); 61 if (WebInspector.attached) 62 this.element.setAttribute("placeholder", newLabel); 63 else { 64 this.element.removeAttribute("placeholder"); 65 this._toolbarLabelElement.textContent = newLabel; 66 } 67 }, 68 69 cancelSearch: function() 70 { 71 this.element.value = ""; 72 this._performSearch(""); 73 }, 74 75 handleShortcut: function(event) 76 { 77 var isMac = WebInspector.isMac(); 78 79 switch (event.keyIdentifier) { 80 case "U+0046": // F key 81 if (isMac) 82 var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey; 83 else 84 var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey; 85 86 if (isFindKey) { 87 this.focusSearchField(); 88 event.handled = true; 89 } 90 break; 91 92 93 case "F3": 94 if (!isMac) { 95 this.focusSearchField(); 96 event.handled = true; 97 } 98 break; 99 100 case "U+0047": // G key 101 var currentPanel = WebInspector.currentPanel; 102 103 if (isMac && event.metaKey && !event.ctrlKey && !event.altKey) { 104 if (event.shiftKey) { 105 if (currentPanel.jumpToPreviousSearchResult) 106 currentPanel.jumpToPreviousSearchResult(); 107 } else if (currentPanel.jumpToNextSearchResult) 108 currentPanel.jumpToNextSearchResult(); 109 event.handled = true; 110 } 111 break; 112 } 113 }, 114 115 activePanelChanged: function() 116 { 117 this.updateSearchLabel(); 118 119 if (!this._currentQuery) 120 return; 121 122 panel = WebInspector.currentPanel; 123 if (panel.performSearch) { 124 function performPanelSearch() 125 { 126 this._updateSearchMatchesCount(); 127 128 panel.currentQuery = this._currentQuery; 129 panel.performSearch(this._currentQuery); 130 } 131 132 // Perform the search on a timeout so the panel switches fast. 133 setTimeout(performPanelSearch.bind(this), 0); 134 } else { 135 // Update to show Not found for panels that can't be searched. 136 this._updateSearchMatchesCount(); 137 } 138 }, 139 140 _updateSearchMatchesCount: function(matches) 141 { 142 if (matches == null) { 143 this._matchesElement.addStyleClass("hidden"); 144 return; 145 } 146 147 if (matches) { 148 if (matches === 1) 149 var matchesString = WebInspector.UIString("1 match"); 150 else 151 var matchesString = WebInspector.UIString("%d matches", matches); 152 } else 153 var matchesString = WebInspector.UIString("Not Found"); 154 155 this._matchesElement.removeStyleClass("hidden"); 156 this._matchesElement.textContent = matchesString; 157 WebInspector.toolbar.resize(); 158 }, 159 160 focusSearchField: function() 161 { 162 this.element.focus(); 163 this.element.select(); 164 }, 165 166 _onSearchFieldManualFocus: function(event) 167 { 168 WebInspector.currentFocusElement = event.target; 169 }, 170 171 _onKeyDown: function(event) 172 { 173 // Escape Key will clear the field and clear the search results 174 if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { 175 // If focus belongs here and text is empty - nothing to do, return unhandled. 176 // When search was selected manually and is currently blank, we'd like Esc stay unhandled 177 // and hit console drawer handler. 178 if (event.target.value === "" && this.currentFocusElement === this.previousFocusElement) 179 return; 180 event.preventDefault(); 181 event.stopPropagation(); 182 183 this.cancelSearch(event); 184 WebInspector.currentFocusElement = WebInspector.previousFocusElement; 185 if (WebInspector.currentFocusElement === event.target) 186 WebInspector.currentFocusElement.currentFocusElement.select(); 187 return false; 188 } 189 190 if (!isEnterKey(event)) 191 return false; 192 193 // Select all of the text so the user can easily type an entirely new query. 194 event.target.select(); 195 196 // Only call performSearch if the Enter key was pressed. Otherwise the search 197 // performance is poor because of searching on every key. The search field has 198 // the incremental attribute set, so we still get incremental searches. 199 this._onSearch(event); 200 201 // Call preventDefault since this was the Enter key. This prevents a "search" event 202 // from firing for key down. This stops performSearch from being called twice in a row. 203 event.preventDefault(); 204 }, 205 206 _onSearch: function(event) 207 { 208 var forceSearch = event.keyIdentifier === "Enter"; 209 this._performSearch(event.target.value, forceSearch, event.shiftKey, false); 210 }, 211 212 _performSearch: function(query, forceSearch, isBackwardSearch, repeatSearch) 213 { 214 var isShortSearch = (query.length < 3); 215 216 // Clear a leftover short search flag due to a non-conflicting forced search. 217 if (isShortSearch && this._shortSearchWasForcedByKeyEvent && this._currentQuery !== query) 218 delete this._shortSearchWasForcedByKeyEvent; 219 220 // Indicate this was a forced search on a short query. 221 if (isShortSearch && forceSearch) 222 this._shortSearchWasForcedByKeyEvent = true; 223 224 if (!query || !query.length || (!forceSearch && isShortSearch)) { 225 // Prevent clobbering a short search forced by the user. 226 if (this._shortSearchWasForcedByKeyEvent) { 227 delete this._shortSearchWasForcedByKeyEvent; 228 return; 229 } 230 231 delete this._currentQuery; 232 233 for (var panelName in WebInspector.panels) { 234 var panel = WebInspector.panels[panelName]; 235 var hadCurrentQuery = !!panel.currentQuery; 236 delete panel.currentQuery; 237 if (hadCurrentQuery && panel.searchCanceled) 238 panel.searchCanceled(); 239 } 240 241 this._updateSearchMatchesCount(); 242 243 return; 244 } 245 246 var currentPanel = WebInspector.currentPanel; 247 if (!repeatSearch && query === currentPanel.currentQuery && currentPanel.currentQuery === this._currentQuery) { 248 // When this is the same query and a forced search, jump to the next 249 // search result for a good user experience. 250 if (forceSearch) { 251 if (!isBackwardSearch && currentPanel.jumpToNextSearchResult) 252 currentPanel.jumpToNextSearchResult(); 253 else if (isBackwardSearch && currentPanel.jumpToPreviousSearchResult) 254 currentPanel.jumpToPreviousSearchResult(); 255 } 256 return; 257 } 258 259 this._currentQuery = query; 260 261 this._updateSearchMatchesCount(); 262 263 if (!currentPanel.performSearch) 264 return; 265 266 currentPanel.currentQuery = query; 267 currentPanel.performSearch(query); 268 } 269 } 270