| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @extends {WebInspector.VBox} |
| * @constructor |
| */ |
| WebInspector.TabbedPane = function() |
| { |
| WebInspector.VBox.call(this, true); |
| this.registerRequiredCSS("ui/tabbedPane.css"); |
| this.element.classList.add("tabbed-pane"); |
| this.contentElement.classList.add("tabbed-pane-shadow"); |
| this.contentElement.tabIndex = -1; |
| this._headerElement = this.contentElement.createChild("div", "tabbed-pane-header"); |
| this._headerElement.createChild("content").select = ".tabbed-pane-header-before"; |
| this._headerContentsElement = this._headerElement.createChild("div", "tabbed-pane-header-contents"); |
| this._tabSlider = createElementWithClass("div", "tabbed-pane-tab-slider"); |
| this._headerElement.createChild("content").select = ".tabbed-pane-header-after"; |
| this._tabsElement = this._headerContentsElement.createChild("div", "tabbed-pane-header-tabs"); |
| this._contentElement = this.contentElement.createChild("div", "tabbed-pane-content"); |
| this._contentElement.createChild("content"); |
| /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ |
| this._tabs = []; |
| /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ |
| this._tabsHistory = []; |
| /** @type {!Object.<string, !WebInspector.TabbedPaneTab>} */ |
| this._tabsById = {}; |
| this._currentTabLocked = false; |
| |
| this._dropDownButton = this._createDropDownButton(); |
| WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._zoomChanged, this); |
| } |
| |
| WebInspector.TabbedPane.EventTypes = { |
| TabSelected: "TabSelected", |
| TabClosed: "TabClosed", |
| TabOrderChanged: "TabOrderChanged" |
| } |
| |
| WebInspector.TabbedPane.prototype = { |
| /** |
| * @param {boolean} locked |
| */ |
| setCurrentTabLocked: function(locked) |
| { |
| this._currentTabLocked = locked; |
| this._headerElement.classList.toggle("locked", this._currentTabLocked); |
| }, |
| |
| /** |
| * @return {?WebInspector.Widget} |
| */ |
| get visibleView() |
| { |
| return this._currentTab ? this._currentTab.view : null; |
| }, |
| |
| /** |
| * @return {!Array.<string>} |
| */ |
| tabIds: function() |
| { |
| return this._tabs.map(tab => tab._id); |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.Widget>} |
| */ |
| tabViews: function() |
| { |
| return this._tabs.map(tab => tab.view); |
| }, |
| |
| /** |
| * @param {string} tabId |
| * @return {?WebInspector.Widget} |
| */ |
| tabView: function(tabId) |
| { |
| return this._tabsById[tabId] ? this._tabsById[tabId].view : null; |
| }, |
| |
| /** |
| * @return {?string} |
| */ |
| get selectedTabId() |
| { |
| return this._currentTab ? this._currentTab.id : null; |
| }, |
| |
| /** |
| * @param {boolean} shrinkableTabs |
| */ |
| setShrinkableTabs: function(shrinkableTabs) |
| { |
| this._shrinkableTabs = shrinkableTabs; |
| }, |
| |
| /** |
| * @param {boolean} verticalTabLayout |
| */ |
| setVerticalTabLayout: function(verticalTabLayout) |
| { |
| this._verticalTabLayout = verticalTabLayout; |
| this.contentElement.classList.add("vertical-tab-layout"); |
| this.invalidateConstraints(); |
| }, |
| |
| /** |
| * @param {boolean} closeableTabs |
| */ |
| setCloseableTabs: function(closeableTabs) |
| { |
| this._closeableTabs = closeableTabs; |
| }, |
| |
| focus: function() |
| { |
| if (this.visibleView) |
| this.visibleView.focus(); |
| else |
| this.contentElement.focus(); |
| }, |
| |
| /** |
| * @return {!Element} |
| */ |
| headerElement: function() |
| { |
| return this._headerElement; |
| }, |
| |
| /** |
| * @param {string} id |
| * @return {boolean} |
| */ |
| isTabCloseable: function(id) |
| { |
| var tab = this._tabsById[id]; |
| return tab ? tab.isCloseable() : false; |
| }, |
| |
| /** |
| * @param {!WebInspector.TabbedPaneTabDelegate} delegate |
| */ |
| setTabDelegate: function(delegate) |
| { |
| var tabs = this._tabs.slice(); |
| for (var i = 0; i < tabs.length; ++i) |
| tabs[i].setDelegate(delegate); |
| this._delegate = delegate; |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {string} tabTitle |
| * @param {!WebInspector.Widget} view |
| * @param {string=} tabTooltip |
| * @param {boolean=} userGesture |
| * @param {boolean=} isCloseable |
| * @param {number=} index |
| */ |
| appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable, index) |
| { |
| isCloseable = typeof isCloseable === "boolean" ? isCloseable : this._closeableTabs; |
| var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, isCloseable, view, tabTooltip); |
| tab.setDelegate(this._delegate); |
| this._tabsById[id] = tab; |
| if (index !== undefined) |
| this._tabs.splice(index, 0, tab); |
| else |
| this._tabs.push(tab); |
| this._tabsHistory.push(tab); |
| view.attach(this); |
| if (this._tabsHistory[0] === tab && this.isShowing()) |
| this.selectTab(tab.id, userGesture); |
| this._updateTabElements(); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {boolean=} userGesture |
| */ |
| closeTab: function(id, userGesture) |
| { |
| this.closeTabs([id], userGesture); |
| }, |
| |
| /** |
| * @param {!Array.<string>} ids |
| * @param {boolean=} userGesture |
| */ |
| closeTabs: function(ids, userGesture) |
| { |
| var focused = this.hasFocus(); |
| for (var i = 0; i < ids.length; ++i) |
| this._innerCloseTab(ids[i], userGesture); |
| this._updateTabElements(); |
| if (this._tabsHistory.length) |
| this.selectTab(this._tabsHistory[0].id, false); |
| if (focused) |
| this.focus(); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {boolean=} userGesture |
| */ |
| _innerCloseTab: function(id, userGesture) |
| { |
| if (!this._tabsById[id]) |
| return; |
| if (userGesture && !this._tabsById[id]._closeable) |
| return; |
| if (this._currentTab && this._currentTab.id === id) |
| this._hideCurrentTab(); |
| |
| var tab = this._tabsById[id]; |
| delete this._tabsById[id]; |
| |
| this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); |
| this._tabs.splice(this._tabs.indexOf(tab), 1); |
| if (tab._shown) |
| this._hideTabElement(tab); |
| tab.view.detach(); |
| |
| var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; |
| this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData); |
| return true; |
| }, |
| |
| /** |
| * @param {string} tabId |
| * @return {boolean} |
| */ |
| hasTab: function(tabId) |
| { |
| return !!this._tabsById[tabId]; |
| }, |
| |
| /** |
| * @return {!Array.<string>} |
| */ |
| allTabs: function() |
| { |
| return this._tabs.map(function(tab) { return tab.id; }); |
| }, |
| |
| /** |
| * @param {string} id |
| * @return {!Array.<string>} |
| */ |
| otherTabs: function(id) |
| { |
| var result = []; |
| for (var i = 0; i < this._tabs.length; ++i) { |
| if (this._tabs[i].id !== id) |
| result.push(this._tabs[i].id); |
| } |
| return result; |
| }, |
| |
| /** |
| * @param {string} id |
| * @return {!Array.<string>} |
| */ |
| _tabsToTheRight: function(id) |
| { |
| var index = -1; |
| for (var i = 0; i < this._tabs.length; ++i) { |
| if (this._tabs[i].id === id) { |
| index = i; |
| break; |
| } |
| } |
| if (index === -1) |
| return []; |
| return this._tabs.slice(index + 1).map(function(tab) { return tab.id; }); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {boolean=} userGesture |
| * @return {boolean} |
| */ |
| selectTab: function(id, userGesture) |
| { |
| if (this._currentTabLocked) |
| return false; |
| var focused = this.hasFocus(); |
| var tab = this._tabsById[id]; |
| if (!tab) |
| return false; |
| if (this._currentTab && this._currentTab.id === id) |
| return true; |
| |
| this.suspendInvalidations(); |
| this._hideCurrentTab(); |
| this._showTab(tab); |
| this.resumeInvalidations(); |
| this._currentTab = tab; |
| |
| this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); |
| this._tabsHistory.splice(0, 0, tab); |
| |
| this._updateTabElements(); |
| if (focused) |
| this.focus(); |
| |
| var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; |
| this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData); |
| return true; |
| }, |
| |
| /** |
| * @param {number} tabsCount |
| * @return {!Array.<string>} |
| */ |
| lastOpenedTabIds: function(tabsCount) |
| { |
| function tabToTabId(tab) { |
| return tab.id; |
| } |
| |
| return this._tabsHistory.slice(0, tabsCount).map(tabToTabId); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {string} iconType |
| * @param {string=} iconTooltip |
| */ |
| setTabIcon: function(id, iconType, iconTooltip) |
| { |
| var tab = this._tabsById[id]; |
| if (tab._setIconType(iconType, iconTooltip)) |
| this._updateTabElements(); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {boolean} enabled |
| */ |
| setTabEnabled: function(id, enabled) |
| { |
| var tab = this._tabsById[id]; |
| tab.tabElement.classList.toggle("disabled", !enabled); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {string} className |
| * @param {boolean=} force |
| */ |
| toggleTabClass: function(id, className, force) |
| { |
| var tab = this._tabsById[id]; |
| if (tab._toggleClass(className, force)) |
| this._updateTabElements(); |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _zoomChanged: function(event) |
| { |
| for (var i = 0; i < this._tabs.length; ++i) |
| delete this._tabs[i]._measuredWidth; |
| if (this.isShowing()) |
| this._updateTabElements(); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {string} tabTitle |
| */ |
| changeTabTitle: function(id, tabTitle) |
| { |
| var tab = this._tabsById[id]; |
| if (tab.title === tabTitle) |
| return; |
| tab.title = tabTitle; |
| this._updateTabElements(); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {!WebInspector.Widget} view |
| */ |
| changeTabView: function(id, view) |
| { |
| var tab = this._tabsById[id]; |
| if (tab.view === view) |
| return; |
| |
| var shouldFocus = tab.view.element.isSelfOrAncestor(WebInspector.currentFocusElement()); |
| |
| this.suspendInvalidations(); |
| |
| var isSelected = this._currentTab && this._currentTab.id === id; |
| if (isSelected) |
| this._hideTab(tab); |
| tab.view.detach(); |
| tab.view = view; |
| tab.view.attach(this); |
| if (isSelected) |
| this._showTab(tab); |
| if (shouldFocus) |
| tab.view.focus(); |
| |
| this.resumeInvalidations(); |
| }, |
| |
| onResize: function() |
| { |
| this._updateTabElements(); |
| }, |
| |
| headerResized: function() |
| { |
| this._updateTabElements(); |
| }, |
| |
| wasShown: function() |
| { |
| var effectiveTab = this._currentTab || this._tabsHistory[0]; |
| if (effectiveTab) |
| this.selectTab(effectiveTab.id); |
| }, |
| |
| /** |
| * @param {boolean} enable |
| */ |
| setTabSlider: function(enable) |
| { |
| this._sliderEnabled = enable; |
| this._tabSlider.classList.toggle("enabled", enable); |
| }, |
| |
| /** |
| * @override |
| * @return {!Constraints} |
| */ |
| calculateConstraints: function() |
| { |
| var constraints = WebInspector.VBox.prototype.calculateConstraints.call(this); |
| var minContentConstraints = new Constraints(new Size(0, 0), new Size(50, 50)); |
| constraints = constraints.widthToMax(minContentConstraints).heightToMax(minContentConstraints); |
| if (this._verticalTabLayout) |
| constraints = constraints.addWidth(new Constraints(new Size(120, 0))); |
| else |
| constraints = constraints.addHeight(new Constraints(new Size(0, 30))); |
| return constraints; |
| }, |
| |
| _updateTabElements: function() |
| { |
| WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements); |
| }, |
| |
| /** |
| * @param {string} text |
| */ |
| setPlaceholderText: function(text) |
| { |
| this._noTabsMessage = text; |
| }, |
| |
| _innerUpdateTabElements: function() |
| { |
| if (!this.isShowing()) |
| return; |
| |
| if (!this._tabs.length) { |
| this._contentElement.classList.add("has-no-tabs"); |
| if (this._noTabsMessage && !this._noTabsMessageElement) { |
| this._noTabsMessageElement = this._contentElement.createChild("div", "tabbed-pane-placeholder fill"); |
| this._noTabsMessageElement.textContent = this._noTabsMessage; |
| } |
| } else { |
| this._contentElement.classList.remove("has-no-tabs"); |
| if (this._noTabsMessageElement) { |
| this._noTabsMessageElement.remove(); |
| delete this._noTabsMessageElement; |
| } |
| } |
| |
| this._measureDropDownButton(); |
| this._updateWidths(); |
| this._updateTabsDropDown(); |
| this._updateTabSlider(); |
| }, |
| |
| /** |
| * @param {number} index |
| * @param {!WebInspector.TabbedPaneTab} tab |
| */ |
| _showTabElement: function(index, tab) |
| { |
| if (index >= this._tabsElement.children.length) |
| this._tabsElement.appendChild(tab.tabElement); |
| else |
| this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]); |
| tab._shown = true; |
| }, |
| |
| /** |
| * @param {!WebInspector.TabbedPaneTab} tab |
| */ |
| _hideTabElement: function(tab) |
| { |
| this._tabsElement.removeChild(tab.tabElement); |
| tab._shown = false; |
| }, |
| |
| _createDropDownButton: function() |
| { |
| var dropDownContainer = createElementWithClass("div", "tabbed-pane-header-tabs-drop-down-container"); |
| dropDownContainer.createChild("div", "glyph"); |
| this._dropDownMenu = new WebInspector.DropDownMenu(dropDownContainer); |
| this._dropDownMenu.addEventListener(WebInspector.DropDownMenu.Events.ItemSelected, this._dropDownMenuItemSelected, this); |
| |
| return dropDownContainer; |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _dropDownMenuItemSelected: function(event) |
| { |
| var tabId = /** @type {string} */ (event.data); |
| this._lastSelectedOverflowTab = this._tabsById[tabId]; |
| this.selectTab(tabId, true); |
| }, |
| |
| _totalWidth: function() |
| { |
| return this._headerContentsElement.getBoundingClientRect().width; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| _numberOfTabsShown: function() |
| { |
| var numTabsShown = 0; |
| for (var tab of this._tabs) { |
| if (tab._shown) |
| numTabsShown++; |
| } |
| return numTabsShown; |
| }, |
| |
| disableOverflowMenu: function() |
| { |
| this._overflowDisabled = true; |
| }, |
| |
| _updateTabsDropDown: function() |
| { |
| var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth || 0); |
| if (this._lastSelectedOverflowTab && this._numberOfTabsShown() !== tabsToShowIndexes.length) { |
| delete this._lastSelectedOverflowTab; |
| this._updateTabsDropDown(); |
| return; |
| } |
| |
| for (var i = 0; i < this._tabs.length; ++i) { |
| if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1) |
| this._hideTabElement(this._tabs[i]); |
| } |
| for (var i = 0; i < tabsToShowIndexes.length; ++i) { |
| var tab = this._tabs[tabsToShowIndexes[i]]; |
| if (!tab._shown) |
| this._showTabElement(i, tab); |
| } |
| |
| if (!this._overflowDisabled) |
| this._populateDropDownFromIndex(); |
| }, |
| |
| _populateDropDownFromIndex: function() |
| { |
| if (this._dropDownButton.parentElement) |
| this._headerContentsElement.removeChild(this._dropDownButton); |
| |
| this._dropDownMenu.clear(); |
| |
| var tabsToShow = []; |
| for (var i = 0; i < this._tabs.length; ++i) { |
| if (!this._tabs[i]._shown) |
| tabsToShow.push(this._tabs[i]); |
| } |
| |
| var selectedId = null; |
| for (var i = 0; i < tabsToShow.length; ++i) { |
| var tab = tabsToShow[i]; |
| this._dropDownMenu.addItem(tab.id, tab.title); |
| if (this._tabsHistory[0] === tab) |
| selectedId = tab.id; |
| } |
| if (tabsToShow.length) { |
| this._headerContentsElement.appendChild(this._dropDownButton); |
| this._dropDownMenu.selectItem(selectedId); |
| } |
| }, |
| |
| _measureDropDownButton: function() |
| { |
| if (this._overflowDisabled || this._measuredDropDownButtonWidth) |
| return; |
| this._dropDownButton.classList.add("measuring"); |
| this._headerContentsElement.appendChild(this._dropDownButton); |
| this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width; |
| this._headerContentsElement.removeChild(this._dropDownButton); |
| this._dropDownButton.classList.remove("measuring"); |
| }, |
| |
| _updateWidths: function() |
| { |
| var measuredWidths = this._measureWidths(); |
| var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE; |
| |
| var i = 0; |
| for (var tab of this._tabs) |
| tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++])); |
| }, |
| |
| _measureWidths: function() |
| { |
| // Add all elements to measure into this._tabsElement |
| this._tabsElement.style.setProperty("width", "2000px"); |
| var measuringTabElements = []; |
| for (var tab of this._tabs) { |
| if (typeof tab._measuredWidth === "number") |
| continue; |
| var measuringTabElement = tab._createTabElement(true); |
| measuringTabElement.__tab = tab; |
| measuringTabElements.push(measuringTabElement); |
| this._tabsElement.appendChild(measuringTabElement); |
| } |
| |
| // Perform measurement |
| for (var i = 0; i < measuringTabElements.length; ++i) { |
| var width = measuringTabElements[i].getBoundingClientRect().width; |
| measuringTabElements[i].__tab._measuredWidth = Math.ceil(width); |
| } |
| |
| // Nuke elements from the UI |
| for (var i = 0; i < measuringTabElements.length; ++i) |
| measuringTabElements[i].remove(); |
| |
| // Combine the results. |
| var measuredWidths = []; |
| for (var tab of this._tabs) |
| measuredWidths.push(tab._measuredWidth); |
| this._tabsElement.style.removeProperty("width"); |
| |
| return measuredWidths; |
| }, |
| |
| /** |
| * @param {!Array.<number>} measuredWidths |
| * @param {number} totalWidth |
| */ |
| _calculateMaxWidth: function(measuredWidths, totalWidth) |
| { |
| if (!measuredWidths.length) |
| return 0; |
| |
| measuredWidths.sort(function(x, y) { return x - y; }); |
| |
| var totalMeasuredWidth = 0; |
| for (var i = 0; i < measuredWidths.length; ++i) |
| totalMeasuredWidth += measuredWidths[i]; |
| |
| if (totalWidth >= totalMeasuredWidth) |
| return measuredWidths[measuredWidths.length - 1]; |
| |
| var totalExtraWidth = 0; |
| for (var i = measuredWidths.length - 1; i > 0; --i) { |
| var extraWidth = measuredWidths[i] - measuredWidths[i - 1]; |
| totalExtraWidth += (measuredWidths.length - i) * extraWidth; |
| |
| if (totalWidth + totalExtraWidth >= totalMeasuredWidth) |
| return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i); |
| } |
| |
| return totalWidth / measuredWidths.length; |
| }, |
| |
| /** |
| * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered |
| * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory |
| * @param {number} totalWidth |
| * @param {number} measuredDropDownButtonWidth |
| * @return {!Array.<number>} |
| */ |
| _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth) |
| { |
| var tabsToShowIndexes = []; |
| |
| var totalTabsWidth = 0; |
| var tabCount = tabsOrdered.length; |
| var tabsToLookAt = tabsOrdered.slice(0); |
| if (this._currentTab !== undefined) |
| tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._currentTab), 1)[0]); |
| if (this._lastSelectedOverflowTab !== undefined) |
| tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._lastSelectedOverflowTab), 1)[0]); |
| for (var i = 0; i < tabCount; ++i) { |
| var tab = this._automaticReorder ? tabsHistory[i] : tabsToLookAt[i]; |
| totalTabsWidth += tab.width(); |
| var minimalRequiredWidth = totalTabsWidth; |
| if (i !== tabCount - 1) |
| minimalRequiredWidth += measuredDropDownButtonWidth; |
| if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth) |
| break; |
| tabsToShowIndexes.push(tabsOrdered.indexOf(tab)); |
| } |
| |
| tabsToShowIndexes.sort(function(x, y) { return x - y; }); |
| |
| return tabsToShowIndexes; |
| }, |
| |
| _hideCurrentTab: function() |
| { |
| if (!this._currentTab) |
| return; |
| |
| this._hideTab(this._currentTab); |
| delete this._currentTab; |
| }, |
| |
| /** |
| * @param {!WebInspector.TabbedPaneTab} tab |
| */ |
| _showTab: function(tab) |
| { |
| tab.tabElement.classList.add("selected"); |
| tab.view.showWidget(this.element); |
| this._updateTabSlider(); |
| }, |
| |
| _updateTabSlider: function() |
| { |
| if (!this._currentTab || !this._sliderEnabled) |
| return; |
| var left = 0; |
| for (var i = 0; i < this._tabs.length && this._currentTab !== this._tabs[i] && this._tabs[i]._shown; i++) |
| left += this._tabs[i]._measuredWidth; |
| var sliderWidth = this._currentTab._shown ? this._currentTab._measuredWidth : this._dropDownButton.offsetWidth; |
| var scaleFactor = window.devicePixelRatio >= 1.5 ? " scaleY(0.75)" : ""; |
| this._tabSlider.style.transform = "translateX(" + left + "px)" + scaleFactor; |
| this._tabSlider.style.width = sliderWidth + "px"; |
| |
| if (this._tabSlider.parentElement !== this._headerContentsElement) |
| this._headerContentsElement.appendChild(this._tabSlider); |
| }, |
| |
| /** |
| * @param {!WebInspector.TabbedPaneTab} tab |
| */ |
| _hideTab: function(tab) |
| { |
| tab.tabElement.classList.remove("selected"); |
| tab.view.hideWidget(); |
| }, |
| |
| /** |
| * @override |
| * @return {!Array.<!Element>} |
| */ |
| elementsToRestoreScrollPositionsFor: function() |
| { |
| return [ this._contentElement ]; |
| }, |
| |
| /** |
| * @param {!WebInspector.TabbedPaneTab} tab |
| * @param {number} index |
| */ |
| _insertBefore: function(tab, index) |
| { |
| this._tabsElement.insertBefore(tab._tabElement || null, this._tabsElement.childNodes[index]); |
| var oldIndex = this._tabs.indexOf(tab); |
| this._tabs.splice(oldIndex, 1); |
| if (oldIndex < index) |
| --index; |
| this._tabs.splice(index, 0, tab); |
| this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabOrderChanged, this._tabs); |
| }, |
| |
| /** |
| * @param {!Element} element |
| */ |
| insertBeforeTabStrip: function(element) |
| { |
| element.classList.add("tabbed-pane-header-before"); |
| this.element.appendChild(element); |
| }, |
| |
| /** |
| * @param {!Element} element |
| */ |
| appendAfterTabStrip: function(element) |
| { |
| element.classList.add("tabbed-pane-header-after"); |
| this.element.appendChild(element); |
| }, |
| |
| renderWithNoHeaderBackground: function() |
| { |
| this._headerElement.classList.add("tabbed-pane-no-header-background"); |
| }, |
| |
| /** |
| * @param {boolean} allow |
| * @param {boolean=} automatic |
| */ |
| setAllowTabReorder: function(allow, automatic) |
| { |
| this._allowTabReorder = allow; |
| this._automaticReorder = automatic; |
| }, |
| |
| __proto__: WebInspector.VBox.prototype |
| } |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.TabbedPane} tabbedPane |
| * @param {string} id |
| * @param {string} title |
| * @param {boolean} closeable |
| * @param {!WebInspector.Widget} view |
| * @param {string=} tooltip |
| */ |
| WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, tooltip) |
| { |
| this._closeable = closeable; |
| this._tabbedPane = tabbedPane; |
| this._id = id; |
| this._title = title; |
| this._tooltip = tooltip; |
| this._view = view; |
| this._shown = false; |
| /** @type {number} */ this._measuredWidth; |
| /** @type {!Element|undefined} */ this._tabElement; |
| } |
| |
| WebInspector.TabbedPaneTab.prototype = { |
| /** |
| * @return {string} |
| */ |
| get id() |
| { |
| return this._id; |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| get title() |
| { |
| return this._title; |
| }, |
| |
| set title(title) |
| { |
| if (title === this._title) |
| return; |
| this._title = title; |
| if (this._titleElement) |
| this._titleElement.textContent = title; |
| delete this._measuredWidth; |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| isCloseable: function() |
| { |
| return this._closeable; |
| }, |
| |
| /** |
| * @param {string} iconType |
| * @param {string=} iconTooltip |
| * @return {boolean} |
| */ |
| _setIconType: function(iconType, iconTooltip) |
| { |
| if (iconType === this._iconType && iconTooltip === this._iconTooltip) |
| return false; |
| this._iconType = iconType; |
| this._iconTooltip = iconTooltip; |
| if (this._tabElement) |
| this._createIconElement(this._tabElement, this._titleElement); |
| delete this._measuredWidth; |
| return true; |
| }, |
| |
| /** |
| * @param {string} className |
| * @param {boolean=} force |
| * @return {boolean} |
| */ |
| _toggleClass: function(className, force) |
| { |
| var element = this.tabElement; |
| var hasClass = element.classList.contains(className); |
| if (hasClass === force) |
| return false; |
| element.classList.toggle(className, force); |
| delete this._measuredWidth; |
| return true; |
| }, |
| |
| /** |
| * @return {!WebInspector.Widget} |
| */ |
| get view() |
| { |
| return this._view; |
| }, |
| |
| set view(view) |
| { |
| this._view = view; |
| }, |
| |
| /** |
| * @return {string|undefined} |
| */ |
| get tooltip() |
| { |
| return this._tooltip; |
| }, |
| |
| set tooltip(tooltip) |
| { |
| this._tooltip = tooltip; |
| if (this._titleElement) |
| this._titleElement.title = tooltip || ""; |
| }, |
| |
| /** |
| * @return {!Element} |
| */ |
| get tabElement() |
| { |
| if (!this._tabElement) |
| this._tabElement = this._createTabElement(false); |
| |
| return this._tabElement; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| width: function() |
| { |
| return this._width; |
| }, |
| |
| /** |
| * @param {number} width |
| */ |
| setWidth: function(width) |
| { |
| this.tabElement.style.width = width === -1 ? "" : (width + "px"); |
| this._width = width; |
| }, |
| |
| /** |
| * @param {!WebInspector.TabbedPaneTabDelegate} delegate |
| */ |
| setDelegate: function(delegate) |
| { |
| this._delegate = delegate; |
| }, |
| |
| /** |
| * @param {!Element} tabElement |
| * @param {!Element} titleElement |
| */ |
| _createIconElement: function(tabElement, titleElement) |
| { |
| if (tabElement.__iconElement) |
| tabElement.__iconElement.remove(); |
| if (!this._iconType) |
| return; |
| |
| var iconElement = createElementWithClass("label", "tabbed-pane-header-tab-icon", "dt-icon-label"); |
| iconElement.type = this._iconType; |
| if (this._iconTooltip) |
| iconElement.title = this._iconTooltip; |
| tabElement.insertBefore(iconElement, titleElement); |
| tabElement.__iconElement = iconElement; |
| }, |
| |
| /** |
| * @param {boolean} measuring |
| * @return {!Element} |
| */ |
| _createTabElement: function(measuring) |
| { |
| var tabElement = createElementWithClass("div", "tabbed-pane-header-tab"); |
| tabElement.id = "tab-" + this._id; |
| tabElement.tabIndex = -1; |
| tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPane, this.id, true); |
| |
| var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title"); |
| titleElement.textContent = this.title; |
| titleElement.title = this.tooltip || ""; |
| this._createIconElement(tabElement, titleElement); |
| if (!measuring) |
| this._titleElement = titleElement; |
| |
| if (this._closeable) |
| tabElement.createChild("div", "tabbed-pane-close-button", "dt-close-button").gray = true; |
| |
| if (measuring) { |
| tabElement.classList.add("measuring"); |
| } else { |
| tabElement.addEventListener("click", this._tabClicked.bind(this), false); |
| tabElement.addEventListener("auxclick", this._tabClicked.bind(this), false); |
| tabElement.addEventListener("mousedown", this._tabMouseDown.bind(this), false); |
| tabElement.addEventListener("mouseup", this._tabMouseUp.bind(this), false); |
| |
| tabElement.addEventListener("contextmenu", this._tabContextMenu.bind(this), false); |
| if (this._tabbedPane._allowTabReorder) |
| WebInspector.installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "-webkit-grabbing", "pointer", 200); |
| } |
| |
| return tabElement; |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _tabClicked: function(event) |
| { |
| var middleButton = event.button === 1; |
| var shouldClose = this._closeable && (middleButton || event.target.classList.contains("tabbed-pane-close-button")); |
| if (!shouldClose) { |
| this._tabbedPane.focus(); |
| return; |
| } |
| this._closeTabs([this.id]); |
| event.consume(true); |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _tabMouseDown: function(event) |
| { |
| if (event.target.classList.contains("tabbed-pane-close-button") || event.button === 1) |
| return; |
| this._tabbedPane.selectTab(this.id, true); |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _tabMouseUp: function(event) |
| { |
| // This is needed to prevent middle-click pasting on linux when tabs are clicked. |
| if (event.button === 1) |
| event.consume(true); |
| }, |
| |
| /** |
| * @param {!Array.<string>} ids |
| */ |
| _closeTabs: function(ids) |
| { |
| if (this._delegate) { |
| this._delegate.closeTabs(this._tabbedPane, ids); |
| return; |
| } |
| this._tabbedPane.closeTabs(ids, true); |
| }, |
| |
| _tabContextMenu: function(event) |
| { |
| /** |
| * @this {WebInspector.TabbedPaneTab} |
| */ |
| function close() |
| { |
| this._closeTabs([this.id]); |
| } |
| |
| /** |
| * @this {WebInspector.TabbedPaneTab} |
| */ |
| function closeOthers() |
| { |
| this._closeTabs(this._tabbedPane.otherTabs(this.id)); |
| } |
| |
| /** |
| * @this {WebInspector.TabbedPaneTab} |
| */ |
| function closeAll() |
| { |
| this._closeTabs(this._tabbedPane.allTabs()); |
| } |
| |
| /** |
| * @this {WebInspector.TabbedPaneTab} |
| */ |
| function closeToTheRight() |
| { |
| this._closeTabs(this._tabbedPane._tabsToTheRight(this.id)); |
| } |
| |
| var contextMenu = new WebInspector.ContextMenu(event); |
| if (this._closeable) { |
| contextMenu.appendItem(WebInspector.UIString.capitalize("Close"), close.bind(this)); |
| contextMenu.appendItem(WebInspector.UIString.capitalize("Close ^others"), closeOthers.bind(this)); |
| contextMenu.appendItem(WebInspector.UIString.capitalize("Close ^tabs to the ^right"), closeToTheRight.bind(this)); |
| contextMenu.appendItem(WebInspector.UIString.capitalize("Close ^all"), closeAll.bind(this)); |
| } |
| if (this._delegate) |
| this._delegate.onContextMenu(this.id, contextMenu); |
| contextMenu.show(); |
| }, |
| |
| /** |
| * @param {!Event} event |
| * @return {boolean} |
| */ |
| _startTabDragging: function(event) |
| { |
| if (event.target.classList.contains("tabbed-pane-close-button")) |
| return false; |
| this._dragStartX = event.pageX; |
| this._tabElement.classList.add("dragging"); |
| this._tabbedPane._tabSlider.remove(); |
| return true; |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _tabDragging: function(event) |
| { |
| var tabElements = this._tabbedPane._tabsElement.childNodes; |
| for (var i = 0; i < tabElements.length; ++i) { |
| var tabElement = tabElements[i]; |
| if (tabElement === this._tabElement) |
| continue; |
| |
| var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft && |
| this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft; |
| if (!intersects) |
| continue; |
| |
| if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2 + 5) |
| break; |
| |
| if (event.pageX - this._dragStartX > 0) { |
| tabElement = tabElement.nextSibling; |
| ++i; |
| } |
| |
| var oldOffsetLeft = this._tabElement.offsetLeft; |
| this._tabbedPane._insertBefore(this, i); |
| this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft; |
| break; |
| } |
| |
| if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0) { |
| this._tabElement.style.setProperty("left", "0px"); |
| return; |
| } |
| if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) { |
| this._tabElement.style.setProperty("left", "0px"); |
| return; |
| } |
| |
| this._tabElement.style.setProperty("left", (event.pageX - this._dragStartX) + "px"); |
| }, |
| |
| /** |
| * @param {!Event} event |
| */ |
| _endTabDragging: function(event) |
| { |
| this._tabElement.classList.remove("dragging"); |
| this._tabElement.style.removeProperty("left"); |
| delete this._dragStartX; |
| this._tabbedPane._updateTabSlider(); |
| } |
| } |
| |
| /** |
| * @interface |
| */ |
| WebInspector.TabbedPaneTabDelegate = function() |
| { |
| } |
| |
| WebInspector.TabbedPaneTabDelegate.prototype = { |
| /** |
| * @param {!WebInspector.TabbedPane} tabbedPane |
| * @param {!Array.<string>} ids |
| */ |
| closeTabs: function(tabbedPane, ids) { }, |
| |
| /** |
| * @param {string} tabId |
| * @param {!WebInspector.ContextMenu} contextMenu |
| */ |
| onContextMenu: function(tabId, contextMenu) { } |
| } |