1
2
3
4
5
6 package net.sourceforge.simplegamenet.util.proportionlayout;
7
8 import java.awt.*;
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.TreeSet;
12
13 /***
14 * The <code>ProportionLayout</code> class is a flexible layout manager that aligns components
15 * vertically and horizontally, without requiring that the components be of the same size.
16 * <p/>
17 * The <code>ProportionLayout</code> class defines a grid wherein components can be laid out. Each
18 * component managed by a <code>ProportionLayout</code> is associated with an instance of {@link
19 * ProportionConstraints}. The <code>ProportionConstraints</code> object specifies where a
20 * component's display area should be located on the grid and how the component should be positioned
21 * within its display area. In addition to its constraints object, the <code>ProportionLayout</code>
22 * also considers each component's minimum and preferred sizes in order to determine a component's
23 * size.
24 * <p/>
25 * The following figures show six components (all buttons) managed by a proportion layout. Figure 1
26 * shows the layout for the components with their preferred size and Figure 2 shows the layout for
27 * the same components resized to a larger size.
28 * <p/>
29 * <center><table COLS=1 ROWS=4 WIDTH=800> <tr ALIGN=CENTER> <td><img
30 * src="doc-files/ProportionLayout-1.gif" ALIGN=center HSPACE=10 VSPACE=7> </td> </tr> <tr
31 * ALIGN=CENTER> <td>Figure 1: the example with its preferred size</td> </tr> <tr ALIGN=CENTER>
32 * <td><img src="doc-files/ProportionLayout-2.gif" ALIGN=center HSPACE=10 VSPACE=7> </td> </tr> <tr
33 * ALIGN=CENTER> <td>Figure 2: the example resized to a larger size</td> </tr> </table></center>
34 * <p/>
35 * In this example there are 3 columns and 3 rows. The proportion of column 1 is set to
36 * NO_PROPORTION and the proportion of columns 2 and 3 are set to 1.0 The proportion of row 1 and 3
37 * are set to NO_PROPORTION and the proportion of row 2 is set to 1.0 The rows and columns with
38 * proportion set to NO_PROPORTION means that they have a fixed size that will always stay the same,
39 * even after resizing. Those with proportion set to 1.0 get all the extra size to divide among them
40 * when the layout is resized to a larger size.
41 * <p/>
42 * Here is the code that implements the example shown above:
43 * <p/>
44 * <hr><blockquote><pre>
45 * import java.awt.*;
46 * import javax.swing.*;
47 * <p/>
48 * import net.sourceforge.simplegamenet.util.proportionlayout.*;
49 * <p/>
50 * public class LayoutTester extends JFrame {
51 * <p/>
52 * public static void main(String[] args) {
53 * LayoutTester currentFrame = new LayoutTester();
54 * currentFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
55 * currentFrame.pack();
56 * currentFrame.show();
57 * }
58 * <p/>
59 * public LayoutTester() {
60 * super("LayoutTester");
61 * Container contentPanel = getContentPane();
62 * ProportionLayout layout = new ProportionLayout();
63 * <p/>
64 * // First add all the columns to the ProportionLayout
65 * layout.appendColumn(10); // column 0
66 * // Column 0 will be an empty space of width 10
67 * layout.appendColumn(0, ProportionLayout.NO_PROPORTION); // column 1
68 * // Column 1 will always be the greatest preferred width of all it's components
69 * // Column 1 will never get any of the additional width
70 * layout.appendColumn(10); // column 2
71 * // Column 2 will be an empty space of width 10
72 * layout.appendColumn(0, 1.0); // column 3
73 * // Column 3 will always be the same width as column 5
74 * // Column 3 and 5 will devide any of the additional width between them
75 * layout.appendColumn(10); // column 4
76 * // Column 4 will be an empty space of width 10
77 * layout.appendColumn(0, 1.0); // column 5
78 * // Column 5 will always be the same width as column 3
79 * // Column 3 and 5 will devide any of the additional width between them
80 * layout.appendColumn(10); // column 6
81 * // Column 6 will be an empty space of width 10
82 * <p/>
83 * // Then add all the rows to the ProportionLayout
84 * layout.appendRow(10); // row 0
85 * // Row 0 will be an empty space of width 10
86 * layout.appendRow(0, ProportionLayout.NO_PROPORTION); // row 1
87 * // Row 1 will always be the greatest preferred height of all it's components
88 * // Row 1 will never get any of the additional height
89 * layout.appendRow(10); // row 2
90 * // Row 2 will be an empty space of width 10
91 * layout.appendRow(0, ProportionLayout.NO_PROPORTION); // row 3
92 * // Row 3 will always be the greatest preferred height of all it's components
93 * // Row 3 will never get any of the additional height
94 * layout.appendRow(10); // row 4
95 * // Row 4 will be an empty space of width 10
96 * layout.appendRow(0, 1.0); // row 5
97 * // Row 5 will get all of the additional height
98 * layout.appendRow(10); // row 6
99 * // Row 6 will be an empty space of width 10
100 * <p/>
101 * contentPanel.setLayout(layout);
102 * contentPanel.add(new JButton("B1"),
103 * new ProportionConstraints(1, 1) );
104 * // column 1, row 1
105 * contentPanel.add(new JButton("Button 2 takes up a lot of space"),
106 * new ProportionConstraints(3, 1) );
107 * // column 3, row 1
108 * contentPanel.add(new JButton("Button 3"),
109 * new ProportionConstraints(5, 1) );
110 * // column 5, row 1
111 * contentPanel.add(new JButton("B4"),
112 * new ProportionConstraints(1, 3, 3, 1) );
113 * // column 1, row 3 with width 3, height 1
114 * contentPanel.add(new JButton("Button 5"),
115 * new ProportionConstraints(1, 5) );
116 * // column 1, row 5
117 * contentPanel.add(new JButton("B6"),
118 * new ProportionConstraints(5, 3, 1, 3) );
119 * // column 5, row 3 with width 1, height 3
120 * <p/>
121 * // ProportionConstaints has additional constructors with more parameters
122 * // that allow a more flexible control of how the component will behave
123 * // inside it's own cell.
124 * }
125 * <p/>
126 * }
127 * </pre></blockquote><hr>
128 *
129 * @author Geoffrey and Jeroen
130 */
131 public class ProportionLayout implements LayoutManager2, java.io.Serializable {
132
133 /***
134 * Make the column or row have it's preferred width or height, without stretching.
135 */
136 public static final double NO_PROPORTION = 0.0;
137
138 /***
139 * The smallest grid that can be laid out by the layout.
140 */
141 static final int MINIMUM_SIZE = 1;
142
143 /***
144 * The preferred grid size that can be laid out by the layout.
145 */
146 static final int PREFERRED_SIZE = 2;
147
148 /***
149 * Contains all the columnLines for this ProportionLayout.
150 */
151 protected ArrayList columnLines;
152 /***
153 * Contains all the rowLines for this ProportionLayout.
154 */
155 protected ArrayList rowLines;
156 /***
157 * Contains the total amount of columnLines for this ProportionLayout.
158 */
159 protected double columnProportionTotal = 0.0;
160 /***
161 * Contains the total amount of rowLines for this ProportionLayout.
162 */
163 protected double rowProportionTotal = 0.0;
164
165 /***
166 * Contains which rowLine and columnLine belong together.
167 */
168 protected HashMap linePartCouples;
169 /***
170 * Contains all the columnLineParts
171 */
172 protected TreeSet columnLineParts;
173 /***
174 * Contains all the rowLineParts
175 */
176 protected TreeSet rowLineParts;
177
178 /***
179 * Creates a new instance of ProportionLayout
180 */
181 public ProportionLayout() {
182 columnLines = new ArrayList();
183 rowLines = new ArrayList();
184 linePartCouples = new HashMap();
185 columnLineParts = new TreeSet();
186 rowLineParts = new TreeSet();
187 }
188
189 /***
190 * Creates a new column at the end of the grid for this ProportionLayout. The minimunWidth is 0
191 * and the lineProportion is NO_PROPORTION.
192 *
193 * @return the gridX value for the new column.
194 * @throws IllegalArgumentException if one of the arguments is an illegal argument.
195 */
196 public int appendColumn() throws IllegalArgumentException {
197 return appendColumn(0, NO_PROPORTION);
198 }
199
200 /***
201 * Creates a new column at the end of the grid for this ProportionLayout. The lineProportion is
202 * NO_PROPORTION.
203 *
204 * @param minimumWidth Spicifies the minimum width for this column.
205 * @return the gridX value for the new column.
206 * @throws IllegalArgumentException if one of the arguments is an illegal argument.
207 */
208 public int appendColumn(int minimumWidth) throws IllegalArgumentException {
209 return appendColumn(minimumWidth, NO_PROPORTION);
210 }
211
212 /***
213 * Creates a new column at the end of the grid for this ProportionLayout.
214 *
215 * @param minimumWidth specifies the minimum width for this column.
216 * @param lineProportion specifies the proportion for this column.
217 * @return the gridX value for the new column.
218 * @throws IllegalArgumentException if one of the arguments is an illegal argument.
219 */
220 public int appendColumn(int minimumWidth, double lineProportion)
221 throws IllegalArgumentException {
222 int size = columnLines.size();
223 columnProportionTotal += lineProportion;
224 columnLines.add(new ProportionLine(minimumWidth, lineProportion));
225 return size;
226 }
227
228 /***
229 * Creates a new row at the end of the grid for this ProportionLayout. The minimunHeight is 0
230 * and the lineProportion is NO_PROPORTION.
231 *
232 * @return the gridY value for the new row.
233 * @throws IllegalArgumentException if one of the arguments is an illegal argument.
234 */
235 public int appendRow() throws IllegalArgumentException {
236 return appendRow(0, NO_PROPORTION);
237 }
238
239 /***
240 * Creates a new row at the end of the grid for this ProportionLayout. The lineProportion is
241 * NO_PROPORTION.
242 *
243 * @param minimumHeight specifies the minimum height for this row.
244 * @return the gridY value for the new row.
245 * @throws IllegalArgumentException if one of the arguments is an illegal argument.
246 */
247 public int appendRow(int minimumHeight) throws IllegalArgumentException {
248 return appendRow(minimumHeight, NO_PROPORTION);
249 }
250
251 /***
252 * Creates a new row at the end of the grid for this ProportionLayout.
253 *
254 * @param minimumHeight specifies the minimum height for this row.
255 * @param lineProportion specifies the proportion for this row.
256 * @return the gridY value for the new row.
257 * @throws IllegalArgumentException if one of the arguments is an illegal argument.
258 */
259 public int appendRow(int minimumHeight, double lineProportion)
260 throws IllegalArgumentException {
261 int size = rowLines.size();
262 rowProportionTotal += lineProportion;
263 rowLines.add(new ProportionLine(minimumHeight, lineProportion));
264 return size;
265 }
266
267 /***
268 * Arranges the grid for the given container of components and specifies the sizeType of it.
269 *
270 * @param parent specified the container to be laid out.
271 * @param sizeType specifies the sizeType for this container.
272 * @return the arranged grid for this specific container of components.
273 */
274 protected Dimension arrangeGrid(Container parent, int sizeType) {
275 synchronized (parent.getTreeLock()) {
276 Insets parentInsets = parent.getInsets();
277 return new Dimension(parentInsets.left + parentInsets.right
278 + ProportionLine.arrangeLines(columnLines,
279 columnLineParts, sizeType),
280 parentInsets.bottom + parentInsets.top
281 + ProportionLine.arrangeLines(rowLines,
282 rowLineParts, sizeType));
283 }
284 }
285
286 /***
287 * Adds a component to the layout, using the specified <code>ProportionConstraints</code> of
288 * that object.
289 *
290 * @param component specifies the component to lay out.
291 * @param object specifies the cell in which the component is to be laid out.
292 * @throws IllegalArgumentException if the object used as parameter is not of the
293 * ProportionConstraints type.
294 * @throws IndexOutOfBoundsException if the index of the object in which you are putting the
295 * component doesn't exist.
296 */
297 public void addLayoutComponent(Component component, Object object)
298 throws IllegalArgumentException,
299 IndexOutOfBoundsException {
300 if (object instanceof ProportionConstraints) {
301 ProportionConstraints constraints = (ProportionConstraints) object;
302 ProportionLinePart[] couple = new ProportionLinePart[2];
303 couple[0] = constraints.getColumnLinePart(component, columnLines.size());
304 couple[1] = constraints.getRowLinePart(component, rowLines.size());
305 linePartCouples.put(component, couple);
306 columnLineParts.add(couple[0]);
307 rowLineParts.add(couple[1]);
308 } else if (object != null) {
309 throw new IllegalArgumentException("Object constraints should be of the"
310 + " ProportionConstraints type");
311 }
312 }
313
314 /***
315 * This method isn't supported.
316 *
317 * @param name the name of the component.
318 * @param component the component to be added.
319 */
320 public void addLayoutComponent(String name, Component component) {
321
322 }
323
324 /***
325 * Removes a component from the ProportionLayout.
326 *
327 * @param component specifies which component has to be removed.
328 */
329 public void removeLayoutComponent(Component component) {
330 ProportionLinePart[] couple = (ProportionLinePart[])
331 linePartCouples.remove(component);
332 columnLineParts.remove(couple[0]);
333 rowLineParts.remove(couple[1]);
334 }
335
336 /***
337 * Determines the minimum size of the target container using this proportion layout.
338 *
339 * @param parent the component to be laid out
340 * @return the laid out component.
341 */
342 public Dimension minimumLayoutSize(Container parent) {
343 synchronized (parent.getTreeLock()) {
344 Insets parentInsets = parent.getInsets();
345 int minimumWidth = parentInsets.left + parentInsets.right
346 + ProportionLine.arrangeLines(columnLines, columnLineParts, MINIMUM_SIZE);
347 int minimumHeight = parentInsets.bottom + parentInsets.top
348 + ProportionLine.arrangeLines(rowLines, rowLineParts, MINIMUM_SIZE);
349 return new Dimension(minimumWidth, minimumHeight);
350 }
351 }
352
353 /***
354 * Determines the preferred size of the target container using this proportion layout.
355 *
356 * @param parent the container to be laid out.
357 * @return the laid out container
358 */
359 public Dimension preferredLayoutSize(Container parent) {
360 synchronized (parent.getTreeLock()) {
361 Insets parentInsets = parent.getInsets();
362 int preferredWidth = parentInsets.left + parentInsets.right
363 + ProportionLine.arrangeLines(columnLines, columnLineParts, PREFERRED_SIZE);
364 int preferredHeight = parentInsets.bottom + parentInsets.top
365 + ProportionLine.arrangeLines(rowLines, rowLineParts, PREFERRED_SIZE);
366 return new Dimension(preferredWidth, preferredHeight);
367 }
368 }
369
370 /***
371 * Returns the maximum dimensions for this layout given the components in the specified target
372 * container.
373 *
374 * @param parent the component which needs to be laid out.
375 * @return the laid out component.
376 */
377 public Dimension maximumLayoutSize(Container parent) {
378 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
379 }
380
381 /***
382 * Returns the alignment along the x axis. This specifies how the component would like to be
383 * aligned relative to other components. The value should be a number between 0 and 1 where 0
384 * represents alignment along the origin, 1 is aligned the furthest away from the origin, 0.5 is
385 * centered, etc.
386 *
387 * @param parent the component/container which needs to be aligned along the x axis.
388 * @return the component/container aligned along the x axis.
389 */
390 public float getLayoutAlignmentX(Container parent) {
391 return 0.5f;
392 }
393
394 /***
395 * Returns the alignment along the y axis. This specifies how the component would like to be
396 * aligned relative to other components. The value should be a number between 0 and 1 where 0
397 * represents alignment along the origin, 1 is aligned the furthest away from the origin, 0.5 is
398 * centered, etc.
399 *
400 * @param parent the component/container which needs to be aligned along the y axis.
401 * @return the component/container aligned along the y axis.
402 */
403 public float getLayoutAlignmentY(Container parent) {
404 return 0.5f;
405 }
406
407 /***
408 * Invalidates the layout, indicating that if the layout manager has cached information it
409 * should be discarded.
410 *
411 * @param parent the container that needs to be invalidated.
412 */
413 public void invalidateLayout(Container parent) {
414 }
415
416 /***
417 * Lays out the specified container using this proportion layout. This method reshapes
418 * components in the specified container in order to satisfy the constraints of this
419 * <code>ProportionLayout</code> object.
420 *
421 * @param parent the container in which to do the layout.
422 */
423 public void layoutContainer(Container parent) {
424
425 synchronized (parent.getTreeLock()) {
426 Dimension parentSize = parent.getSize();
427 Insets parentInsets = parent.getInsets();
428 Component[] components = parent.getComponents();
429 int sizeXType = PREFERRED_SIZE;
430 int sizeYType = PREFERRED_SIZE;
431 int preferredWidth = parentInsets.left + parentInsets.right
432 + ProportionLine.arrangeLines(columnLines, columnLineParts, PREFERRED_SIZE);
433 int preferredHeight = parentInsets.bottom + parentInsets.top
434 + ProportionLine.arrangeLines(rowLines, rowLineParts, PREFERRED_SIZE);
435 int minimumWidth = preferredWidth;
436 int minimumHeight = preferredHeight;
437 int extraWidth = parentSize.width - preferredWidth;
438 int extraHeight = parentSize.height - preferredHeight;
439
440 if (parentSize.width < preferredWidth) {
441 minimumWidth = parentInsets.left + parentInsets.right
442 + ProportionLine.arrangeLines(columnLines, columnLineParts, MINIMUM_SIZE);
443 extraWidth = parentSize.width - minimumWidth;
444 sizeXType = MINIMUM_SIZE;
445 }
446 if (parentSize.height < preferredHeight) {
447 minimumHeight = parentInsets.bottom + parentInsets.top
448 + ProportionLine.arrangeLines(rowLines, rowLineParts, MINIMUM_SIZE);
449 extraHeight = parentSize.height - minimumHeight;
450 sizeYType = MINIMUM_SIZE;
451 }
452 ProportionLine.layoutLines(columnLines, parentInsets.left,
453 extraWidth,
454 preferredWidth - minimumWidth,
455 columnProportionTotal, sizeXType);
456 ProportionLine.layoutLines(rowLines, parentInsets.top,
457 extraHeight,
458 preferredHeight - minimumHeight,
459 rowProportionTotal, sizeYType);
460 for (int i = 0; i < components.length; i++) {
461 ProportionLinePart[] couple = (ProportionLinePart[])
462 linePartCouples.get(components[i]);
463 int componentWidth = 0;
464 int componentHeight = 0;
465 switch (sizeXType) {
466 case MINIMUM_SIZE:
467 componentWidth = components[i].getMinimumSize().width;
468 break;
469 case PREFERRED_SIZE:
470 componentWidth = components[i].getPreferredSize().width;
471 break;
472 }
473 switch (sizeYType) {
474 case MINIMUM_SIZE:
475 componentHeight = components[i].getMinimumSize().height;
476 break;
477 case PREFERRED_SIZE:
478 componentHeight = components[i].getPreferredSize().height;
479 break;
480 }
481 components[i].setBounds(couple[0].getX(columnLines, componentWidth),
482 couple[1].getX(rowLines, componentHeight),
483 couple[0].getWidth(columnLines, componentWidth),
484 couple[1].getWidth(rowLines, componentHeight));
485 }
486 }
487 }
488
489 /***
490 * Returns a string representation of this proportion layout's values.
491 *
492 * @return a string representation of this proportion layout.
493 */
494 public String toString() {
495 return getClass().getName();
496 }
497 }