Stack Layout, uses an orientation to determine if the contents should be arranged horizontally or vertically. : Custom Layout « Swing « 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;
  }


}








14.97.Custom Layout
14.97.1.Custom Layout: DiagonalLayoutCustom Layout: DiagonalLayout
14.97.2.GraphPaperLayout implements LayoutManager2
14.97.3.Circle Layout
14.97.4.This LayoutManager arranges the components into a column. Components are always given their preferred size
14.97.5.Compents are laid out in a circle.
14.97.6.Specialised layout manager for a grid of components.
14.97.7.Stack Layout, uses an orientation to determine if the contents should be arranged horizontally or vertically.
14.97.8.A layout manager that displays a single component in the center of its container.
14.97.9.A simple layoutmanager to overlay all components of a parent.
14.97.10.A layout manager that lays out components along a central axis
14.97.11.Wrapper Layout
14.97.12.Center Layout
14.97.13.DividerLayout is layout that divides two components with the column of actions
14.97.14.X Y Layout