All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.piccolo2d.extras.swt.PSWTPath Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
/*
 * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org
 * Copyright (c) 1998-2008, University of Maryland
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 * and the following disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
 * contributors may be used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.piccolo2d.extras.swt;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;

import org.piccolo2d.PNode;
import org.piccolo2d.nodes.PPath;
import org.piccolo2d.util.PAffineTransform;
import org.piccolo2d.util.PAffineTransformException;
import org.piccolo2d.util.PBounds;
import org.piccolo2d.util.PPaintContext;


/**
 * PSWTPath is a wrapper around a java.awt.geom.GeneralPath, with
 * workarounds for drawing shapes in SWT where necessary.
 * 
 * @version 1.0
 * @author Jesse Grosjean
 */
public class PSWTPath extends PNode {
    private static final long serialVersionUID = 1L;

    /**
     * The property name that identifies a change of this node's path. In any
     * property change event the new value will be a reference to this node's
     * path, but old value will always be null.
     */
    public static final String PROPERTY_SHAPE = "shape";
    private static final String PROPERTY_PATH = "path";
    private static final int PROPERTY_CODE_PATH = 1 << 18;
    private static final String PROPERTY_STROKE_PAINT = "strokePaint";
    private static final int PROPERTY_CODE_STROKE_PAINT = 1 << 16;

    private static final double BOUNDS_TOLERANCE = 0.01;
    private static final Rectangle2D.Float TEMP_RECTANGLE = new Rectangle2D.Float();
    private static final RoundRectangle2D.Float TEMP_ROUNDRECTANGLE = new RoundRectangle2D.Float();
    private static final Ellipse2D.Float TEMP_ELLIPSE = new Ellipse2D.Float();
    private static final Color DEFAULT_STROKE_PAINT = Color.black;
    private static final BasicStroke BASIC_STROKE = new BasicStroke();
    private static final float PEN_WIDTH = 1f;
    private static final float DEFAULT_TRANSPARENCY = 1.0f;

    private Paint strokePaint;

    private boolean updatingBoundsFromPath;
    private Shape origShape;
    private Shape shape;

    private PAffineTransform internalXForm;
    private AffineTransform inverseXForm;

    private double[] shapePts;
    private float transparency = DEFAULT_TRANSPARENCY;

    /**
     * Creates a path representing the rectangle provided.
     * 
     * @param x left of rectangle
     * @param y top of rectangle
     * @param width width of rectangle
     * @param height height of rectangle
     * @return created rectangle
     */
    public static PSWTPath createRectangle(final float x, final float y, final float width, final float height) {
        TEMP_RECTANGLE.setFrame(x, y, width, height);
        final PSWTPath result = new PSWTPath(TEMP_RECTANGLE);
        result.setPaint(Color.white);
        return result;
    }

    /**
     * Creates a path representing the rounded rectangle provided.
     * 
     * @param x left of rectangle
     * @param y top of rectangle
     * @param width width of rectangle
     * @param height height of rectangle
     * @param arcWidth width of the arc at the corners
     * @param arcHeight height of arc at the corners
     * @return created rounded rectangle
     */
    public static PSWTPath createRoundRectangle(final float x, final float y, final float width, final float height,
            final float arcWidth, final float arcHeight) {
        TEMP_ROUNDRECTANGLE.setRoundRect(x, y, width, height, arcWidth, arcHeight);
        final PSWTPath result = new PSWTPath(TEMP_ROUNDRECTANGLE);
        result.setPaint(Color.white);
        return result;
    }

    /**
     * Creates a path representing an ellipse that covers the rectangle
     * provided.
     * 
     * @param x left of rectangle
     * @param y top of rectangle
     * @param width width of rectangle
     * @param height height of rectangle
     * @return created ellipse
     */
    public static PSWTPath createEllipse(final float x, final float y, final float width, final float height) {
        TEMP_ELLIPSE.setFrame(x, y, width, height);
        final PSWTPath result = new PSWTPath(TEMP_ELLIPSE);
        result.setPaint(Color.white);
        return result;
    }

    /**
     * Creates a PPath for the poly-line for the given points.
     * 
     * @param points array of points for the point lines
     * 
     * @return created poly-line for the given points
     */
    public static PSWTPath createPolyline(final Point2D[] points) {
        final PSWTPath result = new PSWTPath();
        result.setPathToPolyline(points);
        result.setPaint(Color.white);
        return result;
    }

    /**
     * Creates a PPath for the poly-line for the given points.
     * 
     * @param xp array of x components of the points of the poly-lines
     * @param yp array of y components of the points of the poly-lines
     * 
     * @return created poly-line for the given points
     */
    public static PSWTPath createPolyline(final float[] xp, final float[] yp) {
        final PSWTPath result = new PSWTPath();
        result.setPathToPolyline(xp, yp);
        result.setPaint(Color.white);
        return result;
    }

    /**
     * Creates an empty PSWTPath.
     */
    public PSWTPath() {
        strokePaint = DEFAULT_STROKE_PAINT;
    }

    /**
     * Creates an SWTPath in the given shape with the default paint and stroke.
     * 
     * @param aShape the desired shape
     */
    public PSWTPath(final Shape aShape) {
        this();
        setShape(aShape);
    }

    // ****************************************************************
    // Stroke
    // ****************************************************************
    /**
     * Returns the paint to use when drawing the stroke of the shape.
     * 
     * @return path's stroke paint
     */
    public Paint getStrokePaint() {
        return strokePaint;
    }

    /**
     * Sets the paint to use when drawing the stroke of the shape.
     * 
     * @param strokeColor new stroke color
     */
    public void setStrokeColor(final Paint strokeColor) {
        final Paint old = strokePaint;
        strokePaint = strokeColor;
        invalidatePaint();
        firePropertyChange(PROPERTY_CODE_STROKE_PAINT, PROPERTY_STROKE_PAINT, old, strokePaint);
    }

    /**
     * Set the bounds of this path. This method works by scaling the path to fit
     * into the specified bounds. This normally works well, but if the specified
     * base bounds get too small then it is impossible to expand the path shape
     * again since all its numbers have tended to zero, so application code may
     * need to take this into consideration.
     * 
     * @param x new left position of bounds
     * @param y new top position of bounds
     * @param width the new width of the bounds
     * @param height the new height of the bounds
     */
    protected void internalUpdateBounds(final double x, final double y, final double width, final double height) {
        if (updatingBoundsFromPath) {
            return;
        }
        if (origShape == null) {
            return;
        }

        final Rectangle2D pathBounds = origShape.getBounds2D();

        if (Math.abs(x - pathBounds.getX()) / x < BOUNDS_TOLERANCE
                && Math.abs(y - pathBounds.getY()) / y < BOUNDS_TOLERANCE
                && Math.abs(width - pathBounds.getWidth()) / width < BOUNDS_TOLERANCE
                && Math.abs(height - pathBounds.getHeight()) / height < BOUNDS_TOLERANCE) {
            return;
        }

        if (internalXForm == null) {
            internalXForm = new PAffineTransform();
        }
        internalXForm.setToIdentity();
        internalXForm.translate(x, y);
        internalXForm.scale(width / pathBounds.getWidth(), height / pathBounds.getHeight());
        internalXForm.translate(-pathBounds.getX(), -pathBounds.getY());

        try {
            inverseXForm = internalXForm.createInverse();
        }
        catch (final Exception e) {
            throw new PAffineTransformException("unable to invert transform", internalXForm);
        }
    }

    /**
     * Returns true if path crosses the provided bounds. Takes visibility of
     * path into account.
     * 
     * @param aBounds bounds being tested for intersection
     * @return true if path visibly crosses bounds
     */
    public boolean intersects(final Rectangle2D aBounds) {
        if (super.intersects(aBounds)) {
            final Rectangle2D srcBounds;
            if (internalXForm == null) {
                srcBounds = aBounds;
            }
            else {
                srcBounds = new PBounds(aBounds);
                internalXForm.inverseTransform(srcBounds, srcBounds);
            }

            if (getPaint() != null && shape.intersects(srcBounds)) {
                return true;
            }
            else if (strokePaint != null) {
                return BASIC_STROKE.createStrokedShape(shape).intersects(srcBounds);
            }
        }
        return false;
    }

    /**
     * Recalculates the path's bounds by examining it's associated shape.
     */
    public void updateBoundsFromPath() {
        updatingBoundsFromPath = true;

        if (origShape == null) {
            resetBounds();
        }
        else {
            final Rectangle2D b = origShape.getBounds2D();

            // Note that this pen width code does not really work for SWT since
            // it assumes
            // that the pen width scales - in actuality it does not. However,
            // the fix would
            // be to have volatile bounds for all shapes which isn't a nice
            // alternative
            super.setBounds(b.getX() - PEN_WIDTH, b.getY() - PEN_WIDTH, b.getWidth() + 2 * PEN_WIDTH, b.getHeight() + 2
                    * PEN_WIDTH);
        }
        updatingBoundsFromPath = false;
    }

    // ****************************************************************
    // Painting
    // ****************************************************************
    /**
     * Paints the path on the context provided.
     * 
     * @param paintContext the context onto which the path will be painted
     */
    protected void paint(final PPaintContext paintContext) {
        final Paint p = getPaint();
        final SWTGraphics2D g2 = (SWTGraphics2D) paintContext.getGraphics();
        g2.setTransparency(transparency);

        if (internalXForm != null) {
            g2.transform(internalXForm);
        }

        if (p != null) {
            g2.setBackground((Color) p);
            fillShape(g2);
        }

        if (strokePaint != null) {
            g2.setColor((Color) strokePaint);
            drawShape(g2);
        }

        if (inverseXForm != null) {
            g2.transform(inverseXForm);
        }
    }

    private void drawShape(final SWTGraphics2D g2) {
        final double lineWidth = g2.getTransformedLineWidth();
        if (shape instanceof Rectangle2D) {
            g2.drawRect(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth, shapePts[3]
                    - lineWidth);
        }
        else if (shape instanceof Ellipse2D) {
            g2.drawOval(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth, shapePts[3]
                    - lineWidth);
        }
        else if (shape instanceof Arc2D) {
            g2.drawArc(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth, shapePts[3]
                    - lineWidth, shapePts[4], shapePts[5]);
        }
        else if (shape instanceof RoundRectangle2D) {
            g2.drawRoundRect(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth,
                    shapePts[3] - lineWidth, shapePts[4], shapePts[5]);
        }
        else {
            g2.draw(shape);
        }
    }

    private void fillShape(final SWTGraphics2D g2) {
        final double lineWidth = g2.getTransformedLineWidth();
        if (shape instanceof Rectangle2D) {
            g2.fillRect(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth, shapePts[3]
                    - lineWidth);
        }
        else if (shape instanceof Ellipse2D) {
            g2.fillOval(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth, shapePts[3]
                    - lineWidth);
        }
        else if (shape instanceof Arc2D) {
            g2.fillArc(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth, shapePts[3]
                    - lineWidth, shapePts[4], shapePts[5]);
        }
        else if (shape instanceof RoundRectangle2D) {
            g2.fillRoundRect(shapePts[0] + lineWidth / 2, shapePts[1] + lineWidth / 2, shapePts[2] - lineWidth,
                    shapePts[3] - lineWidth, shapePts[4], shapePts[5]);
        }
        else {
            g2.fill(shape);
        }
    }

    /**
     * Changes the underlying shape of this PSWTPath.
     * 
     * @param newShape new associated shape of this PSWTPath
     */
    public void setShape(final Shape newShape) {
        shape = cloneShape(newShape);
        origShape = shape;
        updateShapePoints(newShape);

        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, shape);
        updateBoundsFromPath();
        invalidatePaint();
    }

    /**
     * Updates the internal points used to draw the shape.
     * 
     * @param aShape shape to read points from
     */
    public void updateShapePoints(final Shape aShape) {
        if (aShape instanceof Rectangle2D) {
            if (shapePts == null || shapePts.length < 4) {
                shapePts = new double[4];
            }

            shapePts[0] = ((Rectangle2D) shape).getX();
            shapePts[1] = ((Rectangle2D) shape).getY();
            shapePts[2] = ((Rectangle2D) shape).getWidth();
            shapePts[3] = ((Rectangle2D) shape).getHeight();
        }
        else if (aShape instanceof Ellipse2D) {
            if (shapePts == null || shapePts.length < 4) {
                shapePts = new double[4];
            }

            shapePts[0] = ((Ellipse2D) shape).getX();
            shapePts[1] = ((Ellipse2D) shape).getY();
            shapePts[2] = ((Ellipse2D) shape).getWidth();
            shapePts[3] = ((Ellipse2D) shape).getHeight();
        }
        else if (aShape instanceof Arc2D) {
            if (shapePts == null || shapePts.length < 6) {
                shapePts = new double[6];
            }

            shapePts[0] = ((Arc2D) shape).getX();
            shapePts[1] = ((Arc2D) shape).getY();
            shapePts[2] = ((Arc2D) shape).getWidth();
            shapePts[3] = ((Arc2D) shape).getHeight();
            shapePts[4] = ((Arc2D) shape).getAngleStart();
            shapePts[5] = ((Arc2D) shape).getAngleExtent();
        }
        else if (aShape instanceof RoundRectangle2D) {
            if (shapePts == null || shapePts.length < 6) {
                shapePts = new double[6];
            }

            shapePts[0] = ((RoundRectangle2D) shape).getX();
            shapePts[1] = ((RoundRectangle2D) shape).getY();
            shapePts[2] = ((RoundRectangle2D) shape).getWidth();
            shapePts[3] = ((RoundRectangle2D) shape).getHeight();
            shapePts[4] = ((RoundRectangle2D) shape).getArcWidth();
            shapePts[5] = ((RoundRectangle2D) shape).getArcHeight();
        }
        else {
            shapePts = SWTShapeManager.shapeToPolyline(shape);
        }
    }

    /**
     * Clone's the shape provided.
     * 
     * @param aShape shape to be cloned
     * 
     * @return a cloned version of the provided shape
     */
    public Shape cloneShape(final Shape aShape) {
        if (aShape instanceof Rectangle2D) {
            return new PBounds((Rectangle2D) aShape);
        }
        else if (aShape instanceof Ellipse2D) {
            final Ellipse2D e2 = (Ellipse2D) aShape;
            return new Ellipse2D.Double(e2.getX(), e2.getY(), e2.getWidth(), e2.getHeight());
        }
        else if (aShape instanceof Arc2D) {
            final Arc2D a2 = (Arc2D) aShape;
            return new Arc2D.Double(a2.getX(), a2.getY(), a2.getWidth(), a2.getHeight(), a2.getAngleStart(), a2
                    .getAngleExtent(), a2.getArcType());
        }
        else if (aShape instanceof RoundRectangle2D) {
            final RoundRectangle2D r2 = (RoundRectangle2D) aShape;
            return new RoundRectangle2D.Double(r2.getX(), r2.getY(), r2.getWidth(), r2.getHeight(), r2.getArcWidth(),
                    r2.getArcHeight());
        }
        else if (aShape instanceof Line2D) {
            final Line2D l2 = (Line2D) aShape;
            return new Line2D.Double(l2.getP1(), l2.getP2());
        }
        else {
            final GeneralPath aPath = new GeneralPath();
            aPath.append(aShape, false);
            return aPath;
        }
    }

    /**
     * Resets the path to a rectangle with the dimensions and position provided.
     * 
     * @param x left of the rectangle
     * @param y top of te rectangle
     * @param width width of the rectangle
     * @param height height of the rectangle
     */
    public void setPathToRectangle(final float x, final float y, final float width, final float height) {
        TEMP_RECTANGLE.setFrame(x, y, width, height);
        setShape(TEMP_RECTANGLE);
    }

    /**
     * Resets the path to a rectangle with the dimensions and position provided.
     * 
     * @param x left of the rectangle
     * @param y top of te rectangle
     * @param width width of the rectangle
     * @param height height of the rectangle
     * @param arcWidth width of arc in the corners of the rectangle
     * @param arcHeight height of arc in the corners of the rectangle
     */
    public void setPathToRoundRectangle(final float x, final float y, final float width, final float height,
            final float arcWidth, final float arcHeight) {
        TEMP_ROUNDRECTANGLE.setRoundRect(x, y, width, height, arcWidth, arcHeight);
        setShape(TEMP_ROUNDRECTANGLE);
    }

    /**
     * Resets the path to an ellipse positioned at the coordinate provided with
     * the dimensions provided.
     * 
     * @param x left of the ellipse
     * @param y top of the ellipse
     * @param width width of the ellipse
     * @param height height of the ellipse
     */
    public void setPathToEllipse(final float x, final float y, final float width, final float height) {
        TEMP_ELLIPSE.setFrame(x, y, width, height);
        setShape(TEMP_ELLIPSE);
    }

    /**
     * Sets the path to a sequence of segments described by the points.
     * 
     * @param points points to that lie along the generated path
     */
    public void setPathToPolyline(final Point2D[] points) {
        final GeneralPath path = new GeneralPath();
        path.reset();
        path.moveTo((float) points[0].getX(), (float) points[0].getY());
        for (int i = 1; i < points.length; i++) {
            path.lineTo((float) points[i].getX(), (float) points[i].getY());
        }
        setShape(path);
    }

    /**
     * Sets the path to a sequence of segments described by the point components
     * provided.
     * 
     * @param xp the x components of the points along the path
     * @param yp the y components of the points along the path
     */
    public void setPathToPolyline(final float[] xp, final float[] yp) {
        final GeneralPath path = new GeneralPath();
        path.reset();
        path.moveTo(xp[0], yp[0]);
        for (int i = 1; i < xp.length; i++) {
            path.lineTo(xp[i], yp[i]);
        }
        setShape(path);
    }

    /**
     * Return the center of this SWT path node, based on its bounds.
     *
     * @return the center of this SWT path node, based on its bounds
     */
    public Point2D getCenter() {                                                                                                              
        PBounds bounds = getBoundsReference();                                                                                                        
        return new Point2D.Double(bounds.x + (bounds.width / 2.0), bounds.y + (bounds.height / 2.0));
    } 

    /**
     * Return the transparency for this SWT path node.
     *
     * @return the transparency for this SWT path node
     */
    public float getTransparency() {
        return transparency;
    }

    /**
     * Set the transparency for this SWT path node to transparency.
     *
     * @param transparency transparency, must be between 0.0f and 1.0f inclusive
     */
    public void setTransparency(final float transparency) {
        if ((transparency < 0.0f) || (transparency > 1.0f)) {
            throw new IllegalArgumentException("transparency must be between 0.0f and 1.0f inclusive");
        }
        this.transparency = transparency;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy