Source code: org/gjt/sp/jedit/gui/VariableGridLayout.java
1 /*
2 * VariableGridLayout.java - a grid layout manager with variable cell sizes
3 *
4 * Originally written by Dirk Moebius for the jEdit project. This work has been
5 * placed into the public domain. You may use this work in any way and for any
6 * purpose you wish.
7 *
8 * THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
9 * IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
10 * _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
11 * OR REDISTRIBUTION OF THIS SOFTWARE.
12 */
13
14 package org.gjt.sp.jedit.gui;
15
16
17 import java.awt.*;
18
19 /**
20 * The <code>VariableGridLayout</code> class is a layout manager
21 * that lays out a container's components in a rectangular grid
22 * with variable cell sizes.<p>
23 *
24 * The container is divided into rectangles, and one component is placed
25 * in each rectangle. Each row is as large as the largest component in
26 * that row, and each column is as wide as the widest component in
27 * that column.<p>
28 *
29 * This behavior is basically the same as in
30 * <code>java.awt.GridLayout</code>, but with different row heights and
31 * column widths for each row/column.<p>
32 *
33 * For example, the following is an applet that lays out six buttons
34 * into three rows and two columns:<p>
35 *
36 * <blockquote><pre>
37 * import java.awt.*;
38 * import java.applet.Applet;
39 * public class ButtonGrid extends Applet {
40 * public void init() {
41 * setLayout(new VariableGridLayout(VariableGridLayout.FIXED_NUM_COLUMNS, 2));
42 * add(new Button("1"));
43 * add(new Button("2"));
44 * add(new Button("3"));
45 * add(new Button("4"));
46 * add(new Button("5"));
47 * add(new Button("6"));
48 * }
49 * }
50 * </pre></blockquote><p>
51 *
52 * <b>Programmer's remark:</b> VariableGridLayout could be faster, if it would
53 * reside in the package java.awt, because then it could access some
54 * package private fields of <code>Container</code> or
55 * <code>Component</code>. Instead, it has to call
56 * <code>Component.getSize()</code>,
57 * which allocates memory on the heap.<p>
58 *
59 * <b>Todo:</b>
60 * <ul>
61 * <li>Use alignmentX/Y property if the grid cell is larger than the preferred size of the component.
62 * <li>Ability to span components over more than one cell horizontally
63 * </ul>
64 *
65 * @author Dirk Moebius
66 * @version 1.0
67 * @see java.awt.GridLayout
68 */
69 public class VariableGridLayout implements LayoutManager2, java.io.Serializable
70 {
71
72 public static final int FIXED_NUM_ROWS = 1;
73 public static final int FIXED_NUM_COLUMNS = 2;
74
75
76 public VariableGridLayout(int mode, int size, int hgap, int vgap) {
77 if (mode != FIXED_NUM_ROWS && mode != FIXED_NUM_COLUMNS) {
78 throw new IllegalArgumentException("illegal mode; value is " + mode);
79 }
80 if (size <= 0) {
81 throw new IllegalArgumentException("size cannot be zero or less; value is " + size);
82 }
83 if (hgap < 0) {
84 throw new IllegalArgumentException("hgap cannot be negative; value is " + hgap);
85 }
86 if (vgap < 0) {
87 throw new IllegalArgumentException("vgap cannot be negative; value is " + vgap);
88 }
89 this.mode = mode;
90 this.size = size;
91 this.hgap = hgap;
92 this.vgap = vgap;
93 }
94
95
96 /**
97 * Creates a variable grid layout manager with the specified mode
98 * and zero horizontal and vertical gap.
99 */
100 public VariableGridLayout(int mode, int size) {
101 this(mode, size, 0, 0);
102 }
103
104
105 /**
106 * Creates a variable grid layout manager with mode FIXED_NUM_ROWS,
107 * number of rows == 1 and zero horizontal and vertical gap.
108 */
109 public VariableGridLayout() {
110 this(FIXED_NUM_ROWS, 1, 0, 0);
111 }
112
113
114 /**
115 * Not used in this class.
116 */
117 public void addLayoutComponent(String name, Component component) { }
118
119
120 /**
121 * Not used in this class.
122 */
123 public void addLayoutComponent(Component component, Object constraints) { }
124
125
126 /**
127 * Not used in this class.
128 */
129 public void removeLayoutComponent(Component component) { }
130
131
132 /**
133 * Always returns 0.5.
134 */
135 public float getLayoutAlignmentX(Container container) {
136 return 0.5f;
137 }
138
139
140 /**
141 * Always returns 0.5.
142 */
143 public float getLayoutAlignmentY(Container container) {
144 return 0.5f;
145 }
146
147
148 public Dimension preferredLayoutSize(Container parent) {
149 return getLayoutSize(parent, 2);
150 }
151
152
153 public Dimension minimumLayoutSize(Container parent) {
154 return getLayoutSize(parent, 0);
155 }
156
157
158 public Dimension maximumLayoutSize(Container parent) {
159 return getLayoutSize(parent, 1);
160 }
161
162
163 public void layoutContainer(Container parent) {
164 synchronized (parent.getTreeLock()) {
165 update(parent);
166
167 int ncomponents = parent.getComponentCount();
168
169 if (ncomponents == 0) {
170 return;
171 }
172
173 // Pass 1: compute preferred row heights / column widths
174 int total_height = 0;
175 for (int r = 0, i = 0; r < nrows; r++) {
176 for (int c = 0; c < ncols; c++, i++) {
177 if (i < ncomponents) {
178 Dimension d = parent.getComponent(i).getPreferredSize();
179 row_heights[r] = Math.max(row_heights[r], d.height);
180 col_widths[c] = Math.max(col_widths[c], d.width);
181 } else {
182 break;
183 }
184 }
185 total_height += row_heights[r];
186 }
187
188 int total_width = 0;
189 for (int c = 0; c < ncols; c++) {
190 total_width += col_widths[c];
191 }
192
193 // Pass 2: redistribute free space
194 Dimension parent_size = parent.getSize();
195 Insets insets = parent.getInsets();
196 int free_height = parent_size.height - insets.top - insets.bottom - (nrows - 1) * vgap;
197 int free_width = parent_size.width - insets.left - insets.right - (ncols - 1) * hgap;
198
199 if (total_height != free_height) {
200 double dy = (double)free_height / (double)total_height;
201 for (int r = 0; r < nrows; r++) {
202 row_heights[r] = (int) ((double)row_heights[r] * dy);
203 }
204 }
205
206 if (total_width != free_width) {
207 double dx = ((double)free_width) / ((double)total_width);
208 for (int c = 0; c < ncols; c++) {
209 col_widths[c] = (int) ((double)col_widths[c] * dx);
210 }
211 }
212
213 // Pass 3: layout components
214 for (int r = 0, y = insets.top, i = 0; r < nrows; y += row_heights[r] + vgap, r++) {
215 for (int c = 0, x = insets.left; c < ncols; x += col_widths[c] + hgap, c++, i++) {
216 if (i < ncomponents) {
217 parent.getComponent(i).setBounds(x, y, col_widths[c], row_heights[r]);
218 }
219 }
220 }
221
222 } // synchronized
223 }
224
225
226 public void invalidateLayout(Container container) {}
227
228
229 /**
230 * Returns the string representation of this variable grid layout's values.
231 * @return a string representation of this variable grid layout.
232 */
233 public String toString() {
234 return getClass().getName() + "[mode=" + mode + ",size=" + size
235 + ",hgap=" + hgap + ",vgap=" + vgap + "]";
236 }
237
238
239 /**
240 * @param which if 0 compute minimum layout size,
241 * if 1 compute maximum layout size,
242 * otherwise compute preferred layout size.
243 */
244 private Dimension getLayoutSize(Container parent, int which) {
245 synchronized (parent.getTreeLock()){
246 update(parent);
247
248 int ncomponents = parent.getComponentCount();
249 int h = 0;
250 int w = 0;
251
252 for (int r = 0, i = 0; r < nrows; r++) {
253 int row_height = 0;
254 for (int c = 0; c < ncols; c++, i++) {
255 if (i < ncomponents) {
256 switch (which) {
257 case 0:
258 row_height = Math.max(row_height, parent.getComponent(i).getMinimumSize().height);
259 break;
260 case 1:
261 row_height = Math.max(row_height, parent.getComponent(i).getMaximumSize().height);
262 break;
263 default:
264 row_height = Math.max(row_height, parent.getComponent(i).getPreferredSize().height);
265 break;
266 }
267 } else {
268 break;
269 }
270 }
271 h += row_height;
272 }
273
274 for (int c = 0; c < ncols; c++) {
275 int col_width = 0;
276 for (int r = 0; r < nrows; r++) {
277 int i = r * ncols + c;
278 if (i < ncomponents) {
279 switch (which) {
280 case 0:
281 col_width = Math.max(col_width, parent.getComponent(i).getMinimumSize().width);
282 break;
283 case 1:
284 col_width = Math.max(col_width, parent.getComponent(i).getMaximumSize().width);
285 break;
286 default:
287 col_width = Math.max(col_width, parent.getComponent(i).getPreferredSize().width);
288 break;
289 }
290 } else {
291 break;
292 }
293 }
294 w += col_width;
295 }
296
297 Insets insets = parent.getInsets();
298 return new Dimension(w + insets.left + insets.right + ((ncols - 1) * hgap),
299 h + insets.top + insets.bottom + ((nrows - 1) * vgap));
300 }
301 }
302
303
304 private void update(Container container) {
305 int ncomponents = container.getComponentCount();
306 int old_nrows = nrows;
307 int old_ncols = ncols;
308 if (this.mode == FIXED_NUM_ROWS) {
309 nrows = this.size;
310 ncols = (ncomponents + nrows - 1) / nrows;
311 } else {
312 ncols = this.size;
313 nrows = (ncomponents + ncols - 1) / ncols;
314 }
315 if (old_nrows != nrows) {
316 row_heights = new int[nrows];
317 }
318 if (old_ncols != ncols) {
319 col_widths = new int[ncols];
320 }
321 }
322
323
324 private int mode;
325 private int size;
326 private int hgap;
327 private int vgap;
328 private transient int nrows = -1;
329 private transient int ncols = -1;
330 private transient int[] row_heights = null;
331 private transient int[] col_widths = null;
332 }