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 StringBuffer buffer = new StringBuffer(); 1217 buffer.append("HORIZONTAL\n"); 1218 createSpringDescription(buffer, horizontalGroup, " ", HORIZONTAL); 1219 buffer.append("\nVERTICAL\n"); 1220 createSpringDescription(buffer, verticalGroup, " ", VERTICAL); 1221 return buffer.toString(); 1222 } 1223 1224 private void createSpringDescription(StringBuffer buffer, 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 buffer.append(indent + spring.getClass().getName() + " " + 1243 Integer.toHexString(spring.hashCode()) + " " + 1244 origin + 1245 ", size=" + spring.getSize() + 1246 ", alignment=" + spring.getAlignment() + 1247 " prefs=[" + spring.getMinimumSize(axis) + 1248 " " + spring.getPreferredSize(axis) + 1249 " " + spring.getMaximumSize(axis) + 1250 padding + "]\n"); 1251 if (spring instanceof Group) { 1252 List<Spring> springs = ((Group)spring).springs; 1253 indent += " "; 1254 for (int counter = 0; counter < springs.size(); counter++) { 1255 createSpringDescription(buffer, springs.get(counter), indent, 1256 axis); 1257 } 1258 } 1259 } 1260 1261 1262 /** 1263 * Spring consists of a range: min, pref and max, a value some where in 1264 * the middle of that, and a location. Spring caches the 1265 * min/max/pref. If the min/pref/max has internally changes, or needs 1266 * to be updated you must invoke clear. 1267 */ 1268 private abstract class Spring { 1269 private int size; 1270 private int min; 1271 private int max; 1272 private int pref; 1273 private Spring parent; 1274 1275 private Alignment alignment; 1276 1277 Spring() { 1278 min = pref = max = UNSET; 1279 } 1280 1281 /** 1282 * Calculates and returns the minimum size. 1283 * 1284 * @param axis the axis of layout; one of HORIZONTAL or VERTICAL 1285 * @return the minimum size 1286 */ 1287 abstract int calculateMinimumSize(int axis); 1288 1289 /** 1290 * Calculates and returns the preferred size. 1291 * 1292 * @param axis the axis of layout; one of HORIZONTAL or VERTICAL 1293 * @return the preferred size 1294 */ 1295 abstract int calculatePreferredSize(int axis); 1296 1297 /** 1298 * Calculates and returns the minimum size. 1299 * 1300 * @param axis the axis of layout; one of HORIZONTAL or VERTICAL 1301 * @return the minimum size 1302 */ 1303 abstract int calculateMaximumSize(int axis); 1304 1305 /** 1306 * Sets the parent of this Spring. 1307 */ 1308 void setParent(Spring parent) { 1309 this.parent = parent; 1310 } 1311 1312 /** 1313 * Returns the parent of this spring. 1314 */ 1315 Spring getParent() { 1316 return parent; 1317 } 1318 1319 // This is here purely as a convenience for ParallelGroup to avoid 1320 // having to track alignment separately. 1321 void setAlignment(Alignment alignment) { 1322 this.alignment = alignment; 1323 } 1324 1325 /** 1326 * Alignment for this Spring, this may be null. 1327 */ 1328 Alignment getAlignment() { 1329 return alignment; 1330 } 1331 1332 /** 1333 * Returns the minimum size. 1334 */ 1335 final int getMinimumSize(int axis) { 1336 if (min == UNSET) { 1337 min = constrain(calculateMinimumSize(axis)); 1338 } 1339 return min; 1340 } 1341 1342 /** 1343 * Returns the preferred size. 1344 */ 1345 final int getPreferredSize(int axis) { 1346 if (pref == UNSET) { 1347 pref = constrain(calculatePreferredSize(axis)); 1348 } 1349 return pref; 1350 } 1351 1352 /** 1353 * Returns the maximum size. 1354 */ 1355 final int getMaximumSize(int axis) { 1356 if (max == UNSET) { 1357 max = constrain(calculateMaximumSize(axis)); 1358 } 1359 return max; 1360 } 1361 1362 /** 1363 * Sets the value and location of the spring. Subclasses 1364 * will want to invoke super, then do any additional sizing. 1365 * 1366 * @param axis HORIZONTAL or VERTICAL 1367 * @param origin of this Spring 1368 * @param size of the Spring. If size is UNSET, this invokes 1369 * clear. 1370 */ 1371 void setSize(int axis, int origin, int size) { 1372 this.size = size; 1373 if (size == UNSET) { 1374 unset(); 1375 } 1376 } 1377 1378 /** 1379 * Resets the cached min/max/pref. 1380 */ 1381 void unset() { 1382 size = min = pref = max = UNSET; 1383 } 1384 1385 /** 1386 * Returns the current size. 1387 */ 1388 int getSize() { 1389 return size; 1390 } 1391 1392 int constrain(int value) { 1393 return Math.min(value, Short.MAX_VALUE); 1394 } 1395 1396 int getBaseline() { 1397 return -1; 1398 } 1399 1400 BaselineResizeBehavior getBaselineResizeBehavior() { 1401 return BaselineResizeBehavior.OTHER; 1402 } 1403 1404 final boolean isResizable(int axis) { 1405 int min = getMinimumSize(axis); 1406 int pref = getPreferredSize(axis); 1407 return (min != pref || pref != getMaximumSize(axis)); 1408 } 1409 1410 /** 1411 * Returns {@code true} if this spring will ALWAYS have a zero 1412 * size. This should NOT check the current size, rather it's 1413 * meant to quickly test if this Spring will always have a 1414 * zero size. 1415 * 1416 * @param treatAutopaddingAsZeroSized if {@code true}, auto padding 1417 * springs should be treated as having a size of {@code 0} 1418 * @return {@code true} if this spring will have a zero size, 1419 * {@code false} otherwise 1420 */ 1421 abstract boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized); 1422 } 1423 1424 /** 1425 * {@code Group} provides the basis for the two types of 1426 * operations supported by {@code GroupLayout}: laying out 1427 * components one after another ({@link SequentialGroup SequentialGroup}) 1428 * or aligned ({@link ParallelGroup ParallelGroup}). {@code Group} and 1429 * its subclasses have no public constructor; to create one use 1430 * one of {@code createSequentialGroup} or 1431 * {@code createParallelGroup}. Additionally, taking a {@code Group} 1432 * created from one {@code GroupLayout} and using it with another 1433 * will produce undefined results. 1434 * <p> 1435 * Various methods in {@code Group} and its subclasses allow you 1436 * to explicitly specify the range. The arguments to these methods 1437 * can take two forms, either a value greater than or equal to 0, 1438 * or one of {@code DEFAULT_SIZE} or {@code PREFERRED_SIZE}. A 1439 * value greater than or equal to {@code 0} indicates a specific 1440 * size. {@code DEFAULT_SIZE} indicates the corresponding size 1441 * from the component should be used. For example, if {@code 1442 * DEFAULT_SIZE} is passed as the minimum size argument, the 1443 * minimum size is obtained from invoking {@code getMinimumSize} 1444 * on the component. Likewise, {@code PREFERRED_SIZE} indicates 1445 * the value from {@code getPreferredSize} should be used. 1446 * The following example adds {@code myComponent} to {@code group} 1447 * with specific values for the range. That is, the minimum is 1448 * explicitly specified as 100, preferred as 200, and maximum as 1449 * 300. 1450 * <pre> 1451 * group.addComponent(myComponent, 100, 200, 300); 1452 * </pre> 1453 * The following example adds {@code myComponent} to {@code group} using 1454 * a combination of the forms. The minimum size is forced to be the 1455 * same as the preferred size, the preferred size is determined by 1456 * using {@code myComponent.getPreferredSize} and the maximum is 1457 * determined by invoking {@code getMaximumSize} on the component. 1458 * <pre> 1459 * group.addComponent(myComponent, GroupLayout.PREFERRED_SIZE, 1460 * GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE); 1461 * </pre> 1462 * <p> 1463 * Unless otherwise specified all the methods of {@code Group} and 1464 * its subclasses that allow you to specify a range throw an 1465 * {@code IllegalArgumentException} if passed an invalid range. An 1466 * invalid range is one in which any of the values are < 0 and 1467 * not one of {@code PREFERRED_SIZE} or {@code DEFAULT_SIZE}, or 1468 * the following is not met (for specific values): {@code min} 1469 * <= {@code pref} <= {@code max}. 1470 * <p> 1471 * Similarly any methods that take a {@code Component} throw a 1472 * {@code IllegalArgumentException} if passed {@code null} and any methods 1473 * that take a {@code Group} throw an {@code NullPointerException} if 1474 * passed {@code null}. 1475 * 1476 * @see #createSequentialGroup 1477 * @see #createParallelGroup 1478 * @since 1.6 1479 */ 1480 public abstract class Group extends Spring { 1481 // private int origin; 1482 // private int size; 1483 List<Spring> springs; 1484 1485 Group() { 1486 springs = new ArrayList<Spring>(); 1487 } 1488 1489 /** 1490 * Adds a {@code Group} to this {@code Group}. 1491 * 1492 * @param group the {@code Group} to add 1493 * @return this {@code Group} 1494 */ 1495 public Group addGroup(Group group) { 1496 return addSpring(group); 1497 } 1498 1499 /** 1500 * Adds a {@code Component} to this {@code Group}. 1501 * 1502 * @param component the {@code Component} to add 1503 * @return this {@code Group} 1504 */ 1505 public Group addComponent(Component component) { 1506 return addComponent(component, DEFAULT_SIZE, DEFAULT_SIZE, 1507 DEFAULT_SIZE); 1508 } 1509 1510 /** 1511 * Adds a {@code Component} to this {@code Group} 1512 * with the specified size. 1513 * 1514 * @param component the {@code Component} to add 1515 * @param min the minimum size or one of {@code DEFAULT_SIZE} or 1516 * {@code PREFERRED_SIZE} 1517 * @param pref the preferred size or one of {@code DEFAULT_SIZE} or 1518 * {@code PREFERRED_SIZE} 1519 * @param max the maximum size or one of {@code DEFAULT_SIZE} or 1520 * {@code PREFERRED_SIZE} 1521 * @return this {@code Group} 1522 */ 1523 public Group addComponent(Component component, int min, int pref, 1524 int max) { 1525 return addSpring(new ComponentSpring(component, min, pref, max)); 1526 } 1527 1528 /** 1529 * Adds a rigid gap to this {@code Group}. 1530 * 1531 * @param size the size of the gap 1532 * @return this {@code Group} 1533 * @throws IllegalArgumentException if {@code size} is less than 1534 * {@code 0} 1535 */ 1536 public Group addGap(int size) { 1537 return addGap(size, size, size); 1538 } 1539 1540 /** 1541 * Adds a gap to this {@code Group} with the specified size. 1542 * 1543 * @param min the minimum size of the gap 1544 * @param pref the preferred size of the gap 1545 * @param max the maximum size of the gap 1546 * @throws IllegalArgumentException if any of the values are 1547 * less than {@code 0} 1548 * @return this {@code Group} 1549 */ 1550 public Group addGap(int min, int pref, int max) { 1551 return addSpring(new GapSpring(min, pref, max)); 1552 } 1553 1554 Spring getSpring(int index) { 1555 return springs.get(index); 1556 } 1557 1558 int indexOf(Spring spring) { 1559 return springs.indexOf(spring); 1560 } 1561 1562 /** 1563 * Adds the Spring to the list of {@code Spring}s and returns 1564 * the receiver. 1565 */ 1566 Group addSpring(Spring spring) { 1567 springs.add(spring); 1568 spring.setParent(this); 1569 if (!(spring instanceof AutoPreferredGapSpring) || 1570 !((AutoPreferredGapSpring)spring).getUserCreated()) { 1571 springsChanged = true; 1572 } 1573 return this; 1574 } 1575 1576 // 1577 // Spring methods 1578 // 1579 1580 void setSize(int axis, int origin, int size) { 1581 super.setSize(axis, origin, size); 1582 if (size == UNSET) { 1583 for (int counter = springs.size() - 1; counter >= 0; 1584 counter--) { 1585 getSpring(counter).setSize(axis, origin, size); 1586 } 1587 } else { 1588 setValidSize(axis, origin, size); 1589 } 1590 } 1591 1592 /** 1593 * This is invoked from {@code setSize} if passed a value 1594 * other than UNSET. 1595 */ 1596 abstract void setValidSize(int axis, int origin, int size); 1597 1598 int calculateMinimumSize(int axis) { 1599 return calculateSize(axis, MIN_SIZE); 1600 } 1601 1602 int calculatePreferredSize(int axis) { 1603 return calculateSize(axis, PREF_SIZE); 1604 } 1605 1606 int calculateMaximumSize(int axis) { 1607 return calculateSize(axis, MAX_SIZE); 1608 } 1609 1610 /** 1611 * Calculates the specified size. This is called from 1612 * one of the {@code getMinimumSize0}, 1613 * {@code getPreferredSize0} or 1614 * {@code getMaximumSize0} methods. This will invoke 1615 * to {@code operator} to combine the values. 1616 */ 1617 int calculateSize(int axis, int type) { 1618 int count = springs.size(); 1619 if (count == 0) { 1620 return 0; 1621 } 1622 if (count == 1) { 1623 return getSpringSize(getSpring(0), axis, type); 1624 } 1625 int size = constrain(operator(getSpringSize(getSpring(0), axis, 1626 type), getSpringSize(getSpring(1), axis, type))); 1627 for (int counter = 2; counter < count; counter++) { 1628 size = constrain(operator(size, getSpringSize( 1629 getSpring(counter), axis, type))); 1630 } 1631 return size; 1632 } 1633 1634 int getSpringSize(Spring spring, int axis, int type) { 1635 switch(type) { 1636 case MIN_SIZE: 1637 return spring.getMinimumSize(axis); 1638 case PREF_SIZE: 1639 return spring.getPreferredSize(axis); 1640 case MAX_SIZE: 1641 return spring.getMaximumSize(axis); 1642 } 1643 assert false; 1644 return 0; 1645 } 1646 1647 /** 1648 * Used to compute how the two values representing two springs 1649 * will be combined. For example, a group that layed things out 1650 * one after the next would return {@code a + b}. 1651 */ 1652 abstract int operator(int a, int b); 1653 1654 // 1655 // Padding 1656 // 1657 1658 /** 1659 * Adjusts the autopadding springs in this group and its children. 1660 * If {@code insert} is true this will insert auto padding 1661 * springs, otherwise this will only adjust the springs that 1662 * comprise auto preferred padding springs. 1663 * 1664 * @param axis the axis of the springs; HORIZONTAL or VERTICAL 1665 * @param leadingPadding List of AutopaddingSprings that occur before 1666 * this Group 1667 * @param trailingPadding any trailing autopadding springs are added 1668 * to this on exit 1669 * @param leading List of ComponentSprings that occur before this Group 1670 * @param trailing any trailing ComponentSpring are added to this 1671 * List 1672 * @param insert Whether or not to insert AutopaddingSprings or just 1673 * adjust any existing AutopaddingSprings. 1674 */ 1675 abstract void insertAutopadding(int axis, 1676 List<AutoPreferredGapSpring> leadingPadding, 1677 List<AutoPreferredGapSpring> trailingPadding, 1678 List<ComponentSpring> leading, List<ComponentSpring> trailing, 1679 boolean insert); 1680 1681 /** 1682 * Removes any AutopaddingSprings for this Group and its children. 1683 */ 1684 void removeAutopadding() { 1685 unset(); 1686 for (int counter = springs.size() - 1; counter >= 0; counter--) { 1687 Spring spring = springs.get(counter); 1688 if (spring instanceof AutoPreferredGapSpring) { 1689 if (((AutoPreferredGapSpring)spring).getUserCreated()) { 1690 ((AutoPreferredGapSpring)spring).reset(); 1691 } else { 1692 springs.remove(counter); 1693 } 1694 } else if (spring instanceof Group) { 1695 ((Group)spring).removeAutopadding(); 1696 } 1697 } 1698 } 1699 1700 void unsetAutopadding() { 1701 // Clear cached pref/min/max. 1702 unset(); 1703 for (int counter = springs.size() - 1; counter >= 0; counter--) { 1704 Spring spring = springs.get(counter); 1705 if (spring instanceof AutoPreferredGapSpring) { 1706 spring.unset(); 1707 } else if (spring instanceof Group) { 1708 ((Group)spring).unsetAutopadding(); 1709 } 1710 } 1711 } 1712 1713 void calculateAutopadding(int axis) { 1714 for (int counter = springs.size() - 1; counter >= 0; counter--) { 1715 Spring spring = springs.get(counter); 1716 if (spring instanceof AutoPreferredGapSpring) { 1717 // Force size to be reset. 1718 spring.unset(); 1719 ((AutoPreferredGapSpring)spring).calculatePadding(axis); 1720 } else if (spring instanceof Group) { 1721 ((Group)spring).calculateAutopadding(axis); 1722 } 1723 } 1724 // Clear cached pref/min/max. 1725 unset(); 1726 } 1727 1728 @Override 1729 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 1730 for (int i = springs.size() - 1; i >= 0; i--) { 1731 Spring spring = springs.get(i); 1732 if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) { 1733 return false; 1734 } 1735 } 1736 return true; 1737 } 1738 } 1739 1740 1741 /** 1742 * A {@code Group} that positions and sizes its elements 1743 * sequentially, one after another. This class has no public 1744 * constructor, use the {@code createSequentialGroup} method 1745 * to create one. 1746 * <p> 1747 * In order to align a {@code SequentialGroup} along the baseline 1748 * of a baseline aligned {@code ParallelGroup} you need to specify 1749 * which of the elements of the {@code SequentialGroup} is used to 1750 * determine the baseline. The element used to calculate the 1751 * baseline is specified using one of the {@code add} methods that 1752 * take a {@code boolean}. The last element added with a value of 1753 * {@code true} for {@code useAsBaseline} is used to calculate the 1754 * baseline. 1755 * 1756 * @see #createSequentialGroup 1757 * @since 1.6 1758 */ 1759 public class SequentialGroup extends Group { 1760 private Spring baselineSpring; 1761 1762 SequentialGroup() { 1763 } 1764 1765 /** 1766 * {@inheritDoc} 1767 */ 1768 public SequentialGroup addGroup(Group group) { 1769 return (SequentialGroup)super.addGroup(group); 1770 } 1771 1772 /** 1773 * Adds a {@code Group} to this {@code Group}. 1774 * 1775 * @param group the {@code Group} to add 1776 * @param useAsBaseline whether the specified {@code Group} should 1777 * be used to calculate the baseline for this {@code Group} 1778 * @return this {@code Group} 1779 */ 1780 public SequentialGroup addGroup(boolean useAsBaseline, Group group) { 1781 super.addGroup(group); 1782 if (useAsBaseline) { 1783 baselineSpring = group; 1784 } 1785 return this; 1786 } 1787 1788 /** 1789 * {@inheritDoc} 1790 */ 1791 public SequentialGroup addComponent(Component component) { 1792 return (SequentialGroup)super.addComponent(component); 1793 } 1794 1795 /** 1796 * Adds a {@code Component} to this {@code Group}. 1797 * 1798 * @param useAsBaseline whether the specified {@code Component} should 1799 * be used to calculate the baseline for this {@code Group} 1800 * @param component the {@code Component} to add 1801 * @return this {@code Group} 1802 */ 1803 public SequentialGroup addComponent(boolean useAsBaseline, 1804 Component component) { 1805 super.addComponent(component); 1806 if (useAsBaseline) { 1807 baselineSpring = springs.get(springs.size() - 1); 1808 } 1809 return this; 1810 } 1811 1812 /** 1813 * {@inheritDoc} 1814 */ 1815 public SequentialGroup addComponent(Component component, int min, 1816 int pref, int max) { 1817 return (SequentialGroup)super.addComponent( 1818 component, min, pref, max); 1819 } 1820 1821 /** 1822 * Adds a {@code Component} to this {@code Group} 1823 * with the specified size. 1824 * 1825 * @param useAsBaseline whether the specified {@code Component} should 1826 * be used to calculate the baseline for this {@code Group} 1827 * @param component the {@code Component} to add 1828 * @param min the minimum size or one of {@code DEFAULT_SIZE} or 1829 * {@code PREFERRED_SIZE} 1830 * @param pref the preferred size or one of {@code DEFAULT_SIZE} or 1831 * {@code PREFERRED_SIZE} 1832 * @param max the maximum size or one of {@code DEFAULT_SIZE} or 1833 * {@code PREFERRED_SIZE} 1834 * @return this {@code Group} 1835 */ 1836 public SequentialGroup addComponent(boolean useAsBaseline, 1837 Component component, int min, int pref, int max) { 1838 super.addComponent(component, min, pref, max); 1839 if (useAsBaseline) { 1840 baselineSpring = springs.get(springs.size() - 1); 1841 } 1842 return this; 1843 } 1844 1845 /** 1846 * {@inheritDoc} 1847 */ 1848 public SequentialGroup addGap(int size) { 1849 return (SequentialGroup)super.addGap(size); 1850 } 1851 1852 /** 1853 * {@inheritDoc} 1854 */ 1855 public SequentialGroup addGap(int min, int pref, int max) { 1856 return (SequentialGroup)super.addGap(min, pref, max); 1857 } 1858 1859 /** 1860 * Adds an element representing the preferred gap between two 1861 * components. The element created to represent the gap is not 1862 * resizable. 1863 * 1864 * @param comp1 the first component 1865 * @param comp2 the second component 1866 * @param type the type of gap; one of the constants defined by 1867 * {@code LayoutStyle} 1868 * @return this {@code SequentialGroup} 1869 * @throws IllegalArgumentException if {@code type}, {@code comp1} or 1870 * {@code comp2} is {@code null} 1871 * @see LayoutStyle 1872 */ 1873 public SequentialGroup addPreferredGap(JComponent comp1, 1874 JComponent comp2, ComponentPlacement type) { 1875 return addPreferredGap(comp1, comp2, type, DEFAULT_SIZE, 1876 PREFERRED_SIZE); 1877 } 1878 1879 /** 1880 * Adds an element representing the preferred gap between two 1881 * components. 1882 * 1883 * @param comp1 the first component 1884 * @param comp2 the second component 1885 * @param type the type of gap 1886 * @param pref the preferred size of the grap; one of 1887 * {@code DEFAULT_SIZE} or a value >= 0 1888 * @param max the maximum size of the gap; one of 1889 * {@code DEFAULT_SIZE}, {@code PREFERRED_SIZE} 1890 * or a value >= 0 1891 * @return this {@code SequentialGroup} 1892 * @throws IllegalArgumentException if {@code type}, {@code comp1} or 1893 * {@code comp2} is {@code null} 1894 * @see LayoutStyle 1895 */ 1896 public SequentialGroup addPreferredGap(JComponent comp1, 1897 JComponent comp2, ComponentPlacement type, int pref, 1898 int max) { 1899 if (type == null) { 1900 throw new IllegalArgumentException("Type must be non-null"); 1901 } 1902 if (comp1 == null || comp2 == null) { 1903 throw new IllegalArgumentException( 1904 "Components must be non-null"); 1905 } 1906 checkPreferredGapValues(pref, max); 1907 return (SequentialGroup)addSpring(new PreferredGapSpring( 1908 comp1, comp2, type, pref, max)); 1909 } 1910 1911 /** 1912 * Adds an element representing the preferred gap between the 1913 * nearest components. During layout, neighboring 1914 * components are found, and the size of the added gap is set 1915 * based on the preferred gap between the components. If no 1916 * neighboring components are found the gap has a size of {@code 0}. 1917 * <p> 1918 * The element created to represent the gap is not 1919 * resizable. 1920 * 1921 * @param type the type of gap; one of 1922 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1923 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1924 * @return this {@code SequentialGroup} 1925 * @see LayoutStyle 1926 * @throws IllegalArgumentException if {@code type} is not one of 1927 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1928 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1929 */ 1930 public SequentialGroup addPreferredGap(ComponentPlacement type) { 1931 return addPreferredGap(type, DEFAULT_SIZE, DEFAULT_SIZE); 1932 } 1933 1934 /** 1935 * Adds an element representing the preferred gap between the 1936 * nearest components. During layout, neighboring 1937 * components are found, and the minimum of this 1938 * gap is set based on the size of the preferred gap between the 1939 * neighboring components. If no neighboring components are found the 1940 * minimum size is set to 0. 1941 * 1942 * @param type the type of gap; one of 1943 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1944 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1945 * @param pref the preferred size of the grap; one of 1946 * {@code DEFAULT_SIZE} or a value >= 0 1947 * @param max the maximum size of the gap; one of 1948 * {@code DEFAULT_SIZE}, {@code PREFERRED_SIZE} 1949 * or a value >= 0 1950 * @return this {@code SequentialGroup} 1951 * @throws IllegalArgumentException if {@code type} is not one of 1952 * {@code LayoutStyle.ComponentPlacement.RELATED} or 1953 * {@code LayoutStyle.ComponentPlacement.UNRELATED} 1954 * @see LayoutStyle 1955 */ 1956 public SequentialGroup addPreferredGap(ComponentPlacement type, 1957 int pref, int max) { 1958 if (type != ComponentPlacement.RELATED && 1959 type != ComponentPlacement.UNRELATED) { 1960 throw new IllegalArgumentException( 1961 "Type must be one of " + 1962 "LayoutStyle.ComponentPlacement.RELATED or " + 1963 "LayoutStyle.ComponentPlacement.UNRELATED"); 1964 } 1965 checkPreferredGapValues(pref, max); 1966 hasPreferredPaddingSprings = true; 1967 return (SequentialGroup)addSpring(new AutoPreferredGapSpring( 1968 type, pref, max)); 1969 } 1970 1971 /** 1972 * Adds an element representing the preferred gap between an edge 1973 * the container and components that touch the border of the 1974 * container. This has no effect if the added gap does not 1975 * touch an edge of the parent container. 1976 * <p> 1977 * The element created to represent the gap is not 1978 * resizable. 1979 * 1980 * @return this {@code SequentialGroup} 1981 */ 1982 public SequentialGroup addContainerGap() { 1983 return addContainerGap(DEFAULT_SIZE, DEFAULT_SIZE); 1984 } 1985 1986 /** 1987 * Adds an element representing the preferred gap between one 1988 * edge of the container and the next or previous {@code 1989 * Component} with the specified size. This has no 1990 * effect if the next or previous element is not a {@code 1991 * Component} and does not touch one edge of the parent 1992 * container. 1993 * 1994 * @param pref the preferred size; one of {@code DEFAULT_SIZE} or a 1995 * value >= 0 1996 * @param max the maximum size; one of {@code DEFAULT_SIZE}, 1997 * {@code PREFERRED_SIZE} or a value >= 0 1998 * @return this {@code SequentialGroup} 1999 */ 2000 public SequentialGroup addContainerGap(int pref, int max) { 2001 if ((pref < 0 && pref != DEFAULT_SIZE) || 2002 (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE)|| 2003 (pref >= 0 && max >= 0 && pref > max)) { 2004 throw new IllegalArgumentException( 2005 "Pref and max must be either DEFAULT_VALUE " + 2006 "or >= 0 and pref <= max"); 2007 } 2008 hasPreferredPaddingSprings = true; 2009 return (SequentialGroup)addSpring( 2010 new ContainerAutoPreferredGapSpring(pref, max)); 2011 } 2012 2013 int operator(int a, int b) { 2014 return constrain(a) + constrain(b); 2015 } 2016 2017 void setValidSize(int axis, int origin, int size) { 2018 int pref = getPreferredSize(axis); 2019 if (size == pref) { 2020 // Layout at preferred size 2021 for (Spring spring : springs) { 2022 int springPref = spring.getPreferredSize(axis); 2023 spring.setSize(axis, origin, springPref); 2024 origin += springPref; 2025 } 2026 } else if (springs.size() == 1) { 2027 Spring spring = getSpring(0); 2028 spring.setSize(axis, origin, Math.min( 2029 Math.max(size, spring.getMinimumSize(axis)), 2030 spring.getMaximumSize(axis))); 2031 } else if (springs.size() > 1) { 2032 // Adjust between min/pref 2033 setValidSizeNotPreferred(axis, origin, size); 2034 } 2035 } 2036 2037 private void setValidSizeNotPreferred(int axis, int origin, int size) { 2038 int delta = size - getPreferredSize(axis); 2039 assert delta != 0; 2040 boolean useMin = (delta < 0); 2041 int springCount = springs.size(); 2042 if (useMin) { 2043 delta *= -1; 2044 } 2045 2046 // The following algorithm if used for resizing springs: 2047 // 1. Calculate the resizability of each spring (pref - min or 2048 // max - pref) into a list. 2049 // 2. Sort the list in ascending order 2050 // 3. Iterate through each of the resizable Springs, attempting 2051 // to give them (pref - size) / resizeCount 2052 // 4. For any Springs that can not accommodate that much space 2053 // add the remainder back to the amount to distribute and 2054 // recalculate how must space the remaining springs will get. 2055 // 5. Set the size of the springs. 2056 2057 // First pass, sort the resizable springs into the List resizable 2058 List<SpringDelta> resizable = buildResizableList(axis, useMin); 2059 int resizableCount = resizable.size(); 2060 2061 if (resizableCount > 0) { 2062 // How much we would like to give each Spring. 2063 int sDelta = delta / resizableCount; 2064 // Remaining space. 2065 int slop = delta - sDelta * resizableCount; 2066 int[] sizes = new int[springCount]; 2067 int sign = useMin ? -1 : 1; 2068 // Second pass, accumulate the resulting deltas (relative to 2069 // preferred) into sizes. 2070 for (int counter = 0; counter < resizableCount; counter++) { 2071 SpringDelta springDelta = resizable.get(counter); 2072 if ((counter + 1) == resizableCount) { 2073 sDelta += slop; 2074 } 2075 springDelta.delta = Math.min(sDelta, springDelta.delta); 2076 delta -= springDelta.delta; 2077 if (springDelta.delta != sDelta && counter + 1 < 2078 resizableCount) { 2079 // Spring didn't take all the space, reset how much 2080 // each spring will get. 2081 sDelta = delta / (resizableCount - counter - 1); 2082 slop = delta - sDelta * (resizableCount - counter - 1); 2083 } 2084 sizes[springDelta.index] = sign * springDelta.delta; 2085 } 2086 2087 // And finally set the size of each spring 2088 for (int counter = 0; counter < springCount; counter++) { 2089 Spring spring = getSpring(counter); 2090 int sSize = spring.getPreferredSize(axis) + sizes[counter]; 2091 spring.setSize(axis, origin, sSize); 2092 origin += sSize; 2093 } 2094 } else { 2095 // Nothing resizable, use the min or max of each of the 2096 // springs. 2097 for (int counter = 0; counter < springCount; counter++) { 2098 Spring spring = getSpring(counter); 2099 int sSize; 2100 if (useMin) { 2101 sSize = spring.getMinimumSize(axis); 2102 } else { 2103 sSize = spring.getMaximumSize(axis); 2104 } 2105 spring.setSize(axis, origin, sSize); 2106 origin += sSize; 2107 } 2108 } 2109 } 2110 2111 /** 2112 * Returns the sorted list of SpringDelta's for the current set of 2113 * Springs. The list is ordered based on the amount of flexibility of 2114 * the springs. 2115 */ 2116 private List<SpringDelta> buildResizableList(int axis, 2117 boolean useMin) { 2118 // First pass, figure out what is resizable 2119 int size = springs.size(); 2120 List<SpringDelta> sorted = new ArrayList<SpringDelta>(size); 2121 for (int counter = 0; counter < size; counter++) { 2122 Spring spring = getSpring(counter); 2123 int sDelta; 2124 if (useMin) { 2125 sDelta = spring.getPreferredSize(axis) - 2126 spring.getMinimumSize(axis); 2127 } else { 2128 sDelta = spring.getMaximumSize(axis) - 2129 spring.getPreferredSize(axis); 2130 } 2131 if (sDelta > 0) { 2132 sorted.add(new SpringDelta(counter, sDelta)); 2133 } 2134 } 2135 Collections.sort(sorted); 2136 return sorted; 2137 } 2138 2139 private int indexOfNextNonZeroSpring( 2140 int index, boolean treatAutopaddingAsZeroSized) { 2141 while (index < springs.size()) { 2142 Spring spring = springs.get(index); 2143 if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) { 2144 return index; 2145 } 2146 index++; 2147 } 2148 return index; 2149 } 2150 2151 @Override 2152 void insertAutopadding(int axis, 2153 List<AutoPreferredGapSpring> leadingPadding, 2154 List<AutoPreferredGapSpring> trailingPadding, 2155 List<ComponentSpring> leading, List<ComponentSpring> trailing, 2156 boolean insert) { 2157 List<AutoPreferredGapSpring> newLeadingPadding = 2158 new ArrayList<AutoPreferredGapSpring>(leadingPadding); 2159 List<AutoPreferredGapSpring> newTrailingPadding = 2160 new ArrayList<AutoPreferredGapSpring>(1); 2161 List<ComponentSpring> newLeading = 2162 new ArrayList<ComponentSpring>(leading); 2163 List<ComponentSpring> newTrailing = null; 2164 int counter = 0; 2165 // Warning, this must use springs.size, as it may change during the 2166 // loop. 2167 while (counter < springs.size()) { 2168 Spring spring = getSpring(counter); 2169 if (spring instanceof AutoPreferredGapSpring) { 2170 if (newLeadingPadding.size() == 0) { 2171 // Autopadding spring. Set the sources of the 2172 // autopadding spring based on newLeading. 2173 AutoPreferredGapSpring padding = 2174 (AutoPreferredGapSpring)spring; 2175 padding.setSources(newLeading); 2176 newLeading.clear(); 2177 counter = indexOfNextNonZeroSpring(counter + 1, true); 2178 if (counter == springs.size()) { 2179 // Last spring in the list, add it to 2180 // trailingPadding. 2181 if (!(padding instanceof 2182 ContainerAutoPreferredGapSpring)) { 2183 trailingPadding.add(padding); 2184 } 2185 } else { 2186 newLeadingPadding.clear(); 2187 newLeadingPadding.add(padding); 2188 } 2189 } else { 2190 counter = indexOfNextNonZeroSpring(counter + 1, true); 2191 } 2192 } else { 2193 // Not a padding spring 2194 if (newLeading.size() > 0 && insert) { 2195 // There's leading ComponentSprings, create an 2196 // autopadding spring. 2197 AutoPreferredGapSpring padding = 2198 new AutoPreferredGapSpring(); 2199 // Force the newly created spring to be considered 2200 // by NOT incrementing counter 2201 springs.add(counter, padding); 2202 continue; 2203 } 2204 if (spring instanceof ComponentSpring) { 2205 // Spring is a Component, make it the target of any 2206 // leading AutopaddingSpring. 2207 ComponentSpring cSpring = (ComponentSpring)spring; 2208 if (!cSpring.isVisible()) { 2209 counter++; 2210 continue; 2211 } 2212 for (AutoPreferredGapSpring gapSpring : newLeadingPadding) { 2213 gapSpring.addTarget(cSpring, axis); 2214 } 2215 newLeading.clear(); 2216 newLeadingPadding.clear(); 2217 counter = indexOfNextNonZeroSpring(counter + 1, false); 2218 if (counter == springs.size()) { 2219 // Last Spring, add it to trailing 2220 trailing.add(cSpring); 2221 } else { 2222 // Not that last Spring, add it to leading 2223 newLeading.add(cSpring); 2224 } 2225 } else if (spring instanceof Group) { 2226 // Forward call to child Group 2227 if (newTrailing == null) { 2228 newTrailing = new ArrayList<ComponentSpring>(1); 2229 } else { 2230 newTrailing.clear(); 2231 } 2232 newTrailingPadding.clear(); 2233 ((Group)spring).insertAutopadding(axis, 2234 newLeadingPadding, newTrailingPadding, 2235 newLeading, newTrailing, insert); 2236 newLeading.clear(); 2237 newLeadingPadding.clear(); 2238 counter = indexOfNextNonZeroSpring( 2239 counter + 1, (newTrailing.size() == 0)); 2240 if (counter == springs.size()) { 2241 trailing.addAll(newTrailing); 2242 trailingPadding.addAll(newTrailingPadding); 2243 } else { 2244 newLeading.addAll(newTrailing); 2245 newLeadingPadding.addAll(newTrailingPadding); 2246 } 2247 } else { 2248 // Gap 2249 newLeadingPadding.clear(); 2250 newLeading.clear(); 2251 counter++; 2252 } 2253 } 2254 } 2255 } 2256 2257 int getBaseline() { 2258 if (baselineSpring != null) { 2259 int baseline = baselineSpring.getBaseline(); 2260 if (baseline >= 0) { 2261 int size = 0; 2262 for (Spring spring : springs) { 2263 if (spring == baselineSpring) { 2264 return size + baseline; 2265 } else { 2266 size += spring.getPreferredSize(VERTICAL); 2267 } 2268 } 2269 } 2270 } 2271 return -1; 2272 } 2273 2274 BaselineResizeBehavior getBaselineResizeBehavior() { 2275 if (isResizable(VERTICAL)) { 2276 if (!baselineSpring.isResizable(VERTICAL)) { 2277 // Spring to use for baseline isn't resizable. In this case 2278 // baseline resize behavior can be determined based on how 2279 // preceding springs resize. 2280 boolean leadingResizable = false; 2281 for (Spring spring : springs) { 2282 if (spring == baselineSpring) { 2283 break; 2284 } else if (spring.isResizable(VERTICAL)) { 2285 leadingResizable = true; 2286 break; 2287 } 2288 } 2289 boolean trailingResizable = false; 2290 for (int i = springs.size() - 1; i >= 0; i--) { 2291 Spring spring = springs.get(i); 2292 if (spring == baselineSpring) { 2293 break; 2294 } 2295 if (spring.isResizable(VERTICAL)) { 2296 trailingResizable = true; 2297 break; 2298 } 2299 } 2300 if (leadingResizable && !trailingResizable) { 2301 return BaselineResizeBehavior.CONSTANT_DESCENT; 2302 } else if (!leadingResizable && trailingResizable) { 2303 return BaselineResizeBehavior.CONSTANT_ASCENT; 2304 } 2305 // If we get here, both leading and trailing springs are 2306 // resizable. Fall through to OTHER. 2307 } else { 2308 BaselineResizeBehavior brb = baselineSpring.getBaselineResizeBehavior(); 2309 if (brb == BaselineResizeBehavior.CONSTANT_ASCENT) { 2310 for (Spring spring : springs) { 2311 if (spring == baselineSpring) { 2312 return BaselineResizeBehavior.CONSTANT_ASCENT; 2313 } 2314 if (spring.isResizable(VERTICAL)) { 2315 return BaselineResizeBehavior.OTHER; 2316 } 2317 } 2318 } else if (brb == BaselineResizeBehavior.CONSTANT_DESCENT) { 2319 for (int i = springs.size() - 1; i >= 0; i--) { 2320 Spring spring = springs.get(i); 2321 if (spring == baselineSpring) { 2322 return BaselineResizeBehavior.CONSTANT_DESCENT; 2323 } 2324 if (spring.isResizable(VERTICAL)) { 2325 return BaselineResizeBehavior.OTHER; 2326 } 2327 } 2328 } 2329 } 2330 return BaselineResizeBehavior.OTHER; 2331 } 2332 // Not resizable, treat as constant_ascent 2333 return BaselineResizeBehavior.CONSTANT_ASCENT; 2334 } 2335 2336 private void checkPreferredGapValues(int pref, int max) { 2337 if ((pref < 0 && pref != DEFAULT_SIZE && pref != PREFERRED_SIZE) || 2338 (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE)|| 2339 (pref >= 0 && max >= 0 && pref > max)) { 2340 throw new IllegalArgumentException( 2341 "Pref and max must be either DEFAULT_SIZE, " + 2342 "PREFERRED_SIZE, or >= 0 and pref <= max"); 2343 } 2344 } 2345 } 2346 2347 2348 /** 2349 * Used by SequentialGroup in calculating resizability of springs. 2350 */ 2351 private static final class SpringDelta implements Comparable<SpringDelta> { 2352 // Original index. 2353 public final int index; 2354 // Delta, one of pref - min or max - pref. 2355 public int delta; 2356 2357 public SpringDelta(int index, int delta) { 2358 this.index = index; 2359 this.delta = delta; 2360 } 2361 2362 public int compareTo(SpringDelta o) { 2363 return delta - o.delta; 2364 } 2365 2366 public String toString() { 2367 return super.toString() + "[index=" + index + ", delta=" + 2368 delta + "]"; 2369 } 2370 } 2371 2372 2373 /** 2374 * A {@code Group} that aligns and sizes it's children. 2375 * {@code ParallelGroup} aligns it's children in 2376 * four possible ways: along the baseline, centered, anchored to the 2377 * leading edge, or anchored to the trailing edge. 2378 * <h3>Baseline</h3> 2379 * A {@code ParallelGroup} that aligns it's children along the 2380 * baseline must first decide where the baseline is 2381 * anchored. The baseline can either be anchored to the top, or 2382 * anchored to the bottom of the group. That is, the distance between the 2383 * baseline and the beginning of the group can be a constant 2384 * distance, or the distance between the end of the group and the 2385 * baseline can be a constant distance. The possible choices 2386 * correspond to the {@code BaselineResizeBehavior} constants 2387 * {@link 2388 * java.awt.Component.BaselineResizeBehavior#CONSTANT_ASCENT CONSTANT_ASCENT} and 2389 * {@link 2390 * java.awt.Component.BaselineResizeBehavior#CONSTANT_DESCENT CONSTANT_DESCENT}. 2391 * <p> 2392 * The baseline anchor may be explicitly specified by the 2393 * {@code createBaselineGroup} method, or determined based on the elements. 2394 * If not explicitly specified, the baseline will be anchored to 2395 * the bottom if all the elements with a baseline, and that are 2396 * aligned to the baseline, have a baseline resize behavior of 2397 * {@code CONSTANT_DESCENT}; otherwise the baseline is anchored to the top 2398 * of the group. 2399 * <p> 2400 * Elements aligned to the baseline are resizable if they have have 2401 * a baseline resize behavior of {@code CONSTANT_ASCENT} or 2402 * {@code CONSTANT_DESCENT}. Elements with a baseline resize 2403 * behavior of {@code OTHER} or {@code CENTER_OFFSET} are not resizable. 2404 * <p> 2405 * The baseline is calculated based on the preferred height of each 2406 * of the elements that have a baseline. The baseline is 2407 * calculated using the following algorithm: 2408 * {@code max(maxNonBaselineHeight, maxAscent + maxDescent)}, where the 2409 * {@code maxNonBaselineHeight} is the maximum height of all elements 2410 * that do not have a baseline, or are not aligned along the baseline. 2411 * {@code maxAscent} is the maximum ascent (baseline) of all elements that 2412 * have a baseline and are aligned along the baseline. 2413 * {@code maxDescent} is the maximum descent (preferred height - baseline) 2414 * of all elements that have a baseline and are aligned along the baseline. 2415 * <p> 2416 * A {@code ParallelGroup} that aligns it's elements along the baseline 2417 * is only useful along the vertical axis. If you create a 2418 * baseline group and use it along the horizontal axis an 2419 * {@code IllegalStateException} is thrown when you ask 2420 * {@code GroupLayout} for the minimum, preferred or maximum size or 2421 * attempt to layout the components. 2422 * <p> 2423 * Elements that are not aligned to the baseline and smaller than the size 2424 * of the {@code ParallelGroup} are positioned in one of three 2425 * ways: centered, anchored to the leading edge, or anchored to the 2426 * trailing edge. 2427 * 2428 * <h3>Non-baseline {@code ParallelGroup}</h3> 2429 * {@code ParallelGroup}s created with an alignment other than 2430 * {@code BASELINE} align elements that are smaller than the size 2431 * of the group in one of three ways: centered, anchored to the 2432 * leading edge, or anchored to the trailing edge. 2433 * <p> 2434 * The leading edge is based on the axis and {@code 2435 * ComponentOrientation}. For the vertical axis the top edge is 2436 * always the leading edge, and the bottom edge is always the 2437 * trailing edge. When the {@code ComponentOrientation} is {@code 2438 * LEFT_TO_RIGHT}, the leading edge is the left edge and the 2439 * trailing edge the right edge. A {@code ComponentOrientation} of 2440 * {@code RIGHT_TO_LEFT} flips the left and right edges. Child 2441 * elements are aligned based on the specified alignment the 2442 * element was added with. If you do not specify an alignment, the 2443 * alignment specified for the {@code ParallelGroup} is used. 2444 * <p> 2445 * To align elements along the baseline you {@code createBaselineGroup}, 2446 * or {@code createParallelGroup} with an alignment of {@code BASELINE}. 2447 * If the group was not created with a baseline alignment, and you attempt 2448 * to add an element specifying a baseline alignment, an 2449 * {@code IllegalArgumentException} is thrown. 2450 * 2451 * @see #createParallelGroup() 2452 * @see #createBaselineGroup(boolean,boolean) 2453 * @since 1.6 2454 */ 2455 public class ParallelGroup extends Group { 2456 // How children are layed out. 2457 private final Alignment childAlignment; 2458 // Whether or not we're resizable. 2459 private final boolean resizable; 2460 2461 ParallelGroup(Alignment childAlignment, boolean resizable) { 2462 this.childAlignment = childAlignment; 2463 this.resizable = resizable; 2464 } 2465 2466 /** 2467 * {@inheritDoc} 2468 */ 2469 public ParallelGroup addGroup(Group group) { 2470 return (ParallelGroup)super.addGroup(group); 2471 } 2472 2473 /** 2474 * {@inheritDoc} 2475 */ 2476 public ParallelGroup addComponent(Component component) { 2477 return (ParallelGroup)super.addComponent(component); 2478 } 2479 2480 /** 2481 * {@inheritDoc} 2482 */ 2483 public ParallelGroup addComponent(Component component, int min, int pref, 2484 int max) { 2485 return (ParallelGroup)super.addComponent(component, min, pref, max); 2486 } 2487 2488 /** 2489 * {@inheritDoc} 2490 */ 2491 public ParallelGroup addGap(int pref) { 2492 return (ParallelGroup)super.addGap(pref); 2493 } 2494 2495 /** 2496 * {@inheritDoc} 2497 */ 2498 public ParallelGroup addGap(int min, int pref, int max) { 2499 return (ParallelGroup)super.addGap(min, pref, max); 2500 } 2501 2502 /** 2503 * Adds a {@code Group} to this {@code ParallelGroup} with the 2504 * specified alignment. If the child is smaller than the 2505 * {@code Group} it is aligned based on the specified 2506 * alignment. 2507 * 2508 * @param alignment the alignment 2509 * @param group the {@code Group} to add 2510 * @return this {@code ParallelGroup} 2511 * @throws IllegalArgumentException if {@code alignment} is 2512 * {@code null} 2513 */ 2514 public ParallelGroup addGroup(Alignment alignment, Group group) { 2515 checkChildAlignment(alignment); 2516 group.setAlignment(alignment); 2517 return (ParallelGroup)addSpring(group); 2518 } 2519 2520 /** 2521 * Adds a {@code Component} to this {@code ParallelGroup} with 2522 * the specified alignment. 2523 * 2524 * @param alignment the alignment 2525 * @param component the {@code Component} to add 2526 * @return this {@code Group} 2527 * @throws IllegalArgumentException if {@code alignment} is 2528 * {@code null} 2529 */ 2530 public ParallelGroup addComponent(Component component, 2531 Alignment alignment) { 2532 return addComponent(component, alignment, DEFAULT_SIZE, DEFAULT_SIZE, 2533 DEFAULT_SIZE); 2534 } 2535 2536 /** 2537 * Adds a {@code Component} to this {@code ParallelGroup} with the 2538 * specified alignment and size. 2539 * 2540 * @param alignment the alignment 2541 * @param component the {@code Component} to add 2542 * @param min the minimum size 2543 * @param pref the preferred size 2544 * @param max the maximum size 2545 * @throws IllegalArgumentException if {@code alignment} is 2546 * {@code null} 2547 * @return this {@code Group} 2548 */ 2549 public ParallelGroup addComponent(Component component, 2550 Alignment alignment, int min, int pref, int max) { 2551 checkChildAlignment(alignment); 2552 ComponentSpring spring = new ComponentSpring(component, 2553 min, pref, max); 2554 spring.setAlignment(alignment); 2555 return (ParallelGroup)addSpring(spring); 2556 } 2557 2558 boolean isResizable() { 2559 return resizable; 2560 } 2561 2562 int operator(int a, int b) { 2563 return Math.max(a, b); 2564 } 2565 2566 int calculateMinimumSize(int axis) { 2567 if (!isResizable()) { 2568 return getPreferredSize(axis); 2569 } 2570 return super.calculateMinimumSize(axis); 2571 } 2572 2573 int calculateMaximumSize(int axis) { 2574 if (!isResizable()) { 2575 return getPreferredSize(axis); 2576 } 2577 return super.calculateMaximumSize(axis); 2578 } 2579 2580 void setValidSize(int axis, int origin, int size) { 2581 for (Spring spring : springs) { 2582 setChildSize(spring, axis, origin, size); 2583 } 2584 } 2585 2586 void setChildSize(Spring spring, int axis, int origin, int size) { 2587 Alignment alignment = spring.getAlignment(); 2588 int springSize = Math.min( 2589 Math.max(spring.getMinimumSize(axis), size), 2590 spring.getMaximumSize(axis)); 2591 if (alignment == null) { 2592 alignment = childAlignment; 2593 } 2594 switch (alignment) { 2595 case TRAILING: 2596 spring.setSize(axis, origin + size - springSize, 2597 springSize); 2598 break; 2599 case CENTER: 2600 spring.setSize(axis, origin + 2601 (size - springSize) / 2,springSize); 2602 break; 2603 default: // LEADING, or BASELINE 2604 spring.setSize(axis, origin, springSize); 2605 break; 2606 } 2607 } 2608 2609 @Override 2610 void insertAutopadding(int axis, 2611 List<AutoPreferredGapSpring> leadingPadding, 2612 List<AutoPreferredGapSpring> trailingPadding, 2613 List<ComponentSpring> leading, List<ComponentSpring> trailing, 2614 boolean insert) { 2615 for (Spring spring : springs) { 2616 if (spring instanceof ComponentSpring) { 2617 if (((ComponentSpring)spring).isVisible()) { 2618 for (AutoPreferredGapSpring gapSpring : 2619 leadingPadding) { 2620 gapSpring.addTarget((ComponentSpring)spring, axis); 2621 } 2622 trailing.add((ComponentSpring)spring); 2623 } 2624 } else if (spring instanceof Group) { 2625 ((Group)spring).insertAutopadding(axis, leadingPadding, 2626 trailingPadding, leading, trailing, insert); 2627 } else if (spring instanceof AutoPreferredGapSpring) { 2628 ((AutoPreferredGapSpring)spring).setSources(leading); 2629 trailingPadding.add((AutoPreferredGapSpring)spring); 2630 } 2631 } 2632 } 2633 2634 private void checkChildAlignment(Alignment alignment) { 2635 checkChildAlignment(alignment, (this instanceof BaselineGroup)); 2636 } 2637 2638 private void checkChildAlignment(Alignment alignment, 2639 boolean allowsBaseline) { 2640 if (alignment == null) { 2641 throw new IllegalArgumentException("Alignment must be non-null"); 2642 } 2643 if (!allowsBaseline && alignment == Alignment.BASELINE) { 2644 throw new IllegalArgumentException("Alignment must be one of:" + 2645 "LEADING, TRAILING or CENTER"); 2646 } 2647 } 2648 } 2649 2650 2651 /** 2652 * An extension of {@code ParallelGroup} that aligns its 2653 * constituent {@code Spring}s along the baseline. 2654 */ 2655 private class BaselineGroup extends ParallelGroup { 2656 // Whether or not all child springs have a baseline 2657 private boolean allSpringsHaveBaseline; 2658 2659 // max(spring.getBaseline()) of all springs aligned along the baseline 2660 // that have a baseline 2661 private int prefAscent; 2662 2663 // max(spring.getPreferredSize().height - spring.getBaseline()) of all 2664 // springs aligned along the baseline that have a baseline 2665 private int prefDescent; 2666 2667 // Whether baselineAnchoredToTop was explicitly set 2668 private boolean baselineAnchorSet; 2669 2670 // Whether the baseline is anchored to the top or the bottom. 2671 // If anchored to the top the baseline is always at prefAscent, 2672 // otherwise the baseline is at (height - prefDescent) 2673 private boolean baselineAnchoredToTop; 2674 2675 // Whether or not the baseline has been calculated. 2676 private boolean calcedBaseline; 2677 2678 BaselineGroup(boolean resizable) { 2679 super(Alignment.LEADING, resizable); 2680 prefAscent = prefDescent = -1; 2681 calcedBaseline = false; 2682 } 2683 2684 BaselineGroup(boolean resizable, boolean baselineAnchoredToTop) { 2685 this(resizable); 2686 this.baselineAnchoredToTop = baselineAnchoredToTop; 2687 baselineAnchorSet = true; 2688 } 2689 2690 void unset() { 2691 super.unset(); 2692 prefAscent = prefDescent = -1; 2693 calcedBaseline = false; 2694 } 2695 2696 void setValidSize(int axis, int origin, int size) { 2697 checkAxis(axis); 2698 if (prefAscent == -1) { 2699 super.setValidSize(axis, origin, size); 2700 } else { 2701 // do baseline layout 2702 baselineLayout(origin, size); 2703 } 2704 } 2705 2706 int calculateSize(int axis, int type) { 2707 checkAxis(axis); 2708 if (!calcedBaseline) { 2709 calculateBaselineAndResizeBehavior(); 2710 } 2711 if (type == MIN_SIZE) { 2712 return calculateMinSize(); 2713 } 2714 if (type == MAX_SIZE) { 2715 return calculateMaxSize(); 2716 } 2717 if (allSpringsHaveBaseline) { 2718 return prefAscent + prefDescent; 2719 } 2720 return Math.max(prefAscent + prefDescent, 2721 super.calculateSize(axis, type)); 2722 } 2723 2724 private void calculateBaselineAndResizeBehavior() { 2725 // calculate baseline 2726 prefAscent = 0; 2727 prefDescent = 0; 2728 int baselineSpringCount = 0; 2729 BaselineResizeBehavior resizeBehavior = null; 2730 for (Spring spring : springs) { 2731 if (spring.getAlignment() == null || 2732 spring.getAlignment() == Alignment.BASELINE) { 2733 int baseline = spring.getBaseline(); 2734 if (baseline >= 0) { 2735 if (spring.isResizable(VERTICAL)) { 2736 BaselineResizeBehavior brb = spring. 2737 getBaselineResizeBehavior(); 2738 if (resizeBehavior == null) { 2739 resizeBehavior = brb; 2740 } else if (brb != resizeBehavior) { 2741 resizeBehavior = BaselineResizeBehavior. 2742 CONSTANT_ASCENT; 2743 } 2744 } 2745 prefAscent = Math.max(prefAscent, baseline); 2746 prefDescent = Math.max(prefDescent, spring. 2747 getPreferredSize(VERTICAL) - baseline); 2748 baselineSpringCount++; 2749 } 2750 } 2751 } 2752 if (!baselineAnchorSet) { 2753 if (resizeBehavior == BaselineResizeBehavior.CONSTANT_DESCENT){ 2754 this.baselineAnchoredToTop = false; 2755 } else { 2756 this.baselineAnchoredToTop = true; 2757 } 2758 } 2759 allSpringsHaveBaseline = (baselineSpringCount == springs.size()); 2760 calcedBaseline = true; 2761 } 2762 2763 private int calculateMaxSize() { 2764 int maxAscent = prefAscent; 2765 int maxDescent = prefDescent; 2766 int nonBaselineMax = 0; 2767 for (Spring spring : springs) { 2768 int baseline; 2769 int springMax = spring.getMaximumSize(VERTICAL); 2770 if ((spring.getAlignment() == null || 2771 spring.getAlignment() == Alignment.BASELINE) && 2772 (baseline = spring.getBaseline()) >= 0) { 2773 int springPref = spring.getPreferredSize(VERTICAL); 2774 if (springPref != springMax) { 2775 switch (spring.getBaselineResizeBehavior()) { 2776 case CONSTANT_ASCENT: 2777 if (baselineAnchoredToTop) { 2778 maxDescent = Math.max(maxDescent, 2779 springMax - baseline); 2780 } 2781 break; 2782 case CONSTANT_DESCENT: 2783 if (!baselineAnchoredToTop) { 2784 maxAscent = Math.max(maxAscent, 2785 springMax - springPref + baseline); 2786 } 2787 break; 2788 default: // CENTER_OFFSET and OTHER, not resizable 2789 break; 2790 } 2791 } 2792 } else { 2793 // Not aligned along the baseline, or no baseline. 2794 nonBaselineMax = Math.max(nonBaselineMax, springMax); 2795 } 2796 } 2797 return Math.max(nonBaselineMax, maxAscent + maxDescent); 2798 } 2799 2800 private int calculateMinSize() { 2801 int minAscent = 0; 2802 int minDescent = 0; 2803 int nonBaselineMin = 0; 2804 if (baselineAnchoredToTop) { 2805 minAscent = prefAscent; 2806 } else { 2807 minDescent = prefDescent; 2808 } 2809 for (Spring spring : springs) { 2810 int springMin = spring.getMinimumSize(VERTICAL); 2811 int baseline; 2812 if ((spring.getAlignment() == null || 2813 spring.getAlignment() == Alignment.BASELINE) && 2814 (baseline = spring.getBaseline()) >= 0) { 2815 int springPref = spring.getPreferredSize(VERTICAL); 2816 BaselineResizeBehavior brb = spring. 2817 getBaselineResizeBehavior(); 2818 switch (brb) { 2819 case CONSTANT_ASCENT: 2820 if (baselineAnchoredToTop) { 2821 minDescent = Math.max(springMin - baseline, 2822 minDescent); 2823 } else { 2824 minAscent = Math.max(baseline, minAscent); 2825 } 2826 break; 2827 case CONSTANT_DESCENT: 2828 if (!baselineAnchoredToTop) { 2829 minAscent = Math.max( 2830 baseline - (springPref - springMin), 2831 minAscent); 2832 } else { 2833 minDescent = Math.max(springPref - baseline, 2834 minDescent); 2835 } 2836 break; 2837 default: 2838 // CENTER_OFFSET and OTHER are !resizable, use 2839 // the preferred size. 2840 minAscent = Math.max(baseline, minAscent); 2841 minDescent = Math.max(springPref - baseline, 2842 minDescent); 2843 break; 2844 } 2845 } else { 2846 // Not aligned along the baseline, or no baseline. 2847 nonBaselineMin = Math.max(nonBaselineMin, springMin); 2848 } 2849 } 2850 return Math.max(nonBaselineMin, minAscent + minDescent); 2851 } 2852 2853 /** 2854 * Lays out springs that have a baseline along the baseline. All 2855 * others are centered. 2856 */ 2857 private void baselineLayout(int origin, int size) { 2858 int ascent; 2859 int descent; 2860 if (baselineAnchoredToTop) { 2861 ascent = prefAscent; 2862 descent = size - ascent; 2863 } else { 2864 ascent = size - prefDescent; 2865 descent = prefDescent; 2866 } 2867 for (Spring spring : springs) { 2868 Alignment alignment = spring.getAlignment(); 2869 if (alignment == null || alignment == Alignment.BASELINE) { 2870 int baseline = spring.getBaseline(); 2871 if (baseline >= 0) { 2872 int springMax = spring.getMaximumSize(VERTICAL); 2873 int springPref = spring.getPreferredSize(VERTICAL); 2874 int height = springPref; 2875 int y; 2876 switch(spring.getBaselineResizeBehavior()) { 2877 case CONSTANT_ASCENT: 2878 y = origin + ascent - baseline; 2879 height = Math.min(descent, springMax - 2880 baseline) + baseline; 2881 break; 2882 case CONSTANT_DESCENT: 2883 height = Math.min(ascent, springMax - 2884 springPref + baseline) + 2885 (springPref - baseline); 2886 y = origin + ascent + 2887 (springPref - baseline) - height; 2888 break; 2889 default: // CENTER_OFFSET & OTHER, not resizable 2890 y = origin + ascent - baseline; 2891 break; 2892 } 2893 spring.setSize(VERTICAL, y, height); 2894 } else { 2895 setChildSize(spring, VERTICAL, origin, size); 2896 } 2897 } else { 2898 setChildSize(spring, VERTICAL, origin, size); 2899 } 2900 } 2901 } 2902 2903 int getBaseline() { 2904 if (springs.size() > 1) { 2905 // Force the baseline to be calculated 2906 getPreferredSize(VERTICAL); 2907 return prefAscent; 2908 } else if (springs.size() == 1) { 2909 return springs.get(0).getBaseline(); 2910 } 2911 return -1; 2912 } 2913 2914 BaselineResizeBehavior getBaselineResizeBehavior() { 2915 if (springs.size() == 1) { 2916 return springs.get(0).getBaselineResizeBehavior(); 2917 } 2918 if (baselineAnchoredToTop) { 2919 return BaselineResizeBehavior.CONSTANT_ASCENT; 2920 } 2921 return BaselineResizeBehavior.CONSTANT_DESCENT; 2922 } 2923 2924 // If the axis is VERTICAL, throws an IllegalStateException 2925 private void checkAxis(int axis) { 2926 if (axis == HORIZONTAL) { 2927 throw new IllegalStateException( 2928 "Baseline must be used along vertical axis"); 2929 } 2930 } 2931 } 2932 2933 2934 private final class ComponentSpring extends Spring { 2935 private Component component; 2936 private int origin; 2937 2938 // min/pref/max are either a value >= 0 or one of 2939 // DEFAULT_SIZE or PREFERRED_SIZE 2940 private final int min; 2941 private final int pref; 2942 private final int max; 2943 2944 // Baseline for the component, computed as necessary. 2945 private int baseline = -1; 2946 2947 // Whether or not the size has been requested yet. 2948 private boolean installed; 2949 2950 private ComponentSpring(Component component, int min, int pref, 2951 int max) { 2952 this.component = component; 2953 if (component == null) { 2954 throw new IllegalArgumentException( 2955 "Component must be non-null"); 2956 } 2957 2958 checkSize(min, pref, max, true); 2959 2960 this.min = min; 2961 this.max = max; 2962 this.pref = pref; 2963 2964 // getComponentInfo makes sure component is a child of the 2965 // Container GroupLayout is the LayoutManager for. 2966 getComponentInfo(component); 2967 } 2968 2969 int calculateMinimumSize(int axis) { 2970 if (isLinked(axis)) { 2971 return getLinkSize(axis, MIN_SIZE); 2972 } 2973 return calculateNonlinkedMinimumSize(axis); 2974 } 2975 2976 int calculatePreferredSize(int axis) { 2977 if (isLinked(axis)) { 2978 return getLinkSize(axis, PREF_SIZE); 2979 } 2980 int min = getMinimumSize(axis); 2981 int pref = calculateNonlinkedPreferredSize(axis); 2982 int max = getMaximumSize(axis); 2983 return Math.min(max, Math.max(min, pref)); 2984 } 2985 2986 int calculateMaximumSize(int axis) { 2987 if (isLinked(axis)) { 2988 return getLinkSize(axis, MAX_SIZE); 2989 } 2990 return Math.max(getMinimumSize(axis), 2991 calculateNonlinkedMaximumSize(axis)); 2992 } 2993 2994 boolean isVisible() { 2995 return getComponentInfo(getComponent()).isVisible(); 2996 } 2997 2998 int calculateNonlinkedMinimumSize(int axis) { 2999 if (!isVisible()) { 3000 return 0; 3001 } 3002 if (min >= 0) { 3003 return min; 3004 } 3005 if (min == PREFERRED_SIZE) { 3006 return calculateNonlinkedPreferredSize(axis); 3007 } 3008 assert (min == DEFAULT_SIZE); 3009 return getSizeAlongAxis(axis, component.getMinimumSize()); 3010 } 3011 3012 int calculateNonlinkedPreferredSize(int axis) { 3013 if (!isVisible()) { 3014 return 0; 3015 } 3016 if (pref >= 0) { 3017 return pref; 3018 } 3019 assert (pref == DEFAULT_SIZE || pref == PREFERRED_SIZE); 3020 return getSizeAlongAxis(axis, component.getPreferredSize()); 3021 } 3022 3023 int calculateNonlinkedMaximumSize(int axis) { 3024 if (!isVisible()) { 3025 return 0; 3026 } 3027 if (max >= 0) { 3028 return max; 3029 } 3030 if (max == PREFERRED_SIZE) { 3031 return calculateNonlinkedPreferredSize(axis); 3032 } 3033 assert (max == DEFAULT_SIZE); 3034 return getSizeAlongAxis(axis, component.getMaximumSize()); 3035 } 3036 3037 private int getSizeAlongAxis(int axis, Dimension size) { 3038 return (axis == HORIZONTAL) ? size.width : size.height; 3039 } 3040 3041 private int getLinkSize(int axis, int type) { 3042 if (!isVisible()) { 3043 return 0; 3044 } 3045 ComponentInfo ci = getComponentInfo(component); 3046 return ci.getLinkSize(axis, type); 3047 } 3048 3049 void setSize(int axis, int origin, int size) { 3050 super.setSize(axis, origin, size); 3051 this.origin = origin; 3052 if (size == UNSET) { 3053 baseline = -1; 3054 } 3055 } 3056 3057 int getOrigin() { 3058 return origin; 3059 } 3060 3061 void setComponent(Component component) { 3062 this.component = component; 3063 } 3064 3065 Component getComponent() { 3066 return component; 3067 } 3068 3069 int getBaseline() { 3070 if (baseline == -1) { 3071 Spring horizontalSpring = getComponentInfo(component). 3072 horizontalSpring; 3073 int width = horizontalSpring.getPreferredSize(HORIZONTAL); 3074 int height = getPreferredSize(VERTICAL); 3075 if (width > 0 && height > 0) { 3076 baseline = component.getBaseline(width, height); 3077 } 3078 } 3079 return baseline; 3080 } 3081 3082 BaselineResizeBehavior getBaselineResizeBehavior() { 3083 return getComponent().getBaselineResizeBehavior(); 3084 } 3085 3086 private boolean isLinked(int axis) { 3087 return getComponentInfo(component).isLinked(axis); 3088 } 3089 3090 void installIfNecessary(int axis) { 3091 if (!installed) { 3092 installed = true; 3093 if (axis == HORIZONTAL) { 3094 getComponentInfo(component).horizontalSpring = this; 3095 } else { 3096 getComponentInfo(component).verticalSpring = this; 3097 } 3098 } 3099 } 3100 3101 @Override 3102 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3103 return !isVisible(); 3104 } 3105 } 3106 3107 3108 /** 3109 * Spring representing the preferred distance between two components. 3110 */ 3111 private class PreferredGapSpring extends Spring { 3112 private final JComponent source; 3113 private final JComponent target; 3114 private final ComponentPlacement type; 3115 private final int pref; 3116 private final int max; 3117 3118 PreferredGapSpring(JComponent source, JComponent target, 3119 ComponentPlacement type, int pref, int max) { 3120 this.source = source; 3121 this.target = target; 3122 this.type = type; 3123 this.pref = pref; 3124 this.max = max; 3125 } 3126 3127 int calculateMinimumSize(int axis) { 3128 return getPadding(axis); 3129 } 3130 3131 int calculatePreferredSize(int axis) { 3132 if (pref == DEFAULT_SIZE || pref == PREFERRED_SIZE) { 3133 return getMinimumSize(axis); 3134 } 3135 int min = getMinimumSize(axis); 3136 int max = getMaximumSize(axis); 3137 return Math.min(max, Math.max(min, pref)); 3138 } 3139 3140 int calculateMaximumSize(int axis) { 3141 if (max == PREFERRED_SIZE || max == DEFAULT_SIZE) { 3142 return getPadding(axis); 3143 } 3144 return Math.max(getMinimumSize(axis), max); 3145 } 3146 3147 private int getPadding(int axis) { 3148 int position; 3149 if (axis == HORIZONTAL) { 3150 position = SwingConstants.EAST; 3151 } else { 3152 position = SwingConstants.SOUTH; 3153 } 3154 return getLayoutStyle0().getPreferredGap(source, 3155 target, type, position, host); 3156 } 3157 3158 @Override 3159 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3160 return false; 3161 } 3162 } 3163 3164 3165 /** 3166 * Spring represented a certain amount of space. 3167 */ 3168 private class GapSpring extends Spring { 3169 private final int min; 3170 private final int pref; 3171 private final int max; 3172 3173 GapSpring(int min, int pref, int max) { 3174 checkSize(min, pref, max, false); 3175 this.min = min; 3176 this.pref = pref; 3177 this.max = max; 3178 } 3179 3180 int calculateMinimumSize(int axis) { 3181 if (min == PREFERRED_SIZE) { 3182 return getPreferredSize(axis); 3183 } 3184 return min; 3185 } 3186 3187 int calculatePreferredSize(int axis) { 3188 return pref; 3189 } 3190 3191 int calculateMaximumSize(int axis) { 3192 if (max == PREFERRED_SIZE) { 3193 return getPreferredSize(axis); 3194 } 3195 return max; 3196 } 3197 3198 @Override 3199 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3200 return false; 3201 } 3202 } 3203 3204 3205 /** 3206 * Spring reprensenting the distance between any number of sources and 3207 * targets. The targets and sources are computed during layout. An 3208 * instance of this can either be dynamically created when 3209 * autocreatePadding is true, or explicitly created by the developer. 3210 */ 3211 private class AutoPreferredGapSpring extends Spring { 3212 List<ComponentSpring> sources; 3213 ComponentSpring source; 3214 private List<AutoPreferredGapMatch> matches; 3215 int size; 3216 int lastSize; 3217 private final int pref; 3218 private final int max; 3219 // Type of gap 3220 private ComponentPlacement type; 3221 private boolean userCreated; 3222 3223 private AutoPreferredGapSpring() { 3224 this.pref = PREFERRED_SIZE; 3225 this.max = PREFERRED_SIZE; 3226 this.type = ComponentPlacement.RELATED; 3227 } 3228 3229 AutoPreferredGapSpring(int pref, int max) { 3230 this.pref = pref; 3231 this.max = max; 3232 } 3233 3234 AutoPreferredGapSpring(ComponentPlacement type, int pref, int max) { 3235 this.type = type; 3236 this.pref = pref; 3237 this.max = max; 3238 this.userCreated = true; 3239 } 3240 3241 public void setSource(ComponentSpring source) { 3242 this.source = source; 3243 } 3244 3245 public void setSources(List<ComponentSpring> sources) { 3246 this.sources = new ArrayList<ComponentSpring>(sources); 3247 } 3248 3249 public void setUserCreated(boolean userCreated) { 3250 this.userCreated = userCreated; 3251 } 3252 3253 public boolean getUserCreated() { 3254 return userCreated; 3255 } 3256 3257 void unset() { 3258 lastSize = getSize(); 3259 super.unset(); 3260 size = 0; 3261 } 3262 3263 public void reset() { 3264 size = 0; 3265 sources = null; 3266 source = null; 3267 matches = null; 3268 } 3269 3270 public void calculatePadding(int axis) { 3271 size = UNSET; 3272 int maxPadding = UNSET; 3273 if (matches != null) { 3274 LayoutStyle p = getLayoutStyle0(); 3275 int position; 3276 if (axis == HORIZONTAL) { 3277 if (isLeftToRight()) { 3278 position = SwingConstants.EAST; 3279 } else { 3280 position = SwingConstants.WEST; 3281 } 3282 } else { 3283 position = SwingConstants.SOUTH; 3284 } 3285 for (int i = matches.size() - 1; i >= 0; i--) { 3286 AutoPreferredGapMatch match = matches.get(i); 3287 maxPadding = Math.max(maxPadding, 3288 calculatePadding(p, position, match.source, 3289 match.target)); 3290 } 3291 } 3292 if (size == UNSET) { 3293 size = 0; 3294 } 3295 if (maxPadding == UNSET) { 3296 maxPadding = 0; 3297 } 3298 if (lastSize != UNSET) { 3299 size += Math.min(maxPadding, lastSize); 3300 } 3301 } 3302 3303 private int calculatePadding(LayoutStyle p, int position, 3304 ComponentSpring source, 3305 ComponentSpring target) { 3306 int delta = target.getOrigin() - (source.getOrigin() + 3307 source.getSize()); 3308 if (delta >= 0) { 3309 int padding; 3310 if ((source.getComponent() instanceof JComponent) && 3311 (target.getComponent() instanceof JComponent)) { 3312 padding = p.getPreferredGap( 3313 (JComponent)source.getComponent(), 3314 (JComponent)target.getComponent(), type, position, 3315 host); 3316 } else { 3317 padding = 10; 3318 } 3319 if (padding > delta) { 3320 size = Math.max(size, padding - delta); 3321 } 3322 return padding; 3323 } 3324 return 0; 3325 } 3326 3327 public void addTarget(ComponentSpring spring, int axis) { 3328 int oAxis = (axis == HORIZONTAL) ? VERTICAL : HORIZONTAL; 3329 if (source != null) { 3330 if (areParallelSiblings(source.getComponent(), 3331 spring.getComponent(), oAxis)) { 3332 addValidTarget(source, spring); 3333 } 3334 } else { 3335 Component component = spring.getComponent(); 3336 for (int counter = sources.size() - 1; counter >= 0; 3337 counter--){ 3338 ComponentSpring source = sources.get(counter); 3339 if (areParallelSiblings(source.getComponent(), 3340 component, oAxis)) { 3341 addValidTarget(source, spring); 3342 } 3343 } 3344 } 3345 } 3346 3347 private void addValidTarget(ComponentSpring source, 3348 ComponentSpring target) { 3349 if (matches == null) { 3350 matches = new ArrayList<AutoPreferredGapMatch>(1); 3351 } 3352 matches.add(new AutoPreferredGapMatch(source, target)); 3353 } 3354 3355 int calculateMinimumSize(int axis) { 3356 return size; 3357 } 3358 3359 int calculatePreferredSize(int axis) { 3360 if (pref == PREFERRED_SIZE || pref == DEFAULT_SIZE) { 3361 return size; 3362 } 3363 return Math.max(size, pref); 3364 } 3365 3366 int calculateMaximumSize(int axis) { 3367 if (max >= 0) { 3368 return Math.max(getPreferredSize(axis), max); 3369 } 3370 return size; 3371 } 3372 3373 String getMatchDescription() { 3374 return (matches == null) ? "" : matches.toString(); 3375 } 3376 3377 public String toString() { 3378 return super.toString() + getMatchDescription(); 3379 } 3380 3381 @Override 3382 boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { 3383 return treatAutopaddingAsZeroSized; 3384 } 3385 } 3386 3387 3388 /** 3389 * Represents two springs that should have autopadding inserted between 3390 * them. 3391 */ 3392 private final static class AutoPreferredGapMatch { 3393 public final ComponentSpring source; 3394 public final ComponentSpring target; 3395 3396 AutoPreferredGapMatch(ComponentSpring source, ComponentSpring target) { 3397 this.source = source; 3398 this.target = target; 3399 } 3400 3401 private String toString(ComponentSpring spring) { 3402 return spring.getComponent().getName(); 3403 } 3404 3405 public String toString() { 3406 return "[" + toString(source) + "-" + toString(target) + "]"; 3407 } 3408 } 3409 3410 3411 /** 3412 * An extension of AutopaddingSpring used for container level padding. 3413 */ 3414 private class ContainerAutoPreferredGapSpring extends 3415 AutoPreferredGapSpring { 3416 private List<ComponentSpring> targets; 3417 3418 ContainerAutoPreferredGapSpring() { 3419 super(); 3420 setUserCreated(true); 3421 } 3422 3423 ContainerAutoPreferredGapSpring(int pref, int max) { 3424 super(pref, max); 3425 setUserCreated(true); 3426 } 3427 3428 public void addTarget(ComponentSpring spring, int axis) { 3429 if (targets == null) { 3430 targets = new ArrayList<ComponentSpring>(1); 3431 } 3432 targets.add(spring); 3433 } 3434 3435 public void calculatePadding(int axis) { 3436 LayoutStyle p = getLayoutStyle0(); 3437 int maxPadding = 0; 3438 int position; 3439 size = 0; 3440 if (targets != null) { 3441 // Leading 3442 if (axis == HORIZONTAL) { 3443 if (isLeftToRight()) { 3444 position = SwingConstants.WEST; 3445 } else { 3446 position = SwingConstants.EAST; 3447 } 3448 } else { 3449 position = SwingConstants.SOUTH; 3450 } 3451 for (int i = targets.size() - 1; i >= 0; i--) { 3452 ComponentSpring targetSpring = targets.get(i); 3453 int padding = 10; 3454 if (targetSpring.getComponent() instanceof JComponent) { 3455 padding = p.getContainerGap( 3456 (JComponent)targetSpring.getComponent(), 3457 position, host); 3458 maxPadding = Math.max(padding, maxPadding); 3459 padding -= targetSpring.getOrigin(); 3460 } else { 3461 maxPadding = Math.max(padding, maxPadding); 3462 } 3463 size = Math.max(size, padding); 3464 } 3465 } else { 3466 // Trailing 3467 if (axis == HORIZONTAL) { 3468 if (isLeftToRight()) { 3469 position = SwingConstants.EAST; 3470 } else { 3471 position = SwingConstants.WEST; 3472 } 3473 } else { 3474 position = SwingConstants.SOUTH; 3475 } 3476 if (sources != null) { 3477 for (int i = sources.size() - 1; i >= 0; i--) { 3478 ComponentSpring sourceSpring = sources.get(i); 3479 maxPadding = Math.max(maxPadding, 3480 updateSize(p, sourceSpring, position)); 3481 } 3482 } else if (source != null) { 3483 maxPadding = updateSize(p, source, position); 3484 } 3485 } 3486 if (lastSize != UNSET) { 3487 size += Math.min(maxPadding, lastSize); 3488 } 3489 } 3490 3491 private int updateSize(LayoutStyle p, ComponentSpring sourceSpring, 3492 int position) { 3493 int padding = 10; 3494 if (sourceSpring.getComponent() instanceof JComponent) { 3495 padding = p.getContainerGap( 3496 (JComponent)sourceSpring.getComponent(), position, 3497 host); 3498 } 3499 int delta = Math.max(0, getParent().getSize() - 3500 sourceSpring.getSize() - sourceSpring.getOrigin()); 3501 size = Math.max(size, padding - delta); 3502 return padding; 3503 } 3504 3505 String getMatchDescription() { 3506 if (targets != null) { 3507 return "leading: " + targets.toString(); 3508 } 3509 if (sources != null) { 3510 return "trailing: " + sources.toString(); 3511 } 3512 return "--"; 3513 } 3514 } 3515 3516 3517 // LinkInfo contains the set of ComponentInfosthat are linked along a 3518 // particular axis. 3519 private static class LinkInfo { 3520 private final int axis; 3521 private final List<ComponentInfo> linked; 3522 private int size; 3523 3524 LinkInfo(int axis) { 3525 linked = new ArrayList<ComponentInfo>(); 3526 size = UNSET; 3527 this.axis = axis; 3528 } 3529 3530 public void add(ComponentInfo child) { 3531 LinkInfo childMaster = child.getLinkInfo(axis, false); 3532 if (childMaster == null) { 3533 linked.add(child); 3534 child.setLinkInfo(axis, this); 3535 } else if (childMaster != this) { 3536 linked.addAll(childMaster.linked); 3537 for (ComponentInfo childInfo : childMaster.linked) { 3538 childInfo.setLinkInfo(axis, this); 3539 } 3540 } 3541 clearCachedSize(); 3542 } 3543 3544 public void remove(ComponentInfo info) { 3545 linked.remove(info); 3546 info.setLinkInfo(axis, null); 3547 if (linked.size() == 1) { 3548 linked.get(0).setLinkInfo(axis, null); 3549 } 3550 clearCachedSize(); 3551 } 3552 3553 public void clearCachedSize() { 3554 size = UNSET; 3555 } 3556 3557 public int getSize(int axis) { 3558 if (size == UNSET) { 3559 size = calculateLinkedSize(axis); 3560 } 3561 return size; 3562 } 3563 3564 private int calculateLinkedSize(int axis) { 3565 int size = 0; 3566 for (ComponentInfo info : linked) { 3567 ComponentSpring spring; 3568 if (axis == HORIZONTAL) { 3569 spring = info.horizontalSpring; 3570 } else { 3571 assert (axis == VERTICAL); 3572 spring = info.verticalSpring; 3573 } 3574 size = Math.max(size, 3575 spring.calculateNonlinkedPreferredSize(axis)); 3576 } 3577 return size; 3578 } 3579 } 3580 3581 /** 3582 * Tracks the horizontal/vertical Springs for a Component. 3583 * This class is also used to handle Springs that have their sizes 3584 * linked. 3585 */ 3586 private class ComponentInfo { 3587 // Component being layed out 3588 private Component component; 3589 3590 ComponentSpring horizontalSpring; 3591 ComponentSpring verticalSpring; 3592 3593 // If the component's size is linked to other components, the 3594 // horizontalMaster and/or verticalMaster reference the group of 3595 // linked components. 3596 private LinkInfo horizontalMaster; 3597 private LinkInfo verticalMaster; 3598 3599 private boolean visible; 3600 private Boolean honorsVisibility; 3601 3602 ComponentInfo(Component component) { 3603 this.component = component; 3604 updateVisibility(); 3605 } 3606 3607 public void dispose() { 3608 // Remove horizontal/vertical springs 3609 removeSpring(horizontalSpring); 3610 horizontalSpring = null; 3611 removeSpring(verticalSpring); 3612 verticalSpring = null; 3613 // Clean up links 3614 if (horizontalMaster != null) { 3615 horizontalMaster.remove(this); 3616 } 3617 if (verticalMaster != null) { 3618 verticalMaster.remove(this); 3619 } 3620 } 3621 3622 void setHonorsVisibility(Boolean honorsVisibility) { 3623 this.honorsVisibility = honorsVisibility; 3624 } 3625 3626 private void removeSpring(Spring spring) { 3627 if (spring != null) { 3628 ((Group)spring.getParent()).springs.remove(spring); 3629 } 3630 } 3631 3632 public boolean isVisible() { 3633 return visible; 3634 } 3635 3636 /** 3637 * Updates the cached visibility. 3638 * 3639 * @return true if the visibility changed 3640 */ 3641 boolean updateVisibility() { 3642 boolean honorsVisibility; 3643 if (this.honorsVisibility == null) { 3644 honorsVisibility = GroupLayout.this.getHonorsVisibility(); 3645 } else { 3646 honorsVisibility = this.honorsVisibility; 3647 } 3648 boolean newVisible = (honorsVisibility) ? 3649 component.isVisible() : true; 3650 if (visible != newVisible) { 3651 visible = newVisible; 3652 return true; 3653 } 3654 return false; 3655 } 3656 3657 public void setBounds(Insets insets, int parentWidth, boolean ltr) { 3658 int x = horizontalSpring.getOrigin(); 3659 int w = horizontalSpring.getSize(); 3660 int y = verticalSpring.getOrigin(); 3661 int h = verticalSpring.getSize(); 3662 3663 if (!ltr) { 3664 x = parentWidth - x - w; 3665 } 3666 component.setBounds(x + insets.left, y + insets.top, w, h); 3667 } 3668 3669 public void setComponent(Component component) { 3670 this.component = component; 3671 if (horizontalSpring != null) { 3672 horizontalSpring.setComponent(component); 3673 } 3674 if (verticalSpring != null) { 3675 verticalSpring.setComponent(component); 3676 } 3677 } 3678 3679 public Component getComponent() { 3680 return component; 3681 } 3682 3683 /** 3684 * Returns true if this component has its size linked to 3685 * other components. 3686 */ 3687 public boolean isLinked(int axis) { 3688 if (axis == HORIZONTAL) { 3689 return horizontalMaster != null; 3690 } 3691 assert (axis == VERTICAL); 3692 return (verticalMaster != null); 3693 } 3694 3695 private void setLinkInfo(int axis, LinkInfo linkInfo) { 3696 if (axis == HORIZONTAL) { 3697 horizontalMaster = linkInfo; 3698 } else { 3699 assert (axis == VERTICAL); 3700 verticalMaster = linkInfo; 3701 } 3702 } 3703 3704 public LinkInfo getLinkInfo(int axis) { 3705 return getLinkInfo(axis, true); 3706 } 3707 3708 private LinkInfo getLinkInfo(int axis, boolean create) { 3709 if (axis == HORIZONTAL) { 3710 if (horizontalMaster == null && create) { 3711 // horizontalMaster field is directly set by adding 3712 // us to the LinkInfo. 3713 new LinkInfo(HORIZONTAL).add(this); 3714 } 3715 return horizontalMaster; 3716 } else { 3717 assert (axis == VERTICAL); 3718 if (verticalMaster == null && create) { 3719 // verticalMaster field is directly set by adding 3720 // us to the LinkInfo. 3721 new LinkInfo(VERTICAL).add(this); 3722 } 3723 return verticalMaster; 3724 } 3725 } 3726 3727 public void clearCachedSize() { 3728 if (horizontalMaster != null) { 3729 horizontalMaster.clearCachedSize(); 3730 } 3731 if (verticalMaster != null) { 3732 verticalMaster.clearCachedSize(); 3733 } 3734 } 3735 3736 int getLinkSize(int axis, int type) { 3737 if (axis == HORIZONTAL) { 3738 return horizontalMaster.getSize(axis); 3739 } else { 3740 assert (axis == VERTICAL); 3741 return verticalMaster.getSize(axis); 3742 } 3743 } 3744 3745 } 3746 }