/*#*******#*********#*********#*********#*********#*********#*********#*********
 *	MyLine.java
 *
 * Copyright(c) 1998 Zat, Inc.
 ******************************************************************************/
package com.MyCompany.examplebean;

import java.util.*;
import java.io.*;
import java.awt.*;
		
import java.awt.event.*;
/**
 * The MyLine bean is a Component that draws a line on the screen between two
 * specified points in its Component rectangle (Point_A and Point_B). It also
 * generates a LineClickEvent whenever the mouse is within ClickableDistance
 * pixels of the line.
 */
public class MyLine extends Component
implements java.io.Serializable {
	// constants used to set the Transparency property.  For illustration, we've
	// used enumerated ints. Otherwise, a boolean property would be better.
	public static final int	OPAQUE		= 0,
							TRANSPARENT	= 1;
							
	protected boolean opaque;
	// Stores the Transparency property. 

	transient protected Vector clickListeners;
	// The listener Vector is transient; we don't want to be storing references 
	// to arbitrary external objects when we are saved.
	
	protected Point point_a;
	protected Point point_b;
	// The endpoints of the line drawn by the bean. These are both property
	// storage variables. Points are serializable, so we can keep them with us 
	// when we are serialized (ie, we don't have to make them transient).
	
	protected int clickableDistance;
	// The distance from the line in pixels within which a mouse click will
	// generate a LineClickEvent. This is a property.

	protected Rectangle boundingRect;
	// We keep a Rectangle that restricts the region in which we need to
	// pay attention to mouse clicks. This Rectangle is the bounding rectangle 
	// of our line, plus clickableDistance pixels on all sides.
	
	
	///////////////////////// Initialization Methods //////////////////////////
	
	/**
	 * This initialize() method is called both when the bean is constructed 
	 * with "new" and when the bean is deserialized. In both cases, a new
	 * listener Vector needs to be constructed.
	 *
	 * Also, because AWT listener state is transient, we always need to register
	 * a MouseListener to generate LineClickEvents when appropriate.
	 */
	private void initialize() {
		clickListeners = new Vector();
		this.addMouseListener( new MouseAdapter() {
			public void mouseClicked( MouseEvent e ) {
				processClick( e );
			}
		});
	}
	
	public MyLine() {
		initialize(); // initialize transient state
		
		// set up some reasonable defaults for the non-transient values
		point_a = new Point();
		point_b = new Point();
		clickableDistance = 1;
		opaque = false;
		boundingRect = new Rectangle();
		recalc();
	}

	/*
	 * The readObject method is called whenever the bean is deserialized (i.e.,
	 * read from disk or the network). All initialization of transient state
	 * should be performed here.
	 *
	 * We just call the initialize() method.
	 *
	 * NOTE: This method MUST be private, and should always call
	 * in.defaultReadObject() first.
	 */
	private void readObject( java.io.ObjectInputStream in )
	throws IOException, ClassNotFoundException {
		in.defaultReadObject();
		initialize();
	}
	
	/////////////////////// Property accessor methods ///////////////////////
	//
	// Here are the methods for getting and setting our property values.
	// The properties we define are:
	// Point   Point_A              A line endpoint
	// Point   Point_B              Another line endpoint
	// Color   Color                The color of the line
	// int     ClickableDistance    The distance from the line within which
	//                              we generate LineClick events.
	// int     Transparency			An enumerated property that determines
	//								whether we paint a background.
	//
	// In addition, we also inherit all of the properties defined by Component:
	// Location, Size, Foreground, Font, etc.
	//
	////////////////////////////////////////////////////////////////////////// 
	public void setPoint_A( Point a ) {
		if (point_a.equals( a )) {
			// requested Point is identical to the current value; do nothing.
			return;
		}
		
		// We maintain our own copy of the point requested, so that
		// the value we store can't be changed externally (unlike the Point we 
		// were passed, no external objects have a reference to the Point
		// that we store). 
		point_a.setLocation( a );
		recalc(); // recalculate the bounding rectangle.
		repaint();
	}
	public Point getPoint_A() {
		return new Point( point_a );
		// return a new Point, so that noone can fudge with our properties.
	}
	
	public void setPoint_B( Point b ) {
		if (point_b.equals( b )) {
			return;
		}
		
		point_b.setLocation( b );
		recalc();
		repaint();
	} 
	public Point getPoint_B() {
		return new Point( point_b );
	}
	
	public void setColor( Color color ) {
		// Since every Component already has two Color properties (Foreground 
		// and Background), we will reuse one of these to store our Color
		// property. Note that external objects can also call setForeground
		// to achieve the same effect.
		setForeground( color );
		repaint();
	}
	public Color getColor() {
		// Since Color objects are immutable, we can distribute references to
		// the object that stores our Color property without worrying about
		// it being changed externally. String objects are the same way.
		return getForeground();
	} 

	public void setClickableDistance( int pixels ) {
		// sets the sensitivity of the LineClick mechanism. If a click is
		// within ClickableDistance pixels of the line, an event will be
		// generated. Negative values are silently converted to 0.
		clickableDistance = Math.max( 0, pixels );
		recalc();
	}
	public int getClickableDistance() {
		return clickableDistance;
	}
	
	public void setTransparency( int value ) {
		switch ( value ) {
			case OPAQUE:
				opaque = true;
				break;
			case TRANSPARENT:
				opaque = false;
				break;
			default:
				throw new IllegalArgumentException();
		}
		
		repaint();
	}
	public int getTransparency() {
		return (opaque ? OPAQUE : TRANSPARENT);
	}
	
	//////////////////////////// Event Registration //////////////////////////// 
	//
	// Here are the event registration methods. These two methods will let the
	// bean tool know that we can send events to LineClickListeners.
	//
	//////////////////////////////////////////////////////////////////////////// 
	public void addLineClickListener( LineClickListener clickListener ) {
		if ( ! clickListeners.contains( clickListener ) ) {
			clickListeners.addElement( clickListener );
		}
	}
	public void removeLineClickListener( LineClickListener clickListener ) {
		clickListeners.removeElement( clickListener );
	}
	
	///////////////////////// Visual Representation ///////////////////////////
	//
	// The paint method overrides the empty Component.paint().  The AWT calls
	// this method on Components whenever their displays should be refreshed
	//
	// The AWT passes us a java.awt.Graphics object that allows us to draw 
	// shapes inside our Component rectangle.
	///////////////////////////////////////////////////////////////////////////
	public void paint( Graphics g ) {
		if ( opaque ) {
			g.setColor( getBackground() );
			g.fillRect( 0, 0, getSize().width, getSize().height );
		}
		
		g.setColor( getForeground() );
		g.drawLine( point_a.x, point_a.y, point_b.x, point_b.y );
	}
	
	///////////////////////////// Utility Methods //////////////////////////////
	//
	// The remaining methods are used for computation.
	//
	//////////////////////////////////////////////////////////////////////////// 

	/////////////////////////////// Event Generation ///////////////////////////
	// this is a utility method, called by processClick, that sends a 
	// LineClickEventto each of the LineClickListeners that have requested them.
	protected void distributeLineClickEvent( double distance, MouseEvent mouseEvent ) {
		LineClickEvent event = new LineClickEvent( distance, mouseEvent, this );
		for ( int i = 0; i < clickListeners.size();	i++ ) {
			((LineClickListener)clickListeners.elementAt( i )).lineClicked( event );
		}
	}
	
	// This method is called by our MouseListener. It calls distributeLineClickEvent
	// whenever the mouse is clicked near the line... 
	void processClick( MouseEvent event ) {
		// Get the location of the click
		Point p = event.getPoint();
		
		// see if this click is inside the bounding rectangle we've calculated
		if ( ! boundingRect.contains( p ) ) {
			// nope. Ignore it.
			return;
		}

		// first we need to find the point on our line that is closest to the
		// point clicked (p)
		double closestX, closestY;
		
		if ( point_a.equals( point_b ) ) {
			// our line is really a single point, so just see if the click
			// is near point_a 
			closestX = (double)point_a.x;
			closestY = (double)point_a.y;
		} else {
			// do some ugly geometry
			Point a = new Point( point_a );
			Point b = new Point( point_b );
			// use copies of our property values so that coincedent property
			// changes won't hose our computations
			
			double u = (((double)((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y) ) )
															/
						(Math.pow( (double)(b.x - a.x), 2d ) + Math.pow( (double)(b.y - a.y), 2d )) );
			
			closestX = a.x + u * (b.x - a.x);
			closestY = a.y + u * (b.y - a.y);
		}
		
		double distance = Math.sqrt(	  Math.pow( (double)p.x - closestX, 2d )
										+ Math.pow( (double)p.y - closestY, 2d ) );
		if ( distance <= clickableDistance ) {
			distributeLineClickEvent( distance, event );
			// this click was within clickableDistance pixels of the line, so
			// generate an event and pass it to all of our registered listeners.
		}
	}
	
	/**
	 * The recalcAndRepaint() method is called whenever the clickable region
	 * changes. It recalculates the bounding rectangle used to filter click
	 * events, and refreshes the display by calling Component.repaint() (which
	 * magically calls our paint(..) method).
	 */
	protected void recalc() {
		boundingRect.setBounds( Math.min(point_a.x, point_b.x) - clickableDistance,
								Math.min(point_a.y, point_b.y) - clickableDistance,
								Math.abs(point_a.x - point_b.x) + 2 * clickableDistance,
								Math.abs(point_a.y - point_b.y) + 2 * clickableDistance );
	}
}
