All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.layout.GroupLayout Maven / Gradle / Ivy

Go to download

Swing Layout Extensions goal is to make it easy to create professional cross platform layouts with Swing. This project has an eye towards the needs of GUI builders, such as NetBeans. This project consists of the following pieces: * Ability to get the baseline for components. * Ability to get the preferred gap between components. * A new LayoutManager that utilizes both of these concepts and is tuned toward a free-form drag and drop layout model as can be provided by GUI builders.

The newest version!
/*
 * Copyright (C) 2005-2006 Sun Microsystems, Inc. All rights reserved. Use is
 * subject to license terms.
 */ 

package org.jdesktop.layout;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import javax.swing.SwingConstants;
import javax.swing.JComponent;
import java.util.*;

/**
 * GroupLayout is a LayoutManager that hierarchically groups components to
 * achieve common, and not so common, layouts.  Grouping is done by instances
 * of the Group class.  GroupLayout supports two types of groups:
 * 
 *   
Sequential:A sequential group positions its child * elements sequentially, one after another. *
Parallel:A parallel group positions its child * elements in the same space on top of each other. Parallel groups * can also align the child elements along their baseline. *
* Each Group can contain any number of child groups, Components or gaps. * GroupLayout treats each axis independently. That is, there is a group * representing the horizontal axis, and a separate group representing the * vertical axis. The horizontal group is responsible for setting the x * and width of its contents, where as the vertical group is responsible for * setting the y and height of its contents. *

* The following code builds a simple layout consisting of two labels in * one column, followed by two textfields in the next column: *

 *   JComponent panel = ...;
 *   GroupLayout layout = new GroupLayout(panel);
 *   panel.setLayout(layout);
 *   layout.setAutocreateGaps(true);
 *   layout.setAutocreateContainerGaps(true);
 *   GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
 *   hGroup.add(layout.createParallelGroup().add(label1).add(label2)).
 *          add(layout.createParallelGroup().add(tf1).add(tf2));
 *   layout.setHorizontalGroup(hGroup);
 *   GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
 *   vGroup.add(layout.createParallelGroup(GroupLayout.BASELINE).add(label1).add(tf1)).
 *          add(layout.createParallelGroup(GroupLayout.BASELINE).add(label2).add(tf2));
 *   layout.setVerticalGroup(vGroup);
 * 
*

* This layout consists of the following: *

  • The horizontal axis consists of a sequential group containing two * parallel groups. The first parallel group consists of the labels, * with the second parallel group consisting of the text fields. *
  • The vertical axis similarly consists of a sequential group * containing two parallel groups. The parallel groups align their * contents along the baseline. The first parallel group consists * of the first label and text field, and the second group consists * of the second label and text field. *
* There are a couple of things to notice in this code: *
    *
  • You need not explicitly add the components to the container, this * is indirectly done by using one of the add methods. *
  • The various add methods of Groups return * themselves. This allows for easy chaining of invocations. For * example, group.add(label1).add(label2); is equivalent to * group.add(label1);group.add(label2);. *
  • There are no public constructors for the Groups, instead * use the create methods of GroupLayout. *
* GroupLayout offer the ability to automatically insert the appropriate gap * between components. This can be turned on using the * setAutocreateGaps() method. Similarly you can use * the setAutocreateContainerGaps() method to insert gaps * between the components and the container. * * @version $Revision: 1.24 $ * @author Tomas Pavek * @author Jan Stola * @author Scott Violet */ public class GroupLayout implements LayoutManager2 { // Used in size calculations private static final int MIN_SIZE = 0; private static final int PREF_SIZE = 1; private static final int MAX_SIZE = 2; // Used by prepare, indicates min, pref or max isn't going to be used. private static final int SPECIFIC_SIZE = 3; private static final int UNSET = Integer.MIN_VALUE; /** * Possible argument when linking sizes of components. Specifies the * the two component should share the same size along the horizontal * axis. * * @see #linkSize(java.awt.Component[],int) */ public static final int HORIZONTAL = 1; /** * Possible argument when linking sizes of components. Specifies the * the two component should share the same size along the vertical * axis. * * @see #linkSize(java.awt.Component[],int) */ public static final int VERTICAL = 2; private static final int NO_ALIGNMENT = 0; /** * Possible alignment type. Indicates the elements should be * aligned to the origin. For the horizontal axis with a left to * right orientation this means aligned to the left. * * @see #createParallelGroup(int) */ public static final int LEADING = 1; /** * Possible alignment type. Indicates the elements should be * aligned to the end. For the horizontal axis with a left to * right orientation this means aligned to the right. * * @see #createParallelGroup(int) */ public static final int TRAILING = 2; /** * Possible alignment type. Indicates the elements should centered in * the spaced provided. * * @see #createParallelGroup(int) */ public static final int CENTER = 4; /** * Possible alignment type. Indicates the elements should aligned along * their baseline. * * @see #createParallelGroup(int) */ public static final int BASELINE = 3; /** * Possible value for the add methods that takes a Component. * Indicates the size from the component should be used. */ public static final int DEFAULT_SIZE = -1; /** * Possible value for the add methods that takes a Component. * Indicates the preferred size should be used. */ public static final int PREFERRED_SIZE = -2; // Whether or not we automatically try and create the preferred // padding between components. private boolean autocreatePadding; // Whether or not we automatically try and create the preferred // padding between containers private boolean autocreateContainerPadding; /** * Group responsible for layout along the horizontal axis. This is NOT * the user specified group, use getHorizontalGroup to dig that out. */ private Group horizontalGroup; /** * Group responsible for layout along the vertical axis. This is NOT * the user specified group, use getVerticalGroup to dig that out. */ private Group verticalGroup; // Maps from Component to ComponentInfo. This is used for tracking // information specific to a Component. private Map componentInfos; // Container we're doing layout for. private Container host; // Used by areParallelSiblings, cached to avoid excessive garbage. private Set tmpParallelSet; // Indicates Springs have changed in some way since last change. private boolean springsChanged; // Indicates invalidateLayout has been invoked. private boolean isValid; // Whether or not any preferred padding (or container padding) springs exist private boolean hasPreferredPaddingSprings; /** * The LayoutStyle instance to use, if null the sharedInstance is used. */ private LayoutStyle layoutStyle; /** * If true, components that are not visible are treated as though they * aren't there. */ private boolean honorsVisibility; private static void checkSize(int min, int pref, int max, boolean isComponentSpring) { checkResizeType(min, isComponentSpring); if (!isComponentSpring && pref < 0) { throw new IllegalArgumentException("Pref must be >= 0"); } else if (isComponentSpring) { checkResizeType(pref, true); } checkResizeType(max, isComponentSpring); checkLessThan(min, pref); checkLessThan(pref, max); } private static void checkResizeType(int type, boolean isComponentSpring) { if (type < 0 && ((isComponentSpring && type != DEFAULT_SIZE && type != PREFERRED_SIZE) || (!isComponentSpring && type != PREFERRED_SIZE))) { throw new IllegalArgumentException("Invalid size"); } } private static void checkLessThan(int min, int max) { if (min >= 0 && max >= 0 && min > max) { throw new IllegalArgumentException( "Following is not met: min<=pref<=max"); } } /** * Creates a GroupLayout for the specified JComponent. * * @param host the Container to layout * @throws IllegalArgumentException if host is null */ public GroupLayout(Container host) { if (host == null) { throw new IllegalArgumentException("Container must be non-null"); } honorsVisibility = true; this.host = host; setHorizontalGroup(createParallelGroup(LEADING, true)); setVerticalGroup(createParallelGroup(LEADING, true)); componentInfos = new HashMap(); tmpParallelSet = new HashSet(); } /** * Sets whether component visiblity is considered when sizing and * positioning components. A value of true indicates that * non-visible components should not be treated as part of the * layout. A value of false indicates that components should be * positioned and sized regardless of visibility. *

* A value of false is useful when the visibility of components * is dynamically adjusted and you don't want surrounding components and * the sizing to change. *

* The specified value is used for components that do not have an * explicit visibility specified. *

* The default is true. * * @param honorsVisibility whether component visiblity is considered when * sizing and positioning components * @see #setHonorsVisibility(Component,Boolean) */ public void setHonorsVisibility(boolean honorsVisibility) { if (this.honorsVisibility != honorsVisibility) { this.honorsVisibility = honorsVisibility; springsChanged = true; isValid = false; invalidateHost(); } } /** * Returns whether component visiblity is considered when sizing and * positioning components. * * @return whether component visiblity is considered when sizing and * positioning components */ public boolean getHonorsVisibility() { return honorsVisibility; } /** * Sets whether the component's visiblity is considered for * sizing and positioning. A value of Boolean.TRUE * indicates that if component is not visible it should * not be treated as part of the layout. A value of false * indicates that component is positioned and sized * regardless of it's visibility. A value of null * indicates the value specified by the single argument method * setHonorsVisibility should be used. *

* If component is not a child of the Container this * GroupLayout is managing, it will be added to the * Container. * * @param component the component * @param honorsVisibility whether component's visiblity should be * considered for sizing and positioning * @throws IllegalArgumentException if component is null * @see #setHonorsVisibility(boolean) */ public void setHonorsVisibility(Component component, Boolean honorsVisibility) { if (component == null) { throw new IllegalArgumentException("Component must be non-null"); } getComponentInfo(component).setHonorsVisibility(honorsVisibility); springsChanged = true; isValid = false; invalidateHost(); } /** * Returns a textual description of this GroupLayout. The return value * is intended for debugging purposes only. * * @return textual description of this GroupLayout **/ public String toString() { if (springsChanged) { registerComponents(horizontalGroup, HORIZONTAL); registerComponents(verticalGroup, VERTICAL); } StringBuffer buffer = new StringBuffer(); buffer.append("HORIZONTAL\n"); dump(buffer, horizontalGroup, " ", HORIZONTAL); buffer.append("\nVERTICAL\n"); dump(buffer, verticalGroup, " ", VERTICAL); return buffer.toString(); } private void dump(StringBuffer buffer, Spring spring, String indent, int axis) { String origin = ""; String padding = ""; if (spring instanceof ComponentSpring) { ComponentSpring cSpring = (ComponentSpring)spring; origin = Integer.toString(cSpring.getOrigin()) + " "; String name = cSpring.getComponent().getName(); if (name != null) { origin = "name=" + name + ", "; } } if (spring instanceof AutopaddingSpring) { AutopaddingSpring paddingSpring = (AutopaddingSpring)spring; padding = ", userCreated=" + paddingSpring.getUserCreated() + ", matches=" + paddingSpring.getMatchDescription(); } buffer.append(indent + spring.getClass().getName() + " " + Integer.toHexString(spring.hashCode()) + " " + origin + ", size=" + spring.getSize() + ", alignment=" + spring.getAlignment() + " prefs=[" + spring.getMinimumSize(axis) + " " + spring.getPreferredSize(axis) + " " + spring.getMaximumSize(axis) + padding + "]\n"); if (spring instanceof Group) { List springs = ((Group)spring).springs; indent += " "; for (int counter = 0; counter < springs.size(); counter++) { dump(buffer, (Spring)springs.get(counter), indent, axis); } } } /** * Sets whether or not a gap between components * should automatically be created. For example, if this is true * and you add two components to a SequentialGroup a * gap between the two will automatically be created. The default * is false. * * @param autocreatePadding whether or not to automatically created a gap * between components and the container */ public void setAutocreateGaps(boolean autocreatePadding) { if (this.autocreatePadding != autocreatePadding) { this.autocreatePadding = autocreatePadding; invalidateHost(); } } /** * Returns true if gaps between components are automatically be created. * * @return true if gaps between components should automatically be created */ public boolean getAutocreateGaps() { return autocreatePadding; } /** * Sets whether or not gaps between the container and the first/last * components should automatically be created. The default * is false. * * @param autocreatePadding whether or not to automatically create * gaps between the container and first/last components. */ public void setAutocreateContainerGaps(boolean autocreatePadding) { if (autocreatePadding != autocreateContainerPadding) { autocreateContainerPadding = autocreatePadding; horizontalGroup = createTopLevelGroup(getHorizontalGroup()); verticalGroup = createTopLevelGroup(getVerticalGroup()); invalidateHost(); } } /** * Returns whether or not gaps between the container and the * first/last components should automatically be created. The default * is false. * * @return whether or not the gaps between the container and the * first/last components should automatically be created */ public boolean getAutocreateContainerGaps() { return autocreateContainerPadding; } /** * Sets the Group that is responsible for * layout along the horizontal axis. * * @param group Group responsible for layout along * the horizontal axis * @throws IllegalArgumentException if group is null */ public void setHorizontalGroup(Group group) { if (group == null) { throw new IllegalArgumentException("Group must be non-null"); } horizontalGroup = createTopLevelGroup(group); invalidateHost(); } /** * Returns the Group that is responsible for * layout along the horizontal axis. * * @return ParallelGroup responsible for layout along * the horizontal axis. */ public Group getHorizontalGroup() { int index = 0; if (horizontalGroup.springs.size() > 1) { index = 1; } return (Group)horizontalGroup.springs.get(index); } /** * Sets the Group that is responsible for * layout along the vertical axis. * * @param group Group responsible for layout along * the vertical axis. * @throws IllegalArgumentException if group is null. */ public void setVerticalGroup(Group group) { if (group == null) { throw new IllegalArgumentException("Group must be non-null"); } verticalGroup = createTopLevelGroup(group); invalidateHost(); } /** * Returns the ParallelGroup that is responsible for * layout along the vertical axis. * * @return ParallelGroup responsible for layout along * the vertical axis. */ public Group getVerticalGroup() { int index = 0; if (verticalGroup.springs.size() > 1) { index = 1; } return (Group)verticalGroup.springs.get(index); } /** * Wraps the user specified group in a sequential group. If * container gaps should be generate the necessary springs are * added. */ private Group createTopLevelGroup(Group specifiedGroup) { SequentialGroup group = createSequentialGroup(); if (getAutocreateContainerGaps()) { group.addSpring(new ContainerAutopaddingSpring()); group.add(specifiedGroup); group.addSpring(new ContainerAutopaddingSpring()); } else { group.add(specifiedGroup); } return group; } /** * Creates and returns a SequentialGroup. * * @return a new SequentialGroup */ public SequentialGroup createSequentialGroup() { return new SequentialGroup(); } /** * Creates and returns a ParallelGroup with a * LEADING alignment. This is a cover method for the more * general createParallelGroup(int) method. * * @return a new ParallelGroup * @see #createParallelGroup(int) */ public ParallelGroup createParallelGroup() { return createParallelGroup(LEADING); } /** * Creates and returns an ParallelGroup. The alignment * specifies how children elements should be positioned when the * the parallel group is given more space than necessary. For example, * if a ParallelGroup with an alignment of TRAILING is given 100 pixels * and a child only needs 50 pixels, the child will be positioned at the * position 50. * * @param alignment alignment for the elements of the Group, one * of LEADING, TRAILING, * CENTER or BASELINE. * @throws IllegalArgumentException if alignment is not one of * LEADING, TRAILING, * CENTER or BASELINE * @return a new ParallelGroup */ public ParallelGroup createParallelGroup(int alignment) { return createParallelGroup(alignment, true); } /** * Creates and returns an ParallelGroup. The alignment * specifies how children elements should be positioned when the * the parallel group is given more space than necessary. For example, * if a ParallelGroup with an alignment of TRAILING is given 100 pixels * and a child only needs 50 pixels, the child will be positioned at the * position 50. * * @param alignment alignment for the elements of the Group, one * of LEADING, TRAILING, * CENTER or BASELINE. * @param resizable whether or not the group is resizable. If the group * is not resizable the min/max size will be the same as the * preferred. * @throws IllegalArgumentException if alignment is not one of * LEADING, TRAILING, * CENTER or BASELINE * @return a new ParallelGroup */ public ParallelGroup createParallelGroup(int alignment, boolean resizable) { if (alignment == BASELINE) { return new BaselineGroup(resizable); } return new ParallelGroup(alignment, resizable); } /** * Creates and returns a ParallelGroup that aligns it's * elements along the baseline. * * @param resizable whether the group is resizable * @param anchorBaselineToTop whether the baseline is anchored to * the top or bottom of the group * @see #createBaselineGroup * @see ParallelGroup */ public ParallelGroup createBaselineGroup(boolean resizable, boolean anchorBaselineToTop) { return new BaselineGroup(resizable, anchorBaselineToTop); } /** * Forces the set of components to have the same size. * This can be used multiple times to force * any number of components to share the same size. *

* Linked Components are not be resizable. * * @param components Components to force to have same size. * @throws IllegalArgumentException if components is * null, or contains null. */ public void linkSize(Component[] components) { linkSize(components, HORIZONTAL | VERTICAL); } /** * Forces the set of components to have the same size. * This can be used multiple times to force * any number of components to share the same size. *

* Linked Components are not be resizable. * * @param components Components to force to have same size. * @param axis Axis to bind size, one of HORIZONTAL, VERTICAL or * HORIZONTAL | VERTICAL * @throws IllegalArgumentException if components is * null, or contains null. * @throws IllegalArgumentException if axis does not * contain HORIZONTAL or VERTICAL */ public void linkSize(Component[] components, int axis) { if (components == null) { throw new IllegalArgumentException("Components must be non-null"); } boolean horizontal = ((axis & HORIZONTAL) == HORIZONTAL); boolean vertical = ((axis & VERTICAL) == VERTICAL); if (!vertical && !horizontal) { throw new IllegalArgumentException( "Axis must contain HORIZONTAL or VERTICAL"); } for (int counter = components.length - 1; counter >= 0; counter--) { Component c = components[counter]; if (components[counter] == null) { throw new IllegalArgumentException( "Components must be non-null"); } // Force the component to be added getComponentInfo(c); } if (horizontal) { linkSize0(components, HORIZONTAL); } if (vertical) { linkSize0(components, VERTICAL); } invalidateHost(); } private void linkSize0(Component[] components, int axis) { LinkInfo master = getComponentInfo( components[components.length - 1]).getLinkInfo(axis); for (int counter = components.length - 2; counter >= 0; counter--) { master.add(getComponentInfo(components[counter])); } } /** * Removes an existing component replacing it with the specified component. * * @param existingComponent the Component that should be removed and * replaced with newComponent * @param newComponent the Component to put in existingComponents place * @throws IllegalArgumentException is either of the Components are null or * if existingComponent is not being managed by this layout manager */ public void replace(Component existingComponent, Component newComponent) { if (existingComponent == null || newComponent == null) { throw new IllegalArgumentException("Components must be non-null"); } // Make sure all the components have been registered, otherwise we may // not update the correct Springs. if (springsChanged) { registerComponents(horizontalGroup, HORIZONTAL); registerComponents(verticalGroup, VERTICAL); } ComponentInfo info = (ComponentInfo)componentInfos. remove(existingComponent); if (info == null) { throw new IllegalArgumentException("Component must already exist"); } host.remove(existingComponent); if (newComponent.getParent() != host) { host.add(newComponent); } info.setComponent(newComponent); componentInfos.put(newComponent, info); invalidateHost(); } /** * Sets the LayoutStyle this GroupLayout is to use. A value of null can * be used to indicate the shared instance of LayoutStyle should be used. * * @param layoutStyle the LayoutStyle to use */ public void setLayoutStyle(LayoutStyle layoutStyle) { this.layoutStyle = layoutStyle; invalidateHost(); } /** * Returns the LayoutStyle instance to use * * @return the LayoutStyle instance to use */ public LayoutStyle getLayoutStyle() { return layoutStyle; } private LayoutStyle getLayoutStyle0() { LayoutStyle layoutStyle = getLayoutStyle(); if (layoutStyle == null) { layoutStyle = LayoutStyle.getSharedInstance(); } return layoutStyle; } private void invalidateHost() { if (host instanceof JComponent) { ((JComponent)host).revalidate(); } else { host.invalidate(); } host.repaint(); } // // LayoutManager // /** * Notification that a Component has been added to * the parent container. Developers should not invoke this method * directly, instead you should use one of the Group * methods to add a Component. * * @param name the string to be associated with the component * @param component the Component to be added */ public void addLayoutComponent(String name, Component component) { } /** * Notification that a Component has been removed from * the parent container. You should not invoke this method * directly, instead invoke remove on the parent * Container. * * @param component the component to be removed * @see java.awt.Component#remove */ public void removeLayoutComponent(Component component) { ComponentInfo info = (ComponentInfo)componentInfos.remove(component); if (info != null) { info.dispose(); springsChanged = true; isValid = false; } } /** * Returns the preferred size for the specified container. * * @param parent the container to return size for * @throws IllegalArgumentException if parent is not * the same Container that this was created with * @throws IllegalStateException if any of the components added to * this layout are not in both a horizontal and vertical group * @see java.awt.Container#getPreferredSize */ public Dimension preferredLayoutSize(Container parent) { checkParent(parent); prepare(PREF_SIZE); return adjustSize(horizontalGroup.getPreferredSize(HORIZONTAL), verticalGroup.getPreferredSize(VERTICAL)); } /** * Returns the minimum size for the specified container. * * @param parent the container to return size for * @throws IllegalArgumentException if parent is not * the same Container that this was created with * @throws IllegalStateException if any of the components added to * this layout are not in both a horizontal and vertical group * @see java.awt.Container#getMinimumSize */ public Dimension minimumLayoutSize(Container parent) { checkParent(parent); prepare(MIN_SIZE); return adjustSize(horizontalGroup.getMinimumSize(HORIZONTAL), verticalGroup.getMinimumSize(VERTICAL)); } /** * Lays out the specified container. * * @param parent the container to be laid out * @throws IllegalStateException if any of the components added to * this layout are not in both a horizontal and vertical group */ public void layoutContainer(Container parent) { // Step 1: Prepare for layout. prepare(SPECIFIC_SIZE); Insets insets = parent.getInsets(); int width = parent.getWidth() - insets.left - insets.right; int height = parent.getHeight() - insets.top - insets.bottom; boolean ltr = isLeftToRight(); if (getAutocreateGaps() || getAutocreateContainerGaps() || hasPreferredPaddingSprings) { // Step 2: Calculate autopadding springs calculateAutopadding(horizontalGroup, HORIZONTAL, SPECIFIC_SIZE, 0, width); calculateAutopadding(verticalGroup, VERTICAL, SPECIFIC_SIZE, 0, height); } // Step 3: set the size of the groups. horizontalGroup.setSize(HORIZONTAL, 0, width); verticalGroup.setSize(VERTICAL, 0, height); // Step 4: apply the size to the components. Iterator componentInfo = componentInfos.values().iterator(); while (componentInfo.hasNext()) { ComponentInfo info = (ComponentInfo)componentInfo.next(); Component c = info.getComponent(); info.setBounds(insets, width, ltr); } } // // LayoutManager2 // /** * Notification that a Component has been added to * the parent container. You should not invoke this method * directly, instead you should use one of the Group * methods to add a Component. * * @param component The component added * @param constraints Description of where to place the component. */ public void addLayoutComponent(Component component, Object constraints) { } /** * Returns the maximum size for the specified container. * * @param parent the container to return size for * @throws IllegalArgumentException if parent is not * the same Container that this was created with * @throws IllegalStateException if any of the components added to * this layout are not in both a horizontal and vertical group * @see java.awt.Container#getMaximumSize */ public Dimension maximumLayoutSize(Container parent) { checkParent(parent); prepare(MAX_SIZE); return adjustSize(horizontalGroup.getMaximumSize(HORIZONTAL), verticalGroup.getMaximumSize(VERTICAL)); } /** * Returns the alignment along the x axis. This specifies how * the component would like to be aligned relative to other * components. The value should be a number between 0 and 1 * where 0 represents alignment along the origin, 1 is aligned * the furthest away from the origin, 0.5 is centered, etc. * * @param parent Container hosting this LayoutManager * @throws IllegalArgumentException if parent is not * the same Container that this was created with * @return alignment */ public float getLayoutAlignmentX(Container parent) { checkParent(parent); return .5f; } /** * Returns the alignment along the y axis. This specifies how * the component would like to be aligned relative to other * components. The value should be a number between 0 and 1 * where 0 represents alignment along the origin, 1 is aligned * the furthest away from the origin, 0.5 is centered, etc. * * @param parent Container hosting this LayoutManager * @throws IllegalArgumentException if parent is not * the same Container that this was created with * @return alignment */ public float getLayoutAlignmentY(Container parent) { checkParent(parent); return .5f; } /** * Invalidates the layout, indicating that if the layout manager * has cached information it should be discarded. * * @param parent Container hosting this LayoutManager * @throws IllegalArgumentException if parent is not * the same Container that this was created with */ public void invalidateLayout(Container parent) { checkParent(parent); // invalidateLayout is called from Container.invalidate, which // does NOT grab the treelock. All other methods do. To make sure // there aren't any possible threading problems we grab the tree lock // here. synchronized(parent.getTreeLock()) { isValid = false; } } private void prepare(int sizeType) { boolean visChanged = false; // Step 1: If not-valid, clear springs and update visibility. if (!isValid) { isValid = true; horizontalGroup.setSize(HORIZONTAL, UNSET, UNSET); verticalGroup.setSize(VERTICAL, UNSET, UNSET); for (Iterator cis = componentInfos.values().iterator(); cis.hasNext();) { ComponentInfo ci = (ComponentInfo)cis.next(); if (ci.updateVisibility()) { visChanged = true; } ci.clearCachedSize(); } } // Step 2: Make sure components are bound to ComponentInfos if (springsChanged) { registerComponents(horizontalGroup, HORIZONTAL); registerComponents(verticalGroup, VERTICAL); } // Step 3: Adjust the autopadding. This removes existing // autopadding, then recalculates where it should go. if (springsChanged || visChanged) { checkComponents(); horizontalGroup.removeAutopadding(); verticalGroup.removeAutopadding(); if (getAutocreateGaps()) { insertAutopadding(true); } else if (hasPreferredPaddingSprings || getAutocreateContainerGaps()) { insertAutopadding(false); } springsChanged = false; } // Step 4: (for min/pref/max size calculations only) calculate the // autopadding. This invokes for unsetting the calculated values, then // recalculating them. // If sizeType == SPECIFIC_SIZE, it indicates we're doing layout, this // step will be done later on. if (sizeType != SPECIFIC_SIZE && (getAutocreateGaps() || getAutocreateContainerGaps() || hasPreferredPaddingSprings)) { calculateAutopadding(horizontalGroup, HORIZONTAL, sizeType, 0, 0); calculateAutopadding(verticalGroup, VERTICAL, sizeType, 0, 0); } } private void calculateAutopadding(Group group, int axis, int sizeType, int origin, int size) { group.unsetAutopadding(); switch(sizeType) { case MIN_SIZE: size = group.getMinimumSize(axis); break; case PREF_SIZE: size = group.getPreferredSize(axis); break; case MAX_SIZE: size = group.getMaximumSize(axis); break; } group.setSize(axis, origin, size); group.calculateAutopadding(axis); } private void checkComponents() { Iterator infos = componentInfos.values().iterator(); while (infos.hasNext()) { ComponentInfo info = (ComponentInfo)infos.next(); if (info.horizontalSpring == null) { throw new IllegalStateException(info.component + " is not attached to a horizontal group"); } if (info.verticalSpring == null) { throw new IllegalStateException(info.component + " is not attached to a vertical group"); } } } private void registerComponents(Group group, int axis) { List springs = group.springs; for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = (Spring)springs.get(counter); if (spring instanceof ComponentSpring) { ((ComponentSpring)spring).installIfNecessary(axis); } else if (spring instanceof Group) { registerComponents((Group)spring, axis); } } } private Dimension adjustSize(int width, int height) { Insets insets = host.getInsets(); return new Dimension(width + insets.left + insets.right, height + insets.top + insets.bottom); } private void checkParent(Container parent) { if (parent != host) { throw new IllegalArgumentException( "GroupLayout can only be used with one Container at a time"); } } /** * Returns the ComponentInfo for the specified Component. */ private ComponentInfo getComponentInfo(Component component) { ComponentInfo info = (ComponentInfo)componentInfos.get(component); if (info == null) { info = new ComponentInfo(component); componentInfos.put(component, info); if (component.getParent() != host) { host.add(component); } } return info; } /** * Adjusts the autopadding springs for the horizontal and vertical * groups. If insert is true this will insert auto padding * springs, otherwise this will only adjust the springs that * comprise auto preferred padding springs. */ private void insertAutopadding(boolean insert) { horizontalGroup.insertAutopadding(HORIZONTAL, new ArrayList(1), new ArrayList(1), new ArrayList(1), new ArrayList(1), insert); verticalGroup.insertAutopadding(VERTICAL, new ArrayList(1), new ArrayList(1), new ArrayList(1), new ArrayList(1), insert); } /** * Returns true if the two Components have a common ParallelGroup ancestor * along the particular axis. */ private boolean areParallelSiblings(Component source, Component target, int axis) { ComponentInfo sourceInfo = getComponentInfo(source); ComponentInfo targetInfo = getComponentInfo(target); Spring sourceSpring; Spring targetSpring; if (axis == HORIZONTAL) { sourceSpring = sourceInfo.horizontalSpring; targetSpring = targetInfo.horizontalSpring; } else { sourceSpring = sourceInfo.verticalSpring; targetSpring = targetInfo.verticalSpring; } Set sourcePath = tmpParallelSet; sourcePath.clear(); Spring spring = sourceSpring.getParent(); while (spring != null) { sourcePath.add(spring); spring = spring.getParent(); } spring = targetSpring.getParent(); while (spring != null) { if (sourcePath.contains(spring)) { sourcePath.clear(); while (spring != null) { if (spring instanceof ParallelGroup) { return true; } spring = spring.getParent(); } return false; } spring = spring.getParent(); } sourcePath.clear(); return false; } private boolean isLeftToRight() { return host.getComponentOrientation().isLeftToRight(); } /** * Spring consists of a range: min, pref and max a value some where in * the middle of that and a location. Subclasses must override * methods to get the min/max/pref and will likely want to override * the setSize method. Spring automatically caches the * min/max/pref. If the min/pref/max has internally changes, or needs * to be updated you must invoked clear. */ abstract class Spring { private int size; private int min; private int max; private int pref; private Spring parent; private int alignment; Spring() { min = pref = max = UNSET; } /** * Calculates and returns the minimum size. * * @param axis the axis of layout; one of HORIZONTAL or VERTICAL * @return the minimum size */ abstract int calculateMinimumSize(int axis); /** * Calculates and returns the preferred size. * * @param axis the axis of layout; one of HORIZONTAL or VERTICAL * @return the preferred size */ abstract int calculatePreferredSize(int axis); /** * Calculates and returns the minimum size. * * @param axis the axis of layout; one of HORIZONTAL or VERTICAL * @return the minimum size */ abstract int calculateMaximumSize(int axis); /** * Sets the parent of this Spring. */ void setParent(Spring parent) { this.parent = parent; } /** * Returns the parent of this spring. */ Spring getParent() { return parent; } // This is here purely as a conveniance for ParallelGroup to avoid // having to track alignment separately. void setAlignment(int alignment) { this.alignment = alignment; } int getAlignment() { return alignment; } /** * Returns the minimum size. */ final int getMinimumSize(int axis) { if (min == UNSET) { min = constrain(calculateMinimumSize(axis)); } return min; } /** * Returns the preferred size. */ final int getPreferredSize(int axis) { if (pref == UNSET) { pref = constrain(calculatePreferredSize(axis)); } return pref; } /** * Returns the maximum size. */ final int getMaximumSize(int axis) { if (max == UNSET) { max = constrain(calculateMaximumSize(axis)); } return max; } /** * Resets the cached min/max/pref. */ void unset() { size = min = pref = max = UNSET; } /** * Sets the value and location of the spring. Subclasses * will want to invoke super, then do any additional sizing. * * @param axis HORIZONTAL or VERTICAL * @param origin of this Spring * @param size of the Spring. If size is UNSET, this invokes * clear. */ void setSize(int axis, int origin, int size) { this.size = size; if (size == UNSET) { unset(); } } /** * Returns the current size. */ int getSize() { return size; } int constrain(int value) { return Math.min(value, Short.MAX_VALUE); } int getBaseline() { return -1; } int getBaselineResizeBehavior() { return Baseline.BRB_OTHER; } final boolean isResizable(int axis) { int min = getMinimumSize(axis); int pref = getPreferredSize(axis); return (min != pref || pref != getMaximumSize(axis)); } /** * Returns true if this Spring will ALWAYS have a zero size. This should * NOT check the current size, rather it's meant to * quickly test if this Spring will always have a zero size. */ abstract boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized); } /** * Group provides for commonality between the two types of operations * supported by GroupLayout: laying out components one * after another (SequentialGroup) or layout on top * of each other (ParallelGroup). Use one of * createSequentialGroup or * createParallelGroup to create one. */ public abstract class Group extends Spring { // private int origin; // private int size; List springs; Group() { springs = new ArrayList(); } int indexOf(Spring spring) { return springs.indexOf(spring); } /** * Adds the Spring to the list of Springs and returns * the receiver. */ Group addSpring(Spring spring) { springs.add(spring); spring.setParent(this); if (!(spring instanceof AutopaddingSpring) || !((AutopaddingSpring)spring).getUserCreated()) { springsChanged = true; } return this; } // // Spring methods // void setSize(int axis, int origin, int size) { super.setSize(axis, origin, size); if (size == UNSET) { for (int counter = springs.size() - 1; counter >= 0; counter--) { getSpring(counter).setSize(axis, origin, size); } } else { setValidSize(axis, origin, size); } } /** * This is invoked from setSize if passed a value * other than UNSET. */ abstract void setValidSize(int axis, int origin, int size); int calculateMinimumSize(int axis) { return calculateSize(axis, MIN_SIZE); } int calculatePreferredSize(int axis) { return calculateSize(axis, PREF_SIZE); } int calculateMaximumSize(int axis) { return calculateSize(axis, MAX_SIZE); } /** * Used to compute how the two values representing two springs * will be combined. For example, a group that layed things out * one after the next would return a + b. */ abstract int operator(int a, int b); /** * Calculates the specified size. This is called from * one of the getMinimumSize0, * getPreferredSize0 or * getMaximumSize0 methods. This will invoke * to operator to combine the values. */ int calculateSize(int axis, int type) { int count = springs.size(); if (count == 0) { return 0; } if (count == 1) { return getSpringSize(getSpring(0), axis, type); } int size = constrain(operator(getSpringSize(getSpring(0), axis, type), getSpringSize(getSpring(1), axis, type))); for (int counter = 2; counter < count; counter++) { size = constrain(operator(size, getSpringSize(getSpring(counter), axis, type))); } return size; } Spring getSpring(int index) { return (Spring)springs.get(index); } int getSpringSize(Spring spring, int axis, int type) { switch(type) { case MIN_SIZE: return spring.getMinimumSize(axis); case PREF_SIZE: return spring.getPreferredSize(axis); case MAX_SIZE: return spring.getMaximumSize(axis); } assert false; return 0; } // Padding /** * Adjusts the autopadding springs in this group and its children. * If insert is true this will insert auto padding * springs, otherwise this will only adjust the springs that * comprise auto preferred padding springs. * * @param axis the axis of the springs; HORIZONTAL or VERTICAL * @param leadingPadding List of AutopaddingSprings that occur before * this Group * @param trailingPadding any trailing autopadding springs are added * to this on exit * @param leading List of ComponentSprings that occur before this Group * @param trailing any trailing ComponentSpring are added to this * List * @param insert Whether or not to insert AutopaddingSprings or just * adjust any existing AutopaddingSprings. */ abstract void insertAutopadding(int axis, List leadingPadding, List trailingPadding, List leading, List trailing, boolean insert); /** * Removes any AutopaddingSprings. */ void removeAutopadding() { unset(); for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = (Spring)springs.get(counter); if (spring instanceof AutopaddingSpring) { if (((AutopaddingSpring)spring).getUserCreated()) { ((AutopaddingSpring)spring).reset(); } else { springs.remove(counter); } } else if (spring instanceof Group) { ((Group)spring).removeAutopadding(); } } } void unsetAutopadding() { // Clear cached pref/min/max. unset(); for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = (Spring)springs.get(counter); if (spring instanceof AutopaddingSpring) { ((AutopaddingSpring)spring).unset(); } else if (spring instanceof Group) { ((Group)spring).unsetAutopadding(); } } } void calculateAutopadding(int axis) { for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = (Spring)springs.get(counter); if (spring instanceof AutopaddingSpring) { // Force size to be reset. spring.unset(); ((AutopaddingSpring)spring).calculatePadding(axis); } else if (spring instanceof Group) { ((Group)spring).calculateAutopadding(axis); } } // Clear cached pref/min/max. unset(); } boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { for (int i = springs.size() -1; i >= 0; i--) { Spring spring = (Spring)springs.get(i); if (!spring.willHaveZeroSize(treatAutopaddingAsZeroSized)) { return false; } } return true; } } /** * A Group that lays out its elements sequentially, one * after another. This class has no public constructor, use the * createSequentialGroup method to create one. * * @see #createSequentialGroup() */ public class SequentialGroup extends Group { private Spring baselineSpring; SequentialGroup() { } /** * Adds the specified Group to this * SequentialGroup * * @param group the Group to add * @return this Group */ public SequentialGroup add(Group group) { return (SequentialGroup)addSpring(group); } /** * Adds a Group to this Group. * * @param group the Group to add * @param useAsBaseline whether the specified Group should * be used to calculate the baseline for this Group * @return this Group */ public SequentialGroup add(boolean useAsBaseline, Group group) { add(group); if (useAsBaseline) { baselineSpring = group; } return this; } /** * Adds the specified Component. If the Component's min/max * are different from its pref than the component will be resizable. * * @param component the Component to add * @return this SequentialGroup */ public SequentialGroup add(Component component) { return add(component, DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE); } /** * Adds a Component to this Group. * * @param useAsBaseline whether the specified Component should * be used to calculate the baseline for this Group * @param component the Component to add * @return this Group */ public SequentialGroup add(boolean useAsBaseline, Component component) { add(component); if (useAsBaseline) { baselineSpring = getSpring(springs.size() - 1); } return this; } /** * Adds the specified Component. Min, pref and max * can be absolute values, or they can be one of * DEFAULT_SIZE or PREFERRED_SIZE. For * example, the following: *

         *   add(component, PREFERRED_SIZE, PREFERRED_SIZE, 1000);
         * 
* Forces a max of 1000, with the min and preferred equalling that * of the preferred size of component. * * @param component the Component to add * @param min the minimum size * @param pref the preferred size * @param max the maximum size * @throws IllegalArgumentException if min, pref or max are * not positive and not one of PREFERRED_SIZE or DEFAULT_SIZE * @return this SequentialGroup */ public SequentialGroup add(Component component, int min, int pref, int max) { return (SequentialGroup)addSpring(new ComponentSpring( component, min, pref, max)); } /** * Adds a Component to this Group * with the specified size. * * @param useAsBaseline whether the specified Component should * be used to calculate the baseline for this Group * @param component the Component to add * @param min the minimum size or one of DEFAULT_SIZE or * PREFERRED_SIZE * @param pref the preferred size or one of DEFAULT_SIZE or * PREFERRED_SIZE * @param max the maximum size or one of DEFAULT_SIZE or * PREFERRED_SIZE * @return this Group */ public SequentialGroup add(boolean useAsBaseline, Component component, int min, int pref, int max) { add(component, min, pref, max); if (useAsBaseline) { baselineSpring = getSpring(springs.size() - 1); } return this; } /** * Adds a rigid gap. * * @param pref the size of the gap * @throws IllegalArgumentException if min < 0 or pref < 0 or max < 0 * or the following is not meant min <= pref <= max * @return this SequentialGroup */ public SequentialGroup add(int pref) { return add(pref, pref, pref); } /** * Adds a gap with the specified size. * * @param min the minimum size of the gap, or PREFERRED_SIZE * @param pref the preferred size of the gap * @param max the maximum size of the gap, or PREFERRED_SIZE * @throws IllegalArgumentException if min < 0 or pref < 0 or max < 0 * or the following is not meant min <= pref <= max * @return this SequentialGroup */ public SequentialGroup add(int min, int pref, int max) { return (SequentialGroup)addSpring(new GapSpring(min, pref, max)); } /** * Adds an element representing the preferred gap between the two * components. * * @param comp1 the first component * @param comp2 the second component * @param type the type of gap; one of the constants defined by * LayoutStyle * @return this SequentialGroup * @throws IllegalArgumentException if type is not a * valid LayoutStyle constant * @see LayoutStyle */ public SequentialGroup addPreferredGap(JComponent comp1, JComponent comp2, int type) { return addPreferredGap(comp1, comp2, type, false); } /** * Adds an element representing the preferred gap between the two * components. * * @param comp1 the first component * @param comp2 the second component * @param type the type of gap; one of the constants defined by * LayoutStyle * @param canGrow true if the gap can grow if more * space is available * @return this SequentialGroup * @throws IllegalArgumentException if type is not a * valid LayoutStyle constant * @see LayoutStyle */ public SequentialGroup addPreferredGap(JComponent comp1, JComponent comp2, int type, boolean canGrow) { if (type != LayoutStyle.RELATED && type != LayoutStyle.UNRELATED && type != LayoutStyle.INDENT) { throw new IllegalArgumentException("Invalid type argument"); } if (comp1 == null || comp2 == null) { throw new IllegalArgumentException( "Components must be non-null"); } return (SequentialGroup)addSpring(new PaddingSpring( comp1, comp2, type, canGrow)); } /** * Adds an element representing the preferred gap between the * nearest components. That is, during layout the neighboring * components are found, and the min, pref and max of this * element is set based on the preferred gap between the * components. If no neighboring components are found the * min, pref and max are set to 0. * * @param type the type of gap; one of the LayoutStyle constants * @return this SequentialGroup * @throws IllegalArgumentException if type is not one of * LayoutStyle.RELATED or * LayoutStyle.UNRELATED * @see LayoutStyle */ public SequentialGroup addPreferredGap(int type) { return addPreferredGap(type, DEFAULT_SIZE, DEFAULT_SIZE); } /** * Adds an element for the preferred gap between the * nearest components. That is, during layout the neighboring * components are found, and the min of this * element is set based on the preferred gap between the * components. If no neighboring components are found the * min is set to 0. This method allows you to specify the * preferred and maximum size by way of the pref * and max arguments. These can either be a * value >= 0, in which case the preferred or max is the max * of the argument and the preferred gap, of DEFAULT_VALUE in * which case the value is the same as the preferred gap. * * @param type the type of gap; one of LayoutStyle.RELATED or * LayoutStyle.UNRELATED * @param pref the preferred size; one of DEFAULT_SIZE or a value > 0 * @param max the maximum size; one of DEFAULT_SIZE, PREFERRED_SIZE * or a value > 0 * @return this SequentialGroup * @throws IllegalArgumentException if type is not one of * LayoutStyle.RELATED or * LayoutStyle.UNRELATED or pref/max is * != DEFAULT_SIZE and < 0, or pref > max * @see LayoutStyle */ public SequentialGroup addPreferredGap(int type, int pref, int max) { if (type != LayoutStyle.RELATED && type != LayoutStyle.UNRELATED) { throw new IllegalArgumentException( "Padding type must be one of Padding.RELATED or Padding.UNRELATED"); } if ((pref < 0 && pref != DEFAULT_SIZE && pref != PREFERRED_SIZE) || (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE)|| (pref >= 0 && max >= 0 && pref > max)) { throw new IllegalArgumentException( "Pref and max must be either DEFAULT_SIZE, " + "PREFERRED_SIZE, or >= 0 and pref <= max"); } hasPreferredPaddingSprings = true; return (SequentialGroup)addSpring(new AutopaddingSpring( type, pref, max)); } /** * Adds an element representing the preferred gap between one edge * of the container and the next/previous Component. This will have * no effect if the next/previous element is not a Component and does * not touch one edge of the parent container. * * @return this SequentialGroup. */ public SequentialGroup addContainerGap() { return addContainerGap(DEFAULT_SIZE, DEFAULT_SIZE); } /** * Adds an element representing the preferred gap between one edge * of the container and the next/previous Component. This will have * no effect if the next/previous element is not a Component and does * not touch one edge of the parent container. * * @param pref the preferred size; one of DEFAULT_SIZE or a value > 0 * @param max the maximum size; one of DEFAULT_SIZE, PREFERRED_SIZE * or a value > 0. * @throws IllegalArgumentException if pref/max is * != DEFAULT_SIZE and < 0, or pref > max * @return this SequentialGroup */ public SequentialGroup addContainerGap(int pref, int max) { if ((pref < 0 && pref != DEFAULT_SIZE) || (max < 0 && max != DEFAULT_SIZE && max != PREFERRED_SIZE) || (pref >= 0 && max >= 0 && pref > max)) { throw new IllegalArgumentException( "Pref and max must be either DEFAULT_VALUE or >= 0 and pref <= max"); } hasPreferredPaddingSprings = true; return (SequentialGroup)addSpring( new ContainerAutopaddingSpring(pref, max)); } int operator(int a, int b) { return constrain(a) + constrain(b); } void setValidSize(int axis, int origin, int size) { int pref = getPreferredSize(axis); if (size == pref) { for (int counter = 0, max = springs.size(); counter < max; counter++) { Spring spring = getSpring(counter); int springPref = spring.getPreferredSize(axis); spring.setSize(axis, origin, springPref); origin += springPref; } } else if (springs.size() == 1) { Spring spring = getSpring(0); spring.setSize(axis, origin, Math.min( Math.max(size, spring.getMinimumSize(axis)), spring.getMaximumSize(axis))); } else if (springs.size() > 1) { // Adjust between min/pref setValidSizeNotPreferred(axis, origin, size); } } private void setValidSizeNotPreferred(int axis, int origin, int size) { int delta = size - getPreferredSize(axis); assert delta != 0; boolean useMin = (delta < 0); int springCount = springs.size(); if (useMin) { delta *= -1; } // The following algorithm if used for resizing springs: // 1. Calculate the resizability of each spring (pref - min or // max - pref) into a list. // 2. Sort the list in ascending order // 3. Iterate through each of the resizable Springs, attempting // to give them (pref - size) / resizeCount // 4. For any Springs that can not accomodate that much space // add the remainder back to the amount to distribute and // recalculate how must space the remaining springs will get. // 5. Set the size of the springs. // First pass, sort the resizable springs into resizable List resizable = buildResizableList(axis, useMin); int resizableCount = resizable.size(); if (resizableCount > 0) { // How much we would like to give each Spring. int sDelta = delta / resizableCount; // Remaining space. int slop = delta - sDelta * resizableCount; int[] sizes = new int[springCount]; int sign = useMin ? -1 : 1; // Second pass, accumulate the resulting deltas (relative to // preferred) into sizes. for (int counter = 0; counter < resizableCount; counter++) { SpringDelta springDelta = (SpringDelta)resizable. get(counter); if ((counter + 1) == resizableCount) { sDelta += slop; } springDelta.delta = Math.min(sDelta, springDelta.delta); delta -= springDelta.delta; if (springDelta.delta != sDelta && counter + 1 < resizableCount) { // Spring didn't take all the space, reset how much // each spring will get. sDelta = delta / (resizableCount - counter - 1); slop = delta - sDelta * (resizableCount - counter - 1); } sizes[springDelta.index] = sign * springDelta.delta; } // And finally set the size of each spring for (int counter = 0; counter < springCount; counter++) { Spring spring = getSpring(counter); int sSize = spring.getPreferredSize(axis) + sizes[counter]; spring.setSize(axis, origin, sSize); origin += sSize; } } else { // Nothing resizable, use the min or max of each of the // springs. for (int counter = 0; counter < springCount; counter++) { Spring spring = getSpring(counter); int sSize; if (useMin) { sSize = spring.getMinimumSize(axis); } else { sSize = spring.getMaximumSize(axis); } spring.setSize(axis, origin, sSize); origin += sSize; } } } /** * Returns the sorted list of SpringDelta's for the current set of * Springs. */ private List buildResizableList(int axis, boolean useMin) { // First pass, figure out what is resizable int size = springs.size(); List sorted = new ArrayList(size); for (int counter = 0; counter < size; counter++) { Spring spring = getSpring(counter); int sDelta; if (useMin) { sDelta = spring.getPreferredSize(axis) - spring.getMinimumSize(axis); } else { sDelta = spring.getMaximumSize(axis) - spring.getPreferredSize(axis); } if (sDelta > 0) { sorted.add(new SpringDelta(counter, sDelta)); } } Collections.sort(sorted); return sorted; } private int indexOfNextNonZeroSpring(int index, boolean treatAutopaddingAsZeroSized) { while (index < springs.size()) { Spring spring = (Spring)springs.get(index); if (!((Spring)spring).willHaveZeroSize(treatAutopaddingAsZeroSized)) { return index; } index++; } return index; } void insertAutopadding(int axis, List leadingPadding, List trailingPadding, List leading, List trailing, boolean insert) { List newLeadingPadding = new ArrayList(leadingPadding); List newTrailingPadding = new ArrayList(1); List newLeading = new ArrayList(leading); List newTrailing = null; int counter = 0; // Warning, this must use springs.size, as it may change during the // loop. while (counter < springs.size()) { Spring spring = getSpring(counter); if (spring instanceof AutopaddingSpring) { if (newLeadingPadding.size() == 0) { AutopaddingSpring padding = (AutopaddingSpring)spring; padding.setSources(newLeading); newLeading.clear(); int nextCounter = indexOfNextNonZeroSpring(counter + 1, true); if (nextCounter == springs.size()) { // Last spring in the list, add it to trailingPadding. if (!(padding instanceof ContainerAutopaddingSpring)) { trailingPadding.add(padding); } } else { newLeadingPadding.clear(); newLeadingPadding.add(padding); } counter = nextCounter; } else { counter = indexOfNextNonZeroSpring(counter + 1, true); } } else { // Not a padding spring if (newLeading.size() > 0 && insert) { // There's leading ComponentSprings, create an // autopadding spring. AutopaddingSpring padding = new AutopaddingSpring(); // Force the newly created spring to be considered // by NOT incrementing counter springs.add(counter, padding); continue; } if (spring instanceof ComponentSpring) { // Spring is a Component, make it the target of any // leading AutopaddingSpring. ComponentSpring cSpring = (ComponentSpring)spring; if (!cSpring.isVisible()) { counter++; continue; } for (int i = 0; i < newLeadingPadding.size(); i++) { ((AutopaddingSpring)newLeadingPadding.get(i)). addTarget(cSpring, axis); } newLeading.clear(); newLeadingPadding.clear(); int nextCounter = indexOfNextNonZeroSpring(counter + 1, false); if (nextCounter == springs.size()) { // Last Spring, add it to trailing trailing.add(cSpring); } else { // Not that last Spring, add it to leading newLeading.add(cSpring); } counter = nextCounter; } else if (spring instanceof Group) { // Forward call to child Group if (newTrailing == null) { newTrailing = new ArrayList(1); } else { newTrailing.clear(); } newTrailingPadding.clear(); ((Group)spring).insertAutopadding(axis, newLeadingPadding, newTrailingPadding, newLeading, newTrailing, insert); newLeading.clear(); newLeadingPadding.clear(); int nextCounter = indexOfNextNonZeroSpring(counter + 1, newTrailing.size() == 0); if (nextCounter == springs.size()) { trailing.addAll(newTrailing); trailingPadding.addAll(newTrailingPadding); } else { newLeading.addAll(newTrailing); newLeadingPadding.addAll(newTrailingPadding); } counter = nextCounter; } else { // Gap newLeadingPadding.clear(); newLeading.clear(); counter++; } } } } int getBaseline() { if (baselineSpring != null) { int baseline = baselineSpring.getBaseline(); if (baseline >= 0) { int size = 0; for (int i = 0, max = springs.size(); i < max; i++) { Spring spring = getSpring(i); if (spring == baselineSpring) { return size + baseline; } else { size += spring.getPreferredSize(VERTICAL); } } } } return -1; } int getBaselineResizeBehavior() { if (isResizable(VERTICAL)) { if (!baselineSpring.isResizable(VERTICAL)) { // Spring to use for baseline isn't resizable. In this case // baseline resize behavior can be determined based on how // preceeding springs resize. boolean leadingResizable = false; for (int i = 0, max = springs.size(); i < max; i++) { Spring spring = getSpring(i); if (spring == baselineSpring) { break; } else if (spring.isResizable(VERTICAL)) { leadingResizable = true; break; } } boolean trailingResizable = false; for (int i = springs.size() - 1; i >= 0; i--) { Spring spring = getSpring(i); if (spring == baselineSpring) { break; } if (spring.isResizable(VERTICAL)) { trailingResizable = true; break; } } if (leadingResizable && !trailingResizable) { return Baseline.BRB_CONSTANT_DESCENT; } else if (!leadingResizable && trailingResizable) { return Baseline.BRB_CONSTANT_ASCENT; } // If we get here, both leading and trailing springs are // resizable. Fall through to OTHER. } else { int brb = baselineSpring.getBaselineResizeBehavior(); if (brb == Baseline.BRB_CONSTANT_ASCENT) { for (int i = 0, max = springs.size(); i < max; i++) { Spring spring = getSpring(i); if (spring == baselineSpring) { return Baseline.BRB_CONSTANT_ASCENT; } if (spring.isResizable(VERTICAL)) { return Baseline.BRB_OTHER; } } } else if (brb == Baseline.BRB_CONSTANT_DESCENT) { for (int i = springs.size() - 1; i >= 0; i--) { Spring spring = getSpring(i); if (spring == baselineSpring) { return Baseline.BRB_CONSTANT_DESCENT; } if (spring.isResizable(VERTICAL)) { return Baseline.BRB_OTHER; } } } } return Baseline.BRB_OTHER; } // Not resizable, treat as constant_ascent return Baseline.BRB_CONSTANT_ASCENT; } } /** * Used in figuring out how much space to give resizable springs. */ private static final class SpringDelta implements Comparable { // Original index. public final int index; // Delta, one of pref - min or max - pref. public int delta; public SpringDelta(int index, int delta) { this.index = index; this.delta = delta; } public int compareTo(Object o) { return delta - ((SpringDelta)o).delta; } public String toString() { return super.toString() + "[index=" + index + ", delta=" + delta + "]"; } } /** * A Group that lays out its elements on top of each * other. If a child element is smaller than the provided space it * is aligned based on the alignment of the child (if specified) or * on the alignment of the ParallelGroup. * * @see #createParallelGroup() */ public class ParallelGroup extends Group { // How children are layed out. private final int childAlignment; // Whether or not we're resizable. private final boolean resizable; ParallelGroup(int childAlignment, boolean resizable) { this.childAlignment = childAlignment; this.resizable = resizable; } /** * Adds the specified Group. * * @param group the Group to add * @return this Group */ public ParallelGroup add(Group group) { return (ParallelGroup)addSpring(group); } /** * Adds the specified Component. If the Component's min/max * are different from its pref than the component will be resizable. * * @param component the Component to add * @return this ParallelGroup */ public ParallelGroup add(Component component) { return add(component, DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE); } /** * Adds the specified Component. Min, pref and max * can be absolute values, or they can be one of * DEFAULT_SIZE or PREFERRED_SIZE. For * example, the following: *
         *   add(component, PREFERRED_SIZE, PREFERRED_SIZE, 1000);
         * 
* Forces a max of 1000, with the min and preferred equalling that * of the preferred size of component. * * @param component the Component to add * @param min the minimum size * @param pref the preferred size * @param max the maximum size * @throws IllegalArgumentException if min, pref or max are * not positive and not one of PREFERRED_SIZE or DEFAULT_SIZE. * @return this SequentialGroup */ public ParallelGroup add(Component component, int min, int pref, int max) { return (ParallelGroup)addSpring(new ComponentSpring( component, min, pref, max)); } /** * Adds a rigid gap. * * @param pref the size of the gap * @throws IllegalArgumentException if min < 0 or pref < 0 or max < 0 * or the following is not meant min <= pref <= max. * @return this ParallelGroup */ public ParallelGroup add(int pref) { return add(pref, pref, pref); } /** * Adds a gap with the specified size. * * @param min the minimum size of the gap * @param pref the preferred size of the gap * @param max the maximum size of the gap * @throws IllegalArgumentException if min < 0 or pref < 0 or max < 0 * or the following is not meant min <= pref <= max. * @return this ParallelGroup */ public ParallelGroup add(int min, int pref, int max) { return (ParallelGroup)addSpring(new GapSpring(min, pref, max)); } /** * Adds the specified Group as a child of this group. * * @param alignment the alignment of the Group. * @param group the Group to add * @return this ParallelGroup * @throws IllegalArgumentException if alignment is not one of * LEADING, TRAILING or * CENTER */ public ParallelGroup add(int alignment, Group group) { checkChildAlignment(alignment); group.setAlignment(alignment); return (ParallelGroup)addSpring(group); } /** * Adds the specified Component. If the Component's min/max * are different from its pref than the component will be resizable. * * @param alignment the alignment for the component * @param component the Component to add * @return this Group * @throws IllegalArgumentException if alignment is not one of * LEADING, TRAILING or * CENTER */ public ParallelGroup add(int alignment, Component component) { return add(alignment, component, DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE); } /** * Adds the specified Component. Min, pref and max * can be absolute values, or they can be one of * DEFAULT_SIZE or PREFERRED_SIZE. For * example, the following: *
         *   add(component, PREFERRED_SIZE, PREFERRED_SIZE, 1000);
         * 
* Forces a max of 1000, with the min and preferred equalling that * of the preferred size of component. * * @param alignment the alignment for the component. * @param component the Component to add * @param min the minimum size * @param pref the preferred size * @param max the maximum size * @throws IllegalArgumentException if min, pref or max are * not positive and not one of PREFERRED_SIZE or DEFAULT_SIZE. * @return this Group */ public ParallelGroup add(int alignment, Component component, int min, int pref, int max) { checkChildAlignment(alignment); ComponentSpring spring = new ComponentSpring(component, min, pref, max); spring.setAlignment(alignment); return (ParallelGroup)addSpring(spring); } boolean isResizable() { return resizable; } int operator(int a, int b) { return Math.max(a, b); } int calculateMinimumSize(int axis) { if (!isResizable()) { return getPreferredSize(axis); } return super.calculateMinimumSize(axis); } int calculateMaximumSize(int axis) { if (!isResizable()) { return getPreferredSize(axis); } return super.calculateMaximumSize(axis); } void setValidSize(int axis, int origin, int size) { for (int i = 0, max = springs.size(); i < max; i++) { setChildSize(getSpring(i), axis, origin, size); } } void setChildSize(Spring spring, int axis, int origin, int size) { int alignment = spring.getAlignment(); int springSize = Math.min( Math.max(spring.getMinimumSize(axis), size), spring.getMaximumSize(axis)); if (alignment == NO_ALIGNMENT) { alignment = childAlignment; } switch (alignment) { case TRAILING: spring.setSize(axis, origin + size - springSize, springSize); break; case CENTER: spring.setSize(axis, origin + (size - springSize) / 2,springSize); break; default: // LEADING, or BASELINE spring.setSize(axis, origin, springSize); break; } } void insertAutopadding(int axis, List leadingPadding, List trailingPadding, List leading, List trailing, boolean insert) { for (int counter = 0, max = springs.size(); counter < max; counter++) { Spring spring = getSpring(counter); if (spring instanceof ComponentSpring) { if (((ComponentSpring)spring).isVisible()) { for (int i = 0; i < leadingPadding.size(); i++) { ((AutopaddingSpring)leadingPadding.get(i)).addTarget( (ComponentSpring)spring, axis); } trailing.add(spring); } } else if (spring instanceof Group) { ((Group)spring).insertAutopadding(axis, leadingPadding, trailingPadding, leading, trailing, insert); } else if (spring instanceof AutopaddingSpring) { ((AutopaddingSpring)spring).setSources(leading); trailingPadding.add(spring); } } } private void checkChildAlignment(int alignment) { boolean allowsBaseline = (this instanceof BaselineGroup); if (!allowsBaseline && alignment == BASELINE) { throw new IllegalArgumentException("Alignment must be one of:" + "LEADING, TRAILING or CENTER"); } if (alignment != CENTER && alignment != BASELINE && alignment != LEADING && alignment != TRAILING) { throw new IllegalArgumentException("Alignment must be one of:" + "LEADING, TRAILING or CENTER"); } } } /** * An extension of ParallelGroup that aligns its * constituent Springs along the baseline. */ private class BaselineGroup extends ParallelGroup { // Whether or not all child springs have a baseline private boolean allSpringsHaveBaseline; // max(spring.getBaseline()) of all springs aligned along the baseline // that have a baseline private int prefAscent; // max(spring.getPreferredSize().height - spring.getBaseline()) of all // springs aligned along the baseline that have a baseline private int prefDescent; // Whether baselineAnchoredToTop was explicitly set private boolean baselineAnchorSet; // Whether the baseline is anchored to the top or the bottom. // If anchored to the top the baseline is always at prefAscent, // otherwise the baseline is at (height - prefDescent) private boolean baselineAnchoredToTop; // Whether or not the baseline has been calculated. private boolean calcedBaseline; BaselineGroup(boolean resizable) { super(LEADING, resizable); prefAscent = prefDescent = -1; calcedBaseline = false; } BaselineGroup(boolean resizable, boolean baselineAnchoredToTop) { this(resizable); this.baselineAnchoredToTop = baselineAnchoredToTop; baselineAnchorSet = true; } void unset() { super.unset(); prefAscent = prefDescent = -1; calcedBaseline = false; } void setValidSize(int axis, int origin, int size) { checkAxis(axis); if (prefAscent == -1) { super.setValidSize(axis, origin, size); } else { // do baseline layout baselineLayout(origin, size); } } int calculateSize(int axis, int type) { checkAxis(axis); if (!calcedBaseline) { calculateBaselineAndResizeBehavior(); } if (type == MIN_SIZE) { return calculateMinSize(); } if (type == MAX_SIZE) { return calculateMaxSize(); } if (allSpringsHaveBaseline) { return prefAscent + prefDescent; } return Math.max(prefAscent + prefDescent, super.calculateSize(axis, type)); } private void calculateBaselineAndResizeBehavior() { // calculate baseline prefAscent = 0; prefDescent = 0; int baselineSpringCount = 0; int resizeBehavior = Baseline.BRB_NONE; for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = getSpring(counter); if (spring.getAlignment() == NO_ALIGNMENT || spring.getAlignment() == BASELINE) { int baseline = spring.getBaseline(); if (baseline >= 0) { if (spring.isResizable(VERTICAL)) { int brb = spring. getBaselineResizeBehavior(); if (resizeBehavior == Baseline.BRB_NONE) { resizeBehavior = brb; } else if (brb != resizeBehavior) { resizeBehavior = Baseline.BRB_CONSTANT_ASCENT; } } prefAscent = Math.max(prefAscent, baseline); prefDescent = Math.max(prefDescent, spring. getPreferredSize(VERTICAL) - baseline); baselineSpringCount++; } } } if (!baselineAnchorSet) { if (resizeBehavior == Baseline.BRB_CONSTANT_DESCENT){ this.baselineAnchoredToTop = false; } else { this.baselineAnchoredToTop = true; } } allSpringsHaveBaseline = (baselineSpringCount == springs.size()); calcedBaseline = true; } private int calculateMaxSize() { int maxAscent = prefAscent; int maxDescent = prefDescent; int nonBaselineMax = 0; for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = getSpring(counter); int baseline; int springMax = spring.getMaximumSize(VERTICAL); if ((spring.getAlignment() == NO_ALIGNMENT || spring.getAlignment() == BASELINE) && (baseline = spring.getBaseline()) >= 0) { int springPref = spring.getPreferredSize(VERTICAL); if (springPref != springMax) { switch (spring.getBaselineResizeBehavior()) { case Baseline.BRB_CONSTANT_ASCENT: if (baselineAnchoredToTop) { maxDescent = Math.max(maxDescent, springMax - baseline); } break; case Baseline.BRB_CONSTANT_DESCENT: if (!baselineAnchoredToTop) { maxAscent = Math.max(maxAscent, springMax - springPref + baseline); } break; default: // CENTER_OFFSET and OTHER, not resizable break; } } } else { // Not aligned along the baseline, or no baseline. nonBaselineMax = Math.max(nonBaselineMax, springMax); } } return Math.max(nonBaselineMax, maxAscent + maxDescent); } private int calculateMinSize() { int minAscent = 0; int minDescent = 0; int nonBaselineMin = 0; if (baselineAnchoredToTop) { minAscent = prefAscent; } else { minDescent = prefDescent; } for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = getSpring(counter); int springMin = spring.getMinimumSize(VERTICAL); int baseline; if ((spring.getAlignment() == NO_ALIGNMENT || spring.getAlignment() == BASELINE) && (baseline = spring.getBaseline()) >= 0) { int springPref = spring.getPreferredSize(VERTICAL); switch (spring.getBaselineResizeBehavior()) { case Baseline.BRB_CONSTANT_ASCENT: if (baselineAnchoredToTop) { minDescent = Math.max(springMin - baseline, minDescent); } else { minAscent = Math.max(baseline, minAscent); } break; case Baseline.BRB_CONSTANT_DESCENT: if (!baselineAnchoredToTop) { minAscent = Math.max( baseline - (springPref - springMin), minAscent); } else { minDescent = Math.max(springPref - baseline, minDescent); } break; default: // CENTER_OFFSET and OTHER are !resizable, use // the preferred size. minAscent = Math.max(baseline, minAscent); minDescent = Math.max(springPref - baseline, minDescent); break; } } else { // Not aligned along the baseline, or no baseline. nonBaselineMin = Math.max(nonBaselineMin, springMin); } } return Math.max(nonBaselineMin, minAscent + minDescent); } /** * Lays out springs that have a baseline along the baseline. All * others are centered. */ private void baselineLayout(int origin, int size) { int ascent; int descent; if (baselineAnchoredToTop) { ascent = prefAscent; descent = size - ascent; } else { ascent = size - prefDescent; descent = prefDescent; } for (int counter = springs.size() - 1; counter >= 0; counter--) { Spring spring = getSpring(counter); int alignment = spring.getAlignment(); if (alignment == NO_ALIGNMENT || alignment == BASELINE) { int baseline = spring.getBaseline(); if (baseline >= 0) { int springMax = spring.getMaximumSize(VERTICAL); int springPref = spring.getPreferredSize(VERTICAL); int height = springPref; int y; switch(spring.getBaselineResizeBehavior()) { case Baseline.BRB_CONSTANT_ASCENT: y = origin + ascent - baseline; height = Math.min(descent, springMax - baseline) + baseline; break; case Baseline.BRB_CONSTANT_DESCENT: height = Math.min(ascent, springMax - springPref + baseline) + (springPref - baseline); y = origin + ascent + (springPref - baseline) - height; break; default: // CENTER_OFFSET & OTHER, not resizable y = origin + ascent - baseline; break; } spring.setSize(VERTICAL, y, height); } else { setChildSize(spring, VERTICAL, origin, size); } } else { setChildSize(spring, VERTICAL, origin, size); } } } int getBaseline() { if (springs.size() > 1) { // Force the baseline to be calculated getPreferredSize(VERTICAL); return prefAscent; } else if (springs.size() == 1) { return getSpring(0).getBaseline(); } return -1; } int getBaselineResizeBehavior() { if (springs.size() == 1) { return getSpring(0).getBaselineResizeBehavior(); } if (baselineAnchoredToTop) { return Baseline.BRB_CONSTANT_ASCENT; } return Baseline.BRB_CONSTANT_DESCENT; } // If the axis is VERTICAL, throws an IllegalStateException private void checkAxis(int axis) { if (axis == HORIZONTAL) { throw new IllegalStateException( "Baseline must be used along vertical axis"); } } } /** * A Spring representing one axis of a Component. * There are three ways to configure this: *
    *
  • Use the pref/min/max from the component *
  • Use the pref from the component and fix the min to 0 or max * to a big number. *
  • Force the min/max/pref to be a certain value. * If the Component's size is to be linked to another components than * the min/max/pref all come from the ComponentInfo. */ private final class ComponentSpring extends Spring { private Component component; private int origin; // min/pref/max are either a value >= 0 or one of // DEFAULT_SIZE or PREFERRED_SIZE private final int min; private final int pref; private final int max; // Baseline for the component. private int baseline = -1; // Whether or not the size has been requested yet. private boolean installed; private ComponentSpring(Component component, int min, int pref, int max) { this.component = component; if (component == null) { throw new IllegalArgumentException( "Component must be non-null"); } checkSize(min, pref, max, true); this.min = min; this.max = max; this.pref = pref; // getComponentInfo makes sure component is a child of the // Container GroupLayout is the LayoutManager for. getComponentInfo(component); } int calculateMinimumSize(int axis) { if (isLinked(axis)) { return getLinkSize(axis, MIN_SIZE); } return calculateNonlinkedMinimumSize(axis); } int calculatePreferredSize(int axis) { if (isLinked(axis)) { return getLinkSize(axis, PREF_SIZE); } int min = getMinimumSize(axis); int pref = calculateNonlinkedPreferredSize(axis); int max = getMaximumSize(axis); return Math.min(max, Math.max(min, pref)); } int calculateMaximumSize(int axis) { if (isLinked(axis)) { return getLinkSize(axis, MAX_SIZE); } return Math.max(getMinimumSize(axis), calculateNonlinkedMaximumSize(axis)); } boolean isVisible() { return getComponentInfo(getComponent()).isVisible(); } int calculateNonlinkedMinimumSize(int axis) { if (!isVisible()) { return 0; } if (min >= 0) { return min; } if (min == PREFERRED_SIZE) { return calculateNonlinkedPreferredSize(axis); } assert (min == DEFAULT_SIZE); return getSizeAlongAxis(axis, component.getMinimumSize()); } int calculateNonlinkedPreferredSize(int axis) { if (!isVisible()) { return 0; } if (pref >= 0) { return pref; } assert (pref == DEFAULT_SIZE || pref == PREFERRED_SIZE); return getSizeAlongAxis(axis, component.getPreferredSize()); } int calculateNonlinkedMaximumSize(int axis) { if (!isVisible()) { return 0; } if (max >= 0) { return max; } if (max == PREFERRED_SIZE) { return calculateNonlinkedPreferredSize(axis); } assert (max == DEFAULT_SIZE); return getSizeAlongAxis(axis, component.getMaximumSize()); } private int getSizeAlongAxis(int axis, Dimension size) { return (axis == HORIZONTAL) ? size.width : size.height; } private int getLinkSize(int axis, int type) { if (!isVisible()) { return 0; } ComponentInfo ci = getComponentInfo(component); return ci.getLinkSize(axis, type); } void setSize(int axis, int origin, int size) { super.setSize(axis, origin, size); this.origin = origin; if (size == UNSET) { baseline = -1; } } int getOrigin() { return origin; } void setComponent(Component component) { this.component = component; } Component getComponent() { return component; } int getBaseline() { if (baseline == -1 && (component instanceof JComponent)) { Spring horizontalSpring = getComponentInfo(component). horizontalSpring; int width = horizontalSpring.getPreferredSize(HORIZONTAL); int height = getPreferredSize(VERTICAL); if (width > 0 && height > 0) { baseline = Baseline.getBaseline((JComponent)component, width, getPreferredSize(VERTICAL)); } } return baseline; } int getBaselineResizeBehavior() { return Baseline.getBaselineResizeBehavior(getComponent()); } private boolean isLinked(int axis) { return getComponentInfo(component).isLinked(axis); } void installIfNecessary(int axis) { if (!installed) { installed = true; if (axis == HORIZONTAL) { getComponentInfo(component).horizontalSpring = this; } else { getComponentInfo(component).verticalSpring = this; } } } boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { return !isVisible(); } } /** * Spring representing the preferred distance between two components. */ private final class PaddingSpring extends Spring { private final JComponent source; private final JComponent target; private final int type; private final boolean canGrow; PaddingSpring(JComponent source, JComponent target, int type, boolean canGrow) { this.source = source; this.target = target; this.type = type; this.canGrow = canGrow; } int calculateMinimumSize(int axis) { return getPadding(axis); } int calculatePreferredSize(int axis) { return getPadding(axis); } int calculateMaximumSize(int axis) { if (canGrow) { return Short.MAX_VALUE; } return getPadding(axis); } private int getPadding(int axis) { int position; if (axis == HORIZONTAL) { position = SwingConstants.EAST; } else { position = SwingConstants.SOUTH; } return getLayoutStyle0().getPreferredGap(source, target, type, position, host); } boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { return false; } } /** * Spring represented a certain amount of space. */ private final class GapSpring extends Spring { private final int min; private final int pref; private final int max; GapSpring(int min, int pref, int max) { checkSize(min, pref, max, false); this.min = min; this.pref = pref; this.max = max; } int calculateMinimumSize(int axis) { if (min == PREFERRED_SIZE) { return getPreferredSize(axis); } return min; } int calculatePreferredSize(int axis) { return pref; } int calculateMaximumSize(int axis) { if (max == PREFERRED_SIZE) { return getPreferredSize(axis); } return max; } boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { return false; } } /** * Spring reprensenting the distance between any number of sources and * targets. The targets and sources are computed during layout. An * instance of this can either be dynamically created when * autocreatePadding is true, or explicitly created by the developer. */ private class AutopaddingSpring extends Spring { List sources; ComponentSpring source; private List matches; int size; int lastSize; private final int pref; private final int max; private int type; private boolean userCreated; private AutopaddingSpring() { this.pref = PREFERRED_SIZE; this.max = PREFERRED_SIZE; this.type = LayoutStyle.RELATED; } AutopaddingSpring(int pref, int max) { this.pref = pref; this.max = max; } AutopaddingSpring(int type, int pref, int max) { this.type = type; this.pref = pref; this.max = max; this.userCreated = true; } public void setSource(ComponentSpring source) { this.source = source; } public void setSources(List sources) { this.sources = new ArrayList(sources); } public void setUserCreated(boolean userCreated) { this.userCreated = userCreated; } public boolean getUserCreated() { return userCreated; } void unset() { lastSize = getSize(); super.unset(); size = 0; } public void reset() { size = 0; sources = null; source = null; matches = null; } public void calculatePadding(int axis) { size = UNSET; int maxPadding = UNSET; if (matches != null) { LayoutStyle p = getLayoutStyle0(); int position; if (axis == HORIZONTAL) { if (isLeftToRight()) { position = SwingConstants.EAST; } else { position = SwingConstants.WEST; } } else { position = SwingConstants.SOUTH; } for (int i = matches.size() - 1; i >= 0; i--) { AutopaddingMatch match = (AutopaddingMatch)matches.get(i); maxPadding = Math.max(maxPadding, calculatePadding(p, position, match.source, match.target)); } } if (size == UNSET) { size = 0; } if (maxPadding == UNSET) { maxPadding = 0; } if (lastSize != UNSET) { size += Math.min(maxPadding, lastSize); } } private int calculatePadding(LayoutStyle p, int position, ComponentSpring source, ComponentSpring target) { int delta = target.getOrigin() - (source.getOrigin() + source.getSize()); if (delta >= 0) { int padding; if ((source.getComponent() instanceof JComponent) && (target.getComponent() instanceof JComponent)) { padding = p.getPreferredGap((JComponent)source.getComponent(), (JComponent)target.getComponent(), type, position, host); } else { padding = 10; } if (padding > delta) { size = Math.max(size, padding - delta); } return padding; } return 0; } public void addTarget(ComponentSpring spring, int axis) { int oAxis = (axis == HORIZONTAL) ? VERTICAL : HORIZONTAL; if (source != null) { if (areParallelSiblings(source.getComponent(), spring.getComponent(), oAxis)) { addValidTarget(source, spring); } } else { Component component = spring.getComponent(); for (int counter = sources.size() - 1; counter >= 0; counter--){ ComponentSpring source = (ComponentSpring)sources. get(counter); if (areParallelSiblings(source.getComponent(), component, oAxis)) { addValidTarget(source, spring); } } } } private void addValidTarget(ComponentSpring source, ComponentSpring target) { if (matches == null) { matches = new ArrayList(1); } matches.add(new AutopaddingMatch(source, target)); } int calculateMinimumSize(int axis) { return size; } int calculatePreferredSize(int axis) { if (pref == PREFERRED_SIZE || pref == DEFAULT_SIZE) { return size; } return Math.max(size, pref); } int calculateMaximumSize(int axis) { if (max >= 0) { return Math.max(getPreferredSize(axis), max); } return size; } String getMatchDescription() { return (matches == null) ? "" : matches.toString(); } public String toString() { return super.toString() + getMatchDescription(); } boolean willHaveZeroSize(boolean treatAutopaddingAsZeroSized) { return treatAutopaddingAsZeroSized; } } /** * Represents two springs that should have autopadding inserted between * them. */ private final static class AutopaddingMatch { public final ComponentSpring source; public final ComponentSpring target; AutopaddingMatch(ComponentSpring source, ComponentSpring target) { this.source = source; this.target = target; } private String toString(ComponentSpring spring) { return spring.getComponent().getName(); } public String toString() { return "[" + toString(source) + "-" + toString(target) + "]"; } } /** * An extension of AutopaddingSpring used for container level padding. */ private class ContainerAutopaddingSpring extends AutopaddingSpring { private List targets; ContainerAutopaddingSpring() { super(); setUserCreated(true); } ContainerAutopaddingSpring(int pref, int max) { super(pref, max); setUserCreated(true); } public void addTarget(ComponentSpring spring, int axis) { if (targets == null) { targets = new ArrayList(1); } targets.add(spring); } public void calculatePadding(int axis) { LayoutStyle p = getLayoutStyle0(); int maxPadding = 0; int position; size = 0; if (targets != null) { // Leading if (axis == HORIZONTAL) { if (isLeftToRight()) { position = SwingConstants.WEST; } else { position = SwingConstants.EAST; } } else { position = SwingConstants.SOUTH; } for (int i = targets.size() - 1; i >= 0; i--) { ComponentSpring targetSpring = (ComponentSpring)targets. get(i); int padding = 10; if (targetSpring.getComponent() instanceof JComponent) { padding = p.getContainerGap( (JComponent)targetSpring.getComponent(), position, host); maxPadding = Math.max(padding, maxPadding); padding -= targetSpring.getOrigin(); } else { maxPadding = Math.max(padding, maxPadding); } size = Math.max(size, padding); } } else { // Trailing if (axis == HORIZONTAL) { if (isLeftToRight()) { position = SwingConstants.EAST; } else { position = SwingConstants.WEST; } } else { position = SwingConstants.SOUTH; } if (sources != null) { for (int i = sources.size() - 1; i >= 0; i--) { ComponentSpring sourceSpring = (ComponentSpring)sources. get(i); maxPadding = Math.max(maxPadding, updateSize(p, sourceSpring, position)); } } else if (source != null) { maxPadding = updateSize(p, source, position); } } if (lastSize != UNSET) { size += Math.min(maxPadding, lastSize); } } private int updateSize(LayoutStyle p, ComponentSpring sourceSpring, int position) { int padding = 10; if (sourceSpring.getComponent() instanceof JComponent) { padding = p.getContainerGap( (JComponent)sourceSpring.getComponent(), position, host); } int delta = Math.max(0, getParent().getSize() - sourceSpring.getSize() - sourceSpring.getOrigin()); size = Math.max(size, padding - delta); return padding; } String getMatchDescription() { if (targets != null) { return "leading: " + targets.toString(); } if (sources != null) { return "trailing: " + sources.toString(); } return "--"; } } // LinkInfo contains the set of ComponentInfosthat are linked along a // particular axis. private static final class LinkInfo { private final int axis; private final List linked; private int size; LinkInfo(int axis) { linked = new ArrayList(); size = UNSET; this.axis = axis; } public void add(ComponentInfo child) { LinkInfo childMaster = child.getLinkInfo(axis, false); if (childMaster == null) { linked.add(child); child.setLinkInfo(axis, this); } else if (childMaster != this) { linked.addAll(childMaster.linked); for (int i = 0; i < childMaster.linked.size(); i++) { ComponentInfo childInfo = (ComponentInfo)childMaster.linked.get(i); childInfo.setLinkInfo(axis, this); } } clearCachedSize(); } public void remove(ComponentInfo info) { linked.remove(info); info.setLinkInfo(axis, null); if (linked.size() == 1) { ((ComponentInfo)linked.get(0)).setLinkInfo(axis, null); } clearCachedSize(); } public void clearCachedSize() { size = UNSET; } public int getSize(int axis) { if (size == UNSET) { size = calculateLinkedSize(axis); } return size; } private int calculateLinkedSize(int axis) { int size = 0; for (int i = 0; i < linked.size(); i++) { ComponentInfo info = (ComponentInfo)linked.get(i); ComponentSpring spring; if (axis == HORIZONTAL) { spring = info.horizontalSpring; } else { assert (axis == VERTICAL); spring = info.verticalSpring; } size = Math.max(size, spring.calculateNonlinkedPreferredSize(axis)); } return size; } } /** * Tracks the horizontal/vertical Springs for a Component. * This class is also used to handle Springs that have their sizes * linked. */ private final class ComponentInfo { // Component being layed out private Component component; ComponentSpring horizontalSpring; ComponentSpring verticalSpring; // If the component's size is linked to other components, the // horizontalMaster and/or verticalMaster reference the group of // linked components. private LinkInfo horizontalMaster; private LinkInfo verticalMaster; private boolean visible; private Boolean honorsVisibility; ComponentInfo(Component component) { this.component = component; updateVisibility(); } public void dispose() { // Remove horizontal/vertical springs removeSpring(horizontalSpring); horizontalSpring = null; removeSpring(verticalSpring); verticalSpring = null; // Clean up links if (horizontalMaster != null) { horizontalMaster.remove(this); } if (verticalMaster != null) { verticalMaster.remove(this); } } void setHonorsVisibility(Boolean honorsVisibility) { this.honorsVisibility = honorsVisibility; } private void removeSpring(Spring spring) { if (spring != null) { ((Group)spring.getParent()).springs.remove(spring); } } public boolean isVisible() { return visible; } /** * Updates the cached visibility. * * @return true if the visibility changed */ boolean updateVisibility() { boolean honorsVisibility; if (this.honorsVisibility == null) { honorsVisibility = GroupLayout.this.getHonorsVisibility(); } else { honorsVisibility = this.honorsVisibility.booleanValue(); } boolean newVisible = (honorsVisibility) ? component.isVisible() : true; if (visible != newVisible) { visible = newVisible; return true; } return false; } public void setBounds(Insets insets, int parentWidth, boolean ltr) { int x = horizontalSpring.getOrigin(); int w = horizontalSpring.getSize(); int y = verticalSpring.getOrigin(); int h = verticalSpring.getSize(); if (!ltr) { x = parentWidth - x - w; } component.setBounds(x + insets.left, y + insets.top, w, h); } public void setComponent(Component component) { this.component = component; if (horizontalSpring != null) { horizontalSpring.setComponent(component); } if (verticalSpring != null) { verticalSpring.setComponent(component); } } public Component getComponent() { return component; } /** * Returns true if this component has its size linked to * other components. */ public boolean isLinked(int axis) { if (axis == HORIZONTAL) { return horizontalMaster != null; } assert (axis == VERTICAL); return (verticalMaster != null); } private void setLinkInfo(int axis, LinkInfo linkInfo) { if (axis == HORIZONTAL) { horizontalMaster = linkInfo; } else { assert (axis == VERTICAL); verticalMaster = linkInfo; } } public LinkInfo getLinkInfo(int axis) { return getLinkInfo(axis, true); } private LinkInfo getLinkInfo(int axis, boolean create) { if (axis == HORIZONTAL) { if (horizontalMaster == null && create) { // horizontalMaster field is directly set by adding // us to the LinkInfo. new LinkInfo(HORIZONTAL).add(this); } return horizontalMaster; } else { assert (axis == VERTICAL); if (verticalMaster == null && create) { // verticalMaster field is directly set by adding // us to the LinkInfo. new LinkInfo(VERTICAL).add(this); } return verticalMaster; } } public void clearCachedSize() { if (horizontalMaster != null) { horizontalMaster.clearCachedSize(); } if (verticalMaster != null) { verticalMaster.clearCachedSize(); } } int getLinkSize(int axis, int type) { if (axis == HORIZONTAL) { return horizontalMaster.getSize(axis); } else { assert (axis == VERTICAL); return verticalMaster.getSize(axis); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy