/*
HtmlLayout - A superior Java LayoutManager
Copyright (C) 1998 Paul Buchheit
HtmlLayout is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
HtmlLayout is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
HtmlLayout was created by Paul Buchheit, paul@braindamage.org
HtmlLayout lives at http://braindamage.org/HtmlLayout
There you can find the latest information and software.
*/
package htmllayout;
import java.util.Hashtable;
import java.awt.*;
/**
* HtmlLayout uses a string containing HTML like table tags to
* layout components.
*
Ex:
*
* <table rows=2 cols=2>
* <tr>
* <td> A label
* <td component=someName>
* <tr>
* <td component=anotherName vert=max>
* <td component="some other name" horz=max>
*
* Tags and their options
* Table
*
* - Contains TR
*
* - horz= LEFT | RIGHT | CENTER | FIT | (MAX)
*
Specifies how the table fills the available width
*
* - vert= TOP | BOTTOM | CENTER | FIT | (MAX)
*
Specifies how the table fills the available height
*
* - rows=y ; y = rowCount (req)
*
- cols=y ; y = colCount (req)
*
- hgap=y ; y = horizontal gap between components
*
- vgap=y ; y = vertical gap between components
*
- hpad=y ; y = horizontal padding (components made wider)
*
- vpad=y ; y = vertical padding (components made taller)
*
*
* TR
*
* - Contains TD
*
* - vgap=y ; y = gap between this row and the row above
*
*
* TD
*
* - Contains text (creates a label) or TABLE but only if no component
* is specified. Nested tables inherit the gaps and pads of their
* enclosing table.
*
* - colspan=y
*
- rowspan=y
*
- horz= (LEFT) | RIGHT | CENTER | FIT | MAX
*
Specifies how the component fills the available cell width
*
* - vert= TOP | BOTTOM | (CENTER) | FIT | MAX
*
Specifies how the component fills the available cell height
*
* - hgap=y ; y = horizontal gap between components
*
- vgap=y ; y = vertical gap between components
*
- hpad=y ; y = horizontal padding
*
- vpad=y ; y = vertical padding (components made larger)
*
- component="componentName" ; this is the name that must be used
* when add is called, as in add(comp, "componentName")
*
*
* @author Paul Buchheit
*/
public class HtmlLayout implements LayoutManager
{
static final String anonLabelName = new String("Anonymous label");
boolean labelsAdded;
// x in xLayoutSize()
static final int MIN = 0, PREF = 1;
// alignment
static final int
LEFT = 0, RIGHT = 1,
CENTER = 2, FIT=3, MAX = 4,
TOP = 5, BOTTOM = 6;
static final String ALIGNNAMES[] = {
"LEFT", "RIGHT",
"CENTER", "FIT", "MAX",
"TOP", "BOTTOM"};
/* these tables are shared by the layout and all nested layouts */
Hashtable nameToCell;
Hashtable compToCell;
/* this belongs only to this layout */
Cell cells[], cellsColFirst[];
int rows, cols;
int horzAlign = MAX, vertAlign = MAX;
Dimension prefDim = new Dimension(-1, -1);
/** Creates an HtmlLayout with the specified "HTML" string.
@param html
The "HTML" which specifies the layout, if this is not valid
table-html a BadTableHtmlException will be thrown. Html can
be tested ahead of time using HtmlLayoutTest.
@see htmllayout.BadTableHtmlException
@see htmllayout.HtmlLayoutTest
*/
public HtmlLayout(String html) {
nameToCell = new Hashtable(30);
compToCell = new Hashtable(30);
parse(new Scanner(html), true, null);
addCellsToTable(nameToCell);
}
HtmlLayout(TableParser parent) {
parse(parent.in, false, parent);
}
/** If this layout includes any automatic lables (text within a TD)
* this will add them to container parent.
* It is not always necessary to call this, if you don't it will
* automatically be called when the container is first sized or
* layed out. Ordinarily this is ok but not always, such as when
* using CardLayout. Calling addLabels manually at the same time
* as the other components are added will never be a problem.
*/
public void addLabels(Container parent) {
for(int i = 0; i < cells.length; i++)
cells[i].addLabels(parent);
labelsAdded = true;
}
/**
* @see java.awt.LayoutManager#addLayoutComponent
*/
public void addLayoutComponent(String name, Component comp) {
prefDim.width = -1;
if(name == anonLabelName)
return;
if(name == null)
throw new IllegalArgumentException("null component name");
Cell c = (Cell)nameToCell.get(name);
if(c == null)
throw new IllegalArgumentException(
"Cannot add to layout: unknown component name " + name);
compToCell.put(comp, c);
c.comp = comp;
}
/**
* @see java.awt.LayoutManager#removeLayoutComponent
*/
public void removeLayoutComponent(Component comp) {
prefDim.width = -1;
Cell c = (Cell)compToCell.remove(comp);
if(c != null)
c.comp = null;
}
/**
* @see java.awt.LayoutManager#preferredLayoutSize
*/
public Dimension preferredLayoutSize(Container parent) {
return layoutSize(parent, PREF);
}
/**
* @see java.awt.LayoutManager#minimumLayoutSize
*/
public Dimension minimumLayoutSize(Container parent) {
return layoutSize(parent, MIN);
}
void parse(Scanner s, boolean eatTable, TableParser parent) {
TableParser tp = new TableParser(s, eatTable, parent);
rows = tp.rows;
cols = tp.cols;
horzAlign = tp.horz;
vertAlign = tp.vert;
cells = new Cell[tp.cellCount];
int cpos = 0;
cellsColFirst = new Cell[tp.cellCount];
int ccfpos = 0;
for(int i = 0; i < rows * cols; i++) {
if(tp.cells[i / cols][i % cols] != null) {
cells[cpos] = tp.cells[i / cols][i % cols];
cpos++;
}
if(tp.cells[i % rows][i / rows] != null) {
cellsColFirst[ccfpos] = tp.cells[i % rows][i / rows];
ccfpos++;
}
}
}
Dimension layoutSize(Container parent, int whichSize) {
if(!labelsAdded)
addLabels(parent);
return layoutSize(parent.getInsets(), whichSize);
}
Dimension layoutSize(Insets insets, int whichSize) {
int ypos[] = new int[rows + 1];
int xpos[] = new int[cols + 1];
for(int i = 0; i < cells.length; i++) {
cells[i].updateSize(whichSize);
}
for(int i = 0; i < cells.length; i++) {
cells[i].addToYTable(ypos);
cellsColFirst[i].addToXTable(xpos);
}
Dimension d = new Dimension(xpos[cols], ypos[rows]);
prefDim.width = d.width;
prefDim.height = d.height;
d.width += insets.left + insets.right;
d.height += insets.top + insets.bottom;
return d;
}
/**
* @see java.awt.LayoutManager#layoutContainer
*/
public void layoutContainer(Container parent) {
if(!labelsAdded)
addLabels(parent);
Insets insets = parent.getInsets();
Dimension d = parent.getSize();
int top = insets.top;
int bottom = d.height - insets.bottom;
int left = insets.left;
int right = d.width - insets.right;
preferredLayoutSize(parent); // will set prefDim
if(right - left < prefDim.width || bottom - top < prefDim.height) {
minimumLayoutSize(parent); // will set prefDim to minSize
}
layout(top, bottom, left, right);
}
void layout(int top, int bottom, int left, int right) {
if(right - left > prefDim.width) {
if(horzAlign != MAX && horzAlign != FIT) {
left = calcTopOrLeft(left, right, prefDim.width, horzAlign);
right = left + prefDim.width;
}
}
if(bottom - top > prefDim.height) {
if(vertAlign != MAX && vertAlign != FIT) {
top = calcTopOrLeft(top, bottom, prefDim.height, vertAlign);
bottom = top + prefDim.height;
}
}
doLayout(top, bottom, left, right);
}
private void doLayout(int top, int bottom, int left, int right) {
int ypos[] = new int[rows + 1];
int xpos[] = new int[cols + 1];
layoutDim(xpos, left, right, true);
layoutDim(ypos, top, bottom, false);
for(int i = 0; i < cells.length; i++)
cells[i].finalLayout(xpos, ypos);
}
private void layoutDim(int pos[], int start, int end, boolean isX) {
pos[0] = start;
boolean want[] = new boolean[pos.length - 1];
for(int i = 0; i < cells.length; i++) {
if(isX) {
cellsColFirst[i].firstXLayout(pos, want);
} else {
cells[i].firstYLayout(pos, want);
}
}
int diff = end - pos[want.length];
if(diff < 0) {
squeeze(-diff, pos, isX);
} else if(diff > 0) {
grow(diff, pos, want);
}
pos[pos.length - 1] = end;
}
// squeeze the components to make this 'pixels' pixels smaller
private void squeeze(int pixels, int vals[], boolean isX) {
int touch[][] = new int[vals.length][isX ? rows : cols];
int limit[] = new int[vals.length];
int downto = vals[vals.length - 1] - pixels;
// less than 2 is dumb and I think bad things could happen
if(downto < 2)
return;
while(vals[vals.length - 1] > downto) {
int count[] = new int[vals.length];
for(int i = 0; i < limit.length; i++)
limit[i] = Integer.MAX_VALUE;
for(int i = 0; i < cells.length; i++) {
if(isX) {
cellsColFirst[i].squeezeX(vals, touch, count, limit);
} else {
cells[i].squeezeY(vals, touch, count, limit);
}
}
boolean scale[] = new boolean[vals.length];
int max = maxSqueeze(
vals.length - 1, touch, count, limit, vals, scale);
int amount = Math.min(pixels, max);
int width = vals[vals.length - 1] - vals[0];
int slide = 0;
for(int i = 0; i < vals.length; i++) {
if(scale[i]) {
slide = amount * (vals[i] - vals[0]) / width;
}
vals[i] -= slide;
}
pixels -= slide;
}
}
private int maxSqueeze(int rule, int touch[][], int count[], int limit[],
int vals[], boolean scale[]) {
// this rule will be scaled, not slidden
scale[rule] = true;
// how much can we go before more parts touch our left side?
int max = limit[rule];
for(int i = 0; i < count[rule]; i++) {
int othermax = maxSqueeze(
touch[rule][i], touch, count, limit, vals, scale);
if(othermax < max)
max = othermax;
}
return max;
}
private void grow(int pixels, int vals[], boolean where[]) {
int count = 0;
for(int i = 0; i < where.length; i++) {
if(where[i])
count++;
}
boolean all = count == 0;
if(all)
count = where.length;
double dif = (pixels - 0.001) / count;
double a = 0.0;
for(int i = 0; i < where.length; i++) {
if(all || where[i])
a += dif;
vals[i + 1] += (int)a;
}
}
static int calcTopOrLeft(int tol, int bor, int wid, int align)
{
int newTol;
switch(align) {
case LEFT:
case TOP:
newTol = tol;
break;
case RIGHT:
case BOTTOM:
newTol = bor - wid;
break;
case CENTER:
newTol = tol + (bor - tol - wid) / 2;
break;
default:
throw new Error("illegal align value");
}
return newTol;
}
void addCellsToTable(Hashtable nToCell) {
for(int i = 0; i < cells.length; i++) {
cells[i].addToNameTable(nToCell);
}
}
private String descString() {
return
" dim = " +
cols + ", " + rows +
" fill = " +
horzAlign + ", " + vertAlign;
}
void dump(int space) {
for(int i = 0; i < space; i++) System.err.print(' ');
System.err.println("HtmlLayout" + descString());
for(int i = 0; i < cells.length; i++)
cells[i].dump(space + 2);
}
public String toString() {
String s = "[HtmlLayout" + descString() + " cells =";
for(int i = 0; i < cells.length; i++)
s += " " + cells[i];
return s + "]";
}
}
Ads help maintain this website.