001/*
002 * $Id: PainterHighlighter.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;
024import java.beans.PropertyChangeEvent;
025import java.beans.PropertyChangeListener;
026
027import org.jdesktop.swingx.painter.AbstractPainter;
028import org.jdesktop.swingx.painter.Painter;
029import org.jdesktop.swingx.renderer.PainterAware;
030
031/**
032 * Highlighter implementation which uses a Painter to decorate the component.
033 * <p>
034 * 
035 * As Painter implementations can be mutable and Highlighters have the
036 * responsibility to notify their own listeners about any changes which might
037 * effect the visuals, this class provides api to install/uninstall a listener
038 * to the painter, as appropriate. It takes care of Painters of type
039 * AbstractHighlighter by registering a PropertyChangeListener. Subclasses might
040 * override to correctly handle different types as well.
041 * <p>
042 * 
043 * Subclasses might be implemented to change the Painter during the decoration
044 * process, which must not passed-on to the Highlighter's listeners. The default
045 * routing is controlled by a flag isAdjusting. This is set/reset in this
046 * implementation's highlight method to ease subclass' burden (and to keep
047 * backward compatibility with implementations preceding the introduction of the
048 * painter listener). That is, subclasses are free to change painter properties
049 * during the decoration.
050 * <p>
051 * 
052 * As an example, a ValueBasedPainterHighlighter might safely change any painter
053 * property to decorate a component depending on content.
054 * 
055 * <pre><code>
056 * &#64;Override
057 * protected Component doHighlight(Component renderer, ComponentAdapter adapter) {
058 *      float end = getEndOfGradient((Number) adapter.getValue());
059 *      RelativePainter painter = (RelativePainter) getPainter();
060 *      painter.setXFraction(end);
061 *      ((PainterAware) renderer).setPainter(painter);
062 *      return renderer;
063 * }
064 * 
065 * &#64;Override
066 * protected boolean canHighlight(Component renderer, ComponentAdapter adapter) {
067 *     return super.canHighlight(renderer, adapter) &&
068 *        (adapter.getValue() instanceof Number);
069 * }
070 * </code></pre>
071 * 
072 * NOTE: this will change once the Painter api is stable.
073 * 
074 * @author Jeanette Winzenburg
075 */
076public class PainterHighlighter extends AbstractHighlighter {
077
078    /** The painter to use for decoration. */
079    private Painter painter;
080    /** The listener registered with the Painter. */
081    private PropertyChangeListener painterListener;
082    /** 
083     * A flag indicating whether or not changes in the Painter
084     * should be passed-on to the Highlighter's ChangeListeners. 
085     */
086    private boolean isAdjusting;
087
088    /**
089     * Instantiates a PainterHighlighter with null painter and 
090     * default predicate.
091     */
092    public PainterHighlighter() {
093        this(null, null);
094    }
095    /**
096     * Instantiates a PainterHighlighter with null painter which
097     * uses the given predicate.
098     * 
099     * @param predicate the HighlightPredicate which controls the highlight
100     *   application.
101     */
102    public PainterHighlighter(HighlightPredicate predicate) {
103        this(predicate, null);
104    }
105    
106    /**
107     * Instantiates a PainterHighlighter with the given Painter and 
108     * default predicate.
109     * 
110     * @param painter the painter to use
111     */
112    public PainterHighlighter(Painter painter) {
113        this(null, painter);
114    }
115
116    /**
117     * Instantiates a PainterHighlighter with the given painter and 
118     * predicate.
119     * @param predicate
120     * @param painter
121     */
122    public PainterHighlighter(HighlightPredicate predicate, Painter painter) {
123        super(predicate);
124        setPainter(painter);
125    }
126
127    
128
129    /**
130     * Returns to Painter used in this Highlighter. 
131     * 
132     * @return the Painter used in this Highlighter, may be null.
133     */
134    public Painter getPainter() {
135        return painter;
136    }
137
138    /**
139     * Sets the Painter to use in this Highlighter, may be null.
140     * Un/installs the listener to changes painter's properties.
141     * 
142     * @param painter the Painter to uses for decoration.
143     */
144    public void setPainter(Painter painter) {
145        if (areEqual(painter, getPainter())) return;
146        uninstallPainterListener();
147        this.painter = painter;
148        installPainterListener();
149        fireStateChanged();
150    }
151
152    /**
153     * Installs a listener to the painter if appropriate.
154     * This implementation registers its painterListener if
155     * the Painter is of type AbstractPainter.
156     */
157    protected void installPainterListener() {
158        if (getPainter() instanceof AbstractPainter) {
159            ((AbstractPainter) getPainter()).addPropertyChangeListener(getPainterListener());
160        }
161    }
162
163    /**
164     * Uninstalls a listener from the painter if appropriate.
165     * This implementation removes its painterListener if
166     * the Painter is of type AbstractPainter.
167     */
168    protected void uninstallPainterListener() {
169        if (getPainter() instanceof AbstractPainter) {
170            ((AbstractPainter) getPainter()).removePropertyChangeListener(painterListener);
171        }
172    }
173
174 
175    /**
176     * Lazyly creates and returns the property change listener used
177     * to listen to changes of the painter.
178     * 
179     * @return the property change listener used to listen to changes
180     *   of the painter.
181     */
182    protected final PropertyChangeListener getPainterListener() {
183        if (painterListener == null) {
184            painterListener = createPainterListener();
185        }
186        return painterListener;
187    }
188
189    /**
190     * Creates and returns the property change listener used
191     * to listen to changes of the painter. <p>
192     * 
193     * This implementation fires a stateChanged on receiving
194     * any propertyChange, if the isAdjusting flag is false. 
195     * Otherwise does nothing.
196     * 
197     * @return the property change listener used to listen to changes
198     *   of the painter.
199     */
200    protected PropertyChangeListener createPainterListener() {
201        PropertyChangeListener l = new PropertyChangeListener() {
202
203            @Override
204            public void propertyChange(PropertyChangeEvent evt) {
205                if (isAdjusting) return;
206                fireStateChanged();
207            }
208            
209        };
210        return l;
211    }
212    
213    /**
214     * {@inheritDoc} <p>
215     * 
216     * Overridden to set/reset the flag indicating whether or not 
217     * painter's property changes should be passed on to the 
218     * Highlighter's listener. 
219     */
220    @Override
221    public Component highlight(Component component, ComponentAdapter adapter) {
222        isAdjusting = true;
223        Component stamp = super.highlight(component, adapter);
224        isAdjusting = false;
225        return stamp;
226    }
227
228    /**
229     * {@inheritDoc}
230     * <p>
231     * This implementation sets the painter if it is not null. Does nothing
232     * otherwise.
233     */
234    @Override
235    protected Component doHighlight(Component component,
236            ComponentAdapter adapter) {
237       ((PainterAware) component).setPainter(painter);
238        return component;
239    }
240
241    /**
242     * {@inheritDoc} <p>
243     * 
244     * Overridden to return false if the Painter is null or the component is not
245     *   of type PainterAware.
246     */
247    @Override
248    protected boolean canHighlight(Component component, ComponentAdapter adapter) {
249        return getPainter() != null && (component instanceof PainterAware);
250    }
251    
252    
253}