/* * Concept profile generation tool suite * Copyright (C) 2015 Biosemantics Group, Erasmus University Medical Center, * Rotterdam, The Netherlands * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> */ package org.erasmusmc.tablelayout; import java.awt.*; import java.util.*; /** * TableLayout is a layout manager that arranges components in rows and columns * like a spreadsheet. TableLayout allows each row or column to be a different * size. A row or column can be given an absolute size in pixels, a percentage * of the available space, or it can grow and shrink to fill the remaining space * after other rows and columns have been resized. * * <p>Using spreadsheet terminology, a cell is the intersection of a row and * column. Cells have finite, non-negative sizes measured in pixels. The * dimensions of a cell depend solely upon the dimensions of its row and column. * </p> * * <p>A component occupies a rectangular group of one or more cells. If the * component occupies more than one cell, the component is resized to fit * perfectly in the rectangular region of cells. If the component occupies a * single cell, it can be aligned in four ways within that cell.</p> * * <p>A single cell component can be stretched horizontally to fit the cell * (full justification), or it can be placed in the center of the cell. The * component could also be left justified or right justified. Similarly, the * component can be full, center, top, or bottom justified in the vertical.</p> * * <pre> * public static void main (String args[]) * { * // Create a frame * Frame frame = new Frame("Example of TableLayout"); * frame.setBounds (100, 100, 300, 300); * <spc> * // Create a TableLayout for the frame * double border = 10; * double size[][] = * {{border, 0.10, 20, TableLayout.FILL, 20, 0.20, border}, // Columns * {border, 0.20, 20, TableLayout.FILL, 20, 0.20, border}}; // Rows * <spc> * frame.setLayout (new TableLayout(size)); * <spc> * // Create some buttons * String label[] = {"Top", "Bottom", "Left", "Right", "Center", "Overlap"}; * Button button[] = new Button[label.length]; * <spc> * for (int i = 0; i < label.length; i++) * button[i] = new Button(label[i]); * <spc> * // Add buttons * frame.add (button[0], "1, 1, 5, 1"); // Top * frame.add (button[1], "1, 5, 5, 5"); // Bottom * frame.add (button[2], "1, 3 "); // Left * frame.add (button[3], "5, 3 "); // Right * frame.add (button[4], "3, 3, c, c"); // Center * frame.add (button[5], "3, 3, 3, 5"); // Overlap * <spc> * // Allow user to close the window to terminate the program * frame.addWindowListener * (new WindowListener() * { * public void windowClosing (WindowEvent e) * { * System.exit (0); * } * <spc> * public void windowOpened (WindowEvent e) {} * public void windowClosed (WindowEvent e) {} * public void windowIconified (WindowEvent e) {} * public void windowDeiconified (WindowEvent e) {} * public void windowActivated (WindowEvent e) {} * public void windowDeactivated (WindowEvent e) {} * } * ); * <spc> * // Show frame * frame.show(); * } * </pre> * * @author Daniel E. Barbalace */ public class TableLayout implements java.awt.LayoutManager2, java.io.Serializable, org.erasmusmc.tablelayout.TableLayoutConstants { private static final long serialVersionUID = -5686070153033444350L; /** Default row/column size */ protected static final double defaultSize[][] = {{}, {}}; /** Widths of columns expressed in absolute and relative terms */ protected double columnSpec[]; /** Heights of rows expressed in absolute and relative terms */ protected double rowSpec[]; /** Widths of columns in pixels */ protected int columnSize[]; /** Heights of rows in pixels */ protected int rowSize[]; /** Offsets of columns in pixels. The left boarder of column n is at columnOffset[n] and the right boarder is at columnOffset[n + 1] for all columns including the last one. columnOffset.length = columnSize.length + 1 */ protected int columnOffset[]; /** Offsets of rows in pixels. The left boarder of row n is at rowOffset[n] and the right boarder is at rowOffset[n + 1] for all rows including the last one. rowOffset.length = rowSize.length + 1 */ protected int rowOffset[]; /** List of components and their sizes */ protected LinkedList list; /** Indicates whether or not the size of the cells are known for the last known size of the container. If dirty is true or the container has been resized, the cell sizes must be recalculated using calculateSize. */ protected boolean dirty; /** Previous known width of the container */ protected int oldWidth; /** Previous known height of the container */ protected int oldHeight; //****************************************************************************** //** Constructors *** //****************************************************************************** /** * Constructs an instance of TableLayout. This TableLayout will have one row * and one column. */ public TableLayout () { this (defaultSize); } /** * Constructs an instance of TableLayout. * * @param size widths of columns and heights of rows in the format, * {{col0, col1, col2, ..., colN}, {row0, row1, row2, ..., rowM}} * If this parameter is invalid, the TableLayout will have * exactly one row and one column. */ public TableLayout (double size[][]) { // Make sure rows and columns and nothing else is specified if ((size != null) && (size.length == 2)) { // Get the rows and columns double tempCol[] = size[0]; double tempRow[] = size[1]; // Create new rows and columns columnSpec = new double[tempCol.length]; rowSpec = new double[tempRow.length]; // Copy rows and columns System.arraycopy (tempCol, 0, columnSpec, 0, columnSpec.length); System.arraycopy (tempRow, 0, rowSpec, 0, rowSpec.length); // Make sure rows and columns are valid for (int counter = 0; counter < columnSpec.length; counter++) if ((columnSpec[counter] < 0.0) && (columnSpec[counter] != FILL) && (columnSpec[counter] != PREFERRED) && (columnSpec[counter] != MINIMUM)) { columnSpec[counter] = 0.0; } for (int counter = 0; counter < rowSpec.length; counter++) if ((rowSpec[counter] < 0.0) && (rowSpec[counter] != FILL) && (rowSpec[counter] != PREFERRED) && (rowSpec[counter] != MINIMUM)) { rowSpec[counter] = 0.0; } } else { double tempCol[] = {FILL}; double tempRow[] = {FILL}; setColumn (tempCol); setRow (tempRow); } // Create an empty list of components list = new LinkedList(); // Indicate that the cell sizes are not known dirty = true; } //****************************************************************************** //** Get/Set methods *** //****************************************************************************** /** * Gets the constraints of a given component. * * @param component desired component * * @return If the given component is found, the constraints associated with * that component. If the given component is null or is not found, * null is returned. */ public TableLayoutConstraints getConstraints (Component component) { ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if (entry.component == component) return new TableLayoutConstraints (entry.col1, entry.row1, entry.col2, entry.row2, entry.hAlign, entry.vAlign); } return null; } /** * Sets the constraints of a given component. * * @param component desired component. This parameter cannot be null. * @param constraint new set of constraints. This parameter cannot be null. * * @return If the given component is found, the constraints associated with * that component. If the given component is null or is not found, * null is returned. */ public void setConstraints (Component component, TableLayoutConstraints constraint) { // Check parameters if (component == null) throw new IllegalArgumentException ("Parameter component cannot be null."); else if (constraint == null) throw new IllegalArgumentException ("Parameter constraint cannot be null."); // Find and update constraints for the given component ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if (entry.component == component) iterator.set (new Entry(component, constraint)); } } /** * Adjusts the number and sizes of rows in this layout. After calling this * method, the caller should request this layout manager to perform the * layout. This can be done with the following code: * * <pre> * layout.layoutContainer(container); * container.repaint(); * </pre> * * or * * <pre> * window.pack() * </pre> * * If this is not done, the changes in the layout will not be seen until the * container is resized. * * @param column heights of each of the columns * * @see getColumn */ public void setColumn (double column[]) { // Copy columns columnSpec = new double[column.length]; System.arraycopy (column, 0, columnSpec, 0, columnSpec.length); // Make sure columns are valid for (int counter = 0; counter < columnSpec.length; counter++) if ((columnSpec[counter] < 0.0) && (columnSpec[counter] != FILL) && (columnSpec[counter] != PREFERRED) && (columnSpec[counter] != MINIMUM)) { columnSpec[counter] = 0.0; } // Indicate that the cell sizes are not known dirty = true; } /** * Adjusts the number and sizes of rows in this layout. After calling this * method, the caller should request this layout manager to perform the * layout. This can be done with the following code: * * <code> * layout.layoutContainer(container); * container.repaint(); * </code> * * or * * <pre> * window.pack() * </pre> * * If this is not done, the changes in the layout will not be seen until the * container is resized. * * @param row widths of each of the rows. This parameter cannot be null. * * @see getRow */ public void setRow (double row[]) { // Copy rows rowSpec = new double[row.length]; System.arraycopy (row, 0, rowSpec, 0, rowSpec.length); // Make sure rows are valid for (int counter = 0; counter < rowSpec.length; counter++) if ((rowSpec[counter] < 0.0) && (rowSpec[counter] != FILL) && (rowSpec[counter] != PREFERRED) && (rowSpec[counter] != MINIMUM)) { rowSpec[counter] = 0.0; } // Indicate that the cell sizes are not known dirty = true; } /** * Adjusts the width of a single column in this layout. After calling this * method, the caller should request this layout manager to perform the * layout. This can be done with the following code: * * <code> * layout.layoutContainer(container); * container.repaint(); * </code> * * or * * <pre> * window.pack() * </pre> * * If this is not done, the changes in the layout will not be seen until the * container is resized. * * @param i zero-based index of column to set. If this parameter is not * valid, an ArrayOutOfBoundsException will be thrown. * @param size width of the column. This parameter cannot be null. * * @see getColumn */ public void setColumn (int i, double size) { // Make sure size is valid if ((size < 0.0) && (size != FILL) && (size != PREFERRED) && (size != MINIMUM)) { size = 0.0; } // Copy new size columnSpec[i] = size; // Indicate that the cell sizes are not known dirty = true; } /** * Adjusts the height of a single row in this layout. After calling this * method, the caller should request this layout manager to perform the * layout. This can be done with the following code: * * <code> * layout.layoutContainer(container); * container.repaint(); * </code> * * or * * <pre> * window.pack() * </pre> * * If this is not done, the changes in the layout will not be seen until the * container is resized. * * @param i zero-based index of row to set. If this parameter is not * valid, an ArrayOutOfBoundsException will be thrown. * @param size height of the row. This parameter cannot be null. * * @see getRow */ public void setRow (int i, double size) { // Make sure size is valid if ((size < 0.0) && (size != FILL) && (size != PREFERRED) && (size != MINIMUM)) { size = 0.0; } // Copy new size rowSpec[i] = size; // Indicate that the cell sizes are not known dirty = true; } /** * Gets the sizes of columns in this layout. * * @return widths of each of the columns * * @see setColumn */ public double [] getColumn () { // Copy columns double column[] = new double[columnSpec.length]; System.arraycopy (columnSpec, 0, column, 0, column.length); return column; } /** * Gets the height of a single row in this layout. * * @return height of the requested row * * @see setRow */ public double [] getRow () { // Copy rows double row[] = new double[rowSpec.length]; System.arraycopy (rowSpec, 0, row, 0, row.length); return row; } /** * Gets the width of a single column in this layout. * * @param i zero-based index of row to get. If this parameter is not valid, * an ArrayOutOfBoundsException will be thrown. * * @return width of the requested column * * @see setRow */ public double getColumn (int i) { return columnSpec[i]; } /** * Gets the sizes of a row in this layout. * * @param i zero-based index of row to get. If this parameter is not valid, * an ArrayOutOfBoundsException will be thrown. * * @return height of each of the requested row * * @see setRow */ public double getRow (int i) { return rowSpec[i]; } /** * Gets the number of columns in this layout. * * @return the number of columns */ public int getNumColumn () { return columnSpec.length; } /** * Gets the number of rows in this layout. * * @return the number of rows */ public int getNumRow () { return rowSpec.length; } //****************************************************************************** //** Insertion/Deletion methods *** //****************************************************************************** /** * Inserts a column in this layout. All components to the right of the * insertion point are moved right one column. The container will need to * be laid out after this method returns. See <code>setColumn</code>. * * @param i zero-based index at which to insert the column. * @param size size of the column to be inserted * * @see setColumn * @see deleteColumn */ public void insertColumn (int i, double size) { // Make sure position is valid if ((i < 0) || (i > columnSpec.length)) throw new IllegalArgumentException ("Parameter i is invalid. i = " + i + ". Valid range is [0, " + columnSpec.length + "]."); // Make sure column size is valid if ((size < 0.0) && (size != FILL) && (size != PREFERRED) && (size != MINIMUM)) { size = 0.0; } // Copy columns double column[] = new double[columnSpec.length + 1]; System.arraycopy (columnSpec, 0, column, 0, i); System.arraycopy (columnSpec, i, column, i + 1, columnSpec.length - i); // Insert column column[i] = size; columnSpec = column; // Move all components that are to the right of new row ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { // Get next entry Entry entry = (Entry) iterator.next(); // Is the first column to the right of the new column if (entry.col1 >= i) // Move first column entry.col1++; // Is the second column to the right of the new column if (entry.col2 >= i) // Move second column entry.col2++; } // Indicate that the cell sizes are not known dirty = true; } /** * Inserts a row in this layout. All components below the insertion point * are moved down one row. The container will need to be laid out after this * method returns. See <code>setRow</code>. * * @param i zero-based index at which to insert the column. * @param size size of the row to be inserted * * @see setRow * @see deleteRow */ public void insertRow (int i, double size) { // Make sure position is valid if ((i < 0) || (i > rowSpec.length)) throw new IllegalArgumentException ("Parameter i is invalid. i = " + i + ". Valid range is [0, " + rowSpec.length + "]."); // Make sure row size is valid if ((size < 0.0) && (size != FILL) && (size != PREFERRED) && (size != MINIMUM)) { size = 0.0; } // Copy rows double row[] = new double[rowSpec.length + 1]; System.arraycopy (rowSpec, 0, row, 0, i); System.arraycopy (rowSpec, i, row, i + 1, rowSpec.length - i); // Insert row row[i] = size; rowSpec = row; // Move all components that are below the new row ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { // Get next entry Entry entry = (Entry) iterator.next(); // Is the first row to the right of the new row if (entry.row1 >= i) // Move first row entry.row1++; // Is the second row to the right of the new row if (entry.row2 >= i) // Move second row entry.row2++; } // Indicate that the cell sizes are not known dirty = true; } /** * Deletes a column in this layout. All components to the right of the * deletion point are moved left one column. The container will need to * be laid out after this method returns. See <code>setColumn</code>. * * @param i zero-based index of column to delete * * @see setColumn * @see deleteColumn */ public void deleteColumn (int i) { // Make sure position is valid if ((i < 0) || (i >= columnSpec.length)) throw new IllegalArgumentException ("Parameter i is invalid. i = " + i + ". Valid range is [0, " + (columnSpec.length - 1) + "]."); // Copy columns double column[] = new double[columnSpec.length - 1]; System.arraycopy (columnSpec, 0, column, 0, i); System.arraycopy (columnSpec, i + 1, column, i, columnSpec.length - i - 1); // Delete column columnSpec = column; // Move all components that are to the right of row deleted ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { // Get next entry Entry entry = (Entry) iterator.next(); // Is the first column to the right of the new column if (entry.col1 >= i) // Move first column entry.col1--; // Is the second column to the right of the new column if (entry.col2 >= i) // Move second column entry.col2--; } // Indicate that the cell sizes are not known dirty = true; } /** * Deletes a row in this layout. All components below the deletion point are * moved up one row. The container will need to be laid out after this method * returns. See <code>setRow</code>. There must be at least two rows in order * to delete a row. * * @param i zero-based index of column to delete * * @see setRow * @see deleteRow */ public void deleteRow (int i) { // Make sure position is valid if ((i < 0) || (i >= rowSpec.length)) throw new IllegalArgumentException ("Parameter i is invalid. i = " + i + ". Valid range is [0, " + (rowSpec.length - 1) + "]."); // Copy rows double row[] = new double[rowSpec.length - 1]; System.arraycopy (rowSpec, 0, row, 0, i); System.arraycopy (rowSpec, i + 1, row, i, rowSpec.length - i - 1); // Delete row rowSpec = row; // Move all components that are to below the row deleted ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { // Get next entry Entry entry = (Entry) iterator.next(); // Is the first row below the new row if (entry.row1 >= i) // Move first row entry.row1--; // Is the second row below the new row if (entry.row2 >= i) // Move second row entry.row2--; } // Indicate that the cell sizes are not known dirty = true; } //****************************************************************************** //** Misc methods *** //****************************************************************************** /** * Converts this TableLayout to a string. * * @return a string representing the columns and row sizes in the form * "{{col0, col1, col2, ..., colN}, {row0, row1, row2, ..., rowM}}" */ public String toString () { int counter; String value = "TableLayout {{"; if (columnSpec.length > 0) { for (counter = 0; counter < columnSpec.length - 1; counter++) value += columnSpec[counter] + ", "; value += columnSpec[columnSpec.length - 1] + "}, {"; } else value += "}, {"; if (rowSpec.length > 0) { for (counter = 0; counter < rowSpec.length - 1; counter++) value += rowSpec[counter] + ", "; value += rowSpec[rowSpec.length - 1] + "}}"; } else value += "}}"; return value; } /** * Draws a grid on the given container. This is useful for seeing where the * rows and columns go. In the container's paint method, call this method. * * @param container container using this TableLayout * @param g graphics content of container (can be offscreen) */ public void drawGrid (Container container, Graphics g) { int counter; // Counting variable; // Calculate the sizes of the rows and columns Dimension d = container.getSize(); if (dirty || (d.width != oldWidth) || (d.height != oldHeight)) calculateSize (container); // Initialize y int y = 0; for (int row = 0; row < rowSize.length; row++) { // Initialize x int x = 0; for (int column = 0; column < columnSize.length; column++) { // Use a random color to make things easy to see Color color = new Color((int) (Math.random() * 0xFFFFFFL)); g.setColor (color); // Draw the cell as a solid rectangle g.fillRect (x, y, columnSize[column], rowSize[row]); // Increment x x += columnSize[column]; } // Increment y y += rowSize[row]; } } /** * Determines whether or not there are any hidden components. A hidden * component is one that will not be shown with this layout's current * configuration. Such a component is, at least partly, in an invalid row * or column. For example, on a table with five rows, row -1 and row 5 are both * invalid. Valid rows are 0 through 4, inclusively. * * @return True, if there are any hidden components. False, otherwise. * * @see overlapping */ public boolean hidden () { // Assume no components are hidden boolean hidden = false; // Check all components ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { // Get next entry Entry entry = (Entry) iterator.next(); // Is this component valid if ((entry.row1 < 0) || (entry.col1 < 0) || (entry.row2 > rowSpec.length) || (entry.col2 > columnSpec.length)) { hidden = true; break; } } return hidden; } /** * Determines whether or not there are any overlapping components. Two * components overlap if they cover at least one common cell. * * @return True, if there are any overlapping components. False, otherwise. * * @see hidden */ public boolean overlapping () { // Count contraints int numEntry = list.size(); // If there are no components, they can't be overlapping if (numEntry == 0) return false; // Assume no components are overlapping boolean overlapping = false; // Put entries in an array Entry entry[] = (Entry[]) list.toArray(new Entry[numEntry]); // Check all components for (int knowUnique = 1; knowUnique < numEntry; knowUnique++) for (int checking = knowUnique - 1; checking >= 0; checking--) if ( ( (entry[checking].col1 >= entry[knowUnique].col1) && (entry[checking].col1 <= entry[knowUnique].col2) && (entry[checking].row1 >= entry[knowUnique].row1) && (entry[checking].row1 <= entry[knowUnique].row2) ) || ( (entry[checking].col2 >= entry[knowUnique].col1) && (entry[checking].col2 <= entry[knowUnique].col2) && (entry[checking].row2 >= entry[knowUnique].row1) && (entry[checking].row2 <= entry[knowUnique].row2) ) ) { overlapping = true; break; } return overlapping; } /** * Calculates the sizes of the rows and columns based on the absolute and * relative sizes specified in <code>rowSpec</code> and <code>columnSpec</code> * and the size of the container. The result is stored in <code>rowSize</code> * and <code>columnSize</code>. * * @param container container using this TableLayout */ protected void calculateSize (Container container) { int counter; // Counting variable; // Get number of rows and columns int numColumn = columnSpec.length; int numRow = rowSpec.length; // Create array to hold actual sizes in pixels columnSize = new int[numColumn]; rowSize = new int[numRow]; // Get the container's insets Insets inset = container.getInsets(); // Get the size of the container's available space Dimension d = container.getSize(); int totalWidth = d.width - inset.left - inset.right; int totalHeight = d.height - inset.top - inset.bottom; // Initially, the available space is the total space int availableWidth = totalWidth; int availableHeight = totalHeight; // Assign absolute widths; this reduces available width for (counter = 0; counter < numColumn; counter++) // Is the current column an absolue size if ((columnSpec[counter] >= 1.0) || (columnSpec[counter] == 0.0)) { // Assign absolute width columnSize[counter] = (int) (columnSpec[counter] + 0.5); // Reduce available width availableWidth -= columnSize[counter]; } // Assign absolute heights; this reduces available height for (counter = 0; counter < numRow; counter++) // Is the current column an absolue size if ((rowSpec[counter] >= 1.0) || (rowSpec[counter] == 0.0)) { // Assign absolute width rowSize[counter] = (int) (rowSpec[counter] + 0.5); // Reduce available width availableHeight -= rowSize[counter]; } // Assign preferred and minimum widths; this reduces available width. // Assignment of preferred/minimum with is like assignment of absolute // widths except that each column must determine the maximum // preferred/minimum width of the components that are completely contained // within the column. for (counter = 0; counter < numColumn; counter++) // Is the current column a preferred size if ((columnSpec[counter] == PREFERRED) || (columnSpec[counter] == MINIMUM)) { // Assume a maximum width of zero int maxWidth = 0; // Find maximum preferred width of all components completely // contained within this column ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if ((entry.col1 == counter) && (entry.col2 == counter)) { Dimension p = (columnSpec[counter] == PREFERRED) ? entry.component.getPreferredSize() : entry.component.getMinimumSize(); int width = (p == null) ? 0 : p.width; if (maxWidth < width) maxWidth = width; } } // Assign preferred width columnSize[counter] = maxWidth; // Reduce available width availableWidth -= maxWidth; } // Assign preferred and minimum heights; this reduces available height. // Assignment of preferred/minimum with is like assignment of absolute // heights except that each row must determine the maximum // preferred/minimum height of the components that are completely contained // within the row. for (counter = 0; counter < numRow; counter++) // Is the current row a preferred size if ((rowSpec[counter] == PREFERRED) || (rowSpec[counter] == MINIMUM)) { // Assume a maximum height of zero int maxHeight = 0; // Find maximum preferred height of all components completely // contained within this row ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if ((entry.row1 == counter) && (entry.row2 == counter)) { Dimension p = (rowSpec[counter] == PREFERRED) ? entry.component.getPreferredSize() : entry.component.getMinimumSize(); int height = (p == null) ? 0 : p.height; if (maxHeight < height) maxHeight = height; } } // Assign preferred height rowSize[counter] = maxHeight; // Reduce available height availableHeight -= maxHeight; } // Remember how much space is available for relatively sized cells int relativeWidth = availableWidth; int relativeHeight = availableHeight; // Make sure relativeWidth and relativeHeight are non-negative if (relativeWidth < 0) relativeWidth = 0; if (relativeHeight < 0) relativeHeight = 0; // Assign relative widths for (counter = 0; counter < numColumn; counter++) // Is the current column an relative size if ((columnSpec[counter] > 0.0) && (columnSpec[counter] < 1.0)) { // Assign relative width columnSize[counter] = (int) (columnSpec[counter] * relativeWidth + 0.5); // Reduce available width availableWidth -= columnSize[counter]; } // Assign relative widths for (counter = 0; counter < numRow; counter++) // Is the current column an relative size if ((rowSpec[counter] > 0.0) && (rowSpec[counter] < 1.0)) { // Assign relative width rowSize[counter] = (int) (rowSpec[counter] * relativeHeight + 0.5); // Reduce available width availableHeight -= rowSize[counter]; } // Make sure availableWidth and availableHeight are non-negative if (availableWidth < 0) availableWidth = 0; if (availableHeight < 0) availableHeight = 0; // Count the number of "fill" cells int numFillWidth = 0; int numFillHeight = 0; for (counter = 0; counter < numColumn; counter++) if (columnSpec[counter] == FILL) numFillWidth++; for (counter = 0; counter < numRow; counter++) if (rowSpec[counter] == FILL) numFillHeight++; // If numFillWidth (numFillHeight) is zero, the cooresponding if statements // will always evaluate to false and the division will not occur. // If there are more than one "fill" cell, slack may occur due to rounding // errors int slackWidth = availableWidth; int slackHeight = availableHeight; // Assign "fill" cells equal amounts of the remaining space for (counter = 0; counter < numColumn; counter++) if (columnSpec[counter] == FILL) { columnSize[counter] = availableWidth / numFillWidth; slackWidth -= columnSize[counter]; } for (counter = 0; counter < numRow; counter++) if (rowSpec[counter] == FILL) { rowSize[counter] = availableHeight / numFillHeight; slackHeight -= rowSize[counter]; } // Add slack to the last "fill" cell for (counter = numColumn - 1; counter >= 0; counter--) { if (columnSpec[counter] == FILL) { columnSize[counter] += slackWidth; break; } } for (counter = numRow - 1; counter >= 0; counter--) { if (rowSpec[counter] == FILL) { rowSize[counter] += slackHeight; break; } } // Calculate offsets of each column (done for effeciency) columnOffset = new int[numColumn + 1]; columnOffset[0] = inset.left; for (counter = 0; counter < numColumn; counter++) columnOffset[counter + 1] = columnOffset[counter] + columnSize[counter]; // Calculate offsets of each row (done for effeciency) rowOffset = new int[numRow + 1]; rowOffset[0] = inset.top; for (counter = 0; counter < numRow; counter++) rowOffset[counter + 1] = rowOffset[counter] + rowSize[counter]; // Indicate that the size of the cells are known for the container's // current size dirty = false; oldWidth = totalWidth; oldHeight = totalHeight; } //****************************************************************************** //** java.awt.event.LayoutManager methods *** //****************************************************************************** /** * To lay out the specified container using this layout. This method reshapes * the components in the specified target container in order to satisfy the * constraints of all components. * * <p>User code should not have to call this method directly.</p> * * @param container container being served by this layout manager */ public void layoutContainer (Container container) { int x, y; // Coordinates of the currnet component in pixels int w, h; // Width and height of the current component in pixels // Calculate sizes if container has changed size or components were added Dimension d = container.getSize(); if (dirty || (d.width != oldWidth) || (d.height != oldHeight)) calculateSize (container); // Get components Component component[] = container.getComponents(); // Layout components for (int counter = 0; counter < component.length; counter++) { try { // Get the entry entry for the next component ListIterator iterator = list.listIterator(0); Entry entry = null; while (iterator.hasNext()) { entry = (Entry) iterator.next(); if (entry.component == component[counter]) break; else entry = null; } // Skip any components that have not been place in a specific cell if (entry == null) break; // Does the entry occupy a single cell if (entry.singleCell) { // The following block of code has been optimized so that the // preferred size of the component is only obtained if it is // needed. There are components in which the getPreferredSize // method is extremely expensive, such as data driven controls // with a large amount of data. // Get the preferred size of the component int preferredWidth = 0; int preferredHeight = 0; if ((entry.hAlign != FULL) || (entry.vAlign != FULL)) { Dimension preferredSize = component[counter].getPreferredSize(); preferredWidth = preferredSize.width; preferredHeight = preferredSize.height; } // Determine cell width and height int cellWidth = columnSize[entry.col1]; int cellHeight = rowSize[entry.row1]; // Determine the width of the component if ((entry.hAlign == FULL) || (cellWidth < preferredWidth)) // Use the width of the cell w = cellWidth; else // Use the prefered width of the component w = preferredWidth; // Determine left and right boarders switch (entry.hAlign) { case LEFT : // Align left side along left edge of cell x = columnOffset[entry.col1]; break; case RIGHT : // Align right side along right edge of cell x = columnOffset[entry.col1 + 1] - w; break; case CENTER : // Center justify component x = columnOffset[entry.col1] + ((cellWidth - w) >> 1); break; case FULL : // Align left side along left edge of cell x = columnOffset[entry.col1]; break; default : // This is a never should happen case, but just in case x = 0; } // Determine the height of the component if ((entry.vAlign == FULL) || (cellHeight < preferredHeight)) // Use the height of the cell h = cellHeight; else // Use the prefered height of the component h = preferredHeight; // Determine top and bottom boarders switch (entry.vAlign) { case TOP : // Align top side along top edge of cell y = rowOffset[entry.row1]; break; case BOTTOM : // Align right side along right edge of cell y = rowOffset[entry.row1 + 1] - h; break; case CENTER : // Center justify component y = rowOffset[entry.row1] + ((cellHeight - h) >> 1); break; case FULL : // Align right side along right edge of cell y = rowOffset[entry.row1]; break; default : // This is a never should happen case, but just in case y = 0; } } else { // Align left side with left boarder of first column x = columnOffset[entry.col1]; // Align top side along top edge of first row y = rowOffset[entry.row1]; // Align right side with right boarder of second column w = columnOffset[entry.col2 + 1] - columnOffset[entry.col1]; // Align bottom side with bottom boarder of second row h = rowOffset[entry.row2 + 1] - rowOffset[entry.row1]; } // Move and resize component component[counter].setBounds (x, y, w, h); } catch (Exception error) { // If any error occurs, skip this component continue; } } } /** * Determines the preferred size of the container argument using this layout. * The preferred size is the smallest size that, if used for the container's * size, will ensure that all components are at least as large as their * preferred size. This method cannot guarantee that all components will be * their preferred size. For example, if component A and component B are each * allocate half of the container's width and component A wants to be 10 pixels * wide while component B wants to be 100 pixels wide, they cannot both be * accommodated. Since in general components rather be larger than their * preferred size instead of smaller, component B's request will be fulfilled. * The preferred size of the container would be 200 pixels. * * @param container container being served by this layout manager * * @return a dimension indicating the container's preferred size */ public Dimension preferredLayoutSize (Container container) { Dimension size; // Preferred size of current component int scaledWidth = 0; // Preferred width of scalled components int scaledHeight = 0; // Preferred height of scalled components int temp; // Temporary variable used to compare sizes int counter; // Counting variable // Determine percentage of space allocated to fill components. This is // one minus the sum of all scalable components. double fillWidthRatio = 1.0; double fillHeightRatio = 1.0; int numFillWidth = 0; int numFillHeight = 0; for (counter = 0; counter < columnSpec.length; counter++) if ((columnSpec[counter] > 0.0) && (columnSpec[counter] < 1.0)) fillWidthRatio -= columnSpec[counter]; else if (columnSpec[counter] == FILL) numFillWidth++; for (counter = 0; counter < rowSpec.length; counter++) if ((rowSpec[counter] > 0.0) && (rowSpec[counter] < 1.0)) fillHeightRatio -= rowSpec[counter]; else if (rowSpec[counter] == FILL) numFillHeight++; // Adjust fill ratios to reflect number of fill rows/columns if (numFillWidth > 1) fillWidthRatio /= numFillWidth; if (numFillHeight > 1) fillHeightRatio /= numFillHeight; // Cap fill ratio bottoms to 0.0 if (fillWidthRatio < 0.0) fillWidthRatio = 0.0; if (fillHeightRatio < 0.0) fillHeightRatio = 0.0; // Calculate preferred/minimum column widths int columnPrefMin[] = new int[columnSpec.length]; for (counter = 0; counter < columnSpec.length; counter++) // Is the current column a preferred/minimum size if ((columnSpec[counter] == PREFERRED) || (columnSpec[counter] == MINIMUM)) { // Assume a maximum width of zero int maxWidth = 0; // Find maximum preferred/minimum width of all components completely // contained within this column ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if ((entry.col1 == counter) && (entry.col2 == counter)) { Dimension p = (columnSpec[counter] == PREFERRED) ? entry.component.getPreferredSize() : entry.component.getMinimumSize(); int width = (p == null) ? 0 : p.width; if (maxWidth < width) maxWidth = width; } } // Set column's preferred/minimum width columnPrefMin[counter] = maxWidth; } // Calculate preferred/minimum row heights int rowPrefMin[] = new int[rowSpec.length]; for (counter = 0; counter < rowSpec.length; counter++) // Is the current row a preferred/minimum size if ((rowSpec[counter] == PREFERRED) || (rowSpec[counter] == MINIMUM)) { // Assume a maximum height of zero int maxHeight = 0; // Find maximum preferred height of all components completely // contained within this row ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if ((entry.row1 == counter) && (entry.row1 == counter)) { Dimension p = (rowSpec[counter] == PREFERRED) ? entry.component.getPreferredSize() : entry.component.getMinimumSize(); int height = (p == null) ? 0 : p.height; if (maxHeight < height) maxHeight = height; } } // Add preferred height rowPrefMin[counter] += maxHeight; } // Find maximum preferred size of all scaled components ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { // Get next entry Entry entry = (Entry) iterator.next(); // Make sure entry is in valid rows and columns if ((entry.col1 < 0) || (entry.col1 >= columnSpec.length) || (entry.col2 >= columnSpec.length) || (entry.row1 < 0) || (entry.row1 >= rowSpec.length) || (entry.row2 >= rowSpec.length)) { // Skip the bad component continue; } // Get preferred size of current component size = entry.component.getPreferredSize(); // Calculate portion of component that is not absolutely sized int scalableWidth = size.width; int scalableHeight = size.height; for (counter = entry.col1; counter <= entry.col2; counter++) if (columnSpec[counter] >= 1.0) scalableWidth -= columnSpec[counter]; else if ((columnSpec[counter] == PREFERRED) || (columnSpec[counter] == MINIMUM)) { scalableWidth -= columnPrefMin[counter]; } for (counter = entry.row1; counter <= entry.row2; counter++) if (rowSpec[counter] >= 1.0) scalableHeight -= rowSpec[counter]; else if ((rowSpec[counter] == PREFERRED) || (rowSpec[counter] == MINIMUM)) { scalableHeight -= rowPrefMin[counter]; } //---------------------------------------------------------------------- // Determine total percentage of scalable space that the component // occupies by adding the relative columns and the fill columns double relativeWidth = 0.0; for (counter = entry.col1; counter <= entry.col2; counter++) { // Column is scaled if ((columnSpec[counter] > 0.0) && (columnSpec[counter] < 1.0)) // Add scaled size to relativeWidth relativeWidth += columnSpec[counter]; // Column is fill else if ((columnSpec[counter] == FILL) && (fillWidthRatio != 0.0)) // Add fill size to relativeWidth relativeWidth += fillWidthRatio; } // Determine the total scaled width as estimated by this component if (relativeWidth == 0) temp = 0; else temp = (int) (scalableWidth / relativeWidth + 0.5); // If the container needs to be bigger, make it so if (scaledWidth < temp) scaledWidth = temp; //---------------------------------------------------------------------- // Determine total percentage of scalable space that the component // occupies by adding the relative columns and the fill columns double relativeHeight = 0.0; for (counter = entry.row1; counter <= entry.row2; counter++) { // Row is scaled if ((rowSpec[counter] > 0.0) && (rowSpec[counter] < 1.0)) // Add scaled size to relativeHeight relativeHeight += rowSpec[counter]; // Row is fill else if ((rowSpec[counter] == FILL) && (fillHeightRatio != 0.0)) // Add fill size to relativeHeight relativeHeight += fillHeightRatio; } // Determine the total scaled width as estimated by this component if (relativeHeight == 0) temp = 0; else temp = (int) (scalableHeight / relativeHeight + 0.5); // If the container needs to be bigger, make it so if (scaledHeight < temp) scaledHeight = temp; } // totalWidth is the scaledWidth plus the sum of all absolute widths and all // preferred widths int totalWidth = scaledWidth; for (counter = 0; counter < columnSpec.length; counter++) // Is the current column an absolute size if (columnSpec[counter] >= 1.0) totalWidth += (int) (columnSpec[counter] + 0.5); // Is the current column a preferred/minimum size else if ((columnSpec[counter] == PREFERRED) || (columnSpec[counter] == MINIMUM)) { // Add preferred/minimum width totalWidth += columnPrefMin[counter]; } // totalHeight is the scaledHeight plus the sum of all absolute heights and // all preferred widths int totalHeight = scaledHeight; for (counter = 0; counter < rowSpec.length; counter++) // Is the current row an absolute size if (rowSpec[counter] >= 1.0) totalHeight += (int) (rowSpec[counter] + 0.5); // Is the current row a preferred size else if ((rowSpec[counter] == PREFERRED) || (rowSpec[counter] == MINIMUM)) { // Add preferred/minimum width totalHeight += rowPrefMin[counter]; } // Compensate for container's insets Insets inset = container.getInsets(); totalWidth += inset.left + inset.right; totalHeight += inset.top + inset.bottom; return new Dimension(totalWidth, totalHeight); } /** * Determines the minimum size of the container argument using this layout. * The minimum size is the smallest size that, if used for the container's * size, will ensure that all components are at least as large as their * minimum size. This method cannot guarantee that all components will be * their minimum size. For example, if component A and component B are each * allocate half of the container's width and component A wants to be 10 pixels * wide while component B wants to be 100 pixels wide, they cannot both be * accommodated. Since in general components rather be larger than their * minimum size instead of smaller, component B's request will be fulfilled. * The minimum size of the container would be 200 pixels. * * @param container container being served by this layout manager * * @return a dimension indicating the container's minimum size */ public Dimension minimumLayoutSize (Container container) { Dimension size; // Minimum size of current component int scaledWidth = 0; // Minimum width of scalled components int scaledHeight = 0; // Minimum height of scalled components int fillWidth = 0; // Minimum width of fill components int fillHeight = 0; // Minimum height of fill components int temp; // Temporary variable used to compare sizes int counter; // Counting variable // Determine percentage of space allocated to fill components. This is // one minus the sum of all scalable components. double fillWidthRatio = 1.0; double fillHeightRatio = 1.0; int numFillWidth = 0; int numFillHeight = 0; for (counter = 0; counter < columnSpec.length; counter++) if ((columnSpec[counter] > 0.0) && (columnSpec[counter] < 1.0)) fillWidthRatio -= columnSpec[counter]; else if (columnSpec[counter] == FILL) numFillWidth++; for (counter = 0; counter < rowSpec.length; counter++) if ((rowSpec[counter] > 0.0) && (rowSpec[counter] < 1.0)) fillHeightRatio -= rowSpec[counter]; else if (rowSpec[counter] == FILL) numFillHeight++; // Adjust fill ratios to reflect number of fill rows/columns if (numFillWidth > 1) fillWidthRatio /= numFillWidth; if (numFillHeight > 1) fillHeightRatio /= numFillHeight; // Cap fill ratio bottoms to 0.0 if (fillWidthRatio < 0.0) fillWidthRatio = 0.0; if (fillHeightRatio < 0.0) fillHeightRatio = 0.0; // Find maximum minimum size of all scaled components ListIterator iterator = list.listIterator(0); while (iterator.hasNext()) { // Get next entry Entry entry = (Entry) iterator.next(); // Make sure entry is in valid rows and columns if ((entry.col1 < 0) || (entry.col1 >= columnSpec.length) || (entry.col2 >= columnSpec.length) || (entry.row1 < 0) || (entry.row1 >= rowSpec.length) || (entry.row2 >= rowSpec.length)) { // Skip the bad component continue; } // Get minimum size of current component size = entry.component.getMinimumSize(); // Calculate portion of component that is not absolutely sized int scalableWidth = size.width; int scalableHeight = size.height; for (counter = entry.col1; counter <= entry.col2; counter++) if (columnSpec[counter] >= 1.0) scalableWidth -= columnSpec[counter]; for (counter = entry.row1; counter <= entry.row2; counter++) if (rowSpec[counter] >= 1.0) scalableHeight -= rowSpec[counter]; //---------------------------------------------------------------------- // Determine total percentage of scalable space that the component // occupies by adding the relative columns and the fill columns double relativeWidth = 0.0; for (counter = entry.col1; counter <= entry.col2; counter++) { // Column is scaled if ((columnSpec[counter] > 0.0) && (columnSpec[counter] < 1.0)) // Add scaled size to relativeWidth relativeWidth += columnSpec[counter]; // Column is fill else if ((columnSpec[counter] == FILL) && (fillWidthRatio != 0.0)) // Add fill size to relativeWidth relativeWidth += fillWidthRatio; } // Determine the total scaled width as estimated by this component if (relativeWidth == 0) temp = 0; else temp = (int) (scalableWidth / relativeWidth + 0.5); // If the container needs to be bigger, make it so if (scaledWidth < temp) scaledWidth = temp; //---------------------------------------------------------------------- // Determine total percentage of scalable space that the component // occupies by adding the relative columns and the fill columns double relativeHeight = 0.0; for (counter = entry.row1; counter <= entry.row2; counter++) { // Row is scaled if ((rowSpec[counter] > 0.0) && (rowSpec[counter] < 1.0)) // Add scaled size to relativeHeight relativeHeight += rowSpec[counter]; // Row is fill else if ((rowSpec[counter] == FILL) && (fillHeightRatio != 0.0)) // Add fill size to relativeHeight relativeHeight += fillHeightRatio; } // Determine the total scaled width as estimated by this component if (relativeHeight == 0) temp = 0; else temp = (int) (scalableHeight / relativeHeight + 0.5); // If the container needs to be bigger, make it so if (scaledHeight < temp) scaledHeight = temp; } // totalWidth is the scaledWidth plus the sum of all absolute widths and all // preferred widths int totalWidth = scaledWidth; for (counter = 0; counter < columnSpec.length; counter++) // Is the current column an absolute size if (columnSpec[counter] >= 1.0) totalWidth += (int) (columnSpec[counter] + 0.5); // Is the current column a preferred size else if ((columnSpec[counter] == PREFERRED) || (columnSpec[counter] == MINIMUM)) { // Assume a maximum width of zero int maxWidth = 0; // Find maximum preferred width of all components completely // contained within this column iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if ((entry.col1 == counter) && (entry.col2 == counter)) { Dimension p = (columnSpec[counter] == PREFERRED) ? entry.component.getPreferredSize() : entry.component.getMinimumSize(); int width = (p == null) ? 0 : p.width; if (maxWidth < width) maxWidth = width; } } // Add preferred width totalWidth += maxWidth; } // totalHeight is the scaledHeight plus the sum of all absolute heights and // all preferred widths int totalHeight = scaledHeight; for (counter = 0; counter < rowSpec.length; counter++) // Is the current row an absolute size if (rowSpec[counter] >= 1.0) totalHeight += (int) (rowSpec[counter] + 0.5); // Is the current row a preferred size else if ((rowSpec[counter] == PREFERRED) || (rowSpec[counter] == MINIMUM)) { // Assume a maximum height of zero int maxHeight = 0; // Find maximum preferred height of all components completely // contained within this row iterator = list.listIterator(0); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if ((entry.row1 == counter) && (entry.row1 == counter)) { Dimension p = (rowSpec[counter] == PREFERRED) ? entry.component.getPreferredSize() : entry.component.getMinimumSize(); int height = (p == null) ? 0 : p.height; if (maxHeight < height) maxHeight = height; } } // Add preferred height totalHeight += maxHeight; } // Compensate for container's insets Insets inset = container.getInsets(); totalWidth += inset.left + inset.right; totalHeight += inset.top + inset.bottom; return new Dimension(totalWidth, totalHeight); } /** * Adds the specified component with the specified name to the layout. * * @param name indicates entry's position and anchor * @param component component to add */ public void addLayoutComponent (String name, Component component) { addLayoutComponent (component, name); } //****************************************************************************** //** java.awt.event.LayoutManager2 methods *** //****************************************************************************** /** * Adds the specified component with the specified name to the layout. * * @param component component to add * @param constraint indicates entry's position and alignment */ public void addLayoutComponent (Component component, Object constraint) { if (constraint instanceof String) { // Create an entry to associate component with its constraints constraint = new TableLayoutConstraints((String) constraint); // Add component and constraints to the list list.add (new Entry(component, (TableLayoutConstraints) constraint)); } else if (constraint instanceof TableLayoutConstraints) { // Add component and constraints to the list list.add (new Entry(component, (TableLayoutConstraints) constraint)); } else if (constraint == null) throw new IllegalArgumentException("No constraint for the component"); else throw new IllegalArgumentException ("Cannot accept a constraint of class " + constraint.getClass()); } /** * Removes the specified component from the layout. * * @param component component being removed */ public void removeLayoutComponent (Component component) { list.remove (component); } /** * Returns the maximum dimensions for this layout given the components in the * specified target container. * * @param target the component which needs to be laid out * * @return unconditionally, a Dimension of Integer.MAX_VALUE by * Integer.MAX_VALUE since TableLayout does not limit the * maximum size of a container */ public Dimension maximumLayoutSize (Container target) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } /** * Returns the alignment along the x axis. This specifies how the component * would like to be aligned relative to other components. The value should be * a number between 0 and 1 where 0 represents alignment along the origin, 1 is * aligned the furthest away from the origin, 0.5 is centered, etc. * * @return unconditionally, 0.5 */ public float getLayoutAlignmentX (Container parent) { return 0.5f; } /** * Returns the alignment along the y axis. This specifies how the component * would like to be aligned relative to other components. The value should be * a number between 0 and 1 where 0 represents alignment along the origin, 1 is * aligned the furthest away from the origin, 0.5 is centered, etc. * * @return unconditionally, 0.5 */ public float getLayoutAlignmentY (Container parent) { return 0.5f; } /** * Invalidates the layout, indicating that if the layout manager has cached * information it should be discarded. */ public void invalidateLayout (Container target) { dirty = true; } //****************************************************************************** //*** Inner Class *** //****************************************************************************** // The following inner class is used to bind components to their constraints protected class Entry extends TableLayoutConstraints { /** Component bound by the constraints */ protected Component component; /** Does the component occupy a single cell */ protected boolean singleCell; /** * Constructs an Entry that binds a component to a set of constraints. * * @param component component being bound * @param constranit constraints being applied */ public Entry (Component component, TableLayoutConstraints constraint) { super (constraint.col1, constraint.row1, constraint.col2, constraint.row2, constraint.hAlign, constraint.vAlign); singleCell = ((row1 == row2) && (col1 == col2)); this.component = component; } /** * Determines whether or not two entries are equal. * * @param object object being compared to; must be a Component if it * is equal to this TableLayoutConstraints. * * @return True, if the entries refer to the same component object. * False, otherwise. */ public boolean equals (Object object) { boolean equal = false; if (object instanceof Component) { Component component = (Component) object; equal = (this.component == component); } return equal; } } }