1/*
2 * Copyright (C) 2009 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 * @param {!Element} element
35 */
36WebInspector.StatusBarItem = function(element)
37{
38    this.element = element;
39    this._enabled = true;
40}
41
42WebInspector.StatusBarItem.prototype = {
43    /**
44     * @param {boolean} value
45     */
46    setEnabled: function(value)
47    {
48        if (this._enabled === value)
49            return;
50        this._enabled = value;
51        this._applyEnabledState();
52    },
53
54    /**
55     * @protected
56     */
57    _applyEnabledState: function()
58    {
59        this.element.disabled = !this._enabled;
60    },
61
62    __proto__: WebInspector.Object.prototype
63}
64
65/**
66 * @constructor
67 * @extends {WebInspector.StatusBarItem}
68 * @param {string} text
69 * @param {string=} className
70 */
71WebInspector.StatusBarText = function(text, className)
72{
73    WebInspector.StatusBarItem.call(this, document.createElement("span"));
74    this.element.className = "status-bar-item status-bar-text";
75    if (className)
76        this.element.classList.add(className);
77    this.element.textContent = text;
78}
79
80WebInspector.StatusBarText.prototype = {
81    /**
82     * @param {string} text
83     */
84    setText: function(text)
85    {
86        this.element.textContent = text;
87    },
88
89    __proto__: WebInspector.StatusBarItem.prototype
90}
91
92
93/**
94 * @constructor
95 * @extends {WebInspector.StatusBarItem}
96 * @param {string} title
97 * @param {string} className
98 * @param {number=} states
99 */
100WebInspector.StatusBarButton = function(title, className, states)
101{
102    WebInspector.StatusBarItem.call(this, document.createElement("button"));
103    this.element.className = className + " status-bar-item";
104    this.element.addEventListener("click", this._clicked.bind(this), false);
105
106    this.glyph = document.createElement("div");
107    this.glyph.className = "glyph";
108    this.element.appendChild(this.glyph);
109
110    this.glyphShadow = document.createElement("div");
111    this.glyphShadow.className = "glyph shadow";
112    this.element.appendChild(this.glyphShadow);
113
114    this.states = states;
115    if (!states)
116        this.states = 2;
117
118    if (states == 2)
119        this._state = false;
120    else
121        this._state = 0;
122
123    this.title = title;
124    this.className = className;
125    this._visible = true;
126}
127
128WebInspector.StatusBarButton.prototype = {
129    _clicked: function()
130    {
131        this.dispatchEventToListeners("click");
132        if (this._longClickInterval) {
133            clearInterval(this._longClickInterval);
134            delete this._longClickInterval;
135        }
136    },
137
138    /**
139     * @override
140     */
141    _applyEnabledState: function()
142    {
143        this.element.disabled = !this._enabled;
144        if (this._longClickInterval) {
145            clearInterval(this._longClickInterval);
146            delete this._longClickInterval;
147        }
148    },
149
150    /**
151     * @return {boolean}
152     */
153    enabled: function()
154    {
155        return this._enabled;
156    },
157
158    get title()
159    {
160        return this._title;
161    },
162
163    set title(x)
164    {
165        if (this._title === x)
166            return;
167        this._title = x;
168        this.element.title = x;
169    },
170
171    get state()
172    {
173        return this._state;
174    },
175
176    set state(x)
177    {
178        if (this._state === x)
179            return;
180
181        if (this.states === 2)
182            this.element.enableStyleClass("toggled-on", x);
183        else {
184            this.element.classList.remove("toggled-" + this._state);
185            if (x !== 0)
186                this.element.classList.add("toggled-" + x);
187        }
188        this._state = x;
189    },
190
191    get toggled()
192    {
193        if (this.states !== 2)
194            throw("Only used toggled when there are 2 states, otherwise, use state");
195        return this.state;
196    },
197
198    set toggled(x)
199    {
200        if (this.states !== 2)
201            throw("Only used toggled when there are 2 states, otherwise, use state");
202        this.state = x;
203    },
204
205    get visible()
206    {
207        return this._visible;
208    },
209
210    set visible(x)
211    {
212        if (this._visible === x)
213            return;
214
215        this.element.enableStyleClass("hidden", !x);
216        this._visible = x;
217    },
218
219    makeLongClickEnabled: function()
220    {
221        var boundMouseDown = mouseDown.bind(this);
222        var boundMouseUp = mouseUp.bind(this);
223
224        this.element.addEventListener("mousedown", boundMouseDown, false);
225        this.element.addEventListener("mouseout", boundMouseUp, false);
226        this.element.addEventListener("mouseup", boundMouseUp, false);
227
228        var longClicks = 0;
229
230        this._longClickData = { mouseUp: boundMouseUp, mouseDown: boundMouseDown };
231
232        /**
233         * @param {?Event} e
234         * @this {WebInspector.StatusBarButton}
235         */
236        function mouseDown(e)
237        {
238            if (e.which !== 1)
239                return;
240            longClicks = 0;
241            this._longClickInterval = setInterval(longClicked.bind(this), 200);
242        }
243
244        /**
245         * @param {?Event} e
246         * @this {WebInspector.StatusBarButton}
247         */
248        function mouseUp(e)
249        {
250            if (e.which !== 1)
251                return;
252            if (this._longClickInterval) {
253                clearInterval(this._longClickInterval);
254                delete this._longClickInterval;
255            }
256        }
257
258        /**
259         * @this {WebInspector.StatusBarButton}
260         */
261        function longClicked()
262        {
263            ++longClicks;
264            this.dispatchEventToListeners(longClicks === 1 ? "longClickDown" : "longClickPress");
265        }
266    },
267
268    unmakeLongClickEnabled: function()
269    {
270        if (!this._longClickData)
271            return;
272        this.element.removeEventListener("mousedown", this._longClickData.mouseDown, false);
273        this.element.removeEventListener("mouseout", this._longClickData.mouseUp, false);
274        this.element.removeEventListener("mouseup", this._longClickData.mouseUp, false);
275        delete this._longClickData;
276    },
277
278    /**
279     * @param {?function():!Array.<!WebInspector.StatusBarButton>} buttonsProvider
280     */
281    setLongClickOptionsEnabled: function(buttonsProvider)
282    {
283        if (buttonsProvider) {
284            if (!this._longClickOptionsData) {
285                this.makeLongClickEnabled();
286
287                this.longClickGlyph = document.createElement("div");
288                this.longClickGlyph.className = "fill long-click-glyph";
289                this.element.appendChild(this.longClickGlyph);
290
291                this.longClickGlyphShadow = document.createElement("div");
292                this.longClickGlyphShadow.className = "fill long-click-glyph shadow";
293                this.element.appendChild(this.longClickGlyphShadow);
294
295                var longClickDownListener = this._showOptions.bind(this);
296                this.addEventListener("longClickDown", longClickDownListener, this);
297
298                this._longClickOptionsData = {
299                    glyphElement: this.longClickGlyph,
300                    glyphShadowElement: this.longClickGlyphShadow,
301                    longClickDownListener: longClickDownListener
302                };
303            }
304            this._longClickOptionsData.buttonsProvider = buttonsProvider;
305        } else {
306            if (!this._longClickOptionsData)
307                return;
308            this.element.removeChild(this._longClickOptionsData.glyphElement);
309            this.element.removeChild(this._longClickOptionsData.glyphShadowElement);
310
311            this.removeEventListener("longClickDown", this._longClickOptionsData.longClickDownListener, this);
312            delete this._longClickOptionsData;
313
314            this.unmakeLongClickEnabled();
315        }
316    },
317
318    _showOptions: function()
319    {
320        var buttons = this._longClickOptionsData.buttonsProvider();
321        var mainButtonClone = new WebInspector.StatusBarButton(this.title, this.className, this.states);
322        mainButtonClone.addEventListener("click", this._clicked, this);
323        mainButtonClone.state = this.state;
324        buttons.push(mainButtonClone);
325
326        var mouseUpListener = mouseUp.bind(this);
327        document.documentElement.addEventListener("mouseup", mouseUpListener, false);
328
329        var optionsGlassPane = new WebInspector.GlassPane();
330        var optionsBarElement = optionsGlassPane.element.createChild("div", "alternate-status-bar-buttons-bar");
331        const buttonHeight = 23;
332
333        var hostButtonPosition = this.element.totalOffset();
334
335        var topNotBottom = hostButtonPosition.top + buttonHeight * buttons.length < document.documentElement.offsetHeight;
336
337        if (topNotBottom)
338            buttons = buttons.reverse();
339
340        optionsBarElement.style.height = (buttonHeight * buttons.length) + "px";
341        if (topNotBottom)
342            optionsBarElement.style.top = (hostButtonPosition.top + 1) + "px";
343        else
344            optionsBarElement.style.top = (hostButtonPosition.top - (buttonHeight * (buttons.length - 1))) + "px";
345        optionsBarElement.style.left = (hostButtonPosition.left + 1) + "px";
346
347        var boundMouseOver = mouseOver.bind(this);
348        var boundMouseOut = mouseOut.bind(this);
349        for (var i = 0; i < buttons.length; ++i) {
350            buttons[i].element.addEventListener("mousemove", boundMouseOver, false);
351            buttons[i].element.addEventListener("mouseout", boundMouseOut, false);
352            optionsBarElement.appendChild(buttons[i].element);
353        }
354        var hostButtonIndex = topNotBottom ? 0 : buttons.length - 1;
355        buttons[hostButtonIndex].element.classList.add("emulate-active");
356
357        function mouseOver(e)
358        {
359            if (e.which !== 1)
360                return;
361            var buttonElement = e.target.enclosingNodeOrSelfWithClass("status-bar-item");
362            buttonElement.classList.add("emulate-active");
363        }
364
365        function mouseOut(e)
366        {
367            if (e.which !== 1)
368                return;
369            var buttonElement = e.target.enclosingNodeOrSelfWithClass("status-bar-item");
370            buttonElement.classList.remove("emulate-active");
371        }
372
373        function mouseUp(e)
374        {
375            if (e.which !== 1)
376                return;
377            optionsGlassPane.dispose();
378            document.documentElement.removeEventListener("mouseup", mouseUpListener, false);
379
380            for (var i = 0; i < buttons.length; ++i) {
381                if (buttons[i].element.classList.contains("emulate-active")) {
382                    buttons[i].element.classList.remove("emulate-active");
383                    buttons[i]._clicked();
384                    break;
385                }
386            }
387        }
388    },
389
390    __proto__: WebInspector.StatusBarItem.prototype
391}
392
393/**
394 * @constructor
395 * @extends {WebInspector.StatusBarItem}
396 * @param {?function(!Event)} changeHandler
397 * @param {string=} className
398 */
399WebInspector.StatusBarComboBox = function(changeHandler, className)
400{
401    WebInspector.StatusBarItem.call(this, document.createElement("span"));
402    this.element.className = "status-bar-select-container";
403
404    this._selectElement = this.element.createChild("select", "status-bar-item");
405    this.element.createChild("div", "status-bar-select-arrow");
406    if (changeHandler)
407        this._selectElement.addEventListener("change", changeHandler, false);
408    if (className)
409        this._selectElement.classList.add(className);
410}
411
412WebInspector.StatusBarComboBox.prototype = {
413    /**
414     * @return {!Element}
415     */
416    selectElement: function()
417    {
418        return this._selectElement;
419    },
420
421    /**
422     * @return {number}
423     */
424    size: function()
425    {
426        return this._selectElement.childElementCount;
427    },
428
429    /**
430     * @param {!Element} option
431     */
432    addOption: function(option)
433    {
434        this._selectElement.appendChild(option);
435    },
436
437    /**
438     * @param {string} label
439     * @param {string=} title
440     * @param {string=} value
441     * @return {!Element}
442     */
443    createOption: function(label, title, value)
444    {
445        var option = this._selectElement.createChild("option");
446        option.text = label;
447        if (title)
448            option.title = title;
449        if (typeof value !== "undefined")
450            option.value = value;
451        return option;
452    },
453
454    /**
455     * @override
456     */
457    _applyEnabledState: function()
458    {
459        this._selectElement.disabled = !this._enabled;
460    },
461
462    /**
463     * @param {!Element} option
464     */
465    removeOption: function(option)
466    {
467        this._selectElement.removeChild(option);
468    },
469
470    removeOptions: function()
471    {
472        this._selectElement.removeChildren();
473    },
474
475    /**
476     * @return {?Element}
477     */
478    selectedOption: function()
479    {
480        if (this._selectElement.selectedIndex >= 0)
481            return this._selectElement[this._selectElement.selectedIndex];
482        return null;
483    },
484
485    /**
486     * @param {!Element} option
487     */
488    select: function(option)
489    {
490        this._selectElement.selectedIndex = Array.prototype.indexOf.call(this._selectElement, option);
491    },
492
493    /**
494     * @param {number} index
495     */
496    setSelectedIndex: function(index)
497    {
498        this._selectElement.selectedIndex = index;
499    },
500
501    /**
502     * @return {number}
503     */
504    selectedIndex: function()
505    {
506        return this._selectElement.selectedIndex;
507    },
508
509    __proto__: WebInspector.StatusBarItem.prototype
510}
511
512/**
513 * @constructor
514 * @extends {WebInspector.StatusBarItem}
515 * @param {string} title
516 */
517WebInspector.StatusBarCheckbox = function(title)
518{
519    WebInspector.StatusBarItem.call(this, document.createElement("label"));
520    this.element.classList.add("status-bar-item", "checkbox");
521    this._checkbox = this.element.createChild("input");
522    this._checkbox.type = "checkbox";
523    this.element.createTextChild(title);
524}
525
526WebInspector.StatusBarCheckbox.prototype = {
527    /**
528     * @return {boolean}
529     */
530    checked: function()
531    {
532        return this._checkbox.checked;
533    },
534
535    __proto__: WebInspector.StatusBarItem.prototype
536}
537