Table Layout
/**
* The utillib library.
* More information is available at http://www.jinchess.com/.
* Copyright (C) 2002, 2003 Alexander Maryanovsky.
* All rights reserved.
*
* The utillib library is free software; you can redistribute
* it and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* The utillib library 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 Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with utillib library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Vector;
/**
* A LayoutManager which lays out the components in a table-like structure.
* Unlike <code>GridLayout</code>, the sizes of the rows and columns
* are dynamic, although properly aligned. The cell sizes are determined
* according to the preferred sizes of the components and each component is
* sized to either its maximum size or the cell size. Components are positioned
* within their cells according to their X and Y alignments.
* When a new component is added, it is placed in the first empty cell, in
* lexigraphic order. A new row is created if necessary.
* To create an empty cell, simply add blank component.
*/
public class TableLayout implements LayoutManager2{
/**
* The amount of columns in the table.
*/
private final int columnCount;
/**
* The gap between columns, in pixels.
*/
private final int xGap;
/**
* The gap between rows, in pixels.
*/
private final int yGap;
/**
* A Vector of rows where each row is a Component array holding the components
* in that row.
*/
private final Vector rows = new Vector();
/**
* Creates a new TableLayout with the specified amount of columns,
* horizontal/vertical gaps between columns/cells.
*/
public TableLayout(int columnCount, int xGap, int yGap){
if (columnCount <= 0)
throw new IllegalArgumentException("The amount of columns must be positive");
if (xGap < 0)
throw new IllegalArgumentException("The horizontal gap may not be negative: "+xGap);
if (yGap < 0)
throw new IllegalArgumentException("The vertical gap may not be negative: "+yGap);
this.columnCount = columnCount;
this.xGap = xGap;
this.yGap = yGap;
}
/**
* Creates a new TableLayout with the specified amount of columns.
*/
public TableLayout(int columnCount){
this(columnCount, 0, 0);
}
/**
* Adds the specified component to the layout.
*/
public void addLayoutComponent(Component component, Object constraints){
synchronized(component.getTreeLock()){
int rowCount = rows.size();
for (int i = 0; i < rowCount; i++){
Component [] row = (Component [])rows.elementAt(i);
for (int j = 0; j < row.length; j++){
if (row[j] == null){
row[j] = component;
return;
}
}
}
Component [] newRow = new Component[columnCount];
newRow[0] = component;
rows.addElement(newRow);
}
}
/**
* Throws an exception.
*/
public void addLayoutComponent(String name, Component component){
throw new UnsupportedOperationException("deprecated addLayoutComponent(String, Component)");
}
/**
* Removes the specified component from the layout.
*/
public void removeLayoutComponent(Component component){
synchronized(component.getTreeLock()){
int rowCount = rows.size();
outer: for (int i = 0; i < rowCount; i++){
Component [] row = (Component [])rows.elementAt(i);
for (int j = 0; j < row.length; j++){
if (row[j] == component){
row[j] = null;
break outer;
}
}
}
// Remove any empty rows at the end.
for (int i = rowCount - 1; i >= 0; i--){
Component [] row = (Component [])rows.elementAt(i);
boolean isEmpty = true;
for (int j = 0; j < row.length; j++){
if (row[j] != null){
isEmpty = false;
break;
}
}
if (isEmpty)
rows.removeElementAt(i);
else
break;
}
}
}
/**
* Returns a matrix of Dimension objects specifying the preferred sizes of the
* components we are going to layout.
*/
private Dimension [][] getPreferredSizes(Container parent){
int rowCount = rows.size();
Dimension [][] prefSizes = new Dimension[rowCount][columnCount];
for (int i = 0; i < rowCount; i++){
Component [] row = (Component [])rows.elementAt(i);
for (int j = 0; j < columnCount; j++){
Component component = row[j];
// Can only happen on the last line when all the remaining components are null as well
if (component == null)
break;
if (component.getParent() != parent)
throw new IllegalStateException("Bad parent specified");
prefSizes[i][j] = component.getPreferredSize();
}
}
return prefSizes;
}
/**
* Calculates and returns a Pair where the first object is an array holding
* the column widths of our layout and the second is the rowHeights.
*/
private Pair calculateLayout(Dimension [][] prefSizes){
int rowCount = rows.size();
int [] columnWidths = new int[columnCount];
int [] rowHeights = new int[rowCount];
// Find the maximum preferred row heights and column widths.
for (int i = 0; i < rowCount; i++){
for (int j = 0; j < columnCount; j++){
Dimension prefSize = prefSizes[i][j];
// Can only happen on the last line when all the remaining components are null as well
if (prefSize == null)
break;
columnWidths[j] = Math.max(columnWidths[j], prefSize.width);
rowHeights[i] = Math.max(rowHeights[i], prefSize.height);
}
}
return new Pair(columnWidths, rowHeights);
}
/**
* Lays out the specified container. Throws an
* <code>IllegalStateException</code> if any of the components added via the
* <code>addLayoutComponent</code> method have a different parent than the
* specified Container.
*/
public void layoutContainer(Container parent){
synchronized(parent.getTreeLock()){
int rowCount = rows.size();
Insets parentInsets = parent.getInsets();
// Collect the preferred sizes.
Dimension [][] prefSizes = getPreferredSizes(parent);
Pair layout = calculateLayout(prefSizes);
int [] columnWidths = (int [])layout.getFirst();
int [] rowHeights = (int [])layout.getSecond();
Dimension prefParentSize = calculatePreferredLayoutSize(parent, columnWidths, rowHeights);
Dimension parentSize = parent.getSize();
Dimension layoutSize =
new Dimension(parentSize.width - xGap*(rowCount - 1) - parentInsets.left - parentInsets.right,
parentSize.height - yGap*(columnCount - 1) - parentInsets.top - parentInsets.bottom);
Dimension prefLayoutSize =
new Dimension(prefParentSize.width - xGap*(rowCount - 1) - parentInsets.left - parentInsets.right,
prefParentSize.height - yGap*(columnCount - 1) - parentInsets.top - parentInsets.bottom);
// Layout the components.
int y = parentInsets.top;
for (int i = 0; i < rowCount; i++){
int x = parentInsets.left;
int cellHeight = (rowHeights[i]*layoutSize.height)/prefLayoutSize.height;
Component [] row = (Component [])rows.elementAt(i);
for (int j = 0; j < row.length; j++){
int cellWidth = (columnWidths[j]*layoutSize.width)/prefLayoutSize.width;
Component component = row[j];
// Can only happen on the last line when all the remaining components are null as well
if (component == null)
break;
Dimension maxSize = component.getMaximumSize();
int compWidth = Math.min(maxSize.width, cellWidth);
int compHeight = Math.min(maxSize.height, cellHeight);
int compX = x + (int)((cellWidth - compWidth)*component.getAlignmentX());
int compY = y + (int)((cellHeight - compHeight)*component.getAlignmentY());
component.setBounds(compX, compY, compWidth, compHeight);
x += cellWidth + xGap;
}
y += cellHeight + yGap;
}
}
}
/**
* We're not caching anything yet, so this call is ignored.
*/
public void invalidateLayout(Container parent){
}
/**
* Returns the preferred layout for the specified parent container.
*/
public Dimension preferredLayoutSize(Container parent){
synchronized(parent.getTreeLock()){
Dimension [][] prefSizes = getPreferredSizes(parent);
Pair layout = calculateLayout(prefSizes);
int [] columnWidths = (int [])layout.getFirst();
int [] rowHeights = (int [])layout.getSecond();
return calculatePreferredLayoutSize(parent, columnWidths, rowHeights);
}
}
/**
* Calculates the preferred layout size for the specified preferred column
* widths and row heights.
*/
private Dimension calculatePreferredLayoutSize(Container parent, int [] columnWidths, int [] rowHeights){
int prefWidth = 0;
int prefHeight = 0;
for (int i = 0; i < columnWidths.length; i++)
prefWidth += columnWidths[i];
for (int i = 0; i < rowHeights.length; i++)
prefHeight += rowHeights[i];
// Add the gaps
prefWidth += xGap*(columnWidths.length - 1);
prefHeight += yGap*(rowHeights.length - 1);
// Add parent insets
Insets parentInsets = parent.getInsets();
prefWidth += parentInsets.left + parentInsets.right;
prefHeight += parentInsets.top + parentInsets.bottom;
return new Dimension(prefWidth, prefHeight);
}
/**
* Returns the same as <code>preferredLayoutSize</code>.
*/
public Dimension minimumLayoutSize(Container parent){
return preferredLayoutSize(parent);
}
/**
* Returns a dimension with maximum possible values.
*/
public Dimension maximumLayoutSize(Container parent){
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
/**
* Returns <code>CENTER_ALIGNMENT</code>;
*/
public float getLayoutAlignmentX(Container parent) {
return Component.CENTER_ALIGNMENT;
}
/**
* Returns <code>CENTER_ALIGNMENT</code>;
*/
public float getLayoutAlignmentY(Container parent) {
return Component.CENTER_ALIGNMENT;
}
}
/**
* The utillib library.
* More information is available at http://www.jinchess.com/.
* Copyright (C) 2002 Alexander Maryanovsky.
* All rights reserved.
*
* The utillib library is free software; you can redistribute
* it and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* The utillib library 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 Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with utillib library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* A wrapper for any two other given objects.
*/
final class Pair{
/**
* The first object.
*/
private final Object first;
/**
* The second object.
*/
private final Object second;
/**
* Creates a new <code>Pair</code> with the two given objects. Either of the
* objects may be <code>null</code>.
*/
public Pair(Object first, Object second){
this.first = first;
this.second = second;
}
/**
* Returns the first object.
*/
public Object getFirst(){
return first;
}
/**
* Returns the second object.
*/
public Object getSecond(){
return second;
}
/**
* Returns a hashcode combined from the hashcodes of the two target objects.
*/
public int hashCode(){
int hash1 = (first == null ? 0 : first.hashCode());
int hash2 = (second == null ? 0 : second.hashCode());
return hash1^hash2;
}
/**
* Returns true iff the given Object is a Pair, and its two objects are the
* same as this one's (comparison done via <code>Utilities.areEqual</code>)
*/
public boolean equals(Object o){
if (o == this)
return true;
if (!(o instanceof Pair))
return false;
Pair pair = (Pair)o;
return Utilities.areEqual(pair.first, first) && Utilities.areEqual(pair.second, second);
}
}
/**
* The utillib library.
* More information is available at http://www.jinchess.com/.
* Copyright (C) 2002, 2003 Alexander Maryanovsky.
* All rights reserved.
*
* The utillib library is free software; you can redistribute
* it and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* The utillib library 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 Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with utillib library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
*/
/**
* A collection of general utility methods.
*/
class Utilities{
/**
* A 0-length Object array.
*/
public static final Object [] EMPTY_ARRAY = new Object[0];
/**
* A 0-length long array.
*/
public static final long [] EMPTY_LONG_ARRAY = new long[0];
/**
* A 0-length int array.
*/
public static final int [] EMPTY_INT_ARRAY = new int[0];
/**
* A 0-length short array.
*/
public static final short [] EMPTY_SHORT_ARRAY = new short[0];
/**
* A 0-length byte array.
*/
public static final byte [] EMPTY_BYTE_ARRAY = new byte[0];
/**
* A 0-length char array.
*/
public static final char [] EMPTY_CHAR_ARRAY = new char[0];
/**
* A 0-length double array.
*/
public static final double [] EMPTY_DOUBLE_ARRAY = new double[0];
/**
* A 0-length float array.
*/
public static final float [] EMPTY_FLOAT_ARRAY = new float[0];
/**
* A 0-length String array.
*/
public static final String [] EMPTY_STRING_ARRAY = new String[0];
/**
* An empty enumeration.
*/
public static final Enumeration EMPTY_ENUM = new Enumeration(){
public boolean hasMoreElements(){return false;}
public Object nextElement(){throw new NoSuchElementException();}
};
/**
* Returns <code>true</code> if the two specified objects are the same.
* Returns <code>false</code> otherwise. To be considered the same, the two
* references must either both be null or invoking <code>equals</code> on one
* of them with the other must return <code>true</code>.
*/
public static boolean areEqual(Object obj1, Object obj2){
return (obj1 == obj2) || (obj1 == null ? false : obj1.equals(obj2));
}
/**
* Maps the specified key to the specified value in the specified
* <code>Hashtable</code>. If the specified value is <code>null</code> any
* existing mapping of the specified key is removed from the
* <code>Hashtable</code>. The old value mapped to the specified key
* is returned, or <code>null</code> if no value was mapped to the key.
*/
public static Object put(Hashtable table, Object key, Object value){
return value == null ? table.remove(key) : table.put(key, value);
}
/**
* Returns <code>true</code> if the specified object is an element of the
* specified array. The specified array may not be <code>null</code>. The
* specified object may be <code>null</code>, in which case this method will
* return <code>true</code> iff one of the indices in the array is empty
* (contains <code>null</code>).
*/
public static boolean contains(Object [] array, Object item){
return (indexOf(array, item) != -1);
}
/**
* Returns the index of the first occurrance of specified object in the
* specified array, or -1 if the specified object is not an element of the
* specified array. The specified object may be <code>null</code> in which
* case the returned index will be the index of the first <code>null</code>
* in the array.
*/
public static int indexOf(Object [] array, Object item){
if (array == null)
throw new IllegalArgumentException("The specified array may not be null");
for (int i = 0; i < array.length; i++)
if (areEqual(item, array[i]))
return i;
return -1;
}
/**
* Returns the index of the first occurrance of specified integer in the
* specified array, or -1 if the specified integer is not an element of the
* specified array.
*/
public static int indexOf(int [] arr, int val){
if (arr == null)
throw new IllegalArgumentException("The specified array may not be null");
for (int i = 0; i < arr.length; i++)
if (arr[i] == val)
return i;
return -1;
}
/**
* Converts the specified array into a string by appending all its elements
* separated by a semicolon.
*/
public static String arrayToString(Object [] arr){
StringBuffer buf = new StringBuffer();
for (int i = 0; i < arr.length; i++){
buf.append(arr[i]);
buf.append("; ");
}
if (arr.length > 0)
buf.setLength(buf.length() - 2); // get rid of the extra "; "
return buf.toString();
}
/**
* Converts the specified <code>Hashtable</code> into a string by putting
* each key and value on a separate line (separated by '\n') and an arrow
* (" -> ") between them.
*/
public static String hashtableToString(Hashtable hashtable){
StringBuffer buf = new StringBuffer();
Enumeration keys = hashtable.keys();
while (keys.hasMoreElements()){
Object key = keys.nextElement();
Object value = hashtable.get(key);
buf.append(key.toString());
buf.append(" -> ");
buf.append(value.toString());
buf.append("\n");
}
return buf.toString();
}
/**
* Returns the maximum element in the specified integer array.
*/
public static int max(int [] arr){
if (arr == null)
throw new IllegalArgumentException("The specified array may not be null");
if (arr.length == 0)
throw new IllegalArgumentException("The specified array must have at least one element");
int n = arr[0];
for (int i = 1; i < arr.length; i++)
if (arr[i] > n)
n = arr[i];
return n;
}
/**
* Returns a hash code for the specified double value.
*/
public static int hashCode(double val){
return hashCode(Double.doubleToLongBits(val));
}
/**
* Returns a hash code for the specified long value.
*/
public static int hashCode(long val){
return (int)(val ^ (val >>> 32));
}
/**
* Returns the name of the package of the specified class.
*/
public static String getPackageName(Class c){
return getPackageName(c.getName());
}
/**
* Returns the name of the package of the class with the specified (full) name.
*/
public static String getPackageName(String className){
int lastDotIndex = className.lastIndexOf(".");
return lastDotIndex == -1 ? "" : className.substring(0, lastDotIndex);
}
/**
* Returns the short name (excluding the package name) of the specified class.
*/
public static String getClassName(Class c){
return getClassName(c.getName());
}
/**
* Returns the short name (excluding the package name) of the class with the
* specified fully qualified name.
*/
public static String getClassName(String className){
int lastDotIndex = className.lastIndexOf(".");
return lastDotIndex == -1 ? className : className.substring(lastDotIndex + 1);
}
}
Related examples in the same category