OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2010 Google Inc. All rights reserved. | 2 * Copyright (C) 2010 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
13 * distribution. | 13 * distribution. |
14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
17 * | 17 * |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 */ | 29 */ |
30 | |
31 /** | 30 /** |
32 * @extends {WebInspector.VBox} | 31 * @unrestricted |
33 * @constructor | |
34 */ | 32 */ |
35 WebInspector.TabbedPane = function() | 33 WebInspector.TabbedPane = class extends WebInspector.VBox { |
36 { | 34 constructor() { |
37 WebInspector.VBox.call(this, true); | 35 super(true); |
38 this.registerRequiredCSS("ui/tabbedPane.css"); | 36 this.registerRequiredCSS('ui/tabbedPane.css'); |
39 this.element.classList.add("tabbed-pane"); | 37 this.element.classList.add('tabbed-pane'); |
40 this.contentElement.classList.add("tabbed-pane-shadow"); | 38 this.contentElement.classList.add('tabbed-pane-shadow'); |
41 this.contentElement.tabIndex = -1; | 39 this.contentElement.tabIndex = -1; |
42 this._headerElement = this.contentElement.createChild("div", "tabbed-pane-he
ader"); | 40 this._headerElement = this.contentElement.createChild('div', 'tabbed-pane-he
ader'); |
43 this._headerContentsElement = this._headerElement.createChild("div", "tabbed
-pane-header-contents"); | 41 this._headerContentsElement = this._headerElement.createChild('div', 'tabbed
-pane-header-contents'); |
44 this._headerContentsElement.setAttribute("aria-label", WebInspector.UIString
("Panels")); | 42 this._headerContentsElement.setAttribute('aria-label', WebInspector.UIString
('Panels')); |
45 this._tabSlider = createElementWithClass("div", "tabbed-pane-tab-slider"); | 43 this._tabSlider = createElementWithClass('div', 'tabbed-pane-tab-slider'); |
46 this._tabsElement = this._headerContentsElement.createChild("div", "tabbed-p
ane-header-tabs"); | 44 this._tabsElement = this._headerContentsElement.createChild('div', 'tabbed-p
ane-header-tabs'); |
47 this._tabsElement.setAttribute("role", "tablist"); | 45 this._tabsElement.setAttribute('role', 'tablist'); |
48 this._contentElement = this.contentElement.createChild("div", "tabbed-pane-c
ontent"); | 46 this._contentElement = this.contentElement.createChild('div', 'tabbed-pane-c
ontent'); |
49 this._contentElement.setAttribute("role", "tabpanel"); | 47 this._contentElement.setAttribute('role', 'tabpanel'); |
50 this._contentElement.createChild("content"); | 48 this._contentElement.createChild('content'); |
51 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ | 49 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ |
52 this._tabs = []; | 50 this._tabs = []; |
53 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ | 51 /** @type {!Array.<!WebInspector.TabbedPaneTab>} */ |
54 this._tabsHistory = []; | 52 this._tabsHistory = []; |
55 /** @type {!Object.<string, !WebInspector.TabbedPaneTab>} */ | 53 /** @type {!Object.<string, !WebInspector.TabbedPaneTab>} */ |
56 this._tabsById = {}; | 54 this._tabsById = {}; |
57 this._currentTabLocked = false; | 55 this._currentTabLocked = false; |
58 this._autoSelectFirstItemOnShow = true; | 56 this._autoSelectFirstItemOnShow = true; |
59 | 57 |
60 this._dropDownButton = this._createDropDownButton(); | 58 this._dropDownButton = this._createDropDownButton(); |
61 WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.Zo
omChanged, this._zoomChanged, this); | 59 WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.Zo
omChanged, this._zoomChanged, this); |
| 60 } |
| 61 |
| 62 /** |
| 63 * @param {boolean} locked |
| 64 */ |
| 65 setCurrentTabLocked(locked) { |
| 66 this._currentTabLocked = locked; |
| 67 this._headerElement.classList.toggle('locked', this._currentTabLocked); |
| 68 } |
| 69 |
| 70 /** |
| 71 * @param {boolean} autoSelect |
| 72 */ |
| 73 setAutoSelectFirstItemOnShow(autoSelect) { |
| 74 this._autoSelectFirstItemOnShow = autoSelect; |
| 75 } |
| 76 |
| 77 /** |
| 78 * @return {?WebInspector.Widget} |
| 79 */ |
| 80 get visibleView() { |
| 81 return this._currentTab ? this._currentTab.view : null; |
| 82 } |
| 83 |
| 84 /** |
| 85 * @return {!Array.<string>} |
| 86 */ |
| 87 tabIds() { |
| 88 return this._tabs.map(tab => tab._id); |
| 89 } |
| 90 |
| 91 /** |
| 92 * @param {string} tabId |
| 93 * @return {number} |
| 94 */ |
| 95 tabIndex(tabId) { |
| 96 return this._tabs.findIndex(tab => tab.id === tabId); |
| 97 } |
| 98 |
| 99 /** |
| 100 * @return {!Array.<!WebInspector.Widget>} |
| 101 */ |
| 102 tabViews() { |
| 103 return this._tabs.map(tab => tab.view); |
| 104 } |
| 105 |
| 106 /** |
| 107 * @param {string} tabId |
| 108 * @return {?WebInspector.Widget} |
| 109 */ |
| 110 tabView(tabId) { |
| 111 return this._tabsById[tabId] ? this._tabsById[tabId].view : null; |
| 112 } |
| 113 |
| 114 /** |
| 115 * @return {?string} |
| 116 */ |
| 117 get selectedTabId() { |
| 118 return this._currentTab ? this._currentTab.id : null; |
| 119 } |
| 120 |
| 121 /** |
| 122 * @param {boolean} shrinkableTabs |
| 123 */ |
| 124 setShrinkableTabs(shrinkableTabs) { |
| 125 this._shrinkableTabs = shrinkableTabs; |
| 126 } |
| 127 |
| 128 /** |
| 129 * @param {boolean} verticalTabLayout |
| 130 */ |
| 131 setVerticalTabLayout(verticalTabLayout) { |
| 132 this._verticalTabLayout = verticalTabLayout; |
| 133 this.contentElement.classList.add('vertical-tab-layout'); |
| 134 this.invalidateConstraints(); |
| 135 } |
| 136 |
| 137 /** |
| 138 * @param {boolean} closeableTabs |
| 139 */ |
| 140 setCloseableTabs(closeableTabs) { |
| 141 this._closeableTabs = closeableTabs; |
| 142 } |
| 143 |
| 144 /** |
| 145 * @override |
| 146 */ |
| 147 focus() { |
| 148 if (this.visibleView) |
| 149 this.visibleView.focus(); |
| 150 else |
| 151 this.contentElement.focus(); |
| 152 } |
| 153 |
| 154 /** |
| 155 * @return {!Element} |
| 156 */ |
| 157 headerElement() { |
| 158 return this._headerElement; |
| 159 } |
| 160 |
| 161 /** |
| 162 * @param {string} id |
| 163 * @return {boolean} |
| 164 */ |
| 165 isTabCloseable(id) { |
| 166 var tab = this._tabsById[id]; |
| 167 return tab ? tab.isCloseable() : false; |
| 168 } |
| 169 |
| 170 /** |
| 171 * @param {!WebInspector.TabbedPaneTabDelegate} delegate |
| 172 */ |
| 173 setTabDelegate(delegate) { |
| 174 var tabs = this._tabs.slice(); |
| 175 for (var i = 0; i < tabs.length; ++i) |
| 176 tabs[i].setDelegate(delegate); |
| 177 this._delegate = delegate; |
| 178 } |
| 179 |
| 180 /** |
| 181 * @param {string} id |
| 182 * @param {string} tabTitle |
| 183 * @param {!WebInspector.Widget} view |
| 184 * @param {string=} tabTooltip |
| 185 * @param {boolean=} userGesture |
| 186 * @param {boolean=} isCloseable |
| 187 * @param {number=} index |
| 188 */ |
| 189 appendTab(id, tabTitle, view, tabTooltip, userGesture, isCloseable, index) { |
| 190 isCloseable = typeof isCloseable === 'boolean' ? isCloseable : this._closeab
leTabs; |
| 191 var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, isCloseable, vi
ew, tabTooltip); |
| 192 tab.setDelegate(this._delegate); |
| 193 this._tabsById[id] = tab; |
| 194 if (index !== undefined) |
| 195 this._tabs.splice(index, 0, tab); |
| 196 else |
| 197 this._tabs.push(tab); |
| 198 this._tabsHistory.push(tab); |
| 199 view.attach(this); |
| 200 if (this._tabsHistory[0] === tab && this.isShowing()) |
| 201 this.selectTab(tab.id, userGesture); |
| 202 this._updateTabElements(); |
| 203 } |
| 204 |
| 205 /** |
| 206 * @param {string} id |
| 207 * @param {boolean=} userGesture |
| 208 */ |
| 209 closeTab(id, userGesture) { |
| 210 this.closeTabs([id], userGesture); |
| 211 } |
| 212 |
| 213 /** |
| 214 * @param {!Array.<string>} ids |
| 215 * @param {boolean=} userGesture |
| 216 */ |
| 217 closeTabs(ids, userGesture) { |
| 218 var focused = this.hasFocus(); |
| 219 for (var i = 0; i < ids.length; ++i) |
| 220 this._innerCloseTab(ids[i], userGesture); |
| 221 this._updateTabElements(); |
| 222 if (this._tabsHistory.length) |
| 223 this.selectTab(this._tabsHistory[0].id, false); |
| 224 if (focused) |
| 225 this.focus(); |
| 226 } |
| 227 |
| 228 /** |
| 229 * @param {string} id |
| 230 * @param {boolean=} userGesture |
| 231 */ |
| 232 _innerCloseTab(id, userGesture) { |
| 233 if (!this._tabsById[id]) |
| 234 return; |
| 235 if (userGesture && !this._tabsById[id]._closeable) |
| 236 return; |
| 237 if (this._currentTab && this._currentTab.id === id) |
| 238 this._hideCurrentTab(); |
| 239 |
| 240 var tab = this._tabsById[id]; |
| 241 delete this._tabsById[id]; |
| 242 |
| 243 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); |
| 244 this._tabs.splice(this._tabs.indexOf(tab), 1); |
| 245 if (tab._shown) |
| 246 this._hideTabElement(tab); |
| 247 tab.view.detach(); |
| 248 |
| 249 var eventData = {tabId: id, view: tab.view, isUserGesture: userGesture}; |
| 250 this.dispatchEventToListeners(WebInspector.TabbedPane.Events.TabClosed, even
tData); |
| 251 return true; |
| 252 } |
| 253 |
| 254 /** |
| 255 * @param {string} tabId |
| 256 * @return {boolean} |
| 257 */ |
| 258 hasTab(tabId) { |
| 259 return !!this._tabsById[tabId]; |
| 260 } |
| 261 |
| 262 /** |
| 263 * @return {!Array.<string>} |
| 264 */ |
| 265 allTabs() { |
| 266 return this._tabs.map(function(tab) { |
| 267 return tab.id; |
| 268 }); |
| 269 } |
| 270 |
| 271 /** |
| 272 * @param {string} id |
| 273 * @return {!Array.<string>} |
| 274 */ |
| 275 otherTabs(id) { |
| 276 var result = []; |
| 277 for (var i = 0; i < this._tabs.length; ++i) { |
| 278 if (this._tabs[i].id !== id) |
| 279 result.push(this._tabs[i].id); |
| 280 } |
| 281 return result; |
| 282 } |
| 283 |
| 284 /** |
| 285 * @param {string} id |
| 286 * @return {!Array.<string>} |
| 287 */ |
| 288 _tabsToTheRight(id) { |
| 289 var index = -1; |
| 290 for (var i = 0; i < this._tabs.length; ++i) { |
| 291 if (this._tabs[i].id === id) { |
| 292 index = i; |
| 293 break; |
| 294 } |
| 295 } |
| 296 if (index === -1) |
| 297 return []; |
| 298 return this._tabs.slice(index + 1).map(function(tab) { |
| 299 return tab.id; |
| 300 }); |
| 301 } |
| 302 |
| 303 /** |
| 304 * @param {string} id |
| 305 * @param {boolean=} userGesture |
| 306 * @return {boolean} |
| 307 */ |
| 308 selectTab(id, userGesture) { |
| 309 if (this._currentTabLocked) |
| 310 return false; |
| 311 var focused = this.hasFocus(); |
| 312 var tab = this._tabsById[id]; |
| 313 if (!tab) |
| 314 return false; |
| 315 if (this._currentTab && this._currentTab.id === id) |
| 316 return true; |
| 317 |
| 318 this.suspendInvalidations(); |
| 319 this._hideCurrentTab(); |
| 320 this._showTab(tab); |
| 321 this.resumeInvalidations(); |
| 322 this._currentTab = tab; |
| 323 |
| 324 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); |
| 325 this._tabsHistory.splice(0, 0, tab); |
| 326 |
| 327 this._updateTabElements(); |
| 328 if (focused) |
| 329 this.focus(); |
| 330 |
| 331 var eventData = {tabId: id, view: tab.view, isUserGesture: userGesture}; |
| 332 this.dispatchEventToListeners(WebInspector.TabbedPane.Events.TabSelected, ev
entData); |
| 333 return true; |
| 334 } |
| 335 |
| 336 /** |
| 337 * @param {number} tabsCount |
| 338 * @return {!Array.<string>} |
| 339 */ |
| 340 lastOpenedTabIds(tabsCount) { |
| 341 function tabToTabId(tab) { |
| 342 return tab.id; |
| 343 } |
| 344 |
| 345 return this._tabsHistory.slice(0, tabsCount).map(tabToTabId); |
| 346 } |
| 347 |
| 348 /** |
| 349 * @param {string} id |
| 350 * @param {string} iconType |
| 351 * @param {string=} iconTooltip |
| 352 */ |
| 353 setTabIcon(id, iconType, iconTooltip) { |
| 354 var tab = this._tabsById[id]; |
| 355 if (tab._setIconType(iconType, iconTooltip)) |
| 356 this._updateTabElements(); |
| 357 } |
| 358 |
| 359 /** |
| 360 * @param {string} id |
| 361 * @param {boolean} enabled |
| 362 */ |
| 363 setTabEnabled(id, enabled) { |
| 364 var tab = this._tabsById[id]; |
| 365 tab.tabElement.classList.toggle('disabled', !enabled); |
| 366 } |
| 367 |
| 368 /** |
| 369 * @param {string} id |
| 370 * @param {string} className |
| 371 * @param {boolean=} force |
| 372 */ |
| 373 toggleTabClass(id, className, force) { |
| 374 var tab = this._tabsById[id]; |
| 375 if (tab._toggleClass(className, force)) |
| 376 this._updateTabElements(); |
| 377 } |
| 378 |
| 379 /** |
| 380 * @param {!WebInspector.Event} event |
| 381 */ |
| 382 _zoomChanged(event) { |
| 383 for (var i = 0; i < this._tabs.length; ++i) |
| 384 delete this._tabs[i]._measuredWidth; |
| 385 if (this.isShowing()) |
| 386 this._updateTabElements(); |
| 387 } |
| 388 |
| 389 /** |
| 390 * @param {string} id |
| 391 * @param {string} tabTitle |
| 392 * @param {string=} tabTooltip |
| 393 */ |
| 394 changeTabTitle(id, tabTitle, tabTooltip) { |
| 395 var tab = this._tabsById[id]; |
| 396 if (tabTooltip !== undefined) |
| 397 tab.tooltip = tabTooltip; |
| 398 if (tab.title !== tabTitle) { |
| 399 tab.title = tabTitle; |
| 400 this._updateTabElements(); |
| 401 } |
| 402 } |
| 403 |
| 404 /** |
| 405 * @param {string} id |
| 406 * @param {!WebInspector.Widget} view |
| 407 */ |
| 408 changeTabView(id, view) { |
| 409 var tab = this._tabsById[id]; |
| 410 if (tab.view === view) |
| 411 return; |
| 412 |
| 413 var shouldFocus = tab.view.hasFocus(); |
| 414 |
| 415 this.suspendInvalidations(); |
| 416 |
| 417 var isSelected = this._currentTab && this._currentTab.id === id; |
| 418 if (isSelected) |
| 419 this._hideTab(tab); |
| 420 tab.view.detach(); |
| 421 tab.view = view; |
| 422 tab.view.attach(this); |
| 423 if (isSelected) |
| 424 this._showTab(tab); |
| 425 if (shouldFocus) |
| 426 tab.view.focus(); |
| 427 |
| 428 this.resumeInvalidations(); |
| 429 } |
| 430 |
| 431 /** |
| 432 * @override |
| 433 */ |
| 434 onResize() { |
| 435 this._updateTabElements(); |
| 436 } |
| 437 |
| 438 headerResized() { |
| 439 this._updateTabElements(); |
| 440 } |
| 441 |
| 442 /** |
| 443 * @override |
| 444 */ |
| 445 wasShown() { |
| 446 var effectiveTab = this._currentTab || this._tabsHistory[0]; |
| 447 if (effectiveTab && this._autoSelectFirstItemOnShow) |
| 448 this.selectTab(effectiveTab.id); |
| 449 } |
| 450 |
| 451 /** |
| 452 * @param {boolean} enable |
| 453 */ |
| 454 setTabSlider(enable) { |
| 455 this._sliderEnabled = enable; |
| 456 this._tabSlider.classList.toggle('enabled', enable); |
| 457 } |
| 458 |
| 459 /** |
| 460 * @override |
| 461 * @return {!Constraints} |
| 462 */ |
| 463 calculateConstraints() { |
| 464 var constraints = super.calculateConstraints(); |
| 465 var minContentConstraints = new Constraints(new Size(0, 0), new Size(50, 50)
); |
| 466 constraints = constraints.widthToMax(minContentConstraints).heightToMax(minC
ontentConstraints); |
| 467 if (this._verticalTabLayout) |
| 468 constraints = constraints.addWidth(new Constraints(new Size(120, 0))); |
| 469 else |
| 470 constraints = constraints.addHeight(new Constraints(new Size(0, 30))); |
| 471 return constraints; |
| 472 } |
| 473 |
| 474 _updateTabElements() { |
| 475 WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements); |
| 476 } |
| 477 |
| 478 /** |
| 479 * @param {string} text |
| 480 */ |
| 481 setPlaceholderText(text) { |
| 482 this._noTabsMessage = text; |
| 483 } |
| 484 |
| 485 _innerUpdateTabElements() { |
| 486 if (!this.isShowing()) |
| 487 return; |
| 488 |
| 489 if (!this._tabs.length) { |
| 490 this._contentElement.classList.add('has-no-tabs'); |
| 491 if (this._noTabsMessage && !this._noTabsMessageElement) { |
| 492 this._noTabsMessageElement = this._contentElement.createChild('div', 'ta
bbed-pane-placeholder fill'); |
| 493 this._noTabsMessageElement.textContent = this._noTabsMessage; |
| 494 } |
| 495 } else { |
| 496 this._contentElement.classList.remove('has-no-tabs'); |
| 497 if (this._noTabsMessageElement) { |
| 498 this._noTabsMessageElement.remove(); |
| 499 delete this._noTabsMessageElement; |
| 500 } |
| 501 } |
| 502 |
| 503 this._measureDropDownButton(); |
| 504 this._updateWidths(); |
| 505 this._updateTabsDropDown(); |
| 506 this._updateTabSlider(); |
| 507 } |
| 508 |
| 509 /** |
| 510 * @param {number} index |
| 511 * @param {!WebInspector.TabbedPaneTab} tab |
| 512 */ |
| 513 _showTabElement(index, tab) { |
| 514 if (index >= this._tabsElement.children.length) |
| 515 this._tabsElement.appendChild(tab.tabElement); |
| 516 else |
| 517 this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[
index]); |
| 518 tab._shown = true; |
| 519 } |
| 520 |
| 521 /** |
| 522 * @param {!WebInspector.TabbedPaneTab} tab |
| 523 */ |
| 524 _hideTabElement(tab) { |
| 525 this._tabsElement.removeChild(tab.tabElement); |
| 526 tab._shown = false; |
| 527 } |
| 528 |
| 529 _createDropDownButton() { |
| 530 var dropDownContainer = createElementWithClass('div', 'tabbed-pane-header-ta
bs-drop-down-container'); |
| 531 dropDownContainer.createChild('div', 'glyph'); |
| 532 this._dropDownMenu = new WebInspector.DropDownMenu(dropDownContainer); |
| 533 this._dropDownMenu.addEventListener( |
| 534 WebInspector.DropDownMenu.Events.ItemSelected, this._dropDownMenuItemSel
ected, this); |
| 535 |
| 536 return dropDownContainer; |
| 537 } |
| 538 |
| 539 /** |
| 540 * @param {!WebInspector.Event} event |
| 541 */ |
| 542 _dropDownMenuItemSelected(event) { |
| 543 var tabId = /** @type {string} */ (event.data); |
| 544 this._lastSelectedOverflowTab = this._tabsById[tabId]; |
| 545 this.selectTab(tabId, true); |
| 546 } |
| 547 |
| 548 _totalWidth() { |
| 549 return this._headerContentsElement.getBoundingClientRect().width; |
| 550 } |
| 551 |
| 552 /** |
| 553 * @return {number} |
| 554 */ |
| 555 _numberOfTabsShown() { |
| 556 var numTabsShown = 0; |
| 557 for (var tab of this._tabs) { |
| 558 if (tab._shown) |
| 559 numTabsShown++; |
| 560 } |
| 561 return numTabsShown; |
| 562 } |
| 563 |
| 564 disableOverflowMenu() { |
| 565 this._overflowDisabled = true; |
| 566 } |
| 567 |
| 568 _updateTabsDropDown() { |
| 569 var tabsToShowIndexes = this._tabsToShowIndexes( |
| 570 this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDow
nButtonWidth || 0); |
| 571 if (this._lastSelectedOverflowTab && this._numberOfTabsShown() !== tabsToSho
wIndexes.length) { |
| 572 delete this._lastSelectedOverflowTab; |
| 573 this._updateTabsDropDown(); |
| 574 return; |
| 575 } |
| 576 |
| 577 for (var i = 0; i < this._tabs.length; ++i) { |
| 578 if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1) |
| 579 this._hideTabElement(this._tabs[i]); |
| 580 } |
| 581 for (var i = 0; i < tabsToShowIndexes.length; ++i) { |
| 582 var tab = this._tabs[tabsToShowIndexes[i]]; |
| 583 if (!tab._shown) |
| 584 this._showTabElement(i, tab); |
| 585 } |
| 586 |
| 587 if (!this._overflowDisabled) |
| 588 this._populateDropDownFromIndex(); |
| 589 } |
| 590 |
| 591 _populateDropDownFromIndex() { |
| 592 if (this._dropDownButton.parentElement) |
| 593 this._headerContentsElement.removeChild(this._dropDownButton); |
| 594 |
| 595 this._dropDownMenu.clear(); |
| 596 |
| 597 var tabsToShow = []; |
| 598 for (var i = 0; i < this._tabs.length; ++i) { |
| 599 if (!this._tabs[i]._shown) |
| 600 tabsToShow.push(this._tabs[i]); |
| 601 } |
| 602 |
| 603 var selectedId = null; |
| 604 for (var i = 0; i < tabsToShow.length; ++i) { |
| 605 var tab = tabsToShow[i]; |
| 606 this._dropDownMenu.addItem(tab.id, tab.title); |
| 607 if (this._tabsHistory[0] === tab) |
| 608 selectedId = tab.id; |
| 609 } |
| 610 if (tabsToShow.length) { |
| 611 this._headerContentsElement.appendChild(this._dropDownButton); |
| 612 this._dropDownMenu.selectItem(selectedId); |
| 613 } |
| 614 } |
| 615 |
| 616 _measureDropDownButton() { |
| 617 if (this._overflowDisabled || this._measuredDropDownButtonWidth) |
| 618 return; |
| 619 this._dropDownButton.classList.add('measuring'); |
| 620 this._headerContentsElement.appendChild(this._dropDownButton); |
| 621 this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRe
ct().width; |
| 622 this._headerContentsElement.removeChild(this._dropDownButton); |
| 623 this._dropDownButton.classList.remove('measuring'); |
| 624 } |
| 625 |
| 626 _updateWidths() { |
| 627 var measuredWidths = this._measureWidths(); |
| 628 var maxWidth = |
| 629 this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), t
his._totalWidth()) : Number.MAX_VALUE; |
| 630 |
| 631 var i = 0; |
| 632 for (var tab of this._tabs) |
| 633 tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWid
ths[i++])); |
| 634 } |
| 635 |
| 636 _measureWidths() { |
| 637 // Add all elements to measure into this._tabsElement |
| 638 this._tabsElement.style.setProperty('width', '2000px'); |
| 639 var measuringTabElements = []; |
| 640 for (var tab of this._tabs) { |
| 641 if (typeof tab._measuredWidth === 'number') |
| 642 continue; |
| 643 var measuringTabElement = tab._createTabElement(true); |
| 644 measuringTabElement.__tab = tab; |
| 645 measuringTabElements.push(measuringTabElement); |
| 646 this._tabsElement.appendChild(measuringTabElement); |
| 647 } |
| 648 |
| 649 // Perform measurement |
| 650 for (var i = 0; i < measuringTabElements.length; ++i) { |
| 651 var width = measuringTabElements[i].getBoundingClientRect().width; |
| 652 measuringTabElements[i].__tab._measuredWidth = Math.ceil(width); |
| 653 } |
| 654 |
| 655 // Nuke elements from the UI |
| 656 for (var i = 0; i < measuringTabElements.length; ++i) |
| 657 measuringTabElements[i].remove(); |
| 658 |
| 659 // Combine the results. |
| 660 var measuredWidths = []; |
| 661 for (var tab of this._tabs) |
| 662 measuredWidths.push(tab._measuredWidth); |
| 663 this._tabsElement.style.removeProperty('width'); |
| 664 |
| 665 return measuredWidths; |
| 666 } |
| 667 |
| 668 /** |
| 669 * @param {!Array.<number>} measuredWidths |
| 670 * @param {number} totalWidth |
| 671 */ |
| 672 _calculateMaxWidth(measuredWidths, totalWidth) { |
| 673 if (!measuredWidths.length) |
| 674 return 0; |
| 675 |
| 676 measuredWidths.sort(function(x, y) { |
| 677 return x - y; |
| 678 }); |
| 679 |
| 680 var totalMeasuredWidth = 0; |
| 681 for (var i = 0; i < measuredWidths.length; ++i) |
| 682 totalMeasuredWidth += measuredWidths[i]; |
| 683 |
| 684 if (totalWidth >= totalMeasuredWidth) |
| 685 return measuredWidths[measuredWidths.length - 1]; |
| 686 |
| 687 var totalExtraWidth = 0; |
| 688 for (var i = measuredWidths.length - 1; i > 0; --i) { |
| 689 var extraWidth = measuredWidths[i] - measuredWidths[i - 1]; |
| 690 totalExtraWidth += (measuredWidths.length - i) * extraWidth; |
| 691 |
| 692 if (totalWidth + totalExtraWidth >= totalMeasuredWidth) |
| 693 return measuredWidths[i - 1] + |
| 694 (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidth
s.length - i); |
| 695 } |
| 696 |
| 697 return totalWidth / measuredWidths.length; |
| 698 } |
| 699 |
| 700 /** |
| 701 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered |
| 702 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory |
| 703 * @param {number} totalWidth |
| 704 * @param {number} measuredDropDownButtonWidth |
| 705 * @return {!Array.<number>} |
| 706 */ |
| 707 _tabsToShowIndexes(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButto
nWidth) { |
| 708 var tabsToShowIndexes = []; |
| 709 |
| 710 var totalTabsWidth = 0; |
| 711 var tabCount = tabsOrdered.length; |
| 712 var tabsToLookAt = tabsOrdered.slice(0); |
| 713 if (this._currentTab !== undefined) |
| 714 tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._curren
tTab), 1)[0]); |
| 715 if (this._lastSelectedOverflowTab !== undefined) |
| 716 tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._lastSe
lectedOverflowTab), 1)[0]); |
| 717 for (var i = 0; i < tabCount; ++i) { |
| 718 var tab = this._automaticReorder ? tabsHistory[i] : tabsToLookAt[i]; |
| 719 totalTabsWidth += tab.width(); |
| 720 var minimalRequiredWidth = totalTabsWidth; |
| 721 if (i !== tabCount - 1) |
| 722 minimalRequiredWidth += measuredDropDownButtonWidth; |
| 723 if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth) |
| 724 break; |
| 725 tabsToShowIndexes.push(tabsOrdered.indexOf(tab)); |
| 726 } |
| 727 |
| 728 tabsToShowIndexes.sort(function(x, y) { |
| 729 return x - y; |
| 730 }); |
| 731 |
| 732 return tabsToShowIndexes; |
| 733 } |
| 734 |
| 735 _hideCurrentTab() { |
| 736 if (!this._currentTab) |
| 737 return; |
| 738 |
| 739 this._hideTab(this._currentTab); |
| 740 delete this._currentTab; |
| 741 } |
| 742 |
| 743 /** |
| 744 * @param {!WebInspector.TabbedPaneTab} tab |
| 745 */ |
| 746 _showTab(tab) { |
| 747 tab.tabElement.classList.add('selected'); |
| 748 tab.tabElement.setAttribute('aria-selected', 'true'); |
| 749 tab.view.showWidget(this.element); |
| 750 this._updateTabSlider(); |
| 751 } |
| 752 |
| 753 _updateTabSlider() { |
| 754 if (!this._currentTab || !this._sliderEnabled) |
| 755 return; |
| 756 var left = 0; |
| 757 for (var i = 0; i < this._tabs.length && this._currentTab !== this._tabs[i]
&& this._tabs[i]._shown; i++) |
| 758 left += this._tabs[i]._measuredWidth; |
| 759 var sliderWidth = this._currentTab._shown ? this._currentTab._measuredWidth
: this._dropDownButton.offsetWidth; |
| 760 var scaleFactor = window.devicePixelRatio >= 1.5 ? ' scaleY(0.75)' : ''; |
| 761 this._tabSlider.style.transform = 'translateX(' + left + 'px)' + scaleFactor
; |
| 762 this._tabSlider.style.width = sliderWidth + 'px'; |
| 763 |
| 764 if (this._tabSlider.parentElement !== this._headerContentsElement) |
| 765 this._headerContentsElement.appendChild(this._tabSlider); |
| 766 } |
| 767 |
| 768 /** |
| 769 * @param {!WebInspector.TabbedPaneTab} tab |
| 770 */ |
| 771 _hideTab(tab) { |
| 772 tab.tabElement.classList.remove('selected'); |
| 773 tab.tabElement.setAttribute('aria-selected', 'false'); |
| 774 tab.view.hideWidget(); |
| 775 } |
| 776 |
| 777 /** |
| 778 * @override |
| 779 * @return {!Array.<!Element>} |
| 780 */ |
| 781 elementsToRestoreScrollPositionsFor() { |
| 782 return [this._contentElement]; |
| 783 } |
| 784 |
| 785 /** |
| 786 * @param {!WebInspector.TabbedPaneTab} tab |
| 787 * @param {number} index |
| 788 */ |
| 789 _insertBefore(tab, index) { |
| 790 this._tabsElement.insertBefore(tab._tabElement || null, this._tabsElement.ch
ildNodes[index]); |
| 791 var oldIndex = this._tabs.indexOf(tab); |
| 792 this._tabs.splice(oldIndex, 1); |
| 793 if (oldIndex < index) |
| 794 --index; |
| 795 this._tabs.splice(index, 0, tab); |
| 796 this.dispatchEventToListeners(WebInspector.TabbedPane.Events.TabOrderChanged
, this._tabs); |
| 797 } |
| 798 |
| 799 /** |
| 800 * @return {!WebInspector.Toolbar} |
| 801 */ |
| 802 leftToolbar() { |
| 803 if (!this._leftToolbar) { |
| 804 this._leftToolbar = new WebInspector.Toolbar('tabbed-pane-left-toolbar'); |
| 805 this._headerElement.insertBefore(this._leftToolbar.element, this._headerEl
ement.firstChild); |
| 806 } |
| 807 return this._leftToolbar; |
| 808 } |
| 809 |
| 810 /** |
| 811 * @return {!WebInspector.Toolbar} |
| 812 */ |
| 813 rightToolbar() { |
| 814 if (!this._rightToolbar) { |
| 815 this._rightToolbar = new WebInspector.Toolbar('tabbed-pane-right-toolbar')
; |
| 816 this._headerElement.appendChild(this._rightToolbar.element); |
| 817 } |
| 818 return this._rightToolbar; |
| 819 } |
| 820 |
| 821 renderWithNoHeaderBackground() { |
| 822 this._headerElement.classList.add('tabbed-pane-no-header-background'); |
| 823 } |
| 824 |
| 825 /** |
| 826 * @param {boolean} allow |
| 827 * @param {boolean=} automatic |
| 828 */ |
| 829 setAllowTabReorder(allow, automatic) { |
| 830 this._allowTabReorder = allow; |
| 831 this._automaticReorder = automatic; |
| 832 } |
62 }; | 833 }; |
63 | 834 |
64 /** @enum {symbol} */ | 835 /** @enum {symbol} */ |
65 WebInspector.TabbedPane.Events = { | 836 WebInspector.TabbedPane.Events = { |
66 TabSelected: Symbol("TabSelected"), | 837 TabSelected: Symbol('TabSelected'), |
67 TabClosed: Symbol("TabClosed"), | 838 TabClosed: Symbol('TabClosed'), |
68 TabOrderChanged: Symbol("TabOrderChanged") | 839 TabOrderChanged: Symbol('TabOrderChanged') |
69 }; | 840 }; |
70 | 841 |
71 WebInspector.TabbedPane.prototype = { | |
72 /** | |
73 * @param {boolean} locked | |
74 */ | |
75 setCurrentTabLocked: function(locked) | |
76 { | |
77 this._currentTabLocked = locked; | |
78 this._headerElement.classList.toggle("locked", this._currentTabLocked); | |
79 }, | |
80 | |
81 /** | |
82 * @param {boolean} autoSelect | |
83 */ | |
84 setAutoSelectFirstItemOnShow: function(autoSelect) | |
85 { | |
86 this._autoSelectFirstItemOnShow = autoSelect; | |
87 }, | |
88 | |
89 /** | |
90 * @return {?WebInspector.Widget} | |
91 */ | |
92 get visibleView() | |
93 { | |
94 return this._currentTab ? this._currentTab.view : null; | |
95 }, | |
96 | |
97 /** | |
98 * @return {!Array.<string>} | |
99 */ | |
100 tabIds: function() | |
101 { | |
102 return this._tabs.map(tab => tab._id); | |
103 }, | |
104 | |
105 /** | |
106 * @param {string} tabId | |
107 * @return {number} | |
108 */ | |
109 tabIndex: function(tabId) | |
110 { | |
111 return this._tabs.findIndex(tab => tab.id === tabId); | |
112 }, | |
113 | |
114 /** | |
115 * @return {!Array.<!WebInspector.Widget>} | |
116 */ | |
117 tabViews: function() | |
118 { | |
119 return this._tabs.map(tab => tab.view); | |
120 }, | |
121 | |
122 /** | |
123 * @param {string} tabId | |
124 * @return {?WebInspector.Widget} | |
125 */ | |
126 tabView: function(tabId) | |
127 { | |
128 return this._tabsById[tabId] ? this._tabsById[tabId].view : null; | |
129 }, | |
130 | |
131 /** | |
132 * @return {?string} | |
133 */ | |
134 get selectedTabId() | |
135 { | |
136 return this._currentTab ? this._currentTab.id : null; | |
137 }, | |
138 | |
139 /** | |
140 * @param {boolean} shrinkableTabs | |
141 */ | |
142 setShrinkableTabs: function(shrinkableTabs) | |
143 { | |
144 this._shrinkableTabs = shrinkableTabs; | |
145 }, | |
146 | |
147 /** | |
148 * @param {boolean} verticalTabLayout | |
149 */ | |
150 setVerticalTabLayout: function(verticalTabLayout) | |
151 { | |
152 this._verticalTabLayout = verticalTabLayout; | |
153 this.contentElement.classList.add("vertical-tab-layout"); | |
154 this.invalidateConstraints(); | |
155 }, | |
156 | |
157 /** | |
158 * @param {boolean} closeableTabs | |
159 */ | |
160 setCloseableTabs: function(closeableTabs) | |
161 { | |
162 this._closeableTabs = closeableTabs; | |
163 }, | |
164 | |
165 /** | |
166 * @override | |
167 */ | |
168 focus: function() | |
169 { | |
170 if (this.visibleView) | |
171 this.visibleView.focus(); | |
172 else | |
173 this.contentElement.focus(); | |
174 }, | |
175 | |
176 /** | |
177 * @return {!Element} | |
178 */ | |
179 headerElement: function() | |
180 { | |
181 return this._headerElement; | |
182 }, | |
183 | |
184 /** | |
185 * @param {string} id | |
186 * @return {boolean} | |
187 */ | |
188 isTabCloseable: function(id) | |
189 { | |
190 var tab = this._tabsById[id]; | |
191 return tab ? tab.isCloseable() : false; | |
192 }, | |
193 | |
194 /** | |
195 * @param {!WebInspector.TabbedPaneTabDelegate} delegate | |
196 */ | |
197 setTabDelegate: function(delegate) | |
198 { | |
199 var tabs = this._tabs.slice(); | |
200 for (var i = 0; i < tabs.length; ++i) | |
201 tabs[i].setDelegate(delegate); | |
202 this._delegate = delegate; | |
203 }, | |
204 | |
205 /** | |
206 * @param {string} id | |
207 * @param {string} tabTitle | |
208 * @param {!WebInspector.Widget} view | |
209 * @param {string=} tabTooltip | |
210 * @param {boolean=} userGesture | |
211 * @param {boolean=} isCloseable | |
212 * @param {number=} index | |
213 */ | |
214 appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable
, index) | |
215 { | |
216 isCloseable = typeof isCloseable === "boolean" ? isCloseable : this._clo
seableTabs; | |
217 var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, isCloseable
, view, tabTooltip); | |
218 tab.setDelegate(this._delegate); | |
219 this._tabsById[id] = tab; | |
220 if (index !== undefined) | |
221 this._tabs.splice(index, 0, tab); | |
222 else | |
223 this._tabs.push(tab); | |
224 this._tabsHistory.push(tab); | |
225 view.attach(this); | |
226 if (this._tabsHistory[0] === tab && this.isShowing()) | |
227 this.selectTab(tab.id, userGesture); | |
228 this._updateTabElements(); | |
229 }, | |
230 | |
231 /** | |
232 * @param {string} id | |
233 * @param {boolean=} userGesture | |
234 */ | |
235 closeTab: function(id, userGesture) | |
236 { | |
237 this.closeTabs([id], userGesture); | |
238 }, | |
239 | |
240 /** | |
241 * @param {!Array.<string>} ids | |
242 * @param {boolean=} userGesture | |
243 */ | |
244 closeTabs: function(ids, userGesture) | |
245 { | |
246 var focused = this.hasFocus(); | |
247 for (var i = 0; i < ids.length; ++i) | |
248 this._innerCloseTab(ids[i], userGesture); | |
249 this._updateTabElements(); | |
250 if (this._tabsHistory.length) | |
251 this.selectTab(this._tabsHistory[0].id, false); | |
252 if (focused) | |
253 this.focus(); | |
254 }, | |
255 | |
256 /** | |
257 * @param {string} id | |
258 * @param {boolean=} userGesture | |
259 */ | |
260 _innerCloseTab: function(id, userGesture) | |
261 { | |
262 if (!this._tabsById[id]) | |
263 return; | |
264 if (userGesture && !this._tabsById[id]._closeable) | |
265 return; | |
266 if (this._currentTab && this._currentTab.id === id) | |
267 this._hideCurrentTab(); | |
268 | |
269 var tab = this._tabsById[id]; | |
270 delete this._tabsById[id]; | |
271 | |
272 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); | |
273 this._tabs.splice(this._tabs.indexOf(tab), 1); | |
274 if (tab._shown) | |
275 this._hideTabElement(tab); | |
276 tab.view.detach(); | |
277 | |
278 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture
}; | |
279 this.dispatchEventToListeners(WebInspector.TabbedPane.Events.TabClosed,
eventData); | |
280 return true; | |
281 }, | |
282 | |
283 /** | |
284 * @param {string} tabId | |
285 * @return {boolean} | |
286 */ | |
287 hasTab: function(tabId) | |
288 { | |
289 return !!this._tabsById[tabId]; | |
290 }, | |
291 | |
292 /** | |
293 * @return {!Array.<string>} | |
294 */ | |
295 allTabs: function() | |
296 { | |
297 return this._tabs.map(function(tab) { return tab.id; }); | |
298 }, | |
299 | |
300 /** | |
301 * @param {string} id | |
302 * @return {!Array.<string>} | |
303 */ | |
304 otherTabs: function(id) | |
305 { | |
306 var result = []; | |
307 for (var i = 0; i < this._tabs.length; ++i) { | |
308 if (this._tabs[i].id !== id) | |
309 result.push(this._tabs[i].id); | |
310 } | |
311 return result; | |
312 }, | |
313 | |
314 /** | |
315 * @param {string} id | |
316 * @return {!Array.<string>} | |
317 */ | |
318 _tabsToTheRight: function(id) | |
319 { | |
320 var index = -1; | |
321 for (var i = 0; i < this._tabs.length; ++i) { | |
322 if (this._tabs[i].id === id) { | |
323 index = i; | |
324 break; | |
325 } | |
326 } | |
327 if (index === -1) | |
328 return []; | |
329 return this._tabs.slice(index + 1).map(function(tab) { return tab.id; })
; | |
330 }, | |
331 | |
332 /** | |
333 * @param {string} id | |
334 * @param {boolean=} userGesture | |
335 * @return {boolean} | |
336 */ | |
337 selectTab: function(id, userGesture) | |
338 { | |
339 if (this._currentTabLocked) | |
340 return false; | |
341 var focused = this.hasFocus(); | |
342 var tab = this._tabsById[id]; | |
343 if (!tab) | |
344 return false; | |
345 if (this._currentTab && this._currentTab.id === id) | |
346 return true; | |
347 | |
348 this.suspendInvalidations(); | |
349 this._hideCurrentTab(); | |
350 this._showTab(tab); | |
351 this.resumeInvalidations(); | |
352 this._currentTab = tab; | |
353 | |
354 this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); | |
355 this._tabsHistory.splice(0, 0, tab); | |
356 | |
357 this._updateTabElements(); | |
358 if (focused) | |
359 this.focus(); | |
360 | |
361 var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture
}; | |
362 this.dispatchEventToListeners(WebInspector.TabbedPane.Events.TabSelected
, eventData); | |
363 return true; | |
364 }, | |
365 | |
366 /** | |
367 * @param {number} tabsCount | |
368 * @return {!Array.<string>} | |
369 */ | |
370 lastOpenedTabIds: function(tabsCount) | |
371 { | |
372 function tabToTabId(tab) { | |
373 return tab.id; | |
374 } | |
375 | |
376 return this._tabsHistory.slice(0, tabsCount).map(tabToTabId); | |
377 }, | |
378 | |
379 /** | |
380 * @param {string} id | |
381 * @param {string} iconType | |
382 * @param {string=} iconTooltip | |
383 */ | |
384 setTabIcon: function(id, iconType, iconTooltip) | |
385 { | |
386 var tab = this._tabsById[id]; | |
387 if (tab._setIconType(iconType, iconTooltip)) | |
388 this._updateTabElements(); | |
389 }, | |
390 | |
391 /** | |
392 * @param {string} id | |
393 * @param {boolean} enabled | |
394 */ | |
395 setTabEnabled: function(id, enabled) | |
396 { | |
397 var tab = this._tabsById[id]; | |
398 tab.tabElement.classList.toggle("disabled", !enabled); | |
399 }, | |
400 | |
401 /** | |
402 * @param {string} id | |
403 * @param {string} className | |
404 * @param {boolean=} force | |
405 */ | |
406 toggleTabClass: function(id, className, force) | |
407 { | |
408 var tab = this._tabsById[id]; | |
409 if (tab._toggleClass(className, force)) | |
410 this._updateTabElements(); | |
411 }, | |
412 | |
413 /** | |
414 * @param {!WebInspector.Event} event | |
415 */ | |
416 _zoomChanged: function(event) | |
417 { | |
418 for (var i = 0; i < this._tabs.length; ++i) | |
419 delete this._tabs[i]._measuredWidth; | |
420 if (this.isShowing()) | |
421 this._updateTabElements(); | |
422 }, | |
423 | |
424 /** | |
425 * @param {string} id | |
426 * @param {string} tabTitle | |
427 * @param {string=} tabTooltip | |
428 */ | |
429 changeTabTitle: function(id, tabTitle, tabTooltip) | |
430 { | |
431 var tab = this._tabsById[id]; | |
432 if (tabTooltip !== undefined) | |
433 tab.tooltip = tabTooltip; | |
434 if (tab.title !== tabTitle) { | |
435 tab.title = tabTitle; | |
436 this._updateTabElements(); | |
437 } | |
438 }, | |
439 | |
440 /** | |
441 * @param {string} id | |
442 * @param {!WebInspector.Widget} view | |
443 */ | |
444 changeTabView: function(id, view) | |
445 { | |
446 var tab = this._tabsById[id]; | |
447 if (tab.view === view) | |
448 return; | |
449 | |
450 var shouldFocus = tab.view.hasFocus(); | |
451 | |
452 this.suspendInvalidations(); | |
453 | |
454 var isSelected = this._currentTab && this._currentTab.id === id; | |
455 if (isSelected) | |
456 this._hideTab(tab); | |
457 tab.view.detach(); | |
458 tab.view = view; | |
459 tab.view.attach(this); | |
460 if (isSelected) | |
461 this._showTab(tab); | |
462 if (shouldFocus) | |
463 tab.view.focus(); | |
464 | |
465 this.resumeInvalidations(); | |
466 }, | |
467 | |
468 onResize: function() | |
469 { | |
470 this._updateTabElements(); | |
471 }, | |
472 | |
473 headerResized: function() | |
474 { | |
475 this._updateTabElements(); | |
476 }, | |
477 | |
478 wasShown: function() | |
479 { | |
480 var effectiveTab = this._currentTab || this._tabsHistory[0]; | |
481 if (effectiveTab && this._autoSelectFirstItemOnShow) | |
482 this.selectTab(effectiveTab.id); | |
483 }, | |
484 | |
485 /** | |
486 * @param {boolean} enable | |
487 */ | |
488 setTabSlider: function(enable) | |
489 { | |
490 this._sliderEnabled = enable; | |
491 this._tabSlider.classList.toggle("enabled", enable); | |
492 }, | |
493 | |
494 /** | |
495 * @override | |
496 * @return {!Constraints} | |
497 */ | |
498 calculateConstraints: function() | |
499 { | |
500 var constraints = WebInspector.VBox.prototype.calculateConstraints.call(
this); | |
501 var minContentConstraints = new Constraints(new Size(0, 0), new Size(50,
50)); | |
502 constraints = constraints.widthToMax(minContentConstraints).heightToMax(
minContentConstraints); | |
503 if (this._verticalTabLayout) | |
504 constraints = constraints.addWidth(new Constraints(new Size(120, 0))
); | |
505 else | |
506 constraints = constraints.addHeight(new Constraints(new Size(0, 30))
); | |
507 return constraints; | |
508 }, | |
509 | |
510 _updateTabElements: function() | |
511 { | |
512 WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElemen
ts); | |
513 }, | |
514 | |
515 /** | |
516 * @param {string} text | |
517 */ | |
518 setPlaceholderText: function(text) | |
519 { | |
520 this._noTabsMessage = text; | |
521 }, | |
522 | |
523 _innerUpdateTabElements: function() | |
524 { | |
525 if (!this.isShowing()) | |
526 return; | |
527 | |
528 if (!this._tabs.length) { | |
529 this._contentElement.classList.add("has-no-tabs"); | |
530 if (this._noTabsMessage && !this._noTabsMessageElement) { | |
531 this._noTabsMessageElement = this._contentElement.createChild("d
iv", "tabbed-pane-placeholder fill"); | |
532 this._noTabsMessageElement.textContent = this._noTabsMessage; | |
533 } | |
534 } else { | |
535 this._contentElement.classList.remove("has-no-tabs"); | |
536 if (this._noTabsMessageElement) { | |
537 this._noTabsMessageElement.remove(); | |
538 delete this._noTabsMessageElement; | |
539 } | |
540 } | |
541 | |
542 this._measureDropDownButton(); | |
543 this._updateWidths(); | |
544 this._updateTabsDropDown(); | |
545 this._updateTabSlider(); | |
546 }, | |
547 | |
548 /** | |
549 * @param {number} index | |
550 * @param {!WebInspector.TabbedPaneTab} tab | |
551 */ | |
552 _showTabElement: function(index, tab) | |
553 { | |
554 if (index >= this._tabsElement.children.length) | |
555 this._tabsElement.appendChild(tab.tabElement); | |
556 else | |
557 this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.chi
ldren[index]); | |
558 tab._shown = true; | |
559 }, | |
560 | |
561 /** | |
562 * @param {!WebInspector.TabbedPaneTab} tab | |
563 */ | |
564 _hideTabElement: function(tab) | |
565 { | |
566 this._tabsElement.removeChild(tab.tabElement); | |
567 tab._shown = false; | |
568 }, | |
569 | |
570 _createDropDownButton: function() | |
571 { | |
572 var dropDownContainer = createElementWithClass("div", "tabbed-pane-heade
r-tabs-drop-down-container"); | |
573 dropDownContainer.createChild("div", "glyph"); | |
574 this._dropDownMenu = new WebInspector.DropDownMenu(dropDownContainer); | |
575 this._dropDownMenu.addEventListener(WebInspector.DropDownMenu.Events.Ite
mSelected, this._dropDownMenuItemSelected, this); | |
576 | |
577 return dropDownContainer; | |
578 }, | |
579 | |
580 /** | |
581 * @param {!WebInspector.Event} event | |
582 */ | |
583 _dropDownMenuItemSelected: function(event) | |
584 { | |
585 var tabId = /** @type {string} */ (event.data); | |
586 this._lastSelectedOverflowTab = this._tabsById[tabId]; | |
587 this.selectTab(tabId, true); | |
588 }, | |
589 | |
590 _totalWidth: function() | |
591 { | |
592 return this._headerContentsElement.getBoundingClientRect().width; | |
593 }, | |
594 | |
595 /** | |
596 * @return {number} | |
597 */ | |
598 _numberOfTabsShown: function() | |
599 { | |
600 var numTabsShown = 0; | |
601 for (var tab of this._tabs) { | |
602 if (tab._shown) | |
603 numTabsShown++; | |
604 } | |
605 return numTabsShown; | |
606 }, | |
607 | |
608 disableOverflowMenu: function() | |
609 { | |
610 this._overflowDisabled = true; | |
611 }, | |
612 | |
613 _updateTabsDropDown: function() | |
614 { | |
615 var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHi
story, this._totalWidth(), this._measuredDropDownButtonWidth || 0); | |
616 if (this._lastSelectedOverflowTab && this._numberOfTabsShown() !== tabsT
oShowIndexes.length) { | |
617 delete this._lastSelectedOverflowTab; | |
618 this._updateTabsDropDown(); | |
619 return; | |
620 } | |
621 | |
622 for (var i = 0; i < this._tabs.length; ++i) { | |
623 if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1) | |
624 this._hideTabElement(this._tabs[i]); | |
625 } | |
626 for (var i = 0; i < tabsToShowIndexes.length; ++i) { | |
627 var tab = this._tabs[tabsToShowIndexes[i]]; | |
628 if (!tab._shown) | |
629 this._showTabElement(i, tab); | |
630 } | |
631 | |
632 if (!this._overflowDisabled) | |
633 this._populateDropDownFromIndex(); | |
634 }, | |
635 | |
636 _populateDropDownFromIndex: function() | |
637 { | |
638 if (this._dropDownButton.parentElement) | |
639 this._headerContentsElement.removeChild(this._dropDownButton); | |
640 | |
641 this._dropDownMenu.clear(); | |
642 | |
643 var tabsToShow = []; | |
644 for (var i = 0; i < this._tabs.length; ++i) { | |
645 if (!this._tabs[i]._shown) | |
646 tabsToShow.push(this._tabs[i]); | |
647 } | |
648 | |
649 var selectedId = null; | |
650 for (var i = 0; i < tabsToShow.length; ++i) { | |
651 var tab = tabsToShow[i]; | |
652 this._dropDownMenu.addItem(tab.id, tab.title); | |
653 if (this._tabsHistory[0] === tab) | |
654 selectedId = tab.id; | |
655 } | |
656 if (tabsToShow.length) { | |
657 this._headerContentsElement.appendChild(this._dropDownButton); | |
658 this._dropDownMenu.selectItem(selectedId); | |
659 } | |
660 }, | |
661 | |
662 _measureDropDownButton: function() | |
663 { | |
664 if (this._overflowDisabled || this._measuredDropDownButtonWidth) | |
665 return; | |
666 this._dropDownButton.classList.add("measuring"); | |
667 this._headerContentsElement.appendChild(this._dropDownButton); | |
668 this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClie
ntRect().width; | |
669 this._headerContentsElement.removeChild(this._dropDownButton); | |
670 this._dropDownButton.classList.remove("measuring"); | |
671 }, | |
672 | |
673 _updateWidths: function() | |
674 { | |
675 var measuredWidths = this._measureWidths(); | |
676 var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWi
dths.slice(), this._totalWidth()) : Number.MAX_VALUE; | |
677 | |
678 var i = 0; | |
679 for (var tab of this._tabs) | |
680 tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measu
redWidths[i++])); | |
681 }, | |
682 | |
683 _measureWidths: function() | |
684 { | |
685 // Add all elements to measure into this._tabsElement | |
686 this._tabsElement.style.setProperty("width", "2000px"); | |
687 var measuringTabElements = []; | |
688 for (var tab of this._tabs) { | |
689 if (typeof tab._measuredWidth === "number") | |
690 continue; | |
691 var measuringTabElement = tab._createTabElement(true); | |
692 measuringTabElement.__tab = tab; | |
693 measuringTabElements.push(measuringTabElement); | |
694 this._tabsElement.appendChild(measuringTabElement); | |
695 } | |
696 | |
697 // Perform measurement | |
698 for (var i = 0; i < measuringTabElements.length; ++i) { | |
699 var width = measuringTabElements[i].getBoundingClientRect().width; | |
700 measuringTabElements[i].__tab._measuredWidth = Math.ceil(width); | |
701 } | |
702 | |
703 // Nuke elements from the UI | |
704 for (var i = 0; i < measuringTabElements.length; ++i) | |
705 measuringTabElements[i].remove(); | |
706 | |
707 // Combine the results. | |
708 var measuredWidths = []; | |
709 for (var tab of this._tabs) | |
710 measuredWidths.push(tab._measuredWidth); | |
711 this._tabsElement.style.removeProperty("width"); | |
712 | |
713 return measuredWidths; | |
714 }, | |
715 | |
716 /** | |
717 * @param {!Array.<number>} measuredWidths | |
718 * @param {number} totalWidth | |
719 */ | |
720 _calculateMaxWidth: function(measuredWidths, totalWidth) | |
721 { | |
722 if (!measuredWidths.length) | |
723 return 0; | |
724 | |
725 measuredWidths.sort(function(x, y) { return x - y; }); | |
726 | |
727 var totalMeasuredWidth = 0; | |
728 for (var i = 0; i < measuredWidths.length; ++i) | |
729 totalMeasuredWidth += measuredWidths[i]; | |
730 | |
731 if (totalWidth >= totalMeasuredWidth) | |
732 return measuredWidths[measuredWidths.length - 1]; | |
733 | |
734 var totalExtraWidth = 0; | |
735 for (var i = measuredWidths.length - 1; i > 0; --i) { | |
736 var extraWidth = measuredWidths[i] - measuredWidths[i - 1]; | |
737 totalExtraWidth += (measuredWidths.length - i) * extraWidth; | |
738 | |
739 if (totalWidth + totalExtraWidth >= totalMeasuredWidth) | |
740 return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - t
otalMeasuredWidth) / (measuredWidths.length - i); | |
741 } | |
742 | |
743 return totalWidth / measuredWidths.length; | |
744 }, | |
745 | |
746 /** | |
747 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered | |
748 * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory | |
749 * @param {number} totalWidth | |
750 * @param {number} measuredDropDownButtonWidth | |
751 * @return {!Array.<number>} | |
752 */ | |
753 _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredD
ropDownButtonWidth) | |
754 { | |
755 var tabsToShowIndexes = []; | |
756 | |
757 var totalTabsWidth = 0; | |
758 var tabCount = tabsOrdered.length; | |
759 var tabsToLookAt = tabsOrdered.slice(0); | |
760 if (this._currentTab !== undefined) | |
761 tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._
currentTab), 1)[0]); | |
762 if (this._lastSelectedOverflowTab !== undefined) | |
763 tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this._
lastSelectedOverflowTab), 1)[0]); | |
764 for (var i = 0; i < tabCount; ++i) { | |
765 var tab = this._automaticReorder ? tabsHistory[i] : tabsToLookAt[i]; | |
766 totalTabsWidth += tab.width(); | |
767 var minimalRequiredWidth = totalTabsWidth; | |
768 if (i !== tabCount - 1) | |
769 minimalRequiredWidth += measuredDropDownButtonWidth; | |
770 if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth) | |
771 break; | |
772 tabsToShowIndexes.push(tabsOrdered.indexOf(tab)); | |
773 } | |
774 | |
775 tabsToShowIndexes.sort(function(x, y) { return x - y; }); | |
776 | |
777 return tabsToShowIndexes; | |
778 }, | |
779 | |
780 _hideCurrentTab: function() | |
781 { | |
782 if (!this._currentTab) | |
783 return; | |
784 | |
785 this._hideTab(this._currentTab); | |
786 delete this._currentTab; | |
787 }, | |
788 | |
789 /** | |
790 * @param {!WebInspector.TabbedPaneTab} tab | |
791 */ | |
792 _showTab: function(tab) | |
793 { | |
794 tab.tabElement.classList.add("selected"); | |
795 tab.tabElement.setAttribute("aria-selected", "true"); | |
796 tab.view.showWidget(this.element); | |
797 this._updateTabSlider(); | |
798 }, | |
799 | |
800 _updateTabSlider: function() | |
801 { | |
802 if (!this._currentTab || !this._sliderEnabled) | |
803 return; | |
804 var left = 0; | |
805 for (var i = 0; i < this._tabs.length && this._currentTab !== this._tabs
[i] && this._tabs[i]._shown; i++) | |
806 left += this._tabs[i]._measuredWidth; | |
807 var sliderWidth = this._currentTab._shown ? this._currentTab._measuredWi
dth : this._dropDownButton.offsetWidth; | |
808 var scaleFactor = window.devicePixelRatio >= 1.5 ? " scaleY(0.75)" : ""; | |
809 this._tabSlider.style.transform = "translateX(" + left + "px)" + scaleFa
ctor; | |
810 this._tabSlider.style.width = sliderWidth + "px"; | |
811 | |
812 if (this._tabSlider.parentElement !== this._headerContentsElement) | |
813 this._headerContentsElement.appendChild(this._tabSlider); | |
814 }, | |
815 | |
816 /** | |
817 * @param {!WebInspector.TabbedPaneTab} tab | |
818 */ | |
819 _hideTab: function(tab) | |
820 { | |
821 tab.tabElement.classList.remove("selected"); | |
822 tab.tabElement.setAttribute("aria-selected", "false"); | |
823 tab.view.hideWidget(); | |
824 }, | |
825 | |
826 /** | |
827 * @override | |
828 * @return {!Array.<!Element>} | |
829 */ | |
830 elementsToRestoreScrollPositionsFor: function() | |
831 { | |
832 return [ this._contentElement ]; | |
833 }, | |
834 | |
835 /** | |
836 * @param {!WebInspector.TabbedPaneTab} tab | |
837 * @param {number} index | |
838 */ | |
839 _insertBefore: function(tab, index) | |
840 { | |
841 this._tabsElement.insertBefore(tab._tabElement || null, this._tabsElemen
t.childNodes[index]); | |
842 var oldIndex = this._tabs.indexOf(tab); | |
843 this._tabs.splice(oldIndex, 1); | |
844 if (oldIndex < index) | |
845 --index; | |
846 this._tabs.splice(index, 0, tab); | |
847 this.dispatchEventToListeners(WebInspector.TabbedPane.Events.TabOrderCha
nged, this._tabs); | |
848 }, | |
849 | |
850 /** | |
851 * @return {!WebInspector.Toolbar} | |
852 */ | |
853 leftToolbar: function() | |
854 { | |
855 if (!this._leftToolbar) { | |
856 this._leftToolbar = new WebInspector.Toolbar("tabbed-pane-left-toolb
ar"); | |
857 this._headerElement.insertBefore(this._leftToolbar.element, this._he
aderElement.firstChild); | |
858 } | |
859 return this._leftToolbar; | |
860 }, | |
861 | |
862 /** | |
863 * @return {!WebInspector.Toolbar} | |
864 */ | |
865 rightToolbar: function() | |
866 { | |
867 if (!this._rightToolbar) { | |
868 this._rightToolbar = new WebInspector.Toolbar("tabbed-pane-right-too
lbar"); | |
869 this._headerElement.appendChild(this._rightToolbar.element); | |
870 } | |
871 return this._rightToolbar; | |
872 }, | |
873 | |
874 renderWithNoHeaderBackground: function() | |
875 { | |
876 this._headerElement.classList.add("tabbed-pane-no-header-background"); | |
877 }, | |
878 | |
879 /** | |
880 * @param {boolean} allow | |
881 * @param {boolean=} automatic | |
882 */ | |
883 setAllowTabReorder: function(allow, automatic) | |
884 { | |
885 this._allowTabReorder = allow; | |
886 this._automaticReorder = automatic; | |
887 }, | |
888 | |
889 __proto__: WebInspector.VBox.prototype | |
890 }; | |
891 | |
892 /** | 842 /** |
893 * @constructor | 843 * @unrestricted |
894 * @param {!WebInspector.TabbedPane} tabbedPane | |
895 * @param {string} id | |
896 * @param {string} title | |
897 * @param {boolean} closeable | |
898 * @param {!WebInspector.Widget} view | |
899 * @param {string=} tooltip | |
900 */ | 844 */ |
901 WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, to
oltip) | 845 WebInspector.TabbedPaneTab = class { |
902 { | 846 /** |
| 847 * @param {!WebInspector.TabbedPane} tabbedPane |
| 848 * @param {string} id |
| 849 * @param {string} title |
| 850 * @param {boolean} closeable |
| 851 * @param {!WebInspector.Widget} view |
| 852 * @param {string=} tooltip |
| 853 */ |
| 854 constructor(tabbedPane, id, title, closeable, view, tooltip) { |
903 this._closeable = closeable; | 855 this._closeable = closeable; |
904 this._tabbedPane = tabbedPane; | 856 this._tabbedPane = tabbedPane; |
905 this._id = id; | 857 this._id = id; |
906 this._title = title; | 858 this._title = title; |
907 this._tooltip = tooltip; | 859 this._tooltip = tooltip; |
908 this._view = view; | 860 this._view = view; |
909 this._shown = false; | 861 this._shown = false; |
910 /** @type {number} */ this._measuredWidth; | 862 /** @type {number} */ this._measuredWidth; |
911 /** @type {!Element|undefined} */ this._tabElement; | 863 /** @type {!Element|undefined} */ this._tabElement; |
912 }; | 864 } |
913 | 865 |
914 WebInspector.TabbedPaneTab.prototype = { | 866 /** |
| 867 * @return {string} |
| 868 */ |
| 869 get id() { |
| 870 return this._id; |
| 871 } |
| 872 |
| 873 /** |
| 874 * @return {string} |
| 875 */ |
| 876 get title() { |
| 877 return this._title; |
| 878 } |
| 879 |
| 880 /** |
| 881 * @param {string} title |
| 882 */ |
| 883 set title(title) { |
| 884 if (title === this._title) |
| 885 return; |
| 886 this._title = title; |
| 887 if (this._titleElement) |
| 888 this._titleElement.textContent = title; |
| 889 delete this._measuredWidth; |
| 890 } |
| 891 |
| 892 /** |
| 893 * @return {boolean} |
| 894 */ |
| 895 isCloseable() { |
| 896 return this._closeable; |
| 897 } |
| 898 |
| 899 /** |
| 900 * @param {string} iconType |
| 901 * @param {string=} iconTooltip |
| 902 * @return {boolean} |
| 903 */ |
| 904 _setIconType(iconType, iconTooltip) { |
| 905 if (iconType === this._iconType && iconTooltip === this._iconTooltip) |
| 906 return false; |
| 907 this._iconType = iconType; |
| 908 this._iconTooltip = iconTooltip; |
| 909 if (this._tabElement) |
| 910 this._createIconElement(this._tabElement, this._titleElement); |
| 911 delete this._measuredWidth; |
| 912 return true; |
| 913 } |
| 914 |
| 915 /** |
| 916 * @param {string} className |
| 917 * @param {boolean=} force |
| 918 * @return {boolean} |
| 919 */ |
| 920 _toggleClass(className, force) { |
| 921 var element = this.tabElement; |
| 922 var hasClass = element.classList.contains(className); |
| 923 if (hasClass === force) |
| 924 return false; |
| 925 element.classList.toggle(className, force); |
| 926 delete this._measuredWidth; |
| 927 return true; |
| 928 } |
| 929 |
| 930 /** |
| 931 * @return {!WebInspector.Widget} |
| 932 */ |
| 933 get view() { |
| 934 return this._view; |
| 935 } |
| 936 |
| 937 /** |
| 938 * @param {!WebInspector.Widget} view |
| 939 */ |
| 940 set view(view) { |
| 941 this._view = view; |
| 942 } |
| 943 |
| 944 /** |
| 945 * @return {string|undefined} |
| 946 */ |
| 947 get tooltip() { |
| 948 return this._tooltip; |
| 949 } |
| 950 |
| 951 /** |
| 952 * @param {string|undefined} tooltip |
| 953 */ |
| 954 set tooltip(tooltip) { |
| 955 this._tooltip = tooltip; |
| 956 if (this._titleElement) |
| 957 this._titleElement.title = tooltip || ''; |
| 958 } |
| 959 |
| 960 /** |
| 961 * @return {!Element} |
| 962 */ |
| 963 get tabElement() { |
| 964 if (!this._tabElement) |
| 965 this._tabElement = this._createTabElement(false); |
| 966 |
| 967 return this._tabElement; |
| 968 } |
| 969 |
| 970 /** |
| 971 * @return {number} |
| 972 */ |
| 973 width() { |
| 974 return this._width; |
| 975 } |
| 976 |
| 977 /** |
| 978 * @param {number} width |
| 979 */ |
| 980 setWidth(width) { |
| 981 this.tabElement.style.width = width === -1 ? '' : (width + 'px'); |
| 982 this._width = width; |
| 983 } |
| 984 |
| 985 /** |
| 986 * @param {!WebInspector.TabbedPaneTabDelegate} delegate |
| 987 */ |
| 988 setDelegate(delegate) { |
| 989 this._delegate = delegate; |
| 990 } |
| 991 |
| 992 /** |
| 993 * @param {!Element} tabElement |
| 994 * @param {!Element} titleElement |
| 995 */ |
| 996 _createIconElement(tabElement, titleElement) { |
| 997 if (tabElement.__iconElement) |
| 998 tabElement.__iconElement.remove(); |
| 999 if (!this._iconType) |
| 1000 return; |
| 1001 |
| 1002 var iconElement = createElementWithClass('label', 'tabbed-pane-header-tab-ic
on', 'dt-icon-label'); |
| 1003 iconElement.type = this._iconType; |
| 1004 if (this._iconTooltip) |
| 1005 iconElement.title = this._iconTooltip; |
| 1006 tabElement.insertBefore(iconElement, titleElement); |
| 1007 tabElement.__iconElement = iconElement; |
| 1008 } |
| 1009 |
| 1010 /** |
| 1011 * @param {boolean} measuring |
| 1012 * @return {!Element} |
| 1013 */ |
| 1014 _createTabElement(measuring) { |
| 1015 var tabElement = createElementWithClass('div', 'tabbed-pane-header-tab'); |
| 1016 tabElement.id = 'tab-' + this._id; |
| 1017 tabElement.tabIndex = -1; |
| 1018 tabElement.setAttribute('role', 'tab'); |
| 1019 tabElement.setAttribute('aria-selected', 'false'); |
| 1020 tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPa
ne, this.id, true); |
| 1021 |
| 1022 var titleElement = tabElement.createChild('span', 'tabbed-pane-header-tab-ti
tle'); |
| 1023 titleElement.textContent = this.title; |
| 1024 titleElement.title = this.tooltip || ''; |
| 1025 this._createIconElement(tabElement, titleElement); |
| 1026 if (!measuring) |
| 1027 this._titleElement = titleElement; |
| 1028 |
| 1029 if (this._closeable) |
| 1030 tabElement.createChild('div', 'tabbed-pane-close-button', 'dt-close-button
').gray = true; |
| 1031 |
| 1032 if (measuring) { |
| 1033 tabElement.classList.add('measuring'); |
| 1034 } else { |
| 1035 tabElement.addEventListener('click', this._tabClicked.bind(this), false); |
| 1036 tabElement.addEventListener('auxclick', this._tabClicked.bind(this), false
); |
| 1037 tabElement.addEventListener('mousedown', this._tabMouseDown.bind(this), fa
lse); |
| 1038 tabElement.addEventListener('mouseup', this._tabMouseUp.bind(this), false)
; |
| 1039 |
| 1040 tabElement.addEventListener('contextmenu', this._tabContextMenu.bind(this)
, false); |
| 1041 if (this._tabbedPane._allowTabReorder) |
| 1042 WebInspector.installDragHandle( |
| 1043 tabElement, this._startTabDragging.bind(this), this._tabDragging.bin
d(this), |
| 1044 this._endTabDragging.bind(this), '-webkit-grabbing', 'pointer', 200)
; |
| 1045 } |
| 1046 |
| 1047 return tabElement; |
| 1048 } |
| 1049 |
| 1050 /** |
| 1051 * @param {!Event} event |
| 1052 */ |
| 1053 _tabClicked(event) { |
| 1054 var middleButton = event.button === 1; |
| 1055 var shouldClose = this._closeable && (middleButton || event.target.classList
.contains('tabbed-pane-close-button')); |
| 1056 if (!shouldClose) { |
| 1057 this._tabbedPane.focus(); |
| 1058 return; |
| 1059 } |
| 1060 this._closeTabs([this.id]); |
| 1061 event.consume(true); |
| 1062 } |
| 1063 |
| 1064 /** |
| 1065 * @param {!Event} event |
| 1066 */ |
| 1067 _tabMouseDown(event) { |
| 1068 if (event.target.classList.contains('tabbed-pane-close-button') || event.but
ton === 1) |
| 1069 return; |
| 1070 this._tabbedPane.selectTab(this.id, true); |
| 1071 } |
| 1072 |
| 1073 /** |
| 1074 * @param {!Event} event |
| 1075 */ |
| 1076 _tabMouseUp(event) { |
| 1077 // This is needed to prevent middle-click pasting on linux when tabs are cli
cked. |
| 1078 if (event.button === 1) |
| 1079 event.consume(true); |
| 1080 } |
| 1081 |
| 1082 /** |
| 1083 * @param {!Array.<string>} ids |
| 1084 */ |
| 1085 _closeTabs(ids) { |
| 1086 if (this._delegate) { |
| 1087 this._delegate.closeTabs(this._tabbedPane, ids); |
| 1088 return; |
| 1089 } |
| 1090 this._tabbedPane.closeTabs(ids, true); |
| 1091 } |
| 1092 |
| 1093 _tabContextMenu(event) { |
915 /** | 1094 /** |
916 * @return {string} | 1095 * @this {WebInspector.TabbedPaneTab} |
917 */ | 1096 */ |
918 get id() | 1097 function close() { |
919 { | 1098 this._closeTabs([this.id]); |
920 return this._id; | 1099 } |
921 }, | |
922 | 1100 |
923 /** | 1101 /** |
924 * @return {string} | 1102 * @this {WebInspector.TabbedPaneTab} |
925 */ | 1103 */ |
926 get title() | 1104 function closeOthers() { |
927 { | 1105 this._closeTabs(this._tabbedPane.otherTabs(this.id)); |
928 return this._title; | 1106 } |
929 }, | |
930 | |
931 set title(title) | |
932 { | |
933 if (title === this._title) | |
934 return; | |
935 this._title = title; | |
936 if (this._titleElement) | |
937 this._titleElement.textContent = title; | |
938 delete this._measuredWidth; | |
939 }, | |
940 | 1107 |
941 /** | 1108 /** |
942 * @return {boolean} | 1109 * @this {WebInspector.TabbedPaneTab} |
943 */ | 1110 */ |
944 isCloseable: function() | 1111 function closeAll() { |
945 { | 1112 this._closeTabs(this._tabbedPane.allTabs()); |
946 return this._closeable; | 1113 } |
947 }, | |
948 | 1114 |
949 /** | 1115 /** |
950 * @param {string} iconType | 1116 * @this {WebInspector.TabbedPaneTab} |
951 * @param {string=} iconTooltip | |
952 * @return {boolean} | |
953 */ | 1117 */ |
954 _setIconType: function(iconType, iconTooltip) | 1118 function closeToTheRight() { |
955 { | 1119 this._closeTabs(this._tabbedPane._tabsToTheRight(this.id)); |
956 if (iconType === this._iconType && iconTooltip === this._iconTooltip) | 1120 } |
957 return false; | 1121 |
958 this._iconType = iconType; | 1122 var contextMenu = new WebInspector.ContextMenu(event); |
959 this._iconTooltip = iconTooltip; | 1123 if (this._closeable) { |
960 if (this._tabElement) | 1124 contextMenu.appendItem(WebInspector.UIString.capitalize('Close'), close.bi
nd(this)); |
961 this._createIconElement(this._tabElement, this._titleElement); | 1125 contextMenu.appendItem(WebInspector.UIString.capitalize('Close ^others'),
closeOthers.bind(this)); |
962 delete this._measuredWidth; | 1126 contextMenu.appendItem(WebInspector.UIString.capitalize('Close ^tabs to th
e ^right'), closeToTheRight.bind(this)); |
963 return true; | 1127 contextMenu.appendItem(WebInspector.UIString.capitalize('Close ^all'), clo
seAll.bind(this)); |
964 }, | 1128 } |
965 | 1129 if (this._delegate) |
966 /** | 1130 this._delegate.onContextMenu(this.id, contextMenu); |
967 * @param {string} className | 1131 contextMenu.show(); |
968 * @param {boolean=} force | 1132 } |
969 * @return {boolean} | 1133 |
970 */ | 1134 /** |
971 _toggleClass: function(className, force) | 1135 * @param {!Event} event |
972 { | 1136 * @return {boolean} |
973 var element = this.tabElement; | 1137 */ |
974 var hasClass = element.classList.contains(className); | 1138 _startTabDragging(event) { |
975 if (hasClass === force) | 1139 if (event.target.classList.contains('tabbed-pane-close-button')) |
976 return false; | 1140 return false; |
977 element.classList.toggle(className, force); | 1141 this._dragStartX = event.pageX; |
978 delete this._measuredWidth; | 1142 this._tabElement.classList.add('dragging'); |
979 return true; | 1143 this._tabbedPane._tabSlider.remove(); |
980 }, | 1144 return true; |
981 | 1145 } |
982 /** | 1146 |
983 * @return {!WebInspector.Widget} | 1147 /** |
984 */ | 1148 * @param {!Event} event |
985 get view() | 1149 */ |
986 { | 1150 _tabDragging(event) { |
987 return this._view; | 1151 var tabElements = this._tabbedPane._tabsElement.childNodes; |
988 }, | 1152 for (var i = 0; i < tabElements.length; ++i) { |
989 | 1153 var tabElement = tabElements[i]; |
990 set view(view) | 1154 if (tabElement === this._tabElement) |
991 { | 1155 continue; |
992 this._view = view; | 1156 |
993 }, | 1157 var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._ta
bElement.offsetLeft && |
994 | 1158 this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElemen
t.offsetLeft; |
995 /** | 1159 if (!intersects) |
996 * @return {string|undefined} | 1160 continue; |
997 */ | 1161 |
998 get tooltip() | 1162 if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2
+ 5) |
999 { | 1163 break; |
1000 return this._tooltip; | 1164 |
1001 }, | 1165 if (event.pageX - this._dragStartX > 0) { |
1002 | 1166 tabElement = tabElement.nextSibling; |
1003 set tooltip(tooltip) | 1167 ++i; |
1004 { | 1168 } |
1005 this._tooltip = tooltip; | 1169 |
1006 if (this._titleElement) | 1170 var oldOffsetLeft = this._tabElement.offsetLeft; |
1007 this._titleElement.title = tooltip || ""; | 1171 this._tabbedPane._insertBefore(this, i); |
1008 }, | 1172 this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft; |
1009 | 1173 break; |
1010 /** | 1174 } |
1011 * @return {!Element} | 1175 |
1012 */ | 1176 if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0)
{ |
1013 get tabElement() | 1177 this._tabElement.style.setProperty('left', '0px'); |
1014 { | 1178 return; |
1015 if (!this._tabElement) | 1179 } |
1016 this._tabElement = this._createTabElement(false); | 1180 if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) { |
1017 | 1181 this._tabElement.style.setProperty('left', '0px'); |
1018 return this._tabElement; | 1182 return; |
1019 }, | 1183 } |
1020 | 1184 |
1021 /** | 1185 this._tabElement.style.setProperty('left', (event.pageX - this._dragStartX)
+ 'px'); |
1022 * @return {number} | 1186 } |
1023 */ | 1187 |
1024 width: function() | 1188 /** |
1025 { | 1189 * @param {!Event} event |
1026 return this._width; | 1190 */ |
1027 }, | 1191 _endTabDragging(event) { |
1028 | 1192 this._tabElement.classList.remove('dragging'); |
1029 /** | 1193 this._tabElement.style.removeProperty('left'); |
1030 * @param {number} width | 1194 delete this._dragStartX; |
1031 */ | 1195 this._tabbedPane._updateTabSlider(); |
1032 setWidth: function(width) | 1196 } |
1033 { | |
1034 this.tabElement.style.width = width === -1 ? "" : (width + "px"); | |
1035 this._width = width; | |
1036 }, | |
1037 | |
1038 /** | |
1039 * @param {!WebInspector.TabbedPaneTabDelegate} delegate | |
1040 */ | |
1041 setDelegate: function(delegate) | |
1042 { | |
1043 this._delegate = delegate; | |
1044 }, | |
1045 | |
1046 /** | |
1047 * @param {!Element} tabElement | |
1048 * @param {!Element} titleElement | |
1049 */ | |
1050 _createIconElement: function(tabElement, titleElement) | |
1051 { | |
1052 if (tabElement.__iconElement) | |
1053 tabElement.__iconElement.remove(); | |
1054 if (!this._iconType) | |
1055 return; | |
1056 | |
1057 var iconElement = createElementWithClass("label", "tabbed-pane-header-ta
b-icon", "dt-icon-label"); | |
1058 iconElement.type = this._iconType; | |
1059 if (this._iconTooltip) | |
1060 iconElement.title = this._iconTooltip; | |
1061 tabElement.insertBefore(iconElement, titleElement); | |
1062 tabElement.__iconElement = iconElement; | |
1063 }, | |
1064 | |
1065 /** | |
1066 * @param {boolean} measuring | |
1067 * @return {!Element} | |
1068 */ | |
1069 _createTabElement: function(measuring) | |
1070 { | |
1071 var tabElement = createElementWithClass("div", "tabbed-pane-header-tab")
; | |
1072 tabElement.id = "tab-" + this._id; | |
1073 tabElement.tabIndex = -1; | |
1074 tabElement.setAttribute("role", "tab"); | |
1075 tabElement.setAttribute("aria-selected", "false"); | |
1076 tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabb
edPane, this.id, true); | |
1077 | |
1078 var titleElement = tabElement.createChild("span", "tabbed-pane-header-ta
b-title"); | |
1079 titleElement.textContent = this.title; | |
1080 titleElement.title = this.tooltip || ""; | |
1081 this._createIconElement(tabElement, titleElement); | |
1082 if (!measuring) | |
1083 this._titleElement = titleElement; | |
1084 | |
1085 if (this._closeable) | |
1086 tabElement.createChild("div", "tabbed-pane-close-button", "dt-close-
button").gray = true; | |
1087 | |
1088 if (measuring) { | |
1089 tabElement.classList.add("measuring"); | |
1090 } else { | |
1091 tabElement.addEventListener("click", this._tabClicked.bind(this), fa
lse); | |
1092 tabElement.addEventListener("auxclick", this._tabClicked.bind(this),
false); | |
1093 tabElement.addEventListener("mousedown", this._tabMouseDown.bind(thi
s), false); | |
1094 tabElement.addEventListener("mouseup", this._tabMouseUp.bind(this),
false); | |
1095 | |
1096 tabElement.addEventListener("contextmenu", this._tabContextMenu.bind
(this), false); | |
1097 if (this._tabbedPane._allowTabReorder) | |
1098 WebInspector.installDragHandle(tabElement, this._startTabDraggin
g.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "-w
ebkit-grabbing", "pointer", 200); | |
1099 } | |
1100 | |
1101 return tabElement; | |
1102 }, | |
1103 | |
1104 /** | |
1105 * @param {!Event} event | |
1106 */ | |
1107 _tabClicked: function(event) | |
1108 { | |
1109 var middleButton = event.button === 1; | |
1110 var shouldClose = this._closeable && (middleButton || event.target.class
List.contains("tabbed-pane-close-button")); | |
1111 if (!shouldClose) { | |
1112 this._tabbedPane.focus(); | |
1113 return; | |
1114 } | |
1115 this._closeTabs([this.id]); | |
1116 event.consume(true); | |
1117 }, | |
1118 | |
1119 /** | |
1120 * @param {!Event} event | |
1121 */ | |
1122 _tabMouseDown: function(event) | |
1123 { | |
1124 if (event.target.classList.contains("tabbed-pane-close-button") || event
.button === 1) | |
1125 return; | |
1126 this._tabbedPane.selectTab(this.id, true); | |
1127 }, | |
1128 | |
1129 /** | |
1130 * @param {!Event} event | |
1131 */ | |
1132 _tabMouseUp: function(event) | |
1133 { | |
1134 // This is needed to prevent middle-click pasting on linux when tabs are
clicked. | |
1135 if (event.button === 1) | |
1136 event.consume(true); | |
1137 }, | |
1138 | |
1139 /** | |
1140 * @param {!Array.<string>} ids | |
1141 */ | |
1142 _closeTabs: function(ids) | |
1143 { | |
1144 if (this._delegate) { | |
1145 this._delegate.closeTabs(this._tabbedPane, ids); | |
1146 return; | |
1147 } | |
1148 this._tabbedPane.closeTabs(ids, true); | |
1149 }, | |
1150 | |
1151 _tabContextMenu: function(event) | |
1152 { | |
1153 /** | |
1154 * @this {WebInspector.TabbedPaneTab} | |
1155 */ | |
1156 function close() | |
1157 { | |
1158 this._closeTabs([this.id]); | |
1159 } | |
1160 | |
1161 /** | |
1162 * @this {WebInspector.TabbedPaneTab} | |
1163 */ | |
1164 function closeOthers() | |
1165 { | |
1166 this._closeTabs(this._tabbedPane.otherTabs(this.id)); | |
1167 } | |
1168 | |
1169 /** | |
1170 * @this {WebInspector.TabbedPaneTab} | |
1171 */ | |
1172 function closeAll() | |
1173 { | |
1174 this._closeTabs(this._tabbedPane.allTabs()); | |
1175 } | |
1176 | |
1177 /** | |
1178 * @this {WebInspector.TabbedPaneTab} | |
1179 */ | |
1180 function closeToTheRight() | |
1181 { | |
1182 this._closeTabs(this._tabbedPane._tabsToTheRight(this.id)); | |
1183 } | |
1184 | |
1185 var contextMenu = new WebInspector.ContextMenu(event); | |
1186 if (this._closeable) { | |
1187 contextMenu.appendItem(WebInspector.UIString.capitalize("Close"), cl
ose.bind(this)); | |
1188 contextMenu.appendItem(WebInspector.UIString.capitalize("Close ^othe
rs"), closeOthers.bind(this)); | |
1189 contextMenu.appendItem(WebInspector.UIString.capitalize("Close ^tabs
to the ^right"), closeToTheRight.bind(this)); | |
1190 contextMenu.appendItem(WebInspector.UIString.capitalize("Close ^all"
), closeAll.bind(this)); | |
1191 } | |
1192 if (this._delegate) | |
1193 this._delegate.onContextMenu(this.id, contextMenu); | |
1194 contextMenu.show(); | |
1195 }, | |
1196 | |
1197 /** | |
1198 * @param {!Event} event | |
1199 * @return {boolean} | |
1200 */ | |
1201 _startTabDragging: function(event) | |
1202 { | |
1203 if (event.target.classList.contains("tabbed-pane-close-button")) | |
1204 return false; | |
1205 this._dragStartX = event.pageX; | |
1206 this._tabElement.classList.add("dragging"); | |
1207 this._tabbedPane._tabSlider.remove(); | |
1208 return true; | |
1209 }, | |
1210 | |
1211 /** | |
1212 * @param {!Event} event | |
1213 */ | |
1214 _tabDragging: function(event) | |
1215 { | |
1216 var tabElements = this._tabbedPane._tabsElement.childNodes; | |
1217 for (var i = 0; i < tabElements.length; ++i) { | |
1218 var tabElement = tabElements[i]; | |
1219 if (tabElement === this._tabElement) | |
1220 continue; | |
1221 | |
1222 var intersects = tabElement.offsetLeft + tabElement.clientWidth > th
is._tabElement.offsetLeft && | |
1223 this._tabElement.offsetLeft + this._tabElement.clientWidth > tab
Element.offsetLeft; | |
1224 if (!intersects) | |
1225 continue; | |
1226 | |
1227 if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidt
h / 2 + 5) | |
1228 break; | |
1229 | |
1230 if (event.pageX - this._dragStartX > 0) { | |
1231 tabElement = tabElement.nextSibling; | |
1232 ++i; | |
1233 } | |
1234 | |
1235 var oldOffsetLeft = this._tabElement.offsetLeft; | |
1236 this._tabbedPane._insertBefore(this, i); | |
1237 this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft; | |
1238 break; | |
1239 } | |
1240 | |
1241 if (!this._tabElement.previousSibling && event.pageX - this._dragStartX
< 0) { | |
1242 this._tabElement.style.setProperty("left", "0px"); | |
1243 return; | |
1244 } | |
1245 if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0)
{ | |
1246 this._tabElement.style.setProperty("left", "0px"); | |
1247 return; | |
1248 } | |
1249 | |
1250 this._tabElement.style.setProperty("left", (event.pageX - this._dragStar
tX) + "px"); | |
1251 }, | |
1252 | |
1253 /** | |
1254 * @param {!Event} event | |
1255 */ | |
1256 _endTabDragging: function(event) | |
1257 { | |
1258 this._tabElement.classList.remove("dragging"); | |
1259 this._tabElement.style.removeProperty("left"); | |
1260 delete this._dragStartX; | |
1261 this._tabbedPane._updateTabSlider(); | |
1262 } | |
1263 }; | 1197 }; |
1264 | 1198 |
1265 /** | 1199 /** |
1266 * @interface | 1200 * @interface |
1267 */ | 1201 */ |
1268 WebInspector.TabbedPaneTabDelegate = function() | 1202 WebInspector.TabbedPaneTabDelegate = function() {}; |
1269 { | 1203 |
| 1204 WebInspector.TabbedPaneTabDelegate.prototype = { |
| 1205 /** |
| 1206 * @param {!WebInspector.TabbedPane} tabbedPane |
| 1207 * @param {!Array.<string>} ids |
| 1208 */ |
| 1209 closeTabs: function(tabbedPane, ids) {}, |
| 1210 |
| 1211 /** |
| 1212 * @param {string} tabId |
| 1213 * @param {!WebInspector.ContextMenu} contextMenu |
| 1214 */ |
| 1215 onContextMenu: function(tabId, contextMenu) {} |
1270 }; | 1216 }; |
1271 | |
1272 WebInspector.TabbedPaneTabDelegate.prototype = { | |
1273 /** | |
1274 * @param {!WebInspector.TabbedPane} tabbedPane | |
1275 * @param {!Array.<string>} ids | |
1276 */ | |
1277 closeTabs: function(tabbedPane, ids) { }, | |
1278 | |
1279 /** | |
1280 * @param {string} tabId | |
1281 * @param {!WebInspector.ContextMenu} contextMenu | |
1282 */ | |
1283 onContextMenu: function(tabId, contextMenu) { } | |
1284 }; | |
OLD | NEW |