browser/components/tabview/search.js

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 /* ******************************
michael@0 6 *
michael@0 7 * This file incorporates work from:
michael@0 8 * Quicksilver Score (qs_score):
michael@0 9 * http://rails-oceania.googlecode.com/svn/lachiecox/qs_score/trunk/qs_score.js
michael@0 10 * This incorporated work is covered by the following copyright and
michael@0 11 * permission notice:
michael@0 12 * Copyright 2008 Lachie Cox
michael@0 13 * Licensed under the MIT license.
michael@0 14 * http://jquery.org/license
michael@0 15 *
michael@0 16 * ***************************** */
michael@0 17
michael@0 18 // **********
michael@0 19 // Title: search.js
michael@0 20 // Implementation for the search functionality of Firefox Panorama.
michael@0 21
michael@0 22 // ##########
michael@0 23 // Class: TabUtils
michael@0 24 //
michael@0 25 // A collection of helper functions for dealing with both <TabItem>s and
michael@0 26 // <xul:tab>s without having to worry which one is which.
michael@0 27 let TabUtils = {
michael@0 28 // ----------
michael@0 29 // Function: toString
michael@0 30 // Prints [TabUtils] for debug use.
michael@0 31 toString: function TabUtils_toString() {
michael@0 32 return "[TabUtils]";
michael@0 33 },
michael@0 34
michael@0 35 // ---------
michael@0 36 // Function: nameOfTab
michael@0 37 // Given a <TabItem> or a <xul:tab> returns the tab's name.
michael@0 38 nameOf: function TabUtils_nameOf(tab) {
michael@0 39 // We can have two types of tabs: A <TabItem> or a <xul:tab>
michael@0 40 // because we have to deal with both tabs represented inside
michael@0 41 // of active Panoramas as well as for windows in which
michael@0 42 // Panorama has yet to be activated. We uses object sniffing to
michael@0 43 // determine the type of tab and then returns its name.
michael@0 44 return tab.label != undefined ? tab.label : tab.$tabTitle[0].textContent;
michael@0 45 },
michael@0 46
michael@0 47 // ---------
michael@0 48 // Function: URLOf
michael@0 49 // Given a <TabItem> or a <xul:tab> returns the URL of tab.
michael@0 50 URLOf: function TabUtils_URLOf(tab) {
michael@0 51 // Convert a <TabItem> to <xul:tab>
michael@0 52 if ("tab" in tab)
michael@0 53 tab = tab.tab;
michael@0 54 return tab.linkedBrowser.currentURI.spec;
michael@0 55 },
michael@0 56
michael@0 57 // ---------
michael@0 58 // Function: faviconURLOf
michael@0 59 // Given a <TabItem> or a <xul:tab> returns the URL of tab's favicon.
michael@0 60 faviconURLOf: function TabUtils_faviconURLOf(tab) {
michael@0 61 return tab.image != undefined ? tab.image : tab.$favImage[0].src;
michael@0 62 },
michael@0 63
michael@0 64 // ---------
michael@0 65 // Function: focus
michael@0 66 // Given a <TabItem> or a <xul:tab>, focuses it and it's window.
michael@0 67 focus: function TabUtils_focus(tab) {
michael@0 68 // Convert a <TabItem> to a <xul:tab>
michael@0 69 if ("tab" in tab)
michael@0 70 tab = tab.tab;
michael@0 71 tab.ownerDocument.defaultView.gBrowser.selectedTab = tab;
michael@0 72 tab.ownerDocument.defaultView.focus();
michael@0 73 }
michael@0 74 };
michael@0 75
michael@0 76 // ##########
michael@0 77 // Class: TabMatcher
michael@0 78 //
michael@0 79 // A class that allows you to iterate over matching and not-matching tabs,
michael@0 80 // given a case-insensitive search term.
michael@0 81 function TabMatcher(term) {
michael@0 82 this.term = term;
michael@0 83 }
michael@0 84
michael@0 85 TabMatcher.prototype = {
michael@0 86 // ----------
michael@0 87 // Function: toString
michael@0 88 // Prints [TabMatcher (term)] for debug use.
michael@0 89 toString: function TabMatcher_toString() {
michael@0 90 return "[TabMatcher (" + this.term + ")]";
michael@0 91 },
michael@0 92
michael@0 93 // ---------
michael@0 94 // Function: _filterAndSortForMatches
michael@0 95 // Given an array of <TabItem>s and <xul:tab>s returns a new array
michael@0 96 // of tabs whose name matched the search term, sorted by lexical
michael@0 97 // closeness.
michael@0 98 _filterAndSortForMatches: function TabMatcher__filterAndSortForMatches(tabs) {
michael@0 99 let self = this;
michael@0 100 tabs = tabs.filter(function TabMatcher__filterAndSortForMatches_filter(tab) {
michael@0 101 let name = TabUtils.nameOf(tab);
michael@0 102 let url = TabUtils.URLOf(tab);
michael@0 103 return name.match(self.term, "i") || url.match(self.term, "i");
michael@0 104 });
michael@0 105
michael@0 106 tabs.sort(function TabMatcher__filterAndSortForMatches_sort(x, y) {
michael@0 107 let yScore = self._scorePatternMatch(self.term, TabUtils.nameOf(y));
michael@0 108 let xScore = self._scorePatternMatch(self.term, TabUtils.nameOf(x));
michael@0 109 return yScore - xScore;
michael@0 110 });
michael@0 111
michael@0 112 return tabs;
michael@0 113 },
michael@0 114
michael@0 115 // ---------
michael@0 116 // Function: _filterForUnmatches
michael@0 117 // Given an array of <TabItem>s returns an unsorted array of tabs whose name
michael@0 118 // does not match the the search term.
michael@0 119 _filterForUnmatches: function TabMatcher__filterForUnmatches(tabs) {
michael@0 120 let self = this;
michael@0 121 return tabs.filter(function TabMatcher__filterForUnmatches_filter(tab) {
michael@0 122 let name = tab.$tabTitle[0].textContent;
michael@0 123 let url = TabUtils.URLOf(tab);
michael@0 124 return !name.match(self.term, "i") && !url.match(self.term, "i");
michael@0 125 });
michael@0 126 },
michael@0 127
michael@0 128 // ---------
michael@0 129 // Function: _getTabsForOtherWindows
michael@0 130 // Returns an array of <TabItem>s and <xul:tabs>s representing tabs
michael@0 131 // from all windows but the current window. <TabItem>s will be returned
michael@0 132 // for windows in which Panorama has been activated at least once, while
michael@0 133 // <xul:tab>s will be returned for windows in which Panorama has never
michael@0 134 // been activated.
michael@0 135 _getTabsForOtherWindows: function TabMatcher__getTabsForOtherWindows() {
michael@0 136 let enumerator = Services.wm.getEnumerator("navigator:browser");
michael@0 137 let allTabs = [];
michael@0 138
michael@0 139 while (enumerator.hasMoreElements()) {
michael@0 140 let win = enumerator.getNext();
michael@0 141 // This function gets tabs from other windows, not from the current window
michael@0 142 if (win != gWindow)
michael@0 143 allTabs.push.apply(allTabs, win.gBrowser.tabs);
michael@0 144 }
michael@0 145 return allTabs;
michael@0 146 },
michael@0 147
michael@0 148 // ----------
michael@0 149 // Function: matchedTabsFromOtherWindows
michael@0 150 // Returns an array of <TabItem>s and <xul:tab>s that match the search term
michael@0 151 // from all windows but the current window. <TabItem>s will be returned for
michael@0 152 // windows in which Panorama has been activated at least once, while
michael@0 153 // <xul:tab>s will be returned for windows in which Panorama has never
michael@0 154 // been activated.
michael@0 155 // (new TabMatcher("app")).matchedTabsFromOtherWindows();
michael@0 156 matchedTabsFromOtherWindows: function TabMatcher_matchedTabsFromOtherWindows() {
michael@0 157 if (this.term.length < 2)
michael@0 158 return [];
michael@0 159
michael@0 160 let tabs = this._getTabsForOtherWindows();
michael@0 161 return this._filterAndSortForMatches(tabs);
michael@0 162 },
michael@0 163
michael@0 164 // ----------
michael@0 165 // Function: matched
michael@0 166 // Returns an array of <TabItem>s which match the current search term.
michael@0 167 // If the term is less than 2 characters in length, it returns nothing.
michael@0 168 matched: function TabMatcher_matched() {
michael@0 169 if (this.term.length < 2)
michael@0 170 return [];
michael@0 171
michael@0 172 let tabs = TabItems.getItems();
michael@0 173 return this._filterAndSortForMatches(tabs);
michael@0 174 },
michael@0 175
michael@0 176 // ----------
michael@0 177 // Function: unmatched
michael@0 178 // Returns all of <TabItem>s that .matched() doesn't return.
michael@0 179 unmatched: function TabMatcher_unmatched() {
michael@0 180 let tabs = TabItems.getItems();
michael@0 181 if (this.term.length < 2)
michael@0 182 return tabs;
michael@0 183
michael@0 184 return this._filterForUnmatches(tabs);
michael@0 185 },
michael@0 186
michael@0 187 // ----------
michael@0 188 // Function: doSearch
michael@0 189 // Performs the search. Lets you provide three functions.
michael@0 190 // The first is on all matched tabs in the window, the second on all unmatched
michael@0 191 // tabs in the window, and the third on all matched tabs in other windows.
michael@0 192 // The first two functions take two parameters: A <TabItem> and its integer index
michael@0 193 // indicating the absolute rank of the <TabItem> in terms of match to
michael@0 194 // the search term. The last function also takes two paramaters, but can be
michael@0 195 // passed both <TabItem>s and <xul:tab>s and the index is offset by the
michael@0 196 // number of matched tabs inside the window.
michael@0 197 doSearch: function TabMatcher_doSearch(matchFunc, unmatchFunc, otherFunc) {
michael@0 198 let matches = this.matched();
michael@0 199 let unmatched = this.unmatched();
michael@0 200 let otherMatches = this.matchedTabsFromOtherWindows();
michael@0 201
michael@0 202 matches.forEach(function(tab, i) {
michael@0 203 matchFunc(tab, i);
michael@0 204 });
michael@0 205
michael@0 206 otherMatches.forEach(function(tab,i) {
michael@0 207 otherFunc(tab, i+matches.length);
michael@0 208 });
michael@0 209
michael@0 210 unmatched.forEach(function(tab, i) {
michael@0 211 unmatchFunc(tab, i);
michael@0 212 });
michael@0 213 },
michael@0 214
michael@0 215 // ----------
michael@0 216 // Function: _scorePatternMatch
michael@0 217 // Given a pattern string, returns a score between 0 and 1 of how well
michael@0 218 // that pattern matches the original string. It mimics the heuristics
michael@0 219 // of the Mac application launcher Quicksilver.
michael@0 220 _scorePatternMatch: function TabMatcher__scorePatternMatch(pattern, matched, offset) {
michael@0 221 offset = offset || 0;
michael@0 222 pattern = pattern.toLowerCase();
michael@0 223 matched = matched.toLowerCase();
michael@0 224
michael@0 225 if (pattern.length == 0)
michael@0 226 return 0.9;
michael@0 227 if (pattern.length > matched.length)
michael@0 228 return 0.0;
michael@0 229
michael@0 230 for (let i = pattern.length; i > 0; i--) {
michael@0 231 let sub_pattern = pattern.substring(0,i);
michael@0 232 let index = matched.indexOf(sub_pattern);
michael@0 233
michael@0 234 if (index < 0)
michael@0 235 continue;
michael@0 236 if (index + pattern.length > matched.length + offset)
michael@0 237 continue;
michael@0 238
michael@0 239 let next_string = matched.substring(index+sub_pattern.length);
michael@0 240 let next_pattern = null;
michael@0 241
michael@0 242 if (i >= pattern.length)
michael@0 243 next_pattern = '';
michael@0 244 else
michael@0 245 next_pattern = pattern.substring(i);
michael@0 246
michael@0 247 let remaining_score = this._scorePatternMatch(next_pattern, next_string, offset + index);
michael@0 248
michael@0 249 if (remaining_score > 0) {
michael@0 250 let score = matched.length-next_string.length;
michael@0 251
michael@0 252 if (index != 0) {
michael@0 253 let c = matched.charCodeAt(index-1);
michael@0 254 if (c == 32 || c == 9) {
michael@0 255 for (let j = (index - 2); j >= 0; j--) {
michael@0 256 c = matched.charCodeAt(j);
michael@0 257 score -= ((c == 32 || c == 9) ? 1 : 0.15);
michael@0 258 }
michael@0 259 } else {
michael@0 260 score -= index;
michael@0 261 }
michael@0 262 }
michael@0 263
michael@0 264 score += remaining_score * next_string.length;
michael@0 265 score /= matched.length;
michael@0 266 return score;
michael@0 267 }
michael@0 268 }
michael@0 269 return 0.0;
michael@0 270 }
michael@0 271 };
michael@0 272
michael@0 273 // ##########
michael@0 274 // Class: TabHandlers
michael@0 275 //
michael@0 276 // A object that handles all of the event handlers.
michael@0 277 let TabHandlers = {
michael@0 278 _mouseDownLocation: null,
michael@0 279
michael@0 280 // ---------
michael@0 281 // Function: onMatch
michael@0 282 // Adds styles and event listeners to the matched tab items.
michael@0 283 onMatch: function TabHandlers_onMatch(tab, index) {
michael@0 284 tab.addClass("onTop");
michael@0 285 index != 0 ? tab.addClass("notMainMatch") : tab.removeClass("notMainMatch");
michael@0 286
michael@0 287 // Remove any existing handlers before adding the new ones.
michael@0 288 // If we don't do this, then we may add more handlers than
michael@0 289 // we remove.
michael@0 290 tab.$canvas
michael@0 291 .unbind("mousedown", TabHandlers._hideHandler)
michael@0 292 .unbind("mouseup", TabHandlers._showHandler);
michael@0 293
michael@0 294 tab.$canvas
michael@0 295 .mousedown(TabHandlers._hideHandler)
michael@0 296 .mouseup(TabHandlers._showHandler);
michael@0 297 },
michael@0 298
michael@0 299 // ---------
michael@0 300 // Function: onUnmatch
michael@0 301 // Removes styles and event listeners from the unmatched tab items.
michael@0 302 onUnmatch: function TabHandlers_onUnmatch(tab, index) {
michael@0 303 tab.$container.removeClass("onTop");
michael@0 304 tab.removeClass("notMainMatch");
michael@0 305
michael@0 306 tab.$canvas
michael@0 307 .unbind("mousedown", TabHandlers._hideHandler)
michael@0 308 .unbind("mouseup", TabHandlers._showHandler);
michael@0 309 },
michael@0 310
michael@0 311 // ---------
michael@0 312 // Function: onOther
michael@0 313 // Removes styles and event listeners from the unmatched tabs.
michael@0 314 onOther: function TabHandlers_onOther(tab, index) {
michael@0 315 // Unlike the other on* functions, in this function tab can
michael@0 316 // either be a <TabItem> or a <xul:tab>. In other functions
michael@0 317 // it is always a <TabItem>. Also note that index is offset
michael@0 318 // by the number of matches within the window.
michael@0 319 let item = iQ("<div/>")
michael@0 320 .addClass("inlineMatch")
michael@0 321 .click(function TabHandlers_onOther_click(event) {
michael@0 322 Search.hide(event);
michael@0 323 TabUtils.focus(tab);
michael@0 324 });
michael@0 325
michael@0 326 iQ("<img/>")
michael@0 327 .attr("src", TabUtils.faviconURLOf(tab))
michael@0 328 .appendTo(item);
michael@0 329
michael@0 330 iQ("<span/>")
michael@0 331 .text(TabUtils.nameOf(tab))
michael@0 332 .appendTo(item);
michael@0 333
michael@0 334 index != 0 ? item.addClass("notMainMatch") : item.removeClass("notMainMatch");
michael@0 335 item.appendTo("#results");
michael@0 336 iQ("#otherresults").show();
michael@0 337 },
michael@0 338
michael@0 339 // ---------
michael@0 340 // Function: _hideHandler
michael@0 341 // Performs when mouse down on a canvas of tab item.
michael@0 342 _hideHandler: function TabHandlers_hideHandler(event) {
michael@0 343 iQ("#search").fadeOut();
michael@0 344 iQ("#searchshade").fadeOut();
michael@0 345 TabHandlers._mouseDownLocation = {x:event.clientX, y:event.clientY};
michael@0 346 },
michael@0 347
michael@0 348 // ---------
michael@0 349 // Function: _showHandler
michael@0 350 // Performs when mouse up on a canvas of tab item.
michael@0 351 _showHandler: function TabHandlers_showHandler(event) {
michael@0 352 // If the user clicks on a tab without moving the mouse then
michael@0 353 // they are zooming into the tab and we need to exit search
michael@0 354 // mode.
michael@0 355 if (TabHandlers._mouseDownLocation.x == event.clientX &&
michael@0 356 TabHandlers._mouseDownLocation.y == event.clientY) {
michael@0 357 Search.hide();
michael@0 358 return;
michael@0 359 }
michael@0 360
michael@0 361 iQ("#searchshade").show();
michael@0 362 iQ("#search").show();
michael@0 363 iQ("#searchbox")[0].focus();
michael@0 364 // Marshal the search.
michael@0 365 setTimeout(Search.perform, 0);
michael@0 366 }
michael@0 367 };
michael@0 368
michael@0 369 // ##########
michael@0 370 // Class: Search
michael@0 371 //
michael@0 372 // A object that handles the search feature.
michael@0 373 let Search = {
michael@0 374 _initiatedBy: "",
michael@0 375 _blockClick: false,
michael@0 376 _currentHandler: null,
michael@0 377
michael@0 378 // ----------
michael@0 379 // Function: toString
michael@0 380 // Prints [Search] for debug use.
michael@0 381 toString: function Search_toString() {
michael@0 382 return "[Search]";
michael@0 383 },
michael@0 384
michael@0 385 // ----------
michael@0 386 // Function: init
michael@0 387 // Initializes the searchbox to be focused, and everything else to be hidden,
michael@0 388 // and to have everything have the appropriate event handlers.
michael@0 389 init: function Search_init() {
michael@0 390 let self = this;
michael@0 391
michael@0 392 iQ("#search").hide();
michael@0 393 iQ("#searchshade").hide().mousedown(function Search_init_shade_mousedown(event) {
michael@0 394 if (event.target.id != "searchbox" && !self._blockClick)
michael@0 395 self.hide();
michael@0 396 });
michael@0 397
michael@0 398 iQ("#searchbox").keyup(function Search_init_box_keyup() {
michael@0 399 self.perform();
michael@0 400 })
michael@0 401 .attr("title", tabviewString("button.searchTabs"));
michael@0 402
michael@0 403 iQ("#searchbutton").mousedown(function Search_init_button_mousedown() {
michael@0 404 self._initiatedBy = "buttonclick";
michael@0 405 self.ensureShown();
michael@0 406 self.switchToInMode();
michael@0 407 })
michael@0 408 .attr("title", tabviewString("button.searchTabs"));
michael@0 409
michael@0 410 window.addEventListener("focus", function Search_init_window_focus() {
michael@0 411 if (self.isEnabled()) {
michael@0 412 self._blockClick = true;
michael@0 413 setTimeout(function() {
michael@0 414 self._blockClick = false;
michael@0 415 }, 0);
michael@0 416 }
michael@0 417 }, false);
michael@0 418
michael@0 419 this.switchToBeforeMode();
michael@0 420 },
michael@0 421
michael@0 422 // ----------
michael@0 423 // Function: _beforeSearchKeyHandler
michael@0 424 // Handles all keydown before the search interface is brought up.
michael@0 425 _beforeSearchKeyHandler: function Search__beforeSearchKeyHandler(event) {
michael@0 426 // Only match reasonable text-like characters for quick search.
michael@0 427 if (event.altKey || event.ctrlKey || event.metaKey)
michael@0 428 return;
michael@0 429
michael@0 430 if ((event.keyCode > 0 && event.keyCode <= event.DOM_VK_DELETE) ||
michael@0 431 event.keyCode == event.DOM_VK_CONTEXT_MENU ||
michael@0 432 event.keyCode == event.DOM_VK_SLEEP ||
michael@0 433 (event.keyCode >= event.DOM_VK_F1 &&
michael@0 434 event.keyCode <= event.DOM_VK_SCROLL_LOCK) ||
michael@0 435 event.keyCode == event.DOM_VK_META ||
michael@0 436 event.keyCode == 91 || // 91 = left windows key
michael@0 437 event.keyCode == 92 || // 92 = right windows key
michael@0 438 (!event.keyCode && !event.charCode)) {
michael@0 439 return;
michael@0 440 }
michael@0 441
michael@0 442 // If we are already in an input field, allow typing as normal.
michael@0 443 if (event.target.nodeName == "INPUT")
michael@0 444 return;
michael@0 445
michael@0 446 // / is used to activate the search feature so the key shouldn't be entered
michael@0 447 // into the search box.
michael@0 448 if (event.keyCode == KeyEvent.DOM_VK_SLASH) {
michael@0 449 event.stopPropagation();
michael@0 450 event.preventDefault();
michael@0 451 }
michael@0 452
michael@0 453 this.switchToInMode();
michael@0 454 this._initiatedBy = "keydown";
michael@0 455 this.ensureShown(true);
michael@0 456 },
michael@0 457
michael@0 458 // ----------
michael@0 459 // Function: _inSearchKeyHandler
michael@0 460 // Handles all keydown while search mode.
michael@0 461 _inSearchKeyHandler: function Search__inSearchKeyHandler(event) {
michael@0 462 let term = iQ("#searchbox").val();
michael@0 463 if ((event.keyCode == event.DOM_VK_ESCAPE) ||
michael@0 464 (event.keyCode == event.DOM_VK_BACK_SPACE && term.length <= 1 &&
michael@0 465 this._initiatedBy == "keydown")) {
michael@0 466 this.hide(event);
michael@0 467 return;
michael@0 468 }
michael@0 469
michael@0 470 let matcher = this.createSearchTabMatcher();
michael@0 471 let matches = matcher.matched();
michael@0 472 let others = matcher.matchedTabsFromOtherWindows();
michael@0 473 if (event.keyCode == event.DOM_VK_RETURN &&
michael@0 474 (matches.length > 0 || others.length > 0)) {
michael@0 475 this.hide(event);
michael@0 476 if (matches.length > 0)
michael@0 477 matches[0].zoomIn();
michael@0 478 else
michael@0 479 TabUtils.focus(others[0]);
michael@0 480 }
michael@0 481 },
michael@0 482
michael@0 483 // ----------
michael@0 484 // Function: switchToBeforeMode
michael@0 485 // Make sure the event handlers are appropriate for the before-search mode.
michael@0 486 switchToBeforeMode: function Search_switchToBeforeMode() {
michael@0 487 let self = this;
michael@0 488 if (this._currentHandler)
michael@0 489 iQ(window).unbind("keydown", this._currentHandler);
michael@0 490 this._currentHandler = function Search_switchToBeforeMode_handler(event) {
michael@0 491 self._beforeSearchKeyHandler(event);
michael@0 492 }
michael@0 493 iQ(window).keydown(this._currentHandler);
michael@0 494 },
michael@0 495
michael@0 496 // ----------
michael@0 497 // Function: switchToInMode
michael@0 498 // Make sure the event handlers are appropriate for the in-search mode.
michael@0 499 switchToInMode: function Search_switchToInMode() {
michael@0 500 let self = this;
michael@0 501 if (this._currentHandler)
michael@0 502 iQ(window).unbind("keydown", this._currentHandler);
michael@0 503 this._currentHandler = function Search_switchToInMode_handler(event) {
michael@0 504 self._inSearchKeyHandler(event);
michael@0 505 }
michael@0 506 iQ(window).keydown(this._currentHandler);
michael@0 507 },
michael@0 508
michael@0 509 createSearchTabMatcher: function Search_createSearchTabMatcher() {
michael@0 510 return new TabMatcher(iQ("#searchbox").val());
michael@0 511 },
michael@0 512
michael@0 513 // ----------
michael@0 514 // Function: isEnabled
michael@0 515 // Checks whether search mode is enabled or not.
michael@0 516 isEnabled: function Search_isEnabled() {
michael@0 517 return iQ("#search").css("display") != "none";
michael@0 518 },
michael@0 519
michael@0 520 // ----------
michael@0 521 // Function: hide
michael@0 522 // Hides search mode.
michael@0 523 hide: function Search_hide(event) {
michael@0 524 if (!this.isEnabled())
michael@0 525 return;
michael@0 526
michael@0 527 iQ("#searchbox").val("");
michael@0 528 iQ("#searchshade").hide();
michael@0 529 iQ("#search").hide();
michael@0 530
michael@0 531 iQ("#searchbutton").css({ opacity:.8 });
michael@0 532
michael@0 533 #ifdef XP_MACOSX
michael@0 534 UI.setTitlebarColors(true);
michael@0 535 #endif
michael@0 536
michael@0 537 this.perform();
michael@0 538 this.switchToBeforeMode();
michael@0 539
michael@0 540 if (event) {
michael@0 541 // when hiding the search mode, we need to prevent the keypress handler
michael@0 542 // in UI__setTabViewFrameKeyHandlers to handle the key press again. e.g. Esc
michael@0 543 // which is already handled by the key down in this class.
michael@0 544 if (event.type == "keydown")
michael@0 545 UI.ignoreKeypressForSearch = true;
michael@0 546 event.preventDefault();
michael@0 547 event.stopPropagation();
michael@0 548 }
michael@0 549
michael@0 550 // Return focus to the tab window
michael@0 551 UI.blurAll();
michael@0 552 gTabViewFrame.contentWindow.focus();
michael@0 553
michael@0 554 let newEvent = document.createEvent("Events");
michael@0 555 newEvent.initEvent("tabviewsearchdisabled", false, false);
michael@0 556 dispatchEvent(newEvent);
michael@0 557 },
michael@0 558
michael@0 559 // ----------
michael@0 560 // Function: perform
michael@0 561 // Performs a search.
michael@0 562 perform: function Search_perform() {
michael@0 563 let matcher = this.createSearchTabMatcher();
michael@0 564
michael@0 565 // Remove any previous other-window search results and
michael@0 566 // hide the display area.
michael@0 567 iQ("#results").empty();
michael@0 568 iQ("#otherresults").hide();
michael@0 569 iQ("#otherresults>.label").text(tabviewString("search.otherWindowTabs"));
michael@0 570
michael@0 571 matcher.doSearch(TabHandlers.onMatch, TabHandlers.onUnmatch, TabHandlers.onOther);
michael@0 572 },
michael@0 573
michael@0 574 // ----------
michael@0 575 // Function: ensureShown
michael@0 576 // Ensures the search feature is displayed. If not, display it.
michael@0 577 // Parameters:
michael@0 578 // - a boolean indicates whether this is triggered by a keypress or not
michael@0 579 ensureShown: function Search_ensureShown(activatedByKeypress) {
michael@0 580 let $search = iQ("#search");
michael@0 581 let $searchShade = iQ("#searchshade");
michael@0 582 let $searchbox = iQ("#searchbox");
michael@0 583 iQ("#searchbutton").css({ opacity: 1 });
michael@0 584
michael@0 585 // NOTE: when this function is called by keydown handler, next keypress
michael@0 586 // event or composition events of IME will be fired on the focused editor.
michael@0 587 function dispatchTabViewSearchEnabledEvent() {
michael@0 588 let newEvent = document.createEvent("Events");
michael@0 589 newEvent.initEvent("tabviewsearchenabled", false, false);
michael@0 590 dispatchEvent(newEvent);
michael@0 591 };
michael@0 592
michael@0 593 if (!this.isEnabled()) {
michael@0 594 $searchShade.show();
michael@0 595 $search.show();
michael@0 596
michael@0 597 #ifdef XP_MACOSX
michael@0 598 UI.setTitlebarColors({active: "#717171", inactive: "#EDEDED"});
michael@0 599 #endif
michael@0 600
michael@0 601 if (activatedByKeypress) {
michael@0 602 // set the focus so key strokes are entered into the textbox.
michael@0 603 $searchbox[0].focus();
michael@0 604 dispatchTabViewSearchEnabledEvent();
michael@0 605 } else {
michael@0 606 // marshal the focusing, otherwise it ends up with searchbox[0].focus gets
michael@0 607 // called before the search button gets the focus after being pressed.
michael@0 608 setTimeout(function setFocusAndDispatchSearchEnabledEvent() {
michael@0 609 $searchbox[0].focus();
michael@0 610 dispatchTabViewSearchEnabledEvent();
michael@0 611 }, 0);
michael@0 612 }
michael@0 613 }
michael@0 614 }
michael@0 615 };
michael@0 616

mercurial