/*
* jEdit - Programmer's Text Editor
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright © 2011 jEdit contributors
*
* This program 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 2
* of the License, or any later version.
* This program 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.util;
//{{{ Imports
import javax.swing.*;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.WeakHashMap;
//}}}
/**
* An enhancement of the {@link DefaultTreeCellRenderer} to be used as superclass for custom
* tree cell renderers. Using {@code DefaultTreeCellRenderer} as superclass for a custom tree
* cell renderer without further measures is not stable in regards to on-the-fly Look and Feel
* changes, at least not with Java 6. For Java 7 it should be tested again.
* <p>
* With Java 6 the {@code DefaultTreeCellRenderer} initializes some values according to the
* Look and Feel in its constructor. If the {@code DefaultTreeCellRenderer} is created by the
* {@link JTree} code, it is recreated on a Look and Feel change. This way all works fine. But
* if a tree cell renderer is set explicitly on the {@code JTree}, no matter whether
* {@code DefaultTreeCellRenderer}, a subclass of it or a complete own implementation, the
* set instance is used beyond Look and Feel boundaries and this causes two problems.
* <ol>
* <li>
* The values that were initialized in the constructor of {@code DefaultTreeCellRenderer}
* are not reset according to the new Look and Feel. Some values are partly set by the
* {@code JTree} code, but this is not complete and reliable and thus the renderer paints
* the tree cells wrongly.
* </li>
* <li>
* The Look and Feel change is first applied to the {@code JTree}, then the sizes of
* the tree cells which are saved in a cache are recalculated. Only <b>after</b> that,
* the children of the {@code JTree} get the new Look and Feel applied, amongst them
* also the tree cell renderer.<br>
* So even if a custom tree cell renderer is aware of on-the-fly Look and Feel changes
* by reinitializing values from the Look and Feel if it changes, those cached sizes
* are still calculated for the old Look and Feel. The only way to work around this is
* to cause the cached sizes to be recalculated. This can be done by changing any
* significant property of the {@code JTree} which influences size calculations.
* </li>
* </ol>
* <p>
* To work around the described problems this enhanced tree cell renderer listens for Look
* and Feel changes on the {@code JTree} where this renderer is used, requests a subclass
* to create a new instance of the renderer and sets it on the {@code JTree}. By doing so
* the {@code DefaultTreeCellRenderer} reinitializes to the new Look and Feel in its
* constructor and the {@code JTree} recalculates the cached size values because a different
* object is set as tree cell renderer.
*/
public abstract class EnhancedTreeCellRenderer extends DefaultTreeCellRenderer
{
//{{{ getTreeCellRendererComponent() method
public final Component getTreeCellRendererComponent(JTree tree,
Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus)
{
if (!propertyChangeListeners.containsKey(tree))
{
PropertyChangeListener propertyChangeListener = new PropertyChangeListener()
{
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (!(evt.getSource() instanceof JTree))
return;
JTree tree = (JTree) evt.getSource();
if (tree.getCellRenderer() == EnhancedTreeCellRenderer.this)
tree.setCellRenderer(newInstance());
tree.removePropertyChangeListener("UI", propertyChangeListeners.remove(tree));
}
};
tree.addPropertyChangeListener("UI", propertyChangeListener);
propertyChangeListeners.put(tree, propertyChangeListener);
}
super.getTreeCellRendererComponent(tree,value,
selected,expanded,leaf,row,hasFocus);
configureTreeCellRendererComponent(tree,value,
selected,expanded,leaf,row,hasFocus);
return this;
} //}}}
//{{{ newInstance() method
/**
* Creates a new instance of the tree cell renderer. Each invocation has to
* return a different object. Saving a reference and returning the same
* instance from different calls of this method is <b>not</b> appropriate.
* <p>
* Any one-time initializations that are necessary and are not made in the
* constructor should be made in this method. The simplest implementation
* of this method will just call the constructor and return the result.
* <p>
* This is an instance method so that the new instance can be set up with
* information from the current instance.
*
* @return a new readily initialized instance of this class
*/
protected abstract TreeCellRenderer newInstance();
//}}}
//{{{ configureTreeCellRendererComponent() method
/**
* Configures this instance of the renderer component based on the passed in
* components. The value is set from messaging the tree with convertValueToText,
* which ultimately invokes toString on value. The foreground color is set
* based on the selection and the icon is set based on the leaf and expanded
* parameters. The parameters of this method are the same as the ones of
* {@link #getTreeCellRendererComponent(JTree, Object, boolean, boolean, boolean, int, boolean)}.
*
* @param tree The tree in which this renderer component is used currently
* @param value The value to be displayed for the tree cell to be rendered
* @param selected Whether the tree cell to be rendered is selected
* @param expanded Whether the tree cell to be rendered is expanded
* @param leaf Whether the tree cell to be rendered is a leaf
* @param row The row index of the tree cell to be rendered
* @param hasFocus Whether the tree cell to be rendered has the focus
*/
protected abstract void configureTreeCellRendererComponent(JTree tree,
Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus);
//}}}
//{{{ Instance variables
private final Map<JTree, PropertyChangeListener> propertyChangeListeners = new WeakHashMap<JTree, PropertyChangeListener>();
//}}}
}