0001: /*
0002: * PnutsLayout.java
0003: *
0004: * Copyright (c) 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0005: *
0006: * See the file "LICENSE.txt" for information on usage and redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
0007: */
0008:
0009: package pnuts.awt;
0010:
0011: import java.awt.CardLayout;
0012: import java.awt.Component;
0013: import java.awt.Container;
0014: import java.awt.Dimension;
0015: import java.awt.Graphics;
0016: import java.awt.Insets;
0017: import java.awt.LayoutManager;
0018: import java.awt.LayoutManager2;
0019: import java.awt.Point;
0020: import java.awt.Rectangle;
0021: import java.io.Serializable;
0022: import java.util.ArrayList;
0023: import java.util.BitSet;
0024: import java.util.Map;
0025: import java.util.HashMap;
0026: import java.util.Iterator;
0027: import java.util.List;
0028: import java.util.StringTokenizer;
0029:
0030: import javax.swing.JPanel;
0031:
0032: /**
0033: * The PnutsLayout is a general purpose geometry manager. It is very easy to use and can accomplish any grid based layout.
0034: *
0035: * <pre>
0036: * setLayout(new PnutsLayout(3));
0037: * add(button1, "ipadding=20");
0038: * add(button2, "padding=20");
0039: * add(button3, "colspan=2");
0040: * add(button4, "rowspan=2");
0041: * add(button3, "align=top:left");
0042: * add(button3, "align=bottom:right");
0043: * add(button3, "width=100:center");
0044: * </pre>
0045: *
0046: * <table border>
0047: * <tr>
0048: * <th>Property</th>
0049: * <th>Meaning</th>
0050: * <th>Default</th>
0051: * <th>constructor</th>
0052: * <th>add()</th>
0053: * </tr>
0054: * <tr>
0055: * <td>columns</td>
0056: * <td>The number of columns.</td>
0057: * <td>1</td>
0058: * <td>true</td>
0059: * <td>false</td>
0060: * </tr>
0061: * <tr>
0062: * <td>uniform</td>
0063: * <td>Sets the width or height to be the same for each column. Can be x/y/xy/none.</td>
0064: * <td>none</td>
0065: * <td>true</td>
0066: * <td>false</td>
0067: * </tr>
0068: * <tr>
0069: * <td>colspan</td>
0070: * <td>Number of columns the component occupies.</td>
0071: * <td>1</td>
0072: * <td>false</td>
0073: * <td>true</td>
0074: * </tr>
0075: * <tr>
0076: * <td>rowspan</td>
0077: * <td>Number of rows the component occupies.</td>
0078: * <td>1</td>
0079: * <td>false</td>
0080: * <td>true</td>
0081: * </tr>
0082: * <tr>
0083: * <td>spacing</td>
0084: * <td>Minimum spacing that will be put around the component. Can be a single value (5) or top:right:bottom:left (eg 5:10:5:10).</td>
0085: * <td>0</td>
0086: * <td>true</td>
0087: * <td>true</td>
0088: * </tr>
0089: * <tr>
0090: * <td>padding</td>
0091: * <td>Padding around the component. Can be a single value (5) or top:right:bottom:left (eg 5:10:5:10).</td>
0092: * <td>0</td>
0093: * <td>true</td>
0094: * <td>true</td>
0095: * </tr>
0096: * <tr>
0097: * <td>ipadding</td>
0098: * <td>Padding inside the component (making it larger). Can be a single value (5) or x:y (eg 5:10).</td>
0099: * <td>0</td>
0100: * <td>true</td>
0101: * <td>true</td>
0102: * </tr>
0103: * <tr>
0104: * <td>fill</td>
0105: * <td>Sets the width and/or height of the component to a percentage of the cell width. Can be x/y/xy/none or a single value (5) or
0106: * x:y (eg 25:75). 0 or none sizes the component to its preferred size.</td>
0107: * <td>none</td>
0108: * <td>true</td>
0109: * <td>true</td>
0110: * </tr>
0111: * <tr>
0112: * <td>align</td>
0113: * <td>Alignment of the component in the cell. Can be top/bottom/left/right/center or top/bottom/center:left/right/center (eg
0114: * top:right).</td>
0115: * <td>center</td>
0116: * <td>true</td>
0117: * <td>true</td>
0118: * </tr>
0119: * <tr>
0120: * <td>expand</td>
0121: * <td>Expands the size of the cell so the table takes up the size of the container. Can be x/y/xy/none.</td>
0122: * <td>none</td>
0123: * <td>true</td>
0124: * <td>true</td>
0125: * </tr>
0126: * <tr>
0127: * <td>border</td>
0128: * <td>Draws the grid borders of the table. Great for debugging. Can be true/false.</td>
0129: * <td>false</td>
0130: * <td>true</td>
0131: * <td>true</td>
0132: * </tr>
0133: * </table>
0134: * <p>
0135: * @version 2.0
0136: * @author Toyokazu Tomatsu
0137: * @author Nathan Sweet (misc@n4te.com)
0138: */
0139: public class PnutsLayout implements LayoutManager2, Serializable {
0140: public static final int CENTER = 2;
0141: public static final int TOP = 4;
0142: public static final int BOTTOM = 8;
0143: public static final int LEFT = 16;
0144: public static final int RIGHT = 32;
0145: public static final int X = 64;
0146: public static final int Y = 128;
0147: public static final int NONE = 0;
0148:
0149: /**
0150: * @serial
0151: */
0152: private final TableConstraints tableConstraints = new TableConstraints();
0153:
0154: /**
0155: * @serial
0156: */
0157: private final Map cellConstraints = new HashMap();
0158:
0159: /**
0160: * @serial
0161: */
0162: private int widths[];
0163:
0164: /**
0165: * @serial
0166: */
0167: private int heights[];
0168:
0169: /**
0170: * @serial
0171: */
0172: private int xExpand[];
0173:
0174: /**
0175: * @serial
0176: */
0177: private int yExpand[];
0178:
0179: /**
0180: * @serial
0181: */
0182: private int xExpandCount;
0183:
0184: /**
0185: * @serial
0186: */
0187: private int yExpandCount;
0188:
0189: /**
0190: * Grid column index to column x coordinate.
0191: * @serial
0192: */
0193: private int columnLeft[];
0194:
0195: /**
0196: * Grid row index to row y coordinate.
0197: * @serial
0198: */
0199: private int rowTop[];
0200:
0201: /**
0202: * Row and column to spacing bottom and right.
0203: * @serial
0204: */
0205: private int cellSpacing[][][];
0206:
0207: /**
0208: * Component index to x grid position.
0209: * @serial
0210: */
0211: private int gridPosition_x[] = new int[8];
0212:
0213: /**
0214: * Component index to y grid position.
0215: * @serial
0216: */
0217: private int gridPosition_y[] = new int[8];
0218:
0219: /**
0220: * @serial
0221: */
0222: private Dimension preferredSize;
0223:
0224: /**
0225: * @serial
0226: */
0227: private boolean valid = false;
0228:
0229: /**
0230: * The number of rows
0231: * @serial
0232: */
0233: private int rows;
0234:
0235: /**
0236: * BorderPanel used for drawing grid borders.
0237: */
0238: private BorderPanel borderPanel;
0239:
0240: /**
0241: * Constructs a PnutsLayout with a single column.
0242: */
0243: public PnutsLayout() {
0244: this (1);
0245: }
0246:
0247: /**
0248: * Constructs a PnutsLayout with the specified number of columns.
0249: */
0250: public PnutsLayout(int columns) {
0251: tableConstraints.setColumns(columns);
0252: }
0253:
0254: /*
0255: * Constructs a PnutsLayout from a constraint String. See PnutsLayout class comments for constraints usage.
0256: */
0257: public PnutsLayout(String str) {
0258: this (str2table(str));
0259: }
0260:
0261: /**
0262: * Constructs a PnutsLayout from a constraint Map. See PnutsLayout class comments for constraints usage.
0263: * @param map
0264: */
0265: public PnutsLayout(Map map) {
0266: tableConstraints.configure(map);
0267: }
0268:
0269: /**
0270: * Sets the constraints for a component. See PnutsLayout class comments for constraints usage.
0271: */
0272: public void setConstraints(Component comp, String str) {
0273: setConstraints(comp, new CellConstraints(str2table(str)));
0274: }
0275:
0276: /**
0277: * Sets the constraints for a component. See PnutsLayout class comments for constraints usage.
0278: */
0279: public void setConstraints(Component comp, Map map) {
0280: setConstraints(comp, new CellConstraints(map));
0281: }
0282:
0283: /**
0284: * Sets the constraints for a component. See PnutsLayout class comments for constraints usage.
0285: */
0286: public void setConstraints(Component comp,
0287: CellConstraints constraints) {
0288: cellConstraints.put(comp, constraints);
0289: invalidate();
0290: }
0291:
0292: /**
0293: * Compute geometries that do not depend on the size of the container.
0294: */
0295: private void bindContainer(Container target) {
0296: int columnCount = tableConstraints.getColumns();
0297:
0298: BitSet map[] = new BitSet[columnCount];
0299: for (int i = 0; i < columnCount; i++)
0300: map[i] = new BitSet();
0301:
0302: int compCount = target.getComponentCount();
0303:
0304: gridPosition_x = new int[compCount * 2];
0305: gridPosition_y = new int[compCount * 2];
0306:
0307: /**
0308: * 1) Decide components' logical location. 2) Count total columns and rows.
0309: */
0310: rows = 0;
0311: for (int i = 0, gridX = 0, gridY = 0; i < compCount; i++) {
0312: Component comp = target.getComponent(i);
0313: if (comp instanceof BorderPanel)
0314: continue;
0315: CellConstraints constraints = (CellConstraints) cellConstraints
0316: .get(comp);
0317:
0318: int colspan = constraints.getColspan();
0319: int rowspan = constraints.getRowspan();
0320: while (!fit(gridX, gridY, colspan, rowspan, map)) {
0321: if (++gridX >= columnCount) {
0322: gridX = 0;
0323: ++gridY;
0324: }
0325: }
0326: gridPosition_y[i] = gridY;
0327: gridPosition_x[i] = gridX;
0328:
0329: for (int jj = 0; jj < colspan; jj++) {
0330: for (int kk = 0; kk < rowspan; kk++) {
0331: map[gridX + jj].set(gridY + kk);
0332: }
0333: }
0334:
0335: if (rows < gridY + rowspan) {
0336: rows = gridY + rowspan;
0337: }
0338:
0339: if (++gridX >= columnCount) {
0340: gridX = 0;
0341: ++gridY;
0342: }
0343: }
0344:
0345: cellSpacing = new int[columnCount][rows][2];
0346: columnLeft = new int[columnCount + 1];
0347: rowTop = new int[rows + 1];
0348: widths = new int[columnCount];
0349: heights = new int[rows];
0350: yExpand = new int[rows];
0351: xExpand = new int[columnCount];
0352:
0353: /*
0354: * 3) Mark expanded locations.
0355: */
0356: for (int i = 0, gridX = 0, gridY = 0; i < compCount; i++) {
0357: Component comp = target.getComponent(i);
0358: if (comp instanceof BorderPanel)
0359: continue;
0360: CellConstraints constraints = (CellConstraints) cellConstraints
0361: .get(comp);
0362:
0363: gridY = gridPosition_y[i];
0364: gridX = gridPosition_x[i];
0365:
0366: int colspan = constraints.getColspan();
0367: int rowspan = constraints.getRowspan();
0368: int expand = constraints.getExpand();
0369:
0370: if ((expand & X) != 0) {
0371: for (int ii = 0; ii < colspan; ii++)
0372: xExpand[gridX + ii] |= 1;
0373: }
0374: if ((expand & Y) != 0) {
0375: for (int ii = 0; ii < rowspan; ii++)
0376: yExpand[gridY + ii] |= 1;
0377: }
0378: gridX += colspan;
0379: if (gridX >= columnCount) {
0380: gridX = 0;
0381: gridY++;
0382: }
0383: }
0384:
0385: boolean xFixed = (tableConstraints.getFixed() & X) != 0;
0386: boolean yFixed = (tableConstraints.getFixed() & Y) != 0;
0387:
0388: /*
0389: * 4) Compute the size of cells.
0390: */
0391: int maxWidth = 0;
0392: int maxHeight = 0;
0393: boolean cellNeedsBorder = false;
0394: for (int i = 0; i < compCount; i++) {
0395: Component comp = target.getComponent(i);
0396: if (comp instanceof BorderPanel)
0397: continue;
0398: CellConstraints constraints = (CellConstraints) cellConstraints
0399: .get(comp);
0400: int colspan = constraints.getColspan();
0401: int rowspan = constraints.getRowspan();
0402:
0403: if (constraints.getBorder())
0404: cellNeedsBorder = true;
0405:
0406: Dimension d = comp.getPreferredSize();
0407: int ipadding[] = constraints.getIPadding();
0408: int compWidth = d.width + ipadding[0];
0409: int compHeight = d.height + ipadding[1];
0410:
0411: int gridX = gridPosition_x[i];
0412: int gridY = gridPosition_y[i];
0413:
0414: // Cloned because these will be modified.
0415: int padding[] = (int[]) constraints.getPadding().clone();
0416: int spacing[] = (int[]) constraints.getSpacing().clone();
0417: // Spacing around other components does not add up.
0418: if (gridY > 0) {
0419: spacing[0] -= cellSpacing[gridX][gridY - 1][0];
0420: if (spacing[0] < 0)
0421: spacing[0] = 0;
0422: }
0423: if (gridX > 0) {
0424: spacing[3] -= cellSpacing[gridX - 1][gridY][1];
0425: if (spacing[3] < 0)
0426: spacing[3] = 0;
0427: }
0428: // Store bottom and right spacing at all grid locations component occupies.
0429: for (int colOffset = 0; colOffset < colspan; colOffset++) {
0430: for (int rowOffset = 0; rowOffset < rowspan; rowOffset++) {
0431: cellSpacing[gridX + colOffset][gridY + rowOffset][0] = spacing[2];
0432: cellSpacing[gridX + colOffset][gridY + rowOffset][1] = spacing[1];
0433: }
0434: }
0435: // Increase padding by the modified spacing.
0436: padding[0] += spacing[0];
0437: padding[1] += spacing[1];
0438: padding[2] += spacing[2];
0439: padding[3] += spacing[3];
0440:
0441: compWidth += padding[1] + padding[3];
0442:
0443: int expand_count = 0;
0444: int no_expand_widths = 0;
0445: for (int ii = 0; ii < colspan; ii++) {
0446: if ((xExpand[gridX + ii] & 1) != 0) { // expand is set
0447: expand_count++;
0448: } else {
0449: no_expand_widths += widths[gridX + ii];
0450: }
0451: }
0452:
0453: if (colspan == 1) {
0454: if (compWidth > widths[gridX])
0455: widths[gridX] = compWidth;
0456: if (maxWidth < widths[gridX])
0457: maxWidth = widths[gridX];
0458: } else {
0459: for (int ii = 0; ii < colspan; ii++) {
0460: if ((xExpand[gridX + ii] & 1) != 0) { // expand is set
0461: int expandedWidth = (compWidth - no_expand_widths)
0462: / expand_count;
0463: if (expandedWidth > widths[gridX + ii])
0464: widths[gridX + ii] = expandedWidth;
0465: }
0466: if (xFixed && maxWidth < widths[gridX + ii])
0467: maxWidth = widths[gridX + ii];
0468: }
0469: }
0470:
0471: if (heights[gridY] < (compHeight + padding[0] + padding[2])
0472: / rowspan) {
0473: heights[gridY] = (compHeight + padding[0] + padding[2])
0474: / rowspan;
0475: if (yFixed && maxHeight < heights[gridY])
0476: maxHeight = heights[gridY];
0477: }
0478: }
0479:
0480: if (xFixed) {
0481: for (int i = 0; i < columnCount; i++)
0482: widths[i] = maxWidth;
0483: }
0484: if (yFixed) {
0485: for (int i = 0; i < rows; i++)
0486: heights[i] = maxHeight;
0487: }
0488:
0489: int width = 0, height = 0;
0490: for (int j = 0; j < columnCount; j++)
0491: width += widths[j];
0492: for (int j = 0; j < rows; j++)
0493: height += heights[j];
0494: Insets insets = target.getInsets();
0495: width += insets.left + insets.right;
0496: height += insets.top + insets.bottom;
0497: preferredSize = new Dimension(width, height);
0498:
0499: xExpandCount = 0;
0500: for (int i = 0; i < xExpand.length; i++) {
0501: if ((xExpand[i] & 1) != 0)
0502: xExpandCount++;
0503: }
0504: yExpandCount = 0;
0505: for (int i = 0; i < yExpand.length; i++) {
0506: if ((yExpand[i] & 1) != 0) {
0507: yExpandCount++;
0508: }
0509: }
0510:
0511: if (cellNeedsBorder || tableConstraints.getBorder()) {
0512: borderPanel = new BorderPanel();
0513: target.add(borderPanel);
0514: }
0515:
0516: valid = true;
0517: }
0518:
0519: /**
0520: * Lays out the container. This method will actually reshape the components in the target in order to satisfy the constraints
0521: * of the PnutsLayout object.
0522: * @see Container
0523: */
0524: public void layoutContainer(Container target) {
0525: int columnCount = tableConstraints.getColumns();
0526:
0527: int compCount = target.getComponentCount();
0528: if (!valid)
0529: bindContainer(target);
0530:
0531: int targetWidth = target.getSize().width;
0532: int targetHeight = target.getSize().height;
0533:
0534: int aw = 0;
0535: int ah = 0;
0536: if (xExpandCount > 0)
0537: aw = (targetWidth - preferredSize.width) / xExpandCount;
0538: if (yExpandCount > 0)
0539: ah = (targetHeight - preferredSize.height) / yExpandCount;
0540:
0541: Insets insets = target.getInsets();
0542: int w = insets.left;
0543: columnLeft[0] = w;
0544: for (int i = 1; i <= columnCount; i++) {
0545: columnLeft[i] = columnLeft[i - 1] + widths[i - 1];
0546: if ((xExpand[i - 1] & 1) != 0) {
0547: columnLeft[i] += aw;
0548: }
0549: }
0550: int h = insets.top;
0551: rowTop[0] = h;
0552: for (int i = 1; i <= rows; i++) {
0553: rowTop[i] = rowTop[i - 1] + heights[i - 1];
0554: if ((yExpand[i - 1] & 1) != 0) {
0555: rowTop[i] += ah;
0556: }
0557: }
0558:
0559: for (int i = 0; i < compCount; i++) {
0560: Component comp = target.getComponent(i);
0561: if (comp instanceof BorderPanel)
0562: continue;
0563: CellConstraints constraints = (CellConstraints) cellConstraints
0564: .get(comp);
0565: int colspan = constraints.getColspan();
0566: int rowspan = constraints.getRowspan();
0567: int align = constraints.getAlign();
0568: int[] fill = constraints.getFill();
0569:
0570: Dimension d = comp.getPreferredSize();
0571: int ipadding[] = constraints.getIPadding();
0572: int compWidth = d.width + ipadding[0];
0573: int compHeight = d.height + ipadding[1];
0574:
0575: int gridX = gridPosition_x[i];
0576: int gridY = gridPosition_y[i];
0577:
0578: // Cloned because these will be modified.
0579: int padding[] = (int[]) constraints.getPadding().clone();
0580: int spacing[] = (int[]) constraints.getSpacing().clone();
0581: // Spacing around other components does not add up.
0582: if (gridY > 0) {
0583: spacing[0] -= cellSpacing[gridX][gridY - 1][0];
0584: if (spacing[0] < 0)
0585: spacing[0] = 0;
0586: }
0587: if (gridX > 0) {
0588: spacing[3] -= cellSpacing[gridX - 1][gridY][1];
0589: if (spacing[3] < 0)
0590: spacing[3] = 0;
0591: }
0592: // Store bottom and right spacing at all grid locations component occupies.
0593: for (int colOffset = 0; colOffset < colspan; colOffset++) {
0594: for (int rowOffset = 0; rowOffset < rowspan; rowOffset++) {
0595: cellSpacing[gridX + colOffset][gridY + rowOffset][0] = spacing[2];
0596: cellSpacing[gridX + colOffset][gridY + rowOffset][1] = spacing[1];
0597: }
0598: }
0599: // Increase padding by the modified spacing.
0600: padding[0] += spacing[0];
0601: padding[1] += spacing[1];
0602: padding[2] += spacing[2];
0603: padding[3] += spacing[3];
0604:
0605: // Optionally set component size to a percentage of the cell size.
0606: if (fill[0] > 0)
0607: compWidth = (int) ((columnLeft[gridX + colspan]
0608: - columnLeft[gridX] - (padding[3] + padding[1])) * (fill[0] / 100F));
0609: if (fill[1] > 0)
0610: compHeight = (int) ((rowTop[gridY + rowspan]
0611: - rowTop[gridY] - (padding[0] + padding[2])) * (fill[1] / 100F));
0612:
0613: // Compute location of component.
0614: int x, y;
0615:
0616: if ((align & LEFT) != 0) {
0617: x = columnLeft[gridX] + padding[3];
0618: } else if ((align & RIGHT) != 0) {
0619: x = columnLeft[gridX + colspan] - compWidth
0620: - padding[1];
0621: } else {
0622: x = (columnLeft[gridX] + columnLeft[gridX + colspan]
0623: - compWidth + padding[3] - padding[1]) / 2;
0624: }
0625:
0626: if ((align & TOP) != 0) {
0627: y = rowTop[gridY] + padding[0];
0628: } else if ((align & BOTTOM) != 0) {
0629: y = rowTop[gridY + rowspan] - compHeight - padding[2];
0630: } else {
0631: y = (rowTop[gridY] + rowTop[gridY + rowspan]
0632: - compHeight + padding[0] - padding[2]) / 2;
0633: }
0634:
0635: comp.setBounds(x, y, compWidth, compHeight);
0636: }
0637:
0638: if (borderPanel != null)
0639: borderPanel.setSize(target.getSize());
0640: }
0641:
0642: private boolean fit(int x, int y, int colspan, int rowspan,
0643: BitSet[] map) {
0644: for (int i = 0; i < colspan; i++) {
0645: for (int j = 0; j < rowspan; j++) {
0646: if (x + i >= tableConstraints.getColumns())
0647: return false;
0648: if (map[x + i].get(y + j))
0649: return false;
0650: }
0651: }
0652: return true;
0653: }
0654:
0655: /**
0656: * Returns the number of rows in this layout.
0657: */
0658: public int getRows() {
0659: if (!valid)
0660: throw new RuntimeException(
0661: "PnutsLayout has not been realized.");
0662: return rows;
0663: }
0664:
0665: /**
0666: * Returns the constraints used by the table and the default constraints used for all cells in the table. Modifying this object
0667: * will modify the table's constraints.
0668: */
0669: public TableConstraints getTableConstraints() {
0670: return tableConstraints;
0671: }
0672:
0673: /**
0674: * Returns the constraints used by the cell for the specified component. Modifying this object will modify the cell's
0675: * constraints.
0676: */
0677: public TableConstraints getCellConstraints(Component comp) {
0678: return (TableConstraints) cellConstraints.get(comp);
0679: }
0680:
0681: /**
0682: * Returns the left-top point of the specified cell.
0683: */
0684: public Point getGridPoint(int gridX, int gridY) {
0685: if (!valid)
0686: return null;
0687: return new Point(columnLeft[gridX], rowTop[gridY]);
0688: }
0689:
0690: /**
0691: * Returns the bounding box for child component at the specified index.
0692: */
0693: public Rectangle getGridRectangle(Container parent, int index) {
0694: if (!valid)
0695: return null;
0696:
0697: int gridX = gridPosition_x[index];
0698: int gridY = gridPosition_y[index];
0699: int cellWidth = columnLeft[gridX];
0700: int cellHeight = rowTop[gridY];
0701:
0702: CellConstraints constraints = (CellConstraints) cellConstraints
0703: .get(parent.getComponent(index));
0704: int colspan = constraints.getColspan();
0705: int rowspan = constraints.getRowspan();
0706: return new Rectangle(cellWidth, cellHeight, columnLeft[gridX
0707: + colspan]
0708: - cellWidth, rowTop[gridY + rowspan] - cellHeight);
0709: }
0710:
0711: /**
0712: * Adds the specified component to the layout, using the specified constraint object.
0713: * @param obj Map or String defining constraints for the component.
0714: */
0715: public void addLayoutComponent(Component comp, Object obj) {
0716: if (obj instanceof Map) {
0717: setConstraints(comp, (Map) obj);
0718: } else if (obj instanceof String) {
0719: setConstraints(comp, (String) obj);
0720: }
0721: }
0722:
0723: /**
0724: * Returns the preferred dimensions for this layout given the components in the specified target container.
0725: * @see Container
0726: * @see #minimumLayoutSize
0727: */
0728: public Dimension preferredLayoutSize(Container target) {
0729: if (!valid)
0730: bindContainer(target);
0731: return preferredSize;
0732: }
0733:
0734: /**
0735: * Returns the minimum dimensions needed to layout the components contained in the specified target container.
0736: * @see #preferredLayoutSize
0737: */
0738: public Dimension minimumLayoutSize(Container target) {
0739: return preferredLayoutSize(target);
0740: }
0741:
0742: /**
0743: * Returns the maximum size of this component.
0744: * @see java.awt.Component#getMinimumSize()
0745: * @see java.awt.Component#getPreferredSize()
0746: * @see LayoutManager
0747: */
0748: public Dimension maximumLayoutSize(Container target) {
0749: return target.getMaximumSize();
0750: }
0751:
0752: /**
0753: * Invalidates the layout, indicating that if the PnutsLayout has cached information it should be discarded. This happens
0754: * automatically when cell or table constraints are modified.
0755: */
0756: public void invalidate() {
0757: synchronized (this ) {
0758: if (!valid)
0759: return;
0760: valid = false;
0761: }
0762:
0763: // Remove BorderPanel from its parent.
0764: if (borderPanel != null) {
0765: Container parent = borderPanel.getParent();
0766: if (parent != null)
0767: parent.remove(borderPanel);
0768: borderPanel = null;
0769: }
0770: }
0771:
0772: /**
0773: * Invalidates the layout, indicating that if the layout manager has cached information it should be discarded.
0774: */
0775: public void invalidateLayout(Container target) {
0776: invalidate();
0777: }
0778:
0779: /**
0780: * Adds the specified component with the specified name to the layout.
0781: */
0782: public void addLayoutComponent(String name, Component comp) {
0783: }
0784:
0785: /**
0786: * Removes the specified component from the layout.
0787: */
0788: public void removeLayoutComponent(Component comp) {
0789: }
0790:
0791: /**
0792: * Returns the alignment along the x axis. This specifies how the component would like to be aligned relative to other
0793: * components. The value should be a number between 0 and 1 where 0 represents alignment along the origin, 1 is aligned the
0794: * furthest away from the origin, 0.5 is centered, etc.
0795: */
0796: public float getLayoutAlignmentX(Container target) {
0797: return 0f;
0798: }
0799:
0800: /**
0801: * Returns the alignment along the y axis. This specifies how the component would like to be aligned relative to other
0802: * components. The value should be a number between 0 and 1 where 0 represents alignment along the origin, 1 is aligned the
0803: * furthest away from the origin, 0.5 is centered, etc.
0804: */
0805: public float getLayoutAlignmentY(Container target) {
0806: return 0f;
0807: }
0808:
0809: public String toString() {
0810: return getClass().getName() + "[" + getTableConstraints() + "]";
0811: }
0812:
0813: /**
0814: * Transforms a constraint name/value pair String into a Map.
0815: */
0816: static Map str2table(String constraintString) {
0817: Map table = new HashMap();
0818: StringTokenizer constraints = new StringTokenizer(
0819: constraintString, ",");
0820: while (constraints.hasMoreTokens()) {
0821: String token = constraints.nextToken();
0822: if (constraintString.indexOf('=') == -1)
0823: throw new LayoutException(
0824: "Invalid constraint name/value pair: " + token);
0825: StringTokenizer nameValue = new StringTokenizer(token, "=");
0826: String name = nameValue.nextToken();
0827: String value = null;
0828: if (nameValue.hasMoreTokens())
0829: value = nameValue.nextToken().trim();
0830: table.put(name.trim(), value);
0831: }
0832: return table;
0833: }
0834:
0835: static private final Integer ZERO = new Integer(0);
0836: static private Integer[] integer = new Integer[33];
0837: static {
0838: for (int i = 0; i < integer.length; i++)
0839: integer[i] = new Integer(i);
0840: }
0841:
0842: /**
0843: * Uses caching of low numbered Integer objects to reduce object creation.
0844: * @return An Integer object for the specified int primitive.
0845: */
0846: static private Integer getInteger(int i) {
0847: if (i < 32) {
0848: return integer[i];
0849: } else {
0850: return new Integer(i);
0851: }
0852: }
0853:
0854: /**
0855: * @param obj Must be a String.
0856: * @return An Integer representing the passed in String, or null if the String is null.
0857: * @throws NumberFormatException if the String could not be parsed into an Integer.
0858: */
0859: static private Integer getInteger(Object obj) {
0860: if (obj == null)
0861: return null;
0862: return getInteger(Integer.parseInt((String) obj));
0863: }
0864:
0865: /**
0866: * @param obj Must be a String.
0867: * @return An Integer[] representing the passed in String, or null if the String is null.
0868: * @throws NumberFormatException if the String could not be parsed into an Integer[].
0869: */
0870: static private Integer[] getIntegerArray(Object obj, int length) {
0871: if (obj == null)
0872: return null;
0873: String str = (String) obj;
0874: if (str.indexOf(':') == -1) {
0875: Integer value = getInteger(str);
0876: return new Integer[] { value, value, value, value };
0877: }
0878: Integer[] array = new Integer[length];
0879: StringTokenizer st = new StringTokenizer(str, ":");
0880: for (int i = 0; i < length; i++) {
0881: if (st.hasMoreTokens()) {
0882: String value = st.nextToken();
0883: array[i] = getInteger(Integer.parseInt(value));
0884: } else
0885: array[i] = ZERO;
0886: }
0887: return array;
0888: }
0889:
0890: /**
0891: * Stores constraints common to both the table and cells.
0892: */
0893: public abstract class Constraints {
0894: private Integer[] padding, spacing, ipadding, fill;
0895: private Integer align, expand;
0896: private Boolean border;
0897:
0898: /**
0899: * Configures this contraint object with a Map. See PnutsLayout class comments for constraints usage.
0900: */
0901: public void configure(Map map) {
0902: setSpacing(getIntegerArray(map.get("spacing"), 4));
0903: setPadding(getIntegerArray(map.get("padding"), 4));
0904: setIPadding(getIntegerArray(map.get("ipadding"), 2));
0905: setAlign((String) map.get("align"));
0906: setExpand((String) map.get("expand"));
0907: setFill((String) map.get("fill"));
0908:
0909: String border = (String) map.get("border");
0910: if (border != null)
0911: setBorder(new Boolean("true".equals(border)));
0912:
0913: // Legacy constraints.
0914: if (map.get("padding") == null) {
0915: Integer padx = getInteger(map.get("padx"));
0916: Integer pady = getInteger(map.get("pady"));
0917: if (padx != null || pady != null) {
0918: if (pady == null)
0919: pady = ZERO;
0920: if (padx == null)
0921: padx = ZERO;
0922: setPadding(new Integer[] { pady, padx, pady, padx });
0923: }
0924: }
0925:
0926: if (map.get("ipadding") == null) {
0927: Integer ipadx = getInteger(map.get("ipadx"));
0928: Integer ipady = getInteger(map.get("ipady"));
0929: if (ipadx != null || ipady != null) {
0930: if (ipady == null)
0931: ipady = ZERO;
0932: if (ipadx == null)
0933: ipadx = ZERO;
0934: setIPadding(new Integer[] { ipadx, ipady });
0935: }
0936: }
0937:
0938: if (map.get("align") == null) {
0939: String valign = (String) map.get("valign");
0940: if ("fill".equals(valign)) {
0941: setFill(new Integer[] { getInteger(getFill()[0]),
0942: getInteger(100) });
0943: valign = null;
0944: }
0945: String halign = (String) map.get("halign");
0946: if ("fill".equals(halign)) {
0947: setFill(new Integer[] { getInteger(100),
0948: getInteger(getFill()[1]) });
0949: halign = null;
0950: }
0951: String alignString = null;
0952: if (valign != null && halign != null)
0953: alignString = valign + ':' + halign;
0954: else if (valign != null)
0955: alignString = valign;
0956: else if (halign != null) {
0957: alignString = halign;
0958: }
0959: if (alignString != null)
0960: setAlign(alignString);
0961: }
0962: }
0963:
0964: public int getAlign() {
0965: if (align == null)
0966: return tableConstraints.getAlign();
0967: return align.intValue();
0968: }
0969:
0970: /**
0971: * Can be TOP, RIGHT, BOTTOM, LEFT, or any combination of TOP/BOTTOM and RIGHT/LEFT.
0972: */
0973: public void setAlign(Integer align) {
0974: if (align == null && this == tableConstraints)
0975: return;
0976: this .align = align;
0977: invalidate();
0978: }
0979:
0980: public void setAlign(String alignString) {
0981: if (alignString == null || alignString.length() == 0) {
0982: setAlign((Integer) null);
0983: return;
0984: }
0985:
0986: String halign, valign;
0987: StringTokenizer st = new StringTokenizer(alignString, ":");
0988: if (st.countTokens() == 2) {
0989: valign = st.nextToken();
0990: halign = st.nextToken();
0991: } else {
0992: valign = alignString;
0993: halign = alignString;
0994: }
0995:
0996: int align;
0997:
0998: if ("left".equalsIgnoreCase(halign)) {
0999: align = LEFT;
1000: } else if ("right".equalsIgnoreCase(halign)) {
1001: align = RIGHT;
1002: } else {
1003: align = CENTER;
1004: }
1005:
1006: if ("top".equalsIgnoreCase(valign)) {
1007: align |= TOP;
1008: } else if ("bottom".equalsIgnoreCase(valign)) {
1009: align |= BOTTOM;
1010: } else {
1011: align |= CENTER;
1012: }
1013:
1014: setAlign(getInteger(align));
1015: }
1016:
1017: public int getExpand() {
1018: if (expand == null)
1019: return tableConstraints.getExpand();
1020: return expand.intValue();
1021: }
1022:
1023: /**
1024: * Can be X, Y, X|Y, or NONE.
1025: */
1026: public void setExpand(Integer expand) {
1027: if (expand == null && this == tableConstraints)
1028: return;
1029: this .expand = expand;
1030: invalidate();
1031: }
1032:
1033: public void setExpand(String expandString) {
1034: if ("x".equalsIgnoreCase(expandString)) {
1035: setExpand(getInteger(X));
1036: } else if ("y".equalsIgnoreCase(expandString)) {
1037: setExpand(getInteger(Y));
1038: } else if ("xy".equalsIgnoreCase(expandString)) {
1039: setExpand(getInteger(X | Y));
1040: } else if ("none".equalsIgnoreCase(expandString)) {
1041: setExpand(getInteger(NONE));
1042: } else {
1043: setExpand((Integer) null);
1044: }
1045: }
1046:
1047: public void setFill(String fillString) {
1048: if ("x".equalsIgnoreCase(fillString))
1049: setFill(new Integer[] { getInteger(100), ZERO });
1050: else if ("y".equalsIgnoreCase(fillString))
1051: setFill(new Integer[] { ZERO, getInteger(100) });
1052: else if ("xy".equalsIgnoreCase(fillString))
1053: setFill(new Integer[] { getInteger(100),
1054: getInteger(100) });
1055: else if ("none".equalsIgnoreCase(fillString))
1056: setFill(new Integer[] { ZERO, ZERO });
1057: else
1058: setFill(getIntegerArray(fillString, 2));
1059: }
1060:
1061: public int[] getIPadding() {
1062: if (ipadding == null)
1063: return tableConstraints.getIPadding();
1064: return new int[] { ipadding[0].intValue(),
1065: ipadding[1].intValue() };
1066: }
1067:
1068: public void setIPadding(Integer[] ipadding) {
1069: if (ipadding == null && this == tableConstraints)
1070: return;
1071: this .ipadding = ipadding;
1072: invalidate();
1073: }
1074:
1075: public int[] getPadding() {
1076: if (padding == null)
1077: return tableConstraints.getPadding();
1078: return new int[] { padding[0].intValue(),
1079: padding[1].intValue(), padding[2].intValue(),
1080: padding[3].intValue() };
1081: }
1082:
1083: public void setPadding(Integer[] padding) {
1084: if (padding == null && this == tableConstraints)
1085: return;
1086: this .padding = padding;
1087: invalidate();
1088: }
1089:
1090: public int[] getSpacing() {
1091: if (spacing == null)
1092: return tableConstraints.getSpacing();
1093: return new int[] { spacing[0].intValue(),
1094: spacing[1].intValue(), spacing[2].intValue(),
1095: spacing[3].intValue() };
1096: }
1097:
1098: public void setSpacing(Integer[] spacing) {
1099: if (spacing == null && this == tableConstraints)
1100: return;
1101: this .spacing = spacing;
1102: invalidate();
1103: }
1104:
1105: public boolean getBorder() {
1106: if (border == null)
1107: return tableConstraints.getBorder();
1108: return border.booleanValue();
1109: }
1110:
1111: public void setBorder(Boolean border) {
1112: if (border == null && this == tableConstraints)
1113: return;
1114: this .border = border;
1115: invalidate();
1116: }
1117:
1118: public int[] getFill() {
1119: if (fill == null)
1120: return tableConstraints.getFill();
1121: return new int[] { fill[0].intValue(), fill[1].intValue() };
1122: }
1123:
1124: public void setFill(Integer[] fill) {
1125: if (fill == null && this == tableConstraints)
1126: return;
1127: this .fill = fill;
1128: invalidate();
1129: }
1130:
1131: public String toString() {
1132: StringBuffer buffer = new StringBuffer(150);
1133:
1134: int align = getAlign();
1135: String valign = "";
1136: if ((align & TOP) != 0)
1137: valign = "top";
1138: else if ((align & BOTTOM) != 0)
1139: valign = "bottom";
1140: else {
1141: valign = "center";
1142: }
1143: String halign = "";
1144: if ((align & LEFT) != 0)
1145: halign = "left";
1146: else if ((align & RIGHT) != 0)
1147: halign = "right";
1148: else {
1149: halign = "center";
1150: }
1151: if (valign.equals(halign)) {
1152: if (!valign.equals("center")) {
1153: buffer.append("align=");
1154: buffer.append(valign);
1155: }
1156: } else {
1157: buffer.append("align=");
1158: buffer.append(valign);
1159: buffer.append(':');
1160: buffer.append(halign);
1161: }
1162:
1163: int expand = getExpand();
1164: String expandString = "";
1165: if ((expand & X) != 0)
1166: expandString += "x";
1167: if ((expand & Y) != 0)
1168: expandString += "y";
1169: if (expandString.length() > 0) {
1170: if (buffer.length() > 0)
1171: buffer.append(", ");
1172: buffer.append("expand=");
1173: buffer.append(expandString);
1174: }
1175:
1176: int[] spacing = getSpacing();
1177: if (spacing[0] != 0 || spacing[1] != 0 || spacing[2] != 0
1178: || spacing[3] != 0) {
1179: if (buffer.length() > 0)
1180: buffer.append(", ");
1181: buffer.append("spacing=");
1182: if (spacing[0] == spacing[1]
1183: && spacing[1] == spacing[2]
1184: && spacing[2] == spacing[3]) {
1185: buffer.append(spacing[0]);
1186: } else {
1187: buffer.append(spacing[0]);
1188: buffer.append(':');
1189: buffer.append(spacing[1]);
1190: buffer.append(':');
1191: buffer.append(spacing[2]);
1192: buffer.append(':');
1193: buffer.append(spacing[3]);
1194: }
1195: }
1196:
1197: int[] padding = getPadding();
1198: if (padding[0] != 0 || padding[1] != 0 || padding[2] != 0
1199: || padding[3] != 0) {
1200: if (buffer.length() > 0)
1201: buffer.append(", ");
1202: buffer.append("padding=");
1203: if (padding[0] == padding[1]
1204: && padding[1] == padding[2]
1205: && padding[2] == padding[3]) {
1206: buffer.append(padding[0]);
1207: } else {
1208: buffer.append(padding[0]);
1209: buffer.append(':');
1210: buffer.append(padding[1]);
1211: buffer.append(':');
1212: buffer.append(padding[2]);
1213: buffer.append(':');
1214: buffer.append(padding[3]);
1215: }
1216: }
1217:
1218: int[] ipadding = getIPadding();
1219: if (ipadding[0] != 0 || ipadding[1] != 0) {
1220: if (buffer.length() > 0)
1221: buffer.append(", ");
1222: buffer.append("ipadding=");
1223: if (ipadding[0] == ipadding[1]) {
1224: buffer.append(ipadding[0]);
1225: } else {
1226: buffer.append(ipadding[0]);
1227: buffer.append(':');
1228: buffer.append(ipadding[1]);
1229: }
1230: }
1231:
1232: int[] fill = getFill();
1233: if (fill[0] != 0 || fill[1] != 0) {
1234: if (buffer.length() > 0)
1235: buffer.append(", ");
1236: buffer.append("fill=");
1237: if (fill[0] == fill[1]) {
1238: buffer.append(fill[0]);
1239: } else {
1240: buffer.append(fill[0]);
1241: buffer.append(':');
1242: buffer.append(fill[1]);
1243: }
1244: }
1245:
1246: if (getBorder()) {
1247: if (buffer.length() > 0)
1248: buffer.append(", ");
1249: buffer.append("border=true");
1250: }
1251:
1252: return buffer.toString();
1253: }
1254: }
1255:
1256: /**
1257: * Stores constraints for the table and default constraints for the table cells. These defaults are used for all cells that
1258: * don't specify a value. This means TableConstraints fields canont be set to null.
1259: */
1260: public class TableConstraints extends Constraints {
1261: private int columns = 1;
1262: private int fixed = NONE;
1263:
1264: public TableConstraints() {
1265: setAlign(getInteger(CENTER));
1266: setExpand(getInteger(NONE));
1267: setIPadding(new Integer[] { ZERO, ZERO });
1268: setFill(new Integer[] { ZERO, ZERO });
1269: setSpacing(new Integer[] { ZERO, ZERO, ZERO, ZERO });
1270: setPadding(new Integer[] { ZERO, ZERO, ZERO, ZERO });
1271: setBorder(Boolean.FALSE);
1272: }
1273:
1274: public TableConstraints(Map map) {
1275: this ();
1276: configure(map);
1277: }
1278:
1279: /**
1280: * Any null values in the map will not be set on the TableConstraints.
1281: */
1282: public void configure(Map map) {
1283: super .configure(map);
1284: if (map.get("columns") != null)
1285: setColumns(Integer
1286: .parseInt((String) map.get("columns")));
1287: if (map.get("uniform") != null)
1288: setFixed((String) map.get("colspan"));
1289:
1290: // Legacy constraints.
1291: if (map.get("columns") == null && map.get("cols") != null)
1292: setColumns(Integer.parseInt((String) map.get("cols")));
1293: }
1294:
1295: public int getColumns() {
1296: return columns;
1297: }
1298:
1299: public void setColumns(int columns) {
1300: this .columns = columns;
1301: invalidate();
1302: }
1303:
1304: public int getFixed() {
1305: return fixed;
1306: }
1307:
1308: /**
1309: * Can be X, Y, X|Y, or NONE.
1310: */
1311: public void setFixed(int fixed) {
1312: this .fixed = fixed;
1313: invalidate();
1314: }
1315:
1316: public void setFixed(String expandString) {
1317: if ("x".equalsIgnoreCase(expandString)) {
1318: setFixed(X);
1319: } else if ("y".equalsIgnoreCase(expandString)) {
1320: setFixed(Y);
1321: } else if ("xy".equalsIgnoreCase(expandString)) {
1322: setFixed(X | Y);
1323: } else {
1324: setFixed(NONE);
1325: }
1326: }
1327:
1328: public String toString() {
1329: StringBuffer buffer = new StringBuffer(150);
1330:
1331: buffer.append("columns=");
1332: buffer.append(getColumns());
1333:
1334: int fixed = getFixed();
1335: String fixedString = "";
1336: if ((fixed & X) != 0)
1337: fixedString += "x";
1338: if ((fixed & Y) != 0)
1339: fixedString += "y";
1340: if (fixedString.length() > 0) {
1341: buffer.append(", uniform=");
1342: buffer.append(fixedString);
1343: }
1344:
1345: buffer.append(", ");
1346: buffer.append(super .toString());
1347:
1348: return buffer.toString();
1349: }
1350: }
1351:
1352: /**
1353: * Stores constraints for the cells.
1354: */
1355: public class CellConstraints extends Constraints {
1356: private int colspan = 1, rowspan = 1;
1357:
1358: public CellConstraints() {
1359: }
1360:
1361: public CellConstraints(Map map) {
1362: configure(map);
1363: }
1364:
1365: public void configure(Map map) {
1366: super .configure(map);
1367: if (map.get("colspan") != null)
1368: setColspan(Integer
1369: .parseInt((String) map.get("colspan")));
1370: if (map.get("rowspan") != null)
1371: setRowspan(Integer
1372: .parseInt((String) map.get("rowspan")));
1373: }
1374:
1375: public int getRowspan() {
1376: return rowspan;
1377: }
1378:
1379: public void setRowspan(int rowspan) {
1380: if (rowspan <= 0)
1381: throw new IndexOutOfBoundsException(
1382: "rowspan cannot be less than 1.");
1383: this .rowspan = rowspan;
1384: invalidate();
1385: }
1386:
1387: public int getColspan() {
1388: return colspan;
1389: }
1390:
1391: public void setColspan(int colspan) {
1392: if (colspan <= 0)
1393: throw new IndexOutOfBoundsException(
1394: "colspan cannot be less than 1.");
1395: this .colspan = colspan;
1396: invalidate();
1397: }
1398:
1399: public String toString() {
1400: StringBuffer buffer = new StringBuffer(150);
1401:
1402: buffer.append("colspan=");
1403: buffer.append(getColspan());
1404:
1405: buffer.append(", rowspan=");
1406: buffer.append(getRowspan());
1407:
1408: buffer.append(", ");
1409: buffer.append(super .toString());
1410:
1411: return buffer.toString();
1412: }
1413: }
1414:
1415: /**
1416: * Panel for drawing grid borders.
1417: */
1418: private class BorderPanel extends JPanel {
1419: public BorderPanel() {
1420: setOpaque(false);
1421: }
1422:
1423: public void paint(Graphics g) {
1424: Container target = getParent();
1425: for (int i = 0, n = target.getComponentCount(); i < n; i++) {
1426: Component comp = target.getComponent(i);
1427: if (comp instanceof BorderPanel)
1428: continue;
1429: CellConstraints constraints = (CellConstraints) cellConstraints
1430: .get(comp);
1431: if (!constraints.getBorder())
1432: continue;
1433: int colspan = constraints.getColspan();
1434: int rowspan = constraints.getRowspan();
1435: int gridX = gridPosition_x[i];
1436: int gridY = gridPosition_y[i];
1437: int x = columnLeft[gridX];
1438: int y = rowTop[gridY];
1439: int width = columnLeft[gridX + colspan] - x;
1440: int height = rowTop[gridY + rowspan] - y;
1441: g.drawRect(x, y, width, height);
1442: }
1443: }
1444: }
1445: }
|