swingx-master/swingx-painters/src/main/java/org/jdesktop/swingx/painter/RectanglePainter.java
/*
 * $Id$
 *
 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jdesktop.swingx.painter;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.awt.geom.RoundRectangle2D;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.painter.effects.AreaEffect;
import org.jdesktop.swingx.util.ShapeUtils;

/**
 * A painter which paints square and rounded rectangles
 * 
 * @author joshua.marinacci@sun.com
 */
@JavaBean
@SuppressWarnings("nls")
public class RectanglePainter extends AbstractAreaPainter<Object> {
    private boolean rounded = false;
    //private Insets insets = new Insets(0,0,0,0);
    private int roundWidth = 20;
    private int roundHeight = 20;
    private int width = -1;
    private int height = -1;
    //private double strokeWidth = 1;

    /** Creates a new instance of RectanglePainter */
    public RectanglePainter() {
        this(0, 0, 0, 0);
    }
    
    public RectanglePainter(int top, int left, int bottom, int right) {
        this(top, left, bottom, right, 0, 0);
    }

    public RectanglePainter(int top, int left, int bottom, int right,
            int roundWidth, int roundHeight) {
        this(top, left, bottom, right, roundWidth, roundHeight, roundWidth != 0 || roundHeight != 0, Color.RED, 1f, Color.BLACK);
    }

    public RectanglePainter(int top, int left, int bottom, int right, int roundWidth,
            int roundHeight, boolean rounded, Paint fillPaint, float strokeWidth, Paint borderPaint) {
        this(new Insets(top, left, bottom, right), -1, -1, roundWidth, roundHeight, rounded, fillPaint, strokeWidth, borderPaint);
    }

    public RectanglePainter(Color fillPaint, Color borderPaint) {
        this(fillPaint, borderPaint, 1f, null);
    }

    public RectanglePainter(Paint fillPaint, Paint borderPaint, float borderWidth, Style style) {
        this(new Insets(0, 0, 0, 0), -1, -1, 0, 0, false, fillPaint, borderWidth, borderPaint);
        setStyle(style);
        setDirty(false);
    }

    public RectanglePainter(int width, int height, int cornerRadius, Paint fillPaint) {
        this(new Insets(0, 0, 0, 0), width, height, cornerRadius, cornerRadius, true, fillPaint, 1f, Color.BLACK);
    }

    public RectanglePainter(Insets insets, int width, int height, int roundWidth, int roundHeight,
            boolean rounded, Paint fillPaint, float strokeWidth, Paint borderPaint) {
        this.width = width;
        this.height = height;
        setFillHorizontal(width < 0);
        setFillVertical(height < 0);
        setInsets(insets);
        this.roundWidth = roundWidth;
        this.roundHeight = roundHeight;
        this.rounded = rounded;
        this.setFillPaint(fillPaint);
        this.setBorderWidth(strokeWidth);
        this.setBorderPaint(borderPaint);
        this.setDirty(false);
    }

    /**
     * Indicates if the rectangle is rounded
     * @return if the rectangle is rounded
     */
    public boolean isRounded() {
        return rounded;
    }

    /**
     * sets if the rectangle should be rounded
     * @param rounded if the rectangle should be rounded
     */
    public void setRounded(boolean rounded) {
        boolean oldRounded = isRounded();
        this.rounded = rounded;
        setDirty(true);
        firePropertyChange("rounded",oldRounded,rounded);
    }

    /**
     * gets the round width of the rectangle
     * @return the current round width
     */
    public int getRoundWidth() {
        return roundWidth;
    }

    /**
     * sets the round width of the rectangle
     * @param roundWidth a new round width
     */
    public void setRoundWidth(int roundWidth) {
        int oldRoundWidth = getRoundWidth();
        this.roundWidth = roundWidth;
        setDirty(true);
        firePropertyChange("roundWidth",oldRoundWidth,roundWidth);
    }

    /**
     * gets the round height of the rectangle
     * @return the current round height
     */
    public int getRoundHeight() {
        return roundHeight;
    }

    /**
     * sets the round height of the rectangle
     * @param roundHeight a new round height
     */
    public void setRoundHeight(int roundHeight) {
        int oldRoundHeight = getRoundHeight();
        this.roundHeight = roundHeight;
        setDirty(true);
        firePropertyChange("roundHeight",oldRoundHeight,roundHeight);
    }


    /* ======== drawing code ============ */
    protected RectangularShape calculateShape(int width, int height) {
        Insets insets = getInsets();
        int x = insets.left;
        int y = insets.top;

        // use the position calcs from the super class
        Rectangle bounds = calculateLayout(this.width, this.height, width, height);
        if(this.width != -1 && !isFillHorizontal()) {
            width = this.width;
            x = bounds.x;
        }
        if(this.height != -1 && !isFillVertical()) {
            height = this.height;
            y = bounds.y;
        }

        if(isFillHorizontal()) {
            width = width - insets.left - insets.right;
        }
        if(isFillVertical()) {
            height = height - insets.top - insets.bottom;
        }


        RectangularShape shape = new Rectangle2D.Double(x, y, width, height);
        if(rounded) {
            shape = new RoundRectangle2D.Double(x, y, width, height, roundWidth, roundHeight);
        }
        return shape;
    }



    @Override
    protected void doPaint(Graphics2D g, Object component, int width, int height) {
        Shape shape = provideShape(g, component, width, height);
        switch (getStyle()) {
        case BOTH:
            drawBackground(g,shape,width,height);
            drawBorder(g,shape,width,height);
            break;
        case FILLED:
            drawBackground(g,shape,width,height);
            break;
        case OUTLINE:
            drawBorder(g,shape,width,height);
            break;
        }

        // background
        // border
        // leave the clip to support masking other painters
        ShapeUtils.mergeClip(g,shape);
        /*
        Area area = new Area(g.getClip());
        area.intersect(new Area(shape));//new Rectangle(0,0,width,height)));
        g.setClip(area);*/
        //g.setClip(shape);
    }

    private void drawBorder(Graphics2D g, Shape shape, int width, int height) {
        Paint p = getBorderPaint();
        if(isPaintStretched()) {
            p = calculateSnappedPaint(p, width, height);
        }

        g.setPaint(p);

        g.setStroke(new BasicStroke(getBorderWidth()));
        
        // shrink the border by 1 px
        if(shape instanceof Rectangle2D) {
            Rectangle2D rect = (Rectangle2D) shape;
            
            g.draw(new Rectangle2D.Double(rect.getX(), rect.getY(),
                    rect.getWidth()-1, rect.getHeight()-1));
        } else if(shape instanceof RoundRectangle2D) {
            RoundRectangle2D rect = (RoundRectangle2D) shape;
            
            g.draw(new RoundRectangle2D.Double(rect.getX(), rect.getY(),
                    rect.getWidth()-1, rect.getHeight()-1,
                    rect.getArcWidth(), rect.getArcHeight()));
        } else {
            g.draw(shape);
        }
    }

    private void drawBackground(Graphics2D g, Shape shape, int width, int height) {
        Paint p = getFillPaint();
        if(isPaintStretched()) {
            p = calculateSnappedPaint(p, width, height);
        }

        g.setPaint(p);

        g.fill(shape);
        if(getAreaEffects() != null) {
            for(AreaEffect ef : getAreaEffects()) {
                ef.apply(g, shape, width, height);
            }
        }
    }

    @Override
    protected Shape provideShape(Graphics2D g, Object comp, int width, int height) {
        return calculateShape(width,height);
    }
}

