Grid Layout Ex

Here's what I found useful for laying out components in some circumstances easier and more precise than Java's GridBagLayout. --CostinCozianu.

For the design of the client API, I applied EmulateKeywordAndDefaultParameters.

A layout that describes a grid, pretty much like GridBagLayout, the width of a column is given as a float. If it is greater than 1 than it is converted as int and sets the fixed number of pixels for that column, if it is within (0,1) it represents the weight in the distribution of free space. So is with the rows. Therefore a construction:

        float lineParams[] = new float[] { 50f, .7f, .2f, 50f };
float columnParams[] = new float [] { .1f, .5f,.1f,75f};
new GridLayoutEx(lineParams,columnParams,2,2);

Represents a grid with first and fourth line fixed as 50 pixels, the second and third line divide their space proportionally to 7 and 2 (the sum of the "quota" (0,1) parameters need not be 1).The last column has a fixed size of 75 pixels while the first three columns divide the space proportionally with 1,5,1. There's a gap space between rows and columns of 2 pixels.

package home.costin.ui;

import java.awt.*; import java.io.ObjectStreamException?; import java.io.Serializable;

/**

 * Insert the type's description here.
 * Creation date: (9/18/2001 10:44:40 AM)
 * 
 * License: APACHE LICENSE
 * http://www.apache.org/LICENSE.txt
 * @author: Costin Cozianu
 */
public class GridLayoutEx implements LayoutManager2 {

/**
 * Enumeration constants to use for Fill values
 */
public static final FillOption? 
NO_FILL= new FillOption?(0),
FILL= new FillOption?(1);

private final static FillOption?[] FILL_CONSTS ={NO_FILL,FILL};

/** * TypeSafe Enumeration for FillOptions? * @author ccozianu */ public static class FillOption? implements Serializable { private int i=0; private FillOption?(int i_) {this.i=i_;} private Object readResolve() throws ObjectStreamException? { return FILL_CONSTS[i]; } public boolean equals(Object o) { if (this==o) return true; else return this.i== ((FillOption?)o).i; } public int hashCode(){ return i;} }

/** * Constants for vertical alignment */ public static final VAlignOption VA_CENTER= new VAlignOption(0), VA_BOTTOM= new VAlignOption(1), VA_TOP= new VAlignOption(2);

private final static VAlignOption[] VALIGN_CONSTS ={VA_CENTER,VA_BOTTOM,VA_TOP};

/** * Type-safe enumeration for vertical alignment * @author ccozianu */ public static class VAlignOption implements Serializable { private int i=0; private VAlignOption(int i_) {this.i=i_;} private Object readResolve() throws ObjectStreamException? { return VALIGN_CONSTS[i]; } public boolean equals(Object o) { if (this==o) return true; else return this.i== ((VAlignOption)o).i; } public int hashCode(){ return i;} }

/** * Constants for vertical alignment */ public static final HAlignOption HA_CENTER= new HAlignOption(0), HA_LEFT= new HAlignOption(1), HA_RIGHT= new HAlignOption(2);

private final static HAlignOption[] HALIGN_CONSTS ={HA_CENTER,HA_LEFT,HA_RIGHT};

/** * Type-safe enumeration for vertical alignment * @author ccozianu */ public static class HAlignOption implements Serializable { private int i=0; private HAlignOption(int i_) {this.i=i_;} private Object readResolve() throws ObjectStreamException? { return HALIGN_CONSTS[i]; } public boolean equals(Object o) { if (this==o) return true; else return this.i== ((HAlignOption)o).i; } public int hashCode(){ return i; } }

public static class Constraint implements Serializable, Cloneable { private int line=0, column=0, rowspan=1, colspan=1;

public Constraint setLine(int line_) { this.line= line_; return this; } public Constraint setColumn(int column_ ) { this.column= column_; return this; } public Constraint setRowspan(int rowspan_) {this.rowspan= rowspan_; return this;} public Constraint setColspan(int colspan_ ) { this.colspan= colspan_; return this; }

public Insets insets= new Insets(0,0,0,0); public Constraint setInsets(Insets insets_) {this.insets= insets_; return this;}

private FillOption? verticalFill= NO_FILL; private FillOption? horizontalFill= NO_FILL; public Constraint setVerticalFill(FillOption? option){ this.verticalFill=option; return this;}; public Constraint setHorizontalFill(FillOption? option){ this.horizontalFill=option; return this;};

private VAlignOption verticalAlign= VA_CENTER; private HAlignOption horizontalAlign= HA_CENTER; public Constraint setVerticalAlign(VAlignOption option) {this.verticalAlign= option; return this; } public Constraint setHAlignOption(HAlignOption option) {this.horizontalAlign= option; return this;}

public Object clone() throws CloneNotSupportedException? { /*Constraint result= new Constraint(); result.colspan= colspan; result.rowspan= rowspan; result.column= column; result.line= line; result.insets= (Insets) (insets==null? null : insets.clone());*/ return (Constraint) super.clone(); }

private void computeBoundsInsideTheCell( Component comp, int x, int y, int w, int h){ x += insets.left; y += insets.top; w -= insets.right; h -= insets.bottom; Dimension d= comp.getPreferredSize(); if (horizontalFill==NO_FILL){ if (d.getWidth()< w){ if (horizontalAlign==HA_LEFT){ w= (int)d.getWidth(); } else if (horizontalAlign==HA_RIGHT){ x += ( w - d.getWidth()); w = (int)d.getWidth(); } else if (horizontalAlign==HA_CENTER) { x += (w-d.getWidth())/2; w= (int) d.getWidth(); } }} if (verticalFill==NO_FILL){ if (d.getHeight()< h){ if (verticalAlign==VA_TOP){ h= (int)d.getHeight(); } else if (verticalAlign==VA_BOTTOM){ y += ( h - d.getHeight()); h = (int)d.getHeight(); } else if (verticalAlign==VA_CENTER) { y += (h-d.getHeight())/2; h= (int) d.getHeight(); } }} comp.setBounds(x, y, w, h); } }

java.util.Hashtable components= new java.util.Hashtable();

/** * specifies whether or not the row parameters are specified by quota * if rowParamIsQuota[i]==true then the parameter is a quota * that specifies how much of the available space should be given to the i-th row * and should be read from rowParamsQuota <br> * else the parameter is the fixed height of the row and should be taken from * rowParamsInt */ private boolean[] rowParamIsQuota; private float[] rowParamsQuota; private int[] rowParamsInt; private int rowCount;

private boolean[] columnParamIsQuota; private int[] columnParamsInt; private float[] columnParamsQuotas; private int colCount;

private int reservedWidth; private int hgap; private int vgap; private int reservedHeight;

/** * buffer used to calculate rowCoordinates in layout procedure */ private transient int rowCoordinates[] = null;

/** * buffer used to calculate rowCoordinates in layout procedure */ private transient int columnCoordinates[] = null;

public GridLayoutEx (float [] lineParams, float [] columnParams) { this(lineParams,columnParams,1,1); } public GridLayoutEx (float [] rowParams, float [] columnParams, int hgap, int vgap) { if (rowParams == null || rowParams.length == 0) throw new IllegalArgumentException("Illegal lineParams "); if (columnParams == null || columnParams.length == 0) throw new IllegalArgumentException("Illegal columnParams ");

rowCount= rowParams.length; colCount= columnParams.length; this.hgap= hgap<0 ? 0 : hgap; this.vgap= vgap<0 ? 0 : vgap;

rowParamIsQuota= new boolean[rowCount]; rowParamsQuota= new float[rowCount]; rowParamsInt= new int[rowCount];

columnParamIsQuota= new boolean [colCount]; columnParamsQuotas= new float [colCount]; columnParamsInt= new int[colCount];

float sumOfLines=0; for (int i=0; i<rowCount; i++) { if (rowParams[i] <=0) rowParams[i]= .1f;

if (rowParams[i] <= 1) { rowParamIsQuota[i]= true; sumOfLines += rowParams[i]; } else { rowParamIsQuota[i]= false; reservedHeight += rowParams[i]; rowParamsInt[i]= (int)rowParams[i]; } }

if (sumOfLines != 0) for (int i=0; i<rowCount;i++) if (rowParamIsQuota[i]) rowParamsQuota[i]= rowParams[i]/sumOfLines;

float sumOfColumns=0; for (int i=0; i<colCount; i++) { if (columnParams[i] <=0) columnParams[i]= .1f;

if (columnParams[i] <= 1) { columnParamIsQuota[i]= true; sumOfColumns += columnParams[i]; } else { columnParamIsQuota[i]= false; reservedWidth += columnParams[i]; columnParamsInt[i]= (int)columnParams[i]; } }

if (sumOfColumns != 0) for (int i=0; i<colCount; i++) if (columnParamIsQuota[i]) columnParamsQuotas[i]= columnParams[i]/sumOfColumns;

// allocate lineCoordinates buffer rowCoordinates = new int[rowCount + 1]; columnCoordinates= new int[colCount + 1];

} /** * Adds the specified component to the layout, using the specified * constraint object. * @param comp the component to be added * @param constraints where/how the component is added to the layout. */
public void addLayoutComponent(Component comp, Object constraints) {
Constraint source = null;
if ( constraints == null ||  ! (constraints instanceof Constraint)  )
source= new Constraint();
else
source = (Constraint) constraints;
try {
components.put(comp, source.clone()); }
catch (CloneNotSupportedException? ex) {}
}
/**
 * Adds the specified component with the specified name to
 * the layout.
 * @param name the component name
 * @param comp the component to be added
 */
public void addLayoutComponent(String name, Component comp) {
addLayoutComponent(comp, new Constraint());
} /**
 * performes the algorithm for distributing space
 * @param d0 int the initial offset from 0 where coordinates start being assigned
 * @param length the total length available to be distributed
 * @param gap int
 * @param reservedSpace int
 * @param n int the number of intervals
 * @param paramIsQuota boolean[]
 * @param quotaParams float[]
 * @param fixedParams int[]
 * @param outCoordinates [] (out parameter) the vector to store the result
 */
private void distributeSpace(int d0, int length, int gap, int reservedSpace, int n, boolean[] paramIsQuota, float quotaParams[], int fixedParams[], int outCoordinates[]) {
int currentWidth = 0;
outCoordinates[0] = d0 + gap;

int h= length- reservedSpace - (n+1)*gap;

for (int i = 0; i < n; i++) { if (paramIsQuota[i]) currentWidth = (int) (h * quotaParams[i]); else currentWidth = fixedParams[i];

outCoordinates[i + 1] = outCoordinates[i] + currentWidth + gap;

}

}
/**
 * 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.
 */
public float getLayoutAlignmentX(Container target) {
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.
 */
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
/**
 * Invalidates the layout, indicating that if the layout manager
 * has cached information it should be discarded.
 */
public void invalidateLayout(Container target) {
//nothing is cached
} /**
 * Lays out the container in the specified panel.
 * @param parent the component which needs to be laid out 
 */
public void layoutContainer(Container parent) {
try
{
System.out.println("Layout called with dimensions:" + parent.getBounds());
synchronized (parent.getTreeLock())
{
Insets insets = parent.getInsets();
Component[] compArray = parent.getComponents();
int ncomponents = compArray.length;

if (ncomponents == 0) return; int w = parent.getSize().width - (insets.left + insets.right); int h = parent.getSize().height - (insets.top + insets.bottom); if (w <= 0 || h <= 0) return;

// calculates the beginning of each line in pixel width coordinates distributeSpace(insets.top, h , vgap, reservedHeight, rowCount, rowParamIsQuota,rowParamsQuota,rowParamsInt,rowCoordinates);

// calculates the beginning of each line in pixel width coordinates distributeSpace(insets.left, w , hgap, reservedWidth, colCount, columnParamIsQuota,columnParamsQuotas,columnParamsInt,columnCoordinates);

for (int c = 0; c < ncomponents; c++) { Component comp = compArray[c]; Constraint constraint = null; try { constraint = (Constraint) components.get(comp); } catch (ClassCastException ex) { }; int xx = 0, yy = 0, ww = 0, hh = 0; if (constraint == null) comp.setBounds(-200, -200, 100, 100); else try { xx = columnCoordinates[constraint.column]; yy = rowCoordinates[constraint.line]; ww = columnCoordinates[constraint.column + constraint.colspan] - xx - hgap; hh = rowCoordinates[constraint.line + constraint.rowspan] - yy - vgap; } catch (ArrayIndexOutOfBoundsException ex) { xx = -200; yy = -200; ww = 100; hh = 100; }; constraint.computeBoundsInsideTheCell(comp, xx, yy, ww, hh); } } } catch (Throwable ex) { System.err.println("Exception caucht in Layout:" + ex); ex.printStackTrace(System.err); throw new RuntimeException(ex.getMessage()); }
} /**
 * Test method please 
 * TO DO: add more options
 * @param args java.lang.String[]
 */
public static void main(String[] args) {
try
{
Frame frame= new Frame("Testing GridLayoutEx layout manager");
frame.setSize(600,400);
frame.addWindowListener( new java.awt.event.WindowAdapter?()
{
public void windowClosing(java.awt.event.WindowEvent? event)
{
System.exit(0);
}
}
);

float lineParams[] = new float[] { 50f, .7f, .2f, 50f }; float columnParams[] = new float [] { .1f, .5f,.1f,75f};

frame.setLayout( new GridLayoutEx(lineParams,columnParams,2,2) ); GridLayoutEx.Constraint c = new GridLayoutEx.Constraint(). setColumn(0).setLine(0) .setColspan(3).setHorizontalFill(FILL).setVerticalFill(FILL); Button pack= new Button("Pack"); pack.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed( java.awt.event.ActionEvent evt) { Frame parent= (Frame) ((Button) evt.getSource()).getParent(); parent.pack(); } } ); frame.add( pack, c);

c.setColumn(3).setLine(0).setColspan(1).setRowspan(1).setInsets(new Insets(0,0,10,10)); frame.add( new Button("B2"), c);

c.setColumn(0).setLine(1).setColspan(3).setRowspan(1); c.setHorizontalFill(NO_FILL).setHAlignOption(HA_CENTER); Button b3= new Button("B3"); b3.setSize(100,100); frame.add( new Button("B3"), c);

c.setColumn(3).setLine(1).setColspan(1).setRowspan(1) .setVerticalAlign(VA_BOTTOM).setVerticalFill(NO_FILL); frame.add( new Button("B4"), c);

c.setColumn(0); c.setLine(2).setColspan(3).setRowspan(1); frame.add( new Button("B5"), c);

c.setColumn( 3).setLine( 2).setColspan(1).setRowspan(1) .setVerticalFill(FILL); frame.add( new Button("B6"), c);

c.setColumn( 0).setLine( 3); frame.add( new Button("B7"), c);

c.setColumn(1); frame.add( new Button("B8"), c);

c.setColumn( 2); frame.add( new Button("B9"), c);

c.setColumn(3).setHorizontalFill(FILL).setVerticalFill(FILL); frame.add( new Button("B10"), c);

frame.setVisible(true); } catch(Exception ex) { System.err.println(ex); ex.printStackTrace(System.err); }
}
/** 
 * Returns the maximum size of this component.
 * @see java.awt.Component#getMinimumSize()
 * @see java.awt.Component#getPreferredSize()
 * @see LayoutManager
 */
public Dimension maximumLayoutSize(Container target) {
return preferredLayoutSize(target);
}
/** 
 * Calculates the minimum size dimensions for the specified 
 * panel given the components in the specified parent container.
 * @param parent the component to be laid out
 * @see #preferredLayoutSize
 */
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}

/**
 * Calculates the preferred size dimensions for the specified 
 * panel given the components in the specified parent container.
 * @param parent the component to be laid out
 *  It tries to approximate the size the container to the <b>minimum size</b> such 
 *  that all components get a size greater than or equal to their preferred size
 *  and the rows or columns that are fixed size remain fixed
 * @see #minimumLayoutSize
 */
public Dimension preferredLayoutSize(Container parent) {
Insets insets= parent.getInsets();
Component [] compArray= parent.getComponents();
int [] preferredRowSizes= new int[rowCount];
int [] preferredColSizes= new int[colCount]; 
for (int x=0;x<compArray.length; x++) {
Component comp= compArray[x];
Constraint constraint= (Constraint) components.get(comp);
Dimension currentDim= comp.getPreferredSize();
currentDim.width += constraint.insets.left + constraint.insets.right;
currentDim.height += constraint.insets.top + constraint.insets.bottom;

int beginRow= constraint.line; int endRow= beginRow + constraint.rowspan; float totalRowWeight=0f; for (int row=beginRow;row<endRow;row++) if (rowParamIsQuota[row]) totalRowWeight += rowParamsQuota[row]; for (int r=beginRow; r<endRow;r++) { if (rowParamIsQuota[r]) { int rowSize= (int) (currentDim.height * rowParamsQuota[r] /totalRowWeight); if ( rowSize > preferredRowSizes[r]) preferredRowSizes[r]= rowSize; } }

int beginCol= constraint.column; int endCol= beginCol+ constraint.colspan; float totalColWeight=0f; for (int col=beginCol;col<endCol;col++) if (columnParamIsQuota[col]) totalColWeight += columnParamsQuotas[col]; for (int col=beginCol; col<endCol;col++) { if (columnParamIsQuota[col]) { int colSize= (int) (currentDim.width * columnParamsQuotas[col] / totalColWeight ); if ( colSize > preferredColSizes[col]) preferredColSizes[col]= colSize; } } } //done cycling through components // let cycle through rows and columns to get the dimensions int adjustableHeight=0; for (int row=0;row < rowCount; row++) { if (rowParamIsQuota[row]) { int requiredHeight= ( int) (preferredRowSizes[row] / rowParamsQuota[row]); if (requiredHeight>adjustableHeight) adjustableHeight= requiredHeight; } } int adjustableWidth=0; for (int col=0;col < colCount; col++) { if (columnParamIsQuota[col]) { int requiredWidth= ( int) (preferredColSizes[col] / columnParamsQuotas[col]); if (requiredWidth>adjustableWidth) adjustableWidth= requiredWidth; } } return new Dimension(reservedWidth+ adjustableWidth + colCount * hgap , reservedHeight + adjustableHeight + rowCount * vgap);
}
/**
 * Removes the specified component from the layout.
 * @param comp the component to be removed
 */
public void removeLayoutComponent(Component comp) {
components.remove(comp);
}

}


EditText of this page (last edited April 7, 2005) or FindPage with title or text search