![JAR search and dependency download from the Maven repository](/logo.png)
com.jgoodies.forms.builder.PanelBuilder Maven / Gradle / Ivy
Show all versions of jgoodies-forms Show documentation
/*
* Copyright (c) 2002-2014 JGoodies Software GmbH. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of JGoodies Software GmbH nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jgoodies.forms.builder;
import static com.jgoodies.common.base.Preconditions.checkArgument;
import java.awt.Color;
import java.awt.Component;
import java.awt.FocusTraversalPolicy;
import java.lang.ref.WeakReference;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import com.jgoodies.forms.FormsSetup;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.factories.ComponentFactory;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
/**
* An general purpose panel builder that uses the {@link FormLayout}
* to lay out {@code JPanel}s. It provides convenience methods
* to set a default border and to add labels, titles and titled separators.
*
* The PanelBuilder is the working horse for layouts when more specialized
* builders like the {@link ButtonBarBuilder} or {@link DefaultFormBuilder}
* are inappropriate.
*
* The Forms tutorial includes several examples that present and compare
* different style to build with the PanelBuilder: static row numbers
* vs. row variable, explicit CellConstraints vs. builder cursor,
* static rows vs. dynamically added rows. Also, you may check out the
* Tips & Tricks section of the Forms HTML documentation.
*
* The text arguments passed to the methods {@code #addLabel},
* {@code #addTitle}, and {@code #addSeparator} can contain
* an optional mnemonic marker. The mnemonic and mnemonic index
* are indicated by a single ampersand (&). For example
* "&Save", or "Save &as".
* To use the ampersand itself duplicate it, for example
* "Look&&Feel".
*
* Example:
* This example creates a panel with 3 columns and 3 rows.
*
* FormLayout layout = new FormLayout(
* "pref, $lcgap, 50dlu, $rgap, default", // columns
* "pref, $lg, pref, $lg, pref"); // rows
*
* PanelBuilder builder = new PanelBuilder(layout);
* builder.addLabel("&Title:", CC.xy (1, 1));
* builder.add(new JTextField(), CC.xywh(3, 1, 3, 1));
* builder.addLabel("&Price:", CC.xy (1, 3));
* builder.add(new JTextField(), CC.xy (3, 3));
* builder.addLabel("&Author:", CC.xy (1, 5));
* builder.add(new JTextField(), CC.xy (3, 5));
* builder.add(new JButton("\u2026"), CC.xy (5, 5));
* return builder.getPanel();
*
*
* @author Karsten Lentzsch
* @version $Revision: 1.21 $
*
* @see com.jgoodies.forms.factories.ComponentFactory
* @see I15dPanelBuilder
* @see DefaultFormBuilder
*/
public class PanelBuilder extends AbstractFormBuilder {
// Constants **************************************************************
/**
* A JComponent client property that is used to determine the label
* labeling a component. Copied from the JLabel class.
*/
private static final String LABELED_BY_PROPERTY = "labeledBy";
// Instance Fields ********************************************************
/**
* The instance value for the setLabelFor feature.
* Is initialized using the global default.
*
* @see #setLabelForFeatureEnabledDefault(boolean)
* @see #setLabelForFeatureEnabledDefault(boolean)
*/
private boolean labelForFeatureEnabled;
/**
* Refers to the most recently added label.
* Used to invoke {@link JLabel#setLabelFor(java.awt.Component)}
* for the next component added to the panel that is applicable for
* this feature (for example focusable). After the association
* has been set, the reference will be cleared.
*
* @see #add(Component, CellConstraints)
*/
private WeakReference mostRecentlyAddedLabelReference = null;
// Instance Creation ******************************************************
/**
* Constructs a {@code PanelBuilder} for the given
* layout. Uses an instance of {@code JPanel} as layout container
* with the given layout as layout manager.
*
* @param layout the FormLayout to use
*
* @throws NullPointerException if {@code layout} is {@code null}
*/
public PanelBuilder(FormLayout layout){
this(layout, new JPanel(null));
}
/**
* Constructs a {@code PanelBuilder} for the given
* FormLayout and layout container.
*
* @param layout the FormLayout to use
* @param panel the layout container to build on
*
* @throws NullPointerException if {@code layout} or {@code container} is {@code null}
*/
public PanelBuilder(FormLayout layout, JPanel panel){
super(layout, panel);
opaque(FormsSetup.getOpaqueDefault());
labelForFeatureEnabled = FormsSetup.getLabelForFeatureEnabledDefault();
}
// Accessors **************************************************************
/**
* Returns the panel used to build the form.
* Intended to access panel properties. For returning the built panel,
* you should use {@link #build()}.
*
* @return the panel used by this builder to build the form
*/
public final JPanel getPanel() {
return (JPanel) getContainer();
}
// Modern (Cascading) Style Configuration *********************************
/**
* Sets the panel's background color and the panel to be opaque.
*
* @param background the color to set as new background
*
* @see JComponent#setBackground(Color)
*
* @since 1.6
*/
public PanelBuilder background(Color background) {
getPanel().setBackground(background);
opaque(true);
return this;
}
/**
* Sets the panel's border.
*
* @param border the border to set
*
* @see JComponent#setBorder(Border)
*
* @since 1.6
*/
public PanelBuilder border(Border border) {
getPanel().setBorder(border);
return this;
}
/**
* Sets the panel's border as an EmptyBorder using the given specification
* for the top, left, bottom, right in DLU. For example
* "1dlu, 2dlu, 3dlu, 4dlu" sets an empty border with 1dlu in the top,
* 2dlu in the left side, 3dlu at the bottom, and 4dlu in the right hand
* side.
*
* Equivalent to {@code setBorder(Borders.createEmptyBorder(emptyBorderSpec))}.
*
* @param emptyBorderSpec describes the top, left, bottom, right sizes
* of the EmptyBorder to create
*
* @see Borders#createEmptyBorder(String)
*
* @since 1.6
*/
public PanelBuilder border(String emptyBorderSpec) {
border(Borders.createEmptyBorder(emptyBorderSpec));
return this;
}
/**
* Sets the panel's opaque state.
*
* @param b true for opaque, false for non-opaque
*
* @see JComponent#setOpaque(boolean)
*
* @since 1.6
*/
public PanelBuilder opaque(boolean b) {
getPanel().setOpaque(b);
return this;
}
/**
* Sets the panel's focus traversal policy and sets the panel
* as focus traversal policy provider. Hence, this call is equivalent to:
*
* builder.getPanel().setFocusTraversalPolicy(policy);
* builder.getPanel().setFocusTraversalPolicyProvider(true);
*
*
* @param policy the focus traversal policy that will manage
* keyboard traversal of the children in this builder's panel
*
* @see JComponent#setFocusTraversalPolicy(FocusTraversalPolicy)
* @see JComponent#setFocusTraversalPolicyProvider(boolean)
*
* @since 1.7
*/
public PanelBuilder focusTraversal(FocusTraversalPolicy policy) {
getPanel().setFocusTraversalPolicy(policy);
getPanel().setFocusTraversalPolicyProvider(true);
return this;
}
/**
* Enables or disables the setLabelFor feature for this PanelBuilder.
* The value is initialized from the global default value
* {@link FormsSetup#getLabelForFeatureEnabledDefault()}.
* It is globally disabled by default.
*
* @param b true for enabled, false for disabled
*/
public PanelBuilder labelForFeatureEnabled(boolean b) {
labelForFeatureEnabled = b;
return this;
}
// Old Style Configuration ************************************************
/**
* Sets the panel's border.
*
* @param border the border to set
*
* @see JComponent#setBorder(Border)
* @deprecated Replaced by {@link #border(Border)}
*/
@Deprecated
public void setBorder(Border border) {
getPanel().setBorder(border);
}
/**
* Sets the default dialog border.
*
* @see Borders
* @deprecated Replaced by {@code #border(Borders.DIALOG)}
*/
@Deprecated
public void setDefaultDialogBorder() {
border(Borders.DIALOG);
}
/**
* Sets the panel's opaque state.
*
* @param b true for opaque, false for non-opaque
*
* @see JComponent#setOpaque(boolean)
*
* @since 1.1
* @deprecated Replaced by {@link #opaque(boolean)}
*/
@Deprecated
public void setOpaque(boolean b) {
getPanel().setOpaque(b);
}
// Building ***************************************************************
/**
* Returns the panel used to build the form.
* Intended to return the panel in build methods.
*
* @return the panel used by this builder to build the form
*
* @since 1.6
*/
public final JPanel build() {
return getPanel();
}
// Adding Labels **********************************************************
/**
* Adds a textual label to the form using the default constraints.
*
*
* addLabel("Name:"); // No Mnemonic
* addLabel("N&ame:"); // Mnemonic is 'a'
* addLabel("Save &as:"); // Mnemonic is the second 'a'
* addLabel("Look&&Feel:"); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return the new label
*
* @see ComponentFactory
*/
public final JLabel addLabel(String textWithMnemonic) {
return addLabel(textWithMnemonic, cellConstraints());
}
/**
* Adds a textual label to the form using the specified constraints.
*
*
* addLabel("Name:", CC.xy(1, 1)); // No Mnemonic
* addLabel("N&ame:", CC.xy(1, 1)); // Mnemonic is 'a'
* addLabel("Save &as:", CC.xy(1, 1)); // Mnemonic is the second 'a'
* addLabel("Look&&Feel:", CC.xy(1, 1)); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param constraints the label's cell constraints
* @return the new label
*
* @see ComponentFactory
*/
public final JLabel addLabel(String textWithMnemonic, CellConstraints constraints) {
JLabel label = getComponentFactory().createLabel(textWithMnemonic);
add(label, constraints);
return label;
}
/**
* Adds a textual label to the form using the specified constraints.
*
*
* addLabel("Name:", "1, 1"); // No Mnemonic
* addLabel("N&ame:", "1, 1"); // Mnemonic is 'a'
* addLabel("Save &as:", "1, 1"); // Mnemonic is the second 'a'
* addLabel("Look&&Feel:", "1, 1"); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param encodedConstraints a string representation for the constraints
* @return the new label
*
* @see ComponentFactory
*/
public final JLabel addLabel(String textWithMnemonic, String encodedConstraints) {
return addLabel(textWithMnemonic, new CellConstraints(encodedConstraints));
}
/**
* Adds a label and component to the panel using the given cell constraints.
* Sets the given label as the component label using
* {@link JLabel#setLabelFor(java.awt.Component)}.
*
* Note: The {@link CellConstraints} objects for the label
* and the component must be different. Cell constraints are implicitly
* cloned by the {@code FormLayout} when added to the container.
* However, in this case you may be tempted to reuse a
* {@code CellConstraints} object in the same way as with many other
* builder methods that require a single {@code CellConstraints}
* parameter.
* The pitfall is that the methods {@code CellConstraints.xy*(...)}
* just set the coordinates but do not create a new instance.
* And so the second invocation of {@code xy*(...)} overrides
* the settings performed in the first invocation before the object
* is cloned by the {@code FormLayout}.
*
* Wrong:
* builder.addLabel(
* "&Name:", // Mnemonic is 'N'
* cc.xy(1, 7), // will be modified by the code below
* nameField,
* cc.xy(3, 7) // sets the single instance to (3, 7)
* );
*
* Correct:
* builder.addLabel(
* "&Name:",
* CC.xy(1, 7), // creates an instance
* nameField,
* CC.xy(3, 7) // creates another instance
* );
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param labelConstraints the label's cell constraints
* @param component the component to add
* @param componentConstraints the component's cell constraints
* @return the added label
* @throws IllegalArgumentException if the same cell constraints instance
* is used for the label and the component
*
* @see JLabel#setLabelFor(java.awt.Component)
* @see ComponentFactory
* @see DefaultFormBuilder
*/
public final JLabel addLabel(
String textWithMnemonic, CellConstraints labelConstraints,
Component component, CellConstraints componentConstraints) {
if (labelConstraints == componentConstraints) {
throw new IllegalArgumentException(
"You must provide two CellConstraints instances, " +
"one for the label and one for the component.\n" +
"Consider using the CC class. See the JavaDocs for details.");
}
JLabel label = addLabel(textWithMnemonic, labelConstraints);
add(component, componentConstraints);
label.setLabelFor(component);
return label;
}
// Adding Labels for Read-Only Components ---------------------------------
/**
* Adds a textual label intended for labeling read-only components
* to the form using the default constraints.
*
*
* addROLabel("Name:"); // No Mnemonic
* addROLabel("N&ame:"); // Mnemonic is 'a'
* addROLabel("Save &as:"); // Mnemonic is the second 'a'
* addROLabel("Look&&Feel:"); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return the new label
*
* @since 1.3
*/
public final JLabel addROLabel(String textWithMnemonic) {
return addROLabel(textWithMnemonic, cellConstraints());
}
/**
* Adds a textual label intended for labeling read-only components
* to the form using the specified constraints.
*
*
* addROLabel("Name:", CC.xy(1, 1)); // No Mnemonic
* addROLabel("N&ame:", CC.xy(1, 1)); // Mnemonic is 'a'
* addROLabel("Save &as:", CC.xy(1, 1)); // Mnemonic is the second 'a'
* addROLabel("Look&&Feel:", CC.xy(1, 1)); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param constraints the label's cell constraints
* @return the new label
*
* @since 1.3
*/
public final JLabel addROLabel(String textWithMnemonic, CellConstraints constraints) {
JLabel label = getComponentFactory().createReadOnlyLabel(textWithMnemonic);
add(label, constraints);
return label;
}
/**
* Adds a textual label intended for labeling read-only components
* to the form using the specified constraints.
*
*
* addROLabel("Name:", "1, 1"); // No Mnemonic
* addROLabel("N&ame:", "1, 1"); // Mnemonic is 'a'
* addROLabel("Save &as:", "1, 1"); // Mnemonic is the second 'a'
* addROLabel("Look&&Feel:", "1, 1"); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param encodedConstraints a string representation for the constraints
* @return the new label
*
* @since 1.3
*/
public final JLabel addROLabel(String textWithMnemonic, String encodedConstraints) {
return addROLabel(textWithMnemonic, new CellConstraints(encodedConstraints));
}
/**
* Adds a label and component to the panel using the given cell constraints.
* Sets the given label as the component label using
* {@link JLabel#setLabelFor(java.awt.Component)}.
*
* Note: The {@link CellConstraints} objects for the label
* and the component must be different. Cell constraints are implicitly
* cloned by the FormLayout when added to the container.
* However, in this case you may be tempted to reuse a
* {@code CellConstraints} object in the same way as with many other
* builder methods that require a single {@code CellConstraints}
* parameter.
* The pitfall is that the methods {@code CellConstraints.xy*(...)}
* just set the coordinates but do not create a new instance.
* And so the second invocation of {@code xy*(...)} overrides
* the settings performed in the first invocation before the object
* is cloned by the {@code FormLayout}.
*
* Wrong:
* builder.addROLabel(
* "&Name:", // Mnemonic is 'N'
* cc.xy(1, 7), // will be modified by the code below
* nameField,
* cc.xy(3, 7) // sets the single instance to (3, 7)
* );
*
* Correct:
* builder.addROLabel(
* "&Name:",
* CC.xy(1, 7), // creates an instance
* nameField,
* CC.xy(3, 7) // creates another instance
* );
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param labelConstraints the label's cell constraints
* @param component the component to add
* @param componentConstraints the component's cell constraints
* @return the added label
* @throws IllegalArgumentException if the same cell constraints instance
* is used for the label and the component
*
* @see JLabel#setLabelFor(java.awt.Component)
* @see DefaultFormBuilder
*
* @since 1.3
*/
public final JLabel addROLabel(
String textWithMnemonic, CellConstraints labelConstraints,
Component component, CellConstraints componentConstraints) {
checkConstraints(labelConstraints, componentConstraints);
JLabel label = addROLabel(textWithMnemonic, labelConstraints);
add(component, componentConstraints);
label.setLabelFor(component);
return label;
}
// Adding Titles ----------------------------------------------------------
/**
* Adds a title label to the form using the default constraints.
*
*
* addTitle("Name"); // No mnemonic
* addTitle("N&ame"); // Mnemonic is 'a'
* addTitle("Save &as"); // Mnemonic is the second 'a'
* addTitle("Look&&Feel"); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the title label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return the added title label
*
* @see ComponentFactory
*/
public final JLabel addTitle(String textWithMnemonic) {
return addTitle(textWithMnemonic, cellConstraints());
}
/**
* Adds a title label to the form using the specified constraints.
*
*
* addTitle("Name", CC.xy(1, 1)); // No mnemonic
* addTitle("N&ame", CC.xy(1, 1)); // Mnemonic is 'a'
* addTitle("Save &as", CC.xy(1, 1)); // Mnemonic is the second 'a'
* addTitle("Look&&Feel", CC.xy(1, 1)); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the title label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param constraints the separator's cell constraints
* @return the added title label
*
* @see ComponentFactory
*/
public final JLabel addTitle(String textWithMnemonic, CellConstraints constraints) {
JLabel titleLabel = getComponentFactory().createTitle(textWithMnemonic);
add(titleLabel, constraints);
return titleLabel;
}
/**
* Adds a title label to the form using the specified constraints.
*
*
* addTitle("Name", "1, 1"); // No mnemonic
* addTitle("N&ame", "1, 1"); // Mnemonic is 'a'
* addTitle("Save &as", "1, 1"); // Mnemonic is the second 'a'
* addTitle("Look&&Feel", "1, 1"); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the title label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param encodedConstraints a string representation for the constraints
* @return the added title label
*
* @see ComponentFactory
*/
public final JLabel addTitle(String textWithMnemonic, String encodedConstraints) {
return addTitle(textWithMnemonic, new CellConstraints(encodedConstraints));
}
// Adding Separators ------------------------------------------------------
/**
* Adds a titled separator to the form that spans all columns.
*
*
* addSeparator("Name"); // No Mnemonic
* addSeparator("N&ame"); // Mnemonic is 'a'
* addSeparator("Save &as"); // Mnemonic is the second 'a'
* addSeparator("Look&&Feel"); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the separator label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return the added separator
*/
public final JComponent addSeparator(String textWithMnemonic) {
return addSeparator(textWithMnemonic, getLayout().getColumnCount());
}
/**
* Adds a titled separator to the form using the specified constraints.
*
*
* addSeparator("Name", CC.xy(1, 1)); // No Mnemonic
* addSeparator("N&ame", CC.xy(1, 1)); // Mnemonic is 'a'
* addSeparator("Save &as", CC.xy(1, 1)); // Mnemonic is the second 'a'
* addSeparator("Look&&Feel", CC.xy(1, 1)); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the separator label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param constraints the separator's cell constraints
* @return the added separator
*/
public final JComponent addSeparator(String textWithMnemonic, CellConstraints constraints) {
int titleAlignment = isLeftToRight()
? SwingConstants.LEFT
: SwingConstants.RIGHT;
JComponent titledSeparator =
getComponentFactory().createSeparator(textWithMnemonic, titleAlignment);
add(titledSeparator, constraints);
return titledSeparator;
}
/**
* Adds a titled separator to the form using the specified constraints.
*
*
* addSeparator("Name", "1, 1"); // No Mnemonic
* addSeparator("N&ame", "1, 1"); // Mnemonic is 'a'
* addSeparator("Save &as", "1, 1"); // Mnemonic is the second 'a'
* addSeparator("Look&&Feel", "1, 1"); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the separator label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param encodedConstraints a string representation for the constraints
* @return the added separator
*/
public final JComponent addSeparator(String textWithMnemonic, String encodedConstraints) {
return addSeparator(textWithMnemonic, new CellConstraints(encodedConstraints));
}
/**
* Adds a titled separator to the form that spans the specified columns.
*
*
* addSeparator("Name", 3); // No Mnemonic
* addSeparator("N&ame", 3); // Mnemonic is 'a'
* addSeparator("Save &as", 3); // Mnemonic is the second 'a'
* addSeparator("Look&&Feel", 3); // No mnemonic, text is "look&feel"
*
*
* @param textWithMnemonic the separator label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param columnSpan the number of columns the separator spans
* @return the added separator
*/
public final JComponent addSeparator(String textWithMnemonic, int columnSpan) {
return addSeparator(textWithMnemonic, createLeftAdjustedConstraints(columnSpan));
}
/**
* Adds a label and component to the panel using the given cell constraints.
* Sets the given label as the component label using
* {@link JLabel#setLabelFor(java.awt.Component)}.
*
* Note: The {@link CellConstraints} objects for the label
* and the component must be different. Cell constraints are implicitly
* cloned by the {@code FormLayout} when added to the container.
* However, in this case you may be tempted to reuse a
* {@code CellConstraints} object in the same way as with many other
* builder methods that require a single {@code CellConstraints}
* parameter.
* The pitfall is that the methods {@code CellConstraints.xy*(...)}
* just set the coordinates but do not create a new instance.
* And so the second invocation of {@code xy*(...)} overrides
* the settings performed in the first invocation before the object
* is cloned by the {@code FormLayout}.
*
* Wrong:
* CellConstraints cc = new CellConstraints();
* builder.add(
* nameLabel,
* cc.xy(1, 7), // will be modified by the code below
* nameField,
* cc.xy(3, 7) // sets the single instance to (3, 7)
* );
*
* Correct:
* builder.add(
* nameLabel,
* CC.xy(1, 7), // creates an instance
* nameField,
* CC.xy(3, 7) // creates another instance
* );
*
*
* @param label the label to add
* @param labelConstraints the label's cell constraints
* @param component the component to add
* @param componentConstraints the component's cell constraints
* @return the added label
* @throws IllegalArgumentException if the same cell constraints instance
* is used for the label and the component
*
* @see JLabel#setLabelFor(java.awt.Component)
* @see DefaultFormBuilder
*/
public final JLabel add(JLabel label, CellConstraints labelConstraints,
Component component, CellConstraints componentConstraints) {
checkConstraints(labelConstraints, componentConstraints);
add(label, labelConstraints);
add(component, componentConstraints);
label.setLabelFor(component);
return label;
}
// Overriding Superclass Behavior *****************************************
/**
* Adds a component to the panel using the given cell constraints.
* In addition to the superclass behavior, this implementation
* tracks the most recently added label, and associates it with
* the next added component that is applicable for being set as component
* for the label.
*
* @param component the component to add
* @param cellConstraints the component's cell constraints
* @return the added component
*
* @see #isLabelForApplicable(JLabel, Component)
*/
@Override
public Component add(Component component, CellConstraints cellConstraints) {
Component result = super.add(component, cellConstraints);
manageLabelsAndComponents(component);
return result;
}
// Default Behavior *******************************************************
private void manageLabelsAndComponents(Component c) {
if (!labelForFeatureEnabled) {
return;
}
if (c instanceof JLabel) {
JLabel label = (JLabel) c;
if (label.getLabelFor() == null) {
setMostRecentlyAddedLabel(label);
} else {
clearMostRecentlyAddedLabel();
}
return;
}
JLabel mostRecentlyAddedLabel = getMostRecentlyAddedLabel();
if ( mostRecentlyAddedLabel != null
&& isLabelForApplicable(mostRecentlyAddedLabel, c)) {
setLabelFor(mostRecentlyAddedLabel, c);
clearMostRecentlyAddedLabel();
}
}
/**
* Checks and answers whether the given component shall be set
* as component for a previously added label using
* {@link JLabel#setLabelFor(Component)}.
*
* This default implementation checks whether the component is focusable,
* and - if a JComponent - whether it is already labeled by a JLabel.
* Subclasses may override.
*
* @param label the candidate for labeling {@code component}
* @param component the component that could be labeled by {@code label}
* @return true if focusable, false otherwise
*/
protected boolean isLabelForApplicable(JLabel label, Component component) {
// 1) Is the label labeling a component?
if (label.getLabelFor() != null) {
return false;
}
// 2) Is the component focusable?
if (!component.isFocusable()) {
return false;
}
// 3) Is the component labeled by another label?
if (!(component instanceof JComponent)) {
return true;
}
JComponent c = (JComponent) component;
return c.getClientProperty(LABELED_BY_PROPERTY) == null;
}
/**
* Sets {@code label} as labeling label for {@code component} or an
* appropriate child. In case of a JScrollPane as given component,
* this default implementation labels the view of the scroll pane's
* viewport.
*
* @param label the labeling label
* @param component the component to be labeled, or the parent of
* the labeled component
*/
protected void setLabelFor(JLabel label, Component component) {
Component labeledComponent;
if (component instanceof JScrollPane) {
JScrollPane scrollPane = (JScrollPane) component;
labeledComponent = scrollPane.getViewport().getView();
} else {
labeledComponent = component;
}
label.setLabelFor(labeledComponent);
}
// Helper Code ************************************************************
/**
* Returns the most recently added JLabel that has a mnemonic set
* - if any, {@code null}, if none has been set, or if it has
* been cleared after setting an association before, or if it has been
* cleared by the garbage collector.
*
* @return the most recently added JLabel that has a mnemonic set
* and has not been associated with a component applicable for this
* feature. {@code null} otherwise.
*/
private JLabel getMostRecentlyAddedLabel() {
if (mostRecentlyAddedLabelReference == null) {
return null;
}
JLabel label = (JLabel) mostRecentlyAddedLabelReference.get();
if (label == null) {
return null;
}
return label;
}
/**
* Sets the given label as most recently added label using a weak reference.
*
* @param label the label to be set
*/
private void setMostRecentlyAddedLabel(JLabel label) {
mostRecentlyAddedLabelReference = new WeakReference(label);
}
/**
* Clears the reference to the most recently added mnemonic label.
*/
private void clearMostRecentlyAddedLabel() {
mostRecentlyAddedLabelReference = null;
}
private static void checkConstraints(CellConstraints c1, CellConstraints c2) {
checkArgument(c1 != c2,
"You must provide two CellConstraints instances, " +
"one for the label and one for the component.\n" +
"Consider using the CC factory. See the JavaDocs for details.");
}
}