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