/* * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. 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 Karsten Lentzsch 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 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.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.forms.layout.Sizes; import com.jgoodies.forms.util.Utilities; /** * A singleton implementaton of the {@link ComponentFactory} interface * that creates UI components as required by the * {@link com.jgoodies.forms.builder.PanelBuilder}.<p> * * The texts used in methods <code>#createLabel(String)</code> and * <code>#createTitle(String)</code> 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>. * * @author Karsten Lentzsch * @version $Revision$ */ public final class DefaultComponentFactory implements ComponentFactory { /** * Holds the single instance of this class. */ private static final DefaultComponentFactory INSTANCE = new DefaultComponentFactory(); /** * The character used to indicate the mnemonic position for labels. */ private static final char MNEMONIC_MARKER = '&'; // Instance ************************************************************* private DefaultComponentFactory() { // Suppresses default constructor, ensuring non-instantiability. } /** * 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.<p> * * <pre> * 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 * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) to mark a mnemonic * @return an label with optional mnemonic */ public JLabel createLabel(String textWithMnemonic) { JLabel label = new JLabel(); setTextAndMnemonic(label, textWithMnemonic); return label; } /** * Creates and returns a title label that uses the foreground color * and font of a <code>TitledBorder</code>.<p> * * <pre> * 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 * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) to mark a mnemonic * @return an emphasized title label */ public JLabel createTitle(String textWithMnemonic) { JLabel label = new TitleLabel(); setTextAndMnemonic(label, textWithMnemonic); label.setVerticalAlignment(SwingConstants.CENTER); return label; } /** * 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</code>.<p> * * <pre> * 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 * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) 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</code>.<p> * * <pre> * 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 * </pre> * * @param textWithMnemonic the label's text - * may contain an ampersand (<tt>&</tt>) to mark a mnemonic * @param alignment text alignment, one of <code>SwingConstants.LEFT</code>, * <code>SwingConstants.CENTER</code>, <code>SwingConstants.RIGHT</code> * @return a separator with title label */ public JComponent createSeparator(String textWithMnemonic, int alignment) { if (textWithMnemonic == null || textWithMnemonic.length() == 0) { 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</code>.<p> * * The label's position is determined by the label's horizontal alignment, * which must be one of: * <code>SwingConstants.LEFT</code>, * <code>SwingConstants.CENTER</code>, * <code>SwingConstants.RIGHT</code>.<p> * * TODO: Since this method has been marked public in version 1.0.6, * we need to precisely describe the semantic of this method.<p> * * 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 * @exception NullPointerException if the label is <code>null</code> * * @since 1.0.6 */ public JComponent createSeparator(JLabel label) { if (label == null) { throw new NullPointerException("The label must not be null."); } JPanel panel = new JPanel(new TitledSeparatorLayout(!Utilities .isLafAqua())); panel.setOpaque(false); panel.add(label); panel.add(new JSeparator()); if (label.getHorizontalAlignment() == SwingConstants.CENTER) { panel.add(new JSeparator()); } return panel; } // Helper Code *********************************************************** /** * Sets the text of the given label and optionally a mnemonic. * The given text may contain an ampersand (<tt>&</tt>) * to mark a mnemonic and its position. Such a marker indicates * that the character that follows the ampersand shall be the mnemonic. * If you want to use the ampersand itself duplicate it, for example * <tt>"Look&&Feel"</tt>. * * @param label the label that gets a mnemonic * @param textWithMnemonic the text with optional mnemonic marker */ private static void setTextAndMnemonic(JLabel label, String textWithMnemonic) { int markerIndex = textWithMnemonic.indexOf(MNEMONIC_MARKER); // No marker at all if (markerIndex == -1) { label.setText(textWithMnemonic); return; } int mnemonicIndex = -1; int begin = 0; int end; int length = textWithMnemonic.length(); int quotedMarkers = 0; StringBuffer buffer = new StringBuffer(); do { // Check whether the next index has a mnemonic marker, too if ((markerIndex + 1 < length) && (textWithMnemonic.charAt(markerIndex + 1) == MNEMONIC_MARKER)) { end = markerIndex + 1; quotedMarkers++; } else { end = markerIndex; if (mnemonicIndex == -1) { mnemonicIndex = markerIndex - quotedMarkers; } } buffer.append(textWithMnemonic.substring(begin, end)); begin = end + 1; markerIndex = begin < length ? textWithMnemonic.indexOf( MNEMONIC_MARKER, begin) : -1; } while (markerIndex != -1); buffer.append(textWithMnemonic.substring(begin)); String text = buffer.toString(); label.setText(text); if ((mnemonicIndex != -1) && (mnemonicIndex < text.length())) { label.setDisplayedMnemonic(text.charAt(mnemonicIndex)); label.setDisplayedMnemonicIndex(mnemonicIndex); } } /** * A label that uses the TitleBorder font and color. */ private static final class TitleLabel extends JLabel { private TitleLabel() { // Just invoke the super constructor. } /** * TODO: For the Synth-based L&f we should consider asking * a <code>TitledBorder</code> instance for its font and color using * <code>#getTitleFont</code> and <code>#getTitleColor</code> resp. */ public void updateUI() { super.updateUI(); Color foreground = getTitleColor(); if (foreground != null) { setForeground(foreground); } setFont(getTitleFont()); } private 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 Font getTitleFont() { return Utilities.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 */ 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 */ public void removeLayoutComponent(Component comp) { // Does nothing. } /** * Computes and returns the minimum size dimensions * for the specified container. Forwards this request * to <code>#preferredLayoutSize</code>. * * @param parent the component to be laid out * @return the container's minimum size. * @see #preferredLayoutSize(Container) */ 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) */ 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 */ 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 JLabel getLabel(Container parent) { return (JLabel) parent.getComponent(0); } } }