/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package net.sourceforge.phpdt.internal.ui.dnd;

import org.eclipse.jface.util.Assert;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TreeItem;

/**
 * A drag and drop adapter to be used together with structured viewers. The
 * adapater delegates the <code>dragEnter</code>, <code>dragOperationChanged
 * </code>,
 * <code>dragOver</code> and <code>dropAccept</code> method to the
 * <code>validateDrop</code> method. Furthermore it adds location feedback.
 */
public class JdtViewerDropAdapter implements DropTargetListener {

	/**
	 * Constant describing the position of the mouse cursor relative to the
	 * target object. This means the mouse is positioned slightly before the
	 * target.
	 */
	protected static final int LOCATION_BEFORE = 1;

	/**
	 * Constant describing the position of the mouse cursor relative to the
	 * target object. This means the mouse is positioned slightly after the
	 * target.
	 */
	protected static final int LOCATION_AFTER = 2;

	/**
	 * Constant describing the position of the mouse cursor relative to the
	 * target object. This means the mouse is positioned directly on the target.
	 */
	protected static final int LOCATION_ON = 3;

	/**
	 * Constant describing the position of the mouse cursor relative to the
	 * target object. This means the mouse is not positioned over or near any
	 * valid target.
	 */
	protected static final int LOCATION_NONE = 4;

	/**
	 * The threshold used to determine if the mouse is before or after an item.
	 */
	private static final int LOCATION_EPSILON = 5;

	/**
	 * Style to enable location feedback.
	 */
	public static final int INSERTION_FEEDBACK = 1 << 1;

	private StructuredViewer fViewer;

	private int fFeedback;

	private boolean fShowInsertionFeedback;

	private int fRequestedOperation;

	private int fLastOperation;

	protected int fLocation;

	protected Object fTarget;

	public JdtViewerDropAdapter(StructuredViewer viewer, int feedback) {
		fViewer = viewer;
		Assert.isNotNull(fViewer);
		fFeedback = feedback;
		fLastOperation = -1;
	}

	/**
	 * Controls whether the drop adapter shows insertion feedback or not.
	 * 
	 * @param showInsertionFeedback
	 *            <code>true</code> if the drop adapter is supposed to show
	 *            insertion feedback. Otherwise <code>false</code>
	 */
	public void showInsertionFeedback(boolean showInsertionFeedback) {
		fShowInsertionFeedback = showInsertionFeedback;
	}

	/**
	 * Returns the viewer this adapter is working on.
	 */
	protected StructuredViewer getViewer() {
		return fViewer;
	}

	// ---- Hooks to override
	// -----------------------------------------------------

	/**
	 * The actual drop has occurred. Calls
	 * <code>drop(Object target, DropTargetEvent event)
	 * </code>.
	 * 
	 * @see DropTargetListener#drop(org.eclipse.swt.dnd.DropTargetEvent)
	 */
	public void drop(DropTargetEvent event) {
		drop(fTarget, event);
	}

	/**
	 * The actual drop has occurred.
	 * 
	 * @param target
	 *            the drop target in form of a domain element.
	 * @param event
	 *            the drop traget event
	 */
	public void drop(Object target, DropTargetEvent event) {
	}

	/**
	 * Checks if the drop is valid. The method calls <code>validateDrop
	 * (Object target, DropTargetEvent event). Implementors can alter the 
	 * <code>currentDataType</code> field and the <code>detail</code> field 
	 * to give feedback about drop acceptence.
	 */
	public void validateDrop(DropTargetEvent event) {
		validateDrop(fTarget, event, fRequestedOperation);
	}

	/**
	 * Checks if the drop on the current target is valid. The method can alter
	 * the <code>currentDataType</code> field and the <code>
	 * detail</code>
	 * field to give feedback about drop acceptence.
	 * 
	 * @param target
	 *            the drop target in form of a domain element.
	 * @param event
	 *            the drop traget event
	 * @param operation
	 *            the operation requested by the user.
	 */
	public void validateDrop(Object target, DropTargetEvent event, int operation) {
	}

	public void dragEnter(DropTargetEvent event) {
		dragOperationChanged(event);
	}

	public void dragLeave(DropTargetEvent event) {
		fTarget = null;
		fLocation = LOCATION_NONE;
	}

	public void dragOperationChanged(DropTargetEvent event) {
		fRequestedOperation = event.detail;
		fTarget = computeTarget(event);
		fLocation = computeLocation(event);
		validateDrop(event);
		fLastOperation = event.detail;
		computeFeedback(event);
	}

	public void dragOver(DropTargetEvent event) {
		Object oldTarget = fTarget;
		fTarget = computeTarget(event);

		// set the location feedback
		int oldLocation = fLocation;
		fLocation = computeLocation(event);
		if (oldLocation != fLocation || oldTarget != fTarget
				|| fLastOperation != event.detail) {
			validateDrop(event);
			fLastOperation = event.detail;
		} else {
			event.detail = fLastOperation;
		}
		computeFeedback(event);
	}

	public void dropAccept(DropTargetEvent event) {
		fTarget = computeTarget(event);
		validateDrop(event);
		fLastOperation = event.detail;
	}

	/**
	 * Returns the data held by <code>event.item</code>. Inside a viewer this
	 * corresponds to the items data model element.
	 */
	protected Object computeTarget(DropTargetEvent event) {
		return event.item == null ? null : event.item.getData();
	}

	/**
	 * Returns the position of the given coordinates relative to the given
	 * target. The position is determined to be before, after, or on the item,
	 * based on some threshold value. The return value is one of the LOCATION_*
	 * constants defined in this class.
	 */
	final protected int computeLocation(DropTargetEvent event) {
		if (!(event.item instanceof Item))
			return LOCATION_NONE;

		Item item = (Item) event.item;
		Point coordinates = fViewer.getControl().toControl(
				new Point(event.x, event.y));
		Rectangle bounds = getBounds(item);
		if (bounds == null) {
			return LOCATION_NONE;
		}
		if ((coordinates.y - bounds.y) < LOCATION_EPSILON) {
			return LOCATION_BEFORE;
		}
		if ((bounds.y + bounds.height - coordinates.y) < LOCATION_EPSILON) {
			return LOCATION_AFTER;
		}
		return LOCATION_ON;
	}

	/**
	 * Returns the bounds of the given item, or <code>null</code> if it is not
	 * a valid type of item.
	 */
	private Rectangle getBounds(Item item) {
		if (item instanceof TreeItem)
			return ((TreeItem) item).getBounds();

		if (item instanceof TableItem)
			return ((TableItem) item).getBounds(0);

		return null;
	}

	/**
	 * Sets the drag under feedback corresponding to the value of
	 * <code>fLocation</code> and the <code>INSERTION_FEEDBACK</code> style
	 * bit.
	 */
	protected void computeFeedback(DropTargetEvent event) {
		if (!fShowInsertionFeedback && fLocation != LOCATION_NONE) {
			event.feedback = DND.FEEDBACK_SELECT;
		} else {
			if (fLocation == LOCATION_BEFORE) {
				event.feedback = DND.FEEDBACK_INSERT_BEFORE;
			} else if (fLocation == LOCATION_AFTER) {
				event.feedback = DND.FEEDBACK_INSERT_AFTER;
			}
		}
		event.feedback |= fFeedback;
	}

	/**
	 * Sets the drop operation to </code>DROP_NODE<code>.
	 */
	protected void clearDropOperation(DropTargetEvent event) {
		event.detail = DND.DROP_NONE;
	}

	/**
	 * Returns the requested drop operation.
	 */
	protected int getRequestedOperation() {
		return fRequestedOperation;
	}
}
