/* Copyright (C) 2005-2011 Fabio Riccardi */

package com.lightcrafts.ui.toolkit;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.awt.image.BufferedImage;

import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import javax.swing.JPanel;


/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * <p><code>DropShadowPanel</code> is a generic lightweight container casting
 * the shadow of its children. The shadow itself is generated by a
 * {@link org.jdesktop.swingx.util.ShadowFactory} you can provide or modify.</p>
 * <p>Besides the properties provided by the <code>ShadowFactory</code>, the
 * drop shadow panel gives you control over the angle and the distance of the
 * shadow. Any change of these properties, be the properties of the
 * <code>ShadowFactory</code> or the angle or the distance, will triger an update
 * of the panel to repaint it.</p>
 * <p>The default shadow factory provided by this panel is suited for good
 * looking results with regular Swing components. You can also combine this panel
 * with a {@link org.jdesktop.swingx.StackLayout} and a
 * {@link org.jdesktop.swingx.CheckboardPanel} to create interesting effects.</p>
 * <p><b>IMPORTANT NOTICE:</b> this component has not yet been fully tested.</p>
 *
 * @author Romain Guy <romain.guy@mac.com>
 */

public class DropShadowPanel extends JPanel implements PropertyChangeListener {
    // angle and distance of the shadow from the original subjects
    private float angle = 45.0f;
    private int distance = 2;

    // cached values for fast painting
    private int distance_x = 0;
    private int distance_y = 0;

    // when shadow member is equaled to null, the factory is asked to
    // re-generated it
    public BufferedImage shadow = null;
    private ShadowFactory factory = null;

    /**
     * <p>Creates a new checkboard panel with a flow layout. The drop shadow has
     * the following default properties:
     * <ul>
     *   <li><i>angle</i>: 45</li>
     *   <li><i>distance</i>: 2 pixels</li>
     *   <li><i>size</i>: 5 pixels (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     *   <li><i>opacity</i>: 50% (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     *   <li><i>color</i>: Black (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     *   <li><i>rendering quality</i>: VALUE_BLUR_QUALITY_FAST (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     * </ul>
     * This configuration provides good looking results for use with Swing
     * components. To add a drop shadow to a picture, the distance should be
     * increased.</p>
     */
    public DropShadowPanel() {
        this(new ShadowFactory(), new FlowLayout());
    }

    /**
     * <p>Creates a new checkboard panel the specified layout. The drop shadow
     * has the following default properties:
     * <ul>
     *   <li><i>angle</i>: 45</li>
     *   <li><i>distance</i>: 2 pixels</li>
     *   <li><i>size</i>: 5 pixels (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     *   <li><i>opacity</i>: 50% (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     *   <li><i>color</i>: Black (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     *   <li><i>rendering quality</i>: VALUE_BLUR_QUALITY_FAST (see
     *   {@link org.jdesktop.swingx.util.ShadowFactory}</li>
     * </ul>
     * This configuration provides good looking results for use with Swing
     * components. To add a drop shadow to a picture, the distance should be
     * increased.</p>
     */
    public DropShadowPanel(final LayoutManager layout) {
        this(new ShadowFactory(), layout);
    }

    /**
     * <p>Creates a new checkboard panel the specified layout and shadow factory.
     * The drop shadow has the following default properties:
     * <ul>
     *   <li><i>angle</i>: 45</li>
     *   <li><i>distance</i>: 2 pixels</li>
     * </ul>
     * This configuration provides good looking results for use with Swing
     * components. To add a drop shadow to a picture, the distance should be
     * increased.</p>
     */
    public DropShadowPanel(final ShadowFactory factory,
                           final LayoutManager layout) {
        super(layout);
        computeShadowPosition();
        setShadowFactory(factory);
    }

    /**
     * <p>Gets the shadow factory used to generate the shadow.</p>
     *
     * @return this panel's shadow factory
     */
    public ShadowFactory getShadowFactory() {
        return factory;
    }

    /**
     * <p>Sets the shadow factory used by this panel to generate the shadows.</p>
     * <p>If the specified factory is null, the default shadow factory is used
     * instead (see
     * {@link org.jdesktop.swingx.util.ShadowFactory#ShadowFactory()}.</p>
     *
     * @param factory the factory used to generate the shadows for this
     * panel's content
     */
    public void setShadowFactory(ShadowFactory factory) {
        if (factory == null) {
            factory = new ShadowFactory();
        }

        if (factory != this.factory){
            if (this.factory != null) {
                this.factory.removePropertyChangeListener(this);
            }

            this.factory = factory;
            this.factory.addPropertyChangeListener(this);

            shadow = null;
            repaint();
        }
    }

    /**
     * <p>Gets the angle, in degrees, of the shadow relatively to the panel's
     * content.</p>
     *
     * @return this panel's shadow angle
     */
    public float getAngle() {
        return angle;
    }

    /**
     * <p>Sets the angle, in degrees, at which the drop shadow is drawn
     * relatively to this panel's content.</p>
     * <p>The angle should be comprised between 0 and 360 but negative values
     * and values greater than 360 are accepted.</p>
     *
     * @param angle the angle of the shadow relatively to the panel's content
     */
    public void setAngle(final float angle) {
        this.angle = angle;
        computeShadowPosition();
        repaint();
    }

    /**
     * <p>Gets the distance, in pixels, of the shadow relatively to the panel's
     * content.</p>
     *
     * @return this panel's shadow distance
     */
    public int getDistance() {
        return distance;
    }

    /**
     * <p>Sets the distance, in pixels, at which the drop shadow is drawn
     * relatively to this panel's content.</p>
     * <p>The distance can be either positive or negative.</p>
     *
     * @param distance the distance of the shadow relatively to the panel's content
     */
    public void setDistance(final int distance) {
        this.distance = distance;
        computeShadowPosition();
        repaint();
    }

    // computes the shadow position, in pixels, according to the current
    // angle and distance properties
    private void computeShadowPosition() {
        double angleRadians = Math.toRadians(angle);
        distance_x = (int) (Math.cos(angleRadians) * distance);
        distance_y = (int) (Math.sin(angleRadians) * distance);
    }

    /**
     * <p>Due to the nature of this panel, it is assumed it is non opaque.
     * This allows to handle the drop shadow drawing properly.</p>
     *
     * @return always true
     */
    @Override
    public boolean isOpaque() {
        return false;
    }

    /**
     * <p>When this panel is asked to lay out its children components,
     * the shadow is invalidated for redrawing.</p>
     */
    @Override
    public void doLayout() {
        super.doLayout();
        shadow = null;
    }

    /**
     * <p>When the drop shadow is invalidated (see {@link #doLayout}), the
     * content of this panel is drawn in an offscreen picture and the shadow
     * factory is asked to generate the shadow. Otherwise, the shadow is drawn,
     * then the panel is painted as usual.</p>
     *
     * @param g the <code>Graphics</code> context in which to paint
     */
    @Override
    public void paint(Graphics g) {
        if (shadow == null) {
            BufferedImage buffer = new BufferedImage(getWidth(),
                                                     getHeight(),
                                                     BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = buffer.createGraphics();
            super.paint(g2);
            shadow = factory.createShadow(buffer);
            g2.dispose();
            g.drawImage(shadow, distance_x, distance_y, null);
            g.drawImage(buffer, 0, 0, null);
        } else if (isVisible()) {
            g.drawImage(shadow, distance_x, distance_y, null);
            super.paint(g);
        }
    }

    /**
     * <p>Whenever a property of the shadow factory is changed, the shadow
     * is invalidated and the panel is updated for repainting.</p>
     *
     * @param evt the property change event generating the update
     */
    public void propertyChange(PropertyChangeEvent evt) {
        shadow = null;
        repaint();
    }
}
