1/*
2 * Copyright (C) 2013 Apple 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.FindBanner = function(delegate, element) {
27    WebInspector.Object.call(this);
28
29    this._delegate = delegate || null;
30
31    this._element = element || document.createElement("div");
32    this._element.classList.add(WebInspector.FindBanner.StyleClassName);
33
34    this._resultCountLabel = document.createElement("label");
35    this._element.appendChild(this._resultCountLabel);
36
37    this._previousResultButton = document.createElement("button");
38    this._previousResultButton.classList.add(WebInspector.FindBanner.SegmentedButtonStyleClassName);
39    this._previousResultButton.classList.add(WebInspector.FindBanner.LeftSegmentButtonStyleClassName);
40    this._previousResultButton.disabled = true;
41    this._previousResultButton.addEventListener("click", this._previousResultButtonClicked.bind(this));
42    this._element.appendChild(this._previousResultButton);
43
44    var previousResultButtonGlyphElement = document.createElement("div");
45    previousResultButtonGlyphElement.classList.add(WebInspector.FindBanner.SegmentGlyphStyleClassName);
46    this._previousResultButton.appendChild(previousResultButtonGlyphElement);
47
48    this._nextResultButton = document.createElement("button");
49    this._nextResultButton.classList.add(WebInspector.FindBanner.SegmentedButtonStyleClassName);
50    this._nextResultButton.classList.add(WebInspector.FindBanner.RightSegmentButtonStyleClassName);
51    this._nextResultButton.disabled = true;
52    this._nextResultButton.addEventListener("click", this._nextResultButtonClicked.bind(this));
53    this._element.appendChild(this._nextResultButton);
54
55    var nextResultButtonGlyphElement = document.createElement("div");
56    nextResultButtonGlyphElement.classList.add(WebInspector.FindBanner.SegmentGlyphStyleClassName);
57    this._nextResultButton.appendChild(nextResultButtonGlyphElement);
58
59    this._inputField = document.createElement("input");
60    this._inputField.type = "search";
61    this._inputField.spellcheck = false;
62    this._inputField.incremental = true;
63    this._inputField.setAttribute("results", 5);
64    this._inputField.setAttribute("autosave", "inspector-search");
65    this._inputField.addEventListener("keydown", this._inputFieldKeyDown.bind(this), false);
66    this._inputField.addEventListener("keyup", this._inputFieldKeyUp.bind(this), false);
67    this._inputField.addEventListener("search", this._inputFieldSearch.bind(this), false);
68    this._element.appendChild(this._inputField);
69
70    this._doneButton = document.createElement("button");
71    this._doneButton.textContent = WebInspector.UIString("Done");
72    this._doneButton.addEventListener("click", this._doneButtonClicked.bind(this));
73    this._element.appendChild(this._doneButton);
74
75    this._numberOfResults = null;
76    this._searchBackwards = false;
77    this._searchKeyPressed = false;
78    this._previousSearchValue = "";
79
80    this._hideKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape, this.hide.bind(this), this._element);
81    this._populateFindKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "E", this._populateSearchQueryFromSelection.bind(this));
82    this._populateFindKeyboardShortcut.implicitlyPreventsDefault = false;
83    this._findNextKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "G", this._nextResultButtonClicked.bind(this));
84    this._findPreviousKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Shift | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "G", this._previousResultButtonClicked.bind(this));
85
86    this._generateButtonsGlyphsIfNeeded();
87};
88
89WebInspector.Object.addConstructorFunctions(WebInspector.FindBanner);
90
91WebInspector.FindBanner.StyleClassName = "find-banner";
92WebInspector.FindBanner.SupportsFindBannerStyleClassName = "supports-find-banner";
93WebInspector.FindBanner.ShowingFindBannerStyleClassName = "showing-find-banner";
94WebInspector.FindBanner.NoTransitionStyleClassName = "no-find-banner-transition";
95WebInspector.FindBanner.ShowingStyleClassName = "showing";
96WebInspector.FindBanner.SegmentedButtonStyleClassName = "segmented";
97WebInspector.FindBanner.LeftSegmentButtonStyleClassName = "left";
98WebInspector.FindBanner.RightSegmentButtonStyleClassName = "right";
99WebInspector.FindBanner.SegmentGlyphStyleClassName = "glyph";
100
101WebInspector.FindBanner.Event = {
102    DidShow: "find-banner-did-show",
103    DidHide: "find-banner-did-hide"
104}
105
106WebInspector.FindBanner.prototype = {
107    constructor: WebInspector.FindBanner,
108
109    // Public
110
111    get delegate()
112    {
113        return this._delegate;
114    },
115
116    set delegate(newDelegate)
117    {
118        this._delegate = newDelegate || null;
119    },
120
121    get element()
122    {
123        return this._element;
124    },
125
126    get inputField()
127    {
128        return this._inputField;
129    },
130
131    get searchQuery()
132    {
133        return this._inputField.value || "";
134    },
135
136    set searchQuery(query)
137    {
138        this._inputField.value = query || "";
139    },
140
141    get numberOfResults()
142    {
143        return this._numberOfResults;
144    },
145
146    set numberOfResults(numberOfResults)
147    {
148        if (numberOfResults === undefined || isNaN(numberOfResults))
149            numberOfResults = null;
150
151        this._numberOfResults = numberOfResults;
152
153        this._previousResultButton.disabled = this._nextResultButton.disabled = (numberOfResults <= 0);
154
155        if (numberOfResults === null)
156            this._resultCountLabel.textContent = "";
157        else if (numberOfResults <= 0)
158            this._resultCountLabel.textContent = WebInspector.UIString("Not found");
159        else if (numberOfResults === 1)
160            this._resultCountLabel.textContent = WebInspector.UIString("1 match");
161        else if (numberOfResults > 1)
162            this._resultCountLabel.textContent = WebInspector.UIString("%d matches").format(numberOfResults);
163    },
164
165    get targetElement()
166    {
167        return this._targetElement;
168    },
169
170    set targetElement(element)
171    {
172        if (this._targetElement) {
173            var oldTargetElement = this._targetElement;
174
175            this._targetElement.classList.add(WebInspector.FindBanner.NoTransitionStyleClassName);
176            this._targetElement.classList.remove(WebInspector.FindBanner.SupportsFindBannerStyleClassName);
177            this._targetElement.classList.remove(WebInspector.FindBanner.ShowingFindBannerStyleClassName);
178
179            this._element.classList.add(WebInspector.FindBanner.NoTransitionStyleClassName);
180            this._element.classList.remove(WebInspector.FindBanner.ShowingStyleClassName);
181
182            function delayedWork()
183            {
184                oldTargetElement.classList.remove(WebInspector.FindBanner.NoTransitionStyleClassName);
185                this._element.classList.remove(WebInspector.FindBanner.NoTransitionStyleClassName);
186            }
187
188            // Delay so we can remove the no transition style class after the other style changes are committed.
189            setTimeout(delayedWork.bind(this), 0);
190        }
191
192        this._targetElement = element || null;
193
194        if (this._targetElement)
195            this._targetElement.classList.add(WebInspector.FindBanner.SupportsFindBannerStyleClassName);
196    },
197
198    get showing()
199    {
200        return this._element.classList.contains(WebInspector.FindBanner.ShowingStyleClassName);
201    },
202
203    show: function()
204    {
205        console.assert(this._targetElement);
206        if (!this._targetElement)
207            return;
208
209        console.assert(this._targetElement.parentNode);
210        if (!this._targetElement.parentNode)
211            return;
212
213        if (this._element.parentNode !== this._targetElement.parentNode)
214            this._targetElement.parentNode.insertBefore(this._element, this._targetElement);
215
216        function delayedWork()
217        {
218            this._targetElement.classList.add(WebInspector.FindBanner.ShowingFindBannerStyleClassName);
219            this._element.classList.add(WebInspector.FindBanner.ShowingStyleClassName);
220
221            this._inputField.select();
222        }
223
224        // Delay adding the classes in case the target element and/or the find banner were just added to
225        // the document. Adding the class right away will prevent the animation from working the firs time.
226        setTimeout(delayedWork.bind(this), 0);
227
228        this.dispatchEventToListeners(WebInspector.FindBanner.Event.DidShow);
229    },
230
231    hide: function()
232    {
233        console.assert(this._targetElement);
234        if (!this._targetElement)
235            return;
236
237        this._inputField.blur();
238
239        this._targetElement.classList.remove(WebInspector.FindBanner.ShowingFindBannerStyleClassName);
240        this._element.classList.remove(WebInspector.FindBanner.ShowingStyleClassName);
241
242        this.dispatchEventToListeners(WebInspector.FindBanner.Event.DidHide);
243    },
244
245    // Private
246
247    _inputFieldKeyDown: function(event)
248    {
249        if (event.keyIdentifier === "Shift")
250            this._searchBackwards = true;
251        else if (event.keyIdentifier === "Enter")
252            this._searchKeyPressed = true;
253    },
254
255    _inputFieldKeyUp: function(event)
256    {
257        if (event.keyIdentifier === "Shift")
258            this._searchBackwards = false;
259        else if (event.keyIdentifier === "Enter")
260            this._searchKeyPressed = false;
261    },
262
263    _inputFieldSearch: function(event)
264    {
265        if (this._inputField.value) {
266            if (this._previousSearchValue !== this.searchQuery) {
267                if (this._delegate && typeof this._delegate.findBannerPerformSearch === "function")
268                    this._delegate.findBannerPerformSearch(this, this.searchQuery);
269            } else if (this._searchKeyPressed && this._numberOfResults > 0) {
270                if (this._searchBackwards) {
271                    if (this._delegate && typeof this._delegate.findBannerRevealPreviousResult === "function")
272                        this._delegate.findBannerRevealPreviousResult(this);
273                } else {
274                    if (this._delegate && typeof this._delegate.findBannerRevealNextResult === "function")
275                        this._delegate.findBannerRevealNextResult(this);
276                }
277            }
278        } else {
279            this.numberOfResults = null;
280
281            if (this._delegate && typeof this._delegate.findBannerSearchCleared === "function")
282                this._delegate.findBannerSearchCleared(this);
283        }
284
285        this._previousSearchValue = this.searchQuery;
286    },
287
288    _populateSearchQueryFromSelection: function(event)
289    {
290        if (this._delegate && typeof this._delegate.findBannerSearchQueryForSelection === "function") {
291            var query = this._delegate.findBannerSearchQueryForSelection(this);
292            if (query) {
293                this.searchQuery = query;
294
295                if (this._delegate && typeof this._delegate.findBannerPerformSearch === "function")
296                    this._delegate.findBannerPerformSearch(this, this.searchQuery);
297            }
298        }
299    },
300
301    _previousResultButtonClicked: function(event)
302    {
303        if (this._delegate && typeof this._delegate.findBannerRevealPreviousResult === "function")
304            this._delegate.findBannerRevealPreviousResult(this);
305    },
306
307    _nextResultButtonClicked: function(event)
308    {
309        if (this._delegate && typeof this._delegate.findBannerRevealNextResult === "function")
310            this._delegate.findBannerRevealNextResult(this);
311    },
312
313    _doneButtonClicked: function(event)
314    {
315        this.hide();
316    },
317
318    _generateButtonsGlyphsIfNeeded: function()
319    {
320        if (WebInspector.FindBanner._generatedButtonsGlyphs)
321            return;
322
323        WebInspector.FindBanner._generatedButtonsGlyphs = true;
324
325        var specifications = {};
326        specifications["normal"] = {fillColor: [81, 81, 81]};
327        specifications["normal-active"] = {fillColor: [37, 37, 37]};
328
329        var forwardArrow, backArrow;
330        if (WebInspector.Platform.isLegacyMacOS) {
331            forwardArrow = {src: "Images/Legacy/ForwardArrow.svg", width: 7, height: 7};
332            backArrow = {src: "Images/Legacy/BackArrow.svg", width: 7, height: 7};
333        } else {
334            forwardArrow = {src: "Images/ForwardArrow.svg", width: 7, height: 11};
335            backArrow = {src: "Images/BackArrow.svg", width: 7, height: 11};
336        }
337
338        generateColoredImagesForCSS(backArrow.src, specifications, backArrow.width, backArrow.height, "find-banner-previous-arrow-");
339        generateColoredImagesForCSS(forwardArrow.src, specifications, forwardArrow.width, forwardArrow.height, "find-banner-next-arrow-");
340    }
341};
342
343WebInspector.FindBanner.prototype.__proto__ = WebInspector.Object.prototype;
344