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 {string} elementType
35 */
36WebInspector.StatusBarItem = function(elementType)
37{
38    this.element = document.createElement(elementType);
39    this._enabled = true;
40    this._visible = true;
41}
42
43WebInspector.StatusBarItem.prototype = {
44    /**
45     * @param {boolean} value
46     */
47    setEnabled: function(value)
48    {
49        if (this._enabled === value)
50            return;
51        this._enabled = value;
52        this.applyEnabledState();
53    },
54
55    /**
56     * @protected
57     */
58    applyEnabledState: function()
59    {
60        this.element.disabled = !this._enabled;
61    },
62
63    get visible()
64    {
65        return this._visible;
66    },
67
68    set visible(x)
69    {
70        if (this._visible === x)
71            return;
72        this.element.classList.toggle("hidden", !x);
73        this._visible = x;
74    },
75
76    __proto__: WebInspector.Object.prototype
77}
78
79/**
80 * @constructor
81 * @extends {WebInspector.StatusBarItem}
82 * @param {!Array.<string>} counters
83 * @param {string=} className
84 */
85WebInspector.StatusBarCounter = function(counters, className)
86{
87    WebInspector.StatusBarItem.call(this, "div");
88    this.element.className = "status-bar-item status-bar-counter hidden";
89    if (className)
90        this.element.classList.add(className);
91    this.element.addEventListener("click", this._clicked.bind(this), false);
92    /** @type {!Array.<!{element: !Element, counter: string, value: number, title: string}>} */
93    this._counters = [];
94    for (var i = 0; i < counters.length; ++i) {
95        var element = this.element.createChild("span", "status-bar-counter-item");
96        element.createChild("div", counters[i]);
97        element.createChild("span");
98        this._counters.push({counter: counters[i], element: element, value: 0, title: ""});
99    }
100    this._update();
101}
102
103WebInspector.StatusBarCounter.prototype = {
104    /**
105     * @param {string} counter
106     * @param {number} value
107     * @param {string} title
108     */
109    setCounter: function(counter, value, title)
110    {
111        for (var i = 0; i < this._counters.length; ++i) {
112            if (this._counters[i].counter === counter) {
113                this._counters[i].value = value;
114                this._counters[i].title = title;
115                this._update();
116                return;
117            }
118        }
119    },
120
121    _update: function()
122    {
123        var total = 0;
124        var title = "";
125        for (var i = 0; i < this._counters.length; ++i) {
126            var counter = this._counters[i];
127            var value = counter.value;
128            if (!counter.value) {
129                counter.element.classList.add("hidden");
130                continue;
131            }
132            counter.element.classList.remove("hidden");
133            counter.element.classList.toggle("status-bar-counter-item-first", !total);
134            counter.element.querySelector("span").textContent = value;
135            total += value;
136            if (counter.title) {
137                if (title)
138                    title += ", ";
139                title += counter.title;
140            }
141        }
142        this.element.classList.toggle("hidden", !total);
143        this.element.title = title;
144    },
145
146    /**
147     * @param {!Event} event
148     */
149    _clicked: function(event)
150    {
151        this.dispatchEventToListeners("click");
152    },
153
154    __proto__: WebInspector.StatusBarItem.prototype
155}
156
157/**
158 * @constructor
159 * @extends {WebInspector.StatusBarItem}
160 * @param {string} text
161 * @param {string=} className
162 */
163WebInspector.StatusBarText = function(text, className)
164{
165    WebInspector.StatusBarItem.call(this, "span");
166    this.element.className = "status-bar-item status-bar-text";
167    if (className)
168        this.element.classList.add(className);
169    this.element.textContent = text;
170}
171
172WebInspector.StatusBarText.prototype = {
173    /**
174     * @param {string} text
175     */
176    setText: function(text)
177    {
178        this.element.textContent = text;
179    },
180
181    __proto__: WebInspector.StatusBarItem.prototype
182}
183
184/**
185 * @constructor
186 * @extends {WebInspector.StatusBarItem}
187 * @param {string=} placeholder
188 * @param {number=} width
189 */
190WebInspector.StatusBarInput = function(placeholder, width)
191{
192    WebInspector.StatusBarItem.call(this, "input");
193    this.element.className = "status-bar-item";
194    this.element.addEventListener("input", this._onChangeCallback.bind(this), false);
195    if (width)
196        this.element.style.width = width + "px";
197    if (placeholder)
198        this.element.setAttribute("placeholder", placeholder);
199    this._value = "";
200}
201
202WebInspector.StatusBarInput.Event = {
203    TextChanged: "TextChanged"
204};
205
206WebInspector.StatusBarInput.prototype = {
207    /**
208     * @param {string} value
209     */
210    setValue: function(value)
211    {
212        this._value = value;
213        this.element.value = value;
214    },
215
216    /**
217     * @return {string}
218     */
219    value: function()
220    {
221        return this.element.value;
222    },
223
224    _onChangeCallback: function()
225    {
226        this.dispatchEventToListeners(WebInspector.StatusBarInput.Event.TextChanged, this.element.value);
227    },
228
229    __proto__: WebInspector.StatusBarItem.prototype
230}
231
232/**
233 * @constructor
234 * @extends {WebInspector.StatusBarItem}
235 * @param {string} title
236 * @param {string} className
237 * @param {number=} states
238 */
239WebInspector.StatusBarButton = function(title, className, states)
240{
241    WebInspector.StatusBarItem.call(this, "button");
242    this.element.className = className + " status-bar-item";
243    this.element.addEventListener("click", this._clicked.bind(this), false);
244    this._longClickController = new WebInspector.LongClickController(this.element);
245    this._longClickController.addEventListener(WebInspector.LongClickController.Events.LongClick, this._onLongClick.bind(this));
246    this._longClickController.addEventListener(WebInspector.LongClickController.Events.LongPress, this._onLongPress.bind(this));
247
248    this.glyph = this.element.createChild("div", "glyph");
249    this.glyphShadow = this.element.createChild("div", "glyph shadow");
250
251    this.states = states;
252    if (!states)
253        this.states = 2;
254
255    if (states == 2)
256        this._state = false;
257    else
258        this._state = 0;
259
260    this.title = title;
261    this.className = className;
262}
263
264WebInspector.StatusBarButton.prototype = {
265    /**
266     * @param {!WebInspector.Event} event
267     */
268    _onLongClick: function(event)
269    {
270        this.dispatchEventToListeners("longClickDown");
271    },
272
273    /**
274     * @param {!WebInspector.Event} event
275     */
276    _onLongPress: function(event)
277    {
278        this.dispatchEventToListeners("longPressDown");
279    },
280
281    _clicked: function()
282    {
283        this.dispatchEventToListeners("click");
284        this._longClickController.reset();
285    },
286
287    /**
288     * @override
289     */
290    applyEnabledState: function()
291    {
292        this.element.disabled = !this._enabled;
293        this._longClickController.reset();
294    },
295
296    /**
297     * @return {boolean}
298     */
299    enabled: function()
300    {
301        return this._enabled;
302    },
303
304    get title()
305    {
306        return this._title;
307    },
308
309    set title(x)
310    {
311        if (this._title === x)
312            return;
313        this._title = x;
314        this.element.title = x;
315    },
316
317    get state()
318    {
319        return this._state;
320    },
321
322    set state(x)
323    {
324        if (this._state === x)
325            return;
326
327        if (this.states === 2) {
328            this.element.classList.toggle("toggled-on", x);
329        } else {
330            this.element.classList.remove("toggled-" + this._state);
331            if (x !== 0)
332                this.element.classList.add("toggled-" + x);
333        }
334        this._state = x;
335    },
336
337    get toggled()
338    {
339        if (this.states !== 2)
340            throw("Only used toggled when there are 2 states, otherwise, use state");
341        return this.state;
342    },
343
344    set toggled(x)
345    {
346        if (this.states !== 2)
347            throw("Only used toggled when there are 2 states, otherwise, use state");
348        this.state = x;
349    },
350
351    makeLongClickEnabled: function()
352    {
353        this._longClickController.enable();
354    },
355
356    unmakeLongClickEnabled: function()
357    {
358        this._longClickController.disable();
359    },
360
361    /**
362     * @param {?function():!Array.<!WebInspector.StatusBarButton>} buttonsProvider
363     */
364    setLongClickOptionsEnabled: function(buttonsProvider)
365    {
366        if (buttonsProvider) {
367            if (!this._longClickOptionsData) {
368                this.makeLongClickEnabled();
369
370                this.longClickGlyph = this.element.createChild("div", "fill long-click-glyph");
371                this.longClickGlyphShadow = this.element.createChild("div", "fill long-click-glyph shadow");
372
373                var longClickDownListener = this._showOptions.bind(this);
374                this.addEventListener("longClickDown", longClickDownListener, this);
375
376                this._longClickOptionsData = {
377                    glyphElement: this.longClickGlyph,
378                    glyphShadowElement: this.longClickGlyphShadow,
379                    longClickDownListener: longClickDownListener
380                };
381            }
382            this._longClickOptionsData.buttonsProvider = buttonsProvider;
383        } else {
384            if (!this._longClickOptionsData)
385                return;
386            this.element.removeChild(this._longClickOptionsData.glyphElement);
387            this.element.removeChild(this._longClickOptionsData.glyphShadowElement);
388
389            this.removeEventListener("longClickDown", this._longClickOptionsData.longClickDownListener, this);
390            delete this._longClickOptionsData;
391
392            this.unmakeLongClickEnabled();
393        }
394    },
395
396    _showOptions: function()
397    {
398        var buttons = this._longClickOptionsData.buttonsProvider();
399        var mainButtonClone = new WebInspector.StatusBarButton(this.title, this.className, this.states);
400        mainButtonClone.addEventListener("click", this._clicked, this);
401        mainButtonClone.state = this.state;
402        buttons.push(mainButtonClone);
403
404        document.documentElement.addEventListener("mouseup", mouseUp, false);
405
406        var optionsGlassPane = new WebInspector.GlassPane();
407        var optionsBarElement = optionsGlassPane.element.createChild("div", "alternate-status-bar-buttons-bar");
408        const buttonHeight = 23;
409
410        var hostButtonPosition = this.element.totalOffset();
411
412        var topNotBottom = hostButtonPosition.top + buttonHeight * buttons.length < document.documentElement.offsetHeight;
413
414        if (topNotBottom)
415            buttons = buttons.reverse();
416
417        optionsBarElement.style.height = (buttonHeight * buttons.length) + "px";
418        if (topNotBottom)
419            optionsBarElement.style.top = (hostButtonPosition.top + 1) + "px";
420        else
421            optionsBarElement.style.top = (hostButtonPosition.top - (buttonHeight * (buttons.length - 1))) + "px";
422        optionsBarElement.style.left = (hostButtonPosition.left + 1) + "px";
423
424        for (var i = 0; i < buttons.length; ++i) {
425            buttons[i].element.addEventListener("mousemove", mouseOver, false);
426            buttons[i].element.addEventListener("mouseout", mouseOut, false);
427            optionsBarElement.appendChild(buttons[i].element);
428        }
429        var hostButtonIndex = topNotBottom ? 0 : buttons.length - 1;
430        buttons[hostButtonIndex].element.classList.add("emulate-active");
431
432        function mouseOver(e)
433        {
434            if (e.which !== 1)
435                return;
436            var buttonElement = e.target.enclosingNodeOrSelfWithClass("status-bar-item");
437            buttonElement.classList.add("emulate-active");
438        }
439
440        function mouseOut(e)
441        {
442            if (e.which !== 1)
443                return;
444            var buttonElement = e.target.enclosingNodeOrSelfWithClass("status-bar-item");
445            buttonElement.classList.remove("emulate-active");
446        }
447
448        function mouseUp(e)
449        {
450            if (e.which !== 1)
451                return;
452            optionsGlassPane.dispose();
453            document.documentElement.removeEventListener("mouseup", mouseUp, false);
454
455            for (var i = 0; i < buttons.length; ++i) {
456                if (buttons[i].element.classList.contains("emulate-active")) {
457                    buttons[i].element.classList.remove("emulate-active");
458                    buttons[i]._clicked();
459                    break;
460                }
461            }
462        }
463    },
464
465    __proto__: WebInspector.StatusBarItem.prototype
466}
467
468/**
469 * @interface
470 */
471WebInspector.StatusBarItem.Provider = function()
472{
473}
474
475WebInspector.StatusBarItem.Provider.prototype = {
476    /**
477     * @return {?WebInspector.StatusBarItem}
478     */
479    item: function() {}
480}
481
482/**
483 * @constructor
484 * @extends {WebInspector.StatusBarItem}
485 * @param {?function(!Event)} changeHandler
486 * @param {string=} className
487 */
488WebInspector.StatusBarComboBox = function(changeHandler, className)
489{
490    WebInspector.StatusBarItem.call(this, "span");
491    this.element.className = "status-bar-select-container";
492
493    this._selectElement = this.element.createChild("select", "status-bar-item");
494    this.element.createChild("div", "status-bar-select-arrow");
495    if (changeHandler)
496        this._selectElement.addEventListener("change", changeHandler, false);
497    if (className)
498        this._selectElement.classList.add(className);
499}
500
501WebInspector.StatusBarComboBox.prototype = {
502    /**
503     * @return {!Element}
504     */
505    selectElement: function()
506    {
507        return this._selectElement;
508    },
509
510    /**
511     * @return {number}
512     */
513    size: function()
514    {
515        return this._selectElement.childElementCount;
516    },
517
518    /**
519     * @param {!Element} option
520     */
521    addOption: function(option)
522    {
523        this._selectElement.appendChild(option);
524    },
525
526    /**
527     * @param {string} label
528     * @param {string=} title
529     * @param {string=} value
530     * @return {!Element}
531     */
532    createOption: function(label, title, value)
533    {
534        var option = this._selectElement.createChild("option");
535        option.text = label;
536        if (title)
537            option.title = title;
538        if (typeof value !== "undefined")
539            option.value = value;
540        return option;
541    },
542
543    /**
544     * @override
545     */
546    applyEnabledState: function()
547    {
548        this._selectElement.disabled = !this._enabled;
549    },
550
551    /**
552     * @param {!Element} option
553     */
554    removeOption: function(option)
555    {
556        this._selectElement.removeChild(option);
557    },
558
559    removeOptions: function()
560    {
561        this._selectElement.removeChildren();
562    },
563
564    /**
565     * @return {?Element}
566     */
567    selectedOption: function()
568    {
569        if (this._selectElement.selectedIndex >= 0)
570            return this._selectElement[this._selectElement.selectedIndex];
571        return null;
572    },
573
574    /**
575     * @param {!Element} option
576     */
577    select: function(option)
578    {
579        this._selectElement.selectedIndex = Array.prototype.indexOf.call(/** @type {?} */ (this._selectElement), option);
580    },
581
582    /**
583     * @param {number} index
584     */
585    setSelectedIndex: function(index)
586    {
587        this._selectElement.selectedIndex = index;
588    },
589
590    /**
591     * @return {number}
592     */
593    selectedIndex: function()
594    {
595        return this._selectElement.selectedIndex;
596    },
597
598    __proto__: WebInspector.StatusBarItem.prototype
599}
600
601/**
602 * @constructor
603 * @extends {WebInspector.StatusBarItem}
604 * @param {string} title
605 */
606WebInspector.StatusBarCheckbox = function(title)
607{
608    WebInspector.StatusBarItem.call(this, "label");
609    this.element.classList.add("status-bar-item", "checkbox");
610    this.inputElement = this.element.createChild("input");
611    this.inputElement.type = "checkbox";
612    this.element.createTextChild(title);
613}
614
615WebInspector.StatusBarCheckbox.prototype = {
616    /**
617     * @return {boolean}
618     */
619    checked: function()
620    {
621        return this.inputElement.checked;
622    },
623
624    __proto__: WebInspector.StatusBarItem.prototype
625}
626
627/**
628 * @constructor
629 * @extends {WebInspector.StatusBarButton}
630 * @param {string} className
631 * @param {!Array.<string>} states
632 * @param {!Array.<string>} titles
633 * @param {string} initialState
634 * @param {!WebInspector.Setting} currentStateSetting
635 * @param {!WebInspector.Setting} lastStateSetting
636 * @param {?function(string)} stateChangedCallback
637 */
638WebInspector.StatusBarStatesSettingButton = function(className, states, titles, initialState, currentStateSetting, lastStateSetting, stateChangedCallback)
639{
640    WebInspector.StatusBarButton.call(this, "", className, states.length);
641
642    var onClickBound = this._onClick.bind(this);
643    this.addEventListener("click", onClickBound, this);
644
645    this._states = states;
646    this._buttons = [];
647    for (var index = 0; index < states.length; index++) {
648        var button = new WebInspector.StatusBarButton(titles[index], className, states.length);
649        button.state = this._states[index];
650        button.addEventListener("click", onClickBound, this);
651        this._buttons.push(button);
652    }
653
654    this._currentStateSetting = currentStateSetting;
655    this._lastStateSetting = lastStateSetting;
656    this._stateChangedCallback = stateChangedCallback;
657    this.setLongClickOptionsEnabled(this._createOptions.bind(this));
658
659    this._currentState = null;
660    this.toggleState(initialState);
661}
662
663WebInspector.StatusBarStatesSettingButton.prototype = {
664    /**
665     * @param {!WebInspector.Event} e
666     */
667    _onClick: function(e)
668    {
669        this.toggleState(e.target.state);
670    },
671
672    /**
673     * @param {string} state
674     */
675    toggleState: function(state)
676    {
677        if (this._currentState === state)
678            return;
679
680        if (this._currentState)
681            this._lastStateSetting.set(this._currentState);
682        this._currentState = state;
683        this._currentStateSetting.set(this._currentState);
684
685        if (this._stateChangedCallback)
686            this._stateChangedCallback(state);
687
688        var defaultState = this._defaultState();
689        this.state = defaultState;
690        this.title = this._buttons[this._states.indexOf(defaultState)].title;
691    },
692
693    /**
694     * @return {string}
695     */
696    _defaultState: function()
697    {
698        var lastState = this._lastStateSetting.get();
699        if (lastState && this._states.indexOf(lastState) >= 0 && lastState != this._currentState)
700            return lastState;
701        if (this._states.length > 1 && this._currentState === this._states[0])
702            return this._states[1];
703        return this._states[0];
704    },
705
706    /**
707     * @return {!Array.<!WebInspector.StatusBarButton>}
708     */
709    _createOptions: function()
710    {
711        var options = [];
712        for (var index = 0; index < this._states.length; index++) {
713            if (this._states[index] !== this.state && this._states[index] !== this._currentState)
714                options.push(this._buttons[index]);
715        }
716        return options;
717    },
718
719    __proto__: WebInspector.StatusBarButton.prototype
720}
721