Java tutorial
/* * Copyright 2009 Google Inc. * * 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 hu.mapro.gwt.client.widget; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Cursor; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.BeforeSelectionEvent; import com.google.gwt.event.logical.shared.BeforeSelectionHandler; import com.google.gwt.event.logical.shared.HasBeforeSelectionHandlers; import com.google.gwt.event.logical.shared.HasSelectionHandlers; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.layout.client.Layout.Alignment; import com.google.gwt.layout.client.Layout.AnimationCallback; import com.google.gwt.layout.client.Layout.Layer; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CommonResources; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.resources.client.ImageResource.ImageOptions; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.client.ui.AnimatedLayout; import com.google.gwt.user.client.ui.DeckLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.IndexedPanel; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.LayoutPanel; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.ProvidesResize; import com.google.gwt.user.client.ui.ResizeComposite; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; /** * A panel that represents a tabbed set of pages, each of which contains another * widget. Its child widgets are shown as the user selects the various tabs * associated with them. The tabs can contain arbitrary text, HTML, or widgets. * * <p> * This widget will <em>only</em> work in standards mode, which requires that * the HTML page in which it is run have an explicit <!DOCTYPE> * declaration. * </p> * * <h3>CSS Style Rules</h3> * <dl> * <dt>.gwt-TabLayoutPanel * <dd>the panel itself * <dt>.gwt-TabLayoutPanel .gwt-TabLayoutPanelTabs * <dd>the tab bar element * <dt>.gwt-TabLayoutPanel .gwt-TabLayoutPanelTab * <dd>an individual tab * <dt>.gwt-TabLayoutPanel .gwt-TabLayoutPanelTabInner * <dd>an element nested in each tab (useful for styling) * <dt>.gwt-TabLayoutPanel .gwt-TabLayoutPanelContent * <dd>applied to all child content widgets * </dl> * * <p> * <h3>Example</h3> * {@example com.google.gwt.examples.TabLayoutPanelExample} * * <h3>Use in UiBinder Templates</h3> * <p> * A TabLayoutPanel element in a {@link com.google.gwt.uibinder.client.UiBinder * UiBinder} template must have a <code>barHeight</code> attribute with a double * value, and may have a <code>barUnit</code> attribute with a * {@link com.google.gwt.dom.client.Style.Unit Style.Unit} value. * <code>barUnit</code> defaults to PX. * <p> * The children of a TabLayoutPanel element are laid out in <g:tab> elements. * Each tab can have one widget child and one of two types of header elements. A * <g:header> element can hold html, or a <g:customHeader> element can * hold a widget. (Note that the tags of the header elements are not * capitalized. This is meant to signal that the head is not a runtime object, * and so cannot have a <code>ui:field</code> attribute.) * <p> * For example: * * <pre> * <g:TabLayoutPanel barUnit='EM' barHeight='3'> * <g:tab> * <g:header size='7'><b>HTML</b> header</g:header> * <g:Label>able</g:Label> * </g:tab> * <g:tab> * <g:customHeader size='7'> * <g:Label>Custom header</g:Label> * </g:customHeader> * <g:Label>baker</g:Label> * </g:tab> * </g:TabLayoutPanel> * </pre> */ public class MyTabLayoutPanel extends ResizeComposite implements HasWidgets, ProvidesResize, IndexedPanel.ForIsWidget, AnimatedLayout, HasBeforeSelectionHandlers<Integer>, HasSelectionHandlers<Integer> { /** * Resource file used for setting up images in the TabLayoutPanel. */ public interface Resources extends ClientBundle { @ImageOptions(flipRtl = true) ImageResource nextTab(); @ImageOptions(flipRtl = true) ImageResource nextTabDisabled(); @ImageOptions(flipRtl = true) ImageResource previousTab(); @ImageOptions(flipRtl = true) ImageResource previousTabDisabled(); } private class Tab extends SimplePanel { private Element inner; private boolean replacingWidget; public Tab(Widget child) { super(Document.get().createDivElement()); getElement().appendChild(inner = Document.get().createDivElement()); setWidget(child); setStyleName(TAB_STYLE); getElement().addClassName(CommonResources.getInlineBlockStyle()); } public HandlerRegistration addClickHandler(ClickHandler handler) { return addDomHandler(handler, ClickEvent.getType()); } @Override public boolean remove(Widget w) { /* * Removal of items from the TabBar is delegated to the TabLayoutPanel to * ensure consistency. */ int index = tabs.indexOf(this); if (replacingWidget || index < 0) { /* * The tab contents are being replaced, or this tab is no longer in the * panel, so just remove the widget. */ return super.remove(w); } else { // Delegate to the TabLayoutPanel. return MyTabLayoutPanel.this.remove(index); } } public void setSelected(boolean selected) { if (selected) { addStyleDependentName("selected"); } else { removeStyleDependentName("selected"); } } @Override public void setWidget(Widget w) { replacingWidget = true; super.setWidget(w); replacingWidget = false; maybeShowButtons(); } @Override protected com.google.gwt.user.client.Element getContainerElement() { return inner.cast(); } } /** * This extension of DeckLayoutPanel overrides the public mutator methods to * * prevent external callers from adding to the state of the DeckPanel. * <p> * Removal of Widgets is supported so that WidgetCollection.WidgetIterator * operates as expected. * </p> * <p> * We ensure that the DeckLayoutPanel cannot become of of sync with its * associated TabBar by delegating all mutations to the TabBar to this * implementation of DeckLayoutPanel. * </p> */ private class TabbedDeckLayoutPanel extends DeckLayoutPanel { @Override public void add(Widget w) { throw new UnsupportedOperationException("Use TabLayoutPanel.add() to alter the DeckLayoutPanel"); } @Override public void clear() { throw new UnsupportedOperationException("Use TabLayoutPanel.clear() to alter the DeckLayoutPanel"); } @Override public void insert(Widget w, int beforeIndex) { throw new UnsupportedOperationException("Use TabLayoutPanel.insert() to alter the DeckLayoutPanel"); } @Override public boolean remove(Widget w) { /* * Removal of items from the DeckLayoutPanel is delegated to the * TabLayoutPanel to ensure consistency. */ return MyTabLayoutPanel.this.remove(w); } protected void insertProtected(Widget w, int beforeIndex) { super.insert(w, beforeIndex); } protected void removeProtected(Widget w) { super.remove(w); } } private static final String CONTENT_CONTAINER_STYLE = "gwt-TabLayoutPanelContentContainer"; private static final String CONTENT_STYLE = "gwt-TabLayoutPanelContent"; private static final String TAB_STYLE = "gwt-TabLayoutPanelTab"; private static Resources resources; private static final int QUEUE_NEXT = -2; private static final int QUEUE_PREV = -3; private static final int BIG_ENOUGH_TO_NOT_WRAP = 16384; private static Resources getResources() { if (resources == null) { resources = GWT.create(Resources.class); } return resources; } private final TabbedDeckLayoutPanel deckPanel = new TabbedDeckLayoutPanel(); private final FlowPanel tabBar = new FlowPanel(); private final LayoutPanel tabBarAnimator = new LayoutPanel(); private final LayoutPanel tabNavPanel = new LayoutPanel(); private final ArrayList<Tab> tabs = new ArrayList<Tab>(); private SimplePanel nextButtonPanel = new SimplePanel(); private SimplePanel previousButtonPanel = new SimplePanel(); private int selectedIndex = -1; private final Image previous, next, nextDisabled, previousDisabled; private int firstVisibleTab = -1, lastVisibleTab = -1; private List<Integer> tabQueue = new ArrayList<Integer>(); private int tabAnimation; private boolean isAnimating = false; private int tabmargin = 0; /** * Creates an empty tab panel. * * @param barHeight the size of the tab bar * @param barUnit the unit in which the tab bar size is specified * */ public MyTabLayoutPanel(double barHeight, Unit barUnit) { this(barHeight, barUnit, getResources()); } /** * Creates an empty tab panel. * * @param barHeight the size of the tab bar * @param barUnit the unit in which the tab bar size is specified * @param resource the ClientBundle to use for some internal images. */ public MyTabLayoutPanel(double barHeight, Unit barUnit, Resources resource) { LayoutPanel panel = new LayoutPanel(); initWidget(panel); // Add the tab bar to the panel. Panel navPanel = getTabNavPanel(); navPanel.add(tabBarAnimator); tabBarAnimator.add(tabBar); navPanel.add(previousButtonPanel); navPanel.add(nextButtonPanel); navPanel.getElement().getStyle().setHeight(barHeight, barUnit); setTabAnimiationDuration(0); panel.add(navPanel); panel.setWidgetLeftRight(navPanel, 0, Unit.PX, 0, Unit.PX); panel.setWidgetTopHeight(navPanel, 0, Unit.PX, barHeight, barUnit); panel.setWidgetVerticalPosition(navPanel, Alignment.END); // Add the deck panel to the panel. deckPanel.addStyleName(CONTENT_CONTAINER_STYLE); panel.add(deckPanel); panel.setWidgetLeftRight(deckPanel, 0, Unit.PX, 0, Unit.PX); panel.setWidgetTopBottom(deckPanel, barHeight, barUnit, 0, Unit.PX); // Make the tab bar extremely wide so that tabs themselves never wrap. // (Its layout container is overflow:hidden) tabBar.getElement().getStyle().setWidth(BIG_ENOUGH_TO_NOT_WRAP, Unit.PX); tabBar.setStyleName("gwt-TabLayoutPanelTabs"); setStyleName("gwt-TabLayoutPanel"); setTabNavOffset(0); previous = new Image(resource.previousTab()); previous.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { selectPreviousTab(); } }); next = new Image(resource.nextTab()); next.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { selectNextTab(); } }); next.getElement().getStyle().setCursor(Cursor.POINTER); previous.getElement().getStyle().setCursor(Cursor.POINTER); previousDisabled = new Image(resource.previousTabDisabled()); nextDisabled = new Image(resource.nextTabDisabled()); previousButtonPanel.add(previous); nextButtonPanel.add(next); setNavButtonsVisible(false); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void add(IsWidget w) { add(asWidgetOrNull(w)); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void add(IsWidget w, IsWidget tab) { add(asWidgetOrNull(w), asWidgetOrNull(tab)); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void add(IsWidget w, String text) { add(asWidgetOrNull(w), text); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void add(IsWidget w, String text, boolean asHtml) { add(asWidgetOrNull(w), text, asHtml); } @Override public void add(Widget w) { insert(w, getWidgetCount()); } /** * Adds a widget to the panel. If the Widget is already attached, it will be * moved to the right-most index. * * @param child the widget to be added * @param html the html to be shown on its tab */ public void add(Widget child, SafeHtml html) { add(child, html.asString(), true); } /** * Adds a widget to the panel. If the Widget is already attached, it will be * moved to the right-most index. * * @param child the widget to be added * @param text the text to be shown on its tab */ public void add(Widget child, String text) { insert(child, text, getWidgetCount()); } /** * Adds a widget to the panel. If the Widget is already attached, it will be * moved to the right-most index. * * @param child the widget to be added * @param text the text to be shown on its tab * @param asHtml <code>true</code> to treat the specified text as HTML */ public void add(Widget child, String text, boolean asHtml) { insert(child, text, asHtml, getWidgetCount()); } /** * Adds a widget to the panel. If the Widget is already attached, it will be * moved to the right-most index. * * @param child the widget to be added * @param tab the widget to be placed in the associated tab */ public void add(Widget child, Widget tab) { insert(child, tab, getWidgetCount()); } @Override public HandlerRegistration addBeforeSelectionHandler(BeforeSelectionHandler<Integer> handler) { return addHandler(handler, BeforeSelectionEvent.getType()); } @Override public HandlerRegistration addSelectionHandler(SelectionHandler<Integer> handler) { return addHandler(handler, SelectionEvent.getType()); } @Override public void animate(int duration) { animate(duration, null); } @Override public void animate(int duration, AnimationCallback callback) { deckPanel.animate(duration, callback); } @Override public void clear() { Iterator<Widget> it = iterator(); while (it.hasNext()) { it.next(); it.remove(); } } @Override public void forceLayout() { deckPanel.forceLayout(); } /** * Get the duration of the animated transition between tabs. * * @return the duration in milliseconds */ public int getAnimationDuration() { return deckPanel.getAnimationDuration(); } /** * Gets the index of the currently-selected tab. * * @return the selected index, or <code>-1</code> if none is selected. */ public int getSelectedIndex() { return selectedIndex; } /** * Gets the widget in the tab at the given index. * * @param index the index of the tab to be retrieved * @return the tab's widget */ public Widget getTabWidget(int index) { checkIndex(index); return tabs.get(index).getWidget(); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public Widget getTabWidget(IsWidget child) { return getTabWidget(asWidgetOrNull(child)); } /** * Gets the widget in the tab associated with the given child widget. * * @param child the child whose tab is to be retrieved * @return the tab's widget */ public Widget getTabWidget(Widget child) { checkChild(child); return getTabWidget(getWidgetIndex(child)); } /** * Returns the widget at the given index. */ @Override public Widget getWidget(int index) { return deckPanel.getWidget(index); } /** * Returns the number of tabs and widgets. */ @Override public int getWidgetCount() { return deckPanel.getWidgetCount(); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ @Override public int getWidgetIndex(IsWidget child) { return getWidgetIndex(asWidgetOrNull(child)); } /** * Returns the index of the given child, or -1 if it is not a child. */ @Override public int getWidgetIndex(Widget child) { return deckPanel.getWidgetIndex(child); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void insert(IsWidget child, int beforeIndex) { insert(asWidgetOrNull(child), beforeIndex); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void insert(IsWidget child, IsWidget tab, int beforeIndex) { insert(asWidgetOrNull(child), asWidgetOrNull(tab), beforeIndex); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void insert(IsWidget child, String text, boolean asHtml, int beforeIndex) { insert(asWidgetOrNull(child), text, asHtml, beforeIndex); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void insert(IsWidget child, String text, int beforeIndex) { insert(asWidgetOrNull(child), text, beforeIndex); } /** * Inserts a widget into the panel. If the Widget is already attached, it will * be moved to the requested index. * * @param child the widget to be added * @param beforeIndex the index before which it will be inserted */ public void insert(Widget child, int beforeIndex) { insert(child, "", beforeIndex); } /** * Inserts a widget into the panel. If the Widget is already attached, it will * be moved to the requested index. * * @param child the widget to be added * @param html the html to be shown on its tab * @param beforeIndex the index before which it will be inserted */ public void insert(Widget child, SafeHtml html, int beforeIndex) { insert(child, html.asString(), true, beforeIndex); } /** * Inserts a widget into the panel. If the Widget is already attached, it will * be moved to the requested index. * * @param child the widget to be added * @param text the text to be shown on its tab * @param asHtml <code>true</code> to treat the specified text as HTML * @param beforeIndex the index before which it will be inserted */ public void insert(Widget child, String text, boolean asHtml, int beforeIndex) { Widget contents; if (asHtml) { contents = new HTML(text); } else { contents = new Label(text); } insert(child, contents, beforeIndex); } /** * Inserts a widget into the panel. If the Widget is already attached, it will * be moved to the requested index. * * @param child the widget to be added * @param text the text to be shown on its tab * @param beforeIndex the index before which it will be inserted */ public void insert(Widget child, String text, int beforeIndex) { insert(child, text, false, beforeIndex); } /** * Inserts a widget into the panel. If the Widget is already attached, it will * be moved to the requested index. * * @param child the widget to be added * @param tab the widget to be placed in the associated tab * @param beforeIndex the index before which it will be inserted */ public void insert(Widget child, Widget tab, int beforeIndex) { insert(child, new Tab(tab), beforeIndex); } /** * Check whether or not transitions slide in vertically or horizontally. * Defaults to horizontally. * * @return true for vertical transitions, false for horizontal */ public boolean isAnimationVertical() { return deckPanel.isAnimationVertical(); } public boolean isNavButtonsVisible() { return nextButtonPanel.isVisible() && previousButtonPanel.isVisible(); } @Override public Iterator<Widget> iterator() { return deckPanel.iterator(); } @Override public void onResize() { maybeShowButtons(); super.onResize(); tabNavPanel.onResize(); deckPanel.onResize(); if (lastVisibleTab == tabs.size() - 1 && firstVisibleTab != 0) { /* * This is ensures that on resize if we're displaying the last tab, that * the hidden tabs in the beginning of the list will make themselves * visible with the resizing of the resizing of the element containing * this TabLayoutPanel. */ int position = getTabEndPosition(lastVisibleTab); int width = getOffsetWidth(); width = width - position; width -= getPreviousImage().getOffsetWidth(); setTabNavOffset(getTabNavOffset() + width); } else if (lastVisibleTab == tabs.size() - 1 && firstVisibleTab == 0) { // No tabs are display setTabNavOffset(0); } } @Override public boolean remove(int index) { if ((index < 0) || (index >= getWidgetCount())) { return false; } Widget child = getWidget(index); tabBar.remove(index); deckPanel.removeProtected(child); child.removeStyleName(CONTENT_STYLE); Tab tab = tabs.remove(index); tab.getWidget().removeFromParent(); maybeShowButtons(); if (index == selectedIndex) { // If the selected tab is being removed, select the first tab (if there // is one). selectedIndex = -1; if (getWidgetCount() > 0) { selectTab(0); } } else if (index < selectedIndex) { // If the selectedIndex is greater than the one being removed, it needs // to be adjusted. --selectedIndex; } return true; } @Override public boolean remove(Widget w) { int index = getWidgetIndex(w); if (index == -1) { return false; } return remove(index); } /** * Selects to the next tab. If there isn't a next tab, it does nothing. */ public void selectNextTab() { if (!isNavButtonsVisible()) { return; } int index = lastVisibleTab + 1; if (index < tabs.size()) { addToQueue(QUEUE_NEXT); } } /** * Scrolls to the previous tab. If there isn't a previous tab, it does * nothing. */ public void selectPreviousTab() { if (!isNavButtonsVisible()) { return; } int index = firstVisibleTab - 1; if (index >= 0) { addToQueue(QUEUE_PREV); } } /** * Programmatically selects the specified tab and fires events. * * @param index the index of the tab to be selected */ public void selectTab(int index) { selectTab(index, true); } /** * Programmatically selects the specified tab. * * @param index the index of the tab to be selected * @param fireEvents true to fire events, false not to */ public void selectTab(int index, boolean fireEvents) { checkIndex(index); if (index == selectedIndex) { return; } // Fire the before selection event, giving the recipients a chance to // cancel the selection. if (fireEvents) { BeforeSelectionEvent<Integer> event = BeforeSelectionEvent.fire(this, index); if ((event != null) && event.isCanceled()) { return; } } // Update the tabs being selected and unselected. if (selectedIndex != -1) { tabs.get(selectedIndex).setSelected(false); } deckPanel.showWidget(index); tabs.get(index).setSelected(true); selectedIndex = index; addToQueue(index); // Fire the selection event. if (fireEvents) { SelectionEvent.fire(this, index); } } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void selectTab(IsWidget child) { selectTab(asWidgetOrNull(child)); } /** * Convenience overload to allow {@link IsWidget} to be used directly. */ public void selectTab(IsWidget child, boolean fireEvents) { selectTab(asWidgetOrNull(child), fireEvents); } /** * Programmatically selects the specified tab and fires events. * * @param child the child whose tab is to be selected */ public void selectTab(Widget child) { selectTab(getWidgetIndex(child)); } /** * Programmatically selects the specified tab. * * @param child the child whose tab is to be selected * @param fireEvents true to fire events, false not to */ public void selectTab(Widget child, boolean fireEvents) { selectTab(getWidgetIndex(child), fireEvents); } /** * Set the duration of the animated transition between tabs. * * @param duration the duration in milliseconds. */ public void setAnimationDuration(int duration) { deckPanel.setAnimationDuration(duration); } /** * Set whether or not transitions slide in vertically or horizontally. * * @param isVertical true for vertical transitions, false for horizontal */ public void setAnimationVertical(boolean isVertical) { deckPanel.setAnimationVertical(isVertical); } /** * Sets the tab animation duration. */ public void setTabAnimiationDuration(int duration) { tabAnimation = duration; } /** * Sets a tab's HTML contents. * * @param index the index of the tab whose HTML is to be set * @param html the tab's new HTML contents */ public void setTabHTML(int index, SafeHtml html) { setTabHTML(index, html.asString()); } /** * Sets a tab's HTML contents. * * Use care when setting an object's HTML; it is an easy way to expose * script-based security problems. Consider using * {@link #setTabHTML(int, SafeHtml)} or {@link #setTabText(int, String)} * whenever possible. * * @param index the index of the tab whose HTML is to be set * @param html the tab's new HTML contents */ public void setTabHTML(int index, String html) { checkIndex(index); tabs.get(index).setWidget(new HTML(html)); } /** * Sets a tab's text contents. * * @param index the index of the tab whose text is to be set * @param text the object's new text */ public void setTabText(int index, String text) { checkIndex(index); tabs.get(index).setWidget(new Label(text)); } protected void doTabLayout() { if (tabAnimation == 0) { getTabNavPanel().forceLayout(); } else { getTabNavPanel().animate(tabAnimation); } } protected void ensureTabInView(int tab) { assert (tab >= 0 && tab < tabs.size()) : "Index out of bounds"; if (isTabVisible(tab)) { return; } int positionToMoveTo = 0; if (tab < firstVisibleTab) { if (tab != 0) { positionToMoveTo = getTabNavOffset() - getTabEndPosition(tab - 1) + getPreviousImage().getOffsetWidth(); } else { positionToMoveTo = 0; } } else { int width = getTabBarWidth(); int tabWidth = getTabEndPosition(tab) - getTabNavOffset(); positionToMoveTo = width - tabWidth + getPreviousImage().getOffsetWidth(); } setTabNavOffset(positionToMoveTo); } protected void ensureTabInView(Tab tab) { int index = tabs.indexOf(tab); if (index == -1) { return; } ensureTabInView(index); } /** * @return the @{Link Image} of the next button in the enabled state */ protected Image getNextActiveImage() { return next; } /** * @return the @{Link Image} of the next button in the disabled state */ protected Image getNextDisabledImage() { return nextDisabled; } protected Image getNextImage() { return lastVisibleTab == tabs.size() - 1 ? getNextDisabledImage() : getNextActiveImage(); } /** * @return the @{Link Image} of the previous button in the enabled state */ protected Image getPreviousActiveImage() { return previous; } /** * @return the @{Link Image} of the previous button in the disabled state */ protected Image getPreviousDisabledImage() { return previousDisabled; } protected Image getPreviousImage() { return firstVisibleTab == 0 ? getPreviousDisabledImage() : getPreviousActiveImage(); } /** * Gets the width of the tab bar. * * @return how wide the tab bar is. */ protected int getTabBarWidth() { int width = getOffsetWidth(); width -= getPreviousImage().getOffsetWidth() + getNextImage().getOffsetWidth(); return width; } /** * Gets the current tab nav margin in pixels. If the tab nav margin is * manually set to something other than pixels, it will throw an @{link * IllegalArgumentException} * * @return the tab nav margin in pixels. */ protected int getTabNavOffset() { return tabmargin; } /** * Access to the container of the TabNavigation panel and the container of the * previous and next buttons. * * @return the panel containing the previous/next buttons and the panel * containing tabs */ protected LayoutPanel getTabNavPanel() { return tabNavPanel; } protected boolean isTabVisible(int tab) { if (tab >= firstVisibleTab && tab <= lastVisibleTab) { return true; } return false; } /** * Determines whether or not to display the previous and next buttons. * * @return if the buttons are visible or not. */ protected boolean maybeShowButtons() { int width = getOffsetWidth(); if (tabs.size() == 0) { return false; } int tabWidth = 0; Tab tab = tabs.get(tabs.size() - 1); tabWidth += getTabEndPosition(tab) - getTabNavOffset(); boolean retValue = false; if (!isNavButtonsVisible() && tabWidth > width) { setNavButtonsVisible(true); retValue = true; } else if (isNavButtonsVisible() && tabWidth < width) { setNavButtonsVisible(false); retValue = false; } else { retValue = isNavButtonsVisible(); } updateVisibleTabs(); return retValue; } @Override protected void onAttach() { super.onAttach(); /* * We have to wait until after the widget is attached before we can figure * out what the pixel sizes are. */ Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { maybeShowButtons(); } }); } /** * Sets whether or not the Previous and next buttons are visible. * * @param visible whether or not the buttons shoudl be visible */ protected void setNavButtonsVisible(boolean visible) { nextButtonPanel.setVisible(visible); previousButtonPanel.setVisible(visible); if (visible) { setTabPanelSizes(getPreviousImage().getWidth(), getNextImage().getWidth()); } else { setTabPanelSizes(0, 0); } } /** * Sets the left margin of the tab panel. This is used to allow the scrolling * visibility of tabs. * * @param margin is the new margin in pixels. */ protected void setTabNavOffset(int margin) { if (margin > 0) { margin = 0; } this.tabmargin = margin; if (isRtl()) { tabBarAnimator.setWidgetRightWidth(tabBar, margin, Unit.PX, BIG_ENOUGH_TO_NOT_WRAP, Unit.PX); } else { tabBarAnimator.setWidgetLeftWidth(tabBar, margin, Unit.PX, BIG_ENOUGH_TO_NOT_WRAP, Unit.PX); } AnimationCallback callback = new AnimationCallback() { @Override public void onAnimationComplete() { isAnimating = false; updateVisibleTabs(); tabBar.getElement().getStyle().clearTop(); popQueue(); } @Override public void onLayout(Layer layer, double progress) { /* * Layout panel keeps adding a top element. We want to ensure the tabs * are at the bottom, not the top. */ tabBar.getElement().getStyle().clearTop(); } }; tabBarAnimator.animate(tabAnimation, callback); } private void addToQueue(int tab) { tabQueue.add(tab); popQueue(); } private void checkChild(Widget child) { assert getWidgetIndex(child) >= 0 : "Child is not a part of this panel"; } private void checkIndex(int index) { assert (index >= 0) && (index < getWidgetCount()) : "Index out of bounds"; } private int getTabEndPosition(int tab) { assert (tab >= 0 && tab < tabs.size()) : "Index out of bounds"; return getTabEndPosition(tabs.get(tab)); } private int getTabEndPosition(Tab tab) { if (isRtl()) { return getOffsetWidth() + getAbsoluteLeft() - tab.getAbsoluteLeft(); } else { return tab.getAbsoluteLeft() + tab.getOffsetWidth() - getAbsoluteLeft(); } } private void insert(final Widget child, Tab tab, int beforeIndex) { assert (beforeIndex >= 0) && (beforeIndex <= getWidgetCount()) : "beforeIndex out of bounds"; // Check to see if the TabPanel already contains the Widget. If so, // remove it and see if we need to shift the position to the left. int idx = getWidgetIndex(child); if (idx != -1) { remove(child); if (idx < beforeIndex) { beforeIndex--; } } deckPanel.insertProtected(child, beforeIndex); tabs.add(beforeIndex, tab); tabBar.insert(tab, beforeIndex); tab.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { selectTab(child); } }); if (isAttached()) { // if it isn't attached, this gets run onAttach(); maybeShowButtons(); } child.addStyleName(CONTENT_STYLE); if (selectedIndex == -1) { selectTab(0); } else if (selectedIndex >= beforeIndex) { // If we inserted before the currently selected tab, its index has just // increased. selectedIndex++; } } /** * Convenience method for {@link LocaleInfo}.getCurrentLocale().isRtl(). */ private boolean isRtl() { return LocaleInfo.getCurrentLocale().isRTL(); } private void popQueue() { if (isAnimating || tabQueue.size() == 0) { return; } isAnimating = true; int tab = tabQueue.get(0); tabQueue.remove(0); if (tab == QUEUE_NEXT) { tab = lastVisibleTab + 1; } else if (tab == QUEUE_PREV) { tab = firstVisibleTab - 1; } if (tab < 0 || tab >= tabs.size()) { // if someone spammed on the next/previous buttons, do this again. isAnimating = false; popQueue(); return; } ensureTabInView(tab); } /** * Updates the size of the tabNavPanel. */ private void setTabPanelSizes(int prev, int next) { LayoutPanel panel = getTabNavPanel(); Unit unit = Unit.PX; if (isRtl()) { panel.setWidgetRightWidth(previousButtonPanel, 0, unit, prev, unit); panel.setWidgetLeftWidth(nextButtonPanel, 0, unit, next, unit); panel.setWidgetLeftRight(tabBarAnimator, next, unit, prev, unit); } else { panel.setWidgetLeftWidth(previousButtonPanel, 0, unit, prev, unit); panel.setWidgetRightWidth(nextButtonPanel, 0, unit, next, unit); panel.setWidgetLeftRight(tabBarAnimator, prev, unit, next, unit); } panel.forceLayout(); } /** * Changes the images if necessary. */ private void updateImages() { previousButtonPanel.clear(); previousButtonPanel.add(getPreviousImage()); nextButtonPanel.clear(); nextButtonPanel.add(getNextImage()); } /** * Updates the fistVisibleTab and lastVisibleTab variables and updates images * as appropriate. */ private void updateVisibleTabs() { if (tabs.size() == 0) { return; } int width = getTabBarWidth(); int currentPosition = getTabNavOffset(); boolean foundFirst = false; for (int i = 0; i < tabs.size(); i++) { if (currentPosition >= 0 && !foundFirst) { firstVisibleTab = i; foundFirst = true; } currentPosition = getTabEndPosition(i) - getPreviousImage().getOffsetWidth(); if (currentPosition <= width) { // we're still finding last visible tabs lastVisibleTab = i; } else { // we're outside our visible tab range. break; } } updateImages(); } }