/*
 * @(#)ShapePaintable.java    2.4.0   31 August 2005
 *
 * Copyright 2005
 * 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 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>
 *
 * <p>In 2.3.5, the class was refactored to be consistent with
 * the new <code>Paintable</code> interface and
 * the new <code>AbstractPaintable</code> class.
 *
 * <p>In 2.4.0, the class was updated to be consistent with refinements to
 * the <code>Paintable</code> interface.</p>
 *
 * <p>Also, in 2.4.0, the default paint mode was changed from FILL to DRAW
 * and the thickness for the stroke was changed to 2.  The reason for this
 * is aesthetic: when a shape is made up of disconnected sections, doing a
 * FILL operation can produce bizarre results; therefore it seems more
 * natural to make DRAW the default.</p>
 *
 * <p>Finally, in 2.4.0, constructors were added to use a <code>PathList</code>
 * to make a <code>ShapePaintable</code>.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.3
 */
public class ShapePaintable extends AbstractPaintable
{
    
    /** Default paint mode: PaintMode.DRAW. */
    public static final PaintMode DEFAULT_PAINTMODE = PaintMode.DRAW;
    
    /** 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(2). */
    public static final Stroke DEFAULT_DRAWSTROKE = new BasicStroke(2);
    
    
    /** 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 = DEFAULT_PAINTMODE;
    
    /** 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>
     *
     * <ul>
     *   <li><pre>Shape:       null</pre></li>
     *   <li><pre>Paint Mode:  DRAW</pre></li>
     *   <li><pre>Fill Color:  Color.black</pre></li>
     *   <li><pre>Draw Color:  Color.black</pre></li>
     *   <li><pre>Draw Stroke: BasicStroke(2)</pre></li>
     * </ul>
     *
     * <p>Other constructors using <code>Shape</code>:</p>
     *
     * <ul>
     *   <li><code>ShapePaintable(Shape)</code></li>
     *   <li><code>ShapePaintable(Shape, PaintMode)</code></li>
     *   <li><code>ShapePaintable(Shape, PaintMode, Paint)</code></li>
     *   <li><code>ShapePaintable(Shape, PaintMode, Paint, Paint)</code></li>
     *   <li><code>ShapePaintable(Shape, PaintMode, Paint, Paint, Stroke)</code></li>
     * </ul>
     *
     * <p>There are a similar set of constructors using <code>PathList</code>.</p>
     */
    public ShapePaintable()
    {
        this((Shape) null, null, null, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape.</p>
     *
     * @param shape the shape to paint
     */
    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 are ignored.</p>
     *
     * @param shape     the shape to paint
     * @param paintmode the paint mode
     */
    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 are ignored.</p>
     *
     * @param shape     the shape to paint
     * @param paintmode the paint mode
     * @param fillpaint the paint to use for filling
     */
    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 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
     */
    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 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
     */
    public ShapePaintable
        (Shape     shape,
         PaintMode paintmode,
         Paint     fillpaint,
         Paint     drawpaint,
         Stroke    drawstroke)
    {
        setShape(shape);
        setPaintMode(paintmode);
        setFillPaint(fillpaint);
        setDrawPaint(drawpaint);
        setDrawStroke(drawstroke);
    }
    
    
    /**
     * <p>The constructor to specify the shape via a path list.</p>
     *
     * @param pathlist the pathlist to make the shape to paint
     */
    public ShapePaintable(PathList pathlist)
    {
        this((Shape) ((pathlist == null) ? null : pathlist.makeShape()),
             null, null, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape via a path list
     * and the paint mode.</p>
     *
     * <p>Any <code>null</code> parameters are ignored.</p>
     *
     * @param pathlist  the pathlist to make the shape to paint
     * @param paintmode the paint mode
     */
    public ShapePaintable
        (PathList pathlist, PaintMode paintmode)
    {
        this((Shape) ((pathlist == null) ? null : pathlist.makeShape()),
            paintmode, null, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape via a path list,
     * the paint mode,
     * and the fill paint.</p>
     *
     * <p>Any <code>null</code> parameters are ignored.</p>
     *
     * @param pathlist  the pathlist to make the shape to paint
     * @param paintmode the paint mode
     * @param fillpaint the paint to use for filling
     */
    public ShapePaintable
        (PathList pathlist, PaintMode paintmode, Paint fillpaint)
    {
        this((Shape) ((pathlist == null) ? null : pathlist.makeShape()),
            paintmode, fillpaint, null, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape via a path list,
     * the paint mode,
     * the fill paint,
     * and the draw paint.</p>
     *
     * <p>Any <code>null</code> parameters are ignored.</p>
     *
     * @param pathlist  the pathlist to make 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
     */
    public ShapePaintable
        (PathList pathlist, PaintMode paintmode, Paint fillpaint, Paint drawpaint)
    {
        this((Shape) ((pathlist == null) ? null : pathlist.makeShape()),
            paintmode, fillpaint, drawpaint, null);
    }
    
    
    /**
     * <p>The constructor to specify the shape via a path list,
     * the paint mode,
     * the fill paint,
     * the draw paint,
     * and the drawstroke.</p>
     *
     * <p>Any <code>null</code> parameters are ignored.</p>
     *
     * @param pathlist   the pathlist to make 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
     */
    public ShapePaintable
        (PathList  pathlist,
         PaintMode paintmode,
         Paint     fillpaint,
         Paint     drawpaint,
         Stroke    drawstroke)
    {
        this((Shape) ((pathlist == null) ? null : pathlist.makeShape()),
            paintmode, fillpaint, drawpaint, drawstroke);
    }
    
    
    /**
     * <P>Paints onto a <CODE>Graphics</CODE> context using information
     * from this object but without the use of the mutator transform.</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 originalPaint(Graphics g) {
        if ((shape == null) || (g == null) || !isVisible())
            return;
        
        Graphics h = g.create();
        
        paintmode.originalPaint(this, h);
    }
    
    
    /**
     * <p>Returns the actual bounds of the original paintable or
     * <code>null</code> if the paintable is effectively empty.</p>
     */
    public final XRect getActualBounds2D() {
        // null shape
        if (shape == null)
            return null;
        
        // get the actual bounds
        XRect bounds = paintmode.getActualBounds2D(this);
        
        // check for zero width or height
        double w = bounds.getWidth();
        double h = bounds.getHeight();
        
        if ((w <= 0) || (h <= 0))
            return null;
        else
            return bounds;
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the
     * original paintable without mutation.</p>
     *
     * @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
     *         original paintable
     */
    public final boolean originalContains(double x, double y) {
        return paintmode.originalContains(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
     */
    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
     */
    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
     */
    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
     */
    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
     */
    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
     */
    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
     */
    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
     */
    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
     */
    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.
     */
    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
     */
    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
     */
    public final PaintMode getPaintMode() {
        return paintmode;
    }
    
    
    /**
     * <p>Sets the paint mode to fill the shape.</p>
     *
     * <p>Fires property change: SET_PAINT_MODE.</p>
     */
    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>
     */
    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>
     */
    public final void setFillDrawMode() {
        setPaintMode(PaintMode.FILL_DRAW);
    }
    
}
