Back to index...
/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package javax.swing;
import javax.swing.border.*;
import java.awt.LayoutManager;
import java.awt.Component;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Insets;
import java.io.Serializable;
/**
 * The layout manager used by <code>JScrollPane</code>.
 * <code>JScrollPaneLayout</code> is
 * responsible for nine components: a viewport, two scrollbars,
 * a row header, a column header, and four "corner" components.
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans&trade;
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @see JScrollPane
 * @see JViewport
 *
 * @author Hans Muller
 */
public class ScrollPaneLayout
    implements LayoutManager, ScrollPaneConstants, Serializable
{
    /**
     * The scrollpane's viewport child.
     * Default is an empty <code>JViewport</code>.
     * @see JScrollPane#setViewport
     */
    protected JViewport viewport;
    /**
     * The scrollpane's vertical scrollbar child.
     * Default is a <code>JScrollBar</code>.
     * @see JScrollPane#setVerticalScrollBar
     */
    protected JScrollBar vsb;
    /**
     * The scrollpane's horizontal scrollbar child.
     * Default is a <code>JScrollBar</code>.
     * @see JScrollPane#setHorizontalScrollBar
     */
    protected JScrollBar hsb;
    /**
     * The row header child.  Default is <code>null</code>.
     * @see JScrollPane#setRowHeader
     */
    protected JViewport rowHead;
    /**
     * The column header child.  Default is <code>null</code>.
     * @see JScrollPane#setColumnHeader
     */
    protected JViewport colHead;
    /**
     * The component to display in the lower left corner.
     * Default is <code>null</code>.
     * @see JScrollPane#setCorner
     */
    protected Component lowerLeft;
    /**
     * The component to display in the lower right corner.
     * Default is <code>null</code>.
     * @see JScrollPane#setCorner
     */
    protected Component lowerRight;
    /**
     * The component to display in the upper left corner.
     * Default is <code>null</code>.
     * @see JScrollPane#setCorner
     */
    protected Component upperLeft;
    /**
     * The component to display in the upper right corner.
     * Default is <code>null</code>.
     * @see JScrollPane#setCorner
     */
    protected Component upperRight;
    /**
     * The display policy for the vertical scrollbar.
     * The default is <code>ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED</code>.
     * <p>
     * This field is obsolete, please use the <code>JScrollPane</code> field instead.
     *
     * @see JScrollPane#setVerticalScrollBarPolicy
     */
    protected int vsbPolicy = VERTICAL_SCROLLBAR_AS_NEEDED;
    /**
     * The display policy for the horizontal scrollbar.
     * The default is <code>ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED</code>.
     * <p>
     * This field is obsolete, please use the <code>JScrollPane</code> field instead.
     *
     * @see JScrollPane#setHorizontalScrollBarPolicy
     */
    protected int hsbPolicy = HORIZONTAL_SCROLLBAR_AS_NEEDED;
    /**
     * This method is invoked after the ScrollPaneLayout is set as the
     * LayoutManager of a <code>JScrollPane</code>.
     * It initializes all of the internal fields that
     * are ordinarily set by <code>addLayoutComponent</code>.  For example:
     * <pre>
     * ScrollPaneLayout mySPLayout = new ScrollPanelLayout() {
     *     public void layoutContainer(Container p) {
     *         super.layoutContainer(p);
     *         // do some extra work here ...
     *     }
     * };
     * scrollpane.setLayout(mySPLayout):
     * </pre>
     */
    public void syncWithScrollPane(JScrollPane sp) {
        viewport = sp.getViewport();
        vsb = sp.getVerticalScrollBar();
        hsb = sp.getHorizontalScrollBar();
        rowHead = sp.getRowHeader();
        colHead = sp.getColumnHeader();
        lowerLeft = sp.getCorner(LOWER_LEFT_CORNER);
        lowerRight = sp.getCorner(LOWER_RIGHT_CORNER);
        upperLeft = sp.getCorner(UPPER_LEFT_CORNER);
        upperRight = sp.getCorner(UPPER_RIGHT_CORNER);
        vsbPolicy = sp.getVerticalScrollBarPolicy();
        hsbPolicy = sp.getHorizontalScrollBarPolicy();
    }
    /**
     * Removes an existing component.  When a new component, such as
     * the left corner, or vertical scrollbar, is added, the old one,
     * if it exists, must be removed.
     * <p>
     * This method returns <code>newC</code>. If <code>oldC</code> is
     * not equal to <code>newC</code> and is non-<code>null</code>,
     * it will be removed from its parent.
     *
     * @param oldC the <code>Component</code> to replace
     * @param newC the <code>Component</code> to add
     * @return the <code>newC</code>
     */
    protected Component addSingletonComponent(Component oldC, Component newC)
    {
        if ((oldC != null) && (oldC != newC)) {
            oldC.getParent().remove(oldC);
        }
        return newC;
    }
    /**
     * Adds the specified component to the layout. The layout is
     * identified using one of:
     * <ul>
     * <li>ScrollPaneConstants.VIEWPORT
     * <li>ScrollPaneConstants.VERTICAL_SCROLLBAR
     * <li>ScrollPaneConstants.HORIZONTAL_SCROLLBAR
     * <li>ScrollPaneConstants.ROW_HEADER
     * <li>ScrollPaneConstants.COLUMN_HEADER
     * <li>ScrollPaneConstants.LOWER_LEFT_CORNER
     * <li>ScrollPaneConstants.LOWER_RIGHT_CORNER
     * <li>ScrollPaneConstants.UPPER_LEFT_CORNER
     * <li>ScrollPaneConstants.UPPER_RIGHT_CORNER
     * </ul>
     *
     * @param s the component identifier
     * @param c the the component to be added
     * @exception IllegalArgumentException if <code>s</code> is an invalid key
     */
    public void addLayoutComponent(String s, Component c)
    {
        if (s.equals(VIEWPORT)) {
            viewport = (JViewport)addSingletonComponent(viewport, c);
        }
        else if (s.equals(VERTICAL_SCROLLBAR)) {
            vsb = (JScrollBar)addSingletonComponent(vsb, c);
        }
        else if (s.equals(HORIZONTAL_SCROLLBAR)) {
            hsb = (JScrollBar)addSingletonComponent(hsb, c);
        }
        else if (s.equals(ROW_HEADER)) {
            rowHead = (JViewport)addSingletonComponent(rowHead, c);
        }
        else if (s.equals(COLUMN_HEADER)) {
            colHead = (JViewport)addSingletonComponent(colHead, c);
        }
        else if (s.equals(LOWER_LEFT_CORNER)) {
            lowerLeft = addSingletonComponent(lowerLeft, c);
        }
        else if (s.equals(LOWER_RIGHT_CORNER)) {
            lowerRight = addSingletonComponent(lowerRight, c);
        }
        else if (s.equals(UPPER_LEFT_CORNER)) {
            upperLeft = addSingletonComponent(upperLeft, c);
        }
        else if (s.equals(UPPER_RIGHT_CORNER)) {
            upperRight = addSingletonComponent(upperRight, c);
        }
        else {
            throw new IllegalArgumentException("invalid layout key " + s);
        }
    }
    /**
     * Removes the specified component from the layout.
     *
     * @param c the component to remove
     */
    public void removeLayoutComponent(Component c)
    {
        if (c == viewport) {
            viewport = null;
        }
        else if (c == vsb) {
            vsb = null;
        }
        else if (c == hsb) {
            hsb = null;
        }
        else if (c == rowHead) {
            rowHead = null;
        }
        else if (c == colHead) {
            colHead = null;
        }
        else if (c == lowerLeft) {
            lowerLeft = null;
        }
        else if (c == lowerRight) {
            lowerRight = null;
        }
        else if (c == upperLeft) {
            upperLeft = null;
        }
        else if (c == upperRight) {
            upperRight = null;
        }
    }
    /**
     * Returns the vertical scrollbar-display policy.
     *
     * @return an integer giving the display policy
     * @see #setVerticalScrollBarPolicy
     */
    public int getVerticalScrollBarPolicy() {
        return vsbPolicy;
    }
    /**
     * Sets the vertical scrollbar-display policy. The options
     * are:
     * <ul>
     * <li>ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
     * <li>ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER
     * <li>ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
     * </ul>
     * Note: Applications should use the <code>JScrollPane</code> version
     * of this method.  It only exists for backwards compatibility
     * with the Swing 1.0.2 (and earlier) versions of this class.
     *
     * @param x an integer giving the display policy
     * @exception IllegalArgumentException if <code>x</code> is an invalid
     *          vertical scroll bar policy, as listed above
     */
    public void setVerticalScrollBarPolicy(int x) {
        switch (x) {
        case VERTICAL_SCROLLBAR_AS_NEEDED:
        case VERTICAL_SCROLLBAR_NEVER:
        case VERTICAL_SCROLLBAR_ALWAYS:
                vsbPolicy = x;
                break;
        default:
            throw new IllegalArgumentException("invalid verticalScrollBarPolicy");
        }
    }
    /**
     * Returns the horizontal scrollbar-display policy.
     *
     * @return an integer giving the display policy
     * @see #setHorizontalScrollBarPolicy
     */
    public int getHorizontalScrollBarPolicy() {
        return hsbPolicy;
    }
    /**
     * Sets the horizontal scrollbar-display policy.
     * The options are:<ul>
     * <li>ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
     * <li>ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
     * <li>ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS
     * </ul>
     * Note: Applications should use the <code>JScrollPane</code> version
     * of this method.  It only exists for backwards compatibility
     * with the Swing 1.0.2 (and earlier) versions of this class.
     *
     * @param x an int giving the display policy
     * @exception IllegalArgumentException if <code>x</code> is not a valid
     *          horizontal scrollbar policy, as listed above
     */
    public void setHorizontalScrollBarPolicy(int x) {
        switch (x) {
        case HORIZONTAL_SCROLLBAR_AS_NEEDED:
        case HORIZONTAL_SCROLLBAR_NEVER:
        case HORIZONTAL_SCROLLBAR_ALWAYS:
                hsbPolicy = x;
                break;
        default:
            throw new IllegalArgumentException("invalid horizontalScrollBarPolicy");
        }
    }
    /**
     * Returns the <code>JViewport</code> object that displays the
     * scrollable contents.
     * @return the <code>JViewport</code> object that displays the scrollable contents
     * @see JScrollPane#getViewport
     */
    public JViewport getViewport() {
        return viewport;
    }
    /**
     * Returns the <code>JScrollBar</code> object that handles horizontal scrolling.
     * @return the <code>JScrollBar</code> object that handles horizontal scrolling
     * @see JScrollPane#getHorizontalScrollBar
     */
    public JScrollBar getHorizontalScrollBar() {
        return hsb;
    }
    /**
     * Returns the <code>JScrollBar</code> object that handles vertical scrolling.
     * @return the <code>JScrollBar</code> object that handles vertical scrolling
     * @see JScrollPane#getVerticalScrollBar
     */
    public JScrollBar getVerticalScrollBar() {
        return vsb;
    }
    /**
     * Returns the <code>JViewport</code> object that is the row header.
     * @return the <code>JViewport</code> object that is the row header
     * @see JScrollPane#getRowHeader
     */
    public JViewport getRowHeader() {
        return rowHead;
    }
    /**
     * Returns the <code>JViewport</code> object that is the column header.
     * @return the <code>JViewport</code> object that is the column header
     * @see JScrollPane#getColumnHeader
     */
    public JViewport getColumnHeader() {
        return colHead;
    }
    /**
     * Returns the <code>Component</code> at the specified corner.
     * @param key the <code>String</code> specifying the corner
     * @return the <code>Component</code> at the specified corner, as defined in
     *         {@link ScrollPaneConstants}; if <code>key</code> is not one of the
     *          four corners, <code>null</code> is returned
     * @see JScrollPane#getCorner
     */
    public Component getCorner(String key) {
        if (key.equals(LOWER_LEFT_CORNER)) {
            return lowerLeft;
        }
        else if (key.equals(LOWER_RIGHT_CORNER)) {
            return lowerRight;
        }
        else if (key.equals(UPPER_LEFT_CORNER)) {
            return upperLeft;
        }
        else if (key.equals(UPPER_RIGHT_CORNER)) {
            return upperRight;
        }
        else {
            return null;
        }
    }
    /**
     * The preferred size of a <code>ScrollPane</code> is the size of the insets,
     * plus the preferred size of the viewport, plus the preferred size of
     * the visible headers, plus the preferred size of the scrollbars
     * that will appear given the current view and the current
     * scrollbar displayPolicies.
     * <p>Note that the rowHeader is calculated as part of the preferred width
     * and the colHeader is calculated as part of the preferred size.
     *
     * @param parent the <code>Container</code> that will be laid out
     * @return a <code>Dimension</code> object specifying the preferred size of the
     *         viewport and any scrollbars
     * @see ViewportLayout
     * @see LayoutManager
     */
    public Dimension preferredLayoutSize(Container parent)
    {
        /* Sync the (now obsolete) policy fields with the
         * JScrollPane.
         */
        JScrollPane scrollPane = (JScrollPane)parent;
        vsbPolicy = scrollPane.getVerticalScrollBarPolicy();
        hsbPolicy = scrollPane.getHorizontalScrollBarPolicy();
        Insets insets = parent.getInsets();
        int prefWidth = insets.left + insets.right;
        int prefHeight = insets.top + insets.bottom;
        /* Note that viewport.getViewSize() is equivalent to
         * viewport.getView().getPreferredSize() modulo a null
         * view or a view whose size was explicitly set.
         */
        Dimension extentSize = null;
        Dimension viewSize = null;
        Component view = null;
        if (viewport != null) {
            extentSize = viewport.getPreferredSize();
            view = viewport.getView();
            if (view != null) {
                viewSize = view.getPreferredSize();
            } else {
                viewSize = new Dimension(0, 0);
            }
        }
        /* If there's a viewport add its preferredSize.
         */
        if (extentSize != null) {
            prefWidth += extentSize.width;
            prefHeight += extentSize.height;
        }
        /* If there's a JScrollPane.viewportBorder, add its insets.
         */
        Border viewportBorder = scrollPane.getViewportBorder();
        if (viewportBorder != null) {
            Insets vpbInsets = viewportBorder.getBorderInsets(parent);
            prefWidth += vpbInsets.left + vpbInsets.right;
            prefHeight += vpbInsets.top + vpbInsets.bottom;
        }
        /* If a header exists and it's visible, factor its
         * preferred size in.
         */
        if ((rowHead != null) && rowHead.isVisible()) {
            prefWidth += rowHead.getPreferredSize().width;
        }
        if ((colHead != null) && colHead.isVisible()) {
            prefHeight += colHead.getPreferredSize().height;
        }
        /* If a scrollbar is going to appear, factor its preferred size in.
         * If the scrollbars policy is AS_NEEDED, this can be a little
         * tricky:
         *
         * - If the view is a Scrollable then scrollableTracksViewportWidth
         * and scrollableTracksViewportHeight can be used to effectively
         * disable scrolling (if they're true) in their respective dimensions.
         *
         * - Assuming that a scrollbar hasn't been disabled by the
         * previous constraint, we need to decide if the scrollbar is going
         * to appear to correctly compute the JScrollPanes preferred size.
         * To do this we compare the preferredSize of the viewport (the
         * extentSize) to the preferredSize of the view.  Although we're
         * not responsible for laying out the view we'll assume that the
         * JViewport will always give it its preferredSize.
         */
        if ((vsb != null) && (vsbPolicy != VERTICAL_SCROLLBAR_NEVER)) {
            if (vsbPolicy == VERTICAL_SCROLLBAR_ALWAYS) {
                prefWidth += vsb.getPreferredSize().width;
            }
            else if ((viewSize != null) && (extentSize != null)) {
                boolean canScroll = true;
                if (view instanceof Scrollable) {
                    canScroll = !((Scrollable)view).getScrollableTracksViewportHeight();
                }
                if (canScroll && (viewSize.height > extentSize.height)) {
                    prefWidth += vsb.getPreferredSize().width;
                }
            }
        }
        if ((hsb != null) && (hsbPolicy != HORIZONTAL_SCROLLBAR_NEVER)) {
            if (hsbPolicy == HORIZONTAL_SCROLLBAR_ALWAYS) {
                prefHeight += hsb.getPreferredSize().height;
            }
            else if ((viewSize != null) && (extentSize != null)) {
                boolean canScroll = true;
                if (view instanceof Scrollable) {
                    canScroll = !((Scrollable)view).getScrollableTracksViewportWidth();
                }
                if (canScroll && (viewSize.width > extentSize.width)) {
                    prefHeight += hsb.getPreferredSize().height;
                }
            }
        }
        return new Dimension(prefWidth, prefHeight);
    }
    /**
     * The minimum size of a <code>ScrollPane</code> is the size of the insets
     * plus minimum size of the viewport, plus the scrollpane's
     * viewportBorder insets, plus the minimum size
     * of the visible headers, plus the minimum size of the
     * scrollbars whose displayPolicy isn't NEVER.
     *
     * @param parent the <code>Container</code> that will be laid out
     * @return a <code>Dimension</code> object specifying the minimum size
     */
    public Dimension minimumLayoutSize(Container parent)
    {
        /* Sync the (now obsolete) policy fields with the
         * JScrollPane.
         */
        JScrollPane scrollPane = (JScrollPane)parent;
        vsbPolicy = scrollPane.getVerticalScrollBarPolicy();
        hsbPolicy = scrollPane.getHorizontalScrollBarPolicy();
        Insets insets = parent.getInsets();
        int minWidth = insets.left + insets.right;
        int minHeight = insets.top + insets.bottom;
        /* If there's a viewport add its minimumSize.
         */
        if (viewport != null) {
            Dimension size = viewport.getMinimumSize();
            minWidth += size.width;
            minHeight += size.height;
        }
        /* If there's a JScrollPane.viewportBorder, add its insets.
         */
        Border viewportBorder = scrollPane.getViewportBorder();
        if (viewportBorder != null) {
            Insets vpbInsets = viewportBorder.getBorderInsets(parent);
            minWidth += vpbInsets.left + vpbInsets.right;
            minHeight += vpbInsets.top + vpbInsets.bottom;
        }
        /* If a header exists and it's visible, factor its
         * minimum size in.
         */
        if ((rowHead != null) && rowHead.isVisible()) {
            Dimension size = rowHead.getMinimumSize();
            minWidth += size.width;
            minHeight = Math.max(minHeight, size.height);
        }
        if ((colHead != null) && colHead.isVisible()) {
            Dimension size = colHead.getMinimumSize();
            minWidth = Math.max(minWidth, size.width);
            minHeight += size.height;
        }
        /* If a scrollbar might appear, factor its minimum
         * size in.
         */
        if ((vsb != null) && (vsbPolicy != VERTICAL_SCROLLBAR_NEVER)) {
            Dimension size = vsb.getMinimumSize();
            minWidth += size.width;
            minHeight = Math.max(minHeight, size.height);
        }
        if ((hsb != null) && (hsbPolicy != HORIZONTAL_SCROLLBAR_NEVER)) {
            Dimension size = hsb.getMinimumSize();
            minWidth = Math.max(minWidth, size.width);
            minHeight += size.height;
        }
        return new Dimension(minWidth, minHeight);
    }
    /**
     * Lays out the scrollpane. The positioning of components depends on
     * the following constraints:
     * <ul>
     * <li> The row header, if present and visible, gets its preferred
     * width and the viewport's height.
     *
     * <li> The column header, if present and visible, gets its preferred
     * height and the viewport's width.
     *
     * <li> If a vertical scrollbar is needed, i.e. if the viewport's extent
     * height is smaller than its view height or if the <code>displayPolicy</code>
     * is ALWAYS, it's treated like the row header with respect to its
     * dimensions and is made visible.
     *
     * <li> If a horizontal scrollbar is needed, it is treated like the
     * column header (see the paragraph above regarding the vertical scrollbar).
     *
     * <li> If the scrollpane has a non-<code>null</code>
     * <code>viewportBorder</code>, then space is allocated for that.
     *
     * <li> The viewport gets the space available after accounting for
     * the previous constraints.
     *
     * <li> The corner components, if provided, are aligned with the
     * ends of the scrollbars and headers. If there is a vertical
     * scrollbar, the right corners appear; if there is a horizontal
     * scrollbar, the lower corners appear; a row header gets left
     * corners, and a column header gets upper corners.
     * </ul>
     *
     * @param parent the <code>Container</code> to lay out
     */
    public void layoutContainer(Container parent)
    {
        /* Sync the (now obsolete) policy fields with the
         * JScrollPane.
         */
        JScrollPane scrollPane = (JScrollPane)parent;
        vsbPolicy = scrollPane.getVerticalScrollBarPolicy();
        hsbPolicy = scrollPane.getHorizontalScrollBarPolicy();
        Rectangle availR = scrollPane.getBounds();
        availR.x = availR.y = 0;
        Insets insets = parent.getInsets();
        availR.x = insets.left;
        availR.y = insets.top;
        availR.width -= insets.left + insets.right;
        availR.height -= insets.top + insets.bottom;
        /* Get the scrollPane's orientation.
         */
        boolean leftToRight = SwingUtilities.isLeftToRight(scrollPane);
        /* If there's a visible column header remove the space it
         * needs from the top of availR.  The column header is treated
         * as if it were fixed height, arbitrary width.
         */
        Rectangle colHeadR = new Rectangle(0, availR.y, 0, 0);
        if ((colHead != null) && (colHead.isVisible())) {
            int colHeadHeight = Math.min(availR.height,
                                         colHead.getPreferredSize().height);
            colHeadR.height = colHeadHeight;
            availR.y += colHeadHeight;
            availR.height -= colHeadHeight;
        }
        /* If there's a visible row header remove the space it needs
         * from the left or right of availR.  The row header is treated
         * as if it were fixed width, arbitrary height.
         */
        Rectangle rowHeadR = new Rectangle(0, 0, 0, 0);
        if ((rowHead != null) && (rowHead.isVisible())) {
            int rowHeadWidth = Math.min(availR.width,
                                        rowHead.getPreferredSize().width);
            rowHeadR.width = rowHeadWidth;
            availR.width -= rowHeadWidth;
            if ( leftToRight ) {
                rowHeadR.x = availR.x;
                availR.x += rowHeadWidth;
            } else {
                rowHeadR.x = availR.x + availR.width;
            }
        }
        /* If there's a JScrollPane.viewportBorder, remove the
         * space it occupies for availR.
         */
        Border viewportBorder = scrollPane.getViewportBorder();
        Insets vpbInsets;
        if (viewportBorder != null) {
            vpbInsets = viewportBorder.getBorderInsets(parent);
            availR.x += vpbInsets.left;
            availR.y += vpbInsets.top;
            availR.width -= vpbInsets.left + vpbInsets.right;
            availR.height -= vpbInsets.top + vpbInsets.bottom;
        }
        else {
            vpbInsets = new Insets(0,0,0,0);
        }
        /* At this point availR is the space available for the viewport
         * and scrollbars. rowHeadR is correct except for its height and y
         * and colHeadR is correct except for its width and x.  Once we're
         * through computing the dimensions  of these three parts we can
         * go back and set the dimensions of rowHeadR.height, rowHeadR.y,
         * colHeadR.width, colHeadR.x and the bounds for the corners.
         *
         * We'll decide about putting up scrollbars by comparing the
         * viewport views preferred size with the viewports extent
         * size (generally just its size).  Using the preferredSize is
         * reasonable because layout proceeds top down - so we expect
         * the viewport to be laid out next.  And we assume that the
         * viewports layout manager will give the view it's preferred
         * size.  One exception to this is when the view implements
         * Scrollable and Scrollable.getViewTracksViewport{Width,Height}
         * methods return true.  If the view is tracking the viewports
         * width we don't bother with a horizontal scrollbar, similarly
         * if view.getViewTracksViewport(Height) is true we don't bother
         * with a vertical scrollbar.
         */
        Component view = (viewport != null) ? viewport.getView() : null;
        Dimension viewPrefSize =
            (view != null) ? view.getPreferredSize()
                           : new Dimension(0,0);
        Dimension extentSize =
            (viewport != null) ? viewport.toViewCoordinates(availR.getSize())
                               : new Dimension(0,0);
        boolean viewTracksViewportWidth = false;
        boolean viewTracksViewportHeight = false;
        boolean isEmpty = (availR.width < 0 || availR.height < 0);
        Scrollable sv;
        // Don't bother checking the Scrollable methods if there is no room
        // for the viewport, we aren't going to show any scrollbars in this
        // case anyway.
        if (!isEmpty && view instanceof Scrollable) {
            sv = (Scrollable)view;
            viewTracksViewportWidth = sv.getScrollableTracksViewportWidth();
            viewTracksViewportHeight = sv.getScrollableTracksViewportHeight();
        }
        else {
            sv = null;
        }
        /* If there's a vertical scrollbar and we need one, allocate
         * space for it (we'll make it visible later). A vertical
         * scrollbar is considered to be fixed width, arbitrary height.
         */
        Rectangle vsbR = new Rectangle(0, availR.y - vpbInsets.top, 0, 0);
        boolean vsbNeeded;
        if (isEmpty) {
            vsbNeeded = false;
        }
        else if (vsbPolicy == VERTICAL_SCROLLBAR_ALWAYS) {
            vsbNeeded = true;
        }
        else if (vsbPolicy == VERTICAL_SCROLLBAR_NEVER) {
            vsbNeeded = false;
        }
        else {  // vsbPolicy == VERTICAL_SCROLLBAR_AS_NEEDED
            vsbNeeded = !viewTracksViewportHeight && (viewPrefSize.height > extentSize.height);
        }
        if ((vsb != null) && vsbNeeded) {
            adjustForVSB(true, availR, vsbR, vpbInsets, leftToRight);
            extentSize = viewport.toViewCoordinates(availR.getSize());
        }
        /* If there's a horizontal scrollbar and we need one, allocate
         * space for it (we'll make it visible later). A horizontal
         * scrollbar is considered to be fixed height, arbitrary width.
         */
        Rectangle hsbR = new Rectangle(availR.x - vpbInsets.left, 0, 0, 0);
        boolean hsbNeeded;
        if (isEmpty) {
            hsbNeeded = false;
        }
        else if (hsbPolicy == HORIZONTAL_SCROLLBAR_ALWAYS) {
            hsbNeeded = true;
        }
        else if (hsbPolicy == HORIZONTAL_SCROLLBAR_NEVER) {
            hsbNeeded = false;
        }
        else {  // hsbPolicy == HORIZONTAL_SCROLLBAR_AS_NEEDED
            hsbNeeded = !viewTracksViewportWidth && (viewPrefSize.width > extentSize.width);
        }
        if ((hsb != null) && hsbNeeded) {
            adjustForHSB(true, availR, hsbR, vpbInsets);
            /* If we added the horizontal scrollbar then we've implicitly
             * reduced  the vertical space available to the viewport.
             * As a consequence we may have to add the vertical scrollbar,
             * if that hasn't been done so already.  Of course we
             * don't bother with any of this if the vsbPolicy is NEVER.
             */
            if ((vsb != null) && !vsbNeeded &&
                (vsbPolicy != VERTICAL_SCROLLBAR_NEVER)) {
                extentSize = viewport.toViewCoordinates(availR.getSize());
                vsbNeeded = viewPrefSize.height > extentSize.height;
                if (vsbNeeded) {
                    adjustForVSB(true, availR, vsbR, vpbInsets, leftToRight);
                }
            }
        }
        /* Set the size of the viewport first, and then recheck the Scrollable
         * methods. Some components base their return values for the Scrollable
         * methods on the size of the Viewport, so that if we don't
         * ask after resetting the bounds we may have gotten the wrong
         * answer.
         */
        if (viewport != null) {
            viewport.setBounds(availR);
            if (sv != null) {
                extentSize = viewport.toViewCoordinates(availR.getSize());
                boolean oldHSBNeeded = hsbNeeded;
                boolean oldVSBNeeded = vsbNeeded;
                viewTracksViewportWidth = sv.
                                          getScrollableTracksViewportWidth();
                viewTracksViewportHeight = sv.
                                          getScrollableTracksViewportHeight();
                if (vsb != null && vsbPolicy == VERTICAL_SCROLLBAR_AS_NEEDED) {
                    boolean newVSBNeeded = !viewTracksViewportHeight &&
                                     (viewPrefSize.height > extentSize.height);
                    if (newVSBNeeded != vsbNeeded) {
                        vsbNeeded = newVSBNeeded;
                        adjustForVSB(vsbNeeded, availR, vsbR, vpbInsets,
                                     leftToRight);
                        extentSize = viewport.toViewCoordinates
                                              (availR.getSize());
                    }
                }
                if (hsb != null && hsbPolicy ==HORIZONTAL_SCROLLBAR_AS_NEEDED){
                    boolean newHSBbNeeded = !viewTracksViewportWidth &&
                                       (viewPrefSize.width > extentSize.width);
                    if (newHSBbNeeded != hsbNeeded) {
                        hsbNeeded = newHSBbNeeded;
                        adjustForHSB(hsbNeeded, availR, hsbR, vpbInsets);
                        if ((vsb != null) && !vsbNeeded &&
                            (vsbPolicy != VERTICAL_SCROLLBAR_NEVER)) {
                            extentSize = viewport.toViewCoordinates
                                         (availR.getSize());
                            vsbNeeded = viewPrefSize.height >
                                        extentSize.height;
                            if (vsbNeeded) {
                                adjustForVSB(true, availR, vsbR, vpbInsets,
                                             leftToRight);
                            }
                        }
                    }
                }
                if (oldHSBNeeded != hsbNeeded ||
                    oldVSBNeeded != vsbNeeded) {
                    viewport.setBounds(availR);
                    // You could argue that we should recheck the
                    // Scrollable methods again until they stop changing,
                    // but they might never stop changing, so we stop here
                    // and don't do any additional checks.
                }
            }
        }
        /* We now have the final size of the viewport: availR.
         * Now fixup the header and scrollbar widths/heights.
         */
        vsbR.height = availR.height + vpbInsets.top + vpbInsets.bottom;
        hsbR.width = availR.width + vpbInsets.left + vpbInsets.right;
        rowHeadR.height = availR.height + vpbInsets.top + vpbInsets.bottom;
        rowHeadR.y = availR.y - vpbInsets.top;
        colHeadR.width = availR.width + vpbInsets.left + vpbInsets.right;
        colHeadR.x = availR.x - vpbInsets.left;
        /* Set the bounds of the remaining components.  The scrollbars
         * are made invisible if they're not needed.
         */
        if (rowHead != null) {
            rowHead.setBounds(rowHeadR);
        }
        if (colHead != null) {
            colHead.setBounds(colHeadR);
        }
        if (vsb != null) {
            if (vsbNeeded) {
                if (colHead != null &&
                    UIManager.getBoolean("ScrollPane.fillUpperCorner"))
                {
                    if ((leftToRight && upperRight == null) ||
                        (!leftToRight && upperLeft == null))
                    {
                        // This is used primarily for GTK L&F, which needs to
                        // extend the vertical scrollbar to fill the upper
                        // corner near the column header.  Note that we skip
                        // this step (and use the default behavior) if the
                        // user has set a custom corner component.
                        vsbR.y = colHeadR.y;
                        vsbR.height += colHeadR.height;
                    }
                }
                vsb.setVisible(true);
                vsb.setBounds(vsbR);
            }
            else {
                vsb.setVisible(false);
            }
        }
        if (hsb != null) {
            if (hsbNeeded) {
                if (rowHead != null &&
                    UIManager.getBoolean("ScrollPane.fillLowerCorner"))
                {
                    if ((leftToRight && lowerLeft == null) ||
                        (!leftToRight && lowerRight == null))
                    {
                        // This is used primarily for GTK L&F, which needs to
                        // extend the horizontal scrollbar to fill the lower
                        // corner near the row header.  Note that we skip
                        // this step (and use the default behavior) if the
                        // user has set a custom corner component.
                        if (leftToRight) {
                            hsbR.x = rowHeadR.x;
                        }
                        hsbR.width += rowHeadR.width;
                    }
                }
                hsb.setVisible(true);
                hsb.setBounds(hsbR);
            }
            else {
                hsb.setVisible(false);
            }
        }
        if (lowerLeft != null) {
            lowerLeft.setBounds(leftToRight ? rowHeadR.x : vsbR.x,
                                hsbR.y,
                                leftToRight ? rowHeadR.width : vsbR.width,
                                hsbR.height);
        }
        if (lowerRight != null) {
            lowerRight.setBounds(leftToRight ? vsbR.x : rowHeadR.x,
                                 hsbR.y,
                                 leftToRight ? vsbR.width : rowHeadR.width,
                                 hsbR.height);
        }
        if (upperLeft != null) {
            upperLeft.setBounds(leftToRight ? rowHeadR.x : vsbR.x,
                                colHeadR.y,
                                leftToRight ? rowHeadR.width : vsbR.width,
                                colHeadR.height);
        }
        if (upperRight != null) {
            upperRight.setBounds(leftToRight ? vsbR.x : rowHeadR.x,
                                 colHeadR.y,
                                 leftToRight ? vsbR.width : rowHeadR.width,
                                 colHeadR.height);
        }
    }
    /**
     * Adjusts the <code>Rectangle</code> <code>available</code> based on if
     * the vertical scrollbar is needed (<code>wantsVSB</code>).
     * The location of the vsb is updated in <code>vsbR</code>, and
     * the viewport border insets (<code>vpbInsets</code>) are used to offset
     * the vsb. This is only called when <code>wantsVSB</code> has
     * changed, eg you shouldn't invoke adjustForVSB(true) twice.
     */
    private void adjustForVSB(boolean wantsVSB, Rectangle available,
                              Rectangle vsbR, Insets vpbInsets,
                              boolean leftToRight) {
        int oldWidth = vsbR.width;
        if (wantsVSB) {
            int vsbWidth = Math.max(0, Math.min(vsb.getPreferredSize().width,
                                                available.width));
            available.width -= vsbWidth;
            vsbR.width = vsbWidth;
            if( leftToRight ) {
                vsbR.x = available.x + available.width + vpbInsets.right;
            } else {
                vsbR.x = available.x - vpbInsets.left;
                available.x += vsbWidth;
            }
        }
        else {
            available.width += oldWidth;
        }
    }
    /**
     * Adjusts the <code>Rectangle</code> <code>available</code> based on if
     * the horizontal scrollbar is needed (<code>wantsHSB</code>).
     * The location of the hsb is updated in <code>hsbR</code>, and
     * the viewport border insets (<code>vpbInsets</code>) are used to offset
     * the hsb.  This is only called when <code>wantsHSB</code> has
     * changed, eg you shouldn't invoked adjustForHSB(true) twice.
     */
    private void adjustForHSB(boolean wantsHSB, Rectangle available,
                              Rectangle hsbR, Insets vpbInsets) {
        int oldHeight = hsbR.height;
        if (wantsHSB) {
            int hsbHeight = Math.max(0, Math.min(available.height,
                                              hsb.getPreferredSize().height));
            available.height -= hsbHeight;
            hsbR.y = available.y + available.height + vpbInsets.bottom;
            hsbR.height = hsbHeight;
        }
        else {
            available.height += oldHeight;
        }
    }
    /**
     * Returns the bounds of the border around the specified scroll pane's
     * viewport.
     *
     * @return the size and position of the viewport border
     * @deprecated As of JDK version Swing1.1
     *    replaced by <code>JScrollPane.getViewportBorderBounds()</code>.
     */
    @Deprecated
    public Rectangle getViewportBorderBounds(JScrollPane scrollpane) {
        return scrollpane.getViewportBorderBounds();
    }
    /**
     * The UI resource version of <code>ScrollPaneLayout</code>.
     */
    public static class UIResource extends ScrollPaneLayout implements javax.swing.plaf.UIResource {}
}
Back to index...