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