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