1 /**
2  * The utillib library.
3  * More information is available at http://www.jinchess.com/.
4  * Copyright (C) 2002, 2003 Alexander Maryanovsky.
5  * All rights reserved.
6  *
7  * The utillib library is free software; you can redistribute
8  * it and/or modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * The utillib library is distributed in the hope that it will
13  * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with utillib library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 
22 package free.util;
23 
24 import java.awt.*;
25 import java.util.Vector;
26 
27 
28 /**
29  * A LayoutManager which lays out the components in a table-like structure.
30  * Unlike <code>GridLayout</code>, the sizes of the rows and columns
31  * are dynamic, although properly aligned. The cell sizes are determined
32  * according to the preferred sizes of the components and each component is
33  * sized to either its maximum size or the cell size. Components are positioned
34  * within their cells according to their X and Y alignments.
35  * When a new component is added, it is placed in the first empty cell, in
36  * lexigraphic order. A new row is created if necessary.
37  * To create an empty cell, simply add blank component.
38  */
39 
40 public class TableLayout implements LayoutManager2{
41 
42 
43   /**
44    * The amount of columns in the table.
45    */
46 
47   private final int columnCount;
48 
49 
50   /**
51    * The gap between columns, in pixels.
52    */
53 
54   private final int xGap;
55 
56 
57   /**
58    * The gap between rows, in pixels.
59    */
60 
61   private final int yGap;
62 
63 
64 
65   /**
66    * A Vector of rows where each row is a Component array holding the components
67    * in that row.
68    */
69 
70   private final Vector rows = new Vector();
71 
72 
73 
74   /**
75    * Creates a new TableLayout with the specified amount of columns,
76    * horizontal/vertical gaps between columns/cells.
77    */
78 
TableLayout(int columnCount, int xGap, int yGap)79   public TableLayout(int columnCount, int xGap, int yGap){
80     if (columnCount <= 0)
81       throw new IllegalArgumentException("The amount of columns must be positive");
82     if (xGap < 0)
83       throw new IllegalArgumentException("The horizontal gap may not be negative: "+xGap);
84     if (yGap < 0)
85       throw new IllegalArgumentException("The vertical gap may not be negative: "+yGap);
86 
87     this.columnCount = columnCount;
88     this.xGap = xGap;
89     this.yGap = yGap;
90   }
91 
92 
93 
94   /**
95    * Creates a new TableLayout with the specified amount of columns.
96    */
97 
TableLayout(int columnCount)98   public TableLayout(int columnCount){
99     this(columnCount, 0, 0);
100   }
101 
102 
103 
104 
105   /**
106    * Adds the specified component to the layout.
107    */
108 
addLayoutComponent(Component component, Object constraints)109   public void addLayoutComponent(Component component, Object constraints){
110     synchronized(component.getTreeLock()){
111       int rowCount = rows.size();
112       for (int i = 0; i < rowCount; i++){
113         Component [] row = (Component [])rows.elementAt(i);
114         for (int j = 0; j < row.length; j++){
115           if (row[j] == null){
116             row[j] = component;
117             return;
118           }
119         }
120       }
121 
122       Component [] newRow = new Component[columnCount];
123       newRow[0] = component;
124       rows.addElement(newRow);
125     }
126   }
127 
128 
129 
130   /**
131    * Throws an exception.
132    */
133 
addLayoutComponent(String name, Component component)134   public void addLayoutComponent(String name, Component component){
135     throw new UnsupportedOperationException("deprecated addLayoutComponent(String, Component)");
136   }
137 
138 
139 
140 
141   /**
142    * Removes the specified component from the layout.
143    */
144 
removeLayoutComponent(Component component)145   public void removeLayoutComponent(Component component){
146     synchronized(component.getTreeLock()){
147       int rowCount = rows.size();
148       outer: for (int i = 0; i < rowCount; i++){
149         Component [] row = (Component [])rows.elementAt(i);
150         for (int j = 0; j < row.length; j++){
151           if (row[j] == component){
152             row[j] = null;
153             break outer;
154           }
155         }
156       }
157 
158       // Remove any empty rows at the end.
159       for (int i = rowCount - 1; i >= 0; i--){
160         Component [] row = (Component [])rows.elementAt(i);
161         boolean isEmpty = true;
162         for (int j = 0; j < row.length; j++){
163           if (row[j] != null){
164             isEmpty = false;
165             break;
166           }
167         }
168         if (isEmpty)
169           rows.removeElementAt(i);
170         else
171           break;
172       }
173     }
174   }
175 
176 
177 
178 
179   /**
180    * Returns a matrix of Dimension objects specifying the preferred sizes of the
181    * components we are going to layout.
182    */
183 
getPreferredSizes(Container parent)184   private Dimension [][] getPreferredSizes(Container parent){
185     int rowCount = rows.size();
186     Dimension [][] prefSizes = new Dimension[rowCount][columnCount];
187 
188     for (int i = 0; i < rowCount; i++){
189       Component [] row = (Component [])rows.elementAt(i);
190       for (int j = 0; j < columnCount; j++){
191         Component component = row[j];
192 
193         // Can only happen on the last line when all the remaining components are null as well
194         if (component == null)
195           break;
196 
197         if (component.getParent() != parent)
198           throw new IllegalStateException("Bad parent specified");
199 
200         prefSizes[i][j] = component.getPreferredSize();
201       }
202     }
203 
204     return prefSizes;
205   }
206 
207 
208 
209   /**
210    * Calculates and returns a Pair where the first object is an array holding
211    * the column widths of our layout and the second is the rowHeights.
212    */
213 
calculateLayout(Dimension [][] prefSizes)214   private Pair calculateLayout(Dimension [][] prefSizes){
215     int rowCount = rows.size();
216 
217     int [] columnWidths = new int[columnCount];
218     int [] rowHeights = new int[rowCount];
219 
220     // Find the maximum preferred row heights and column widths.
221     for (int i = 0; i < rowCount; i++){
222       for (int j = 0; j < columnCount; j++){
223         Dimension prefSize = prefSizes[i][j];
224 
225         // Can only happen on the last line when all the remaining components are null as well
226         if (prefSize == null)
227           break;
228 
229         columnWidths[j] = Math.max(columnWidths[j], prefSize.width);
230         rowHeights[i] = Math.max(rowHeights[i], prefSize.height);
231       }
232     }
233 
234     return new Pair(columnWidths, rowHeights);
235   }
236 
237 
238 
239 
240   /**
241    * Lays out the specified container. Throws an
242    * <code>IllegalStateException</code> if any of the components added via the
243    * <code>addLayoutComponent</code> method have a different parent than the
244    * specified Container.
245    */
246 
layoutContainer(Container parent)247   public void layoutContainer(Container parent){
248     synchronized(parent.getTreeLock()){
249       int rowCount = rows.size();
250 
251       Insets parentInsets = parent.getInsets();
252 
253       // Collect the preferred sizes.
254       Dimension [][] prefSizes = getPreferredSizes(parent);
255       Pair layout = calculateLayout(prefSizes);
256       int [] columnWidths = (int [])layout.getFirst();
257       int [] rowHeights = (int [])layout.getSecond();
258 
259       Dimension prefParentSize = calculatePreferredLayoutSize(parent, columnWidths, rowHeights);
260       Dimension parentSize = parent.getSize();
261       Dimension layoutSize =
262         new Dimension(parentSize.width - xGap*(rowCount - 1) - parentInsets.left - parentInsets.right,
263                       parentSize.height - yGap*(columnCount - 1) - parentInsets.top - parentInsets.bottom);
264       Dimension prefLayoutSize =
265         new Dimension(prefParentSize.width - xGap*(rowCount - 1) - parentInsets.left - parentInsets.right,
266                       prefParentSize.height - yGap*(columnCount - 1) - parentInsets.top - parentInsets.bottom);
267 
268       // Layout the components.
269       int y = parentInsets.top;
270       for (int i = 0; i < rowCount; i++){
271         int x = parentInsets.left;
272         int cellHeight = (rowHeights[i]*layoutSize.height)/prefLayoutSize.height;
273         Component [] row = (Component [])rows.elementAt(i);
274         for (int j = 0; j < row.length; j++){
275           int cellWidth = (columnWidths[j]*layoutSize.width)/prefLayoutSize.width;
276           Component component = row[j];
277 
278           // Can only happen on the last line when all the remaining components are null as well
279           if (component == null)
280             break;
281 
282           Dimension maxSize = component.getMaximumSize();
283 
284           int compWidth = Math.min(maxSize.width, cellWidth);
285           int compHeight = Math.min(maxSize.height, cellHeight);
286 
287           int compX = x + (int)((cellWidth - compWidth)*component.getAlignmentX());
288           int compY = y + (int)((cellHeight - compHeight)*component.getAlignmentY());
289 
290           component.setBounds(compX, compY, compWidth, compHeight);
291 
292           x += cellWidth + xGap;
293         }
294 
295         y += cellHeight + yGap;
296       }
297     }
298   }
299 
300 
301 
302   /**
303    * We're not caching anything yet, so this call is ignored.
304    */
305 
invalidateLayout(Container parent)306   public void invalidateLayout(Container parent){
307 
308   }
309 
310 
311 
312   /**
313    * Returns the preferred layout for the specified parent container.
314    */
315 
preferredLayoutSize(Container parent)316   public Dimension preferredLayoutSize(Container parent){
317     synchronized(parent.getTreeLock()){
318       Dimension [][] prefSizes = getPreferredSizes(parent);
319       Pair layout = calculateLayout(prefSizes);
320       int [] columnWidths = (int [])layout.getFirst();
321       int [] rowHeights = (int [])layout.getSecond();
322 
323       return calculatePreferredLayoutSize(parent, columnWidths, rowHeights);
324     }
325   }
326 
327 
328 
329   /**
330    * Calculates the preferred layout size for the specified preferred column
331    * widths and row heights.
332    */
333 
calculatePreferredLayoutSize(Container parent, int [] columnWidths, int [] rowHeights)334   private Dimension calculatePreferredLayoutSize(Container parent, int [] columnWidths, int [] rowHeights){
335     int prefWidth = 0;
336     int prefHeight = 0;
337 
338     for (int i = 0; i < columnWidths.length; i++)
339       prefWidth += columnWidths[i];
340     for (int i = 0; i < rowHeights.length; i++)
341       prefHeight += rowHeights[i];
342 
343     // Add the gaps
344     prefWidth += xGap*(columnWidths.length - 1);
345     prefHeight += yGap*(rowHeights.length - 1);
346 
347     // Add parent insets
348     Insets parentInsets = parent.getInsets();
349     prefWidth += parentInsets.left + parentInsets.right;
350     prefHeight += parentInsets.top + parentInsets.bottom;
351 
352 
353     return new Dimension(prefWidth, prefHeight);
354   }
355 
356 
357 
358   /**
359    * Returns the same as <code>preferredLayoutSize</code>.
360    */
361 
minimumLayoutSize(Container parent)362   public Dimension minimumLayoutSize(Container parent){
363     return preferredLayoutSize(parent);
364   }
365 
366 
367 
368 
369   /**
370    * Returns a dimension with maximum possible values.
371    */
372 
maximumLayoutSize(Container parent)373   public Dimension maximumLayoutSize(Container parent){
374     return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
375   }
376 
377 
378 
379   /**
380    * Returns <code>CENTER_ALIGNMENT</code>;
381    */
382 
getLayoutAlignmentX(Container parent)383   public float getLayoutAlignmentX(Container parent) {
384     return Component.CENTER_ALIGNMENT;
385   }
386 
387 
388 
389   /**
390    * Returns <code>CENTER_ALIGNMENT</code>;
391    */
392 
getLayoutAlignmentY(Container parent)393   public float getLayoutAlignmentY(Container parent) {
394     return Component.CENTER_ALIGNMENT;
395   }
396 
397 
398 }
399