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