/*
 * @(#)PaintSwatch.java    2.3.3  10 December 2004
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.*;

/**
 * <p>Class <code>PaintSwatch</code> is a light weight component to draw
 * a rectangular paint swatch with a given paint, size, and optional
 * border.</p>
 *
 * <p>This class also implements the <code>Icon</code> interface.</p>
 *
 * <p>For convenience, the class provides three static border objects so
 * that a swatch border may be changed without changing the space needed
 * in the GUI.</p>
 *
 * @author  Richard Rasala
 * @version 2.3.3
 * @since   2.3.2
 */
public class PaintSwatch
    extends JPTComponent
    implements Icon
{
    /** Bound property name for set paint. */
    public static final String SET_PAINT  = "set.paint";
        
    /** Bound property name for set size. */
    public static final String SET_SIZE   = "set.size";
    
    /** Bound property name for set border. */
    public static final String SET_BORDER = "set.border";
    
    
    /** Default x and y swatch size. */
    public static final int DEFAULT_SWATCH_SIZE = 20;
    
    
    /** The thickness of a standard swatch border. */
    public static final int BORDER_SIZE = 2;
    
    /** A standard black swatch border of standard thickness. */
    public static final Border BLACK_BORDER =
        BorderFactory.createLineBorder(Colors.Black, BORDER_SIZE);
    
    /** A standard white swatch border of standard thickness. */
    public static final Border WHITE_BORDER =
        BorderFactory.createLineBorder(Colors.White, BORDER_SIZE);
    
    /** A standard transparent swatch border of standard thickness. */
    public static final Border TRANSPARENT_BORDER =
        BorderFactory.createLineBorder(Colors.Transparent, BORDER_SIZE);
    
    
    /** The encapsulated paint. */
    private Paint paint = Colors.transparent;
    
    /**
     * The x size.  Must be at least 1.
     * Initialized to 0 to guarantee that area will be set.
     */
    private int xSize = 0;
    
    /**
     * The y size.  Must be at least 1.
     * Initialized to 0 to guarantee that area will be set.
     */
    private int ySize = 0;
    
    /**
     * The area to paint on within the border.
     * Will be set in <code>setSize</code>.
     */
    private Rectangle2D area;
    
    
    /**
     * <p>This constructor
     * sets the swatch paint to <code>Colors.transparent</code>;
     * sets the dimensions of the swatch to (20, 20);
     * sets the border to BLACK_BORDER which has thickness 2.</p>
     *
     * <p>The caller should set the swatch paint to a more useful paint
     * after construction.</p>
     *
     * <p>The preferred size of this component will be (24, 24) to take
     * into account both the swatch size and the border.</p>
     *
     * <p>Other constructors:</p>
     *
     * <ul><code>
     *   <li>PaintSwatch(Paint)</li>
     *   <li>PaintSwatch(Paint, int, int)</li>
     *   <li>PaintSwatch(Paint, Border)</li>
     *   <li>PaintSwatch(Paint, int, int, Border)</li>
     * </code></ul>
     */
    public PaintSwatch() {
        this(null, DEFAULT_SWATCH_SIZE, DEFAULT_SWATCH_SIZE, BLACK_BORDER);
    }
    
    
    /**
     * <p>This constructor provides the paint to paint in the swatch;
     * sets the dimensions of the swatch to (20, 20);
     * sets the border to BLACK_BORDER which has thickness 2.</p>
     *
     * <p>If the given paint is <code>null</code>, sets the paint to
     * <code>Colors.transparent</code>.</p>
     *
     * <p>The preferred size of this component will be (24, 24) to take
     * into account both the swatch size and the border.</p>
     *
     * @param paint the paint to paint in the swatch
     * @see #PaintSwatch()
     */
    public PaintSwatch(Paint paint) {
        this(paint, DEFAULT_SWATCH_SIZE, DEFAULT_SWATCH_SIZE, BLACK_BORDER);
    }
    
    
    /**
     * <p>This constructor provides the paint to paint in the swatch
     * and the x and y dimensions of the swatch;
     * sets the border to BLACK_BORDER which has thickness 2.</p>
     *
     * <p>If the given paint is <code>null</code>, sets the paint to
     * <code>Colors.transparent</code>.</p>
     *
     * <p>The preferred size of this component will be (xSize+4, ySize+4)
     * to take into account both the swatch size and the border.</p>
     *
     * @param paint the paint to paint in the swatch
     * @param xSize the x-dimension of the swatch (must be at least 1)
     * @param ySize the y-dimension of the swatch (must be at least 1)
     * @see #PaintSwatch(Paint)
     */
    public PaintSwatch(Paint paint, int xSize, int ySize) {
        this(paint, xSize, ySize, BLACK_BORDER);
    }
    
    
    /**
     * <p>This constructor provides the paint to paint in the swatch
     * and the border;
     * sets the dimensions of the swatch to (20, 20).</p>
     *
     * <p>If the given paint is <code>null</code>, sets the paint to
     * <code>Colors.transparent</code>.</p>
     *
     * <p>Sets no border if the given border is <code>null</code>.</p>
     *
     * @param paint  the paint to paint in the swatch
     * @param border the border for the swatch
     * @see #PaintSwatch(Paint)
     */
    public PaintSwatch(Paint paint, Border border) {
        this(paint, DEFAULT_SWATCH_SIZE, DEFAULT_SWATCH_SIZE, border);
    }
    
    
    /**
     * <p>This constructor provides the paint to paint in the swatch,
     * the x and y dimensions of the swatch, and the border.</p>
     *
     * <p>If the given paint is <code>null</code>, sets the paint to
     * <code>Colors.transparent</code>.</p>
     *
     * <p>Sets no border if the given border is <code>null</code>.</p>
     *
     * @param paint  the paint to paint in the swatch
     * @param xSize  the x-dimension of the swatch (must be at least 1)
     * @param ySize  the y-dimension of the swatch (must be at least 1)
     * @param border the border for the swatch
     * @see #PaintSwatch(Paint)
     */
    public PaintSwatch(Paint paint, int xSize, int ySize, Border border) {
        setPaint(paint);
        setSize(xSize, ySize);
        setBorder(border);
    }
    
    
    /**
     * <p>Returns the preferred size of the swatch based on the size
     * of the area to be painted and the swatch insets if any.</p>
     *
     * @return the preferred size
     */
    public Dimension getPreferredSize() {
        int w = xSize;
        int h = ySize;
        
        Insets insets = getInsets();
        
        if (insets != null) {
            w += insets.left + insets.right;
            h += insets.top + insets.bottom;
        }
        
        return new Dimension(w, h);
    }
    
    
    /**
     * <p>Returns the same dimension as <code>getPreferredSize</code>.</p>
     *
     * @return the minimum size
     */
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }
    
    
    /**
     * <p>Returns the same dimension as <code>getPreferredSize</code>.</p>
     *
     * @return the maximum size
     */
    public Dimension getMaximumSize() {
        return getPreferredSize();
    }
    
    
    /**
     * <p>Returns the same dimension as <code>getPreferredSize</code>.</p>
     *
     * @return the size
     */
    public Dimension getSize() {
        return getPreferredSize();
    }
    
    
    /**
     * <p>Sets the paint of the swatch.</p>
     *
     * <p>If the given paint is <code>null</code>, sets the paint to
     * <code>Colors.transparent</code>.</p>
     *
     * <p>Does nothing if the given paint is identical to the current
     * paint.</p>
     *
     * <p>Fires property change: SET_PAINT.</p>
     * 
     * @param paint the paint to set
     */
    public void setPaint(Paint paint) {
        if (paint == null)
            paint = Colors.transparent;
        
        if (this.paint == paint)
            return;
        
        this.paint = paint;
        
        repaint();
        
        firePropertyChange(SET_PAINT, null, null);
    }
    
    
    /**
     * <p>Returns the paint of the swatch.</p>
     *
     * @return the paint of the swatch
     */
    public Paint getPaint() {
        return paint;
    }
    
    
    /**
     * <p>Sets the size of the swatch by giving its x-size and y-size.
     * If either number is less than 1, that number is set to 1.
     * The size set by this method does not include the border insets.</p>
     *
     * <p>Does nothing if both parameters equal the corresponding current
     * settings.</p>
     *
     * <p>Note that the method <code>getPreferredSize</code> will return
     * dimension data that includes the border insets.</p>
     *
     * <p>Fires property change: SET_SIZE.</p>
     * 
     * @param xSize the x-dimension of the swatch (must be at least 1)
     * @param ySize the y-dimension of the swatch (must be at least 1)
     */
    public void setSize(int xSize, int ySize) {
        xSize = (xSize >= 1) ? xSize : 1;
        ySize = (ySize >= 1) ? ySize : 1;
        
        if ((this.xSize == xSize) && (this.ySize == ySize))
            return;
        
        this.xSize = xSize;
        this.ySize = ySize;
        
        this.area = new Rectangle2D.Double(0, 0, xSize, ySize);
        
        refreshComponent();
        
        firePropertyChange(SET_SIZE, null, null);
    }
    
    
    /**
     * <p>Returns the current x-dimension of the swatch
     * excluding the border insets.</p>
     *
     * @return the current x-dimension
     */
    public int getXSize() {
        return xSize;
    }
    
    
    /**
     * <p>Returns the current y-dimension of the swatch
     * excluding the border insets.</p>
     *
     * @return the current y-dimension
     */
    public int getYSize() {
        return ySize;
    }
    
    
    /**
     * <p>Calls the inherited <code>setBorder</code> method and
     * then fires the property change SET_BORDER.</p>
     * 
     * <p>Does nothing if the given border is <code>null</code> or
     * if the existing border is the same as the given border.</p>
     *
     * @param border the border for the swatch
     */
    public void setBorder(Border border) {
        if ((border == null) || (getBorder() == border))
            return;
        
        super.setBorder(border);
        
        refreshComponent();
        
        firePropertyChange(SET_BORDER, null, null);
    }
    
    
    /**
     * <p>Returns the icon width as <code>getXSize()</code>.</p>
     *
     * @return the icon width
     */
    public int getIconWidth() {
        return xSize;
    }
    
    
    /**
     * <p>Returns the icon height as <code>getYSize()</code>.</p>
     *
     * @return the icon height
     */
    public int getIconHeight() {
        return ySize;
    }
    
    
    /**
     * <p>Draw the swatch as an icon at the specified location.</p>
     *
     * <p>If the component parameter c is non-<code>null</code>,
     * if it is set to opaque, and if its background color is
     * non-<code>null</code>, then the background color will be
     * painted before the swatch is painted.  This will be
     * useful only if the swatch paint is partially transparent.
     * Otherwise, the background color will be painted over.</p>
     *
     * <p>Does nothing if the graphics context parameter g is
     * <code>null</code>.</p>
     *
     * @param c the component on which the icon will be painted
     * @param g the graphics context on which to paint
     * @param x the x-location
     * @param y the y-location
     */
    public void paintIcon(Component c, Graphics g, int x, int y) {
        if (g == null)
            return;
        
        Graphics2D h = (Graphics2D)g;
        
        Paint older = h.getPaint();
        h.translate(x, y);
        
        if (c != null) {
            if (c.isOpaque()) {
                Color color = c.getBackground();
                
                if (color != null) {
                    h.setPaint(color);
                    h.fill(area);
                }
            }
        }
        
        h.setPaint(paint);
        h.fill(area);
        
        h.setPaint(older);
        h.translate(-x, -y);
    }
    
    
    /**
     * <p>Overrides the inherited <code>paintComponent</code> method
     * to paint the swatch area with the encapsulated paint.</p>
     *
     * <p>If this component is set to opaque and if its background
     * color is non-<code>null</code>, then the background color
     * will be painted before the swatch is painted.  This will be
     * useful only if the swatch paint is partially transparent.
     * Otherwise, the background will be painted over.</p>
     *
     * <p>Does nothing if the graphics context parameter g is
     * <code>null</code>.</p>
     *
     * @param g the graphics context of the swatch
     */
    protected void paintComponent(Graphics g) {
        if (g == null)
            return;
        
        int x = 0;
        int y = 0;
        
        Insets insets = getInsets();
        
        if (insets != null) {
            x = insets.left;
            y = insets.top;
        }
        
        paintIcon(this, g, x, y);
    }
    
}
