com.jgoodies.forms.factories.DefaultComponentFactory 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.factories;
import static com.jgoodies.common.base.Preconditions.checkArgument;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.LayoutManager;
import javax.accessibility.AccessibleContext;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import com.jgoodies.common.base.Preconditions;
import com.jgoodies.common.base.Strings;
import com.jgoodies.common.swing.MnemonicUtils;
import com.jgoodies.forms.layout.Sizes;
import com.jgoodies.forms.util.FormUtils;
/**
* A singleton implementation of the {@link ComponentFactory} interface
* that creates UI components as required by the
* {@link com.jgoodies.forms.builder.PanelBuilder}.
*
* The texts used in methods {@code #createLabel(String)} and
* {@code #createTitle(String)} 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".
*
* @author Karsten Lentzsch
* @version $Revision: 1.21 $
*/
public class DefaultComponentFactory implements ComponentFactory {
/**
* Holds the single instance of this class.
*/
private static final DefaultComponentFactory INSTANCE =
new DefaultComponentFactory();
// Instance *************************************************************
/**
* Returns the sole instance of this factory class.
*
* @return the sole instance of this factory class
*/
public static DefaultComponentFactory getInstance() {
return INSTANCE;
}
// Component Creation ***************************************************
/**
* Creates and returns a label with an optional mnemonic.
*
*
* createLabel("Name"); // No mnemonic
* createLabel("N&ame"); // Mnemonic is 'a'
* createLabel("Save &as"); // Mnemonic is the second 'a'
* createLabel("Look&&Feel"); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return an label with optional mnemonic
*/
@Override
public JLabel createLabel(String textWithMnemonic) {
JLabel label = new FormsLabel();
MnemonicUtils.configure(label, textWithMnemonic);
return label;
}
/**
* Creates and returns a label with an optional mnemonic
* that is intended to label a read-only component.
*
*
* createReadOnlyLabel("Name"); // No mnemonic
* createReadOnlyLabel("N&ame"); // Mnemonic is 'a'
* createReadOnlyLabel("Save &as"); // Mnemonic is the second 'a'
* createReadOnlyLabel("Look&&Feel"); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return an label with optional mnemonic intended for read-only
* components
*
* @since 1.3
*/
@Override
public JLabel createReadOnlyLabel(String textWithMnemonic) {
JLabel label = new ReadOnlyLabel();
MnemonicUtils.configure(label, textWithMnemonic);
return label;
}
/**
* Creates and returns a button that is bound to the given Action.
* Useful to return customized buttons, for example, the JGoodies
* {@code JGButton} is bound to some custom Action properties.
*
* This default implementation just returns a {@code JButton}.
*
* @param action provides [bound] visual properties for the button
* @return the created button
*
* @since 1.4
*/
@Override
public JButton createButton(Action action) {
return new JButton(action);
}
/**
* Creates and returns a title label that uses the foreground color
* and font of a {@code TitledBorder}.
*
*
* createTitle("Name"); // No mnemonic
* createTitle("N&ame"); // Mnemonic is 'a'
* createTitle("Save &as"); // Mnemonic is the second 'a'
* createTitle("Look&&Feel"); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return an emphasized title label
*/
@Override
public JLabel createTitle(String textWithMnemonic) {
JLabel label = new TitleLabel();
MnemonicUtils.configure(label, textWithMnemonic);
label.setVerticalAlignment(SwingConstants.CENTER);
return label;
}
@Override
public JLabel createHeaderLabel(String markedText) {
return createTitle(markedText);
}
/**
* Creates and returns a labeled separator with the label in the left-hand
* side. Useful to separate paragraphs in a panel; often a better choice
* than a {@code TitledBorder}.
*
*
* createSeparator("Name"); // No mnemonic
* createSeparator("N&ame"); // Mnemonic is 'a'
* createSeparator("Save &as"); // Mnemonic is the second 'a'
* createSeparator("Look&&Feel"); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @return a title label with separator on the side
*/
public JComponent createSeparator(String textWithMnemonic) {
return createSeparator(textWithMnemonic, SwingConstants.LEFT);
}
/**
* Creates and returns a labeled separator. Useful to separate
* paragraphs in a panel, which is often a better choice than a
* {@code TitledBorder}.
*
*
* final int LEFT = SwingConstants.LEFT;
* createSeparator("Name", LEFT); // No mnemonic
* createSeparator("N&ame", LEFT); // Mnemonic is 'a'
* createSeparator("Save &as", LEFT); // Mnemonic is the second 'a'
* createSeparator("Look&&Feel", LEFT); // No mnemonic, text is Look&Feel
*
*
* @param textWithMnemonic the label's text -
* may contain an ampersand (&) to mark a mnemonic
* @param alignment text alignment, one of {@code SwingConstants.LEFT},
* {@code SwingConstants.CENTER}, {@code SwingConstants.RIGHT}
* @return a separator with title label
*/
@Override
public JComponent createSeparator(String textWithMnemonic, int alignment) {
if (Strings.isBlank(textWithMnemonic)) {
return new JSeparator();
}
JLabel title = createTitle(textWithMnemonic);
title.setHorizontalAlignment(alignment);
return createSeparator(title);
}
/**
* Creates and returns a labeled separator. Useful to separate
* paragraphs in a panel, which is often a better choice than a
* {@code TitledBorder}.
*
* The label's position is determined by the label's horizontal alignment,
* which must be one of:
* {@code SwingConstants.LEFT},
* {@code SwingConstants.CENTER},
* {@code SwingConstants.RIGHT}.
*
* TODO: Since this method has been marked public in version 1.0.6,
* we need to precisely describe the semantic of this method.
*
* TODO: Check if we can relax the constraint for the label alignment
* and also accept LEADING and TRAILING.
*
* @param label the title label component
* @return a separator with title label
*
* @throws NullPointerException if the label is {@code null}
* @throws IllegalArgumentException if the label's horizontal alignment
* is not one of: {@code SwingConstants.LEFT},
* {@code SwingConstants.CENTER},
* {@code SwingConstants.RIGHT}.
*
* @since 1.0.6
*/
public JComponent createSeparator(JLabel label) {
Preconditions.checkNotNull(label, "The label must not be null.");
int horizontalAlignment = label.getHorizontalAlignment();
checkArgument(
horizontalAlignment == SwingConstants.LEFT
|| horizontalAlignment == SwingConstants.CENTER
|| horizontalAlignment == SwingConstants.RIGHT,
"The label's horizontal alignment must be one of: LEFT, CENTER, RIGHT.");
JPanel panel = new JPanel(new TitledSeparatorLayout(!FormUtils.isLafAqua()));
panel.setOpaque(false);
panel.add(label);
panel.add(new JSeparator());
if (horizontalAlignment == SwingConstants.CENTER) {
panel.add(new JSeparator());
}
return panel;
}
// Helper Classes *********************************************************
/**
* Differs from its superclass JLabel in that it removes a trailing
* colon (':') - if any - from the label's accessible name.
* This in turn leads to improved accessible names for components
* labeled by FormLabels via {@code #setLabelFor}.
*/
private static class FormsLabel extends JLabel {
// Accessibility Support ----------------------------------------------
/**
* Lazily creates and returns the {@link AccessibleContext} associated
* with this label, an instance of {@code AccessibleFormsLabel}.
*
* @return this link's AccessibleContext
*/
@Override
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleFormsLabel();
}
return accessibleContext;
}
/**
* This class implements accessibility support for FormsLabels.
* Cuts off trailing colons from the accessible name - if any.
*/
private final class AccessibleFormsLabel extends AccessibleJLabel {
/**
* Returns the accessible name of this label.
* Unlike the superclass behavior, this implementation
* cuts off a trailing colon (':') - if any.
*
* @return the label name
*
* @see AccessibleContext#setAccessibleName
*/
@Override
public String getAccessibleName() {
if (accessibleName != null) {
return accessibleName;
}
String text = FormsLabel.this.getText();
if (text == null) {
return super.getAccessibleName();
}
return text.endsWith(":")
? text.substring(0, text.length() - 1)
: text;
}
}
}
/**
* A FormsLabel subclasses intended to label read-only components.
* Aims to set the disabled foreground color.
*/
private static final class ReadOnlyLabel extends FormsLabel {
private static final String[] UIMANAGER_KEYS =
{"Label.disabledForeground",
"Label.disabledText",
"Label[Disabled].textForeground",
"textInactiveText"};
@Override
public void updateUI() {
super.updateUI();
setForeground(getDisabledForeground());
}
private static Color getDisabledForeground() {
Color foreground;
for (String key : UIMANAGER_KEYS) {
foreground = UIManager.getColor(key);
if (foreground != null) {
// System.out.println("Matching key=" + key + "; color=" + foreground);
return foreground;
}
}
return null;
}
}
/**
* A label that uses the TitleBorder font and color.
*/
private static final class TitleLabel extends FormsLabel {
private TitleLabel() {
// Just invoke the super constructor.
}
/**
* TODO: For the Synth-based L&f we should consider asking
* a {@code TitledBorder} instance for its font and color using
* {@code #getTitleFont} and {@code #getTitleColor} resp.
*/
@Override
public void updateUI() {
super.updateUI();
Color foreground = getTitleColor();
if (foreground != null) {
setForeground(foreground);
}
setFont(getTitleFont());
}
private static Color getTitleColor() {
return UIManager.getColor("TitledBorder.titleColor");
}
/**
* Looks up and returns the font used for title labels.
* Since Mac Aqua uses an inappropriate titled border font,
* we use a bold label font instead. Actually if the title
* is used in a titled separator, the bold weight is questionable.
* It seems that most native Aqua tools use a plain label in
* titled separators.
*
* @return the font used for title labels
*/
private static Font getTitleFont() {
return FormUtils.isLafAqua()
? UIManager.getFont("Label.font").deriveFont(Font.BOLD)
: UIManager.getFont("TitledBorder.font");
}
}
/**
* A layout for the title label and separator(s) in titled separators.
*/
private static final class TitledSeparatorLayout implements LayoutManager {
private final boolean centerSeparators;
/**
* Constructs a TitledSeparatorLayout that either centers the separators
* or aligns them along the font baseline of the title label.
*
* @param centerSeparators true to center, false to align along
* the font baseline of the title label
*/
private TitledSeparatorLayout(boolean centerSeparators) {
this.centerSeparators = centerSeparators;
}
/**
* Does nothing. This layout manager looks up the components
* from the layout container and used the component's index
* in the child array to identify the label and separators.
*
* @param name the string to be associated with the component
* @param comp the component to be added
*/
@Override
public void addLayoutComponent(String name, Component comp) {
// Does nothing.
}
/**
* Does nothing. This layout manager looks up the components
* from the layout container and used the component's index
* in the child array to identify the label and separators.
*
* @param comp the component to be removed
*/
@Override
public void removeLayoutComponent(Component comp) {
// Does nothing.
}
/**
* Computes and returns the minimum size dimensions
* for the specified container. Forwards this request
* to {@code #preferredLayoutSize}.
*
* @param parent the component to be laid out
* @return the container's minimum size.
* @see #preferredLayoutSize(Container)
*/
@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
/**
* Computes and returns the preferred size dimensions
* for the specified container. Returns the title label's
* preferred size.
*
* @param parent the component to be laid out
* @return the container's preferred size.
* @see #minimumLayoutSize(Container)
*/
@Override
public Dimension preferredLayoutSize(Container parent) {
Component label = getLabel(parent);
Dimension labelSize = label.getPreferredSize();
Insets insets = parent.getInsets();
int width = labelSize.width + insets.left + insets.right;
int height = labelSize.height + insets.top + insets.bottom;
return new Dimension(width, height);
}
/**
* Lays out the specified container.
*
* @param parent the container to be laid out
*/
@Override
public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
// Look up the parent size and insets
Dimension size = parent.getSize();
Insets insets = parent.getInsets();
int width = size.width - insets.left - insets.right;
// Look up components and their sizes
JLabel label = getLabel(parent);
Dimension labelSize = label.getPreferredSize();
int labelWidth = labelSize.width;
int labelHeight = labelSize.height;
Component separator1 = parent.getComponent(1);
int separatorHeight = separator1.getPreferredSize().height;
FontMetrics metrics = label.getFontMetrics(label.getFont());
int ascent = metrics.getMaxAscent();
int hGapDlu = centerSeparators ? 3 : 1;
int hGap = Sizes.dialogUnitXAsPixel(hGapDlu, label);
int vOffset = centerSeparators
? 1 + (labelHeight - separatorHeight) / 2
: ascent - separatorHeight / 2;
int alignment = label.getHorizontalAlignment();
int y = insets.top;
if (alignment == SwingConstants.LEFT) {
int x = insets.left;
label.setBounds(x, y, labelWidth, labelHeight);
x += labelWidth;
x += hGap;
int separatorWidth = size.width - insets.right - x;
separator1.setBounds(x, y + vOffset, separatorWidth, separatorHeight);
} else if (alignment == SwingConstants.RIGHT) {
int x = insets.left + width - labelWidth;
label.setBounds(x, y, labelWidth, labelHeight);
x -= hGap;
x--;
int separatorWidth = x - insets.left;
separator1.setBounds(insets.left, y + vOffset, separatorWidth, separatorHeight);
} else {
int xOffset = (width - labelWidth - 2 * hGap) / 2;
int x = insets.left;
separator1.setBounds(x, y + vOffset, xOffset-1, separatorHeight);
x += xOffset;
x += hGap;
label.setBounds(x, y, labelWidth, labelHeight);
x += labelWidth;
x += hGap;
Component separator2 = parent.getComponent(2);
int separatorWidth = size.width - insets.right - x;
separator2.setBounds(x, y + vOffset, separatorWidth, separatorHeight);
}
}
}
private static JLabel getLabel(Container parent) {
return (JLabel) parent.getComponent(0);
}
}
}