001/*
002 * $Id: AbstractHighlighter.java 3927 2011-02-22 16:34:11Z kleopatra $
003 *
004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.decorator;
022
023import java.awt.Component;
024
025import javax.swing.event.ChangeEvent;
026import javax.swing.event.ChangeListener;
027
028import org.jdesktop.swingx.event.WeakEventListenerList;
029
030/**
031 * Abstract <code>Highlighter</code> implementation which manages change 
032 * notification and supports conditional highlighting. 
033 * Subclasses are required to fire ChangeEvents on internal changes which might
034 * effect the highlight. The HighlightPredicate controls whether or not
035 * a highlight should be applied for the given ComponentAdapter, 
036 * subclasses must guarantee to respect its decision. 
037 * <p>
038 * 
039 * Concrete custom implementations should focus on a single (or few) visual
040 * attribute to highlight. This allows easy re-use by composition.  F.i. a custom
041 * FontHighlighter:
042 * 
043 * <pre><code>
044 * public static class FontHighlighter extends AbstractHighlighter {
045 * 
046 *     private Font font;
047 * 
048 *     public FontHighlighter(HighlightPredicate predicate, Font font) {
049 *         super(predicate);
050 *         setFont(font);
051 *     }
052 * 
053 *     &#64;Override
054 *     protected Component doHighlight(Component component,
055 *             ComponentAdapter adapter) {
056 *         component.setFont(font);
057 *         return component;
058 *     }
059 *     
060 *     public final void setFont(Font font) {
061 *        if (equals(font, this.font)) return;
062 *        this.font = font;
063 *        fireStateChanged();
064 *     }
065 *     
066 * 
067 * }
068 * 
069 * </code></pre>
070 * 
071 * Client code can combine the effect with a f.i. Color decoration, and use a
072 * shared HighlightPredicate to apply both for the same condition.
073 * 
074 * <pre><code>
075 * HighlightPredicate predicate = new HighlightPredicate() {
076 *     public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
077 *         Object value = adapter.getFilteredValueAt(adapter.row, adapter.column);
078 *         return (value instanceof Number) &amp;&amp; ((Number) value).intValue() &lt; 0;
079 *     }
080 * };
081 * table.setHighlighters(
082 *         new ColorHighlighter(predicate, Color.RED, null),
083 *         new FontHighlighter(predicate, myBoldFont));
084 * </code></pre>
085 * 
086 * @author Jeanette Winzenburg
087 * 
088 * @see HighlightPredicate
089 * @see org.jdesktop.swingx.renderer.ComponentProvider
090 */
091public abstract class AbstractHighlighter implements Highlighter {
092
093    /**
094     * Only one <code>ChangeEvent</code> is needed per model instance since the
095     * event's only (read-only) state is the source property.  The source
096     * of events generated here is always "this".
097     */
098    private transient ChangeEvent changeEvent;
099
100    /** The listeners waiting for model changes. */
101    protected WeakEventListenerList listenerList = new WeakEventListenerList();
102
103    /** the HighlightPredicate to use. */
104    private HighlightPredicate predicate;
105
106    /**
107     * Instantiates a Highlighter with default HighlightPredicate.
108     *
109     * @see #setHighlightPredicate(HighlightPredicate)
110     */
111    public AbstractHighlighter() {
112        this(null);
113    }
114
115    /**
116     * Instantiates a Highlighter with the given 
117     * HighlightPredicate.<p>
118     * 
119     * @param predicate the HighlightPredicate to use.
120     * 
121     * @see #setHighlightPredicate(HighlightPredicate)
122     */
123    public AbstractHighlighter(HighlightPredicate predicate) {
124        setHighlightPredicate(predicate);
125    }
126
127    /**
128     * Set the HighlightPredicate used to decide whether a cell should
129     * be highlighted. If null, sets the predicate to HighlightPredicate.ALWAYS.
130     * 
131     * The default value is HighlightPredicate.ALWAYS. 
132     * 
133     * @param predicate the HighlightPredicate to use. 
134     */
135    public void setHighlightPredicate(HighlightPredicate predicate) {
136        if (predicate == null) {
137            predicate = HighlightPredicate.ALWAYS;
138        }
139        if (areEqual(predicate, getHighlightPredicate())) return;
140        this.predicate = predicate;
141        fireStateChanged();
142    }
143
144    /**
145     * Returns the HighlightPredicate used to decide whether a cell 
146     * should be highlighted. Guaranteed to be never null.
147     * 
148     * @return the HighlightPredicate to use, never null.
149     */
150    public HighlightPredicate getHighlightPredicate() {
151        return predicate;
152    }
153
154    //----------------------- implement predicate respecting highlight
155
156    /**
157     * {@inheritDoc}
158     * 
159     * This calls doHighlight to apply the decoration if both HighlightPredicate
160     * isHighlighted and canHighlight return true. Returns the undecorated component otherwise.
161     * 
162     * @param component the cell renderer component that is to be decorated
163     * @param adapter the ComponentAdapter for this decorate operation
164     * 
165     * @see #canHighlight(Component, ComponentAdapter)
166     * @see #doHighlight(Component, ComponentAdapter)
167     * @see #getHighlightPredicate()
168     */
169    @Override
170    public Component highlight(Component component, ComponentAdapter adapter) {
171        if (canHighlight(component, adapter) && 
172                getHighlightPredicate().isHighlighted(component, adapter)) {
173            component = doHighlight(component, adapter);
174        }
175        return component;
176    }
177
178    /**
179     * Subclasses may override to further limit the highlighting based
180     * on Highlighter state, f.i. a PainterHighlighter can only be applied
181     * to PainterAware components. <p>
182     * 
183     * This implementation returns true always.
184     * 
185     * @param component
186     * @param adapter
187     * @return a boolean indication if the adapter can be highlighted based
188     *   general state. This implementation returns true always.
189     */
190    protected boolean canHighlight(Component component, ComponentAdapter adapter) {
191        return true;
192    }
193
194
195    /**
196     * Apply the highlights. 
197     * 
198     * @param component the cell renderer component that is to be decorated
199     * @param adapter the ComponentAdapter for this decorate operation
200     * 
201     * @see #highlight(Component, ComponentAdapter)
202     */
203    protected abstract Component doHighlight(Component component,
204            ComponentAdapter adapter);
205
206
207    /**
208     * Returns true if the to objects are either both null or equal
209     * each other.
210     * 
211     * @param oneItem one item
212     * @param anotherItem another item 
213     * @return true if both are null or equal other, false otherwise.
214     */
215    protected boolean areEqual(Object oneItem, Object anotherItem) {
216        if ((oneItem == null) && (anotherItem == null)) return true;
217        if (anotherItem != null) {
218            return anotherItem.equals(oneItem);
219        }
220        return false;
221    }
222
223    //------------------------ implement Highlighter change notification
224
225    /**
226     * Adds a <code>ChangeListener</code>. ChangeListeners are
227     * notified after changes of any attribute. 
228     *
229     * @param l the ChangeListener to add
230     * @see #removeChangeListener
231     */
232    @Override
233    public final void addChangeListener(ChangeListener l) {
234        listenerList.add(ChangeListener.class, l);
235    }
236
237    /**
238     * Removes a <code>ChangeListener</code>e. 
239     *
240     * @param l the <code>ChangeListener</code> to remove
241     * @see #addChangeListener
242     */
243    @Override
244    public final void removeChangeListener(ChangeListener l) {
245        listenerList.remove(ChangeListener.class, l);
246    }
247
248    /**
249     * Returns an array of all the change listeners
250     * registered on this <code>Highlighter</code>.
251     *
252     * @return all of this model's <code>ChangeListener</code>s 
253     *         or an empty
254     *         array if no change listeners are currently registered
255     *
256     * @see #addChangeListener
257     * @see #removeChangeListener
258     *
259     * @since 1.4
260     */
261    @Override
262    public final ChangeListener[] getChangeListeners() {
263        return listenerList.getListeners(ChangeListener.class);
264    }
265
266    /** 
267     * Notifies registered <code>ChangeListener</code>s about
268     * state changes.<p>
269     * 
270     * Note: subclasses should be polite and implement any property
271     * setters to fire only if the property is really changed. 
272     *  
273     */
274    protected final void fireStateChanged() {
275        Object[] listeners = listenerList.getListenerList();
276        for (int i = listeners.length - 2; i >= 0; i -= 2) {
277            if (listeners[i] == ChangeListener.class) {
278                if (changeEvent == null) {
279                    changeEvent = new ChangeEvent(this);
280                }
281                ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent);
282            }
283        }
284    }
285
286}