/*
* 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/>
*/
/*
* The org.opensourcephysics.media.core package defines the Open Source Physics
* media framework for working with video and other media.
*
* Copyright (c) 2014 Douglas Brown and Wolfgang Christian.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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
*
* For additional information and documentation on Open Source Physics,
* please see <http://www.opensourcephysics.org/>.
*/
package org.opensourcephysics.media.core;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.event.SwingPropertyChangeSupport;
import org.opensourcephysics.display.DrawingPanel;
import org.opensourcephysics.display.Interactive;
/**
* This is a Point2D that implements the Interactive and
* Trackable interfaces with additional utility methods. Classes
* that extend TPoint should interpret the stored x and y values
* as image coordinates. TPoint has an empty draw method.
*
* @author Douglas Brown
* @version 1.0
*/
public class TPoint extends Point2D.Double implements Interactive, Trackable {
// static fields
protected static boolean coordsVisibleInMouseBox = true;
protected static VidCartesianCoordinateStringBuilder coordinateStrBuilder = new VidCartesianCoordinateStringBuilder();
// instance fields
protected boolean enabled = true;
protected boolean trackEditTrigger = false;
protected boolean coordsEditTrigger = false;
protected boolean stepEditTrigger = false;
protected boolean isAdjusting = false;
protected Point screenPt;
protected Point2D worldPt;
protected PropertyChangeSupport support;
protected TPoint attachedTo;
/**
* Constructs a TPoint with image coordinates (0, 0).
*/
public TPoint() {
this(0, 0);
}
/**
* Constructs a TPoint with specified image coordinates.
*
* @param x the x coordinate
* @param y the y coordinate
*/
public TPoint(double x, double y) {
super(x, y);
}
/**
* Constructs a TPoint with image coordinates specified by
* a Point2D (commonly another TPoint).
*
* @param point the Point2D
*/
public TPoint(Point2D point) {
this(point.getX(), point.getY());
}
/**
* Empty draw method. This method should be overridden by subclasses.
*
* @param panel the drawing panel requesting the drawing
* @param _g the graphics context on which to draw
*/
public void draw(DrawingPanel panel, Graphics _g) {
/** implemented in subclasses */
}
/**
* Returns null. This method should be overridden by subclasses.
*
* @param panel the drawing panel
* @param xpix the x pixel position on the panel
* @param ypix the y pixel position on the panel
* @return null
*/
public Interactive findInteractive(DrawingPanel panel, int xpix, int ypix) {
return null;
}
/**
* Sets the x position in imagespace.
*
* @param x the x position
*/
public void setX(double x) {
setXY(x, getY());
}
/**
* Sets the y position in imagespace.
*
* @param y the y position
*/
public void setY(double y) {
setXY(getX(), y);
}
/**
* Sets the x and y positions in imagespace.
*
* @param x the x position
* @param y the y position
*/
public void setXY(double x, double y) {
setLocation(x, y);
}
/**
* Overrides Point2D.Double setLocation method.
*
* @param x the x position
* @param y the y position
*/
public void setLocation(double x, double y) {
if((getX()==x)&&(getY()==y)) {
return;
}
super.setLocation(x, y);
if(support!=null) {
support.firePropertyChange("location", null, this); //$NON-NLS-1$
}
}
/**
* Gets the frame number this TPoint uses for coordinate system
* transforms and other identification. Step-based subclasses can
* override this method to report their own frame number.
*
* @param vidPanel the video panel
* @return the frame number
*/
public int getFrameNumber(VideoPanel vidPanel) {
return vidPanel.getFrameNumber();
}
/**
* Gets the screen position of this TPoint on the specified VideoPanel.
*
* @param vidPanel the video panel
* @return the screen point
*/
public Point getScreenPosition(VideoPanel vidPanel) {
AffineTransform toScreen = vidPanel.getPixelTransform();
if(!vidPanel.isDrawingInImageSpace()) {
int n = getFrameNumber(vidPanel);
toScreen.concatenate(vidPanel.getCoords().getToWorldTransform(n));
}
if(screenPt==null) {
screenPt = new Point();
}
toScreen.transform(this, screenPt);
return screenPt;
}
/**
* Sets the screen position of this TPoint on the specified VideoPanel.
*
* @param x the screen x coordinate
* @param y the screen y coordinate
* @param vidPanel the video panel
*/
public void setScreenPosition(int x, int y, VideoPanel vidPanel) {
if(screenPt==null) {
screenPt = new Point();
}
if(worldPt==null) {
worldPt = new Point2D.Double();
}
screenPt.setLocation(x, y);
AffineTransform toScreen = vidPanel.getPixelTransform();
if(!vidPanel.isDrawingInImageSpace()) {
int n = getFrameNumber(vidPanel);
toScreen.concatenate(vidPanel.getCoords().getToWorldTransform(n));
}
try {
toScreen.inverseTransform(screenPt, worldPt);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
setXY(worldPt.getX(), worldPt.getY());
}
/**
* Sets the screen position of this TPoint. This can be overridden
* by subclasses to change the behavior based on input event methods that
* report the state of keys like shift, control, alt, etc.
*
* @param x the screen x coordinate
* @param y the screen y coordinate
* @param vidPanel the video panel
* @param e the input event making the request
*/
public void setScreenPosition(int x, int y, VideoPanel vidPanel, InputEvent e) {
setScreenPosition(x, y, vidPanel);
}
/**
* Gets the world position of this TPoint on the specified VideoPanel.
*
* @param vidPanel the video panel
* @return the world position
*/
public Point2D getWorldPosition(VideoPanel vidPanel) {
int n = getFrameNumber(vidPanel);
AffineTransform at = vidPanel.getCoords().getToWorldTransform(n);
if(worldPt==null) {
worldPt = new Point2D.Double();
}
return at.transform(this, worldPt);
}
/**
* Sets the world position of this TPoint on the specified VideoPanel.
*
* @param x the world x coordinate
* @param y the world y coordinate
* @param vidPanel the video panel
*/
public void setWorldPosition(double x, double y, VideoPanel vidPanel) {
int n = getFrameNumber(vidPanel);
AffineTransform at = vidPanel.getCoords().getToWorldTransform(n);
if(worldPt==null) {
worldPt = new Point2D.Double();
}
worldPt.setLocation(x, y);
try {
at.inverseTransform(worldPt, worldPt);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
setXY(worldPt.getX(), worldPt.getY());
}
/**
* Shows the world coordinates of this TPoint in the mouse box of
* the specified VideoPanel.
*
* @param vidPanel the video panel
*/
public void showCoordinates(VideoPanel vidPanel) {
if(coordsVisibleInMouseBox) {
getWorldPosition(vidPanel);
String s = coordinateStrBuilder.getCoordinateString(worldPt.getX(), worldPt.getY());
vidPanel.setMessage(s, 0);
}
}
/**
* Attaches this TPoint to another so it follows it.
*
* @param p the point to attach to
* @return true if a change has occurred
*/
public boolean attachTo(TPoint p) {
if (p==null || p==this) return false;
if (p==attachedTo && p.getX()==this.getX() && p.getY()==this.getY()) return false;
// detach this from any previous point
detach();
// attach by adding property change listener
attachedTo = p;
p.addPropertyChangeListener("location", new Follower()); //$NON-NLS-1$
setXY(p.getX(), p.getY());
return true;
}
/**
* Detaches this TPoint.
*/
public void detach() {
if (attachedTo!=null) {
PropertyChangeListener[] listeners = attachedTo.support.getPropertyChangeListeners("location"); //$NON-NLS-1$
for (PropertyChangeListener next: listeners) {
if (next instanceof Follower) {
Follower follower = (Follower)next;
if (follower.getTarget()==this)
attachedTo.removePropertyChangeListener("location", next); //$NON-NLS-1$
}
}
attachedTo = null;
}
}
/**
* Determines if this point is attached to another.
*
* @return true if attached
*/
public boolean isAttached() {
return attachedTo!=null;
}
/**
* Sets whether this responds to mouse hits.
*
* @param enabled <code>true</code> if this responds to mouse hits.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Gets whether this responds to mouse hits.
*
* @return <code>true</code> if this responds to mouse hits.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Sets the trackEditTrigger property. A trackEditTrigger triggers
* undoable track edits when moved.
*
* @param edit <code>true</code> to make this a trackEditTrigger.
*/
public void setTrackEditTrigger(boolean edit) {
trackEditTrigger = edit;
}
/**
* Reports whether this is a trackEditTrigger. A trackEditTrigger triggers
* undoable track edits when moved.
*
* @return <code>true</code> if this is a trackEditTrigger.
*/
public boolean isTrackEditTrigger() {
return trackEditTrigger;
}
/**
* Sets the coordsEditTrigger property. A coordsEditTrigger triggers
* undoable coords edits when moved.
*
* @param edit <code>true</code> to make this a coordsEditTrigger.
*/
public void setCoordsEditTrigger(boolean edit) {
coordsEditTrigger = edit;
}
/**
* Reports whether this is a coordsEditTrigger. A coordsEditTrigger triggers
* undoable coords edits when moved.
*
* @return <code>true</code> if this is a coordsEditTrigger.
*/
public boolean isCoordsEditTrigger() {
return coordsEditTrigger;
}
/**
* Sets the stepEditTrigger property. A stepEditTrigger triggers
* undoable step edits when moved.
*
* @param stepEditTrigger <code>true</code> to make this a stepEditTrigger.
*/
public void setStepEditTrigger(boolean stepEditTrigger) {
this.stepEditTrigger = stepEditTrigger;
}
/**
* Reports whether this is a stepEditTrigger. A stepEditTrigger triggers
* undoable step edits when moved.
*
* @return <code>true</code> if this is a stepEditTrigger.
*/
public boolean isStepEditTrigger() {
return stepEditTrigger;
}
/**
* Gets the screen bounds of this object.
*
* @param vidPanel the video panel
* @return the bounding rectangle
*/
public Rectangle getBounds(VideoPanel vidPanel) {
return null;
}
/**
* Reports whether information is available to set min/max values.
*
* @return <code>false</code>
*/
public boolean isMeasured() {
return false;
}
/**
* Gets the minimum x needed to draw this object.
*
* @return minimum x
*/
public double getXMin() {
return getX();
}
/**
* Gets the maximum x needed to draw this object.
*
* @return maximum x
*/
public double getXMax() {
return getX();
}
/**
* Gets the minimum y needed to draw this object.
*
* @return minimum y
*/
public double getYMin() {
return getY();
}
/**
* Gets the maximum y needed to draw this object.
*
* @return maximum y
*/
public double getYMax() {
return getY();
}
/**
* Returns the angle measured ccw from the positive x-axis to the line
* between this TPoint and the specified coordinates.
*
* @param x the x coordinate
* @param y the x coordinate
* @return the angle in radians
*/
public double angle(double x, double y) {
return Math.atan2(y-getY(), x-getX());
}
/**
* Returns the angle measured ccw from the positive x-axis to a line
* that goes from this TPoint to the specified Point2D.
*
* @param pt the Point2D
* @return the angle in radians
*/
public double angle(Point2D pt) {
return Math.atan2(pt.getY()-getY(), pt.getX()-getX());
}
/**
* Returns the sine of the angle measured ccw from the positive x-axis
* to the line between this TPoint and the specified coordinates.
*
* @param x the x coordinate
* @param y the x coordinate
* @return the sine of the angle
*/
public double sin(double x, double y) {
return(getY()-y)/distance(x, y);
}
/**
* Returns the sine of the angle measured ccw from the positive x-axis
* to the line between this TPoint and the specified Point2D.
*
* @param pt the Point2D
* @return the sine of the angle
*/
public double sin(Point2D pt) {
return(getY()-pt.getY())/distance(pt);
}
/**
* Returns the cosine of the angle measured ccw from the positive x-axis
* to the line between this TPoint and the specified coordinates.
*
* @param x the x coordinate
* @param y the x coordinate
* @return the cosine of the angle
*/
public double cos(double x, double y) {
return(x-getX())/distance(x, y);
}
/**
* Returns the cosine of the angle measured ccw from the positive x-axis
* to the line between this TPoint and the specified Point2D.
*
* @param pt the Point2D
* @return the cosine of the angle
*/
public double cos(Point2D pt) {
return(pt.getX()-getX())/distance(pt);
}
/**
* Centers this TPoint between the two specified points. Note that
* this method does not call setXY.
*
* @param pt1 the first Point2D
* @param pt2 the second Point2D
*/
public void center(Point2D pt1, Point2D pt2) {
double x = (pt1.getX()+pt2.getX())/2.0;
double y = (pt1.getY()+pt2.getY())/2.0;
setLocation(x, y);
}
/**
* Translates this TPoint by the specified displacement.
*
* @param dx the x displacement in imagespace
* @param dy the y displacement in imagespace
*/
public void translate(double dx, double dy) {
setXY(getX()+dx, getY()+dy);
}
/**
* Sets the adjusting flag. This is normally set by the mouse handler
* when this point is dragged or stops being dragged.
*
* @param adjusting true if being dragged
*/
public void setAdjusting(boolean adjusting) {
isAdjusting = adjusting;
}
/**
* Gets the adjusting flag.
*
* @return true if adjusting
*/
public boolean isAdjusting() {
return isAdjusting;
}
/**
* Adds a PropertyChangeListener.
*
* @param listener the object requesting property change notification
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
if(support==null) {
support = new SwingPropertyChangeSupport(this);
}
support.addPropertyChangeListener(listener);
}
/**
* Adds a PropertyChangeListener for a specified property.
*
* @param property the name of the property of interest to the listener
* @param listener the object requesting property change notification
*/
public void addPropertyChangeListener(String property, PropertyChangeListener listener) {
if(support==null) {
support = new SwingPropertyChangeSupport(this);
}
support.addPropertyChangeListener(property, listener);
}
/**
* Removes a PropertyChangeListener.
*
* @param listener the listener requesting removal
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
if(support!=null) {
support.removePropertyChangeListener(listener);
}
}
/**
* Removes a PropertyChangeListener for a specified property.
*
* @param property the name of the property
* @param listener the listener to remove
*/
public void removePropertyChangeListener(String property, PropertyChangeListener listener) {
support.removePropertyChangeListener(property, listener);
}
/**
* Returns a String describing this.
*
* @return a descriptive string
*/
public String toString() {
return "TPoint ["+x+", "+y+"]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Compares this to the specified object.
*
* @param object the object
* @return <code>true</code> if this equals the specified object
*/
public boolean equals(Object obj) {
if (obj==this) return true;
if((obj==null) || (obj.getClass()!=this.getClass()))
return false;
TPoint p = (TPoint)obj;
return p.getX()==getX() && p.getY()==getY()
&& p.screenPt==screenPt && p.worldPt==worldPt;
}
/**
* Sets the position of this point on the line between end1 and end2
* nearest the specified screen position.
*
* @param xScreen the x screen position
* @param yScreen the y screen position
* @param vidPanel the videoPanel drawing this point
* @param end1 one end of the line
* @param end2 the other end of the line
*/
public void setPositionOnLine(int xScreen, int yScreen, VideoPanel vidPanel, TPoint end1, TPoint end2) {
// get image coordinates of the screen point
if(screenPt==null) {
screenPt = new Point();
}
if(worldPt==null) {
worldPt = new Point2D.Double();
}
screenPt.setLocation(xScreen, yScreen);
AffineTransform toScreen = vidPanel.getPixelTransform();
if(!vidPanel.isDrawingInImageSpace()) {
int n = getFrameNumber(vidPanel);
toScreen.concatenate(vidPanel.getCoords().getToWorldTransform(n));
}
try {
toScreen.inverseTransform(screenPt, worldPt);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
// set location to nearest point on line between end1 and end2
double dx = end2.getX()-end1.getX();
double dy = end2.getY()-end1.getY();
double u = ((worldPt.getX()-end1.getX())*dx+(worldPt.getY()-end1.getY())*dy)/end1.distanceSq(end2);
if(java.lang.Double.isNaN(u)) {
u = 0;
}
double xLine = end1.getX()+u*dx;
double yLine = end1.getY()+u*dy;
setLocation(xLine, yLine);
}
//______________________________________________ inner class _________________________________________
private class Follower implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
TPoint p = (TPoint)e.getSource();
setXY(p.getX(), p.getY());
}
public TPoint getTarget() {
return TPoint.this;
}
}
}
/*
* 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
*/