/*
* Copyright (c) 2008, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.swingui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Arrays;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.log4j.Logger;
import ca.sqlpower.dao.session.SPFontLoader;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
/**
* This class is a {@link DataEntryPanel} that asks the user to pick a font.
*/
public class FontSelector implements DataEntryPanel {
private static final Logger logger = Logger.getLogger(FontSelector.class);
private static enum FontStyle {
PLAIN("Plain", Font.PLAIN),
BOLD("Bold", Font.BOLD),
ITALIC("Italic", Font.ITALIC),
BOLD_ITALIC("Bold Italic", Font.BOLD | Font.ITALIC);
private final String humanName;
private final int styleCode;
FontStyle(String name, int styleCode) {
humanName = name;
this.styleCode = styleCode;
}
/**
* Returns a font derived from the given font which has the style
* specified by this enum constant.
*
* @param f
* The original font
* @return A new font with the same size and family as f, but with this
* enum constant's style.
*/
public Font apply(Font f) {
return f.deriveFont(styleCode);
}
@Override
public String toString() {
return humanName;
}
public static FontStyle forCode(int styleCode) {
for (FontStyle sty : values()) {
if (styleCode == sty.styleCode) {
return sty;
}
}
throw new IllegalArgumentException("Unknown font style code: " + styleCode);
}
public int getStyleCode() {
return styleCode;
}
}
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* The font the user has chosen. This is updated whenever any of the
* font choosing components fires a change event.
*/
private Font selectedFont;
/**
* A list populated with all font names available on the local system.
*/
private final JList fontNameList;
/**
* A list populated with a number of common font sizes the user might want
* to pick. Choosing an item on this list fills in the value in the font
* size text field.
*/
private final JList fontSizeList;
/**
* The field that actually specifies the font size. Can be updated directly
* to any positive integer value, or by clicking a preset value in the
* {@link #fontSizeList}.
*/
private final JSpinner fontSizeSpinner;
/**
* The font style chooser (bold, italic, bold italic, plain).
*/
private final JList styleChoice;
/** The list of font sizes */
private static final Integer[] FONT_SIZES = {
4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 18,
20, 24, 30, 36, 40, 48, 60, 72, 96, 144 };
/**
* The display area.
*/
private final JTextArea previewArea = new JTextArea("The Quick Wabit Architected the Loaded Matchmaker");
/**
* The panel we return as this data entry panel's GUI.
*/
private final JPanel panel;
/**
* The original font we started with. If this data entry panel is canceled,
* the user's current choice will revert to this value.
*/
private final Font originalFont;
private final SPFontLoader fontLoader;
private class SelectionHandler implements ChangeListener, ListSelectionListener {
boolean updatingListSelection = false;
public void stateChanged(ChangeEvent e) {
if (updatingListSelection) return;
try {
updatingListSelection = true;
Integer newSize = (Integer) fontSizeSpinner.getValue();
int newSizeIndexInList = Arrays.binarySearch(FONT_SIZES, newSize);
if (newSizeIndexInList >= 0) {
fontSizeList.setSelectedIndex(newSizeIndexInList);
fontSizeList.ensureIndexIsVisible(newSizeIndexInList);
} else {
fontSizeList.clearSelection();
}
} finally {
updatingListSelection = false;
}
previewFont();
}
public void valueChanged(ListSelectionEvent e) {
previewFont();
}
}
/**
* This constructor will create the Font Selector given a parent frame and a
* previous font.
*
* @param font The font to start with in the preview dialog. If null, the system
* default font will be used.
*/
public FontSelector(Font font) {
this(
font == null
? Font.decode(null)
: font,
GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(),
null);
}
public FontSelector(Font font, String[] fontList, SPFontLoader fontLoader) {
this.fontLoader = fontLoader;
if (font == null) {
if (fontList == null
|| fontList.length == 0)
{
throw new IllegalArgumentException("The fontList parameter requires at least one valid font.");
}
font = Font.decode(fontList[0]);
if (font == null) {
throw new IllegalArgumentException("The fontList[0] element cannot be loaded.");
}
}
logger.debug("Creating new font selector with given font: " + font);
this.originalFont = font;
SelectionHandler selectionHandler = new SelectionHandler();
fontNameList = new JList(fontList);
fontNameList.addListSelectionListener(selectionHandler);
fontSizeSpinner = new JSpinner(new SpinnerNumberModel(font.getSize(), 1, 200, 1));
fontSizeSpinner.addChangeListener(selectionHandler);
fontSizeList = new JList(FONT_SIZES);
fontSizeList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (fontSizeList.getSelectedValue() != null) {
fontSizeSpinner.setValue((Integer) fontSizeList.getSelectedValue());
}
}
});
styleChoice = new JList(FontStyle.values());
styleChoice.setSelectedValue(FontStyle.forCode(font.getStyle()), true);
styleChoice.addListSelectionListener(selectionHandler);
FormLayout layout = new FormLayout(
"pref:grow, 4dlu, pref, 4dlu, pref",
"pref, 4dlu, pref, 4dlu, fill:pref:grow");
layout.setHonorsVisibility(true);
DefaultFormBuilder builder = new DefaultFormBuilder(layout);
CellConstraints cc = new CellConstraints();
builder.add(new JScrollPane(fontNameList), cc.xywh(1, 1, 1, 3));
builder.add(fontSizeSpinner, cc.xywh(3, 1, 1, 1));
builder.add(new JScrollPane(fontSizeList), cc.xywh(3, 3, 1, 1));
builder.add(new JScrollPane(styleChoice), cc.xywh(5, 1, 1, 3));
previewArea.setBackground(Color.WHITE);
previewArea.setPreferredSize(new Dimension(300, 100));
builder.add(previewArea, cc.xywh(1, 5, 5, 1));
// Set defaults after creating layout so the "scroll to visible" works
fontSizeList.setSelectedValue(Integer.valueOf(font.getSize()), true);
fontNameList.setSelectedValue(font.getFamily(), true);
logger.debug("Set family list to \"" + font.getFamily() +
"\" and size to " + Integer.valueOf(font.getSize()));
panel = builder.getPanel();
previewFont(); // ensure view is up to date!
}
/**
* Updates the {@link #selectedFont} and the font in the preview area. This
* method gets called from the change handlers installed on all the chooser
* components.
*/
private void previewFont() {
String name = (String) fontNameList.getSelectedValue();
FontStyle style = (FontStyle) styleChoice.getSelectedValue();
int size = ((Integer) fontSizeSpinner.getValue()).intValue();
if (fontLoader == null) {
setSelectedFont(new Font(name, style.getStyleCode(), size));
} else {
Font font = fontLoader.loadFontFromName(name);
font = font.deriveFont(style.getStyleCode());
font = font.deriveFont(Float.valueOf(String.valueOf(size)));
setSelectedFont(font);
}
}
/**
* Returns the font the user has constructed through the choices in this
* font selector.
*/
public Font getSelectedFont() {
return selectedFont;
}
public void setSelectedFont(Font selectedFont) {
Font oldFont = this.selectedFont;
this.selectedFont = selectedFont;
pcs.firePropertyChange("selectedFont", oldFont, selectedFont);
previewArea.setFont(selectedFont);
}
public JPanel getPanel() {
return panel;
}
public boolean applyChanges() {
return true;
}
/**
* Reverts to the original font.
*/
public void discardChanges() {
setSelectedFont(originalFont);
}
public boolean hasUnsavedChanges() {
return false;
}
/**
* Sets the text that will appear in the preview area. If you do not
* call this method, the font selector will show its catchy default
* phrase.
*/
public void setPreviewText(String text) {
previewArea.setText(text);
}
/**
* Returns the current preview text. Keep in mind the text is in an
* editable text area, so the user could have modified the text by the
* time you call this method, and the entered text could be multi-line.
*/
public String getPreviewText() {
return previewArea.getText();
}
public void setShowingPreview(boolean show) {
previewArea.setVisible(show);
}
public boolean isShowingPreview() {
return previewArea.isVisible();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final FontSelector fs = new FontSelector(Font.decode("Courier bold 20"));
fs.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("New font selection: " + fs.getSelectedFont());
}
});
JFrame dummyFrame = new JFrame();
JDialog d = DataEntryPanelBuilder.createDataEntryPanelDialog(
fs, dummyFrame, "Font Selector Demo!", "Yeehaw");
d.setModal(true);
d.setVisible(true);
System.out.println("Selected font: " + fs.getSelectedFont());
d.dispose();
dummyFrame.dispose();
}
});
}
/**
* Adds a property change listener that will be notified every time
* the selected font has changed. The property name is "selectedFont".
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public PropertyChangeListener[] getPropertyChangeListeners() {
return pcs.getPropertyChangeListeners();
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
}