import java.awt.geom.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

/* This class shows examples of how to use the rotate, scale, and
   translate transformations in Java. The class displays a rectangle
   with two selection handles. One of the selection handles appears
   rotated and the other is scaled to twice its normal size in the
   x direction. The class also draws the x- and y-axes so that the
   user can see the coordinate space set up by the current transform.

   The user can interact with the application in one of three ways:

   1) Pressing the left mouse button rotates the rectangle by 45 degrees.
   2) Pressing the middle mouse button translates the rectangle 50 pixels
	in both the x and y directions
   3) Pressing the right mouse button scales the rectangle by a factor
	of .5 (actually it adds .5 to the current scale factor).

   The application also prints out the value of the mouse point relative
   to the rectangle's coordinate space.
*/

public class transform extends JFrame {

    class TransPanel extends JPanel {
        /* The object for the two handles */
	Rectangle2D handle = new Rectangle2D.Double(0, 0, 20, 20);

	/* The rectangle that gets transformed */
	Rectangle2D target = new Rectangle2D.Double(0, 0, 200, 100);

  	/* The current transformation amounts */
	double angle = 0.0;
	double scale = 1.0;
	double translate = 0.0;

	/* The position where the mouse was last pressed */
	int mouseX = 0;
	int mouseY = 0;
	
	TransPanel() {
	    addMouseListener(new MouseAdapter() {
		public void mouseClicked(MouseEvent e) {
		    // left mouse button rotates an additional 45 degrees
		    if (e.getButton() == MouseEvent.BUTTON1) {
		        angle += Math.PI / 4;
		    }
		    // right mouse button scales by an additional factor of .5
	  	    else if (e.getButton() == MouseEvent.BUTTON3) {
			scale += 0.5;
		    }
		    // middle mouse button translates an additional 50
		    // pixels in the x and y direction
		    else if (e.getButton() == MouseEvent.BUTTON2) {
			translate += 50;
		    }
	 	    // record the mouse point 
		    mouseX = e.getX(); mouseY = e.getY();
		    repaint();
		}
  	    });
	}

	public void paintComponent(Graphics g) {
	    super.paintComponent(g);

	    Graphics2D g2 = (Graphics2D)g;
	    AffineTransform saveTransform = g2.getTransform();
	    Color saveColor = g2.getColor();

	    // re-create the transform each time we repaint
	    AffineTransform at = new AffineTransform();
	    Dimension d = getSize();
	    int width = d.width;
	    int height = d.height;
	    Line2D xAxis = new Line2D.Double(0, 0, width, 0);
	    Line2D yAxis = new Line2D.Double(0, 0, 0, height);

	    // first translate the coordinate space so that the rectangle
	    // get's centered. This transformation does *not* center the
	    // coordinate space--instead it places it at the upper left
	    // corner of the rectangle
	    at.translate(width / 2 - target.getWidth() / 2,
			 height / 2 - target.getHeight() / 2);

	    // adjust the rectangle's coordinate system by the current
	    // transformation amounts
	    at.translate(translate, translate);
	    at.scale(scale, scale);

	    // notice that we rotate about the target's center, rather than
	    // about the origin of the coordinate system
	    at.rotate(angle, target.getWidth()/2, target.getHeight()/2);
	    g2.transform(at);

	    // draw the coordinate axes and the rectangle
 	    g2.draw(xAxis);
	    g2.draw(yAxis);
	    g2.draw(target);

	    // transform the mouse point to the rectangle's coordinate space
	    // and print out the transformed point. You can do a sanity 
	    // check on the transformed point's value by comparing it with
	    // the upper left corner of the rectangle. Do the transformed
	    // values appear to lie at the right point relative to the 
	    // upper left corner of the rectangle? They should.
	    Point2D mousePoint = new Point2D.Double(mouseX, mouseY);
	    try {
	        Point2D XFormedPoint = at.inverseTransform(mousePoint, null);
	        System.out.println("transformed mouse point = (" + XFormedPoint.getX() + ", " + XFormedPoint.getY() + ")");
	    }
	    catch (NoninvertibleTransformException e) {
		System.out.println(e);
	    }
	    
	    /* We need to modify the rectangle's coordinate system in order
	       to position the two handles. With each handle we want to
	       start with the rectangle's coordinate system so we need to
	       save a master copy of the rectangle's coordinate system. */
	    AffineTransform masterXForm = g2.getTransform();

	    // translate the first handle to the upper left corner and then
	    // rotate it
	    g2.translate(-handle.getWidth()/2, 
			 -handle.getHeight()/2);
	    g2.rotate(Math.PI/4, handle.getWidth()/2, handle.getHeight()/2);
	    g2.fill(handle);

	    // get back the rectangle's coordinate system so that the second
	    // handle can be positioned. We first move the handle so that
	    // its upper left corner lies at the lower right corner of the
	    // rectangle. Then we scale the handle to its correct size and
	    // center it about the lower right corner of the rectangle. It
	    // might seem that the two translations can be combined but that
	    // is not possible. Suppose the two translations are combined.
	    // The resulting translation must be:
	    //  (target.wd - handle.wd / 2), (target.ht - handle.ht / 2)
	    // This translation must either precede the scaling or succeed
	    // it. Let's examine both cases:
	    // 
	    // 1) The translation precedes the scaling: The translation
	    //    set's the handles coordinate space to start at the same
	    //    point where the handle's upper, left corner should
	    //    ultimately reside. When the scaling is applied, it
	    //    stretches the coordinate space starting at this upper,
	    //    left corner. The result is that the upper, left corner
	    //    of the handle is in the correct position but the lower,
	    //    right corner gets stretched too far (1/4 of the handle lies
	    //    to the left of the rectangle's lower right corner and 3/4
	    //    of the handle lies to the right of the rectangle's lower
	    //    right corner.
	    // 
     	    // 2) The translation succeeds the scaling: The scaling is now
	    //    applied to the rectangle's entire coordinate space, which
	    //    means that the translation will get elongated to twice
	    //    the length you want it to. The handle will now show up
	    //    well to the right of the lower, right corner of the
	    //    rectangle. The problem is that the appropriate scaling gets
	    //    applied to the handle.getWidth part of the translation but
	    //    the wrong scaling gets applied to the target.getWidth part
	    //    of the translation. By doing the translation for 
	    //    target.getWidth before the coordinate space gets elongated,
	    //    the handle gets placed properly.
	    g2.setTransform(masterXForm);
	    g2.translate(target.getWidth(), target.getHeight());
	    g2.scale(2.0, 1.0);
	    g2.translate(-handle.getWidth()/2, -handle.getHeight()/2);
	    g2.fill(handle);
	    g2.setTransform(saveTransform);
	    g2.setColor(saveColor);
	}

        public Dimension getPreferredSize() {
	    return new Dimension(400, 400);
	}
    }

    public transform() {
	getContentPane().add(new TransPanel());
    }

    public static void main(String argv[]) {
	transform t = new transform();
	t.pack();
	t.show();
    }
}