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

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