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);
38 this.element.classList.add("tabbed-pane");
39 this.element.tabIndex = -1;
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");
44 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */
46 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */
47 this._tabsHistory = [];
48 /** @type {!Object.<string, !WebInspector.TabbedPaneTab>} */
50 this._currentTabLocked = false;
52 this._dropDownButton = this._createDropDownButton();
53 WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._zoomChanged, this);
56 WebInspector.TabbedPane.EventTypes = {
57 TabSelected: "TabSelected",
58 TabClosed: "TabClosed"
61 WebInspector.TabbedPane.prototype = {
63 * @param {boolean} locked
65 setCurrentTabLocked: function(locked)
67 this._currentTabLocked = locked;
68 this._headerElement.classList.toggle("locked", this._currentTabLocked);
72 * @return {?WebInspector.View}
76 return this._currentTab ? this._currentTab.view : null;
80 * @return {!Array.<!WebInspector.View>}
85 * @param {!WebInspector.TabbedPaneTab} tab
86 * @return {!WebInspector.View}
88 function tabToView(tab)
92 return this._tabs.map(tabToView);
100 return this._currentTab ? this._currentTab.id : null;
104 * @type {boolean} shrinkableTabs
106 set shrinkableTabs(shrinkableTabs)
108 this._shrinkableTabs = shrinkableTabs;
112 * @type {boolean} verticalTabLayout
114 set verticalTabLayout(verticalTabLayout)
116 this._verticalTabLayout = verticalTabLayout;
117 this.invalidateConstraints();
121 * @type {boolean} closeableTabs
123 set closeableTabs(closeableTabs)
125 this._closeableTabs = closeableTabs;
129 * @param {boolean} retainTabOrder
130 * @param {function(string, string):number=} tabOrderComparator
132 setRetainTabOrder: function(retainTabOrder, tabOrderComparator)
134 this._retainTabOrder = retainTabOrder;
135 this._tabOrderComparator = tabOrderComparator;
141 defaultFocusedElement: function()
143 return this.visibleView ? this.visibleView.defaultFocusedElement() : null;
148 if (this.visibleView)
149 this.visibleView.focus();
151 this.element.focus();
157 headerElement: function()
159 return this._headerElement;
166 isTabCloseable: function(id)
168 var tab = this._tabsById[id];
169 return tab ? tab.isCloseable() : false;
173 * @param {!WebInspector.TabbedPaneTabDelegate} delegate
175 setTabDelegate: function(delegate)
177 var tabs = this._tabs.slice();
178 for (var i = 0; i < tabs.length; ++i)
179 tabs[i].setDelegate(delegate);
180 this._delegate = delegate;
185 * @param {string} tabTitle
186 * @param {!WebInspector.View} view
187 * @param {string=} tabTooltip
188 * @param {boolean=} userGesture
189 * @param {boolean=} isCloseable
191 appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable)
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;
199 * @param {!WebInspector.TabbedPaneTab} tab1
200 * @param {!WebInspector.TabbedPaneTab} tab2
201 * @this {WebInspector.TabbedPane}
204 function comparator(tab1, tab2)
206 return this._tabOrderComparator(tab1.id, tab2.id);
209 if (this._tabOrderComparator)
210 this._tabs.splice(insertionIndexForObjectInListSortedByFunction(tab, this._tabs, comparator.bind(this)), 0, tab);
212 this._tabs.push(tab);
214 this._tabsHistory.push(tab);
216 if (this._tabsHistory[0] === tab && this.isShowing())
217 this.selectTab(tab.id, userGesture);
219 this._updateTabElements();
224 * @param {boolean=} userGesture
226 closeTab: function(id, userGesture)
228 this.closeTabs([id], userGesture);
232 * @param {!Array.<string>} ids
233 * @param {boolean=} userGesture
235 closeTabs: function(ids, userGesture)
237 var focused = this.hasFocus();
238 for (var i = 0; i < ids.length; ++i)
239 this._innerCloseTab(ids[i], userGesture);
240 this._updateTabElements();
241 if (this._tabsHistory.length)
242 this.selectTab(this._tabsHistory[0].id, false);
249 * @param {boolean=} userGesture
251 _innerCloseTab: function(id, userGesture)
253 if (!this._tabsById[id])
255 if (userGesture && !this._tabsById[id]._closeable)
257 if (this._currentTab && this._currentTab.id === id)
258 this._hideCurrentTab();
260 var tab = this._tabsById[id];
261 delete this._tabsById[id];
263 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
264 this._tabs.splice(this._tabs.indexOf(tab), 1);
266 this._hideTabElement(tab);
268 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
269 this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData);
274 * @param {string} tabId
277 hasTab: function(tabId)
279 return !!this._tabsById[tabId];
283 * @return {!Array.<string>}
288 var tabs = this._tabs.slice();
289 for (var i = 0; i < tabs.length; ++i)
290 result.push(tabs[i].id);
296 * @return {!Array.<string>}
298 otherTabs: function(id)
301 var tabs = this._tabs.slice();
302 for (var i = 0; i < tabs.length; ++i) {
303 if (tabs[i].id !== id)
304 result.push(tabs[i].id);
311 * @param {boolean=} userGesture
314 selectTab: function(id, userGesture)
316 if (this._currentTabLocked)
318 var focused = this.hasFocus();
319 var tab = this._tabsById[id];
322 if (this._currentTab && this._currentTab.id === id)
325 this._hideCurrentTab();
327 this._currentTab = tab;
329 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
330 this._tabsHistory.splice(0, 0, tab);
332 this._updateTabElements();
336 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
337 this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData);
342 * @param {number} tabsCount
343 * @return {!Array.<string>}
345 lastOpenedTabIds: function(tabsCount)
347 function tabToTabId(tab) {
351 return this._tabsHistory.slice(0, tabsCount).map(tabToTabId);
356 * @param {string} iconClass
357 * @param {string=} iconTooltip
359 setTabIcon: function(id, iconClass, iconTooltip)
361 var tab = this._tabsById[id];
362 if (tab._setIconClass(iconClass, iconTooltip))
363 this._updateTabElements();
368 * @param {string} className
369 * @param {boolean=} force
371 toggleTabClass: function(id, className, force)
373 var tab = this._tabsById[id];
374 if (tab._toggleClass(className, force))
375 this._updateTabElements();
379 * @param {!WebInspector.Event} event
381 _zoomChanged: function(event)
383 for (var i = 0; i < this._tabs.length; ++i)
384 delete this._tabs[i]._measuredWidth;
385 if (this.isShowing())
386 this._updateTabElements();
391 * @param {string} tabTitle
393 changeTabTitle: function(id, tabTitle)
395 var tab = this._tabsById[id];
396 if (tab.title === tabTitle)
398 tab.title = tabTitle;
399 this._updateTabElements();
404 * @param {!WebInspector.View} view
406 changeTabView: function(id, view)
408 var tab = this._tabsById[id];
409 if (this._currentTab && this._currentTab.id === tab.id) {
410 if (tab.view !== view)
420 * @param {string=} tabTooltip
422 changeTabTooltip: function(id, tabTooltip)
424 var tab = this._tabsById[id];
425 tab.tooltip = tabTooltip;
430 this._updateTabElements();
433 headerResized: function()
435 this._updateTabElements();
440 var effectiveTab = this._currentTab || this._tabsHistory[0];
442 this.selectTab(effectiveTab.id);
446 * @return {!Constraints}
448 calculateConstraints: function()
450 var constraints = WebInspector.VBox.prototype.calculateConstraints.call(this);
451 var minContentConstraints = new Constraints(new Size(0, 0), new Size(50, 50));
452 constraints = constraints.widthToMax(minContentConstraints).heightToMax(minContentConstraints);
453 if (this._verticalTabLayout)
454 constraints = constraints.addWidth(new Constraints(new Size(this._headerElement.offsetWidth, 0)));
456 constraints = constraints.addHeight(new Constraints(new Size(0, this._headerElement.offsetHeight)));
460 _updateTabElements: function()
462 WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements);
466 * @param {string} text
468 setPlaceholderText: function(text)
470 this._noTabsMessage = text;
473 _innerUpdateTabElements: function()
475 if (!this.isShowing())
478 if (!this._tabs.length) {
479 this._contentElement.classList.add("has-no-tabs");
480 if (this._noTabsMessage && !this._noTabsMessageElement) {
481 this._noTabsMessageElement = this._contentElement.createChild("div", "tabbed-pane-placeholder fill");
482 this._noTabsMessageElement.textContent = this._noTabsMessage;
485 this._contentElement.classList.remove("has-no-tabs");
486 if (this._noTabsMessageElement) {
487 this._noTabsMessageElement.remove();
488 delete this._noTabsMessageElement;
492 if (!this._measuredDropDownButtonWidth)
493 this._measureDropDownButton();
495 this._updateWidths();
496 this._updateTabsDropDown();
500 * @param {number} index
501 * @param {!WebInspector.TabbedPaneTab} tab
503 _showTabElement: function(index, tab)
505 if (index >= this._tabsElement.children.length)
506 this._tabsElement.appendChild(tab.tabElement);
508 this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]);
513 * @param {!WebInspector.TabbedPaneTab} tab
515 _hideTabElement: function(tab)
517 this._tabsElement.removeChild(tab.tabElement);
521 _createDropDownButton: function()
523 var dropDownContainer = createElementWithClass("div", "tabbed-pane-header-tabs-drop-down-container");
524 var dropDownButton = dropDownContainer.createChild("div", "tabbed-pane-header-tabs-drop-down");
525 dropDownButton.createTextChild("\u00bb");
527 this._dropDownMenu = new WebInspector.DropDownMenu();
528 this._dropDownMenu.addEventListener(WebInspector.DropDownMenu.Events.ItemSelected, this._dropDownMenuItemSelected, this);
529 dropDownButton.appendChild(this._dropDownMenu.element);
531 return dropDownContainer;
535 * @param {!WebInspector.Event} event
537 _dropDownMenuItemSelected: function(event)
539 var tabId = /** @type {string} */ (event.data);
540 this.selectTab(tabId, true);
543 _totalWidth: function()
545 return this._headerContentsElement.getBoundingClientRect().width;
548 _updateTabsDropDown: function()
550 var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth);
552 for (var i = 0; i < this._tabs.length; ++i) {
553 if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1)
554 this._hideTabElement(this._tabs[i]);
556 for (var i = 0; i < tabsToShowIndexes.length; ++i) {
557 var tab = this._tabs[tabsToShowIndexes[i]];
559 this._showTabElement(i, tab);
562 this._populateDropDownFromIndex();
565 _populateDropDownFromIndex: function()
567 if (this._dropDownButton.parentElement)
568 this._headerContentsElement.removeChild(this._dropDownButton);
570 this._dropDownMenu.clear();
573 for (var i = 0; i < this._tabs.length; ++i) {
574 if (!this._tabs[i]._shown)
575 tabsToShow.push(this._tabs[i]);
579 function compareFunction(tab1, tab2)
581 return tab1.title.localeCompare(tab2.title);
583 if (!this._retainTabOrder)
584 tabsToShow.sort(compareFunction);
586 var selectedId = null;
587 for (var i = 0; i < tabsToShow.length; ++i) {
588 var tab = tabsToShow[i];
589 this._dropDownMenu.addItem(tab.id, tab.title);
590 if (this._tabsHistory[0] === tab)
593 if (tabsToShow.length) {
594 this._headerContentsElement.appendChild(this._dropDownButton);
595 this._dropDownMenu.selectItem(selectedId);
599 _measureDropDownButton: function()
601 this._dropDownButton.classList.add("measuring");
602 this._headerContentsElement.appendChild(this._dropDownButton);
603 this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width;
604 this._headerContentsElement.removeChild(this._dropDownButton);
605 this._dropDownButton.classList.remove("measuring");
608 _updateWidths: function()
610 var measuredWidths = this._measureWidths();
611 var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE;
614 for (var tabId in this._tabs) {
615 var tab = this._tabs[tabId];
616 tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++]));
620 _measureWidths: function()
622 // Add all elements to measure into this._tabsElement
623 this._tabsElement.style.setProperty("width", "2000px");
624 var measuringTabElements = [];
625 for (var tabId in this._tabs) {
626 var tab = this._tabs[tabId];
627 if (typeof tab._measuredWidth === "number")
629 var measuringTabElement = tab._createTabElement(true);
630 measuringTabElement.__tab = tab;
631 measuringTabElements.push(measuringTabElement);
632 this._tabsElement.appendChild(measuringTabElement);
635 // Perform measurement
636 for (var i = 0; i < measuringTabElements.length; ++i)
637 measuringTabElements[i].__tab._measuredWidth = measuringTabElements[i].getBoundingClientRect().width;
639 // Nuke elements from the UI
640 for (var i = 0; i < measuringTabElements.length; ++i)
641 measuringTabElements[i].remove();
643 // Combine the results.
644 var measuredWidths = [];
645 for (var tabId in this._tabs)
646 measuredWidths.push(this._tabs[tabId]._measuredWidth);
647 this._tabsElement.style.removeProperty("width");
649 return measuredWidths;
653 * @param {!Array.<number>} measuredWidths
654 * @param {number} totalWidth
656 _calculateMaxWidth: function(measuredWidths, totalWidth)
658 if (!measuredWidths.length)
661 measuredWidths.sort(function(x, y) { return x - y });
663 var totalMeasuredWidth = 0;
664 for (var i = 0; i < measuredWidths.length; ++i)
665 totalMeasuredWidth += measuredWidths[i];
667 if (totalWidth >= totalMeasuredWidth)
668 return measuredWidths[measuredWidths.length - 1];
670 var totalExtraWidth = 0;
671 for (var i = measuredWidths.length - 1; i > 0; --i) {
672 var extraWidth = measuredWidths[i] - measuredWidths[i - 1];
673 totalExtraWidth += (measuredWidths.length - i) * extraWidth;
675 if (totalWidth + totalExtraWidth >= totalMeasuredWidth)
676 return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i);
679 return totalWidth / measuredWidths.length;
683 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered
684 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory
685 * @param {number} totalWidth
686 * @param {number} measuredDropDownButtonWidth
687 * @return {!Array.<number>}
689 _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth)
691 var tabsToShowIndexes = [];
693 var totalTabsWidth = 0;
694 var tabCount = tabsOrdered.length;
695 for (var i = 0; i < tabCount; ++i) {
696 var tab = this._retainTabOrder ? tabsOrdered[i] : tabsHistory[i];
697 totalTabsWidth += tab.width();
698 var minimalRequiredWidth = totalTabsWidth;
699 if (i !== tabCount - 1)
700 minimalRequiredWidth += measuredDropDownButtonWidth;
701 if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth)
703 tabsToShowIndexes.push(tabsOrdered.indexOf(tab));
706 tabsToShowIndexes.sort(function(x, y) { return x - y });
708 return tabsToShowIndexes;
711 _hideCurrentTab: function()
713 if (!this._currentTab)
716 this._hideTab(this._currentTab);
717 delete this._currentTab;
721 * @param {!WebInspector.TabbedPaneTab} tab
723 _showTab: function(tab)
725 tab.tabElement.classList.add("selected");
726 tab.view.show(this._contentElement);
730 * @param {!WebInspector.TabbedPaneTab} tab
732 _hideTab: function(tab)
734 tab.tabElement.classList.remove("selected");
739 * @return {!Array.<!Element>}
741 elementsToRestoreScrollPositionsFor: function()
743 return [ this._contentElement ];
747 * @param {!WebInspector.TabbedPaneTab} tab
748 * @param {number} index
750 _insertBefore: function(tab, index)
752 this._tabsElement.insertBefore(tab._tabElement || null, this._tabsElement.childNodes[index]);
753 var oldIndex = this._tabs.indexOf(tab);
754 this._tabs.splice(oldIndex, 1);
755 if (oldIndex < index)
757 this._tabs.splice(index, 0, tab);
760 __proto__: WebInspector.VBox.prototype
765 * @param {!WebInspector.TabbedPane} tabbedPane
767 * @param {string} title
768 * @param {boolean} closeable
769 * @param {!WebInspector.View} view
770 * @param {string=} tooltip
772 WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, tooltip)
774 this._closeable = closeable;
775 this._tabbedPane = tabbedPane;
778 this._tooltip = tooltip;
781 /** @type {number} */ this._measuredWidth;
782 /** @type {!Element|undefined} */ this._tabElement;
785 WebInspector.TabbedPaneTab.prototype = {
804 if (title === this._title)
807 if (this._titleElement)
808 this._titleElement.textContent = title;
809 delete this._measuredWidth;
815 iconClass: function()
817 return this._iconClass;
823 isCloseable: function()
825 return this._closeable;
829 * @param {string} iconClass
830 * @param {string=} iconTooltip
833 _setIconClass: function(iconClass, iconTooltip)
835 if (iconClass === this._iconClass && iconTooltip === this._iconTooltip)
837 this._iconClass = iconClass;
838 this._iconTooltip = iconTooltip;
839 if (this._iconElement)
840 this._iconElement.remove();
841 if (this._iconClass && this._tabElement)
842 this._iconElement = this._createIconElement(this._tabElement, this._titleElement);
843 delete this._measuredWidth;
848 * @param {string} className
849 * @param {boolean=} force
852 _toggleClass: function(className, force)
854 var element = this.tabElement;
855 var hasClass = element.classList.contains(className);
856 if (hasClass === force)
858 element.classList.toggle(className, force);
859 delete this._measuredWidth;
864 * @return {!WebInspector.View}
877 * @return {string|undefined}
881 return this._tooltip;
886 this._tooltip = tooltip;
887 if (this._titleElement)
888 this._titleElement.title = tooltip || "";
896 if (!this._tabElement)
897 this._tabElement = this._createTabElement(false);
899 return this._tabElement;
911 * @param {number} width
913 setWidth: function(width)
915 this.tabElement.style.width = width === -1 ? "" : (width + "px");
920 * @param {!WebInspector.TabbedPaneTabDelegate} delegate
922 setDelegate: function(delegate)
924 this._delegate = delegate;
927 _createIconElement: function(tabElement, titleElement)
929 var iconElement = createElementWithClass("span", "tabbed-pane-header-tab-icon " + this._iconClass);
930 if (this._iconTooltip)
931 iconElement.title = this._iconTooltip;
932 tabElement.insertBefore(iconElement, titleElement);
937 * @param {boolean} measuring
940 _createTabElement: function(measuring)
942 var tabElement = createElementWithClass("div", "tabbed-pane-header-tab");
943 tabElement.id = "tab-" + this._id;
944 tabElement.tabIndex = -1;
945 tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPane, this.id, true);
947 var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title");
948 titleElement.textContent = this.title;
949 titleElement.title = this.tooltip || "";
951 this._createIconElement(tabElement, titleElement);
953 this._titleElement = titleElement;
956 tabElement.createChild("div", "close-button-gray");
959 tabElement.classList.add("measuring");
961 tabElement.addEventListener("click", this._tabClicked.bind(this), false);
962 tabElement.addEventListener("mousedown", this._tabMouseDown.bind(this), false);
963 tabElement.addEventListener("mouseup", this._tabMouseUp.bind(this), false);
965 if (this._closeable) {
966 tabElement.addEventListener("contextmenu", this._tabContextMenu.bind(this), false);
967 WebInspector.installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "pointer");
975 * @param {!Event} event
977 _tabClicked: function(event)
979 var middleButton = event.button === 1;
980 var shouldClose = this._closeable && (middleButton || event.target.classList.contains("close-button-gray"));
982 this._tabbedPane.focus();
985 this._closeTabs([this.id]);
990 * @param {!Event} event
992 _tabMouseDown: function(event)
994 if (event.target.classList.contains("close-button-gray") || event.button === 1)
996 this._tabbedPane.selectTab(this.id, true);
1000 * @param {!Event} event
1002 _tabMouseUp: function(event)
1004 // This is needed to prevent middle-click pasting on linux when tabs are clicked.
1005 if (event.button === 1)
1006 event.consume(true);
1010 * @param {!Array.<string>} ids
1012 _closeTabs: function(ids)
1014 if (this._delegate) {
1015 this._delegate.closeTabs(this._tabbedPane, ids);
1018 this._tabbedPane.closeTabs(ids, true);
1021 _tabContextMenu: function(event)
1024 * @this {WebInspector.TabbedPaneTab}
1028 this._closeTabs([this.id]);
1032 * @this {WebInspector.TabbedPaneTab}
1034 function closeOthers()
1036 this._closeTabs(this._tabbedPane.otherTabs(this.id));
1040 * @this {WebInspector.TabbedPaneTab}
1044 this._closeTabs(this._tabbedPane.allTabs());
1047 var contextMenu = new WebInspector.ContextMenu(event);
1048 contextMenu.appendItem(WebInspector.UIString("Close"), close.bind(this));
1049 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close others" : "Close Others"), closeOthers.bind(this));
1050 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close all" : "Close All"), closeAll.bind(this));
1055 * @param {!Event} event
1058 _startTabDragging: function(event)
1060 if (event.target.classList.contains("close-button-gray"))
1062 this._dragStartX = event.pageX;
1067 * @param {!Event} event
1069 _tabDragging: function(event)
1071 var tabElements = this._tabbedPane._tabsElement.childNodes;
1072 for (var i = 0; i < tabElements.length; ++i) {
1073 var tabElement = tabElements[i];
1074 if (tabElement === this._tabElement)
1077 var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft &&
1078 this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft;
1082 if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2 + 5)
1085 if (event.pageX - this._dragStartX > 0) {
1086 tabElement = tabElement.nextSibling;
1090 var oldOffsetLeft = this._tabElement.offsetLeft;
1091 this._tabbedPane._insertBefore(this, i);
1092 this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft;
1096 if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0) {
1097 this._tabElement.style.setProperty("left", "0px");
1100 if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) {
1101 this._tabElement.style.setProperty("left", "0px");
1105 this._tabElement.style.setProperty("position", "relative");
1106 this._tabElement.style.setProperty("left", (event.pageX - this._dragStartX) + "px");
1110 * @param {!Event} event
1112 _endTabDragging: function(event)
1114 this._tabElement.style.removeProperty("position");
1115 this._tabElement.style.removeProperty("left");
1116 delete this._dragStartX;
1123 WebInspector.TabbedPaneTabDelegate = function()
1127 WebInspector.TabbedPaneTabDelegate.prototype = {
1129 * @param {!WebInspector.TabbedPane} tabbedPane
1130 * @param {!Array.<string>} ids
1132 closeTabs: function(tabbedPane, ids) { }
1137 * @param {!WebInspector.TabbedPane} tabbedPane
1138 * @param {string} extensionPoint
1139 * @param {function(string, !WebInspector.View)=} viewCallback
1141 WebInspector.ExtensibleTabbedPaneController = function(tabbedPane, extensionPoint, viewCallback)
1143 this._tabbedPane = tabbedPane;
1144 this._extensionPoint = extensionPoint;
1145 this._viewCallback = viewCallback;
1146 this._tabOrders = {};
1147 /** @type {!Object.<string, !Promise.<!WebInspector.View>>} */
1148 this._promiseForId = {};
1150 this._tabbedPane.setRetainTabOrder(true, this._tabOrderComparator.bind(this));
1151 this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
1152 /** @type {!Map.<string, ?WebInspector.View>} */
1153 this._views = new Map();
1157 WebInspector.ExtensibleTabbedPaneController.prototype = {
1158 _initialize: function()
1160 /** @type {!Map.<string, !Runtime.Extension>} */
1161 this._extensions = new Map();
1162 var extensions = self.runtime.extensions(this._extensionPoint);
1164 for (var i = 0; i < extensions.length; ++i) {
1165 var descriptor = extensions[i].descriptor();
1166 var id = descriptor["name"];
1167 this._tabOrders[id] = i;
1168 var title = WebInspector.UIString(descriptor["title"]);
1169 var settingName = descriptor["setting"];
1170 var setting = settingName ? /** @type {!WebInspector.Setting|undefined} */ (WebInspector.settings[settingName]) : null;
1172 this._extensions.set(id, extensions[i]);
1175 setting.addChangeListener(this._toggleSettingBasedView.bind(this, id, title, setting));
1177 this._tabbedPane.appendTab(id, title, new WebInspector.View());
1179 this._tabbedPane.appendTab(id, title, new WebInspector.View());
1185 * @param {string} id
1186 * @param {string} title
1187 * @param {!WebInspector.Setting} setting
1189 _toggleSettingBasedView: function(id, title, setting)
1191 this._tabbedPane.closeTab(id);
1193 this._tabbedPane.appendTab(id, title, new WebInspector.View());
1197 * @param {!WebInspector.Event} event
1199 _tabSelected: function(event)
1201 var tabId = /** @type {string} */ (event.data.tabId);
1202 this.viewForId(tabId).then(viewLoaded.bind(this)).done();
1205 * @this {WebInspector.ExtensibleTabbedPaneController}
1206 * @param {!WebInspector.View} view
1208 function viewLoaded(view)
1210 var shouldFocus = this._tabbedPane.visibleView.element.isSelfOrAncestor(WebInspector.currentFocusElement());
1211 this._tabbedPane.changeTabView(tabId, view);
1218 * @return {!Array.<string>}
1222 return this._extensions.keysArray();
1226 * @param {string} id
1227 * @return {!Promise.<!WebInspector.View>}
1229 viewForId: function(id)
1231 if (this._views.has(id))
1232 return Promise.resolve(/** @type {!WebInspector.View} */ (this._views.get(id)));
1233 if (!this._extensions.has(id))
1234 return Promise.rejectWithError("No view registered for given type and id: " + this._extensionPoint + ", " + id);
1235 if (this._promiseForId[id])
1236 return this._promiseForId[id];
1238 var promise = this._extensions.get(id).instancePromise();
1239 this._promiseForId[id] = /** @type {!Promise.<!WebInspector.View>} */ (promise);
1240 return promise.then(cacheView.bind(this));
1243 * @param {!Object} object
1244 * @this {WebInspector.ExtensibleTabbedPaneController}
1246 function cacheView(object)
1248 var view = /** @type {!WebInspector.View} */ (object);
1249 delete this._promiseForId[id];
1250 this._views.set(id, view);
1251 if (this._viewCallback && view)
1252 this._viewCallback(id, view);
1258 * @param {string} id1
1259 * @param {string} id2
1262 _tabOrderComparator: function(id1, id2)
1264 return this._tabOrders[id2] = this._tabOrders[id1];