Home | History | Annotate | Download | only in ui
      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  */
     36 WebInspector.StatusBarItem = function(elementType)
     37 {
     38     this.element = document.createElement(elementType);
     39     this._enabled = true;
     40     this._visible = true;
     41 }
     42 
     43 WebInspector.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  */
     85 WebInspector.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 
    103 WebInspector.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  */
    163 WebInspector.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 
    172 WebInspector.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  */
    190 WebInspector.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 
    202 WebInspector.StatusBarInput.Event = {
    203     TextChanged: "TextChanged"
    204 };
    205 
    206 WebInspector.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  */
    239 WebInspector.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 
    264 WebInspector.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  */
    471 WebInspector.StatusBarItem.Provider = function()
    472 {
    473 }
    474 
    475 WebInspector.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  */
    488 WebInspector.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 
    501 WebInspector.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  */
    606 WebInspector.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 
    615 WebInspector.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  */
    638 WebInspector.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 
    663 WebInspector.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