/*
* RiverLayout is released under the LGPL licence. You may therefore freely use it in
* both non-commerical and commercial applications and you don't need to open up your source code.
* Improvements made to RiverLayout should however be returned to the project for the benefit of everyone.
*
* Contact etc
* RiverLayout is written by me, David Ekholm, Datadosen david@jalbum.net
* If you like it or have some suggestions, please let me know.
* You can reach me by mail or phone (although email is preferred):
* Datadosen
* David Ekholm
* Mantalsvägen 33
* s-175 50 Järfälla
* Sweden
* Phone: +46 8 580 15668. Mobile: +46 70 486 77 38
*/
package op.tools;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
public class RiverLayout
extends FlowLayout
implements LayoutManager, java.io.Serializable {
public static final String LINE_BREAK = "br";
public static final String PARAGRAPH_BREAK = "p";
public static final String TAB_STOP = "tab";
public static final String HFILL = "hfill";
public static final String VFILL = "vfill";
public static final String LEFT = "left";
public static final String RIGHT = "right";
public static final String CENTER = "center";
public static final String VTOP = "vtop";
public static final String VCENTER = "vcenter";
Map constraints = new HashMap();
String valign = VCENTER;
int hgap;
int vgap;
Insets extraInsets;
Insets totalInsets = new Insets(0, 0, 0, 0);// Dummy values. Set by getInsets()
/**
* <p>RiverLayout makes it very simple to construct user gui.interfaces as components
* are laid out similar to how text is added to a word processor (Components flow
* like a "river". RiverLayout is however much more powerful than FlowLayout:
* Components added with the add() method generally gets laid out horizontally,
* but one may add a string before the component being added to specify "constraints"
* like this:
* add("br hfill", new JTextField("Your name here");
* The code above forces a "line break" and extends the added component horizontally.
* Without the "hfill" constraint, the component would take on its preferred size.
* </p>
* <p>
* List of constraints:<ul>
* <li>br - Add a line break
* <li>p - Add a paragraph break
* <li>tab - Add a tab stop (handy for constructing forms with labels followed by fields)
* <li>hfill - Extend component horizontally
* <li>vfill - Extent component vertically (currently only one allowed)
* <li>left - Align following components to the left (default)
* <li>center - Align following components horizontally centered
* <li>right - Align following components to the right
* <li>vtop - Align following components vertically top aligned
* <li>vcenter - Align following components vertically centered (default)
* </ul>
* </p>
* RiverLayout is LGPL licenced - use it freely in free and commercial programs
*
* @author David Ekholm
* @version 1.1 (2005-05-23) -Bugfix: JScrollPanes were oversized (sized to their containing component)
* if the container containing the JScrollPane was resized.
*/
public RiverLayout() {
this(10, 5);
}
public RiverLayout(int hgap, int vgap) {
this.hgap = hgap;
this.vgap = vgap;
setExtraInsets(new Insets(0, hgap, hgap, hgap));
}
/**
* Gets the horizontal gap between components.
*/
public int getHgap() {
return hgap;
}
/**
* Sets the horizontal gap between components.
*/
public void setHgap(int hgap) {
this.hgap = hgap;
}
/**
* Gets the vertical gap between components.
*/
public int getVgap() {
return vgap;
}
public Insets getExtraInsets() {
return extraInsets;
}
public void setExtraInsets(Insets newExtraInsets) {
extraInsets = newExtraInsets;
}
protected Insets getInsets(Container target) {
Insets insets = target.getInsets();
totalInsets.top = insets.top + extraInsets.top;
totalInsets.left = insets.left + extraInsets.left;
totalInsets.bottom = insets.bottom + extraInsets.bottom;
totalInsets.right = insets.right + extraInsets.right;
return totalInsets;
}
/**
* Sets the vertical gap between components.
*/
public void setVgap(int vgap) {
this.vgap = vgap;
}
/**
* @param name the name of the component
* @param comp the component to be added
*/
public void addLayoutComponent(String name, Component comp) {
constraints.put(comp, name);
}
/**
* Removes the specified component from the layout. Not used by
* this class.
*
* @param comp the component to remove
* @see java.awt.Container#removeAll
*/
public void removeLayoutComponent(Component comp) {
constraints.remove(comp);
}
boolean isFirstInRow(Component comp) {
String cons = (String) constraints.get(comp);
return cons != null && (cons.indexOf(RiverLayout.LINE_BREAK) != -1 ||
cons.indexOf(RiverLayout.PARAGRAPH_BREAK) != -1);
}
boolean hasHfill(Component comp) {
return hasConstraint(comp, RiverLayout.HFILL);
}
boolean hasVfill(Component comp) {
return hasConstraint(comp, RiverLayout.VFILL);
}
boolean hasConstraint(Component comp, String test) {
String cons = (String) constraints.get(comp);
if (cons == null) return false;
StringTokenizer tokens = new StringTokenizer(cons);
while (tokens.hasMoreTokens())
if (tokens.nextToken().equals(test)) return true;
return false;
}
/**
* Figure out tab stop x-positions
*/
protected Ruler calcTabs(Container target) {
Ruler ruler = new Ruler();
int nmembers = target.getComponentCount();
int x = 0;
int tabIndex = 0; // First tab stop
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
// if (m.isVisible()) {
if (isFirstInRow(m) || i == 0) {
x = 0;
tabIndex = 0;
} else x += hgap;
if (hasConstraint(m, TAB_STOP)) {
ruler.setTab(tabIndex, x); // Will only increase
x = ruler.getTab(tabIndex++); // Jump forward if neccesary
}
Dimension d = m.getPreferredSize();
x += d.width;
}
// }
return ruler;
}
/**
* Returns the preferred dimensions for this layout given the
* <i>visible</i> components in the specified target container.
*
* @param target the component which needs to be laid out
* @return the preferred dimensions to lay out the
* subcomponents of the specified container
* @see Container
* @see #minimumLayoutSize
* @see java.awt.Container#getPreferredSize
*/
public Dimension preferredLayoutSize(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
Dimension rowDim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
boolean firstVisibleComponent = true;
int tabIndex = 0;
Ruler ruler = calcTabs(target);
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
// if (m.isVisible()) {
if (isFirstInRow(m)) {
tabIndex = 0;
dim.width = Math.max(dim.width, rowDim.width);
dim.height += rowDim.height + vgap;
if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2 * vgap;
rowDim = new Dimension(0, 0);
}
if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++);
Dimension d = m.getPreferredSize();
rowDim.height = Math.max(rowDim.height, d.height);
if (firstVisibleComponent) {
firstVisibleComponent = false;
} else {
rowDim.width += hgap;
}
rowDim.width += d.width;
// }
}
dim.width = Math.max(dim.width, rowDim.width);
dim.height += rowDim.height;
Insets insets = getInsets(target);
dim.width += insets.left + insets.right;// + hgap * 2;
dim.height += insets.top + insets.bottom;// + vgap * 2;
return dim;
}
}
/**
* Returns the minimum dimensions needed to layout the <i>visible</i>
* components contained in the specified target container.
*
* @param target the component which needs to be laid out
* @return the minimum dimensions to lay out the
* subcomponents of the specified container
* @see #preferredLayoutSize
* @see java.awt.Container
* @see java.awt.Container#doLayout
*/
public Dimension minimumLayoutSize(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
Dimension rowDim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
boolean firstVisibleComponent = true;
int tabIndex = 0;
Ruler ruler = calcTabs(target);
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
// if (m.isVisible()) {
if (isFirstInRow(m)) {
tabIndex = 0;
dim.width = Math.max(dim.width, rowDim.width);
dim.height += rowDim.height + vgap;
if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2 * vgap;
rowDim = new Dimension(0, 0);
}
if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++);
Dimension d = m.getMinimumSize();
rowDim.height = Math.max(rowDim.height, d.height);
if (firstVisibleComponent) {
firstVisibleComponent = false;
} else {
rowDim.width += hgap;
}
rowDim.width += d.width;
// }
}
dim.width = Math.max(dim.width, rowDim.width);
dim.height += rowDim.height;
Insets insets = getInsets(target);
dim.width += insets.left + insets.right;// + hgap * 2;
dim.height += insets.top + insets.bottom;// + vgap * 2;
return dim;
}
}
/**
* Centers the elements in the specified row, if there is any slack.
*
* @param target the component which needs to be moved
* @param x the x coordinate
* @param y the y coordinate
* @param width the width dimensions
* @param height the height dimensions
* @param rowStart the beginning of the row
* @param rowEnd the the ending of the row
*/
protected void moveComponents(Container target, int x, int y, int width,
int height,
int rowStart, int rowEnd, boolean ltr, Ruler ruler) {
synchronized (target.getTreeLock()) {
switch (getAlignment()) {
case FlowLayout.LEFT:
x += ltr ? 0 : width;
break;
case FlowLayout.CENTER:
x += width / 2;
break;
case FlowLayout.RIGHT:
x += ltr ? width : 0;
break;
case LEADING:
break;
case TRAILING:
x += width;
break;
}
int tabIndex = 0;
for (int i = rowStart; i < rowEnd; i++) {
Component m = target.getComponent(i);
// if (m.isVisible()) {
if (hasConstraint(m, TAB_STOP)) x = getInsets(target).left + ruler.getTab(tabIndex++);
int dy = (valign == VTOP) ? 0 : (height - m.getHeight()) / 2;
if (ltr) {
m.setLocation(x, y + dy);
} else {
m.setLocation(target.getWidth() - x - m.getWidth(),
y + dy);
}
x += m.getWidth() + hgap;
// }
}
}
}
protected void relMove(Container target, int dx, int dy, int rowStart,
int rowEnd) {
synchronized (target.getTreeLock()) {
for (int i = rowStart; i < rowEnd; i++) {
Component m = target.getComponent(i);
// if (m.isVisible()) {
m.setLocation(m.getX() + dx, m.getY() + dy);
// }
}
}
}
protected void adjustAlignment(Component m) {
if (hasConstraint(m, RiverLayout.LEFT)) setAlignment(FlowLayout.LEFT);
else if (hasConstraint(m, RiverLayout.RIGHT)) setAlignment(FlowLayout.RIGHT);
else if (hasConstraint(m, RiverLayout.CENTER)) setAlignment(FlowLayout.CENTER);
if (hasConstraint(m, RiverLayout.VTOP)) valign = VTOP;
else if (hasConstraint(m, RiverLayout.VCENTER)) valign = VCENTER;
}
/**
* Lays out the container. This method lets each component take
* its preferred size by reshaping the components in the
* target container in order to satisfy the constraints of
* this <code>FlowLayout</code> object.
*
* @param target the specified component being laid out
* @see Container
* @see java.awt.Container#doLayout
*/
public void layoutContainer(Container target) {
setAlignment(FlowLayout.LEFT);
synchronized (target.getTreeLock()) {
Insets insets = getInsets(target);
int maxwidth = target.getWidth() -
(insets.left + insets.right);
int maxheight = target.getHeight() -
(insets.top + insets.bottom);
int nmembers = target.getComponentCount();
int x = 0, y = insets.top + vgap;
int rowh = 0, start = 0, moveDownStart = 0;
boolean ltr = target.getComponentOrientation().isLeftToRight();
Component toHfill = null;
Component toVfill = null;
Ruler ruler = calcTabs(target);
int tabIndex = 0;
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
//if (m.isVisible()) {
Dimension d = m.getPreferredSize();
m.setSize(d.width, d.height);
if (isFirstInRow(m)) tabIndex = 0;
if (hasConstraint(m, TAB_STOP)) x = ruler.getTab(tabIndex++);
if (!isFirstInRow(m)) {
if (i > 0 && !hasConstraint(m, TAB_STOP)) {
x += hgap;
}
x += d.width;
rowh = Math.max(rowh, d.height);
} else {
if (toVfill != null && moveDownStart == 0) {
moveDownStart = i;
}
if (toHfill != null) {
toHfill.setSize(toHfill.getWidth() + maxwidth - x,
toHfill.getHeight());
x = maxwidth;
}
moveComponents(target, insets.left, y,
maxwidth - x,
rowh, start, i, ltr, ruler);
x = d.width;
y += vgap + rowh;
if (hasConstraint(m, PARAGRAPH_BREAK)) y += 2 * vgap;
rowh = d.height;
start = i;
toHfill = null;
}
//}
if (hasHfill(m)) {
toHfill = m;
}
if (hasVfill(m)) {
toVfill = m;
}
adjustAlignment(m);
}
if (toVfill != null && moveDownStart == 0) { // Don't move anything if hfill component is last component
moveDownStart = nmembers;
}
if (toHfill != null) { // last component
toHfill.setSize(toHfill.getWidth() + maxwidth - x,
toHfill.getHeight());
x = maxwidth;
}
moveComponents(target, insets.left, y, maxwidth - x, rowh,
start, nmembers, ltr, ruler);
int yslack = maxheight - (y + rowh);
if (yslack != 0 && toVfill != null) {
toVfill.setSize(toVfill.getWidth(), yslack + toVfill.getHeight());
relMove(target, 0, yslack, moveDownStart, nmembers);
}
}
}
}
class Ruler {
private Vector tabs = new Vector();
public void setTab(int num, int xpos) {
if (num >= tabs.size()) tabs.add(num, new Integer(xpos));
else {
// Transpose all tabs from this tab stop and onwards
int delta = xpos - getTab(num);
if (delta > 0) {
for (int i = num; i < tabs.size(); i++) {
tabs.set(i, new Integer(getTab(i) + delta));
}
}
}
}
public int getTab(int num) {
return ((Integer) tabs.get(num)).intValue();
}
public String toString() {
StringBuffer ret = new StringBuffer(getClass().getName() + " {");
for (int i = 0; i < tabs.size(); i++) {
ret.append(tabs.get(i));
if (i < tabs.size() - 1) ret.append(',');
}
ret.append('}');
return ret.toString();
}
}