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

Java tutorial

Introduction

Here is the source code for com.google.gwt.user.client.ui.VerticalSplitPanel.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.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;

/**
 * A panel that arranges two widgets in a single vertical column and allows the
 * user to interactively change the proportion of the height dedicated to each
 * of the two widgets. Widgets contained within a
 * <code>VerticalSplitterPanel</code> will be automatically decorated with
 * scrollbars when necessary.
 * 
 * <p>
 * This widget will <em>only</em> work in quirks mode. If your application is in
 * Standards Mode, use {@link SplitLayoutPanel} instead.
 * </p>
 * 
 * <p>
 * <img class='gallery' src='doc-files/VerticalSplitPanel.png'/>
 * </p>
 * 
 * <h3>CSS Style Rules</h3>
 * <ul>
 * <li>.gwt-VerticalSplitPanel { the panel itself }</li>
 * <li>.gwt-VerticalSplitPanel vsplitter { the splitter }</li>
 * </ul>
 * 
 * @deprecated Use {@link SplitLayoutPanel} instead, but understand that it is
 *             not a drop in replacement for this class. It requires standards
 *             mode, and is most easily used under a {@link RootLayoutPanel} (as
 *             opposed to a {@link RootPanel}
 *
 * @see SplitLayoutPanel
 */
@Deprecated
public final class VerticalSplitPanel extends SplitPanel {
    /**
     * The default resources used by this widget.
     */
    public interface Resources extends ClientBundle {
        /**
         * An image representing the drag thumb.
         */
        @Source("splitPanelThumb.png")
        ImageResource verticalSplitPanelThumb();
    }

    /**
     * Provides a base implementation for splitter layout that relies on CSS
     * positioned layout.
     */
    private static class Impl {
        private static void expandToFitParentHorizontally(Element elem) {
            addAbsolutePositoning(elem);
            DOM.setStyleAttribute(elem, "left", "0");
            DOM.setStyleAttribute(elem, "right", "0");
        }

        protected VerticalSplitPanel panel;

        public void init(VerticalSplitPanel panel) {
            this.panel = panel;

            DOM.setStyleAttribute(panel.getElement(), "position", "relative");

            final Element topElem = panel.getElement(TOP);
            final Element bottomElem = panel.getElement(BOTTOM);

            expandToFitParentHorizontally(topElem);
            expandToFitParentHorizontally(bottomElem);
            expandToFitParentHorizontally(panel.getSplitElement());

            expandToFitParentUsingCssOffsets(panel.container);

            // Snap the bottom wrapper to the bottom side.
            DOM.setStyleAttribute(bottomElem, "bottom", "0");
        }

        public void onAttach() {
        }

        public void onDetach() {
        }

        public void onSplitterResize(int px) {
            setSplitPosition(px);
        }

        public void setSplitPosition(int px) {
            final Element splitElem = panel.getSplitElement();

            final int rootElemHeight = getOffsetHeight(panel.container);
            final int splitElemHeight = getOffsetHeight(splitElem);

            if (rootElemHeight < splitElemHeight) {
                return;
            }

            int newBottomHeight = rootElemHeight - px - splitElemHeight;
            if (px < 0) {
                px = 0;
                newBottomHeight = rootElemHeight - splitElemHeight;
            } else if (newBottomHeight < 0) {
                px = rootElemHeight - splitElemHeight;
                newBottomHeight = 0;
            }

            updateElements(panel.getElement(TOP), splitElem, panel.getElement(BOTTOM), px, px + splitElemHeight,
                    newBottomHeight);
        }

        /**
         * @param topElem
         * @param splitElem
         * @param bottomElem
         * @param topHeight
         * @param bottomTop
         * @param bottomHeight
         */
        protected void updateElements(Element topElem, Element splitElem, Element bottomElem, int topHeight,
                int bottomTop, int bottomHeight) {
            setHeight(topElem, topHeight + "px");

            setTop(splitElem, topHeight + "px");

            setTop(bottomElem, bottomTop + "px");

            // bottom's height is handled by CSS.
        }
    }

    /**
     * Provides an implementation for IE6/7 that relies on 100% length in CSS.
     */
    @SuppressWarnings("unused")
    // will be used by IE6 permutation
    private static class ImplIE6 extends Impl {

        private static void expandToFitParentHorizontally(Element elem) {
            addAbsolutePositoning(elem);
            setLeft(elem, "0");
            setWidth(elem, "100%");
        }

        private boolean isResizeInProgress = false;

        private int splitPosition;

        private boolean isTopHidden = false, isBottomHidden = false;

        @Override
        public void init(VerticalSplitPanel panel) {
            this.panel = panel;

            final Element elem = panel.getElement();

            // Prevents inherited text-align settings from interfering with the
            // panel's layout.
            DOM.setStyleAttribute(elem, "textAlign", "left");
            DOM.setStyleAttribute(elem, "position", "relative");

            final Element topElem = panel.getElement(TOP);
            final Element bottomElem = panel.getElement(BOTTOM);

            expandToFitParentHorizontally(topElem);
            expandToFitParentHorizontally(bottomElem);
            expandToFitParentHorizontally(panel.getSplitElement());

            expandToFitParentUsingPercentages(panel.container);
        }

        @Override
        public void onAttach() {
            addResizeListener(panel.container);
            onResize();
        }

        @Override
        public void onDetach() {
            DOM.setElementProperty(panel.container, "onresize", null);
        }

        @Override
        public void onSplitterResize(int px) {
            /*
             * IE6/7 has event priority issues that will prevent the repaints from
             * happening quickly enough causing the interaction to seem unresponsive.
             * The following is simply a poor man's mouse event coalescing.
             */
            final int resizeUpdatePeriod = 20; // ms
            if (!isResizeInProgress) {
                isResizeInProgress = true;
                new Timer() {
                    @Override
                    public void run() {
                        setSplitPosition(splitPosition);
                        isResizeInProgress = false;
                    }
                }.schedule(resizeUpdatePeriod);
            }
            splitPosition = px;
        }

        @Override
        protected void updateElements(Element topElem, Element splitElem, Element bottomElem, int topHeight,
                int bottomTop, int bottomHeight) {
            /*
             * IE6/7 has a quirk where a zero height element with non-zero height
             * children will expand larger than 100%. To prevent this, the width is
             * explicitly set to zero when height is zero.
             */
            if (topHeight == 0) {
                setWidth(topElem, "0px");
                isTopHidden = true;
            } else if (isTopHidden) {
                setWidth(topElem, "100%");
                isTopHidden = false;
            }

            if (bottomHeight == 0) {
                setWidth(bottomElem, "0px");
                isBottomHidden = true;
            } else if (isBottomHidden) {
                setWidth(bottomElem, "100%");
                isBottomHidden = false;
            }

            super.updateElements(topElem, splitElem, bottomElem, topHeight, bottomTop, bottomHeight);

            // IE6/7 cannot update properly with CSS alone.
            setHeight(bottomElem, bottomHeight + "px");
        }

        private native void addResizeListener(Element container) /*-{
                                                                 var self = this;
                                                                 container.onresize = $entry(function() {
                                                                 self.@com.google.gwt.user.client.ui.VerticalSplitPanel$ImplIE6::onResize()();
                                                                 });
                                                                 }-*/;

        private void onResize() {
            setSplitPosition(getOffsetHeight(panel.getElement(TOP)));
        }
    }

    /**
     * Constant makes for readable calls to {@link #getElement(int)} and
     * {@link #getWidget(int)}.
     */
    private static final int TOP = 0;

    /**
     * Constant makes for readable calls to {@link #getElement(int)} and
     * {@link #getWidget(int)}.
     */
    private static final int BOTTOM = 1;

    // Captures the height of the top container when drag resizing starts.
    private int initialTopHeight = 0;

    // Captures the offset of a user's mouse pointer during drag resizing.
    private int initialThumbPos = 0;

    // A style-free element to serve as the root container.
    private final Element container;

    private final Impl impl = GWT.create(Impl.class);

    private String lastSplitPosition;

    public VerticalSplitPanel() {
        this(GWT.<Resources>create(Resources.class));
    }

    /**
     * Creates an empty vertical split panel.
     * @deprecated replaced by {@link #VerticalSplitPanel(Resources)}
     */
    @Deprecated
    public VerticalSplitPanel(VerticalSplitPanelImages images) {
        this(images.verticalSplitPanelThumb());
    }

    public VerticalSplitPanel(Resources resources) {
        this(AbstractImagePrototype.create(resources.verticalSplitPanelThumb()));
    }

    private VerticalSplitPanel(AbstractImagePrototype thumbImage) {
        super(DOM.createDiv(), DOM.createDiv(), preventBoxStyles(DOM.createDiv()),
                preventBoxStyles(DOM.createDiv()));

        container = preventBoxStyles(DOM.createDiv());

        buildDOM(thumbImage);

        setStyleName("gwt-VerticalSplitPanel");

        impl.init(this);

        setSplitPosition("50%");
    }

    /**
     * Gets the widget in the bottom of the panel.
     * 
     * @return the widget, <code>null</code> if there is not one
     */
    public Widget getBottomWidget() {
        return getWidget(BOTTOM);
    }

    /**
     * Gets the widget in the top of the panel.
     * 
     * @return the widget, <code>null</code> if there is not one
     */
    public Widget getTopWidget() {
        return getWidget(TOP);
    }

    /**
     * Sets the widget in the bottom of the panel.
     * 
     * @param w the widget
     */
    public void setBottomWidget(Widget w) {
        setWidget(BOTTOM, w);
    }

    @Override
    public void setHeight(String height) {
        super.setHeight(height);
    }

    @Override
    public void setSplitPosition(String pos) {
        lastSplitPosition = pos;
        final Element topElem = getElement(TOP);
        setHeight(topElem, pos);
        impl.setSplitPosition(getOffsetHeight(topElem));
    }

    /**
     * Sets the widget in the top of the panel.
     * 
     * @param w the widget
     */
    public void setTopWidget(Widget w) {
        setWidget(TOP, w);
    }

    /**
     * <b>Affected Elements:</b>
     * <ul>
     * <li>-splitter = the container containing the splitter element.</li>
     * <li>-top = the container above the splitter.</li>
     * <li>-bottom = the container below the splitter.</li>
     * </ul>
     * 
     * @see UIObject#onEnsureDebugId(String)
     */
    @Override
    protected void onEnsureDebugId(String baseID) {
        super.onEnsureDebugId(baseID);
        ensureDebugId(getElement(TOP), baseID, "top");
        ensureDebugId(getElement(BOTTOM), baseID, "bottom");
    }

    @Override
    protected void onLoad() {
        impl.onAttach();

        /*
         * Set the position realizing it might not work until after layout runs.
         * This first call is simply to try to avoid a jitter effect if possible.
         */
        setSplitPosition(lastSplitPosition);
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                setSplitPosition(lastSplitPosition);
            }
        });
    }

    @Override
    protected void onUnload() {
        impl.onDetach();
    }

    @Override
    void onSplitterResize(int x, int y) {
        impl.onSplitterResize(initialTopHeight + y - initialThumbPos);
    }

    @Override
    void onSplitterResizeStarted(int x, int y) {
        initialThumbPos = y;
        initialTopHeight = getOffsetHeight(getElement(TOP));
    }

    private void buildDOM(AbstractImagePrototype thumb) {
        final Element topDiv = getElement(TOP);
        final Element bottomDiv = getElement(BOTTOM);
        final Element splitDiv = getSplitElement();

        DOM.appendChild(getElement(), container);

        DOM.appendChild(container, topDiv);
        DOM.appendChild(container, splitDiv);
        DOM.appendChild(container, bottomDiv);

        /*
         * The style name is placed on the table rather than splitElem to allow the
         * splitter to be styled without interfering with layout.
         */
        DOM.setInnerHTML(splitDiv,
                "<div class='vsplitter' " + "style='text-align:center;'>" + thumb.getHTML() + "</div>");

        addScrolling(topDiv);
        addScrolling(bottomDiv);
    }
}