| 1 30 31 package com.jgoodies.forms.layout; 32 33 import java.awt.Component ; 34 import java.awt.Container ; 35 import java.awt.Dimension ; 36 import java.awt.Insets ; 37 import java.awt.LayoutManager2 ; 38 import java.awt.Rectangle ; 39 import java.util.*; 40 41 42 143 144 public final class FormLayout implements LayoutManager2 { 145 146 150 private final List colSpecs; 151 152 156 private final List rowSpecs; 157 158 162 private int[][] colGroupIndices; 163 164 168 private int[][] rowGroupIndices; 169 170 174 private final Map constraintMap; 175 176 177 179 183 private List[] colComponents; 184 185 189 private List[] rowComponents; 190 191 195 private final ComponentSizeCache componentSizeCache; 196 197 203 private final Measure minimumWidthMeasure; 204 private final Measure minimumHeightMeasure; 205 private final Measure preferredWidthMeasure; 206 private final Measure preferredHeightMeasure; 207 208 209 211 219 public FormLayout(ColumnSpec[] colSpecs, RowSpec[] rowSpecs) { 220 if (colSpecs == null) 221 throw new NullPointerException ("Column specifications must not be null."); 222 if (rowSpecs == null) 223 throw new NullPointerException ("Row specifications must not be null."); 224 225 this.colSpecs = new ArrayList(Arrays.asList(colSpecs)); 226 this.rowSpecs = new ArrayList(Arrays.asList(rowSpecs)); 227 colGroupIndices = new int[][]{}; 228 rowGroupIndices = new int[][]{}; 229 int initialCapacity = colSpecs.length * rowSpecs.length / 4; 230 constraintMap = new HashMap(initialCapacity); 231 componentSizeCache = new ComponentSizeCache(initialCapacity); 232 minimumWidthMeasure = new MinimumWidthMeasure(componentSizeCache); 233 minimumHeightMeasure = new MinimumHeightMeasure(componentSizeCache); 234 preferredWidthMeasure = new PreferredWidthMeasure(componentSizeCache); 235 preferredHeightMeasure = new PreferredHeightMeasure(componentSizeCache); 236 } 237 238 239 249 public FormLayout(String encodedColumnSpecs, String encodedRowSpecs) { 250 this(decodeColSpecs(encodedColumnSpecs), 251 decodeRowSpecs(encodedRowSpecs)); 252 } 253 254 255 269 public FormLayout(String encodedColumnSpecs) { 270 this(encodedColumnSpecs, ""); 271 } 272 273 274 276 281 public int getColumnCount() { 282 return colSpecs.size(); 283 } 284 285 290 public int getRowCount() { 291 return rowSpecs.size(); 292 } 293 294 301 public ColumnSpec getColumnSpec(int columnIndex) { 302 return (ColumnSpec) colSpecs.get(columnIndex - 1); 303 } 304 305 312 public RowSpec getRowSpec(int rowIndex) { 313 return (RowSpec) rowSpecs.get(rowIndex - 1); 314 } 315 316 322 public void appendColumn(ColumnSpec columnSpec) { 323 if (columnSpec == null) { 324 throw new NullPointerException ("The column spec must not be null."); 325 } 326 colSpecs.add(columnSpec); 327 } 328 329 346 public void insertColumn(int columnIndex, ColumnSpec columnSpec) { 347 if (columnIndex < 1 || columnIndex > getColumnCount()) { 348 throw new IndexOutOfBoundsException ( 349 "The column index " + columnIndex + 350 "must be in the range [1, " + getColumnCount() + "]."); 351 } 352 colSpecs.add(columnIndex - 1, columnSpec); 353 shiftComponentsHorizontally(columnIndex, false); 354 adjustGroupIndices(colGroupIndices, columnIndex, false); 355 } 356 357 358 377 public void removeColumn(int columnIndex) { 378 if (columnIndex < 1 || columnIndex > getColumnCount()) { 379 throw new IndexOutOfBoundsException ( 380 "The column index " + columnIndex + 381 " must be in the range [1, " + getColumnCount() + "]."); 382 } 383 colSpecs.remove(columnIndex - 1); 384 shiftComponentsHorizontally(columnIndex, true); 385 adjustGroupIndices(colGroupIndices, columnIndex, true); 386 } 387 388 394 public void appendRow(RowSpec rowSpec) { 395 if (rowSpec == null) { 396 throw new NullPointerException ("The row spec must not be null."); 397 } 398 rowSpecs.add(rowSpec); 399 } 400 401 418 public void insertRow(int rowIndex, RowSpec rowSpec) { 419 if (rowIndex < 1 || rowIndex > getRowCount()) { 420 throw new IndexOutOfBoundsException ( 421 "The row index " + rowIndex + 422 " must be in the range [1, " + getRowCount() + "]."); 423 } 424 rowSpecs.add(rowIndex - 1, rowSpec); 425 shiftComponentsVertically(rowIndex, false); 426 adjustGroupIndices(rowGroupIndices, rowIndex, false); 427 } 428 429 446 public void removeRow(int rowIndex) { 447 if (rowIndex < 1 || rowIndex > getRowCount()) { 448 throw new IndexOutOfBoundsException ( 449 "The row index " + rowIndex + 450 "must be in the range [1, " + getRowCount() + "]."); 451 } 452 rowSpecs.remove(rowIndex - 1); 453 shiftComponentsVertically(rowIndex, true); 454 adjustGroupIndices(rowGroupIndices, rowIndex, true); 455 } 456 457 458 466 private void shiftComponentsHorizontally(int columnIndex, boolean remove) { 467 final int offset = remove ? -1 : 1; 468 for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext(); ) { 469 Map.Entry entry = (Map.Entry) i.next(); 470 CellConstraints constraints = (CellConstraints) entry.getValue(); 471 int x1 = constraints.gridX; 472 int w = constraints.gridWidth; 473 int x2 = x1 + w - 1; 474 if (x1 == columnIndex && remove) { 475 throw new IllegalStateException ( 476 "The removed column " + columnIndex + 477 " must not contain component origins.\n" + 478 "Illegal component=" + entry.getKey()); 479 } else if (x1 >= columnIndex) { 480 constraints.gridX += offset; 481 } else if (x2 >= columnIndex) { 482 constraints.gridWidth += offset; 483 } 484 } 485 } 486 487 495 private void shiftComponentsVertically(int rowIndex, boolean remove) { 496 final int offset = remove ? -1 : 1; 497 for (Iterator i = constraintMap.entrySet().iterator(); i.hasNext(); ) { 498 Map.Entry entry = (Map.Entry) i.next(); 499 CellConstraints constraints = (CellConstraints) entry.getValue(); 500 int y1 = constraints.gridY; 501 int h = constraints.gridHeight; 502 int y2 = y1 + h - 1; 503 if (y1 == rowIndex && remove) { 504 throw new IllegalStateException ( 505 "The removed row " + rowIndex + 506 " must not contain component origins.\n" + 507 "Illegal component=" + entry.getKey()); 508 } else if (y1 >= rowIndex) { 509 constraints.gridY += offset; 510 } else if (y2 >= rowIndex) { 511 constraints.gridHeight += offset; 512 } 513 } 514 } 515 516 525 private void adjustGroupIndices(int[][] allGroupIndices, 526 int modifiedIndex, boolean remove) { 527 final int offset = remove ? -1 : +1; 528 for (int group = 0; group < allGroupIndices.length; group++) { 529 int[] groupIndices = allGroupIndices[group]; 530 for (int i = 0; i < groupIndices.length; i++) { 531 int index = groupIndices[i]; 532 if (index == modifiedIndex && remove) { 533 throw new IllegalStateException ( 534 "The removed index " + modifiedIndex + " must not be grouped."); 535 } else if (index >= modifiedIndex) { 536 groupIndices[i] += offset; 537 } 538 } 539 } 540 } 541 542 543 545 553 public void setConstraints(Component component, CellConstraints constraints) { 554 if (component == null) 555 throw new NullPointerException ("Component must not be null."); 556 if (constraints == null) 557 throw new NullPointerException ("Constraint must not be null."); 558 559 constraints.ensureValidGridBounds(getColumnCount(), getRowCount()); 560 constraintMap.put(component, constraints.clone()); 561 } 562 563 572 public CellConstraints getConstraints(Component component) { 573 if (component == null) 574 throw new NullPointerException ("Component must not be null."); 575 576 CellConstraints constraints = (CellConstraints) constraintMap.get(component); 577 if (constraints == null) 578 throw new NullPointerException ("Component has not been added to the container."); 579 580 return (CellConstraints) constraints.clone(); 581 } 582 583 588 private void removeConstraints(Component component) { 589 constraintMap.remove(component); 590 componentSizeCache.removeEntry(component); 591 } 592 593 594 596 601 public int[][] getColumnGroups() { 602 return deepClone(colGroupIndices); 603 } 604 605 621 public void setColumnGroups(int[][] colGroupIndices) { 622 int maxColumn = getColumnCount(); 623 boolean[] usedIndices = new boolean[maxColumn + 1]; 624 for (int group = 0; group < colGroupIndices.length; group++) { 625 for (int j = 0; j < colGroupIndices[group].length; j++) { 626 int colIndex = colGroupIndices[group][j]; 627 if (colIndex < 1 || colIndex > maxColumn) { 628 throw new IndexOutOfBoundsException ( 629 "Invalid column group index " + colIndex + 630 " in group " + (group+1)); 631 } 632 if (usedIndices[colIndex]) { 633 throw new IllegalArgumentException ( 634 "Column index " + colIndex + " must not be used in multiple column groups."); 635 } else { 636 usedIndices[colIndex] = true; 637 } 638 } 639 } 640 this.colGroupIndices = deepClone(colGroupIndices); 641 } 642 643 649 public void addGroupedColumn(int columnIndex) { 650 int[][] newColGroups = getColumnGroups(); 651 if (newColGroups.length == 0) { 653 newColGroups = new int[][]{{columnIndex}}; 654 } else { 655 int lastGroupIndex = newColGroups.length-1; 656 int[] lastGroup = newColGroups[lastGroupIndex]; 657 int groupSize = lastGroup.length; 658 int[] newLastGroup = new int[groupSize+1]; 659 System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize); 660 newLastGroup[groupSize] = columnIndex; 661 newColGroups[lastGroupIndex] = newLastGroup; 662 } 663 setColumnGroups(newColGroups); 664 } 665 666 671 public int[][] getRowGroups() { 672 return deepClone(rowGroupIndices); 673 } 674 675 690 public void setRowGroups(int[][] rowGroupIndices) { 691 int rowCount = getRowCount(); 692 boolean[] usedIndices = new boolean[rowCount + 1]; 693 for (int i = 0; i < rowGroupIndices.length; i++) { 694 for (int j = 0; j < rowGroupIndices[i].length; j++) { 695 int rowIndex = rowGroupIndices[i][j]; 696 if (rowIndex < 1 || rowIndex > rowCount) { 697 throw new IndexOutOfBoundsException ( 698 "Invalid row group index " + rowIndex + 699 " in group " + (i+1)); 700 } 701 if (usedIndices[rowIndex]) { 702 throw new IllegalArgumentException ( 703 "Row index " + rowIndex + " must not be used in multiple row groups."); 704 } else { 705 usedIndices[rowIndex] = true; 706 } 707 } 708 } 709 this.rowGroupIndices = deepClone(rowGroupIndices); 710 } 711 712 718 public void addGroupedRow(int rowIndex) { 719 int[][] newRowGroups = getRowGroups(); 720 if (newRowGroups.length == 0) { 722 newRowGroups = new int[][]{{rowIndex}}; 723 } else { 724 int lastGroupIndex = newRowGroups.length-1; 725 int[] lastGroup = newRowGroups[lastGroupIndex]; 726 int groupSize = lastGroup.length; 727 int[] newLastGroup = new int[groupSize+1]; 728 System.arraycopy(lastGroup, 0, newLastGroup, 0, groupSize); 729 newLastGroup[groupSize] = rowIndex; 730 newRowGroups[lastGroupIndex] = newLastGroup; 731 } 732 setRowGroups(newRowGroups); 733 } 734 735 736 738 746 public void addLayoutComponent(String name, Component component) { 747 throw new UnsupportedOperationException ( 748 "Use #addLayoutComponent(Component, Object) instead."); 749 } 750 751 763 public void addLayoutComponent(Component comp, Object constraints) { 764 if (constraints instanceof String ) { 765 setConstraints(comp, new CellConstraints((String ) constraints)); 766 } else if (constraints instanceof CellConstraints) { 767 setConstraints(comp, (CellConstraints) constraints); 768 } else if (constraints == null) { 769 throw new NullPointerException ("Constraints must not be null."); 770 } else { 771 throw new IllegalArgumentException ("Illegal constraint type " + constraints.getClass()); 772 } 773 } 774 775 784 public void removeLayoutComponent(Component comp) { 785 removeConstraints(comp); 786 } 787 788 789 791 800 public Dimension minimumLayoutSize(Container parent) { 801 return computeLayoutSize(parent, 802 minimumWidthMeasure, 803 minimumHeightMeasure); 804 } 805 806 816 public Dimension preferredLayoutSize(Container parent) { 817 return computeLayoutSize(parent, 818 preferredWidthMeasure, 819 preferredHeightMeasure); 820 } 821 822 832 public Dimension maximumLayoutSize(Container target) { 833 return new Dimension (Integer.MAX_VALUE, Integer.MAX_VALUE); 834 } 835 836 845 public float getLayoutAlignmentX(Container parent) { 846 return 0.5f; 847 } 848 849 858 public float getLayoutAlignmentY(Container parent) { 859 return 0.5f; 860 } 861 862 863 867 public void invalidateLayout(Container target) { 868 invalidateCaches(); 869 } 870 871 872 894 public void layoutContainer(Container parent) { 895 synchronized (parent.getTreeLock()) { 896 initializeColAndRowComponentLists(); 897 Dimension size = parent.getSize(); 898 899 Insets insets = parent.getInsets(); 900 int totalWidth
|