/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/gui/VariableGridLayout.java
Java | 643 lines | 443 code | 44 blank | 156 comment | 77 complexity | 216a6b6f57dbe6e8e902866f784361c4 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
1/*
2 * VariableGridLayout.java - a grid layout manager with variable cell sizes
3 * :tabSize=8:indentSize=8:noTabs=false:
4 *
5 * Originally written by Dirk Moebius for the jEdit project. This work has been
6 * placed into the public domain. You may use this work in any way and for any
7 * purpose you wish.
8 *
9 * THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
10 * IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
11 * _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
12 * OR REDISTRIBUTION OF THIS SOFTWARE.
13 */
14
15package org.gjt.sp.jedit.gui;
16
17import java.awt.Component;
18import java.awt.Container;
19import java.awt.Dimension;
20import java.awt.Insets;
21import java.awt.LayoutManager2;
22
23import java.util.Arrays;
24
25/**
26 * The <code>VariableGridLayout</code> class is a layout manager
27 * that lays out a container's components in a rectangular grid
28 * with variable cell sizes.<p>
29 *
30 * The container is divided into rectangles, and one component is placed
31 * in each rectangle. Each row is as large as the largest component in
32 * that row, and each column is as wide as the widest component in
33 * that column.<p>
34 *
35 * This behavior is basically the same as in
36 * <code>java.awt.GridLayout</code>, but with different row heights and
37 * column widths for each row/column.<p>
38 *
39 * For example, the following is an applet that lays out six buttons
40 * into three rows and two columns:<p>
41 *
42 * <blockquote><pre>
43 * import java.awt.*;
44 * import java.applet.Applet;
45 * public class ButtonGrid extends Applet {
46 * public void init() {
47 * setLayout(new VariableGridLayout(VariableGridLayout.FIXED_NUM_COLUMNS, 2));
48 * add(new Button("1"));
49 * add(new Button("2"));
50 * add(new Button("3"));
51 * add(new Button("4"));
52 * add(new Button("5"));
53 * add(new Button("6"));
54 * }
55 * }
56 * </pre></blockquote><p>
57 *
58 * <b>Programmer's remark:</b> VariableGridLayout could be faster, if it would
59 * reside in the package java.awt, because then it could access some
60 * package private fields of <code>Container</code> or
61 * <code>Component</code>. Instead, it has to call
62 * <code>Component.getSize()</code>,
63 * which allocates memory on the heap.<p>
64 *
65 * <b>Todo:</b>
66 * <ul>
67 * <li>Ability to span components over more than one cell horizontally and vertically.
68 * </ul>
69 *
70 * @author Dirk Moebius, Bj??rn "Vampire" Kautler
71 * @version 1.5
72 * @see java.awt.GridLayout
73 */
74public class VariableGridLayout implements LayoutManager2, java.io.Serializable
75{
76 public static final int FIXED_NUM_ROWS = 1;
77 public static final int FIXED_NUM_COLUMNS = 2;
78
79 private static enum LayoutSize { MINIMUM, MAXIMUM, PREFERRED }
80
81 /**
82 * Creates a variable grid layout manager with the specified mode,
83 * size, horizontal and vertical gap, eventually taking minimum and maximum
84 * sizes into account when distributing free space, depending on takeSizesIntoAccount
85 * and the specified distance to the borders.
86 *
87 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
88 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS (>0)
89 * @param hgap The horizontal space between cells (>=0)
90 * @param vgap The vertical space between cells (>=0)
91 * @param takeSizesIntoAccount Whether to take minimum and maximum sizes into account when distributing free space
92 * @param distanceToBorders The distances to the borders
93 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
94 */
95 public VariableGridLayout(int mode, int size, int hgap, int vgap, boolean takeSizesIntoAccount, Insets distanceToBorders)
96 {
97 if (mode != FIXED_NUM_ROWS && mode != FIXED_NUM_COLUMNS)
98 {
99 throw new IllegalArgumentException("illegal mode; value is " + mode);
100 }
101 if (size <= 0)
102 {
103 throw new IllegalArgumentException("size cannot be zero or less; value is " + size);
104 }
105 if (hgap < 0)
106 {
107 throw new IllegalArgumentException("hgap cannot be negative; value is " + hgap);
108 }
109 if (vgap < 0)
110 {
111 throw new IllegalArgumentException("vgap cannot be negative; value is " + vgap);
112 }
113 this.mode = mode;
114 this.size = size;
115 this.hgap = hgap;
116 this.vgap = vgap;
117 this.takeSizesIntoAccount = takeSizesIntoAccount;
118 this.distanceToBorders = (Insets)distanceToBorders.clone();
119 }
120
121 /**
122 * Creates a variable grid layout manager with the specified mode,
123 * size, horizontal and vertical gap, eventually taking minimum and maximum
124 * sizes into account when distributing free space, depending on takeSizesIntoAccount
125 * and zero distance to borders.
126 *
127 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
128 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS (>0)
129 * @param hgap The horizontal space between cells (>=0)
130 * @param vgap The vertical space between cells (>=0)
131 * @param takeSizesIntoAccount Whether to take minimum and maximum sizes into account when distributing free space
132 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
133 */
134 public VariableGridLayout(int mode, int size, int hgap, int vgap, boolean takeSizesIntoAccount)
135 {
136 this(mode, size, hgap, vgap, takeSizesIntoAccount, new Insets(0,0,0,0));
137 }
138
139 /**
140 * Creates a variable grid layout manager with the specified mode,
141 * size, horizontal and vertical gap, and zero distance to borders.
142 * The minimum and maximum Component sizes are not taken into account
143 * when distributing free space.
144 *
145 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
146 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS
147 * @param hgap The horizontal space between cells
148 * @param vgap The vertical space between cells
149 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
150 */
151 public VariableGridLayout(int mode, int size, int hgap, int vgap)
152 {
153 this(mode, size, hgap, vgap, false, new Insets(0,0,0,0));
154 }
155
156 /**
157 * Creates a variable grid layout manager with the specified mode
158 * and size, zero horizontal and vertical gap, and zero distance to borders.
159 * Does not take minimum and maximum Component sizes into account when distributing
160 * free space.
161 *
162 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
163 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS
164 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0
165 */
166 public VariableGridLayout(int mode, int size)
167 {
168 this(mode, size, 0, 0, false, new Insets(0,0,0,0));
169 }
170
171 /**
172 * Creates a variable grid layout manager with mode FIXED_NUM_ROWS,
173 * number of rows == 1, zero horizontal and vertical gap, and zero distance to borders.
174 * Does not take minimum and maximum Component sizes into account when
175 * distributing free space.
176 */
177 public VariableGridLayout()
178 {
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 /**
190 * Not used in this class.
191 */
192 public void addLayoutComponent(Component component, Object constraints)
193 {
194 }
195
196 /**
197 * Not used in this class.
198 */
199 public void removeLayoutComponent(Component component)
200 {
201 }
202
203 /**
204 * Always returns 0.5.
205 */
206 public float getLayoutAlignmentX(Container container)
207 {
208 return 0.5f;
209 }
210
211 /**
212 * Always returns 0.5.
213 */
214 public float getLayoutAlignmentY(Container container)
215 {
216 return 0.5f;
217 }
218
219 public Dimension preferredLayoutSize(Container parent)
220 {
221 return getLayoutSize(parent,LayoutSize.PREFERRED);
222 }
223
224 public Dimension minimumLayoutSize(Container parent)
225 {
226 return getLayoutSize(parent,LayoutSize.MINIMUM);
227 }
228
229 public Dimension maximumLayoutSize(Container parent)
230 {
231 return getLayoutSize(parent,LayoutSize.MAXIMUM);
232 }
233
234 public void layoutContainer(Container parent)
235 {
236 synchronized (parent.getTreeLock())
237 {
238 update(parent);
239
240 int ncomponents = parent.getComponentCount();
241
242 if (ncomponents == 0)
243 {
244 return;
245 }
246
247 // Pass 1: compute minimum, preferred and maximum row heights / column widths
248 int total_height = 0;
249 Arrays.fill(row_heights,0);
250 Arrays.fill(col_widths,0);
251 if (takeSizesIntoAccount)
252 {
253 Arrays.fill(minimum_row_heights,0);
254 Arrays.fill(minimum_col_widths,0);
255 Arrays.fill(maximum_row_heights,Integer.MAX_VALUE);
256 Arrays.fill(maximum_col_widths,Integer.MAX_VALUE);
257 }
258 for (int r = 0, i = 0; r < nrows; r++)
259 {
260 for (int c = 0; c < ncols; c++, i++)
261 {
262 if (i < ncomponents)
263 {
264 Component comp = parent.getComponent(i);
265 Dimension d = comp.getPreferredSize();
266 row_heights[r] = Math.max(row_heights[r], d.height);
267 col_widths[c] = Math.max(col_widths[c], d.width);
268 if (takeSizesIntoAccount)
269 {
270 d = comp.getMinimumSize();
271 minimum_row_heights[r] = Math.max(minimum_row_heights[r], d.height);
272 minimum_col_widths[c] = Math.max(minimum_col_widths[c], d.width);
273 d = comp.getMaximumSize();
274 maximum_row_heights[r] = Math.min(maximum_row_heights[r], d.height);
275 maximum_col_widths[c] = Math.min(maximum_col_widths[c], d.width);
276 }
277 }
278 else
279 {
280 break;
281 }
282 }
283 if (takeSizesIntoAccount)
284 {
285 // correct cases where
286 // minimum_row_heights[row] <= row_heights[row] <= maximum_row_heights[row]
287 // is not true by clipping to the minimum_row_heights and maximum_row_heights
288 if (minimum_row_heights[r] >= maximum_row_heights[r])
289 {
290 maximum_row_heights[r] = minimum_row_heights[r];
291 row_heights[r] = minimum_row_heights[r];
292 }
293 else if (row_heights[r] < minimum_row_heights[r])
294 {
295 row_heights[r] = minimum_row_heights[r];
296 }
297 else if (row_heights[r] > maximum_row_heights[r])
298 {
299 row_heights[r] = maximum_row_heights[r];
300 }
301 }
302 total_height += row_heights[r];
303 }
304
305 int total_width = 0;
306 for (int c = 0; c < ncols; c++)
307 {
308 if (takeSizesIntoAccount)
309 {
310 // correct cases where
311 // minimum_col_widths[col] <= col_widths[col] <= maximum_col_widths[col]
312 // is not true by clipping to the minimum_col_widths and maximum_col_widths
313 if (minimum_col_widths[c] >= maximum_col_widths[c])
314 {
315 maximum_col_widths[c] = minimum_col_widths[c];
316 col_widths[c] = minimum_col_widths[c];
317 }
318 else if (col_widths[c] < minimum_col_widths[c])
319 {
320 col_widths[c] = minimum_col_widths[c];
321 }
322 else if (col_widths[c] > maximum_col_widths[c])
323 {
324 col_widths[c] = maximum_col_widths[c];
325 }
326 }
327 total_width += col_widths[c];
328 }
329
330 // Pass 2: redistribute free space
331 Dimension parent_size = parent.getSize();
332 Insets insets = parent.getInsets();
333 int free_height = parent_size.height
334 - insets.top - insets.bottom
335 - (nrows - 1) * vgap
336 - distanceToBorders.top - distanceToBorders.bottom;
337 int free_width = parent_size.width
338 - insets.left - insets.right
339 - (ncols - 1) * hgap
340 - distanceToBorders.left - distanceToBorders.right;
341
342 redistributeSpace(total_height,free_height,
343 takeSizesIntoAccount,
344 nrows,row_heights,
345 minimum_row_heights,
346 maximum_row_heights);
347
348 redistributeSpace(total_width,free_width,
349 takeSizesIntoAccount,
350 ncols,col_widths,
351 minimum_col_widths,
352 maximum_col_widths);
353
354 // Pass 3: layout components
355 for (int r = 0, y = insets.top + distanceToBorders.top, i = 0; r < nrows; y += row_heights[r] + vgap, r++)
356 {
357 for (int c = 0, x = insets.left + distanceToBorders.left; c < ncols; x += col_widths[c] + hgap, c++, i++)
358 {
359 if (i < ncomponents)
360 {
361 Component comp = parent.getComponent(i);
362 Dimension d = comp.getMaximumSize();
363 int width = col_widths[c];
364 int height = row_heights[r];
365 int xCorrection = 0;
366 int yCorrection = 0;
367 if (width > d.width)
368 {
369 xCorrection = (int)((width - d.width) * comp.getAlignmentX());
370 width = d.width;
371 }
372 if (height > d.height)
373 {
374 yCorrection = (int)((height-d.height) * comp.getAlignmentY());
375 height = d.height;
376 }
377
378 comp.setBounds(x + xCorrection, y + yCorrection, width, height);
379 }
380 }
381 }
382 } // synchronized
383 }
384
385 public void invalidateLayout(Container container)
386 {
387 }
388
389 /**
390 * Returns the string representation of this variable grid layout's values.
391 * @return a string representation of this variable grid layout.
392 */
393 public String toString()
394 {
395 return getClass().getName() + "[mode="
396 + ((FIXED_NUM_ROWS == mode) ? "FIXED_NUM_ROWS"
397 : ((FIXED_NUM_COLUMNS == mode) ? "FIXED_NUM_COLUMNS"
398 : "UNKNOWN(" + mode + ")")) + ",size=" + size
399 + ",hgap=" + hgap + ",vgap=" + vgap
400 + ",takeSizesIntoAccount=" + takeSizesIntoAccount
401 + ",distanceToBorders=" + distanceToBorders + "]";
402 }
403
404 /**
405 * @param which if LayoutSize.MINIMUM compute minimum layout size,
406 * if LayoutSize.MAXIMUM compute maximum layout size,
407 * if LayoutSize.PREFERRED compute preferred layout size.
408 */
409 private Dimension getLayoutSize(Container parent, LayoutSize which)
410 {
411 synchronized (parent.getTreeLock())
412 {
413 update(parent);
414
415 int ncomponents = parent.getComponentCount();
416 long h = 0;
417 long w = 0;
418
419 for (int r = 0, i = 0; r < nrows; r++)
420 {
421 int row_height = 0;
422 for (int c = 0; c < ncols; c++, i++)
423 {
424 if (i < ncomponents)
425 {
426 switch (which)
427 {
428 case MINIMUM:
429 row_height = Math.max(row_height, parent.getComponent(i).getMinimumSize().height);
430 break;
431
432 case MAXIMUM:
433 row_height = Math.max(row_height, parent.getComponent(i).getMaximumSize().height);
434 break;
435
436 case PREFERRED:
437 row_height = Math.max(row_height, parent.getComponent(i).getPreferredSize().height);
438 break;
439
440 default:
441 throw new InternalError("Missing case branch for LayoutSize: " + which);
442 }
443 }
444 }
445 h += row_height;
446 }
447
448 for (int c = 0; c < ncols; c++)
449 {
450 int col_width = 0;
451 for (int r = 0; r < nrows; r++)
452 {
453 int i = r * ncols + c;
454 if (i < ncomponents)
455 {
456 switch (which)
457 {
458 case MINIMUM:
459 col_width = Math.max(col_width, parent.getComponent(i).getMinimumSize().width);
460 break;
461
462 case MAXIMUM:
463 col_width = Math.max(col_width, parent.getComponent(i).getMaximumSize().width);
464 break;
465
466 case PREFERRED:
467 col_width = Math.max(col_width, parent.getComponent(i).getPreferredSize().width);
468 break;
469
470 default:
471 throw new InternalError("Missing case branch for LayoutSize: " + which);
472 }
473 }
474 }
475 w += col_width;
476 }
477
478 Insets insets = parent.getInsets();
479 w += insets.left + insets.right + ((ncols - 1) * hgap) + distanceToBorders.left + distanceToBorders.right;
480 h += insets.top + insets.bottom + ((nrows - 1) * vgap) + distanceToBorders.top + distanceToBorders.bottom;
481 if (w > Integer.MAX_VALUE)
482 {
483 w = Integer.MAX_VALUE;
484 }
485 if (h > Integer.MAX_VALUE)
486 {
487 h = Integer.MAX_VALUE;
488 }
489 return new Dimension((int)w,(int)h);
490 }
491 }
492
493 private void update(Container container)
494 {
495 int ncomponents = container.getComponentCount();
496 int old_nrows = nrows;
497 int old_ncols = ncols;
498 if (this.mode == FIXED_NUM_ROWS)
499 {
500 nrows = this.size;
501 ncols = (ncomponents + nrows - 1) / nrows;
502 }
503 else
504 {
505 ncols = this.size;
506 nrows = (ncomponents + ncols - 1) / ncols;
507 }
508 if (old_nrows != nrows)
509 {
510 row_heights = new int[nrows];
511 if (takeSizesIntoAccount)
512 {
513 minimum_row_heights = new int[nrows];
514 maximum_row_heights = new int[nrows];
515 }
516 }
517 if (old_ncols != ncols)
518 {
519 col_widths = new int[ncols];
520 if (takeSizesIntoAccount)
521 {
522 minimum_col_widths = new int[ncols];
523 maximum_col_widths = new int[ncols];
524 }
525 }
526 }
527
528 private void redistributeSpace(int total_size, int free_size, boolean takeSizesIntoAccount,
529 int nelements, int[] element_sizes,
530 int[] minimum_element_sizes, int[] maximum_element_sizes)
531 {
532 if (total_size != free_size)
533 {
534 if (takeSizesIntoAccount)
535 {
536 boolean grow = total_size < free_size;
537 // calculate the size that is available for redistribution
538 free_size = (free_size - total_size) * (grow ? 1 : -1);
539 while (free_size != 0)
540 {
541 // calculate the amount of elements that can be resized without violating
542 // the minimum and maximum sizes and their current cumulated size
543 int modifyableAmount = 0;
544 int modifySize = 0;
545 for (int i = 0 ; i < nelements ; i++)
546 {
547 if ((grow && (element_sizes[i] < maximum_element_sizes[i])) ||
548 (!grow && (element_sizes[i] > minimum_element_sizes[i])))
549 {
550 modifyableAmount++;
551 modifySize += element_sizes[i];
552 }
553 }
554 boolean checkBounds = true;
555 // if all elements are at their minimum or maximum size, resize all elements
556 if (0 == modifyableAmount)
557 {
558 for (int i = 0 ; i < nelements ; i++)
559 {
560 modifySize += element_sizes[i];
561 }
562 checkBounds = false;
563 modifyableAmount = nelements;
564 }
565 // to prevent an endless loop if the container gets resized to a very small amount
566 if (modifySize == 0)
567 {
568 break;
569 }
570 // resize the elements
571 if (free_size < modifyableAmount)
572 {
573 for (int i = 0 ; i < nelements ; i++)
574 {
575 if ((free_size != 0) &&
576 (!checkBounds ||
577 (checkBounds &&
578 (grow && (element_sizes[i] < maximum_element_sizes[i])) ||
579 (!grow && (element_sizes[i] > minimum_element_sizes[i])))))
580 {
581 element_sizes[i] += (grow ? 1 : -1);
582 if (0 > element_sizes[i])
583 {
584 element_sizes[i] = 0;
585 }
586 free_size--;
587 }
588 }
589 }
590 else
591 {
592 int modifySizeAddition = 0;
593 for (int i = 0 ; i < nelements ; i++)
594 {
595 int modifyableSize = (checkBounds ? (grow ? maximum_element_sizes[i] - element_sizes[i] : element_sizes[i] - minimum_element_sizes[i]) : Integer.MAX_VALUE - element_sizes[i]);
596 int elementModifySize = (int)((double)free_size / (double)modifySize * (double)element_sizes[i]);
597 if (elementModifySize <= modifyableSize)
598 {
599 element_sizes[i] += (grow ? elementModifySize : -elementModifySize);
600 modifySizeAddition += (grow ? elementModifySize : -elementModifySize);
601 free_size -= elementModifySize;
602 }
603 else
604 {
605 element_sizes[i] += (grow ? modifyableSize : -modifyableSize);
606 modifySizeAddition += (grow ? modifyableSize : -modifyableSize);
607 free_size -= modifyableSize;
608 }
609 if (0 > element_sizes[i])
610 {
611 element_sizes[i] = 0;
612 }
613 }
614 modifySize += modifySizeAddition;
615 }
616 }
617 }
618 else
619 {
620 double d = (double)free_size / (double)total_size;
621 for (int i = 0; i < nelements; i++)
622 {
623 element_sizes[i] = (int)(element_sizes[i] * d);
624 }
625 }
626 }
627 }
628
629 private int mode;
630 private int size;
631 private int hgap;
632 private int vgap;
633 private boolean takeSizesIntoAccount;
634 private Insets distanceToBorders;
635 private transient int nrows = -1;
636 private transient int ncols = -1;
637 private transient int[] minimum_row_heights = null;
638 private transient int[] minimum_col_widths = null;
639 private transient int[] row_heights = null;
640 private transient int[] col_widths = null;
641 private transient int[] maximum_row_heights = null;
642 private transient int[] maximum_col_widths = null;
643}