TabbedPane.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. /*
  2. * Copyright (C) 2010 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @extends {WebInspector.View}
  32. * @constructor
  33. */
  34. WebInspector.TabbedPane = function()
  35. {
  36. WebInspector.View.call(this);
  37. this.registerRequiredCSS("tabbedPane.css");
  38. this.element.addStyleClass("tabbed-pane");
  39. this._headerElement = this.element.createChild("div", "tabbed-pane-header");
  40. this._headerContentsElement = this._headerElement.createChild("div", "tabbed-pane-header-contents");
  41. this._tabsElement = this._headerContentsElement.createChild("div", "tabbed-pane-header-tabs");
  42. this._contentElement = this.element.createChild("div", "tabbed-pane-content scroll-target");
  43. this._tabs = [];
  44. this._tabsHistory = [];
  45. this._tabsById = {};
  46. this.element.addEventListener("click", this.focus.bind(this), true);
  47. this.element.addEventListener("mouseup", this.onMouseUp.bind(this), false);
  48. this._dropDownButton = this._createDropDownButton();
  49. }
  50. WebInspector.TabbedPane.EventTypes = {
  51. TabSelected: "TabSelected",
  52. TabClosed: "TabClosed"
  53. }
  54. WebInspector.TabbedPane.prototype = {
  55. /**
  56. * @return {WebInspector.View}
  57. */
  58. get visibleView()
  59. {
  60. return this._currentTab ? this._currentTab.view : null;
  61. },
  62. /**
  63. * @return {string}
  64. */
  65. get selectedTabId()
  66. {
  67. return this._currentTab ? this._currentTab.id : null;
  68. },
  69. /**
  70. * @type {boolean} shrinkableTabs
  71. */
  72. set shrinkableTabs(shrinkableTabs)
  73. {
  74. this._shrinkableTabs = shrinkableTabs;
  75. },
  76. /**
  77. * @type {boolean} verticalTabLayout
  78. */
  79. set verticalTabLayout(verticalTabLayout)
  80. {
  81. this._verticalTabLayout = verticalTabLayout;
  82. },
  83. /**
  84. * @type {boolean} closeableTabs
  85. */
  86. set closeableTabs(closeableTabs)
  87. {
  88. this._closeableTabs = closeableTabs;
  89. },
  90. /**
  91. * @param {boolean} retainTabsOrder
  92. */
  93. setRetainTabsOrder: function(retainTabsOrder)
  94. {
  95. this._retainTabsOrder = retainTabsOrder;
  96. },
  97. defaultFocusedElement: function()
  98. {
  99. return this.visibleView ? this.visibleView.defaultFocusedElement() : null;
  100. },
  101. /**
  102. * @param {WebInspector.TabbedPaneTabDelegate} delegate
  103. */
  104. setTabDelegate: function(delegate)
  105. {
  106. var tabs = this._tabs.slice();
  107. for (var i = 0; i < tabs.length; ++i)
  108. tabs[i].setDelegate(delegate);
  109. this._delegate = delegate;
  110. },
  111. /**
  112. * @param {Event} event
  113. */
  114. onMouseUp: function(event)
  115. {
  116. // This is needed to prevent middle-click pasting on linux when tabs are clicked.
  117. if (event.button === 1)
  118. event.consume(true);
  119. },
  120. /**
  121. * @param {string} id
  122. * @param {string} tabTitle
  123. * @param {WebInspector.View} view
  124. * @param {string=} tabTooltip
  125. * @param {boolean=} userGesture
  126. */
  127. appendTab: function(id, tabTitle, view, tabTooltip, userGesture)
  128. {
  129. var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, this._closeableTabs, view, tabTooltip);
  130. tab.setDelegate(this._delegate);
  131. this._tabsById[id] = tab;
  132. this._tabs.push(tab);
  133. this._tabsHistory.push(tab);
  134. if (this._tabsHistory[0] === tab)
  135. this.selectTab(tab.id, userGesture);
  136. this._updateTabElements();
  137. },
  138. /**
  139. * @param {string} id
  140. * @param {boolean=} userGesture
  141. */
  142. closeTab: function(id, userGesture)
  143. {
  144. this.closeTabs([id], userGesture);
  145. },
  146. /**
  147. * @param {Array.<string>} ids
  148. * @param {boolean=} userGesture
  149. */
  150. closeTabs: function(ids, userGesture)
  151. {
  152. for (var i = 0; i < ids.length; ++i)
  153. this._innerCloseTab(ids[i], userGesture);
  154. this._updateTabElements();
  155. if (this._tabsHistory.length)
  156. this.selectTab(this._tabsHistory[0].id, userGesture);
  157. },
  158. /**
  159. * @param {string} id
  160. * @param {boolean=} userGesture
  161. */
  162. _innerCloseTab: function(id, userGesture)
  163. {
  164. if (this._currentTab && this._currentTab.id === id)
  165. this._hideCurrentTab();
  166. var tab = this._tabsById[id];
  167. delete this._tabsById[id];
  168. this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
  169. this._tabs.splice(this._tabs.indexOf(tab), 1);
  170. if (tab._shown)
  171. this._hideTabElement(tab);
  172. var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
  173. this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData);
  174. return true;
  175. },
  176. /**
  177. * @return {Array.<string>}
  178. */
  179. allTabs: function()
  180. {
  181. var result = [];
  182. var tabs = this._tabs.slice();
  183. for (var i = 0; i < tabs.length; ++i)
  184. result.push(tabs[i].id);
  185. return result;
  186. },
  187. /**
  188. * @param {string} id
  189. * @return {Array.<string>}
  190. */
  191. otherTabs: function(id)
  192. {
  193. var result = [];
  194. var tabs = this._tabs.slice();
  195. for (var i = 0; i < tabs.length; ++i) {
  196. if (tabs[i].id !== id)
  197. result.push(tabs[i].id);
  198. }
  199. return result;
  200. },
  201. /**
  202. * @param {string} id
  203. * @param {boolean=} userGesture
  204. */
  205. selectTab: function(id, userGesture)
  206. {
  207. var tab = this._tabsById[id];
  208. if (!tab)
  209. return;
  210. if (this._currentTab && this._currentTab.id === id)
  211. return;
  212. this._hideCurrentTab();
  213. this._showTab(tab);
  214. this._currentTab = tab;
  215. this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
  216. this._tabsHistory.splice(0, 0, tab);
  217. this._updateTabElements();
  218. var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
  219. this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData);
  220. return true;
  221. },
  222. /**
  223. * @param {number} tabsCount
  224. * @return {Array.<string>}
  225. */
  226. lastOpenedTabIds: function(tabsCount)
  227. {
  228. function tabToTabId(tab) {
  229. return tab.id;
  230. }
  231. return this._tabsHistory.slice(0, tabsCount).map(tabToTabId);
  232. },
  233. /**
  234. * @param {string} id
  235. * @param {string} iconClass
  236. * @param {string=} iconTooltip
  237. */
  238. setTabIcon: function(id, iconClass, iconTooltip)
  239. {
  240. var tab = this._tabsById[id];
  241. tab._setIconClass(iconClass, iconTooltip);
  242. this._updateTabElements();
  243. },
  244. /**
  245. * @param {string} id
  246. * @param {string} tabTitle
  247. */
  248. changeTabTitle: function(id, tabTitle)
  249. {
  250. var tab = this._tabsById[id];
  251. tab.title = tabTitle;
  252. this._updateTabElements();
  253. },
  254. /**
  255. * @param {string} id
  256. * @param {WebInspector.View} view
  257. */
  258. changeTabView: function(id, view)
  259. {
  260. var tab = this._tabsById[id];
  261. if (this._currentTab && this._currentTab.id === tab.id) {
  262. this._hideTab(tab);
  263. tab.view = view;
  264. this._showTab(tab);
  265. } else
  266. tab.view = view;
  267. },
  268. /**
  269. * @param {string} id
  270. * @param {string=} tabTooltip
  271. */
  272. changeTabTooltip: function(id, tabTooltip)
  273. {
  274. var tab = this._tabsById[id];
  275. tab.tooltip = tabTooltip;
  276. },
  277. onResize: function()
  278. {
  279. this._updateTabElements();
  280. },
  281. _updateTabElements: function()
  282. {
  283. WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements);
  284. },
  285. /**
  286. * @param {string} text
  287. */
  288. setPlaceholderText: function(text)
  289. {
  290. this._noTabsMessage = text;
  291. },
  292. _innerUpdateTabElements: function()
  293. {
  294. if (!this.isShowing())
  295. return;
  296. if (!this._tabs.length) {
  297. this._contentElement.addStyleClass("has-no-tabs");
  298. if (this._noTabsMessage && !this._noTabsMessageElement) {
  299. this._noTabsMessageElement = this._contentElement.createChild("div", "tabbed-pane-placeholder fill");
  300. this._noTabsMessageElement.textContent = this._noTabsMessage;
  301. }
  302. } else {
  303. this._contentElement.removeStyleClass("has-no-tabs");
  304. if (this._noTabsMessageElement) {
  305. this._noTabsMessageElement.remove();
  306. delete this._noTabsMessageElement;
  307. }
  308. }
  309. if (!this._measuredDropDownButtonWidth)
  310. this._measureDropDownButton();
  311. this._updateWidths();
  312. this._updateTabsDropDown();
  313. },
  314. /**
  315. * @param {number} index
  316. * @param {WebInspector.TabbedPaneTab} tab
  317. */
  318. _showTabElement: function(index, tab)
  319. {
  320. if (index >= this._tabsElement.children.length)
  321. this._tabsElement.appendChild(tab.tabElement);
  322. else
  323. this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]);
  324. tab._shown = true;
  325. },
  326. /**
  327. * @param {WebInspector.TabbedPaneTab} tab
  328. */
  329. _hideTabElement: function(tab)
  330. {
  331. this._tabsElement.removeChild(tab.tabElement);
  332. tab._shown = false;
  333. },
  334. _createDropDownButton: function()
  335. {
  336. var dropDownContainer = document.createElement("div");
  337. dropDownContainer.addStyleClass("tabbed-pane-header-tabs-drop-down-container");
  338. var dropDownButton = dropDownContainer.createChild("div", "tabbed-pane-header-tabs-drop-down");
  339. dropDownButton.appendChild(document.createTextNode("\u00bb"));
  340. this._tabsSelect = dropDownButton.createChild("select", "tabbed-pane-header-tabs-drop-down-select");
  341. this._tabsSelect.addEventListener("change", this._tabsSelectChanged.bind(this), false);
  342. return dropDownContainer;
  343. },
  344. _totalWidth: function()
  345. {
  346. return this._headerContentsElement.getBoundingClientRect().width;
  347. },
  348. _updateTabsDropDown: function()
  349. {
  350. var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth);
  351. for (var i = 0; i < this._tabs.length; ++i) {
  352. if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1)
  353. this._hideTabElement(this._tabs[i]);
  354. }
  355. for (var i = 0; i < tabsToShowIndexes.length; ++i) {
  356. var tab = this._tabs[tabsToShowIndexes[i]];
  357. if (!tab._shown)
  358. this._showTabElement(i, tab);
  359. }
  360. this._populateDropDownFromIndex();
  361. },
  362. _populateDropDownFromIndex: function()
  363. {
  364. if (this._dropDownButton.parentElement)
  365. this._headerContentsElement.removeChild(this._dropDownButton);
  366. this._tabsSelect.removeChildren();
  367. var tabsToShow = [];
  368. for (var i = 0; i < this._tabs.length; ++i) {
  369. if (!this._tabs[i]._shown)
  370. tabsToShow.push(this._tabs[i]);
  371. continue;
  372. }
  373. function compareFunction(tab1, tab2)
  374. {
  375. return tab1.title.localeCompare(tab2.title);
  376. }
  377. tabsToShow.sort(compareFunction);
  378. var selectedIndex = -1;
  379. for (var i = 0; i < tabsToShow.length; ++i) {
  380. var option = new Option(tabsToShow[i].title);
  381. option.tab = tabsToShow[i];
  382. this._tabsSelect.appendChild(option);
  383. if (this._tabsHistory[0] === tabsToShow[i])
  384. selectedIndex = i;
  385. }
  386. if (this._tabsSelect.options.length) {
  387. this._headerContentsElement.appendChild(this._dropDownButton);
  388. this._tabsSelect.selectedIndex = selectedIndex;
  389. }
  390. },
  391. _tabsSelectChanged: function()
  392. {
  393. var options = this._tabsSelect.options;
  394. var selectedOption = options[this._tabsSelect.selectedIndex];
  395. this.selectTab(selectedOption.tab.id, true);
  396. },
  397. _measureDropDownButton: function()
  398. {
  399. this._dropDownButton.addStyleClass("measuring");
  400. this._headerContentsElement.appendChild(this._dropDownButton);
  401. this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width;
  402. this._headerContentsElement.removeChild(this._dropDownButton);
  403. this._dropDownButton.removeStyleClass("measuring");
  404. },
  405. _updateWidths: function()
  406. {
  407. var measuredWidths = this._measureWidths();
  408. var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE;
  409. var i = 0;
  410. for (var tabId in this._tabs) {
  411. var tab = this._tabs[tabId];
  412. tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++]));
  413. }
  414. },
  415. _measureWidths: function()
  416. {
  417. // Add all elements to measure into this._tabsElement
  418. var measuringTabElements = [];
  419. for (var tabId in this._tabs) {
  420. var tab = this._tabs[tabId];
  421. if (typeof tab._measuredWidth === "number")
  422. continue;
  423. var measuringTabElement = tab._createTabElement(true);
  424. measuringTabElement.__tab = tab;
  425. measuringTabElements.push(measuringTabElement);
  426. this._tabsElement.appendChild(measuringTabElement);
  427. }
  428. // Perform measurement
  429. for (var i = 0; i < measuringTabElements.length; ++i)
  430. measuringTabElements[i].__tab._measuredWidth = measuringTabElements[i].getBoundingClientRect().width;
  431. // Nuke elements from the UI
  432. for (var i = 0; i < measuringTabElements.length; ++i)
  433. measuringTabElements[i].remove();
  434. // Combine the results.
  435. var measuredWidths = [];
  436. for (var tabId in this._tabs)
  437. measuredWidths.push(this._tabs[tabId]._measuredWidth);
  438. return measuredWidths;
  439. },
  440. /**
  441. * @param {Array.<number>} measuredWidths
  442. * @param {number} totalWidth
  443. */
  444. _calculateMaxWidth: function(measuredWidths, totalWidth)
  445. {
  446. if (!measuredWidths.length)
  447. return 0;
  448. measuredWidths.sort(function(x, y) { return x - y });
  449. var totalMeasuredWidth = 0;
  450. for (var i = 0; i < measuredWidths.length; ++i)
  451. totalMeasuredWidth += measuredWidths[i];
  452. if (totalWidth >= totalMeasuredWidth)
  453. return measuredWidths[measuredWidths.length - 1];
  454. var totalExtraWidth = 0;
  455. for (var i = measuredWidths.length - 1; i > 0; --i) {
  456. var extraWidth = measuredWidths[i] - measuredWidths[i - 1];
  457. totalExtraWidth += (measuredWidths.length - i) * extraWidth;
  458. if (totalWidth + totalExtraWidth >= totalMeasuredWidth)
  459. return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i);
  460. }
  461. return totalWidth / measuredWidths.length;
  462. },
  463. /**
  464. * @param {Array.<WebInspector.TabbedPaneTab>} tabsOrdered
  465. * @param {Array.<WebInspector.TabbedPaneTab>} tabsHistory
  466. * @param {number} totalWidth
  467. * @param {number} measuredDropDownButtonWidth
  468. * @return {Array.<number>}
  469. */
  470. _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth)
  471. {
  472. var tabsToShowIndexes = [];
  473. var totalTabsWidth = 0;
  474. var tabCount = tabsOrdered.length;
  475. for (var i = 0; i < tabCount; ++i) {
  476. var tab = this._retainTabsOrder ? tabsOrdered[i] : tabsHistory[i];
  477. totalTabsWidth += tab.width();
  478. var minimalRequiredWidth = totalTabsWidth;
  479. if (i !== tabCount - 1)
  480. minimalRequiredWidth += measuredDropDownButtonWidth;
  481. if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth)
  482. break;
  483. tabsToShowIndexes.push(tabsOrdered.indexOf(tab));
  484. }
  485. tabsToShowIndexes.sort(function(x, y) { return x - y });
  486. return tabsToShowIndexes;
  487. },
  488. _hideCurrentTab: function()
  489. {
  490. if (!this._currentTab)
  491. return;
  492. this._hideTab(this._currentTab);
  493. delete this._currentTab;
  494. },
  495. /**
  496. * @param {WebInspector.TabbedPaneTab} tab
  497. */
  498. _showTab: function(tab)
  499. {
  500. tab.tabElement.addStyleClass("selected");
  501. tab.view.show(this._contentElement);
  502. },
  503. /**
  504. * @param {WebInspector.TabbedPaneTab} tab
  505. */
  506. _hideTab: function(tab)
  507. {
  508. tab.tabElement.removeStyleClass("selected");
  509. tab.view.detach();
  510. },
  511. /**
  512. * @override
  513. */
  514. canHighlightPosition: function()
  515. {
  516. return this._currentTab && this._currentTab.view && this._currentTab.view.canHighlightPosition();
  517. },
  518. /**
  519. * @override
  520. */
  521. highlightPosition: function(line, column)
  522. {
  523. if (this.canHighlightPosition())
  524. this._currentTab.view.highlightPosition(line, column);
  525. },
  526. /**
  527. * @return {Array.<Element>}
  528. */
  529. elementsToRestoreScrollPositionsFor: function()
  530. {
  531. return [ this._contentElement ];
  532. },
  533. /**
  534. * @param {WebInspector.TabbedPaneTab} tab
  535. * @param {number} index
  536. */
  537. _insertBefore: function(tab, index)
  538. {
  539. this._tabsElement.insertBefore(tab._tabElement, this._tabsElement.childNodes[index]);
  540. var oldIndex = this._tabs.indexOf(tab);
  541. this._tabs.splice(oldIndex, 1);
  542. if (oldIndex < index)
  543. --index;
  544. this._tabs.splice(index, 0, tab);
  545. },
  546. __proto__: WebInspector.View.prototype
  547. }
  548. /**
  549. * @constructor
  550. * @param {WebInspector.TabbedPane} tabbedPane
  551. * @param {string} id
  552. * @param {string} title
  553. * @param {boolean} closeable
  554. * @param {WebInspector.View} view
  555. * @param {string=} tooltip
  556. */
  557. WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, tooltip)
  558. {
  559. this._closeable = closeable;
  560. this._tabbedPane = tabbedPane;
  561. this._id = id;
  562. this._title = title;
  563. this._tooltip = tooltip;
  564. this._view = view;
  565. this._shown = false;
  566. /** @type {number} */ this._measuredWidth;
  567. /** @type {Element} */ this._tabElement;
  568. }
  569. WebInspector.TabbedPaneTab.prototype = {
  570. /**
  571. * @return {string}
  572. */
  573. get id()
  574. {
  575. return this._id;
  576. },
  577. /**
  578. * @return {string}
  579. */
  580. get title()
  581. {
  582. return this._title;
  583. },
  584. set title(title)
  585. {
  586. if (title === this._title)
  587. return;
  588. this._title = title;
  589. if (this._titleElement)
  590. this._titleElement.textContent = title;
  591. delete this._measuredWidth;
  592. },
  593. /**
  594. * @return {string}
  595. */
  596. iconClass: function()
  597. {
  598. return this._iconClass;
  599. },
  600. /**
  601. * @param {string} iconClass
  602. * @param {string} iconTooltip
  603. */
  604. _setIconClass: function(iconClass, iconTooltip)
  605. {
  606. if (iconClass === this._iconClass && iconTooltip === this._iconTooltip)
  607. return;
  608. this._iconClass = iconClass;
  609. this._iconTooltip = iconTooltip;
  610. if (this._iconElement)
  611. this._iconElement.remove();
  612. if (this._iconClass && this._tabElement)
  613. this._iconElement = this._createIconElement(this._tabElement, this._titleElement);
  614. delete this._measuredWidth;
  615. },
  616. /**
  617. * @return {WebInspector.View}
  618. */
  619. get view()
  620. {
  621. return this._view;
  622. },
  623. set view(view)
  624. {
  625. this._view = view;
  626. },
  627. /**
  628. * @return {string|undefined}
  629. */
  630. get tooltip()
  631. {
  632. return this._tooltip;
  633. },
  634. set tooltip(tooltip)
  635. {
  636. this._tooltip = tooltip;
  637. if (this._titleElement)
  638. this._titleElement.title = tooltip || "";
  639. },
  640. /**
  641. * @return {Element}
  642. */
  643. get tabElement()
  644. {
  645. if (typeof(this._tabElement) !== "undefined")
  646. return this._tabElement;
  647. this._createTabElement(false);
  648. return this._tabElement;
  649. },
  650. /**
  651. * @return {number}
  652. */
  653. width: function()
  654. {
  655. return this._width;
  656. },
  657. /**
  658. * @param {number} width
  659. */
  660. setWidth: function(width)
  661. {
  662. this.tabElement.style.width = width === -1 ? "" : (width + "px");
  663. this._width = width;
  664. },
  665. /**
  666. * @param {WebInspector.TabbedPaneTabDelegate} delegate
  667. */
  668. setDelegate: function(delegate)
  669. {
  670. this._delegate = delegate;
  671. },
  672. _createIconElement: function(tabElement, titleElement)
  673. {
  674. var iconElement = document.createElement("span");
  675. iconElement.className = "tabbed-pane-header-tab-icon " + this._iconClass;
  676. if (this._iconTooltip)
  677. iconElement.title = this._iconTooltip;
  678. tabElement.insertBefore(iconElement, titleElement);
  679. return iconElement;
  680. },
  681. /**
  682. * @param {boolean} measuring
  683. */
  684. _createTabElement: function(measuring)
  685. {
  686. var tabElement = document.createElement("div");
  687. tabElement.addStyleClass("tabbed-pane-header-tab");
  688. tabElement.id = "tab-" + this._id;
  689. tabElement.tabIndex = -1;
  690. var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title");
  691. titleElement.textContent = this.title;
  692. titleElement.title = this.tooltip || "";
  693. if (this._iconClass)
  694. this._createIconElement(tabElement, titleElement);
  695. if (!measuring)
  696. this._titleElement = titleElement;
  697. if (this._closeable)
  698. tabElement.createChild("div", "close-button-gray");
  699. if (measuring)
  700. tabElement.addStyleClass("measuring");
  701. else {
  702. this._tabElement = tabElement;
  703. tabElement.addEventListener("click", this._tabClicked.bind(this), false);
  704. tabElement.addEventListener("mousedown", this._tabMouseDown.bind(this), false);
  705. if (this._closeable) {
  706. tabElement.addEventListener("contextmenu", this._tabContextMenu.bind(this), false);
  707. WebInspector.installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "pointer");
  708. }
  709. }
  710. return tabElement;
  711. },
  712. /**
  713. * @param {Event} event
  714. */
  715. _tabClicked: function(event)
  716. {
  717. var middleButton = event.button === 1;
  718. var shouldClose = this._closeable && (middleButton || event.target.hasStyleClass("close-button-gray"));
  719. if (!shouldClose)
  720. return;
  721. this._closeTabs([this.id]);
  722. event.consume(true);
  723. },
  724. /**
  725. * @param {Event} event
  726. */
  727. _tabMouseDown: function(event)
  728. {
  729. if (event.target.hasStyleClass("close-button-gray") || event.button === 1)
  730. return;
  731. this._tabbedPane.selectTab(this.id, true);
  732. },
  733. /**
  734. * @param {Array.<string>} ids
  735. */
  736. _closeTabs: function(ids)
  737. {
  738. if (this._delegate) {
  739. this._delegate.closeTabs(this._tabbedPane, ids);
  740. return;
  741. }
  742. this._tabbedPane.closeTabs(ids, true);
  743. },
  744. _tabContextMenu: function(event)
  745. {
  746. function close()
  747. {
  748. this._closeTabs([this.id]);
  749. }
  750. function closeOthers()
  751. {
  752. this._closeTabs(this._tabbedPane.otherTabs(this.id));
  753. }
  754. function closeAll()
  755. {
  756. this._closeTabs(this._tabbedPane.allTabs(this.id));
  757. }
  758. var contextMenu = new WebInspector.ContextMenu(event);
  759. contextMenu.appendItem(WebInspector.UIString("Close"), close.bind(this));
  760. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close others" : "Close Others"), closeOthers.bind(this));
  761. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close all" : "Close All"), closeAll.bind(this));
  762. contextMenu.show();
  763. },
  764. /**
  765. * @param {Event} event
  766. * @return {boolean}
  767. */
  768. _startTabDragging: function(event)
  769. {
  770. if (event.target.hasStyleClass("close-button-gray"))
  771. return false;
  772. this._dragStartX = event.pageX;
  773. return true;
  774. },
  775. /**
  776. * @param {Event} event
  777. */
  778. _tabDragging: function(event)
  779. {
  780. var tabElements = this._tabbedPane._tabsElement.childNodes;
  781. for (var i = 0; i < tabElements.length; ++i) {
  782. var tabElement = tabElements[i];
  783. if (tabElement === this._tabElement)
  784. continue;
  785. var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft &&
  786. this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft;
  787. if (!intersects)
  788. continue;
  789. if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2 + 5)
  790. break;
  791. if (event.pageX - this._dragStartX > 0) {
  792. tabElement = tabElement.nextSibling;
  793. ++i;
  794. }
  795. var oldOffsetLeft = this._tabElement.offsetLeft;
  796. this._tabbedPane._insertBefore(this, i);
  797. this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft;
  798. break;
  799. }
  800. if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0) {
  801. this._tabElement.style.setProperty("left", "0px");
  802. return;
  803. }
  804. if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) {
  805. this._tabElement.style.setProperty("left", "0px");
  806. return;
  807. }
  808. this._tabElement.style.setProperty("position", "relative");
  809. this._tabElement.style.setProperty("left", (event.pageX - this._dragStartX) + "px");
  810. },
  811. /**
  812. * @param {Event} event
  813. */
  814. _endTabDragging: function(event)
  815. {
  816. this._tabElement.style.removeProperty("position");
  817. this._tabElement.style.removeProperty("left");
  818. delete this._dragStartX;
  819. }
  820. }
  821. /**
  822. * @interface
  823. */
  824. WebInspector.TabbedPaneTabDelegate = function()
  825. {
  826. }
  827. WebInspector.TabbedPaneTabDelegate.prototype = {
  828. /**
  829. * @param {WebInspector.TabbedPane} tabbedPane
  830. * @param {Array.<string>} ids
  831. */
  832. closeTabs: function(tabbedPane, ids) { }
  833. }