001: /*
002: * VariableGridLayout.java - a grid layout manager with variable cell sizes
003: * :tabSize=8:indentSize=8:noTabs=false:
004: *
005: * Originally written by Dirk Moebius for the jEdit project. This work has been
006: * placed into the public domain. You may use this work in any way and for any
007: * purpose you wish.
008: *
009: * THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
010: * IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
011: * _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
012: * OR REDISTRIBUTION OF THIS SOFTWARE.
013: */
014:
015: package org.gjt.sp.jedit.gui;
016:
017: import java.awt.Component;
018: import java.awt.Container;
019: import java.awt.Dimension;
020: import java.awt.Insets;
021: import java.awt.LayoutManager2;
022:
023: import java.util.Arrays;
024:
025: /**
026: * The <code>VariableGridLayout</code> class is a layout manager
027: * that lays out a container's components in a rectangular grid
028: * with variable cell sizes.<p>
029: *
030: * The container is divided into rectangles, and one component is placed
031: * in each rectangle. Each row is as large as the largest component in
032: * that row, and each column is as wide as the widest component in
033: * that column.<p>
034: *
035: * This behavior is basically the same as in
036: * <code>java.awt.GridLayout</code>, but with different row heights and
037: * column widths for each row/column.<p>
038: *
039: * For example, the following is an applet that lays out six buttons
040: * into three rows and two columns:<p>
041: *
042: * <blockquote><pre>
043: * import java.awt.*;
044: * import java.applet.Applet;
045: * public class ButtonGrid extends Applet {
046: * public void init() {
047: * setLayout(new VariableGridLayout(VariableGridLayout.FIXED_NUM_COLUMNS, 2));
048: * add(new Button("1"));
049: * add(new Button("2"));
050: * add(new Button("3"));
051: * add(new Button("4"));
052: * add(new Button("5"));
053: * add(new Button("6"));
054: * }
055: * }
056: * </pre></blockquote><p>
057: *
058: * <b>Programmer's remark:</b> VariableGridLayout could be faster, if it would
059: * reside in the package java.awt, because then it could access some
060: * package private fields of <code>Container</code> or
061: * <code>Component</code>. Instead, it has to call
062: * <code>Component.getSize()</code>,
063: * which allocates memory on the heap.<p>
064: *
065: * <b>Todo:</b>
066: * <ul>
067: * <li>Ability to span components over more than one cell horizontally and vertically.
068: * </ul>
069: *
070: * @author Dirk Moebius, Björn "Vampire" Kautler
071: * @version 1.5
072: * @see java.awt.GridLayout
073: */
074: public class VariableGridLayout implements LayoutManager2,
075: java.io.Serializable {
076: public static final int FIXED_NUM_ROWS = 1;
077: public static final int FIXED_NUM_COLUMNS = 2;
078:
079: private static enum LayoutSize {
080: MINIMUM, MAXIMUM, PREFERRED
081: }
082:
083: /**
084: * Creates a variable grid layout manager with the specified mode,
085: * size, horizontal and vertical gap, eventually taking minimum and maximum
086: * sizes into account when distributing free space, depending on takeSizesIntoAccount
087: * and the specified distance to the borders.
088: *
089: * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
090: * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS (>0)
091: * @param hgap The horizontal space between cells (>=0)
092: * @param vgap The vertical space between cells (>=0)
093: * @param takeSizesIntoAccount Whether to take minimum and maximum sizes into account when distributing free space
094: * @param distanceToBorders The distances to the borders
095: * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
096: */
097: public VariableGridLayout(int mode, int size, int hgap, int vgap,
098: boolean takeSizesIntoAccount, Insets distanceToBorders) {
099: if (mode != FIXED_NUM_ROWS && mode != FIXED_NUM_COLUMNS) {
100: throw new IllegalArgumentException(
101: "illegal mode; value is " + mode);
102: }
103: if (size <= 0) {
104: throw new IllegalArgumentException(
105: "size cannot be zero or less; value is " + size);
106: }
107: if (hgap < 0) {
108: throw new IllegalArgumentException(
109: "hgap cannot be negative; value is " + hgap);
110: }
111: if (vgap < 0) {
112: throw new IllegalArgumentException(
113: "vgap cannot be negative; value is " + vgap);
114: }
115: this .mode = mode;
116: this .size = size;
117: this .hgap = hgap;
118: this .vgap = vgap;
119: this .takeSizesIntoAccount = takeSizesIntoAccount;
120: this .distanceToBorders = (Insets) distanceToBorders.clone();
121: }
122:
123: /**
124: * Creates a variable grid layout manager with the specified mode,
125: * size, horizontal and vertical gap, eventually taking minimum and maximum
126: * sizes into account when distributing free space, depending on takeSizesIntoAccount
127: * and zero distance to borders.
128: *
129: * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
130: * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS (>0)
131: * @param hgap The horizontal space between cells (>=0)
132: * @param vgap The vertical space between cells (>=0)
133: * @param takeSizesIntoAccount Whether to take minimum and maximum sizes into account when distributing free space
134: * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
135: */
136: public VariableGridLayout(int mode, int size, int hgap, int vgap,
137: boolean takeSizesIntoAccount) {
138: this (mode, size, hgap, vgap, takeSizesIntoAccount, new Insets(
139: 0, 0, 0, 0));
140: }
141:
142: /**
143: * Creates a variable grid layout manager with the specified mode,
144: * size, horizontal and vertical gap, and zero distance to borders.
145: * The minimum and maximum Component sizes are not taken into account
146: * when distributing free space.
147: *
148: * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
149: * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS
150: * @param hgap The horizontal space between cells
151: * @param vgap The vertical space between cells
152: * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
153: */
154: public VariableGridLayout(int mode, int size, int hgap, int vgap) {
155: this (mode, size, hgap, vgap, false, new Insets(0, 0, 0, 0));
156: }
157:
158: /**
159: * Creates a variable grid layout manager with the specified mode
160: * and size, zero horizontal and vertical gap, and zero distance to borders.
161: * Does not take minimum and maximum Component sizes into account when distributing
162: * free space.
163: *
164: * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
165: * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS
166: * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0
167: */
168: public VariableGridLayout(int mode, int size) {
169: this (mode, size, 0, 0, false, new Insets(0, 0, 0, 0));
170: }
171:
172: /**
173: * Creates a variable grid layout manager with mode FIXED_NUM_ROWS,
174: * number of rows == 1, zero horizontal and vertical gap, and zero distance to borders.
175: * Does not take minimum and maximum Component sizes into account when
176: * distributing free space.
177: */
178: public VariableGridLayout() {
179: this (FIXED_NUM_ROWS, 1, 0, 0, false, new Insets(0, 0, 0, 0));
180: }
181:
182: /**
183: * Not used in this class.
184: */
185: public void addLayoutComponent(String name, Component component) {
186: }
187:
188: /**
189: * Not used in this class.
190: */
191: public void addLayoutComponent(Component component,
192: Object constraints) {
193: }
194:
195: /**
196: * Not used in this class.
197: */
198: public void removeLayoutComponent(Component component) {
199: }
200:
201: /**
202: * Always returns 0.5.
203: */
204: public float getLayoutAlignmentX(Container container) {
205: return 0.5f;
206: }
207:
208: /**
209: * Always returns 0.5.
210: */
211: public float getLayoutAlignmentY(Container container) {
212: return 0.5f;
213: }
214:
215: public Dimension preferredLayoutSize(Container parent) {
216: return getLayoutSize(parent, LayoutSize.PREFERRED);
217: }
218:
219: public Dimension minimumLayoutSize(Container parent) {
220: return getLayoutSize(parent, LayoutSize.MINIMUM);
221: }
222:
223: public Dimension maximumLayoutSize(Container parent) {
224: return getLayoutSize(parent, LayoutSize.MAXIMUM);
225: }
226:
227: public void layoutContainer(Container parent) {
228: synchronized (parent.getTreeLock()) {
229: update(parent);
230:
231: int ncomponents = parent.getComponentCount();
232:
233: if (ncomponents == 0) {
234: return;
235: }
236:
237: // Pass 1: compute minimum, preferred and maximum row heights / column widths
238: int total_height = 0;
239: Arrays.fill(row_heights, 0);
240: Arrays.fill(col_widths, 0);
241: if (takeSizesIntoAccount) {
242: Arrays.fill(minimum_row_heights, 0);
243: Arrays.fill(minimum_col_widths, 0);
244: Arrays.fill(maximum_row_heights, Integer.MAX_VALUE);
245: Arrays.fill(maximum_col_widths, Integer.MAX_VALUE);
246: }
247: for (int r = 0, i = 0; r < nrows; r++) {
248: for (int c = 0; c < ncols; c++, i++) {
249: if (i < ncomponents) {
250: Component comp = parent.getComponent(i);
251: Dimension d = comp.getPreferredSize();
252: row_heights[r] = Math.max(row_heights[r],
253: d.height);
254: col_widths[c] = Math
255: .max(col_widths[c], d.width);
256: if (takeSizesIntoAccount) {
257: d = comp.getMinimumSize();
258: minimum_row_heights[r] = Math.max(
259: minimum_row_heights[r], d.height);
260: minimum_col_widths[c] = Math.max(
261: minimum_col_widths[c], d.width);
262: d = comp.getMaximumSize();
263: maximum_row_heights[r] = Math.min(
264: maximum_row_heights[r], d.height);
265: maximum_col_widths[c] = Math.min(
266: maximum_col_widths[c], d.width);
267: }
268: } else {
269: break;
270: }
271: }
272: if (takeSizesIntoAccount) {
273: // correct cases where
274: // minimum_row_heights[row] <= row_heights[row] <= maximum_row_heights[row]
275: // is not true by clipping to the minimum_row_heights and maximum_row_heights
276: if (minimum_row_heights[r] >= maximum_row_heights[r]) {
277: maximum_row_heights[r] = minimum_row_heights[r];
278: row_heights[r] = minimum_row_heights[r];
279: } else if (row_heights[r] < minimum_row_heights[r]) {
280: row_heights[r] = minimum_row_heights[r];
281: } else if (row_heights[r] > maximum_row_heights[r]) {
282: row_heights[r] = maximum_row_heights[r];
283: }
284: }
285: total_height += row_heights[r];
286: }
287:
288: int total_width = 0;
289: for (int c = 0; c < ncols; c++) {
290: if (takeSizesIntoAccount) {
291: // correct cases where
292: // minimum_col_widths[col] <= col_widths[col] <= maximum_col_widths[col]
293: // is not true by clipping to the minimum_col_widths and maximum_col_widths
294: if (minimum_col_widths[c] >= maximum_col_widths[c]) {
295: maximum_col_widths[c] = minimum_col_widths[c];
296: col_widths[c] = minimum_col_widths[c];
297: } else if (col_widths[c] < minimum_col_widths[c]) {
298: col_widths[c] = minimum_col_widths[c];
299: } else if (col_widths[c] > maximum_col_widths[c]) {
300: col_widths[c] = maximum_col_widths[c];
301: }
302: }
303: total_width += col_widths[c];
304: }
305:
306: // Pass 2: redistribute free space
307: Dimension parent_size = parent.getSize();
308: Insets insets = parent.getInsets();
309: int free_height = parent_size.height - insets.top
310: - insets.bottom - (nrows - 1) * vgap
311: - distanceToBorders.top - distanceToBorders.bottom;
312: int free_width = parent_size.width - insets.left
313: - insets.right - (ncols - 1) * hgap
314: - distanceToBorders.left - distanceToBorders.right;
315:
316: redistributeSpace(total_height, free_height,
317: takeSizesIntoAccount, nrows, row_heights,
318: minimum_row_heights, maximum_row_heights);
319:
320: redistributeSpace(total_width, free_width,
321: takeSizesIntoAccount, ncols, col_widths,
322: minimum_col_widths, maximum_col_widths);
323:
324: // Pass 3: layout components
325: for (int r = 0, y = insets.top + distanceToBorders.top, i = 0; r < nrows; y += row_heights[r]
326: + vgap, r++) {
327: for (int c = 0, x = insets.left
328: + distanceToBorders.left; c < ncols; x += col_widths[c]
329: + hgap, c++, i++) {
330: if (i < ncomponents) {
331: Component comp = parent.getComponent(i);
332: Dimension d = comp.getMaximumSize();
333: int width = col_widths[c];
334: int height = row_heights[r];
335: int xCorrection = 0;
336: int yCorrection = 0;
337: if (width > d.width) {
338: xCorrection = (int) ((width - d.width) * comp
339: .getAlignmentX());
340: width = d.width;
341: }
342: if (height > d.height) {
343: yCorrection = (int) ((height - d.height) * comp
344: .getAlignmentY());
345: height = d.height;
346: }
347:
348: comp.setBounds(x + xCorrection,
349: y + yCorrection, width, height);
350: }
351: }
352: }
353: } // synchronized
354: }
355:
356: public void invalidateLayout(Container container) {
357: }
358:
359: /**
360: * Returns the string representation of this variable grid layout's values.
361: * @return a string representation of this variable grid layout.
362: */
363: public String toString() {
364: return getClass().getName()
365: + "[mode="
366: + ((FIXED_NUM_ROWS == mode) ? "FIXED_NUM_ROWS"
367: : ((FIXED_NUM_COLUMNS == mode) ? "FIXED_NUM_COLUMNS"
368: : "UNKNOWN(" + mode + ")")) + ",size="
369: + size + ",hgap=" + hgap + ",vgap=" + vgap
370: + ",takeSizesIntoAccount=" + takeSizesIntoAccount
371: + ",distanceToBorders=" + distanceToBorders + "]";
372: }
373:
374: /**
375: * @param which if LayoutSize.MINIMUM compute minimum layout size,
376: * if LayoutSize.MAXIMUM compute maximum layout size,
377: * if LayoutSize.PREFERRED compute preferred layout size.
378: */
379: private Dimension getLayoutSize(Container parent, LayoutSize which) {
380: synchronized (parent.getTreeLock()) {
381: update(parent);
382:
383: int ncomponents = parent.getComponentCount();
384: long h = 0;
385: long w = 0;
386:
387: for (int r = 0, i = 0; r < nrows; r++) {
388: int row_height = 0;
389: for (int c = 0; c < ncols; c++, i++) {
390: if (i < ncomponents) {
391: switch (which) {
392: case MINIMUM:
393: row_height = Math.max(row_height,
394: parent.getComponent(i)
395: .getMinimumSize().height);
396: break;
397:
398: case MAXIMUM:
399: row_height = Math.max(row_height,
400: parent.getComponent(i)
401: .getMaximumSize().height);
402: break;
403:
404: case PREFERRED:
405: row_height = Math.max(row_height,
406: parent.getComponent(i)
407: .getPreferredSize().height);
408: break;
409:
410: default:
411: throw new InternalError(
412: "Missing case branch for LayoutSize: "
413: + which);
414: }
415: }
416: }
417: h += row_height;
418: }
419:
420: for (int c = 0; c < ncols; c++) {
421: int col_width = 0;
422: for (int r = 0; r < nrows; r++) {
423: int i = r * ncols + c;
424: if (i < ncomponents) {
425: switch (which) {
426: case MINIMUM:
427: col_width = Math.max(col_width,
428: parent.getComponent(i)
429: .getMinimumSize().width);
430: break;
431:
432: case MAXIMUM:
433: col_width = Math.max(col_width,
434: parent.getComponent(i)
435: .getMaximumSize().width);
436: break;
437:
438: case PREFERRED:
439: col_width = Math.max(col_width,
440: parent.getComponent(i)
441: .getPreferredSize().width);
442: break;
443:
444: default:
445: throw new InternalError(
446: "Missing case branch for LayoutSize: "
447: + which);
448: }
449: }
450: }
451: w += col_width;
452: }
453:
454: Insets insets = parent.getInsets();
455: w += insets.left + insets.right + ((ncols - 1) * hgap)
456: + distanceToBorders.left + distanceToBorders.right;
457: h += insets.top + insets.bottom + ((nrows - 1) * vgap)
458: + distanceToBorders.top + distanceToBorders.bottom;
459: if (w > Integer.MAX_VALUE) {
460: w = Integer.MAX_VALUE;
461: }
462: if (h > Integer.MAX_VALUE) {
463: h = Integer.MAX_VALUE;
464: }
465: return new Dimension((int) w, (int) h);
466: }
467: }
468:
469: private void update(Container container) {
470: int ncomponents = container.getComponentCount();
471: int old_nrows = nrows;
472: int old_ncols = ncols;
473: if (this .mode == FIXED_NUM_ROWS) {
474: nrows = this .size;
475: ncols = (ncomponents + nrows - 1) / nrows;
476: } else {
477: ncols = this .size;
478: nrows = (ncomponents + ncols - 1) / ncols;
479: }
480: if (old_nrows != nrows) {
481: row_heights = new int[nrows];
482: if (takeSizesIntoAccount) {
483: minimum_row_heights = new int[nrows];
484: maximum_row_heights = new int[nrows];
485: }
486: }
487: if (old_ncols != ncols) {
488: col_widths = new int[ncols];
489: if (takeSizesIntoAccount) {
490: minimum_col_widths = new int[ncols];
491: maximum_col_widths = new int[ncols];
492: }
493: }
494: }
495:
496: private void redistributeSpace(int total_size, int free_size,
497: boolean takeSizesIntoAccount, int nelements,
498: int[] element_sizes, int[] minimum_element_sizes,
499: int[] maximum_element_sizes) {
500: if (total_size != free_size) {
501: if (takeSizesIntoAccount) {
502: boolean grow = total_size < free_size;
503: // calculate the size that is available for redistribution
504: free_size = (free_size - total_size) * (grow ? 1 : -1);
505: while (free_size != 0) {
506: // calculate the amount of elements that can be resized without violating
507: // the minimum and maximum sizes and their current cumulated size
508: int modifyableAmount = 0;
509: int modifySize = 0;
510: for (int i = 0; i < nelements; i++) {
511: if ((grow && (element_sizes[i] < maximum_element_sizes[i]))
512: || (!grow && (element_sizes[i] > minimum_element_sizes[i]))) {
513: modifyableAmount++;
514: modifySize += element_sizes[i];
515: }
516: }
517: boolean checkBounds = true;
518: // if all elements are at their minimum or maximum size, resize all elements
519: if (0 == modifyableAmount) {
520: for (int i = 0; i < nelements; i++) {
521: modifySize += element_sizes[i];
522: }
523: checkBounds = false;
524: modifyableAmount = nelements;
525: }
526: // to prevent an endless loop if the container gets resized to a very small amount
527: if (modifySize == 0) {
528: break;
529: }
530: // resize the elements
531: if (free_size < modifyableAmount) {
532: for (int i = 0; i < nelements; i++) {
533: if ((free_size != 0)
534: && (!checkBounds || (checkBounds
535: && (grow && (element_sizes[i] < maximum_element_sizes[i])) || (!grow && (element_sizes[i] > minimum_element_sizes[i]))))) {
536: element_sizes[i] += (grow ? 1 : -1);
537: if (0 > element_sizes[i]) {
538: element_sizes[i] = 0;
539: }
540: free_size--;
541: }
542: }
543: } else {
544: int modifySizeAddition = 0;
545: for (int i = 0; i < nelements; i++) {
546: int modifyableSize = (checkBounds ? (grow ? maximum_element_sizes[i]
547: - element_sizes[i]
548: : element_sizes[i]
549: - minimum_element_sizes[i])
550: : Integer.MAX_VALUE
551: - element_sizes[i]);
552: int elementModifySize = (int) ((double) free_size
553: / (double) modifySize * (double) element_sizes[i]);
554: if (elementModifySize <= modifyableSize) {
555: element_sizes[i] += (grow ? elementModifySize
556: : -elementModifySize);
557: modifySizeAddition += (grow ? elementModifySize
558: : -elementModifySize);
559: free_size -= elementModifySize;
560: } else {
561: element_sizes[i] += (grow ? modifyableSize
562: : -modifyableSize);
563: modifySizeAddition += (grow ? modifyableSize
564: : -modifyableSize);
565: free_size -= modifyableSize;
566: }
567: if (0 > element_sizes[i]) {
568: element_sizes[i] = 0;
569: }
570: }
571: modifySize += modifySizeAddition;
572: }
573: }
574: } else {
575: double d = (double) free_size / (double) total_size;
576: for (int i = 0; i < nelements; i++) {
577: element_sizes[i] = (int) (element_sizes[i] * d);
578: }
579: }
580: }
581: }
582:
583: private int mode;
584: private int size;
585: private int hgap;
586: private int vgap;
587: private boolean takeSizesIntoAccount;
588: private Insets distanceToBorders;
589: private transient int nrows = -1;
590: private transient int ncols = -1;
591: private transient int[] minimum_row_heights = null;
592: private transient int[] minimum_col_widths = null;
593: private transient int[] row_heights = null;
594: private transient int[] col_widths = null;
595: private transient int[] maximum_row_heights = null;
596: private transient int[] maximum_col_widths = null;
597: }
|