Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

TabSheet.java 38KB

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