001/* 002 * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved. 003 * 004 * Redistribution and use in source and binary forms, with or without 005 * modification, are permitted provided that the following conditions are met: 006 * 007 * o Redistributions of source code must retain the above copyright notice, 008 * this list of conditions and the following disclaimer. 009 * 010 * o Redistributions in binary form must reproduce the above copyright notice, 011 * this list of conditions and the following disclaimer in the documentation 012 * and/or other materials provided with the distribution. 013 * 014 * o Neither the name of JGoodies Karsten Lentzsch nor the names of 015 * its contributors may be used to endorse or promote products derived 016 * from this software without specific prior written permission. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 027 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030 031package com.jgoodies.forms.layout; 032 033import java.awt.Component; 034import java.awt.Container; 035import java.awt.Dimension; 036import java.awt.Insets; 037import java.awt.LayoutManager2; 038import java.awt.Rectangle; 039import java.io.IOException; 040import java.io.ObjectOutputStream; 041import java.io.Serializable; 042import java.util.ArrayList; 043import java.util.Arrays; 044import java.util.HashMap; 045import java.util.Iterator; 046import java.util.LinkedList; 047import java.util.List; 048import java.util.Map; 049 050/** 051 * FormLayout is a powerful, flexible and precise general purpose 052 * layout manager. It aligns components vertically and horizontally in 053 * a dynamic rectangular grid of cells, with each component occupying one or 054 * more cells. 055 * A whitepaper 056 * about the FormLayout ships with the product documentation and is available 057 * <a href="https://web.archive.org/web/20061209004117/http://www.jgoodies.com:80/articles/forms.pdf">online</a>.<p> 058 * 059 * To use FormLayout you first define the grid by specifying the 060 * columns and rows. In a second step you add components to the grid. You can 061 * specify columns and rows via human-readable String descriptions or via 062 * arrays of {@link ColumnSpec} and {@link RowSpec} instances.<p> 063 * 064 * Each component managed by a FormLayout is associated with an instance of 065 * {@link CellConstraints}. The constraints object specifies where a component 066 * should be located on the form's grid and how the component should be 067 * positioned. In addition to its constraints object the 068 * <code>FormLayout</code> also considers each component's minimum and 069 * preferred sizes in order to determine a component's size.<p> 070 * 071 * FormLayout has been designed to work with non-visual builders that help you 072 * specify the layout and fill the grid. For example, the 073 * {@link com.jgoodies.forms.builder.ButtonBarBuilder} assists you in building button 074 * bars; it creates a standardized FormLayout and provides a minimal API that 075 * specializes in adding buttons. Other builders can create frequently used 076 * panel design, for example a form that consists of rows of label-component 077 * pairs.<p> 078 * 079 * FormLayout has been prepared to work with different types of sizes as 080 * defined by the {@link Size} interface.<p> 081 * 082 * <strong>Example 1</strong> (Plain FormLayout):<br> 083 * The following example creates a panel with 3 data columns and 3 data rows; 084 * the columns and rows are specified before components are added 085 * to the form. 086 * <pre> 087 * FormLayout layout = new FormLayout( 088 * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns 089 * "pref, 3dlu, pref, 3dlu, pref"); // rows 090 * 091 * CellConstraints cc = new CellConstraints(); 092 * JPanel panel = new JPanel(layout); 093 * panel.add(new JLabel("Label1"), cc.xy (1, 1)); 094 * panel.add(new JTextField(), cc.xywh(3, 1, 3, 1)); 095 * panel.add(new JLabel("Label2"), cc.xy (1, 3)); 096 * panel.add(new JTextField(), cc.xy (3, 3)); 097 * panel.add(new JLabel("Label3"), cc.xy (1, 5)); 098 * panel.add(new JTextField(), cc.xy (3, 5)); 099 * panel.add(new JButton("/u2026"), cc.xy (5, 5)); 100 * return panel; 101 * </pre><p> 102 * 103 * <strong>Example 2</strong> (Using PanelBuilder):<br> 104 * This example creates the same panel as above using the 105 * {@link com.jgoodies.forms.builder.PanelBuilder} to add components to the form. 106 * <pre> 107 * FormLayout layout = new FormLayout( 108 * "right:pref, 6dlu, 50dlu, 4dlu, default", // columns 109 * "pref, 3dlu, pref, 3dlu, pref"); // rows 110 * 111 * PanelBuilder builder = new PanelBuilder(layout); 112 * CellConstraints cc = new CellConstraints(); 113 * builder.addLabel("Label1", cc.xy (1, 1)); 114 * builder.add(new JTextField(), cc.xywh(3, 1, 3, 1)); 115 * builder.addLabel("Label2", cc.xy (1, 3)); 116 * builder.add(new JTextField(), cc.xy (3, 3)); 117 * builder.addLabel("Label3", cc.xy (1, 5)); 118 * builder.add(new JTextField(), cc.xy (3, 5)); 119 * builder.add(new JButton("/u2026"), cc.xy (5, 5)); 120 * return builder.getPanel(); 121 * </pre><p> 122 * 123 * <strong>Example 3</strong> (Using DefaultFormBuilder):<br> 124 * This example utilizes the 125 * {@link com.jgoodies.forms.builder.DefaultFormBuilder} that 126 * ships with the source distribution. 127 * <pre> 128 * FormLayout layout = new FormLayout( 129 * "right:pref, 6dlu, 50dlu, 4dlu, default"); // 5 columns; add rows later 130 * 131 * DefaultFormBuilder builder = new DefaultFormBuilder(layout); 132 * builder.append("Label1", new JTextField(), 3); 133 * builder.append("Label2", new JTextField()); 134 * builder.append("Label3", new JTextField()); 135 * builder.append(new JButton("/u2026")); 136 * return builder.getPanel(); 137 * </pre><p> 138 * 139 * TODO: In the Forms 1.0.x invisible components are not taken into account 140 * when the FormLayout lays out the container. Add an optional setting for 141 * this on both the container-level and component-level. So one can specify 142 * that invisible components shall be taken into account, but may exclude 143 * individual components. Or the other way round, exclude invisible components, 144 * and include individual components. The API of both the FormLayout and 145 * CellConstraints classes shall be extended to support this option. 146 * This feature is planned for the Forms version 1.1 and is described in 147 * <a href="https://forms.dev.java.net/issues/show_bug.cgi?id=28">issue #28</a> 148 * of the Forms' issue tracker where you can track the progress. 149 * 150 * @author Karsten Lentzsch 151 * @version $Revision$ 152 * 153 * @see ColumnSpec 154 * @see RowSpec 155 * @see CellConstraints 156 * @see com.jgoodies.forms.builder.AbstractFormBuilder 157 * @see com.jgoodies.forms.builder.ButtonBarBuilder 158 * @see com.jgoodies.forms.builder.DefaultFormBuilder 159 * @see com.jgoodies.forms.factories.FormFactory 160 * @see Size 161 * @see Sizes 162 */ 163@SuppressWarnings("serial") 164public final class FormLayout implements LayoutManager2, Serializable { 165 166 /** 167 * Holds the column specifications. 168 * 169 * @see ColumnSpec 170 * @see #getColumnCount() 171 * @see #getColumnSpec(int) 172 * @see #appendColumn(ColumnSpec) 173 * @see #insertColumn(int, ColumnSpec) 174 * @see #removeColumn(int) 175 */ 176 private final List colSpecs; 177 178 /** 179 * Holds the row specifications. 180 * 181 * @see RowSpec 182 * @see #getRowCount() 183 * @see #getRowSpec(int) 184 * @see #appendRow(RowSpec) 185 * @see #insertRow(int, RowSpec) 186 * @see #removeRow(int) 187 */ 188 private final List rowSpecs; 189 190 /** 191 * Holds the column groups as an array of arrays of column indices. 192 * 193 * @see #getColumnGroups() 194 * @see #setColumnGroups(int[][]) 195 * @see #addGroupedColumn(int) 196 */ 197 private int[][] colGroupIndices; 198 199 /** 200 * Holds the row groups as an array of arrays of row indices. 201 * 202 * @see #getRowGroups() 203 * @see #setRowGroups(int[][]) 204 * @see #addGroupedRow(int) 205 */ 206 private int[][] rowGroupIndices; 207 208 /** 209 * Maps components to their associated <code>CellConstraints</code>. 210 * 211 * @see CellConstraints 212 * @see #getConstraints(Component) 213 * @see #setConstraints(Component, CellConstraints) 214 */ 215 private final Map constraintMap; 216 217 // Fields used by the Layout Algorithm ********************************** 218 219 /** 220 * Holds the components that occupy exactly one column. 221 * For each column we keep a list of these components. 222 */ 223 private transient List[] colComponents; 224 225 /** 226 * Holds the components that occupy exactly one row. 227 * For each row we keep a list of these components. 228 */ 229 private transient List[] rowComponents; 230 231 /** 232 * Caches component minimum and preferred sizes. 233 * All requests for component sizes shall be directed to the cache. 234 */ 235 private final ComponentSizeCache componentSizeCache; 236 237 /** 238 * These functional objects are used to measure component sizes. 239 * They abstract from horizontal and vertical orientation and so, 240 * allow to implement the layout algorithm for both orientations with a 241 * single set of methods. 242 */ 243 private final Measure minimumWidthMeasure; 244 private final Measure minimumHeightMeasure; 245 private final Measure preferredWidthMeasure; 246 private final Measure preferredHeightMeasure; 247 248 // Instance Creation **************************************************** 249 250 /** 251 * Constructs an empty FormLayout. Columns and rows must be added 252 * before components can be added to the layout container.<p> 253 * 254 * This constructor is intended to be used in environments 255 * that add columns and rows dynamically. 256 */ 257 public FormLayout() { 258 this(new ColumnSpec[0], new RowSpec[0]); 259 } 260 261 /** 262 * Constructs a FormLayout using the given encoded column specifications. 263 * The constructed layout has no rows; these must be added 264 * before components can be added to the layout container.<p> 265 * 266 * This constructor is primarily intended to be used with builder classes 267 * that add rows dynamically, such as the <code>DefaultFormBuilder</code>.<p> 268 * 269 * <strong>Examples:</strong><pre> 270 * // Label, gap, component 271 * FormLayout layout = new FormLayout( 272 * "pref, 4dlu, pref"); 273 * 274 * // Right-aligned label, gap, component, gap, component 275 * FormLayout layout = new FormLayout( 276 * "right:pref, 4dlu, 50dlu, 4dlu, 50dlu"); 277 * 278 * // Left-aligned labels, gap, components, gap, components 279 * FormLayout layout = new FormLayout( 280 * "left:pref, 4dlu, pref, 4dlu, pref"); 281 * </pre> See the class comment for more examples. 282 * 283 * @param encodedColumnSpecs comma separated encoded column specifications 284 * @exception NullPointerException if encodedColumnSpecs is <code>null</code> 285 */ 286 public FormLayout(String encodedColumnSpecs) { 287 this(ColumnSpec.decodeSpecs(encodedColumnSpecs), new RowSpec[0]); 288 } 289 290 /** 291 * Constructs a FormLayout using the given 292 * encoded column and row specifications.<p> 293 * 294 * This constructor is recommended for most hand-coded layouts.<p> 295 * 296 * <strong>Examples:</strong><pre> 297 * FormLayout layout = new FormLayout( 298 * "pref, 4dlu, pref", // columns 299 * "p, 3dlu, p"); // rows 300 * 301 * FormLayout layout = new FormLayout( 302 * "right:pref, 4dlu, pref", // columns 303 * "p, 3dlu, p, 3dlu, fill:p:grow"); // rows 304 * 305 * FormLayout layout = new FormLayout( 306 * "left:pref, 4dlu, 50dlu", // columns 307 * "p, 2px, p, 3dlu, p, 9dlu, p"); // rows 308 * 309 * FormLayout layout = new FormLayout( 310 * "max(75dlu;pref), 4dlu, default", // columns 311 * "p, 3dlu, p, 3dlu, p, 3dlu, p"); // rows 312 * </pre> See the class comment for more examples. 313 * 314 * @param encodedColumnSpecs comma separated encoded column specifications 315 * @param encodedRowSpecs comma separated encoded row specifications 316 * @exception NullPointerException if encodedColumnSpecs or encodedRowSpecs 317 * is <code>null</code> 318 */ 319 public FormLayout(String encodedColumnSpecs, String encodedRowSpecs) { 320 this(ColumnSpec.decodeSpecs(encodedColumnSpecs), 321 RowSpec.decodeSpecs(encodedRowSpecs)); 322 } 323 324 /** 325 * Constructs a FormLayout using the given column and row specifications. 326 * 327 * @param colSpecs an array of column specifications. 328 * @param rowSpecs an array of row specifications. 329 * @exception NullPointerException if colSpecs or rowSpecs is null 330 */ 331 public FormLayout(ColumnSpec[] colSpecs, RowSpec[] rowSpecs) { 332 if (colSpecs == null) { 333 throw new NullPointerException( 334 "The column specifications must not be null."); 335 } 336 if (rowSpecs == null) { 337 throw new NullPointerException( 338 "The row specifications must not be null."); 339 } 340 341 this.colSpecs = new ArrayList(Arrays.asList(colSpecs)); 342 this.rowSpecs = new ArrayList(Arrays.asList(rowSpecs)); 343 colGroupIndices = new int[][] {}; 344 rowGroupIndices = new int[][] {}; 345 int initialCapacity = colSpecs.length * rowSpecs.length / 4; 346 constraintMap = new HashMap(initialCapacity); 347 componentSizeCache = new ComponentSizeCache(initialCapacity); 348 minimumWidthMeasure = new MinimumWidthMeasure(componentSizeCache); 349 minimumHeightMeasure = new MinimumHeightMeasure(componentSizeCache); 350 preferredWidthMeasure = new PreferredWidthMeasure(componentSizeCache); 351 preferredHeightMeasure = new PreferredHeightMeasure(componentSizeCache); 352 } 353 354 // Accessing the Column and Row Specifications ************************** 355 356 /** 357 * Returns the number of columns in this layout. 358 * 359 * @return the number of columns 360 */ 361 public int getColumnCount() { 362 return colSpecs.size(); 363 } 364 365 /** 366 * Returns the number of rows in this layout. 367 * 368 * @return the number of rows 369 */ 370 public int getRowCount() { 371 return rowSpecs.size(); 372 } 373 374 /** 375 * Returns the <code>ColumnSpec</code> at the specified column index. 376 * 377 * @param columnIndex the column index of the requested <code>ColumnSpec</code> 378 * @return the <code>ColumnSpec</code> at the specified column 379 * @exception IndexOutOfBoundsException if the column index is out of range 380 */ 381 public ColumnSpec getColumnSpec(int columnIndex) { 382 return (ColumnSpec) colSpecs.get(columnIndex - 1); 383 } 384 385 /** 386 * Sets the <code>ColumnSpec</code> at the specified column index. 387 * 388 * @param columnIndex the index of the column to be changed 389 * @param columnSpec the <code>ColumnSpec</code> to be set 390 * @exception NullPointerException if the column specification is null 391 * @exception IndexOutOfBoundsException if the column index is out of range 392 */ 393 public void setColumnSpec(int columnIndex, ColumnSpec columnSpec) { 394 if (columnSpec == null) { 395 throw new NullPointerException("The column spec must not be null."); 396 } 397 colSpecs.set(columnIndex - 1, columnSpec); 398 } 399 400 /** 401 * Returns the <code>RowSpec</code> at the specified row index. 402 * 403 * @param rowIndex the row index of the requested <code>RowSpec</code> 404 * @return the <code>RowSpec</code> at the specified row 405 * @exception IndexOutOfBoundsException if the row index is out of range 406 */ 407 public RowSpec getRowSpec(int rowIndex) { 408 return (RowSpec) rowSpecs.get(rowIndex - 1); 409 } 410 411 /** 412 * Sets the <code>RowSpec</code> at the specified row index. 413 * 414 * @param rowIndex the index of the row to be changed 415 * @param rowSpec the <code>RowSpec</code> to be set 416 * @exception NullPointerException if the row specification is null 417 * @exception IndexOutOfBoundsException if the row index is out of range 418 */ 419 public void setRowSpec(int rowIndex, RowSpec rowSpec) { 420 if (rowSpec == null) { 421 throw new NullPointerException("The row spec must not be null."); 422 } 423 rowSpecs.set(rowIndex - 1, rowSpec); 424 } 425 426 /** 427 * Appends the given column specification to the right hand side of all 428 * columns. 429 * 430 * @param columnSpec the column specification to be added 431 * @exception NullPointerException if the column specification is null 432 */ 433 public void appendColumn(ColumnSpec columnSpec) { 434 if (columnSpec == null) { 435 throw new NullPointerException("The column spec must not be null."); 436 } 437 colSpecs.add(columnSpec); 438 } 439 440 /** 441 * Inserts the specified column at the specified position. Shifts components 442 * that intersect the new column to the right hand side and readjusts 443 * column groups.<p> 444 * 445 * The component shift works as follows: components that were located on 446 * the right hand side of the inserted column are shifted one column to 447 * the right; component column span is increased by one if it intersects 448 * the new column.<p> 449 * 450 * Column group indices that are greater or equal than the given column 451 * index will be increased by one. 452 * 453 * @param columnIndex index of the column to be inserted 454 * @param columnSpec specification of the column to be inserted 455 * @exception IndexOutOfBoundsException if the column index is out of range 456 */ 457 public void insertColumn(int columnIndex, ColumnSpec columnSpec) { 458 if (columnIndex < 1 || columnIndex > getColumnCount()) { 459 throw new IndexOutOfBoundsException("The column index " 460 + columnIndex + "must be in the range [1, " 461 + getColumnCount() + "]."); 462 } 463 colSpecs.add(columnIndex - 1, columnSpec); 464 shiftComponentsHorizontally(columnIndex, false); 465 adjustGroupIndices(colGroupIndices, columnIndex, false); 466 } 467 468 /** 469 * Removes the column with the given column index from the layout. 470 * Components will be rearranged and column groups will be readjusted. 471 * Therefore, the column must not contain components and must not be part 472 * of a column group.<p> 473 * 474 * The component shift works as follows: components that were located on 475 * the right hand side of the removed column are moved one column to the 476 * left; component column span is decreased by one if it intersects the 477 * removed column.<p> 478 * 479 * Column group indices that are greater than the column index will be 480 * decreased by one.<p> 481 * 482 * <strong>Note:</strong> If one of the constraints mentioned above 483 * is violated, this layout's state becomes illegal and it is unsafe 484 * to work with this layout. 485 * A typical layout implementation can ensure that these constraints are 486 * not violated. However, in some cases you may need to check these 487 * conditions before you invoke this method. The Forms extras contain 488 * source code for class <code>FormLayoutUtils</code> that provides 489 * the required test methods:<br> 490 * <code>#columnContainsComponents(Container, int)</code> and<br> 491 * <code>#isGroupedColumn(FormLayout, int)</code>. 492 * 493 * @param columnIndex index of the column to remove 494 * @exception IndexOutOfBoundsException if the column index is out of range 495 * @exception IllegalStateException if the column contains components 496 * or if the column is already grouped 497 * 498 */ 499 public void removeColumn(int columnIndex) { 500 if (columnIndex < 1 || columnIndex > getColumnCount()) { 501 throw new IndexOutOfBoundsException("The column index " 502 + columnIndex + " must be in the range [1, " 503 + getColumnCount() + "]."); 504 } 505 colSpecs.remove(columnIndex - 1); 506 shiftComponentsHorizontally(columnIndex, true); 507 adjustGroupIndices(colGroupIndices, columnIndex, true); 508 } 509 510 /** 511 * Appends the given row specification to the bottom of all rows. 512 * 513 * @param rowSpec the row specification to be added to the form layout 514 * @exception NullPointerException if the rowSpec is null 515 */ 516 public void appendRow(RowSpec rowSpec) { 517 if (rowSpec == null) { 518 throw new NullPointerException("The row spec must not be null."); 519 } 520 rowSpecs.add(rowSpec); 521 } 522 523 /** 524 * Inserts the specified column at the specified position. Shifts 525 * components that intersect the new column to the right and readjusts 526 * column groups.<p> 527 * 528 * The component shift works as follows: components that were located on 529 * the right hand side of the inserted column are shifted one column to 530 * the right; component column span is increased by one if it intersects 531 * the new column.<p> 532 * 533 * Column group indices that are greater or equal than the given column 534 * index will be increased by one. 535 * 536 * @param rowIndex index of the row to be inserted 537 * @param rowSpec specification of the row to be inserted 538 * @exception IndexOutOfBoundsException if the row index is out of range 539 */ 540 public void insertRow(int rowIndex, RowSpec rowSpec) { 541 if (rowIndex < 1 || rowIndex > getRowCount()) { 542 throw new IndexOutOfBoundsException("The row index " + rowIndex 543 + " must be in the range [1, " + getRowCount() + "]."); 544 } 545 rowSpecs.add(rowIndex - 1, rowSpec); 546 shiftComponentsVertically(rowIndex, false); 547 adjustGroupIndices(rowGroupIndices, rowIndex, false); 548 } 549 550 /** 551 * Removes the row with the given row index from the layout. Components 552 * will be rearranged and row groups will be readjusted. Therefore, the 553 * row must not contain components and must not be part of a row group.<p> 554 * 555 * The component shift works as follows: components that were located 556 * below the removed row are moved up one row; component row span is 557 * decreased by one if it intersects the removed row.<p> 558 * 559 * Row group indices that are greater than the row index will be decreased 560 * by one.<p> 561 * 562 * <strong>Note:</strong> If one of the constraints mentioned above 563 * is violated, this layout's state becomes illegal and it is unsafe 564 * to work with this layout. 565 * A typical layout implementation can ensure that these constraints are 566 * not violated. However, in some cases you may need to check these 567 * conditions before you invoke this method. The Forms extras contain 568 * source code for class <code>FormLayoutUtils</code> that provides 569 * the required test methods:<br> 570 * <code>#rowContainsComponents(Container, int)</code> and<br> 571 * <code>#isGroupedRow(FormLayout, int)</code>. 572 * 573 * @param rowIndex index of the row to remove 574 * @exception IndexOutOfBoundsException if the row index is out of range 575 * @exception IllegalStateException if the row contains components 576 * or if the row is already grouped 577 * 578 */ 579 public void removeRow(int rowIndex) { 580 if (rowIndex < 1 || rowIndex > getRowCount()) { 581 throw new IndexOutOfBoundsException("The row index " + rowIndex 582 + "must be in the range [1, " + getRowCount() + "]."); 583 } 584 rowSpecs.remove(rowIndex - 1); 585 shiftComponentsVertically(rowIndex, true); 586 adjustGroupIndices(rowGroupIndices, rowIndex, true); 587 } 588 589 /** 590 * Shifts components horizontally, either to the right if a column has been 591 * inserted or to the left if a column has been removed. 592 * 593 * @param columnIndex index of the column to remove 594 * @param remove true for remove, false for insert 595 * @exception IllegalStateException if a removed column contains components 596 */ 597 private void shiftComponentsHorizontally(int columnIndex, boolean remove) { 598 final int offset = remove ? -1 : 1; 599 for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { 600 Map.Entry entry = (Map.Entry) i.next(); 601 CellConstraints constraints = (CellConstraints) entry.getValue(); 602 int x1 = constraints.gridX; 603 int w = constraints.gridWidth; 604 int x2 = x1 + w - 1; 605 if (x1 == columnIndex && remove) { 606 throw new IllegalStateException("The removed column " 607 + columnIndex + " must not contain component origins.\n" 608 + "Illegal component=" + entry.getKey()); 609 } else if (x1 >= columnIndex) { 610 constraints.gridX += offset; 611 } else if (x2 >= columnIndex) { 612 constraints.gridWidth += offset; 613 } 614 } 615 } 616 617 /** 618 * Shifts components vertically, either to the bottom if a row has been 619 * inserted or to the top if a row has been removed. 620 * 621 * @param rowIndex index of the row to remove 622 * @param remove true for remove, false for insert 623 * @exception IllegalStateException if a removed column contains components 624 */ 625 private void shiftComponentsVertically(int rowIndex, boolean remove) { 626 final int offset = remove ? -1 : 1; 627 for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { 628 Map.Entry entry = (Map.Entry) i.next(); 629 CellConstraints constraints = (CellConstraints) entry.getValue(); 630 int y1 = constraints.gridY; 631 int h = constraints.gridHeight; 632 int y2 = y1 + h - 1; 633 if (y1 == rowIndex && remove) { 634 throw new IllegalStateException("The removed row " + rowIndex 635 + " must not contain component origins.\n" 636 + "Illegal component=" + entry.getKey()); 637 } else if (y1 >= rowIndex) { 638 constraints.gridY += offset; 639 } else if (y2 >= rowIndex) { 640 constraints.gridHeight += offset; 641 } 642 } 643 } 644 645 /** 646 * Adjusts group indices. Shifts the given groups to left, right, up, 647 * down according to the specified remove or add flag. 648 * 649 * @param allGroupIndices the groups to be adjusted 650 * @param modifiedIndex the modified column or row index 651 * @param remove true for remove, false for add 652 * @exception IllegalStateException if we remove and the index is grouped 653 */ 654 private void adjustGroupIndices(int[][] allGroupIndices, int modifiedIndex, 655 boolean remove) { 656 final int offset = remove ? -1 : +1; 657 for (int[] allGroupIndice : allGroupIndices) { 658 int[] groupIndices = allGroupIndice; 659 for (int i = 0; i < groupIndices.length; i++) { 660 int index = groupIndices[i]; 661 if (index == modifiedIndex && remove) { 662 throw new IllegalStateException("The removed index " 663 + modifiedIndex + " must not be grouped."); 664 } else if (index >= modifiedIndex) { 665 groupIndices[i] += offset; 666 } 667 } 668 } 669 } 670 671 // Accessing Constraints ************************************************ 672 673 /** 674 * Looks up and returns the constraints for the specified component. 675 * A copy of the actual <code>CellConstraints</code> object is returned. 676 * 677 * @param component the component to be queried 678 * @return the <code>CellConstraints</code> for the specified component 679 * @exception NullPointerException if component is <code>null</code> or 680 * has not been added to the container 681 */ 682 public CellConstraints getConstraints(Component component) { 683 if (component == null) { 684 throw new NullPointerException("The component must not be null."); 685 } 686 687 CellConstraints constraints = (CellConstraints) constraintMap 688 .get(component); 689 if (constraints == null) { 690 throw new NullPointerException( 691 "The component has not been added to the container."); 692 } 693 694 return (CellConstraints) constraints.clone(); 695 } 696 697 /** 698 * Sets the constraints for the specified component in this layout. 699 * 700 * @param component the component to be modified 701 * @param constraints the constraints to be applied 702 * @exception NullPointerException if the component or constraints object 703 * is <code>null</code> 704 */ 705 public void setConstraints(Component component, 706 CellConstraints constraints) { 707 if (component == null) { 708 throw new NullPointerException("The component must not be null."); 709 } 710 if (constraints == null) { 711 throw new NullPointerException("The constraints must not be null."); 712 } 713 714 constraints.ensureValidGridBounds(getColumnCount(), getRowCount()); 715 constraintMap.put(component, constraints.clone()); 716 } 717 718 /** 719 * Removes the constraints for the specified component in this layout. 720 * 721 * @param component the component to be modified 722 */ 723 private void removeConstraints(Component component) { 724 constraintMap.remove(component); 725 componentSizeCache.removeEntry(component); 726 } 727 728 // Accessing Column and Row Groups ************************************** 729 730 /** 731 * Returns a deep copy of the column groups. 732 * 733 * @return the column groups as two-dimensional int array 734 */ 735 public int[][] getColumnGroups() { 736 return deepClone(colGroupIndices); 737 } 738 739 /** 740 * Sets the column groups, where each column in a group gets the same 741 * group wide width. Each group is described by an array of integers that 742 * are interpreted as column indices. The parameter is an array of such 743 * group descriptions.<p> 744 * 745 * <strong>Examples:</strong><pre> 746 * // Group columns 1, 3 and 4. 747 * setColumnGroups(new int[][]{ {1, 3, 4}}); 748 * 749 * // Group columns 1, 3, 4, and group columns 7 and 9 750 * setColumnGroups(new int[][]{ {1, 3, 4}, {7, 9}}); 751 * </pre> 752 * 753 * @param colGroupIndices a two-dimensional array of column groups indices 754 * @exception IndexOutOfBoundsException if an index is outside the grid 755 * @exception IllegalArgumentException if a column index is used twice 756 */ 757 public void setColumnGroups(int[][] colGroupIndices) { 758 int maxColumn = getColumnCount(); 759 boolean[] usedIndices = new boolean[maxColumn + 1]; 760 for (int group = 0; group < colGroupIndices.length; group++) { 761 for (int j = 0; j < colGroupIndices[group].length; j++) { 762 int colIndex = colGroupIndices[group][j]; 763 if (colIndex < 1 || colIndex > maxColumn) { 764 throw new IndexOutOfBoundsException( 765 "Invalid column group index " + colIndex 766 + " in group " + (group + 1)); 767 } 768 if (usedIndices[colIndex]) { 769 throw new IllegalArgumentException("Column index " 770 + colIndex 771 + " must not be used in multiple column groups."); 772 } 773 usedIndices[colIndex] = true; 774 } 775 } 776 this.colGroupIndices = deepClone(colGroupIndices); 777 } 778 779 /** 780 * Adds the specified column index to the last column group. 781 * In case there are no groups, a new group will be created. 782 * 783 * @param columnIndex the column index to be set grouped 784 */ 785 public void addGroupedColumn(int columnIndex) { 786 int[][] newColGroups = getColumnGroups(); 787 // Create a group if none exists. 788 if (newColGroups.length == 0) { 789 newColGroups = new int[][] { { columnIndex } }; 790 } else { 791 int lastGroupIndex = newColGroups.length - 1; 792 int[] lastGroup = newColGroups[lastGroupIndex]; 793 int groupSize = lastGroup.length; 794 int[] newLastGroup = new int[groupSize + 1]; 795 System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize); 796 newLastGroup[groupSize] = columnIndex; 797 newColGroups[lastGroupIndex] = newLastGroup; 798 } 799 setColumnGroups(newColGroups); 800 } 801 802 /** 803 * Returns a deep copy of the row groups. 804 * 805 * @return the row groups as two-dimensional int array 806 */ 807 public int[][] getRowGroups() { 808 return deepClone(rowGroupIndices); 809 } 810 811 /** 812 * Sets the row groups, where each row in such a group gets the same group 813 * wide height. Each group is described by an array of integers that are 814 * interpreted as row indices. The parameter is an array of such group 815 * descriptions.<p> 816 * 817 * <strong>Examples:</strong><pre> 818 * // Group rows 1 and 2. 819 * setRowGroups(new int[][]{ {1, 2}}); 820 * 821 * // Group rows 1 and 2, and group rows 5, 7, and 9. 822 * setRowGroups(new int[][]{ {1, 2}, {5, 7, 9}}); 823 * </pre> 824 * 825 * @param rowGroupIndices a two-dimensional array of row group indices. 826 * @exception IndexOutOfBoundsException if an index is outside the grid 827 */ 828 public void setRowGroups(int[][] rowGroupIndices) { 829 int rowCount = getRowCount(); 830 boolean[] usedIndices = new boolean[rowCount + 1]; 831 for (int i = 0; i < rowGroupIndices.length; i++) { 832 for (int j = 0; j < rowGroupIndices[i].length; j++) { 833 int rowIndex = rowGroupIndices[i][j]; 834 if (rowIndex < 1 || rowIndex > rowCount) { 835 throw new IndexOutOfBoundsException( 836 "Invalid row group index " + rowIndex + " in group " 837 + (i + 1)); 838 } 839 if (usedIndices[rowIndex]) { 840 throw new IllegalArgumentException("Row index " + rowIndex 841 + " must not be used in multiple row groups."); 842 } 843 usedIndices[rowIndex] = true; 844 } 845 } 846 this.rowGroupIndices = deepClone(rowGroupIndices); 847 } 848 849 /** 850 * Adds the specified row index to the last row group. 851 * In case there are no groups, a new group will be created. 852 * 853 * @param rowIndex the index of the row that should be grouped 854 */ 855 public void addGroupedRow(int rowIndex) { 856 int[][] newRowGroups = getRowGroups(); 857 // Create a group if none exists. 858 if (newRowGroups.length == 0) { 859 newRowGroups = new int[][] { { rowIndex } }; 860 } else { 861 int lastGroupIndex = newRowGroups.length - 1; 862 int[] lastGroup = newRowGroups[lastGroupIndex]; 863 int groupSize = lastGroup.length; 864 int[] newLastGroup = new int[groupSize + 1]; 865 System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize); 866 newLastGroup[groupSize] = rowIndex; 867 newRowGroups[lastGroupIndex] = newLastGroup; 868 } 869 setRowGroups(newRowGroups); 870 } 871 872 // Implementing the LayoutManager and LayoutManager2 Interfaces ********* 873 874 /** 875 * Throws an <code>UnsupportedOperationException</code>. Does not add 876 * the specified component with the specified name to the layout. 877 * 878 * @param name indicates entry's position and anchor 879 * @param component component to add 880 * @exception UnsupportedOperationException always 881 */ 882 @Override 883 public void addLayoutComponent(String name, Component component) { 884 throw new UnsupportedOperationException( 885 "Use #addLayoutComponent(Component, Object) instead."); 886 } 887 888 /** 889 * Adds the specified component to the layout, using the specified 890 * <code>constraints</code> object. Note that constraints are mutable and 891 * are, therefore, cloned when cached. 892 * 893 * @param comp the component to be added 894 * @param constraints the component's cell constraints 895 * @exception NullPointerException if <code>constraints</code> is <code>null</code> 896 * @exception IllegalArgumentException if <code>constraints</code> is not a 897 * <code>CellConstraints</code> or a String that cannot be used to construct 898 * a <code>CellConstraints</code> 899 */ 900 @Override 901 public void addLayoutComponent(Component comp, Object constraints) { 902 if (constraints instanceof String) { 903 setConstraints(comp, new CellConstraints((String) constraints)); 904 } else if (constraints instanceof CellConstraints) { 905 setConstraints(comp, (CellConstraints) constraints); 906 } else if (constraints == null) { 907 throw new NullPointerException("The constraints must not be null."); 908 } else { 909 throw new IllegalArgumentException( 910 "Illegal constraint type " + constraints.getClass()); 911 } 912 } 913 914 /** 915 * Removes the specified component from this layout.<p> 916 * 917 * Most applications do not call this method directly. 918 * 919 * @param comp the component to be removed. 920 * @see Container#remove(java.awt.Component) 921 * @see Container#removeAll() 922 */ 923 @Override 924 public void removeLayoutComponent(Component comp) { 925 removeConstraints(comp); 926 } 927 928 // Layout Requests ****************************************************** 929 930 /** 931 * Determines the minimum size of the <code>parent</code> container 932 * using this form layout.<p> 933 * 934 * Most applications do not call this method directly. 935 * 936 * @param parent the container in which to do the layout 937 * @return the minimum size of the <code>parent</code> container 938 * 939 * @see Container#doLayout() 940 */ 941 @Override 942 public Dimension minimumLayoutSize(Container parent) { 943 return computeLayoutSize(parent, minimumWidthMeasure, 944 minimumHeightMeasure); 945 } 946 947 /** 948 * Determines the preferred size of the <code>parent</code> 949 * container using this form layout.<p> 950 * 951 * Most applications do not call this method directly. 952 * 953 * @param parent the container in which to do the layout 954 * @return the preferred size of the <code>parent</code> container 955 * 956 * @see Container#getPreferredSize() 957 */ 958 @Override 959 public Dimension preferredLayoutSize(Container parent) { 960 return computeLayoutSize(parent, preferredWidthMeasure, 961 preferredHeightMeasure); 962 } 963 964 /** 965 * Returns the maximum dimensions for this layout given the components 966 * in the specified target container. 967 * 968 * @param target the container which needs to be laid out 969 * @see Container 970 * @see #minimumLayoutSize(Container) 971 * @see #preferredLayoutSize(Container) 972 * @return the maximum dimensions for this layout 973 */ 974 @Override 975 public Dimension maximumLayoutSize(Container target) { 976 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); 977 } 978 979 /** 980 * Returns the alignment along the x axis. This specifies how 981 * the component would like to be aligned relative to other 982 * components. The value should be a number between 0 and 1 983 * where 0 represents alignment along the origin, 1 is aligned 984 * the furthest away from the origin, 0.5 is centered, etc. 985 * 986 * @param parent the parent container 987 * @return the value <code>0.5f</code> to indicate center alignment 988 */ 989 @Override 990 public float getLayoutAlignmentX(Container parent) { 991 return 0.5f; 992 } 993 994 /** 995 * Returns the alignment along the y axis. This specifies how 996 * the component would like to be aligned relative to other 997 * components. The value should be a number between 0 and 1 998 * where 0 represents alignment along the origin, 1 is aligned 999 * the furthest away from the origin, 0.5 is centered, etc. 1000 * 1001 * @param parent the parent container 1002 * @return the value <code>0.5f</code> to indicate center alignment 1003 */ 1004 @Override 1005 public float getLayoutAlignmentY(Container parent) { 1006 return 0.5f; 1007 } 1008 1009 /** 1010 * Invalidates the layout, indicating that if the layout manager 1011 * has cached information it should be discarded. 1012 * 1013 * @param target the container that holds the layout to be invalidated 1014 */ 1015 @Override 1016 public void invalidateLayout(Container target) { 1017 invalidateCaches(); 1018 } 1019 1020 /** 1021 * Lays out the specified container using this form layout. This method 1022 * reshapes components in the specified container in order to satisfy the 1023 * constraints of this <code>FormLayout</code> object.<p> 1024 * 1025 * Most applications do not call this method directly.<p> 1026 * 1027 * The form layout performs the following steps: 1028 * <ol> 1029 * <li>find components that occupy exactly one column or row 1030 * <li>compute minimum widths and heights 1031 * <li>compute preferred widths and heights 1032 * <li>give cols and row equal size if they share a group 1033 * <li>compress default columns and rows if total is less than pref size 1034 * <li>give cols and row equal size if they share a group 1035 * <li>distribute free space 1036 * <li>set components bounds 1037 * </ol> 1038 * 1039 * @param parent the container in which to do the layout 1040 * @see Container 1041 * @see Container#doLayout() 1042 */ 1043 @Override 1044 public void layoutContainer(Container parent) { 1045 synchronized (parent.getTreeLock()) { 1046 initializeColAndRowComponentLists(); 1047 Dimension size = parent.getSize(); 1048 1049 Insets insets = parent.getInsets(); 1050 int totalWidth = size.width - insets.left - insets.right; 1051 int totalHeight = size.height - insets.top - insets.bottom; 1052 1053 int[] x = computeGridOrigins(parent, totalWidth, insets.left, 1054 colSpecs, colComponents, colGroupIndices, 1055 minimumWidthMeasure, preferredWidthMeasure); 1056 int[] y = computeGridOrigins(parent, totalHeight, insets.top, 1057 rowSpecs, rowComponents, rowGroupIndices, 1058 minimumHeightMeasure, preferredHeightMeasure); 1059 1060 layoutComponents(x, y); 1061 } 1062 } 1063 1064 // Layout Algorithm ***************************************************** 1065 1066 /** 1067 * Initializes two lists for columns and rows that hold a column's 1068 * or row's components that span only this column or row.<p> 1069 * 1070 * Iterates over all components and their associated constraints; 1071 * every component that has a column span or row span of 1 1072 * is put into the column's or row's component list.<p> 1073 * 1074 * As of the Forms version 1.0.x invisible components are not taken 1075 * into account when the container is layed out. See the TODO in the 1076 * JavaDoc class comment for details on this issue. 1077 */ 1078 private void initializeColAndRowComponentLists() { 1079 colComponents = new LinkedList[getColumnCount()]; 1080 for (int i = 0; i < getColumnCount(); i++) { 1081 colComponents[i] = new LinkedList(); 1082 } 1083 1084 rowComponents = new LinkedList[getRowCount()]; 1085 for (int i = 0; i < getRowCount(); i++) { 1086 rowComponents[i] = new LinkedList(); 1087 } 1088 1089 for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { 1090 Map.Entry entry = (Map.Entry) i.next(); 1091 Component component = (Component) entry.getKey(); 1092 if (!component.isVisible()) { 1093 continue; 1094 } 1095 1096 CellConstraints constraints = (CellConstraints) entry.getValue(); 1097 if (constraints.gridWidth == 1) { 1098 colComponents[constraints.gridX - 1].add(component); 1099 } 1100 1101 if (constraints.gridHeight == 1) { 1102 rowComponents[constraints.gridY - 1].add(component); 1103 } 1104 } 1105 } 1106 1107 /** 1108 * Computes and returns the layout size of the given <code>parent</code> 1109 * container using the specified measures. 1110 * 1111 * @param parent the container in which to do the layout 1112 * @param defaultWidthMeasure the measure used to compute the default width 1113 * @param defaultHeightMeasure the measure used to compute the default height 1114 * @return the layout size of the <code>parent</code> container 1115 */ 1116 private Dimension computeLayoutSize(Container parent, 1117 Measure defaultWidthMeasure, Measure defaultHeightMeasure) { 1118 synchronized (parent.getTreeLock()) { 1119 initializeColAndRowComponentLists(); 1120 int[] colWidths = maximumSizes(parent, colSpecs, colComponents, 1121 minimumWidthMeasure, preferredWidthMeasure, 1122 defaultWidthMeasure); 1123 int[] rowHeights = maximumSizes(parent, rowSpecs, rowComponents, 1124 minimumHeightMeasure, preferredHeightMeasure, 1125 defaultHeightMeasure); 1126 int[] groupedWidths = groupedSizes(colGroupIndices, colWidths); 1127 int[] groupedHeights = groupedSizes(rowGroupIndices, rowHeights); 1128 1129 // Convert sizes to origins. 1130 int[] xOrigins = computeOrigins(groupedWidths, 0); 1131 int[] yOrigins = computeOrigins(groupedHeights, 0); 1132 1133 int width1 = sum(groupedWidths); 1134 int height1 = sum(groupedHeights); 1135 int maxWidth = width1; 1136 int maxHeight = height1; 1137 1138 /* 1139 * Take components that span multiple columns or rows into account. 1140 * This shall be done if and only if a component spans an interval 1141 * that can grow. 1142 */ 1143 // First computes the maximum number of cols/rows a component 1144 // can span without spanning a growing column. 1145 int[] maxFixedSizeColsTable = computeMaximumFixedSpanTable( 1146 colSpecs); 1147 int[] maxFixedSizeRowsTable = computeMaximumFixedSpanTable( 1148 rowSpecs); 1149 1150 for (Iterator i = constraintMap.entrySet().iterator(); i 1151 .hasNext();) { 1152 Map.Entry entry = (Map.Entry) i.next(); 1153 Component component = (Component) entry.getKey(); 1154 if (!component.isVisible()) { 1155 continue; 1156 } 1157 1158 CellConstraints constraints = (CellConstraints) entry 1159 .getValue(); 1160 if (constraints.gridWidth > 1 1161 && constraints.gridWidth > maxFixedSizeColsTable[constraints.gridX 1162 - 1]) { 1163 //int compWidth = minimumWidthMeasure.sizeOf(component); 1164 int compWidth = defaultWidthMeasure.sizeOf(component); 1165 //int compWidth = preferredWidthMeasure.sizeOf(component); 1166 int gridX1 = constraints.gridX - 1; 1167 int gridX2 = gridX1 + constraints.gridWidth; 1168 int lead = xOrigins[gridX1]; 1169 int trail = width1 - xOrigins[gridX2]; 1170 int myWidth = lead + compWidth + trail; 1171 if (myWidth > maxWidth) { 1172 maxWidth = myWidth; 1173 } 1174 } 1175 1176 if (constraints.gridHeight > 1 1177 && constraints.gridHeight > maxFixedSizeRowsTable[constraints.gridY 1178 - 1]) { 1179 //int compHeight = minimumHeightMeasure.sizeOf(component); 1180 int compHeight = defaultHeightMeasure.sizeOf(component); 1181 //int compHeight = preferredHeightMeasure.sizeOf(component); 1182 int gridY1 = constraints.gridY - 1; 1183 int gridY2 = gridY1 + constraints.gridHeight; 1184 int lead = yOrigins[gridY1]; 1185 int trail = height1 - yOrigins[gridY2]; 1186 int myHeight = lead + compHeight + trail; 1187 if (myHeight > maxHeight) { 1188 maxHeight = myHeight; 1189 } 1190 } 1191 } 1192 Insets insets = parent.getInsets(); 1193 int width = maxWidth + insets.left + insets.right; 1194 int height = maxHeight + insets.top + insets.bottom; 1195 return new Dimension(width, height); 1196 } 1197 } 1198 1199 /** 1200 * Computes and returns the grid's origins. 1201 * 1202 * @param container the layout container 1203 * @param totalSize the total size to assign 1204 * @param offset the offset from left or top margin 1205 * @param formSpecs the column or row specs, resp. 1206 * @param componentLists the components list for each col/row 1207 * @param minMeasure the measure used to determine min sizes 1208 * @param prefMeasure the measure used to determine pre sizes 1209 * @param groupIndices the group specification 1210 * @return an int array with the origins 1211 */ 1212 private int[] computeGridOrigins(Container container, int totalSize, 1213 int offset, List formSpecs, List[] componentLists, 1214 int[][] groupIndices, Measure minMeasure, Measure prefMeasure) { 1215 /* For each spec compute the minimum and preferred size that is 1216 * the maximum of all component minimum and preferred sizes resp. 1217 */ 1218 int[] minSizes = maximumSizes(container, formSpecs, componentLists, 1219 minMeasure, prefMeasure, minMeasure); 1220 int[] prefSizes = maximumSizes(container, formSpecs, componentLists, 1221 minMeasure, prefMeasure, prefMeasure); 1222 1223 int[] groupedMinSizes = groupedSizes(groupIndices, minSizes); 1224 int[] groupedPrefSizes = groupedSizes(groupIndices, prefSizes); 1225 int totalMinSize = sum(groupedMinSizes); 1226 int totalPrefSize = sum(groupedPrefSizes); 1227 int[] compressedSizes = compressedSizes(formSpecs, totalSize, 1228 totalMinSize, totalPrefSize, groupedMinSizes, prefSizes); 1229 int[] groupedSizes = groupedSizes(groupIndices, compressedSizes); 1230 int totalGroupedSize = sum(groupedSizes); 1231 int[] sizes = distributedSizes(formSpecs, totalSize, totalGroupedSize, 1232 groupedSizes); 1233 return computeOrigins(sizes, offset); 1234 } 1235 1236 /** 1237 * Computes origins from sizes taking the specified offset into account. 1238 * 1239 * @param sizes the array of sizes 1240 * @param offset an offset for the first origin 1241 * @return an array of origins 1242 */ 1243 private int[] computeOrigins(int[] sizes, int offset) { 1244 int count = sizes.length; 1245 int[] origins = new int[count + 1]; 1246 origins[0] = offset; 1247 for (int i = 1; i <= count; i++) { 1248 origins[i] = origins[i - 1] + sizes[i - 1]; 1249 } 1250 return origins; 1251 } 1252 1253 /** 1254 * Lays out the components using the given x and y origins, the column 1255 * and row specifications, and the component constraints.<p> 1256 * 1257 * The actual computation is done by each component's form constraint 1258 * object. We just compute the cell, the cell bounds and then hand over 1259 * the component, cell bounds, and measure to the form constraints. 1260 * This will allow potential subclasses of <code>CellConstraints</code> 1261 * to do special micro-layout corrections. For example, such a subclass 1262 * could map JComponent classes to visual layout bounds that may 1263 * lead to a slightly different bounds. 1264 * 1265 * @param x an int array of the horizontal origins 1266 * @param y an int array of the vertical origins 1267 */ 1268 private void layoutComponents(int[] x, int[] y) { 1269 Rectangle cellBounds = new Rectangle(); 1270 for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext();) { 1271 Map.Entry entry = (Map.Entry) i.next(); 1272 Component component = (Component) entry.getKey(); 1273 CellConstraints constraints = (CellConstraints) entry.getValue(); 1274 1275 int gridX = constraints.gridX - 1; 1276 int gridY = constraints.gridY - 1; 1277 int gridWidth = constraints.gridWidth; 1278 int gridHeight = constraints.gridHeight; 1279 cellBounds.x = x[gridX]; 1280 cellBounds.y = y[gridY]; 1281 cellBounds.width = x[gridX + gridWidth] - cellBounds.x; 1282 cellBounds.height = y[gridY + gridHeight] - cellBounds.y; 1283 1284 constraints.setBounds(component, this, cellBounds, 1285 minimumWidthMeasure, minimumHeightMeasure, 1286 preferredWidthMeasure, preferredHeightMeasure); 1287 } 1288 } 1289 1290 /** 1291 * Invalidates the component size caches. 1292 */ 1293 private void invalidateCaches() { 1294 componentSizeCache.invalidate(); 1295 } 1296 1297 /** 1298 * Computes and returns the sizes for the given form specs, component 1299 * lists and measures fot minimum, preferred, and default size. 1300 * 1301 * @param container the layout container 1302 * @param formSpecs the column or row specs, resp. 1303 * @param componentLists the components list for each col/row 1304 * @param minMeasure the measure used to determine min sizes 1305 * @param prefMeasure the measure used to determine pre sizes 1306 * @param defaultMeasure the measure used to determine default sizes 1307 * @return the column or row sizes 1308 */ 1309 private int[] maximumSizes(Container container, List formSpecs, 1310 List[] componentLists, Measure minMeasure, Measure prefMeasure, 1311 Measure defaultMeasure) { 1312 FormSpec formSpec; 1313 int size = formSpecs.size(); 1314 int[] result = new int[size]; 1315 for (int i = 0; i < size; i++) { 1316 formSpec = (FormSpec) formSpecs.get(i); 1317 result[i] = formSpec.maximumSize(container, componentLists[i], 1318 minMeasure, prefMeasure, defaultMeasure); 1319 } 1320 return result; 1321 } 1322 1323 /** 1324 * Computes and returns the compressed sizes. Compresses space for columns 1325 * and rows iff the available space is less than the total preferred size 1326 * but more than the total minimum size.<p> 1327 * 1328 * Only columns and row that are specified to be compressible will be 1329 * affected. You can specify a column and row as compressible by 1330 * giving it the component size <tt>default</tt>. 1331 * 1332 * @param formSpecs the column or row specs to use 1333 * @param totalSize the total available size 1334 * @param totalMinSize the sum of all minimum sizes 1335 * @param totalPrefSize the sum of all preferred sizes 1336 * @param minSizes an int array of column/row minimum sizes 1337 * @param prefSizes an int array of column/row preferred sizes 1338 * @return an int array of compressed column/row sizes 1339 */ 1340 private int[] compressedSizes(List formSpecs, int totalSize, 1341 int totalMinSize, int totalPrefSize, int[] minSizes, 1342 int[] prefSizes) { 1343 1344 // If we have less space than the total min size answer the min sizes. 1345 if (totalSize < totalMinSize) { 1346 return minSizes; 1347 } 1348 // If we have more space than the total pref size answer the pref sizes. 1349 if (totalSize >= totalPrefSize) { 1350 return prefSizes; 1351 } 1352 1353 int count = formSpecs.size(); 1354 int[] sizes = new int[count]; 1355 1356 double totalCompressionSpace = totalPrefSize - totalSize; 1357 double maxCompressionSpace = totalPrefSize - totalMinSize; 1358 double compressionFactor = totalCompressionSpace / maxCompressionSpace; 1359 1360 // System.out.println("Total compression space=" + totalCompressionSpace); 1361 // System.out.println("Max compression space =" + maxCompressionSpace); 1362 // System.out.println("Compression factor =" + compressionFactor); 1363 1364 for (int i = 0; i < count; i++) { 1365 FormSpec formSpec = (FormSpec) formSpecs.get(i); 1366 sizes[i] = prefSizes[i]; 1367 if (formSpec.getSize() == Sizes.DEFAULT) { 1368 sizes[i] -= (int) Math.round( 1369 (prefSizes[i] - minSizes[i]) * compressionFactor); 1370 } 1371 } 1372 return sizes; 1373 } 1374 1375 /** 1376 * Computes and returns the grouped sizes. 1377 * Gives grouped columns and rows the same size. 1378 * 1379 * @param groups the group specification 1380 * @param rawSizes the raw sizes before the grouping 1381 * @return the grouped sizes 1382 */ 1383 private int[] groupedSizes(int[][] groups, int[] rawSizes) { 1384 // Return the compressed sizes if there are no groups. 1385 if (groups == null || groups.length == 0) { 1386 return rawSizes; 1387 } 1388 1389 // Initialize the result with the given compressed sizes. 1390 int[] sizes = new int[rawSizes.length]; 1391 for (int i = 0; i < sizes.length; i++) { 1392 sizes[i] = rawSizes[i]; 1393 } 1394 1395 // For each group equalize the sizes. 1396 for (int[] groupIndices : groups) { 1397 int groupMaxSize = 0; 1398 // Compute the group's maximum size. 1399 for (int groupIndice : groupIndices) { 1400 int index = groupIndice - 1; 1401 groupMaxSize = Math.max(groupMaxSize, sizes[index]); 1402 } 1403 // Set all sizes of this group to the group's maximum size. 1404 for (int groupIndice : groupIndices) { 1405 int index = groupIndice - 1; 1406 sizes[index] = groupMaxSize; 1407 } 1408 } 1409 return sizes; 1410 } 1411 1412 /** 1413 * Distributes free space over columns and rows and 1414 * returns the sizes after this distribution process. 1415 * 1416 * @param formSpecs the column/row specifications to work with 1417 * @param totalSize the total available size 1418 * @param totalPrefSize the sum of all preferred sizes 1419 * @param inputSizes the input sizes 1420 * @return the distributed sizes 1421 */ 1422 private int[] distributedSizes(List formSpecs, int totalSize, 1423 int totalPrefSize, int[] inputSizes) { 1424 double totalFreeSpace = totalSize - totalPrefSize; 1425 // Do nothing if there's no free space. 1426 if (totalFreeSpace < 0) { 1427 return inputSizes; 1428 } 1429 1430 // Compute the total weight. 1431 int count = formSpecs.size(); 1432 double totalWeight = 0.0; 1433 for (int i = 0; i < count; i++) { 1434 FormSpec formSpec = (FormSpec) formSpecs.get(i); 1435 totalWeight += formSpec.getResizeWeight(); 1436 } 1437 1438 // Do nothing if there's no resizing column. 1439 if (totalWeight == 0.0) { 1440 return inputSizes; 1441 } 1442 1443 int[] sizes = new int[count]; 1444 1445 double restSpace = totalFreeSpace; 1446 int roundedRestSpace = (int) totalFreeSpace; 1447 for (int i = 0; i < count; i++) { 1448 FormSpec formSpec = (FormSpec) formSpecs.get(i); 1449 double weight = formSpec.getResizeWeight(); 1450 if (weight == FormSpec.NO_GROW) { 1451 sizes[i] = inputSizes[i]; 1452 } else { 1453 double roundingCorrection = restSpace - roundedRestSpace; 1454 double extraSpace = totalFreeSpace * weight / totalWeight; 1455 double correctedExtraSpace = extraSpace - roundingCorrection; 1456 int roundedExtraSpace = (int) Math.round(correctedExtraSpace); 1457 sizes[i] = inputSizes[i] + roundedExtraSpace; 1458 restSpace -= extraSpace; 1459 roundedRestSpace -= roundedExtraSpace; 1460 } 1461 } 1462 return sizes; 1463 } 1464 1465 /** 1466 * Computes and returns the sum of integers in the given array of ints. 1467 * 1468 * @param sizes an array of ints to sum up 1469 * @return the sum of ints in the array 1470 */ 1471 private int sum(int[] sizes) { 1472 int sum = 0; 1473 for (int i = sizes.length - 1; i >= 0; i--) { 1474 sum += sizes[i]; 1475 } 1476 return sum; 1477 } 1478 1479 /** 1480 * Computes and returns a table that maps a column/row index 1481 * to the maximum number of columns/rows that a component can span 1482 * without spanning a growing column.<p> 1483 * 1484 * Iterates over the specs from right to left/bottom to top, 1485 * sets the table value to zero if a spec can grow, 1486 * otherwise increases the span by one.<p> 1487 * 1488 * <strong>Examples:</strong><pre> 1489 * "pref, 4dlu, pref, 2dlu, p:grow, 2dlu, pref" -> 1490 * [4, 3, 2, 1, 0, MAX_VALUE, MAX_VALUE] 1491 * 1492 * "p:grow, 4dlu, p:grow, 9dlu, pref" -> 1493 * [0, 1, 0, MAX_VALUE, MAX_VALUE] 1494 * 1495 * "p, 4dlu, p, 2dlu, 0:grow" -> 1496 * [4, 3, 2, 1, 0] 1497 * </pre> 1498 * 1499 * @param formSpecs the column specs or row specs 1500 * @return a table that maps a spec index to the maximum span for 1501 * fixed size specs 1502 */ 1503 private int[] computeMaximumFixedSpanTable(List formSpecs) { 1504 int size = formSpecs.size(); 1505 int[] table = new int[size]; 1506 int maximumFixedSpan = Integer.MAX_VALUE; // Could be 1 1507 for (int i = size - 1; i >= 0; i--) { 1508 FormSpec spec = (FormSpec) formSpecs.get(i); // ArrayList access 1509 if (spec.canGrow()) { 1510 maximumFixedSpan = 0; 1511 } 1512 table[i] = maximumFixedSpan; 1513 if (maximumFixedSpan < Integer.MAX_VALUE) { 1514 maximumFixedSpan++; 1515 } 1516 } 1517 return table; 1518 } 1519 1520 // Measuring Component Sizes ******************************************** 1521 1522 /** 1523 * An interface that describes how to measure a <code>Component</code>. 1524 * Used to abstract from horizontal and vertical dimensions as well as 1525 * minimum and preferred sizes. 1526 */ 1527 static interface Measure { 1528 1529 /** 1530 * Computes and returns the size of the given <code>Component</code>. 1531 * 1532 * @param component the component to measure 1533 * @return the component's size 1534 */ 1535 int sizeOf(Component component); 1536 } 1537 1538 /** 1539 * An abstract implementation of the <code>Measure</code> interface 1540 * that caches component sizes. 1541 */ 1542 private abstract static class CachingMeasure 1543 implements Measure, Serializable { 1544 1545 /** 1546 * Holds previously requested component sizes. 1547 * Used to minimize size requests to subcomponents. 1548 */ 1549 protected final ComponentSizeCache cache; 1550 1551 private CachingMeasure(ComponentSizeCache cache) { 1552 this.cache = cache; 1553 } 1554 1555 } 1556 1557 /** 1558 * Measures a component by computing its minimum width. 1559 */ 1560 private static final class MinimumWidthMeasure extends CachingMeasure { 1561 private MinimumWidthMeasure(ComponentSizeCache cache) { 1562 super(cache); 1563 } 1564 1565 @Override 1566 public int sizeOf(Component c) { 1567 return cache.getMinimumSize(c).width; 1568 } 1569 } 1570 1571 /** 1572 * Measures a component by computing its minimum height. 1573 */ 1574 private static final class MinimumHeightMeasure extends CachingMeasure { 1575 private MinimumHeightMeasure(ComponentSizeCache cache) { 1576 super(cache); 1577 } 1578 1579 @Override 1580 public int sizeOf(Component c) { 1581 return cache.getMinimumSize(c).height; 1582 } 1583 } 1584 1585 /** 1586 * Measures a component by computing its preferred width. 1587 */ 1588 private static final class PreferredWidthMeasure extends CachingMeasure { 1589 private PreferredWidthMeasure(ComponentSizeCache cache) { 1590 super(cache); 1591 } 1592 1593 @Override 1594 public int sizeOf(Component c) { 1595 return cache.getPreferredSize(c).width; 1596 } 1597 } 1598 1599 /** 1600 * Measures a component by computing its preferred height. 1601 */ 1602 private static final class PreferredHeightMeasure extends CachingMeasure { 1603 private PreferredHeightMeasure(ComponentSizeCache cache) { 1604 super(cache); 1605 } 1606 1607 @Override 1608 public int sizeOf(Component c) { 1609 return cache.getPreferredSize(c).height; 1610 } 1611 } 1612 1613 // Caching Component Sizes ********************************************** 1614 1615 /** 1616 * A cache for component minimum and preferred sizes. 1617 * Used to reduce the requests to determine a component's size. 1618 */ 1619 private static final class ComponentSizeCache implements Serializable { 1620 1621 /** Maps components to their minimum sizes. */ 1622 private final Map minimumSizes; 1623 1624 /** Maps components to their preferred sizes. */ 1625 private final Map preferredSizes; 1626 1627 /** 1628 * Constructs a <code>ComponentSizeCache</code>. 1629 * 1630 * @param initialCapacity the initial cache capacity 1631 */ 1632 private ComponentSizeCache(int initialCapacity) { 1633 minimumSizes = new HashMap(initialCapacity); 1634 preferredSizes = new HashMap(initialCapacity); 1635 } 1636 1637 /** 1638 * Invalidates the cache. Clears all stored size information. 1639 */ 1640 void invalidate() { 1641 minimumSizes.clear(); 1642 preferredSizes.clear(); 1643 } 1644 1645 /** 1646 * Returns the minimum size for the given component. Tries to look up 1647 * the value from the cache; lazily creates the value if it has not 1648 * been requested before. 1649 * 1650 * @param component the component to compute the minimum size 1651 * @return the component's minimum size 1652 */ 1653 Dimension getMinimumSize(Component component) { 1654 Dimension size = (Dimension) minimumSizes.get(component); 1655 if (size == null) { 1656 size = component.getMinimumSize(); 1657 minimumSizes.put(component, size); 1658 } 1659 return size; 1660 } 1661 1662 /** 1663 * Returns the preferred size for the given component. Tries to look 1664 * up the value from the cache; lazily creates the value if it has not 1665 * been requested before. 1666 * 1667 * @param component the component to compute the preferred size 1668 * @return the component's preferred size 1669 */ 1670 Dimension getPreferredSize(Component component) { 1671 Dimension size = (Dimension) preferredSizes.get(component); 1672 if (size == null) { 1673 size = component.getPreferredSize(); 1674 preferredSizes.put(component, size); 1675 } 1676 return size; 1677 } 1678 1679 void removeEntry(Component component) { 1680 minimumSizes.remove(component); 1681 preferredSizes.remove(component); 1682 } 1683 } 1684 1685 // Exposing the Layout Information ************************************** 1686 1687 /** 1688 * Computes and returns the horizontal and vertical grid origins. 1689 * Performs the same layout process as <code>#layoutContainer</code> 1690 * but does not layout the components.<p> 1691 * 1692 * This method has been added only to make it easier to debug 1693 * the form layout. <strong>You must not call this method directly; 1694 * It may be removed in a future release or the visibility 1695 * may be reduced.</strong> 1696 * 1697 * @param parent the <code>Container</code> to inspect 1698 * @return an object that comprises the grid x and y origins 1699 */ 1700 public LayoutInfo getLayoutInfo(Container parent) { 1701 synchronized (parent.getTreeLock()) { 1702 initializeColAndRowComponentLists(); 1703 Dimension size = parent.getSize(); 1704 1705 Insets insets = parent.getInsets(); 1706 int totalWidth = size.width - insets.left - insets.right; 1707 int totalHeight = size.height - insets.top - insets.bottom; 1708 1709 int[] x = computeGridOrigins(parent, totalWidth, insets.left, 1710 colSpecs, colComponents, colGroupIndices, 1711 minimumWidthMeasure, preferredWidthMeasure); 1712 int[] y = computeGridOrigins(parent, totalHeight, insets.top, 1713 rowSpecs, rowComponents, rowGroupIndices, 1714 minimumHeightMeasure, preferredHeightMeasure); 1715 return new LayoutInfo(x, y); 1716 } 1717 } 1718 1719 /** 1720 * Stores column and row origins. 1721 */ 1722 public static final class LayoutInfo { 1723 1724 /** 1725 * Holds the origins of the columns. 1726 */ 1727 public final int[] columnOrigins; 1728 1729 /** 1730 * Holds the origins of the rows. 1731 */ 1732 public final int[] rowOrigins; 1733 1734 private LayoutInfo(int[] xOrigins, int[] yOrigins) { 1735 this.columnOrigins = xOrigins; 1736 this.rowOrigins = yOrigins; 1737 } 1738 1739 /** 1740 * Returns the layout's horizontal origin, the origin of the first column. 1741 * 1742 * @return the layout's horizontal origin, the origin of the first column. 1743 */ 1744 public int getX() { 1745 return columnOrigins[0]; 1746 } 1747 1748 /** 1749 * Returns the layout's vertical origin, the origin of the first row. 1750 * 1751 * @return the layout's vertical origin, the origin of the first row. 1752 */ 1753 public int getY() { 1754 return rowOrigins[0]; 1755 } 1756 1757 /** 1758 * Returns the layout's width, the size between the first and the last 1759 * column origin. 1760 * 1761 * @return the layout's width. 1762 */ 1763 public int getWidth() { 1764 return columnOrigins[columnOrigins.length - 1] - columnOrigins[0]; 1765 } 1766 1767 /** 1768 * Returns the layout's height, the size between the first and last row. 1769 * 1770 * @return the layout's height. 1771 */ 1772 public int getHeight() { 1773 return rowOrigins[rowOrigins.length - 1] - rowOrigins[0]; 1774 } 1775 1776 } 1777 1778 // Helper Code ********************************************************** 1779 1780 /** 1781 * Creates and returns a deep copy of the given array. 1782 * Unlike <code>#clone</code> that performs a shallow copy, 1783 * this method copies both array levels. 1784 * 1785 * @param array the array to clone 1786 * @return a deep copy of the given array 1787 * 1788 * @see Object#clone() 1789 */ 1790 private int[][] deepClone(int[][] array) { 1791 int[][] result = new int[array.length][]; 1792 for (int i = 0; i < result.length; i++) { 1793 result[i] = array[i].clone(); 1794 } 1795 return result; 1796 } 1797 1798 // Serialization ******************************************************** 1799 1800 /** 1801 * In addition to the default serialization mechanism this class 1802 * invalidates the component size cache. The cache will be populated 1803 * again after the deserialization. 1804 * Also, the fields <code>colComponents</code> and 1805 * <code>rowComponents</code> have been marked as transient 1806 * to exclude them from the serialization. 1807 */ 1808 private void writeObject(ObjectOutputStream out) throws IOException { 1809 invalidateCaches(); 1810 out.defaultWriteObject(); 1811 } 1812 1813 // Debug Helper Code **************************************************** 1814 1815 /* 1816 // Prints the given column widths and row heights. 1817 private void printSizes(String title, int[] colWidths, int[] rowHeights) { 1818 System.out.println(); 1819 System.out.println(title); 1820 int totalWidth = 0; 1821 System.out.print("Column widths: "); 1822 for (int i=0; i < getColumnCount(); i++) { 1823 int width = colWidths[i]; 1824 totalWidth += width; 1825 System.out.print(width + ", "); 1826 } 1827 System.out.println(" Total=" + totalWidth); 1828 1829 int totalHeight = 0; 1830 System.out.print("Row heights: "); 1831 for (int i=0; i < getRowCount(); i++) { 1832 int height = rowHeights[i]; 1833 totalHeight += height; 1834 System.out.print(height + ", "); 1835 } 1836 System.out.println(" Total=" + totalHeight); 1837 System.out.println(); 1838 } 1839 1840 */ 1841 1842}