View Javadoc

1   /*
2    * ProportionLayout.java
3    *
4    * Created on 1 december 2002, 10:24
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         // This method isn't supported
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 }