You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VTabsheet.java 45KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344
  1. /*
  2. * Copyright 2000-2013 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.ui;
  17. import java.util.Iterator;
  18. import java.util.List;
  19. import com.google.gwt.aria.client.Id;
  20. import com.google.gwt.aria.client.LiveValue;
  21. import com.google.gwt.aria.client.Roles;
  22. import com.google.gwt.aria.client.SelectedValue;
  23. import com.google.gwt.core.client.Scheduler;
  24. import com.google.gwt.dom.client.DivElement;
  25. import com.google.gwt.dom.client.Style;
  26. import com.google.gwt.dom.client.Style.Visibility;
  27. import com.google.gwt.dom.client.TableCellElement;
  28. import com.google.gwt.dom.client.TableElement;
  29. import com.google.gwt.event.dom.client.BlurEvent;
  30. import com.google.gwt.event.dom.client.BlurHandler;
  31. import com.google.gwt.event.dom.client.ClickEvent;
  32. import com.google.gwt.event.dom.client.ClickHandler;
  33. import com.google.gwt.event.dom.client.FocusEvent;
  34. import com.google.gwt.event.dom.client.FocusHandler;
  35. import com.google.gwt.event.dom.client.HasBlurHandlers;
  36. import com.google.gwt.event.dom.client.HasFocusHandlers;
  37. import com.google.gwt.event.dom.client.HasKeyDownHandlers;
  38. import com.google.gwt.event.dom.client.KeyCodes;
  39. import com.google.gwt.event.dom.client.KeyDownEvent;
  40. import com.google.gwt.event.dom.client.KeyDownHandler;
  41. import com.google.gwt.event.shared.HandlerRegistration;
  42. import com.google.gwt.user.client.Command;
  43. import com.google.gwt.user.client.DOM;
  44. import com.google.gwt.user.client.Element;
  45. import com.google.gwt.user.client.Event;
  46. import com.google.gwt.user.client.ui.ComplexPanel;
  47. import com.google.gwt.user.client.ui.SimplePanel;
  48. import com.google.gwt.user.client.ui.Widget;
  49. import com.google.gwt.user.client.ui.impl.FocusImpl;
  50. import com.vaadin.client.ApplicationConnection;
  51. import com.vaadin.client.BrowserInfo;
  52. import com.vaadin.client.ComponentConnector;
  53. import com.vaadin.client.ConnectorMap;
  54. import com.vaadin.client.Focusable;
  55. import com.vaadin.client.TooltipInfo;
  56. import com.vaadin.client.UIDL;
  57. import com.vaadin.client.Util;
  58. import com.vaadin.client.VCaption;
  59. import com.vaadin.client.ui.aria.AriaHelper;
  60. import com.vaadin.shared.AbstractComponentState;
  61. import com.vaadin.shared.EventId;
  62. import com.vaadin.shared.ui.ComponentStateUtil;
  63. import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
  64. import com.vaadin.shared.ui.tabsheet.TabsheetConstants;
  65. public class VTabsheet extends VTabsheetBase implements Focusable,
  66. FocusHandler, BlurHandler, KeyDownHandler {
  67. private static class VCloseEvent {
  68. private Tab tab;
  69. VCloseEvent(Tab tab) {
  70. this.tab = tab;
  71. }
  72. public Tab getTab() {
  73. return tab;
  74. }
  75. }
  76. private interface VCloseHandler {
  77. public void onClose(VCloseEvent event);
  78. }
  79. /**
  80. * Representation of a single "tab" shown in the TabBar
  81. *
  82. */
  83. public static class Tab extends SimplePanel implements HasFocusHandlers,
  84. HasBlurHandlers, HasKeyDownHandlers {
  85. private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
  86. private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
  87. + "-first";
  88. private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME
  89. + "-selected";
  90. private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
  91. + "-first";
  92. private static final String TD_FOCUS_CLASSNAME = TD_CLASSNAME
  93. + "-focus";
  94. private static final String TD_FOCUS_FIRST_CLASSNAME = TD_FOCUS_CLASSNAME
  95. + "-first";
  96. private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
  97. + "-disabled";
  98. private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
  99. private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
  100. + "-selected";
  101. private static final String DIV_FOCUS_CLASSNAME = DIV_CLASSNAME
  102. + "-focus";
  103. private TabCaption tabCaption;
  104. Element td = getElement();
  105. private VCloseHandler closeHandler;
  106. private boolean enabledOnServer = true;
  107. private Element div;
  108. private TabBar tabBar;
  109. private boolean hiddenOnServer = false;
  110. private String styleName;
  111. private String id;
  112. private Tab(TabBar tabBar) {
  113. super(DOM.createTD());
  114. this.tabBar = tabBar;
  115. setStyleName(td, TD_CLASSNAME);
  116. Roles.getTabRole().set(getElement());
  117. Roles.getTabRole().setAriaSelectedState(getElement(),
  118. SelectedValue.FALSE);
  119. div = DOM.createDiv();
  120. focusImpl.setTabIndex(td, -1);
  121. setStyleName(div, DIV_CLASSNAME);
  122. DOM.appendChild(td, div);
  123. tabCaption = new TabCaption(this, getTabsheet()
  124. .getApplicationConnection());
  125. add(tabCaption);
  126. Roles.getTabRole().setAriaLabelledbyProperty(getElement(),
  127. Id.of(tabCaption.getElement()));
  128. addFocusHandler(getTabsheet());
  129. addBlurHandler(getTabsheet());
  130. addKeyDownHandler(getTabsheet());
  131. }
  132. public boolean isHiddenOnServer() {
  133. return hiddenOnServer;
  134. }
  135. public void setHiddenOnServer(boolean hiddenOnServer) {
  136. this.hiddenOnServer = hiddenOnServer;
  137. Roles.getTabRole().setAriaHiddenState(getElement(), hiddenOnServer);
  138. }
  139. @Override
  140. protected Element getContainerElement() {
  141. // Attach caption element to div, not td
  142. return div;
  143. }
  144. public boolean isEnabledOnServer() {
  145. return enabledOnServer;
  146. }
  147. public void setEnabledOnServer(boolean enabled) {
  148. enabledOnServer = enabled;
  149. Roles.getTabRole().setAriaDisabledState(getElement(), !enabled);
  150. setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
  151. if (!enabled) {
  152. focusImpl.setTabIndex(td, -1);
  153. }
  154. }
  155. public void addClickHandler(ClickHandler handler) {
  156. tabCaption.addClickHandler(handler);
  157. }
  158. public void setCloseHandler(VCloseHandler closeHandler) {
  159. this.closeHandler = closeHandler;
  160. }
  161. /**
  162. * Toggles the style names for the Tab
  163. *
  164. * @param selected
  165. * true if the Tab is selected
  166. * @param first
  167. * true if the Tab is the first visible Tab
  168. */
  169. public void setStyleNames(boolean selected, boolean first) {
  170. setStyleNames(selected, first, false);
  171. }
  172. public void setStyleNames(boolean selected, boolean first,
  173. boolean keyboardFocus) {
  174. setStyleName(td, TD_FIRST_CLASSNAME, first);
  175. setStyleName(td, TD_SELECTED_CLASSNAME, selected);
  176. setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
  177. setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
  178. setStyleName(td, TD_FOCUS_CLASSNAME, keyboardFocus);
  179. setStyleName(td, TD_FOCUS_FIRST_CLASSNAME, keyboardFocus && first);
  180. setStyleName(div, DIV_FOCUS_CLASSNAME, keyboardFocus);
  181. }
  182. public void setTabulatorIndex(int tabIndex) {
  183. focusImpl.setTabIndex(td, tabIndex);
  184. }
  185. public boolean isClosable() {
  186. return tabCaption.isClosable();
  187. }
  188. public void onClose() {
  189. closeHandler.onClose(new VCloseEvent(this));
  190. }
  191. public VTabsheet getTabsheet() {
  192. return tabBar.getTabsheet();
  193. }
  194. public void updateFromUIDL(UIDL tabUidl) {
  195. tabCaption.updateCaption(tabUidl);
  196. // Apply the styleName set for the tab
  197. String newStyleName = tabUidl
  198. .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME);
  199. // Find the nth td element
  200. if (newStyleName != null && !newStyleName.isEmpty()) {
  201. if (!newStyleName.equals(styleName)) {
  202. // If we have a new style name
  203. if (styleName != null && !styleName.isEmpty()) {
  204. // Remove old style name if present
  205. td.removeClassName(TD_CLASSNAME + "-" + styleName);
  206. }
  207. // Set new style name
  208. td.addClassName(TD_CLASSNAME + "-" + newStyleName);
  209. styleName = newStyleName;
  210. }
  211. } else if (styleName != null) {
  212. // Remove the set stylename if no stylename is present in the
  213. // uidl
  214. td.removeClassName(TD_CLASSNAME + "-" + styleName);
  215. styleName = null;
  216. }
  217. String newId = tabUidl.getStringAttribute("id");
  218. if (newId != null && !newId.isEmpty()) {
  219. td.setId(newId);
  220. id = newId;
  221. } else if (id != null) {
  222. td.removeAttribute("id");
  223. id = null;
  224. }
  225. }
  226. public void recalculateCaptionWidth() {
  227. tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
  228. }
  229. @Override
  230. public HandlerRegistration addFocusHandler(FocusHandler handler) {
  231. return addDomHandler(handler, FocusEvent.getType());
  232. }
  233. @Override
  234. public HandlerRegistration addBlurHandler(BlurHandler handler) {
  235. return addDomHandler(handler, BlurEvent.getType());
  236. }
  237. @Override
  238. public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
  239. return addDomHandler(handler, KeyDownEvent.getType());
  240. }
  241. public void focus() {
  242. focusImpl.focus(td);
  243. }
  244. public void blur() {
  245. focusImpl.blur(td);
  246. }
  247. public boolean hasTooltip() {
  248. return tabCaption.getTooltipInfo() != null;
  249. }
  250. public TooltipInfo getTooltipInfo() {
  251. return tabCaption.getTooltipInfo();
  252. }
  253. public void setAssistiveDescription(String descriptionId) {
  254. Roles.getTablistRole().setAriaDescribedbyProperty(getElement(),
  255. Id.of(descriptionId));
  256. }
  257. public void removeAssistiveDescription() {
  258. Roles.getTablistRole().removeAriaDescribedbyProperty(getElement());
  259. }
  260. }
  261. public static class TabCaption extends VCaption {
  262. private boolean closable = false;
  263. private Element closeButton;
  264. private Tab tab;
  265. TabCaption(Tab tab, ApplicationConnection client) {
  266. super(client);
  267. this.tab = tab;
  268. AriaHelper.ensureHasId(getElement());
  269. }
  270. public boolean updateCaption(UIDL uidl) {
  271. if (uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION)
  272. || uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE)) {
  273. setTooltipInfo(new TooltipInfo(
  274. uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
  275. uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE)));
  276. } else {
  277. setTooltipInfo(null);
  278. }
  279. // TODO need to call this instead of super because the caption does
  280. // not have an owner
  281. boolean ret = updateCaptionWithoutOwner(
  282. uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
  283. uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
  284. uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
  285. uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
  286. uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON),
  287. uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON_ALT));
  288. setClosable(uidl.hasAttribute("closable"));
  289. return ret;
  290. }
  291. private VTabsheet getTabsheet() {
  292. return tab.getTabsheet();
  293. }
  294. @Override
  295. public void onBrowserEvent(Event event) {
  296. if (closable && event.getTypeInt() == Event.ONCLICK
  297. && event.getEventTarget().cast() == closeButton) {
  298. tab.onClose();
  299. event.stopPropagation();
  300. event.preventDefault();
  301. }
  302. super.onBrowserEvent(event);
  303. if (event.getTypeInt() == Event.ONLOAD) {
  304. getTabsheet().tabSizeMightHaveChanged(getTab());
  305. }
  306. }
  307. public Tab getTab() {
  308. return tab;
  309. }
  310. public void setClosable(boolean closable) {
  311. this.closable = closable;
  312. if (closable && closeButton == null) {
  313. closeButton = DOM.createSpan();
  314. closeButton.setInnerHTML("×");
  315. closeButton
  316. .setClassName(VTabsheet.CLASSNAME + "-caption-close");
  317. Roles.getTabRole().setAriaHiddenState(closeButton, true);
  318. Roles.getTabRole().setAriaDisabledState(closeButton, true);
  319. getElement().appendChild(closeButton);
  320. } else if (!closable && closeButton != null) {
  321. getElement().removeChild(closeButton);
  322. closeButton = null;
  323. }
  324. if (closable) {
  325. addStyleDependentName("closable");
  326. } else {
  327. removeStyleDependentName("closable");
  328. }
  329. }
  330. public boolean isClosable() {
  331. return closable;
  332. }
  333. @Override
  334. public int getRequiredWidth() {
  335. int width = super.getRequiredWidth();
  336. if (closeButton != null) {
  337. width += Util.getRequiredWidth(closeButton);
  338. }
  339. return width;
  340. }
  341. public Element getCloseButton() {
  342. return closeButton;
  343. }
  344. }
  345. static class TabBar extends ComplexPanel implements ClickHandler,
  346. VCloseHandler {
  347. private final Element tr = DOM.createTR();
  348. private final Element spacerTd = DOM.createTD();
  349. private Tab selected;
  350. private VTabsheet tabsheet;
  351. TabBar(VTabsheet tabsheet) {
  352. this.tabsheet = tabsheet;
  353. Element el = DOM.createTable();
  354. Roles.getPresentationRole().set(el);
  355. Element tbody = DOM.createTBody();
  356. DOM.appendChild(el, tbody);
  357. DOM.appendChild(tbody, tr);
  358. setStyleName(spacerTd, CLASSNAME + "-spacertd");
  359. DOM.appendChild(tr, spacerTd);
  360. DOM.appendChild(spacerTd, DOM.createDiv());
  361. setElement(el);
  362. }
  363. @Override
  364. public void onClose(VCloseEvent event) {
  365. Tab tab = event.getTab();
  366. if (!tab.isEnabledOnServer()) {
  367. return;
  368. }
  369. int tabIndex = getWidgetIndex(tab);
  370. getTabsheet().sendTabClosedEvent(tabIndex);
  371. }
  372. protected Element getContainerElement() {
  373. return tr;
  374. }
  375. public int getTabCount() {
  376. return getWidgetCount();
  377. }
  378. public Tab addTab() {
  379. Tab t = new Tab(this);
  380. int tabIndex = getTabCount();
  381. // Logical attach
  382. insert(t, tr, tabIndex, true);
  383. if (tabIndex == 0) {
  384. // Set the "first" style
  385. t.setStyleNames(false, true);
  386. }
  387. t.addClickHandler(this);
  388. t.setCloseHandler(this);
  389. return t;
  390. }
  391. @Override
  392. public void onClick(ClickEvent event) {
  393. TabCaption caption = (TabCaption) event.getSource();
  394. Element targetElement = event.getNativeEvent().getEventTarget()
  395. .cast();
  396. // the tab should not be focused if the close button was clicked
  397. if (targetElement == caption.getCloseButton()) {
  398. return;
  399. }
  400. int index = getWidgetIndex(caption.getParent());
  401. getTabsheet().focus();
  402. getTabsheet().loadTabSheet(index);
  403. }
  404. public VTabsheet getTabsheet() {
  405. return tabsheet;
  406. }
  407. public Tab getTab(int index) {
  408. if (index < 0 || index >= getTabCount()) {
  409. return null;
  410. }
  411. return (Tab) super.getWidget(index);
  412. }
  413. public void selectTab(int index) {
  414. final Tab newSelected = getTab(index);
  415. final Tab oldSelected = selected;
  416. newSelected.setStyleNames(true, isFirstVisibleTab(index), true);
  417. newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex);
  418. Roles.getTabRole().setAriaSelectedState(newSelected.getElement(),
  419. SelectedValue.TRUE);
  420. if (oldSelected != null && oldSelected != newSelected) {
  421. oldSelected.setStyleNames(false,
  422. isFirstVisibleTab(getWidgetIndex(oldSelected)));
  423. oldSelected.setTabulatorIndex(-1);
  424. Roles.getTabRole().setAriaSelectedState(
  425. oldSelected.getElement(), SelectedValue.FALSE);
  426. }
  427. // Update the field holding the currently selected tab
  428. selected = newSelected;
  429. // The selected tab might need more (or less) space
  430. newSelected.recalculateCaptionWidth();
  431. getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
  432. }
  433. public void navigateTab(int fromIndex, int toIndex) {
  434. Tab newNavigated = getTab(toIndex);
  435. if (newNavigated == null) {
  436. throw new IllegalArgumentException(
  437. "Tab at provided index toIndex was not found");
  438. }
  439. Tab oldNavigated = getTab(fromIndex);
  440. newNavigated.setStyleNames(newNavigated.equals(selected),
  441. isFirstVisibleTab(toIndex), true);
  442. if (oldNavigated != null && fromIndex != toIndex) {
  443. oldNavigated.setStyleNames(oldNavigated.equals(selected),
  444. isFirstVisibleTab(fromIndex), false);
  445. }
  446. }
  447. public void removeTab(int i) {
  448. Tab tab = getTab(i);
  449. if (tab == null) {
  450. return;
  451. }
  452. remove(tab);
  453. /*
  454. * If this widget was selected we need to unmark it as the last
  455. * selected
  456. */
  457. if (tab == selected) {
  458. selected = null;
  459. }
  460. // FIXME: Shouldn't something be selected instead?
  461. }
  462. private boolean isFirstVisibleTab(int index) {
  463. return getFirstVisibleTab() == index;
  464. }
  465. /**
  466. * Returns the index of the first visible tab
  467. *
  468. * @return
  469. */
  470. private int getFirstVisibleTab() {
  471. return getNextVisibleTab(-1);
  472. }
  473. /**
  474. * Find the next visible tab. Returns -1 if none is found.
  475. *
  476. * @param i
  477. * @return
  478. */
  479. private int getNextVisibleTab(int i) {
  480. int tabs = getTabCount();
  481. do {
  482. i++;
  483. } while (i < tabs && getTab(i).isHiddenOnServer());
  484. if (i == tabs) {
  485. return -1;
  486. } else {
  487. return i;
  488. }
  489. }
  490. /**
  491. * Find the previous visible tab. Returns -1 if none is found.
  492. *
  493. * @param i
  494. * @return
  495. */
  496. private int getPreviousVisibleTab(int i) {
  497. do {
  498. i--;
  499. } while (i >= 0 && getTab(i).isHiddenOnServer());
  500. return i;
  501. }
  502. public int scrollLeft(int currentFirstVisible) {
  503. int prevVisible = getPreviousVisibleTab(currentFirstVisible);
  504. if (prevVisible == -1) {
  505. return -1;
  506. }
  507. Tab newFirst = getTab(prevVisible);
  508. newFirst.setVisible(true);
  509. newFirst.recalculateCaptionWidth();
  510. return prevVisible;
  511. }
  512. public int scrollRight(int currentFirstVisible) {
  513. int nextVisible = getNextVisibleTab(currentFirstVisible);
  514. if (nextVisible == -1) {
  515. return -1;
  516. }
  517. Tab currentFirst = getTab(currentFirstVisible);
  518. currentFirst.setVisible(false);
  519. currentFirst.recalculateCaptionWidth();
  520. return nextVisible;
  521. }
  522. }
  523. public static final String CLASSNAME = "v-tabsheet";
  524. public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer";
  525. public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller";
  526. /** For internal use only. May be removed or replaced in the future. */
  527. public final Element tabs; // tabbar and 'scroller' container
  528. Tab focusedTab;
  529. /**
  530. * The tabindex property (position in the browser's focus cycle.) Named like
  531. * this to avoid confusion with activeTabIndex.
  532. */
  533. int tabulatorIndex = 0;
  534. private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
  535. private final Element scroller; // tab-scroller element
  536. private final Element scrollerNext; // tab-scroller next button element
  537. private final Element scrollerPrev; // tab-scroller prev button element
  538. /**
  539. * The index of the first visible tab (when scrolled)
  540. */
  541. private int scrollerIndex = 0;
  542. final TabBar tb = new TabBar(this);
  543. /** For internal use only. May be removed or replaced in the future. */
  544. public final VTabsheetPanel tp = new VTabsheetPanel();
  545. /** For internal use only. May be removed or replaced in the future. */
  546. public final Element contentNode;
  547. private final Element deco;
  548. /** For internal use only. May be removed or replaced in the future. */
  549. public boolean waitingForResponse;
  550. private String currentStyle;
  551. /**
  552. * @return Whether the tab could be selected or not.
  553. */
  554. private boolean canSelectTab(final int tabIndex) {
  555. Tab tab = tb.getTab(tabIndex);
  556. if (client == null || disabled || waitingForResponse) {
  557. return false;
  558. }
  559. if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) {
  560. return false;
  561. }
  562. // Note that we return true when tabIndex == activeTabIndex; the active
  563. // tab could be selected, it's just a no-op.
  564. return true;
  565. }
  566. /**
  567. * Load the content of a tab of the provided index.
  568. *
  569. * @param index
  570. * of the tab to load
  571. */
  572. public void loadTabSheet(int tabIndex) {
  573. if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) {
  574. tb.selectTab(tabIndex);
  575. // If this TabSheet already has focus, set the new selected tab
  576. // as focused.
  577. if (focusedTab != null) {
  578. focusedTab = tb.getTab(tabIndex);
  579. focusedTab.focus();
  580. }
  581. activeTabIndex = tabIndex;
  582. focusedTabIndex = tabIndex;
  583. addStyleDependentName("loading");
  584. // Hide the current contents so a loading indicator can be shown
  585. // instead
  586. Widget currentlyDisplayedWidget = tp.getWidget(tp
  587. .getVisibleWidget());
  588. currentlyDisplayedWidget.getElement().getParentElement().getStyle()
  589. .setVisibility(Visibility.HIDDEN);
  590. client.updateVariable(id, "selected", tabKeys.get(tabIndex)
  591. .toString(), true);
  592. waitingForResponse = true;
  593. }
  594. }
  595. public ApplicationConnection getApplicationConnection() {
  596. return client;
  597. }
  598. public void tabSizeMightHaveChanged(Tab tab) {
  599. // icon onloads may change total width of tabsheet
  600. if (isDynamicWidth()) {
  601. updateDynamicWidth();
  602. }
  603. updateTabScroller();
  604. }
  605. void sendTabClosedEvent(int tabIndex) {
  606. client.updateVariable(id, "close", tabKeys.get(tabIndex), true);
  607. }
  608. boolean isDynamicWidth() {
  609. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  610. this);
  611. return paintable.isUndefinedWidth();
  612. }
  613. boolean isDynamicHeight() {
  614. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  615. this);
  616. return paintable.isUndefinedHeight();
  617. }
  618. public VTabsheet() {
  619. super(CLASSNAME);
  620. addHandler(this, FocusEvent.getType());
  621. addHandler(this, BlurEvent.getType());
  622. // Tab scrolling
  623. DOM.setStyleAttribute(getElement(), "overflow", "hidden");
  624. tabs = DOM.createDiv();
  625. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  626. Roles.getTablistRole().set(tabs);
  627. Roles.getTablistRole().setAriaLiveProperty(tabs, LiveValue.OFF);
  628. scroller = DOM.createDiv();
  629. Roles.getTablistRole().setAriaHiddenState(scroller, true);
  630. DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
  631. scrollerPrev = DOM.createButton();
  632. scrollerPrev.setTabIndex(-1);
  633. DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
  634. + "Prev");
  635. Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true);
  636. DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
  637. scrollerNext = DOM.createButton();
  638. scrollerNext.setTabIndex(-1);
  639. DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
  640. + "Next");
  641. Roles.getTablistRole().setAriaHiddenState(scrollerNext, true);
  642. DOM.sinkEvents(scrollerNext, Event.ONCLICK);
  643. DOM.appendChild(getElement(), tabs);
  644. // Tabs
  645. tp.setStyleName(CLASSNAME + "-tabsheetpanel");
  646. contentNode = DOM.createDiv();
  647. Roles.getTabpanelRole().set(contentNode);
  648. deco = DOM.createDiv();
  649. addStyleDependentName("loading"); // Indicate initial progress
  650. tb.setStyleName(CLASSNAME + "-tabs");
  651. DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content");
  652. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  653. add(tb, tabs);
  654. DOM.appendChild(scroller, scrollerPrev);
  655. DOM.appendChild(scroller, scrollerNext);
  656. DOM.appendChild(getElement(), contentNode);
  657. add(tp, contentNode);
  658. DOM.appendChild(getElement(), deco);
  659. DOM.appendChild(tabs, scroller);
  660. // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
  661. // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
  662. // .getFirstChild(tb.getElement()))), "display", "none");
  663. }
  664. @Override
  665. public void onBrowserEvent(Event event) {
  666. if (event.getTypeInt() == Event.ONCLICK) {
  667. // Tab scrolling
  668. if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
  669. int newFirstIndex = tb.scrollLeft(scrollerIndex);
  670. if (newFirstIndex != -1) {
  671. scrollerIndex = newFirstIndex;
  672. updateTabScroller();
  673. }
  674. event.stopPropagation();
  675. return;
  676. } else if (isClippedTabs()
  677. && DOM.eventGetTarget(event) == scrollerNext) {
  678. int newFirstIndex = tb.scrollRight(scrollerIndex);
  679. if (newFirstIndex != -1) {
  680. scrollerIndex = newFirstIndex;
  681. updateTabScroller();
  682. }
  683. event.stopPropagation();
  684. return;
  685. }
  686. }
  687. super.onBrowserEvent(event);
  688. }
  689. /**
  690. * Checks if the tab with the selected index has been scrolled out of the
  691. * view (on the left side).
  692. *
  693. * @param index
  694. * @return
  695. */
  696. private boolean scrolledOutOfView(int index) {
  697. return scrollerIndex > index;
  698. }
  699. /** For internal use only. May be removed or replaced in the future. */
  700. public void handleStyleNames(UIDL uidl, AbstractComponentState state) {
  701. // Add proper stylenames for all elements (easier to prevent unwanted
  702. // style inheritance)
  703. if (ComponentStateUtil.hasStyles(state)) {
  704. final List<String> styles = state.styles;
  705. if (currentStyle == null || !currentStyle.equals(styles.toString())) {
  706. currentStyle = styles.toString();
  707. final String tabsBaseClass = TABS_CLASSNAME;
  708. String tabsClass = tabsBaseClass;
  709. final String contentBaseClass = CLASSNAME + "-content";
  710. String contentClass = contentBaseClass;
  711. final String decoBaseClass = CLASSNAME + "-deco";
  712. String decoClass = decoBaseClass;
  713. for (String style : styles) {
  714. tb.addStyleDependentName(style);
  715. tabsClass += " " + tabsBaseClass + "-" + style;
  716. contentClass += " " + contentBaseClass + "-" + style;
  717. decoClass += " " + decoBaseClass + "-" + style;
  718. }
  719. DOM.setElementProperty(tabs, "className", tabsClass);
  720. DOM.setElementProperty(contentNode, "className", contentClass);
  721. DOM.setElementProperty(deco, "className", decoClass);
  722. borderW = -1;
  723. }
  724. } else {
  725. tb.setStyleName(CLASSNAME + "-tabs");
  726. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  727. DOM.setElementProperty(contentNode, "className", CLASSNAME
  728. + "-content");
  729. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  730. }
  731. if (uidl.hasAttribute("hidetabs")) {
  732. tb.setVisible(false);
  733. addStyleName(CLASSNAME + "-hidetabs");
  734. } else {
  735. tb.setVisible(true);
  736. removeStyleName(CLASSNAME + "-hidetabs");
  737. }
  738. }
  739. /** For internal use only. May be removed or replaced in the future. */
  740. public void updateDynamicWidth() {
  741. // Find width consumed by tabs
  742. TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
  743. .getRows().getItem(0).getCells().getItem(tb.getTabCount());
  744. int spacerWidth = spacerCell.getOffsetWidth();
  745. DivElement div = (DivElement) spacerCell.getFirstChildElement();
  746. int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth();
  747. int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth;
  748. // Find content width
  749. Style style = tp.getElement().getStyle();
  750. String overflow = style.getProperty("overflow");
  751. style.setProperty("overflow", "hidden");
  752. style.setPropertyPx("width", tabsWidth);
  753. boolean hasTabs = tp.getWidgetCount() > 0;
  754. Style wrapperstyle = null;
  755. if (hasTabs) {
  756. wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
  757. .getParentElement().getStyle();
  758. wrapperstyle.setPropertyPx("width", tabsWidth);
  759. }
  760. // Get content width from actual widget
  761. int contentWidth = 0;
  762. if (hasTabs) {
  763. contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
  764. }
  765. style.setProperty("overflow", overflow);
  766. // Set widths to max(tabs,content)
  767. if (tabsWidth < contentWidth) {
  768. tabsWidth = contentWidth;
  769. }
  770. int outerWidth = tabsWidth + getContentAreaBorderWidth();
  771. tabs.getStyle().setPropertyPx("width", outerWidth);
  772. style.setPropertyPx("width", tabsWidth);
  773. if (hasTabs) {
  774. wrapperstyle.setPropertyPx("width", tabsWidth);
  775. }
  776. contentNode.getStyle().setPropertyPx("width", tabsWidth);
  777. super.setWidth(outerWidth + "px");
  778. updateOpenTabSize();
  779. }
  780. @Override
  781. public void renderTab(final UIDL tabUidl, int index, boolean selected,
  782. boolean hidden) {
  783. Tab tab = tb.getTab(index);
  784. if (tab == null) {
  785. tab = tb.addTab();
  786. }
  787. tab.updateFromUIDL(tabUidl);
  788. tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
  789. tab.setHiddenOnServer(hidden);
  790. if (scrolledOutOfView(index)) {
  791. // Should not set tabs visible if they are scrolled out of view
  792. hidden = true;
  793. }
  794. // Set the current visibility of the tab (in the browser)
  795. tab.setVisible(!hidden);
  796. /*
  797. * Force the width of the caption container so the content will not wrap
  798. * and tabs won't be too narrow in certain browsers
  799. */
  800. tab.recalculateCaptionWidth();
  801. if (selected) {
  802. renderContent(tabUidl.getChildUIDL(0));
  803. tb.selectTab(index);
  804. }
  805. }
  806. /**
  807. * @deprecated as of 7.1, VTabsheet only keeps the active tab in the DOM
  808. * without any place holders.
  809. */
  810. @Deprecated
  811. public class PlaceHolder extends VLabel {
  812. public PlaceHolder() {
  813. super("");
  814. }
  815. }
  816. private void renderContent(final UIDL contentUIDL) {
  817. final ComponentConnector content = client.getPaintable(contentUIDL);
  818. Widget newWidget = content.getWidget();
  819. assert tp.getWidgetCount() <= 1;
  820. if (tp.getWidgetCount() == 0) {
  821. tp.add(newWidget);
  822. } else if (tp.getWidget(0) != newWidget) {
  823. tp.remove(0);
  824. tp.add(newWidget);
  825. }
  826. assert tp.getWidgetCount() <= 1;
  827. // There's never any other index than 0, but maintaining API for now
  828. tp.showWidget(0);
  829. VTabsheet.this.iLayout();
  830. updateOpenTabSize();
  831. VTabsheet.this.removeStyleDependentName("loading");
  832. }
  833. /** For internal use only. May be removed or replaced in the future. */
  834. public void updateContentNodeHeight() {
  835. if (!isDynamicHeight()) {
  836. int contentHeight = getOffsetHeight();
  837. contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
  838. contentHeight -= tb.getOffsetHeight();
  839. if (contentHeight < 0) {
  840. contentHeight = 0;
  841. }
  842. // Set proper values for content element
  843. DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
  844. } else {
  845. DOM.setStyleAttribute(contentNode, "height", "");
  846. }
  847. }
  848. public void iLayout() {
  849. updateTabScroller();
  850. }
  851. /**
  852. * Sets the size of the visible tab (component). As the tab is set to
  853. * position: absolute (to work around a firefox flickering bug) we must keep
  854. * this up-to-date by hand.
  855. * <p>
  856. * For internal use only. May be removed or replaced in the future.
  857. */
  858. public void updateOpenTabSize() {
  859. /*
  860. * The overflow=auto element must have a height specified, otherwise it
  861. * will be just as high as the contents and no scrollbars will appear
  862. */
  863. int height = -1;
  864. int width = -1;
  865. int minWidth = 0;
  866. if (!isDynamicHeight()) {
  867. height = contentNode.getOffsetHeight();
  868. }
  869. if (!isDynamicWidth()) {
  870. width = contentNode.getOffsetWidth() - getContentAreaBorderWidth();
  871. } else {
  872. /*
  873. * If the tabbar is wider than the content we need to use the tabbar
  874. * width as minimum width so scrollbars get placed correctly (at the
  875. * right edge).
  876. */
  877. minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
  878. }
  879. tp.fixVisibleTabSize(width, height, minWidth);
  880. }
  881. /**
  882. * Layouts the tab-scroller elements, and applies styles.
  883. */
  884. private void updateTabScroller() {
  885. if (!isDynamicWidth()) {
  886. ComponentConnector paintable = ConnectorMap.get(client)
  887. .getConnector(this);
  888. DOM.setStyleAttribute(tabs, "width", paintable.getState().width);
  889. }
  890. // Make sure scrollerIndex is valid
  891. if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) {
  892. scrollerIndex = tb.getFirstVisibleTab();
  893. } else if (tb.getTabCount() > 0
  894. && tb.getTab(scrollerIndex).isHiddenOnServer()) {
  895. scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
  896. }
  897. boolean scrolled = isScrolledTabs();
  898. boolean clipped = isClippedTabs();
  899. if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
  900. DOM.setStyleAttribute(scroller, "display", "");
  901. DOM.setElementProperty(scrollerPrev, "className",
  902. SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
  903. DOM.setElementProperty(scrollerNext, "className",
  904. SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
  905. } else {
  906. DOM.setStyleAttribute(scroller, "display", "none");
  907. }
  908. if (BrowserInfo.get().isSafari()) {
  909. // fix tab height for safari, bugs sometimes if tabs contain icons
  910. String property = tabs.getStyle().getProperty("height");
  911. if (property == null || property.equals("")) {
  912. tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
  913. }
  914. /*
  915. * another hack for webkits. tabscroller sometimes drops without
  916. * "shaking it" reproducable in
  917. * com.vaadin.tests.components.tabsheet.TabSheetIcons
  918. */
  919. final Style style = scroller.getStyle();
  920. style.setProperty("whiteSpace", "normal");
  921. Scheduler.get().scheduleDeferred(new Command() {
  922. @Override
  923. public void execute() {
  924. style.setProperty("whiteSpace", "");
  925. }
  926. });
  927. }
  928. }
  929. /** For internal use only. May be removed or replaced in the future. */
  930. public void showAllTabs() {
  931. scrollerIndex = tb.getFirstVisibleTab();
  932. for (int i = 0; i < tb.getTabCount(); i++) {
  933. Tab t = tb.getTab(i);
  934. if (!t.isHiddenOnServer()) {
  935. t.setVisible(true);
  936. }
  937. }
  938. }
  939. private boolean isScrolledTabs() {
  940. return scrollerIndex > tb.getFirstVisibleTab();
  941. }
  942. private boolean isClippedTabs() {
  943. return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb
  944. .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth()
  945. - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
  946. }
  947. private boolean isClipped(Tab tab) {
  948. return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft()
  949. + getOffsetWidth() - scroller.getOffsetWidth();
  950. }
  951. @Override
  952. protected void clearPaintables() {
  953. int i = tb.getTabCount();
  954. while (i > 0) {
  955. tb.removeTab(--i);
  956. }
  957. tp.clear();
  958. }
  959. @Override
  960. public Iterator<Widget> getWidgetIterator() {
  961. return tp.iterator();
  962. }
  963. private int borderW = -1;
  964. /** For internal use only. May be removed or replaced in the future. */
  965. public int getContentAreaBorderWidth() {
  966. if (borderW < 0) {
  967. borderW = Util.measureHorizontalBorder(contentNode);
  968. }
  969. return borderW;
  970. }
  971. @Override
  972. public int getTabCount() {
  973. return tb.getTabCount();
  974. }
  975. @Override
  976. public ComponentConnector getTab(int index) {
  977. if (tp.getWidgetCount() > index) {
  978. Widget widget = tp.getWidget(index);
  979. return ConnectorMap.get(client).getConnector(widget);
  980. }
  981. return null;
  982. }
  983. @Override
  984. public void removeTab(int index) {
  985. tb.removeTab(index);
  986. // Removing content from tp is handled by the connector
  987. }
  988. @Override
  989. public void onBlur(BlurEvent event) {
  990. getApplicationConnection().getVTooltip().hideTooltip();
  991. if (focusedTab != null && event.getSource() instanceof Tab) {
  992. focusedTab.removeAssistiveDescription();
  993. focusedTab = null;
  994. if (client.hasEventListeners(this, EventId.BLUR)) {
  995. client.updateVariable(id, EventId.BLUR, "", true);
  996. }
  997. }
  998. }
  999. @Override
  1000. public void onFocus(FocusEvent event) {
  1001. if (focusedTab == null && event.getSource() instanceof Tab) {
  1002. focusedTab = (Tab) event.getSource();
  1003. if (client.hasEventListeners(this, EventId.FOCUS)) {
  1004. client.updateVariable(id, EventId.FOCUS, "", true);
  1005. }
  1006. if (focusedTab.hasTooltip()) {
  1007. focusedTab.setAssistiveDescription(getApplicationConnection()
  1008. .getVTooltip().getUniqueId());
  1009. getApplicationConnection().getVTooltip().showAssistive(
  1010. focusedTab.getTooltipInfo());
  1011. }
  1012. }
  1013. }
  1014. @Override
  1015. public void focus() {
  1016. tb.getTab(activeTabIndex).focus();
  1017. }
  1018. public void blur() {
  1019. tb.getTab(activeTabIndex).blur();
  1020. }
  1021. @Override
  1022. public void onKeyDown(KeyDownEvent event) {
  1023. if (event.getSource() instanceof Tab) {
  1024. int keycode = event.getNativeEvent().getKeyCode();
  1025. if (!event.isAnyModifierKeyDown()) {
  1026. if (keycode == getPreviousTabKey()) {
  1027. selectPreviousTab();
  1028. event.stopPropagation();
  1029. } else if (keycode == getNextTabKey()) {
  1030. selectNextTab();
  1031. event.stopPropagation();
  1032. } else if (keycode == getCloseTabKey()) {
  1033. Tab tab = tb.getTab(activeTabIndex);
  1034. if (tab.isClosable()) {
  1035. tab.onClose();
  1036. }
  1037. } else if (keycode == getSelectTabKey()) {
  1038. loadTabSheet(focusedTabIndex);
  1039. }
  1040. }
  1041. }
  1042. }
  1043. /**
  1044. * @return The key code of the keyboard shortcut that selects the previous
  1045. * tab in a focused tabsheet.
  1046. */
  1047. protected int getPreviousTabKey() {
  1048. return KeyCodes.KEY_LEFT;
  1049. }
  1050. protected int getSelectTabKey() {
  1051. return 32; // Space key
  1052. }
  1053. /**
  1054. * @return The key code of the keyboard shortcut that selects the next tab
  1055. * in a focused tabsheet.
  1056. */
  1057. protected int getNextTabKey() {
  1058. return KeyCodes.KEY_RIGHT;
  1059. }
  1060. /**
  1061. * @return The key code of the keyboard shortcut that closes the currently
  1062. * selected tab in a focused tabsheet.
  1063. */
  1064. protected int getCloseTabKey() {
  1065. return KeyCodes.KEY_DELETE;
  1066. }
  1067. private void selectPreviousTab() {
  1068. int newTabIndex = focusedTabIndex;
  1069. // Find the previous visible and enabled tab if any.
  1070. do {
  1071. newTabIndex--;
  1072. } while (newTabIndex >= 0 && !canSelectTab(newTabIndex));
  1073. if (newTabIndex >= 0) {
  1074. tb.navigateTab(focusedTabIndex, newTabIndex);
  1075. focusedTabIndex = newTabIndex;
  1076. // If this TabSheet already has focus, set the new selected tab
  1077. // as focused.
  1078. if (focusedTab != null) {
  1079. focusedTab = tb.getTab(focusedTabIndex);
  1080. focusedTab.focus();
  1081. }
  1082. if (isScrolledTabs()) {
  1083. // Scroll until the new active tab is visible
  1084. int newScrollerIndex = scrollerIndex;
  1085. while (tb.getTab(focusedTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
  1086. && newScrollerIndex != -1) {
  1087. newScrollerIndex = tb.scrollLeft(newScrollerIndex);
  1088. }
  1089. scrollerIndex = newScrollerIndex;
  1090. updateTabScroller();
  1091. }
  1092. }
  1093. }
  1094. private void selectNextTab() {
  1095. int newTabIndex = focusedTabIndex;
  1096. // Find the next visible and enabled tab if any.
  1097. do {
  1098. newTabIndex++;
  1099. } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex));
  1100. if (newTabIndex < getTabCount()) {
  1101. tb.navigateTab(focusedTabIndex, newTabIndex);
  1102. focusedTabIndex = newTabIndex;
  1103. // If this TabSheet already has focus, set the new selected tab
  1104. // as focused.
  1105. if (focusedTab != null) {
  1106. focusedTab = tb.getTab(focusedTabIndex);
  1107. focusedTab.focus();
  1108. }
  1109. if (isClippedTabs()) {
  1110. // Scroll until the new active tab is completely visible
  1111. int newScrollerIndex = scrollerIndex;
  1112. while (isClipped(tb.getTab(focusedTabIndex))
  1113. && newScrollerIndex != -1) {
  1114. newScrollerIndex = tb.scrollRight(newScrollerIndex);
  1115. }
  1116. scrollerIndex = newScrollerIndex;
  1117. updateTabScroller();
  1118. }
  1119. }
  1120. }
  1121. }