/*
* @(#)JFontChooser.java
*
* Copyright (c) 2008 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.gui;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.ArrayList;
import java.util.concurrent.*;
import javax.swing.*;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.*;
import org.jhotdraw.gui.fontchooser.*;
import org.jhotdraw.gui.plaf.*;
import org.jhotdraw.gui.plaf.palette.PaletteFontChooserUI;
/**
* Font chooser dialog.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class JFontChooser extends JComponent {
private static final long serialVersionUID = 1L;
/**
* @see #getUIClassID
* @see #readObject
*/
private static final String uiClassID = "FontChooserUI";
/**
* Identifies the "selectedFont" property.
*/
public static final String SELECTED_FONT_PROPERTY = "selectedFont";
/**
* Identifies the "selectionPath" property.
*/
public static final String SELECTION_PATH_PROPERTY = "selectionPath";
/** Instruction to cancel the current selection. */
public static final String CANCEL_SELECTION = "CancelSelection";
/**
* Instruction to approve the current selection
* (same as pressing yes or ok).
*/
public static final String APPROVE_SELECTION = "ApproveSelection";
/**
* Identifies the "model" property.
*/
public static final String MODEL_PROPERTY = "model";
/**
* Holds the selected path of the JFontChooser.
*/
@Nullable private TreePath selectionPath;
/**
* Holds the selected font of the JFontChooser.
*/
@Nullable private Font selectedFont;
/**
* Holds the model of the JFontChooser.
*/
private FontChooserModel model;
// ********************************
// ***** Dialog Return Values *****
// ********************************
/**
* Return value if cancel is chosen.
*/
public static final int CANCEL_OPTION = 1;
/**
* Return value if approve (yes, ok) is chosen.
*/
public static final int APPROVE_OPTION = 0;
/**
* Return value if an error occured.
*/
public static final int ERROR_OPTION = -1;
private int returnValue = ERROR_OPTION;
// DIALOG
@Nullable private JDialog dialog = null;
/**
* This future is used to load fonts lazily
*/
private static FutureTask<Font[]> future;
private TreeModelListener modelHandler = new TreeModelListener() {
@Override
public void treeNodesChanged(TreeModelEvent e) {
updateSelectionPath(getSelectedFont());
}
@Override
public void treeNodesInserted(TreeModelEvent e) {
updateSelectionPath(getSelectedFont());
}
@Override
public void treeNodesRemoved(TreeModelEvent e) {
updateSelectionPath(getSelectedFont());
}
@Override
public void treeStructureChanged(TreeModelEvent e) {
updateSelectionPath(getSelectedFont());
}
};
/** Creates new form JFontChooser */
public JFontChooser() {
loadAllFonts();
model = new DefaultFontChooserModel.UIResource();
model.addTreeModelListener(modelHandler);
updateUI();
addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName() == "ancestor" && evt.getNewValue() != null) {
try {
((DefaultFontChooserModel) model).setFonts(getAllFonts());
} catch (Exception ex) {
ex.printStackTrace();
}
JFontChooser.this.removePropertyChangeListener(this);
}
}
});
}
/**
* Resets the UI property with a value from the current look and feel.
*
* @see JComponent#updateUI
*/
@Override
public void updateUI() {
// Try to get a browser UI from the UIManager.
// Fall back to BasicBrowserUI, if none is available.
if (UIManager.get(getUIClassID()) != null) {
setUI((FontChooserUI) UIManager.getUI(this));
} else {
setUI(PaletteFontChooserUI.createUI(this));
}
}
/**
* Returns the look and feel (L&F) object that renders this component.
*
* @return the PanelUI object that renders this component
* @since 1.4
*/
public FontChooserUI getUI() {
return (FontChooserUI) ui;
}
/**
* Sets the look and feel (L&F) object that renders this component.
*
* @param ui the PanelUI L&F object
* @see UIDefaults#getUI
*/
public void setUI(FontChooserUI ui) {
super.setUI(ui);
}
/**
* Returns a string that specifies the name of the L&F class
* that renders this component.
*
* @return "FontChooserUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
@Override
public String getUIClassID() {
return uiClassID;
}
/**
* Called by the UI when the user hits the Approve button
* (labeled "Open" or "Save", by default). This can also be
* called by the programmer.
* This method causes an action event to fire
* with the command string equal to
* <code>APPROVE_SELECTION</code>.
*
* @see #APPROVE_SELECTION
*/
public void approveSelection() {
returnValue = APPROVE_OPTION;
if (dialog != null) {
dialog.setVisible(false);
}
fireActionPerformed(APPROVE_SELECTION);
}
/**
* Called by the UI when the user chooses the Cancel button.
* This can also be called by the programmer.
* This method causes an action event to fire
* with the command string equal to
* <code>CANCEL_SELECTION</code>.
*
* @see #CANCEL_SELECTION
*/
public void cancelSelection() {
returnValue = CANCEL_OPTION;
if (dialog != null) {
dialog.setVisible(false);
}
fireActionPerformed(CANCEL_SELECTION);
}
/**
* Adds an <code>ActionListener</code> to the font chooser.
*
* @param l the listener to be added
*
* @see #approveSelection
* @see #cancelSelection
*/
public void addActionListener(ActionListener l) {
listenerList.add(ActionListener.class, l);
}
/**
* Removes an <code>ActionListener</code> from the font chooser.
*
* @param l the listener to be removed
*
* @see #addActionListener
*/
public void removeActionListener(ActionListener l) {
listenerList.remove(ActionListener.class, l);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the <code>command</code> parameter.
*/
protected void fireActionPerformed(String command) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
long mostRecentEventTime = EventQueue.getMostRecentEventTime();
int modifiers = 0;
AWTEvent currentEvent = EventQueue.getCurrentEvent();
if (currentEvent instanceof InputEvent) {
modifiers = ((InputEvent) currentEvent).getModifiers();
} else if (currentEvent instanceof ActionEvent) {
modifiers = ((ActionEvent) currentEvent).getModifiers();
}
ActionEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ActionListener.class) {
// Lazily create the event:
if (e == null) {
e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
command, mostRecentEventTime,
modifiers);
}
((ActionListener) listeners[i + 1]).actionPerformed(e);
}
}
}
/**
* Gets the selected Font.
* This is a bound property.
*
* @return The selected font, or null, if no font is selected.
*/
@Nullable public TreePath getSelectionPath() {
return selectionPath;
}
/**
* Sets the selected Font.
* This is a bound property.
* <p>
* Changing the selection path, causes a change of the
* selected font, if the selected font is not the last
* path segment of the selection path.
*
* @param newValue The new selected font, or null if no font is to be
* selected..
*/
public void setSelectionPath(@Nullable TreePath newValue) {
TreePath oldValue = selectionPath;
this.selectionPath = newValue;
firePropertyChange(SELECTION_PATH_PROPERTY, oldValue, newValue);
if (selectionPath != null && selectionPath.getPathCount() == 4) {
setSelectedFont(((FontFaceNode) selectionPath.getLastPathComponent()).getFont());
}
}
/**
* Starts loading all fonts from the local graphics environment
* using a worker thread.
*/
public synchronized static void loadAllFonts() {
if (future == null) {
future = new FutureTask<Font[]>(new Callable<Font[]>() {
@Override
public Font[] call() throws Exception {
Font[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
// get rid of bogus fonts
ArrayList<Font> goodFonts = new ArrayList<Font>(fonts.length);
for (Font f : fonts) {
//System.out.println("JFontChooser "+f.getFontName());
Font decoded = Font.decode(f.getFontName());
if (decoded.getFontName().equals(f.getFontName()) || decoded.getFontName().endsWith("-Derived")) {
goodFonts.add(f);
} else {
//System.out.println("JFontChooser ***bogus*** "+decoded.getFontName());
}
}
return goodFonts.toArray(new Font[goodFonts.size()]);
// return fonts;
}
});
new Thread(future).start();
}
}
/**
* Gets all fonts from the graphics environment. This may take a long
* time. It is recommended to call loadAllFonts during the startup
* of an application. If you do this, you can retrieve the fonts from
* this method from the AWT Event Dispatcher Thread.
*
* @return All fonts.
*/
public static synchronized Font[] getAllFonts() {
loadAllFonts();
try {
return future.get().clone();
} catch (InterruptedException ex) {
return new Font[0];
} catch (ExecutionException ex) {
return new Font[0];
}
}
/**
* Gets the selected Font.
* This is a bound property.
*
* @return The selected font, or null, if no font is selected.
*/
@Nullable public Font getSelectedFont() {
return selectedFont;
}
/**
* Sets the selected Font.
* <p>
* Changing the selected font, causes a change of the
* selection path, if the selected font is not the last
* path segment of the selection path.
*
* This is a bound property.
*
* @param newValue The new selected font, or null if no font is to be
* selected.
*/
public void setSelectedFont(@Nullable Font newValue) {
Font oldValue = selectedFont;
this.selectedFont = newValue;
firePropertyChange(SELECTED_FONT_PROPERTY, oldValue, newValue);
updateSelectionPath(newValue);
}
/**
* Updates the selection path to the selected font.
* <p>
* This method is invoked, when a font is selected, and when then
* structure of the model has changed.
*
* @param newValue
*/
protected void updateSelectionPath(@Nullable Font newValue) {
if (newValue == null || selectionPath == null || selectionPath.getPathCount() != 4 ||
!((FontFaceNode) selectionPath.getLastPathComponent()).getFont().getFontName().equals(newValue.getFontName())) {
if (newValue == null) {
setSelectionPath(null);
} else {
TreePath path = selectionPath;
FontCollectionNode oldCollection = (path != null && path.getPathCount() > 1) ? (FontCollectionNode) path.getPathComponent(1) : null;
FontFamilyNode oldFamily = (path != null && path.getPathCount() > 2) ? (FontFamilyNode) path.getPathComponent(2) : null;
FontFaceNode oldFace = (path != null && path.getPathCount() > 3) ? (FontFaceNode) path.getPathComponent(3) : null;
FontCollectionNode newCollection = oldCollection;
FontFamilyNode newFamily = oldFamily;
FontFaceNode newFace = null;
// search in the current family
if (newFace == null && newFamily != null) {
for (FontFaceNode face : newFamily.faces()) {
if (face.getFont().getFontName().equals(newValue.getFontName())) {
newFace = face;
break;
}
}
}
// search in the current collection
if (newFace == null && newCollection != null) {
for (FontFamilyNode family : newCollection.families()) {
for (FontFaceNode face : family.faces()) {
if (face.getFont().getFontName().equals(newValue.getFontName())) {
newFamily = family;
newFace = face;
break;
}
}
}
}
// search in all collections
if (newFace == null) {
TreeNode root = (TreeNode) getModel().getRoot();
OuterLoop:
for (int i = 0, n = root.getChildCount(); i < n; i++) {
FontCollectionNode collection = (FontCollectionNode) root.getChildAt(i);
for (FontFamilyNode family : collection.families()) {
for (FontFaceNode face : family.faces()) {
if (face.getFont().getFontName().equals(newValue.getFontName())) {
newCollection = collection;
newFamily = family;
newFace = face;
break OuterLoop;
}
}
}
}
}
if (newFace != null) {
setSelectionPath(new TreePath(new Object[]{
getModel().getRoot(), newCollection, newFamily, newFace
}));
} else {
setSelectionPath(null);
}
}
}
}
/**
* Gets the selected Font.
* This is a bound property.
*
* @return The selected font, or null, if no font is selected.
*/
public FontChooserModel getModel() {
return model;
}
/**
* Sets the selected Font.
* This is a bound property.
*
* @param newValue The new selected font, or null if no font is to be
* selected..
*/
public void setModel(FontChooserModel newValue) {
FontChooserModel oldValue = model;
if (oldValue != null) {
oldValue.removeTreeModelListener(modelHandler);
}
this.model = newValue;
if (newValue != null) {
newValue.addTreeModelListener(modelHandler);
}
firePropertyChange(MODEL_PROPERTY, oldValue, newValue);
updateSelectionPath(selectedFont);
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
* /
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
}// </editor-fold>//GEN-END:initComponents
*/
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}