/*
 * @(#)ClippingWrapper.java    2.4.0   25 May 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.*;
import java.beans.*;

/**
 * <p>The class <code>ClippingWrapper</code> will wrap a <code>Paintable</code>
 * and a clipping <code>Shape</code> to obtain a <code>Paintable</code> that
 * clips the original <code>Paintable</code> to the <code>Shape</code>.</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, this class was updated to be consistent with
 * refinements to the <code>Paintable</code> interface.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.3
 */
public class ClippingWrapper
    extends AbstractPaintable
{
    /** The wrapped paintable. */
    private Paintable paintable = null;
    
    /** The clipping shape. */
    private Shape clippingshape = null;
    
    
    /**
     * The constructor that leaves the paintable and clipping shape
     * as <code>null</code>.
     *
     * @see #ClippingWrapper(Paintable)
     * @see #ClippingWrapper(Paintable, Shape)
     */
    public ClippingWrapper() {}
    
    
    /**
     * The constructor to specify the paintable to be wrapped.
     *
     * @param paintable the paintable to be wrapped
     * @see #ClippingWrapper()
     * @see #ClippingWrapper(Paintable, Shape)
     */
    public ClippingWrapper(Paintable paintable) {
        setPaintable(paintable);
    }
    
    
    /**
     * The constructor to specify the paintable to be wrapped
     * and the clipping shape.
     *
     * @param paintable     the paintable to be wrapped
     * @param clippingshape the clipping shape
     * @see #ClippingWrapper()
     * @see #ClippingWrapper(Paintable)
     */
    public ClippingWrapper(Paintable paintable, Shape clippingshape) {
        setPaintable(paintable);
        setClippingShape(clippingshape);
    }
    
    
    /**
     * <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 wrapped paintable is <code>null</code>, this method will
     * not paint.</p>
     *
     * <p>If the clipping shape is <code>null</code>, no clipping will be
     * done.</p>
     *
     * @param g the graphics context on which to paint
     */
    public final void originalPaint(Graphics g) {
        if ((paintable == null) || (g == null) || !isVisible())
            return;
        
        Graphics2D h = (Graphics2D) g.create();
        
        if (clippingshape != null)
            h.clip(clippingshape);
        
        paintable.paint(h);
    }
    
    
    /**
     * <p>Returns the actual bounds of the clipped paintable or
     * <code>null</code> if the result is effectively empty.</p>
     *
     * <p>Since the internal paintable may have a mutation or defaults
     * this method calls <code>getBounds2D</code> on the internal
     * paintable.  Hence any mutation or defaults set for the internal
     * paintable are respected.</p>
     *
     * <p>Then, if the clipping shape is non-<code>null</code>, its bounds
     * are intersected with the paintable bounds.  If the resulting bounds
     * are non-trivial, they are returned.  Otherwise, <code>null</code>
     * is returned.</p>
     *
     * @return a copy of the 2-dimensional bounds of the original paintable
     *         clipped to the wrapper
     * @see #getBounds2D()
     */
    public final XRect getActualBounds2D() {
        if (paintable == null)
            return null;
        
        // get paintable bounds
        XRect bounds = paintable.getBounds2D();
        
        // intersect paintable bounds with clipping bounds
        if (clippingshape != null) {
            XRect clippingbounds = new XRect(clippingshape.getBounds2D());
            bounds = bounds.createIntersectionRect(clippingbounds);
        }
        
        // test whether bounds are trivial
        double w = bounds.getWidth();
        double h = bounds.getHeight();
        
        if ((w <= 0) || (h <= 0))
            return null;
        
        return bounds;
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the
     * clipped 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
     *         clipped paintable
     */
    public final boolean originalContains(double x, double y) {
        if (paintable == null)
            return false;
        
        if (clippingshape == null)
            return paintable.contains(x, y);
        else
            return paintable.contains(x, y) && clippingshape.contains(x, y);
    }
    
    /**
     * <p>Sets the paintable to be wrapped.</p>
     *
     * <p>The paintable may be set to <code>null</code> to eliminate the
     * paintable.  This object will then paint nothing.</p>
     *
     * <p>Fires property change: SET_PAINTABLE.</p>
     * 
     * @param paintable the paintable to be wrapped
     * @see #getPaintable()
     */
    public final void setPaintable(Paintable paintable) {
        if (paintable == this.paintable)
            return;
        
        removeAndAddForwardingListener(this.paintable, paintable);
        
        this.paintable = paintable;
        
        firePropertyChange(SET_PAINTABLE, null, null);
    }
    
    
    /**
     * Returns the wrapped paintable.
     *
     * @return the wrapped paintable
     * @see #setPaintable(Paintable)
     */
    public final Paintable getPaintable() {
        return paintable;
    }
    
    
    /**
     * <p>Sets the clipping shape.</p>
     *
     * <p>The clipping shape may be set to <code>null</code> to eliminate
     * special clipping of the wrapped paintable.</p>
     *
     * <p>Fires property change: SET_CLIPPING_SHAPE.</p>
     * 
     * @param clippingshape the clipping shape
     * @see #getClippingShape()
     */
    public final void setClippingShape(Shape clippingshape) {
        if (clippingshape == this.clippingshape)
            return;
        
        removeAndAddForwardingListener(this.clippingshape, clippingshape);
        
        this.clippingshape = clippingshape;
        
        firePropertyChange(SET_CLIPPING_SHAPE, null, null);
    }
    
    
    /**
     * Returns the clipping shape.
     *
     * @return the clipping shape
     * @see #setClippingShape(Shape)
     */
    public final Shape getClippingShape() {
        return clippingshape;
    }
    
}
