Mega Code Archive

 
Categories / Java Tutorial / Swing
 

Stack Layout, uses an orientation to determine if the contents should be arranged horizontally or vertically

/*  *  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;   } }