Swing JFC Java

// Table layout manager, with the flexibility of GridBagLayout but the ease
// of use of HTML table declarations.
// See http://www.parallax.co.uk/~rolf/download/table.html
// Copyright (C) Rolf Howarth 1997, 1998 (rolf@parallax.co.uk)
// Permission to freely use, modify and distribute this code is given,
// provided this notice remains attached. This code is provided for
// educational use only and no warranty as to its suitability for any
// other purpose is made.
// Modification history
// 0.1  01 Nov 96  First version
// 1.0  17 Jan 97  Minor bug fix; added column weighting.
// 1.1  08 Apr 98  Don't use methods deprecated in JDK1.1
// 1.2  16 Apr 98  Make own copy of Dimension objects as they're not immutable
import java.awt.*;
import java.util.*;
// Private class to parse and store the options for a single table entry
/**
 * Table layout manager, with the flexibity of GridBagLayout but the ease of
 * use of HTML table declarations.
 *
 * 

use like:   

 *    new TableLayout(cols) 

 *    add(comp, new TableOption(..))  

 *    ..
 * 


 */
public class TableLayout implements LayoutManager,LayoutManager2 {
  private Hashtable options = new Hashtable();
  private TableOption defaultOption;
  private int nrows=0, ncols=0;
  private int ncomponents=0;
  private Component[][] components=null;
  private int MinWidth=0, MinHeight=0, PrefWidth=0, PrefHeight=0;
  private int[] minWidth=null, minHeight=null, prefWidth=null, prefHeight=null;
  private int[] weight=null, columnWidth=null;
  private int hgap=0, vgap=0;
  /**
  * Construct a new table layout manager.
  * @param cols Number of columns, used when adding components to tell when to go to the next row
  * @param defaultAlignment Default defaultAlignment for cells if not specified at the time of adding the component
  * @param hgap Horizontal gap between cells and at edge (in pixels)
  * @param vgap Vertical gap between cells and at edge (in pixels)
  **/
  public TableLayout(int cols, String defaultAlignment, int hgap, int vgap) {
    this(cols, new TableOption(defaultAlignment),hgap,vgap);
  }
    public TableLayout(int cols, TableOption defaultAlignment, int hgap, int vgap) {
        this.ncols = cols;  // the number of columns is specified
    this.nrows = 0;   // the number of rows is calculated
        this.components = new Component[cols][];
    this.defaultOption=defaultAlignment;
        this.hgap = hgap;
        this.vgap = vgap;
    }
  public TableLayout(int cols, String alignment)
  {
    this(cols, alignment, 0, 0);
  }
  public TableLayout(int cols)
  {
    this(cols, "", 0, 0);
  }
  public void addLayoutComponent(String alignment, Component comp)
  {
    options.put(comp, new TableOption(alignment));
  }
  public void removeLayoutComponent(Component comp)
  {
    options.remove(comp);
  }
  // Iterate through the components, counting the number of rows taking into account
  // row and column spanning, then initialise the components[c][r] matrix so that
  // we can retrieve the component at a particular row,column position.
  private void loadComponents(Container parent)
  {
    ncomponents = parent.getComponentCount();
    // If we haven't allocated the right sized array for each column yet, do so now.
    // Note that the number of columns is fixed, but the number of rows is not know
    // and could in the worst case be up the number of components. Unfortunately this
    // means we need to allocate quite big arrays, but the alternative would require
    // complex multiple passes as we try to work out the effect of row spanning.
    if (components[0] == null || components[0].length < ncomponents)
    {
      for (int i=0; i        components[i] = new Component[ncomponents];
    }
    // Nullify the array
    for (int i=0; i    {
      for (int j=0; j        components[i][j] = null;
    }
    // fill the matrix with components, taking row/column spanning into account
    int row=0, col=0;
    for (int i=0; i    {
      // get the next component and its options
      Component comp = parent.getComponent(i);
      TableOption option = (TableOption) options.get(comp);
      if (option==null) option = defaultOption;
      // handle options to force us to column 0 or to skip columns
      if (option.forceColumn >= 0)
      {
        if (col > option.forceColumn)
          ++row;
        col = option.forceColumn;
      }
      col += option.skipColumns;
      if (col>=ncols) { ++row; col=0; }
      // skip over any cells that are already occupied
      while (components[col][row] != null)
      {
        ++col;
        if (col>=ncols) { ++row; col=0; }
      }
      // if using colspan, will we fit on this row?
      if (col+option.colSpan > ncols)
      {
        ++row;
        col = 0;
      }
      // for now, fill all the cells that are occupied by this component
      for (int c=0; c        for (int r=0; r          components[col+c][row+r] = comp;
      // advance to the next cell, ready for the next component
      col += option.colSpan;
      if (col>=ncols) { ++row; col=0; }
    }
    // now we know how many rows there are
    if (col == 0)
      nrows = row;
    else
      nrows = row+1;
    // now we've positioned our components we can thin out the cells so
    // we only remember the top left corner of each component
    for (row=0; row    {
      for (col=0; col      {
        Component comp = components[col][row];
        for (int r=row; r        {
          for (int c=col; c          {
            if (r>row || c>col)
              components[c][r] = null;
          }
        }
      }
    }
  }
  private void measureComponents(Container parent)
  {
    // set basic metrics such as ncomponents & nrows, and load the components
    // into the components[][] array.
    loadComponents(parent);
    // allocate new arrays to store row and column preferred and min sizes, but
    // only if the old arrays aren't big enough
    if (minWidth==null || minWidth.length    {
      minWidth = new int[ncols];
      prefWidth = new int[ncols];
      columnWidth = new int[ncols];
      weight = new int[ncols];
    }
    if (minHeight==null || minHeight.length    {
      minHeight = new int[nrows];
      prefHeight = new int[nrows];
    }
    int i;
    for (i=0; i    {
      minWidth[i] = 0;
      prefWidth[i] = 0;
    }
    for (i=0; i    {
      minHeight[i] = 0;
      prefHeight[i] = 0;
    }
    // measure the minimum and preferred size of each row and column
    for (int row=0; row    {
      for (int col=0; col      {
        Component comp = components[col][row];
        if (comp != null)
        {
          TableOption option = (TableOption) options.get(comp);
          if (option==null) option = defaultOption;
          Dimension minSize = new Dimension(comp.getMinimumSize());
          Dimension prefSize = new Dimension(comp.getPreferredSize());
          // enforce prefSize>=minSize
          if (prefSize.width < minSize.width)
            prefSize.width = minSize.width;
          if (prefSize.height < minSize.height)
            prefSize.height = minSize.height;
          // divide size across all the rows or columns being spanned
          minSize.width /= option.colSpan;
          minSize.height /= option.rowSpan;
          prefSize.width = (prefSize.width - hgap*(option.colSpan-1)) / option.colSpan;
          prefSize.height = (prefSize.height - vgap*(option.rowSpan-1)) / option.rowSpan;
          for (int c=0; c          {
            if (minSize.width > minWidth[col+c])
              minWidth[col+c] = minSize.width;
            if (prefSize.width > prefWidth[col+c])
              prefWidth[col+c] = prefSize.width;
          }
          for (int r=0; r          {
            if (minSize.height > minHeight[row+r])
              minHeight[row+r] = minSize.height;
            if (prefSize.height > prefHeight[row+r])
              prefHeight[row+r] = prefSize.height;
          }
        }
      }
    }
    // add rows and columns to give total min and preferred size of whole grid
    MinWidth = 0;
    MinHeight = 0;
    PrefWidth = hgap;
    PrefHeight = vgap;
    for (i=0; i    {
      MinWidth += minWidth[i];
      PrefWidth += prefWidth[i] + hgap;
    }
    for (i=0; i    {
      MinHeight += minHeight[i];
      PrefHeight += prefHeight[i] + vgap;
    }
  }
  public Dimension minimumLayoutSize(Container parent)
  {
    Insets insets = parent.getInsets();
    measureComponents(parent);
    // System.out.println("Min Size: "+MinWidth+","+MinHeight);
    return new Dimension(insets.left + insets.right + MinWidth,
      insets.top + insets.bottom + MinHeight);
  }
  public Dimension preferredLayoutSize(Container parent)
  {
    Insets insets = parent.getInsets();
    measureComponents(parent);
    // System.out.println("Pref Size: "+PrefWidth+","+PrefHeight);
    // System.out.println("+ insets LR "+insets.left+"+"+insets.right+", TB "+insets.top+"+"+insets.bottom);
    return new Dimension(insets.left + insets.right + PrefWidth,
      insets.top + insets.bottom + PrefHeight);
  }
  public void layoutContainer(Container parent)
  {
    Insets insets = parent.getInsets();
    measureComponents(parent);
    int width = parent.getSize().width - (insets.left + insets.right);
    int height = parent.getSize().height - (insets.top + insets.bottom);
    // System.out.println("Resize "+width+","+height);
    // Decide whether to base our scaling on minimum or preferred sizes, or
    // a mixture of both, separately for width and height scaling.
    // This weighting also tells us how much of the hgap/vgap to use.
    double widthWeighting = 0.0;
    if (width >= PrefWidth || PrefWidth==MinWidth)
      widthWeighting = 1.0;
    else if (width <= MinWidth)
    {
      widthWeighting = 0.0;
      width = MinWidth;
    }
    else
      widthWeighting = (double)(width-MinWidth)/(double)(PrefWidth-MinWidth);
    double heightWeighting = 0.0;
    if (height >= PrefHeight || PrefHeight==MinHeight)
      heightWeighting = 1.0;
    else if (height <= MinHeight)
    {
      heightWeighting = 0.0;
      height = MinHeight;
    }
    else
      heightWeighting = (double)(height-MinHeight)/(double)(PrefHeight-MinHeight);
    // calculate scale factors to scale components to size of container, based
    // on weighted combination of minimum and preferred sizes
    double minWidthScale = (1.0 - widthWeighting) * width/MinWidth;
    //double prefWidthScale = widthWeighting * (width-hgap*(ncols+1))/(PrefWidth-hgap*(ncols+1));
    double minHeightScale = (1.0 - heightWeighting) * height/MinHeight;
    double prefHeightScale = heightWeighting * (height-vgap*(nrows+1))/(PrefHeight-vgap*(nrows+1));
    // only get the full amount of gap if we're working to preferred size
    int vGap = (int) (vgap * heightWeighting);
    int hGap = (int) (hgap * widthWeighting);
    int y = insets.top + vGap;
    for (int c=0; c      weight[c] = prefWidth[c];
    for (int r=0; r    {
      int x = insets.left + hGap;
      int rowHeight = (int)(minHeight[r]*minHeightScale + prefHeight[r]*prefHeightScale);
      // Column padding can vary from row to row, so we need several
      // passes through the columns for each row:
      // First, work out the weighting that deterimines how we distribute column padding
      for (int c=0; c      {
        Component comp = components[c][r];
        if (comp != null)
        {
          TableOption option = (TableOption) options.get(comp);
          if (option==null) option = defaultOption;
          if (option.weight >= 0)
            weight[c] = option.weight;
          else if (option.weight == -1)
            weight[c] = prefWidth[c];
        }
      }
      int totalWeight = 0;
      for (int c=0; c        totalWeight += weight[c];
      int horizSurplus = width - hgap*(ncols+1) - PrefWidth;
      // Then work out column sizes, essentially preferred size + share of padding
      for (int c=0; c      {
        columnWidth[c] = (int) (minWidthScale * minWidth[c] + widthWeighting * prefWidth[c]);
        if (horizSurplus > 0 && totalWeight > 0)
          columnWidth[c] += (int) (widthWeighting * horizSurplus * weight[c] / totalWeight);
      }
      // Only now do we know enough to position all the columns within this row...
      for (int c=0; c      {
        Component comp = components[c][r];
        if (comp != null)
        {
          TableOption option = (TableOption) options.get(comp);
          if (option==null) option = defaultOption;
          // cell size may be bigger than row/column size due to spanning
          int cellHeight = rowHeight;
          int cellWidth = columnWidth[c];
          for (int i=1; i            cellWidth += columnWidth[c+i];
          for (int i=1; i            cellHeight += (int)(minHeight[r+i]*minHeightScale + prefHeight[r+i]*prefHeightScale + vGap);
          Dimension d = new Dimension(comp.getPreferredSize());
          if (d.width > cellWidth || option.horizontal==TableOption.FILL)
            d.width = cellWidth;
          if (d.height > cellHeight || option.vertical==TableOption.FILL)
            d.height = cellHeight;
          int yoff = 0;
          if (option.vertical == TableOption.BOTTOM)
            yoff = cellHeight - d.height;
          else if (option.vertical == TableOption.CENTRE)
            yoff = (cellHeight - d.height) / 2;
          int xoff = 0;
          if (option.horizontal == TableOption.RIGHT)
            xoff = cellWidth - d.width;
          else if (option.horizontal == TableOption.CENTRE)
            xoff = (cellWidth - d.width) / 2;
          // System.out.println(" "+comp.getClass().getName()+" at ("+x+"+"+xoff+","+y+"+"+yoff+"), size "+d.width+","+d.height);
          comp.setBounds(x+xoff,y+yoff,d.width,d.height);
        }
        x += columnWidth[c] + hGap;
      }
      y += rowHeight + vGap;
    }
  }
    public void addLayoutComponent(Component comp, Object constraints) {
        if(constraints instanceof TableOption){
            options.put(comp, constraints);
        }
        else if(constraints==null){
            options.put(comp,defaultOption);
        }
        else throw new IllegalArgumentException("not a valid constraints object="+constraints);
    }
     /**
     * Returns the alignment along the x axis.  This specifies how
     * the component would like to be aligned relative to other
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     * 


     * @return the value 0.5f to indicate centered
     */
    public float getLayoutAlignmentX(Container parent) {
  return 0.5f;
    }
    /**
     * Returns the alignment along the y axis.  This specifies how
     * the component would like to be aligned relative to other
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     * 


     * @return the value 0.5f to indicate centered
     */
    public float getLayoutAlignmentY(Container parent) {
  return 0.5f;
    }
    /**
     * Invalidates the layout, indicating that if the layout manager
     * has cached information it should be discarded.
     */
    public void invalidateLayout(Container target) {
    }
    /**
     * Returns the maximum dimensions for this layout given the components
     * in the specified target container.
     * @param target the container which needs to be laid out
     * @see Container
     * @see #minimumLayoutSize(Container)
     * @see #preferredLayoutSize(Container)
     * @return the maximum dimensions for this layout
     */
    public Dimension maximumLayoutSize(Container target) {
  return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
    }
}
class TableOption{
  public static final int CENTRE=1, FILL=2, LEFT=3, RIGHT=4, TOP=5, BOTTOM=6;
  int horizontal = CENTRE;
  int vertical = CENTRE;
  int rowSpan=1, colSpan=1, skipColumns=0, forceColumn=-1, weight=-2;
    /**
     *
     * @param horizontal one of CENTRE,FILL,LEFT,RIGHT,TOP,BOTTOM
     * @param vertical
     */
    public TableOption(int horizontal, int vertical) {
        this.horizontal = horizontal;
        this.vertical = vertical;
    }
    public TableOption(int horizontal, int vertical, int rowSpan, int colSpan) {
        this.horizontal = horizontal;
        this.vertical = vertical;
        this.rowSpan = rowSpan;
        this.colSpan = colSpan;
    }
    public TableOption(int horizontal, int vertical, int rowSpan, int colSpan, int skipColumns, int forceColumn, int weight) {
        this.horizontal = horizontal;
        this.vertical = vertical;
        this.rowSpan = rowSpan;
        this.colSpan = colSpan;
        this.skipColumns = skipColumns;
        this.forceColumn = forceColumn;
        this.weight = weight;
    }
  TableOption(String alignment) {
    StringTokenizer tk = new StringTokenizer(alignment, ",");
    while (tk.hasMoreTokens())
    {
      String token = tk.nextToken();
      boolean ok = false;
      int delim = token.indexOf("=");
      if (token.equals("NW") || token.equals("W") || token.equals("SW"))
        { horizontal = LEFT; ok=true; }
      if (token.equals("NE") || token.equals("E") || token.equals("SE"))
        { horizontal = RIGHT; ok=true; }
      if (token.equals("N") || token.equals("C") || token.equals("F"))
        { horizontal = CENTRE; ok=true; }
      if (token.equals("F") || token.equals("FH"))
        { horizontal = FILL; ok=true; }
      if (token.equals("N") || token.equals("NW") || token.equals("NE"))
        { vertical = TOP; ok=true; }
      if (token.equals("S") || token.equals("SW") || token.equals("SE"))
        { vertical = BOTTOM; ok=true; }
      if (token.equals("W") || token.equals("C") || token.equals("E"))
        { vertical = CENTRE; ok=true; }
      if (token.equals("F") || token.equals("FV"))
        { vertical = FILL; ok=true; }
      if (delim>0)
      {
        int val = Integer.parseInt(token.substring(delim+1));
        token = token.substring(0,delim);
        if (token.equals("CS") && val>0)
          { colSpan = val; ok=true; }
        else if (token.equals("RS") && val>0)
          { rowSpan = val; ok=true; }
        else if (token.equals("SKIP") && val>0)
          { skipColumns = val; ok=true; }
        else if (token.equals("COL"))
          { forceColumn = val; ok=true; }
        else if (token.equals("WT"))
          { weight = val; ok=true; }
      }
      if (!ok) throw new IllegalArgumentException("TableOption "+token);
    }
  }
}