2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 * @extends {WebInspector.VBox}
35 WebInspector
.TabbedPane = function()
37 WebInspector
.VBox
.call(this, true);
38 this.registerRequiredCSS("ui/tabbedPane.css");
39 this.element
.classList
.add("tabbed-pane");
40 this.contentElement
.classList
.add("tabbed-pane-shadow");
41 this.contentElement
.tabIndex
= -1;
42 this._headerElement
= this.contentElement
.createChild("div", "tabbed-pane-header toolbar-colors");
43 this._headerElement
.createChild("content").select
= ".tabbed-pane-header-before";
44 this._headerContentsElement
= this._headerElement
.createChild("div", "tabbed-pane-header-contents");
45 this._tabSlider
= createElementWithClass("div", "tabbed-pane-tab-slider");
46 this._headerElement
.createChild("content").select
= ".tabbed-pane-header-after";
47 this._tabsElement
= this._headerContentsElement
.createChild("div", "tabbed-pane-header-tabs");
48 this._contentElement
= this.contentElement
.createChild("div", "tabbed-pane-content");
49 this._contentElement
.createChild("content");
50 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */
52 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */
53 this._tabsHistory
= [];
54 /** @type {!Object.<string, !WebInspector.TabbedPaneTab>} */
56 this._currentTabLocked
= false;
58 this._dropDownButton
= this._createDropDownButton();
59 WebInspector
.zoomManager
.addEventListener(WebInspector
.ZoomManager
.Events
.ZoomChanged
, this._zoomChanged
, this);
62 WebInspector
.TabbedPane
.EventTypes
= {
63 TabSelected
: "TabSelected",
64 TabClosed
: "TabClosed",
65 TabOrderChanged
: "TabOrderChanged"
68 WebInspector
.TabbedPane
.prototype = {
70 * @param {boolean} locked
72 setCurrentTabLocked: function(locked
)
74 this._currentTabLocked
= locked
;
75 this._headerElement
.classList
.toggle("locked", this._currentTabLocked
);
79 * @return {?WebInspector.Widget}
83 return this._currentTab
? this._currentTab
.view
: null;
87 * @return {!Array.<!WebInspector.Widget>}
92 * @param {!WebInspector.TabbedPaneTab} tab
93 * @return {!WebInspector.Widget}
95 function tabToView(tab
)
99 return this._tabs
.map(tabToView
);
107 return this._currentTab
? this._currentTab
.id
: null;
111 * @param {boolean} shrinkableTabs
113 setShrinkableTabs: function(shrinkableTabs
)
115 this._shrinkableTabs
= shrinkableTabs
;
119 * @param {boolean} verticalTabLayout
121 setVerticalTabLayout: function(verticalTabLayout
)
123 this._verticalTabLayout
= verticalTabLayout
;
124 this.contentElement
.classList
.add("vertical-tab-layout");
125 this.invalidateConstraints();
129 * @param {boolean} closeableTabs
131 setCloseableTabs: function(closeableTabs
)
133 this._closeableTabs
= closeableTabs
;
140 defaultFocusedElement: function()
142 return this.visibleView
? this.visibleView
.defaultFocusedElement() : this.contentElement
;
147 if (this.visibleView
)
148 this.visibleView
.focus();
150 this.contentElement
.focus();
156 headerElement: function()
158 return this._headerElement
;
165 isTabCloseable: function(id
)
167 var tab
= this._tabsById
[id
];
168 return tab
? tab
.isCloseable() : false;
172 * @param {!WebInspector.TabbedPaneTabDelegate} delegate
174 setTabDelegate: function(delegate
)
176 var tabs
= this._tabs
.slice();
177 for (var i
= 0; i
< tabs
.length
; ++i
)
178 tabs
[i
].setDelegate(delegate
);
179 this._delegate
= delegate
;
184 * @param {string} tabTitle
185 * @param {!WebInspector.Widget} view
186 * @param {string=} tabTooltip
187 * @param {boolean=} userGesture
188 * @param {boolean=} isCloseable
189 * @param {number=} index
191 appendTab: function(id
, tabTitle
, view
, tabTooltip
, userGesture
, isCloseable
, index
)
193 isCloseable
= typeof isCloseable
=== "boolean" ? isCloseable
: this._closeableTabs
;
194 var tab
= new WebInspector
.TabbedPaneTab(this, id
, tabTitle
, isCloseable
, view
, tabTooltip
);
195 tab
.setDelegate(this._delegate
);
196 this._tabsById
[id
] = tab
;
197 if (index
!== undefined)
198 this._tabs
.splice(index
, 0, tab
);
200 this._tabs
.push(tab
);
201 this._tabsHistory
.push(tab
);
202 if (this._tabsHistory
[0] === tab
&& this.isShowing())
203 this.selectTab(tab
.id
, userGesture
);
204 this._updateTabElements();
209 * @param {boolean=} userGesture
211 closeTab: function(id
, userGesture
)
213 this.closeTabs([id
], userGesture
);
217 * @param {!Array.<string>} ids
218 * @param {boolean=} userGesture
220 closeTabs: function(ids
, userGesture
)
222 var focused
= this.hasFocus();
223 for (var i
= 0; i
< ids
.length
; ++i
)
224 this._innerCloseTab(ids
[i
], userGesture
);
225 this._updateTabElements();
226 if (this._tabsHistory
.length
)
227 this.selectTab(this._tabsHistory
[0].id
, false);
234 * @param {boolean=} userGesture
236 _innerCloseTab: function(id
, userGesture
)
238 if (!this._tabsById
[id
])
240 if (userGesture
&& !this._tabsById
[id
]._closeable
)
242 if (this._currentTab
&& this._currentTab
.id
=== id
)
243 this._hideCurrentTab();
245 var tab
= this._tabsById
[id
];
246 delete this._tabsById
[id
];
248 this._tabsHistory
.splice(this._tabsHistory
.indexOf(tab
), 1);
249 this._tabs
.splice(this._tabs
.indexOf(tab
), 1);
251 this._hideTabElement(tab
);
253 var eventData
= { tabId
: id
, view
: tab
.view
, isUserGesture
: userGesture
};
254 this.dispatchEventToListeners(WebInspector
.TabbedPane
.EventTypes
.TabClosed
, eventData
);
259 * @param {string} tabId
262 hasTab: function(tabId
)
264 return !!this._tabsById
[tabId
];
268 * @return {!Array.<string>}
272 return this._tabs
.map(function (tab
) { return tab
.id
; });
277 * @return {!Array.<string>}
279 otherTabs: function(id
)
282 for (var i
= 0; i
< this._tabs
.length
; ++i
) {
283 if (this._tabs
[i
].id
!== id
)
284 result
.push(this._tabs
[i
].id
);
291 * @return {!Array.<string>}
293 _tabsToTheRight: function(id
)
296 for (var i
= 0; i
< this._tabs
.length
; ++i
) {
297 if (this._tabs
[i
].id
=== id
) {
304 return this._tabs
.slice(index
+ 1).map(function (tab
) { return tab
.id
; });
309 * @param {boolean=} userGesture
312 selectTab: function(id
, userGesture
)
314 if (this._currentTabLocked
)
316 var focused
= this.hasFocus();
317 var tab
= this._tabsById
[id
];
320 if (this._currentTab
&& this._currentTab
.id
=== id
)
323 this._hideCurrentTab();
325 this._currentTab
= tab
;
327 this._tabsHistory
.splice(this._tabsHistory
.indexOf(tab
), 1);
328 this._tabsHistory
.splice(0, 0, tab
);
330 this._updateTabElements();
334 var eventData
= { tabId
: id
, view
: tab
.view
, isUserGesture
: userGesture
};
335 this.dispatchEventToListeners(WebInspector
.TabbedPane
.EventTypes
.TabSelected
, eventData
);
340 * @param {number} tabsCount
341 * @return {!Array.<string>}
343 lastOpenedTabIds: function(tabsCount
)
345 function tabToTabId(tab
) {
349 return this._tabsHistory
.slice(0, tabsCount
).map(tabToTabId
);
354 * @param {string} iconType
355 * @param {string=} iconTooltip
357 setTabIcon: function(id
, iconType
, iconTooltip
)
359 var tab
= this._tabsById
[id
];
360 if (tab
._setIconType(iconType
, iconTooltip
))
361 this._updateTabElements();
366 * @param {string} className
367 * @param {boolean=} force
369 toggleTabClass: function(id
, className
, force
)
371 var tab
= this._tabsById
[id
];
372 if (tab
._toggleClass(className
, force
))
373 this._updateTabElements();
377 * @param {!WebInspector.Event} event
379 _zoomChanged: function(event
)
381 for (var i
= 0; i
< this._tabs
.length
; ++i
)
382 delete this._tabs
[i
]._measuredWidth
;
383 if (this.isShowing())
384 this._updateTabElements();
389 * @param {string} tabTitle
391 changeTabTitle: function(id
, tabTitle
)
393 var tab
= this._tabsById
[id
];
394 if (tab
.title
=== tabTitle
)
396 tab
.title
= tabTitle
;
397 this._updateTabElements();
402 * @param {!WebInspector.Widget} view
404 changeTabView: function(id
, view
)
406 var tab
= this._tabsById
[id
];
407 if (this._currentTab
&& this._currentTab
.id
=== tab
.id
) {
408 if (tab
.view
!== view
)
418 this._updateTabElements();
421 headerResized: function()
423 this._updateTabElements();
428 var effectiveTab
= this._currentTab
|| this._tabsHistory
[0];
430 this.selectTab(effectiveTab
.id
);
434 * @param {boolean} enable
436 setTabSlider: function(enable
)
438 this._sliderEnabled
= enable
;
439 this._tabSlider
.classList
.toggle("enabled", enable
);
444 * @return {!Constraints}
446 calculateConstraints: function()
448 var constraints
= WebInspector
.VBox
.prototype.calculateConstraints
.call(this);
449 var minContentConstraints
= new Constraints(new Size(0, 0), new Size(50, 50));
450 constraints
= constraints
.widthToMax(minContentConstraints
).heightToMax(minContentConstraints
);
451 if (this._verticalTabLayout
)
452 constraints
= constraints
.addWidth(new Constraints(new Size(120, 0)));
454 constraints
= constraints
.addHeight(new Constraints(new Size(0, 30)));
458 _updateTabElements: function()
460 WebInspector
.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements
);
464 * @param {string} text
466 setPlaceholderText: function(text
)
468 this._noTabsMessage
= text
;
471 _innerUpdateTabElements: function()
473 if (!this.isShowing())
476 if (!this._tabs
.length
) {
477 this._contentElement
.classList
.add("has-no-tabs");
478 if (this._noTabsMessage
&& !this._noTabsMessageElement
) {
479 this._noTabsMessageElement
= this._contentElement
.createChild("div", "tabbed-pane-placeholder fill");
480 this._noTabsMessageElement
.textContent
= this._noTabsMessage
;
483 this._contentElement
.classList
.remove("has-no-tabs");
484 if (this._noTabsMessageElement
) {
485 this._noTabsMessageElement
.remove();
486 delete this._noTabsMessageElement
;
490 if (!this._measuredDropDownButtonWidth
)
491 this._measureDropDownButton();
493 this._updateWidths();
494 this._updateTabsDropDown();
495 this._updateTabSlider();
499 * @param {number} index
500 * @param {!WebInspector.TabbedPaneTab} tab
502 _showTabElement: function(index
, tab
)
504 if (index
>= this._tabsElement
.children
.length
)
505 this._tabsElement
.appendChild(tab
.tabElement
);
507 this._tabsElement
.insertBefore(tab
.tabElement
, this._tabsElement
.children
[index
]);
512 * @param {!WebInspector.TabbedPaneTab} tab
514 _hideTabElement: function(tab
)
516 this._tabsElement
.removeChild(tab
.tabElement
);
520 _createDropDownButton: function()
522 var dropDownContainer
= createElementWithClass("div", "tabbed-pane-header-tabs-drop-down-container");
523 dropDownContainer
.createChild("div", "glyph");
524 this._dropDownMenu
= new WebInspector
.DropDownMenu(dropDownContainer
);
525 this._dropDownMenu
.addEventListener(WebInspector
.DropDownMenu
.Events
.ItemSelected
, this._dropDownMenuItemSelected
, this);
527 return dropDownContainer
;
531 * @param {!WebInspector.Event} event
533 _dropDownMenuItemSelected: function(event
)
535 var tabId
= /** @type {string} */ (event
.data
);
536 this._lastSelectedOverflowTab
= this._tabsById
[tabId
];
537 this.selectTab(tabId
, true);
540 _totalWidth: function()
542 return this._headerContentsElement
.getBoundingClientRect().width
;
548 _numberOfTabsShown: function()
550 var numTabsShown
= 0;
551 for (var tab
of this._tabs
) {
558 _updateTabsDropDown: function()
560 var tabsToShowIndexes
= this._tabsToShowIndexes(this._tabs
, this._tabsHistory
, this._totalWidth(), this._measuredDropDownButtonWidth
);
561 if (this._lastSelectedOverflowTab
&& this._numberOfTabsShown() !== tabsToShowIndexes
.length
) {
562 delete this._lastSelectedOverflowTab
;
563 this._updateTabsDropDown();
567 for (var i
= 0; i
< this._tabs
.length
; ++i
) {
568 if (this._tabs
[i
]._shown
&& tabsToShowIndexes
.indexOf(i
) === -1)
569 this._hideTabElement(this._tabs
[i
]);
571 for (var i
= 0; i
< tabsToShowIndexes
.length
; ++i
) {
572 var tab
= this._tabs
[tabsToShowIndexes
[i
]];
574 this._showTabElement(i
, tab
);
577 this._populateDropDownFromIndex();
580 _populateDropDownFromIndex: function()
582 if (this._dropDownButton
.parentElement
)
583 this._headerContentsElement
.removeChild(this._dropDownButton
);
585 this._dropDownMenu
.clear();
588 for (var i
= 0; i
< this._tabs
.length
; ++i
) {
589 if (!this._tabs
[i
]._shown
)
590 tabsToShow
.push(this._tabs
[i
]);
594 var selectedId
= null;
595 for (var i
= 0; i
< tabsToShow
.length
; ++i
) {
596 var tab
= tabsToShow
[i
];
597 this._dropDownMenu
.addItem(tab
.id
, tab
.title
);
598 if (this._tabsHistory
[0] === tab
)
601 if (tabsToShow
.length
) {
602 this._headerContentsElement
.appendChild(this._dropDownButton
);
603 this._dropDownMenu
.selectItem(selectedId
);
607 _measureDropDownButton: function()
609 this._dropDownButton
.classList
.add("measuring");
610 this._headerContentsElement
.appendChild(this._dropDownButton
);
611 this._measuredDropDownButtonWidth
= this._dropDownButton
.getBoundingClientRect().width
;
612 this._headerContentsElement
.removeChild(this._dropDownButton
);
613 this._dropDownButton
.classList
.remove("measuring");
616 _updateWidths: function()
618 var measuredWidths
= this._measureWidths();
619 var maxWidth
= this._shrinkableTabs
? this._calculateMaxWidth(measuredWidths
.slice(), this._totalWidth()) : Number
.MAX_VALUE
;
622 for (var tabId
in this._tabs
) {
623 var tab
= this._tabs
[tabId
];
624 tab
.setWidth(this._verticalTabLayout
? -1 : Math
.min(maxWidth
, measuredWidths
[i
++]));
628 _measureWidths: function()
630 // Add all elements to measure into this._tabsElement
631 this._tabsElement
.style
.setProperty("width", "2000px");
632 var measuringTabElements
= [];
633 for (var tabId
in this._tabs
) {
634 var tab
= this._tabs
[tabId
];
635 if (typeof tab
._measuredWidth
=== "number")
637 var measuringTabElement
= tab
._createTabElement(true);
638 measuringTabElement
.__tab
= tab
;
639 measuringTabElements
.push(measuringTabElement
);
640 this._tabsElement
.appendChild(measuringTabElement
);
643 // Perform measurement
644 for (var i
= 0; i
< measuringTabElements
.length
; ++i
) {
645 var width
= measuringTabElements
[i
].getBoundingClientRect().width
;
646 measuringTabElements
[i
].__tab
._measuredWidth
= Math
.ceil(width
);
649 // Nuke elements from the UI
650 for (var i
= 0; i
< measuringTabElements
.length
; ++i
)
651 measuringTabElements
[i
].remove();
653 // Combine the results.
654 var measuredWidths
= [];
655 for (var tabId
in this._tabs
)
656 measuredWidths
.push(this._tabs
[tabId
]._measuredWidth
);
657 this._tabsElement
.style
.removeProperty("width");
659 return measuredWidths
;
663 * @param {!Array.<number>} measuredWidths
664 * @param {number} totalWidth
666 _calculateMaxWidth: function(measuredWidths
, totalWidth
)
668 if (!measuredWidths
.length
)
671 measuredWidths
.sort(function(x
, y
) { return x
- y
; });
673 var totalMeasuredWidth
= 0;
674 for (var i
= 0; i
< measuredWidths
.length
; ++i
)
675 totalMeasuredWidth
+= measuredWidths
[i
];
677 if (totalWidth
>= totalMeasuredWidth
)
678 return measuredWidths
[measuredWidths
.length
- 1];
680 var totalExtraWidth
= 0;
681 for (var i
= measuredWidths
.length
- 1; i
> 0; --i
) {
682 var extraWidth
= measuredWidths
[i
] - measuredWidths
[i
- 1];
683 totalExtraWidth
+= (measuredWidths
.length
- i
) * extraWidth
;
685 if (totalWidth
+ totalExtraWidth
>= totalMeasuredWidth
)
686 return measuredWidths
[i
- 1] + (totalWidth
+ totalExtraWidth
- totalMeasuredWidth
) / (measuredWidths
.length
- i
);
689 return totalWidth
/ measuredWidths
.length
;
693 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered
694 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory
695 * @param {number} totalWidth
696 * @param {number} measuredDropDownButtonWidth
697 * @return {!Array.<number>}
699 _tabsToShowIndexes: function(tabsOrdered
, tabsHistory
, totalWidth
, measuredDropDownButtonWidth
)
701 var tabsToShowIndexes
= [];
703 var totalTabsWidth
= 0;
704 var tabCount
= tabsOrdered
.length
;
705 var tabsToLookAt
= tabsOrdered
.slice(0);
706 if (this._currentTab
!== undefined)
707 tabsToLookAt
.unshift(tabsToLookAt
.splice(tabsToLookAt
.indexOf(this._currentTab
), 1)[0]);
708 if (this._lastSelectedOverflowTab
!== undefined)
709 tabsToLookAt
.unshift(tabsToLookAt
.splice(tabsToLookAt
.indexOf(this._lastSelectedOverflowTab
), 1)[0]);
710 for (var i
= 0; i
< tabCount
; ++i
) {
711 var tab
= this._automaticReorder
? tabsHistory
[i
] : tabsToLookAt
[i
];
712 totalTabsWidth
+= tab
.width();
713 var minimalRequiredWidth
= totalTabsWidth
;
714 if (i
!== tabCount
- 1)
715 minimalRequiredWidth
+= measuredDropDownButtonWidth
;
716 if (!this._verticalTabLayout
&& minimalRequiredWidth
> totalWidth
)
718 tabsToShowIndexes
.push(tabsOrdered
.indexOf(tab
));
721 tabsToShowIndexes
.sort(function(x
, y
) { return x
- y
; });
723 return tabsToShowIndexes
;
726 _hideCurrentTab: function()
728 if (!this._currentTab
)
731 this._hideTab(this._currentTab
);
732 delete this._currentTab
;
736 * @param {!WebInspector.TabbedPaneTab} tab
738 _showTab: function(tab
)
740 tab
.tabElement
.classList
.add("selected");
741 tab
.view
.show(this.element
);
742 this._updateTabSlider();
745 _updateTabSlider: function()
747 if (!this._currentTab
|| !this._sliderEnabled
)
750 for (var i
= 0; i
< this._tabs
.length
&& this._currentTab
!== this._tabs
[i
] && this._tabs
[i
]._shown
; i
++)
751 left
+= this._tabs
[i
]._measuredWidth
;
752 var sliderWidth
= this._currentTab
._shown
? this._currentTab
._measuredWidth
: this._dropDownButton
.offsetWidth
;
753 var scaleFactor
= window
.devicePixelRatio
>= 1.5 ? " scaleY(0.75)" : "";
754 this._tabSlider
.style
.transform
= "translateX(" + left
+ "px)" + scaleFactor
;
755 this._tabSlider
.style
.width
= sliderWidth
+ "px";
757 if (this._tabSlider
.parentElement
!== this._headerContentsElement
)
758 this._headerContentsElement
.appendChild(this._tabSlider
);
762 * @param {!WebInspector.TabbedPaneTab} tab
764 _hideTab: function(tab
)
766 tab
.tabElement
.classList
.remove("selected");
772 * @return {!Array.<!Element>}
774 elementsToRestoreScrollPositionsFor: function()
776 return [ this._contentElement
];
780 * @param {!WebInspector.TabbedPaneTab} tab
781 * @param {number} index
783 _insertBefore: function(tab
, index
)
785 this._tabsElement
.insertBefore(tab
._tabElement
|| null, this._tabsElement
.childNodes
[index
]);
786 var oldIndex
= this._tabs
.indexOf(tab
);
787 this._tabs
.splice(oldIndex
, 1);
788 if (oldIndex
< index
)
790 this._tabs
.splice(index
, 0, tab
);
791 this.dispatchEventToListeners(WebInspector
.TabbedPane
.EventTypes
.TabOrderChanged
, this._tabs
);
795 * @param {!Element} element
797 insertBeforeTabStrip: function(element
)
799 element
.classList
.add("tabbed-pane-header-before");
800 this.element
.appendChild(element
);
804 * @param {!Element} element
806 appendAfterTabStrip: function(element
)
808 element
.classList
.add("tabbed-pane-header-after");
809 this.element
.appendChild(element
);
812 renderWithNoHeaderBackground: function()
814 this._headerElement
.classList
.add("tabbed-pane-no-header-background");
818 * @param {boolean} allow
819 * @param {boolean=} automatic
821 setAllowTabReorder: function(allow
, automatic
)
823 this._allowTabReorder
= allow
;
824 this._automaticReorder
= automatic
;
827 __proto__
: WebInspector
.VBox
.prototype
832 * @param {!WebInspector.TabbedPane} tabbedPane
834 * @param {string} title
835 * @param {boolean} closeable
836 * @param {!WebInspector.Widget} view
837 * @param {string=} tooltip
839 WebInspector
.TabbedPaneTab = function(tabbedPane
, id
, title
, closeable
, view
, tooltip
)
841 this._closeable
= closeable
;
842 this._tabbedPane
= tabbedPane
;
845 this._tooltip
= tooltip
;
848 /** @type {number} */ this._measuredWidth
;
849 /** @type {!Element|undefined} */ this._tabElement
;
852 WebInspector
.TabbedPaneTab
.prototype = {
871 if (title
=== this._title
)
874 if (this._titleElement
)
875 this._titleElement
.textContent
= title
;
876 delete this._measuredWidth
;
882 isCloseable: function()
884 return this._closeable
;
888 * @param {string} iconType
889 * @param {string=} iconTooltip
892 _setIconType: function(iconType
, iconTooltip
)
894 if (iconType
=== this._iconType
&& iconTooltip
=== this._iconTooltip
)
896 this._iconType
= iconType
;
897 this._iconTooltip
= iconTooltip
;
898 if (this._tabElement
)
899 this._createIconElement(this._tabElement
, this._titleElement
);
900 delete this._measuredWidth
;
905 * @param {string} className
906 * @param {boolean=} force
909 _toggleClass: function(className
, force
)
911 var element
= this.tabElement
;
912 var hasClass
= element
.classList
.contains(className
);
913 if (hasClass
=== force
)
915 element
.classList
.toggle(className
, force
);
916 delete this._measuredWidth
;
921 * @return {!WebInspector.Widget}
934 * @return {string|undefined}
938 return this._tooltip
;
943 this._tooltip
= tooltip
;
944 if (this._titleElement
)
945 this._titleElement
.title
= tooltip
|| "";
953 if (!this._tabElement
)
954 this._tabElement
= this._createTabElement(false);
956 return this._tabElement
;
968 * @param {number} width
970 setWidth: function(width
)
972 this.tabElement
.style
.width
= width
=== -1 ? "" : (width
+ "px");
977 * @param {!WebInspector.TabbedPaneTabDelegate} delegate
979 setDelegate: function(delegate
)
981 this._delegate
= delegate
;
985 * @param {!Element} tabElement
986 * @param {!Element} titleElement
988 _createIconElement: function(tabElement
, titleElement
)
990 if (tabElement
.__iconElement
)
991 tabElement
.__iconElement
.remove();
995 var iconElement
= createElementWithClass("label", "tabbed-pane-header-tab-icon", "dt-icon-label");
996 iconElement
.type
= this._iconType
;
997 if (this._iconTooltip
)
998 iconElement
.title
= this._iconTooltip
;
999 tabElement
.insertBefore(iconElement
, titleElement
);
1000 tabElement
.__iconElement
= iconElement
;
1004 * @param {boolean} measuring
1005 * @return {!Element}
1007 _createTabElement: function(measuring
)
1009 var tabElement
= createElementWithClass("div", "tabbed-pane-header-tab");
1010 tabElement
.id
= "tab-" + this._id
;
1011 tabElement
.tabIndex
= -1;
1012 tabElement
.selectTabForTest
= this._tabbedPane
.selectTab
.bind(this._tabbedPane
, this.id
, true);
1014 var titleElement
= tabElement
.createChild("span", "tabbed-pane-header-tab-title");
1015 titleElement
.textContent
= this.title
;
1016 titleElement
.title
= this.tooltip
|| "";
1017 this._createIconElement(tabElement
, titleElement
);
1019 this._titleElement
= titleElement
;
1021 if (this._closeable
)
1022 tabElement
.createChild("div", "tabbed-pane-close-button", "dt-close-button").gray
= true;
1025 tabElement
.classList
.add("measuring");
1027 tabElement
.addEventListener("click", this._tabClicked
.bind(this), false);
1028 tabElement
.addEventListener("mousedown", this._tabMouseDown
.bind(this), false);
1029 tabElement
.addEventListener("mouseup", this._tabMouseUp
.bind(this), false);
1031 tabElement
.addEventListener("contextmenu", this._tabContextMenu
.bind(this), false);
1032 if (this._tabbedPane
._allowTabReorder
)
1033 WebInspector
.installDragHandle(tabElement
, this._startTabDragging
.bind(this), this._tabDragging
.bind(this), this._endTabDragging
.bind(this), "-webkit-grabbing", "pointer");
1040 * @param {!Event} event
1042 _tabClicked: function(event
)
1044 var middleButton
= event
.button
=== 1;
1045 var shouldClose
= this._closeable
&& (middleButton
|| event
.target
.classList
.contains("tabbed-pane-close-button"));
1047 this._tabbedPane
.focus();
1050 this._closeTabs([this.id
]);
1051 event
.consume(true);
1055 * @param {!Event} event
1057 _tabMouseDown: function(event
)
1059 if (event
.target
.classList
.contains("tabbed-pane-close-button") || event
.button
=== 1)
1061 this._tabbedPane
.selectTab(this.id
, true);
1065 * @param {!Event} event
1067 _tabMouseUp: function(event
)
1069 // This is needed to prevent middle-click pasting on linux when tabs are clicked.
1070 if (event
.button
=== 1)
1071 event
.consume(true);
1075 * @param {!Array.<string>} ids
1077 _closeTabs: function(ids
)
1079 if (this._delegate
) {
1080 this._delegate
.closeTabs(this._tabbedPane
, ids
);
1083 this._tabbedPane
.closeTabs(ids
, true);
1086 _tabContextMenu: function(event
)
1089 * @this {WebInspector.TabbedPaneTab}
1093 this._closeTabs([this.id
]);
1097 * @this {WebInspector.TabbedPaneTab}
1099 function closeOthers()
1101 this._closeTabs(this._tabbedPane
.otherTabs(this.id
));
1105 * @this {WebInspector.TabbedPaneTab}
1109 this._closeTabs(this._tabbedPane
.allTabs());
1113 * @this {WebInspector.TabbedPaneTab}
1115 function closeToTheRight()
1117 this._closeTabs(this._tabbedPane
._tabsToTheRight(this.id
));
1120 var contextMenu
= new WebInspector
.ContextMenu(event
);
1121 if (this._closeable
) {
1122 contextMenu
.appendItem(WebInspector
.UIString
.capitalize("Close"), close
.bind(this));
1123 contextMenu
.appendItem(WebInspector
.UIString
.capitalize("Close ^others"), closeOthers
.bind(this));
1124 contextMenu
.appendItem(WebInspector
.UIString
.capitalize("Close ^tabs to the ^right"), closeToTheRight
.bind(this));
1125 contextMenu
.appendItem(WebInspector
.UIString
.capitalize("Close ^all"), closeAll
.bind(this));
1128 this._delegate
.onContextMenu(this.id
, contextMenu
);
1133 * @param {!Event} event
1136 _startTabDragging: function(event
)
1138 if (event
.target
.classList
.contains("tabbed-pane-close-button"))
1140 this._dragStartX
= event
.pageX
;
1145 * @param {!Event} event
1147 _tabDragging: function(event
)
1149 var tabElements
= this._tabbedPane
._tabsElement
.childNodes
;
1150 for (var i
= 0; i
< tabElements
.length
; ++i
) {
1151 var tabElement
= tabElements
[i
];
1152 if (tabElement
=== this._tabElement
)
1155 var intersects
= tabElement
.offsetLeft
+ tabElement
.clientWidth
> this._tabElement
.offsetLeft
&&
1156 this._tabElement
.offsetLeft
+ this._tabElement
.clientWidth
> tabElement
.offsetLeft
;
1160 if (Math
.abs(event
.pageX
- this._dragStartX
) < tabElement
.clientWidth
/ 2 + 5)
1163 if (event
.pageX
- this._dragStartX
> 0) {
1164 tabElement
= tabElement
.nextSibling
;
1168 var oldOffsetLeft
= this._tabElement
.offsetLeft
;
1169 this._tabbedPane
._insertBefore(this, i
);
1170 this._dragStartX
+= this._tabElement
.offsetLeft
- oldOffsetLeft
;
1174 if (!this._tabElement
.previousSibling
&& event
.pageX
- this._dragStartX
< 0) {
1175 this._tabElement
.style
.setProperty("left", "0px");
1178 if (!this._tabElement
.nextSibling
&& event
.pageX
- this._dragStartX
> 0) {
1179 this._tabElement
.style
.setProperty("left", "0px");
1183 this._tabElement
.classList
.add("dragging");
1184 this._tabElement
.style
.setProperty("left", (event
.pageX
- this._dragStartX
) + "px");
1185 this._tabbedPane
._tabSlider
.remove();
1189 * @param {!Event} event
1191 _endTabDragging: function(event
)
1193 this._tabElement
.classList
.remove("dragging");
1194 this._tabElement
.style
.removeProperty("left");
1195 delete this._dragStartX
;
1196 this._tabbedPane
._updateTabSlider();
1203 WebInspector
.TabbedPaneTabDelegate = function()
1207 WebInspector
.TabbedPaneTabDelegate
.prototype = {
1209 * @param {!WebInspector.TabbedPane} tabbedPane
1210 * @param {!Array.<string>} ids
1212 closeTabs: function(tabbedPane
, ids
) { },
1215 * @param {string} tabId
1216 * @param {!WebInspector.ContextMenu} contextMenu
1218 onContextMenu: function(tabId
, contextMenu
) { }
1223 * @param {!WebInspector.TabbedPane} tabbedPane
1224 * @param {string} extensionPoint
1225 * @param {function(string, !WebInspector.Widget)=} viewCallback
1227 WebInspector
.ExtensibleTabbedPaneController = function(tabbedPane
, extensionPoint
, viewCallback
)
1229 this._tabbedPane
= tabbedPane
;
1230 this._extensionPoint
= extensionPoint
;
1231 this._viewCallback
= viewCallback
;
1232 this._tabOrders
= {};
1233 /** @type {!Object.<string, !Promise.<?WebInspector.Widget>>} */
1234 this._promiseForId
= {};
1236 this._tabbedPane
.addEventListener(WebInspector
.TabbedPane
.EventTypes
.TabSelected
, this._tabSelected
, this);
1237 /** @type {!Map.<string, ?WebInspector.Widget>} */
1238 this._views
= new Map();
1242 WebInspector
.ExtensibleTabbedPaneController
.prototype = {
1243 _initialize: function()
1245 /** @type {!Map.<string, !Runtime.Extension>} */
1246 this._extensions
= new Map();
1247 var extensions
= self
.runtime
.extensions(this._extensionPoint
);
1249 for (var i
= 0; i
< extensions
.length
; ++i
) {
1250 var descriptor
= extensions
[i
].descriptor();
1251 var id
= descriptor
["name"];
1252 this._tabOrders
[id
] = i
;
1253 var title
= WebInspector
.UIString(descriptor
["title"]);
1255 this._extensions
.set(id
, extensions
[i
]);
1256 this._tabbedPane
.appendTab(id
, title
, new WebInspector
.Widget());
1261 * @param {!WebInspector.Event} event
1263 _tabSelected: function(event
)
1265 var tabId
= /** @type {string} */ (event
.data
.tabId
);
1266 this.viewForId(tabId
).then(viewLoaded
.bind(this));
1269 * @this {WebInspector.ExtensibleTabbedPaneController}
1270 * @param {?WebInspector.Widget} view
1272 function viewLoaded(view
)
1276 this._tabbedPane
.changeTabView(tabId
, view
);
1277 var shouldFocus
= this._tabbedPane
.visibleView
.element
.isSelfOrAncestor(WebInspector
.currentFocusElement());
1284 * @return {!Array.<string>}
1288 return this._extensions
.keysArray();
1292 * @param {string} id
1293 * @return {!Promise.<?WebInspector.Widget>}
1295 viewForId: function(id
)
1297 if (this._views
.has(id
))
1298 return Promise
.resolve(/** @type {?WebInspector.Widget} */ (this._views
.get(id
)));
1299 if (!this._extensions
.has(id
))
1300 return Promise
.resolve(/** @type {?WebInspector.Widget} */ (null));
1301 if (this._promiseForId
[id
])
1302 return this._promiseForId
[id
];
1304 var promise
= this._extensions
.get(id
).instancePromise();
1305 this._promiseForId
[id
] = /** @type {!Promise.<?WebInspector.Widget>} */ (promise
);
1306 return promise
.then(cacheView
.bind(this));
1309 * @param {!Object} object
1310 * @this {WebInspector.ExtensibleTabbedPaneController}
1312 function cacheView(object
)
1314 var view
= /** @type {!WebInspector.Widget} */ (object
);
1315 delete this._promiseForId
[id
];
1316 this._views
.set(id
, view
);
1317 if (this._viewCallback
&& view
)
1318 this._viewCallback(id
, view
);