Mega Code Archive

 
Categories / Java / Swing JFC
 

Table layout manager

// 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.  *  * <p>use like:   </br>  *    new TableLayout(cols) </br>  *    add(comp, new TableOption(..))  </br>  *    ..  * </p>  */ 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<ncols; ++i)         components[i] = new Component[ncomponents];     }     // Nullify the array     for (int i=0; i<ncols; ++i)     {       for (int j=0; j<components[i].length; ++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<ncomponents; ++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<option.colSpan; ++c)         for (int r=0; r<option.rowSpan; ++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<nrows; ++row)     {       for (col=0; col<ncols; ++col)       {         Component comp = components[col][row];         for (int r=row; r<nrows && components[col][r]==comp; ++r)         {           for (int c=col; c<ncols && components[c][r]==comp; ++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<ncols)     {       minWidth = new int[ncols];       prefWidth = new int[ncols];       columnWidth = new int[ncols];       weight = new int[ncols];     }     if (minHeight==null || minHeight.length<nrows)     {       minHeight = new int[nrows];       prefHeight = new int[nrows];     }     int i;     for (i=0; i<ncols; ++i)     {       minWidth[i] = 0;       prefWidth[i] = 0;     }     for (i=0; i<nrows; ++i)     {       minHeight[i] = 0;       prefHeight[i] = 0;     }     // measure the minimum and preferred size of each row and column     for (int row=0; row<nrows; ++row)     {       for (int col=0; col<ncols; ++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<option.colSpan; ++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<option.rowSpan; ++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<ncols; ++i)     {       MinWidth += minWidth[i];       PrefWidth += prefWidth[i] + hgap;     }     for (i=0; i<nrows; ++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<ncols; ++c)       weight[c] = prefWidth[c];     for (int r=0; r<nrows; ++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<ncols; ++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<ncols; ++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<ncols; ++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<ncols; ++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<option.colSpan; ++i)             cellWidth += columnWidth[c+i];           for (int i=1; i<option.rowSpan; ++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.      * <p>      * @return the value <code>0.5f</code> 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.      * <p>      * @return the value <code>0.5f</code> 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);     }   } }