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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.ui;
  5. import java.io.Serializable;
  6. import java.lang.reflect.Method;
  7. import java.util.Collection;
  8. import java.util.Collections;
  9. import java.util.HashMap;
  10. import java.util.HashSet;
  11. import java.util.Iterator;
  12. import java.util.LinkedList;
  13. import java.util.Map;
  14. import com.vaadin.terminal.ErrorMessage;
  15. import com.vaadin.terminal.KeyMapper;
  16. import com.vaadin.terminal.PaintException;
  17. import com.vaadin.terminal.PaintTarget;
  18. import com.vaadin.terminal.Resource;
  19. import com.vaadin.terminal.gwt.client.ui.VTabsheet;
  20. import com.vaadin.terminal.gwt.server.CommunicationManager;
  21. import com.vaadin.ui.TabSheet.CloseHandler;
  22. import com.vaadin.ui.TabSheet.Tab;
  23. import com.vaadin.ui.themes.Reindeer;
  24. import com.vaadin.ui.themes.Runo;
  25. /**
  26. * TabSheet component.
  27. *
  28. * Tabs are typically identified by the component contained on the tab (see
  29. * {@link ComponentContainer}), and tab metadata (including caption, icon,
  30. * visibility, enabledness, closability etc.) is kept in separate {@link Tab}
  31. * instances.
  32. *
  33. * Tabs added with {@link #addComponent(Component)} get the caption and the icon
  34. * of the component at the time when the component is created, and these are not
  35. * automatically updated after tab creation.
  36. *
  37. * A tab sheet can have multiple tab selection listeners and one tab close
  38. * handler ({@link CloseHandler}), which by default removes the tab from the
  39. * TabSheet.
  40. *
  41. * The {@link TabSheet} can be styled with the .v-tabsheet, .v-tabsheet-tabs and
  42. * .v-tabsheet-content styles. Themes may also have pre-defined variations of
  43. * the tab sheet presentation, such as {@link Reindeer#TABSHEET_BORDERLESS},
  44. * {@link Runo#TABSHEET_SMALL} and several other styles in {@link Reindeer}.
  45. *
  46. * The current implementation does not load the tabs to the UI before the first
  47. * time they are shown, but this may change in future releases.
  48. *
  49. * @author IT Mill Ltd.
  50. * @version
  51. * @VERSION@
  52. * @since 3.0
  53. */
  54. @SuppressWarnings("serial")
  55. @ClientWidget(VTabsheet.class)
  56. public class TabSheet extends AbstractComponentContainer {
  57. /**
  58. * List of component tabs (tab contents). In addition to being on this list,
  59. * there is a {@link Tab} object in tabs for each tab with meta-data about
  60. * the tab.
  61. */
  62. private final LinkedList<Component> components = new LinkedList<Component>();
  63. /**
  64. * Map containing information related to the tabs (caption, icon etc).
  65. */
  66. private final HashMap<Component, Tab> tabs = new HashMap<Component, Tab>();
  67. /**
  68. * Selected tab content component.
  69. */
  70. private Component selected = null;
  71. /**
  72. * Mapper between server-side component instances (tab contents) and keys
  73. * given to the client that identify tabs.
  74. */
  75. private final KeyMapper keyMapper = new KeyMapper();
  76. /**
  77. * When true, the tab selection area is not displayed to the user.
  78. */
  79. private boolean tabsHidden;
  80. /**
  81. * Tabs that have been shown to the user (have been painted as selected).
  82. */
  83. private HashSet<Component> paintedTabs = new HashSet<Component>();
  84. /**
  85. * Handler to be called when a tab is closed.
  86. */
  87. private CloseHandler closeHandler;
  88. /**
  89. * Constructs a new Tabsheet. Tabsheet is immediate by default, and the
  90. * default close handler removes the tab being closed.
  91. */
  92. public TabSheet() {
  93. super();
  94. // expand horizontally by default
  95. setWidth(100, UNITS_PERCENTAGE);
  96. setImmediate(true);
  97. setCloseHandler(new CloseHandler() {
  98. public void onTabClose(TabSheet tabsheet, Component c) {
  99. tabsheet.removeComponent(c);
  100. }
  101. });
  102. }
  103. /**
  104. * Gets the component container iterator for going through all the
  105. * components (tab contents).
  106. *
  107. * @return the unmodifiable Iterator of the tab content components
  108. */
  109. public Iterator<Component> getComponentIterator() {
  110. return Collections.unmodifiableList(components).iterator();
  111. }
  112. /**
  113. * Removes a component and its corresponding tab.
  114. *
  115. * If the tab was selected, the first eligible (visible and enabled)
  116. * remaining tab is selected.
  117. *
  118. * @param c
  119. * the component to be removed.
  120. */
  121. @Override
  122. public void removeComponent(Component c) {
  123. if (c != null && components.contains(c)) {
  124. super.removeComponent(c);
  125. keyMapper.remove(c);
  126. components.remove(c);
  127. tabs.remove(c);
  128. if (c.equals(selected)) {
  129. if (components.isEmpty()) {
  130. selected = null;
  131. } else {
  132. // select the first enabled and visible tab, if any
  133. updateSelection();
  134. fireSelectedTabChange();
  135. }
  136. }
  137. requestRepaint();
  138. }
  139. }
  140. /**
  141. * Removes a {@link Tab} and the component associated with it, as previously
  142. * added with {@link #addTab(Component)},
  143. * {@link #addTab(Component, String, Resource)} or
  144. * {@link #addComponent(Component)}.
  145. * <p>
  146. * If the tab was selected, the first eligible (visible and enabled)
  147. * remaining tab is selected.
  148. * </p>
  149. *
  150. * @see #addTab(Component)
  151. * @see #addTab(Component, String, Resource)
  152. * @see #addComponent(Component)
  153. * @see #removeComponent(Component)
  154. * @param tab
  155. * the Tab to remove
  156. */
  157. public void removeTab(Tab tab) {
  158. for (Component c : tabs.keySet()) {
  159. if (tabs.get(c).equals(tab)) {
  160. removeComponent(c);
  161. break;
  162. }
  163. }
  164. }
  165. /**
  166. * Adds a new tab into TabSheet. Component caption and icon are copied to
  167. * the tab metadata at creation time.
  168. *
  169. * @see #addTab(Component)
  170. *
  171. * @param c
  172. * the component to be added.
  173. */
  174. @Override
  175. public void addComponent(Component c) {
  176. addTab(c);
  177. }
  178. /**
  179. * Adds a new tab into TabSheet.
  180. *
  181. * The first tab added to a tab sheet is automatically selected and a tab
  182. * selection event is fired.
  183. *
  184. * If the component is already present in the tab sheet, changes its caption
  185. * and icon and returns the corresponding (old) tab, preserving other tab
  186. * metadata.
  187. *
  188. * @param c
  189. * the component to be added onto tab - should not be null.
  190. * @param caption
  191. * the caption to be set for the component and used rendered in
  192. * tab bar
  193. * @param icon
  194. * the icon to be set for the component and used rendered in tab
  195. * bar
  196. * @return the created {@link Tab}
  197. */
  198. public Tab addTab(Component c, String caption, Resource icon) {
  199. if (c == null) {
  200. return null;
  201. } else if (tabs.containsKey(c)) {
  202. Tab tab = tabs.get(c);
  203. tab.setCaption(caption);
  204. tab.setIcon(icon);
  205. return tab;
  206. } else {
  207. components.addLast(c);
  208. Tab tab = new TabSheetTabImpl(caption, icon);
  209. tabs.put(c, tab);
  210. if (selected == null) {
  211. selected = c;
  212. fireSelectedTabChange();
  213. }
  214. super.addComponent(c);
  215. requestRepaint();
  216. return tab;
  217. }
  218. }
  219. /**
  220. * Adds a new tab into TabSheet. Component caption and icon are copied to
  221. * the tab metadata at creation time.
  222. *
  223. * If the tab sheet already contains the component, its tab is returned.
  224. *
  225. * @param c
  226. * the component to be added onto tab - should not be null.
  227. * @return the created {@link Tab}
  228. */
  229. public Tab addTab(Component c) {
  230. if (c == null) {
  231. return null;
  232. } else if (tabs.containsKey(c)) {
  233. return tabs.get(c);
  234. } else {
  235. return addTab(c, c.getCaption(), c.getIcon());
  236. }
  237. }
  238. /**
  239. * Moves all components from another container to this container. The
  240. * components are removed from the other container.
  241. *
  242. * If the source container is a {@link TabSheet}, component captions and
  243. * icons are copied from it.
  244. *
  245. * @param source
  246. * the container components are removed from.
  247. */
  248. @Override
  249. public void moveComponentsFrom(ComponentContainer source) {
  250. for (final Iterator<Component> i = source.getComponentIterator(); i
  251. .hasNext();) {
  252. final Component c = i.next();
  253. String caption = null;
  254. Resource icon = null;
  255. if (TabSheet.class.isAssignableFrom(source.getClass())) {
  256. caption = ((TabSheet) source).getTabCaption(c);
  257. icon = ((TabSheet) source).getTabIcon(c);
  258. }
  259. source.removeComponent(c);
  260. addTab(c, caption, icon);
  261. }
  262. }
  263. /**
  264. * Paints the content of this component.
  265. *
  266. * @param target
  267. * the paint target
  268. * @throws PaintException
  269. * if the paint operation failed.
  270. */
  271. @Override
  272. public void paintContent(PaintTarget target) throws PaintException {
  273. if (areTabsHidden()) {
  274. target.addAttribute("hidetabs", true);
  275. }
  276. target.startTag("tabs");
  277. Collection<Component> orphaned = new HashSet<Component>(paintedTabs);
  278. for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) {
  279. final Component component = i.next();
  280. orphaned.remove(component);
  281. Tab tab = tabs.get(component);
  282. target.startTag("tab");
  283. if (!tab.isEnabled() && tab.isVisible()) {
  284. target.addAttribute("disabled", true);
  285. }
  286. if (!tab.isVisible()) {
  287. target.addAttribute("hidden", true);
  288. }
  289. if (tab.isClosable()) {
  290. target.addAttribute("closable", true);
  291. }
  292. final Resource icon = tab.getIcon();
  293. if (icon != null) {
  294. target.addAttribute("icon", icon);
  295. }
  296. final String caption = tab.getCaption();
  297. if (caption != null && caption.length() > 0) {
  298. target.addAttribute("caption", caption);
  299. }
  300. final String description = tab.getDescription();
  301. if (description != null) {
  302. target.addAttribute("description", description);
  303. }
  304. final ErrorMessage componentError = tab.getComponentError();
  305. if (componentError != null) {
  306. componentError.paint(target);
  307. }
  308. target.addAttribute("key", keyMapper.key(component));
  309. if (component.equals(selected)) {
  310. target.addAttribute("selected", true);
  311. component.paint(target);
  312. paintedTabs.add(component);
  313. } else if (paintedTabs.contains(component)) {
  314. component.paint(target);
  315. } else {
  316. component.requestRepaintRequests();
  317. }
  318. target.endTag("tab");
  319. }
  320. target.endTag("tabs");
  321. if (selected != null) {
  322. target.addVariable(this, "selected", keyMapper.key(selected));
  323. }
  324. // clean possibly orphaned entries in paintedTabs
  325. for (Component component2 : orphaned) {
  326. paintedTabs.remove(component2);
  327. }
  328. }
  329. /**
  330. * Are the tab selection parts ("tabs") hidden.
  331. *
  332. * @return true if the tabs are hidden in the UI
  333. */
  334. public boolean areTabsHidden() {
  335. return tabsHidden;
  336. }
  337. /**
  338. * Hides or shows the tab selection parts ("tabs").
  339. *
  340. * @param tabsHidden
  341. * true if the tabs should be hidden
  342. */
  343. public void hideTabs(boolean tabsHidden) {
  344. this.tabsHidden = tabsHidden;
  345. requestRepaint();
  346. }
  347. /**
  348. * Gets tab caption. The tab is identified by the tab content component.
  349. *
  350. * @param c
  351. * the component in the tab
  352. * @deprecated Use {@link #getTab(Component)} and {@link Tab#getCaption()}
  353. * instead.
  354. */
  355. @Deprecated
  356. public String getTabCaption(Component c) {
  357. Tab info = tabs.get(c);
  358. if (info == null) {
  359. return "";
  360. } else {
  361. return info.getCaption();
  362. }
  363. }
  364. /**
  365. * Sets tab caption. The tab is identified by the tab content component.
  366. *
  367. * @param c
  368. * the component in the tab
  369. * @param caption
  370. * the caption to set.
  371. * @deprecated Use {@link #getTab(Component)} and
  372. * {@link Tab#setCaption(String)} instead.
  373. */
  374. @Deprecated
  375. public void setTabCaption(Component c, String caption) {
  376. Tab info = tabs.get(c);
  377. if (info != null) {
  378. info.setCaption(caption);
  379. requestRepaint();
  380. }
  381. }
  382. /**
  383. * Gets the icon for a tab. The tab is identified by the tab content
  384. * component.
  385. *
  386. * @param c
  387. * the component in the tab
  388. * @deprecated Use {@link #getTab(Component)} and {@link Tab#getIcon()}
  389. * instead.
  390. */
  391. @Deprecated
  392. public Resource getTabIcon(Component c) {
  393. Tab info = tabs.get(c);
  394. if (info == null) {
  395. return null;
  396. } else {
  397. return info.getIcon();
  398. }
  399. }
  400. /**
  401. * Sets icon for the given component. The tab is identified by the tab
  402. * content component.
  403. *
  404. * @param c
  405. * the component in the tab
  406. * @param icon
  407. * the icon to set
  408. * @deprecated Use {@link #getTab(Component)} and
  409. * {@link Tab#setIcon(Resource)} instead.
  410. */
  411. @Deprecated
  412. public void setTabIcon(Component c, Resource icon) {
  413. Tab info = tabs.get(c);
  414. if (info != null) {
  415. info.setIcon(icon);
  416. requestRepaint();
  417. }
  418. }
  419. /**
  420. * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
  421. * object can be used for setting caption,icon, etc for the tab.
  422. *
  423. * @param c
  424. * the component
  425. * @return
  426. */
  427. public Tab getTab(Component c) {
  428. return tabs.get(c);
  429. }
  430. /**
  431. * Sets the selected tab. The tab is identified by the tab content
  432. * component.
  433. *
  434. * @param c
  435. */
  436. public void setSelectedTab(Component c) {
  437. if (c != null && components.contains(c) && !c.equals(selected)) {
  438. selected = c;
  439. updateSelection();
  440. fireSelectedTabChange();
  441. requestRepaint();
  442. }
  443. }
  444. /**
  445. * Checks if the current selection is valid, and updates the selection if
  446. * the previously selected component is not visible and enabled. The first
  447. * visible and enabled tab is selected if the current selection is empty or
  448. * invalid.
  449. *
  450. * This method does not fire tab change events, but the caller should do so
  451. * if appropriate.
  452. *
  453. * @return true if selection was changed, false otherwise
  454. */
  455. private boolean updateSelection() {
  456. Component originalSelection = selected;
  457. for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) {
  458. final Component component = i.next();
  459. Tab tab = tabs.get(component);
  460. /*
  461. * If we have no selection, if the current selection is invisible or
  462. * if the current selection is disabled (but the whole component is
  463. * not) we select this tab instead
  464. */
  465. Tab selectedTabInfo = null;
  466. if (selected != null) {
  467. selectedTabInfo = tabs.get(selected);
  468. }
  469. if (selected == null || selectedTabInfo == null
  470. || !selectedTabInfo.isVisible()
  471. || !selectedTabInfo.isEnabled()) {
  472. // The current selection is not valid so we need to change
  473. // it
  474. if (tab.isEnabled() && tab.isVisible()) {
  475. selected = component;
  476. break;
  477. } else {
  478. /*
  479. * The current selection is not valid but this tab cannot be
  480. * selected either.
  481. */
  482. selected = null;
  483. }
  484. }
  485. }
  486. return originalSelection != selected;
  487. }
  488. /**
  489. * Gets the selected tab content component.
  490. *
  491. * @return the selected tab contents
  492. */
  493. public Component getSelectedTab() {
  494. return selected;
  495. }
  496. // inherits javadoc
  497. @Override
  498. public void changeVariables(Object source, Map variables) {
  499. if (variables.containsKey("selected")) {
  500. setSelectedTab((Component) keyMapper.get((String) variables
  501. .get("selected")));
  502. }
  503. if (variables.containsKey("close")) {
  504. final Component tab = (Component) keyMapper.get((String) variables
  505. .get("close"));
  506. if (tab != null) {
  507. closeHandler.onTabClose(this, tab);
  508. }
  509. }
  510. }
  511. /**
  512. * Replaces a component (tab content) with another. This can be used to
  513. * change tab contents or to rearrange tabs. The tab position and some
  514. * metadata are preserved when moving components within the same
  515. * {@link TabSheet}.
  516. *
  517. * If the oldComponent is not present in the tab sheet, the new one is added
  518. * at the end.
  519. *
  520. * If the oldComponent is already in the tab sheet but the newComponent
  521. * isn't, the old tab is replaced with a new one, and the caption and icon
  522. * of the old one are copied to the new tab.
  523. *
  524. * If both old and new components are present, their positions are swapped.
  525. *
  526. * {@inheritDoc}
  527. */
  528. public void replaceComponent(Component oldComponent, Component newComponent) {
  529. if (selected == oldComponent) {
  530. // keep selection w/o selectedTabChange event
  531. selected = newComponent;
  532. }
  533. Tab newTab = tabs.get(newComponent);
  534. Tab oldTab = tabs.get(oldComponent);
  535. // Gets the captions
  536. String oldCaption = null;
  537. Resource oldIcon = null;
  538. String newCaption = null;
  539. Resource newIcon = null;
  540. if (oldTab != null) {
  541. oldCaption = oldTab.getCaption();
  542. oldIcon = oldTab.getIcon();
  543. }
  544. if (newTab != null) {
  545. newCaption = newTab.getCaption();
  546. newIcon = newTab.getIcon();
  547. } else {
  548. newCaption = newComponent.getCaption();
  549. newIcon = newComponent.getIcon();
  550. }
  551. // Gets the locations
  552. int oldLocation = -1;
  553. int newLocation = -1;
  554. int location = 0;
  555. for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
  556. final Component component = i.next();
  557. if (component == oldComponent) {
  558. oldLocation = location;
  559. }
  560. if (component == newComponent) {
  561. newLocation = location;
  562. }
  563. location++;
  564. }
  565. if (oldLocation == -1) {
  566. addComponent(newComponent);
  567. } else if (newLocation == -1) {
  568. removeComponent(oldComponent);
  569. keyMapper.remove(oldComponent);
  570. newTab = addTab(newComponent);
  571. components.remove(newComponent);
  572. components.add(oldLocation, newComponent);
  573. newTab.setCaption(oldCaption);
  574. newTab.setIcon(oldIcon);
  575. } else {
  576. if (oldLocation > newLocation) {
  577. components.remove(oldComponent);
  578. components.add(newLocation, oldComponent);
  579. components.remove(newComponent);
  580. components.add(oldLocation, newComponent);
  581. } else {
  582. components.remove(newComponent);
  583. components.add(oldLocation, newComponent);
  584. components.remove(oldComponent);
  585. components.add(newLocation, oldComponent);
  586. }
  587. if (newTab != null) {
  588. // This should always be true
  589. newTab.setCaption(oldCaption);
  590. newTab.setIcon(oldIcon);
  591. }
  592. if (oldTab != null) {
  593. // This should always be true
  594. oldTab.setCaption(newCaption);
  595. oldTab.setIcon(newIcon);
  596. }
  597. requestRepaint();
  598. }
  599. }
  600. /* Click event */
  601. private static final Method SELECTED_TAB_CHANGE_METHOD;
  602. static {
  603. try {
  604. SELECTED_TAB_CHANGE_METHOD = SelectedTabChangeListener.class
  605. .getDeclaredMethod("selectedTabChange",
  606. new Class[] { SelectedTabChangeEvent.class });
  607. } catch (final java.lang.NoSuchMethodException e) {
  608. // This should never happen
  609. throw new java.lang.RuntimeException(
  610. "Internal error finding methods in TabSheet");
  611. }
  612. }
  613. /**
  614. * Selected tab change event. This event is sent when the selected (shown)
  615. * tab in the tab sheet is changed.
  616. *
  617. * @author IT Mill Ltd.
  618. * @version
  619. * @VERSION@
  620. * @since 3.0
  621. */
  622. public class SelectedTabChangeEvent extends Component.Event {
  623. /**
  624. * New instance of selected tab change event
  625. *
  626. * @param source
  627. * the Source of the event.
  628. */
  629. public SelectedTabChangeEvent(Component source) {
  630. super(source);
  631. }
  632. /**
  633. * TabSheet where the event occurred.
  634. *
  635. * @return the Source of the event.
  636. */
  637. public TabSheet getTabSheet() {
  638. return (TabSheet) getSource();
  639. }
  640. }
  641. /**
  642. * Selected tab change event listener. The listener is called whenever
  643. * another tab is selected, including when adding the first tab to a
  644. * tabsheet.
  645. *
  646. * @author IT Mill Ltd.
  647. *
  648. * @version
  649. * @VERSION@
  650. * @since 3.0
  651. */
  652. public interface SelectedTabChangeListener extends Serializable {
  653. /**
  654. * Selected (shown) tab in tab sheet has has been changed.
  655. *
  656. * @param event
  657. * the selected tab change event.
  658. */
  659. public void selectedTabChange(SelectedTabChangeEvent event);
  660. }
  661. /**
  662. * Adds a tab selection listener
  663. *
  664. * @param listener
  665. * the Listener to be added.
  666. */
  667. public void addListener(SelectedTabChangeListener listener) {
  668. addListener(SelectedTabChangeEvent.class, listener,
  669. SELECTED_TAB_CHANGE_METHOD);
  670. }
  671. /**
  672. * Removes a tab selection listener
  673. *
  674. * @param listener
  675. * the Listener to be removed.
  676. */
  677. public void removeListener(SelectedTabChangeListener listener) {
  678. removeListener(SelectedTabChangeEvent.class, listener,
  679. SELECTED_TAB_CHANGE_METHOD);
  680. }
  681. /**
  682. * Sends an event that the currently selected tab has changed.
  683. */
  684. protected void fireSelectedTabChange() {
  685. fireEvent(new SelectedTabChangeEvent(this));
  686. }
  687. @Override
  688. public void removeListener(RepaintRequestListener listener) {
  689. super.removeListener(listener);
  690. if (listener instanceof CommunicationManager) {
  691. // clean the paintedTabs here instead of detach to avoid subtree
  692. // caching issues when detached-attached without render
  693. paintedTabs.clear();
  694. }
  695. }
  696. /**
  697. * Tab meta-data for a component in a {@link TabSheet}.
  698. *
  699. * The meta-data includes the tab caption, icon, visibility and enabledness,
  700. * closability, description (tooltip) and an optional component error shown
  701. * in the tab.
  702. *
  703. * Tabs are identified by the component contained on them in most cases, and
  704. * the meta-data can be obtained with {@link TabSheet#getTab(Component)}.
  705. */
  706. public interface Tab extends Serializable {
  707. /**
  708. * Returns the visible status for the tab. An invisible tab is not shown
  709. * in the tab bar and cannot be selected.
  710. *
  711. * @return true for visible, false for hidden
  712. */
  713. public boolean isVisible();
  714. /**
  715. * Sets the visible status for the tab. An invisible tab is not shown in
  716. * the tab bar and cannot be selected, selection is changed
  717. * automatically when there is an attempt to select an invisible tab.
  718. *
  719. * @param visible
  720. * true for visible, false for hidden
  721. */
  722. public void setVisible(boolean visible);
  723. /**
  724. * Returns the closability status for the tab.
  725. *
  726. * @return true if the tab is allowed to be closed by the end user,
  727. * false for not allowing closing
  728. */
  729. public boolean isClosable();
  730. /**
  731. * Sets the closability status for the tab. A closable tab can be closed
  732. * by the user through the user interface. This also controls if a close
  733. * button is shown to the user or not.
  734. * <p>
  735. * Note! Currently only supported by TabSheet, not Accordion.
  736. * </p>
  737. *
  738. * @param visible
  739. * true if the end user is allowed to close the tab, false
  740. * for not allowing to close. Should default to false.
  741. */
  742. public void setClosable(boolean closable);
  743. /**
  744. * Returns the enabled status for the tab. A disabled tab is shown as
  745. * such in the tab bar and cannot be selected.
  746. *
  747. * @return true for enabled, false for disabled
  748. */
  749. public boolean isEnabled();
  750. /**
  751. * Sets the enabled status for the tab. A disabled tab is shown as such
  752. * in the tab bar and cannot be selected.
  753. *
  754. * @param enabled
  755. * true for enabled, false for disabled
  756. */
  757. public void setEnabled(boolean enabled);
  758. /**
  759. * Sets the caption for the tab.
  760. *
  761. * @param caption
  762. * the caption to set
  763. */
  764. public void setCaption(String caption);
  765. /**
  766. * Gets the caption for the tab.
  767. */
  768. public String getCaption();
  769. /**
  770. * Gets the icon for the tab.
  771. */
  772. public Resource getIcon();
  773. /**
  774. * Sets the icon for the tab.
  775. *
  776. * @param icon
  777. * the icon to set
  778. */
  779. public void setIcon(Resource icon);
  780. /**
  781. * Gets the description for the tab. The description can be used to
  782. * briefly describe the state of the tab to the user, and is typically
  783. * shown as a tooltip when hovering over the tab.
  784. *
  785. * @return the description for the tab
  786. */
  787. public String getDescription();
  788. /**
  789. * Sets the description for the tab. The description can be used to
  790. * briefly describe the state of the tab to the user, and is typically
  791. * shown as a tooltip when hovering over the tab.
  792. *
  793. * @param description
  794. * the new description string for the tab.
  795. */
  796. public void setDescription(String description);
  797. /**
  798. * Sets an error indicator to be shown in the tab. This can be used e.g.
  799. * to communicate to the user that there is a problem in the contents of
  800. * the tab.
  801. *
  802. * @see AbstractComponent#setComponentError(ErrorMessage)
  803. *
  804. * @param componentError
  805. * error message or null for none
  806. */
  807. public void setComponentError(ErrorMessage componentError);
  808. /**
  809. * Gets the curent error message shown for the tab.
  810. *
  811. * @see AbstractComponent#setComponentError(ErrorMessage)
  812. *
  813. * @param error
  814. * message or null if none
  815. */
  816. public ErrorMessage getComponentError();
  817. }
  818. /**
  819. * TabSheet's implementation of {@link Tab} - tab metadata.
  820. */
  821. public class TabSheetTabImpl implements Tab {
  822. private String caption = "";
  823. private Resource icon = null;
  824. private boolean enabled = true;
  825. private boolean visible = true;
  826. private boolean closable = false;
  827. private String description = null;
  828. private ErrorMessage componentError = null;
  829. public TabSheetTabImpl(String caption, Resource icon) {
  830. if (caption == null) {
  831. caption = "";
  832. }
  833. this.caption = caption;
  834. this.icon = icon;
  835. }
  836. /**
  837. * Returns the tab caption. Can never be null.
  838. */
  839. public String getCaption() {
  840. return caption;
  841. }
  842. public void setCaption(String caption) {
  843. this.caption = caption;
  844. requestRepaint();
  845. }
  846. public Resource getIcon() {
  847. return icon;
  848. }
  849. public void setIcon(Resource icon) {
  850. this.icon = icon;
  851. requestRepaint();
  852. }
  853. public boolean isEnabled() {
  854. return enabled;
  855. }
  856. public void setEnabled(boolean enabled) {
  857. this.enabled = enabled;
  858. if (updateSelection()) {
  859. fireSelectedTabChange();
  860. }
  861. requestRepaint();
  862. }
  863. public boolean isVisible() {
  864. return visible;
  865. }
  866. public void setVisible(boolean visible) {
  867. this.visible = visible;
  868. if (updateSelection()) {
  869. fireSelectedTabChange();
  870. }
  871. requestRepaint();
  872. }
  873. public boolean isClosable() {
  874. return closable;
  875. }
  876. public void setClosable(boolean closable) {
  877. this.closable = closable;
  878. requestRepaint();
  879. }
  880. public void close() {
  881. }
  882. public String getDescription() {
  883. return description;
  884. }
  885. public void setDescription(String description) {
  886. this.description = description;
  887. requestRepaint();
  888. }
  889. public ErrorMessage getComponentError() {
  890. return componentError;
  891. }
  892. public void setComponentError(ErrorMessage componentError) {
  893. this.componentError = componentError;
  894. requestRepaint();
  895. }
  896. }
  897. /**
  898. * CloseHandler is used to process tab closing events. Default behavior is
  899. * to remove the tab from the TabSheet.
  900. *
  901. * @author Jouni Koivuviita / IT Mill Ltd.
  902. * @since 6.2.0
  903. *
  904. */
  905. public interface CloseHandler extends Serializable {
  906. /**
  907. * Called when a user has pressed the close icon of a tab in the client
  908. * side widget.
  909. *
  910. * @param tabsheet
  911. * the TabSheet to which the tab belongs to
  912. * @param tabContent
  913. * the component that corresponds to the tab whose close
  914. * button was clicked
  915. */
  916. void onTabClose(final TabSheet tabsheet, final Component tabContent);
  917. }
  918. /**
  919. * Provide a custom {@link CloseHandler} for this TabSheet if you wish to
  920. * perform some additional tasks when a user clicks on a tabs close button,
  921. * e.g. show a confirmation dialogue before removing the tab.
  922. *
  923. * To remove the tab, if you provide your own close handler, you must call
  924. * {@link #removeComponent(Component)} yourself.
  925. *
  926. * The default CloseHandler for TabSheet will only remove the tab.
  927. *
  928. * @param handler
  929. */
  930. public void setCloseHandler(CloseHandler handler) {
  931. closeHandler = handler;
  932. }
  933. }