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