123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638 |
- /*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
- package com.vaadin.ui;
-
- import java.io.Serializable;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.Map;
-
- import org.jsoup.nodes.Attributes;
- import org.jsoup.nodes.Element;
-
- import com.vaadin.event.FieldEvents.BlurEvent;
- import com.vaadin.event.FieldEvents.BlurListener;
- import com.vaadin.event.FieldEvents.BlurNotifier;
- import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator;
- import com.vaadin.event.FieldEvents.FocusEvent;
- import com.vaadin.event.FieldEvents.FocusListener;
- import com.vaadin.event.FieldEvents.FocusNotifier;
- import com.vaadin.server.ErrorMessage;
- import com.vaadin.server.KeyMapper;
- import com.vaadin.server.Resource;
- import com.vaadin.shared.ComponentConstants;
- import com.vaadin.shared.Registration;
- import com.vaadin.shared.ui.tabsheet.TabState;
- import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc;
- import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc;
- import com.vaadin.shared.ui.tabsheet.TabsheetState;
- import com.vaadin.ui.Component.Focusable;
- import com.vaadin.ui.declarative.DesignAttributeHandler;
- import com.vaadin.ui.declarative.DesignContext;
- import com.vaadin.ui.declarative.DesignException;
-
- /**
- * TabSheet component.
- *
- * Tabs are typically identified by the component contained on the tab (see
- * {@link ComponentContainer}), and tab metadata (including caption, icon,
- * visibility, enabledness, closability etc.) is kept in separate {@link Tab}
- * instances.
- *
- * Tabs added with {@link #addComponent(Component)} get the caption and the icon
- * of the component at the time when the component is created, and these are not
- * automatically updated after tab creation.
- *
- * A tab sheet can have multiple tab selection listeners and one tab close
- * handler ({@link CloseHandler}), which by default removes the tab from the
- * TabSheet.
- *
- * The {@link TabSheet} can be styled with the .v-tabsheet, .v-tabsheet-tabs and
- * .v-tabsheet-content styles.
- *
- * The current implementation does not load the tabs to the UI before the first
- * time they are shown, but this may change in future releases.
- *
- * @author Vaadin Ltd.
- * @since 3.0
- */
- public class TabSheet extends AbstractComponentContainer
- implements Focusable, FocusNotifier, BlurNotifier, SelectiveRenderer {
-
- /**
- * Client to server RPC implementation for TabSheet.
- *
- * @since 7.2
- */
- protected class TabsheetServerRpcImpl implements TabsheetServerRpc {
-
- @Override
- public void setSelected(String key) {
- setSelectedTab(keyMapper.get(key));
- }
-
- @Override
- public void closeTab(String key) {
- final Component tab = keyMapper.get(key);
- if (tab != null) {
- closeHandler.onTabClose(TabSheet.this, tab);
- }
- }
- }
-
- /**
- * List of component tabs (tab contents). In addition to being on this list,
- * there is a {@link Tab} object in tabs for each tab with meta-data about
- * the tab.
- */
- private final ArrayList<Component> components = new ArrayList<>();
-
- /**
- * Map containing information related to the tabs (caption, icon etc).
- */
- private final HashMap<Component, Tab> tabs = new HashMap<>();
-
- /**
- * Selected tab content component.
- */
- private Component selected = null;
-
- /**
- * Mapper between server-side component instances (tab contents) and keys
- * given to the client that identify tabs.
- */
- private final KeyMapper<Component> keyMapper = new KeyMapper<>();
-
- /**
- * Handler to be called when a tab is closed.
- */
- private CloseHandler closeHandler;
-
- /**
- * Constructs a new TabSheet. A TabSheet is immediate by default, and the
- * default close handler removes the tab being closed.
- */
- public TabSheet() {
- super();
-
- registerRpc(rpc);
- registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent));
-
- // expand horizontally by default
- setWidth(100, UNITS_PERCENTAGE);
- setCloseHandler(new CloseHandler() {
-
- @Override
- public void onTabClose(TabSheet tabsheet, Component c) {
- tabsheet.removeComponent(c);
- }
- });
- }
-
- /**
- * Constructs a new TabSheet containing the given components.
- *
- * @param components
- * The components to add to the tab sheet. Each component will be
- * added to a separate tab.
- */
- public TabSheet(Component... components) {
- this();
- addComponents(components);
- }
-
- /**
- * Gets the component container iterator for going through all the
- * components (tab contents).
- *
- * @return the unmodifiable Iterator of the tab content components
- */
-
- @Override
- public Iterator<Component> iterator() {
- return Collections.unmodifiableList(components).iterator();
- }
-
- /**
- * Gets the number of contained components (tabs). Consistent with the
- * iterator returned by {@link #getComponentIterator()}.
- *
- * @return the number of contained components
- */
-
- @Override
- public int getComponentCount() {
- return components.size();
- }
-
- /**
- * Removes a component and its corresponding tab.
- *
- * If the tab was selected, the first eligible (visible and enabled)
- * remaining tab is selected.
- *
- * @param component
- * the component to be removed.
- */
-
- @Override
- public void removeComponent(Component component) {
- if (component != null && components.contains(component)) {
-
- int componentIndex = components.indexOf(component);
- super.removeComponent(component);
- keyMapper.remove(component);
- components.remove(component);
-
- Tab removedTab = tabs.remove(component);
-
- getState().tabs
- .remove(((TabSheetTabImpl) removedTab).getTabState());
-
- if (component.equals(selected)) {
- if (components.isEmpty()) {
- setSelected(null);
- } else {
-
- int newSelectedIndex = selectedTabIndexAfterTabRemove(
- componentIndex);
-
- // Make sure the component actually exists, in case someone
- // override it and provide a non existing component.
- if (0 <= newSelectedIndex
- && newSelectedIndex < components.size()) {
-
- Component newSelectedComponent = components
- .get(newSelectedIndex);
-
- // Select only if the tab is enabled.
- Tab newSelectedTab = tabs.get(newSelectedComponent);
- if (newSelectedTab.isEnabled()) {
- setSelected(newSelectedComponent);
- }
- }
-
- // select the first enabled and visible tab, if any
- updateSelection();
- fireSelectedTabChange();
- }
- }
- }
- }
-
- /**
- * Called when a selected tab is removed to specify the new tab to select.
- * Can be overridden to provide another algorithm that calculates the new
- * selection.
- *
- * Current implementation will choose the first enabled tab to the right of
- * the removed tab if it's not the last one, otherwise will choose the
- * closer enabled tab to the left.
- *
- * @since 7.4
- * @param removedTabIndex
- * the index of the selected tab which was just remove.
- * @return the index of the tab to be selected or -1 if there are no more
- * enabled tabs to select.
- */
- protected int selectedTabIndexAfterTabRemove(int removedTabIndex) {
-
- for (int i = removedTabIndex; i < components.size(); i++) {
- Tab tab = getTab(i);
- if (tab.isEnabled()) {
- return i;
- }
- }
-
- for (int i = removedTabIndex - 1; i >= 0; i--) {
- Tab tab = getTab(i);
- if (tab.isEnabled()) {
- return i;
- }
- }
-
- return -1;
- }
-
- /**
- * Removes a {@link Tab} and the component associated with it, as previously
- * added with {@link #addTab(Component)},
- * {@link #addTab(Component, String, Resource)} or
- * {@link #addComponent(Component)}.
- * <p>
- * If the tab was selected, the first eligible (visible and enabled)
- * remaining tab is selected.
- * </p>
- *
- * @see #addTab(Component)
- * @see #addTab(Component, String, Resource)
- * @see #addComponent(Component)
- * @see #removeComponent(Component)
- * @param tab
- * the Tab to remove
- */
- public void removeTab(Tab tab) {
- removeComponent(tab.getComponent());
- }
-
- /**
- * Adds a new tab into TabSheet. Component caption and icon are copied to
- * the tab metadata at creation time.
- *
- * @see #addTab(Component)
- *
- * @param c
- * the component to be added.
- */
-
- @Override
- public void addComponent(Component c) {
- addTab(c);
- }
-
- /**
- * Adds a new tab into TabSheet.
- *
- * The first tab added to a tab sheet is automatically selected and a tab
- * selection event is fired.
- *
- * If the component is already present in the tab sheet, changes its caption
- * and returns the corresponding (old) tab, preserving other tab metadata.
- *
- * @param c
- * the component to be added onto tab - should not be null.
- * @param caption
- * the caption to be set for the component and used rendered in
- * tab bar
- * @return the created {@link Tab}
- */
- public Tab addTab(Component c, String caption) {
- return addTab(c, caption, null);
- }
-
- /**
- * Adds a new tab into TabSheet.
- *
- * The first tab added to a tab sheet is automatically selected and a tab
- * selection event is fired.
- *
- * If the component is already present in the tab sheet, changes its caption
- * and icon and returns the corresponding (old) tab, preserving other tab
- * metadata.
- *
- * @param c
- * the component to be added onto tab - should not be null.
- * @param caption
- * the caption to be set for the component and used rendered in
- * tab bar
- * @param icon
- * the icon to be set for the component and used rendered in tab
- * bar
- * @return the created {@link Tab}
- */
- public Tab addTab(Component c, String caption, Resource icon) {
- return addTab(c, caption, icon, components.size());
- }
-
- /**
- * Adds a new tab into TabSheet.
- *
- * The first tab added to a tab sheet is automatically selected and a tab
- * selection event is fired.
- *
- * If the component is already present in the tab sheet, changes its caption
- * and icon and returns the corresponding (old) tab, preserving other tab
- * metadata like the position.
- *
- * @param tabComponent
- * the component to be added onto tab - should not be null.
- * @param caption
- * the caption to be set for the component and used rendered in
- * tab bar
- * @param icon
- * the icon to be set for the component and used rendered in tab
- * bar
- * @param position
- * the position at where the the tab should be added.
- * @return the created {@link Tab}
- */
- public Tab addTab(Component tabComponent, String caption, Resource icon,
- int position) {
- if (tabComponent == null) {
- return null;
- } else if (tabs.containsKey(tabComponent)) {
- Tab tab = tabs.get(tabComponent);
- tab.setCaption(caption);
- tab.setIcon(icon);
- return tab;
- } else {
- components.add(position, tabComponent);
-
- TabSheetTabImpl tab = new TabSheetTabImpl(
- keyMapper.key(tabComponent), caption, icon);
-
- getState().tabs.add(position, tab.getTabState());
- tabs.put(tabComponent, tab);
-
- if (selected == null) {
- setSelected(tabComponent);
- fireSelectedTabChange();
- }
-
- super.addComponent(tabComponent);
-
- return tab;
- }
- }
-
- /**
- * Adds a new tab into TabSheet. Component caption and icon are copied to
- * the tab metadata at creation time.
- *
- * If the tab sheet already contains the component, its tab is returned.
- *
- * @param c
- * the component to be added onto tab - should not be null.
- * @return the created {@link Tab}
- */
- public Tab addTab(Component c) {
- return addTab(c, components.size());
- }
-
- /**
- * Adds a new tab into TabSheet. Component caption and icon are copied to
- * the tab metadata at creation time.
- *
- * If the tab sheet already contains the component, its tab is returned.
- *
- * @param component
- * the component to be added onto tab - should not be null.
- * @param position
- * The position where the tab should be added
- * @return the created {@link Tab}
- */
- public Tab addTab(Component component, int position) {
- Tab result = tabs.get(component);
-
- if (result == null) {
- result = addTab(component, component.getCaption(),
- component.getIcon(), position);
- }
-
- return result;
- }
-
- /**
- * Moves all components from another container to this container. The
- * components are removed from the other container.
- *
- * If the source container is a {@link TabSheet}, component captions and
- * icons are copied from it.
- *
- * @param source
- * the container components are removed from.
- */
-
- @Override
- public void moveComponentsFrom(ComponentContainer source) {
- for (final Iterator<Component> i = source.getComponentIterator(); i
- .hasNext();) {
- final Component c = i.next();
- String caption = null;
- Resource icon = null;
- String iconAltText = "";
- if (TabSheet.class.isAssignableFrom(source.getClass())) {
- Tab tab = ((TabSheet) source).getTab(c);
- caption = tab.getCaption();
- icon = tab.getIcon();
- iconAltText = tab.getIconAlternateText();
- }
- source.removeComponent(c);
- Tab tab = addTab(c, caption, icon);
- tab.setIconAlternateText(iconAltText);
- }
- }
-
- /**
- * Are the tab selection parts ("tabs") hidden.
- *
- * @return true if the tabs are hidden in the UI
- * @deprecated as of 7.5, use {@link #isTabsVisible()} instead
- */
- @Deprecated
- public boolean areTabsHidden() {
- return !isTabsVisible();
- }
-
- /**
- * Hides or shows the tab selection parts ("tabs").
- *
- * @param tabsHidden
- * true if the tabs should be hidden
- * @deprecated as of 7.5, use {@link #setTabsVisible(boolean)} instead
- */
- @Deprecated
- public void hideTabs(boolean tabsHidden) {
- setTabsVisible(!tabsHidden);
- }
-
- /**
- * Sets whether the tab selection part should be shown in the UI
- *
- * @since 7.5
- * @param tabsVisible
- * true if the tabs should be shown in the UI, false otherwise
- */
- public void setTabsVisible(boolean tabsVisible) {
- getState().tabsVisible = tabsVisible;
- }
-
- /**
- * Checks if the tab selection part should be shown in the UI
- *
- * @return true if the tabs are shown in the UI, false otherwise
- * @since 7.5
- */
- public boolean isTabsVisible() {
- return getState(false).tabsVisible;
-
- }
-
- /**
- * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
- * object can be used for setting caption,icon, etc for the tab.
- *
- * @param c
- * the component
- * @return The tab instance associated with the given component, or null if
- * the tabsheet does not contain the component.
- */
- public Tab getTab(Component c) {
- return tabs.get(c);
- }
-
- /**
- * Returns the {@link Tab} (metadata) for a component. The {@link Tab}
- * object can be used for setting caption,icon, etc for the tab.
- *
- * @param position
- * the position of the tab
- * @return The tab in the given position, or null if the position is out of
- * bounds.
- */
- public Tab getTab(int position) {
- if (position >= 0 && position < getComponentCount()) {
- return getTab(components.get(position));
- } else {
- return null;
- }
- }
-
- /**
- * Sets the selected tab. The tab is identified by the tab content
- * component. Does nothing if the tabsheet doesn't contain the component.
- *
- * @param c
- */
- public void setSelectedTab(Component c) {
- if (c != null && components.contains(c) && !c.equals(selected)) {
- setSelected(c);
- updateSelection();
- fireSelectedTabChange();
- markAsDirty();
- getRpcProxy(TabsheetClientRpc.class).revertToSharedStateSelection();
- }
- }
-
- /**
- * Sets the selected tab in the TabSheet. Ensures that the selected tab is
- * repainted if needed.
- *
- * @param component
- * The new selection or null for no selection
- */
- private void setSelected(Component component) {
- Tab tab = tabs.get(selected);
-
- selected = component;
- // Repaint of the selected component is needed as only the selected
- // component is communicated to the client. Otherwise this will be a
- // "cached" update even though the client knows nothing about the
- // connector
- if (selected != null) {
- tab = getTab(component);
-
- if (tab != null && tab.getDefaultFocusComponent() != null) {
- tab.getDefaultFocusComponent().focus();
- }
-
- getState().selected = keyMapper.key(selected);
-
- selected.markAsDirtyRecursive();
- } else {
- getState().selected = null;
- }
- }
-
- /**
- * Sets the selected tab. The tab is identified by the corresponding
- * {@link Tab Tab} instance. Does nothing if the tabsheet doesn't contain
- * the given tab.
- *
- * @param tab
- */
- public void setSelectedTab(Tab tab) {
- if (tab != null) {
- setSelectedTab(tab.getComponent());
- }
- }
-
- /**
- * Sets the selected tab, identified by its position. Does nothing if the
- * position is out of bounds.
- *
- * @param position
- */
- public void setSelectedTab(int position) {
- setSelectedTab(getTab(position));
- }
-
- /**
- * Checks if the current selection is valid, and updates the selection if
- * the previously selected component is not visible and enabled. The first
- * visible and enabled tab is selected if the current selection is empty or
- * invalid.
- *
- * This method does not fire tab change events, but the caller should do so
- * if appropriate.
- *
- * @return true if selection was changed, false otherwise
- */
- private boolean updateSelection() {
- Component originalSelection = selected;
- for (final Iterator<Component> i = getComponentIterator(); i
- .hasNext();) {
- final Component component = i.next();
-
- Tab tab = tabs.get(component);
-
- /*
- * If we have no selection, if the current selection is invisible or
- * if the current selection is disabled (but the whole component is
- * not) we select this tab instead
- */
- Tab selectedTabInfo = null;
- if (selected != null) {
- selectedTabInfo = tabs.get(selected);
- }
- if (selected == null || selectedTabInfo == null
- || !selectedTabInfo.isVisible()
- || !selectedTabInfo.isEnabled()) {
-
- // The current selection is not valid so we need to change
- // it
- if (tab.isEnabled() && tab.isVisible()) {
- setSelected(component);
- break;
- } else {
- /*
- * The current selection is not valid but this tab cannot be
- * selected either.
- */
- setSelected(null);
- }
- }
- }
- return originalSelection != selected;
- }
-
- /**
- * Gets the selected tab content component.
- *
- * @return the selected tab contents
- */
- public Component getSelectedTab() {
- return selected;
- }
-
- private TabsheetServerRpcImpl rpc = new TabsheetServerRpcImpl();
-
- /**
- * Replaces a component (tab content) with another. This can be used to
- * change tab contents or to rearrange tabs. The tab position and some
- * metadata are preserved when moving components within the same
- * {@link TabSheet}.
- *
- * If the oldComponent is not present in the tab sheet, the new one is added
- * at the end.
- *
- * If the oldComponent is already in the tab sheet but the newComponent
- * isn't, the old tab is replaced with a new one, and the caption and icon
- * of the old one are copied to the new tab.
- *
- * If both old and new components are present, their positions are swapped.
- *
- * {@inheritDoc}
- */
-
- @Override
- public void replaceComponent(Component oldComponent,
- Component newComponent) {
- boolean selectAfterInserting = false;
-
- if (selected == oldComponent) {
- selectAfterInserting = true;
- }
-
- Tab newTab = tabs.get(newComponent);
- Tab oldTab = tabs.get(oldComponent);
-
- // Gets the locations
- int oldLocation = -1;
- int newLocation = -1;
- int location = 0;
-
- for (final Iterator<Component> i = components.iterator(); i
- .hasNext();) {
- final Component component = i.next();
-
- if (component == oldComponent) {
- oldLocation = location;
- }
- if (component == newComponent) {
- newLocation = location;
- }
-
- location++;
- }
-
- if (oldLocation == -1) {
- addComponent(newComponent);
- } else if (newLocation == -1) {
- if (selected == oldComponent) {
- setSelected(null);
- }
- removeComponent(oldComponent);
- newTab = addTab(newComponent, oldLocation);
-
- if (selectAfterInserting) {
- setSelected(newComponent);
- }
-
- // Copy all relevant metadata to the new tab (#8793)
- // TODO Should reuse the old tab instance instead?
- copyTabMetadata(oldTab, newTab);
- } else {
- components.set(oldLocation, newComponent);
- components.set(newLocation, oldComponent);
-
- if (selectAfterInserting) {
- setSelected(newComponent);
- // SelectedTabChangeEvent should be fired here as selected Tab
- // is changed.
- // Other cases are handled implicitly by removeComponent() and
- // addComponent()addTab()
- fireSelectedTabChange();
- }
-
- // Tab associations are not changed, but metadata is swapped between
- // the instances
- // TODO Should reassociate the instances instead?
- Tab tmp = new TabSheetTabImpl(null, null, null);
- copyTabMetadata(newTab, tmp);
- copyTabMetadata(oldTab, newTab);
- copyTabMetadata(tmp, oldTab);
-
- markAsDirty();
- }
- }
-
- /* Click event */
-
- private static final Method SELECTED_TAB_CHANGE_METHOD;
- static {
- try {
- SELECTED_TAB_CHANGE_METHOD = SelectedTabChangeListener.class
- .getDeclaredMethod("selectedTabChange",
- new Class[] { SelectedTabChangeEvent.class });
- } catch (final java.lang.NoSuchMethodException e) {
- // This should never happen
- throw new java.lang.RuntimeException(
- "Internal error finding methods in TabSheet");
- }
- }
-
- /**
- * Selected tab change event. This event is sent when the selected (shown)
- * tab in the tab sheet is changed.
- *
- * @author Vaadin Ltd.
- * @since 3.0
- */
- public static class SelectedTabChangeEvent extends Component.Event {
-
- /**
- * New instance of selected tab change event
- *
- * @param source
- * the Source of the event.
- */
- public SelectedTabChangeEvent(Component source) {
- super(source);
- }
-
- /**
- * TabSheet where the event occurred.
- *
- * @return the Source of the event.
- */
- public TabSheet getTabSheet() {
- return (TabSheet) getSource();
- }
- }
-
- /**
- * Selected tab change event listener. The listener is called whenever
- * another tab is selected, including when adding the first tab to a
- * tabsheet.
- *
- * @author Vaadin Ltd.
- *
- * @since 3.0
- */
- public interface SelectedTabChangeListener extends Serializable {
-
- /**
- * Selected (shown) tab in tab sheet has has been changed.
- *
- * @param event
- * the selected tab change event.
- */
- public void selectedTabChange(SelectedTabChangeEvent event);
- }
-
- /**
- * Adds a tab selection listener.
- *
- * @see Registration
- *
- * @param listener
- * the Listener to be added, not null
- * @return a registration object for removing the listener
- */
- public Registration addSelectedTabChangeListener(
- SelectedTabChangeListener listener) {
- addListener(SelectedTabChangeEvent.class, listener,
- SELECTED_TAB_CHANGE_METHOD);
- return () -> removeListener(SelectedTabChangeEvent.class, listener,
- SELECTED_TAB_CHANGE_METHOD);
- }
-
- /**
- * Removes a tab selection listener.
- *
- * @param listener
- * the Listener to be removed.
- *
- * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
- * registration object returned from
- * {@link #removeSelectedTabChangeListener(SelectedTabChangeListener)}
- * .
- */
- @Deprecated
- public void removeSelectedTabChangeListener(
- SelectedTabChangeListener listener) {
- removeListener(SelectedTabChangeEvent.class, listener,
- SELECTED_TAB_CHANGE_METHOD);
- }
-
- /**
- * Sends an event that the currently selected tab has changed.
- */
- protected void fireSelectedTabChange() {
- fireEvent(new SelectedTabChangeEvent(this));
- }
-
- /**
- * Tab meta-data for a component in a {@link TabSheet}.
- *
- * The meta-data includes the tab caption, icon, visibility and enabledness,
- * closability, description (tooltip) and an optional component error shown
- * in the tab.
- *
- * Tabs are identified by the component contained on them in most cases, and
- * the meta-data can be obtained with {@link TabSheet#getTab(Component)}.
- */
- public interface Tab extends Serializable {
- /**
- * Returns the visible status for the tab. An invisible tab is not shown
- * in the tab bar and cannot be selected.
- *
- * @return true for visible, false for hidden
- */
- public boolean isVisible();
-
- /**
- * Sets the visible status for the tab. An invisible tab is not shown in
- * the tab bar and cannot be selected, selection is changed
- * automatically when there is an attempt to select an invisible tab.
- *
- * @param visible
- * true for visible, false for hidden
- */
- public void setVisible(boolean visible);
-
- /**
- * Returns the closability status for the tab.
- *
- * @return true if the tab is allowed to be closed by the end user,
- * false for not allowing closing
- */
- public boolean isClosable();
-
- /**
- * Sets the closability status for the tab. A closable tab can be closed
- * by the user through the user interface. This also controls if a close
- * button is shown to the user or not.
- * <p>
- * Note! Currently only supported by TabSheet, not Accordion.
- * </p>
- *
- * @param closable
- * true if the end user is allowed to close the tab, false
- * for not allowing to close. Should default to false.
- */
- public void setClosable(boolean closable);
-
- /**
- * Set the component that should automatically focused when the tab is
- * selected.
- *
- * @param component
- * the component to focus
- */
- public void setDefaultFocusComponent(Focusable component);
-
- /**
- * Get the component that should be automatically focused when the tab
- * is selected.
- *
- * @return the focusable component
- */
- public Focusable getDefaultFocusComponent();
-
- /**
- * Returns the enabled status for the tab. A disabled tab is shown as
- * such in the tab bar and cannot be selected.
- *
- * @return true for enabled, false for disabled
- */
- public boolean isEnabled();
-
- /**
- * Sets the enabled status for the tab. A disabled tab is shown as such
- * in the tab bar and cannot be selected.
- *
- * @param enabled
- * true for enabled, false for disabled
- */
- public void setEnabled(boolean enabled);
-
- /**
- * Sets the caption for the tab.
- *
- * @param caption
- * the caption to set
- */
- public void setCaption(String caption);
-
- /**
- * Gets the caption for the tab.
- */
- public String getCaption();
-
- /**
- * Gets the icon for the tab.
- */
- public Resource getIcon();
-
- /**
- * Sets the icon for the tab.
- *
- * @param icon
- * the icon to set
- */
- public void setIcon(Resource icon);
-
- /**
- * Sets the icon and alt text for the tab.
- *
- * @param icon
- * the icon to set
- */
- public void setIcon(Resource icon, String iconAltText);
-
- /**
- * Gets the icon alt text for the tab.
- *
- * @since 7.2
- */
- public String getIconAlternateText();
-
- /**
- * Sets the icon alt text for the tab.
- *
- * @since 7.2
- *
- * @param iconAltText
- * the icon to set
- */
- public void setIconAlternateText(String iconAltText);
-
- /**
- * Gets the description for the tab. The description can be used to
- * briefly describe the state of the tab to the user, and is typically
- * shown as a tooltip when hovering over the tab.
- *
- * @return the description for the tab
- */
- public String getDescription();
-
- /**
- * Sets the description for the tab. The description can be used to
- * briefly describe the state of the tab to the user, and is typically
- * shown as a tooltip when hovering over the tab.
- *
- * @param description
- * the new description string for the tab.
- */
- public void setDescription(String description);
-
- /**
- * Sets an error indicator to be shown in the tab. This can be used e.g.
- * to communicate to the user that there is a problem in the contents of
- * the tab.
- *
- * @see AbstractComponent#setComponentError(ErrorMessage)
- *
- * @param componentError
- * error message or null for none
- */
- public void setComponentError(ErrorMessage componentError);
-
- /**
- * Gets the current error message shown for the tab.
- *
- * TODO currently not sent to the client
- *
- * @see AbstractComponent#setComponentError(ErrorMessage)
- */
- public ErrorMessage getComponentError();
-
- /**
- * Get the component related to the Tab
- */
- public Component getComponent();
-
- /**
- * Sets a style name for the tab. The style name will be rendered as a
- * HTML class name, which can be used in a CSS definition.
- *
- * <pre>
- * Tab tab = tabsheet.addTab(tabContent, "Tab text");
- * tab.setStyleName("mystyle");
- * </pre>
- * <p>
- * The used style name will be prefixed with "
- * {@code v-tabsheet-tabitemcell-}". For example, if you give a tab the
- * style "{@code mystyle}", the tab will get a "
- * {@code v-tabsheet-tabitemcell-mystyle}" style. You could then style
- * the component with:
- * </p>
- *
- * <pre>
- * .v-tabsheet-tabitemcell-mystyle {font-style: italic;}
- * </pre>
- *
- * @param styleName
- * the new style to be set for tab
- * @see #getStyleName()
- */
- public void setStyleName(String styleName);
-
- /**
- * Gets the user-defined CSS style name of the tab. Built-in style names
- * defined in Vaadin or GWT are not returned.
- *
- * @return the style name or of the tab
- * @see #setStyleName(String)
- */
- public String getStyleName();
-
- /**
- * Adds an unique id for component that is used in the client-side for
- * testing purposes. Keeping identifiers unique is the responsibility of
- * the programmer.
- *
- * @param id
- * An alphanumeric id
- */
- public void setId(String id);
-
- /**
- * Gets currently set debug identifier
- *
- * @return current id, null if not set
- */
- public String getId();
- }
-
- /**
- * TabSheet's implementation of {@link Tab} - tab metadata.
- */
- public class TabSheetTabImpl implements Tab {
-
- private TabState tabState;
-
- private Focusable defaultFocus;
-
- private ErrorMessage componentError;
-
- public TabSheetTabImpl(String key, String caption, Resource icon) {
- tabState = new TabState();
-
- if (caption == null) {
- caption = "";
- }
-
- tabState.key = key;
- tabState.caption = caption;
-
- setIcon(icon);
- }
-
- /**
- * Returns the tab caption. Can never be null.
- */
-
- @Override
- public String getCaption() {
- return tabState.caption;
- }
-
- @Override
- public void setCaption(String caption) {
- tabState.caption = caption;
- markAsDirty();
- }
-
- @Override
- public Resource getIcon() {
- return getResource(ComponentConstants.ICON_RESOURCE + tabState.key);
- }
-
- @Override
- public void setIcon(Resource icon) {
- // this might not be ideal (resetting icon altText), but matches
- // previous semantics
- setIcon(icon, "");
- }
-
- @Override
- public String getIconAlternateText() {
- return tabState.iconAltText;
- }
-
- @Override
- public void setIconAlternateText(String iconAltText) {
- tabState.iconAltText = iconAltText;
- markAsDirty();
- }
-
- @Override
- public void setDefaultFocusComponent(Focusable defaultFocus) {
- this.defaultFocus = defaultFocus;
- }
-
- @Override
- public Focusable getDefaultFocusComponent() {
- return defaultFocus;
- }
-
- @Override
- public boolean isEnabled() {
- return tabState.enabled;
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- tabState.enabled = enabled;
-
- if (updateSelection()) {
- fireSelectedTabChange();
- }
- markAsDirty();
- }
-
- @Override
- public boolean isVisible() {
- return tabState.visible;
- }
-
- @Override
- public void setVisible(boolean visible) {
- tabState.visible = visible;
-
- if (updateSelection()) {
- fireSelectedTabChange();
- }
- markAsDirty();
- }
-
- @Override
- public boolean isClosable() {
- return tabState.closable;
- }
-
- @Override
- public void setClosable(boolean closable) {
- tabState.closable = closable;
-
- markAsDirty();
- }
-
- @Override
- public String getDescription() {
- return tabState.description;
- }
-
- @Override
- public void setDescription(String description) {
- tabState.description = description;
- markAsDirty();
- }
-
- @Override
- public ErrorMessage getComponentError() {
- return componentError;
- }
-
- @Override
- public void setComponentError(ErrorMessage componentError) {
- this.componentError = componentError;
-
- String formattedHtmlMessage = componentError != null
- ? componentError.getFormattedHtmlMessage() : null;
- tabState.componentError = formattedHtmlMessage;
-
- markAsDirty();
- }
-
- @Override
- public Component getComponent() {
- for (Map.Entry<Component, Tab> entry : tabs.entrySet()) {
- if (equals(entry.getValue())) {
- return entry.getKey();
- }
- }
- return null;
- }
-
- @Override
- public void setStyleName(String styleName) {
- tabState.styleName = styleName;
-
- markAsDirty();
- }
-
- @Override
- public String getStyleName() {
- return tabState.styleName;
- }
-
- protected TabState getTabState() {
- return tabState;
- }
-
- @Override
- public void setId(String id) {
- tabState.id = id;
- markAsDirty();
-
- }
-
- @Override
- public String getId() {
- return tabState.id;
- }
-
- @Override
- public void setIcon(Resource icon, String iconAltText) {
- setResource(ComponentConstants.ICON_RESOURCE + tabState.key, icon);
- tabState.iconAltText = iconAltText;
- }
- }
-
- /**
- * CloseHandler is used to process tab closing events. Default behavior is
- * to remove the tab from the TabSheet.
- *
- * @author Jouni Koivuviita / Vaadin Ltd.
- * @since 6.2.0
- *
- */
- public interface CloseHandler extends Serializable {
-
- /**
- * Called when a user has pressed the close icon of a tab in the client
- * side widget.
- *
- * @param tabsheet
- * the TabSheet to which the tab belongs to
- * @param tabContent
- * the component that corresponds to the tab whose close
- * button was clicked
- */
- void onTabClose(final TabSheet tabsheet, final Component tabContent);
- }
-
- /**
- * Provide a custom {@link CloseHandler} for this TabSheet if you wish to
- * perform some additional tasks when a user clicks on a tabs close button,
- * e.g. show a confirmation dialogue before removing the tab.
- *
- * To remove the tab, if you provide your own close handler, you must call
- * {@link #removeComponent(Component)} yourself.
- *
- * The default CloseHandler for TabSheet will only remove the tab.
- *
- * @param handler
- */
- public void setCloseHandler(CloseHandler handler) {
- closeHandler = handler;
- }
-
- /**
- * Sets the position of the tab.
- *
- * @param tab
- * The tab
- * @param position
- * The new position of the tab
- */
- public void setTabPosition(Tab tab, int position) {
- int oldPosition = getTabPosition(tab);
- components.remove(oldPosition);
- components.add(position, tab.getComponent());
-
- getState().tabs.remove(oldPosition);
- getState().tabs.add(position, ((TabSheetTabImpl) tab).getTabState());
- }
-
- /**
- * Gets the position of the tab
- *
- * @param tab
- * The tab
- * @return
- */
- public int getTabPosition(Tab tab) {
- return components.indexOf(tab.getComponent());
- }
-
- @Override
- public void focus() {
- super.focus();
- }
-
- @Override
- public int getTabIndex() {
- return getState(false).tabIndex;
- }
-
- @Override
- public void setTabIndex(int tabIndex) {
- getState().tabIndex = tabIndex;
- }
-
- @Override
- public Registration addBlurListener(BlurListener listener) {
- addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
- BlurListener.blurMethod);
- return () -> removeListener(BlurEvent.EVENT_ID, BlurEvent.class,
- listener);
- }
-
- @Override
- @Deprecated
- public void removeBlurListener(BlurListener listener) {
- removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
- }
-
- @Override
- public Registration addFocusListener(FocusListener listener) {
- addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
- FocusListener.focusMethod);
- return () -> removeListener(FocusEvent.EVENT_ID, FocusEvent.class,
- listener);
- }
-
- @Override
- @Deprecated
- public void removeFocusListener(FocusListener listener) {
- removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
- }
-
- @Override
- public boolean isRendered(Component childComponent) {
- return childComponent == getSelectedTab();
- }
-
- /**
- * Copies properties from one Tab to another.
- *
- * @param from
- * The tab whose data to copy.
- * @param to
- * The tab to which copy the data.
- */
- private static void copyTabMetadata(Tab from, Tab to) {
- to.setCaption(from.getCaption());
- to.setIcon(from.getIcon(), from.getIconAlternateText());
- to.setDescription(from.getDescription());
- to.setVisible(from.isVisible());
- to.setEnabled(from.isEnabled());
- to.setClosable(from.isClosable());
- to.setStyleName(from.getStyleName());
- to.setComponentError(from.getComponentError());
- }
-
- @Override
- protected TabsheetState getState(boolean markAsDirty) {
- return (TabsheetState) super.getState(markAsDirty);
- }
-
- @Override
- protected TabsheetState getState() {
- return (TabsheetState) super.getState();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
- * com.vaadin.ui.declarative.DesignContext)
- */
- @Override
- public void readDesign(Element design, DesignContext designContext) {
- super.readDesign(design, designContext);
- // create new tabs
- for (Element tab : design.children()) {
- if (!tab.tagName().equals("tab")) {
- throw new DesignException(
- "Invalid tag name for tabsheet tab " + tab.tagName());
- }
- readTabFromDesign(tab, designContext);
- }
- }
-
- /**
- * Reads the given tab element from design
- *
- * @since 7.4
- *
- * @param tabElement
- * the element to be read
- * @param designContext
- * the design context
- */
- private void readTabFromDesign(Element tabElement,
- DesignContext designContext) {
- Attributes attr = tabElement.attributes();
- if (tabElement.children().size() != 1) {
- throw new DesignException(
- "A tab must have exactly one child element");
- }
- // create the component that is in tab content
- Element content = tabElement.child(0);
- Component child = designContext.readDesign(content);
- Tab tab = this.addTab(child);
- if (attr.hasKey("visible")) {
- tab.setVisible(DesignAttributeHandler.readAttribute("visible", attr,
- Boolean.class));
- }
- if (attr.hasKey("closable")) {
- tab.setClosable(DesignAttributeHandler.readAttribute("closable",
- attr, Boolean.class));
- }
- if (attr.hasKey("caption")) {
- tab.setCaption(DesignAttributeHandler.readAttribute("caption", attr,
- String.class));
- }
- if (attr.hasKey("enabled")) {
- tab.setEnabled(DesignAttributeHandler.readAttribute("enabled", attr,
- Boolean.class));
- }
- if (attr.hasKey("icon")) {
- tab.setIcon(DesignAttributeHandler.readAttribute("icon", attr,
- Resource.class));
- }
- if (attr.hasKey("icon-alt")) {
- tab.setIconAlternateText(DesignAttributeHandler
- .readAttribute("icon-alt", attr, String.class));
- }
- if (attr.hasKey("description")) {
- tab.setDescription(DesignAttributeHandler
- .readAttribute("description", attr, String.class));
- }
- if (attr.hasKey("style-name")) {
- tab.setStyleName(DesignAttributeHandler.readAttribute("style-name",
- attr, String.class));
- }
- if (attr.hasKey("id")) {
- tab.setId(DesignAttributeHandler.readAttribute("id", attr,
- String.class));
- }
- if (attr.hasKey("selected")) {
- boolean selected = DesignAttributeHandler.readAttribute("selected",
- attr, Boolean.class);
- if (selected) {
- this.setSelectedTab(tab.getComponent());
- }
- }
- }
-
- /**
- * Writes the given tab to design
- *
- * @since 7.4
- * @param design
- * the design node for tabsheet
- * @param designContext
- * the design context
- * @param tab
- * the tab to be written
- */
- private void writeTabToDesign(Element design, DesignContext designContext,
- Tab tab) {
- // get default tab instance
- Tab def = new TabSheetTabImpl(null, null, null);
- // create element for tab
- Element tabElement = design.appendElement("tab");
- // add tab content
- tabElement.appendChild(designContext.createElement(tab.getComponent()));
- Attributes attr = tabElement.attributes();
- // write attributes
- DesignAttributeHandler.writeAttribute("visible", attr, tab.isVisible(),
- def.isVisible(), Boolean.class, designContext);
- DesignAttributeHandler.writeAttribute("closable", attr,
- tab.isClosable(), def.isClosable(), Boolean.class,
- designContext);
- DesignAttributeHandler.writeAttribute("caption", attr, tab.getCaption(),
- def.getCaption(), String.class, designContext);
- DesignAttributeHandler.writeAttribute("enabled", attr, tab.isEnabled(),
- def.isEnabled(), Boolean.class, designContext);
- DesignAttributeHandler.writeAttribute("icon", attr, tab.getIcon(),
- def.getIcon(), Resource.class, designContext);
- DesignAttributeHandler.writeAttribute("icon-alt", attr,
- tab.getIconAlternateText(), def.getIconAlternateText(),
- String.class, designContext);
- DesignAttributeHandler.writeAttribute("description", attr,
- tab.getDescription(), def.getDescription(), String.class,
- designContext);
- DesignAttributeHandler.writeAttribute("style-name", attr,
- tab.getStyleName(), def.getStyleName(), String.class,
- designContext);
- DesignAttributeHandler.writeAttribute("id", attr, tab.getId(),
- def.getId(), String.class, designContext);
- if (getSelectedTab() != null
- && getSelectedTab().equals(tab.getComponent())) {
- // use write attribute to get consistent handling for boolean
- DesignAttributeHandler.writeAttribute("selected", attr, true, false,
- boolean.class, designContext);
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.AbstractComponent#getCustomAttributes()
- */
- @Override
- protected Collection<String> getCustomAttributes() {
- Collection<String> attributes = super.getCustomAttributes();
- // no need to list tab attributes since they are considered internal
- return attributes;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
- * , com.vaadin.ui.declarative.DesignContext)
- */
- @Override
- public void writeDesign(Element design, DesignContext designContext) {
- super.writeDesign(design, designContext);
- TabSheet def = (TabSheet) designContext.getDefaultInstance(this);
- Attributes attr = design.attributes();
-
- // write tabs
- if (!designContext.shouldWriteChildren(this, def)) {
- return;
- }
- for (Component component : this) {
- Tab tab = this.getTab(component);
- writeTabToDesign(design, designContext, tab);
- }
- }
-
- /**
- * Sets whether HTML is allowed in the tab captions.
- * <p>
- * If set to true, the captions are rendered in the browser as HTML and the
- * developer is responsible for ensuring no harmful HTML is used. If set to
- * false, the content is rendered in the browser as plain text.
- * <p>
- * The default is false, i.e. render tab captions as plain text
- *
- * @param tabCaptionsAsHtml
- * true if the tab captions are rendered as HTML, false if
- * rendered as plain text
- * @since 7.4
- */
- public void setTabCaptionsAsHtml(boolean tabCaptionsAsHtml) {
- getState().tabCaptionsAsHtml = tabCaptionsAsHtml;
- }
-
- /**
- * Checks whether HTML is allowed in the tab captions.
- * <p>
- * The default is false, i.e. render tab captions as plain text
- *
- * @return true if the tab captions are rendered as HTML, false if rendered
- * as plain text
- * @since 7.4
- */
- public boolean isTabCaptionsAsHtml() {
- return getState(false).tabCaptionsAsHtml;
- }
- }
|