1/* 2 * Copyright (C) 2013 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31/** 32 * @constructor 33 * @extends {WebInspector.Object} 34 */ 35WebInspector.FilterBar = function() 36{ 37 this._filtersShown = false; 38 this._element = document.createElement("div"); 39 this._element.className = "hbox"; 40 41 this._filterButton = new WebInspector.StatusBarButton(WebInspector.UIString("Filter"), "filters-toggle", 3); 42 this._filterButton.element.addEventListener("mousedown", this._handleFilterButtonClick.bind(this), false); 43 44 this._filters = []; 45} 46 47WebInspector.FilterBar.Events = { 48 FiltersToggled: "FiltersToggled" 49} 50 51WebInspector.FilterBar.FilterBarState = { 52 Inactive : "inactive", 53 Active : "active", 54 Shown : "shown" 55}; 56 57WebInspector.FilterBar.prototype = { 58 /** 59 * @return {!WebInspector.StatusBarButton} 60 */ 61 filterButton: function() 62 { 63 return this._filterButton; 64 }, 65 66 /** 67 * @return {!Element} 68 */ 69 filtersElement: function() 70 { 71 return this._element; 72 }, 73 74 /** 75 * @return {boolean} 76 */ 77 filtersToggled: function() 78 { 79 return this._filtersShown; 80 }, 81 82 /** 83 * @param {!WebInspector.FilterUI} filter 84 */ 85 addFilter: function(filter) 86 { 87 this._filters.push(filter); 88 this._element.appendChild(filter.element()); 89 filter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged, this); 90 this._updateFilterButton(); 91 }, 92 93 /** 94 * @param {!WebInspector.Event} event 95 */ 96 _filterChanged: function(event) 97 { 98 this._updateFilterButton(); 99 }, 100 101 /** 102 * @return {string} 103 */ 104 _filterBarState: function() 105 { 106 if (this._filtersShown) 107 return WebInspector.FilterBar.FilterBarState.Shown; 108 var isActive = false; 109 for (var i = 0; i < this._filters.length; ++i) { 110 if (this._filters[i].isActive()) 111 return WebInspector.FilterBar.FilterBarState.Active; 112 } 113 return WebInspector.FilterBar.FilterBarState.Inactive; 114 }, 115 116 _updateFilterButton: function() 117 { 118 this._filterButton.state = this._filterBarState(); 119 }, 120 121 /** 122 * @param {?Event} event 123 */ 124 _handleFilterButtonClick: function(event) 125 { 126 this._filtersShown = !this._filtersShown; 127 this._updateFilterButton(); 128 this.dispatchEventToListeners(WebInspector.FilterBar.Events.FiltersToggled, this._filtersShown); 129 }, 130 131 clear: function() 132 { 133 this._element.removeChildren(); 134 this._filters = []; 135 this._updateFilterButton(); 136 }, 137 138 __proto__: WebInspector.Object.prototype 139} 140 141/** 142 * @interface 143 * @extends {WebInspector.EventTarget} 144 */ 145WebInspector.FilterUI = function() 146{ 147} 148 149WebInspector.FilterUI.Events = { 150 FilterChanged: "FilterChanged" 151} 152 153WebInspector.FilterUI.prototype = { 154 /** 155 * @return {boolean} 156 */ 157 isActive: function() { }, 158 159 /** 160 * @return {!Element} 161 */ 162 element: function() { } 163} 164 165/** 166 * @constructor 167 * @implements {WebInspector.FilterUI} 168 * @extends {WebInspector.Object} 169 * @param {boolean=} supportRegex 170 */ 171WebInspector.TextFilterUI = function(supportRegex) 172{ 173 this._supportRegex = !!supportRegex; 174 this._regex = null; 175 176 this._filterElement = document.createElement("div"); 177 this._filterElement.className = "filter-text-filter"; 178 179 this._filterInputElement = this._filterElement.createChild("input", "search-replace toolbar-replace-control"); 180 this._filterInputElement.placeholder = WebInspector.UIString("Filter"); 181 this._filterInputElement.id = "filter-input-field"; 182 this._filterInputElement.addEventListener("mousedown", this._onFilterFieldManualFocus.bind(this), false); // when the search field is manually selected 183 this._filterInputElement.addEventListener("input", this._onInput.bind(this), false); 184 this._filterInputElement.addEventListener("change", this._onInput.bind(this), false); 185 186 if (this._supportRegex) { 187 this._filterElement.classList.add("supports-regex"); 188 this._regexCheckBox = this._filterElement.createChild("input"); 189 this._regexCheckBox.type = "checkbox"; 190 this._regexCheckBox.id = "text-filter-regex"; 191 this._regexCheckBox.addEventListener("change", this._onInput.bind(this), false); 192 193 this._regexLabel = this._filterElement.createChild("label"); 194 this._regexLabel.htmlFor = "text-filter-regex"; 195 this._regexLabel.textContent = WebInspector.UIString("Regex"); 196 } 197} 198 199WebInspector.TextFilterUI.prototype = { 200 /** 201 * @return {boolean} 202 */ 203 isActive: function() 204 { 205 return !!this._filterInputElement.value; 206 }, 207 208 /** 209 * @return {!Element} 210 */ 211 element: function() 212 { 213 return this._filterElement; 214 }, 215 216 /** 217 * @return {string} 218 */ 219 value: function() 220 { 221 return this._filterInputElement.value; 222 }, 223 224 /** 225 * @param {string} value 226 */ 227 setValue: function(value) 228 { 229 this._filterInputElement.value = value; 230 this._valueChanged(); 231 }, 232 233 /** 234 * @return {?RegExp} 235 */ 236 regex: function() 237 { 238 return this._regex; 239 }, 240 241 /** 242 * @param {?Event} event 243 */ 244 _onFilterFieldManualFocus: function(event) 245 { 246 WebInspector.setCurrentFocusElement(event.target); 247 }, 248 249 /** 250 * @param {!WebInspector.Event} event 251 */ 252 _onInput: function(event) 253 { 254 this._valueChanged(); 255 }, 256 257 _valueChanged: function() { 258 var filterQuery = this.value(); 259 260 this._regex = null; 261 this._filterInputElement.classList.remove("filter-text-invalid"); 262 if (filterQuery) { 263 if (this._supportRegex && this._regexCheckBox.checked) { 264 try { 265 this._regex = new RegExp(filterQuery, "i"); 266 } catch (e) { 267 this._filterInputElement.classList.add("filter-text-invalid"); 268 } 269 } else { 270 this._regex = createPlainTextSearchRegex(filterQuery, "i"); 271 } 272 } 273 274 this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null); 275 }, 276 277 __proto__: WebInspector.Object.prototype 278} 279 280/** 281 * @constructor 282 * @implements {WebInspector.FilterUI} 283 * @extends {WebInspector.Object} 284 */ 285WebInspector.NamedBitSetFilterUI = function() 286{ 287 this._filtersElement = document.createElement("div"); 288 this._filtersElement.className = "filter-bitset-filter status-bar-item"; 289 this._filtersElement.title = WebInspector.UIString("Use %s Click to select multiple types.", WebInspector.KeyboardShortcut.shortcutToString("", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta)); 290 291 this._allowedTypes = {}; 292 this._typeFilterElements = {}; 293 this.addBit(WebInspector.NamedBitSetFilterUI.ALL_TYPES, WebInspector.UIString("All")); 294 this._filtersElement.createChild("div", "filter-bitset-filter-divider"); 295 this._toggleTypeFilter(WebInspector.NamedBitSetFilterUI.ALL_TYPES, false); 296} 297 298WebInspector.NamedBitSetFilterUI.ALL_TYPES = "all"; 299 300WebInspector.NamedBitSetFilterUI.prototype = { 301 /** 302 * @return {boolean} 303 */ 304 isActive: function() 305 { 306 return !this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES]; 307 }, 308 309 /** 310 * @param {!WebInspector.Setting} setting 311 */ 312 bindSetting: function(setting) 313 { 314 console.assert(!this._setting); 315 this._setting = setting; 316 setting.addChangeListener(this._settingChanged.bind(this)); 317 this._settingChanged(); 318 }, 319 320 /** 321 * @return {!Element} 322 */ 323 element: function() 324 { 325 return this._filtersElement; 326 }, 327 328 /** 329 * @param {string} typeName 330 * @return {boolean} 331 */ 332 accept: function(typeName) 333 { 334 return !!this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] || !!this._allowedTypes[typeName]; 335 }, 336 337 _settingChanged: function() 338 { 339 var allowedTypes = this._setting.get(); 340 this._allowedTypes = {}; 341 for (var typeName in this._typeFilterElements) { 342 if (allowedTypes[typeName]) 343 this._allowedTypes[typeName] = true; 344 } 345 this._update(); 346 }, 347 348 _update: function() 349 { 350 if ((Object.keys(this._allowedTypes).length === 0) || this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES]) { 351 this._allowedTypes = {}; 352 this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] = true; 353 } 354 for (var typeName in this._typeFilterElements) 355 this._typeFilterElements[typeName].enableStyleClass("selected", this._allowedTypes[typeName]); 356 this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null); 357 }, 358 359 /** 360 * @param {string} name 361 * @param {string} label 362 */ 363 addBit: function(name, label) 364 { 365 var typeFilterElement = this._filtersElement.createChild("li", name); 366 typeFilterElement.typeName = name; 367 typeFilterElement.createTextChild(label); 368 typeFilterElement.addEventListener("click", this._onTypeFilterClicked.bind(this), false); 369 this._typeFilterElements[name] = typeFilterElement; 370 }, 371 372 /** 373 * @param {!Event} e 374 */ 375 _onTypeFilterClicked: function(e) 376 { 377 var toggle; 378 if (WebInspector.isMac()) 379 toggle = e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey; 380 else 381 toggle = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey; 382 this._toggleTypeFilter(e.target.typeName, toggle); 383 }, 384 385 /** 386 * @param {string} typeName 387 * @param {boolean} allowMultiSelect 388 */ 389 _toggleTypeFilter: function(typeName, allowMultiSelect) 390 { 391 if (allowMultiSelect && typeName !== WebInspector.NamedBitSetFilterUI.ALL_TYPES) 392 this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] = false; 393 else 394 this._allowedTypes = {}; 395 396 this._allowedTypes[typeName] = !this._allowedTypes[typeName]; 397 398 if (this._setting) 399 this._setting.set(this._allowedTypes); 400 else 401 this._update(); 402 }, 403 404 __proto__: WebInspector.Object.prototype 405} 406 407/** 408 * @constructor 409 * @implements {WebInspector.FilterUI} 410 * @extends {WebInspector.Object} 411 * @param {!Array.<{value: *, label: string, title: string}>} options 412 */ 413WebInspector.ComboBoxFilterUI = function(options) 414{ 415 this._filterElement = document.createElement("div"); 416 this._filterElement.className = "filter-combobox-filter"; 417 418 this._options = options; 419 this._filterComboBox = new WebInspector.StatusBarComboBox(this._filterChanged.bind(this)); 420 for (var i = 0; i < options.length; ++i) { 421 var filterOption = options[i]; 422 var option = document.createElement("option"); 423 option.text = filterOption.label; 424 option.title = filterOption.title; 425 this._filterComboBox.addOption(option); 426 this._filterComboBox.element.title = this._filterComboBox.selectedOption().title; 427 } 428 this._filterElement.appendChild(this._filterComboBox.element); 429} 430 431WebInspector.ComboBoxFilterUI.prototype = { 432 /** 433 * @return {boolean} 434 */ 435 isActive: function() 436 { 437 return this._filterComboBox.selectedIndex() !== 0; 438 }, 439 440 /** 441 * @return {!Element} 442 */ 443 element: function() 444 { 445 return this._filterElement; 446 }, 447 448 /** 449 * @param {string} typeName 450 * @return {*} 451 */ 452 value: function(typeName) 453 { 454 var option = this._options[this._filterComboBox.selectedIndex()]; 455 return option.value; 456 }, 457 458 /** 459 * @param {?Event} event 460 */ 461 _filterChanged: function(event) 462 { 463 var option = this._options[this._filterComboBox.selectedIndex()]; 464 this._filterComboBox.element.title = option.title; 465 this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null); 466 }, 467 468 __proto__: WebInspector.Object.prototype 469} 470 471/** 472 * @constructor 473 * @implements {WebInspector.FilterUI} 474 * @extends {WebInspector.Object} 475 * @param {string} className 476 * @param {string} title 477 * @param {boolean=} activeWhenChecked 478 * @param {!WebInspector.Setting=} setting 479 */ 480WebInspector.CheckboxFilterUI = function(className, title, activeWhenChecked, setting) 481{ 482 this._filterElement = document.createElement("div"); 483 this._filterElement.classList.add("filter-checkbox-filter", "filter-checkbox-filter-" + className); 484 this._activeWhenChecked = !!activeWhenChecked; 485 this._createCheckbox(title); 486 487 if (setting) { 488 this._setting = setting; 489 setting.addChangeListener(this._settingChanged.bind(this)); 490 this._settingChanged(); 491 } else { 492 this._checked = !this._activeWhenChecked; 493 this._update(); 494 } 495} 496 497WebInspector.CheckboxFilterUI.prototype = { 498 /** 499 * @return {boolean} 500 */ 501 isActive: function() 502 { 503 return this._activeWhenChecked === this._checked; 504 }, 505 506 /** 507 * @return {!Element} 508 */ 509 element: function() 510 { 511 return this._filterElement; 512 }, 513 514 /** 515 * @return {boolean} 516 */ 517 checked: function() 518 { 519 return this._checked; 520 }, 521 522 _update: function() 523 { 524 this._checkElement.enableStyleClass("checkbox-filter-checkbox-checked", this._checked); 525 this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null); 526 }, 527 528 _settingChanged: function() 529 { 530 this._checked = this._setting.get(); 531 this._update(); 532 }, 533 534 /** 535 * @param {?Event} event 536 */ 537 _onClick: function(event) 538 { 539 this._checked = !this._checked; 540 if (this._setting) 541 this._setting.set(this._checked); 542 else 543 this._update(); 544 }, 545 546 /** 547 * @param {string} title 548 */ 549 _createCheckbox: function(title) 550 { 551 var label = this._filterElement.createChild("label"); 552 var checkBorder = label.createChild("div", "checkbox-filter-checkbox"); 553 this._checkElement = checkBorder.createChild("div", "checkbox-filter-checkbox-check"); 554 this._filterElement.addEventListener("click", this._onClick.bind(this), false); 555 var typeElement = label.createChild("span", "type"); 556 typeElement.textContent = title; 557 }, 558 559 __proto__: WebInspector.Object.prototype 560} 561