/*
 * @(#)TileBox.java    2.4.0   15 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>The class <code>TileBox</code> will wrap an object by converting it
 * to a <code>Paintable</code> if it is not one already.</p>
 *
 * <p>This class extends <code>Tile</code> to add two features:</p>
 *
 * <ul>
 *   <li>An integer border to expand size of the bounding box if desired.</li>
 *   <li>A change to the <code>originalContains</code> method to return
 *       true if a point is within the bounding box not just within the
 *       paintable that is being wrapped.</li>
 * </ul>
 *
 * <p>Because of the second feature, it is much easier to drag a TileBox
 * using the mouse than to drag a corresponding Tile since less accuracy
 * is required of the user.</p> 
 *
 * @author Richard Rasala
 * @since   2.4.0
 */
public class TileBox
    extends Tile
{
    /** Bound property name for set border size. */
    public static final String SET_BORDER_SIZE = "set.border.size";
    
    
    /** The border size. */
    protected int border = 0;
    
    
    /**
     * The constructor that leaves the paintable unspecified;
     * the background paint is set to <code>null</code>;
     * the border is set to 0.
     */
    public TileBox() {}
    
    
    /**
     * The constructor to specify the object to be wrapped;
     * the background paint is set to <code>null</code>;
     * the border is set to 0.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     */
    public TileBox(Object o) {
        initializeTile(o, null, null);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped
     * and the background paint;
     * the border is set to 0.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param background the background paint for the tile
     */
    public TileBox(Object o, Paint background) {
        initializeTile(o, background, null);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped
     * and the initial mutator;
     * the background paint is set to <code>null</code>;
     * the border is set to 0.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param m          the initial mutator
     */
    public TileBox(Object o, AffineTransform m) {
        initializeTile(o, null, m);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped,
     * the background paint, and the initial mutator;
     * the border is set to 0.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param background the background paint for the tile
     * @param m          the initial mutator
     */
    public TileBox(Object o, Paint background, AffineTransform m) {
        initializeTile(o, background, m);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped;
     * the background paint is set to <code>null</code>;
     * the border is set to the given border.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param border     the border size for the tile box
     */
    public TileBox(Object o, int border) {
        initializeTileBox(o, null, null, border);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped
     * and the background paint;
     * the border is set to the given border.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param background the background paint for the tile
     * @param border     the border size for the tile box
     */
    public TileBox(Object o, Paint background, int border) {
        initializeTileBox(o, background, null, border);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped
     * and the initial mutator;
     * the background paint is set to <code>null</code>;
     * the border is set to the given border.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param m          the initial mutator
     * @param border     the border size for the tile box
     */
    public TileBox(Object o, AffineTransform m, int border) {
        initializeTileBox(o, null, m, border);
    }
    
    
    /**
     * The constructor to specify the object to be wrapped,
     * the background paint, and the initial mutator;
     * the border is set to the given border.
     *
     * @param o          the object to convert to a paintable to wrap in this tile
     * @param background the background paint for the tile
     * @param m          the initial mutator
     * @param border     the border size for the tile box
     */
    public TileBox(Object o, Paint background, AffineTransform m, int border) {
        initializeTileBox(o, background, m, border);
    }
    
    
    /**
     * The common initialization method.
     *
     * @param o          the object to convert to a paintable to wrap in this tile box
     * @param background the background paint for the tile box
     * @param m          the initial mutator
     * @param border     the border size for the tile box
     */
    public void initializeTileBox
        (Object o, Paint background, AffineTransform m, int border)
    {
        initializeTile(o, background, m);
        setBorderSize(border);
    }
    
    
    /**
     * <p>Returns the actual bounds of this tile box with border added
     * or <code>null</code> if the tile box is effectively empty.</p>
     *
     * <p>If the internal paintable is non-<code>null</code>, then
     * returns a copy of the 2-dimensional bounds of the internal
     * paintable expanded by the border but prior to any mutation
     * of this object.</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>
     *
     * @return a copy of the 2-dimensional bounds of the original paintable
     */
    public XRect getActualBounds2D() {
        if (paintable == null)
            return null;
        
        XRect rect = paintable.getBounds2D();
        
        if (border == 0)
            return rect;
        
        double x = rect.getX();
        double y = rect.getY();
        double w = rect.getWidth();
        double h = rect.getHeight();
        
        double b = border;
        double t = 2 * b;
        
        return new XRect(x - b, y - b, w + t, h + t);
    }
    
    
    /**
     * <p>Tests if a point specified by coordinates is inside the
     * bounding box of the tile box 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 bounding box
     */
    public boolean originalContains(double x, double y) {
        if (paintable == null)
            return false;
        
        return getActualBounds2D().contains(x, y);
    }
    
    
    /**
     * <p>Sets the border for the tile box, that is, the extra amount
     * to add to the bounding box on all sides.</p>
     *
     * <p>Forces the border to be &gt;= 0.</p>
     *
     * <p>Fires property change: SET_BORDER_SIZE.</p>
     * 
     * @param border     the border size for the tile box
     */
    public void setBorderSize(int border) {
        if (border < 0)
            border = 0;
        
        if (border == this.border)
            return;
        
        this.border = border;
        
        firePropertyChange(SET_BORDER_SIZE, null, null);
    }
    
    
    /**
     * <p>Gets the border for the tile box, that is, the extra amount
     * to add to the bounding box on all sides.</p>
     */
    public int getBorderSize() {
        return border;
    }
    
}
