com.google.gwt.user.client.ui.DeckPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.user.client.ui.DeckPanel.java

Source

/*
 * Copyright 2008 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 com.google.gwt.user.client.ui;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;

/**
 * A panel that displays all of its child widgets in a 'deck', where only one
 * can be visible at a time. It is used by
 * {@link com.google.gwt.user.client.ui.TabPanel}.
 * 
 * <p>
 * Once a widget has been added to a DeckPanel, its visibility, width, and
 * height attributes will be manipulated. When the widget is removed from the
 * DeckPanel, it will be visible, and its width and height attributes will be
 * cleared.
 * </p>
 */
public class DeckPanel extends ComplexPanel implements HasAnimation, InsertPanel.ForIsWidget {
    /**
     * An {@link Animation} used to slide in the new content.
     */
    private static class SlideAnimation extends Animation {
        /**
         * The {@link Element} holding the {@link Widget} with a lower index.
         */
        private Element container1 = null;

        /**
         * The {@link Element} holding the {@link Widget} with a higher index.
         */
        private Element container2 = null;

        /**
         * A boolean indicating whether container1 is growing or shrinking.
         */
        private boolean growing = false;

        /**
         * The fixed height of a {@link TabPanel} in pixels. If the {@link TabPanel}
         * does not have a fixed height, this will be set to -1.
         */
        private int fixedHeight = -1;

        /**
         * The old {@link Widget} that is being hidden.
         */
        private Widget oldWidget = null;

        /**
         * Switch to a new {@link Widget}.
         * 
         * @param oldWidget the {@link Widget} to hide
         * @param newWidget the {@link Widget} to show
         * @param animate true to animate, false to switch instantly
         */
        public void showWidget(Widget oldWidget, Widget newWidget, boolean animate) {
            // Immediately complete previous animation
            cancel();

            // Get the container and index of the new widget
            Element newContainer = getContainer(newWidget);
            int newIndex = DOM.getChildIndex(DOM.getParent(newContainer), newContainer);

            // If we aren't showing anything, don't bother with the animation
            if (oldWidget == null) {
                UIObject.setVisible(newContainer, true);
                newWidget.setVisible(true);
                return;
            }
            this.oldWidget = oldWidget;

            // Get the container and index of the old widget
            Element oldContainer = getContainer(oldWidget);
            int oldIndex = DOM.getChildIndex(DOM.getParent(oldContainer), oldContainer);

            // Figure out whether to grow or shrink the container
            if (newIndex > oldIndex) {
                container1 = oldContainer;
                container2 = newContainer;
                growing = false;
            } else {
                container1 = newContainer;
                container2 = oldContainer;
                growing = true;
            }

            // Start the animation
            if (animate) {
                // Figure out if the deck panel has a fixed height
                com.google.gwt.dom.client.Element deckElem = container1.getParentElement();
                int deckHeight = deckElem.getOffsetHeight();
                if (growing) {
                    fixedHeight = container2.getOffsetHeight();
                    container2.getStyle().setPropertyPx("height", Math.max(1, fixedHeight - 1));
                } else {
                    fixedHeight = container1.getOffsetHeight();
                    container1.getStyle().setPropertyPx("height", Math.max(1, fixedHeight - 1));
                }
                if (deckElem.getOffsetHeight() != deckHeight) {
                    fixedHeight = -1;
                }

                // Only scope to the deck if it's fixed height, otherwise it can affect
                // the rest of the page, even if it's not visible to the user.
                run(ANIMATION_DURATION, fixedHeight == -1 ? null : deckElem);
            } else {
                onInstantaneousRun();
            }

            // We call newWidget.setVisible(true) immediately after showing the
            // widget's container so users can delay render their widget. Ultimately,
            // we should have a better way of handling this, but we need to call
            // setVisible for legacy support.
            newWidget.setVisible(true);
        }

        @Override
        protected void onComplete() {
            if (growing) {
                DOM.setStyleAttribute(container1, "height", "100%");
                UIObject.setVisible(container1, true);
                UIObject.setVisible(container2, false);
                DOM.setStyleAttribute(container2, "height", "100%");
            } else {
                UIObject.setVisible(container1, false);
                DOM.setStyleAttribute(container1, "height", "100%");
                DOM.setStyleAttribute(container2, "height", "100%");
                UIObject.setVisible(container2, true);
            }
            DOM.setStyleAttribute(container1, "overflow", "visible");
            DOM.setStyleAttribute(container2, "overflow", "visible");
            container1 = null;
            container2 = null;
            hideOldWidget();
        }

        @Override
        protected void onStart() {
            // Start the animation
            DOM.setStyleAttribute(container1, "overflow", "hidden");
            DOM.setStyleAttribute(container2, "overflow", "hidden");
            onUpdate(0.0);
            UIObject.setVisible(container1, true);
            UIObject.setVisible(container2, true);
        }

        @Override
        protected void onUpdate(double progress) {
            if (!growing) {
                progress = 1.0 - progress;
            }

            // Container1 expands (shrinks) to its target height
            int height1;
            int height2;
            if (fixedHeight == -1) {
                height1 = (int) (progress * DOM.getElementPropertyInt(container1, "scrollHeight"));
                height2 = (int) ((1.0 - progress) * DOM.getElementPropertyInt(container2, "scrollHeight"));
            } else {
                height1 = (int) (progress * fixedHeight);
                height2 = fixedHeight - height1;
            }

            // Issue 2339: If the height is 0px, IE7 will display the entire content
            // widget instead of hiding it completely.
            if (height1 == 0) {
                height1 = 1;
                height2 = Math.max(1, height2 - 1);
            } else if (height2 == 0) {
                height2 = 1;
                height1 = Math.max(1, height1 - 1);
            }
            DOM.setStyleAttribute(container1, "height", height1 + "px");
            DOM.setStyleAttribute(container2, "height", height2 + "px");
        }

        /**
         * Hide the old widget when the animation completes.
         */
        private void hideOldWidget() {
            // Issue 2510: Hiding the widget isn't necessary because we hide its
            // wrapper, but its in here for legacy support.
            oldWidget.setVisible(false);
            oldWidget = null;
        }

        private void onInstantaneousRun() {
            UIObject.setVisible(container1, growing);
            UIObject.setVisible(container2, !growing);
            container1 = null;
            container2 = null;
            hideOldWidget();
        }
    }

    /**
     * The duration of the animation.
     */
    private static final int ANIMATION_DURATION = 350;

    /**
     * The {@link Animation} used to slide in the new {@link Widget}.
     */
    private static SlideAnimation slideAnimation;

    /**
     * The the container {@link Element} around a {@link Widget}.
     * 
     * @param w the {@link Widget}
     * @return the container {@link Element}
     */
    private static Element getContainer(Widget w) {
        return DOM.getParent(w.getElement());
    }

    private boolean isAnimationEnabled = false;

    private Widget visibleWidget;

    /**
     * Creates an empty deck panel.
     */
    public DeckPanel() {
        setElement(DOM.createDiv());
    }

    @Override
    public void add(Widget w) {
        Element container = createWidgetContainer();
        DOM.appendChild(getElement(), container);

        // The order of these methods is very important. In order to preserve
        // backward compatibility, the offsetWidth and offsetHeight of the child
        // widget should be defined (greater than zero) when w.onLoad() is called.
        // As a result, we first initialize the container with a height of 0px, then
        // we attach the child widget to the container. See Issue 2321 for more
        // details.
        super.add(w, container);

        // After w.onLoad is called, it is safe to make the container invisible and
        // set the height of the container and widget to 100%.
        finishWidgetInitialization(container, w);
    }

    /**
     * Gets the index of the currently-visible widget.
     * 
     * @return the visible widget's index
     */
    public int getVisibleWidget() {
        return getWidgetIndex(visibleWidget);
    }

    public void insert(IsWidget w, int beforeIndex) {
        insert(asWidgetOrNull(w), beforeIndex);
    }

    public void insert(Widget w, int beforeIndex) {
        Element container = createWidgetContainer();
        DOM.insertChild(getElement(), container, beforeIndex);

        // See add(Widget) for important comments
        insert(w, container, beforeIndex, true);
        finishWidgetInitialization(container, w);
    }

    public boolean isAnimationEnabled() {
        return isAnimationEnabled;
    }

    @Override
    public boolean remove(Widget w) {
        Element container = getContainer(w);
        boolean removed = super.remove(w);
        if (removed) {
            resetChildWidget(w);

            DOM.removeChild(getElement(), container);
            if (visibleWidget == w) {
                visibleWidget = null;
            }
        }
        return removed;
    }

    public void setAnimationEnabled(boolean enable) {
        isAnimationEnabled = enable;
    }

    /**
     * Shows the widget at the specified index. This causes the currently- visible
     * widget to be hidden.
     * 
     * @param index the index of the widget to be shown
     */
    public void showWidget(int index) {
        checkIndexBoundsForAccess(index);
        Widget oldWidget = visibleWidget;
        visibleWidget = getWidget(index);

        if (visibleWidget != oldWidget) {
            if (slideAnimation == null) {
                slideAnimation = new SlideAnimation();
            }
            slideAnimation.showWidget(oldWidget, visibleWidget, isAnimationEnabled && isAttached());
        }
    }

    /**
     * Setup the container around the widget.
     */
    private Element createWidgetContainer() {
        Element container = DOM.createDiv();
        DOM.setStyleAttribute(container, "width", "100%");
        DOM.setStyleAttribute(container, "height", "0px");
        DOM.setStyleAttribute(container, "padding", "0px");
        DOM.setStyleAttribute(container, "margin", "0px");
        return container;
    }

    /**
     * Setup the container around the widget.
     */
    private void finishWidgetInitialization(Element container, Widget w) {
        UIObject.setVisible(container, false);
        DOM.setStyleAttribute(container, "height", "100%");

        // Set 100% by default.
        Element element = w.getElement();
        if (DOM.getStyleAttribute(element, "width").equals("")) {
            w.setWidth("100%");
        }
        if (DOM.getStyleAttribute(element, "height").equals("")) {
            w.setHeight("100%");
        }

        // Issue 2510: Hiding the widget isn't necessary because we hide its
        // wrapper, but it's in here for legacy support.
        w.setVisible(false);
    }

    /**
     * Reset the dimensions of the widget when it is removed.
     */
    private void resetChildWidget(Widget w) {
        w.setSize("", "");
        w.setVisible(true);
    }
}