//********************************************************************** // Package //********************************************************************* package org.freixas.tablelayout; //********************************************************************** // Import list //********************************************************************** import java.awt.*; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; /** * This layout was inspired by the HTML table: it lays out components * much like the HTML table lays out table data. It is as capable as * the GridBagLayout, but much easier to use. *
* When you create a TableLayout, you pass in a String which defines a * set of attributes for the table. The TableLayout is then assigned * to a Container. As you add each Component to the Container, you can * also associate the Component with its own set of attributes. *
* The format of the attributes is similar to HTML attributes. The * attributes are case insensitive and can be separated with * any whitespace. Attributes which take a value are followed by an * '=' and then an integer. Here's an example: "cols=6 rgap=2 cgap=5 * w". *
* Attributes are evaluated from left to right. If you duplicate an * attribute, the right-most one wins. *
All attributes can be specified for both the table and the * individual Components. Some attributes are only used by the table, * some are only used by the Components and some are used by both: the * table instance is the default which each Component can override. *
* When you create a table, you will almost always specify the number * of columns in it. The default is 1. Columns are filled from left to * right. When all columns are filled, a new row is automatically * created. *
* You can override the default Component placement somewhat with "col" * and "skip". col takes as a value, the column number in which the * Component should be placed. Column numbers begin with 0. The * default is to place the Component in the next available table cell. * One caveat when using col is that if you have already passed the * given column, the layout adds another row and places the Component * in the column on that new row. *
* "skip" allows you to skip a number of cells. The default is 0. If * the layout reaches the end of the row, skipping continues on the * next row. *
* You can make a Component span multiple rows or columns with rspan * and cspan. The default value for these is 1. Later components will * skip over any occupied cells, which is particularly important to * note for row spanning. *
* You can create some space between cells in the table. This space is * only placed between cells; never along the edges of the * table. This allows you to nest table layouts and keep consistent * cell spacing. The attributes used are rgap and cgap and their * default value is 0. *
* Within a cell, you can also create some space between the cells * edges and the Component in the cell. The attributes are itop, * ibottom, ileft and iright. Their default value is 0. *
* Given that you have something to draw and a space to draw it in, * you have some choices as to where to place it and how to fill it, * particularly when the drawing area is bigger than required. *
* Placement attributes allow you to place the item in one of eight * compass directions or centered. The entire table can be placed * within the container using tn, tne, te, tse, ts, tsw, tw, tnw and * tc. Components can be placed within their cell using n, ne, e, se, * s, sw, w, nw and c. *
* Fill attributes allow you to fill the item to cover all available * space. Horizontal and vertical filling are handled separately. For * tables, the attributes are tfh, tfv and tf. "tf" fills in both * directions. For Components, use fh, fv and f. *
* Placement attributes turn off all filling. Fill attributes turn off * placement, but only in the fill direction. So "n fh" will stretch a * Component horizontally, but will place it at the "north" position * (at the top of the cell). *
* The default value for both the table and the individual Components * is to fill in both directions. You will almost always want to * specify your own values. *
* When a table is filled, if the available space exceeds the space * required, we stretch the table to fill the space. This implies that * we have to stretch each cell. How much each cell should be filled * is what weighting is all about. The attributes are rweight and * cweight which take an integer weight factor. The default is 0. *
* Note that stretching a cell is not the same as stretching the * Component inside the cell unless the component uses filling. *
* If you'd like some simple rules of thumbs, use these: *
* Ok, here are the dirty details. *
* If the available size is greater than the table's preferred size * and table filling is enabled, weighting is used (they are otherwise * ignored). *
* Weights are obtained by looking at each row and column and locating * the largest weight; this becomes the row or column weight. If all * weights are 0, we treat them as though they are all 1. We create * a sum for all row weights and one for all column weights. This * number defines the number of units into which the excess space will * be divided. *
* For example, with three column weights of 1, 1, and 1, the space is * divided into 3 units. If the excess space is 30 pixels, each unit * is 10 pixels, which is the extra space each column receives. If the * column weights were 0, 2, and 1, the space is still divided into 3 * units. But the column weights specify how many units each column * receives. So, column 0 will receive nothing, column 1, 20 pixels * and column 2, 10 pixels. *
* Keep in mind that rows and columns are handled separately. One may * need filling and the other not. *
* When we don't have enough space for the preferred row or column * sizes, we ignore the user-defined weights and treat each row or * column as having equal weight. The approach then, is as above * except that we are reducing cell sizes. Another difference is that * no cell will be made smaller than its minimum size. *
* The row and column weights given are applied to the row or column * in which the Component begins. *
*
* This table summarizes the attribute information: *
*
| Name | *Description | *Has Value? | *Default | *Scope | *
| cols | *Number of columns | *Yes | *1 | *Table | *
| col | *Place Component in this column | *Yes | *Next empty column | *Component | *
| skip | *Skip a number of columns | *Yes | *0 | *Component | *
| rspan, cspan | *Row and column spanning | *Yes | *1 | *Component | *
| titop, tibottom, tileft, tiright | *Table insets | *Yes | *0 | *Table | *
| rgap, cgap | *Row and column gaps | *Yes | *0 | *Table | *
| itop, ibottom, ileft, iright | *Component insets | *Yes | *0 | *Table/Component | *
| tn, tne, te, tse, ts, tsw, tw, tnw, tc, tf, tfh, tfv | *Table placement and fill | *No | *tf | *Table | *
| n, ne, e, se, s, sw, w, nw, c, f, fh, fv | *Component placement and fill | *No | *f | *Table/Component | *
| rweight, cweight | *Row and column weights | *Yes | *0 | *Table/Component | *
* If we grow the table, we pay attention to the user's weighting * factors. If we shrink the table, we assign all cells a weight * factor of 1. * * @param nCells The number of cells in the row or column. * @param fill True if the table rows or columns should fill the * available space. * @param shrink True if the available size is less than the preferred * size. * @param minSize The minimum sizes of each row or column. * @param PrefSize The sum of the preferred sizes of all cells in the * row or column plus any cell gaps. * @param prefSize The preferred sizes of each row or column. * @param CellWeight The sum of all cell weights in the row or column. * @param cellWeight The weight of each row or column. * @param adjSize The adjusted size of each row or column. The * contents of this array are set and returned. */ private void adjustCellSizes( int nCells, boolean fill, boolean shrink, int[] minSize, int PrefSize, int[] prefSize, int fullSize, int CellWeight, int[] cellWeight, int[] adjSize, Container parent) { // The sum of the weights (CellWeight) determines how many units // any excess (or reduced) space should be divided into. The // unitOfSpace variable is the size of each unit. // // We use weighting under two conditions: // // * We are shrinking the table. // // * We have more space than we need and the user asked us to // fill the available space // // When we have more than enough space for the preferred row and // column sizes, we follow the user's weighting. There is a // special case if all weights are 0: the weights are treated as // thought they were all 1. // // When do not have enough space, we weight everything the same. double unitOfSpace = 0.0; if (shrink || fill) { unitOfSpace = (double)(fullSize - PrefSize) / (double)((shrink || CellWeight == 0) ? nCells : CellWeight); } else { // No adjustment needed: use the preferred sizes for (int i = 0; i < nCells; i++) { adjSize[i] = prefSize[i]; } return; } double extraSpace; int iExtraSpace; double error = 0.0; int iError = -999999; int adjWeight; for (int i = 0; i < nCells; i++) { // Initialize the adjusted size to the preferred size adjSize[i] = prefSize[i]; // Get the cell weight based on various conditions adjWeight = (shrink || CellWeight == 0) ? 1 : cellWeight[i]; // Determine how much extra space to give each cell. The space // is the weight (number of units) times the unit size. We can // only assign an integer number of pixels, which creates a // fractional error extraSpace = unitOfSpace * adjWeight; iExtraSpace = (int)extraSpace; adjSize[i] += iExtraSpace; error += extraSpace - iExtraSpace; iError = (int)error; // Increment/decrement this cell by the accumulated integer // error, if it's not 0 if (shrink) { if (iError < 0) { adjSize[i] += iError; } } else { if (iError > 0) { adjSize[i] += iError; } } error -= iError; // If we're shrinking, we need to prevent any cell from // shrinking below its minimum size. The error is adjusted to // include the space added to the cell if (shrink && (adjSize[i] < minSize[i])) { error -= minSize[i] - adjSize[i]; adjSize[i] = minSize[i]; } iError = (int)error; } // If we are growing, the error should be less than 1 pixel. If we // are shrinking, we limit each cell to its minimum size, so we // can accumulate larger errors as cells refuse to shrink. So we // distribute the error to cells that can still shrink. We repeat // this until we've reduced the error to 0 or we're unable to // shrink the error anymore. // // Remember that iError is a negative number if (shrink || iError < 0) { int lastIError; do { lastIError = iError; for (int i = 0; i < nCells; i++) { // If the cell is already at its minimum size, skip it if (adjSize[i] > minSize[i]) { adjSize[i]--; iError++; } } } while (iError < 0 && iError > lastIError); } // DEBUG // if ("DEBUG".equals(parent.getName())) { // System.out.println(" iError " + iError); // for (int i = 0; i < nCells; i++) { // System.out.println(" " + i + ") Adjusted size = " + adjSize[i]); // } // } } /** * For each component, determine its row/col position and place it in * an array for easy access later. Elements spanning multiple rows * and/or columns are placed in the NW row/col slot in the array. * Results are placed in class fields. * * @param parent The parent container. */ private void placeComponents( Container parent) { // If we haven't added or removed a component since the last time // placeComponents() was called, we assume the current results are // OK if (components != null) return; int compCount = parent.getComponentCount(); // Get the number of columns specified by the user nCols = tableAttributes.columns; // Create the array of components CompArray compArray = new CompArray(tableAttributes.columns, compCount); // Fill the array with components, taking row/column spanning // into account int row = 0; int col = 0; for (int i = 0; i < compCount; i++) { // Get the next component and its options Component comp = parent.getComponent(i); Attributes attributes = (Attributes)compAttributes.get(comp); // If the column span is greater than the column size, // truncate it to the column size attributes.cSpan = attributes.originalCSpan; if (attributes.cSpan > tableAttributes.columns) { attributes.cSpan = tableAttributes.columns; } // Handle options to force us to column 0 or to skip columns if (attributes.column != Attributes.NEXT_COLUMN) { if (col > attributes.column) row++; col = attributes.column; } col += attributes.skip; if (col >= nCols) { row++; col = 0; } // Skip over any cells that are already occupied while (compArray.get(row, col) != null) { col++; if (col >= nCols) { row++; col = 0; } } // If spanning multiple columns, will we fit on this row? if (col + attributes.cSpan > nCols) { row++; col = 0; } // For now, fill all the cells that are occupied by this // component for (int c = 0; c < attributes.cSpan; c++) { for (int r = 0; r < attributes.rSpan; r++) { compArray.set(row + r, col + c, comp); } } // Advance to the next cell, ready for the next component col += attributes.cSpan; if (col >= nCols) { row++; col = 0; } } // Now we know how many rows there are. We can use a normal, // properly sized array from now on. The array returned includes // the maximum row into which anything was entered, including any // row spans components = compArray.getArray(); nRows = components.length; // Now we've positioned our components we can thin out the cells so // we only remember the top left corner of each component for (row = 0; row < nRows; row++) { for (col = 0; col < nCols; col++) { Component comp = components[row][col]; for (int r = row; r < nRows && components[r][col] == comp; r++) { for (int c = col; c < nCols && components[r][c] == comp; c++) { if (r > row || c > col) { components[r][c] = null; } } } } } // DEBUG // if ("DEBUG".equals(parent.getName())) { // System.out.println("placeComponents finished: rows = " + // nRows + " cols = " + nCols); // for (int r = 0; r < nRows; r++) { // System.out.println("Row " + r + ":"); // for (int c = 0; c < nCols; c++) { // System.out.println(" Col " + c + " Comp " + // ((components[r][c] == null) ? // "none" : // components[r][c].getClass().getName())); // } // } // } } /** * In this method, we will determine the minimum, preferred and * maximum sizes of the components as layed out by the table layout * manager * * @param parent The parent container. */ private void measureComponents( Container parent) { if (useCacheMeasureResults) return; // Determine the row/col positions for the components placeComponents(parent); // Allocate new arrays to store row and column preferred and min // sizes, but only if the old arrays aren't big enough if (minWidth == null || minWidth.length < nCols) { minWidth = new int[nCols]; prefWidth = new int[nCols]; maxWidth = new int[nCols]; adjWidth = new int[nCols]; colWeight = new int[nCols]; } if (minHeight == null || minHeight.length < nRows) { minHeight = new int[nRows]; prefHeight = new int[nRows]; maxHeight = new int[nRows]; adjHeight = new int[nRows]; rowWeight = new int[nRows]; } for (int i = 0; i < nCols; i++) { minWidth[i] = 0; prefWidth[i] = 0; maxWidth[i] = 0; colWeight[i] = 0; } for (int i = 0; i < nRows; i++) { minHeight[i] = 0; prefHeight[i] = 0; maxHeight[i] = 0; rowWeight[i] = 0; } // Measure the minimum and preferred size of each row and column for (int row = 0; row < nRows; row++) { for (int col = 0; col < nCols; col++) { Component comp = components[row][col]; if (comp != null) { Attributes attributes = (Attributes)compAttributes.get(comp); Dimension minSize = new Dimension(comp.getMinimumSize()); Dimension prefSize = new Dimension(comp.getPreferredSize()); Dimension maxSize = new Dimension(comp.getMaximumSize()); // Add the cell insets minSize.width += attributes.cellInsets.left + attributes.cellInsets.right; minSize.height += attributes.cellInsets.top + attributes.cellInsets.bottom; prefSize.width += attributes.cellInsets.left + attributes.cellInsets.right; prefSize.height += attributes.cellInsets.top + attributes.cellInsets.bottom; maxSize.width += attributes.cellInsets.left + attributes.cellInsets.right; maxSize.height += attributes.cellInsets.right + attributes.cellInsets.bottom; // Make sure that 0 <= minSize <= prefSize <= maxSize limitDimension(minSize, new Dimension(0, 0)); limitDimension(prefSize, minSize); limitDimension(maxSize, prefSize); // First pass, we determine the sizes while ignoring // components which span columns or rows if (attributes.cSpan == 1) { minWidth[col] = Math.max(minSize.width, minWidth[col]); prefWidth[col] = Math.max(prefSize.width, prefWidth[col]); maxWidth[col] = Math.max(maxSize.width, maxWidth[col]); } if (attributes.rSpan == 1) { minHeight[row] = Math.max(minSize.height, minHeight[row]); prefHeight[row] = Math.max(prefSize.height, prefHeight[row]); maxHeight[row] = Math.max(maxSize.height, maxHeight[row]); } // Get the row and column weights. The weight is the // maximum value for the row or column if (attributes.cWeight > colWeight[col]) { colWeight[col] = attributes.cWeight; } if (attributes.rWeight > rowWeight[row]) { rowWeight[row] = attributes.rWeight; } } } } // Do it again, but just for components which span multiple cells. // for (int row = 0; row < nRows; row++) { for (int col = 0; col < nCols; col++) { Component comp = components[row][col]; if (comp != null) { Attributes attributes = (Attributes)compAttributes.get(comp); if (attributes.rSpan == 1 && attributes.cSpan == 1) continue; Dimension minSize = new Dimension(comp.getMinimumSize()); Dimension prefSize = new Dimension(comp.getPreferredSize()); Dimension maxSize = new Dimension(comp.getMaximumSize()); // Add the cell insets minSize.width += attributes.cellInsets.top + attributes.cellInsets.bottom; minSize.height += attributes.cellInsets.left + attributes.cellInsets.right; prefSize.width += attributes.cellInsets.top + attributes.cellInsets.bottom; prefSize.height += attributes.cellInsets.left + attributes.cellInsets.right; maxSize.width += attributes.cellInsets.top + attributes.cellInsets.bottom; maxSize.height += attributes.cellInsets.left + attributes.cellInsets.right; // Make sure that 0 <= minSize <= prefSize <= maxSize limitDimension(minSize, new Dimension(0, 0)); limitDimension(prefSize, minSize); limitDimension(maxSize, prefSize); if (attributes.cSpan > 1) { adjustForSpans(col, minSize.width, minWidth, colWeight, attributes.cSpan, tableAttributes.cGap); adjustForSpans(col, prefSize.width, prefWidth, colWeight, attributes.cSpan, tableAttributes.cGap); adjustForSpans(col, maxSize.width, maxWidth, colWeight, attributes.cSpan, tableAttributes.cGap); } if (attributes.rSpan > 1) { adjustForSpans(row, minSize.height, minHeight, rowWeight, attributes.rSpan, tableAttributes.rGap); adjustForSpans(row, prefSize.height, prefHeight, rowWeight, attributes.rSpan, tableAttributes.rGap); adjustForSpans(row, maxSize.height, maxHeight, rowWeight, attributes.rSpan, tableAttributes.rGap); } } } } // Add up all the individual values MinWidth = 0; MinHeight = 0; PrefWidth = 0; PrefHeight = 0; MaxWidth = 0; MaxHeight = 0; ColWeight = 0; RowWeight = 0; // Sum up everything for (int i = 0; i < nCols; i++) { MinWidth += minWidth[i]; PrefWidth += prefWidth[i]; MaxWidth += maxWidth[i]; ColWeight += colWeight[i]; } for (int i = 0; i < nRows; i++) { MinHeight += minHeight[i]; PrefHeight += prefHeight[i]; MaxHeight += maxHeight[i]; RowWeight += rowWeight[i]; } // Add in the table gaps int cExtra = tableAttributes.cGap * (nCols - 1); int rExtra = tableAttributes.rGap * (nRows - 1); MinWidth += cExtra; PrefWidth += cExtra; MaxWidth += cExtra; MinHeight += rExtra; PrefHeight += rExtra; MaxHeight += rExtra; // DEBUG // if ("DEBUG".equals(parent.getName())) { // System.out.println("MeasureComponents:"); // System.out.println(" Min " + MinWidth + ", " + MinHeight); // System.out.println(" Pref " + PrefWidth + ", " + PrefHeight); // System.out.println(" Max " + MaxWidth + ", " + MaxHeight); // System.out.println(" Weight " + ColWeight + ", " + RowWeight ); // for (int c = 0; c < nCols; c++) { // System.out.println(" Col " + c + // " min " + minWidth[c] + // " pref " + prefWidth[c] + // " max " + maxWidth[c] + // " wgt " + colWeight[c]); // } // for (int r = 0; r < nRows; r++) { // System.out.println(" Row " + r + // " min " + minHeight[r] + // " pref " + prefHeight[r] + // " max " + maxHeight[r] + // " wgt " + rowWeight[r]); // } // } // We keep using these results until the layout is invalidated useCacheMeasureResults = true; } /** * Make sure the first dimension is greater than or equal to the * second. Also make sure the first dimension is less than an absolute * maximum. * * @param d1 The first dimension (may be modified). * @param d2 The second dimension (will not be modified). */ private void limitDimension( Dimension d1, Dimension d2) { if (d1.width < d2.width) d1.width = d2.width; if (d1.height < d2.height) d1.height = d2.height; if (d1.width > Short.MAX_VALUE) d1.width = Short.MAX_VALUE; if (d1.height > Short.MAX_VALUE) d1.height = Short.MAX_VALUE; } /** * If a component spans multiple rows or columns, we need to * distribute portions of its size to the individual rows and columns. * * @param pos Row or column position where the span component starts. * @param compSize The height or width of the component * @param sizes The array of widths or heights to adjust. * @param span The number of cells spanned by the component. * @param gap The row or column gap. */ private void adjustForSpans( int pos, int compSize, int[] sizes, int[] weight, int span, int gap) { // The total size is the size of the rows or columns plus all the // space in between int totalSize = 0; for (int i = 0; i < span; i++) { totalSize += sizes[pos + i]; } totalSize += gap * (span - 1); // If the spanned component is bigger than the the rows or columns // it spans, we divide the extra space based on the weights of the // spanned rows or columns if (compSize > totalSize) { int extra = compSize - totalSize; int totalWeight = 0; for (int i = 0; i < span; i++) { totalWeight += weight[pos + i]; } if (totalWeight == 0) totalWeight = span; int remainder = extra; for (int i = 0; i < span; i++) { int portion = (extra * weight[pos + i]) / totalWeight; sizes[pos + i] += portion; remainder -= portion; } // Because of truncation, we may have a little left over which // we give to the last row or column if (remainder > 0) { sizes[pos + span - 1] += remainder; } } } //********************************************************************** // Inner Classes //********************************************************************** //********************************************************************** // // Attributes // // This class converts a string attribute into a data structure. // //********************************************************************** // Attributes not needing assigment static String[] attr = { "tn", "tne", "tnw", "ts", "tse", "tsw", "te", "tw", "tc", "tfh", "tfv", "tf", "n", "ne", "nw", "s", "se", "sw", "e", "w", "c", "fh", "fv", "f", }; // Attributes needing assigment static String[] assgn = { "cols", "rgap", "cgap", "titop", "tibottom", "tileft", "tiright", "itop", "ibottom", "ileft", "iright", "rweight", "cweight", "rspan", "cspan", "col", "skip" }; private class Attributes { // Constants used for fill and placement operations static final int CENTER = 0; static final int LEFT = 1; static final int RIGHT = 2; static final int TOP = 3; static final int BOTTOM = 4; static final int FILL = 5; // Constants for column placement static final int NEXT_COLUMN = -1; // The attributes in their original string form String attrString; // Table-only options int columns = 1; int tableHorizontal = FILL; int tableVertical = FILL; int rGap = 0; int cGap = 0; Insets tableInsets = new Insets(0, 0, 0, 0); // Table/cell options int horizontal = FILL; int vertical = FILL; Insets cellInsets = new Insets(0, 0, 0, 0); int rWeight = 0; int cWeight = 0; // Cell-only options int rSpan = 1; int cSpan = 1; int originalCSpan = cSpan; int column = NEXT_COLUMN; int skip = 0; int tkPos = 0; boolean isTableAttributes = false; Attributes( String attrString) { this(attrString, true); } Attributes( String attrString, boolean isTableAttributes) { // Save the string for later access this.attrString = attrString; this.isTableAttributes = isTableAttributes; parse(); } public String toString() { String sep = System.getProperty("line.separator"); return "TableLayout Attributes:" + sep + "isTableAttributes = " + isTableAttributes + sep + "columns = " + columns + sep + "tableHorizontal = " + tableHorizontal + " " + "tableVertical = " + tableVertical + sep + "rGap = " + rGap + " " + "cGap = " + cGap + sep + "tableInsets = " + tableInsets + sep + "horizontal = " + horizontal + " " + "vertical = " + vertical + sep + "cellInsets = " + cellInsets + sep + "rWeight = " + rWeight + " " + "cWeight = " + cWeight + sep + "rSpan = " + rSpan + " " + "cSpan = " + cSpan + sep + "originalCSpan = " + originalCSpan + sep + "column = " + column + sep + "skip = " + skip; } String getStringAttributes() { return attrString; } private char getTokenChar() { if (tkPos >= attrString.length()) return 0; return Character.toLowerCase(attrString.charAt(tkPos++)); } private String getToken() { StringBuffer token = new StringBuffer(); char c = getTokenChar(); // Skip whitespace if (Character.isWhitespace(c)) { do { c = getTokenChar(); } while (Character.isWhitespace(c)); } // Attributes if (Character.isLetter(c)) { do { token.append(c); c = getTokenChar(); } while (Character.isLetter(c)); if (c != 0) tkPos--; } // Integers else if (Character.isDigit(c)) { do { token.append(c); c = getTokenChar(); } while (Character.isDigit(c)); if (c != 0) tkPos--; } // End of string else if (c == 0) { return null; } // Everything else is a single-character token else { token.append(c); } return new String(token); } void parse() { // Initialize this set of attributes so it starts out as a copy of // the given default, at least for those options where the cell // can override a table default if (!isTableAttributes) { horizontal = tableAttributes.horizontal; vertical = tableAttributes.vertical; cellInsets = (Insets)tableAttributes.cellInsets.clone(); rWeight = tableAttributes.rWeight; cWeight = tableAttributes.cWeight; } if (attrString == null) return; tkPos = 0; while (tkPos < attrString.length()) { parseOption(); } // We have checked the syntax, now check the semantics if (isTableAttributes) { if (columns == 0) { reportSemanticError("cols=0"); } } else { if (rSpan == 0) { reportSemanticError("rspan=0"); } if (cSpan == 0) { reportSemanticError("cspan=0"); } if (column >= tableAttributes.columns) { reportSemanticError("col=" + column + " (max is " + (tableAttributes.columns - 1) + ")"); } } } private void parseOption() { // Get the next token String token = getToken(); if (token == null) return; boolean attributeFound = false; for (int i = 0; i < attr.length; i++) { if (token.equals(attr[i])) { parseAttribute(token); return; } } for (int i = 0; i < assgn.length; i++) { if (token.equals(assgn[i])) { parseAssignment(token); return; } } reportError(token, "Unrecognized attribute"); } private void parseAttribute( String token) { // Table placement and fill if ("tnw".equals(token) || "tw".equals(token) || "tsw".equals(token)) { tableHorizontal = LEFT; } if ("tne".equals(token) || "te".equals(token) || "tse".equals(token)) { tableHorizontal = RIGHT; } if ("tn".equals(token) || "tc".equals(token) || "ts".equals(token)) { tableHorizontal = CENTER; } if ("tf".equals(token) || "tfh".equals(token)) { tableHorizontal = FILL; } if ("tn".equals(token) || "tnw".equals(token) || "tne".equals(token)) { tableVertical = TOP; } if ("ts".equals(token) || "tsw".equals(token) || "tse".equals(token)) { tableVertical = BOTTOM; } if ("tw".equals(token) || "tc".equals(token) || "te".equals(token)) { tableVertical = CENTER; } if ("tf".equals(token) || "tfv".equals(token)) { tableVertical = FILL; } // Cell placement and fill if ("nw".equals(token) || "w".equals(token) || "sw".equals(token)) { horizontal = LEFT; } if ("ne".equals(token) || "e".equals(token) || "se".equals(token)) { horizontal = RIGHT; } if ("n".equals(token) || "c".equals(token) || "s".equals(token)) { horizontal = CENTER; } if ("f".equals(token) || "fh".equals(token)) { horizontal = FILL; } if ("n".equals(token) || "nw".equals(token) || "ne".equals(token)) { vertical = TOP; } if ("s".equals(token) || "sw".equals(token) || "se".equals(token)) { vertical = BOTTOM; } if ("w".equals(token) || "c".equals(token) || "e".equals(token)) { vertical = CENTER; } if ("f".equals(token) || "fv".equals(token)) { vertical = FILL; } } private void parseAssignment( String token) { String attr = token; token = getToken(); if (token != null) { if ("=".equals(token)) { token = getToken(); if (token != null) { int value = 0; try { value = Integer.parseInt(token); } catch (NumberFormatException e) { reportError(token, "Expected an integer"); } if ("cols".equals(attr)) columns = value; else if ("rgap".equals(attr)) rGap = value; else if ("cgap".equals(attr)) cGap = value; else if ("titop".equals(attr)) tableInsets.top = value; else if ("tibottom".equals(attr)) tableInsets.bottom = value; else if ("tileft".equals(attr)) tableInsets.left = value; else if ("tiright".equals(attr)) tableInsets.right = value; else if ("itop".equals(attr)) cellInsets.top = value; else if ("ibottom".equals(attr)) cellInsets.bottom = value; else if ("ileft".equals(attr)) cellInsets.left = value; else if ("iright".equals(attr)) cellInsets.right = value; else if ("rweight".equals(attr)) rWeight = value; else if ("cweight".equals(attr)) cWeight = value; else if ("rspan".equals(attr)) rSpan = value; else if ("cspan".equals(attr)) originalCSpan = cSpan = value; else if ("col".equals(attr)) column = value; else if ("skip".equals(attr)) skip = value; return; } } reportError(token, "Expected an '='"); } reportError(token, "Expected an '='"); } private void reportError( String token, String message) { throw new IllegalArgumentException( "TableLayout: " + message + "; near '" + token + "' at position " + tkPos + " in '" + attrString + "'"); } private void reportSemanticError( String message) { throw new IllegalArgumentException( "TableLayout: Invalid value: " + message); } } //********************************************************************** // // CompArray // // We'd like to use a 2-dimensional array to help us sort out the // layout of the various components. But in order to create the array, // we need to know the number of rows. In order to get the number of // rows, we need to lay out the components. So we have problem. // // It is not obvious how to determine the number of rows from the // component count, due to row and column spans. So we've created the // CompArray class to help out. It makes its best guess at the size of // the array. If we need additional rows, it expands the array as // efficiently as it can. When we're done, we can ask it to create a // correctly sized array to hold our data. // //********************************************************************** private class CompArray { private int nCols; private int nRows; private int maxRow = 0; Component[][] compArray = null; CompArray( int nCols, int compCount) { this.nRows = (compCount + (nCols - 1)) / nCols; this.nRows = Math.max(this.nRows, 1); this.nCols = nCols; compArray = new Component[nRows][]; for (int i = 0; i < nRows; i++) { compArray[i] = new Component[nCols]; Arrays.fill(compArray[i], null); } } Component get( int row, int col) { if (row >= nRows) resize(row + 1); return compArray[row][col]; } void set( int row, int col, Component comp) { if (row >= nRows) resize(row + 1); compArray[row][col] = comp; maxRow = Math.max(row, maxRow); } Component[][] getArray() { int maxRows = maxRow + 1; Component[][] array = new Component[maxRows][]; for (int r = 0; r < maxRows; r++) { array[r] = new Component[nCols]; System.arraycopy(compArray[r], 0, array[r], 0, nCols); } return array; } private void resize( int newRows) { // When we exceed a threshold, bump up the size by at least 10 if (newRows - nRows < 10) newRows = nRows + 10; // Create the new row array and copy the old one into it Component[][] newArray = new Component[newRows][]; System.arraycopy(compArray, 0, newArray, 0, nRows); // Initialize each new row to nulls for (int i = nRows; i < newRows; i++) { newArray[i] = new Component[nCols]; Arrays.fill(newArray[i], null); } compArray = newArray; nRows = newRows; } } //********************************************************************** // End Inner Classes //********************************************************************** }