Stack Layout, uses an orientation to determine if the contents should be arranged horizontally or vertically. : Customized Layout « Swing JFC « Java






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


}

   
    
  








Related examples in the same category

1.Custom layout: EdgeLayout
2.Customized layout managerCustomized layout manager
3.ColumnLayoutColumnLayout
4.Applet GUI demo of TreeLayout layout manager
5.Relative Layout Manager for Java J2SE
6.Basically two (or more) columns of different, but constant, widths
7.GraphPaperLayoutGraphPaperLayout
8.Table Layout
9.Table Layout implements LayoutManager2
10.Table layout manager
11.Flex Layout
12.Square Layout
13.Center Layout
14.Wrapper Layout
15.Tile Layout
16.Custom Layout DemoCustom Layout Demo
17.X Y Layout
18.DividerLayout is layout that divides two components with the column of actions
19.A simple layoutmanager to overlay all components of a parent.
20.A layout manager that displays a single component in the center of its container.
21.A layout manager that spaces components over six columns in seven different formats.
22.Compents are laid out in a circle.
23.Special simple layout used in TabbedContainer
24.Place components at exact locations (x, y, width, height) and then determine how they behave when the window containing them (their parent) is resized
25.Specialised layout manager for a grid of components.