/* * $Id$ * * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx.decorator; import java.awt.Component; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.jdesktop.swingx.event.WeakEventListenerList; /** * Abstract <code>Highlighter</code> implementation which manages change * notification and supports conditional highlighting. * Subclasses are required to fire ChangeEvents on internal changes which might * effect the highlight. The HighlightPredicate controls whether or not * a highlight should be applied for the given ComponentAdapter, * subclasses must guarantee to respect its decision. * <p> * * Concrete custom implementations should focus on a single (or few) visual * attribute to highlight. This allows easy re-use by composition. F.i. a custom * FontHighlighter: * * <pre><code> * public static class FontHighlighter extends AbstractHighlighter { * * private Font font; * * public FontHighlighter(HighlightPredicate predicate, Font font) { * super(predicate); * setFont(font); * } * * @Override * protected Component doHighlight(Component component, * ComponentAdapter adapter) { * component.setFont(font); * return component; * } * * public final void setFont(Font font) { * if (equals(font, this.font)) return; * this.font = font; * fireStateChanged(); * } * * * } * * </code></pre> * * Client code can combine the effect with a f.i. Color decoration, and use a * shared HighlightPredicate to apply both for the same condition. * * <pre><code> * HighlightPredicate predicate = new HighlightPredicate() { * public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { * Object value = adapter.getFilteredValueAt(adapter.row, adapter.column); * return (value instanceof Number) && ((Number) value).intValue() < 0; * } * }; * table.setHighlighters( * new ColorHighlighter(predicate, Color.RED, null), * new FontHighlighter(predicate, myBoldFont)); * </code></pre> * * @author Jeanette Winzenburg * * @see HighlightPredicate * @see org.jdesktop.swingx.renderer.ComponentProvider */ public abstract class AbstractHighlighter implements Highlighter { /** * Only one <code>ChangeEvent</code> is needed per model instance since the * event's only (read-only) state is the source property. The source * of events generated here is always "this". */ private transient ChangeEvent changeEvent; /** The listeners waiting for model changes. */ protected WeakEventListenerList listenerList = new WeakEventListenerList(); /** the HighlightPredicate to use. */ private HighlightPredicate predicate; /** * Instantiates a Highlighter with default HighlightPredicate. * * @see #setHighlightPredicate(HighlightPredicate) */ public AbstractHighlighter() { this(null); } /** * Instantiates a Highlighter with the given * HighlightPredicate.<p> * * @param predicate the HighlightPredicate to use. * * @see #setHighlightPredicate(HighlightPredicate) */ public AbstractHighlighter(HighlightPredicate predicate) { setHighlightPredicate(predicate); } /** * Set the HighlightPredicate used to decide whether a cell should * be highlighted. If null, sets the predicate to HighlightPredicate.ALWAYS. * * The default value is HighlightPredicate.ALWAYS. * * @param predicate the HighlightPredicate to use. */ public void setHighlightPredicate(HighlightPredicate predicate) { if (predicate == null) { predicate = HighlightPredicate.ALWAYS; } if (areEqual(predicate, getHighlightPredicate())) return; this.predicate = predicate; fireStateChanged(); } /** * Returns the HighlightPredicate used to decide whether a cell * should be highlighted. Guaranteed to be never null. * * @return the HighlightPredicate to use, never null. */ public HighlightPredicate getHighlightPredicate() { return predicate; } //----------------------- implement predicate respecting highlight /** * {@inheritDoc} * * This calls doHighlight to apply the decoration if both HighlightPredicate * isHighlighted and canHighlight return true. Returns the undecorated component otherwise. * * @param component the cell renderer component that is to be decorated * @param adapter the ComponentAdapter for this decorate operation * * @see #canHighlight(Component, ComponentAdapter) * @see #doHighlight(Component, ComponentAdapter) * @see #getHighlightPredicate() */ @Override public Component highlight(Component component, ComponentAdapter adapter) { if (canHighlight(component, adapter) && getHighlightPredicate().isHighlighted(component, adapter)) { component = doHighlight(component, adapter); } return component; } /** * Subclasses may override to further limit the highlighting based * on Highlighter state, f.i. a PainterHighlighter can only be applied * to PainterAware components. <p> * * This implementation returns true always. * * @param component * @param adapter * @return a boolean indication if the adapter can be highlighted based * general state. This implementation returns true always. */ protected boolean canHighlight(Component component, ComponentAdapter adapter) { return true; } /** * Apply the highlights. * * @param component the cell renderer component that is to be decorated * @param adapter the ComponentAdapter for this decorate operation * * @see #highlight(Component, ComponentAdapter) */ protected abstract Component doHighlight(Component component, ComponentAdapter adapter); /** * Returns true if the to objects are either both null or equal * each other. * * @param oneItem one item * @param anotherItem another item * @return true if both are null or equal other, false otherwise. */ protected boolean areEqual(Object oneItem, Object anotherItem) { if ((oneItem == null) && (anotherItem == null)) return true; if (anotherItem != null) { return anotherItem.equals(oneItem); } return false; } //------------------------ implement Highlighter change notification /** * Adds a <code>ChangeListener</code>. ChangeListeners are * notified after changes of any attribute. * * @param l the ChangeListener to add * @see #removeChangeListener */ @Override public final void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } /** * Removes a <code>ChangeListener</code>e. * * @param l the <code>ChangeListener</code> to remove * @see #addChangeListener */ @Override public final void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } /** * Returns an array of all the change listeners * registered on this <code>Highlighter</code>. * * @return all of this model's <code>ChangeListener</code>s * or an empty * array if no change listeners are currently registered * * @see #addChangeListener * @see #removeChangeListener * * @since 1.4 */ @Override public final ChangeListener[] getChangeListeners() { return listenerList.getListeners(ChangeListener.class); } /** * Notifies registered <code>ChangeListener</code>s about * state changes.<p> * * Note: subclasses should be polite and implement any property * setters to fire only if the property is really changed. * */ protected final void fireStateChanged() { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { if (changeEvent == null) { changeEvent = new ChangeEvent(this); } ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent); } } } }