ChangeConnectionHandle.java

/*
 * @(#)ChangeConnectionHandle.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.Undoable;
import CH.ifa.draw.util.UndoableAdapter;
import java.awt.*;

/**
 * ChangeConnectionHandle factors the common code for handles
 * that can be used to reconnect connections.
 *
 * @see ChangeConnectionEndHandle
 * @see ChangeConnectionStartHandle
 *
 * @version <$CURRENT_VERSION$>
 */
public abstract class ChangeConnectionHandle extends AbstractHandle {

	private Connector         fOriginalTarget;
	private Figure            myTarget;
	private ConnectionFigure  myConnection;
	private Point             fStart;

	/**
	 * Initializes the change connection handle.
	 */
	protected ChangeConnectionHandle(Figure owner) {
		super(owner);
		setConnection((ConnectionFigure) owner());
		setTargetFigure(null);
	}

	/**
	 * Returns the target connector of the change.
	 */
	protected abstract Connector target();

	/**
	 * Disconnects the connection.
	 */
	protected abstract void disconnect();

	/**
	 * Connect the connection with the given figure.
	 */
	protected abstract void connect(Connector c);

	/**
	 * Sets the location of the target point.
	 */
	protected abstract void setPoint(int x, int y);

	/**
	 * Gets the side of the connection that is unaffected by
	 * the change.
	 */
	protected Connector source() {
		if (target() == getConnection().getStartConnector()) {
			return getConnection().getEndConnector();
		}
		return getConnection().getStartConnector();
	}

	/**
	 * Disconnects the connection.
	 */
	public void invokeStart(int  x, int  y, DrawingView view) {
		fOriginalTarget = target();
		fStart = new Point(x, y);

		setUndoActivity(createUndoActivity(view));
		((ChangeConnectionHandle.UndoActivity)getUndoActivity()).setOldConnector(target());

		disconnect();
	}

	/**
	 * Finds a new target of the connection.
	 */
	public void invokeStep (int x, int y, int anchorX, int anchorY, DrawingView view) {
		Point p = new Point(x, y);
		Figure f = findConnectableFigure(x, y, view.drawing());
		// track the figure containing the mouse
		if (f != getTargetFigure()) {
			if (getTargetFigure() != null) {
				getTargetFigure().connectorVisibility(false);
			}
			setTargetFigure(f);
			if (getTargetFigure() != null) {
				getTargetFigure().connectorVisibility(true);
			}
		}

		Connector target = findConnectionTarget(p.x, p.y, view.drawing());
		if (target != null) {
			p = Geom.center(target.displayBox());
		}
		setPoint(p.x, p.y);
	}

	/**
	 * Connects the figure to the new target. If there is no
	 * new target the connection reverts to its original one.
	 */
	public void invokeEnd(int x, int y, int anchorX, int anchorY, DrawingView view) {
		Connector target = findConnectionTarget(x, y, view.drawing());
		if (target == null) {
			target = fOriginalTarget;
		}

		setPoint(x, y);
		connect(target);
		getConnection().updateConnection();

		Connector oldConnector = ((ChangeConnectionHandle.UndoActivity)
			getUndoActivity()).getOldConnector();
		// there has been no change so there is nothing to undo
		if ((oldConnector == null)
				|| (target() == null)
				|| (oldConnector.owner() == target().owner())) {
			setUndoActivity(null);
		}
		else {
			getUndoActivity().setAffectedFigures(new SingleFigureEnumerator(getConnection()));
		}

		if (getTargetFigure() != null) {
			getTargetFigure().connectorVisibility(false);
			setTargetFigure(null);
		}
	}

	private Connector findConnectionTarget(int x, int y, Drawing drawing) {
		Figure target = findConnectableFigure(x, y, drawing);

		if ((target != null) && target.canConnect()
			 && target != fOriginalTarget
			 && !target.includes(owner())
			 && getConnection().canConnect(source().owner(), target)) {
				return findConnector(x, y, target);
		}
		return null;
	}

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

	/**
	 * Draws this handle.
	 */
	public void draw(Graphics g) {
		Rectangle r = displayBox();

		g.setColor(Color.green);
		g.fillRect(r.x, r.y, r.width, r.height);

		g.setColor(Color.black);
		g.drawRect(r.x, r.y, r.width, r.height);
	}

	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()) {
				if (figure.containsPoint(x, y)) {
					return figure;
				}
			}
		}
		return null;
	}
	
	protected void setConnection(ConnectionFigure newConnection) {
		myConnection = newConnection;
	}
	
	protected ConnectionFigure getConnection() {
		return myConnection;
	}
	
	protected void setTargetFigure(Figure newTarget) {
		myTarget = newTarget;
	}
	
	protected Figure getTargetFigure() {
		return myTarget;
	}

	/**
	 * Factory method for undo activity. To be overriden by subclasses.
	 */
	protected abstract Undoable createUndoActivity(DrawingView newView);
	
	public static abstract class UndoActivity extends UndoableAdapter {
		private Connector myOldConnector;
		
		public UndoActivity(DrawingView newView) {
			super(newView);
			setUndoable(true);
			setRedoable(true);
		}
		
		public boolean undo() {
			if (!super.undo()) {
				return false;
			}

			swapConnectors();
			return true;
		}
	
		public boolean redo() {
			// do not call execute directly as the selection might has changed
			if (!isRedoable()) {
				return false;
			}

			swapConnectors();
			return true;
		}

		private void swapConnectors() {
			FigureEnumeration fe = getAffectedFigures();
			if (fe.hasMoreElements()) {
				ConnectionFigure connection = (ConnectionFigure)fe.nextFigure();
				setOldConnector(replaceConnector(connection));
				connection.updateConnection();
			}
		}

		protected abstract Connector replaceConnector(ConnectionFigure connection);
				
		public void setOldConnector(Connector newOldConnector) {
			myOldConnector = newOldConnector;
		}
		
		public Connector getOldConnector() {
			return myOldConnector;
		}
	}
}