Swing component: Combobox for font selection

There are many great Swing components around, but we could not find a nice combobox for font selection, that includes not only the font names but also some preview characters and maybe a history of the recently selected fonts. You know such a combobox from OpenOffice.org and other word processors.

Well, here it is now. Our FontChooserComboBox offers font preview, customizable preview strings, optionally a list of the recent fonts and even auto completion for the font name. And best of all, the code is public domain, so you can use it for whatever you want. However, if you improve it, please share your work with us!

Here is a demo video:

[youtube=http://www.youtube.com/watch?v=8pOVB0jRXuA]

A webstart demo:

Here are the files:

Comments are welcome 🙂

6 thoughts on “Swing component: Combobox for font selection

  1. Sebastián

    I had to comment out this line:

    // @Override public Dimension getPreferredSize()
    // {
    // //default height: like a normal combo box
    // return new Dimension(super.getPreferredSize().width, new JComboBox().getPreferredSize().height);
    // }

    Why would you do that? I needed a ComboBox smaller in a JPanel. I create a Dimension according the width of the strings, and a custom panel height.

    Cheers,

    Reply
  2. Andi

    Hi Sebastián,

    the source code tells you: “This file is public domain. However, if you improve it, please share your work with … Thanks!” 🙂

    I don’t remember the reason for getPreferredSize(), but it really looks strange! 😉 Have fun with the component.

    Bye,

    Andi

    Reply
  3. sterling

    I am working on a font editor with font, style, size, color and took your code. It works well with what I am doing so I copied and made some edits to allow selecting the style (bold, italic) but it is giving me a strange bug where I cant seem to select a style when the editor is enabled. When I disable the editor, though, the selection works but the text at the top of the box become off center vertically. Ant Ideas?

    [code]package thirdparty;

    import static java.lang.Math.max;
    import static java.lang.Math.min;

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.FlowLayout;
    import java.awt.Font;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;

    import javax.swing.JComboBox;
    import javax.swing.JLabel;
    import javax.swing.JList;
    import javax.swing.JPanel;
    import javax.swing.JSeparator;
    import javax.swing.JTextField;
    import javax.swing.ListCellRenderer;
    import javax.swing.plaf.basic.BasicComboBoxEditor;
    import javax.swing.text.AttributeSet;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.Caret;
    import javax.swing.text.PlainDocument;

    /**
    * Combobox which lists all installed fonts, sorted alphabetically. In the
    * dropdown, each font name is shown in the default font together with some
    * characters in its own font, which can be customized calling the
    * <code>setPreviewString</code> method.
    *
    * In the main text field, the default font is used to display the font name. It
    * is editable and supports auto completion.
    *
    * The last <code>n</code> selected fonts can be shown on the top by calling
    * <code>setRecentFontsCount(n)</code>.
    *
    * This file is public domain. However, if you improve it, please share your
    * work with andi@xenoage.com. Thanks!
    *
    * @author Andreas Wenger
    */
    public class FontStyleChooserComboBox extends JComboBox {

    private int previewFontSize;
    private String previewString = "AaBbCc";
    private int recentFontsCount = 5;

    private List<String> fontNames;
    private HashMap<String, Item> itemsCache = new HashMap<String, Item>();

    /**
    * Creates a new {@link FontStyleChooserComboBox}.
    */
    public FontStyleChooserComboBox() {
    // load available font names
    String[] fontNames = new String[] { "Plain", "Bold", "Italic",
    "Bold Italic" };
    Arrays.sort(fontNames);
    this.fontNames = Arrays.asList(fontNames);

    // fill combo box
    JLabel label = new JLabel();
    this.previewFontSize = label.getFont().getSize();
    updateList(null);

    // set editor and item components
    this.setEditable(true);
    this.setEditor(new FontChooserComboBoxEditor());
    this.setRenderer(new FontChooserComboBoxRenderer());

    }

    /**
    * Gets the font size of the preview characters.
    */
    public int getPreviewFontSize() {
    return previewFontSize;
    }

    /**
    * Sets the font size of the preview characters.
    */
    public void setPreviewFontSize(int previewFontSize) {
    this.previewFontSize = previewFontSize;
    updateList(getSelectedFontName());
    }

    /**
    * Gets the preview characters, or null.
    */
    public String getPreviewString() {
    return previewString;
    }

    /**
    * Sets the preview characters, or the empty string or null to display no
    * preview but only the font names.
    */
    public void setPreviewString(String previewString) {
    this.previewString = (previewString != null
    && previewString.length() > 0 ? previewString : null);
    updateList(getSelectedFontName());
    }

    /**
    * Gets the number of recently selected fonts, or 0.
    */
    public int getRecentFontsCount() {
    return recentFontsCount;
    }

    private void updateList(String selectedFontName) {
    // list items
    removeAllItems();
    itemsCache.clear();

    // regular items
    for (String fontName : fontNames) {
    Item item =
    new Item(fontName);
    addItem(item);
    itemsCache.put(fontName, item);
    }
    // reselect item
    if (selectedFontName != null)
    setSelectedItem(selectedFontName);
    }

    /**
    * Gets the selected font name, or null.
    */
    public String getSelectedFontName() {
    if (this.getSelectedItem() != null)
    return ((Item) this.getSelectedItem()).font.getFontName();
    else
    return null;
    }

    @Override
    public Dimension getPreferredSize() {
    // default height: like a normal combo box
    return new Dimension(super.getPreferredSize().width,
    new JComboBox().getPreferredSize().height);
    }

    /**
    * Sets the selected font by the given name. If it does not exist, nothing
    * happens.
    */
    public void setSelectedItem(String fontName) {

    Item item = itemsCache.get(fontName); // then in regular items
    if (item != null)
    setSelectedItem(item);
    }

    /**
    * The editor component of the list. This is an editable text area which
    * supports auto completion.
    *
    * @author Andreas Wenger
    */
    class FontChooserComboBoxEditor extends BasicComboBoxEditor {

    /**
    * Plain text document for the text area. Needed for text selection.
    *
    * Inspired by http://www.java2s.com/Code/Java/Swing-Components/
    * AutocompleteComboBox.htm
    *
    * @author Andreas Wenger
    */
    class AutoCompletionDocument extends PlainDocument {

    private JTextField textField = FontChooserComboBoxEditor.this.editor;

    @Override
    public void replace(int i, int j, String s,
    AttributeSet attributeset) throws BadLocationException {
    super.remove(i, j);
    insertString(i, s, attributeset);
    }

    @Override
    public void insertString(int i, String s, AttributeSet attributeset)
    throws BadLocationException {
    if (s != null && !"".equals(s)) {
    String s1 = getText(0, i);
    String s2 = getMatch(s1 + s);
    int j = (i + s.length()) – 1;
    if (s2 == null) {
    s2 = getMatch(s1);
    j–;
    }
    if (s2 != null)
    FontStyleChooserComboBox.this.setSelectedItem(s2);
    super.remove(0, getLength());
    super.insertString(0, s2, attributeset);
    textField.setSelectionStart(j + 1);
    textField.setSelectionEnd(getLength());
    }
    }

    @Override
    public void remove(int i, int j) throws BadLocationException {
    int k = textField.getSelectionStart();
    if (k > 0)
    k–;
    String s = getMatch(getText(0, k));

    super.remove(0, getLength());
    super.insertString(0, s, null);

    if (s != null)
    FontStyleChooserComboBox.this.setSelectedItem(s);
    try {
    textField.setSelectionStart(k);
    textField.setSelectionEnd(getLength());
    } catch (Exception exception) {
    }
    }

    }

    private FontChooserComboBoxEditor() {
    editor.setDocument(new AutoCompletionDocument());
    if (fontNames.size() > 0)
    editor.setText(fontNames.get(0).toString());
    }

    private String getMatch(String input) {
    for (String fontName : fontNames) {
    if (fontName.toLowerCase().startsWith(input.toLowerCase()))
    return fontName;
    }
    return null;
    }

    public void replaceSelection(String s) {
    AutoCompletionDocument doc = (AutoCompletionDocument) editor
    .getDocument();
    try {
    Caret caret = editor.getCaret();
    int i = min(caret.getDot(), caret.getMark());
    int j = max(caret.getDot(), caret.getMark());
    doc.replace(i, j – i, s, null);
    } catch (BadLocationException ex) {
    }
    }
    }

    /**
    * The renderer for a list item.
    *
    * @author Andreas Wenger
    */
    class FontChooserComboBoxRenderer implements ListCellRenderer {

    public Component getListCellRendererComponent(JList list, Object value,
    int index, boolean isSelected, boolean cellHasFocus) {
    // extract the component from the item’s value
    Item item = (Item) value;
    boolean s = (isSelected && !item.isSeparator);
    item.setBackground(s ? list.getSelectionBackground() : list
    .getBackground());
    item.setForeground(s ? list.
    getSelectionForeground() : list
    .getForeground());
    return item;
    }
    }

    public Font changeStyle(String fontName){
    Font font = null;
    if (fontName.toLowerCase().equals("plain")) {
    font = new JLabel().getFont().deriveFont(Font.PLAIN);
    } else if (fontName.toLowerCase().equals("bold")) {
    font = new JLabel().getFont().deriveFont(Font.BOLD);
    } else if (fontName.toLowerCase().equals("italic")) {
    font = new JLabel().getFont().deriveFont(Font.ITALIC);
    } else if (fontName.toLowerCase().equals("bold italic")) {
    font = new JLabel().getFont().deriveFont(
    Font.BOLD | Font.ITALIC);
    }
    return font;
    }

    /**
    * The component for a list item.
    *
    * @author Andreas Wenger
    */
    class Item extends JPanel {

    private Font font;
    private final boolean isSeparator;

    private Item(String fontName) {
    if (fontName != null) {
    font = changeStyle(fontName);
    this.isSeparator = false;
    } else {
    this.font = null;
    this.isSeparator = true;
    }

    this.setOpaque(true);

    if (!isSeparator) {
    this.setLayout(new FlowLayout(FlowLayout.LEFT));

    JLabel labelFont = new JLabel(fontName);
    labelFont.setFont(font);
    this.add(labelFont);
    } else {
    // separator
    this.setLayout(new BorderLayout());
    this.add(new JSeparator(JSeparator.HORIZONTAL),
    BorderLayout.CENTER);
    }
    }

    @Override
    public String toString() {
    if (font != null)
    return font.getFamily();
    else
    return "";
    }
    }
    }

    [/code]

    Reply
    1. Sterling

      fixed the centering problem. Replacing

      this.setLayout(new FlowLayout(FlowLayout.LEFT));

      in the Item class with

      FlowLayout f = new FlowLayout(FlowLayout.LEFT);
      f.setVgap(0);
      this.setLayout(f);

      solves that one.

      Reply

Leave a Reply to Andi Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.