package edu.princeton.toy.choosers; import java.awt.*; import java.awt.event.*; import java.awt.font.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import edu.princeton.swing.*; /** * TFontChooserPane manages a set of fonts for various areas and allows the user to change those * fonts for those areas. * * @author btsang * @version 7.1 */ public class TFontChooserPane extends JPanel implements ActionListener, ListSelectionListener { private static final String CLASS_STRING = TFontChooserPane.class.toString(); /** * The command that causes the TFontChooserPane to update its components with the internal * data. */ public static final String UPDATE_COMMAND = CLASS_STRING + "#updateCommand"; /** * A list of all font families. This will later be rearranged so that "Default" is * at the top. */ public static final String FONT_FAMILIES[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); /** * Strings corresponding to the styles available. */ public static final String FONT_STYLES[] = { "Plain", "Bold", "Italic", "Bold Italic" }; /** * Font style masks corresponding to the FONT_STYLES array. */ public static final int FONT_STYLE_MASKS[] = { Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD | Font.ITALIC }; /** * A list of all font sizes. This is assumed to be in ascending order. */ public static final int FONT_SIZES[] = { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 }; private JComboBox areaComboBox; private PList fontList; private PList styleList; private PList sizeList; private JLabel previewLabel; private int areaCount; private String fontAreas[]; private int familyIndices[]; private int styleIndices[]; private int sizeIndices[]; private Font fonts[]; private boolean ignoreUpdate; /** * Move the default font to the of the FONT_FAMILIES array. */ static { int index = -1; for (int ctr = 0; ctr < FONT_FAMILIES.length && index == -1; ctr++) { if ("Default".equals(FONT_FAMILIES[ctr])) index = ctr; } if (index != -1) { for (int ctr = index; ctr > 0; ctr--) FONT_FAMILIES[ctr] = FONT_FAMILIES[ctr - 1]; FONT_FAMILIES[0] = "Default"; } } /** * Creates a new TFontChooserPane. */ public TFontChooserPane(String fontAreas[]) { super(new GridBagLayout()); int areaCount = fontAreas.length; this.fontAreas = new String[areaCount]; for (int ctr = 0; ctr < areaCount; ctr++) { if (fontAreas[ctr] == null) throw new NullPointerException(); this.fontAreas[ctr] = fontAreas[ctr]; } this.areaCount = areaCount; familyIndices = new int[areaCount]; styleIndices = new int[areaCount]; sizeIndices = new int[areaCount]; fonts = new Font[areaCount]; ignoreUpdate = false; add( new JLabel("Area"), new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.VERTICAL, new Insets(2, 2, 2, 2), 0, 0 ) ); { // Prepare the area combo box areaComboBox = new JComboBox(this.fontAreas); areaComboBox.addActionListener(this); add( areaComboBox, new GridBagConstraints( 1, 0, 3, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.VERTICAL, new Insets(2, 2, 2, 2), 0, 0 ) ); } add( new JLabel("Font"), new GridBagConstraints( 0, 1, 2, 1, 2.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.VERTICAL, new Insets(2, 2, 2, 2), 0, 0 ) ); add( new JLabel("Style"), new GridBagConstraints( 2, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.VERTICAL, new Insets(2, 2, 2, 2), 0, 0 ) ); add( new JLabel("Size"), new GridBagConstraints( 3, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.VERTICAL, new Insets(2, 2, 2, 2), 0, 0 ) ); { // Prepare the font list fontList = new PList(FONT_FAMILIES); fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); fontList.setSelectedIndex(0); fontList.addListSelectionListener(this); add( new JScrollPane( fontList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ), new GridBagConstraints( 0, 2, 2, 2, 2.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(2, 2, 2, 2), 0, 0 ) ); } { // Prepare the style list styleList = new PList(FONT_STYLES); styleList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); styleList.setSelectedIndex(0); styleList.addListSelectionListener(this); add( new JScrollPane( styleList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ), new GridBagConstraints( 2, 2, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(2, 2, 2, 2), 0, 0 ) ); } { // Prepare the size list Integer objectArray[] = new Integer[FONT_SIZES.length]; for (int ctr = 0; ctr < objectArray.length; ctr++) objectArray[ctr] = new Integer(FONT_SIZES[ctr]); sizeList = new PList(objectArray); sizeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); sizeList.setSelectedIndex(0); sizeList.addListSelectionListener(this); add( new JScrollPane( sizeList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ), new GridBagConstraints( 3, 2, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(2, 2, 2, 2), 0, 0 ) ); } { // Prepare the preview box previewLabel = new JLabel("AaBb YyZz 123", SwingConstants.CENTER); previewLabel.setMinimumSize(new Dimension(140, 100)); previewLabel.setPreferredSize(new Dimension(140, 100)); previewLabel.setBorder(new EtchedBorder()); add( previewLabel, new GridBagConstraints( 2, 3, 2, 1, 2.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(2, 2, 2, 2), 0, 0 ) ); } doCommand(UPDATE_COMMAND, null); } /** * Gets the font for a given area. * * @param areaIndex The index of the area whose font we're getting. An invalid value will * result in an ArrayIndexOutOfBoundsException. * @return The font for that area. */ public Font getFont(int areaIndex) { return fonts[areaIndex]; } /** * Sets the font for a given area. * * @param font The new font for the given area. A null value will result in a * NullPointerException. * @param areaIndex The index of the area whose font we're setting. An invalid value will * result in an ArrayIndexOutOfBoundsException. */ public void setFont(Font font, int areaIndex) { setFont(font.getFamily(), font.isBold(), font.isItalic(), font.getSize(), areaIndex); } /** * Sets the font for a given area. * * @param family The family of the font. If null was passed or the family was not found, then & "Default" will be used. * @param bold Wheter or not the font is bold. * @param italic Wheter or not the font is italic. * @param size The size of the font. The font size will be adjusted to the closest available * size. * @param areaIndex The index of the area whose font we're setting. */ public synchronized void setFont(String family, boolean bold, boolean italic, int size, int areaIndex) { if (areaIndex < 0 || areaIndex >= areaCount) throw new ArrayIndexOutOfBoundsException(); // Get the family's index int index = -1; if (family != null) { for (int ctr = 0; ctr < FONT_FAMILIES.length && index == -1; ctr++) { if (FONT_FAMILIES[ctr].equalsIgnoreCase(family)) index = ctr; } } if (index == -1) familyIndices[areaIndex] = 0; else familyIndices[areaIndex] = index; // Get the style's index styleIndices[areaIndex] = (bold?1:0) | (italic?2:0); // Get the size's index index = 0; while (index < FONT_SIZES.length && FONT_SIZES[index] < size) { index++; } if (index == FONT_SIZES.length) { sizeIndices[areaIndex] = FONT_SIZES.length - 1; } else if (index == 0) { sizeIndices[areaIndex] = 0; } else { if (FONT_SIZES[index] - size <= size - FONT_SIZES[index - 1]) { sizeIndices[areaIndex] = index; } else { sizeIndices[areaIndex] = index - 1; } } if (areaComboBox.getSelectedIndex() == areaIndex) { doCommand(UPDATE_COMMAND, null); fontList.ensureIndexIsVisible(familyIndices[areaIndex]); styleList.ensureIndexIsVisible(styleIndices[areaIndex]); sizeList.ensureIndexIsVisible(sizeIndices[areaIndex]); } else { fonts[areaIndex] = new Font( FONT_FAMILIES[familyIndices[areaIndex]], FONT_STYLE_MASKS[styleIndices[areaIndex]], FONT_SIZES[sizeIndices[areaIndex]] ); } } /** * Performs a command based on the argument. * * @param command A string representing the command. Note that pointer equality (not string * equality) is tested here, so it is important to use the string constants defined in this * class. An IllegalArgumentException will be thrown if the argument is invalid. * @return True iff the command was executed sucessfully. */ public synchronized boolean doCommand(String command, Object extraInfo) { //////////////////////////////////////////////////////////////////////////////////////////// if (command == UPDATE_COMMAND) { ignoreUpdate = true; try { int areaIndex = areaComboBox.getSelectedIndex(); if (areaIndex == -1) { areaIndex = 0; areaComboBox.setSelectedIndex(0); } fontList.setSelectedIndex(familyIndices[areaIndex]); styleList.setSelectedIndex(styleIndices[areaIndex]); sizeList.setSelectedIndex(sizeIndices[areaIndex]); fonts[areaIndex] = new Font( FONT_FAMILIES[familyIndices[areaIndex]], FONT_STYLE_MASKS[styleIndices[areaIndex]], FONT_SIZES[sizeIndices[areaIndex]] ); previewLabel.setFont(fonts[areaIndex]); } finally { ignoreUpdate = false; } return true; } throw new IllegalArgumentException(); } /** * Implement ActionListener to pay attention to changes in the areaComboBox. */ public void actionPerformed(ActionEvent e) { if (ignoreUpdate) return; int areaIndex = areaComboBox.getSelectedIndex(); doCommand(UPDATE_COMMAND, null); if (areaIndex != -1) { fontList.ensureIndexIsVisible(familyIndices[areaIndex]); styleList.ensureIndexIsVisible(styleIndices[areaIndex]); sizeList.ensureIndexIsVisible(sizeIndices[areaIndex]); } } /** * Implement ListSelectionListener to pay attention to changes in the fontList, styleList, and * sizeList. */ public void valueChanged(ListSelectionEvent e) { if (ignoreUpdate) return; int areaIndex = areaComboBox.getSelectedIndex(); if (areaIndex != -1) { int index = fontList.getSelectedIndex(); if (index != -1) familyIndices[areaIndex] = index; index = styleList.getSelectedIndex(); if (index != -1) styleIndices[areaIndex] = index; index = sizeList.getSelectedIndex(); if (index != -1) sizeIndices[areaIndex] = index; } doCommand(UPDATE_COMMAND, null); } }