Flex Layout
// FlexLayout
// Copyright (C) by Andrea Carboni.
// This file may be distributed under the terms of the LGPL license.
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.util.StringTokenizer;
/** This class implements a combination of the GridBag and Border layouts. The
* container is subdivided into rows and columns (basically a grid). Each cell
* can be empty or can contain a component and a component can occupy one or more
* cells. If a component is smaller than its cell, it can be aligned both horizontally
* and vertically. The width of a column is the width of its largest component. The
* height of a row is the height of its tallest component. Furthermore, a row or a
* column can be expandable, that is its width/height changes depending on the container's
* size.<BR><BR>
* <B>General design issues:</B><BR>
* First, decide the grid size. Consider that some components (like panels) usually
* spread over more cells so it is a good idea to make the grid a little bigger.<BR><BR>
* Second, decide which row/column expand (this is done by the setColProp/setRowProp
* methods).<BR><BR>
* Third, add components to container (layout) giving proper constraints.<BR><BR>
* <B>Example:</B><BR><PRE>
* FlexLayout flexL = new FlexLayout(2, 3, 4, 4); //--- (1)
* flexL.setColProp(1, FlexLayout.EXPAND); //--- (2)
* flexL.setRowProp(2, FlexLayout.EXPAND); //--- (3)
* setLayout(flexL); //--- (4)
*
* add("0,0", new Label("Name")); //--- (5)
* add("0,1", new Label("Surname")); //--- (6)
* add("1,0,x", textField); //--- (7)
* add("1,1,x", textField); //--- (8)
* add("0,2,x,x,2", panel); //--- (9)
* </PRE><BR>
* <I>Description:</I><BR>
* line 1: creates a FlexLayout of 2 columns and 3 rows. The two 4 represent the gap
* between each column and each row. To modify this gap use setXgap and setYgap.<BR>
* line 2 & 3: column 1 and row 2 are set to expandable (the default is USEPREFERRED)
* remember that row and column id starts from index 0.<BR>
* line 4: the layout is added to the container.<BR>
* line 5 & 6: add two labels in position (0,0) and (0,1)
* line 7 & 8: add two textfields in position (1,0) and (1,1). The x means that the
* width will be expanded to occupy all column size. Because column 1 is set to expandable,
* when the container is resized the textfields are resized too.<BR>
* line 9: adds a panel in position (0,2). Then panel is expanded both horizontally and
* vertically and spreads over 2 cells in x direction.<BR><BR>
* The complete command string is:<BR><PRE>
* "x,y,xa,ya,xs,ys" where:
* x,y specify the component position (are the column-row indexes)
* xa specifies the x align of the component and can be:
* l - for left
* c - for center
* r - for right
* x - exands the component's width
* (default is l)
* ya specifies the y align of the component and can be:
* t - for top
* c - for center
* b - for bottom
* x - exands the component's height
* (default is c)
* xs and ys specify the number or columns/rows occupied by the component (default is 1)
* </PRE>
* <B>Notes:</B><BR>
* - if a component occupies more than 1 cell in x direction, its preferred width is NOT
* taken into account (same for y).<BR>
* - if a column / row is empty, its size is set to a default value. This value can be
* changed using setXNullgap/setYNullgap methods.
* <BR><BR>
* Have fun!!!
*
* @author Andrea Carboni
*/
public class FlexLayout implements LayoutManager
{
private int xgap;
private int ygap;
private int width;
private int height;
private int xnullgap = 24;
private int ynullgap = 24;
private FlexCell cells[][];
//--- flags for rows and columns ---
public static final int USEPREFERRED = 0; //--- default ---
public static final int EXPAND = 1;
private int xflags[];
private int yflags[];
private int xpref[];
private int ypref[];
//---------------------------------------------------------------------------
public FlexLayout(int width, int height)
{
this(width, height, 4, 4);
}
//---------------------------------------------------------------------------
public FlexLayout(int width, int height, int xgap, int ygap)
{
if (width <1 || height <1)
throw new IllegalArgumentException("width & height must be >0");
this.width = width;
this.height = height;
this.xgap = xgap;
this.ygap = ygap;
cells = new FlexCell[width][height];
xflags = new int[width];
yflags = new int[height];
xpref = new int[width];
ypref = new int[height];
}
//---------------------------------------------------------------------------
public void setColProp(int index, int flag)
{
xflags[index] = flag;
}
//---------------------------------------------------------------------------
public void setRowProp(int index, int flag)
{
yflags[index] = flag;
}
//---------------------------------------------------------------------------
public void setNullGaps(int xgap, int ygap)
{
xnullgap = xgap;
ynullgap = ygap;
}
//---------------------------------------------------------------------------
public void setXgap(int xgap) { this.xgap = xgap; }
public void setYgap(int ygap) { this.ygap = ygap; }
public int getXgap() { return xgap; }
public int getYgap() { return ygap; }
public int getXNullgap() { return xnullgap; }
public int getYNullgap() { return ynullgap; }
public int getWidth() { return width; }
public int getHeight() { return height; }
//---------------------------------------------------------------------------
//---
//--- LayoutManager Interface
//---
//---------------------------------------------------------------------------
public void addLayoutComponent(String name, Component comp)
{
StringTokenizer strTk = new StringTokenizer(name,",");
int x = 1;
int y = 1;
int xalign = FlexCell.LEFT;
int yalign = FlexCell.CENTERY;
int xext = 1;
int yext = 1;
if (strTk.hasMoreTokens()) x = Integer.parseInt(strTk.nextToken());
if (strTk.hasMoreTokens()) y = Integer.parseInt(strTk.nextToken());
if (strTk.hasMoreTokens())
{
String align = strTk.nextToken().toLowerCase();
if (align.equals("l")) xalign = FlexCell.LEFT;
if (align.equals("c")) xalign = FlexCell.CENTERX;
if (align.equals("r")) xalign = FlexCell.RIGHT;
if (align.equals("x")) xalign = FlexCell.EXPANDX;
}
if (strTk.hasMoreTokens())
{
String align = strTk.nextToken().toLowerCase();
if (align.equals("t")) yalign = FlexCell.TOP;
if (align.equals("c")) yalign = FlexCell.CENTERY;
if (align.equals("b")) yalign = FlexCell.BOTTOM;
if (align.equals("x")) yalign = FlexCell.EXPANDY;
}
if (strTk.hasMoreTokens()) xext = Integer.parseInt(strTk.nextToken());
if (strTk.hasMoreTokens()) yext = Integer.parseInt(strTk.nextToken());
cells[x][y] = new FlexCell(xalign, yalign, xext, yext, comp);
}
//---------------------------------------------------------------------------
public void removeLayoutComponent(Component comp)
{
for(int x=0; x<width; x++)
for(int y=0; y<height; y++)
if (cells[x][y].component == comp)
{
cells[x][y] = null;
return;
}
}
//---------------------------------------------------------------------------
public Dimension preferredLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
calcMaxWidthArray();
calcMaxHeightArray();
int maxW = 0;
int maxH = 0;
for(int x=0; x<width; x++) maxW += xpref[x];
for(int y=0; y<height; y++) maxH += ypref[y];
Insets insets = parent.getInsets();
return new Dimension(insets.left + insets.right + maxW + (width-1)*xgap,
insets.top + insets.bottom + maxH + (height-1)*ygap);
}
}
//---------------------------------------------------------------------------
public Dimension minimumLayoutSize(Container parent)
{
return preferredLayoutSize(parent);
}
//---------------------------------------------------------------------------
public void layoutContainer(Container parent)
{
synchronized (parent.getTreeLock())
{
FlexCell cell;
//--- maximun preferred Width & Height calculus for each column ---
calcMaxWidthArray();
calcMaxHeightArray();
//---------------------------------------------------------------------
//--- Layout Calculus
//---------------------------------------------------------------------
Insets i = parent.getInsets();
int maxWidth = parent.getSize().width - i.left - i.right - (width-1)*xgap;
int maxHeight = parent.getSize().height- i.top - i.bottom- (height-1)*ygap;
//--- total space occupied by fixed columns (and rows) ---
int fixedWidth = 0;
int fixedHeight = 0;
//--- number of variable columns (and rows) ---
int varXCount = 0;
int varYCount = 0;
//--- width of a variable column (or row) ---
int varWidth = 0;
int varHeight = 0;
//---------------------------------------------------------------------
//--- Handle Columns
//---------------------------------------------------------------------
//--- calc totale space occupied by fixed columns ---
for(int x=0; x<width; x++)
{
if (xflags[x] == USEPREFERRED) fixedWidth += xpref[x];
else varXCount++;
}
//--- calc width of a generic variable column ---
if (varXCount != 0)
{
varWidth = (maxWidth - fixedWidth) / varXCount;
if (varWidth < 0) varWidth = 0;
}
//--- calc width for expandable columns ---
for(int x=0; x<width; x++) if (xflags[x] == EXPAND) xpref[x] = varWidth;
//---------------------------------------------------------------------
//--- Handle Rows
//---------------------------------------------------------------------
//--- calc totale space occupied by fixed rows ---
for(int y=0; y<height; y++)
{
if (yflags[y] == USEPREFERRED) fixedHeight += ypref[y];
else varYCount++;
}
//--- calc height of a generic variable row ---
if (varYCount != 0)
{
varHeight = (maxHeight - fixedHeight) / varYCount;
if (varHeight < 0) varHeight = 0;
}
//--- calc width for expandable rows ---
for(int y=0; y<height; y++) if (yflags[y] == EXPAND) ypref[y] = varHeight;
//---------------------------------------------------------------------
//--- Do Layout
//---------------------------------------------------------------------
int currentX = i.left;
int currentY = i.top;
int compX, compY, compW, compH;
int cellW, cellH;
int xc, yc;
for(int y=0; y<height; y++)
{
currentX = i.left;
for(int x=0; x<width; x++)
{
//--- calculate current cell width and height ---
cellW = 0;
cellH = 0;
cell = cells[x][y];
if (cell != null)
{
for(xc = x; xc< x + cell.xext; xc++) cellW += xpref[xc];
for(yc = y; yc< y + cell.yext; yc++) cellH += ypref[yc];
Dimension compSize = cell.component.getPreferredSize();
//------------------------------------------------------------
//--- calculate compX & compW depending on align
//------------------------------------------------------------
switch(cell.xalign)
{
case FlexCell.LEFT:
compX = currentX;
compW = compSize.width;
break;
case FlexCell.CENTERX:
compX = currentX + (cellW - compSize.width)/2;
compW = compSize.width;
break;
case FlexCell.RIGHT:
compX = currentX + cellW - compSize.width;
compW = compSize.width;
break;
case FlexCell.EXPANDX:
compX = currentX;
compW = cellW;
break;
default: System.out.println("FlexLayout: invalid X align type");
compX = currentX;
compW = cellW;
break;
}
//------------------------------------------------------------
//--- calculate compY & compH depending on align
//------------------------------------------------------------
switch(cell.yalign)
{
case FlexCell.TOP:
compY = currentY;
compH = compSize.height;
break;
case FlexCell.CENTERY:
compY = currentY + (cellH - compSize.height)/2;
compH = compSize.height;
break;
case FlexCell.BOTTOM:
compY = currentY + cellH - compSize.height;
compH = compSize.height;
break;
case FlexCell.EXPANDY:
compY = currentY;
compH = cellH;
break;
default: System.out.println("FlexLayout: invalid Y align type");
compY = currentY;
compH = cellH;
break;
}
//--- resize component ---
cell.component.setBounds(compX, compY, compW + (cell.xext-1)*xgap,
compH + (cell.yext-1)*ygap);
}
currentX += xpref[x] + xgap;
}
currentY += ypref[y] + ygap;
}
}
}
//---------------------------------------------------------------------------
private void calcMaxWidthArray()
{
//--- maximun preferred Width calculus for each column ---
FlexCell cell;
int maxPrefW, curPrefW;
for(int x=0; x<width; x++)
{
maxPrefW = 0;
for(int y=0; y<height; y++)
{
cell = cells[x][y];
if (cell != null && cell.xext == 1)
{
curPrefW = cell.component.getPreferredSize().width;
if (curPrefW > maxPrefW) maxPrefW = curPrefW;
}
}
if (maxPrefW == 0) maxPrefW = xnullgap;
xpref[x] = maxPrefW;
}
}
//---------------------------------------------------------------------------
private void calcMaxHeightArray()
{
//--- maximun preferred Height calculus for each row ---
FlexCell cell;
int maxPrefH, curPrefH;
for(int y=0; y<height; y++)
{
maxPrefH = 0;
for(int x=0; x<width; x++)
{
cell = cells[x][y];
if (cell != null && cell.yext == 1)
{
curPrefH = cell.component.getPreferredSize().height;
if (curPrefH > maxPrefH) maxPrefH = curPrefH;
}
}
if (maxPrefH == 0) maxPrefH = ynullgap;
ypref[y] = maxPrefH;
}
}
}
class FlexCell
{
//--- X align constants ---
public static final int LEFT = 0;
public static final int CENTERX = 1;
public static final int RIGHT = 2;
public static final int EXPANDX = 3;
//--- Y align constants ---
public static final int TOP = 0;
public static final int CENTERY = 1;
public static final int BOTTOM = 2;
public static final int EXPANDY = 3;
public int xalign, xext;
public int yalign, yext;
public Component component;
//---------------------------------------------------------------------------
public FlexCell(int xalign, int yalign, int xext, int yext, Component c)
{
this.xalign = xalign;
this.yalign = yalign;
this.xext = xext;
this.yext = yext;
component = c;
}
}
Related examples in the same category