/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.display; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.controls.XMLLoader; /** * A shape that implements Interactive. * @author Wolfgang Christian * @version 1.0 */ public class InteractiveShape extends AbstractInteractive implements Measurable { // fillColor uses color in the superclass public Color edgeColor = Color.red; // the edge color protected Shape shape; protected String shapeClass; protected double theta; protected double width, height; // an estimate of the shape's width and height protected double xoff, yoff; // offset from center protected boolean pixelSized = false; // width and height are fixed and given in pixels AffineTransform toPixels = new AffineTransform(); boolean enableMeasure = false; // enables the measure so that this object affects a drawing panel's scale /** * Constructs an InteractiveShape with the given coordinates. * * @param s * @param _x coordinate * @param _y coordinate */ public InteractiveShape(Shape s, double _x, double _y) { color = new Color(255, 128, 128, 128); // transparent light red fill color shape = s; x = _x; y = _y; if(shape==null) { return; } Rectangle2D bounds = shape.getBounds2D(); width = bounds.getWidth(); height = bounds.getHeight(); shapeClass = shape.getClass().getName(); shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(shape); } /** * Constructs an InteractiveShape at the origin. * @param s */ public InteractiveShape(Shape s) { this(s, 0, 0); } /** * Creates an interactive ellipse. * * @param x * @param y * @param w * @param h * @return InteractiveShape */ public static InteractiveShape createEllipse(double x, double y, double w, double h) { Shape shape = new Ellipse2D.Double(-w/2, -h/2, w, h); InteractiveShape is = new InteractiveShape(shape, x, y); is.width = w; is.height = h; return is; } /** * Creates an interactive circle. * * @param x * @param y * @param d the diameter * @return the interactive circle */ public static InteractiveShape createCircle(double x, double y, double d) { return createEllipse(x, y, d, d); } /** * Creates an interactive rectangle. * @param x * @param y * @param w * @param h * @return the interactive rectangle */ public static InteractiveShape createRectangle(double x, double y, double w, double h) { Shape shape = new Rectangle2D.Double(-w/2, -h/2, w, h); InteractiveShape is = new InteractiveShape(shape, x, y); is.width = w; is.height = h; return is; } /** * Creates an interactive triangle with a base parallel to the x axis. * * @param x * @param y * @param b base * @param h height * @return the interactive triangle */ public static InteractiveShape createTriangle(double x, double y, double b, double h) { GeneralPath path = new GeneralPath(); path.moveTo((float) (-b/2), (float) (-h/2)); path.lineTo((float) (+b/2), (float) (-h/2)); path.lineTo(0, (float) (h/2)); path.closePath(); Shape shape = path; InteractiveShape is = new InteractiveShape(shape, x, y); is.width = b; is.height = h; return is; } /** * Creates an interactive image. * @param x * @param y * @param image * @return the rectangle */ public static InteractiveShape createImage(Image image, double x, double y) { InteractiveImage is = new InteractiveImage(image, x, y); return is; } /** * Creates an interactive image. * @param x * @param y * @param text * @return the rectangle */ public static InteractiveShape createTextLine(double x, double y, String text) { InteractiveTextLine is = new InteractiveTextLine(text, x, y); return is; } /** * Creates an interactive arrow. * @param x * @param y * @param w base * @param h height * @return the arrow */ public static InteractiveShape createArrow(double x, double y, double w, double h) { InteractiveArrow is = new InteractiveArrow(x, y, w, h); is.setHeightDrag(false); is.setWidthDrag(false); is.hideBounds = true; return is; } /** * Creates an interactive arrow. * @param x * @param y * @param w base * @param h height * @return the arrow */ public static InteractiveShape createCenteredArrow(double x, double y, double w, double h) { InteractiveCenteredArrow is = new InteractiveCenteredArrow(x, y, w, h); is.setHeightDrag(false); is.setWidthDrag(false); is.hideBounds = true; return is; } /** * Creates an interactive square. * @param x * @param y * @param w * @return the interactive square */ public static InteractiveShape createSquare(double x, double y, double w) { Shape shape = new Rectangle2D.Double(-w/2, -w/2, w, w); return new InteractiveShape(shape, x, y); } /** * Transforms the shape. * * @param transformation AffineTransform */ public void transform(AffineTransform transformation) { shape = transformation.createTransformedShape(shape); } /** * Draws the shape. * * @param panel the drawing panel * @param g the graphics context */ public void draw(DrawingPanel panel, Graphics g) { Graphics2D g2 = ((Graphics2D) g); toPixels = panel.getPixelTransform(); Shape temp; if(pixelSized) { Point2D pt = new Point2D.Double(x, y); pt = toPixels.transform(pt, pt); // translate the shape to correct pixel coordinates temp = new AffineTransform(1, 0, 0, -1, -x+pt.getX()+xoff, y+pt.getY()-yoff).createTransformedShape(shape); temp = AffineTransform.getRotateInstance(-theta, pt.getX(), pt.getY()).createTransformedShape(temp); } else { temp = toPixels.createTransformedShape(shape); } g2.setPaint(color); g2.fill(temp); g2.setPaint(edgeColor); g2.draw(temp); } /** * Tests if the specified coordinates are inside the boundary of the * <code>Shape</code>. * @param x * @param y * @return <code>true</code> if the specified coordinates are inside * the <code>Shape</code> boundary; <code>false</code> * otherwise. */ public boolean contains(double x, double y) { if(shape.contains(x, y)) { return true; } return false; } /** * Gets the Java shape that is being drawn. * @return the shape */ public Shape getShape() { return shape; } /** * Transforms the shape using the given matrix. * @param mat double[][] */ public void tranform(double[][] mat) { shape = (new AffineTransform(mat[0][0], mat[1][0], mat[0][1], mat[1][1], mat[0][2], mat[1][2])).createTransformedShape(shape); } /** * Determines if the shape is enabled and if the given pixel coordinates are within the shape. * * @param panel DrawingPanel * @param xpix int * @param ypix int * @return boolean */ public boolean isInside(DrawingPanel panel, int xpix, int ypix) { if((shape==null)||!enabled) { return false; } if(shape.contains(panel.pixToX(xpix), panel.pixToY(ypix))) { return true; } return false; } /** * Sets the shape's drawing colors. * * The error bar color is set equal to the edge color. * * @param _fillColor * @param _edgeColor */ public void setMarkerColor(Color _fillColor, Color _edgeColor) { color = _fillColor; edgeColor = _edgeColor; } /** * Sets the rotation angle in radians. * * @param theta the new angle */ public void setTheta(double theta) { if(!pixelSized) { shape = AffineTransform.getRotateInstance(theta-this.theta, x, y).createTransformedShape(shape); } this.theta = theta; } /** * Sets the pixelSized flag. * * Pixel sized shapes use pixels for width and height. * * @param enable boolean */ public void setPixelSized(boolean enable) { this.pixelSized = enable; } /** * Gets the width of this shape. * * @return double */ public double getWidth() { return width; } /** * Sets the width of the shape to the given value. * * @param width double */ public void setWidth(double width) { width = Math.abs(width); double w = width/this.width; if(w<0.02) { return; } if(pixelSized) { shape = AffineTransform.getTranslateInstance(-x, -y).createTransformedShape(shape); shape = AffineTransform.getScaleInstance(w, 1).createTransformedShape(shape); shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(shape); } else { shape = AffineTransform.getTranslateInstance(-x, -y).createTransformedShape(shape); shape = AffineTransform.getRotateInstance(-theta).createTransformedShape(shape); shape = AffineTransform.getScaleInstance(w, 1).createTransformedShape(shape); shape = AffineTransform.getRotateInstance(theta).createTransformedShape(shape); shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(shape); } xoff *= w; this.width = width; } /** * Gets the height of this shape. * * @return double */ public double getHeight() { return height; } /** * Sets the height of the shape to the given value. * @param height double */ public void setHeight(double height) { height = Math.abs(height); double h = height/this.height; if(h<0.02) { return; } if(pixelSized) { shape = AffineTransform.getTranslateInstance(-x, -y).createTransformedShape(shape); shape = AffineTransform.getScaleInstance(1, h).createTransformedShape(shape); shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(shape); } else { shape = AffineTransform.getTranslateInstance(-x, -y).createTransformedShape(shape); shape = AffineTransform.getRotateInstance(-theta).createTransformedShape(shape); shape = AffineTransform.getScaleInstance(1, h).createTransformedShape(shape); shape = AffineTransform.getRotateInstance(theta).createTransformedShape(shape); shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(shape); } yoff *= h; this.height = height; } /** * Sets the drawing offset; * * Fixed size shapes cannot be offset. * * @param xoffset double * @param yoffset double */ public void setOffset(double xoffset, double yoffset) { if(!pixelSized) { // change the actual shape shape = AffineTransform.getTranslateInstance(x+xoffset, y+yoffset).createTransformedShape(shape); } xoff = xoffset; yoff = yoffset; } /** * Sets the x and y coordinates. * * @param _x * @param _y */ public void setXY(double _x, double _y) { shape = AffineTransform.getTranslateInstance(_x-x, _y-y).createTransformedShape(shape); x = _x; y = _y; } /** * Sets the x coordinate. * * @param _x */ public void setX(double _x) { shape = AffineTransform.getTranslateInstance(_x-x, 0).createTransformedShape(shape); x = _x; } /** * Sets the y coordinate. * * @param _y */ public void setY(double _y) { shape = AffineTransform.getTranslateInstance(0, _y-y).createTransformedShape(shape); y = _y; } /** * Gets a description of this object. * @return String */ public String toString() { return "InteractiveShape:"+"\n \t shape="+shapeClass+"\n \t x="+x+"\n \t y="+y+"\n \t width="+width+"\n \t height="+height+"\n \t theta="+theta; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ } /** * Enables the measured flag so that this arrow effects the scale of a drawing panel. * * @return minimum */ public void setMeasured(boolean _enableMeasure) { enableMeasure = _enableMeasure; } /** * Determines if this circle should effect the scale of a drawing panel. * * @return minimum */ public boolean isMeasured() { return enableMeasure; } /** * Implements measurable by getting the x center of the circle. * * @return minimum */ public double getXMin() { if(pixelSized) { return x-width/toPixels.getScaleX()/2; } return shape.getBounds2D().getX(); } /** * Implements measurable by getting the x center of the circle. * * @return maximum */ public double getXMax() { if(pixelSized) { return x+width/toPixels.getScaleX()/2; } return shape.getBounds2D().getX()+shape.getBounds2D().getWidth(); } /** * Implements measurable by getting the y center of the circle. * * @return minimum */ public double getYMin() { if(pixelSized) { return y-height/toPixels.getScaleY()/2; } return shape.getBounds2D().getY(); } /** * Implements measurable by getting the y center of the circle. * * @return maximum */ public double getYMax() { if(pixelSized) { return y+height/toPixels.getScaleY()/2; } return shape.getBounds2D().getY()+shape.getBounds2D().getHeight(); } /** * Gets the XML object loader for this class. * @return ObjectLoader */ public static XML.ObjectLoader getLoader() { return new InteractiveShapeLoader(); } /** * A class to save and load InteractiveShape in an XMLControl. */ protected static class InteractiveShapeLoader extends XMLLoader { public void saveObject(XMLControl control, Object obj) { InteractiveShape interactiveShape = (InteractiveShape) obj; control.setValue("geometry", interactiveShape.shapeClass); //$NON-NLS-1$ control.setValue("x", interactiveShape.x); //$NON-NLS-1$ control.setValue("y", interactiveShape.y); //$NON-NLS-1$ control.setValue("width", interactiveShape.width); //$NON-NLS-1$ control.setValue("height", interactiveShape.height); //$NON-NLS-1$ control.setValue("x offset", interactiveShape.xoff); //$NON-NLS-1$ control.setValue("y offset", interactiveShape.yoff); //$NON-NLS-1$ control.setValue("theta", interactiveShape.theta); //$NON-NLS-1$ control.setValue("pixel sized", interactiveShape.pixelSized); //$NON-NLS-1$ control.setValue("is enabled", interactiveShape.isEnabled()); //$NON-NLS-1$ control.setValue("is measured", interactiveShape.isMeasured()); //$NON-NLS-1$ control.setValue("color", interactiveShape.color); //$NON-NLS-1$ Shape shape = AffineTransform.getRotateInstance(-interactiveShape.theta, interactiveShape.x, interactiveShape.y).createTransformedShape(interactiveShape.shape); control.setValue("general path", shape); //$NON-NLS-1$ } public Object createObject(XMLControl control) { return new InteractiveShape(new Rectangle2D.Double(0, 0, 0, 0)); // default shape is a rectangle for now } protected Shape getShape(String type, double x, double y, double w, double h) { if(type.equals(Ellipse2D.Double.class.getName())) { return new Ellipse2D.Double(x-w/2, y-h/2, w, h); } else if(type.equals(Rectangle2D.Double.class.getName())) { return new Rectangle2D.Double(x-w/2, y-h/2, w, h); } else { return null; } } public Object loadObject(XMLControl control, Object obj) { InteractiveShape interactiveShape = (InteractiveShape) obj; String type = control.getString("geometry"); //$NON-NLS-1$ double x = control.getDouble("x"); //$NON-NLS-1$ double y = control.getDouble("y"); //$NON-NLS-1$ double theta = control.getDouble("theta"); //$NON-NLS-1$ Shape shape = getShape(type, x, y, control.getDouble("width"), control.getDouble("height")); //$NON-NLS-1$ //$NON-NLS-2$ if(shape==null) { // check for special geometry interactiveShape.shape = (GeneralPath) control.getObject("general path"); //$NON-NLS-1$ } else { interactiveShape.shape = shape; } // the shape should already be scaled so just set the instatance fields interactiveShape.width = control.getDouble("width"); //$NON-NLS-1$ interactiveShape.height = control.getDouble("height"); //$NON-NLS-1$ interactiveShape.xoff = control.getDouble("x offset"); //$NON-NLS-1$ interactiveShape.yoff = control.getDouble("y offset"); //$NON-NLS-1$ interactiveShape.x = x; interactiveShape.y = y; interactiveShape.setPixelSized(control.getBoolean("pixel sized")); //$NON-NLS-1$ interactiveShape.setEnabled(control.getBoolean("is enabled")); //$NON-NLS-1$ interactiveShape.setMeasured(control.getBoolean("is measured")); //$NON-NLS-1$ interactiveShape.color = (Color) control.getObject("color"); //$NON-NLS-1$ interactiveShape.setTheta(theta); // orient the shape return obj; } static { // needs this loader XML.setLoader(java.awt.geom.GeneralPath.class, new GeneralPathLoader()); } } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */