Java tutorial
/* * 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.<p> * * The PanelBuilder is the working horse for layouts when more specialized * builders like the {@link ButtonBarBuilder} or {@link DefaultFormBuilder} * are inappropriate.<p> * * 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.<p> * * 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 (<tt>&</tt>). For example * <tt>"&Save"</tt>, or <tt>"Save &as"</tt>. * To use the ampersand itself duplicate it, for example * <tt>"Look&&Feel"</tt>.<p> * * <strong>Example:</strong><br> * This example creates a panel with 3 columns and 3 rows. * <pre> * 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(); * </pre> * * @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.<p> * * 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: * <pre> * builder.getPanel().setFocusTraversalPolicy(policy); * builder.getPanel().setFocusTraversalPolicyProvider(true); * </pre> * * @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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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 <i>the</i> component label using * {@link JLabel#setLabelFor(java.awt.Component)}.<p> * * <strong>Note:</strong> 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 <em>not</em> 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}.<p> * * <strong>Wrong:</strong><pre> * 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) * ); * </pre> * <strong>Correct:</strong><pre> * builder.addLabel( * "&Name:", * CC.xy(1, 7), // creates an instance * nameField, * CC.xy(3, 7) // creates another instance * ); * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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 <i>the</i> component label using * {@link JLabel#setLabelFor(java.awt.Component)}.<p> * * <strong>Note:</strong> 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 <em>not</em> 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}.<p> * * <strong>Wrong:</strong><pre> * 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) * ); * </pre> * <strong>Correct:</strong><pre> * builder.addROLabel( * "&Name:", * CC.xy(1, 7), // creates an instance * nameField, * CC.xy(3, 7) // creates another instance * ); * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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 * </pre> * * @param textWithMnemonic the title label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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 * </pre> * * @param textWithMnemonic the title label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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 * </pre> * * @param textWithMnemonic the title label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the separator label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the separator label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the separator label's text - * may contain an ampersand (<tt>&</tt>) 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.<p> * * <pre> * 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" * </pre> * * @param textWithMnemonic the separator label's text - * may contain an ampersand (<tt>&</tt>) 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 <i>the</i> component label using * {@link JLabel#setLabelFor(java.awt.Component)}.<p> * * <strong>Note:</strong> 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 <em>not</em> 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}.<p> * * <strong>Wrong:</strong><pre> * 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) * ); * </pre> * <strong>Correct:</strong><pre> * builder.add( * nameLabel, * CC.xy(1, 7), // creates an instance * nameField, * CC.xy(3, 7) // creates another instance * ); * </pre> * * @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."); } }