package jas2.util.multifunctionpanel;

import java.awt.*;
import java.util.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import jas2.util.multibuttontoolbar.*;
import jas2.plot.EditableLabel;
import java.beans.*;


/**
 * A UberPanel contains objects which can be moved around and resized
 * without any effort (code) on their part.  This panel can be placed inside
 * a content pane of anything with a JRootPane (e.g. a JFrame).  The
 * UberPanel will temporarily substitute its own glass pane (an inner
 * class) for that of the JRootPane that contains it in order to move the
 * objects around.
 * 
 * @author Peter Armstrong
 * @author Tony Johnson
 */
public class UberPanel extends JPanel {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	//
	//constructors
	//
	public UberPanel() {
		this(Color.blue);
	}
	public UberPanel(Color color) {
		super(null); // no layout manager
		enableEvents(AWTEvent.CONTAINER_EVENT_MASK |
					 AWTEvent.MOUSE_EVENT_MASK |
					 AWTEvent.MOUSE_MOTION_EVENT_MASK);
		mogp = new UberGlassPane(color);
		cursorState = UberPanelStates.DEFAULT;
		arrowHeadDrawState = ArrowheadDrawStates.NONE;
		arrowHeadFillState = false;
		labelBorderDrawState = LabelBorderDrawStates.NONE;
	}
	public void addNotify() {
		super.addNotify();
		previousGlassPane = getRootPane().getGlassPane();
		getRootPane().setGlassPane(mogp);
		mogp.setVisible(true);
	}
	public void removeNotify() {
		super.removeNotify();
		getRootPane().setGlassPane(previousGlassPane);
	}


	
	//
	//member variables
	//
	private UberGlassPane mogp;
	private Component previousGlassPane;
	private Point dragOffset;
	private Component selectedComponent;
	private JPopupMenu pm;
	private int cursorState;
	private int arrowHeadDrawState;
	private int labelBorderDrawState;
	private Point dragStart;
	private EditableLabel tempLabel;
	private UberArrow tempArrow;
	private boolean arrowHeadFillState;
	private float arrowHeadLength = 15.0f;
	private int numRepaints = 0;
	private static final int REPAINT_FUDGE_FACTOR = 10;
	private static final int FULL_REPAINT = 5;
	private static int numEvents = 0;//debug
	
	//
	//methods
	//
	public int getCursorState() {
		return cursorState;
	}
	public void setCursorState(int newState) {
		cursorState = newState;
		UberPanel.this.setCursor(Cursor.getPredefinedCursor(getCursorDisplayType()));
	}
	public int getArrowHeadDrawState() {
		return arrowHeadDrawState;
	}
	public void setArrowHeadDrawState(int newState) {
		arrowHeadDrawState = newState;
	}
	public float getArrowHeadLength() {
		return arrowHeadLength;
	}
	public void setArrowHeadLength(float newLength) {
		arrowHeadLength = newLength;
	}
	public boolean getArrowHeadFillState() {
		return arrowHeadFillState;
	}
	public void setArrowHeadFillState(boolean newState) {
		arrowHeadFillState = newState;
	}
	public int getLabelBorderDrawState() {
		return labelBorderDrawState;
	}
	public void setLabelBorderDrawState(int newState) {
		labelBorderDrawState = newState;
	}
	public void showArrowConfigDialog() {
	}
	/**
	 * @return the type of cursor to display
	 */
	public int getCursorDisplayType() {
		switch (getCursorState()) {
			default:
			case UberPanelStates.DEFAULT :
				System.out.println("DEFAULT");
				return Cursor.DEFAULT_CURSOR;
			case UberPanelStates.ARROW_DRAW :
				System.out.println("ARROW_DRAW");
				return Cursor.CROSSHAIR_CURSOR;
			case UberPanelStates.LABEL_DRAW :
				System.out.println("LABEL_DRAW");
				return Cursor.CROSSHAIR_CURSOR;
		}
	}
	protected void processMouseEvent(MouseEvent e) {
		if (e.isPopupTrigger()) {
			pm.show(e.getComponent(), e.getX(), e.getY());
		} else {
			switch (e.getID()) {
				case MouseEvent.MOUSE_CLICKED :
					if (!mogp.theMOB.isCursorOverTheBorder(e.getPoint())) {
						selectedComponent = null;
						this.requestFocus();
					}
					break;
				case MouseEvent.MOUSE_PRESSED :
					if (getCursor().getType() == Cursor.CROSSHAIR_CURSOR) {
						dragStart = e.getPoint();
						switch (getCursorState()) {
						case UberPanelStates.ARROW_DRAW :
							tempArrow = new UberArrow(dragStart);
							tempArrow.setLocation(dragStart);
							//tempArrow.setBorder(BorderFactory.createLineBorder(Color.black));//debug
							add(tempArrow);
							tempArrow.setVisible(true);
							break;
						case UberPanelStates.LABEL_DRAW :
							tempLabel = new jas2.plot.EditableLabel("Click here to enter some text.","");
							switch (labelBorderDrawState) {
							case LabelBorderDrawStates.NONE :
								break;
							case LabelBorderDrawStates.THIN :
								tempLabel.setBorder(BorderFactory.createLineBorder(Color.black, 1));
								break;
							case LabelBorderDrawStates.THICK :
								tempLabel.setBorder(BorderFactory.createLineBorder(Color.black, 5));
								break;
							default :
							}
							tempLabel.setLocation(dragStart);
							tempLabel.setVisible(true);
							tempLabel.setRequestFocusEnabled(true);
							add(tempLabel);
						default :
						}
					}
					break;
				case MouseEvent.MOUSE_RELEASED :
					if (getCursor().getType() == Cursor.CROSSHAIR_CURSOR) {
						switch (getCursorState()) {
							case UberPanelStates.ARROW_DRAW :
							case UberPanelStates.LABEL_DRAW :
								repaint();
								break;
							default :
						}
					}
					break;
				default :
			}
		}
	}
	protected void processMouseMotionEvent(MouseEvent e) {
		if (e.getID() == e.MOUSE_DRAGGED) {
			if (getCursor().getType() == Cursor.CROSSHAIR_CURSOR) {
				switch (getCursorState()) {
					case UberPanelStates.ARROW_DRAW :
						updateArrowSizeAndLocation(tempArrow, e.getPoint());
						//repainting just a rect is more efficient but it
						//doesn't work very well, so every FULL_REPAINT times
						//we do a (guess what!) full repaint
						if (((++numRepaints) % FULL_REPAINT) == 0) {
							repaint();
						} else {
							repaint(
								new Rectangle(
									dragStart.x - REPAINT_FUDGE_FACTOR,
									dragStart.y - REPAINT_FUDGE_FACTOR,
									e.getPoint().x - dragStart.x + 2*REPAINT_FUDGE_FACTOR,
									e.getPoint().y - dragStart.y + 2*REPAINT_FUDGE_FACTOR));
						}
						break;
					case UberPanelStates.LABEL_DRAW :
						Point end = e.getPoint();
						if ((end.x > dragStart.x) && (end.y > dragStart.y)) {
							tempLabel.setSize(end.x - dragStart.x, end.y - dragStart.y);
						}
						validate();
						break;
					default :
				}
			}
		}
	}
	/**
	 * Even though the start point is fixed, we might need to change the
	 * location of the tempArrow.  Note that the arrow always goes between
	 * 2 diagonally opposite corners of the tempArrow.  However, WHICH corners
	 * it goes between are not fixed.
	 */
	private void updateArrowSizeAndLocation(UberArrow p_arrow, Point p_newFinish) {
		//System.out.println("updateArrowSizeAndLocation");
		Point start = p_arrow.getStart();
		p_arrow.updateFinish(p_newFinish);
		if (start.x < p_newFinish.x) {
			if (start.y < p_newFinish.y) {
				p_arrow.setLocation(start.x, start.y);
			} else {
				p_arrow.setLocation(start.x, p_newFinish.y);
			}
		} else {
			if (start.y < p_newFinish.y) {
				p_arrow.setLocation(p_newFinish.x, start.y);
			} else {
				p_arrow.setLocation(p_newFinish.x, p_newFinish.y);
			}
		}
		int buf = p_arrow.getArrowHeadBuffer();
		p_arrow.setLocation(p_arrow.getLocation().x - buf, p_arrow.getLocation().y - buf);
		p_arrow.setSize((Math.abs(p_arrow.getStart().x - p_arrow.getFinish().x) + 2*buf),
						(Math.abs(p_arrow.getStart().y - p_arrow.getFinish().y) + 2*buf));
		//who cares if our components can get partially past the edge of the
		//window, the part of them that does is invisible!
		p_arrow.updateThetaAndArrowHeads();
		p_arrow.invalidate();
	}
	
	//
	//inner classes
	//
	class UberComponent extends JComponent {
		/**
		 * We need to request focus when clicked so that things like handles
		 * work for the UberComponent just like they do for all other subclasses
		 * of JComponent (which also request focus when clicked).
		 */
		protected void processMouseEvent(MouseEvent e) {
			switch (e.getID()) {
			case MouseEvent.MOUSE_CLICKED :
				requestFocus();
				break;
			default :
				super.processMouseEvent(e);
			}
		}
		
		/**
		 * When the delete key is pressed lose focus and remove yourself from the Uber.
		 */
		protected void processKeyEvent(KeyEvent e) {
			switch (e.getKeyChar()) {
			case KeyEvent.VK_DELETE :
				UberPanel.this.requestFocus();
				UberPanel.this.remove(this);
				break;
			default :
				super.processKeyEvent(e);
			}
		}
	}
	
	/**
	 * UberArrow is, not surprisingly, an arrow on an UberPanel.
	 * It extends JComponent in order to override the contains(int x, int y) method.
	 * 
	 * @author Peter Armstrong
	 * @see ArrowHeadSpec.vsd
	 * @see ArrowPointContainmentAlgorithm.vsd
	 */
	class UberArrow extends UberComponent {
		//
		//public UberArrow methods
		//
		/**
		 * @param start the point which is the start of the arrow
		 */
		public UberArrow(Point start) {
			this(start, 10.0);
		}
		
		/**
		 * @param start the point which is the start of the arrow
		 * @param alpha the angle (0 <= deg <= 89) of half the arrowhead.
		 */
		public UberArrow(Point start, double alpha) {
			m1_finish = m1_start = start;
			m1_alpha = alpha;
			switch (getArrowHeadDrawState()) {
			case ArrowheadDrawStates.NONE:
				break;
			case ArrowheadDrawStates.START :
				m1_startArrowHead = new UberArrowHead(getArrowHeadLength(), getArrowHeadFillState(), false);
				m1_arrowHeadBuffer = m1_startArrowHead.getArrowHeadBuffer();
				break;
			case ArrowheadDrawStates.FINISH :
				m1_finishArrowHead = new UberArrowHead(getArrowHeadLength(), getArrowHeadFillState(), true);
				m1_arrowHeadBuffer = m1_finishArrowHead.getArrowHeadBuffer();
				break;
			case ArrowheadDrawStates.BOTH :
				m1_startArrowHead = new UberArrowHead(getArrowHeadLength(), getArrowHeadFillState(), false);
				m1_finishArrowHead = new UberArrowHead(getArrowHeadLength(), getArrowHeadFillState(), true);
				//could use either arrowhead since they're same length:
				m1_arrowHeadBuffer = m1_startArrowHead.getArrowHeadBuffer();
				break;
			default :
				System.out.println("ERROR: illegal arrowHeadState");//!PA - error handling later
				break;
			}
			enableEvents(AWTEvent.KEY_EVENT_MASK |
						 AWTEvent.MOUSE_EVENT_MASK |
						 AWTEvent.MOUSE_MOTION_EVENT_MASK);
			setBorder(BorderFactory.createEmptyBorder());
		}
		public int getArrowHeadBuffer() {
			return m1_arrowHeadBuffer;
		}
		/**
		 * @return true if the point is part of the arrow (or is close enough)
		 * else return false
		 */
		public boolean contains(int x, int y) {
			return contains(new Point(x,y));
		}
		/**
		 * @return true if the point is part of the arrow (or is close enough)
		 * else return false (this overrides the method which just uses the
		 * rectangular shape of the Component)
		 */
		public boolean contains(Point p) {
			return(isPointInLineArea(p) ||
				   ((m1_startArrowHead == null) ? false : m1_startArrowHead.contains(p)) ||
				   ((m1_finishArrowHead == null) ? false : m1_finishArrowHead.contains(p)));
		}
		/**
		 * @param start the point which is the beginning of the arrow
		 */
		public void updateStart(Point start) {
			m1_start = start;
		}
		/**
		 * @param finish the point which is the end of the arrow
		 */
		public void updateFinish(Point finish) {
			m1_finish = finish;
		}
		/**
		 * Call this method to update the member variable which stores theta
		 * whenever you change the shape of the arrow.
		 */
		public void updateThetaAndArrowHeads() {
			calcTheta();
			if (m1_startArrowHead != null) {
				m1_startArrowHead.recalculateArea();
			}
			if (m1_finishArrowHead != null) {
				m1_finishArrowHead.recalculateArea();
			}
		}
		/**
		 * Draw the arrow.
		 */
		public void paintComponent(Graphics g) {
			switch (getQuadrant()) {
			case 1:
				g.drawLine(m1_arrowHeadBuffer, getSize().height - m1_arrowHeadBuffer,
						   getSize().width - m1_arrowHeadBuffer, m1_arrowHeadBuffer);
				break;
			case 2:
				g.drawLine(getSize().width - m1_arrowHeadBuffer, getSize().height - m1_arrowHeadBuffer,
						   m1_arrowHeadBuffer, m1_arrowHeadBuffer);
				break;
			case 3:
				g.drawLine(getSize().width - m1_arrowHeadBuffer, m1_arrowHeadBuffer, 
						   m1_arrowHeadBuffer, getSize().height - m1_arrowHeadBuffer);
				break;
			case 4:
				g.drawLine(m1_arrowHeadBuffer, m1_arrowHeadBuffer,
						   getSize().width - m1_arrowHeadBuffer, getSize().height - m1_arrowHeadBuffer);
				break;
			default:
			}
			if (m1_startArrowHead != null) {
				m1_startArrowHead.drawYourself(g);
			}
			if (m1_finishArrowHead != null) {
				m1_finishArrowHead.drawYourself(g);
			}
		}
		/**
		 * @return the quadrant that the line would end in if it started at
		 * the origin (vertical lines are considered to be in Q1 or Q2;
		 * horizontal lines are considered to be in Q1 or Q4).
		 * 
		 *       |
		 *    2  |  1
		 *  -----------
		 *    3  |  4
		 *       |
		 */
		public int getQuadrant() {
			if (getXlen() >= 0) {
				if (getYlen() > 0) {
					return 4;
				} else {
					return 1;
				}
			} else {
				if (getYlen() > 0) {
					return 3;
				} else {
					return 2;
				}
			}
		}
		
		//
		//private UberArrow methods
		//
		private boolean isPointInLineArea(Point p) {
			int[] poly_x = new int[4];
			int[] poly_y = new int[4];
			Polygon bounds;
			switch (getQuadrant()) {
			case 1:
				poly_x[0] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[1] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[0] = getSize().height - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[1] = getSize().height - m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_x[3] = getSize().width - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[2] = getSize().width - m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[3] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[2] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				break;
			case 2:
				poly_x[0] = getSize().width - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[1] = getSize().width - m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[0] = getSize().height - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[1] = getSize().height - m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_x[3] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[2] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[3] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[2] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				break;
			case 3:
				poly_x[0] = getSize().width - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[1] = getSize().width - m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[0] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[1] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_x[3] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[2] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[3] = getSize().height - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[2] = getSize().height - m1_arrowHeadBuffer + m1_lineAreaWidth;
				break;
			case 4:
				poly_x[0] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[1] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[0] = m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[1] = m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_x[3] = getSize().width - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_x[2] = getSize().width - m1_arrowHeadBuffer + m1_lineAreaWidth;
				poly_y[3] = getSize().height - m1_arrowHeadBuffer - m1_lineAreaWidth;
				poly_y[2] = getSize().height - m1_arrowHeadBuffer + m1_lineAreaWidth;
				break;
			default:
			}
			bounds = new Polygon(poly_x, poly_y, 4);
			//getGraphics().drawPolygon(bounds);//debug
			return bounds.contains(p);
		}
		private void calcTheta() {
			int x = getXlen();
			int y = getYlen();
			//System.out.println("calc theta: x = " + x + ", y = " + y);
			if (x == 0) {//if line is vertical
				if (y > 0) {
					m1_theta = Math.PI/2.0;
				} else {
					m1_theta = 1.5*Math.PI;
				}
			} else {
				m1_theta = Math.atan(((double)y)/((double)x));
				switch (getQuadrant()) {
					case 2:
					case 3:
						m1_theta += Math.PI;
						break;
					case 4:
						m1_theta += 2.0*Math.PI;
						break;
					default:
				}
			}
		}
		
		public Point getStart() {
			return m1_start;
		};
		public Point getFinish() {
			return m1_finish;
		};
		/**
		 * WARNING: this can return + and - values!  Determine the quadrant accordingly!
		 */
		private int getXlen() {
			return m1_finish.x - m1_start.x;
		}
		/**
		 * WARNING: this can return + and - values!  Determine the quadrant accordingly!
		 */
		private int getYlen() {
			return m1_finish.y - m1_start.y;
		}
		
		//
		//private UberArrow member variables
		//
		private double m1_alpha, m1_theta;		//see ArrowPointContainmentAlgorithm.vsd
		private Point m1_start;					// .--> the . on this left to right arrow
		private Point m1_finish;				// -->. the . on this left to right arrow
		private UberArrowHead m1_startArrowHead;	// <-- the < on this left to right arrow
		private UberArrowHead m1_finishArrowHead;// --> the > on this left to right arrow
		private int m1_lineAreaWidth = 5;		//see ArrowPointContainmentAlgorithm.vsd
		private int m1_arrowHeadBuffer = 0;		//space around arrowhead so it draws correctly

		//
		//inner classes
		//
		private class UberArrowHead {
			/**
			 * @param len the arrowhead length (x1,y1) to (x3,y3) on ArrowHeadSpec.vsd
			 * @param filled true if the arrowhead is filled, else false
			 * @param finish true if the arrowhead is where the arrow finishes, not where it starts
			 * (for a left to right arrow this is --> but not <--)
			 */
			public UberArrowHead(double len, boolean filled, boolean finish) {
				m2_len = len;
				m2_filled = filled;
				m2_finish = finish;
				
				//Now we need to determine the extra space to put around the
				//arrowhead in the component so that the arrowhead is not
				//chopped off by the border of the JComponent (which it would
				//be in almost all cases!).  Rather than making this an exact
				//value, we just upper bound it as the distance from (x2,y2)
				//to (x3,y3) on ArrowHeadSpec.vsd.
				//
				//NOTE: It IS safe to just do this once in the constructor
				//since m1_alpha is fixed to the value it gets in the
				//UberArrow constructor.
				m2_arrowHeadBuffer = (int)(Math.ceil(m2_len * Math.tan(m1_alpha)));
				//System.out.println("m2_arrowheadBuffer = " + m2_arrowHeadBuffer + " pixels.");//debug
			}
			public int getArrowHeadBuffer() {
				return m2_arrowHeadBuffer;
			}
			public void recalculateArea() {
				//System.out.println("recalc");
				Point pt = null;
				if (m2_finish) {
					switch (getQuadrant()) {
					case 1:
						pt = new Point(UberArrow.this.getSize().width - m2_arrowHeadBuffer, m2_arrowHeadBuffer);
						break;
					case 2:
						pt = new Point(m2_arrowHeadBuffer, m2_arrowHeadBuffer);
						break;
					case 3:
						pt = new Point(m2_arrowHeadBuffer, UberArrow.this.getSize().height - m2_arrowHeadBuffer);
						break;
					case 4:
						pt = new Point(UberArrow.this.getSize().width - m2_arrowHeadBuffer,
									   UberArrow.this.getSize().height - m2_arrowHeadBuffer);
						break;
					default:
					}
				} else {
					switch (getQuadrant()) {
					case 1:
						pt = new Point(m2_arrowHeadBuffer, UberArrow.this.getSize().height - m2_arrowHeadBuffer);
						break;
					case 2:
						pt = new Point(UberArrow.this.getSize().width - m2_arrowHeadBuffer,
									   UberArrow.this.getSize().height - m2_arrowHeadBuffer);
						break;
					case 3:
						pt = new Point(UberArrow.this.getSize().width - m2_arrowHeadBuffer, m2_arrowHeadBuffer);
						break;
					case 4:
						pt = new Point(m2_arrowHeadBuffer, m2_arrowHeadBuffer);
						break;
					default:
					}
				}
				//now pt is the point at the tip of the arrow (relative to JComponent!!)
				
				//Note: On ArrowHeadSpec.vsd, x(i+1) = xs[i] here.  Same for ys.
				double cosA = Math.cos(m1_alpha);
				int[] xs = new int[]{
						pt.x,
						addOrSubtract(!m2_finish, pt.x, (int)((m2_len/(Math.cos(m1_alpha)))*(Math.cos(m1_theta - m1_alpha)))),
						addOrSubtract(!m2_finish, pt.x, (int)(m2_len * Math.cos(m1_theta))),
						addOrSubtract(!m2_finish, pt.x, (int)((m2_len/(Math.cos(m1_alpha)))*(Math.cos(m1_theta + m1_alpha))))
					};
				int[] ys = new int[]{
						pt.y,
						addOrSubtract(!m2_finish, pt.y, (int)((m2_len/(Math.cos(m1_alpha)))*(Math.sin(m1_theta - m1_alpha)))),
						addOrSubtract(!m2_finish, pt.y, (int)(m2_len * Math.sin(m1_theta))),
						addOrSubtract(!m2_finish, pt.y, (int)((m2_len/(Math.cos(m1_alpha)))*(Math.sin(m1_theta + m1_alpha))))
					};
				//System.out.println("reassigning arrowhead");
				m2_arrowHead = new Polygon(xs, ys, 4);
			}
			
			public void drawYourself(Graphics g) {
				if (m2_filled) {
					g.fillPolygon(m2_arrowHead);
				} else {
					g.drawPolygon(m2_arrowHead);
				}
			}
			
			public boolean contains(Point p1) {
				return m2_arrowHead.contains(p1);
			}
			
			public boolean contains(int x, int y) {
				return m2_arrowHead.contains(x,y);
			}
			
			//
			//private stuff
			//
			private int addOrSubtract(boolean add, int a, int b) {
				return (add ? a + b : a - b);
			}
			private Polygon m2_arrowHead;
			private double m2_len;
			private boolean m2_filled, m2_finish;
			private int m2_arrowHeadBuffer;
		}//class UberArrowHead
	}//class UberArrow
	
	public interface UberPanelStates {
		int DEFAULT = 0, ARROW_DRAW = 1, LABEL_DRAW = 2;
	}
	public interface ArrowheadDrawStates {
		int NONE = 0, FINISH = 1, START = 2, BOTH = 3;
	}
	public interface LabelBorderDrawStates {
		int NONE = 0, THIN = 1, THICK = 2;
	}
	
	/**
	 * The GlassPane of the UberPanel.
	 */
	class UberGlassPane extends JComponent {
		//
		//constructors
		//
		UberGlassPane() {
			this(Color.blue);
		}
		
		UberGlassPane(Color color) {
			enableEvents(AWTEvent.CONTAINER_EVENT_MASK |
						 AWTEvent.MOUSE_EVENT_MASK |
						 AWTEvent.MOUSE_MOTION_EVENT_MASK);
			theMOB = new MovableObjectBorder(color, 7, 7, 5, Color.red, 8, 6);
		}
		
		//
		//member variables
		//
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		
		Point offsetFromTopLeftCorner;
			//The offsetFromTopLeftCorner is used to adjust the location of
			//the component being MOVED (not resized).  We compute this offset
			//once and store it in this point, and then whenever we move the
			//component we need to adjust it by this offset.  This creates
			//the effect of the cursor staying at the same spot on the
			//component as it was when the user started dragging.
		
		Rectangle initialSize = null;
		
		MovableObjectBorder theMOB;		//"I'm gonna make you a border you can't refuse."
		private int currentCursor;
		private int repaintExtra = 20;	//the number of pixels around the component to repaint

		//
		//methods
		//
		public void paint(Graphics g) {
			theMOB.paint(g);
		}
		/**
		 * Returns the type of cursor (guaranteed not to return null).
		 * 
		 * @param p the point that the cursor is over
		 */
		public int getCursor(final Point p) {
			if (theMOB != null) {
				return theMOB.getCursor(p);
			} else {
				Cursor theCursor = super.getCursor();
				if (theCursor == null) {
					return Cursor.DEFAULT_CURSOR;
				} else {
					return theCursor.getType();
				}
			}
		}
		
		/**
		 * All I want to do is get from point a to point b :)
		 * 
		 * @param a the point we're finding the distance from
		 * @param b the point whose distance from a we're finding
		 */
		private Point findOffset(Point a, Point b) {
			return new Point((b.x - a.x), (b.y - a.y));
		}
		
		protected void processMouseEvent(MouseEvent e) {
			if (e.isPopupTrigger()) {
				System.out.println("popup from glass pane!");
				//theToolBarPopupMenu.show(e.getComponent(), e.getXlen(), e.getYlen());
			} else {
				switch (e.getID()) {
				case MouseEvent.MOUSE_CLICKED :
				    redispatchMouseEvent(e);
					break;
				case MouseEvent.MOUSE_ENTERED :
				    redispatchMouseEvent(e);
					break;
				case MouseEvent.MOUSE_EXITED :
				    redispatchMouseEvent(e);
					break;
				case MouseEvent.MOUSE_PRESSED :
					if (mogp.getCursor() != null) {
						switch (mogp.getCursor().getType()) {
							case Cursor.MOVE_CURSOR :
								Point topLeftCorner = selectedComponent.getLocation();
								offsetFromTopLeftCorner = findOffset(topLeftCorner, e.getPoint());
								break;
							case Cursor.E_RESIZE_CURSOR :
							case Cursor.W_RESIZE_CURSOR :
							case Cursor.N_RESIZE_CURSOR :
							case Cursor.S_RESIZE_CURSOR :
							case Cursor.NE_RESIZE_CURSOR :
							case Cursor.NW_RESIZE_CURSOR :
							case Cursor.SE_RESIZE_CURSOR :
							case Cursor.SW_RESIZE_CURSOR :
								initialSize = selectedComponent.getBounds();
								break;
							case Cursor.CROSSHAIR_CURSOR :
								dragStart = e.getPoint();
								break;
							default:
								redispatchMouseEvent(e);
								break;
						}
					}
					break;
				case MouseEvent.MOUSE_RELEASED :
					repaint();
					if (mogp.getCursor() != null) {
						if (mogp.getCursor().getType() == Cursor.MOVE_CURSOR) {
							moveSelectedComponent(e.getPoint());
							//moveWithParent = false;//!PA - figure out how to set this in object
						} else {
						    redispatchMouseEvent(e);
						}
					} else {
					    redispatchMouseEvent(e);
					}
					break;
				default :
					break;
				}
			}
		}
		protected void processMouseMotionEvent(MouseEvent e) {
			switch (e.getID()) {
			case MouseEvent.MOUSE_MOVED :
		        redispatchMouseEvent(e);
				int newCursor = theMOB.getCursor(e.getPoint());
				if (newCursor != currentCursor) {
					mogp.setCursor(Cursor.getPredefinedCursor(newCursor));
					currentCursor = newCursor;
				}
				break;
			case MouseEvent.MOUSE_DRAGGED :
				Rectangle r = theMOB.getBorderRectangleOnTheGlassPane();
				repaint(r.x - repaintExtra,
						r.y - repaintExtra,
						r.width + repaintExtra * 2,
						r.height + repaintExtra * 2);
				if (mogp.getCursor().getType() == Cursor.MOVE_CURSOR) {
					moveSelectedComponent(e.getPoint());
				} else {
					switch (mogp.getCursor().getType()) {
						case Cursor.N_RESIZE_CURSOR :
							resizeVertical(e.getPoint(), true);
							break;
						case Cursor.E_RESIZE_CURSOR :
							resizeHorizontal(e.getPoint(), false);
							break;
						case Cursor.S_RESIZE_CURSOR :
							resizeVertical(e.getPoint(), false);
							break;
						case Cursor.W_RESIZE_CURSOR :
							resizeHorizontal(e.getPoint(), true);
							break;
						case Cursor.NE_RESIZE_CURSOR :
							resizeVertical(e.getPoint(), true);
							resizeHorizontal(e.getPoint(), false);
							break;
						case Cursor.NW_RESIZE_CURSOR :
							resizeVertical(e.getPoint(), true);
							resizeHorizontal(e.getPoint(), true);
							break;
						case Cursor.SE_RESIZE_CURSOR :
							resizeVertical(e.getPoint(), false);
							resizeHorizontal(e.getPoint(), false);
							break;
						case Cursor.SW_RESIZE_CURSOR :
							resizeVertical(e.getPoint(), false);
							resizeHorizontal(e.getPoint(), true);
							break;
						default:
					        redispatchMouseEvent(e);
							return;
					}
	//				moveWithParent = false;//!PA - replace with new version of this
					validate();
				}
				break;
			default :
				break;
			}
		}
		private boolean isPointInWindow(Point thePoint) {
			return (thePoint.x <= UberPanel.this.getSize().width &&
					thePoint.x > 0 &&
					thePoint.y <= UberPanel.this.getSize().height &&
					thePoint.y > 0);
		}
		private void moveSelectedComponent(Point currentPoint) {
			Point topLeftCorner		= new Point(currentPoint.x - offsetFromTopLeftCorner.x,
												currentPoint.y - offsetFromTopLeftCorner.y);
			int buttonLeftEdge = topLeftCorner.x;
			int buttonRightEdge = topLeftCorner.x + selectedComponent.getBounds().width;
			int buttonTopEdge = topLeftCorner.y;
			int buttonBottomEdge = topLeftCorner.y + selectedComponent.getBounds().height;
			if (buttonLeftEdge < 0) {
				topLeftCorner.x = 0;
			} else if (buttonRightEdge >= UberPanel.this.getSize().width) {
				topLeftCorner.x = UberPanel.this.getSize().width - selectedComponent.getBounds().width;
			}
			if (buttonTopEdge < 0) {
				topLeftCorner.y = 0;
			} else if (buttonBottomEdge >= UberPanel.this.getSize().height) {
				topLeftCorner.y = UberPanel.this.getSize().height - selectedComponent.getBounds().height;
			}
			selectedComponent.setLocation(topLeftCorner);
		}
		/**
		 * Transforms the cursor location to the closest location on the
		 * content pane.
		 * 
		 * @param currentPoint the actual cursor location (point)
		 * @return the transformed point
		 */
		private Point mapToPointInWindow(Point currentPoint) {
			Point retval = new Point();
			if (currentPoint.x > UberPanel.this.getSize().width) {
				retval.x = UberPanel.this.getSize().width;
			} else if (currentPoint.x < 0) {
				retval.x = 0;
			} else {
				retval.x = currentPoint.x;
			}
			if (currentPoint.y > UberPanel.this.getSize().height) {
				retval.y = UberPanel.this.getSize().height;
			} else if (currentPoint.y < 0) {
				retval.y = 0;
			} else {
				retval.y = currentPoint.y;
			}
			return retval;
		}
		private void resizeSelectedComponentIfArrow() {
			//Yeah, yeah, this is a bit of a hack but I can't figure out how to get the UberArrow
			//to listen to other components calling setBounds() on it.  If I could then I wouldn't
			//have to do this...
			if (selectedComponent instanceof UberArrow) {
				int buf = ((UberArrow)selectedComponent).getArrowHeadBuffer();
				int startx = 0;
				int starty = 0;
				int finishx = 0;
				int finishy = 0;
				switch (((UberArrow)selectedComponent).getQuadrant()) {
				case 1:
					System.out.println("q1");
					startx = selectedComponent.getBounds().x + buf;
					starty = selectedComponent.getBounds().y + selectedComponent.getBounds().height - 2*buf;
					finishx = selectedComponent.getBounds().x + selectedComponent.getBounds().width - 2*buf;
					finishy = selectedComponent.getBounds().y + buf;
					break;
				case 2:
					System.out.println("q2");
					startx = selectedComponent.getBounds().x + selectedComponent.getBounds().width - 2*buf;
					starty = selectedComponent.getBounds().y + selectedComponent.getBounds().height - 2*buf;
					finishx = selectedComponent.getBounds().x + buf;
					finishy = selectedComponent.getBounds().y + buf;
					break;
				case 3:
					System.out.println("q3");
					startx = selectedComponent.getBounds().x + selectedComponent.getBounds().width - 2*buf;
					starty = selectedComponent.getBounds().y + buf;
					finishx = selectedComponent.getBounds().x + buf;
					finishy = selectedComponent.getBounds().y + selectedComponent.getBounds().height - 2*buf;
					break;
				case 4:
					System.out.println("q4");
					startx = selectedComponent.getBounds().x + buf;
					starty = selectedComponent.getBounds().y + buf;
					finishx = selectedComponent.getBounds().x + selectedComponent.getBounds().width - 2*buf;
					finishy = selectedComponent.getBounds().y + selectedComponent.getBounds().height - 2*buf;
					break;
				default:
					break;
				}
				((UberArrow)selectedComponent).updateStart(new Point(startx, starty));
				((UberArrow)selectedComponent).updateFinish(new Point(finishx, finishy));
				((UberArrow)selectedComponent).updateThetaAndArrowHeads();
			}
		}
		/**
		 * Resize either north or south.  If and when Java gets a parameterized
		 * type mechanism this method should be combined with resizeHorizontal().
		 */
		private void resizeVertical(Point currentPoint, boolean isNorthHandle) {
			Point currentWindowPoint = mapToPointInWindow(currentPoint);
			Point initialWindowPoint =
				SwingUtilities.convertPoint(
					UberPanel.this,
					new Point(initialSize.x, initialSize.y),
					mogp);
			Rectangle b = selectedComponent.getBounds();
			int newHeight;
			if (isNorthHandle) {
				newHeight = initialSize.height + initialWindowPoint.y - currentWindowPoint.y;
			} else {
				newHeight = currentWindowPoint.y - initialWindowPoint.y;
			}
			if (newHeight >= selectedComponent.getMinimumSize().height) {
				if (isNorthHandle) {
					b.y = SwingUtilities.convertPoint(mogp, currentWindowPoint, UberPanel.this).y;
				}
				b.height = newHeight;
			} else {
				if (isNorthHandle) {
					b.y = SwingUtilities.convertPoint(
							mogp,
							new Point(initialWindowPoint.x,
									  initialWindowPoint.y +
										initialSize.height -
										selectedComponent.getMinimumSize().height),
							UberPanel.this).y;
				}
				b.height = selectedComponent.getMinimumSize().height;
			}
			selectedComponent.setBounds(b);
			resizeSelectedComponentIfArrow();
		}
		/**
		 * Resize either east or west.  If and when Java gets a parameterized
		 * type mechanism this method should be combined with resizeVertical().
		 */
		private void resizeHorizontal(Point currentPoint, boolean isWestHandle) {
			Point currentWindowPoint = mapToPointInWindow(currentPoint);
			Point initialWindowPoint =
				SwingUtilities.convertPoint(
					UberPanel.this,
					new Point(initialSize.x, initialSize.y),
					mogp);
			Rectangle b = selectedComponent.getBounds();
			int newWidth;
			if (isWestHandle) {
				newWidth = initialSize.width + initialWindowPoint.x - currentWindowPoint.x;
			} else {
				newWidth = currentWindowPoint.x - initialWindowPoint.x;
			}
			if (newWidth >= selectedComponent.getMinimumSize().width) {
				if (isWestHandle) {
					b.x = SwingUtilities.convertPoint(mogp, currentWindowPoint, UberPanel.this).x;
				}
				b.width = newWidth;
			} else {
				if (isWestHandle) {
					b.x = SwingUtilities.convertPoint(
							mogp,
							new Point(initialWindowPoint.x +
										initialSize.width -
										selectedComponent.getMinimumSize().width,
									  initialWindowPoint.y),
							UberPanel.this).x;
				}
				b.width = selectedComponent.getMinimumSize().width;
			}
			selectedComponent.setBounds(b);
			resizeSelectedComponentIfArrow();
		}
		private void redispatchMouseEvent(MouseEvent e) {
		    Point glassPanePoint = e.getPoint();
		    Component component = null;
		    Container container = UberPanel.this;
		    Point containerPoint = SwingUtilities.convertPoint(mogp, glassPanePoint, UberPanel.this);
		    if (containerPoint.y < 0) {
		        container = getRootPane().getJMenuBar();
		        containerPoint = SwingUtilities.convertPoint(mogp, glassPanePoint, getRootPane().getJMenuBar());
			}
		    //XXX: If the event is from a component in a popped-up menu,
		    //XXX: then the container should probably be the menu's
		    //XXX: JPopupMenu, and containerPoint should be adjusted
		    //XXX: accordingly.
			if ((container != null) && ((containerPoint.y >= 0) || (getRootPane().getJMenuBar() != null))) {
		        component = SwingUtilities.getDeepestComponentAt(
								container, containerPoint.x, containerPoint.y);
			}
		    if (component == null) return;
		    Point componentPoint = SwingUtilities.convertPoint(mogp, glassPanePoint, component);
		    component.dispatchEvent(new MouseEvent(	component, e.getID(), e.getWhen(), e.getModifiers(),
													componentPoint.x, componentPoint.y,
													e.getClickCount(), e.isPopupTrigger()));
			return;
		}//redispatchMouseEvent()
		
		//
		//inner classes of UberGlassPane
		//
		
		/**
		 * A MovableObjectBorder is a border but not a Border.
		 */
		class MovableObjectBorder
		{
			//
			//constructors
			//
			MovableObjectBorder(
				Color lineColor, int lineWidth, int lineHeight,
				int insetsFromRealBorder, Color handleColor,
				int cornerHandleSideLength, int sideHandleSideLength)
			{
				this.lineColor = lineColor;
				this.lineWidth = lineWidth;
				this.lineHeight = lineHeight;
				this.insetsFromRealBorder = insetsFromRealBorder;
				this.handleColor = handleColor;
				this.cornerHandleSideLength = cornerHandleSideLength;
				this.sideHandleSideLength = sideHandleSideLength;
				for (int i=0; i= r.x - extra) &&
						(p.x <= r.x + r.width + extra) &&
						(p.y >= r.y - extra) &&
						(p.y <= r.y + r.height + extra) )
				{
					//we are within the border area so we have a chance
					if (	(p.x <= r.x + lineWidth) ||								//left edge
							(p.x >= r.x + r.width - lineWidth) || //right edge
							(p.y <= r.y + lineHeight) ||								//top edge
							(p.y >= r.y + r.height - lineHeight)	//bottom edge
						)
					{
						return true;
					} else {
						return false;
					}
				} else {
					return false;
				}
			}
			
			//
			//inner classes
			//
			final private class Handle extends Rectangle
			{
				Handle(int size)
				{
					setSize(size-1,size-1);
					thick2 = size/2;
				}
				void set(int type, int x, int y)
				{
					setLocation(x - thick2, y - thick2);
					this.type = type;
				}
				int getCursor()
				{
					return type;
				}
				private int thick2;
				private int type;
			}
			
		}//MovableObjectBorder
	}//UberGlassPane
}//UberPanel

 

Ads help maintain this website.