Mega Code Archive

 
Categories / Java / Swing JFC
 

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);    }              }