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 40KB

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