Home | History | Annotate | Download | only in front_end
      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  */
     35 WebInspector.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 
     47 WebInspector.FilterBar.Events = {
     48     FiltersToggled: "FiltersToggled"
     49 }
     50 
     51 WebInspector.FilterBar.FilterBarState = {
     52     Inactive : "inactive",
     53     Active : "active",
     54     Shown : "shown"
     55 };
     56 
     57 WebInspector.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  */
    145 WebInspector.FilterUI = function()
    146 {
    147 }
    148 
    149 WebInspector.FilterUI.Events = {
    150     FilterChanged: "FilterChanged"
    151 }
    152 
    153 WebInspector.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  */
    171 WebInspector.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 
    199 WebInspector.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  */
    285 WebInspector.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 
    298 WebInspector.NamedBitSetFilterUI.ALL_TYPES = "all";
    299 
    300 WebInspector.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  */
    413 WebInspector.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 
    431 WebInspector.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  */
    480 WebInspector.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 
    497 WebInspector.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