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 scroll-target");
44 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */
46 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */
47 this._tabsHistory = [];
48 /** @type {!Object.<string, !WebInspector.TabbedPaneTab>} */
51 this._dropDownButton = this._createDropDownButton();
52 WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._zoomChanged, this);
55 WebInspector.TabbedPane.EventTypes = {
56 TabSelected: "TabSelected",
57 TabClosed: "TabClosed"
60 WebInspector.TabbedPane.prototype = {
62 * @return {?WebInspector.View}
66 return this._currentTab ? this._currentTab.view : null;
70 * @return {!Array.<!WebInspector.View>}
75 * @param {!WebInspector.TabbedPaneTab} tab
76 * @return {!WebInspector.View}
78 function tabToView(tab)
82 return this._tabs.map(tabToView);
90 return this._currentTab ? this._currentTab.id : null;
94 * @type {boolean} shrinkableTabs
96 set shrinkableTabs(shrinkableTabs)
98 this._shrinkableTabs = shrinkableTabs;
102 * @type {boolean} verticalTabLayout
104 set verticalTabLayout(verticalTabLayout)
106 this._verticalTabLayout = verticalTabLayout;
107 this.invalidateConstraints();
111 * @type {boolean} closeableTabs
113 set closeableTabs(closeableTabs)
115 this._closeableTabs = closeableTabs;
119 * @param {boolean} retainTabOrder
120 * @param {function(string, string):number=} tabOrderComparator
122 setRetainTabOrder: function(retainTabOrder, tabOrderComparator)
124 this._retainTabOrder = retainTabOrder;
125 this._tabOrderComparator = tabOrderComparator;
131 defaultFocusedElement: function()
133 return this.visibleView ? this.visibleView.defaultFocusedElement() : null;
138 if (this.visibleView)
139 this.visibleView.focus();
141 this.element.focus();
147 headerElement: function()
149 return this._headerElement;
156 isTabCloseable: function(id)
158 var tab = this._tabsById[id];
159 return tab ? tab.isCloseable() : false;
163 * @param {!WebInspector.TabbedPaneTabDelegate} delegate
165 setTabDelegate: function(delegate)
167 var tabs = this._tabs.slice();
168 for (var i = 0; i < tabs.length; ++i)
169 tabs[i].setDelegate(delegate);
170 this._delegate = delegate;
175 * @param {string} tabTitle
176 * @param {!WebInspector.View} view
177 * @param {string=} tabTooltip
178 * @param {boolean=} userGesture
179 * @param {boolean=} isCloseable
181 appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable)
183 isCloseable = typeof isCloseable === "boolean" ? isCloseable : this._closeableTabs;
184 var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, isCloseable, view, tabTooltip);
185 tab.setDelegate(this._delegate);
186 this._tabsById[id] = tab;
189 * @param {!WebInspector.TabbedPaneTab} tab1
190 * @param {!WebInspector.TabbedPaneTab} tab2
191 * @this {WebInspector.TabbedPane}
194 function comparator(tab1, tab2)
196 return this._tabOrderComparator(tab1.id, tab2.id);
199 if (this._retainTabOrder && this._tabOrderComparator)
200 this._tabs.splice(insertionIndexForObjectInListSortedByFunction(tab, this._tabs, comparator.bind(this)), 0, tab);
202 this._tabs.push(tab);
204 this._tabsHistory.push(tab);
206 if (this._tabsHistory[0] === tab && this.isShowing())
207 this.selectTab(tab.id, userGesture);
209 this._updateTabElements();
214 * @param {boolean=} userGesture
216 closeTab: function(id, userGesture)
218 this.closeTabs([id], userGesture);
222 * @param {!Array.<string>} ids
223 * @param {boolean=} userGesture
225 closeTabs: function(ids, userGesture)
227 var focused = this.hasFocus();
228 for (var i = 0; i < ids.length; ++i)
229 this._innerCloseTab(ids[i], userGesture);
230 this._updateTabElements();
231 if (this._tabsHistory.length)
232 this.selectTab(this._tabsHistory[0].id, false);
239 * @param {boolean=} userGesture
241 _innerCloseTab: function(id, userGesture)
243 if (!this._tabsById[id])
245 if (userGesture && !this._tabsById[id]._closeable)
247 if (this._currentTab && this._currentTab.id === id)
248 this._hideCurrentTab();
250 var tab = this._tabsById[id];
251 delete this._tabsById[id];
253 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
254 this._tabs.splice(this._tabs.indexOf(tab), 1);
256 this._hideTabElement(tab);
258 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
259 this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData);
264 * @param {string} tabId
267 hasTab: function(tabId)
269 return !!this._tabsById[tabId];
273 * @return {!Array.<string>}
278 var tabs = this._tabs.slice();
279 for (var i = 0; i < tabs.length; ++i)
280 result.push(tabs[i].id);
286 * @return {!Array.<string>}
288 otherTabs: function(id)
291 var tabs = this._tabs.slice();
292 for (var i = 0; i < tabs.length; ++i) {
293 if (tabs[i].id !== id)
294 result.push(tabs[i].id);
301 * @param {boolean=} userGesture
303 selectTab: function(id, userGesture)
305 var focused = this.hasFocus();
306 var tab = this._tabsById[id];
309 if (this._currentTab && this._currentTab.id === id)
312 this._hideCurrentTab();
314 this._currentTab = tab;
316 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
317 this._tabsHistory.splice(0, 0, tab);
319 this._updateTabElements();
323 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
324 this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData);
328 * @param {number} tabsCount
329 * @return {!Array.<string>}
331 lastOpenedTabIds: function(tabsCount)
333 function tabToTabId(tab) {
337 return this._tabsHistory.slice(0, tabsCount).map(tabToTabId);
342 * @param {string} iconClass
343 * @param {string=} iconTooltip
345 setTabIcon: function(id, iconClass, iconTooltip)
347 var tab = this._tabsById[id];
348 if (tab._setIconClass(iconClass, iconTooltip))
349 this._updateTabElements();
353 * @param {!WebInspector.Event} event
355 _zoomChanged: function(event)
357 for (var i = 0; i < this._tabs.length; ++i)
358 delete this._tabs[i]._measuredWidth;
359 if (this.isShowing())
360 this._updateTabElements();
365 * @param {string} tabTitle
367 changeTabTitle: function(id, tabTitle)
369 var tab = this._tabsById[id];
370 if (tab.title === tabTitle)
372 tab.title = tabTitle;
373 this._updateTabElements();
378 * @param {!WebInspector.View} view
380 changeTabView: function(id, view)
382 var tab = this._tabsById[id];
383 if (this._currentTab && this._currentTab.id === tab.id) {
384 if (tab.view !== view)
394 * @param {string=} tabTooltip
396 changeTabTooltip: function(id, tabTooltip)
398 var tab = this._tabsById[id];
399 tab.tooltip = tabTooltip;
404 this._updateTabElements();
407 headerResized: function()
409 this._updateTabElements();
414 var effectiveTab = this._currentTab || this._tabsHistory[0];
416 this.selectTab(effectiveTab.id);
420 * @return {!Constraints}
422 calculateConstraints: function()
424 var constraints = WebInspector.VBox.prototype.calculateConstraints.call(this);
425 var minContentConstraints = new Constraints(new Size(0, 0), new Size(50, 50));
426 constraints = constraints.widthToMax(minContentConstraints).heightToMax(minContentConstraints);
427 if (this._verticalTabLayout)
428 constraints = constraints.addWidth(new Constraints(new Size(this._headerElement.offsetWidth, 0)));
430 constraints = constraints.addHeight(new Constraints(new Size(0, this._headerElement.offsetHeight)));
434 _updateTabElements: function()
436 WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements);
440 * @param {string} text
442 setPlaceholderText: function(text)
444 this._noTabsMessage = text;
447 _innerUpdateTabElements: function()
449 if (!this.isShowing())
452 if (!this._tabs.length) {
453 this._contentElement.classList.add("has-no-tabs");
454 if (this._noTabsMessage && !this._noTabsMessageElement) {
455 this._noTabsMessageElement = this._contentElement.createChild("div", "tabbed-pane-placeholder fill");
456 this._noTabsMessageElement.textContent = this._noTabsMessage;
459 this._contentElement.classList.remove("has-no-tabs");
460 if (this._noTabsMessageElement) {
461 this._noTabsMessageElement.remove();
462 delete this._noTabsMessageElement;
466 if (!this._measuredDropDownButtonWidth)
467 this._measureDropDownButton();
469 this._updateWidths();
470 this._updateTabsDropDown();
474 * @param {number} index
475 * @param {!WebInspector.TabbedPaneTab} tab
477 _showTabElement: function(index, tab)
479 if (index >= this._tabsElement.children.length)
480 this._tabsElement.appendChild(tab.tabElement);
482 this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]);
487 * @param {!WebInspector.TabbedPaneTab} tab
489 _hideTabElement: function(tab)
491 this._tabsElement.removeChild(tab.tabElement);
495 _createDropDownButton: function()
497 var dropDownContainer = document.createElement("div");
498 dropDownContainer.classList.add("tabbed-pane-header-tabs-drop-down-container");
499 var dropDownButton = dropDownContainer.createChild("div", "tabbed-pane-header-tabs-drop-down");
500 dropDownButton.appendChild(document.createTextNode("\u00bb"));
502 this._dropDownMenu = new WebInspector.DropDownMenu();
503 this._dropDownMenu.addEventListener(WebInspector.DropDownMenu.Events.ItemSelected, this._dropDownMenuItemSelected, this);
504 dropDownButton.appendChild(this._dropDownMenu.element);
506 return dropDownContainer;
510 * @param {!WebInspector.Event} event
512 _dropDownMenuItemSelected: function(event)
514 var tabId = /** @type {string} */ (event.data);
515 this.selectTab(tabId, true);
518 _totalWidth: function()
520 return this._headerContentsElement.getBoundingClientRect().width;
523 _updateTabsDropDown: function()
525 var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth);
527 for (var i = 0; i < this._tabs.length; ++i) {
528 if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1)
529 this._hideTabElement(this._tabs[i]);
531 for (var i = 0; i < tabsToShowIndexes.length; ++i) {
532 var tab = this._tabs[tabsToShowIndexes[i]];
534 this._showTabElement(i, tab);
537 this._populateDropDownFromIndex();
540 _populateDropDownFromIndex: function()
542 if (this._dropDownButton.parentElement)
543 this._headerContentsElement.removeChild(this._dropDownButton);
545 this._dropDownMenu.clear();
548 for (var i = 0; i < this._tabs.length; ++i) {
549 if (!this._tabs[i]._shown)
550 tabsToShow.push(this._tabs[i]);
554 function compareFunction(tab1, tab2)
556 return tab1.title.localeCompare(tab2.title);
558 if (!this._retainTabOrder)
559 tabsToShow.sort(compareFunction);
561 var selectedId = null;
562 for (var i = 0; i < tabsToShow.length; ++i) {
563 var tab = tabsToShow[i];
564 this._dropDownMenu.addItem(tab.id, tab.title);
565 if (this._tabsHistory[0] === tab)
568 if (tabsToShow.length) {
569 this._headerContentsElement.appendChild(this._dropDownButton);
570 this._dropDownMenu.selectItem(selectedId);
574 _measureDropDownButton: function()
576 this._dropDownButton.classList.add("measuring");
577 this._headerContentsElement.appendChild(this._dropDownButton);
578 this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width;
579 this._headerContentsElement.removeChild(this._dropDownButton);
580 this._dropDownButton.classList.remove("measuring");
583 _updateWidths: function()
585 var measuredWidths = this._measureWidths();
586 var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE;
589 for (var tabId in this._tabs) {
590 var tab = this._tabs[tabId];
591 tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++]));
595 _measureWidths: function()
597 // Add all elements to measure into this._tabsElement
598 this._tabsElement.style.setProperty("width", "2000px");
599 var measuringTabElements = [];
600 for (var tabId in this._tabs) {
601 var tab = this._tabs[tabId];
602 if (typeof tab._measuredWidth === "number")
604 var measuringTabElement = tab._createTabElement(true);
605 measuringTabElement.__tab = tab;
606 measuringTabElements.push(measuringTabElement);
607 this._tabsElement.appendChild(measuringTabElement);
610 // Perform measurement
611 for (var i = 0; i < measuringTabElements.length; ++i)
612 measuringTabElements[i].__tab._measuredWidth = measuringTabElements[i].getBoundingClientRect().width;
614 // Nuke elements from the UI
615 for (var i = 0; i < measuringTabElements.length; ++i)
616 measuringTabElements[i].remove();
618 // Combine the results.
619 var measuredWidths = [];
620 for (var tabId in this._tabs)
621 measuredWidths.push(this._tabs[tabId]._measuredWidth);
622 this._tabsElement.style.removeProperty("width");
624 return measuredWidths;
628 * @param {!Array.<number>} measuredWidths
629 * @param {number} totalWidth
631 _calculateMaxWidth: function(measuredWidths, totalWidth)
633 if (!measuredWidths.length)
636 measuredWidths.sort(function(x, y) { return x - y });
638 var totalMeasuredWidth = 0;
639 for (var i = 0; i < measuredWidths.length; ++i)
640 totalMeasuredWidth += measuredWidths[i];
642 if (totalWidth >= totalMeasuredWidth)
643 return measuredWidths[measuredWidths.length - 1];
645 var totalExtraWidth = 0;
646 for (var i = measuredWidths.length - 1; i > 0; --i) {
647 var extraWidth = measuredWidths[i] - measuredWidths[i - 1];
648 totalExtraWidth += (measuredWidths.length - i) * extraWidth;
650 if (totalWidth + totalExtraWidth >= totalMeasuredWidth)
651 return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i);
654 return totalWidth / measuredWidths.length;
658 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered
659 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory
660 * @param {number} totalWidth
661 * @param {number} measuredDropDownButtonWidth
662 * @return {!Array.<number>}
664 _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth)
666 var tabsToShowIndexes = [];
668 var totalTabsWidth = 0;
669 var tabCount = tabsOrdered.length;
670 for (var i = 0; i < tabCount; ++i) {
671 var tab = this._retainTabOrder ? tabsOrdered[i] : tabsHistory[i];
672 totalTabsWidth += tab.width();
673 var minimalRequiredWidth = totalTabsWidth;
674 if (i !== tabCount - 1)
675 minimalRequiredWidth += measuredDropDownButtonWidth;
676 if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth)
678 tabsToShowIndexes.push(tabsOrdered.indexOf(tab));
681 tabsToShowIndexes.sort(function(x, y) { return x - y });
683 return tabsToShowIndexes;
686 _hideCurrentTab: function()
688 if (!this._currentTab)
691 this._hideTab(this._currentTab);
692 delete this._currentTab;
696 * @param {!WebInspector.TabbedPaneTab} tab
698 _showTab: function(tab)
700 tab.tabElement.classList.add("selected");
701 tab.view.show(this._contentElement);
705 * @param {!WebInspector.TabbedPaneTab} tab
707 _hideTab: function(tab)
709 tab.tabElement.classList.remove("selected");
714 * @return {!Array.<!Element>}
716 elementsToRestoreScrollPositionsFor: function()
718 return [ this._contentElement ];
722 * @param {!WebInspector.TabbedPaneTab} tab
723 * @param {number} index
725 _insertBefore: function(tab, index)
727 this._tabsElement.insertBefore(tab._tabElement, this._tabsElement.childNodes[index]);
728 var oldIndex = this._tabs.indexOf(tab);
729 this._tabs.splice(oldIndex, 1);
730 if (oldIndex < index)
732 this._tabs.splice(index, 0, tab);
735 __proto__: WebInspector.VBox.prototype
740 * @param {!WebInspector.TabbedPane} tabbedPane
742 * @param {string} title
743 * @param {boolean} closeable
744 * @param {!WebInspector.View} view
745 * @param {string=} tooltip
747 WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, tooltip)
749 this._closeable = closeable;
750 this._tabbedPane = tabbedPane;
753 this._tooltip = tooltip;
756 /** @type {number} */ this._measuredWidth;
757 /** @type {!Element|undefined} */ this._tabElement;
760 WebInspector.TabbedPaneTab.prototype = {
779 if (title === this._title)
782 if (this._titleElement)
783 this._titleElement.textContent = title;
784 delete this._measuredWidth;
790 iconClass: function()
792 return this._iconClass;
798 isCloseable: function()
800 return this._closeable;
804 * @param {string} iconClass
805 * @param {string} iconTooltip
808 _setIconClass: function(iconClass, iconTooltip)
810 if (iconClass === this._iconClass && iconTooltip === this._iconTooltip)
812 this._iconClass = iconClass;
813 this._iconTooltip = iconTooltip;
814 if (this._iconElement)
815 this._iconElement.remove();
816 if (this._iconClass && this._tabElement)
817 this._iconElement = this._createIconElement(this._tabElement, this._titleElement);
818 delete this._measuredWidth;
823 * @return {!WebInspector.View}
836 * @return {string|undefined}
840 return this._tooltip;
845 this._tooltip = tooltip;
846 if (this._titleElement)
847 this._titleElement.title = tooltip || "";
855 if (!this._tabElement)
856 this._tabElement = this._createTabElement(false);
858 return this._tabElement;
870 * @param {number} width
872 setWidth: function(width)
874 this.tabElement.style.width = width === -1 ? "" : (width + "px");
879 * @param {!WebInspector.TabbedPaneTabDelegate} delegate
881 setDelegate: function(delegate)
883 this._delegate = delegate;
886 _createIconElement: function(tabElement, titleElement)
888 var iconElement = document.createElement("span");
889 iconElement.className = "tabbed-pane-header-tab-icon " + this._iconClass;
890 if (this._iconTooltip)
891 iconElement.title = this._iconTooltip;
892 tabElement.insertBefore(iconElement, titleElement);
897 * @param {boolean} measuring
900 _createTabElement: function(measuring)
902 var tabElement = document.createElement("div");
903 tabElement.classList.add("tabbed-pane-header-tab");
904 tabElement.id = "tab-" + this._id;
905 tabElement.tabIndex = -1;
906 tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPane, this.id, true);
908 var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title");
909 titleElement.textContent = this.title;
910 titleElement.title = this.tooltip || "";
912 this._createIconElement(tabElement, titleElement);
914 this._titleElement = titleElement;
917 tabElement.createChild("div", "close-button-gray");
920 tabElement.classList.add("measuring");
922 tabElement.addEventListener("click", this._tabClicked.bind(this), false);
923 tabElement.addEventListener("mousedown", this._tabMouseDown.bind(this), false);
924 tabElement.addEventListener("mouseup", this._tabMouseUp.bind(this), false);
926 if (this._closeable) {
927 tabElement.addEventListener("contextmenu", this._tabContextMenu.bind(this), false);
928 WebInspector.installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "pointer");
936 * @param {?Event} event
938 _tabClicked: function(event)
940 var middleButton = event.button === 1;
941 var shouldClose = this._closeable && (middleButton || event.target.classList.contains("close-button-gray"));
943 this._tabbedPane.focus();
946 this._closeTabs([this.id]);
951 * @param {?Event} event
953 _tabMouseDown: function(event)
955 if (event.target.classList.contains("close-button-gray") || event.button === 1)
957 this._tabbedPane.selectTab(this.id, true);
961 * @param {?Event} event
963 _tabMouseUp: function(event)
965 // This is needed to prevent middle-click pasting on linux when tabs are clicked.
966 if (event.button === 1)
971 * @param {!Array.<string>} ids
973 _closeTabs: function(ids)
975 if (this._delegate) {
976 this._delegate.closeTabs(this._tabbedPane, ids);
979 this._tabbedPane.closeTabs(ids, true);
982 _tabContextMenu: function(event)
985 * @this {WebInspector.TabbedPaneTab}
989 this._closeTabs([this.id]);
993 * @this {WebInspector.TabbedPaneTab}
995 function closeOthers()
997 this._closeTabs(this._tabbedPane.otherTabs(this.id));
1001 * @this {WebInspector.TabbedPaneTab}
1005 this._closeTabs(this._tabbedPane.allTabs());
1008 var contextMenu = new WebInspector.ContextMenu(event);
1009 contextMenu.appendItem(WebInspector.UIString("Close"), close.bind(this));
1010 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close others" : "Close Others"), closeOthers.bind(this));
1011 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close all" : "Close All"), closeAll.bind(this));
1016 * @param {!Event} event
1019 _startTabDragging: function(event)
1021 if (event.target.classList.contains("close-button-gray"))
1023 this._dragStartX = event.pageX;
1028 * @param {!Event} event
1030 _tabDragging: function(event)
1032 var tabElements = this._tabbedPane._tabsElement.childNodes;
1033 for (var i = 0; i < tabElements.length; ++i) {
1034 var tabElement = tabElements[i];
1035 if (tabElement === this._tabElement)
1038 var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft &&
1039 this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft;
1043 if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2 + 5)
1046 if (event.pageX - this._dragStartX > 0) {
1047 tabElement = tabElement.nextSibling;
1051 var oldOffsetLeft = this._tabElement.offsetLeft;
1052 this._tabbedPane._insertBefore(this, i);
1053 this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft;
1057 if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0) {
1058 this._tabElement.style.setProperty("left", "0px");
1061 if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) {
1062 this._tabElement.style.setProperty("left", "0px");
1066 this._tabElement.style.setProperty("position", "relative");
1067 this._tabElement.style.setProperty("left", (event.pageX - this._dragStartX) + "px");
1071 * @param {!Event} event
1073 _endTabDragging: function(event)
1075 this._tabElement.style.removeProperty("position");
1076 this._tabElement.style.removeProperty("left");
1077 delete this._dragStartX;
1084 WebInspector.TabbedPaneTabDelegate = function()
1088 WebInspector.TabbedPaneTabDelegate.prototype = {
1090 * @param {!WebInspector.TabbedPane} tabbedPane
1091 * @param {!Array.<string>} ids
1093 closeTabs: function(tabbedPane, ids) { }
1098 * @param {!WebInspector.TabbedPane} tabbedPane
1099 * @param {string} extensionPoint
1100 * @param {function(string, !WebInspector.View)=} viewCallback
1102 WebInspector.ExtensibleTabbedPaneController = function(tabbedPane, extensionPoint, viewCallback)
1104 this._tabbedPane = tabbedPane;
1105 this._extensionPoint = extensionPoint;
1106 this._viewCallback = viewCallback;
1108 this._tabbedPane.setRetainTabOrder(true, WebInspector.moduleManager.orderComparator(extensionPoint, "name", "order"));
1109 this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
1110 /** @type {!StringMap.<?WebInspector.View>} */
1111 this._views = new StringMap();
1115 WebInspector.ExtensibleTabbedPaneController.prototype = {
1116 _initialize: function()
1118 this._extensions = {};
1119 var extensions = WebInspector.moduleManager.extensions(this._extensionPoint);
1121 for (var i = 0; i < extensions.length; ++i) {
1122 var descriptor = extensions[i].descriptor();
1123 var id = descriptor["name"];
1124 var title = WebInspector.UIString(descriptor["title"]);
1125 var settingName = descriptor["setting"];
1126 var setting = settingName ? /** @type {!WebInspector.Setting|undefined} */ (WebInspector.settings[settingName]) : null;
1128 this._extensions[id] = extensions[i];
1131 setting.addChangeListener(this._toggleSettingBasedView.bind(this, id, title, setting));
1133 this._tabbedPane.appendTab(id, title, new WebInspector.View());
1135 this._tabbedPane.appendTab(id, title, new WebInspector.View());
1141 * @param {string} id
1142 * @param {string} title
1143 * @param {!WebInspector.Setting} setting
1145 _toggleSettingBasedView: function(id, title, setting)
1147 this._tabbedPane.closeTab(id);
1149 this._tabbedPane.appendTab(id, title, new WebInspector.View());
1153 * @param {!WebInspector.Event} event
1155 _tabSelected: function(event)
1157 var tabId = this._tabbedPane.selectedTabId;
1160 var view = this._viewForId(tabId);
1162 this._tabbedPane.changeTabView(tabId, view);
1166 * @return {?WebInspector.View}
1168 _viewForId: function(id)
1170 if (this._views.contains(id))
1171 return /** @type {!WebInspector.View} */ (this._views.get(id));
1172 var view = this._extensions[id] ? /** @type {!WebInspector.View} */ (this._extensions[id].instance()) : null;
1173 this._views.put(id, view);
1174 if (this._viewCallback && view)
1175 this._viewCallback(id, view);