1 /* 2 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing; 26 27 import java.awt.Component; 28 import java.awt.Container; 29 import java.awt.Dimension; 30 import java.awt.Insets; 31 import java.awt.LayoutManager2; 32 import java.util.*; 33 import static java.awt.Component.BaselineResizeBehavior; 34 import static javax.swing.LayoutStyle.ComponentPlacement; 35 import static javax.swing.SwingConstants.HORIZONTAL; 36 import static javax.swing.SwingConstants.VERTICAL; 37 38 /** 39 * {@code GroupLayout} is a {@code LayoutManager} that hierarchically 40 * groups components in order to position them in a {@code Container}. 41 * {@code GroupLayout} is intended for use by builders, but may be 42 * hand-coded as well. 43 * Grouping is done by instances of the {@link Group Group} class. {@code 44 * GroupLayout} supports two types of groups. A sequential group 45 * positions its child elements sequentially, one after another. A 46 * parallel group aligns its child elements in one of four ways. 47 * <p> 48 * Each group may contain any number of elements, where an element is 49 * a {@code Group}, {@code Component}, or gap. A gap can be thought 50 * of as an invisible component with a minimum, preferred and maximum 51 * size. In addition {@code GroupLayout} supports a preferred gap, 52 * whose value comes from {@code LayoutStyle}. 53 * <p> 54 * Elements are similar to a spring. Each element has a range as 55 * specified by a minimum, preferred and maximum. Gaps have either a 56 * developer-specified range, or a range determined by {@code 57 * LayoutStyle}. The range for {@code Component}s is determined from 58 * the {@code Component}'s {@code getMinimumSize}, {@code 59 * getPreferredSize} and {@code getMaximumSize} methods. In addition, 60 * when adding {@code Component}s you may specify a particular range 61 * to use instead of that from the component. The range for a {@code 62 * Group} is determined by the type of group. A {@code ParallelGroup}'s 63 * range is the maximum of the ranges of its elements. A {@code 64 * SequentialGroup}'s range is the sum of the ranges of its elements. 65 * <p> 66 * {@code GroupLayout} treats each axis independently. That is, there 67 * is a group representing the horizontal axis, and a group 68 * representing the vertical axis. The horizontal group is 69 * responsible for determining the minimum, preferred and maximum size 70 * along the horizontal axis as well as setting the x and width of the 71 * components contained in it. The vertical group is responsible for 72 * determining the minimum, preferred and maximum size along the 73 * vertical axis as well as setting the y and height of the 74 * components contained in it. Each {@code Component} must exist in both 75 * a horizontal and vertical group, otherwise an {@code IllegalStateException} 76 * is thrown during layout, or when the minimum, preferred or 77 * maximum size is requested. 78 * <p> 79 * The following diagram shows a sequential group along the horizontal 80 * axis. The sequential group contains three components. A parallel group 81 * was used along the vertical axis. 82 * <p style="text-align:center"> 83 * <img src="doc-files/groupLayout.1.gif" alt="Sequential group along the horizontal axis in three components"> 84 * <p> 85 * To reinforce that each axis is treated independently the diagram shows 86 * the range of each group and element along each axis. The 87 * range of each component has been projected onto the axes, 88 * and the groups are rendered in blue (horizontal) and red (vertical). 89 * For readability there is a gap between each of the elements in the 90 * sequential group. 91 * <p> 92 * The sequential group along the horizontal axis is rendered as a solid 93 * blue line. Notice the sequential group is the sum of the children elements 94 * it contains. 95 * <p> 96 * Along the vertical axis the parallel group is the maximum of the height 97 * of each of the components. As all three components have the same height, 98 * the parallel group has the same height. 99 * <p> 100 * The following diagram shows the same three components, but with the 101 * parallel group along the horizontal axis and the sequential group along 102 * the vertical axis. 103 * 104 * <p style="text-align:center"> 105 * <img src="doc-files/groupLayout.2.gif" alt="Sequential group along the vertical axis in three components"> 106 * <p> 107 * As {@code c1} is the largest of the three components, the parallel 108 * group is sized to {@code c1}. As {@code c2} and {@code c3} are smaller 109 * than {@code c1} they are aligned based on the alignment specified 110 * for the component (if specified) or the default alignment of the 111 * parallel group. In the diagram {@code c2} and {@code c3} were created 112 * with an alignment of {@code LEADING}. If the component orientation were 113 * right-to-left then {@code c2} and {@code c3} would be positioned on 114 * the opposite side. 115 * <p> 116 * The following diagram shows a sequential group along both the horizontal 117 * and vertical axis. 118 * <p style="text-align:center"> 119 * <img src="doc-files/groupLayout.3.gif" alt="Sequential group along both the horizontal and vertical axis in three components"> 120 * <p> 121 * {@code GroupLayout} provides the ability to insert gaps between 122 * {@code Component}s. The size of the gap is determined by an 123 * instance of {@code LayoutStyle}. This may be turned on using the 124 * {@code setAutoCreateGaps} method. Similarly, you may use 125 * the {@code setAutoCreateContainerGaps} method to insert gaps 126 * between components that touch the edge of the parent container and the 127 * container. 128 * <p> 129 * The following builds a panel consisting of two labels in 130 * one column, followed by two textfields in the next column: 131 * <pre> 132 * JComponent panel = ...; 133 * GroupLayout layout = new GroupLayout(panel); 134 * panel.setLayout(layout); 135 * 136 * // Turn on automatically adding gaps between components 137 * layout.setAutoCreateGaps(true); 138 * 139 * // Turn on automatically creating gaps between components that touch 140 * // the edge of the container and the container. 141 * layout.setAutoCreateContainerGaps(true); 142 * 143 * // Create a sequential group for the horizontal axis. 144 * 145 * GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup(); 146 * 147 * // The sequential group in turn contains two parallel groups. 148 * // One parallel group contains the labels, the other the text fields. 149 * // Putting the labels in a parallel group along the horizontal axis 150 * // positions them at the same x location. 151 * // 152 * // Variable indentation is used to reinforce the level of grouping. 153 * hGroup.addGroup(layout.createParallelGroup(). 154 * addComponent(label1).addComponent(label2)); 155 * hGroup.addGroup(layout.createParallelGroup(). 156 * addComponent(tf1).addComponent(tf2)); 157 * layout.setHorizontalGroup(hGroup); 158 * 159 * // Create a sequential group for the vertical axis. 160 * GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup(); 161 * 162 * // The sequential group contains two parallel groups that align 163 * // the contents along the baseline. The first parallel group contains 164 * // the first label and text field, and the second parallel group contains 165 * // the second label and text field. By using a sequential group 166 * // the labels and text fields are positioned vertically after one another. 167 * vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE). 168 * addComponent(label1).addComponent(tf1)); 169 * vGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE). 170 * addComponent(label2).addComponent(tf2)); 171 * layout.setVerticalGroup(vGroup); 172 * </pre> 173 * <p> 174 * When run the following is produced. 175 * <p style="text-align:center"> 176 * <img src="doc-files/groupLayout.example.png" alt="Produced horizontal/vertical form"> 177 * <p> 178 * This layout consists of the following. 179 * <ul><li>The horizontal axis consists of a sequential group containing two 180 * parallel groups. The first parallel group contains the labels, 181 * and the second parallel group contains the text fields. 182 * <li>The vertical axis consists of a sequential group 183 * containing two parallel groups. The parallel groups are configured 184 * to align their components along the baseline. The first parallel 185 * group contains the first label and first text field, and 186 * the second group consists of the second label and second 187 * text field. 188 * </ul> 189 * There are a couple of things to notice in this code: 190 * <ul> 191 * <li>You need not explicitly add the components to the container; this 192 * is indirectly done by using one of the {@code add} methods of 193 * {@code Group}. 194 * <li>The various {@code add} methods return 195 * the caller. This allows for easy chaining of invocations. For 196 * example, {@code group.addComponent(label1).addComponent(label2);} is 197 * equivalent to 198 * {@code group.addComponent(label1); group.addComponent(label2);}. 199 * <li>There are no public constructors for {@code Group}s; instead 200 * use the create methods of {@code GroupLayout}. 201 * </ul> 202 * 203 * @author Tomas Pavek 204 * @author Jan Stola 205 * @author Scott Violet 206 * @since 1.6 207 */ 208 public class GroupLayout implements LayoutManager2 { 209 // Used in size calculations 210 private static final int MIN_SIZE = 0; 211 212 private static final int PREF_SIZE = 1; 213 214 private static final int MAX_SIZE = 2; 215 216 // Used by prepare, indicates min, pref or max isn't going to be used. 217 private static final int SPECIFIC_SIZE = 3; 218 219 private static final int UNSET = Integer.MIN_VALUE; 220 221 /** 222 * Indicates the size from the component or gap should be used for a 223 * particular range value. 224 * 225 * @see Group 226 */ 227 public static final int DEFAULT_SIZE = -1; 228 229 /** 230 * Indicates the preferred size from the component or gap should 231 * be used for a particular range value. 232 * 233 * @see Group 234 */ 235 public static final int PREFERRED_SIZE = -2; 236 237 // Whether or not we automatically try and create the preferred 238 // padding between components. 239 private boolean autocreatePadding; 240 241 // Whether or not we automatically try and create the preferred 242 // padding between components the touch the edge of the container and 243 // the container. 244 private boolean autocreateContainerPadding; 245 246 /** 247 * Group responsible for layout along the horizontal axis. This is NOT 248 * the user specified group, use getHorizontalGroup to dig that out. 249 */ 250 private Group horizontalGroup; 251 252 /** 253 * Group responsible for layout along the vertical axis. This is NOT 254 * the user specified group, use getVerticalGroup to dig that out. 255 */ 256 private Group verticalGroup; 257 258 // Maps from Component to ComponentInfo. This is used for tracking 259 // information specific to a Component. 260 private Map<Component,ComponentInfo> componentInfos; 261 262 // Container we're doing layout for. 263 private Container host; 264 265 // Used by areParallelSiblings, cached to avoid excessive garbage. 266 private Set<Spring> tmpParallelSet; 267 268 // Indicates Springs have changed in some way since last change. 269 private boolean springsChanged; 270 271 // Indicates invalidateLayout has been invoked. 272 private boolean isValid; 273 274 // Whether or not any preferred padding (or container padding) springs 275 // exist 276 private boolean hasPreferredPaddingSprings; 277 278 /** 279 * The LayoutStyle instance to use, if null the sharedInstance is used. 280 */ 281 private LayoutStyle layoutStyle; 282 283 /** 284 * If true, components that are not visible are treated as though they 285 * aren't there. 286 */ 287 private boolean honorsVisibility; 288 289 290 /** 291 * Enumeration of the possible ways {@code ParallelGroup} can align 292 * its children. 293 * 294 * @see #createParallelGroup(Alignment) 295 * @since 1.6 296 */ 297 public enum Alignment { 298 /** 299 * Indicates the elements should be 300 * aligned to the origin. For the horizontal axis with a left to 301 * right orientation this means aligned to the left edge. For the 302 * vertical axis leading means aligned to the top edge. 303 * 304 * @see #createParallelGroup(Alignment) 305 */ 306 LEADING, 307 308 /** 309 * Indicates the elements should be aligned to the end of the 310 * region. For the horizontal axis with a left to right 311 * orientation this means aligned to the right edge. For the 312 * vertical axis trailing means aligned to the bottom edge. 313 * 314 * @see #createParallelGroup(Alignment) 315 */ 316 TRAILING, 317 318 /** 319 * Indicates the elements should be centered in 320 * the region. 321 * 322 * @see #createParallelGroup(Alignment) 323 */ 324 CENTER, 325 326 /** 327 * Indicates the elements should be aligned along 328 * their baseline. 329 * 330 * @see #createParallelGroup(Alignment) 331 * @see #createBaselineGroup(boolean,boolean) 332 */ 333 BASELINE 334 } 335 336 337 private static void checkSize(int min, int pref, int max, 338 boolean isComponentSpring) { 339 checkResizeType(min, isComponentSpring); 340 if (!isComponentSpring && pref < 0) { 341 throw new IllegalArgumentException("Pref must be >= 0"); 342 } else if (isComponentSpring) { 343 checkResizeType(pref, true); 344 } 345 checkResizeType(max, isComponentSpring); 346 checkLessThan(min, pref); 347 checkLessThan(pref, max); 348 } 349 350 private static void checkResizeType(int type, boolean isComponentSpring) { 351 if (type < 0 && ((isComponentSpring && type != DEFAULT_SIZE && 352 type != PREFERRED_SIZE) || 353 (!isComponentSpring && type != PREFERRED_SIZE))) { 354 throw new IllegalArgumentException("Invalid size"); 355 } 356 } 357 358 private static void checkLessThan(int min, int max) { 359 if (min >= 0 && max >= 0 && min > max) { 360 throw new IllegalArgumentException( 361 "Following is not met: min<=pref<=max"); 362 } 363 } 364 365 /** 366 * Creates a {@code GroupLayout} for the specified {@code Container}. 367 * 368 * @param host the {@code Container} the {@code GroupLayout} is 369 * the {@code LayoutManager} for 370 * @throws IllegalArgumentException if host is {@code null} 371 */ 372 public GroupLayout(Container host) { 373 if (host == null) { 374 throw new IllegalArgumentException("Container must be non-null"); 375 } 376 honorsVisibility = true; 377 this.host = host; 378 setHorizontalGroup(createParallelGroup(Alignment.LEADING, true)); 379 setVerticalGroup(createParallelGroup(Alignment.LEADING, true)); 380 componentInfos = new HashMap<Component,ComponentInfo>(); 381 tmpParallelSet = new HashSet<Spring>(); 382 } 383 384 /** 385 * Sets whether component visibility is considered when sizing and 386 * positioning components. A value of {@code true} indicates that 387 * non-visible components should not be treated as part of the 388 * layout. A value of {@code false} indicates that components should be 389 * positioned and sized regardless of visibility. 390 * <p> 391 * A value of {@code false} is useful when the visibility of components 392 * is dynamically adjusted and you don't want surrounding components and 393 * the sizing to change. 394 * <p> 395 * The specified value is used for components that do not have an 396 * explicit visibility specified. 397 * <p> 398 * The default is {@code true}. 399 * 400 * @param honorsVisibility whether component visibility is considered when 401 * sizing and positioning components 402 * @see #setHonorsVisibility(Component,Boolean) 403 */ 404 public void setHonorsVisibility(boolean honorsVisibility) { 405 if (this.honorsVisibility != honorsVisibility) { 406 this.honorsVisibility = honorsVisibility; 407 springsChanged = true; 408 isValid = false; 409 invalidateHost(); 410 } 411 } 412 413 /** 414 * Returns whether component visibility is considered when sizing and 415 * positioning components. 416 * 417 * @return whether component visibility is considered when sizing and 418 * positioning components 419 */ 420 public boolean getHonorsVisibility() { 421 return honorsVisibility; 422 } 423 424 /** 425 * Sets whether the component's visibility is considered for 426 * sizing and positioning. A value of {@code Boolean.TRUE} 427 * indicates that if {@code component} is not visible it should 428 * not be treated as part of the layout. A value of {@code false} 429 * indicates that {@code component} is positioned and sized 430 * regardless of it's visibility. A value of {@code null} 431 * indicates the value specified by the single argument method {@code 432 * setHonorsVisibility} should be used. 433 * <p> 434 * If {@code component} is not a child of the {@code Container} this 435 * {@code GroupLayout} is managing, it will be added to the 436 * {@code Container}. 437 * 438 * @param component the component 439 * @param honorsVisibility whether visibility of this {@code component} should be 440 * considered for sizing and positioning 441 * @throws IllegalArgumentException if {@code component} is {@code null} 442 * @see #setHonorsVisibility(Component,Boolean) 443 */ 444 public void setHonorsVisibility(Component component, 445 Boolean honorsVisibility) { 446 if (component == null) { 447 throw new IllegalArgumentException("Component must be non-null"); 448 } 449 getComponentInfo(component).setHonorsVisibility(honorsVisibility); 450 springsChanged = true; 451 isValid = false; 452 invalidateHost(); 453 } 454 455 /** 456 * Sets whether a gap between components should automatically be 457 * created. For example, if this is {@code true} and you add two 458 * components to a {@code SequentialGroup} a gap between the 459 * two components is automatically be created. The default is 460 * {@code false}. 461 * 462 * @param autoCreatePadding whether a gap between components is 463 * automatically created 464 */ 465 public void setAutoCreateGaps(boolean autoCreatePadding) { 466 if (this.autocreatePadding != autoCreatePadding) { 467 this.autocreatePadding = autoCreatePadding; 468 invalidateHost(); 469 } 470 } 471 472 /** 473 * Returns {@code true} if gaps between components are automatically 474 * created. 475 * 476 * @return {@code true} if gaps between components are automatically 477 * created 478 */ 479 public boolean getAutoCreateGaps() { 480 return autocreatePadding; 481 } 482 483 /** 484 * Sets whether a gap between the container and components that 485 * touch the border of the container should automatically be 486 * created. The default is {@code false}. 487 * 488 * @param autoCreateContainerPadding whether a gap between the container and 489 * components that touch the border of the container should 490 * automatically be created 491 */ 492 public void setAutoCreateContainerGaps(boolean autoCreateContainerPadding){ 493 if (this.autocreateContainerPadding != autoCreateContainerPadding) { 494 this.autocreateContainerPadding = autoCreateContainerPadding; 495 horizontalGroup = createTopLevelGroup(getHorizontalGroup()); 496 verticalGroup = createTopLevelGroup(getVerticalGroup()); 497 invalidateHost(); 498 } 499 } 500 501 /** 502 * Returns {@code true} if gaps between the container and components that 503 * border the container are automatically created. 504 * 505 * @return {@code true} if gaps between the container and components that 506 * border the container are automatically created 507 */ 508 public boolean getAutoCreateContainerGaps() { 509 return autocreateContainerPadding; 510 } 511 512 /** 513 * Sets the {@code Group} that positions and sizes 514 * components along the horizontal axis. 515 * 516 * @param group the {@code Group} that positions and sizes 517 * components along the horizontal axis 518 * @throws IllegalArgumentException if group is {@code null} 519 */ 520 public void setHorizontalGroup(Group group) { 521 if (group == null) { 522 throw new IllegalArgumentException("Group must be non-null"); 523 } 524 horizontalGroup = createTopLevelGroup(group); 525 invalidateHost(); 526 } 527 528 /** 529 * Returns the {@code Group} that positions and sizes components 530 * along the horizontal axis. 531 * 532 * @return the {@code Group} responsible for positioning and 533 * sizing component along the horizontal axis 534 */ 535 private Group getHorizontalGroup() { 536 int index = 0; 537 if (horizontalGroup.springs.size() > 1) { 538 index = 1; 539 } 540 return (Group)horizontalGroup.springs.get(index); 541 } 542 543 /** 544 * Sets the {@code Group} that positions and sizes 545 * components along the vertical axis. 546 * 547 * @param group the {@code Group} that positions and sizes 548 * components along the vertical axis 549 * @throws IllegalArgumentException if group is {@code null} 550 */ 551 public void setVerticalGroup(Group group) { 552 if (group == null) { 553 throw new IllegalArgumentException("Group must be non-null"); 554 } 555 verticalGroup = createTopLevelGroup(group); 556 invalidateHost(); 557 } 558 559 /** 560 * Returns the {@code Group} that positions and sizes components 561 * along the vertical axis. 562 * 563 * @return the {@code Group} responsible for positioning and 564 * sizing component along the vertical axis 565 */ 566 private Group getVerticalGroup() { 567 int index = 0; 568 if (verticalGroup.springs.size() > 1) { 569 index = 1; 570 } 571 return (Group)verticalGroup.springs.get(index); 572 } 573 574 /** 575 * Wraps the user specified group in a sequential group. If 576 * container gaps should be generated the necessary springs are 577 * added. 578 */ 579 private Group createTopLevelGroup(Group specifiedGroup) { 580 SequentialGroup group = createSequentialGroup(); 581 if (getAutoCreateContainerGaps()) { 582 group.addSpring(new ContainerAutoPreferredGapSpring()); 583 group.addGroup(specifiedGroup); 584 group.addSpring(new ContainerAutoPreferredGapSpring()); 585 } else { 586 group.addGroup(specifiedGroup); 587 } 588 return group; 589 } 590 591 /** 592 * Creates and returns a {@code SequentialGroup}. 593 * 594 * @return a new {@code SequentialGroup} 595 */ 596 public SequentialGroup createSequentialGroup() { 597 return new SequentialGroup(); 598 } 599 600 /** 601 * Creates and returns a {@code ParallelGroup} with an alignment of 602 * {@code Alignment.LEADING}. This is a cover method for the more 603 * general {@code createParallelGroup(Alignment)} method. 604 * 605 * @return a new {@code ParallelGroup} 606 * @see #createParallelGroup(Alignment) 607 */ 608 public ParallelGroup createParallelGroup() { 609 return createParallelGroup(Alignment.LEADING); 610 } 611 612 /** 613 * Creates and returns a {@code ParallelGroup} with the specified 614 * alignment. This is a cover method for the more general {@code 615 * createParallelGroup(Alignment,boolean)} method with {@code true} 616 * supplied for the second argument. 617 * 618 * @param alignment the alignment for the elements of the group 619 * @throws IllegalArgumentException if {@code alignment} is {@code null} 620 * @return a new {@code ParallelGroup} 621 * @see #createBaselineGroup 622 * @see ParallelGroup 623 */ 624 public ParallelGroup createParallelGroup(Alignment alignment) { 625 return createParallelGroup(alignment, true); 626 } 627 628 /** 629 * Creates and returns a {@code ParallelGroup} with the specified 630 * alignment and resize behavior. The {@code 631 * alignment} argument specifies how children elements are 632 * positioned that do not fill the group. For example, if a {@code 633 * ParallelGroup} with an alignment of {@code TRAILING} is given 634 * 100 and a child only needs 50, the child is 635 * positioned at the position 50 (with a component orientation of 636 * left-to-right). 637 * <p> 638 * Baseline alignment is only useful when used along the vertical 639 * axis. A {@code ParallelGroup} created with a baseline alignment 640 * along the horizontal axis is treated as {@code LEADING}. 641 * <p> 642 * Refer to {@link GroupLayout.ParallelGroup ParallelGroup} for details on 643 * the behavior of baseline groups. 644 * 645 * @param alignment the alignment for the elements of the group 646 * @param resizable {@code true} if the group is resizable; if the group 647 * is not resizable the preferred size is used for the 648 * minimum and maximum size of the group 649 * @throws IllegalArgumentException if {@code alignment} is {@code null} 650 * @return a new {@code ParallelGroup} 651 * @see #createBaselineGroup 652 * @see GroupLayout.ParallelGroup 653 */ 654 public ParallelGroup createParallelGroup(Alignment alignment, 655 boolean resizable){ 656 if (alignment == null) { 657 throw new IllegalArgumentException("alignment must be non null"); 658 } 659 660 if (alignment == Alignment.BASELINE) { 661 return new BaselineGroup(resizable); 662 } 663 return new ParallelGroup(alignment, resizable); 664 } 665 666 /** 667 * Creates and returns a {@code ParallelGroup} that aligns it's 668 * elements along the baseline. 669 * 670 * @param resizable whether the group is resizable 671 * @param anchorBaselineToTop whether the baseline is anchored to 672 * the top or bottom of the group 673 * @return the {@code ParallelGroup} 674 * @see #createBaselineGroup 675 * @see ParallelGroup 676 */ 677 public ParallelGroup createBaselineGroup(boolean resizable, 678 boolean anchorBaselineToTop) { 679 return new BaselineGroup(resizable, anchorBaselineToTop); 680 } 681 682 /** 683 * Forces the specified components to have the same size 684 * regardless of their preferred, minimum or maximum sizes. Components that 685 * are linked are given the maximum of the preferred size of each of 686 * the linked components. For example, if you link two components with 687 * a preferred width of 10 and 20, both components are given a width of 20. 688 * <p> 689 * This can be used multiple times to force any number of 690 * components to share the same size. 691 * <p> 692 * Linked Components are not be resizable. 693 * 694 * @param components the {@code Component}s that are to have the same size 695 * @throws IllegalArgumentException if {@code components} is 696 * {@code null}, or contains {@code null} 697 * @see #linkSize(int,Component[]) 698 */ 699 public void linkSize(Component... components) { 700 linkSize(SwingConstants.HORIZONTAL, components); 701 linkSize(SwingConstants.VERTICAL, components); 702 } 703 704 /** 705 * Forces the specified components to have the same size along the 706 * specified axis regardless of their preferred, minimum or 707 * maximum sizes. Components that are linked are given the maximum 708 * of the preferred size of each of the linked components. For 709 * example, if you link two components along the horizontal axis 710 * and the preferred width is 10 and 20, both components are given 711 * a width of 20. 712 * <p> 713 * This can be used multiple times to force any number of 714 * components to share the same size. 715 * <p> 716 * Linked {@code Component}s are not be resizable. 717 * 718 * @param components the {@code Component}s that are to have the same size 719 * @param axis the axis to link the size along; one of 720 * {@code SwingConstants.HORIZONTAL} or 721 * {@code SwingConstans.VERTICAL} 722 * @throws IllegalArgumentException if {@code components} is 723 * {@code null}, or contains {@code null}; or {@code axis} 724 * is not {@code SwingConstants.HORIZONTAL} or 725 * {@code SwingConstants.VERTICAL} 726 */ 727 public void linkSize(int axis, Component... components) { 728 if (components == null) { 729 throw new IllegalArgumentException("Components must be non-null"); 730 } 731 for (int counter = components.length - 1; counter >= 0; counter--) { 732 Component c = components[counter]; 733 if (components[counter] == null) { 734 throw new IllegalArgumentException( 735 "Components must be non-null"); 736 } 737 // Force the component to be added 738 getComponentInfo(c); 739 } 740 int glAxis; 741 if (axis == SwingConstants.HORIZONTAL) { 742 glAxis = HORIZONTAL; 743 } else if (axis == SwingConstants.VERTICAL) { 744 glAxis = VERTICAL; 745 } else { 746 throw new IllegalArgumentException("Axis must be one of " + 747 "SwingConstants.HORIZONTAL or SwingConstants.VERTICAL"); 748 } 749 LinkInfo master = getComponentInfo( 750 components[components.length - 1]).getLinkInfo(glAxis); 751 for (int counter = components.length - 2; counter >= 0; counter--) { 752 master.add(getComponentInfo(components[counter])); 753 } 754 invalidateHost(); 755 } 756 757 /** 758 * Replaces an existing component with a new one. 759 * 760 * @param existingComponent the component that should be removed 761 * and replaced with {@code newComponent} 762 * @param newComponent the component to put in 763 * {@code existingComponent}'s place 764 * @throws IllegalArgumentException if either of the components are 765 * {@code null} or {@code existingComponent} is not being managed 766 * by this layout manager 767 */ 768 public void replace(Component existingComponent, Component newComponent) { 769 if (existingComponent == null || newComponent == null) { 770 throw new IllegalArgumentException("Components must be non-null"); 771 } 772 // Make sure all the components have been registered, otherwise we may 773 // not update the correct Springs. 774 if (springsChanged) { 775 registerComponents(horizontalGroup, HORIZONTAL); 776 registerComponents(verticalGroup, VERTICAL); 777 } 778 ComponentInfo info = componentInfos.remove(existingComponent); 779 if (info == null) { 780 throw new IllegalArgumentException("Component must already exist"); 781 } 782 host.remove(existingComponent); 783 if (newComponent.getParent() != host) { 784 host.add(newComponent); 785 } 786 info.setComponent(newComponent); 787 componentInfos.put(newComponent, info); 788 invalidateHost(); 789 } 790 791 /** 792 * Sets the {@code LayoutStyle} used to calculate the preferred 793 * gaps between components. A value of {@code null} indicates the 794 * shared instance of {@code LayoutStyle} should be used. 795 * 796 * @param layoutStyle the {@code LayoutStyle} to use 797 * @see LayoutStyle 798 */ 799 public void setLayoutStyle(LayoutStyle layoutStyle) { 800 this.layoutStyle = layoutStyle; 801 invalidateHost(); 802 } 803 804 /** 805 * Returns the {@code LayoutStyle} used for calculating the preferred 806 * gap between components. This returns the value specified to 807 * {@code setLayoutStyle}, which may be {@code null}. 808 * 809 * @return the {@code LayoutStyle} used for calculating the preferred 810 * gap between components 811 */ 812 public LayoutStyle getLayoutStyle() { 813 return layoutStyle; 814 } 815 816 private LayoutStyle getLayoutStyle0() { 817 LayoutStyle layoutStyle = getLayoutStyle(); 818 if (layoutStyle == null) { 819 layoutStyle = LayoutStyle.getInstance(); 820 } 821 return layoutStyle; 822 } 823 824 private void invalidateHost() { 825 if (host instanceof JComponent) { 826 ((JComponent)host).revalidate(); 827 } else { 828 host.invalidate(); 829 } 830 host.repaint(); 831 } 832 833 // 834 // LayoutManager 835 // 836 /** 837 * Notification that a {@code Component} has been added to 838 * the parent container. You should not invoke this method 839 * directly, instead you should use one of the {@code Group} 840 * methods to add a {@code Component}. 841 * 842 * @param name the string to be associated with the component 843 * @param component the {@code Component} to be added 844 */ 845 public void addLayoutComponent(String name, Component component) { 846 } 847 848 /** 849 * Notification that a {@code Component} has been removed from 850 * the parent container. You should not invoke this method 851 * directly, instead invoke {@code remove} on the parent 852 * {@code Container}. 853 * 854 * @param component the component to be removed 855 * @see java.awt.Component#remove 856 */ 857 public void removeLayoutComponent(Component component) { 858 ComponentInfo info = componentInfos.remove(component); 859 if (info != null) { 860 info.dispose(); 861 springsChanged = true; 862 isValid = false; 863 } 864 } 865 866 /** 867 * Returns the preferred size for the specified container. 868 * 869 * @param parent the container to return the preferred size for 870 * @return the preferred size for {@code parent} 871 * @throws IllegalArgumentException if {@code parent} is not 872 * the same {@code Container} this was created with 873 * @throws IllegalStateException if any of the components added to 874 * this layout are not in both a horizontal and vertical group 875 * @see java.awt.Container#getPreferredSize 876 */ 877 public Dimension preferredLayoutSize(Container parent) { 878 checkParent(parent); 879 prepare(PREF_SIZE); 880 return adjustSize(horizontalGroup.getPreferredSize(HORIZONTAL), 881 verticalGroup.getPreferredSize(VERTICAL)); 882 } 883 884 /** 885 * Returns the minimum size for the specified container. 886 * 887 * @param parent the container to return the size for 888 * @return the minimum size for {@code parent} 889 * @throws IllegalArgumentException if {@code parent} is not 890 * the same {@code Container} that this was created with 891 * @throws IllegalStateException if any of the components added to 892 * this layout are not in both a horizontal and vertical group 893 * @see java.awt.Container#getMinimumSize 894 */ 895 public Dimension minimumLayoutSize(Container parent) { 896 checkParent(parent); 897 prepare(MIN_SIZE); 898 return adjustSize(horizontalGroup.getMinimumSize(HORIZONTAL), 899 verticalGroup.getMinimumSize(VERTICAL)); 900 } 901 902 /** 903 * Lays out the specified container. 904 * 905 * @param parent the container to be laid out 906 * @throws IllegalStateException if any of the components added to 907 * this layout are not in both a horizontal and vertical group 908 */ 909 public void layoutContainer(Container parent) { 910 // Step 1: Prepare for layout. 911 prepare(SPECIFIC_SIZE); 912 Insets insets = parent.getInsets(); 913 int width = parent.getWidth() - insets.left - insets.right; 914 int height = parent.getHeight() - insets.top - insets.bottom; 915 boolean ltr = isLeftToRight(); 916 if (getAutoCreateGaps() || getAutoCreateContainerGaps() || 917 hasPreferredPaddingSprings) { 918 // Step 2: Calculate autopadding springs 919 calculateAutopadding(horizontalGroup, HORIZONTAL, SPECIFIC_SIZE, 0, 920 width); 921 calculateAutopadding(verticalGroup, VERTICAL, SPECIFIC_SIZE, 0, 922 height); 923 } 924 // Step 3: set the size of the groups. 925 horizontalGroup.setSize(HORIZONTAL, 0, width); 926 verticalGroup.setSize(VERTICAL, 0, height); 927 // Step 4: apply the size to the components. 928 for (ComponentInfo info : componentInfos.values()) { 929 info.setBounds(insets, width, ltr); 930 } 931 } 932 933 // 934 // LayoutManager2 935 // 936 /** 937 * Notification that a {@code Component} has been added to 938 * the parent container. You should not invoke this method 939 * directly, instead you should use one of the {@code Group} 940 * methods to add a {@code Component}. 941 * 942 * @param component the component added 943 * @param constraints description of where to place the component 944 */ 945 public void addLayoutComponent(Component component, Object constraints) { 946 } 947 948 /** 949 * Returns the maximum size for the specified container. 950 * 951 * @param parent the container to return the size for 952 * @return the maximum size for {@code parent} 953 * @throws IllegalArgumentException if {@code parent} is not 954 * the same {@code Container} that this was created with 955 * @throws IllegalStateException if any of the components added to 956 * this layout are not in both a horizontal and vertical group 957 * @see java.awt.Container#getMaximumSize 958 */ 959 public Dimension maximumLayoutSize(Container parent) { 960 checkParent(parent); 961 prepare(MAX_SIZE); 962 return adjustSize(horizontalGroup.getMaximumSize(HORIZONTAL), 963 verticalGroup.getMaximumSize(VERTICAL)); 964 } 965 966 /** 967 * Returns the alignment along the x axis. This specifies how 968 * the component would like to be aligned relative to other 969 * components. The value should be a number between 0 and 1 970 * where 0 represents alignment along the origin, 1 is aligned 971 * the furthest away from the origin, 0.5 is centered, etc. 972 * 973 * @param parent the {@code Container} hosting this {@code LayoutManager} 974 * @throws IllegalArgumentException if {@code parent} is not 975 * the same {@code Container} that this was created with 976 * @return the alignment; this implementation returns {@code .5} 977 */ 978 public float getLayoutAlignmentX(Container parent) { 979 checkParent(parent); 980 return .5f; 981 } 982 983 /** 984 * Returns the alignment along the y axis. This specifies how 985 * the component would like to be aligned relative to other 986 * components. The value should be a number between 0 and 1 987 * where 0 represents alignment along the origin, 1 is aligned 988 * the furthest away from the origin, 0.5 is centered, etc. 989 * 990 * @param parent the {@code Container} hosting this {@code LayoutManager} 991 * @throws IllegalArgumentException if {@code parent} is not 992 * the same {@code Container} that this was created with 993 * @return alignment; this implementation returns {@code .5} 994 */ 995 public float getLayoutAlignmentY(Container parent) { 996 checkParent(parent); 997 return .5f; 998 } 999 1000 /** 1001 * Invalidates the layout, indicating that if the layout manager 1002 * has cached information it should be discarded. 1003 * 1004 * @param parent the {@code Container} hosting this LayoutManager 1005 * @throws IllegalArgumentException if {@code parent} is not 1006 * the same {@code Container} that this was created with 1007 */ 1008 public void invalidateLayout(Container parent) { 1009 checkParent(parent); 1010 // invalidateLayout is called from Container.invalidate, which 1011 // does NOT grab the treelock. All other methods do. To make sure 1012 // there aren't any possible threading problems we grab the tree lock 1013 // here. 1014 synchronized(parent.getTreeLock()) { 1015 isValid = false; 1016 } 1017 } 1018 1019 private void prepare(int sizeType) { 1020 boolean visChanged = false; 1021 // Step 1: If not-valid, clear springs and update visibility. 1022 if (!isValid) { 1023 isValid = true; 1024 horizontalGroup.setSize(HORIZONTAL, UNSET, UNSET); 1025 verticalGroup.setSize(VERTICAL, UNSET, UNSET); 1026 for (ComponentInfo ci : componentInfos.values()) { 1027 if (ci.updateVisibility()) { 1028 visChanged = true; 1029 } 1030 ci.clearCachedSize(); 1031 } 1032 } 1033 // Step 2: Make sure components are bound to ComponentInfos 1034 if (springsChanged) { 1035 registerComponents(horizontalGroup, HORIZONTAL); 1036 registerComponents(verticalGroup, VERTICAL); 1037 } 1038 // Step 3: Adjust the autopadding. This removes existing 1039 // autopadding, then recalculates where it should go. 1040 if (springsChanged || visChanged) { 1041 checkComponents(); 1042 horizontalGroup.removeAutopadding(); 1043 verticalGroup.removeAutopadding(); 1044 if (getAutoCreateGaps()) { 1045 insertAutopadding(true); 1046 } else if (hasPreferredPaddingSprings || 1047 getAutoCreateContainerGaps()) { 1048 insertAutopadding(false); 1049 } 1050 springsChanged = false; 1051 } 1052 // Step 4: (for min/pref/max size calculations only) calculate the 1053 // autopadding. This invokes for unsetting the calculated values, then 1054 // recalculating them. 1055 // If sizeType == SPECIFIC_SIZE, it indicates we're doing layout, this 1056 // step will be done later on. 1057 if (sizeType != SPECIFIC_SIZE && (getAutoCreateGaps() || 1058 getAutoCreateContainerGaps() || hasPreferredPaddingSprings)) { 1059 calculateAutopadding(horizontalGroup, HORIZONTAL, sizeType, 0, 0); 1060 calculateAutopadding(verticalGroup, VERTICAL, sizeType, 0, 0); 1061 } 1062 } 1063 1064 private void calculateAutopadding(Group group, int axis, int sizeType, 1065 int origin, int size) { 1066 group.unsetAutopadding(); 1067 switch(sizeType) { 1068 case MIN_SIZE: 1069 size = group.getMinimumSize(axis); 1070 break; 1071 case PREF_SIZE: 1072 size = group.getPreferredSize(axis); 1073 break; 1074 case MAX_SIZE: 1075 size = group.getMaximumSize(axis); 1076 break; 1077 default: 1078 break; 1079 } 1080 group.setSize(axis, origin, size); 1081 group.calculateAutopadding(axis); 1082 } 1083 1084 private void checkComponents() { 1085 for (ComponentInfo info : componentInfos.values()) { 1086 if (info.horizontalSpring == null) { 1087 throw new IllegalStateException(info.component + 1088 " is not attached to a horizontal group"); 1089 } 1090 if (info.verticalSpring == null) { 1091 throw new IllegalStateException(info.component + 1092 " is not attached to a vertical group"); 1093 } 1094 } 1095 } 1096 1097 private void registerComponents(Group group, int axis) { 1098 List<Spring> springs = group.springs; 1099 for (int counter = springs.size() - 1; counter >= 0; counter--) { 1100 Spring spring = springs.get(counter); 1101 if (spring instanceof ComponentSpring) { 1102 ((ComponentSpring)spring).installIfNecessary(axis); 1103 } else if (spring instanceof Group) { 1104 registerComponents((Group)spring, axis); 1105 } 1106 } 1107 } 1108 1109 private Dimension adjustSize(int width, int height) { 1110 Insets insets = host.getInsets(); 1111 return new Dimension(width + insets.left + insets.right, 1112 height + insets.top + insets.bottom); 1113 } 1114 1115 private void checkParent(Container parent) { 1116 if (parent != host) { 1117 throw new IllegalArgumentException( 1118 "GroupLayout can only be used with one Container at a time"); 1119 } 1120 } 1121 1122 /** 1123 * Returns the {@code ComponentInfo} for the specified Component, 1124 * creating one if necessary. 1125 */ 1126 private ComponentInfo getComponentInfo(Component component) { 1127 ComponentInfo info = componentInfos.get(component); 1128 if (info == null) { 1129 info = new ComponentInfo(component); 1130 componentInfos.put(component, info); 1131 if (component.getParent() != host) { 1132 host.add(component); 1133 } 1134 } 1135 return info; 1136 } 1137 1138 /** 1139 * Adjusts the autopadding springs for the horizontal and vertical 1140 * groups. If {@code insert} is {@code true} this will insert auto padding 1141 * springs, otherwise this will only adjust the springs that 1142 * comprise auto preferred padding springs. 1143 */ 1144 private void insertAutopadding(boolean insert) { 1145 horizontalGroup.insertAutopadding(HORIZONTAL, 1146 new ArrayList<AutoPreferredGapSpring>(1), 1147 new ArrayList<AutoPreferredGapSpring>(1), 1148 new ArrayList<ComponentSpring>(1), 1149 new ArrayList<ComponentSpring>(1), insert); 1150 verticalGroup.insertAutopadding(VERTICAL, 1151 new ArrayList<AutoPreferredGapSpring>(1), 1152 new ArrayList<AutoPreferredGapSpring>(1), 1153 new ArrayList<ComponentSpring>(1), 1154 new ArrayList<ComponentSpring>(1), insert); 1155 } 1156 1157 /** 1158 * Returns {@code true} if the two Components have a common ParallelGroup 1159 * ancestor along the particular axis. 1160 */ 1161 private boolean areParallelSiblings(Component source, Component target, 1162 int axis) { 1163 ComponentInfo sourceInfo = getComponentInfo(source); 1164 ComponentInfo targetInfo = getComponentInfo(target); 1165 Spring sourceSpring; 1166 Spring targetSpring; 1167 if (axis == HORIZONTAL) { 1168 sourceSpring = sourceInfo.horizontalSpring; 1169 targetSpring = targetInfo.horizontalSpring; 1170 } else { 1171 sourceSpring = sourceInfo.verticalSpring; 1172 targetSpring = targetInfo.verticalSpring; 1173 } 1174 Set<Spring> sourcePath = tmpParallelSet; 1175 sourcePath.clear(); 1176 Spring spring = sourceSpring.getParent(); 1177 while (spring != null) { 1178 sourcePath.add(spring); 1179 spring = spring.getParent(); 1180 } 1181 spring = targetSpring.getParent(); 1182 while (spring != null) { 1183 if (sourcePath.contains(spring)) { 1184 sourcePath.clear(); 1185 while (spring != null) { 1186 if (spring instanceof ParallelGroup) { 1187 return true; 1188 } 1189 spring = spring.getParent(); 1190 } 1191 return false; 1192 } 1193 spring = spring.getParent(); 1194 } 1195 sourcePath.clear(); 1196 return false; 1197 } 1198 1199 private boolean isLeftToRight() { 1200 return host.getComponentOrientation().isLeftToRight(); 1201 } 1202 1203 /** 1204 * Returns a string representation of this {@code GroupLayout}. 1205 * This method is intended to be used for debugging purposes, 1206 * and the content and format of the returned string may vary 1207 * between implementations. 1208 * 1209 * @return a string representation of this {@code GroupLayout} 1210 **/ 1211 public String toString() { 1212 if (springsChanged) { 1213 registerComponents(horizontalGroup, HORIZONTAL); 1214 registerComponents(verticalGroup, VERTICAL); 1215 } 1216 StringBuilder sb = new StringBuilder(); 1217 sb.append("HORIZONTAL\n"); 1218 createSpringDescription(sb, horizontalGroup, " ", HORIZONTAL); 1219 sb.append("\nVERTICAL\n"); 1220 createSpringDescription(sb, verticalGroup, " ", VERTICAL); 1221 return sb.toString(); 1222 } 1223 1224 private void createSpringDescription(StringBuilder sb, Spring spring, 1225 String indent, int axis) { 1226 String origin = ""; 1227 String padding = ""; 1228 if (spring instanceof ComponentSpring) { 1229 ComponentSpring cSpring = (ComponentSpring)spring; 1230 origin = Integer.toString(cSpring.getOrigin()) + " "; 1231 String name = cSpring.getComponent().getName(); 1232 if (name != null) { 1233 origin = "name=" + name + ", "; 1234 } 1235 } 1236 if (spring instanceof AutoPreferredGapSpring) { 1237 AutoPreferredGapSpring paddingSpring = 1238 (AutoPreferredGapSpring)spring; 1239 padding = ", userCreated=" + paddingSpring.getUserCreated() + 1240 ", matches=" + paddingSpring.getMatchDescription(); 1241 } 1242 sb.append(indent).append(spring.getClass().getName()).append(' ') 1243 .append(Integer.toHexString(spring.hashCode())).append(' ') 1244 .append(origin).append(", size=").append(spring.getSize()) 1245 .append(", alignment=").append(spring.getAlignment()) 1246 .append(" prefs=[").append(spring.getMinimumSize(axis)) 1247 .append(' ').append(spring.getPreferredSize(axis)).append(' ') 1248 .append(spring.getMaximumSize(axis)).append(padding) 1249 .append("]\n"); 1250 if (spring instanceof Group) { 1251 List<Spring> springs = ((Group)spring).springs; 1252 indent += " "; 1253 for (int counter = 0; counter < springs.size(); counter++) { 1254 createSpringDescription(sb, springs.get(counter), indent, 1255 axis); 1256 } 1257 } 1258 } 1259 1260 1261 /** 1262 * Spring consists of a range: min, pref and max, a value some where in 1263 * the middle of that, and a location. Spring caches the 1264 * min/max/pref. If the min/pref/max has internally changes, or needs 1265 * to be updated you must invoke clear. 1266 */ 1267 private abstract class Spring { 1268 private int size; 1269 private int min; 1270 private int max; 1271 private int pref; 1272 private Spring parent; 1273 1274 private Alignment alignment; 1275 1276 Spring() { 1277 min = pref = max = UNSET; 1278 } 1279 1280 /** 1281 * Calculates and returns the minimum size. 1282 * 1283 * @param axis the axis of layout; one of HORIZONTAL or VERTICAL 1284 * @return the minimum size 1285 */ 1286 abstract int calculateMinimumSize(int axis); 1287 1288 /** 1289 * Calculates and returns the preferred size. 1290 * 1291 * @param axis the axis of layout; one of HORIZONTAL or VERTICAL 1292 * @return the preferred size 1293 */ 1294 abstract int calculatePreferredSize(int axis); 1295 1296 /** 1297 * Calculates and returns the minimum size. 1298 * 1299 * @param axis the axis of layout; one of HORIZONTAL or VERTICAL 1300 * @return the minimum size 1301 */ 1302 abstract int calculateMaximumSize(int axis); 1303 1304 /** 1305 * Sets the parent of this Spring. 1306 */ 1307 void setParent(Spring parent) { 1308 this.parent = parent; 1309 } 1310 1311 /** 1312 * Returns the parent of this spring. 1313 */ 1314 Spring getParent() { 1315 return parent; 1316 } 1317 1318 // This is here purely as a convenience for ParallelGroup to avoid 1319 // having to track alignment separately. 1320 void setAlignment(Alignment alignment) { 1321 this.alignment = alignment; 1322 } 1323 1324 /** 1325 * Alignment for this Spring, this may be null. 1326 */ 1327 Alignment getAlignment() { 1328 return alignment; 1329 } 1330 1331 /** 1332 * Returns the minimum size. 1333 */ 1334 final int getMinimumSize(int axis) { 1335 if (min == UNSET) { 1336 min = constrain(calculateMinimumSize(axis)); 1337 } 1338 return min; 1339 } 1340 1341 /** 1342 * Returns the preferred size. 1343 */ 1344 final int getPreferredSize(int axis) { 1345 if (pref == UNSET) { 1346 pref = constrain(calculatePreferredSize(axis)); 1347 } 1348 return pref; 1349 } 1350 1351 /** 1352 * Returns the maximum size. 1353 */ 1354 final int getMaximumSize(int axis) { 1355 if (max == UNSET) { 1356 max = constrain(calculateMaximumSize(axis)); 1357 } 1358 return max; 1359 } 1360 1361 /** 1362 * Sets the value and location of the spring. Subclasses 1363 * will want to invoke super, then do any additional sizing. 1364 * 1365 * @param axis HORIZONTAL or VERTICAL 1366 * @param origin of this Spring 1367 * @param size of the Spring. If size is UNSET, this invokes 1368 * clear. 1369 */ 1370 void setSize(int axis, int origin, int size) { 1371 this.size = size; 1372 if (size == UNSET) { 1373 unset(); 1374 } 1375 } 1376 1377 /** 1378 * Resets the cached min/max/pref. 1379 */ 1380 void unset() { 1381 size = min = pref = max = UNSET; 1382 } 1383 1384 /** 1385 * Returns the current size. 1386 */ 1387 int getSize() { 1388 return size; 1389 } 1390 1391 int constrain(int value) { 1392 return Math.min(value, Short.MAX_VALUE); 1393 } 1394 1395 int getBaseline() { 1396 return -1; 1397 } 1398 1399 BaselineResizeBehavior getBaselineResizeBehavior() { 1400 return BaselineResizeBehavior.OTHER; 1401 } 1402 1403 final boolean isResizable(int axis) { 1404 int min = getMinimumSize(axis); 1405 int pref = getPreferredSize(axis); 1406 return (min != pref || pref != getMaximumSize(axis)); 1407 } 1408 1409 /** 1410 * Returns {@code true} if this spring will ALWAYS have a zero 1411 * size. This should NOT check the current size, rather it's 1412 * meant to quickly test if this Spring will always have a 1413 * zero size. 1414 * 1415 * @param treatAutopaddingAsZeroSized if {@code true}, auto padding 1416 * springs should be treated as having a size of {@code 0} 1417 * @return {@code true} if this spring will have a zero size, 1418 * {@code false} otherwise 1419 */ 1420 abstract boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized); 1421 } 1422 1423 /** 1424 * {@code Group} provides the basis for the two types of 1425 * operations supported by {@code GroupLayout}: laying out 1426 * components one after another ({@link SequentialGroup SequentialGroup}) 1427 * or aligned ({@link ParallelGroup ParallelGroup}). {@code Group} and 1428 * its subclasses have no public constructor; to create one use 1429 * one of {@code createSequentialGroup} or 1430 * {@code createParallelGroup}. Additionally, taking a {@code Group} 1431 * created from one {@code GroupLayout} and using it with another 1432 * will produce undefined results. 1433 * <p> 1434 * Various methods in {@code Group} and its subclasses allow you 1435 * to explicitly specify the range. The arguments to these methods 1436 * can take two forms, either a value greater than or equal to 0, 1437 * or one of {@code DEFAULT_SIZE} or {@code PREFERRED_SIZE}. A 1438 * value greater than or equal to {@code 0} indicates a specific 1439 * size. {@code DEFAULT_SIZE} indicates the corresponding size 1440 * from the component should be used. For example, if {@code 1441 * DEFAULT_SIZE} is passed as the minimum size argument, the 1442 * minimum size is obtained from invoking {@code getMinimumSize} 1443 * on the component. Likewise, {@code PREFERRED_SIZE} indicates 1444 * the value from {@code getPreferredSize} should be used. 1445 * The following example adds {@code myComponent} to {@code group} 1446 * with specific values for the range. That is, the minimum is 1447 * explicitly specified as 100, preferred as 200, and maximum as 1448 * 300. 1449 * <pre> 1450 * group.addComponent(myComponent, 100, 200, 300); 1451 * </pre> 1452 * The following example adds {@code myComponent} to {@code group} using 1453 * a combination of the forms. The minimum size is forced to be the 1454 * same as the preferred size, the preferred size is determined by 1455 * using {@code myComponent.getPreferredSize} and the maximum is 1456 * determined by invoking {@code getMaximumSize} on the component. 1457 * <pre> 1458 * group.addComponent(myComponent, GroupLayout.PREFERRED_SIZE, 1459 * GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE); 1460 * </pre> 1461 * <p> 1462 * Unless otherwise specified all the methods of {@code Group} and 1463 * its subclasses that allow you to specify a range throw an 1464 * {@code IllegalArgumentException} if passed an invalid range. An 1465 * invalid range is one in which any of the values are < 0 and 1466 * not one of {@code PREFERRED_SIZE} or {@code DEFAULT_SIZE}, or 1467 * the following is not met (for specific values): {@code min} 1468 * <= {@code pref} <= {@code max}. 1469 * <p> 1470 * Similarly any methods that take a {@code Component} throw a 1471 * {@code IllegalArgumentException} if passed {@code null} and any methods 1472 * that take a {@code Group} throw an {@code NullPointerException} if 1473 * passed {@code null}. 1474 * 1475 * @see #createSequentialGroup 1476 * @see #createParallelGroup 1477 * @since 1.6 1478 */ 1479 public abstract class Group extends Spring { 1480 // private int origin; 1481 // private int size; 1482 List<Spring> springs; 1483 1484 Group() { 1485 springs = new ArrayList<Spring>(); 1486 } 1487 1488 /** 1489 * Adds a {@code Group} to this {@code Group}. 1490 * 1491 * @param group the {@code Group} to add 1492 * @return this {@code Group} 1493 */ 1494 public Group addGroup(Group group) { 1495 return addSpring(group); 1496 } 1497 1498 /** 1499 * Adds a {@code Component} to this {@code Group}. 1500 * 1501 * @param component the {@code Component} to add 1502 * @return this {@code Group} 1503 */ 1504 public Group addComponent(Component component) { 1505 return addComponent(component, DEFAULT_SIZE, DEFAULT_SIZE, 1506 DEFAULT_SIZE); 1507 } 1508 1509 /** 1510 * Adds a {@code Component} to this {@code Group} 1511 * with the specified size. 1512 * 1513 * @param component the {@code Component} to add 1514 * @param min the minimum size or one of {@code DEFAULT_SIZE} or 1515 * {@code PREFERRED_SIZE} 1516 * @param pref the preferred size or one of {@code DEFAULT_SIZE} or 1517 * {@code PREFERRED_SIZE} 1518 * @param max the maximum size or one of {@code DEFAULT_SIZE} or 1519 * {@code PREFERRED_SIZE} 1520 * @return this {@code Group} 1521 */ 1522 public Group addComponent(Component component, int min, int pref, 1523 int max) { 1524 return addSpring(new ComponentSpring(component, min, pref, max)); 1525 } 1526 1527 /** 1528 * Adds a rigid gap to this {@code Group}. 1529 * 1530 * @param size the size of the gap 1531 * @return this {@code Group} 1532 * @throws IllegalArgumentException if {@code size} is less than 1533 * {@code 0} 1534 */ 1535 public Group addGap(int size) { 1536 return addGap(size, size, size); 1537 } 1538 1539 /** 1540 * Adds a gap to this {@code Group} with the specified size. 1541 * 1542 * @param min the minimum size of the gap 1543 * @param pref the preferred size of the gap 1544 * @param max the maximum size of the gap 1545 * @throws IllegalArgumentException if any of the values are 1546 * less than {@code 0} 1547 * @return this {@code Group} 1548 */ 1549 public Group addGap(int min, int pref, int max) { 1550 return addSpring(new GapSpring(min, pref, max)); 1551 } 1552 1553 Spring getSpring(int index) { 1554 return springs.get(index); 1555 } 1556 1557 int indexOf(Spring spring) { 1558 return springs.indexOf(spring); 1559 } 1560 1561 /** 1562 * Adds the Spring to the list of {@code Spring}s and returns 1563 * the receiver. 1564 */ 1565 Group addSpring(Spring spring) { 1566 springs.add(spring); 1567 spring.setParent(this); 1568 if (!(spring instanceof AutoPreferredGapSpring) || 1569 !((AutoPreferredGapSpring)spring).getUserCreated()) { 1570 springsChanged = true; 1571 } 1572 return this; 1573 } 1574 1575 // 1576 // Spring methods 1577 // 1578 1579 void setSize(int axis, int origin, int size) { 1580 super.setSize(axis, origin, size); 1581 if (size == UNSET) { 1582 for (int counter = springs.size() - 1; counter >= 0; 1583 counter--) { 1584 getSpring(counter).setSize(axis, origin, size); 1585 } 1586 } else { 1587 setValidSize(axis, origin, size); 1588 } 1589 } 1590 1591 /** 1592 * This is invoked from {@code setSize} if passed a value 1593 * other than UNSET. 1594 */ 1595 abstract void setValidSize(int axis, int origin, int size); 1596 1597 int calculateMinimumSize(int axis) { 1598 return calculateSize(axis, MIN_SIZE); 1599 } 1600 1601 int calculatePreferredSize(int axis) { 1602 return calculateSize(axis, PREF_SIZE); 1603 } 1604 1605 int calculateMaximumSize(int axis) { 1606 return calculateSize(axis, MAX_SIZE); 1607 } 1608 1609 /** 1610 * Calculates the specified size. This is called from 1611 * one of the {@code getMinimumSize0}, 1612 * {@code getPreferredSize0} or 1613 * {@code getMaximumSize0} methods. This will invoke 1614 * to {@code operator} to combine the values. 1615 */ 1616 int calculateSize(int axis, int type) { 1617 int count = springs.size(); 1618 if (count == 0) { 1619 return 0; 1620 } 1621 if (count == 1) { 1622 return getSpringSize(getSpring(0), axis, type); 1623 } 1624 int size = constrain(operator(getSpringSize(getSpring(0), axis, 1625 type), getSpringSize(getSpring(1), axis, type))); 1626 for (int counter = 2; counter < count; counter++) { 1627 size = constrain(operator(size, getSpringSize( 1628 getSpring(counter), axis, type))); 1629 } 1630 return size; 1631 } 1632 1633 int getSpringSize(Spring spring, int axis, int type) { 1634 switch(type) { 1635 case MIN_SIZE: 1636 return spring.getMinimumSize(axis); 1637 case PREF_SIZE: 1638 return spring.getPreferredSize(axis); 1639 case MAX_SIZE: 1640 return spring.getMaximumSize(axis); 1641 } 1642 assert false; 1643 return 0; 1644 } 1645 1646 /** 1647 * Used to compute how the two values representing two springs 1648 * will be combined. For example, a group that layed things out 1649 * one after the next would return {@code a + b}. 1650 */ 1651 abstract int operator(int a, int b); 1652 1653 // 1654 // Padding 1655 // 1656 1657 /** 1658 * Adjusts the autopadding springs in this group and its children. 1659 * If {@code insert} is true this will insert auto padding 1660 * springs, otherwise this will only adjust the springs that 1661 * comprise auto preferred padding springs. 1662 * 1663 * @param axis the axis of the springs; HORIZONTAL or VERTICAL 1664 * @param leadingPadding List of AutopaddingSprings that occur before 1665 * this Group 1666 * @param trailingPadding any trailing autopadding springs are added 1667 * to this on exit 1668 * @param leading List of ComponentSprings that occur before this Group 1669 * @param trailing any trailing ComponentSpring are added to this 1670 * List 1671 * @param insert Whether or not to insert AutopaddingSprings or just 1672 * adjust any existing AutopaddingSprings. 1673 */ 1674 abstract void insertAutopadding(int axis, 1675 List<AutoPreferredGapSpring> leadingPadding, 1676 List<AutoPreferredGapSpring> trailingPadding, 1677 List<ComponentSpring> leading, List<ComponentSpring> trailing, 1678 boolean insert); 1679 1680 /** 1681 * Removes any AutopaddingSprings for this Group and its children. 1682 */ 1683 void removeAutopadding() { 1684 unset(); 1685 for (int counter = springs.size() - 1; counter >= 0; counter--) { 1686 Spring spring = springs.get(counter); 1687 if (spring instanceof AutoPreferredGapSpring) { 1688 if (((AutoPreferredGapSpring)spring).getUserCreated()) { 1689 ((AutoPreferredGapSpring)spring).reset(); 1690 } else { 1691 springs.remove(counter); 1692 } 1693 } else if (spring instanceof Group) { 1694 ((Group)spring).removeAutopadding(); 1695 } 1696 } 1697 } 1698 1699 void unsetAutopadding() { 1700 // Clear cached pref/min/max. 1701 unset(); 1702 for (int counter = springs.size() - 1; counter >= 0; counter--) { 1703 Spring spring = springs.get(counter); 1704 if (spring instanceof AutoPreferredGapSpring) { 1705 spring.unset(); 1706 } else if (spring instanceof Group) { 1707 ((Group)spring).unsetAutopadding(); 1708 } 1709 } 1710 } 1711 1712 void calculateAutopadding(int axis) { 1713 for (int counter = springs.size() - 1; counter >= 0; counter--) { 1714 Spring spring = springs.get(counter); 1715 if (spring instanceof AutoPreferredGapSpring) { 1716 // Force size to be reset. 1717 spring.unset(); 1718 ((AutoPreferredGapSpring)spring).calculatePadding(axis); 1719 } else if (spring instanceof Group) { 1720 ((Group)spring).calculateAutopadding(axis); 1721 } 1722 } 1723 // Clear cached pref/min/max. 1724 unset(); 1725 } 1726 1727 @Override 1728 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 1729 for (int i = springs.size() - 1; i >= 0; i--) { 1730 Spring spring = springs.get(i); 1731 if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) { 1732 return false; 1733 } 1734 } 1735 return true; 1736 } 1737 } 1738 1739 1740 /** 1741 * A {@code Group} that positions and sizes its elements 1742 * sequentially, one after another. This class has no public 1743 * constructor, use the {@code createSequentialGroup} method 1744 * to create one. 1745 * <p> 1746 * In order to align a {@code SequentialGroup} along the baseline 1747 * of a baseline aligned {@code ParallelGroup} you need to specify 1748 * which of the elements of the {@code SequentialGroup} is used to 1749 * determine the baseline. The element used to calculate the 1750 * baseline is specified using one of the {@code add} methods that 1751 * take a {@code boolean}. The last element added with a value of 1752 * {@code true} for {@code useAsBaseline} is used to calculate the 1753 * baseline. 1754 * 1755 * @see #createSequentialGroup 1756 * @since 1.6 1757 */ 1758 public class SequentialGroup extends Group { 1759 private Spring baselineSpring; 1760 1761 SequentialGroup() { 1762 } 1763 1764 /** 1765 * {@inheritDoc} 1766 */ 1767 public SequentialGroup addGroup(Group group) { 1768 return (SequentialGroup)super.addGroup(group); 1769 } 1770 1771 /** 1772 * Adds a {@code Group} to this {@code Group}. 1773 * 1774 * @param group the {@code Group} to add 1775 * @param useAsBaseline whether the specified {@code Group} should 1776 * be used to calculate the baseline for this {@code Group} 1777 * @return this {@code Group} 1778 */ 1779 public SequentialGroup addGroup(boolean useAsBaseline, Group group) { 1780 super.addGroup(group); 1781 if (useAsBaseline) { 1782 baselineSpring = group; 1783 } 1784 return this; 1785 } 1786 1787 /** 1788 * {@inheritDoc} 1789 */ 1790 public SequentialGroup addComponent(Component component) { 1791 return (SequentialGroup)super.addComponent(component); 1792 } 1793 1794 /** 1795 * Adds a {@code Component} to this {@code Group}. 1796 * 1797 * @param useAsBaseline whether the specified {@code Component} should 1798 * be used to calculate the baseline for this {@code Group} 1799 * @param component the {@code Component} to add 1800 * @return this {@code Group} 1801 */ 1802 public SequentialGroup addComponent(boolean useAsBaseline, 1803 Component component) { 1804 super.addComponent(component); 1805 if (useAsBaseline) { 1806 baselineSpring = springs.get(springs.size() - 1); 1807 } 1808 return this; 1809 } 1810 1811 /** 1812 * {@inheritDoc} 1813 */ 1814 public SequentialGroup addComponent(Component component, int min, 1815 int pref, int max) { 1816 return (SequentialGroup)super.addComponent( 1817 component, min, pref, max); 1818 } 1819 1820 /** 1821 * Adds a {@code Component} to this {@code Group} 1822 * with the specified size. 1823 * 1824 * @param useAsBaseline whether the specified {@code Component} should 1825 * be used to calculate the baseline for this {@code Group} 1826 * @param component the {@code Component} to add 1827 * @param min the minimum size or one of {@code DEFAULT_SIZE} or 1828 * {@code PREFERRED_SIZE} 1829 * @param pref the preferred size or one of {@code DEFAULT_SIZE} or 1830 * {@code PREFERRED_SIZE} 1831 * @param max the maximum size or one of {@code DEFAULT_SIZE} or 1832 * {@code PREFERRED_SIZE} 1833 * @return this {@code Group} 1834 */ 1835 public SequentialGroup addComponent(boolean useAsBaseline, 1836 Component component, int min, int pref, int max) { 1837 super.addComponent(component, min, pref, max); 1838 if (useAsBaseline) { 1839 baselineSpring = springs.get(springs.size() - 1); 1840 } 1841 return this; 1842 } 1843 1844 /** 1845 * {@inheritDoc} 1846 */ 1847 public SequentialGroup addGap(int size) { 1848 return (SequentialGroup)super.addGap(size); 1849 } 1850 1851 /** 1852 * {@inheritDoc} 1853 */ 1854 public SequentialGroup addGap(int min, int pref, int max) { 1855 return (SequentialGroup)super.addGap(min, pref, max); 1856 } 1857 1858 /** 1859 * Adds an element representing the preferred gap between two 1860 * components. The element created to represent the gap is not 1861 * resizable. 1862 * 1863 * @param comp1 the first component 1864 * @param comp2 the second component 1865 * @param type the type of gap; one of the constants defined by 1866 * {@code LayoutStyle} 1867 * @return this {@code SequentialGroup} 1868 * @throws IllegalArgumentException if {@code type}, {@code comp1} or 1869 * {@code comp2} is {@code null} 1870 * @see LayoutStyle 1871 */ 1872 public SequentialGroup addPreferredGap(JComponent comp1, 1873 JComponent comp2, ComponentPlacement type) { 1874 return addPreferredGap(comp1, comp2, type, DEFAULT_SIZE, 1875 PREFERRED_SIZE); 1876 } 1877 1878 /** 1879 * Adds an element representing the preferred gap between two 1880 * components. 1881 * 1882 * @param comp1 the first component 1883 * @param comp2 the second component 1884 * @param type the type of gap 1885 * @param pref the preferred size of the grap; one of 1886 * {@code DEFAULT_SIZE} or a value >= 0 1887 * @param max the maximum size of the gap; one of 1888 * {@code DEFAULT_SIZE}, {@code PREFERRED_SIZE} 1889 * or a value >= 0 1890 * @return this {@code SequentialGroup} 1891 * @throws IllegalArgumentException if {@code type}, {@code comp1} or 1892 * {@code comp2} is {@code null} 1893 * @see LayoutStyle 1894 */ 1895 public SequentialGroup addPreferredGap(JComponent comp1, 1896 JComponent comp2, ComponentPlacement type, int pref, 1897 int max) { 1898 if (type == null) { 1899 throw new IllegalArgumentException("Type must be non-null"); 1900 } 1901 if (comp1 == null || comp2 == null) { 1902 throw new IllegalArgumentException( 1903 "Components must be non-null"); 1904 } 1905 checkPreferredGapValues(pref, max); 1906 return (SequentialGroup)addSpring(new PreferredGapSpring( 1907 comp1, comp2, type, pref, max)); 1908 } 1909 1910 /** 1911 * Adds an element representing the preferred gap between the 1912 * nearest components. During layout, neighboring 1913 * components are found, and the size of the added gap is set 1914 * based on the preferred gap between the components. If no 1915 * neighboring components are found the gap has a size of {@code 0}. 1916 * <p> 1917 * The element created to represent the gap is not 1918 * resizable. 1919 * 1920 * @param type the type of gap; one of 1921 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1922 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1923 * @return this {@code SequentialGroup} 1924 * @see LayoutStyle 1925 * @throws IllegalArgumentException if {@code type} is not one of 1926 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1927 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1928 */ 1929 public SequentialGroup addPreferredGap(ComponentPlacement type) { 1930 return addPreferredGap(type, DEFAULT_SIZE, DEFAULT_SIZE); 1931 } 1932 1933 /** 1934 * Adds an element representing the preferred gap between the 1935 * nearest components. During layout, neighboring 1936 * components are found, and the minimum of this 1937 * gap is set based on the size of the preferred gap between the 1938 * neighboring components. If no neighboring components are found the 1939 * minimum size is set to 0. 1940 * 1941 * @param type the type of gap; one of 1942 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1943 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1944 * @param pref the preferred size of the grap; one of 1945 * {@code DEFAULT_SIZE} or a value >= 0 1946 * @param max the maximum size of the gap; one of 1947 * {@code DEFAULT_SIZE}, {@code PREFERRED_SIZE} 1948 * or a value >= 0 1949 * @return this {@code SequentialGroup} 1950 * @throws IllegalArgumentException if {@code type} is not one of 1951 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1952 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1953 * @see LayoutStyle 1954 */ 1955 public SequentialGroup addPreferredGap(ComponentPlacement type, 1956 int pref, int max) { 1957 if (type != ComponentPlacement.RELATED && 1958 type != ComponentPlacement.UNRELATED) { 1959 throw new IllegalArgumentException( 1960 "Type must be one of " + 1961 "LayoutStyle.ComponentPlacement.RELATED or " + 1962 "LayoutStyle.ComponentPlacement.UNRELATED"); 1963 } 1964 checkPreferredGapValues(pref, max); 1965 hasPreferredPaddingSprings = true; 1966 return (SequentialGroup)addSpring(new AutoPreferredGapSpring( 1967 type, pref, max)); 1968 } 1969 1970 /** 1971 * Adds an element representing the preferred gap between an edge 1972 * the container and components that touch the border of the 1973 * container. This has no effect if the added gap does not 1974 * touch an edge of the parent container. 1975 * <p> 1976 * The element created to represent the gap is not 1977 * resizable. 1978 * 1979 * @return this {@code SequentialGroup} 1980 */ 1981 public SequentialGroup addContainerGap() { 1982 return addContainerGap(DEFAULT_SIZE, DEFAULT_SIZE); 1983 } 1984 1985 /** 1986 * Adds an element representing the preferred gap between one 1987 * edge of the container and the next or previous {@code 1988 * Component} with the specified size. This has no 1989 * effect if the next or previous element is not a {@code 1990 * Component} and does not touch one edge of the parent 1991 * container. 1992 * 1993 * @param pref the preferred size; one of {@code DEFAULT_SIZE} or a 1994 * value >= 0 1995 * @param max the maximum size; one of {@code DEFAULT_SIZE}, 1996 * {@code PREFERRED_SIZE} or a value >= 0 1997 * @return this {@code SequentialGroup} 1998 */ 1999 public SequentialGroup addContainerGap(int pref, int max) { 2000 if ((pref < 0 && pref != DEFAULT_SIZE) || 2001 (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE)|| 2002 (pref >= 0 && max >= 0 && pref > max)) { 2003 throw new IllegalArgumentException( 2004 "Pref and max must be either DEFAULT_VALUE " + 2005 "or >= 0 and pref <= max"); 2006 } 2007 hasPreferredPaddingSprings = true; 2008 return (SequentialGroup)addSpring( 2009 new ContainerAutoPreferredGapSpring(pref, max)); 2010 } 2011 2012 int operator(int a, int b) { 2013 return constrain(a) + constrain(b); 2014 } 2015 2016 void setValidSize(int axis, int origin, int size) { 2017 int pref = getPreferredSize(axis); 2018 if (size == pref) { 2019 // Layout at preferred size 2020 for (Spring spring : springs) { 2021 int springPref = spring.getPreferredSize(axis); 2022 spring.setSize(axis, origin, springPref); 2023 origin += springPref; 2024 } 2025 } else if (springs.size() == 1) { 2026 Spring spring = getSpring(0); 2027 spring.setSize(axis, origin, Math.min( 2028 Math.max(size, spring.getMinimumSize(axis)), 2029 spring.getMaximumSize(axis))); 2030 } else if (springs.size() > 1) { 2031 // Adjust between min/pref 2032 setValidSizeNotPreferred(axis, origin, size); 2033 } 2034 } 2035 2036 private void setValidSizeNotPreferred(int axis, int origin, int size) { 2037 int delta = size - getPreferredSize(axis); 2038 assert delta != 0; 2039 boolean useMin = (delta < 0); 2040 int springCount = springs.size(); 2041 if (useMin) { 2042 delta *= -1; 2043 } 2044 2045 // The following algorithm if used for resizing springs: 2046 // 1. Calculate the resizability of each spring (pref - min or 2047 // max - pref) into a list. 2048 // 2. Sort the list in ascending order 2049 // 3. Iterate through each of the resizable Springs, attempting 2050 // to give them (pref - size) / resizeCount 2051 // 4. For any Springs that can not accommodate that much space 2052 // add the remainder back to the amount to distribute and 2053 // recalculate how must space the remaining springs will get. 2054 // 5. Set the size of the springs. 2055 2056 // First pass, sort the resizable springs into the List resizable 2057 List<SpringDelta> resizable = buildResizableList(axis, useMin); 2058 int resizableCount = resizable.size(); 2059 2060 if (resizableCount > 0) { 2061 // How much we would like to give each Spring. 2062 int sDelta = delta / resizableCount; 2063 // Remaining space. 2064 int slop = delta - sDelta * resizableCount; 2065 int[] sizes = new int[springCount]; 2066 int sign = useMin ? -1 : 1; 2067 // Second pass, accumulate the resulting deltas (relative to 2068 // preferred) into sizes. 2069 for (int counter = 0; counter < resizableCount; counter++) { 2070 SpringDelta springDelta = resizable.get(counter); 2071 if ((counter + 1) == resizableCount) { 2072 sDelta += slop; 2073 } 2074 springDelta.delta = Math.min(sDelta, springDelta.delta); 2075 delta -= springDelta.delta; 2076 if (springDelta.delta != sDelta && counter + 1 < 2077 resizableCount) { 2078 // Spring didn't take all the space, reset how much 2079 // each spring will get. 2080 sDelta = delta / (resizableCount - counter - 1); 2081 slop = delta - sDelta * (resizableCount - counter - 1); 2082 } 2083 sizes[springDelta.index] = sign * springDelta.delta; 2084 } 2085 2086 // And finally set the size of each spring 2087 for (int counter = 0; counter < springCount; counter++) { 2088 Spring spring = getSpring(counter); 2089 int sSize = spring.getPreferredSize(axis) + sizes[counter]; 2090 spring.setSize(axis, origin, sSize); 2091 origin += sSize; 2092 } 2093 } else { 2094 // Nothing resizable, use the min or max of each of the 2095 // springs. 2096 for (int counter = 0; counter < springCount; counter++) { 2097 Spring spring = getSpring(counter); 2098 int sSize; 2099 if (useMin) { 2100 sSize = spring.getMinimumSize(axis); 2101 } else { 2102 sSize = spring.getMaximumSize(axis); 2103 } 2104 spring.setSize(axis, origin, sSize); 2105 origin += sSize; 2106 } 2107 } 2108 } 2109 2110 /** 2111 * Returns the sorted list of SpringDelta's for the current set of 2112 * Springs. The list is ordered based on the amount of flexibility of 2113 * the springs. 2114 */ 2115 private List<SpringDelta> buildResizableList(int axis, 2116 boolean useMin) { 2117 // First pass, figure out what is resizable 2118 int size = springs.size(); 2119 List<SpringDelta> sorted = new ArrayList<SpringDelta>(size); 2120 for (int counter = 0; counter < size; counter++) { 2121 Spring spring = getSpring(counter); 2122 int sDelta; 2123 if (useMin) { 2124 sDelta = spring.getPreferredSize(axis) - 2125 spring.getMinimumSize(axis); 2126 } else { 2127 sDelta = spring.getMaximumSize(axis) - 2128 spring.getPreferredSize(axis); 2129 } 2130 if (sDelta > 0) { 2131 sorted.add(new SpringDelta(counter, sDelta)); 2132 } 2133 } 2134 Collections.sort(sorted); 2135 return sorted; 2136 } 2137 2138 private int indexOfNextNonZeroSpring( 2139 int index, boolean treatAutopaddingAsZeroSized) { 2140 while (index < springs.size()) { 2141 Spring spring = springs.get(index); 2142 if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) { 2143 return index; 2144 } 2145 index++; 2146 } 2147 return index; 2148 } 2149 2150 @Override 2151 void insertAutopadding(int axis, 2152 List<AutoPreferredGapSpring> leadingPadding, 2153 List<AutoPreferredGapSpring> trailingPadding, 2154 List<ComponentSpring> leading, List<ComponentSpring> trailing, 2155 boolean insert) { 2156 List<AutoPreferredGapSpring> newLeadingPadding = 2157 new ArrayList<AutoPreferredGapSpring>(leadingPadding); 2158 List<AutoPreferredGapSpring> newTrailingPadding = 2159 new ArrayList<AutoPreferredGapSpring>(1); 2160 List<ComponentSpring> newLeading = 2161 new ArrayList<ComponentSpring>(leading); 2162 List<ComponentSpring> newTrailing = null; 2163 int counter = 0; 2164 // Warning, this must use springs.size, as it may change during the 2165 // loop. 2166 while (counter < springs.size()) { 2167 Spring spring = getSpring(counter); 2168 if (spring instanceof AutoPreferredGapSpring) { 2169 if (newLeadingPadding.size() == 0) { 2170 // Autopadding spring. Set the sources of the 2171 // autopadding spring based on newLeading. 2172 AutoPreferredGapSpring padding = 2173 (AutoPreferredGapSpring)spring; 2174 padding.setSources(newLeading); 2175 newLeading.clear(); 2176 counter = indexOfNextNonZeroSpring(counter + 1, true); 2177 if (counter == springs.size()) { 2178 // Last spring in the list, add it to 2179 // trailingPadding. 2180 if (!(padding instanceof 2181 ContainerAutoPreferredGapSpring)) { 2182 trailingPadding.add(padding); 2183 } 2184 } else { 2185 newLeadingPadding.clear(); 2186 newLeadingPadding.add(padding); 2187 } 2188 } else { 2189 counter = indexOfNextNonZeroSpring(counter + 1, true); 2190 } 2191 } else { 2192 // Not a padding spring 2193 if (newLeading.size() > 0 && insert) { 2194 // There's leading ComponentSprings, create an 2195 // autopadding spring. 2196 AutoPreferredGapSpring padding = 2197 new AutoPreferredGapSpring(); 2198 // Force the newly created spring to be considered 2199 // by NOT incrementing counter 2200 springs.add(counter, padding); 2201 continue; 2202 } 2203 if (spring instanceof ComponentSpring) { 2204 // Spring is a Component, make it the target of any 2205 // leading AutopaddingSpring. 2206 ComponentSpring cSpring = (ComponentSpring)spring; 2207 if (!cSpring.isVisible()) { 2208 counter++; 2209 continue; 2210 } 2211 for (AutoPreferredGapSpring gapSpring : newLeadingPadding) { 2212 gapSpring.addTarget(cSpring, axis); 2213 } 2214 newLeading.clear(); 2215 newLeadingPadding.clear(); 2216 counter = indexOfNextNonZeroSpring(counter + 1, false); 2217 if (counter == springs.size()) { 2218 // Last Spring, add it to trailing 2219 trailing.add(cSpring); 2220 } else { 2221 // Not that last Spring, add it to leading 2222 newLeading.add(cSpring); 2223 } 2224 } else if (spring instanceof Group) { 2225 // Forward call to child Group 2226 if (newTrailing == null) { 2227 newTrailing = new ArrayList<ComponentSpring>(1); 2228 } else { 2229 newTrailing.clear(); 2230 } 2231 newTrailingPadding.clear(); 2232 ((Group)spring).insertAutopadding(axis, 2233 newLeadingPadding, newTrailingPadding, 2234 newLeading, newTrailing, insert); 2235 newLeading.clear(); 2236 newLeadingPadding.clear(); 2237 counter = indexOfNextNonZeroSpring( 2238 counter + 1, (newTrailing.size() == 0)); 2239 if (counter == springs.size()) { 2240 trailing.addAll(newTrailing); 2241 trailingPadding.addAll(newTrailingPadding); 2242 } else { 2243 newLeading.addAll(newTrailing); 2244 newLeadingPadding.addAll(newTrailingPadding); 2245 } 2246 } else { 2247 // Gap 2248 newLeadingPadding.clear(); 2249 newLeading.clear(); 2250 counter++; 2251 } 2252 } 2253 } 2254 } 2255 2256 int getBaseline() { 2257 if (baselineSpring != null) { 2258 int baseline = baselineSpring.getBaseline(); 2259 if (baseline >= 0) { 2260 int size = 0; 2261 for (Spring spring : springs) { 2262 if (spring == baselineSpring) { 2263 return size + baseline; 2264 } else { 2265 size += spring.getPreferredSize(VERTICAL); 2266 } 2267 } 2268 } 2269 } 2270 return -1; 2271 } 2272 2273 BaselineResizeBehavior getBaselineResizeBehavior() { 2274 if (isResizable(VERTICAL)) { 2275 if (!baselineSpring.isResizable(VERTICAL)) { 2276 // Spring to use for baseline isn't resizable. In this case 2277 // baseline resize behavior can be determined based on how 2278 // preceding springs resize. 2279 boolean leadingResizable = false; 2280 for (Spring spring : springs) { 2281 if (spring == baselineSpring) { 2282 break; 2283 } else if (spring.isResizable(VERTICAL)) { 2284 leadingResizable = true; 2285 break; 2286 } 2287 } 2288 boolean trailingResizable = false; 2289 for (int i = springs.size() - 1; i >= 0; i--) { 2290 Spring spring = springs.get(i); 2291 if (spring == baselineSpring) { 2292 break; 2293 } 2294 if (spring.isResizable(VERTICAL)) { 2295 trailingResizable = true; 2296 break; 2297 } 2298 } 2299 if (leadingResizable && !trailingResizable) { 2300 return BaselineResizeBehavior.CONSTANT_DESCENT; 2301 } else if (!leadingResizable && trailingResizable) { 2302 return BaselineResizeBehavior.CONSTANT_ASCENT; 2303 } 2304 // If we get here, both leading and trailing springs are 2305 // resizable. Fall through to OTHER. 2306 } else { 2307 BaselineResizeBehavior brb = baselineSpring.getBaselineResizeBehavior(); 2308 if (brb == BaselineResizeBehavior.CONSTANT_ASCENT) { 2309 for (Spring spring : springs) { 2310 if (spring == baselineSpring) { 2311 return BaselineResizeBehavior.CONSTANT_ASCENT; 2312 } 2313 if (spring.isResizable(VERTICAL)) { 2314 return BaselineResizeBehavior.OTHER; 2315 } 2316 } 2317 } else if (brb == BaselineResizeBehavior.CONSTANT_DESCENT) { 2318 for (int i = springs.size() - 1; i >= 0; i--) { 2319 Spring spring = springs.get(i); 2320 if (spring == baselineSpring) { 2321 return BaselineResizeBehavior.CONSTANT_DESCENT; 2322 } 2323 if (spring.isResizable(VERTICAL)) { 2324 return BaselineResizeBehavior.OTHER; 2325 } 2326 } 2327 } 2328 } 2329 return BaselineResizeBehavior.OTHER; 2330 } 2331 // Not resizable, treat as constant_ascent 2332 return BaselineResizeBehavior.CONSTANT_ASCENT; 2333 } 2334 2335 private void checkPreferredGapValues(int pref, int max) { 2336 if ((pref < 0 && pref != DEFAULT_SIZE && pref != PREFERRED_SIZE) || 2337 (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE)|| 2338 (pref >= 0 && max >= 0 && pref > max)) { 2339 throw new IllegalArgumentException( 2340 "Pref and max must be either DEFAULT_SIZE, " + 2341 "PREFERRED_SIZE, or >= 0 and pref <= max"); 2342 } 2343 } 2344 } 2345 2346 2347 /** 2348 * Used by SequentialGroup in calculating resizability of springs. 2349 */ 2350 private static final class SpringDelta implements Comparable<SpringDelta> { 2351 // Original index. 2352 public final int index; 2353 // Delta, one of pref - min or max - pref. 2354 public int delta; 2355 2356 public SpringDelta(int index, int delta) { 2357 this.index = index; 2358 this.delta = delta; 2359 } 2360 2361 public int compareTo(SpringDelta o) { 2362 return delta - o.delta; 2363 } 2364 2365 public String toString() { 2366 return super.toString() + "[index=" + index + ", delta=" + 2367 delta + "]"; 2368 } 2369 } 2370 2371 2372 /** 2373 * A {@code Group} that aligns and sizes it's children. 2374 * {@code ParallelGroup} aligns it's children in 2375 * four possible ways: along the baseline, centered, anchored to the 2376 * leading edge, or anchored to the trailing edge. 2377 * <h3>Baseline</h3> 2378 * A {@code ParallelGroup} that aligns it's children along the 2379 * baseline must first decide where the baseline is 2380 * anchored. The baseline can either be anchored to the top, or 2381 * anchored to the bottom of the group. That is, the distance between the 2382 * baseline and the beginning of the group can be a constant 2383 * distance, or the distance between the end of the group and the 2384 * baseline can be a constant distance. The possible choices 2385 * correspond to the {@code BaselineResizeBehavior} constants 2386 * {@link 2387 * java.awt.Component.BaselineResizeBehavior#CONSTANT_ASCENT CONSTANT_ASCENT} and 2388 * {@link 2389 * java.awt.Component.BaselineResizeBehavior#CONSTANT_DESCENT CONSTANT_DESCENT}. 2390 * <p> 2391 * The baseline anchor may be explicitly specified by the 2392 * {@code createBaselineGroup} method, or determined based on the elements. 2393 * If not explicitly specified, the baseline will be anchored to 2394 * the bottom if all the elements with a baseline, and that are 2395 * aligned to the baseline, have a baseline resize behavior of 2396 * {@code CONSTANT_DESCENT}; otherwise the baseline is anchored to the top 2397 * of the group. 2398 * <p> 2399 * Elements aligned to the baseline are resizable if they have have 2400 * a baseline resize behavior of {@code CONSTANT_ASCENT} or 2401 * {@code CONSTANT_DESCENT}. Elements with a baseline resize 2402 * behavior of {@code OTHER} or {@code CENTER_OFFSET} are not resizable. 2403 * <p> 2404 * The baseline is calculated based on the preferred height of each 2405 * of the elements that have a baseline. The baseline is 2406 * calculated using the following algorithm: 2407 * {@code max(maxNonBaselineHeight, maxAscent + maxDescent)}, where the 2408 * {@code maxNonBaselineHeight} is the maximum height of all elements 2409 * that do not have a baseline, or are not aligned along the baseline. 2410 * {@code maxAscent} is the maximum ascent (baseline) of all elements that 2411 * have a baseline and are aligned along the baseline. 2412 * {@code maxDescent} is the maximum descent (preferred height - baseline) 2413 * of all elements that have a baseline and are aligned along the baseline. 2414 * <p> 2415 * A {@code ParallelGroup} that aligns it's elements along the baseline 2416 * is only useful along the vertical axis. If you create a 2417 * baseline group and use it along the horizontal axis an 2418 * {@code IllegalStateException} is thrown when you ask 2419 * {@code GroupLayout} for the minimum, preferred or maximum size or 2420 * attempt to layout the components. 2421 * <p> 2422 * Elements that are not aligned to the baseline and smaller than the size 2423 * of the {@code ParallelGroup} are positioned in one of three 2424 * ways: centered, anchored to the leading edge, or anchored to the 2425 * trailing edge. 2426 * 2427 * <h3>Non-baseline {@code ParallelGroup}</h3> 2428 * {@code ParallelGroup}s created with an alignment other than 2429 * {@code BASELINE} align elements that are smaller than the size 2430 * of the group in one of three ways: centered, anchored to the 2431 * leading edge, or anchored to the trailing edge. 2432 * <p> 2433 * The leading edge is based on the axis and {@code 2434 * ComponentOrientation}. For the vertical axis the top edge is 2435 * always the leading edge, and the bottom edge is always the 2436 * trailing edge. When the {@code ComponentOrientation} is {@code 2437 * LEFT_TO_RIGHT}, the leading edge is the left edge and the 2438 * trailing edge the right edge. A {@code ComponentOrientation} of 2439 * {@code RIGHT_TO_LEFT} flips the left and right edges. Child 2440 * elements are aligned based on the specified alignment the 2441 * element was added with. If you do not specify an alignment, the 2442 * alignment specified for the {@code ParallelGroup} is used. 2443 * <p> 2444 * To align elements along the baseline you {@code createBaselineGroup}, 2445 * or {@code createParallelGroup} with an alignment of {@code BASELINE}. 2446 * If the group was not created with a baseline alignment, and you attempt 2447 * to add an element specifying a baseline alignment, an 2448 * {@code IllegalArgumentException} is thrown. 2449 * 2450 * @see #createParallelGroup() 2451 * @see #createBaselineGroup(boolean,boolean) 2452 * @since 1.6 2453 */ 2454 public class ParallelGroup extends Group { 2455 // How children are layed out. 2456 private final Alignment childAlignment; 2457 // Whether or not we're resizable. 2458 private final boolean resizable; 2459 2460 ParallelGroup(Alignment childAlignment, boolean resizable) { 2461 this.childAlignment = childAlignment; 2462 this.resizable = resizable; 2463 } 2464 2465 /** 2466 * {@inheritDoc} 2467 */ 2468 public ParallelGroup addGroup(Group group) { 2469 return (ParallelGroup)super.addGroup(group); 2470 } 2471 2472 /** 2473 * {@inheritDoc} 2474 */ 2475 public ParallelGroup addComponent(Component component) { 2476 return (ParallelGroup)super.addComponent(component); 2477 } 2478 2479 /** 2480 * {@inheritDoc} 2481 */ 2482 public ParallelGroup addComponent(Component component, int min, int pref, 2483 int max) { 2484 return (ParallelGroup)super.addComponent(component, min, pref, max); 2485 } 2486 2487 /** 2488 * {@inheritDoc} 2489 */ 2490 public ParallelGroup addGap(int pref) { 2491 return (ParallelGroup)super.addGap(pref); 2492 } 2493 2494 /** 2495 * {@inheritDoc} 2496 */ 2497 public ParallelGroup addGap(int min, int pref, int max) { 2498 return (ParallelGroup)super.addGap(min, pref, max); 2499 } 2500 2501 /** 2502 * Adds a {@code Group} to this {@code ParallelGroup} with the 2503 * specified alignment. If the child is smaller than the 2504 * {@code Group} it is aligned based on the specified 2505 * alignment. 2506 * 2507 * @param alignment the alignment 2508 * @param group the {@code Group} to add 2509 * @return this {@code ParallelGroup} 2510 * @throws IllegalArgumentException if {@code alignment} is 2511 * {@code null} 2512 */ 2513 public ParallelGroup addGroup(Alignment alignment, Group group) { 2514 checkChildAlignment(alignment); 2515 group.setAlignment(alignment); 2516 return (ParallelGroup)addSpring(group); 2517 } 2518 2519 /** 2520 * Adds a {@code Component} to this {@code ParallelGroup} with 2521 * the specified alignment. 2522 * 2523 * @param alignment the alignment 2524 * @param component the {@code Component} to add 2525 * @return this {@code Group} 2526 * @throws IllegalArgumentException if {@code alignment} is 2527 * {@code null} 2528 */ 2529 public ParallelGroup addComponent(Component component, 2530 Alignment alignment) { 2531 return addComponent(component, alignment, DEFAULT_SIZE, DEFAULT_SIZE, 2532 DEFAULT_SIZE); 2533 } 2534 2535 /** 2536 * Adds a {@code Component} to this {@code ParallelGroup} with the 2537 * specified alignment and size. 2538 * 2539 * @param alignment the alignment 2540 * @param component the {@code Component} to add 2541 * @param min the minimum size 2542 * @param pref the preferred size 2543 * @param max the maximum size 2544 * @throws IllegalArgumentException if {@code alignment} is 2545 * {@code null} 2546 * @return this {@code Group} 2547 */ 2548 public ParallelGroup addComponent(Component component, 2549 Alignment alignment, int min, int pref, int max) { 2550 checkChildAlignment(alignment); 2551 ComponentSpring spring = new ComponentSpring(component, 2552 min, pref, max); 2553 spring.setAlignment(alignment); 2554 return (ParallelGroup)addSpring(spring); 2555 } 2556 2557 boolean isResizable() { 2558 return resizable; 2559 } 2560 2561 int operator(int a, int b) { 2562 return Math.max(a, b); 2563 } 2564 2565 int calculateMinimumSize(int axis) { 2566 if (!isResizable()) { 2567 return getPreferredSize(axis); 2568 } 2569 return super.calculateMinimumSize(axis); 2570 } 2571 2572 int calculateMaximumSize(int axis) { 2573 if (!isResizable()) { 2574 return getPreferredSize(axis); 2575 } 2576 return super.calculateMaximumSize(axis); 2577 } 2578 2579 void setValidSize(int axis, int origin, int size) { 2580 for (Spring spring : springs) { 2581 setChildSize(spring, axis, origin, size); 2582 } 2583 } 2584 2585 void setChildSize(Spring spring, int axis, int origin, int size) { 2586 Alignment alignment = spring.getAlignment(); 2587 int springSize = Math.min( 2588 Math.max(spring.getMinimumSize(axis), size), 2589 spring.getMaximumSize(axis)); 2590 if (alignment == null) { 2591 alignment = childAlignment; 2592 } 2593 switch (alignment) { 2594 case TRAILING: 2595 spring.setSize(axis, origin + size - springSize, 2596 springSize); 2597 break; 2598 case CENTER: 2599 spring.setSize(axis, origin + 2600 (size - springSize) / 2,springSize); 2601 break; 2602 default: // LEADING, or BASELINE 2603 spring.setSize(axis, origin, springSize); 2604 break; 2605 } 2606 } 2607 2608 @Override 2609 void insertAutopadding(int axis, 2610 List<AutoPreferredGapSpring> leadingPadding, 2611 List<AutoPreferredGapSpring> trailingPadding, 2612 List<ComponentSpring> leading, List<ComponentSpring> trailing, 2613 boolean insert) { 2614 for (Spring spring : springs) { 2615 if (spring instanceof ComponentSpring) { 2616 if (((ComponentSpring)spring).isVisible()) { 2617 for (AutoPreferredGapSpring gapSpring : 2618 leadingPadding) { 2619 gapSpring.addTarget((ComponentSpring)spring, axis); 2620 } 2621 trailing.add((ComponentSpring)spring); 2622 } 2623 } else if (spring instanceof Group) { 2624 ((Group)spring).insertAutopadding(axis, leadingPadding, 2625 trailingPadding, leading, trailing, insert); 2626 } else if (spring instanceof AutoPreferredGapSpring) { 2627 ((AutoPreferredGapSpring)spring).setSources(leading); 2628 trailingPadding.add((AutoPreferredGapSpring)spring); 2629 } 2630 } 2631 } 2632 2633 private void checkChildAlignment(Alignment alignment) { 2634 checkChildAlignment(alignment, (this instanceof BaselineGroup)); 2635 } 2636 2637 private void checkChildAlignment(Alignment alignment, 2638 boolean allowsBaseline) { 2639 if (alignment == null) { 2640 throw new IllegalArgumentException("Alignment must be non-null"); 2641 } 2642 if (!allowsBaseline && alignment == Alignment.BASELINE) { 2643 throw new IllegalArgumentException("Alignment must be one of:" + 2644 "LEADING, TRAILING or CENTER"); 2645 } 2646 } 2647 } 2648 2649 2650 /** 2651 * An extension of {@code ParallelGroup} that aligns its 2652 * constituent {@code Spring}s along the baseline. 2653 */ 2654 private class BaselineGroup extends ParallelGroup { 2655 // Whether or not all child springs have a baseline 2656 private boolean allSpringsHaveBaseline; 2657 2658 // max(spring.getBaseline()) of all springs aligned along the baseline 2659 // that have a baseline 2660 private int prefAscent; 2661 2662 // max(spring.getPreferredSize().height - spring.getBaseline()) of all 2663 // springs aligned along the baseline that have a baseline 2664 private int prefDescent; 2665 2666 // Whether baselineAnchoredToTop was explicitly set 2667 private boolean baselineAnchorSet; 2668 2669 // Whether the baseline is anchored to the top or the bottom. 2670 // If anchored to the top the baseline is always at prefAscent, 2671 // otherwise the baseline is at (height - prefDescent) 2672 private boolean baselineAnchoredToTop; 2673 2674 // Whether or not the baseline has been calculated. 2675 private boolean calcedBaseline; 2676 2677 BaselineGroup(boolean resizable) { 2678 super(Alignment.LEADING, resizable); 2679 prefAscent = prefDescent = -1; 2680 calcedBaseline = false; 2681 } 2682 2683 BaselineGroup(boolean resizable, boolean baselineAnchoredToTop) { 2684 this(resizable); 2685 this.baselineAnchoredToTop = baselineAnchoredToTop; 2686 baselineAnchorSet = true; 2687 } 2688 2689 void unset() { 2690 super.unset(); 2691 prefAscent = prefDescent = -1; 2692 calcedBaseline = false; 2693 } 2694 2695 void setValidSize(int axis, int origin, int size) { 2696 checkAxis(axis); 2697 if (prefAscent == -1) { 2698 super.setValidSize(axis, origin, size); 2699 } else { 2700 // do baseline layout 2701 baselineLayout(origin, size); 2702 } 2703 } 2704 2705 int calculateSize(int axis, int type) { 2706 checkAxis(axis); 2707 if (!calcedBaseline) { 2708 calculateBaselineAndResizeBehavior(); 2709 } 2710 if (type == MIN_SIZE) { 2711 return calculateMinSize(); 2712 } 2713 if (type == MAX_SIZE) { 2714 return calculateMaxSize(); 2715 } 2716 if (allSpringsHaveBaseline) { 2717 return prefAscent + prefDescent; 2718 } 2719 return Math.max(prefAscent + prefDescent, 2720 super.calculateSize(axis, type)); 2721 } 2722 2723 private void calculateBaselineAndResizeBehavior() { 2724 // calculate baseline 2725 prefAscent = 0; 2726 prefDescent = 0; 2727 int baselineSpringCount = 0; 2728 BaselineResizeBehavior resizeBehavior = null; 2729 for (Spring spring : springs) { 2730 if (spring.getAlignment() == null || 2731 spring.getAlignment() == Alignment.BASELINE) { 2732 int baseline = spring.getBaseline(); 2733 if (baseline >= 0) { 2734 if (spring.isResizable(VERTICAL)) { 2735 BaselineResizeBehavior brb = spring. 2736 getBaselineResizeBehavior(); 2737 if (resizeBehavior == null) { 2738 resizeBehavior = brb; 2739 } else if (brb != resizeBehavior) { 2740 resizeBehavior = BaselineResizeBehavior. 2741 CONSTANT_ASCENT; 2742 } 2743 } 2744 prefAscent = Math.max(prefAscent, baseline); 2745 prefDescent = Math.max(prefDescent, spring. 2746 getPreferredSize(VERTICAL) - baseline); 2747 baselineSpringCount++; 2748 } 2749 } 2750 } 2751 if (!baselineAnchorSet) { 2752 if (resizeBehavior == BaselineResizeBehavior.CONSTANT_DESCENT){ 2753 this.baselineAnchoredToTop = false; 2754 } else { 2755 this.baselineAnchoredToTop = true; 2756 } 2757 } 2758 allSpringsHaveBaseline = (baselineSpringCount == springs.size()); 2759 calcedBaseline = true; 2760 } 2761 2762 private int calculateMaxSize() { 2763 int maxAscent = prefAscent; 2764 int maxDescent = prefDescent; 2765 int nonBaselineMax = 0; 2766 for (Spring spring : springs) { 2767 int baseline; 2768 int springMax = spring.getMaximumSize(VERTICAL); 2769 if ((spring.getAlignment() == null || 2770 spring.getAlignment() == Alignment.BASELINE) && 2771 (baseline = spring.getBaseline()) >= 0) { 2772 int springPref = spring.getPreferredSize(VERTICAL); 2773 if (springPref != springMax) { 2774 switch (spring.getBaselineResizeBehavior()) { 2775 case CONSTANT_ASCENT: 2776 if (baselineAnchoredToTop) { 2777 maxDescent = Math.max(maxDescent, 2778 springMax - baseline); 2779 } 2780 break; 2781 case CONSTANT_DESCENT: 2782 if (!baselineAnchoredToTop) { 2783 maxAscent = Math.max(maxAscent, 2784 springMax - springPref + baseline); 2785 } 2786 break; 2787 default: // CENTER_OFFSET and OTHER, not resizable 2788 break; 2789 } 2790 } 2791 } else { 2792 // Not aligned along the baseline, or no baseline. 2793 nonBaselineMax = Math.max(nonBaselineMax, springMax); 2794 } 2795 } 2796 return Math.max(nonBaselineMax, maxAscent + maxDescent); 2797 } 2798 2799 private int calculateMinSize() { 2800 int minAscent = 0; 2801 int minDescent = 0; 2802 int nonBaselineMin = 0; 2803 if (baselineAnchoredToTop) { 2804 minAscent = prefAscent; 2805 } else { 2806 minDescent = prefDescent; 2807 } 2808 for (Spring spring : springs) { 2809 int springMin = spring.getMinimumSize(VERTICAL); 2810 int baseline; 2811 if ((spring.getAlignment() == null || 2812 spring.getAlignment() == Alignment.BASELINE) && 2813 (baseline = spring.getBaseline()) >= 0) { 2814 int springPref = spring.getPreferredSize(VERTICAL); 2815 BaselineResizeBehavior brb = spring. 2816 getBaselineResizeBehavior(); 2817 switch (brb) { 2818 case CONSTANT_ASCENT: 2819 if (baselineAnchoredToTop) { 2820 minDescent = Math.max(springMin - baseline, 2821 minDescent); 2822 } else { 2823 minAscent = Math.max(baseline, minAscent); 2824 } 2825 break; 2826 case CONSTANT_DESCENT: 2827 if (!baselineAnchoredToTop) { 2828 minAscent = Math.max( 2829 baseline - (springPref - springMin), 2830 minAscent); 2831 } else { 2832 minDescent = Math.max(springPref - baseline, 2833 minDescent); 2834 } 2835 break; 2836 default: 2837 // CENTER_OFFSET and OTHER are !resizable, use 2838 // the preferred size. 2839 minAscent = Math.max(baseline, minAscent); 2840 minDescent = Math.max(springPref - baseline, 2841 minDescent); 2842 break; 2843 } 2844 } else { 2845 // Not aligned along the baseline, or no baseline. 2846 nonBaselineMin = Math.max(nonBaselineMin, springMin); 2847 } 2848 } 2849 return Math.max(nonBaselineMin, minAscent + minDescent); 2850 } 2851 2852 /** 2853 * Lays out springs that have a baseline along the baseline. All 2854 * others are centered. 2855 */ 2856 private void baselineLayout(int origin, int size) { 2857 int ascent; 2858 int descent; 2859 if (baselineAnchoredToTop) { 2860 ascent = prefAscent; 2861 descent = size - ascent; 2862 } else { 2863 ascent = size - prefDescent; 2864 descent = prefDescent; 2865 } 2866 for (Spring spring : springs) { 2867 Alignment alignment = spring.getAlignment(); 2868 if (alignment == null || alignment == Alignment.BASELINE) { 2869 int baseline = spring.getBaseline(); 2870 if (baseline >= 0) { 2871 int springMax = spring.getMaximumSize(VERTICAL); 2872 int springPref = spring.getPreferredSize(VERTICAL); 2873 int height = springPref; 2874 int y; 2875 switch(spring.getBaselineResizeBehavior()) { 2876 case CONSTANT_ASCENT: 2877 y = origin + ascent - baseline; 2878 height = Math.min(descent, springMax - 2879 baseline) + baseline; 2880 break; 2881 case CONSTANT_DESCENT: 2882 height = Math.min(ascent, springMax - 2883 springPref + baseline) + 2884 (springPref - baseline); 2885 y = origin + ascent + 2886 (springPref - baseline) - height; 2887 break; 2888 default: // CENTER_OFFSET & OTHER, not resizable 2889 y = origin + ascent - baseline; 2890 break; 2891 } 2892 spring.setSize(VERTICAL, y, height); 2893 } else { 2894 setChildSize(spring, VERTICAL, origin, size); 2895 } 2896 } else { 2897 setChildSize(spring, VERTICAL, origin, size); 2898 } 2899 } 2900 } 2901 2902 int getBaseline() { 2903 if (springs.size() > 1) { 2904 // Force the baseline to be calculated 2905 getPreferredSize(VERTICAL); 2906 return prefAscent; 2907 } else if (springs.size() == 1) { 2908 return springs.get(0).getBaseline(); 2909 } 2910 return -1; 2911 } 2912 2913 BaselineResizeBehavior getBaselineResizeBehavior() { 2914 if (springs.size() == 1) { 2915 return springs.get(0).getBaselineResizeBehavior(); 2916 } 2917 if (baselineAnchoredToTop) { 2918 return BaselineResizeBehavior.CONSTANT_ASCENT; 2919 } 2920 return BaselineResizeBehavior.CONSTANT_DESCENT; 2921 } 2922 2923 // If the axis is VERTICAL, throws an IllegalStateException 2924 private void checkAxis(int axis) { 2925 if (axis == HORIZONTAL) { 2926 throw new IllegalStateException( 2927 "Baseline must be used along vertical axis"); 2928 } 2929 } 2930 } 2931 2932 2933 private final class ComponentSpring extends Spring { 2934 private Component component; 2935 private int origin; 2936 2937 // min/pref/max are either a value >= 0 or one of 2938 // DEFAULT_SIZE or PREFERRED_SIZE 2939 private final int min; 2940 private final int pref; 2941 private final int max; 2942 2943 // Baseline for the component, computed as necessary. 2944 private int baseline = -1; 2945 2946 // Whether or not the size has been requested yet. 2947 private boolean installed; 2948 2949 private ComponentSpring(Component component, int min, int pref, 2950 int max) { 2951 this.component = component; 2952 if (component == null) { 2953 throw new IllegalArgumentException( 2954 "Component must be non-null"); 2955 } 2956 2957 checkSize(min, pref, max, true); 2958 2959 this.min = min; 2960 this.max = max; 2961 this.pref = pref; 2962 2963 // getComponentInfo makes sure component is a child of the 2964 // Container GroupLayout is the LayoutManager for. 2965 getComponentInfo(component); 2966 } 2967 2968 int calculateMinimumSize(int axis) { 2969 if (isLinked(axis)) { 2970 return getLinkSize(axis, MIN_SIZE); 2971 } 2972 return calculateNonlinkedMinimumSize(axis); 2973 } 2974 2975 int calculatePreferredSize(int axis) { 2976 if (isLinked(axis)) { 2977 return getLinkSize(axis, PREF_SIZE); 2978 } 2979 int min = getMinimumSize(axis); 2980 int pref = calculateNonlinkedPreferredSize(axis); 2981 int max = getMaximumSize(axis); 2982 return Math.min(max, Math.max(min, pref)); 2983 } 2984 2985 int calculateMaximumSize(int axis) { 2986 if (isLinked(axis)) { 2987 return getLinkSize(axis, MAX_SIZE); 2988 } 2989 return Math.max(getMinimumSize(axis), 2990 calculateNonlinkedMaximumSize(axis)); 2991 } 2992 2993 boolean isVisible() { 2994 return getComponentInfo(getComponent()).isVisible(); 2995 } 2996 2997 int calculateNonlinkedMinimumSize(int axis) { 2998 if (!isVisible()) { 2999 return 0; 3000 } 3001 if (min >= 0) { 3002 return min; 3003 } 3004 if (min == PREFERRED_SIZE) { 3005 return calculateNonlinkedPreferredSize(axis); 3006 } 3007 assert (min == DEFAULT_SIZE); 3008 return getSizeAlongAxis(axis, component.getMinimumSize()); 3009 } 3010 3011 int calculateNonlinkedPreferredSize(int axis) { 3012 if (!isVisible()) { 3013 return 0; 3014 } 3015 if (pref >= 0) { 3016 return pref; 3017 } 3018 assert (pref == DEFAULT_SIZE || pref == PREFERRED_SIZE); 3019 return getSizeAlongAxis(axis, component.getPreferredSize()); 3020 } 3021 3022 int calculateNonlinkedMaximumSize(int axis) { 3023 if (!isVisible()) { 3024 return 0; 3025 } 3026 if (max >= 0) { 3027 return max; 3028 } 3029 if (max == PREFERRED_SIZE) { 3030 return calculateNonlinkedPreferredSize(axis); 3031 } 3032 assert (max == DEFAULT_SIZE); 3033 return getSizeAlongAxis(axis, component.getMaximumSize()); 3034 } 3035 3036 private int getSizeAlongAxis(int axis, Dimension size) { 3037 return (axis == HORIZONTAL) ? size.width : size.height; 3038 } 3039 3040 private int getLinkSize(int axis, int type) { 3041 if (!isVisible()) { 3042 return 0; 3043 } 3044 ComponentInfo ci = getComponentInfo(component); 3045 return ci.getLinkSize(axis, type); 3046 } 3047 3048 void setSize(int axis, int origin, int size) { 3049 super.setSize(axis, origin, size); 3050 this.origin = origin; 3051 if (size == UNSET) { 3052 baseline = -1; 3053 } 3054 } 3055 3056 int getOrigin() { 3057 return origin; 3058 } 3059 3060 void setComponent(Component component) { 3061 this.component = component; 3062 } 3063 3064 Component getComponent() { 3065 return component; 3066 } 3067 3068 int getBaseline() { 3069 if (baseline == -1) { 3070 Spring horizontalSpring = getComponentInfo(component). 3071 horizontalSpring; 3072 int width = horizontalSpring.getPreferredSize(HORIZONTAL); 3073 int height = getPreferredSize(VERTICAL); 3074 if (width > 0 && height > 0) { 3075 baseline = component.getBaseline(width, height); 3076 } 3077 } 3078 return baseline; 3079 } 3080 3081 BaselineResizeBehavior getBaselineResizeBehavior() { 3082 return getComponent().getBaselineResizeBehavior(); 3083 } 3084 3085 private boolean isLinked(int axis) { 3086 return getComponentInfo(component).isLinked(axis); 3087 } 3088 3089 void installIfNecessary(int axis) { 3090 if (!installed) { 3091 installed = true; 3092 if (axis == HORIZONTAL) { 3093 getComponentInfo(component).horizontalSpring = this; 3094 } else { 3095 getComponentInfo(component).verticalSpring = this; 3096 } 3097 } 3098 } 3099 3100 @Override 3101 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3102 return !isVisible(); 3103 } 3104 } 3105 3106 3107 /** 3108 * Spring representing the preferred distance between two components. 3109 */ 3110 private class PreferredGapSpring extends Spring { 3111 private final JComponent source; 3112 private final JComponent target; 3113 private final ComponentPlacement type; 3114 private final int pref; 3115 private final int max; 3116 3117 PreferredGapSpring(JComponent source, JComponent target, 3118 ComponentPlacement type, int pref, int max) { 3119 this.source = source; 3120 this.target = target; 3121 this.type = type; 3122 this.pref = pref; 3123 this.max = max; 3124 } 3125 3126 int calculateMinimumSize(int axis) { 3127 return getPadding(axis); 3128 } 3129 3130 int calculatePreferredSize(int axis) { 3131 if (pref == DEFAULT_SIZE || pref == PREFERRED_SIZE) { 3132 return getMinimumSize(axis); 3133 } 3134 int min = getMinimumSize(axis); 3135 int max = getMaximumSize(axis); 3136 return Math.min(max, Math.max(min, pref)); 3137 } 3138 3139 int calculateMaximumSize(int axis) { 3140 if (max == PREFERRED_SIZE || max == DEFAULT_SIZE) { 3141 return getPadding(axis); 3142 } 3143 return Math.max(getMinimumSize(axis), max); 3144 } 3145 3146 private int getPadding(int axis) { 3147 int position; 3148 if (axis == HORIZONTAL) { 3149 position = SwingConstants.EAST; 3150 } else { 3151 position = SwingConstants.SOUTH; 3152 } 3153 return getLayoutStyle0().getPreferredGap(source, 3154 target, type, position, host); 3155 } 3156 3157 @Override 3158 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3159 return false; 3160 } 3161 } 3162 3163 3164 /** 3165 * Spring represented a certain amount of space. 3166 */ 3167 private class GapSpring extends Spring { 3168 private final int min; 3169 private final int pref; 3170 private final int max; 3171 3172 GapSpring(int min, int pref, int max) { 3173 checkSize(min, pref, max, false); 3174 this.min = min; 3175 this.pref = pref; 3176 this.max = max; 3177 } 3178 3179 int calculateMinimumSize(int axis) { 3180 if (min == PREFERRED_SIZE) { 3181 return getPreferredSize(axis); 3182 } 3183 return min; 3184 } 3185 3186 int calculatePreferredSize(int axis) { 3187 return pref; 3188 } 3189 3190 int calculateMaximumSize(int axis) { 3191 if (max == PREFERRED_SIZE) { 3192 return getPreferredSize(axis); 3193 } 3194 return max; 3195 } 3196 3197 @Override 3198 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3199 return false; 3200 } 3201 } 3202 3203 3204 /** 3205 * Spring reprensenting the distance between any number of sources and 3206 * targets. The targets and sources are computed during layout. An 3207 * instance of this can either be dynamically created when 3208 * autocreatePadding is true, or explicitly created by the developer. 3209 */ 3210 private class AutoPreferredGapSpring extends Spring { 3211 List<ComponentSpring> sources; 3212 ComponentSpring source; 3213 private List<AutoPreferredGapMatch> matches; 3214 int size; 3215 int lastSize; 3216 private final int pref; 3217 private final int max; 3218 // Type of gap 3219 private ComponentPlacement type; 3220 private boolean userCreated; 3221 3222 private AutoPreferredGapSpring() { 3223 this.pref = PREFERRED_SIZE; 3224 this.max = PREFERRED_SIZE; 3225 this.type = ComponentPlacement.RELATED; 3226 } 3227 3228 AutoPreferredGapSpring(int pref, int max) { 3229 this.pref = pref; 3230 this.max = max; 3231 } 3232 3233 AutoPreferredGapSpring(ComponentPlacement type, int pref, int max) { 3234 this.type = type; 3235 this.pref = pref; 3236 this.max = max; 3237 this.userCreated = true; 3238 } 3239 3240 public void setSource(ComponentSpring source) { 3241 this.source = source; 3242 } 3243 3244 public void setSources(List<ComponentSpring> sources) { 3245 this.sources = new ArrayList<ComponentSpring>(sources); 3246 } 3247 3248 public void setUserCreated(boolean userCreated) { 3249 this.userCreated = userCreated; 3250 } 3251 3252 public boolean getUserCreated() { 3253 return userCreated; 3254 } 3255 3256 void unset() { 3257 lastSize = getSize(); 3258 super.unset(); 3259 size = 0; 3260 } 3261 3262 public void reset() { 3263 size = 0; 3264 sources = null; 3265 source = null; 3266 matches = null; 3267 } 3268 3269 public void calculatePadding(int axis) { 3270 size = UNSET; 3271 int maxPadding = UNSET; 3272 if (matches != null) { 3273 LayoutStyle p = getLayoutStyle0(); 3274 int position; 3275 if (axis == HORIZONTAL) { 3276 if (isLeftToRight()) { 3277 position = SwingConstants.EAST; 3278 } else { 3279 position = SwingConstants.WEST; 3280 } 3281 } else { 3282 position = SwingConstants.SOUTH; 3283 } 3284 for (int i = matches.size() - 1; i >= 0; i--) { 3285 AutoPreferredGapMatch match = matches.get(i); 3286 maxPadding = Math.max(maxPadding, 3287 calculatePadding(p, position, match.source, 3288 match.target)); 3289 } 3290 } 3291 if (size == UNSET) { 3292 size = 0; 3293 } 3294 if (maxPadding == UNSET) { 3295 maxPadding = 0; 3296 } 3297 if (lastSize != UNSET) { 3298 size += Math.min(maxPadding, lastSize); 3299 } 3300 } 3301 3302 private int calculatePadding(LayoutStyle p, int position, 3303 ComponentSpring source, 3304 ComponentSpring target) { 3305 int delta = target.getOrigin() - (source.getOrigin() + 3306 source.getSize()); 3307 if (delta >= 0) { 3308 int padding; 3309 if ((source.getComponent() instanceof JComponent) && 3310 (target.getComponent() instanceof JComponent)) { 3311 padding = p.getPreferredGap( 3312 (JComponent)source.getComponent(), 3313 (JComponent)target.getComponent(), type, position, 3314 host); 3315 } else { 3316 padding = 10; 3317 } 3318 if (padding > delta) { 3319 size = Math.max(size, padding - delta); 3320 } 3321 return padding; 3322 } 3323 return 0; 3324 } 3325 3326 public void addTarget(ComponentSpring spring, int axis) { 3327 int oAxis = (axis == HORIZONTAL) ? VERTICAL : HORIZONTAL; 3328 if (source != null) { 3329 if (areParallelSiblings(source.getComponent(), 3330 spring.getComponent(), oAxis)) { 3331 addValidTarget(source, spring); 3332 } 3333 } else { 3334 Component component = spring.getComponent(); 3335 for (int counter = sources.size() - 1; counter >= 0; 3336 counter--){ 3337 ComponentSpring source = sources.get(counter); 3338 if (areParallelSiblings(source.getComponent(), 3339 component, oAxis)) { 3340 addValidTarget(source, spring); 3341 } 3342 } 3343 } 3344 } 3345 3346 private void addValidTarget(ComponentSpring source, 3347 ComponentSpring target) { 3348 if (matches == null) { 3349 matches = new ArrayList<AutoPreferredGapMatch>(1); 3350 } 3351 matches.add(new AutoPreferredGapMatch(source, target)); 3352 } 3353 3354 int calculateMinimumSize(int axis) { 3355 return size; 3356 } 3357 3358 int calculatePreferredSize(int axis) { 3359 if (pref == PREFERRED_SIZE || pref == DEFAULT_SIZE) { 3360 return size; 3361 } 3362 return Math.max(size, pref); 3363 } 3364 3365 int calculateMaximumSize(int axis) { 3366 if (max >= 0) { 3367 return Math.max(getPreferredSize(axis), max); 3368 } 3369 return size; 3370 } 3371 3372 String getMatchDescription() { 3373 return (matches == null) ? "" : matches.toString(); 3374 } 3375 3376 public String toString() { 3377 return super.toString() + getMatchDescription(); 3378 } 3379 3380 @Override 3381 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3382 return treatAutopaddingAsZeroSized; 3383 } 3384 } 3385 3386 3387 /** 3388 * Represents two springs that should have autopadding inserted between 3389 * them. 3390 */ 3391 private final static class AutoPreferredGapMatch { 3392 public final ComponentSpring source; 3393 public final ComponentSpring target; 3394 3395 AutoPreferredGapMatch(ComponentSpring source, ComponentSpring target) { 3396 this.source = source; 3397 this.target = target; 3398 } 3399 3400 private String toString(ComponentSpring spring) { 3401 return spring.getComponent().getName(); 3402 } 3403 3404 public String toString() { 3405 return "[" + toString(source) + "-" + toString(target) + "]"; 3406 } 3407 } 3408 3409 3410 /** 3411 * An extension of AutopaddingSpring used for container level padding. 3412 */ 3413 private class ContainerAutoPreferredGapSpring extends 3414 AutoPreferredGapSpring { 3415 private List<ComponentSpring> targets; 3416 3417 ContainerAutoPreferredGapSpring() { 3418 super(); 3419 setUserCreated(true); 3420 } 3421 3422 ContainerAutoPreferredGapSpring(int pref, int max) { 3423 super(pref, max); 3424 setUserCreated(true); 3425 } 3426 3427 public void addTarget(ComponentSpring spring, int axis) { 3428 if (targets == null) { 3429 targets = new ArrayList<ComponentSpring>(1); 3430 } 3431 targets.add(spring); 3432 } 3433 3434 public void calculatePadding(int axis) { 3435 LayoutStyle p = getLayoutStyle0(); 3436 int maxPadding = 0; 3437 int position; 3438 size = 0; 3439 if (targets != null) { 3440 // Leading 3441 if (axis == HORIZONTAL) { 3442 if (isLeftToRight()) { 3443 position = SwingConstants.WEST; 3444 } else { 3445 position = SwingConstants.EAST; 3446 } 3447 } else { 3448 position = SwingConstants.SOUTH; 3449 } 3450 for (int i = targets.size() - 1; i >= 0; i--) { 3451 ComponentSpring targetSpring = targets.get(i); 3452 int padding = 10; 3453 if (targetSpring.getComponent() instanceof JComponent) { 3454 padding = p.getContainerGap( 3455 (JComponent)targetSpring.getComponent(), 3456 position, host); 3457 maxPadding = Math.max(padding, maxPadding); 3458 padding -= targetSpring.getOrigin(); 3459 } else { 3460 maxPadding = Math.max(padding, maxPadding); 3461 } 3462 size = Math.max(size, padding); 3463 } 3464 } else { 3465 // Trailing 3466 if (axis == HORIZONTAL) { 3467 if (isLeftToRight()) { 3468 position = SwingConstants.EAST; 3469 } else { 3470 position = SwingConstants.WEST; 3471 } 3472 } else { 3473 position = SwingConstants.SOUTH; 3474 } 3475 if (sources != null) { 3476 for (int i = sources.size() - 1; i >= 0; i--) { 3477 ComponentSpring sourceSpring = sources.get(i); 3478 maxPadding = Math.max(maxPadding, 3479 updateSize(p, sourceSpring, position)); 3480 } 3481 } else if (source != null) { 3482 maxPadding = updateSize(p, source, position); 3483 } 3484 } 3485 if (lastSize != UNSET) { 3486 size += Math.min(maxPadding, lastSize); 3487 } 3488 } 3489 3490 private int updateSize(LayoutStyle p, ComponentSpring sourceSpring, 3491 int position) { 3492 int padding = 10; 3493 if (sourceSpring.getComponent() instanceof JComponent) { 3494 padding = p.getContainerGap( 3495 (JComponent)sourceSpring.getComponent(), position, 3496 host); 3497 } 3498 int delta = Math.max(0, getParent().getSize() - 3499 sourceSpring.getSize() - sourceSpring.getOrigin()); 3500 size = Math.max(size, padding - delta); 3501 return padding; 3502 } 3503 3504 String getMatchDescription() { 3505 if (targets != null) { 3506 return "leading: " + targets.toString(); 3507 } 3508 if (sources != null) { 3509 return "trailing: " + sources.toString(); 3510 } 3511 return "--"; 3512 } 3513 } 3514 3515 3516 // LinkInfo contains the set of ComponentInfosthat are linked along a 3517 // particular axis. 3518 private static class LinkInfo { 3519 private final int axis; 3520 private final List<ComponentInfo> linked; 3521 private int size; 3522 3523 LinkInfo(int axis) { 3524 linked = new ArrayList<ComponentInfo>(); 3525 size = UNSET; 3526 this.axis = axis; 3527 } 3528 3529 public void add(ComponentInfo child) { 3530 LinkInfo childMaster = child.getLinkInfo(axis, false); 3531 if (childMaster == null) { 3532 linked.add(child); 3533 child.setLinkInfo(axis, this); 3534 } else if (childMaster != this) { 3535 linked.addAll(childMaster.linked); 3536 for (ComponentInfo childInfo : childMaster.linked) { 3537 childInfo.setLinkInfo(axis, this); 3538 } 3539 } 3540 clearCachedSize(); 3541 } 3542 3543 public void remove(ComponentInfo info) { 3544 linked.remove(info); 3545 info.setLinkInfo(axis, null); 3546 if (linked.size() == 1) { 3547 linked.get(0).setLinkInfo(axis, null); 3548 } 3549 clearCachedSize(); 3550 } 3551 3552 public void clearCachedSize() { 3553 size = UNSET; 3554 } 3555 3556 public int getSize(int axis) { 3557 if (size == UNSET) { 3558 size = calculateLinkedSize(axis); 3559 } 3560 return size; 3561 } 3562 3563 private int calculateLinkedSize(int axis) { 3564 int size = 0; 3565 for (ComponentInfo info : linked) { 3566 ComponentSpring spring; 3567 if (axis == HORIZONTAL) { 3568 spring = info.horizontalSpring; 3569 } else { 3570 assert (axis == VERTICAL); 3571 spring = info.verticalSpring; 3572 } 3573 size = Math.max(size, 3574 spring.calculateNonlinkedPreferredSize(axis)); 3575 } 3576 return size; 3577 } 3578 } 3579 3580 /** 3581 * Tracks the horizontal/vertical Springs for a Component. 3582 * This class is also used to handle Springs that have their sizes 3583 * linked. 3584 */ 3585 private class ComponentInfo { 3586 // Component being layed out 3587 private Component component; 3588 3589 ComponentSpring horizontalSpring; 3590 ComponentSpring verticalSpring; 3591 3592 // If the component's size is linked to other components, the 3593 // horizontalMaster and/or verticalMaster reference the group of 3594 // linked components. 3595 private LinkInfo horizontalMaster; 3596 private LinkInfo verticalMaster; 3597 3598 private boolean visible; 3599 private Boolean honorsVisibility; 3600 3601 ComponentInfo(Component component) { 3602 this.component = component; 3603 updateVisibility(); 3604 } 3605 3606 public void dispose() { 3607 // Remove horizontal/vertical springs 3608 removeSpring(horizontalSpring); 3609 horizontalSpring = null; 3610 removeSpring(verticalSpring); 3611 verticalSpring = null; 3612 // Clean up links 3613 if (horizontalMaster != null) { 3614 horizontalMaster.remove(this); 3615 } 3616 if (verticalMaster != null) { 3617 verticalMaster.remove(this); 3618 } 3619 } 3620 3621 void setHonorsVisibility(Boolean honorsVisibility) { 3622 this.honorsVisibility = honorsVisibility; 3623 } 3624 3625 private void removeSpring(Spring spring) { 3626 if (spring != null) { 3627 ((Group)spring.getParent()).springs.remove(spring); 3628 } 3629 } 3630 3631 public boolean isVisible() { 3632 return visible; 3633 } 3634 3635 /** 3636 * Updates the cached visibility. 3637 * 3638 * @return true if the visibility changed 3639 */ 3640 boolean updateVisibility() { 3641 boolean honorsVisibility; 3642 if (this.honorsVisibility == null) { 3643 honorsVisibility = GroupLayout.this.getHonorsVisibility(); 3644 } else { 3645 honorsVisibility = this.honorsVisibility; 3646 } 3647 boolean newVisible = (honorsVisibility) ? 3648 component.isVisible() : true; 3649 if (visible != newVisible) { 3650 visible = newVisible; 3651 return true; 3652 } 3653 return false; 3654 } 3655 3656 public void setBounds(Insets insets, int parentWidth, boolean ltr) { 3657 int x = horizontalSpring.getOrigin(); 3658 int w = horizontalSpring.getSize(); 3659 int y = verticalSpring.getOrigin(); 3660 int h = verticalSpring.getSize(); 3661 3662 if (!ltr) { 3663 x = parentWidth - x - w; 3664 } 3665 component.setBounds(x + insets.left, y + insets.top, w, h); 3666 } 3667 3668 public void setComponent(Component component) { 3669 this.component = component; 3670 if (horizontalSpring != null) { 3671 horizontalSpring.setComponent(component); 3672 } 3673 if (verticalSpring != null) { 3674 verticalSpring.setComponent(component); 3675 } 3676 } 3677 3678 public Component getComponent() { 3679 return component; 3680 } 3681 3682 /** 3683 * Returns true if this component has its size linked to 3684 * other components. 3685 */ 3686 public boolean isLinked(int axis) { 3687 if (axis == HORIZONTAL) { 3688 return horizontalMaster != null; 3689 } 3690 assert (axis == VERTICAL); 3691 return (verticalMaster != null); 3692 } 3693 3694 private void setLinkInfo(int axis, LinkInfo linkInfo) { 3695 if (axis == HORIZONTAL) { 3696 horizontalMaster = linkInfo; 3697 } else { 3698 assert (axis == VERTICAL); 3699 verticalMaster = linkInfo; 3700 } 3701 } 3702 3703 public LinkInfo getLinkInfo(int axis) { 3704 return getLinkInfo(axis, true); 3705 } 3706 3707 private LinkInfo getLinkInfo(int axis, boolean create) { 3708 if (axis == HORIZONTAL) { 3709 if (horizontalMaster == null && create) { 3710 // horizontalMaster field is directly set by adding 3711 // us to the LinkInfo. 3712 new LinkInfo(HORIZONTAL).add(this); 3713 } 3714 return horizontalMaster; 3715 } else { 3716 assert (axis == VERTICAL); 3717 if (verticalMaster == null && create) { 3718 // verticalMaster field is directly set by adding 3719 // us to the LinkInfo. 3720 new LinkInfo(VERTICAL).add(this); 3721 } 3722 return verticalMaster; 3723 } 3724 } 3725 3726 public void clearCachedSize() { 3727 if (horizontalMaster != null) { 3728 horizontalMaster.clearCachedSize(); 3729 } 3730 if (verticalMaster != null) { 3731 verticalMaster.clearCachedSize(); 3732 } 3733 } 3734 3735 int getLinkSize(int axis, int type) { 3736 if (axis == HORIZONTAL) { 3737 return horizontalMaster.getSize(axis); 3738 } else { 3739 assert (axis == VERTICAL); 3740 return verticalMaster.getSize(axis); 3741 } 3742 } 3743 3744 } 3745 }