org.jdesktop.swingx.decorator.AbstractHighlighter Maven / Gradle / Ivy
/*
* $Id: AbstractHighlighter.java 3927 2011-02-22 16:34:11Z kleopatra $
*
* 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 Highlighter
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.
*
*
* 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:
*
*
* 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();
* }
*
*
* }
*
*
*
* Client code can combine the effect with a f.i. Color decoration, and use a
* shared HighlightPredicate to apply both for the same condition.
*
*
* 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));
*
*
* @author Jeanette Winzenburg
*
* @see HighlightPredicate
* @see org.jdesktop.swingx.renderer.ComponentProvider
*/
public abstract class AbstractHighlighter implements Highlighter {
/**
* Only one ChangeEvent
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.
*
* @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.
*
* 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 ChangeListener
. 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 ChangeListener
e.
*
* @param l the ChangeListener
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 Highlighter
.
*
* @return all of this model's ChangeListener
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 ChangeListener
s about
* state changes.
*
* 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);
}
}
}
}