Java tutorial
/* * StackLayout.java * 2005-07-15 */ import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager; import javax.swing.JSeparator; import javax.swing.JToolBar; import javax.swing.SwingConstants; /** * Similar to BoxLayout, uses an orientation to determine if the contents * should be arranged horizontally or vertically. By default, Resizes each * item to equal width or height (depending on the orientation) based on the * maximum preferred width or height of all items. * @author Christopher Bach */ public class StackLayout implements LayoutManager { public static final int HORIZONTAL = SwingConstants.HORIZONTAL; public static final int VERTICAL = SwingConstants.VERTICAL; private int ourOrientation = HORIZONTAL; private int ourSpacing = 0; private boolean ourDepthsMatched = true; private boolean ourLengthsMatched = false; private boolean ourFill = false; private boolean ourDrop = false; private int ourSqueezeFactor = 100; /** * Creates a new StackLayout with a horizontal orientation. */ public StackLayout() { } /** * Creates a new StackLayout with the specified orientation. */ public StackLayout(int orientation) { setOrientation(orientation); } /** * Creates a new StackLayout with the specified orientation and spacing. */ public StackLayout(int orientation, int spacing) { setOrientation(orientation); setSpacing(spacing); } /** * Creates a new StackLayout matching the component lengths and * depths as indicated. */ public StackLayout(boolean matchLengths, boolean matchDepths) { setMatchesComponentLengths(matchLengths); setMatchesComponentDepths(matchDepths); } /** * Creates a new StackLayout with the specified orientation * and spacing, matching the component lengths and depths * as indicated. */ public StackLayout(int orientation, int spacing, boolean matchLengths, boolean matchDepths) { setOrientation(orientation); setSpacing(spacing); setMatchesComponentLengths(matchLengths); setMatchesComponentDepths(matchDepths); } /** * Sets this StackLayout's orientation, either * SwingConstants.HORIZONTAL or SwingConstants.VERTICAL. */ public void setOrientation(int orientation) { if (orientation == HORIZONTAL || orientation == VERTICAL) { ourOrientation = orientation; } } /** * Returns this StackLayout's orientation, either * SwingConstants.HORIZONTAL or SwingConstants.VERTICAL. */ public int getOrientation() { return ourOrientation; } /** * Sets the spacing between components that this StackLayout uses * when laying out the components. */ public void setSpacing(int spacing) { ourSpacing = Math.max(0, spacing); } /** * Returns the spacing between components that this StackLayout uses * when laying out the components. */ public int getSpacing() { return ourSpacing; } /** * Sets whether or not the last component in the stack * should be stretched to fill any remaining space within * the parent container. The default value is false. */ public void setFillsTrailingSpace(boolean shouldFill) { ourFill = shouldFill; } /** * Returns whether or not the last component in the stack * should be stretched to fill any remaining space within * the parent container. */ public boolean fillsTrailingSpace() { return ourFill; } /** * Sets whether or not components in the stack that do not * fit in the parent container should be left out of the layout. * The default value is false; */ public void setDropsPartialComponents(boolean shouldDrop) { ourDrop = shouldDrop; } /** * Returns whether or not components in the stack that do not * fit in the parent container should be left out of the layout. */ public boolean dropsPartialComponents() { return ourDrop; } /** * Sets whether or not all components in the stack will be sized * to the same height (when in a horizontal orientation) or width * (when in a vertical orientation). The default value is true. */ public void setMatchesComponentDepths(boolean match) { ourDepthsMatched = match; } /** * Returns whether or not all components in the stack will be sized * to the same height (when in a horizontal orientation) or width * (when in a vertical orientation). */ public boolean matchesComponentDepths() { return ourDepthsMatched; } /** * Sets whether or not all components in the stack will be sized * to the same width (when in a horizontal orientation) or height * (when in a vertical orientation). The default value is false. */ public void setMatchesComponentLengths(boolean match) { ourLengthsMatched = match; } /** * Returns whether or not all components in the stack will be sized * to the same width (when in a horizontal orientation) or height * (when in a vertical orientation). */ public boolean matchesComponentLengths() { return ourLengthsMatched; } /** * Sets the percentage of a component's preferred size that it * may be squeezed in order to attempt to fit all components * into the layout. The squeeze factor will only be applied * when this layout is set to match component lengths. * * For example, if the parent container is 100 pixels wide * and holds two buttons, the largest having a preferred * width of 80 pixels, a squeeze factor of 50 will allow each * button to be sized to as small as 40 pixels wide (50 percent * of the preferred width. * * The default value is 100. */ public void setSqueezeFactor(int factor) { if (factor < 0) ourSqueezeFactor = 0; else if (factor > 100) ourSqueezeFactor = 100; else ourSqueezeFactor = factor; } /** * Returns the percentage of a component's preferred size that it * may be squeezed in order to attempt to fit all components * into the layout. */ public int getSqueezeFactor() { return ourSqueezeFactor; } ////// LayoutManager implementation ////// /** * Adds the specified component with the specified name to this layout. */ public void addLayoutComponent(String name, Component comp) { } /** * Removes the specified component from this layout. */ public void removeLayoutComponent(Component comp) { } /** * Returns the preferred size for this layout to arrange the * indicated parent's children. */ public Dimension preferredLayoutSize(Container parent) { if (parent instanceof JToolBar) { setOrientation(((JToolBar) parent).getOrientation()); } return preferredLayoutSize(parent, ourOrientation); } /** * Returns the preferred size for this layout to arrange the * indicated parent's children at the specified orientation. */ // public, because it's useful - not one of the LayoutManager methods public Dimension preferredLayoutSize(Container parent, int orientation) { synchronized (parent.getTreeLock()) { Component[] comps = parent.getComponents(); Dimension total = new Dimension(0, 0); int depth = calculatePreferredDepth(comps, orientation); int length = (ourLengthsMatched ? calculateAdjustedLength(comps, orientation, ourSpacing) : calculatePreferredLength(comps, orientation, ourSpacing)); total.width = (orientation == HORIZONTAL ? length : depth); total.height = (orientation == HORIZONTAL ? depth : length); Insets in = parent.getInsets(); total.width += in.left + in.right; total.height += in.top + in.bottom; return total; } } /** * Returns the minimum size for this layout to arrange the * indicated parent's children at the specified orientation. */ public Dimension minimumLayoutSize(Container parent) { synchronized (parent.getTreeLock()) { if (parent instanceof JToolBar) { setOrientation(((JToolBar) parent).getOrientation()); } Component[] comps = parent.getComponents(); Dimension total = new Dimension(0, 0); int depth = calculatePreferredDepth(comps, ourOrientation); int length = calculateMinimumLength(comps, ourOrientation, ourSpacing); total.width = (ourOrientation == HORIZONTAL ? length : depth); total.height = (ourOrientation == HORIZONTAL ? depth : length); Insets in = parent.getInsets(); total.width += in.left + in.right; total.height += in.top + in.bottom; return total; } } /** * Lays out the child components within the indicated parent container. */ public void layoutContainer(Container parent) { synchronized (parent.getTreeLock()) { if (parent instanceof JToolBar) { setOrientation(((JToolBar) parent).getOrientation()); } layoutComponents(parent); } } private void layoutComponents(Container parent) { Component[] components = parent.getComponents(); Insets in = parent.getInsets(); int maxHeight = parent.getHeight() - in.top - in.bottom; int maxWidth = parent.getWidth() - in.left - in.right; boolean horiz = (ourOrientation == HORIZONTAL); int totalDepth = calculatePreferredDepth(components, ourOrientation); totalDepth = Math.max(totalDepth, (horiz ? maxHeight : maxWidth)); int prefLength = (ourLengthsMatched ? calculateAdjustedLength(components, ourOrientation, ourSpacing) : calculatePreferredLength(components, ourOrientation, ourSpacing)); int totalLength = Math.min(prefLength, (horiz ? maxWidth : maxHeight)); int a = (horiz ? in.left : in.top); int b = (horiz ? in.top : in.left); int l = 0, d = 0, sum = 0; int matchedLength = 0; Dimension prefsize = null; if (ourLengthsMatched) { matchedLength = (horiz ? getMaxPrefWidth(components) : getMaxPrefHeight(components)); if (prefLength > totalLength && ourSqueezeFactor < 100) { int minLength = calculateMinimumLength(components, ourOrientation, ourSpacing); if (minLength >= totalLength) { matchedLength = (matchedLength * ourSqueezeFactor) / 100; } else { int numSeparators = countSeparators(components); int numComponents = components.length - numSeparators; int diff = (prefLength - totalLength) / numComponents; if ((prefLength - totalLength) % numComponents > 0) diff++; matchedLength -= diff; } } } for (int i = 0; i < components.length; i++) { prefsize = components[i].getPreferredSize(); if (!ourLengthsMatched) l = (horiz ? prefsize.width : prefsize.height); else l = matchedLength; if (components[i] instanceof JSeparator) { // l = Math.min(prefsize.width, prefsize.height); l = (horiz ? prefsize.width : prefsize.height); d = totalDepth; sum += l; if (ourDrop && sum > totalLength) l = 0; } else { sum += l; if (ourDrop && sum > totalLength) l = 0; else if (ourFill && !ourLengthsMatched && i == components.length - 1) { l = Math.max(l, (horiz ? maxWidth : maxHeight)); } if (ourDepthsMatched) d = totalDepth; else d = (horiz ? prefsize.height : prefsize.width); } if (horiz) components[i].setBounds(a, b + (totalDepth - d) / 2, l, d); else components[i].setBounds(b + (totalDepth - d) / 2, a, d, l); a += l + ourSpacing; sum += ourSpacing; } } /** * Returns the largest preferred width of the provided components. */ private int getMaxPrefWidth(Component[] components) { int maxWidth = 0; int componentWidth = 0; Dimension d = null; for (int i = 0; i < components.length; i++) { d = components[i].getPreferredSize(); componentWidth = d.width; if (components[i] instanceof JSeparator) { componentWidth = Math.min(d.width, d.height); } maxWidth = Math.max(maxWidth, componentWidth); } return maxWidth; } /** * Returns the largest preferred height of the provided components. */ private int getMaxPrefHeight(Component[] components) { int maxHeight = 0; int componentHeight = 0; Dimension d = null; for (int i = 0; i < components.length; i++) { d = components[i].getPreferredSize(); componentHeight = d.height; if (components[i] instanceof JSeparator) { componentHeight = Math.min(d.width, d.height); } else maxHeight = Math.max(maxHeight, componentHeight); } return maxHeight; } /** * Calculates the preferred "length" of this layout for the provided * components based on the largest component preferred size. */ private int calculateAdjustedLength(Component[] components, int orientation, int spacing) { int total = 0; int componentLength = (orientation == HORIZONTAL ? getMaxPrefWidth(components) : getMaxPrefHeight(components)); for (int i = 0; i < components.length; i++) { if (components[i] instanceof JSeparator) { Dimension d = components[i].getPreferredSize(); // total += Math.min(d.width, d.height); total += (orientation == HORIZONTAL ? d.width : d.height); } else total += componentLength; } int gaps = Math.max(0, spacing * (components.length - 1)); total += gaps; return total; } /** * Calculates the minimum "length" of this layout for the provided * components, taking the squeeze factor into account when necessary. */ private int calculateMinimumLength(Component[] components, int orientation, int spacing) { if (!ourLengthsMatched) return calculatePreferredLength(components, orientation, spacing); if (ourSqueezeFactor == 100) return calculateAdjustedLength(components, orientation, spacing); int total = 0; int componentLength = (orientation == HORIZONTAL ? getMaxPrefWidth(components) : getMaxPrefHeight(components)); componentLength = (componentLength * ourSqueezeFactor) / 100; for (int i = 0; i < components.length; i++) { if (components[i] instanceof JSeparator) { Dimension d = components[i].getPreferredSize(); // total += Math.min(d.width, d.height); total += (orientation == HORIZONTAL ? d.width : d.height); } else total += componentLength; } int gaps = Math.max(0, spacing * (components.length - 1)); total += gaps; return total; } /** * Calculates the preferred "length" of this layout for the provided * components. */ private int calculatePreferredLength(Component[] components, int orientation, int spacing) { int total = 0; Dimension d = null; for (int i = 0; i < components.length; i++) { d = components[i].getPreferredSize(); // if (components[i] instanceof JSeparator) // { // total += Math.min(d.width, d.height); // } // // else total += (orientation == HORIZONTAL ? d.width : d.height); } int gaps = Math.max(0, spacing * (components.length - 1)); total += gaps; return total; } /** * Returns the preferred "depth" of this layout for the provided * components. */ private int calculatePreferredDepth(Component[] components, int orientation) { if (orientation == HORIZONTAL) return getMaxPrefHeight(components); else if (orientation == VERTICAL) return getMaxPrefWidth(components); else return 0; } private int countSeparators(Component[] components) { int count = 0; for (int i = 0; i < components.length; i++) { if (components[i] instanceof JSeparator) count++; } return count; } }