Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / ui / 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 = 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     /**
305      * @param {!WebInspector.Event} event
306      */
307     _onInput: function(event)
308     {
309         this._valueChanged(true);
310     },
311
312     /**
313      * @param {!WebInspector.Event} event
314      */
315     _onChange: function(event)
316     {
317         this._valueChanged(false);
318     },
319
320     focus: function()
321     {
322         this._filterInputElement.focus();
323     },
324
325     /**
326      * @param {?WebInspector.TextFilterUI.SuggestionBuilder} suggestionBuilder
327      */
328     setSuggestionBuilder: function(suggestionBuilder)
329     {
330         this._cancelSuggestion();
331         this._suggestionBuilder = suggestionBuilder;
332     },
333
334     _updateSuggestions: function()
335     {
336         if (!this._suggestionBuilder)
337             return;
338         var suggestions = this._suggestionBuilder.buildSuggestions(this._filterInputElement);
339         if (suggestions && suggestions.length) {
340             if (this._suppressSuggestion)
341                 delete this._suppressSuggestion;
342             else
343                 this._suggestionBuilder.applySuggestion(this._filterInputElement, suggestions[0], true);
344             var anchorBox = this._filterInputElement.boxInWindow().relativeTo(new AnchorBox(-3, 0));
345             this._suggestBox.updateSuggestions(anchorBox, suggestions, 0, true, "");
346         } else {
347             this._suggestBox.hide();
348         }
349     },
350
351     /**
352      * @param {boolean} showSuggestions
353      */
354     _valueChanged: function(showSuggestions)
355     {
356         if (showSuggestions)
357             this._updateSuggestions();
358         else
359             this._suggestBox.hide();
360
361         var filterQuery = this.value();
362
363         this._regex = null;
364         this._filterInputElement.classList.remove("filter-text-invalid");
365         if (filterQuery) {
366             if (this._supportRegex && this._regexCheckBox.checked) {
367                 try {
368                     this._regex = new RegExp(filterQuery, "i");
369                 } catch (e) {
370                     this._filterInputElement.classList.add("filter-text-invalid");
371                 }
372             } else {
373                 this._regex = createPlainTextSearchRegex(filterQuery, "i");
374             }
375         }
376
377         this._dispatchFilterChanged();
378     },
379
380     _dispatchFilterChanged: function()
381     {
382         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
383     },
384
385     /**
386      * @param {!KeyboardEvent} event
387      * @return {boolean}
388      */
389     _onInputKeyDown: function(event)
390     {
391         var handled = false;
392         if (event.keyIdentifier === "U+0008") { // Backspace
393             this._suppressSuggestion = true;
394         } else if (this._suggestBox.visible()) {
395             if (event.keyIdentifier === "U+001B") { // Esc
396                 this._cancelSuggestion();
397                 handled = true;
398             } else if (event.keyIdentifier === "U+0009") { // Tab
399                 this._suggestBox.acceptSuggestion();
400                 this._valueChanged(true);
401                 handled = true;
402             } else {
403                 handled = this._suggestBox.keyPressed(event);
404             }
405         }
406         if (handled)
407             event.consume(true);
408         return handled;
409     },
410
411     /**
412      * @override
413      * @param {string} suggestion
414      * @param {boolean=} isIntermediateSuggestion
415      */
416     applySuggestion: function(suggestion, isIntermediateSuggestion)
417     {
418         if (!this._suggestionBuilder)
419             return;
420         this._suggestionBuilder.applySuggestion(this._filterInputElement, suggestion, !!isIntermediateSuggestion);
421         if (isIntermediateSuggestion)
422             this._dispatchFilterChanged();
423     },
424
425     /** @override */
426     acceptSuggestion: function()
427     {
428         this._filterInputElement.scrollLeft = this._filterInputElement.scrollWidth;
429         this._valueChanged(true);
430     },
431
432     __proto__: WebInspector.Object.prototype
433 }
434
435 /**
436  * @interface
437  */
438 WebInspector.TextFilterUI.SuggestionBuilder = function()
439 {
440 }
441
442 WebInspector.TextFilterUI.SuggestionBuilder.prototype = {
443     /**
444      * @param {!HTMLInputElement} input
445      * @return {?Array.<string>}
446      */
447     buildSuggestions: function(input) { },
448
449     /**
450      * @param {!HTMLInputElement} input
451      * @param {string} suggestion
452      * @param {boolean} isIntermediate
453      */
454     applySuggestion: function(input, suggestion, isIntermediate) { },
455
456     /**
457      * @param {!HTMLInputElement} input
458      */
459     unapplySuggestion: function(input) { }
460 }
461
462 /**
463  * @constructor
464  * @extends {WebInspector.Object}
465  * @implements {WebInspector.FilterUI}
466  * @param {!Array.<!WebInspector.NamedBitSetFilterUI.Item>} items
467  * @param {!WebInspector.Setting=} setting
468  */
469 WebInspector.NamedBitSetFilterUI = function(items, setting)
470 {
471     this._filtersElement = document.createElement("div");
472     this._filtersElement.className = "filter-bitset-filter status-bar-item";
473     this._filtersElement.title = WebInspector.UIString("Use %s Click to select multiple types.", WebInspector.KeyboardShortcut.shortcutToString("", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta));
474
475     this._allowedTypes = {};
476     this._typeFilterElements = {};
477     this._addBit(WebInspector.NamedBitSetFilterUI.ALL_TYPES, WebInspector.UIString("All"));
478     this._filtersElement.createChild("div", "filter-bitset-filter-divider");
479
480     for (var i = 0; i < items.length; ++i)
481         this._addBit(items[i].name, items[i].label);
482
483     if (setting) {
484         this._setting = setting;
485         setting.addChangeListener(this._settingChanged.bind(this));
486         this._settingChanged();
487     } else {
488         this._toggleTypeFilter(WebInspector.NamedBitSetFilterUI.ALL_TYPES, false);
489     }
490 }
491
492 /** @typedef {{name: string, label: string}} */
493 WebInspector.NamedBitSetFilterUI.Item;
494
495 WebInspector.NamedBitSetFilterUI.ALL_TYPES = "all";
496
497 WebInspector.NamedBitSetFilterUI.prototype = {
498     /**
499      * @return {boolean}
500      */
501     isActive: function()
502     {
503         return !this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES];
504     },
505
506     /**
507      * @return {!Element}
508      */
509     element: function()
510     {
511         return this._filtersElement;
512     },
513
514     /**
515      * @param {string} typeName
516      * @return {boolean}
517      */
518     accept: function(typeName)
519     {
520         return !!this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] || !!this._allowedTypes[typeName];
521     },
522
523     _settingChanged: function()
524     {
525         var allowedTypes = this._setting.get();
526         this._allowedTypes = {};
527         for (var typeName in this._typeFilterElements) {
528             if (allowedTypes[typeName])
529                 this._allowedTypes[typeName] = true;
530         }
531         this._update();
532     },
533
534     _update: function()
535     {
536         if ((Object.keys(this._allowedTypes).length === 0) || this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES]) {
537             this._allowedTypes = {};
538             this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] = true;
539         }
540         for (var typeName in this._typeFilterElements)
541             this._typeFilterElements[typeName].classList.toggle("selected", this._allowedTypes[typeName]);
542         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
543     },
544
545     /**
546      * @param {string} name
547      * @param {string} label
548      */
549     _addBit: function(name, label)
550     {
551         var typeFilterElement = this._filtersElement.createChild("li", name);
552         typeFilterElement.typeName = name;
553         typeFilterElement.createTextChild(label);
554         typeFilterElement.addEventListener("click", this._onTypeFilterClicked.bind(this), false);
555         this._typeFilterElements[name] = typeFilterElement;
556     },
557
558     /**
559      * @param {!Event} e
560      */
561     _onTypeFilterClicked: function(e)
562     {
563         var toggle;
564         if (WebInspector.isMac())
565             toggle = e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey;
566         else
567             toggle = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey;
568         this._toggleTypeFilter(e.target.typeName, toggle);
569     },
570
571     /**
572      * @param {string} typeName
573      * @param {boolean} allowMultiSelect
574      */
575     _toggleTypeFilter: function(typeName, allowMultiSelect)
576     {
577         if (allowMultiSelect && typeName !== WebInspector.NamedBitSetFilterUI.ALL_TYPES)
578             this._allowedTypes[WebInspector.NamedBitSetFilterUI.ALL_TYPES] = false;
579         else
580             this._allowedTypes = {};
581
582         this._allowedTypes[typeName] = !this._allowedTypes[typeName];
583
584         if (this._setting)
585             this._setting.set(this._allowedTypes);
586         else
587             this._update();
588     },
589
590     __proto__: WebInspector.Object.prototype
591 }
592
593 /**
594  * @constructor
595  * @implements {WebInspector.FilterUI}
596  * @extends {WebInspector.Object}
597  * @param {!Array.<!{value: *, label: string, title: string}>} options
598  */
599 WebInspector.ComboBoxFilterUI = function(options)
600 {
601     this._filterElement = document.createElement("div");
602     this._filterElement.className = "filter-combobox-filter";
603
604     this._options = options;
605     this._filterComboBox = new WebInspector.StatusBarComboBox(this._filterChanged.bind(this));
606     for (var i = 0; i < options.length; ++i) {
607         var filterOption = options[i];
608         var option = document.createElement("option");
609         option.text = filterOption.label;
610         option.title = filterOption.title;
611         this._filterComboBox.addOption(option);
612         this._filterComboBox.element.title = this._filterComboBox.selectedOption().title;
613     }
614     this._filterElement.appendChild(this._filterComboBox.element);
615 }
616
617 WebInspector.ComboBoxFilterUI.prototype = {
618     /**
619      * @return {boolean}
620      */
621     isActive: function()
622     {
623         return this._filterComboBox.selectedIndex() !== 0;
624     },
625
626     /**
627      * @return {!Element}
628      */
629     element: function()
630     {
631         return this._filterElement;
632     },
633
634     /**
635      * @param {string} typeName
636      * @return {*}
637      */
638     value: function(typeName)
639     {
640         var option = this._options[this._filterComboBox.selectedIndex()];
641         return option.value;
642     },
643
644     /**
645      * @param {number} index
646      */
647     setSelectedIndex: function(index)
648     {
649         this._filterComboBox.setSelectedIndex(index);
650     },
651
652     /**
653      * @return {number}
654      */
655     selectedIndex: function(index)
656     {
657         return this._filterComboBox.selectedIndex();
658     },
659
660     /**
661      * @param {?Event} event
662      */
663     _filterChanged: function(event)
664     {
665         var option = this._options[this._filterComboBox.selectedIndex()];
666         this._filterComboBox.element.title = option.title;
667         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
668     },
669
670     __proto__: WebInspector.Object.prototype
671 }
672
673 /**
674  * @constructor
675  * @implements {WebInspector.FilterUI}
676  * @extends {WebInspector.Object}
677  * @param {string} className
678  * @param {string} title
679  * @param {boolean=} activeWhenChecked
680  * @param {!WebInspector.Setting=} setting
681  */
682 WebInspector.CheckboxFilterUI = function(className, title, activeWhenChecked, setting)
683 {
684     this._filterElement = document.createElement("div");
685     this._filterElement.classList.add("filter-checkbox-filter", "filter-checkbox-filter-" + className);
686     this._activeWhenChecked = !!activeWhenChecked;
687     this._createCheckbox(title);
688
689     if (setting) {
690         this._setting = setting;
691         setting.addChangeListener(this._settingChanged.bind(this));
692         this._settingChanged();
693     } else {
694         this._checked = !this._activeWhenChecked;
695         this._update();
696     }
697 }
698
699 WebInspector.CheckboxFilterUI.prototype = {
700     /**
701      * @return {boolean}
702      */
703     isActive: function()
704     {
705         return this._activeWhenChecked === this._checked;
706     },
707
708     /**
709      * @return {!Element}
710      */
711     element: function()
712     {
713         return this._filterElement;
714     },
715
716     /**
717      * @return {boolean}
718      */
719     checked: function()
720     {
721         return this._checked;
722     },
723
724     /**
725      * @param {boolean} state
726      */
727     setState: function(state)
728     {
729         this._checked = state;
730         this._update();
731     },
732
733     _update: function()
734     {
735         this._checkElement.classList.toggle("checkbox-filter-checkbox-checked", this._checked);
736         this.dispatchEventToListeners(WebInspector.FilterUI.Events.FilterChanged, null);
737     },
738
739     _settingChanged: function()
740     {
741         this._checked = this._setting.get();
742         this._update();
743     },
744
745     /**
746      * @param {?Event} event
747      */
748     _onClick: function(event)
749     {
750         this._checked = !this._checked;
751         if (this._setting)
752             this._setting.set(this._checked);
753         else
754             this._update();
755     },
756
757     /**
758      * @param {string} title
759      */
760     _createCheckbox: function(title)
761     {
762         var label = this._filterElement.createChild("label");
763         var checkBorder = label.createChild("div", "checkbox-filter-checkbox");
764         this._checkElement = checkBorder.createChild("div", "checkbox-filter-checkbox-check");
765         this._filterElement.addEventListener("click", this._onClick.bind(this), false);
766         var typeElement = label.createChild("span", "type");
767         typeElement.textContent = title;
768     },
769
770     __proto__: WebInspector.Object.prototype
771 }