1 /* 2 * Copyright (C) 2010 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 * @extends {WebInspector.View} 33 * @constructor 34 */ 35 WebInspector.TabbedPane = function() 36 { 37 WebInspector.View.call(this); 38 this.registerRequiredCSS("tabbedPane.css"); 39 this.element.addStyleClass("tabbed-pane"); 40 this._headerElement = this.element.createChild("div", "tabbed-pane-header"); 41 this._headerContentsElement = this._headerElement.createChild("div", "tabbed-pane-header-contents"); 42 this._tabsElement = this._headerContentsElement.createChild("div", "tabbed-pane-header-tabs"); 43 this._contentElement = this.element.createChild("div", "tabbed-pane-content scroll-target"); 44 this._tabs = []; 45 this._tabsHistory = []; 46 this._tabsById = {}; 47 this.element.addEventListener("click", this.focus.bind(this), true); 48 this.element.addEventListener("mouseup", this.onMouseUp.bind(this), false); 49 50 this._dropDownButton = this._createDropDownButton(); 51 } 52 53 WebInspector.TabbedPane.EventTypes = { 54 TabSelected: "TabSelected", 55 TabClosed: "TabClosed" 56 } 57 58 WebInspector.TabbedPane.prototype = { 59 /** 60 * @return {WebInspector.View} 61 */ 62 get visibleView() 63 { 64 return this._currentTab ? this._currentTab.view : null; 65 }, 66 67 /** 68 * @return {string} 69 */ 70 get selectedTabId() 71 { 72 return this._currentTab ? this._currentTab.id : null; 73 }, 74 75 /** 76 * @type {boolean} shrinkableTabs 77 */ 78 set shrinkableTabs(shrinkableTabs) 79 { 80 this._shrinkableTabs = shrinkableTabs; 81 }, 82 83 /** 84 * @type {boolean} verticalTabLayout 85 */ 86 set verticalTabLayout(verticalTabLayout) 87 { 88 this._verticalTabLayout = verticalTabLayout; 89 }, 90 91 /** 92 * @type {boolean} closeableTabs 93 */ 94 set closeableTabs(closeableTabs) 95 { 96 this._closeableTabs = closeableTabs; 97 }, 98 99 /** 100 * @param {boolean} retainTabsOrder 101 */ 102 setRetainTabsOrder: function(retainTabsOrder) 103 { 104 this._retainTabsOrder = retainTabsOrder; 105 }, 106 107 defaultFocusedElement: function() 108 { 109 return this.visibleView ? this.visibleView.defaultFocusedElement() : null; 110 }, 111 112 /** 113 * @param {WebInspector.TabbedPaneTabDelegate} delegate 114 */ 115 setTabDelegate: function(delegate) 116 { 117 var tabs = this._tabs.slice(); 118 for (var i = 0; i < tabs.length; ++i) 119 tabs[i].setDelegate(delegate); 120 this._delegate = delegate; 121 }, 122 123 /** 124 * @param {Event} event 125 */ 126 onMouseUp: function(event) 127 { 128 // This is needed to prevent middle-click pasting on linux when tabs are clicked. 129 if (event.button === 1) 130 event.consume(true); 131 }, 132 133 /** 134 * @param {string} id 135 * @param {string} tabTitle 136 * @param {WebInspector.View} view 137 * @param {string=} tabTooltip 138 * @param {boolean=} userGesture 139 */ 140 appendTab: function(id, tabTitle, view, tabTooltip, userGesture) 141 { 142 var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, this._closeableTabs, view, tabTooltip); 143 tab.setDelegate(this._delegate); 144 this._tabsById[id] = tab; 145 146 this._tabs.push(tab); 147 this._tabsHistory.push(tab); 148 149 if (this._tabsHistory[0] === tab) 150 this.selectTab(tab.id, userGesture); 151 152 this._updateTabElements(); 153 }, 154 155 /** 156 * @param {string} id 157 * @param {boolean=} userGesture 158 */ 159 closeTab: function(id, userGesture) 160 { 161 this.closeTabs([id], userGesture); 162 }, 163 164 /** 165 * @param {Array.<string>} ids 166 * @param {boolean=} userGesture 167 */ 168 closeTabs: function(ids, userGesture) 169 { 170 for (var i = 0; i < ids.length; ++i) 171 this._innerCloseTab(ids[i], userGesture); 172 this._updateTabElements(); 173 if (this._tabsHistory.length) 174 this.selectTab(this._tabsHistory[0].id, userGesture); 175 }, 176 177 /** 178 * @param {string} id 179 * @param {boolean=} userGesture 180 */ 181 _innerCloseTab: function(id, userGesture) 182 { 183 if (this._currentTab && this._currentTab.id === id) 184 this._hideCurrentTab(); 185 186 var tab = this._tabsById[id]; 187 delete this._tabsById[id]; 188 189 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); 190 this._tabs.splice(this._tabs.indexOf(tab), 1); 191 if (tab._shown) 192 this._hideTabElement(tab); 193 194 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; 195 this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData); 196 return true; 197 }, 198 199 /** 200 * @return {Array.<string>} 201 */ 202 allTabs: function() 203 { 204 var result = []; 205 var tabs = this._tabs.slice(); 206 for (var i = 0; i < tabs.length; ++i) 207 result.push(tabs[i].id); 208 return result; 209 }, 210 211 /** 212 * @param {string} id 213 * @return {Array.<string>} 214 */ 215 otherTabs: function(id) 216 { 217 var result = []; 218 var tabs = this._tabs.slice(); 219 for (var i = 0; i < tabs.length; ++i) { 220 if (tabs[i].id !== id) 221 result.push(tabs[i].id); 222 } 223 return result; 224 }, 225 226 /** 227 * @param {string} id 228 * @param {boolean=} userGesture 229 */ 230 selectTab: function(id, userGesture) 231 { 232 var tab = this._tabsById[id]; 233 if (!tab) 234 return; 235 if (this._currentTab && this._currentTab.id === id) 236 return; 237 238 this._hideCurrentTab(); 239 this._showTab(tab); 240 this._currentTab = tab; 241 242 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); 243 this._tabsHistory.splice(0, 0, tab); 244 245 this._updateTabElements(); 246 247 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; 248 this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData); 249 return true; 250 }, 251 252 /** 253 * @param {number} tabsCount 254 * @return {Array.<string>} 255 */ 256 lastOpenedTabIds: function(tabsCount) 257 { 258 function tabToTabId(tab) { 259 return tab.id; 260 } 261 262 return this._tabsHistory.slice(0, tabsCount).map(tabToTabId); 263 }, 264 265 /** 266 * @param {string} id 267 * @param {string} iconClass 268 * @param {string=} iconTooltip 269 */ 270 setTabIcon: function(id, iconClass, iconTooltip) 271 { 272 var tab = this._tabsById[id]; 273 tab._setIconClass(iconClass, iconTooltip); 274 this._updateTabElements(); 275 }, 276 277 /** 278 * @param {string} id 279 * @param {string} tabTitle 280 */ 281 changeTabTitle: function(id, tabTitle) 282 { 283 var tab = this._tabsById[id]; 284 tab.title = tabTitle; 285 this._updateTabElements(); 286 }, 287 288 /** 289 * @param {string} id 290 * @param {WebInspector.View} view 291 */ 292 changeTabView: function(id, view) 293 { 294 var tab = this._tabsById[id]; 295 if (this._currentTab && this._currentTab.id === tab.id) { 296 this._hideTab(tab); 297 tab.view = view; 298 this._showTab(tab); 299 } else 300 tab.view = view; 301 }, 302 303 /** 304 * @param {string} id 305 * @param {string=} tabTooltip 306 */ 307 changeTabTooltip: function(id, tabTooltip) 308 { 309 var tab = this._tabsById[id]; 310 tab.tooltip = tabTooltip; 311 }, 312 313 onResize: function() 314 { 315 this._updateTabElements(); 316 }, 317 318 _updateTabElements: function() 319 { 320 WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements); 321 }, 322 323 /** 324 * @param {string} text 325 */ 326 setPlaceholderText: function(text) 327 { 328 this._noTabsMessage = text; 329 }, 330 331 _innerUpdateTabElements: function() 332 { 333 if (!this.isShowing()) 334 return; 335 336 if (!this._tabs.length) { 337 this._contentElement.addStyleClass("has-no-tabs"); 338 if (this._noTabsMessage && !this._noTabsMessageElement) { 339 this._noTabsMessageElement = this._contentElement.createChild("div", "tabbed-pane-placeholder fill"); 340 this._noTabsMessageElement.textContent = this._noTabsMessage; 341 } 342 } else { 343 this._contentElement.removeStyleClass("has-no-tabs"); 344 if (this._noTabsMessageElement) { 345 this._noTabsMessageElement.remove(); 346 delete this._noTabsMessageElement; 347 } 348 } 349 350 if (!this._measuredDropDownButtonWidth) 351 this._measureDropDownButton(); 352 353 this._updateWidths(); 354 this._updateTabsDropDown(); 355 }, 356 357 /** 358 * @param {number} index 359 * @param {WebInspector.TabbedPaneTab} tab 360 */ 361 _showTabElement: function(index, tab) 362 { 363 if (index >= this._tabsElement.children.length) 364 this._tabsElement.appendChild(tab.tabElement); 365 else 366 this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]); 367 tab._shown = true; 368 }, 369 370 /** 371 * @param {WebInspector.TabbedPaneTab} tab 372 */ 373 _hideTabElement: function(tab) 374 { 375 this._tabsElement.removeChild(tab.tabElement); 376 tab._shown = false; 377 }, 378 379 _createDropDownButton: function() 380 { 381 var dropDownContainer = document.createElement("div"); 382 dropDownContainer.addStyleClass("tabbed-pane-header-tabs-drop-down-container"); 383 var dropDownButton = dropDownContainer.createChild("div", "tabbed-pane-header-tabs-drop-down"); 384 dropDownButton.appendChild(document.createTextNode("\u00bb")); 385 this._tabsSelect = dropDownButton.createChild("select", "tabbed-pane-header-tabs-drop-down-select"); 386 this._tabsSelect.addEventListener("change", this._tabsSelectChanged.bind(this), false); 387 return dropDownContainer; 388 }, 389 390 _totalWidth: function() 391 { 392 return this._headerContentsElement.getBoundingClientRect().width; 393 }, 394 395 _updateTabsDropDown: function() 396 { 397 var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth); 398 399 for (var i = 0; i < this._tabs.length; ++i) { 400 if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1) 401 this._hideTabElement(this._tabs[i]); 402 } 403 for (var i = 0; i < tabsToShowIndexes.length; ++i) { 404 var tab = this._tabs[tabsToShowIndexes[i]]; 405 if (!tab._shown) 406 this._showTabElement(i, tab); 407 } 408 409 this._populateDropDownFromIndex(); 410 }, 411 412 _populateDropDownFromIndex: function() 413 { 414 if (this._dropDownButton.parentElement) 415 this._headerContentsElement.removeChild(this._dropDownButton); 416 417 this._tabsSelect.removeChildren(); 418 var tabsToShow = []; 419 for (var i = 0; i < this._tabs.length; ++i) { 420 if (!this._tabs[i]._shown) 421 tabsToShow.push(this._tabs[i]); 422 continue; 423 } 424 425 function compareFunction(tab1, tab2) 426 { 427 return tab1.title.localeCompare(tab2.title); 428 } 429 tabsToShow.sort(compareFunction); 430 431 var selectedIndex = -1; 432 for (var i = 0; i < tabsToShow.length; ++i) { 433 var option = new Option(tabsToShow[i].title); 434 option.tab = tabsToShow[i]; 435 this._tabsSelect.appendChild(option); 436 if (this._tabsHistory[0] === tabsToShow[i]) 437 selectedIndex = i; 438 } 439 if (this._tabsSelect.options.length) { 440 this._headerContentsElement.appendChild(this._dropDownButton); 441 this._tabsSelect.selectedIndex = selectedIndex; 442 } 443 }, 444 445 _tabsSelectChanged: function() 446 { 447 var options = this._tabsSelect.options; 448 var selectedOption = options[this._tabsSelect.selectedIndex]; 449 this.selectTab(selectedOption.tab.id, true); 450 }, 451 452 _measureDropDownButton: function() 453 { 454 this._dropDownButton.addStyleClass("measuring"); 455 this._headerContentsElement.appendChild(this._dropDownButton); 456 this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width; 457 this._headerContentsElement.removeChild(this._dropDownButton); 458 this._dropDownButton.removeStyleClass("measuring"); 459 }, 460 461 _updateWidths: function() 462 { 463 var measuredWidths = this._measureWidths(); 464 var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE; 465 466 var i = 0; 467 for (var tabId in this._tabs) { 468 var tab = this._tabs[tabId]; 469 tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++])); 470 } 471 }, 472 473 _measureWidths: function() 474 { 475 // Add all elements to measure into this._tabsElement 476 var measuringTabElements = []; 477 for (var tabId in this._tabs) { 478 var tab = this._tabs[tabId]; 479 if (typeof tab._measuredWidth === "number") 480 continue; 481 var measuringTabElement = tab._createTabElement(true); 482 measuringTabElement.__tab = tab; 483 measuringTabElements.push(measuringTabElement); 484 this._tabsElement.appendChild(measuringTabElement); 485 } 486 487 // Perform measurement 488 for (var i = 0; i < measuringTabElements.length; ++i) 489 measuringTabElements[i].__tab._measuredWidth = measuringTabElements[i].getBoundingClientRect().width; 490 491 // Nuke elements from the UI 492 for (var i = 0; i < measuringTabElements.length; ++i) 493 measuringTabElements[i].remove(); 494 495 // Combine the results. 496 var measuredWidths = []; 497 for (var tabId in this._tabs) 498 measuredWidths.push(this._tabs[tabId]._measuredWidth); 499 500 return measuredWidths; 501 }, 502 503 /** 504 * @param {Array.<number>} measuredWidths 505 * @param {number} totalWidth 506 */ 507 _calculateMaxWidth: function(measuredWidths, totalWidth) 508 { 509 if (!measuredWidths.length) 510 return 0; 511 512 measuredWidths.sort(function(x, y) { return x - y }); 513 514 var totalMeasuredWidth = 0; 515 for (var i = 0; i < measuredWidths.length; ++i) 516 totalMeasuredWidth += measuredWidths[i]; 517 518 if (totalWidth >= totalMeasuredWidth) 519 return measuredWidths[measuredWidths.length - 1]; 520 521 var totalExtraWidth = 0; 522 for (var i = measuredWidths.length - 1; i > 0; --i) { 523 var extraWidth = measuredWidths[i] - measuredWidths[i - 1]; 524 totalExtraWidth += (measuredWidths.length - i) * extraWidth; 525 526 if (totalWidth + totalExtraWidth >= totalMeasuredWidth) 527 return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i); 528 } 529 530 return totalWidth / measuredWidths.length; 531 }, 532 533 /** 534 * @param {Array.<WebInspector.TabbedPaneTab>} tabsOrdered 535 * @param {Array.<WebInspector.TabbedPaneTab>} tabsHistory 536 * @param {number} totalWidth 537 * @param {number} measuredDropDownButtonWidth 538 * @return {Array.<number>} 539 */ 540 _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth) 541 { 542 var tabsToShowIndexes = []; 543 544 var totalTabsWidth = 0; 545 var tabCount = tabsOrdered.length; 546 for (var i = 0; i < tabCount; ++i) { 547 var tab = this._retainTabsOrder ? tabsOrdered[i] : tabsHistory[i]; 548 totalTabsWidth += tab.width(); 549 var minimalRequiredWidth = totalTabsWidth; 550 if (i !== tabCount - 1) 551 minimalRequiredWidth += measuredDropDownButtonWidth; 552 if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth) 553 break; 554 tabsToShowIndexes.push(tabsOrdered.indexOf(tab)); 555 } 556 557 tabsToShowIndexes.sort(function(x, y) { return x - y }); 558 559 return tabsToShowIndexes; 560 }, 561 562 _hideCurrentTab: function() 563 { 564 if (!this._currentTab) 565 return; 566 567 this._hideTab(this._currentTab); 568 delete this._currentTab; 569 }, 570 571 /** 572 * @param {WebInspector.TabbedPaneTab} tab 573 */ 574 _showTab: function(tab) 575 { 576 tab.tabElement.addStyleClass("selected"); 577 tab.view.show(this._contentElement); 578 }, 579 580 /** 581 * @param {WebInspector.TabbedPaneTab} tab 582 */ 583 _hideTab: function(tab) 584 { 585 tab.tabElement.removeStyleClass("selected"); 586 tab.view.detach(); 587 }, 588 589 /** 590 * @override 591 */ 592 canHighlightPosition: function() 593 { 594 return this._currentTab && this._currentTab.view && this._currentTab.view.canHighlightPosition(); 595 }, 596 597 /** 598 * @override 599 */ 600 highlightPosition: function(line, column) 601 { 602 if (this.canHighlightPosition()) 603 this._currentTab.view.highlightPosition(line, column); 604 }, 605 606 /** 607 * @return {Array.<Element>} 608 */ 609 elementsToRestoreScrollPositionsFor: function() 610 { 611 return [ this._contentElement ]; 612 }, 613 614 /** 615 * @param {WebInspector.TabbedPaneTab} tab 616 * @param {number} index 617 */ 618 _insertBefore: function(tab, index) 619 { 620 this._tabsElement.insertBefore(tab._tabElement, this._tabsElement.childNodes[index]); 621 var oldIndex = this._tabs.indexOf(tab); 622 this._tabs.splice(oldIndex, 1); 623 if (oldIndex < index) 624 --index; 625 this._tabs.splice(index, 0, tab); 626 }, 627 628 __proto__: WebInspector.View.prototype 629 } 630 631 632 /** 633 * @constructor 634 * @param {WebInspector.TabbedPane} tabbedPane 635 * @param {string} id 636 * @param {string} title 637 * @param {boolean} closeable 638 * @param {WebInspector.View} view 639 * @param {string=} tooltip 640 */ 641 WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, tooltip) 642 { 643 this._closeable = closeable; 644 this._tabbedPane = tabbedPane; 645 this._id = id; 646 this._title = title; 647 this._tooltip = tooltip; 648 this._view = view; 649 this._shown = false; 650 /** @type {number} */ this._measuredWidth; 651 /** @type {Element} */ this._tabElement; 652 } 653 654 WebInspector.TabbedPaneTab.prototype = { 655 /** 656 * @return {string} 657 */ 658 get id() 659 { 660 return this._id; 661 }, 662 663 /** 664 * @return {string} 665 */ 666 get title() 667 { 668 return this._title; 669 }, 670 671 set title(title) 672 { 673 if (title === this._title) 674 return; 675 this._title = title; 676 if (this._titleElement) 677 this._titleElement.textContent = title; 678 delete this._measuredWidth; 679 }, 680 681 /** 682 * @return {string} 683 */ 684 iconClass: function() 685 { 686 return this._iconClass; 687 }, 688 689 /** 690 * @param {string} iconClass 691 * @param {string} iconTooltip 692 */ 693 _setIconClass: function(iconClass, iconTooltip) 694 { 695 if (iconClass === this._iconClass && iconTooltip === this._iconTooltip) 696 return; 697 this._iconClass = iconClass; 698 this._iconTooltip = iconTooltip; 699 if (this._iconElement) 700 this._iconElement.remove(); 701 if (this._iconClass) 702 this._iconElement = this._createIconElement(this._tabElement, this._titleElement); 703 delete this._measuredWidth; 704 }, 705 706 /** 707 * @return {WebInspector.View} 708 */ 709 get view() 710 { 711 return this._view; 712 }, 713 714 set view(view) 715 { 716 this._view = view; 717 }, 718 719 /** 720 * @return {string|undefined} 721 */ 722 get tooltip() 723 { 724 return this._tooltip; 725 }, 726 727 set tooltip(tooltip) 728 { 729 this._tooltip = tooltip; 730 if (this._titleElement) 731 this._titleElement.title = tooltip || ""; 732 }, 733 734 /** 735 * @return {Element} 736 */ 737 get tabElement() 738 { 739 if (typeof(this._tabElement) !== "undefined") 740 return this._tabElement; 741 742 this._createTabElement(false); 743 return this._tabElement; 744 }, 745 746 /** 747 * @return {number} 748 */ 749 width: function() 750 { 751 return this._width; 752 }, 753 754 /** 755 * @param {number} width 756 */ 757 setWidth: function(width) 758 { 759 this.tabElement.style.width = width === -1 ? "" : (width + "px"); 760 this._width = width; 761 }, 762 763 /** 764 * @param {WebInspector.TabbedPaneTabDelegate} delegate 765 */ 766 setDelegate: function(delegate) 767 { 768 this._delegate = delegate; 769 }, 770 771 _createIconElement: function(tabElement, titleElement) 772 { 773 var iconElement = document.createElement("span"); 774 iconElement.className = "tabbed-pane-header-tab-icon " + this._iconClass; 775 if (this._iconTooltip) 776 iconElement.title = this._iconTooltip; 777 tabElement.insertBefore(iconElement, titleElement); 778 return iconElement; 779 }, 780 781 /** 782 * @param {boolean} measuring 783 */ 784 _createTabElement: function(measuring) 785 { 786 var tabElement = document.createElement("div"); 787 tabElement.addStyleClass("tabbed-pane-header-tab"); 788 tabElement.id = "tab-" + this._id; 789 tabElement.tabIndex = -1; 790 791 var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title"); 792 titleElement.textContent = this.title; 793 titleElement.title = this.tooltip || ""; 794 if (this._iconClass) 795 this._createIconElement(tabElement, titleElement); 796 if (!measuring) 797 this._titleElement = titleElement; 798 799 if (this._closeable) 800 tabElement.createChild("div", "close-button-gray"); 801 802 if (measuring) 803 tabElement.addStyleClass("measuring"); 804 else { 805 this._tabElement = tabElement; 806 tabElement.addEventListener("click", this._tabClicked.bind(this), false); 807 tabElement.addEventListener("mousedown", this._tabMouseDown.bind(this), false); 808 if (this._closeable) { 809 tabElement.addEventListener("contextmenu", this._tabContextMenu.bind(this), false); 810 WebInspector.installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "pointer"); 811 } 812 } 813 814 return tabElement; 815 }, 816 817 /** 818 * @param {Event} event 819 */ 820 _tabClicked: function(event) 821 { 822 if (this._closeable && (event.button === 1 || event.target.hasStyleClass("close-button-gray"))) 823 this._closeTabs([this.id]); 824 }, 825 826 /** 827 * @param {Event} event 828 */ 829 _tabMouseDown: function(event) 830 { 831 if (event.target.hasStyleClass("close-button-gray") || event.button === 1) 832 return; 833 this._tabbedPane.selectTab(this.id, true); 834 }, 835 836 /** 837 * @param {Array.<string>} ids 838 */ 839 _closeTabs: function(ids) 840 { 841 if (this._delegate) { 842 this._delegate.closeTabs(this._tabbedPane, ids); 843 return; 844 } 845 this._tabbedPane.closeTabs(ids, true); 846 }, 847 848 _tabContextMenu: function(event) 849 { 850 function close() 851 { 852 this._closeTabs([this.id]); 853 } 854 855 function closeOthers() 856 { 857 this._closeTabs(this._tabbedPane.otherTabs(this.id)); 858 } 859 860 function closeAll() 861 { 862 this._closeTabs(this._tabbedPane.allTabs(this.id)); 863 } 864 865 var contextMenu = new WebInspector.ContextMenu(event); 866 contextMenu.appendItem(WebInspector.UIString("Close"), close.bind(this)); 867 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close others" : "Close Others"), closeOthers.bind(this)); 868 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close all" : "Close All"), closeAll.bind(this)); 869 contextMenu.show(); 870 }, 871 872 /** 873 * @param {Event} event 874 * @return {boolean} 875 */ 876 _startTabDragging: function(event) 877 { 878 if (event.target.hasStyleClass("close-button-gray")) 879 return false; 880 this._dragStartX = event.pageX; 881 return true; 882 }, 883 884 /** 885 * @param {Event} event 886 */ 887 _tabDragging: function(event) 888 { 889 var tabElements = this._tabbedPane._tabsElement.childNodes; 890 for (var i = 0; i < tabElements.length; ++i) { 891 var tabElement = tabElements[i]; 892 if (tabElement === this._tabElement) 893 continue; 894 895 var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft && 896 this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft; 897 if (!intersects) 898 continue; 899 900 if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2 + 5) 901 break; 902 903 if (event.pageX - this._dragStartX > 0) { 904 tabElement = tabElement.nextSibling; 905 ++i; 906 } 907 908 var oldOffsetLeft = this._tabElement.offsetLeft; 909 this._tabbedPane._insertBefore(this, i); 910 this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft; 911 break; 912 } 913 914 if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0) { 915 this._tabElement.style.setProperty("left", "0px"); 916 return; 917 } 918 if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) { 919 this._tabElement.style.setProperty("left", "0px"); 920 return; 921 } 922 923 this._tabElement.style.setProperty("position", "relative"); 924 this._tabElement.style.setProperty("left", (event.pageX - this._dragStartX) + "px"); 925 }, 926 927 /** 928 * @param {Event} event 929 */ 930 _endTabDragging: function(event) 931 { 932 this._tabElement.style.removeProperty("position"); 933 this._tabElement.style.removeProperty("left"); 934 delete this._dragStartX; 935 } 936 } 937 938 /** 939 * @interface 940 */ 941 WebInspector.TabbedPaneTabDelegate = function() 942 { 943 } 944 945 WebInspector.TabbedPaneTabDelegate.prototype = { 946 /** 947 * @param {WebInspector.TabbedPane} tabbedPane 948 * @param {Array.<string>} ids 949 */ 950 closeTabs: function(tabbedPane, ids) { } 951 } 952