You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2057 lines
72 KiB
2057 lines
72 KiB
package com.fr.design.layout; |
|
|
|
|
|
import java.awt.Component; |
|
import java.awt.ComponentOrientation; |
|
import java.awt.Container; |
|
import java.awt.Dimension; |
|
import java.awt.Insets; |
|
import java.awt.LayoutManager2; |
|
import java.io.Serializable; |
|
import java.lang.reflect.Method; |
|
import java.util.LinkedList; |
|
import java.util.ListIterator; |
|
|
|
import com.fr.general.GeneralUtils; |
|
|
|
|
|
/** |
|
* <p>TableLayout is a layout manager that is more powerful than GridBagLayout |
|
* yet much easier to use.</p> |
|
* |
|
* <b>Background</b> |
|
* |
|
* <p>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> |
|
* |
|
* <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. The |
|
* component can be aligned within those cells using four vertical and six |
|
* horizontal justifications. The vertical justifications are left, center, |
|
* right, and full. The horizontal justifications are left, center, right, |
|
* full, leading, and trailing. With full justification the component is |
|
* stretched either vertically or horizontally to fit the cell or group of |
|
* cells.<p> |
|
* |
|
* <b>Justification</b> |
|
* |
|
* <p>Leading and trailing justification are used to support languages that |
|
* are read from right to left. See the |
|
* <code>java.awt.ComponentOrientation</code> class for details and |
|
* http://java.sun.com/products/jfc/tsc/articles/bidi for an introduction to |
|
* component orientation and bidirectional text support. The leading |
|
* justification will align the component along the leading edge of the |
|
* container and the trailing justification will align the component along the |
|
* trailing edge. There is no leading or trailing justification along the |
|
* vertical axis since all modern languages are read from top to bottom and |
|
* no bottom-to-top orientation is defined in |
|
* <code>java.awt.ComponentOrientation.</code></p> |
|
* |
|
* <p>For components using the <code>ComponentOrientation.LEFT_TO_RIGHT</code> |
|
* orientation, the leading edge is the left edge and the trailing edge is the |
|
* right one. For components using the <code>ComponentOrientation.RIGHT_TO_LEFT |
|
* </code> orientation, the opposite is true. For components that are using |
|
* <code>ComponentOrientation.UNKNOWN</code> and for Java runtime environments |
|
* that do not support component orientation, left-to-right orientation is |
|
* assumed for backwards compatibility.</p> |
|
* |
|
* <b>Gaps</b> |
|
* |
|
* <p>Horizontal and vertical gaps can be placed between rows and columns in two |
|
* ways. If uniformed gaps are desired, the <code>setHGap</code> and <code> |
|
* setVGap</code> methods may be used. To vary the size of gaps, simply use |
|
* empty rows and columns with absolute sizes. Similiarly, to make a border |
|
* around a container that does not have insets, use empty rows and columns |
|
* along the edges of the container.</p> |
|
* |
|
* <b>Constraints</b> |
|
* |
|
* <p>Using TableLayout is a simple two step process. First, create a grid for |
|
* your container by specifying row and column sizes using either a TableLayout |
|
* constructor or the <code>insertRow</code> and <code>insertColumn</code> |
|
* methods. Second, add components to the cells formed by the rows and |
|
* columns.</p> |
|
* |
|
* <p>When adding a component to a container that uses TableLayout, you |
|
* specify the component's constraints that state which cells the component |
|
* will occupy and how the component will be aligned. The constraints |
|
* can be specified into two ways. The <code>TableLayoutConstraints</code> |
|
* class can be used to systematically specify the constraints. This is |
|
* useful to dynamic code, bean builders, and rapid application development |
|
* software.</p> |
|
* |
|
* <p>For manual coding, a quicker and easier way to specify constraints is with |
|
* a short string in the form "x1, y1, x2, y2, hAlign, vAlign" where (x1, y1) |
|
* identifies the top left cell (column x1, row y1) for the component and |
|
* (x2, y2) identfies the bottom right cell. x2 and y2 are optional. If they |
|
* are not specified, the component will occupy only one cell, (x1, y1). |
|
* hAlign and vAlign are also optional with default values of full |
|
* justification. Alignments may be spelt fully as in "LEFT" or abbreviated as |
|
* in "L". The text is not case sensitive, but it is recommended that uppercase |
|
* is used for two reasons. First, these text values are in essence constants. |
|
* Second, some fonts use the same glyphs for representing a lowercase L and |
|
* the number one. Ex., "l" vs. "1". Even fonts that do not will often use |
|
* similar glyphs so using uppercase avoids confusion.</p> |
|
* |
|
* <b>Dynamically altering the layout</b> |
|
* |
|
* <p>Rows and columns can be dynamically created, resized, and removed at any |
|
* time, even if the container is visible. Components will be shifted |
|
* appropriately as rows and columns are inserted or removed, just as cells |
|
* are shifted in a spreadsheet.</p> |
|
* |
|
* <p>Rows and columns can be made "hidden" or effectively invisible by setting |
|
* their size to zero. They can be shown again by setting their size back to |
|
* a non-zero value. This is very useful for toggle form elements without |
|
* having to remove individual components.</p> |
|
* |
|
* <b>Preferred sizes</b> |
|
* |
|
* <p>Often it is desireable to make a row or column just large enough to ensure |
|
* that all components contained partially or wholly in that row or column are |
|
* their preferred size. To make this easy, there is a constant called |
|
* <code>PREFERRED</code> that can be used to specify row or column sizes. |
|
* There is another constant called <code>MINIMUM</code> that does a similar |
|
* task using components' minimum sizes instead of their preferred sizes.</p> |
|
* |
|
* <p>There is no corresponding <code>MAXIMUM</code> constant for several |
|
* reasons. First, it is mathematically impossible to honor both the minimum |
|
* and maximum sizes of more than one component when conflicts arise. For |
|
* example, say components a and b are in the same row. If a's maximum height |
|
* is less than b's minimum height, then one of these constraints must be |
|
* violated. Since TableLayout is a complete, general Cartesian layout manager, |
|
* it would be possible to specify conflicting constraints if a <code>MAXIMUM |
|
* </code> constant existed.<p> |
|
* |
|
* <p>Second, the ability to make a component grow up to a maximum size is |
|
* primarily of interest to layout managers like <code>SpringLayout</code> that |
|
* have to balance the sizes of components because the presence of one component |
|
* affects the size of another. Other than the effect of preferred and minimum |
|
* size rows/columns, which are essentially convenient ways of specifying |
|
* absolute sizes, the existence and constraints of one component does not |
|
* affect any other components when using TableLayout. This is accomplished |
|
* because rows and columns are explicit in TableLayout.</p> |
|
* |
|
* <p>Third, the ability to constrain a component to its maximum size is |
|
* subsumed by the ability to constrain it to its preferred size, which is |
|
* precisely what happens when a component is aligned using anything but |
|
* full justification. In the case of full justification, the component's |
|
* maximum size is by definition unbounded.</p> |
|
* |
|
* <b>Example</b> |
|
* |
|
* <pre> |
|
* import java.awt.*; |
|
* import javax.swing.*; |
|
* import com.fr.design.core.layout.TableLayout; |
|
* <spc> |
|
* public class Preferred extends JFrame |
|
* { |
|
* <spc> |
|
* public static void main (String args[]) |
|
* { |
|
* new Preferred(); |
|
* } |
|
* <spc> |
|
* public Preferred () |
|
* { |
|
* super("The Power of Preferred Sizes"); |
|
* setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
|
* Container pane = getContentPane(); |
|
* <spc> |
|
* // b - border |
|
* // f - FILL |
|
* // p - PREFERRED |
|
* // vs - vertical space between labels and text fields |
|
* // vg - vertical gap between form elements |
|
* // hg - horizontal gap between form elements |
|
* <spc> |
|
* double b = 10; |
|
* double f = TableLayout.FILL; |
|
* double p = TableLayout.PREFERRED; |
|
* double vs = 5; |
|
* double vg = 10; |
|
* double hg = 10; |
|
* <spc> |
|
* double size[][] = |
|
* {{b, f, hg, p, hg, p, b}, |
|
* {b, p, vs, p, vg, p, vs, p, vg, p, vs, p, vg, p, b}}; |
|
* <spc> |
|
* TableLayout layout = new TableLayout(size); |
|
* pane.setLayout (layout); |
|
* <spc> |
|
* // Create all controls |
|
* UILabel labelName = new UILabel("Name"); |
|
* UILabel labelAddress = new UILabel("Address"); |
|
* UILabel labelCity = new UILabel("City"); |
|
* UILabel labelState = new UILabel("State"); |
|
* UILabel labelZip = new UILabel("Zip"); |
|
* <spc> |
|
* UITextField textfieldName = new UITextField(10); |
|
* UITextField textfieldAddress = new UITextField(20); |
|
* UITextField textfieldCity = new UITextField(10); |
|
* UITextField textfieldState = new UITextField(2); |
|
* UITextField textfieldZip = new UITextField(5); |
|
* <spc> |
|
* UIButton buttonOk = new UIButton("OK"); |
|
* UIButton buttonCancel = new UIButton("Cancel"); |
|
* JPanel panelButton = newJPanel(); |
|
* panelButton.add(buttonOk); |
|
* panelButton.add(buttonCancel); |
|
* <spc> |
|
* // Add all controls |
|
* pane.add(labelName, "1, 1, 5, 1"); |
|
* pane.add(textfieldName, "1, 3, 5, 3"); |
|
* pane.add(labelAddress, "1, 5, 5, 5"); |
|
* pane.add(textfieldAddress, "1, 7, 5, 7"); |
|
* pane.add(labelCity, "1, 9"); |
|
* pane.add(textfieldCity, "1, 11"); |
|
* pane.add(labelState, "3, 9"); |
|
* pane.add(textfieldState, "3, 11"); |
|
* pane.add(labelZip, "5, 9"); |
|
* pane.add(textfieldZip, "5, 11"); |
|
* pane.add(panelButton, "1, 13, 5, 13"); |
|
* <spc> |
|
* pack(); |
|
* show(); |
|
* } |
|
* } |
|
* </pre> |
|
* |
|
*/ |
|
|
|
public class TableLayout implements LayoutManager2, Serializable { |
|
/* |
|
Note: In this file, a cr refers to either a column or a row. cr[C] always |
|
means column and cr[R] always means row. A cr size is either a column |
|
width or a row Height. TableLayout views columns and rows as being |
|
conceptually symmetric. Therefore, much of the code applies to both |
|
columns and rows, and the use of the cr terminology eliminates redundancy. |
|
Also, for ease of reading, z always indicates a parameter whose value is |
|
either C or R. |
|
*/ |
|
/** Default row/column size */ |
|
protected static final double defaultSize[][] = {{}, {}}; |
|
|
|
/** Indicates a column */ |
|
protected static final int C = 0; |
|
|
|
/** Indicates a row */ |
|
protected static final int R = 1; |
|
|
|
/** Used to minimize reflection calls */ |
|
protected static boolean checkForComponentOrientationSupport = true; |
|
|
|
/** Method used to get component orientation while preserving compatability |
|
with earlier versions of java.awt.Container. Necessary for supporting |
|
older JDKs and MicroEdition versions of Java. */ |
|
protected static Method methodGetComponentOrientation; |
|
|
|
|
|
/** Sizes of crs expressed in absolute and relative terms */ |
|
protected double crSpec[][] = {null, null}; |
|
|
|
/** Sizes of crs in pixels */ |
|
protected int crSize[][] = {null, null}; |
|
|
|
/** Offsets of crs in pixels. The left boarder of column n is at |
|
crOffset[C][n] and the right boarder is at cr[C][n + 1] for all |
|
columns including the last one. crOffset[C].length = crSize[C].length + 1 */ |
|
protected int crOffset[][] = {null, null}; |
|
|
|
|
|
/** List of components and their sizes */ |
|
protected LinkedList<Entry> 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; |
|
|
|
/** Horizontal gap between columns */ |
|
protected int hGap; |
|
|
|
/** Vertical gap between rows */ |
|
protected int vGap; |
|
/** Indicates that the component is left justified in its cell */ |
|
public static final int LEFT = 0; |
|
/** Indicates that the component is top justified in its cell */ |
|
public static final int TOP = 0; |
|
/** Indicates that the component is centered in its cell */ |
|
public static final int CENTER = 1; |
|
/** Indicates that the component is full justified in its cell */ |
|
public static final int FULL = 2; |
|
/** Indicates that the component is bottom justified in its cell */ |
|
public static final int BOTTOM = 3; |
|
/** Indicates that the component is right justified in its cell */ |
|
public static final int RIGHT = 3; |
|
/** Indicates that the component is leading justified in its cell. |
|
Leading justification means components are left justified if their container |
|
is left-oriented and right justified if their container is right-oriented. |
|
Trailing justification is opposite. |
|
see java.awt.Component#getComponentOrientation */ |
|
public static final int LEADING = 4; |
|
/** Indicates that the component is trailing justified in its cell. |
|
Trailing justification means components are right justified if their |
|
container is left-oriented and left justified if their container is |
|
right-oriented. Leading justification is opposite. |
|
see java.awt.Component#getComponentOrientation */ |
|
public static final int TRAILING = 5; |
|
/** Indicates that the row/column should fill the available space */ |
|
public static final double FILL = -1.0; |
|
/** Indicates that the row/column should be allocated just enough space to |
|
accomidate the preferred size of all components contained completely within |
|
this row/column. */ |
|
public static final double PREFERRED = -2.0; |
|
/** Indicates that the row/column should be allocated just enough space to |
|
accomidate the minimum size of all components contained completely within |
|
this row/column. */ |
|
public static final double MINIMUM = -3.0; |
|
|
|
final double epsilon = 0.0000001; |
|
|
|
//****************************************************************************** |
|
//** Constructors *** |
|
//****************************************************************************** |
|
|
|
|
|
|
|
/** |
|
* Constructs an instance of TableLayout. This TableLayout will have no columns |
|
* or rows. This constructor is most useful for bean-oriented programming and |
|
* dynamically adding columns and rows. |
|
*/ |
|
|
|
public TableLayout() { |
|
init(defaultSize[C], defaultSize[R]); |
|
} |
|
|
|
|
|
/** |
|
* 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 columns and rows and nothing else is specified |
|
if ((size != null) && (size.length == 2)) |
|
init(size[C], size[R]); |
|
else |
|
throw new IllegalArgumentException |
|
("Parameter size should be an array, a[2], where a[0] is the " + |
|
"is an array of column widths and a[1] is an array or row " + |
|
"heights."); |
|
} |
|
|
|
|
|
/** |
|
* Constructs an instance of TableLayout. |
|
* |
|
* @param col widths of columns in the format, {{col0, col1, col2, ..., colN} |
|
* @param row heights of rows in the format, {{row0, row1, row2, ..., rowN} |
|
*/ |
|
|
|
public TableLayout(double[] col, double[] row) { |
|
init(col, row); |
|
} |
|
|
|
|
|
/** |
|
* Initializes the TableLayout for all constructors. |
|
* |
|
* @param col widths of columns in the format, {{col0, col1, col2, ..., colN} |
|
* @param row heights of rows in the format, {{row0, row1, row2, ..., rowN} |
|
*/ |
|
|
|
protected void init(double[] col, double[] row) { |
|
// Check parameters |
|
if (col == null) |
|
throw new IllegalArgumentException("Parameter col cannot be null"); |
|
|
|
if (row == null) |
|
throw new IllegalArgumentException("Parameter row cannot be null"); |
|
|
|
// Create new rows and columns |
|
crSpec[C] = new double[col.length]; |
|
crSpec[R] = new double[row.length]; |
|
|
|
// Copy rows and columns |
|
System.arraycopy(col, 0, crSpec[C], 0, crSpec[C].length); |
|
System.arraycopy(row, 0, crSpec[R], 0, crSpec[R].length); |
|
|
|
// Make sure rows and columns are valid |
|
for (int counter = 0; counter < crSpec[C].length; counter++) |
|
if ((crSpec[C][counter] < 0.0) && |
|
(crSpec[C][counter] != FILL) && |
|
(crSpec[C][counter] != PREFERRED) && |
|
(crSpec[C][counter] != MINIMUM)) { |
|
crSpec[C][counter] = 0.0; |
|
} |
|
|
|
for (int counter = 0; counter < crSpec[R].length; counter++) |
|
if ((crSpec[R][counter] < 0.0) && |
|
(crSpec[R][counter] != FILL) && |
|
(crSpec[R][counter] != PREFERRED) && |
|
(crSpec[R][counter] != MINIMUM)) { |
|
crSpec[R][counter] = 0.0; |
|
} |
|
|
|
// Create an empty list of components |
|
list = new LinkedList<Entry>(); |
|
|
|
// 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<Entry> iterator = list.listIterator(0); |
|
|
|
while (iterator.hasNext()) { |
|
Entry entry = iterator.next(); |
|
|
|
if (entry.component == component) |
|
return new TableLayoutConstraints |
|
(entry.cr1[C], entry.cr1[R], entry.cr2[C], entry.cr2[R], |
|
entry.alignment[C], entry.alignment[R]); |
|
} |
|
|
|
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. |
|
*/ |
|
|
|
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<Entry> iterator = list.listIterator(0); |
|
|
|
while (iterator.hasNext()) { |
|
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 widths of each of the columns |
|
* |
|
* @see #getColumn |
|
*/ |
|
|
|
public void setColumn(double column[]) { |
|
setCr(C, column); |
|
} |
|
|
|
|
|
/** |
|
* 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 heights of each of the rows. This parameter cannot be null. |
|
* |
|
* @see #getRow |
|
*/ |
|
|
|
public void setRow(double row[]) { |
|
setCr(R, row); |
|
} |
|
|
|
|
|
/** |
|
* Sets the sizes of rows or columns for the methods setRow or setColumn. |
|
* |
|
* @param z indicates row or column |
|
* @param size new cr size |
|
*/ |
|
|
|
protected void setCr(int z, double size[]) { |
|
// Copy crs |
|
crSpec[z] = new double[size.length]; |
|
System.arraycopy(size, 0, crSpec[z], 0, crSpec[z].length); |
|
|
|
// Make sure rows are valid |
|
for (int counter = 0; counter < crSpec[z].length; counter++) |
|
if ((crSpec[z][counter] < 0.0) && |
|
(crSpec[z][counter] != FILL) && |
|
(crSpec[z][counter] != PREFERRED) && |
|
(crSpec[z][counter] != MINIMUM)) { |
|
crSpec[z][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) { |
|
setCr(C, i, size); |
|
} |
|
|
|
|
|
/** |
|
* 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) { |
|
setCr(R, i, size); |
|
} |
|
|
|
|
|
/** |
|
* Sets the sizes of rows or columns for the methods setRow or setColumn. |
|
* |
|
* @param z indicates row or column |
|
* @param i indicates which cr to resize |
|
* @param size new cr size |
|
*/ |
|
|
|
|
|
protected void setCr(int z, 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 |
|
crSpec[z][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[crSpec[C].length]; |
|
System.arraycopy(crSpec[C], 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[crSpec[R].length]; |
|
System.arraycopy(crSpec[R], 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 crSpec[C][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 crSpec[R][i]; |
|
} |
|
|
|
|
|
/** |
|
* Gets the number of columns in this layout. |
|
* |
|
* @return the number of columns |
|
*/ |
|
|
|
public int getNumColumn() { |
|
return crSpec[C].length; |
|
} |
|
|
|
|
|
/** |
|
* Gets the number of rows in this layout. |
|
* |
|
* @return the number of rows |
|
*/ |
|
|
|
public int getNumRow() { |
|
return crSpec[R].length; |
|
} |
|
|
|
|
|
/** |
|
* Gets the horizontal gap between colunns. |
|
* |
|
* @return the horizontal gap in pixels |
|
*/ |
|
|
|
public int getHGap() { |
|
return hGap; |
|
} |
|
|
|
|
|
/** |
|
* Gets the vertical gap between rows. |
|
* |
|
* @return the vertical gap in pixels |
|
*/ |
|
|
|
public int getVGap() { |
|
return vGap; |
|
} |
|
|
|
|
|
/** |
|
* Sets the horizontal gap between colunns. |
|
* |
|
* @param hGap the horizontal gap in pixels |
|
*/ |
|
|
|
public void setHGap(int hGap) { |
|
if (hGap >= 0) |
|
this.hGap = hGap; |
|
else |
|
throw new IllegalArgumentException |
|
("Parameter hGap must be non-negative."); |
|
} |
|
|
|
|
|
/** |
|
* Sets the vertical gap between rows. |
|
* |
|
* @param vGap the horizontal gap in pixels |
|
*/ |
|
|
|
public void setVGap(int vGap) { |
|
if (vGap >= 0) |
|
this.vGap = vGap; |
|
else |
|
throw new IllegalArgumentException |
|
("Parameter vGap must be non-negative."); |
|
} |
|
|
|
|
|
|
|
//****************************************************************************** |
|
//** 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) { |
|
insertCr(C, i, size); |
|
} |
|
|
|
|
|
/** |
|
* 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 row |
|
* @param size size of the row to be inserted |
|
* |
|
* @see #setRow |
|
* @see #deleteRow |
|
*/ |
|
|
|
public void insertRow(int i, double size) { |
|
insertCr(R, i, size); |
|
} |
|
|
|
|
|
/** |
|
* Inserts a cr for the methods insertRow or insertColumn. |
|
* |
|
* @param z indicates row or column |
|
* @param i zero-based index at which to insert the cr |
|
* @param size size of cr being inserted |
|
*/ |
|
|
|
public void insertCr(int z, int i, double size) { |
|
// Make sure position is valid |
|
if ((i < 0) || (i > crSpec[z].length)) |
|
throw new IllegalArgumentException |
|
("Parameter i is invalid. i = " + i + ". Valid range is [0, " + |
|
crSpec[z].length + "]."); |
|
|
|
// Make sure row size is valid |
|
if ((size < 0.0) && |
|
(size != FILL) && |
|
(size != PREFERRED) && |
|
(size != MINIMUM)) { |
|
size = 0.0; |
|
} |
|
|
|
// Copy crs |
|
double cr[] = new double[crSpec[z].length + 1]; |
|
System.arraycopy(crSpec[z], 0, cr, 0, i); |
|
System.arraycopy(crSpec[z], i, cr, i + 1, crSpec[z].length - i); |
|
|
|
// Insert cr |
|
cr[i] = size; |
|
crSpec[z] = cr; |
|
|
|
// Move all components that are below the new cr |
|
ListIterator<Entry> iterator = list.listIterator(0); |
|
|
|
while (iterator.hasNext()) { |
|
// Get next entry |
|
Entry entry = iterator.next(); |
|
|
|
// Is the first cr below the new cr |
|
if (entry.cr1[z] >= i) |
|
// Move first cr |
|
entry.cr1[z]++; |
|
|
|
// Is the second cr below the new cr |
|
if (entry.cr2[z] >= i) |
|
// Move second cr |
|
entry.cr2[z]++; |
|
} |
|
|
|
// 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) { |
|
deleteCr(C, i); |
|
} |
|
|
|
|
|
/** |
|
* 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 row to delete |
|
* |
|
* @see #setRow |
|
* @see #deleteRow |
|
*/ |
|
|
|
public void deleteRow(int i) { |
|
deleteCr(R, i); |
|
} |
|
|
|
|
|
/** |
|
* Deletes a cr for the methods deleteRow or deleteColumn. |
|
* |
|
* @param z indicates row or column |
|
* @param i zero-based index of cr to delete |
|
*/ |
|
|
|
protected void deleteCr(int z, int i) { |
|
// Make sure position is valid |
|
if ((i < 0) || (i >= crSpec[z].length)) |
|
throw new IllegalArgumentException |
|
("Parameter i is invalid. i = " + i + ". Valid range is [0, " + |
|
(crSpec[z].length - 1) + "]."); |
|
|
|
// Copy rows |
|
double cr[] = new double[crSpec[z].length - 1]; |
|
System.arraycopy(crSpec[z], 0, cr, 0, i); |
|
System.arraycopy(crSpec[z], i + 1, cr, i, crSpec[z].length - i - 1); |
|
|
|
// Delete row |
|
crSpec[z] = cr; |
|
|
|
// Move all components that are to below the row deleted |
|
ListIterator<Entry> iterator = list.listIterator(0); |
|
|
|
while (iterator.hasNext()) { |
|
// Get next entry |
|
Entry entry = iterator.next(); |
|
|
|
// Is the first row below the new row |
|
if (entry.cr1[z] > i) |
|
// Move first row |
|
entry.cr1[z]--; |
|
|
|
// Is the second row below the new row |
|
if (entry.cr2[z] > i) |
|
// Move second row |
|
entry.cr2[z]--; |
|
} |
|
|
|
// 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 (crSpec[C].length > 0) { |
|
for (counter = 0; counter < crSpec[C].length - 1; counter++) |
|
value += crSpec[C][counter] + ", "; |
|
|
|
value += crSpec[C][crSpec[C].length - 1] + "}, {"; |
|
} else |
|
value += "}, {"; |
|
|
|
if (crSpec[R].length > 0) { |
|
for (counter = 0; counter < crSpec[R].length - 1; counter++) |
|
value += crSpec[R][counter] + ", "; |
|
|
|
value += crSpec[R][crSpec[R].length - 1] + "}}"; |
|
} else |
|
value += "}}"; |
|
|
|
return value; |
|
} |
|
|
|
|
|
/** |
|
* Determines whether or not there are any components with invalid constraints. |
|
* An invalid constraint is one that references a non-existing 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. This method is useful for |
|
* debugging. |
|
* |
|
* @return a list of TableLayout.Entry instances refering to the invalid |
|
* constraints and corresponding components |
|
* |
|
* @see #getOverlappingEntry |
|
*/ |
|
|
|
public java.util.List<Entry> getInvalidEntry() { |
|
LinkedList<Entry> listInvalid = new LinkedList<Entry>(); |
|
|
|
try { |
|
ListIterator<Entry> iterator = list.listIterator(0); |
|
|
|
while (iterator.hasNext()) { |
|
Entry entry = iterator.next(); |
|
|
|
if ((entry.cr1[R] < 0) || (entry.cr1[C] < 0) || |
|
(entry.cr2[R] >= crSpec[R].length) || |
|
(entry.cr2[C] >= crSpec[C].length)) { |
|
listInvalid.add(entry.copy()); |
|
} |
|
} |
|
} catch (CloneNotSupportedException error) { |
|
throw new RuntimeException("Unexpected CloneNotSupportedException"); |
|
} |
|
|
|
return listInvalid; |
|
} |
|
|
|
|
|
/** |
|
* Gets a list of overlapping components and their constraints. Two |
|
* components overlap if they cover at least one common cell. This method is |
|
* useful for debugging. |
|
* |
|
* @return a list of zero or more TableLayout.Entry instances |
|
* |
|
* @see #getInvalidEntry |
|
*/ |
|
|
|
public java.util.List<Entry> getOverlappingEntry() { |
|
LinkedList<Entry> listOverlapping = new LinkedList<Entry>(); |
|
|
|
try { |
|
// Count contraints |
|
int numEntry = list.size(); |
|
|
|
// If there are no components, they can't be overlapping |
|
if (numEntry == 0) |
|
return listOverlapping; |
|
|
|
// Put entries in an array |
|
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].cr1[C] >= entry[knowUnique].cr1[C]) && |
|
(entry[checking].cr1[C] <= entry[knowUnique].cr2[C]) && |
|
(entry[checking].cr1[R] >= entry[knowUnique].cr1[R]) && |
|
(entry[checking].cr1[R] <= entry[knowUnique].cr2[R]) |
|
) |
|
|| |
|
( |
|
(entry[checking].cr2[C] >= entry[knowUnique].cr1[C]) && |
|
(entry[checking].cr2[C] <= entry[knowUnique].cr2[C]) && |
|
(entry[checking].cr2[R] >= entry[knowUnique].cr1[R]) && |
|
(entry[checking].cr2[R] <= entry[knowUnique].cr2[R]) |
|
) |
|
) { |
|
listOverlapping.add(entry[checking].copy()); |
|
} |
|
} catch (CloneNotSupportedException error) { |
|
throw new RuntimeException("Unexpected CloneNotSupportedException"); |
|
} |
|
|
|
return listOverlapping; |
|
} |
|
|
|
|
|
|
|
//****************************************************************************** |
|
//** Calculation methods *** |
|
//****************************************************************************** |
|
|
|
|
|
|
|
/** |
|
* Calculates the sizes of the rows and columns based on the absolute and |
|
* relative sizes specified in <code>crSpec[R]</code> and <code>crSpec[C]</code> |
|
* and the size of the container. The result is stored in <code>crSize[R]</code> |
|
* and <code>crSize[C]</code>. |
|
* |
|
* @param container container using this TableLayout |
|
*/ |
|
|
|
protected void calculateSize(Container container) { |
|
// Get the container's insets |
|
Insets inset = container.getInsets(); |
|
|
|
// Get the size of the container's available space |
|
Dimension d = container.getSize(); |
|
int availableWidth = d.width - inset.left - inset.right; |
|
int availableHeight = d.height - inset.top - inset.bottom; |
|
|
|
// Compensate for horiztonal and vertical gaps |
|
if (crSpec[C].length > 0) |
|
availableWidth -= hGap * (crSpec[C].length - 1); |
|
|
|
if (crSpec[R].length > 0) |
|
availableHeight -= vGap * (crSpec[R].length - 1); |
|
|
|
// Create array to hold actual sizes in pixels |
|
crSize[C] = new int[crSpec[C].length]; |
|
crSize[R] = new int[crSpec[R].length]; |
|
|
|
// Assign absolute sizes (must be done before assignPrefMinSize) |
|
availableWidth = assignAbsoluteSize(C, availableWidth); |
|
availableHeight = assignAbsoluteSize(R, availableHeight); |
|
|
|
// Assign preferred and minimum sizes (must be done after assignAbsoluteSize) |
|
availableWidth = assignPrefMinSize(C, availableWidth, MINIMUM); |
|
availableWidth = assignPrefMinSize(C, availableWidth, PREFERRED); |
|
availableHeight = assignPrefMinSize(R, availableHeight, MINIMUM); |
|
availableHeight = assignPrefMinSize(R, availableHeight, PREFERRED); |
|
|
|
// Assign relative sizes |
|
availableWidth = assignRelativeSize(C, availableWidth); |
|
availableHeight = assignRelativeSize(R, availableHeight); |
|
|
|
// Assign fill sizes |
|
assignFillSize(C, availableWidth); |
|
assignFillSize(R, availableHeight); |
|
|
|
// Calculate cr offsets for effeciency |
|
calculateOffset(C, inset); |
|
calculateOffset(R, inset); |
|
|
|
// Indicate that the size of the cells are known for the container's |
|
// current size |
|
dirty = false; |
|
oldWidth = d.width; |
|
oldHeight = d.height; |
|
} |
|
|
|
|
|
/** |
|
* Assigns absolute sizes. |
|
* |
|
* @param z indicates row or column |
|
* @param availableSize amount of space available in the container |
|
* |
|
* @return the amount of space available after absolute crs have been assigned |
|
* sizes |
|
*/ |
|
|
|
protected int assignAbsoluteSize(int z, int availableSize) { |
|
int numCr = crSpec[z].length; |
|
|
|
for (int counter = 0; counter < numCr; counter++) |
|
if ((crSpec[z][counter] >= 1.0) || (crSpec[z][counter] == 0.0)) { |
|
crSize[z][counter] = (int) (crSpec[z][counter] + 0.5); |
|
availableSize -= crSize[z][counter]; |
|
} |
|
|
|
return availableSize; |
|
} |
|
|
|
|
|
/** |
|
* Assigns relative sizes. |
|
* |
|
* @param z indicates row or column |
|
* @param availableSize amount of space available in the container |
|
* |
|
* @return the amount of space available after relative crs have been assigned |
|
* sizes |
|
*/ |
|
|
|
protected int assignRelativeSize(int z, int availableSize) { |
|
int relativeSize = (availableSize < 0) ? 0 : availableSize; |
|
int numCr = crSpec[z].length; |
|
|
|
for (int counter = 0; counter < numCr; counter++) |
|
if ((crSpec[z][counter] > 0.0) && (crSpec[z][counter] < 1.0)) { |
|
crSize[z][counter] = |
|
(int) (crSpec[z][counter] * relativeSize + 0.5); |
|
|
|
availableSize -= crSize[z][counter]; |
|
} |
|
|
|
return availableSize; |
|
} |
|
|
|
|
|
/** |
|
* Assigns FILL sizes. |
|
* |
|
* @param z indicates row or column |
|
* @param availableSize amount of space available in the container |
|
*/ |
|
@SuppressWarnings("squid:S3518") |
|
protected void assignFillSize(int z, int availableSize) { |
|
// Skip if there is no more space to allocate |
|
if (availableSize <= 0) |
|
return; |
|
|
|
// Count the number of "fill" cells |
|
int numFillSize = 0; |
|
int numCr = crSpec[z].length; |
|
|
|
for (int counter = 0; counter < numCr; counter++) |
|
if (crSpec[z][counter] == FILL) |
|
numFillSize++; |
|
|
|
// If numFillSize is zero, the if statement below 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 slackSize = availableSize; |
|
|
|
// Assign "fill" cells equal amounts of the remaining space |
|
for (int counter = 0; counter < numCr; counter++) |
|
if (crSpec[z][counter] == FILL) { |
|
crSize[z][counter] = availableSize / numFillSize; |
|
slackSize -= crSize[z][counter]; |
|
} |
|
|
|
// Assign one pixel of slack to each FILL cr, starting at the last one, |
|
// until all slack has been consumed |
|
for (int counter = numCr - 1; (counter >= 0) && (slackSize > 0); counter--) { |
|
if (crSpec[z][counter] == FILL) { |
|
crSize[z][counter]++; |
|
slackSize--; |
|
} |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Calculates the offset of each cr. |
|
* |
|
* @param z indicates row or column |
|
*/ |
|
|
|
protected void calculateOffset(int z, Insets inset) { |
|
int numCr = crSpec[z].length; |
|
|
|
crOffset[z] = new int[numCr + 1]; |
|
crOffset[z][0] = (z == C) ? inset.left : inset.top; |
|
|
|
for (int counter = 0; counter < numCr; counter++) |
|
crOffset[z][counter + 1] = |
|
crOffset[z][counter] + crSize[z][counter]; |
|
} |
|
|
|
|
|
/** |
|
* Assigned sizes to preferred and minimum size columns and rows. This |
|
* reduces the available width and height. Minimum widths/heights must be |
|
* calculated first because they affect preferred widths/heights, but not vice |
|
* versa. The end result is that any component contained wholly or partly in |
|
* a column/row of minimum/preferred width or height will get at least its |
|
* minimum/preferred width or height, respectively. |
|
* |
|
* @param z indicates row or column |
|
* @param availableSize amount of space available in the container |
|
* @param typeOfSize indicates preferred or minimum |
|
* |
|
* @return the amount of space available after absolute crs have been assigned |
|
* sizes |
|
*/ |
|
@SuppressWarnings("squid:S3518") |
|
protected int assignPrefMinSize |
|
(int z, int availableSize, double typeOfSize) { |
|
// Get variables referring to columns or rows (crs) |
|
int numCr = crSpec[z].length; |
|
|
|
// Address every cr |
|
for (int counter = 0; counter < numCr; counter++) |
|
// Is the current cr a preferred/minimum (based on typeOfSize) size |
|
if (crSpec[z][counter] == typeOfSize) { |
|
// Assume a maximum width of zero |
|
int maxSize = 0; |
|
|
|
// Find maximum preferred/min width of all components completely |
|
// or partially contained within this cr |
|
ListIterator<Entry> iterator = list.listIterator(0); |
|
|
|
nextComponent: |
|
while (iterator.hasNext()) { |
|
Entry entry = iterator.next(); |
|
|
|
// Skip invalid entries |
|
if ((entry.cr1[z] < 0) || (entry.cr2[z] >= numCr)) |
|
continue nextComponent; |
|
|
|
// Find the maximum desired size of this cr based on all crs |
|
// the current component occupies |
|
if ((entry.cr1[z] <= counter) && (entry.cr2[z] >= counter)) { |
|
// Setup size and number of adjustable crs |
|
Dimension p = (typeOfSize == PREFERRED) ? |
|
entry.component.getPreferredSize() : |
|
entry.component.getMinimumSize(); |
|
|
|
int size = (p == null) ? 0 : |
|
((z == C) ? p.width : p.height); |
|
int numAdjustable = 0; |
|
|
|
// Calculate for preferred size |
|
if (typeOfSize == PREFERRED) |
|
// Consider all crs this component occupies |
|
for (int entryCr = entry.cr1[z]; |
|
entryCr <= entry.cr2[z]; entryCr++) { |
|
// Subtract absolute, relative, and minumum cr |
|
// sizes, which have already been calculated |
|
if ((crSpec[z][entryCr] >= 0.0) || |
|
(crSpec[z][entryCr] == MINIMUM)) { |
|
size -= crSize[z][entryCr]; |
|
} |
|
// Count preferred/min width columns |
|
else if (crSpec[z][entryCr] == PREFERRED) |
|
numAdjustable++; |
|
// Skip any component that occupies a fill cr |
|
// because the fill should fulfill the size |
|
// requirements |
|
else if (crSpec[z][entryCr] == FILL) |
|
continue nextComponent; |
|
} |
|
// Calculate for minimum size |
|
else |
|
// Consider all crs this component occupies |
|
for (int entryCr = entry.cr1[z]; |
|
entryCr <= entry.cr2[z]; entryCr++) { |
|
// Subtract absolute and relative cr sizes, which |
|
// have already been calculated |
|
if (crSpec[z][entryCr] >= 0.0) |
|
size -= crSize[z][entryCr]; |
|
// Count preferred/min width columns |
|
else if ((crSpec[z][entryCr] == PREFERRED) || |
|
(crSpec[z][entryCr] == MINIMUM)) { |
|
numAdjustable++; |
|
} |
|
// Skip any component that occupies a fill cr |
|
// because the fill should fulfill the size |
|
// requirements |
|
else if (crSpec[z][entryCr] == FILL) |
|
continue nextComponent; |
|
} |
|
|
|
// Divide the size evenly among the adjustable crs |
|
size = (int) Math.ceil(size / (double) numAdjustable); |
|
|
|
// Take the maximumn size |
|
if (maxSize < size) |
|
maxSize = size; |
|
} |
|
} |
|
|
|
// Assign preferred size |
|
crSize[z][counter] = maxSize; |
|
|
|
// Reduce available size |
|
availableSize -= maxSize; |
|
} |
|
|
|
return availableSize; |
|
} |
|
|
|
|
|
|
|
//****************************************************************************** |
|
//** 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) { |
|
// 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 component orientation and insets |
|
ComponentOrientation co = getComponentOrientation(container); |
|
boolean isRightToLeft = (co != null) && !co.isLeftToRight(); |
|
Insets insets = container.getInsets(); |
|
|
|
// Get components |
|
Component component[] = container.getComponents(); |
|
|
|
// Layout components |
|
for (int counter = 0; counter < component.length; counter++) { |
|
try { |
|
// Get the entry for the next component |
|
ListIterator<Entry> iterator = list.listIterator(0); |
|
Entry entry = null; |
|
|
|
while (iterator.hasNext()) { |
|
entry = iterator.next(); |
|
|
|
if (entry.component == component[counter]) |
|
break; |
|
else |
|
entry = null; |
|
} |
|
|
|
// Skip any components that have not been place in a specific cell, |
|
// setting the skip component's bounds to zero |
|
if (entry == null) { |
|
component[counter].setBounds(0, 0, 0, 0); |
|
continue; |
|
} |
|
|
|
// 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.alignment[C] != FULL) || (entry.alignment[R] != FULL)) { |
|
Dimension preferredSize = |
|
component[counter].getPreferredSize(); |
|
|
|
preferredWidth = preferredSize.width; |
|
preferredHeight = preferredSize.height; |
|
} |
|
|
|
// Calculate the coordinates and size of the component |
|
int value[] = calculateSizeAndOffset(entry, preferredWidth, true); |
|
int x = value[0]; |
|
int w = value[1]; |
|
value = calculateSizeAndOffset(entry, preferredHeight, false); |
|
int y = value[0]; |
|
int h = value[1]; |
|
|
|
// Compensate for component orientation. |
|
if (isRightToLeft) |
|
x = d.width - x - w + insets.left - insets.right; |
|
|
|
// Move and resize component |
|
component[counter].setBounds(x, y, w, h); |
|
} catch (Exception error) { |
|
// If any error occurs, set the bounds of this component to zero |
|
// and continue |
|
component[counter].setBounds(0, 0, 0, 0); |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Gets the container's component orientation. If a JDK that does not support |
|
* component orientation is being used, then null is returned. |
|
* |
|
* @param container Container whose orientation is being queried |
|
* |
|
* @return the container's orientation or null if no orientation is supported |
|
*/ |
|
|
|
protected ComponentOrientation getComponentOrientation(Container container) { |
|
// This method is implemented to only get the class and method objects |
|
// once so as to reduce expensive reflection operations. If the reflection |
|
// fails, then component orientation is not supported. |
|
|
|
ComponentOrientation co = null; |
|
|
|
try { |
|
if (checkForComponentOrientationSupport) { |
|
methodGetComponentOrientation = |
|
GeneralUtils.classForName("java.awt.Container").getMethod |
|
("getComponentOrientation", new Class[0]); |
|
|
|
checkForComponentOrientationSupport = false; |
|
} |
|
|
|
if (methodGetComponentOrientation != null) { |
|
co = (ComponentOrientation) |
|
methodGetComponentOrientation.invoke(container, new Object[0]); |
|
} |
|
} catch (Exception e) { |
|
} |
|
|
|
return co; |
|
} |
|
|
|
|
|
/** |
|
* Calculates the vertical/horizontal offset and size of a component. |
|
* |
|
* @param entry entry containing component and contraints |
|
* @param preferredSize previously calculated preferred width/height of |
|
* component |
|
* @param isColumn if true, this method is being called to calculate |
|
* the offset/size of a column. if false,... of a row. |
|
* |
|
* @return an array, a, of two integers such that a[0] is the offset and |
|
* a[1] is the size |
|
*/ |
|
|
|
protected int[] calculateSizeAndOffset |
|
(Entry entry, int preferredSize, boolean isColumn) { |
|
// Get references to cr properties |
|
int crOffset[] = isColumn ? this.crOffset[C] : this.crOffset[R]; |
|
int entryAlignment = isColumn ? entry.alignment[C] : entry.alignment[R]; |
|
|
|
// Determine cell set size |
|
int cellSetSize = isColumn ? |
|
crOffset[entry.cr2[C] + 1] - crOffset[entry.cr1[C]] : |
|
crOffset[entry.cr2[R] + 1] - crOffset[entry.cr1[R]]; |
|
|
|
// Determine the size of the component |
|
int size; |
|
|
|
if ((entryAlignment == FULL) || (cellSetSize < preferredSize)) |
|
size = cellSetSize; |
|
else |
|
size = preferredSize; |
|
|
|
// Since the component orientation is adjusted for in the layoutContainer |
|
// method, we can treat leading justification as left justification and |
|
// trailing justification as right justification. |
|
if (isColumn && (entryAlignment == LEADING)) |
|
entryAlignment = LEFT; |
|
|
|
if (isColumn && (entryAlignment == TRAILING)) |
|
entryAlignment = RIGHT; |
|
|
|
// Determine offset |
|
int offset; |
|
|
|
switch (entryAlignment) { |
|
case LEFT: // Align left/top side along left edge of cell |
|
offset = crOffset[isColumn ? entry.cr1[C] : entry.cr1[R]]; |
|
break; |
|
|
|
case RIGHT: // Align right/bottom side along right edge of cell |
|
offset = |
|
crOffset[(isColumn ? entry.cr2[C] : entry.cr2[R]) + 1] - size; |
|
break; |
|
|
|
case CENTER: // Center justify component |
|
offset = crOffset[isColumn ? entry.cr1[C] : entry.cr1[R]] + |
|
((cellSetSize - size) >> 1); |
|
break; |
|
|
|
case FULL: // Align left/top side along left/top edge of cell |
|
offset = crOffset[isColumn ? entry.cr1[C] : entry.cr1[R]]; |
|
break; |
|
|
|
default : // This is a never should happen case, but just in case |
|
offset = 0; |
|
} |
|
|
|
// Compensate for gaps |
|
if (isColumn) { |
|
offset += hGap * entry.cr1[C]; |
|
int cumlativeGap = hGap * (entry.cr2[C] - entry.cr1[C]); |
|
|
|
switch (entryAlignment) { |
|
case RIGHT: |
|
offset += cumlativeGap; |
|
break; |
|
|
|
case CENTER: |
|
offset += cumlativeGap >> 1; |
|
break; |
|
|
|
case FULL: |
|
size += cumlativeGap; |
|
break; |
|
} |
|
} else { |
|
offset += vGap * entry.cr1[R]; |
|
int cumlativeGap = vGap * (entry.cr2[R] - entry.cr1[R]); |
|
|
|
switch (entryAlignment) { |
|
case BOTTOM: |
|
offset += cumlativeGap; |
|
break; |
|
|
|
case CENTER: |
|
offset += cumlativeGap >> 1; |
|
break; |
|
|
|
case FULL: |
|
size += cumlativeGap; |
|
break; |
|
} |
|
} |
|
|
|
// Package return values |
|
int value[] = {offset, size}; |
|
return value; |
|
} |
|
|
|
|
|
/** |
|
* 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) { |
|
return calculateLayoutSize(container, PREFERRED); |
|
} |
|
|
|
|
|
/** |
|
* 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) { |
|
return calculateLayoutSize(container, MINIMUM); |
|
} |
|
|
|
|
|
/** |
|
* Calculates the preferred or minimum size for the methods preferredLayoutSize |
|
* and minimumLayoutSize. |
|
* |
|
* @param container container whose size is being calculated |
|
* @param typeOfSize indicates preferred or minimum |
|
* |
|
* @return a dimension indicating the container's preferred or minimum size |
|
*/ |
|
|
|
protected Dimension calculateLayoutSize(Container container, double typeOfSize) { |
|
// Get preferred/minimum sizes |
|
Entry entryList[] = list.toArray(new Entry[list.size()]); |
|
int numEntry = entryList.length; |
|
Dimension prefMinSize[] = new Dimension[numEntry]; |
|
|
|
for (int i = 0; i < numEntry; i++) |
|
prefMinSize[i] = (typeOfSize == PREFERRED) ? |
|
entryList[i].component.getPreferredSize() : |
|
entryList[i].component.getMinimumSize(); |
|
|
|
// Calculate sizes |
|
int width = |
|
calculateLayoutSize(container, C, typeOfSize, entryList, prefMinSize); |
|
|
|
int height = |
|
calculateLayoutSize(container, R, typeOfSize, entryList, prefMinSize); |
|
|
|
// Compensate for container's insets |
|
Insets inset = container.getInsets(); |
|
width += inset.left + inset.right; |
|
height += inset.top + inset.bottom; |
|
|
|
return new Dimension(width, height); |
|
} |
|
|
|
|
|
/** |
|
* Calculates the preferred or minimum size for the method |
|
* calculateLayoutSize(Container container, double typeOfSize). This method |
|
* is passed the preferred/minimum sizes of the components so that the |
|
* potentially expensive methods getPreferredSize()/getMinimumSize() are not |
|
* called twice for the same component. |
|
* |
|
* @param container container whose size is being calculated |
|
* @param z indicates row or column |
|
* @param typeOfSize indicates preferred or minimum |
|
* @param entryList list of Entry objects |
|
* @param prefMinSize list of preferred or minimum sizes |
|
* |
|
* @return a dimension indicating the container's preferred or minimum size |
|
*/ |
|
@SuppressWarnings("squid:S3518") |
|
protected int calculateLayoutSize |
|
(Container container, int z, double typeOfSize, Entry entryList[], |
|
Dimension prefMinSize[]) { |
|
Dimension size; // Preferred/minimum size of current component |
|
int scaledSize = 0; // Preferred/minimum size of scaled components |
|
int temp; // Temporary variable used to compare sizes |
|
int counter; // Counting variable |
|
|
|
// Get number of crs |
|
int numCr = crSpec[z].length; |
|
|
|
// Determine percentage of space allocated to fill components. This is |
|
// one minus the sum of all scalable components. |
|
double fillSizeRatio = 1.0; |
|
int numFillSize = 0; |
|
|
|
for (counter = 0; counter < numCr; counter++) |
|
if ((crSpec[z][counter] > 0.0) && (crSpec[z][counter] < 1.0)) |
|
fillSizeRatio -= crSpec[z][counter]; |
|
else if (crSpec[z][counter] == FILL) |
|
numFillSize++; |
|
|
|
// Adjust fill ratios to reflect number of fill rows/columns |
|
if (numFillSize > 1) |
|
fillSizeRatio /= numFillSize; |
|
|
|
// Cap fill ratio bottoms to 0.0 |
|
if (fillSizeRatio < 0.0) |
|
fillSizeRatio = 0.0; |
|
|
|
// Create array to hold actual sizes in pixels |
|
crSize[z] = new int[numCr]; |
|
|
|
// Assign absolute sizes (must be done before assignPrefMinSize) |
|
// This is done to calculate absolute cr sizes |
|
assignAbsoluteSize(z, 0); |
|
|
|
// Assign preferred and minimum sizes (must be done after assignAbsoluteSize) |
|
// This is done to calculate preferred/minimum cr sizes |
|
assignPrefMinSize(z, 0, MINIMUM); |
|
assignPrefMinSize(z, 0, PREFERRED); |
|
|
|
int crPrefMin[] = new int[numCr]; |
|
|
|
for (counter = 0; counter < numCr; counter++) |
|
if ((crSpec[z][counter] == PREFERRED) || |
|
(crSpec[z][counter] == MINIMUM)) { |
|
crPrefMin[counter] = crSize[z][counter]; |
|
} |
|
|
|
// Find maximum preferred/minimum size of all scaled components |
|
int numColumn = crSpec[C].length; |
|
int numRow = crSpec[R].length; |
|
int numEntry = entryList.length; |
|
|
|
for (int entryCounter = 0; entryCounter < numEntry; entryCounter++) { |
|
// Get next entry |
|
Entry entry = entryList[entryCounter]; |
|
|
|
// Make sure entry is in valid rows and columns |
|
if ((entry.cr1[C] < 0) || (entry.cr1[C] >= numColumn) || |
|
(entry.cr2[C] >= numColumn) || |
|
(entry.cr1[R] < 0) || (entry.cr1[R] >= numRow) || |
|
(entry.cr2[R] >= numRow)) { |
|
// Skip the bad component |
|
continue; |
|
} |
|
|
|
// Get preferred/minimum size of current component |
|
size = prefMinSize[entryCounter]; |
|
|
|
//---------------------------------------------------------------------- |
|
|
|
// Calculate portion of component that is not absolutely sized |
|
int scalableSize = (z == C) ? size.width : size.height; |
|
|
|
for (counter = entry.cr1[z]; counter <= entry.cr2[z]; counter++) |
|
if (crSpec[z][counter] >= 1.0) |
|
scalableSize -= crSpec[z][counter]; |
|
else if (Math.abs(crSpec[z][counter] - PREFERRED) < epsilon || |
|
Math.abs(crSpec[z][counter] - MINIMUM) < epsilon) { |
|
scalableSize -= crPrefMin[counter]; |
|
} |
|
|
|
//---------------------------------------------------------------------- |
|
|
|
// Determine total percentage of scalable space that the component |
|
// occupies by adding the relative columns and the fill columns |
|
double relativeSize = 0.0; |
|
|
|
for (counter = entry.cr1[z]; counter <= entry.cr2[z]; counter++) { |
|
// Cr is scaled |
|
if ((crSpec[z][counter] > 0.0) && (crSpec[z][counter] < 1.0)) |
|
// Add scaled size to relativeWidth |
|
relativeSize += crSpec[z][counter]; |
|
// Cr is fill |
|
else if (Math.abs(crSpec[z][counter] - FILL) < epsilon && Math.abs(fillSizeRatio - 0.0) >= epsilon) |
|
// Add fill size to relativeWidth |
|
relativeSize += fillSizeRatio; |
|
} |
|
|
|
// Determine the total scaled size as estimated by this component |
|
if (Math.abs(relativeSize - 0) < epsilon) |
|
temp = 0; |
|
else |
|
temp = (int) (scalableSize / relativeSize + 0.5); |
|
|
|
//---------------------------------------------------------------------- |
|
|
|
// If the container needs to be bigger, make it so |
|
if (scaledSize < temp) |
|
scaledSize = temp; |
|
} |
|
|
|
// totalSize is the scaledSize plus the sum of all absolute sizes and all |
|
// preferred sizes |
|
int totalSize = scaledSize; |
|
|
|
for (counter = 0; counter < numCr; counter++) |
|
// Is the current cr an absolute size |
|
if (crSpec[z][counter] >= 1.0) |
|
totalSize += (int) (crSpec[z][counter] + 0.5); |
|
// Is the current cr a preferred/minimum size |
|
else if (Math.abs(crSpec[z][counter] - PREFERRED) < epsilon|| |
|
Math.abs(crSpec[z][counter] - MINIMUM) < epsilon) { |
|
// Add preferred/minimum width |
|
totalSize += crPrefMin[counter]; |
|
} |
|
|
|
// Compensate for horizontal and vertical gap |
|
if (numCr > 0) |
|
totalSize += ((z == C) ? hGap : vGap) * (numCr - 1); |
|
|
|
return totalSize; |
|
} |
|
|
|
|
|
/** |
|
* 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)); |
|
|
|
// Indicate that the cell sizes are not known |
|
dirty = true; |
|
} else if (constraint instanceof TableLayoutConstraints) { |
|
// Add component and constraints to the list |
|
list.add(new Entry(component, (TableLayoutConstraints) constraint)); |
|
|
|
// Indicate that the cell sizes are not known |
|
dirty = true; |
|
} 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) { |
|
// Remove the component |
|
ListIterator<Entry> iterator = list.listIterator(0); |
|
|
|
while (iterator.hasNext()) { |
|
Entry entry = iterator.next(); |
|
|
|
if (entry.component == component) |
|
iterator.remove(); |
|
} |
|
|
|
// Indicate that the cell sizes are not known since |
|
dirty = true; |
|
} |
|
|
|
|
|
/** |
|
* 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 |
|
public static class Entry implements Cloneable { |
|
/** Component bound by the constraints */ |
|
public Component component; |
|
|
|
/** Cell in which the upper-left corner of the component lies */ |
|
public int cr1[]; |
|
|
|
/** Cell in which the lower-right corner of the component lies */ |
|
public int cr2[]; |
|
|
|
/** Horizontal and vertical alignment */ |
|
public int alignment[]; |
|
|
|
/** |
|
* Constructs an Entry that binds a component to a set of constraints. |
|
* |
|
* @param component component being bound |
|
* @param constraint constraints being applied |
|
*/ |
|
|
|
public Entry(Component component, TableLayoutConstraints constraint) { |
|
int cr1[] = {constraint.col1, constraint.row1}; |
|
int cr2[] = {constraint.col2, constraint.row2}; |
|
int alignment[] = {constraint.hAlign, constraint.vAlign}; |
|
|
|
this.cr1 = cr1; |
|
this.cr2 = cr2; |
|
this.alignment = alignment; |
|
this.component = component; |
|
} |
|
|
|
/** |
|
* Copies this Entry. |
|
*/ |
|
|
|
public Entry copy() throws CloneNotSupportedException { |
|
return (Entry)clone(); |
|
} |
|
|
|
/** |
|
* Gets the string representation of this Entry. |
|
* |
|
* @return a string in the form |
|
* "(col1, row1, col2, row2, vAlign, hAlign) component" |
|
*/ |
|
|
|
public String toString() { |
|
TableLayoutConstraints c = new TableLayoutConstraints |
|
(cr1[C], cr1[R], cr2[C], cr2[R], alignment[C], alignment[R]); |
|
|
|
return "(" + c + ") " + component; |
|
} |
|
} |
|
|
|
|
|
} |