Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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