/*
 * @(#)ShapePaintable.java    1.0  24 October 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 java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

/**
 * <p>A <code>ShapePaintable</code> creates an <code>AbstractPaintable</code>
 * using a <code>Shape</code> object and other related data.</p>
 *
 * <p>In 2.3.2, the method <code>getBounds2D</code> was modified to use the
 * default Bounds2D rectangle if that rectangle is non-<code>null</code>
 * before making any other tests or computations.</p>
 *
 * @author  Richard Rasala
 * @version 2.3.2
 * @since   2.3
 */
public class ShapePaintable extends AbstractPaintable
{
    
    /** Default fill paint: Color.black. */
    public static final Paint DEFAULT_FILLPAINT = Color.black;
    
    /** Default draw paint: Color.black. */
    public static final Paint DEFAULT_DRAWPAINT = Color.black;
    
    /** Default draw stroke: BasicStroke(1). */
    public static final Stroke DEFAULT_DRAWSTROKE = new BasicStroke(1);
    
    
    /** Bound property name for set shape. */
    public static final String SET_SHAPE       = "set.shape";
        
    /** Bound property name for set paint mode. */
    public static final String SET_PAINT_MODE  = "set.paint.mode";
        
    /** Bound property name for set fill paint. */
    public static final String SET_FILL_PAINT  = "set.fill.paint";
    
    /** Bound property name for set draw paint. */
    public static final String SET_DRAW_PAINT  = "set.draw.paint";
    
    /** Bound property name for set draw stroke. */
    public static final String SET_DRAW_STROKE = "set.draw.stroke";
    
    
    /** The shape. */
    private Shape shape = null;
    
    /** The paint mode. */
    private PaintMode paintmode = PaintMode.FILL;
    
    /** The fill paint. */
    private Paint fillpaint = DEFAULT_FILLPAINT;
    
    /** The draw paint. */
    private Paint drawpaint = DEFAULT_DRAWPAINT;
    
    /** The draw stroke. */
    private Stroke drawstroke = DEFAULT_DRAWSTROKE;
    
    
    /**
     * <p>The default constructor.</p>
     *
     * <p>Default settings:</p>
     * <code><ul>
     *   <li>Shape:       null</li>
     *   <li>Paint Mode:  FILL</li>
     *   <li>Fill Color:  Color.black</li>
     *   <li>Draw Color:  Color.black</li>
     *   <li>Draw Stroke: BasicStroke(1)</li>
     * </ul></code>
     *
     * @see #ShapePaintable(Shape)
     * @see #ShapePaintable(Shape, PaintMode)
     * @see #ShapePaintable(Shape, PaintMode, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint, Stroke)
     */
    public ShapePaintable()
    {
        this(null, null, null, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape.</p>
     *
     * @param shape the shape to paint
     * @see #ShapePaintable()
     * @see #ShapePaintable(Shape, PaintMode)
     * @see #ShapePaintable(Shape, PaintMode, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint, Stroke)
     */
    public ShapePaintable(Shape shape)
    {
        this(shape, null, null, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape and the paint mode.</p>
     *
     * <p>Any <code>null</code> parameters other than shape are ignored.</p>
     *
     * @param shape     the shape to paint
     * @param paintmode the paint mode
     * @see #ShapePaintable()
     * @see #ShapePaintable(Shape)
     * @see #ShapePaintable(Shape, PaintMode, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint, Stroke)
     */
    public ShapePaintable(Shape shape, PaintMode paintmode)
    {
        this(shape, paintmode, null, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape, the paint mode, and the fill paint.</p>
     *
     * <p>Any <code>null</code> parameters other than shape are ignored.</p>
     *
     * @param shape     the shape to paint
     * @param paintmode the paint mode
     * @param fillpaint the paint to use for filling
     * @see #ShapePaintable()
     * @see #ShapePaintable(Shape)
     * @see #ShapePaintable(Shape, PaintMode)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint, Stroke)
     */
    public ShapePaintable(Shape shape, PaintMode paintmode, Paint fillpaint)
    {
        this(shape, paintmode, fillpaint, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape, the paint mode, the fill paint,
     * and the draw paint.</p>
     *
     * <p>Any <code>null</code> parameters other than shape are ignored.</p>
     *
     * @param shape     the shape to paint
     * @param paintmode the paint mode
     * @param fillpaint the paint to use for filling
     * @param drawpaint the paint to use for drawing
     * @see #ShapePaintable()
     * @see #ShapePaintable(Shape)
     * @see #ShapePaintable(Shape, PaintMode)
     * @see #ShapePaintable(Shape, PaintMode, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint, Stroke)
     */
    public ShapePaintable
        (Shape shape, PaintMode paintmode, Paint fillpaint, Paint drawpaint)
    {
        this(shape, paintmode, fillpaint, drawpaint, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape, the paint mode, the fill paint,
     * the draw paint, and the drawstroke.</p>
     *
     * <p>Any <code>null</code> parameters other than shape are ignored.</p>
     *
     * @param shape      the shape to paint
     * @param paintmode  the paint mode
     * @param fillpaint  the paint to use for filling
     * @param drawpaint  the paint to use for drawing
     * @param drawstroke the stroke to use for drawing
     * @see #ShapePaintable()
     * @see #ShapePaintable(Shape)
     * @see #ShapePaintable(Shape, PaintMode)
     * @see #ShapePaintable(Shape, PaintMode, Paint)
     * @see #ShapePaintable(Shape, PaintMode, Paint, Paint)
     */
    public ShapePaintable
        (Shape     shape,
         PaintMode paintmode,
         Paint     fillpaint,
         Paint     drawpaint,
         Stroke    drawstroke)
    {
        setShape(shape);
        setPaintMode(paintmode);
        setFillPaint(fillpaint);
        setDrawPaint(drawpaint);
        setDrawStroke(drawstroke);
    }
    
    
    /**
     * <p>Paints onto a <code>Graphics</code> context using information from
     * this object.</p>
     *
     * <p>If the current shape or the graphics context is <code>null</code>,
     * this method will not paint.</p>
     *
     * @param g the graphics context on which to paint
     */
    public final void paint(Graphics g) {
        if ((g == null) || !isVisible())
            return;
        
        paintmode.paint(this, g);
    }
    
    
    /**
     * <p>Returns the bounds of the paintable based on the default settings or on
     * more detailed computations.</p>
     *
     * <p>If the value of <code>getDefaultBounds2D</code> is non-<code>null</code>,
     * then this value is returned.</p>
     *
     * <p>Otherwise, if the current shape is <code>null</code> or has zero width or
     * height, this method returns <code>new Rectangle2D.Double()</code>.</p>
     *
     * <p>Otherwise, this method computes the 2-dimensional bounds of the paint
     * region affected by the <code>paint</code> method and returns this rectangle.</p>
     *
     * @return the 2-dimensional bounds of the paint region
     * @see AbstractPaintable#getBounds2D()
     * @see AbstractPaintable#setDefaultBounds2D(Rectangle2D)
     * @see AbstractPaintable#getDefaultBounds2D()
     */
    public final Rectangle2D getBounds2D() {
        // get the default bounds
        Rectangle2D bounds = getDefaultBounds2D();
        
        if (bounds != null)
            return bounds;
        
        // null shape
        if (shape == null)
            return new Rectangle2D.Double();
        
        // get the actual bounds
        bounds = paintmode.getBounds2D(this);
        
        // check for zero width or height
        double x = bounds.getX();
        double y = bounds.getY();
        double w = bounds.getWidth();
        double h = bounds.getHeight();
        
        if ((w <= 0) || (h <= 0))
            return new Rectangle2D.Double(x, y, 0, 0);
        else
            return bounds;
    }
    
    
    /**
     * <p>If the current shape is <code>null</code>, this method returns
     * <code>new Point2D.Double()</code>.</p>
     *
     * <p>If the value of <code>getDefaultCenter</code> is non-<code>null</code>,
     * then this value is returned.</p>
     *
     * <p>Otherwise, this method returns the value computed by the method
     * inherited from its superclass.</p>
     * 
     * @return a copy of the center of the paint region
     * @see AbstractPaintable#getCenter()
     * @see AbstractPaintable#setDefaultCenter(Point2D)
     * @see AbstractPaintable#getDefaultCenter()
     */
    public final Point2D getCenter() {
        // null shape
        if (shape == null)
            return new Point2D.Double();
        
        // check default center
        Point2D center = getDefaultCenter();
        
        if (center != null)
            return center;
        
        // use inherited method
        return super.getCenter();
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the paintable.</p>
     *
     * <p>This method returns <code>false</code> if one or more of the following
     * conditions occurs:</p>
     *
     * <ul>
     *   <li>The point is not in the rectangle <code>getBounds2D</code>.</li>
     *   <li>The method <code>isVisible</code> returns <code>false</code>.</li>
     * </ul>
     *
     * @param  x the x-coordinate of the point
     * @param  y the y-coordinate of the point
     * @return whether or not a specified point is inside the paintable
     */
    public final boolean contains(double x, double y) {
        if (!possiblyContains(x, y))
            return false;
        
        return paintmode.contains(this, x, y);
    }
    
    
    /**
     * <p>Sets the shape of the paintable.</p>
     *
     * <p>The shape may be set to <code>null</code> to eliminate the shape.
     * The paintable will then paint nothing.</p>
     *
     * <p>Fires property change: SET_SHAPE.</p>
     * 
     * @param shape the shape to paint
     * @see #getShape()
     * @see #getStrokedShape()
     * @see #getOutline()
     */
    public final void setShape(Shape shape) {
        if (shape == this.shape)
            return;
        
        removeAndAddForwardingListener(this.shape, shape);
        
        this.shape = shape;
        
        firePropertyChange(SET_SHAPE, null, null);
    }
    
    
    /**
     * Returns the shape of the paintable.
     *
     * @return the shape
     * @see #setShape(Shape)
     * @see #getStrokedShape()
     * @see #getOutline()
     */
    public final Shape getShape() {
        return shape;
    }
    
    
    /**
     * <p>Returns the shape of the stroked boundary taking into account
     * the shape of the paintable and the draw stroke.</p>
     *
     * <p>If <code>getShape</code> returns <code>null</code>, then this
     * method returns <code>null</code> also.</p>
     *
     * @return the stroked shape
     * @see #setShape(Shape)
     * @see #getShape()
     * @see #getOutline()
     */
    public final Shape getStrokedShape() {
        if (shape == null)
            return null;
        
        return drawstroke.createStrokedShape(shape);
    }
    
    
    /**
     * <p>Returns the <code>Shape</code> that represents the outline of
     * the rendered paint region taking into account the
     * <code>PaintMode</code>.</p>
     *
     * <p>The following notes describe the behavior of this method in the
     * case of the paint modes defined in class <code>PaintMode</code>.</p>
     *
     * <p>If the <code>PaintMode</code> is <code>FILL</code> then returns
     * the same shape as <code>getShape</code>.</p>
     *
     * <p>If the <code>PaintMode</code> is <code>DRAW</code> then returns
     * the same shape as <code>getStrokedShape</code>.</p>
     *
     * <p>If the <code>PaintMode</code> is <code>FILL_DRAW</code> then
     * returns the union of the shapes returned by <code>getShape</code>
     * and <code>getStrokedShape</code>.</p>
     *
     * @return the rendered paint region
     * @see #setShape(Shape)
     * @see #getShape()
     * @see #getStrokedShape()
     */
    public Shape getOutline() {
        return paintmode.getOutline(this);
    }
    
    
    /**
     * <p>Sets the fill paint.</p>
     *
     * <p>A <code>null</code> parameter is ignored.</p>
     *
     * <p>Fires property change: SET_FILL_PAINT.</p>
     * 
     * @param fillpaint the paint to use for filling
     * @see #getFillPaint()
     */
    public final void setFillPaint(Paint fillpaint) {
        if ((fillpaint == null) || (fillpaint.equals(this.fillpaint)))
            return;
        
        removeAndAddForwardingListener(this.fillpaint, fillpaint);
        
        this.fillpaint = fillpaint;
        
        firePropertyChange(SET_FILL_PAINT, null, null);
    }
    
    
    /**
     * Returns the fill paint.
     *
     * @return the fill paint
     * @see #setFillPaint(Paint)
     */
    public final Paint getFillPaint() {
        return fillpaint;
    }
    
    
    /**
     * <p>Sets the draw paint.</p>
     *
     * <p>A <code>null</code> parameter is ignored.</p>
     *
     * <p>Fires property change: SET_DRAW_PAINT.</p>
     * 
     * @param drawpaint the paint to use for drawing
     * @see #getDrawPaint()
     */
    public final void setDrawPaint(Paint drawpaint) {
        if ((drawpaint == null) || (drawpaint.equals(this.drawpaint)))
            return;
        
        removeAndAddForwardingListener(this.drawpaint, drawpaint);
        
        this.drawpaint = drawpaint;
        
        firePropertyChange(SET_DRAW_PAINT, null, null);
    }
    
    
    /**
     * Returns the draw paint.
     *
     * @return the draw paint
     * @see #setDrawPaint(Paint)
     */
    public final Paint getDrawPaint() {
        return drawpaint;
    }
    
    
    /**
     * <p>Sets the draw stroke.</p>
     *
     * <p>A <code>null</code> parameter is ignored.</p>
     *
     * <p>Fires property change: SET_DRAW_STROKE.</p>
     * 
     * @param drawstroke the stroke to use for drawing
     * @see #getDrawStroke()
     */
    public final void setDrawStroke(Stroke drawstroke) {
        if ((drawstroke == null) || (drawstroke.equals(this.drawstroke)))
            return;
        
        removeAndAddForwardingListener(this.drawstroke, drawstroke);
        
        this.drawstroke = drawstroke;
        
        firePropertyChange(SET_DRAW_STROKE, null, null);
    }
    
    
    /**
     * Return the draw stroke.
     *
     * @see #setDrawStroke(Stroke)
     */
    public final Stroke getDrawStroke() {
        return drawstroke;
    }
    
    
    /**
     * <p>Sets the paint mode.</p>
     *
     * <p>A <code>null</code> parameter is ignored.</p>
     *
     * <p>Fires property change: SET_PAINT_MODE.</p>
     * 
     * @param paintmode the paint mode
     * @see #getPaintMode()
     * @see #setFillMode()
     * @see #setDrawMode()
     * @see #setFillDrawMode()
     */
    public final void setPaintMode(PaintMode paintmode) {
        if ((paintmode == null) || (paintmode.equals(this.paintmode)))
            return;
        
        removeAndAddForwardingListener(this.paintmode, paintmode);
        
        this.paintmode = paintmode;
        
        firePropertyChange(SET_PAINT_MODE, null, null);
    }
    
    /**
     * Returns the paint mode.
     *
     * @return the paint mode
     * @see #setPaintMode(PaintMode)
     * @see #setFillMode()
     * @see #setDrawMode()
     * @see #setFillDrawMode()
     */
    public final PaintMode getPaintMode() {
        return paintmode;
    }
    
    
    /**
     * <p>Sets the paint mode to fill the shape.</p>
     *
     * <p>Fires property change: SET_PAINT_MODE.</p>
     *
     * @see #setPaintMode(PaintMode)
     * @see #getPaintMode()
     * @see #setDrawMode()
     * @see #setFillDrawMode()
     */
    public final void setFillMode() {
        setPaintMode(PaintMode.FILL);
    }
    
    
    /**
     * <p>Sets the paint mode to draw the shape.</p>
     *
     * <p>Fires property change: SET_PAINT_MODE.</p>
     *
     * @see #setPaintMode(PaintMode)
     * @see #getPaintMode()
     * @see #setFillMode()
     * @see #setFillDrawMode()
     */
    public final void setDrawMode() {
        setPaintMode(PaintMode.DRAW);
    }
    
    
    /**
     * <p>Sets the paint mode to fill and draw the shape.</p>
     *
     * <p>Fires property change: SET_PAINT_MODE.</p>
     *
     * @see #setPaintMode(PaintMode)
     * @see #getPaintMode()
     * @see #setFillMode()
     * @see #setDrawMode()
     */
    public final void setFillDrawMode() {
        setPaintMode(PaintMode.FILL_DRAW);
    }
    
}
