ConnectionTool.java

/*
 * @(#)ConnectionTool.java
 *
 * Project:		JHotdraw - a GUI framework for technical drawings
 *				http://www.jhotdraw.org
 *				http://jhotdraw.sourceforge.net
 * Copyright:	© by the original author(s) and all contributors
 * License:		Lesser GNU Public License (LGPL)
 *				http://www.opensource.org/licenses/lgpl-license.html
 */

package CH.ifa.draw.standard;

import CH.ifa.draw.framework.*;
import CH.ifa.draw.util.Geom;
import CH.ifa.draw.util.UndoableAdapter;
import CH.ifa.draw.util.Undoable;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.*;

/**
 * A tool that can be used to connect figures, to split
 * connections, and to join two segments of a connection.
 * ConnectionTools turns the visibility of the Connectors
 * on when it enters a figure.
 * The connection object to be created is specified by a prototype.
 * <hr>
 * <b>Design Patterns</b><P>
 * <img src="images/red-ball-small.gif" width=6 height=6 alt=" o ">
 * <b><a href=../pattlets/sld029.htm>Prototype</a></b><br>
 * ConnectionTools creates the connection by cloning a prototype.
 * <hr>
 *
 * @see ConnectionFigure
 * @see Object#clone
 *
 * @version <$CURRENT_VERSION$>
 */
public  class ConnectionTool extends AbstractTool {

	/**
	 * the anchor point of the interaction
	 */
	private Connector   myStartConnector;
	private Connector   myEndConnector;
	private Connector   myTargetConnector;

	private Figure myTarget;

	/**
	 * the currently created figure
	 */
	private ConnectionFigure  myConnection;

	/**
	 * the currently manipulated connection point
	 */
	private int  fSplitPoint;

	/**
	 * the currently edited connection
	 */
	private ConnectionFigure  fEditedConnection;

	/**
	 * the figure that was actually added
	 * Note, this can be a different figure from the one which has been created.
	 */
	private Figure myAddedFigure;

	/**
	 * the prototypical figure that is used to create new
	 * connections.
	 */
	private ConnectionFigure  fPrototype;


	public ConnectionTool(DrawingEditor newDrawingEditor, ConnectionFigure newPrototype) {
		super(newDrawingEditor);
		fPrototype = newPrototype;
	}

	/**
	 * Handles mouse move events in the drawing view.
	 */
	public void mouseMove(MouseEvent e, int x, int y) {
		trackConnectors(e, x, y);
	}

	/**
	 * Manipulates connections in a context dependent way. If the
	 * mouse down hits a figure start a new connection. If the mousedown
	 * hits a connection split a segment or join two segments.
	 */
	public void mouseDown(MouseEvent e, int x, int y)
	{
		int ex = e.getX();
		int ey = e.getY();
		setTargetFigure(findConnectionStart(ex, ey, drawing()));
		if (getTargetFigure() != null) {
			setStartConnector(findConnector(ex, ey, getTargetFigure()));
			if (getStartConnector() != null) {
				Point p = new Point(ex, ey);
				setConnection(createConnection());
				getConnection().startPoint(p.x, p.y);
				getConnection().endPoint(p.x, p.y);
				setAddedFigure(view().add(getConnection()));
			}
		}
		else {
			ConnectionFigure connection = findConnection(ex, ey, drawing());
			if (connection != null) {
				if (!connection.joinSegments(ex, ey)) {
					fSplitPoint = connection.splitSegment(ex, ey);
					fEditedConnection = connection;
				}
				else {
					fEditedConnection = null;
				}
			}
		}
	}

	/**
	 * Adjust the created connection or split segment.
	 */
	public void mouseDrag(MouseEvent e, int x, int y) {
		Point p = new Point(e.getX(), e.getY());
		if (getConnection() != null) {
			trackConnectors(e, x, y);
			if (getTargetConnector() != null) {
				p = Geom.center(getTargetConnector().displayBox());
			}
			getConnection().endPoint(p.x, p.y);
		}
		else if (fEditedConnection != null) {
			Point pp = new Point(x, y);
			fEditedConnection.setPointAt(pp, fSplitPoint);
		}
	}

	/**
	 * Connects the figures if the mouse is released over another
	 * figure.
	 */
	public void mouseUp(MouseEvent e, int x, int y) {
		Figure c = null;
		if (getStartConnector() != null) {
			c = findTarget(e.getX(), e.getY(), drawing());
		}

		if (c != null) {
			setEndConnector(findConnector(e.getX(), e.getY(), c));
			if (getEndConnector() != null) {
				getConnection().connectStart(getStartConnector());
				getConnection().connectEnd(getEndConnector());
				getConnection().updateConnection();

				setUndoActivity(createUndoActivity());
				getUndoActivity().setAffectedFigures(
					new SingleFigureEnumerator(getAddedFigure()));
			}
		}
		else if (getConnection() != null) {
			view().remove(getConnection());
		}

		setConnection(null);
		setStartConnector(null);
		setEndConnector(null);
		setAddedFigure(null);
		editor().toolDone();
	}

	public void deactivate() {
		super.deactivate();
		if (getTargetFigure() != null) {
			getTargetFigure().connectorVisibility(false);
		}
	}

	/**
	 * Creates the ConnectionFigure. By default the figure prototype is
	 * cloned.
	 */
	protected ConnectionFigure createConnection() {
		return (ConnectionFigure)fPrototype.clone();
	}

	/**
	 * Finds a connectable figure target.
	 */
	protected Figure findSource(int x, int y, Drawing drawing) {
		return findConnectableFigure(x, y, drawing);
	}

	/**
	 * Finds a connectable figure target.
	 */
	protected Figure findTarget(int x, int y, Drawing drawing) {
		Figure target = findConnectableFigure(x, y, drawing);
		Figure start = getStartConnector().owner();

		if (target != null
			 && getConnection() != null
			 && target.canConnect()
			 && !target.includes(start)
			 && getConnection().canConnect(start, target)) {
			return target;
		}
		return null;
	}

	/**
	 * Finds an existing connection figure.
	 */
	protected ConnectionFigure findConnection(int x, int y, Drawing drawing) {
		Enumeration k = drawing.figuresReverse();
		while (k.hasMoreElements()) {
			Figure figure = (Figure) k.nextElement();
			figure = figure.findFigureInside(x, y);
			if (figure != null && (figure instanceof ConnectionFigure)) {
				return (ConnectionFigure)figure;
			}
		}
		return null;
	}

	private void setConnection(ConnectionFigure newConnection) {
		myConnection = newConnection;
	}
	
	/**
	 * Gets the connection which is created by this tool
	 */
	protected ConnectionFigure getConnection() {
		return myConnection;
	}

	protected void trackConnectors(MouseEvent e, int x, int y) {
		Figure c = null;

		if (getStartConnector() == null) {
			c = findSource(x, y, drawing());
		}
		else {
			c = findTarget(x, y, drawing());
		}

		// track the figure containing the mouse
		if (c != getTargetFigure()) {
			if (getTargetFigure() != null) {
				getTargetFigure().connectorVisibility(false);
			}
			setTargetFigure(c);
			if (getTargetFigure() != null) {
				getTargetFigure().connectorVisibility(true);
			}
		}

		Connector cc = null;
		if (c != null) {
			cc = findConnector(e.getX(), e.getY(), c);
		}
		if (cc != getTargetConnector()) {
			setTargetConnector(cc);
		}

		view().checkDamage();
	}

	private Connector findConnector(int x, int y, Figure f) {
		return f.connectorAt(x, y);
	}

	/**
	 * Finds a connection start figure.
	 */
	protected Figure findConnectionStart(int x, int y, Drawing drawing) {
		Figure target = findConnectableFigure(x, y, drawing);
		if ((target != null) && target.canConnect()) {
			return target;
		}
		return null;
	}

	private Figure findConnectableFigure(int x, int y, Drawing drawing) {
		FigureEnumeration k = drawing.figuresReverse();
		while (k.hasMoreElements()) {
			Figure figure = k.nextFigure();
			if (!figure.includes(getConnection()) && figure.canConnect()
				&& figure.containsPoint(x, y)) {
				return figure;
			}
		}
		return null;
	}

	private void setStartConnector(Connector newStartConnector) {
		myStartConnector = newStartConnector;
	}
	
	protected Connector getStartConnector() {
		return myStartConnector;
	}

	private void setEndConnector(Connector newEndConnector) {
		myEndConnector = newEndConnector;
	}
	
	protected Connector getEndConnector() {
		return myEndConnector;
	}

	private void setTargetConnector(Connector newTargetConnector) {
		myTargetConnector = newTargetConnector;
	}
	
	protected Connector getTargetConnector() {
		return myTargetConnector;
	}
	
	private void setTargetFigure(Figure newTarget) {
		myTarget = newTarget;
	}
	
	protected Figure getTargetFigure() {
		return myTarget;
	}

	/**
	 * Gets the figure that was actually added
	 * Note, this can be a different figure from the one which has been created.
	 */
	protected Figure getAddedFigure() {
		return myAddedFigure;
	}

	private void setAddedFigure(Figure newAddedFigure) {
		myAddedFigure = newAddedFigure;
	}

	/**
	 * Factory method for undo activity
	 */
	protected Undoable createUndoActivity() {
		return new ConnectionTool.UndoActivity(view(), getConnection());
	}

	public static class UndoActivity extends UndoableAdapter {

		private ConnectionFigure  myConnection;
		private Connector   myStartConnector;
		private Connector   myEndConnector;

		public UndoActivity(DrawingView newDrawingView, ConnectionFigure newConnection) {
			super(newDrawingView);
			setConnection(newConnection);
			myStartConnector = getConnection().getStartConnector();
			myEndConnector = getConnection().getEndConnector();
	        setUndoable(true);
			setRedoable(true);
		}

		/*
		 * Undo the activity
		 * @return true if the activity could be undone, false otherwise
		 */
		public boolean undo() {
			if (!super.undo()) {
				return false;
			}

			getConnection().disconnectStart();
			getConnection().disconnectEnd();
			
			FigureEnumeration fe = getAffectedFigures();
			while (fe.hasMoreElements()) {
				getDrawingView().drawing().orphan(fe.nextFigure());
			}

			getDrawingView().clearSelection();

			return true;
		}

		/*
		 * Redo the activity
		 * @return true if the activity could be redone, false otherwise
		 */
		public boolean redo() {
			if (!super.redo()) {
				return false;
			}

			getConnection().connectStart(myStartConnector);
			getConnection().connectEnd(myEndConnector);
			getConnection().updateConnection();

			getDrawingView().insertFigures(getAffectedFigures(), 0, 0, false);

			return true;
		}

		private void setConnection(ConnectionFigure newConnection) {
			myConnection = newConnection;
		}
		
		/**
		 * Gets the currently created figure
		 */
		protected ConnectionFigure getConnection() {
			return myConnection;
		}
	}
}