Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / components / FilterBar.js
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("click", 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      * @param {string} name
60      */
61     setName: function(name)
62     {
63         this._stateSetting = WebInspector.settings.createSetting("filterBar-" + name + "-toggled", false);
64         this._setState(this._stateSetting.get());
65     },
66
67     /**
68      * @return {!WebInspector.StatusBarButton}
69      */
70     filterButton: function()
71     {
72         return this._filterButton;
73     },
74
75     /**
76      * @return {!Element}
77      */
78     filtersElement: function()
79     {
80         return this._element;
81     },
82
83     /**
84      * @return {boolean}
85      */
86     filtersToggled: function()
87     {
88         return this._filtersShown;
89     },
90
91     /**
92      * @param {!WebInspector.FilterUI} filter
93      */
94     addFilter: function(filter)
95     {
96         this._filters.push(filter);
97         this._element.appendChild(filter.element());
98         filter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged, this);
99         this._updateFilterButton();
100     },
101
102     /**
103      * @param {!WebInspector.Event} event
104      */
105     _filterChanged: function(event)
106     {
107         this._updateFilterButton();
108     },
109
110     /**
111      * @return {string}
112      */
113     _filterBarState: function()
114     {
115         if (this._filtersShown)
116             return WebInspector.FilterBar.FilterBarState.Shown;
117         var isActive = false;
118         for (var i = 0; i < this._filters.length; ++i) {
119             if (this._filters[i].isActive())
120                 return WebInspector.FilterBar.FilterBarState.Active;
121         }
122         return WebInspector.FilterBar.FilterBarState.Inactive;
123     },
124
125     _updateFilterButton: function()
126     {
127         this._filterButton.state = this._filterBarState();
128     },
129
130     /**
131      * @param {!Event} event
132      */
133     _handleFilterButtonClick: function(event)
134     {
135         this._setState(!this._filtersShown);
136     },
137
138     /**
139      * @param {boolean} filtersShown
140      */
141     _setState: function(filtersShown)
142     {
143         if (this._filtersShown === filtersShown)
144             return;
145
146         this._filtersShown = filtersShown;
147         if (this._stateSetting)
148             this._stateSetting.set(filtersShown);
149
150         this._updateFilterButton();
151         this.dispatchEventToListeners(WebInspector.FilterBar.Events.FiltersToggled, this._filtersShown);
152         if (this._filtersShown) {
153             for (var i = 0; i < this._filters.length; ++i) {
154                 if (this._filters[i] instanceof WebInspector.TextFilterUI) {
155                     var textFilterUI = /** @type {!WebInspector.TextFilterUI} */ (this._filters[i]);
156                     textFilterUI.focus();
157                 }
158             }
159         }
160     },
161
162     clear: function()
163     {
164         this._element.removeChildren();
165         this._filters = [];
166         this._updateFilterButton();
167     },
168
169     __proto__: WebInspector.Object.prototype
170 }
171
172 /**
173  * @interface
174  * @extends {WebInspector.EventTarget}
175  */
176 WebInspector.FilterUI = function()
177 {
178 }
179
180 WebInspector.FilterUI.Events = {
181     FilterChanged: "FilterChanged"
182 }
183
184 WebInspector.FilterUI.prototype = {
185     /**
186      * @return {boolean}
187      */
188     isActive: function() { },
189
190     /**
191      * @return {!Element}
192      */
193     element: function() { }
194 }
195
196 /**
197  * @constructor
198  * @extends {WebInspector.Object}
199  * @implements {WebInspector.FilterUI}
200  * @implements {WebInspector.SuggestBoxDelegate}
201  * @param {boolean=} supportRegex
202  */
203 WebInspector.TextFilterUI = function(supportRegex)
204 {
205     this._supportRegex = !!supportRegex;
206     this._regex = null;
207
208     this._filterElement = document.createElement("div");
209     this._filterElement.className = "filter-text-filter";
210
211     this._filterInputElement = /** @type {!HTMLInputElement} */ (this._filterElement.createChild("input", "search-replace toolbar-replace-control"));
212     this._filterInputElement.placeholder = WebInspector.UIString("Filter");
213     this._filterInputElement.id = "filter-input-field";
214     this._filterInputElement.addEventListener("mousedown", this._onFilterFieldManualFocus.bind(this), false); // when the search field is manually selected
215     this._filterInputElement.addEventListener("input", this._onInput.bind(this), false);
216     this._filterInputElement.addEventListener("change", this._onChange.bind(this), false);
217     this._filterInputElement.addEventListener("keydown", this._onInputKeyDown.bind(this), true);
218     this._filterInputElement.addEventListener("blur", this._onBlur.bind(this), true);
219
220     /** @type {?WebInspector.TextFilterUI.SuggestionBuilder} */
221     this._suggestionBuilder = null;
222
223     this._suggestBox = new WebInspector.SuggestBox(this);
224
225     if (this._supportRegex) {
226         this._filterElement.classList.add("supports-regex");
227         this._regexCheckBox = this._filterElement.createChild("input");
228         this._regexCheckBox.type = "checkbox";
229         this._regexCheckBox.id = "text-filter-regex";
230         this._regexCheckBox.addEventListener("change", this._onInput.bind(this), false);
231
232         this._regexLabel = this._filterElement.createChild("label");
233         this._regexLabel.htmlFor = "text-filter-regex";
234         this._regexLabel.textContent = WebInspector.UIString("Regex");
235     }
236 }
237
238 WebInspector.TextFilterUI.prototype = {
239     /**
240      * @return {boolean}
241      */
242     isActive: function()
243     {
244         return !!this._filterInputElement.value;
245     },
246
247     /**
248      * @return {!Element}
249      */
250     element: function()
251     {
252         return this._filterElement;
253     },
254
255     /**
256      * @return {string}
257      */
258     value: function()
259     {
260         return this._filterInputElement.value;
261     },
262
263     /**
264      * @param {string} value
265      */
266     setValue: function(value)
267     {
268         this._filterInputElement.value = value;
269         this._valueChanged(false);
270     },
271
272     /**
273      * @return {?RegExp}
274      */
275     regex: function()
276     {
277         return this._regex;
278     },
279
280     /**
281      * @param {!Event} event
282      */
283     _onFilterFieldManualFocus: function(event)
284     {
285         WebInspector.setCurrentFocusElement(event.target);
286     },
287
288     /**
289      * @param {!Event} event
290      */
291     _onBlur: function(event)
292     {
293         this._cancelSuggestion();
294     },
295
296     _cancelSuggestion: function()
297     {
298         if (this._suggestionBuilder && this._suggestBox.visible) {
299             this._suggestionBuilder.unapplySuggestion(this._filterInputElement);
300             this._suggestBox.hide();
301         }
302     },
303
304     _onInput: function()
305     {
306         this._valueChanged(true);
307     },
308
309     _onChange: function()
310     {
311         this._valueChanged(false);
312     },
313
314     focus: function()
315     {
316         this._filterInputElement.focus();
317     },
318
319     /**
320      * @param {?WebInspector.TextFilterUI.SuggestionBuilder} suggestionBuilder
321      */
322     setSuggestionBuilder: function(suggestionBuilder)
323     {
324         this._cancelSuggestion();
325         this._suggestionBuilder = suggestionBuilder;
326     },
327
328     _updateSuggestions: function()
329     {
330         if (!this._suggestionBuilder)
331             return;
332         var suggestions = this._suggestionBuilder.buildSuggestions(this._filterInputElement);
333         if (suggestions && suggestions.length) {
334             if (this._suppressSuggestion)
335                 delete this._suppressSuggestion;
336             else
337                 this._suggestionBuilder.applySuggestion(this._filterInputElement, suggestions[0], true);
338             var anchorBox = this._filterInputElement.boxInWindow().relativeTo(new AnchorBox(-3, 0));
339             this._suggestBox.updateSuggestions(anchorBox, suggestions, 0, true, "");
340         } else {
341             this._suggestBox.hide();
342         }
343     },
344
345     /**
346      * @param {boolean} showSuggestions
347      */
348     _valueChanged: function(showSuggestions)
349     {
350         if (showSuggestions)
351             this._updateSuggestions();
352         else
353             this._suggestBox.hide();
354
355         var filterQuery = this.value();
356
357         this._regex = null;
358         this._filterInputElement.classList.remove("filter-text-invalid");
359         if (filterQuery) {
360             if (this._supportRegex && this._regexCheckBox.checked) {
361                 try {
362                     this._regex = new RegExp(filterQuery, "i");
363                 } catch (e) {
364                     this._filterInputElement.classList.add("filter-text-invalid");
365                 }
366             } else {
367                 this._regex = createPlainTextSearchRegex(filterQuery, "i");
368             }
369         }
370
371         this._dispatchFilterChanged();
372     },
373
374     _dispatchFilterChanged: function()
375     {
376         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
377     },
378
379     /**
380      * @param {!Event} event
381      * @return {boolean}
382      */
383     _onInputKeyDown: function(event)
384     {
385         var handled = false;
386         if (event.keyIdentifier === "U+0008") { // Backspace
387             this._suppressSuggestion = true;
388         } else if (this._suggestBox.visible()) {
389             if (event.keyIdentifier === "U+001B") { // Esc
390                 this._cancelSuggestion();
391                 handled = true;
392             } else if (event.keyIdentifier === "U+0009") { // Tab
393                 this._suggestBox.acceptSuggestion();
394                 this._valueChanged(true);
395                 handled = true;
396             } else {
397                 handled = this._suggestBox.keyPressed(/** @type {!KeyboardEvent} */ (event));
398             }
399         }
400         if (handled)
401             event.consume(true);
402         return handled;
403     },
404
405     /**
406      * @override
407      * @param {string} suggestion
408      * @param {boolean=} isIntermediateSuggestion
409      */
410     applySuggestion: function(suggestion, isIntermediateSuggestion)
411     {
412         if (!this._suggestionBuilder)
413             return;
414         this._suggestionBuilder.applySuggestion(this._filterInputElement, suggestion, !!isIntermediateSuggestion);
415         if (isIntermediateSuggestion)
416             this._dispatchFilterChanged();
417     },
418
419     /** @override */
420     acceptSuggestion: function()
421     {
422         this._filterInputElement.scrollLeft = this._filterInputElement.scrollWidth;
423         this._valueChanged(true);
424     },
425
426     __proto__: WebInspector.Object.prototype
427 }
428
429 /**
430  * @interface
431  */
432 WebInspector.TextFilterUI.SuggestionBuilder = function()
433 {
434 }
435
436 WebInspector.TextFilterUI.SuggestionBuilder.prototype = {
437     /**
438      * @param {!HTMLInputElement} input
439      * @return {?Array.<string>}
440      */
441     buildSuggestions: function(input) { },
442
443     /**
444      * @param {!HTMLInputElement} input
445      * @param {string} suggestion
446      * @param {boolean} isIntermediate
447      */
448     applySuggestion: function(input, suggestion, isIntermediate) { },
449
450     /**
451      * @param {!HTMLInputElement} input
452      */
453     unapplySuggestion: function(input) { }
454 }
455
456 /**
457  * @constructor
458  * @extends {WebInspector.Object}
459  * @implements {WebInspector.FilterUI}
460  * @param {!Array.<!WebInspector.NamedBitSetFilterUI.Item>} items
461  * @param {!WebInspector.Setting=} setting
462  */
463 WebInspector.NamedBitSetFilterUI = function(items, setting)
464 {
465     this._filtersElement = document.createElement("div");
466     this._filtersElement.className = "filter-bitset-filter status-bar-item";
467     this._filtersElement.title = WebInspector.UIString("Use %s Click to select multiple types.", WebInspector.KeyboardShortcut.shortcutToString("", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta));
468
469     this._allowedTypes = {};
470     this._typeFilterElements = {};
471     this._addBit(WebInspector.NamedBitSetFilterUI.ALL_TYPES, WebInspector.UIString("All"));
472     this._filtersElement.createChild("div", "filter-bitset-filter-divider");
473
474     for (var i = 0; i < items.length; ++i)
475         this._addBit(items[i].name, items[i].label);
476
477     if (setting) {
478         this._setting = setting;
479         setting.addChangeListener(this._settingChanged.bind(this));
480         this._settingChanged();
481     } else {
482         this._toggleTypeFilter(WebInspector.NamedBitSetFilterUI.ALL_TYPES, false);
483     }
484 }
485
486 /** @typedef {{name: string, label: string}} */
487 WebInspector.NamedBitSetFilterUI.Item;
488
489 WebInspector.NamedBitSetFilterUI.ALL_TYPES = "all";
490
491 WebInspector.NamedBitSetFilterUI.prototype = {
492     /**
493      * @return {boolean}
494      */
495     isActive: function()
496     {
497         return !this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES];
498     },
499
500     /**
501      * @return {!Element}
502      */
503     element: function()
504     {
505         return this._filtersElement;
506     },
507
508     /**
509      * @param {string} typeName
510      * @return {boolean}
511      */
512     accept: function(typeName)
513     {
514         return !!this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] || !!this._allowedTypes[typeName];
515     },
516
517     _settingChanged: function()
518     {
519         var allowedTypes = this._setting.get();
520         this._allowedTypes = {};
521         for (var typeName in this._typeFilterElements) {
522             if (allowedTypes[typeName])
523                 this._allowedTypes[typeName] = true;
524         }
525         this._update();
526     },
527
528     _update: function()
529     {
530         if ((Object.keys(this._allowedTypes).length === 0) || this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES]) {
531             this._allowedTypes = {};
532             this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] = true;
533         }
534         for (var typeName in this._typeFilterElements)
535             this._typeFilterElements[typeName].classList.toggle("selected", this._allowedTypes[typeName]);
536         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
537     },
538
539     /**
540      * @param {string} name
541      * @param {string} label
542      */
543     _addBit: function(name, label)
544     {
545         var typeFilterElement = this._filtersElement.createChild("li", name);
546         typeFilterElement.typeName = name;
547         typeFilterElement.createTextChild(label);
548         typeFilterElement.addEventListener("click", this._onTypeFilterClicked.bind(this), false);
549         this._typeFilterElements[name] = typeFilterElement;
550     },
551
552     /**
553      * @param {!Event} e
554      */
555     _onTypeFilterClicked: function(e)
556     {
557         var toggle;
558         if (WebInspector.isMac())
559             toggle = e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey;
560         else
561             toggle = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey;
562         this._toggleTypeFilter(e.target.typeName, toggle);
563     },
564
565     /**
566      * @param {string} typeName
567      * @param {boolean} allowMultiSelect
568      */
569     _toggleTypeFilter: function(typeName, allowMultiSelect)
570     {
571         if (allowMultiSelect && typeName !== WebInspector.NamedBitSetFilterUI.ALL_TYPES)
572             this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] = false;
573         else
574             this._allowedTypes = {};
575
576         this._allowedTypes[typeName] = !this._allowedTypes[typeName];
577
578         if (this._setting)
579             this._setting.set(this._allowedTypes);
580         else
581             this._update();
582     },
583
584     __proto__: WebInspector.Object.prototype
585 }
586
587 /**
588  * @constructor
589  * @implements {WebInspector.FilterUI}
590  * @extends {WebInspector.Object}
591  * @param {!Array.<!{value: *, label: string, title: string}>} options
592  */
593 WebInspector.ComboBoxFilterUI = function(options)
594 {
595     this._filterElement = document.createElement("div");
596     this._filterElement.className = "filter-combobox-filter";
597
598     this._options = options;
599     this._filterComboBox = new WebInspector.StatusBarComboBox(this._filterChanged.bind(this));
600     for (var i = 0; i < options.length; ++i) {
601         var filterOption = options[i];
602         var option = document.createElement("option");
603         option.text = filterOption.label;
604         option.title = filterOption.title;
605         this._filterComboBox.addOption(option);
606         this._filterComboBox.element.title = this._filterComboBox.selectedOption().title;
607     }
608     this._filterElement.appendChild(this._filterComboBox.element);
609 }
610
611 WebInspector.ComboBoxFilterUI.prototype = {
612     /**
613      * @return {boolean}
614      */
615     isActive: function()
616     {
617         return this._filterComboBox.selectedIndex() !== 0;
618     },
619
620     /**
621      * @return {!Element}
622      */
623     element: function()
624     {
625         return this._filterElement;
626     },
627
628     /**
629      * @param {string} typeName
630      * @return {*}
631      */
632     value: function(typeName)
633     {
634         var option = this._options[this._filterComboBox.selectedIndex()];
635         return option.value;
636     },
637
638     /**
639      * @param {number} index
640      */
641     setSelectedIndex: function(index)
642     {
643         this._filterComboBox.setSelectedIndex(index);
644     },
645
646     /**
647      * @return {number}
648      */
649     selectedIndex: function(index)
650     {
651         return this._filterComboBox.selectedIndex();
652     },
653
654     /**
655      * @param {!Event} event
656      */
657     _filterChanged: function(event)
658     {
659         var option = this._options[this._filterComboBox.selectedIndex()];
660         this._filterComboBox.element.title = option.title;
661         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
662     },
663
664     __proto__: WebInspector.Object.prototype
665 }
666
667 /**
668  * @constructor
669  * @implements {WebInspector.FilterUI}
670  * @extends {WebInspector.Object}
671  * @param {string} className
672  * @param {string} title
673  * @param {boolean=} activeWhenChecked
674  * @param {!WebInspector.Setting=} setting
675  */
676 WebInspector.CheckboxFilterUI = function(className, title, activeWhenChecked, setting)
677 {
678     this._filterElement = document.createElement("div");
679     this._filterElement.classList.add("filter-checkbox-filter", "filter-checkbox-filter-" + className);
680     this._activeWhenChecked = !!activeWhenChecked;
681     this._createCheckbox(title);
682
683     if (setting) {
684         this._setting = setting;
685         setting.addChangeListener(this._settingChanged.bind(this));
686         this._settingChanged();
687     } else {
688         this._checked = !this._activeWhenChecked;
689         this._update();
690     }
691 }
692
693 WebInspector.CheckboxFilterUI.prototype = {
694     /**
695      * @return {boolean}
696      */
697     isActive: function()
698     {
699         return this._activeWhenChecked === this._checked;
700     },
701
702     /**
703      * @return {!Element}
704      */
705     element: function()
706     {
707         return this._filterElement;
708     },
709
710     /**
711      * @return {boolean}
712      */
713     checked: function()
714     {
715         return this._checked;
716     },
717
718     /**
719      * @param {boolean} state
720      */
721     setState: function(state)
722     {
723         this._checked = state;
724         this._update();
725     },
726
727     _update: function()
728     {
729         this._checkElement.classList.toggle("checkbox-filter-checkbox-checked", this._checked);
730         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
731     },
732
733     _settingChanged: function()
734     {
735         this._checked = this._setting.get();
736         this._update();
737     },
738
739     /**
740      * @param {!Event} event
741      */
742     _onClick: function(event)
743     {
744         this._checked = !this._checked;
745         if (this._setting)
746             this._setting.set(this._checked);
747         else
748             this._update();
749     },
750
751     /**
752      * @param {string} title
753      */
754     _createCheckbox: function(title)
755     {
756         var label = this._filterElement.createChild("label");
757         var checkBorder = label.createChild("div", "checkbox-filter-checkbox");
758         this._checkElement = checkBorder.createChild("div", "checkbox-filter-checkbox-check");
759         this._filterElement.addEventListener("click", this._onClick.bind(this), false);
760         var typeElement = label.createChild("span", "type");
761         typeElement.textContent = title;
762     },
763
764     __proto__: WebInspector.Object.prototype
765 }