/**
* @(#)PaletteFontChooserUI.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.plaf.palette;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.tree.*;
import org.jhotdraw.gui.JFontChooser;
import org.jhotdraw.gui.fontchooser.*;
import org.jhotdraw.gui.plaf.FontChooserUI;
/**
* PaletteFontChooserUI.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class PaletteFontChooserUI extends FontChooserUI {
private FontCollectionNode familyListParent;
private FontFamilyNode faceListParent;
private JFontChooser fontChooser;
private PaletteFontChooserSelectionPanel selectionPanel;
private PaletteFontChooserPreviewPanel previewPanel;
@Nullable private SelectionPanelHandler selectionPanelHandler;
@Nullable private FontChooserHandler chooserHandler;
/**
* The value of this counter is greater 0, if the palette
* font chooser is updating, and should ignore incoming events.
*/
private int isUpdating;
public PaletteFontChooserUI(JFontChooser fontChooser) {
this.fontChooser = fontChooser;
}
/**
* Returns an instance of the UI delegate for the specified component.
* Each subclass must provide its own static <code>createUI</code>
* method that returns an instance of that UI delegate subclass.
* If the UI delegate subclass is stateless, it may return an instance
* that is shared by multiple components. If the UI delegate is
* stateful, then it should return a new instance per component.
* The default implementation of this method throws an error, as it
* should never be invoked.
*/
public static ComponentUI createUI(JComponent c) {
PaletteFontChooserUI ui = new PaletteFontChooserUI((JFontChooser) c);
return ui;
}
/**
* Configures the specified component appropriate for the look and feel.
* This method is invoked when the <code>ComponentUI</code> instance is being installed
* as the UI delegate on the specified component. This method should
* completely configure the component for the look and feel,
* including the following:
* <ol>
* <li>Install any default property values for color, fonts, borders,
* icons, opacity, etc. on the component. Whenever possible,
* property values initialized by the client program should <i>not</i>
* be overridden.
* <li>Install a <code>LayoutManager</code> on the component if necessary.
* <li>Create/add any required sub-components to the component.
* <li>Create/install event listeners on the component.
* <li>Create/install a <code>PropertyChangeListener</code> on the component in order
* to detect and respond to component property changes appropriately.
* <li>Install keyboard UI (mnemonics, traversal, etc.) on the component.
* <li>Initialize any appropriate instance data.
* </ol>
* @param c the component where this UI delegate is being installed
*
* @see #uninstallUI
* @see javax.swing.JComponent#setUI
* @see javax.swing.JComponent#updateUI
*/
@Override
public void installUI(JComponent c) {
installComponents(fontChooser);
installListeners(fontChooser);
}
protected void installComponents(JFontChooser fc) {
fc.removeAll();
fc.setLayout(new BorderLayout());
selectionPanel = new PaletteFontChooserSelectionPanel();
fc.add(selectionPanel, BorderLayout.CENTER);
previewPanel = new PaletteFontChooserPreviewPanel();
fc.add(previewPanel, BorderLayout.NORTH);
updateCollectionList();
updateFamilyList();
updateFaceList();
updatePreview();
}
protected void installListeners(JFontChooser fc) {
selectionPanelHandler = new SelectionPanelHandler();
selectionPanel.getCollectionList().addListSelectionListener(selectionPanelHandler);
selectionPanel.getFamilyList().addListSelectionListener(selectionPanelHandler);
selectionPanel.getFaceList().addListSelectionListener(selectionPanelHandler);
selectionPanel.getCollectionList().addKeyListener(selectionPanelHandler);
selectionPanel.getFamilyList().addKeyListener(selectionPanelHandler);
selectionPanel.getFaceList().addKeyListener(selectionPanelHandler);
selectionPanel.getCollectionList().addMouseListener(selectionPanelHandler);
selectionPanel.getFamilyList().addMouseListener(selectionPanelHandler);
selectionPanel.getFaceList().addMouseListener(selectionPanelHandler);
chooserHandler = new FontChooserHandler();
fontChooser.addPropertyChangeListener(chooserHandler);
if (fontChooser.getModel() != null) {
fontChooser.getModel().addTreeModelListener(chooserHandler);
}
}
/**
* Reverses configuration which was done on the specified component during
* <code>installUI</code>. This method is invoked when this
* <code>UIComponent</code> instance is being removed as the UI delegate
* for the specified component. This method should undo the
* configuration performed in <code>installUI</code>, being careful to
* leave the <code>JComponent</code> instance in a clean state (no
* extraneous listeners, look-and-feel-specific property objects, etc.).
* This should include the following:
* <ol>
* <li>Remove any UI-set borders from the component.
* <li>Remove any UI-set layout managers on the component.
* <li>Remove any UI-added sub-components from the component.
* <li>Remove any UI-added event/property listeners from the component.
* <li>Remove any UI-installed keyboard UI from the component.
* <li>Nullify any allocated instance data objects to allow for GC.
* </ol>
* @param c the component from which this UI delegate is being removed;
* this argument is often ignored,
* but might be used if the UI object is stateless
* and shared by multiple components
*
* @see #installUI
* @see javax.swing.JComponent#updateUI
*/
@Override
public void uninstallUI(JComponent c) {
uninstallListeners(fontChooser);
uninstallComponents(fontChooser);
}
protected void uninstallComponents(JFontChooser fc) {
fontChooser.removeAll();
}
protected void uninstallListeners(JFontChooser fc) {
fontChooser.removePropertyChangeListener(chooserHandler);
selectionPanel.getCollectionList().removeListSelectionListener(selectionPanelHandler);
selectionPanel.getFamilyList().removeListSelectionListener(selectionPanelHandler);
selectionPanel.getFaceList().removeListSelectionListener(selectionPanelHandler);
selectionPanel.getCollectionList().removeKeyListener(selectionPanelHandler);
selectionPanel.getFamilyList().removeKeyListener(selectionPanelHandler);
selectionPanel.getFaceList().removeKeyListener(selectionPanelHandler);
selectionPanel.getCollectionList().removeMouseListener(selectionPanelHandler);
selectionPanel.getFamilyList().removeMouseListener(selectionPanelHandler);
selectionPanel.getFaceList().removeMouseListener(selectionPanelHandler);
if (fontChooser.getModel() != null) {
fontChooser.getModel().removeTreeModelListener(chooserHandler);
}
chooserHandler = null;
selectionPanelHandler = null;
}
private void updateCollectionList() {
isUpdating++;
JList list = selectionPanel.getCollectionList();
DefaultListModel lm = (DefaultListModel) list.getModel();
lm.removeAllElements();
FontChooserModel model = fontChooser.getModel();
Object parent = model.getRoot();
for (int i = 0, n = model.getChildCount(parent); i < n; i++) {
lm.addElement(model.getChild(parent, i));
}
TreePath path = fontChooser.getSelectionPath();
if (path == null || path.getPathCount() < 2) {
list.clearSelection();
} else {
list.setSelectedIndex(
((TreeNode) path.getPathComponent(0)).getIndex((TreeNode) path.getPathComponent(1)));
list.scrollRectToVisible(list.getCellBounds(list.getSelectedIndex(), list.getSelectedIndex()));
}
isUpdating--;
}
private void updateFamilyList() {
isUpdating++;
JList list = selectionPanel.getFamilyList();
FontChooserModel model = fontChooser.getModel();
FontCollectionNode newParent = null;
TreePath path = fontChooser.getSelectionPath();
if (path != null && path.getPathCount() > 1) {
newParent = (FontCollectionNode) path.getPathComponent(1);
}
if (newParent != familyListParent) {
DefaultListModel lm = (DefaultListModel) list.getModel();
lm.removeAllElements();
familyListParent = newParent;
if (familyListParent != null) {
for (int i = 0, n = model.getChildCount(familyListParent); i < n; i++) {
lm.addElement(model.getChild(familyListParent, i));
}
}
}
if (path == null || path.getPathCount() < 3) {
list.clearSelection();
} else {
list.setSelectedIndex(
((TreeNode) path.getPathComponent(1)).getIndex((TreeNode) path.getPathComponent(2)));
list.scrollRectToVisible(list.getCellBounds(list.getSelectedIndex(), list.getSelectedIndex()));
}
isUpdating--;
}
private void updateFaceList() {
isUpdating++;
JList list = selectionPanel.getFaceList();
FontChooserModel model = fontChooser.getModel();
FontFamilyNode newParent = null;
TreePath path = fontChooser.getSelectionPath();
if (path != null && path.getPathCount() > 2) {
newParent = (FontFamilyNode) path.getPathComponent(2);
}
if (newParent != faceListParent) {
DefaultListModel lm = (DefaultListModel) list.getModel();
lm.removeAllElements();
faceListParent = newParent;
if (faceListParent != null) {
for (int i = 0, n = model.getChildCount(faceListParent); i < n; i++) {
lm.addElement(model.getChild(faceListParent, i));
}
}
}
if (path == null || path.getPathCount() < 4) {
list.clearSelection();
} else {
list.setSelectedIndex(
((TreeNode) path.getPathComponent(2)).getIndex((TreeNode) path.getPathComponent(3)));
list.scrollRectToVisible(list.getCellBounds(list.getSelectedIndex(), list.getSelectedIndex()));
}
isUpdating--;
}
private void updatePreview() {
isUpdating++;
previewPanel.setSelectedFont(fontChooser.getSelectedFont());
isUpdating--;
}
private void doCollectionChanged() {
JList list = selectionPanel.getCollectionList();
TreePath path = fontChooser.getSelectionPath();
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 = (FontCollectionNode) list.getSelectedValue();
FontFamilyNode newFamily = null;
FontFaceNode newFace = null;
if ((oldFamily == null || oldFace == null) && fontChooser.getSelectedFont() != null) {
oldFace = new FontFaceNode(fontChooser.getSelectedFont());
oldFamily = new FontFamilyNode(fontChooser.getSelectedFont().getFamily());
}
if (newCollection != null && oldFamily != null) {
for (int i = 0, n = newCollection.getChildCount(); i < n; i++) {
FontFamilyNode aFamily = newCollection.getChildAt(i);
if (aFamily.compareTo(oldFamily) == 0) {
newFamily = aFamily;
break;
}
}
}
if (newFamily != null && oldFace != null) {
// search in the new family for the face
for (FontFaceNode aFace : newFamily.faces()) {
if (aFace.compareTo(oldFace) == 0) {
newFace = aFace;
break;
}
}
} else if (newFace == null && oldFamily != null && oldFace != null) {
OuterLoop:
for (FontFamilyNode aFamily : newCollection.families()) {
for (FontFaceNode aFace : aFamily.faces()) {
if (aFace.compareTo(oldFace) == 0) {
newFace = aFace;
newFamily = (FontFamilyNode) aFace.getParent();
break OuterLoop;
}
}
}
}
if (newCollection != null) {
if (newFamily == null && newCollection.getChildCount() > 0) {
newFamily = newCollection.getChildAt(0);
}
if (newFamily != null) {
if (newFace == null && newFamily.getChildCount() > 0) {
newFace = newFamily.getChildAt(0);
}
}
}
setNewSelectionPath(newCollection, newFamily, newFace);
}
private void doFamilyChanged() {
JList list = selectionPanel.getFamilyList();
TreePath path = fontChooser.getSelectionPath();
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 = (FontFamilyNode) list.getSelectedValue();
FontFaceNode newFace = null;
if (newFamily != null && oldFace != null) {
for (int i = 0, n = newFamily.getChildCount(); i < n; i++) {
FontFaceNode aFace = newFamily.getChildAt(i);
if (aFace.compareTo(oldFace) == 0) {
newFace = aFace;
break;
}
}
}
if (newCollection != null) {
if (newFamily == null && newCollection.getChildCount() > 0) {
newFamily = newCollection.getChildAt(0);
}
if (newFamily != null) {
if (newFace == null && newFamily.getChildCount() > 0) {
newFace = newFamily.getChildAt(0);
}
}
}
setNewSelectionPath(newCollection, newFamily, newFace);
}
private void doFaceChanged() {
JList list = selectionPanel.getFaceList();
TreePath path = fontChooser.getSelectionPath();
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 = (FontFaceNode) list.getSelectedValue();
setNewSelectionPath(newCollection, newFamily, newFace);
}
private void setNewSelectionPath(@Nullable FontCollectionNode newCollection, @Nullable FontFamilyNode newFamily, @Nullable FontFaceNode newFace) {
FontChooserModel model = fontChooser.getModel();
TreePath newPath;
if (newFace != null) {
newPath = new TreePath(new Object[]{
model.getRoot(),
newCollection,
newFamily,
newFace
});
} else if (newFamily != null) {
newPath = new TreePath(new Object[]{
model.getRoot(),
newCollection,
newFamily
});
} else if (newCollection != null) {
newPath = new TreePath(new Object[]{
model.getRoot(),
newCollection
});
} else {
newPath = new TreePath(model.getRoot());
}
fontChooser.setSelectionPath(newPath);
}
private class SelectionPanelHandler implements KeyListener, MouseListener, ListSelectionListener {
@Override
public void valueChanged(ListSelectionEvent evt) {
if (isUpdating ==
0) {
Object src = evt.getSource();
if (src == selectionPanel.getCollectionList()) {
doCollectionChanged();
} else if (src == selectionPanel.getFamilyList()) {
doFamilyChanged();
} else if (src == selectionPanel.getFaceList()) {
doFaceChanged();
}
}
}
@Override
public void keyReleased(KeyEvent evt) {
Object src = evt.getSource();
switch (evt.getKeyCode()) {
case KeyEvent.VK_LEFT:
if (src == selectionPanel.getCollectionList()) {
//
} else if (src == selectionPanel.getFamilyList()) {
selectionPanel.getCollectionList().requestFocus();
} else if (src == selectionPanel.getFaceList()) {
selectionPanel.getFamilyList().requestFocus();
}
evt.consume();
break;
case KeyEvent.VK_RIGHT:
if (src == selectionPanel.getCollectionList()) {
selectionPanel.getFamilyList().requestFocus();
} else if (src == selectionPanel.getFamilyList()) {
selectionPanel.getFaceList().requestFocus();
} else if (src == selectionPanel.getFaceList()) {
//
}
evt.consume();
break;
case KeyEvent.VK_ESCAPE:
fontChooser.cancelSelection();
evt.consume();
break;
case KeyEvent.VK_ENTER:
fontChooser.approveSelection();
evt.consume();
break;
}
}
@Override
public void keyPressed(KeyEvent evt) {
}
@Override
public void keyTyped(KeyEvent evt) {
}
@Override
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 2 && evt.getButton() == MouseEvent.BUTTON1) {
fontChooser.approveSelection();
}
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
private class FontChooserHandler implements PropertyChangeListener, TreeModelListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name == JFontChooser.SELECTION_PATH_PROPERTY) {
updateCollectionList();
updateFamilyList();
updateFaceList();
} else if (name == JFontChooser.SELECTED_FONT_PROPERTY) {
updatePreview();
} else if (name == JFontChooser.MODEL_PROPERTY) {
FontChooserModel m = (FontChooserModel) evt.getOldValue();
if (m != null) {
m.removeTreeModelListener(this);
}
m = (FontChooserModel) evt.getNewValue();
if (m != null) {
m.addTreeModelListener(this);
}
updateCollectionList();
updateFamilyList();
updateFaceList();
}
}
@Override
public void treeNodesChanged(TreeModelEvent e) {
updateCollectionList();
updateFamilyList();
updateFaceList();
}
@Override
public void treeNodesInserted(TreeModelEvent e) {
updateCollectionList();
updateFamilyList();
updateFaceList();
}
@Override
public void treeNodesRemoved(TreeModelEvent e) {
updateCollectionList();
updateFamilyList();
updateFaceList();
}
@Override
public void treeStructureChanged(TreeModelEvent e) {
updateCollectionList();
updateFamilyList();
updateFaceList();
}
}
}