JFont Chooser
// revised from greef ui import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GraphicsEnvironment; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.HeadlessException; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.Serializable; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; /** * <code>JFontChooser</code> provides a pane of controls designed to allow * a user to manipulate and select a font. * * This class provides three levels of API: * <ol> * <li>A static convenience method which shows a modal font-chooser * dialog and returns the font selected by the user. * <li>A static convenience method for creating a font-chooser dialog * where <code>ActionListeners</code> can be specified to be invoked when * the user presses one of the dialog buttons. * <li>The ability to create instances of <code>JFontChooser</code> panes * directly (within any container). <code>PropertyChange</code> listeners * can be added to detect when the current "font" property changes. * </ol> * <p> * * @author Adrian BER */ public class JFontChooser extends JComponent { /** The list of possible font sizes. */ private static final Integer[] SIZES = {8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 24, 26, 28, 32, 36, 40, 48, 56, 64, 72}; /** The list of possible fonts. */ private static final String[] FONTS = GraphicsEnvironment.getLocalGraphicsEnvironment() .getAvailableFontFamilyNames(); private FontSelectionModel selectionModel; private JList fontList; private JList sizeList; private JCheckBox boldCheckBox; private JCheckBox italicCheckBox; private JLabel previewLabel; /** The preview text, if null the font name will be the preview text. */ private String previewText; /** Listener used to update the font of the selection model. */ private SelectionUpdater selectionUpdater = new SelectionUpdater(); /** Listener used to update the font in the components. This should be registered * with the selection model. */ private LabelUpdater labelUpdater = new LabelUpdater(); /** True if the components are being updated and no event should be generated. */ private boolean updatingComponents = false; /** Listener class used to update the font in the components. This should be registered * with the selection model. */ private class LabelUpdater implements ChangeListener { public void stateChanged(ChangeEvent e) { updateComponents(); } } /** Listener class used to update the font of the preview label. */ private class SelectionUpdater implements ChangeListener, ListSelectionListener { public void stateChanged(ChangeEvent e) { if (!updatingComponents) { setFont(buildFont()); } } public void valueChanged(ListSelectionEvent e) { if (!updatingComponents) { setFont(buildFont()); } } } /** * Shows a modal font-chooser dialog and blocks until the * dialog is hidden. If the user presses the "OK" button, then * this method hides/disposes the dialog and returns the selected color. * If the user presses the "Cancel" button or closes the dialog without * pressing "OK", then this method hides/disposes the dialog and returns * <code>null</code>. * * @param component the parent <code>Component</code> for the dialog * @param title the String containing the dialog's title * @return the selected font or <code>null</code> if the user opted out * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless */ public Font showDialog(Component component, String title) { FontTracker ok = new FontTracker(this); JDialog dialog = createDialog(component, title, true, ok, null); dialog.addWindowListener(new FontChooserDialog.Closer()); dialog.addComponentListener(new FontChooserDialog.DisposeOnClose()); dialog.setVisible(true); // blocks until user brings dialog down... return ok.getFont(); } /** * Creates and returns a new dialog containing the specified * <code>ColorChooser</code> pane along with "OK", "Cancel", and "Reset" * buttons. If the "OK" or "Cancel" buttons are pressed, the dialog is * automatically hidden (but not disposed). If the "Reset" * button is pressed, the color-chooser's color will be reset to the * font which was set the last time <code>show</code> was invoked on the * dialog and the dialog will remain showing. * * @param c the parent component for the dialog * @param title the title for the dialog * @param modal a boolean. When true, the remainder of the program * is inactive until the dialog is closed. * @param okListener the ActionListener invoked when "OK" is pressed * @param cancelListener the ActionListener invoked when "Cancel" is pressed * @return a new dialog containing the font-chooser pane * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless */ public JDialog createDialog(Component c, String title, boolean modal, ActionListener okListener, ActionListener cancelListener) { return new FontChooserDialog(c, title, modal, this, okListener, cancelListener); } /** * Creates a color chooser pane with an initial font which is the same font * as the default font for labels. */ public JFontChooser() { this(new DefaultFontSelectionModel()); } /** * Creates a font chooser pane with the specified initial font. * * @param initialFont the initial font set in the chooser */ public JFontChooser(Font initialFont) { this(new DefaultFontSelectionModel(initialFont)); } /** * Creates a font chooser pane with the specified * <code>FontSelectionModel</code>. * * @param model the font selection model used by this component */ public JFontChooser(FontSelectionModel model) { selectionModel = model; init(model.getSelectedFont()); selectionModel.addChangeListener(labelUpdater); } private void init(Font font) { setLayout(new GridBagLayout()); Insets ins = new Insets(2, 2, 2, 2); fontList = new JList(FONTS); fontList.setVisibleRowCount(10); fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); add(new JScrollPane(fontList), new GridBagConstraints(0, 0, 1, 1, 2, 2, GridBagConstraints.CENTER, GridBagConstraints.BOTH, ins, 0, 0)); sizeList = new JList(SIZES); ((JLabel)sizeList.getCellRenderer()).setHorizontalAlignment(JLabel.RIGHT); sizeList.setVisibleRowCount(10); sizeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); add(new JScrollPane(sizeList), new GridBagConstraints(1, 0, 1, 1, 1, 2, GridBagConstraints.CENTER, GridBagConstraints.BOTH, ins, 0, 0)); boldCheckBox = new JCheckBox("Bold"); add(boldCheckBox, new GridBagConstraints(0, 1, 2, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, ins, 0, 0)); italicCheckBox = new JCheckBox("Italic"); add(italicCheckBox, new GridBagConstraints(0, 2, 2, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, ins, 0, 0)); previewLabel = new JLabel(""); previewLabel.setHorizontalAlignment(JLabel.CENTER); previewLabel.setVerticalAlignment(JLabel.CENTER); add(new JScrollPane(previewLabel), new GridBagConstraints(0, 3, 2, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, ins, 0, 0)); setFont(font == null ? previewLabel.getFont() : font); fontList.addListSelectionListener(selectionUpdater); sizeList.addListSelectionListener(selectionUpdater); boldCheckBox.addChangeListener(selectionUpdater); italicCheckBox.addChangeListener(selectionUpdater); } private Font buildFont() { // Font labelFont = previewLabel.getFont(); String fontName = (String)fontList.getSelectedValue(); if (fontName == null) { return null; // fontName = labelFont.getName(); } Integer sizeInt = (Integer)sizeList.getSelectedValue(); if (sizeInt == null) { // size = labelFont.getSize(); return null; } // create the font // // first create the font attributes // HashMap map = new HashMap(); // map.put(TextAttribute.BACKGROUND, Color.white); // map.put(TextAttribute.FAMILY, fontName); // map.put(TextAttribute.FOREGROUND, Color.black); // map.put(TextAttribute.SIZE , new Float(size)); // map.put(TextAttribute.UNDERLINE, italicCheckBox.isSelected() ? TextAttribute.UNDERLINE_LOW_ONE_PIXEL : TextAttribute.UNDERLINE_LOW_TWO_PIXEL); // map.put(TextAttribute.STRIKETHROUGH, italicCheckBox.isSelected() ? TextAttribute.STRIKETHROUGH_ON : Boolean.FALSE); // map.put(TextAttribute.WEIGHT, boldCheckBox.isSelected() ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR); // map.put(TextAttribute.POSTURE, // italicCheckBox.isSelected() ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR); // // return new Font(map); return new Font(fontName, (italicCheckBox.isSelected() ? Font.ITALIC : Font.PLAIN) | (boldCheckBox.isSelected() ? Font.BOLD : Font.PLAIN), sizeInt); } /** Updates the font in the preview component according to the selected values. */ private void updateComponents() { updatingComponents = true; Font font = getFont(); fontList.setSelectedValue(font.getName(), true); sizeList.setSelectedValue(font.getSize(), true); boldCheckBox.setSelected(font.isBold()); italicCheckBox.setSelected(font.isItalic()); if (previewText == null) { previewLabel.setText(font.getName()); } // set the font and fire a property change Font oldValue = previewLabel.getFont(); previewLabel.setFont(font); firePropertyChange("font", oldValue, font); updatingComponents = false; } /** * Returns the data model that handles font selections. * * @return a FontSelectionModel object */ public FontSelectionModel getSelectionModel() { return selectionModel; } /** * Set the model containing the selected font. * * @param newModel the new FontSelectionModel object */ public void setSelectionModel(FontSelectionModel newModel ) { FontSelectionModel oldModel = selectionModel; selectionModel = newModel; oldModel.removeChangeListener(labelUpdater); newModel.addChangeListener(labelUpdater); firePropertyChange("selectionModel", oldModel, newModel); } /** * Gets the current font value from the font chooser. * * @return the current font value of the font chooser */ public Font getFont() { return selectionModel.getSelectedFont(); } /** * Sets the current font of the font chooser to the specified font. * The <code>ColorSelectionModel</code> will fire a <code>ChangeEvent</code> * @param font the font to be set in the font chooser * @see JComponent#addPropertyChangeListener */ public void setFont(Font font) { selectionModel.setSelectedFont(font); } /** Returns the preview text displayed in the preview component. * @return the preview text, if null the font name will be displayed */ public String getPreviewText() { return previewText; } /** Sets the preview text displayed in the preview component. * @param previewText the preview text, if null the font name will be displayed */ public void setPreviewText(String previewText) { this.previewText = previewText; previewLabel.setText(""); updateComponents(); } } /* * Class which builds a font chooser dialog consisting of * a JFontChooser with "Ok", "Cancel", and "Reset" buttons. * * Note: This needs to be fixed to deal with localization! */ class FontChooserDialog extends JDialog { private Font initialFont; private JFontChooser chooserPane; public FontChooserDialog(Component c, String title, boolean modal, JFontChooser chooserPane, ActionListener okListener, ActionListener cancelListener) { super(JOptionPane.getFrameForComponent(c), title, modal); //setResizable(false); String okString = UIManager.getString("ColorChooser.okText"); String cancelString = UIManager.getString("ColorChooser.cancelText"); String resetString = UIManager.getString("ColorChooser.resetText"); /* * Create Lower button panel */ JPanel buttonPane = new JPanel(); buttonPane.setLayout(new FlowLayout(FlowLayout.CENTER)); JButton okButton = new JButton(okString); getRootPane().setDefaultButton(okButton); okButton.setActionCommand("OK"); if (okListener != null) { okButton.addActionListener(okListener); } okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setVisible(false); } }); buttonPane.add(okButton); JButton cancelButton = new JButton(cancelString); // The following few lines are used to register esc to close the dialog Action cancelKeyAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { // todo make it in 1.3 // ActionListener[] listeners // = ((AbstractButton) e.getSource()).getActionListeners(); // for (int i = 0; i < listeners.length; i++) { // listeners[i].actionPerformed(e); // } } }; KeyStroke cancelKeyStroke = KeyStroke.getKeyStroke((char) KeyEvent.VK_ESCAPE); InputMap inputMap = cancelButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap actionMap = cancelButton.getActionMap(); if (inputMap != null && actionMap != null) { inputMap.put(cancelKeyStroke, "cancel"); actionMap.put("cancel", cancelKeyAction); } // end esc handling cancelButton.setActionCommand("cancel"); if (cancelListener != null) { cancelButton.addActionListener(cancelListener); } cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setVisible(false); } }); buttonPane.add(cancelButton); JButton resetButton = new JButton(resetString); resetButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { reset(); } }); int mnemonic = UIManager.getInt("ColorChooser.resetMnemonic"); if (mnemonic != -1) { resetButton.setMnemonic(mnemonic); } buttonPane.add(resetButton); // initialiase the content pane this.chooserPane = chooserPane; Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(chooserPane, BorderLayout.CENTER); contentPane.add(buttonPane, BorderLayout.SOUTH); pack(); setLocationRelativeTo(c); } public void setVisible(boolean visible) { if (visible) initialFont = chooserPane.getFont(); super.setVisible(visible); } public void reset() { chooserPane.setFont(initialFont); } static class Closer extends WindowAdapter implements Serializable { public void windowClosing(WindowEvent e) { Window w = e.getWindow(); w.setVisible(false); } } static class DisposeOnClose extends ComponentAdapter implements Serializable { public void componentHidden(ComponentEvent e) { Window w = (Window) e.getComponent(); w.dispose(); } } } class FontTracker implements ActionListener, Serializable { JFontChooser chooser; Font color; public FontTracker(JFontChooser c) { chooser = c; } public void actionPerformed(ActionEvent e) { color = chooser.getFont(); } public Font getFont() { return color; } } /** * A generic implementation of <code>{@link FontSelectionModel}</code>. * * @author Adrian BER */ class DefaultFontSelectionModel implements FontSelectionModel { /** The default selected font. */ private static final Font DEFAULT_INITIAL_FONT = new Font("Dialog", Font.PLAIN, 12); /** The selected font. */ private Font selectedFont; /** The change listeners notified by a change in this model. */ private EventListenerList listeners = new EventListenerList(); /** * Creates a <code>DefaultFontSelectionModel</code> with the * current font set to <code>Dialog, 12</code>. This is * the default constructor. */ public DefaultFontSelectionModel() { this(DEFAULT_INITIAL_FONT); } /** * Creates a <code>DefaultFontSelectionModel</code> with the * current font set to <code>font</code>, which should be * non-<code>null</code>. Note that setting the font to * <code>null</code> is undefined and may have unpredictable * results. * * @param selectedFont the new <code>Font</code> */ public DefaultFontSelectionModel(Font selectedFont) { if (selectedFont == null) { selectedFont = DEFAULT_INITIAL_FONT; } this.selectedFont = selectedFont; } public Font getSelectedFont() { return selectedFont; } public void setSelectedFont(Font selectedFont) { if (selectedFont != null) { this.selectedFont = selectedFont; fireChangeListeners(); } } public void addChangeListener(ChangeListener listener) { listeners.add(ChangeListener.class, listener); } public void removeChangeListener(ChangeListener listener) { listeners.remove(ChangeListener.class, listener); } /** Fires the listeners registered with this model. */ protected void fireChangeListeners() { ChangeEvent ev = new ChangeEvent(this); Object[] l = listeners.getListeners(ChangeListener.class); for (Object listener : l) { ((ChangeListener) listener).stateChanged(ev); } } } /** * A model that supports selecting a <code>Font</code>. * * @author Adrian BER * * @see java.awt.Font */ interface FontSelectionModel { /** * Returns the selected <code>Font</code> which should be * non-<code>null</code>. * * @return the selected <code>Font</code> * @see #setSelectedFont */ Font getSelectedFont(); /** * Sets the selected font to <code>font</code>. * Note that setting the font to <code>null</code> * is undefined and may have unpredictable results. * This method fires a state changed event if it sets the * current font to a new non-<code>null</code> font. * * @param font the new <code>Font</code> * @see #getSelectedFont * @see #addChangeListener */ void setSelectedFont(Font font); /** * Adds <code>listener</code> as a listener to changes in the model. * @param listener the <code>ChangeListener</code> to be added */ void addChangeListener(ChangeListener listener); /** * Removes <code>listener</code> as a listener to changes in the model. * @param listener the <code>ChangeListener</code> to be removed */ void removeChangeListener(ChangeListener listener); } ////////////////////////////// package com.greef.ui.font; import com.greef.ui.UIUtilities; import javax.swing.*; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import javax.swing.event.DocumentListener; import javax.swing.event.DocumentEvent; import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; /** * @author Adrian BER (beradrian@yahoo.com) */ public class JFontChooserDemo extends JPanel { private static final Insets INSETS = new Insets(5, 5, 5, 5); private JFontChooser fontChooser; private JCheckBox defaultPreviewCheckBox; private JTextField previewTextField; private JLabel previewLabel; private JTextArea codeTextArea; public JFontChooserDemo() { init(); } private void init() { setLayout(new GridBagLayout()); defaultPreviewCheckBox = new JCheckBox("Use font name as the preview text"); defaultPreviewCheckBox.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { boolean selected = defaultPreviewCheckBox.isSelected(); fontChooser.setPreviewText(selected ? null : previewTextField.getText()); previewLabel.setEnabled(!selected); previewTextField.setEnabled(!selected); updateCode(); } }); add(defaultPreviewCheckBox, new GridBagConstraints(0, 0, 2, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, INSETS, 0, 0)); previewLabel = new JLabel("Preview text:"); add(previewLabel, new GridBagConstraints(0, 1, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, INSETS, 0, 0)); previewTextField = new JTextField(); previewTextField.getDocument().addDocumentListener(new DocumentListener() { private void changePreviewText() { fontChooser.setPreviewText(previewTextField.getText()); updateCode(); } public void insertUpdate(DocumentEvent e) { changePreviewText(); } public void removeUpdate(DocumentEvent e) { changePreviewText(); } public void changedUpdate(DocumentEvent e) { changePreviewText(); } }); add(previewTextField, new GridBagConstraints(1, 1, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, INSETS, 0, 0)); JButton testButton = new JButton("Test"); testButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Font font = fontChooser.showDialog(JFontChooserDemo.this, "Choose a font"); JOptionPane.showMessageDialog(JFontChooserDemo.this, font == null ? "You canceled the dialog." : "You have selected " + font.getName() + ", " + font.getSize() + (font.isBold() ? ", Bold" : "") + (font.isItalic() ? ", Italic" : "")); } }); add(testButton, new GridBagConstraints(0, 2, 2, 1, 1, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, INSETS, 0, 0)); codeTextArea = new JTextArea(5, 30); codeTextArea.setOpaque(false); codeTextArea.setEditable(false); codeTextArea.setBorder(BorderFactory.createTitledBorder("Code")); add(codeTextArea, new GridBagConstraints(0, 3, 2, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, INSETS, 0, 0)); setFontChooser(new JFontChooser()); } private void setFontChooser(JFontChooser fontChooser) { this.fontChooser = fontChooser; String previewText = fontChooser.getPreviewText(); defaultPreviewCheckBox.setSelected(previewText == null); previewTextField.setText(previewText); updateCode(); } private void updateCode() { codeTextArea.setText("JFontChooser fontChooser = new JFontChooser();\n" + (defaultPreviewCheckBox.isSelected() ? "" : "fontChooser.setPreviewText(\"" + previewTextField.getText() + "\");\n") + "Font font = fontChooser.showDialog(invokerComponent, \"Choose a font\");\n" + "System.out.println(font == null ? \"You have canceled the dialog.\" : \"You have selected \" + font);"); } public void updateUI() { super.updateUI(); if (fontChooser != null) SwingUtilities.updateComponentTreeUI(fontChooser); } }