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.

TabSheet.java 42KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395
  1. /*
  2. * Copyright 2000-2014 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.ui;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.util.ArrayList;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.Iterator;
  23. import java.util.Map;
  24. import com.vaadin.event.FieldEvents.BlurEvent;
  25. import com.vaadin.event.FieldEvents.BlurListener;
  26. import com.vaadin.event.FieldEvents.BlurNotifier;
  27. import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
  28. import com.vaadin.event.FieldEvents.FocusEvent;
  29. import com.vaadin.event.FieldEvents.FocusListener;
  30. import com.vaadin.event.FieldEvents.FocusNotifier;
  31. import com.vaadin.server.ErrorMessage;
  32. import com.vaadin.server.KeyMapper;
  33. import com.vaadin.server.Resource;
  34. import com.vaadin.shared.ComponentConstants;
  35. import com.vaadin.shared.ui.tabsheet.TabState;
  36. import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc;
  37. import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc;
  38. import com.vaadin.shared.ui.tabsheet.TabsheetState;
  39. import com.vaadin.ui.Component.Focusable;
  40. import com.vaadin.ui.themes.Reindeer;
  41. import com.vaadin.ui.themes.Runo;
  42. /**
  43. * TabSheet component.
  44. *
  45. * Tabs are typically identified by the component contained on the tab (see
  46. * {@link ComponentContainer}), and tab metadata (including caption, icon,
  47. * visibility, enabledness, closability etc.) is kept in separate {@link Tab}
  48. * instances.
  49. *
  50. * Tabs added with {@link #addComponent(Component)} get the caption and the icon
  51. * of the component at the time when the component is created, and these are not
  52. * automatically updated after tab creation.
  53. *
  54. * A tab sheet can have multiple tab selection listeners and one tab close
  55. * handler ({@link CloseHandler}), which by default removes the tab from the
  56. * TabSheet.
  57. *
  58. * The {@link TabSheet} can be styled with the .v-tabsheet, .v-tabsheet-tabs and
  59. * .v-tabsheet-content styles. Themes may also have pre-defined variations of
  60. * the tab sheet presentation, such as {@link Reindeer#TABSHEET_BORDERLESS},
  61. * {@link Runo#TABSHEET_SMALL} and several other styles in {@link Reindeer}.
  62. *
  63. * The current implementation does not load the tabs to the UI before the first
  64. * time they are shown, but this may change in future releases.
  65. *
  66. * @author Vaadin Ltd.
  67. * @since 3.0
  68. */
  69. public class TabSheet extends AbstractComponentContainer implements Focusable,
  70. FocusNotifier, BlurNotifier, SelectiveRenderer {
  71. /**
  72. * Client to server RPC implementation for TabSheet.
  73. *
  74. * @since 7.2
  75. */
  76. protected class TabsheetServerRpcImpl implements TabsheetServerRpc {
  77. @Override
  78. public void setSelected(String key) {
  79. setSelectedTab(keyMapper.get(key));
  80. }
  81. @Override
  82. public void closeTab(String key) {
  83. final Component tab = keyMapper.get(key);
  84. if (tab != null) {
  85. closeHandler.onTabClose(TabSheet.this, tab);
  86. }
  87. }
  88. }
  89. /**
  90. * List of component tabs (tab contents). In addition to being on this list,
  91. * there is a {@link Tab} object in tabs for each tab with meta-data about
  92. * the tab.
  93. */
  94. private final ArrayList<Component> components = new ArrayList<Component>();
  95. /**
  96. * Map containing information related to the tabs (caption, icon etc).
  97. */
  98. private final HashMap<Component, Tab> tabs = new HashMap<Component, Tab>();
  99. /**
  100. * Selected tab content component.
  101. */
  102. private Component selected = null;
  103. /**
  104. * Mapper between server-side component instances (tab contents) and keys
  105. * given to the client that identify tabs.
  106. */
  107. private final KeyMapper<Component> keyMapper = new KeyMapper<Component>();
  108. /**
  109. * Handler to be called when a tab is closed.
  110. */
  111. private CloseHandler closeHandler;
  112. /**
  113. * Constructs a new TabSheet. A TabSheet is immediate by default, and the
  114. * default close handler removes the tab being closed.
  115. */
  116. public TabSheet() {
  117. super();
  118. registerRpc(rpc);
  119. registerRpc(focusBlurRpc);
  120. // expand horizontally by default
  121. setWidth(100, UNITS_PERCENTAGE);
  122. setImmediate(true);
  123. setCloseHandler(new CloseHandler() {
  124. @Override
  125. public void onTabClose(TabSheet tabsheet, Component c) {
  126. tabsheet.removeComponent(c);
  127. }
  128. });
  129. }
  130. /**
  131. * Constructs a new TabSheet containing the given components.
  132. *
  133. * @param components
  134. * The components to add to the tab sheet. Each component will be
  135. * added to a separate tab.
  136. */
  137. public TabSheet(Component... components) {
  138. this();
  139. addComponents(components);
  140. }
  141. /**
  142. * Gets the component container iterator for going through all the
  143. * components (tab contents).
  144. *
  145. * @return the unmodifiable Iterator of the tab content components
  146. */
  147. @Override
  148. public Iterator<Component> iterator() {
  149. return Collections.unmodifiableList(components).iterator();
  150. }
  151. /**
  152. * Gets the number of contained components (tabs). Consistent with the
  153. * iterator returned by {@link #getComponentIterator()}.
  154. *
  155. * @return the number of contained components
  156. */
  157. @Override
  158. public int getComponentCount() {
  159. return components.size();
  160. }
  161. /**
  162. * Removes a component and its corresponding tab.
  163. *
  164. * If the tab was selected, the first eligible (visible and enabled)
  165. * remaining tab is selected.
  166. *
  167. * @param component
  168. * the component to be removed.
  169. */
  170. @Override
  171. public void removeComponent(Component component) {
  172. if (component != null && components.contains(component)) {
  173. super.removeComponent(component);
  174. keyMapper.remove(component);
  175. components.remove(component);
  176. Tab removedTab = tabs.remove(component);
  177. getState().tabs
  178. .remove(((TabSheetTabImpl) removedTab).getTabState());
  179. if (component.equals(selected)) {
  180. if (components.isEmpty()) {
  181. setSelected(null);
  182. } else {
  183. // select the first enabled and visible tab, if any
  184. updateSelection();
  185. fireSelectedTabChange();
  186. }
  187. }
  188. markAsDirty();
  189. }
  190. }
  191. /**
  192. * Removes a {@link Tab} and the component associated with it, as previously
  193. * added with {@link #addTab(Component)},
  194. * {@link #addTab(Component, String, Resource)} or
  195. * {@link #addComponent(Component)}.
  196. * <p>
  197. * If the tab was selected, the first eligible (visible and enabled)
  198. * remaining tab is selected.
  199. * </p>
  200. *
  201. * @see #addTab(Component)
  202. * @see #addTab(Component, String, Resource)
  203. * @see #addComponent(Component)
  204. * @see #removeComponent(Component)
  205. * @param tab
  206. * the Tab to remove
  207. */
  208. public void removeTab(Tab tab) {
  209. removeComponent(tab.getComponent());
  210. }
  211. /**
  212. * Adds a new tab into TabSheet. Component caption and icon are copied to
  213. * the tab metadata at creation time.
  214. *
  215. * @see #addTab(Component)
  216. *
  217. * @param c
  218. * the component to be added.
  219. */
  220. @Override
  221. public void addComponent(Component c) {
  222. addTab(c);
  223. }
  224. /**
  225. * Adds a new tab into TabSheet.
  226. *
  227. * The first tab added to a tab sheet is automatically selected and a tab
  228. * selection event is fired.
  229. *
  230. * If the component is already present in the tab sheet, changes its caption
  231. * and returns the corresponding (old) tab, preserving other tab metadata.
  232. *
  233. * @param c
  234. * the component to be added onto tab - should not be null.
  235. * @param caption
  236. * the caption to be set for the component and used rendered in
  237. * tab bar
  238. * @return the created {@link Tab}
  239. */
  240. public Tab addTab(Component c, String caption) {
  241. return addTab(c, caption, null);
  242. }
  243. /**
  244. * Adds a new tab into TabSheet.
  245. *
  246. * The first tab added to a tab sheet is automatically selected and a tab
  247. * selection event is fired.
  248. *
  249. * If the component is already present in the tab sheet, changes its caption
  250. * and icon and returns the corresponding (old) tab, preserving other tab
  251. * metadata.
  252. *
  253. * @param c
  254. * the component to be added onto tab - should not be null.
  255. * @param caption
  256. * the caption to be set for the component and used rendered in
  257. * tab bar
  258. * @param icon
  259. * the icon to be set for the component and used rendered in tab
  260. * bar
  261. * @return the created {@link Tab}
  262. */
  263. public Tab addTab(Component c, String caption, Resource icon) {
  264. return addTab(c, caption, icon, components.size());
  265. }
  266. /**
  267. * Adds a new tab into TabSheet.
  268. *
  269. * The first tab added to a tab sheet is automatically selected and a tab
  270. * selection event is fired.
  271. *
  272. * If the component is already present in the tab sheet, changes its caption
  273. * and icon and returns the corresponding (old) tab, preserving other tab
  274. * metadata like the position.
  275. *
  276. * @param tabComponent
  277. * the component to be added onto tab - should not be null.
  278. * @param caption
  279. * the caption to be set for the component and used rendered in
  280. * tab bar
  281. * @param icon
  282. * the icon to be set for the component and used rendered in tab
  283. * bar
  284. * @param position
  285. * the position at where the the tab should be added.
  286. * @return the created {@link Tab}
  287. */
  288. public Tab addTab(Component tabComponent, String caption, Resource icon,
  289. int position) {
  290. if (tabComponent == null) {
  291. return null;
  292. } else if (tabs.containsKey(tabComponent)) {
  293. Tab tab = tabs.get(tabComponent);
  294. tab.setCaption(caption);
  295. tab.setIcon(icon);
  296. return tab;
  297. } else {
  298. components.add(position, tabComponent);
  299. TabSheetTabImpl tab = new TabSheetTabImpl(
  300. keyMapper.key(tabComponent), caption, icon);
  301. getState().tabs.add(position, tab.getTabState());
  302. tabs.put(tabComponent, tab);
  303. if (selected == null) {
  304. setSelected(tabComponent);
  305. fireSelectedTabChange();
  306. }
  307. super.addComponent(tabComponent);
  308. markAsDirty();
  309. return tab;
  310. }
  311. }
  312. /**
  313. * Adds a new tab into TabSheet. Component caption and icon are copied to
  314. * the tab metadata at creation time.
  315. *
  316. * If the tab sheet already contains the component, its tab is returned.
  317. *
  318. * @param c
  319. * the component to be added onto tab - should not be null.
  320. * @return the created {@link Tab}
  321. */
  322. public Tab addTab(Component c) {
  323. return addTab(c, components.size());
  324. }
  325. /**
  326. * Adds a new tab into TabSheet. Component caption and icon are copied to
  327. * the tab metadata at creation time.
  328. *
  329. * If the tab sheet already contains the component, its tab is returned.
  330. *
  331. * @param component
  332. * the component to be added onto tab - should not be null.
  333. * @param position
  334. * The position where the tab should be added
  335. * @return the created {@link Tab}
  336. */
  337. public Tab addTab(Component component, int position) {
  338. Tab result = tabs.get(component);
  339. if (result == null) {
  340. result = addTab(component, component.getCaption(),
  341. component.getIcon(), position);
  342. }
  343. return result;
  344. }
  345. /**
  346. * Moves all components from another container to this container. The
  347. * components are removed from the other container.
  348. *
  349. * If the source container is a {@link TabSheet}, component captions and
  350. * icons are copied from it.
  351. *
  352. * @param source
  353. * the container components are removed from.
  354. */
  355. @Override
  356. public void moveComponentsFrom(ComponentContainer source) {
  357. for (final Iterator<Component> i = source.getComponentIterator(); i
  358. .hasNext();) {
  359. final Component c = i.next();
  360. String caption = null;
  361. Resource icon = null;
  362. String iconAltText = "";
  363. if (TabSheet.class.isAssignableFrom(source.getClass())) {
  364. Tab tab = ((TabSheet) source).getTab(c);
  365. caption = tab.getCaption();
  366. icon = tab.getIcon();
  367. iconAltText = tab.getIconAlternateText();
  368. }
  369. source.removeComponent(c);
  370. Tab tab = addTab(c, caption, icon);
  371. tab.setIconAlternateText(iconAltText);
  372. }
  373. }
  374. /**
  375. * Are the tab selection parts ("tabs") hidden.
  376. *
  377. * @return true if the tabs are hidden in the UI
  378. */
  379. public boolean areTabsHidden() {
  380. return !getState(false).tabsVisible;
  381. }
  382. /**
  383. * Hides or shows the tab selection parts ("tabs").
  384. *
  385. * @param tabsHidden
  386. * true if the tabs should be hidden
  387. */
  388. public void hideTabs(boolean tabsHidden) {
  389. getState().tabsVisible = !tabsHidden;
  390. }
  391. /**
  392. * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
  393. * object can be used for setting caption,icon, etc for the tab.
  394. *
  395. * @param c
  396. * the component
  397. * @return The tab instance associated with the given component, or null if
  398. * the tabsheet does not contain the component.
  399. */
  400. public Tab getTab(Component c) {
  401. return tabs.get(c);
  402. }
  403. /**
  404. * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
  405. * object can be used for setting caption,icon, etc for the tab.
  406. *
  407. * @param position
  408. * the position of the tab
  409. * @return The tab in the given position, or null if the position is out of
  410. * bounds.
  411. */
  412. public Tab getTab(int position) {
  413. if (position >= 0 && position < getComponentCount()) {
  414. return getTab(components.get(position));
  415. } else {
  416. return null;
  417. }
  418. }
  419. /**
  420. * Sets the selected tab. The tab is identified by the tab content
  421. * component. Does nothing if the tabsheet doesn't contain the component.
  422. *
  423. * @param c
  424. */
  425. public void setSelectedTab(Component c) {
  426. if (c != null && components.contains(c) && !c.equals(selected)) {
  427. setSelected(c);
  428. updateSelection();
  429. fireSelectedTabChange();
  430. markAsDirty();
  431. getRpcProxy(TabsheetClientRpc.class).revertToSharedStateSelection();
  432. }
  433. }
  434. /**
  435. * Sets the selected tab in the TabSheet. Ensures that the selected tab is
  436. * repainted if needed.
  437. *
  438. * @param component
  439. * The new selection or null for no selection
  440. */
  441. private void setSelected(Component component) {
  442. Tab tab = tabs.get(selected);
  443. selected = component;
  444. // Repaint of the selected component is needed as only the selected
  445. // component is communicated to the client. Otherwise this will be a
  446. // "cached" update even though the client knows nothing about the
  447. // connector
  448. if (selected != null) {
  449. tab = getTab(component);
  450. if (tab != null && tab.getDefaultFocusComponent() != null) {
  451. tab.getDefaultFocusComponent().focus();
  452. }
  453. getState().selected = keyMapper.key(selected);
  454. selected.markAsDirtyRecursive();
  455. } else {
  456. getState().selected = null;
  457. }
  458. }
  459. /**
  460. * Sets the selected tab. The tab is identified by the corresponding
  461. * {@link Tab Tab} instance. Does nothing if the tabsheet doesn't contain
  462. * the given tab.
  463. *
  464. * @param tab
  465. */
  466. public void setSelectedTab(Tab tab) {
  467. if (tab != null) {
  468. setSelectedTab(tab.getComponent());
  469. }
  470. }
  471. /**
  472. * Sets the selected tab, identified by its position. Does nothing if the
  473. * position is out of bounds.
  474. *
  475. * @param position
  476. */
  477. public void setSelectedTab(int position) {
  478. setSelectedTab(getTab(position));
  479. }
  480. /**
  481. * Checks if the current selection is valid, and updates the selection if
  482. * the previously selected component is not visible and enabled. The first
  483. * visible and enabled tab is selected if the current selection is empty or
  484. * invalid.
  485. *
  486. * This method does not fire tab change events, but the caller should do so
  487. * if appropriate.
  488. *
  489. * @return true if selection was changed, false otherwise
  490. */
  491. private boolean updateSelection() {
  492. Component originalSelection = selected;
  493. for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) {
  494. final Component component = i.next();
  495. Tab tab = tabs.get(component);
  496. /*
  497. * If we have no selection, if the current selection is invisible or
  498. * if the current selection is disabled (but the whole component is
  499. * not) we select this tab instead
  500. */
  501. Tab selectedTabInfo = null;
  502. if (selected != null) {
  503. selectedTabInfo = tabs.get(selected);
  504. }
  505. if (selected == null || selectedTabInfo == null
  506. || !selectedTabInfo.isVisible()
  507. || !selectedTabInfo.isEnabled()) {
  508. // The current selection is not valid so we need to change
  509. // it
  510. if (tab.isEnabled() && tab.isVisible()) {
  511. setSelected(component);
  512. break;
  513. } else {
  514. /*
  515. * The current selection is not valid but this tab cannot be
  516. * selected either.
  517. */
  518. setSelected(null);
  519. }
  520. }
  521. }
  522. return originalSelection != selected;
  523. }
  524. /**
  525. * Gets the selected tab content component.
  526. *
  527. * @return the selected tab contents
  528. */
  529. public Component getSelectedTab() {
  530. return selected;
  531. }
  532. private TabsheetServerRpcImpl rpc = new TabsheetServerRpcImpl();
  533. private FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(
  534. this) {
  535. @Override
  536. protected void fireEvent(Event event) {
  537. TabSheet.this.fireEvent(event);
  538. }
  539. };
  540. /**
  541. * Replaces a component (tab content) with another. This can be used to
  542. * change tab contents or to rearrange tabs. The tab position and some
  543. * metadata are preserved when moving components within the same
  544. * {@link TabSheet}.
  545. *
  546. * If the oldComponent is not present in the tab sheet, the new one is added
  547. * at the end.
  548. *
  549. * If the oldComponent is already in the tab sheet but the newComponent
  550. * isn't, the old tab is replaced with a new one, and the caption and icon
  551. * of the old one are copied to the new tab.
  552. *
  553. * If both old and new components are present, their positions are swapped.
  554. *
  555. * {@inheritDoc}
  556. */
  557. @Override
  558. public void replaceComponent(Component oldComponent, Component newComponent) {
  559. boolean selectAfterInserting = false;
  560. if (selected == oldComponent) {
  561. selectAfterInserting = true;
  562. }
  563. Tab newTab = tabs.get(newComponent);
  564. Tab oldTab = tabs.get(oldComponent);
  565. // Gets the locations
  566. int oldLocation = -1;
  567. int newLocation = -1;
  568. int location = 0;
  569. for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
  570. final Component component = i.next();
  571. if (component == oldComponent) {
  572. oldLocation = location;
  573. }
  574. if (component == newComponent) {
  575. newLocation = location;
  576. }
  577. location++;
  578. }
  579. if (oldLocation == -1) {
  580. addComponent(newComponent);
  581. } else if (newLocation == -1) {
  582. removeComponent(oldComponent);
  583. newTab = addTab(newComponent, oldLocation);
  584. if (selectAfterInserting) {
  585. setSelected(newComponent);
  586. }
  587. // Copy all relevant metadata to the new tab (#8793)
  588. // TODO Should reuse the old tab instance instead?
  589. copyTabMetadata(oldTab, newTab);
  590. } else {
  591. components.set(oldLocation, newComponent);
  592. components.set(newLocation, oldComponent);
  593. if (selectAfterInserting) {
  594. setSelected(newComponent);
  595. }
  596. // Tab associations are not changed, but metadata is swapped between
  597. // the instances
  598. // TODO Should reassociate the instances instead?
  599. Tab tmp = new TabSheetTabImpl(null, null, null);
  600. copyTabMetadata(newTab, tmp);
  601. copyTabMetadata(oldTab, newTab);
  602. copyTabMetadata(tmp, oldTab);
  603. markAsDirty();
  604. }
  605. }
  606. /* Click event */
  607. private static final Method SELECTED_TAB_CHANGE_METHOD;
  608. static {
  609. try {
  610. SELECTED_TAB_CHANGE_METHOD = SelectedTabChangeListener.class
  611. .getDeclaredMethod("selectedTabChange",
  612. new Class[] { SelectedTabChangeEvent.class });
  613. } catch (final java.lang.NoSuchMethodException e) {
  614. // This should never happen
  615. throw new java.lang.RuntimeException(
  616. "Internal error finding methods in TabSheet");
  617. }
  618. }
  619. /**
  620. * Selected tab change event. This event is sent when the selected (shown)
  621. * tab in the tab sheet is changed.
  622. *
  623. * @author Vaadin Ltd.
  624. * @since 3.0
  625. */
  626. public static class SelectedTabChangeEvent extends Component.Event {
  627. /**
  628. * New instance of selected tab change event
  629. *
  630. * @param source
  631. * the Source of the event.
  632. */
  633. public SelectedTabChangeEvent(Component source) {
  634. super(source);
  635. }
  636. /**
  637. * TabSheet where the event occurred.
  638. *
  639. * @return the Source of the event.
  640. */
  641. public TabSheet getTabSheet() {
  642. return (TabSheet) getSource();
  643. }
  644. }
  645. /**
  646. * Selected tab change event listener. The listener is called whenever
  647. * another tab is selected, including when adding the first tab to a
  648. * tabsheet.
  649. *
  650. * @author Vaadin Ltd.
  651. *
  652. * @since 3.0
  653. */
  654. public interface SelectedTabChangeListener extends Serializable {
  655. /**
  656. * Selected (shown) tab in tab sheet has has been changed.
  657. *
  658. * @param event
  659. * the selected tab change event.
  660. */
  661. public void selectedTabChange(SelectedTabChangeEvent event);
  662. }
  663. /**
  664. * Adds a tab selection listener
  665. *
  666. * @param listener
  667. * the Listener to be added.
  668. */
  669. public void addSelectedTabChangeListener(SelectedTabChangeListener listener) {
  670. addListener(SelectedTabChangeEvent.class, listener,
  671. SELECTED_TAB_CHANGE_METHOD);
  672. }
  673. /**
  674. * @deprecated As of 7.0, replaced by
  675. * {@link #addSelectedTabChangeListener(SelectedTabChangeListener)}
  676. **/
  677. @Deprecated
  678. public void addListener(SelectedTabChangeListener listener) {
  679. addSelectedTabChangeListener(listener);
  680. }
  681. /**
  682. * Removes a tab selection listener
  683. *
  684. * @param listener
  685. * the Listener to be removed.
  686. */
  687. public void removeSelectedTabChangeListener(
  688. SelectedTabChangeListener listener) {
  689. removeListener(SelectedTabChangeEvent.class, listener,
  690. SELECTED_TAB_CHANGE_METHOD);
  691. }
  692. /**
  693. * @deprecated As of 7.0, replaced by
  694. * {@link #removeSelectedTabChangeListener(SelectedTabChangeListener)}
  695. **/
  696. @Deprecated
  697. public void removeListener(SelectedTabChangeListener listener) {
  698. removeSelectedTabChangeListener(listener);
  699. }
  700. /**
  701. * Sends an event that the currently selected tab has changed.
  702. */
  703. protected void fireSelectedTabChange() {
  704. fireEvent(new SelectedTabChangeEvent(this));
  705. }
  706. /**
  707. * Tab meta-data for a component in a {@link TabSheet}.
  708. *
  709. * The meta-data includes the tab caption, icon, visibility and enabledness,
  710. * closability, description (tooltip) and an optional component error shown
  711. * in the tab.
  712. *
  713. * Tabs are identified by the component contained on them in most cases, and
  714. * the meta-data can be obtained with {@link TabSheet#getTab(Component)}.
  715. */
  716. public interface Tab extends Serializable {
  717. /**
  718. * Returns the visible status for the tab. An invisible tab is not shown
  719. * in the tab bar and cannot be selected.
  720. *
  721. * @return true for visible, false for hidden
  722. */
  723. public boolean isVisible();
  724. /**
  725. * Sets the visible status for the tab. An invisible tab is not shown in
  726. * the tab bar and cannot be selected, selection is changed
  727. * automatically when there is an attempt to select an invisible tab.
  728. *
  729. * @param visible
  730. * true for visible, false for hidden
  731. */
  732. public void setVisible(boolean visible);
  733. /**
  734. * Returns the closability status for the tab.
  735. *
  736. * @return true if the tab is allowed to be closed by the end user,
  737. * false for not allowing closing
  738. */
  739. public boolean isClosable();
  740. /**
  741. * Sets the closability status for the tab. A closable tab can be closed
  742. * by the user through the user interface. This also controls if a close
  743. * button is shown to the user or not.
  744. * <p>
  745. * Note! Currently only supported by TabSheet, not Accordion.
  746. * </p>
  747. *
  748. * @param closable
  749. * true if the end user is allowed to close the tab, false
  750. * for not allowing to close. Should default to false.
  751. */
  752. public void setClosable(boolean closable);
  753. /**
  754. * Set the component that should automatically focused when the tab is
  755. * selected.
  756. *
  757. * @param component
  758. * the component to focus
  759. */
  760. public void setDefaultFocusComponent(Focusable component);
  761. /**
  762. * Get the component that should be automatically focused when the tab
  763. * is selected.
  764. *
  765. * @return the focusable component
  766. */
  767. public Focusable getDefaultFocusComponent();
  768. /**
  769. * Returns the enabled status for the tab. A disabled tab is shown as
  770. * such in the tab bar and cannot be selected.
  771. *
  772. * @return true for enabled, false for disabled
  773. */
  774. public boolean isEnabled();
  775. /**
  776. * Sets the enabled status for the tab. A disabled tab is shown as such
  777. * in the tab bar and cannot be selected.
  778. *
  779. * @param enabled
  780. * true for enabled, false for disabled
  781. */
  782. public void setEnabled(boolean enabled);
  783. /**
  784. * Sets the caption for the tab.
  785. *
  786. * @param caption
  787. * the caption to set
  788. */
  789. public void setCaption(String caption);
  790. /**
  791. * Gets the caption for the tab.
  792. */
  793. public String getCaption();
  794. /**
  795. * Gets the icon for the tab.
  796. */
  797. public Resource getIcon();
  798. /**
  799. * Sets the icon for the tab.
  800. *
  801. * @param icon
  802. * the icon to set
  803. */
  804. public void setIcon(Resource icon);
  805. /**
  806. * Sets the icon and alt text for the tab.
  807. *
  808. * @param icon
  809. * the icon to set
  810. */
  811. public void setIcon(Resource icon, String iconAltText);
  812. /**
  813. * Gets the icon alt text for the tab.
  814. *
  815. * @since 7.2
  816. */
  817. public String getIconAlternateText();
  818. /**
  819. * Sets the icon alt text for the tab.
  820. *
  821. * @since 7.2
  822. *
  823. * @param iconAltText
  824. * the icon to set
  825. */
  826. public void setIconAlternateText(String iconAltText);
  827. /**
  828. * Gets the description for the tab. The description can be used to
  829. * briefly describe the state of the tab to the user, and is typically
  830. * shown as a tooltip when hovering over the tab.
  831. *
  832. * @return the description for the tab
  833. */
  834. public String getDescription();
  835. /**
  836. * Sets the description for the tab. The description can be used to
  837. * briefly describe the state of the tab to the user, and is typically
  838. * shown as a tooltip when hovering over the tab.
  839. *
  840. * @param description
  841. * the new description string for the tab.
  842. */
  843. public void setDescription(String description);
  844. /**
  845. * Sets an error indicator to be shown in the tab. This can be used e.g.
  846. * to communicate to the user that there is a problem in the contents of
  847. * the tab.
  848. *
  849. * @see AbstractComponent#setComponentError(ErrorMessage)
  850. *
  851. * @param componentError
  852. * error message or null for none
  853. */
  854. public void setComponentError(ErrorMessage componentError);
  855. /**
  856. * Gets the current error message shown for the tab.
  857. *
  858. * TODO currently not sent to the client
  859. *
  860. * @see AbstractComponent#setComponentError(ErrorMessage)
  861. */
  862. public ErrorMessage getComponentError();
  863. /**
  864. * Get the component related to the Tab
  865. */
  866. public Component getComponent();
  867. /**
  868. * Sets a style name for the tab. The style name will be rendered as a
  869. * HTML class name, which can be used in a CSS definition.
  870. *
  871. * <pre>
  872. * Tab tab = tabsheet.addTab(tabContent, &quot;Tab text&quot;);
  873. * tab.setStyleName(&quot;mystyle&quot;);
  874. * </pre>
  875. * <p>
  876. * The used style name will be prefixed with "
  877. * {@code v-tabsheet-tabitemcell-}". For example, if you give a tab the
  878. * style "{@code mystyle}", the tab will get a "
  879. * {@code v-tabsheet-tabitemcell-mystyle}" style. You could then style
  880. * the component with:
  881. * </p>
  882. *
  883. * <pre>
  884. * .v-tabsheet-tabitemcell-mystyle {font-style: italic;}
  885. * </pre>
  886. *
  887. * <p>
  888. * This method will trigger a {@link RepaintRequestEvent} on the
  889. * TabSheet to which the Tab belongs.
  890. * </p>
  891. *
  892. * @param styleName
  893. * the new style to be set for tab
  894. * @see #getStyleName()
  895. */
  896. public void setStyleName(String styleName);
  897. /**
  898. * Gets the user-defined CSS style name of the tab. Built-in style names
  899. * defined in Vaadin or GWT are not returned.
  900. *
  901. * @return the style name or of the tab
  902. * @see #setStyleName(String)
  903. */
  904. public String getStyleName();
  905. /**
  906. * Adds an unique id for component that is used in the client-side for
  907. * testing purposes. Keeping identifiers unique is the responsibility of
  908. * the programmer.
  909. *
  910. * @param id
  911. * An alphanumeric id
  912. */
  913. public void setId(String id);
  914. /**
  915. * Gets currently set debug identifier
  916. *
  917. * @return current id, null if not set
  918. */
  919. public String getId();
  920. }
  921. /**
  922. * TabSheet's implementation of {@link Tab} - tab metadata.
  923. */
  924. public class TabSheetTabImpl implements Tab {
  925. private TabState tabState;
  926. private Focusable defaultFocus;
  927. private ErrorMessage componentError;
  928. public TabSheetTabImpl(String key, String caption, Resource icon) {
  929. tabState = new TabState();
  930. if (caption == null) {
  931. caption = "";
  932. }
  933. tabState.key = key;
  934. tabState.caption = caption;
  935. setIcon(icon);
  936. }
  937. /**
  938. * Returns the tab caption. Can never be null.
  939. */
  940. @Override
  941. public String getCaption() {
  942. return tabState.caption;
  943. }
  944. @Override
  945. public void setCaption(String caption) {
  946. tabState.caption = caption;
  947. markAsDirty();
  948. }
  949. @Override
  950. public Resource getIcon() {
  951. return getResource(ComponentConstants.ICON_RESOURCE + tabState.key);
  952. }
  953. @Override
  954. public void setIcon(Resource icon) {
  955. // this might not be ideal (resetting icon altText), but matches
  956. // previous semantics
  957. setIcon(icon, "");
  958. }
  959. @Override
  960. public String getIconAlternateText() {
  961. return tabState.iconAltText;
  962. }
  963. @Override
  964. public void setIconAlternateText(String iconAltText) {
  965. tabState.iconAltText = iconAltText;
  966. markAsDirty();
  967. }
  968. @Override
  969. public void setDefaultFocusComponent(Focusable defaultFocus) {
  970. this.defaultFocus = defaultFocus;
  971. }
  972. @Override
  973. public Focusable getDefaultFocusComponent() {
  974. return defaultFocus;
  975. }
  976. @Override
  977. public boolean isEnabled() {
  978. return tabState.enabled;
  979. }
  980. @Override
  981. public void setEnabled(boolean enabled) {
  982. tabState.enabled = enabled;
  983. if (updateSelection()) {
  984. fireSelectedTabChange();
  985. }
  986. markAsDirty();
  987. }
  988. @Override
  989. public boolean isVisible() {
  990. return tabState.visible;
  991. }
  992. @Override
  993. public void setVisible(boolean visible) {
  994. tabState.visible = visible;
  995. if (updateSelection()) {
  996. fireSelectedTabChange();
  997. }
  998. markAsDirty();
  999. }
  1000. @Override
  1001. public boolean isClosable() {
  1002. return tabState.closable;
  1003. }
  1004. @Override
  1005. public void setClosable(boolean closable) {
  1006. tabState.closable = closable;
  1007. markAsDirty();
  1008. }
  1009. @Override
  1010. public String getDescription() {
  1011. return tabState.description;
  1012. }
  1013. @Override
  1014. public void setDescription(String description) {
  1015. tabState.description = description;
  1016. markAsDirty();
  1017. }
  1018. @Override
  1019. public ErrorMessage getComponentError() {
  1020. return componentError;
  1021. }
  1022. @Override
  1023. public void setComponentError(ErrorMessage componentError) {
  1024. this.componentError = componentError;
  1025. String formattedHtmlMessage = componentError != null ? componentError
  1026. .getFormattedHtmlMessage() : null;
  1027. tabState.componentError = formattedHtmlMessage;
  1028. markAsDirty();
  1029. }
  1030. @Override
  1031. public Component getComponent() {
  1032. for (Map.Entry<Component, Tab> entry : tabs.entrySet()) {
  1033. if (entry.getValue() == this) {
  1034. return entry.getKey();
  1035. }
  1036. }
  1037. return null;
  1038. }
  1039. @Override
  1040. public void setStyleName(String styleName) {
  1041. tabState.styleName = styleName;
  1042. markAsDirty();
  1043. }
  1044. @Override
  1045. public String getStyleName() {
  1046. return tabState.styleName;
  1047. }
  1048. protected TabState getTabState() {
  1049. return tabState;
  1050. }
  1051. @Override
  1052. public void setId(String id) {
  1053. tabState.id = id;
  1054. markAsDirty();
  1055. }
  1056. @Override
  1057. public String getId() {
  1058. return tabState.id;
  1059. }
  1060. @Override
  1061. public void setIcon(Resource icon, String iconAltText) {
  1062. setResource(ComponentConstants.ICON_RESOURCE + tabState.key, icon);
  1063. tabState.iconAltText = iconAltText;
  1064. }
  1065. }
  1066. /**
  1067. * CloseHandler is used to process tab closing events. Default behavior is
  1068. * to remove the tab from the TabSheet.
  1069. *
  1070. * @author Jouni Koivuviita / Vaadin Ltd.
  1071. * @since 6.2.0
  1072. *
  1073. */
  1074. public interface CloseHandler extends Serializable {
  1075. /**
  1076. * Called when a user has pressed the close icon of a tab in the client
  1077. * side widget.
  1078. *
  1079. * @param tabsheet
  1080. * the TabSheet to which the tab belongs to
  1081. * @param tabContent
  1082. * the component that corresponds to the tab whose close
  1083. * button was clicked
  1084. */
  1085. void onTabClose(final TabSheet tabsheet, final Component tabContent);
  1086. }
  1087. /**
  1088. * Provide a custom {@link CloseHandler} for this TabSheet if you wish to
  1089. * perform some additional tasks when a user clicks on a tabs close button,
  1090. * e.g. show a confirmation dialogue before removing the tab.
  1091. *
  1092. * To remove the tab, if you provide your own close handler, you must call
  1093. * {@link #removeComponent(Component)} yourself.
  1094. *
  1095. * The default CloseHandler for TabSheet will only remove the tab.
  1096. *
  1097. * @param handler
  1098. */
  1099. public void setCloseHandler(CloseHandler handler) {
  1100. closeHandler = handler;
  1101. }
  1102. /**
  1103. * Sets the position of the tab.
  1104. *
  1105. * @param tab
  1106. * The tab
  1107. * @param position
  1108. * The new position of the tab
  1109. */
  1110. public void setTabPosition(Tab tab, int position) {
  1111. int oldPosition = getTabPosition(tab);
  1112. components.remove(oldPosition);
  1113. components.add(position, tab.getComponent());
  1114. getState().tabs.remove(oldPosition);
  1115. getState().tabs.add(position, ((TabSheetTabImpl) tab).getTabState());
  1116. }
  1117. /**
  1118. * Gets the position of the tab
  1119. *
  1120. * @param tab
  1121. * The tab
  1122. * @return
  1123. */
  1124. public int getTabPosition(Tab tab) {
  1125. return components.indexOf(tab.getComponent());
  1126. }
  1127. @Override
  1128. public void focus() {
  1129. super.focus();
  1130. }
  1131. @Override
  1132. public int getTabIndex() {
  1133. return getState(false).tabIndex;
  1134. }
  1135. @Override
  1136. public void setTabIndex(int tabIndex) {
  1137. getState().tabIndex = tabIndex;
  1138. }
  1139. @Override
  1140. public void addBlurListener(BlurListener listener) {
  1141. addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  1142. BlurListener.blurMethod);
  1143. }
  1144. /**
  1145. * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)}
  1146. **/
  1147. @Override
  1148. @Deprecated
  1149. public void addListener(BlurListener listener) {
  1150. addBlurListener(listener);
  1151. }
  1152. @Override
  1153. public void removeBlurListener(BlurListener listener) {
  1154. removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
  1155. }
  1156. /**
  1157. * @deprecated As of 7.0, replaced by
  1158. * {@link #removeBlurListener(BlurListener)}
  1159. **/
  1160. @Override
  1161. @Deprecated
  1162. public void removeListener(BlurListener listener) {
  1163. removeBlurListener(listener);
  1164. }
  1165. @Override
  1166. public void addFocusListener(FocusListener listener) {
  1167. addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  1168. FocusListener.focusMethod);
  1169. }
  1170. /**
  1171. * @deprecated As of 7.0, replaced by
  1172. * {@link #addFocusListener(FocusListener)}
  1173. **/
  1174. @Override
  1175. @Deprecated
  1176. public void addListener(FocusListener listener) {
  1177. addFocusListener(listener);
  1178. }
  1179. @Override
  1180. public void removeFocusListener(FocusListener listener) {
  1181. removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
  1182. }
  1183. /**
  1184. * @deprecated As of 7.0, replaced by
  1185. * {@link #removeFocusListener(FocusListener)}
  1186. **/
  1187. @Override
  1188. @Deprecated
  1189. public void removeListener(FocusListener listener) {
  1190. removeFocusListener(listener);
  1191. }
  1192. @Override
  1193. public boolean isRendered(Component childComponent) {
  1194. return childComponent == getSelectedTab();
  1195. }
  1196. /**
  1197. * Copies properties from one Tab to another.
  1198. *
  1199. * @param from
  1200. * The tab whose data to copy.
  1201. * @param to
  1202. * The tab to which copy the data.
  1203. */
  1204. private static void copyTabMetadata(Tab from, Tab to) {
  1205. to.setCaption(from.getCaption());
  1206. to.setIcon(from.getIcon(), from.getIconAlternateText());
  1207. to.setDescription(from.getDescription());
  1208. to.setVisible(from.isVisible());
  1209. to.setEnabled(from.isEnabled());
  1210. to.setClosable(from.isClosable());
  1211. to.setStyleName(from.getStyleName());
  1212. to.setComponentError(from.getComponentError());
  1213. }
  1214. @Override
  1215. protected TabsheetState getState(boolean markAsDirty) {
  1216. return (TabsheetState) super.getState(markAsDirty);
  1217. }
  1218. @Override
  1219. protected TabsheetState getState() {
  1220. return (TabsheetState) super.getState();
  1221. }
  1222. }