/*
 * $Id: PainterHighlighter.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 java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.painter.Painter;
import org.jdesktop.swingx.renderer.PainterAware;

/**
 * Highlighter implementation which uses a Painter to decorate the component.
 * <p>
 * 
 * As Painter implementations can be mutable and Highlighters have the
 * responsibility to notify their own listeners about any changes which might
 * effect the visuals, this class provides api to install/uninstall a listener
 * to the painter, as appropriate. It takes care of Painters of type
 * AbstractHighlighter by registering a PropertyChangeListener. Subclasses might
 * override to correctly handle different types as well.
 * <p>
 * 
 * Subclasses might be implemented to change the Painter during the decoration
 * process, which must not passed-on to the Highlighter's listeners. The default
 * routing is controlled by a flag isAdjusting. This is set/reset in this
 * implementation's highlight method to ease subclass' burden (and to keep
 * backward compatibility with implementations preceding the introduction of the
 * painter listener). That is, subclasses are free to change painter properties
 * during the decoration.
 * <p>
 * 
 * As an example, a ValueBasedPainterHighlighter might safely change any painter
 * property to decorate a component depending on content.
 * 
 * <pre><code>
 * &#64;Override
 * protected Component doHighlight(Component renderer, ComponentAdapter adapter) {
 *      float end = getEndOfGradient((Number) adapter.getValue());
 *      RelativePainter painter = (RelativePainter) getPainter();
 *      painter.setXFraction(end);
 *      ((PainterAware) renderer).setPainter(painter);
 *      return renderer;
 * }
 * 
 * &#64;Override
 * protected boolean canHighlight(Component renderer, ComponentAdapter adapter) {
 *     return super.canHighlight(renderer, adapter) &&
 *        (adapter.getValue() instanceof Number);
 * }
 * </code></pre>
 * 
 * NOTE: this will change once the Painter api is stable.
 * 
 * @author Jeanette Winzenburg
 */
public class PainterHighlighter extends AbstractHighlighter {

    /** The painter to use for decoration. */
    private Painter painter;
    /** The listener registered with the Painter. */
    private PropertyChangeListener painterListener;
    /** 
     * A flag indicating whether or not changes in the Painter
     * should be passed-on to the Highlighter's ChangeListeners. 
     */
    private boolean isAdjusting;

    /**
     * Instantiates a PainterHighlighter with null painter and 
     * default predicate.
     */
    public PainterHighlighter() {
        this(null, null);
    }
    /**
     * Instantiates a PainterHighlighter with null painter which
     * uses the given predicate.
     * 
     * @param predicate the HighlightPredicate which controls the highlight
     *   application.
     */
    public PainterHighlighter(HighlightPredicate predicate) {
        this(predicate, null);
    }
    
    /**
     * Instantiates a PainterHighlighter with the given Painter and 
     * default predicate.
     * 
     * @param painter the painter to use
     */
    public PainterHighlighter(Painter painter) {
        this(null, painter);
    }

    /**
     * Instantiates a PainterHighlighter with the given painter and 
     * predicate.
     * @param predicate
     * @param painter
     */
    public PainterHighlighter(HighlightPredicate predicate, Painter painter) {
        super(predicate);
        setPainter(painter);
    }

    

    /**
     * Returns to Painter used in this Highlighter. 
     * 
     * @return the Painter used in this Highlighter, may be null.
     */
    public Painter getPainter() {
        return painter;
    }

    /**
     * Sets the Painter to use in this Highlighter, may be null.
     * Un/installs the listener to changes painter's properties.
     * 
     * @param painter the Painter to uses for decoration.
     */
    public void setPainter(Painter painter) {
        if (areEqual(painter, getPainter())) return;
        uninstallPainterListener();
        this.painter = painter;
        installPainterListener();
        fireStateChanged();
    }

    /**
     * Installs a listener to the painter if appropriate.
     * This implementation registers its painterListener if
     * the Painter is of type AbstractPainter.
     */
    protected void installPainterListener() {
        if (getPainter() instanceof AbstractPainter) {
            ((AbstractPainter) getPainter()).addPropertyChangeListener(getPainterListener());
        }
    }

    /**
     * Uninstalls a listener from the painter if appropriate.
     * This implementation removes its painterListener if
     * the Painter is of type AbstractPainter.
     */
    protected void uninstallPainterListener() {
        if (getPainter() instanceof AbstractPainter) {
            ((AbstractPainter) getPainter()).removePropertyChangeListener(painterListener);
        }
    }

 
    /**
     * Lazyly creates and returns the property change listener used
     * to listen to changes of the painter.
     * 
     * @return the property change listener used to listen to changes
     *   of the painter.
     */
    protected final PropertyChangeListener getPainterListener() {
        if (painterListener == null) {
            painterListener = createPainterListener();
        }
        return painterListener;
    }

    /**
     * Creates and returns the property change listener used
     * to listen to changes of the painter. <p>
     * 
     * This implementation fires a stateChanged on receiving
     * any propertyChange, if the isAdjusting flag is false. 
     * Otherwise does nothing.
     * 
     * @return the property change listener used to listen to changes
     *   of the painter.
     */
    protected PropertyChangeListener createPainterListener() {
        PropertyChangeListener l = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (isAdjusting) return;
                fireStateChanged();
            }
            
        };
        return l;
    }
    
    /**
     * {@inheritDoc} <p>
     * 
     * Overridden to set/reset the flag indicating whether or not 
     * painter's property changes should be passed on to the 
     * Highlighter's listener. 
     */
    @Override
    public Component highlight(Component component, ComponentAdapter adapter) {
        isAdjusting = true;
        Component stamp = super.highlight(component, adapter);
        isAdjusting = false;
        return stamp;
    }

    /**
     * {@inheritDoc}
     * <p>
     * This implementation sets the painter if it is not null. Does nothing
     * otherwise.
     */
    @Override
    protected Component doHighlight(Component component,
            ComponentAdapter adapter) {
       ((PainterAware) component).setPainter(painter);
        return component;
    }

    /**
     * {@inheritDoc} <p>
     * 
     * Overridden to return false if the Painter is null or the component is not
     *   of type PainterAware.
     */
    @Override
    protected boolean canHighlight(Component component, ComponentAdapter adapter) {
        return getPainter() != null && (component instanceof PainterAware);
    }
    
    
}
