import java.awt.*; import javax.swing.*; /** * A panel that implements the Scrollable interface. This class allows you * to customize the scrollable features by using newly provided setter methods * so you don't have to extend this class every time. * * Scrollable amounts can be specifed as a percentage of the viewport size or * as an actual pixel value. The amount can be changed for both unit and block * scrolling for both horizontal and vertical scrollbars. * * The Scrollable interface only provides a boolean value for determining whether * or not the viewport size (width or height) should be used by the scrollpane * when determining if scrollbars should be made visible. This class supports the * concept of dynamically changing this value based on the size of the viewport. * In this case the viewport size will only be used when it is larger than the * panels size. This has the effect of ensuring the viewport is always full as * components added to the panel will be size to fill the area available, * based on the rules of the applicable layout manager of course. */ public class ScrollablePanel extends JPanel implements Scrollable, SwingConstants { public enum ScrollableSizeHint { NONE, FIT, STRETCH; } public enum IncrementType { PERCENT, PIXELS; } private ScrollableSizeHint scrollableHeight = ScrollableSizeHint.NONE; private ScrollableSizeHint scrollableWidth = ScrollableSizeHint.NONE; private IncrementInfo horizontalBlock; private IncrementInfo horizontalUnit; private IncrementInfo verticalBlock; private IncrementInfo verticalUnit; /** * Default constructor that uses a FlowLayout */ public ScrollablePanel() { this( new FlowLayout() ); } /** * Constuctor for specifying the LayoutManager of the panel. * * @param layout the LayountManger for the panel */ public ScrollablePanel(LayoutManager layout) { super( layout ); IncrementInfo block = new IncrementInfo(IncrementType.PERCENT, 100); IncrementInfo unit = new IncrementInfo(IncrementType.PERCENT, 10); setScrollableBlockIncrement(HORIZONTAL, block); setScrollableBlockIncrement(VERTICAL, block); setScrollableUnitIncrement(HORIZONTAL, unit); setScrollableUnitIncrement(VERTICAL, unit); } /** * Get the height ScrollableSizeHint enum * * @return the ScrollableSizeHint enum for the height */ public ScrollableSizeHint getScrollableHeight() { return scrollableHeight; } /** * Set the ScrollableSizeHint enum for the height. The enum is used to * determine the boolean value that is returned by the * getScrollableTracksViewportHeight() method. The valid values are: * * ScrollableSizeHint.NONE - return "false", which causes the height * of the panel to be used when laying out the children * ScrollableSizeHint.FIT - return "true", which causes the height of * the viewport to be used when laying out the children * ScrollableSizeHint.STRETCH - return "true" when the viewport height * is greater than the height of the panel, "false" otherwise. * * @param scrollableHeight as represented by the ScrollableSizeHint enum. */ public void setScrollableHeight(ScrollableSizeHint scrollableHeight) { this.scrollableHeight = scrollableHeight; revalidate(); } /** * Get the width ScrollableSizeHint enum * * @return the ScrollableSizeHint enum for the width */ public ScrollableSizeHint getScrollableWidth() { return scrollableWidth; } /** * Set the ScrollableSizeHint enum for the width. The enum is used to * determine the boolean value that is returned by the * getScrollableTracksViewportWidth() method. The valid values are: * * ScrollableSizeHint.NONE - return "false", which causes the width * of the panel to be used when laying out the children * ScrollableSizeHint.FIT - return "true", which causes the width of * the viewport to be used when laying out the children * ScrollableSizeHint.STRETCH - return "true" when the viewport width * is greater than the width of the panel, "false" otherwise. * * @param scrollableWidth as represented by the ScrollableSizeHint enum. */ public void setScrollableWidth(ScrollableSizeHint scrollableWidth) { this.scrollableWidth = scrollableWidth; revalidate(); } /** * Get the block IncrementInfo for the specified orientation * * @return the block IncrementInfo for the specified orientation */ public IncrementInfo getScrollableBlockIncrement(int orientation) { return orientation == SwingConstants.HORIZONTAL ? horizontalBlock : verticalBlock; } /** * Specify the information needed to do block scrolling. * * @param orientation specify the scrolling orientation. Must be either: * SwingContants.HORIZONTAL or SwingContants.VERTICAL. * @paran type specify how the amount parameter in the calculation of * the scrollable amount. Valid values are: * IncrementType.PERCENT - treat the amount as a % of the viewport size * IncrementType.PIXEL - treat the amount as the scrollable amount * @param amount a value used with the IncrementType to determine the * scrollable amount */ public void setScrollableBlockIncrement(int orientation, IncrementType type, int amount) { IncrementInfo info = new IncrementInfo(type, amount); setScrollableBlockIncrement(orientation, info); } /** * Specify the information needed to do block scrolling. * * @param orientation specify the scrolling orientation. Must be either: * SwingContants.HORIZONTAL or SwingContants.VERTICAL. * @param info An IncrementInfo object containing information of how to * calculate the scrollable amount. */ public void setScrollableBlockIncrement(int orientation, IncrementInfo info) { switch(orientation) { case SwingConstants.HORIZONTAL: horizontalBlock = info; break; case SwingConstants.VERTICAL: verticalBlock = info; break; default: throw new IllegalArgumentException("Invalid orientation: " + orientation); } } /** * Get the unit IncrementInfo for the specified orientation * * @return the unit IncrementInfo for the specified orientation */ public IncrementInfo getScrollableUnitIncrement(int orientation) { return orientation == SwingConstants.HORIZONTAL ? horizontalUnit : verticalUnit; } /** * Specify the information needed to do unit scrolling. * * @param orientation specify the scrolling orientation. Must be either: * SwingContants.HORIZONTAL or SwingContants.VERTICAL. * @paran type specify how the amount parameter in the calculation of * the scrollable amount. Valid values are: * IncrementType.PERCENT - treat the amount as a % of the viewport size * IncrementType.PIXEL - treat the amount as the scrollable amount * @param amount a value used with the IncrementType to determine the * scrollable amount */ public void setScrollableUnitIncrement(int orientation, IncrementType type, int amount) { IncrementInfo info = new IncrementInfo(type, amount); setScrollableUnitIncrement(orientation, info); } /** * Specify the information needed to do unit scrolling. * * @param orientation specify the scrolling orientation. Must be either: * SwingContants.HORIZONTAL or SwingContants.VERTICAL. * @param info An IncrementInfo object containing information of how to * calculate the scrollable amount. */ public void setScrollableUnitIncrement(int orientation, IncrementInfo info) { switch(orientation) { case SwingConstants.HORIZONTAL: horizontalUnit = info; break; case SwingConstants.VERTICAL: verticalUnit = info; break; default: throw new IllegalArgumentException("Invalid orientation: " + orientation); } } // Implement Scrollable interface public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } public int getScrollableUnitIncrement( Rectangle visible, int orientation, int direction) { switch(orientation) { case SwingConstants.HORIZONTAL: return getScrollableIncrement(horizontalUnit, visible.width); case SwingConstants.VERTICAL: return getScrollableIncrement(verticalUnit, visible.height); default: throw new IllegalArgumentException("Invalid orientation: " + orientation); } } public int getScrollableBlockIncrement( Rectangle visible, int orientation, int direction) { switch(orientation) { case SwingConstants.HORIZONTAL: return getScrollableIncrement(horizontalBlock, visible.width); case SwingConstants.VERTICAL: return getScrollableIncrement(verticalBlock, visible.height); default: throw new IllegalArgumentException("Invalid orientation: " + orientation); } } protected int getScrollableIncrement(IncrementInfo info, int distance) { if (info.getIncrement() == IncrementType.PIXELS) return info.getAmount(); else return distance * info.getAmount() / 100; } public boolean getScrollableTracksViewportWidth() { if (scrollableWidth == ScrollableSizeHint.NONE) return false; if (scrollableWidth == ScrollableSizeHint.FIT) return true; // STRETCH sizing, use the greater of the panel or viewport width if (getParent() instanceof JViewport) { return (((JViewport)getParent()).getWidth() > getPreferredSize().width); } return false; } public boolean getScrollableTracksViewportHeight() { if (scrollableHeight == ScrollableSizeHint.NONE) return false; if (scrollableHeight == ScrollableSizeHint.FIT) return true; // STRETCH sizing, use the greater of the panel or viewport height if (getParent() instanceof JViewport) { return (((JViewport)getParent()).getHeight() > getPreferredSize().height); } return false; } /** * Helper class to hold the information required to calculate the scroll amount. */ static class IncrementInfo { private IncrementType type; private int amount; public IncrementInfo(IncrementType type, int amount) { this.type = type; this.amount = amount; } public IncrementType getIncrement() { return type; } public int getAmount() { return amount; } public String toString() { return "ScrollablePanel[" + type + ", " + amount + "]"; } } }