/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jface.util; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.DropTargetListener; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TransferData; /** * A <code>DelegatingDropAdapter</code> is a <code>DropTargetListener</code> that * maintains and delegates to a set of {@link TransferDropTargetListener}s. Each * <code>TransferDropTargetListener</code> can then be implemented as if it were * the DropTarget's only <code>DropTargetListener</code>. * <p> * On <code>dragEnter</code>, <code>dragOperationChanged</code>, <code>dragOver</code> * and <code>drop</code>, a <i>current</i> listener is obtained from the set of all * <code>TransferDropTargetListeners</code>. The current listener is the first listener * to return <code>true</code> for * {@link TransferDropTargetListener#isEnabled(DropTargetEvent)}. * The current listener is forwarded all <code>DropTargetEvents</code> until some other * listener becomes the current listener, or the drop terminates. * </p> * <p> * After adding all <code>TransferDropTargetListeners</code> to the * <code>DelegatingDropAdapter</code> the combined set of <code>Transfers</code> should * be set in the SWT <code>DropTarget</code>. <code>#getTransfers()</code> provides the * set of <code>Transfer</code> types of all <code>TransferDropTargetListeners</code>. * </p> * <p> * The following example snippet shows a <code>DelegatingDropAdapter</code> with two * <code>TransferDropTargetListeners</code>. One supports dropping resources and * demonstrates how a listener can be disabled in the isEnabled method. * The other listener supports text transfer. * </p> * <code><pre> * final TreeViewer viewer = new TreeViewer(shell, SWT.NONE); * DelegatingDropAdapter dropAdapter = new DelegatingDropAdapter(); * dropAdapter.addDropTargetListener(new TransferDropTargetListener() { * public Transfer getTransfer() { * return ResourceTransfer.getInstance(); * } * public boolean isEnabled(DropTargetEvent event) { * // disable drop listener if there is no viewer selection * if (viewer.getSelection().isEmpty()) * return false; * return true; * } * public void dragEnter(DropTargetEvent event) {} * public void dragLeave(DropTargetEvent event) {} * public void dragOperationChanged(DropTargetEvent event) {} * public void dragOver(DropTargetEvent event) {} * public void drop(DropTargetEvent event) { * if (event.data == null) * return; * IResource[] resources = (IResource[]) event.data; * if (event.detail == DND.DROP_COPY) { * // copy resources * } else { * // move resources * } * * } * public void dropAccept(DropTargetEvent event) {} * }); * dropAdapter.addDropTargetListener(new TransferDropTargetListener() { * public Transfer getTransfer() { * return TextTransfer.getInstance(); * } * public boolean isEnabled(DropTargetEvent event) { * return true; * } * public void dragEnter(DropTargetEvent event) {} * public void dragLeave(DropTargetEvent event) {} * public void dragOperationChanged(DropTargetEvent event) {} * public void dragOver(DropTargetEvent event) {} * public void drop(DropTargetEvent event) { * if (event.data == null) * return; * System.out.println(event.data); * } * public void dropAccept(DropTargetEvent event) {} * }); * viewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE, dropAdapter.getTransfers(), dropAdapter); * </pre></code> * @since 3.0 */ public class DelegatingDropAdapter implements DropTargetListener { private List<TransferDropTargetListener> listeners = new ArrayList<>(); private TransferDropTargetListener currentListener; private int originalDropType; /** * Adds the given <code>TransferDropTargetListener</code>. * * @param listener the new listener */ public void addDropTargetListener(TransferDropTargetListener listener) { listeners.add(listener); } /** * The cursor has entered the drop target boundaries. The current listener is * updated, and <code>#dragEnter()</code> is forwarded to the current listener. * * @param event the drop target event * @see DropTargetListener#dragEnter(DropTargetEvent) */ @Override public void dragEnter(DropTargetEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drag Enter: " + toString()); //$NON-NLS-1$ originalDropType = event.detail; updateCurrentListener(event); } /** * The cursor has left the drop target boundaries. The event is forwarded to the * current listener. * * @param event the drop target event * @see DropTargetListener#dragLeave(DropTargetEvent) */ @Override public void dragLeave(final DropTargetEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drag Leave: " + toString()); //$NON-NLS-1$ setCurrentListener(null, event); } /** * The operation being performed has changed (usually due to the user changing * a drag modifier key while dragging). Updates the current listener and forwards * this event to that listener. * * @param event the drop target event * @see DropTargetListener#dragOperationChanged(DropTargetEvent) */ @Override public void dragOperationChanged(final DropTargetEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drag Operation Changed to: " + event.detail); //$NON-NLS-1$ originalDropType = event.detail; TransferDropTargetListener oldListener = getCurrentListener(); updateCurrentListener(event); final TransferDropTargetListener newListener = getCurrentListener(); // only notify the current listener if it hasn't changed based on the // operation change. otherwise the new listener would get a dragEnter // followed by a dragOperationChanged with the exact same event. if (newListener != null && newListener == oldListener) { SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { newListener.dragOperationChanged(event); } }); } } /** * The cursor is moving over the drop target. Updates the current listener and * forwards this event to that listener. If no listener can handle the drag * operation the <code>event.detail</code> field is set to <code>DND.DROP_NONE</code> * to indicate an invalid drop. * * @param event the drop target event * @see DropTargetListener#dragOver(DropTargetEvent) */ @Override public void dragOver(final DropTargetEvent event) { TransferDropTargetListener oldListener = getCurrentListener(); updateCurrentListener(event); final TransferDropTargetListener newListener = getCurrentListener(); // only notify the current listener if it hasn't changed based on the // drag over. otherwise the new listener would get a dragEnter // followed by a dragOver with the exact same event. if (newListener != null && newListener == oldListener) { SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { newListener.dragOver(event); } }); } } /** * Forwards this event to the current listener, if there is one. Sets the * current listener to <code>null</code> afterwards. * * @param event the drop target event * @see DropTargetListener#drop(DropTargetEvent) */ @Override public void drop(final DropTargetEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drop: " + toString()); //$NON-NLS-1$ updateCurrentListener(event); if (getCurrentListener() != null) { SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { getCurrentListener().drop(event); } }); } setCurrentListener(null, event); } /** * Forwards this event to the current listener if there is one. * * @param event the drop target event * @see DropTargetListener#dropAccept(DropTargetEvent) */ @Override public void dropAccept(final DropTargetEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drop Accept: " + toString()); //$NON-NLS-1$ if (getCurrentListener() != null) { SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { getCurrentListener().dropAccept(event); } }); } } /** * Returns the listener which currently handles drop events. * * @return the <code>TransferDropTargetListener</code> which currently * handles drop events. */ private TransferDropTargetListener getCurrentListener() { return currentListener; } /** * Returns the transfer data type supported by the given listener. * Returns <code>null</code> if the listener does not support any of the * specified data types. * * @param dataTypes available data types * @param listener <code>TransferDropTargetListener</code> to use for testing * supported data types. * @return the transfer data type supported by the given listener or * <code>null</code>. */ private TransferData getSupportedTransferType(TransferData[] dataTypes, TransferDropTargetListener listener) { for (TransferData dataType : dataTypes) { if (listener.getTransfer().isSupportedType(dataType)) { return dataType; } } return null; } /** * Returns the combined set of <code>Transfer</code> types of all * <code>TransferDropTargetListeners</code>. * * @return the combined set of <code>Transfer</code> types */ public Transfer[] getTransfers() { Transfer[] types = new Transfer[listeners.size()]; for (int i = 0; i < listeners.size(); i++) { TransferDropTargetListener listener = listeners .get(i); types[i] = listener.getTransfer(); } return types; } /** * Returns <code>true</code> if there are no listeners to delegate events to. * * @return <code>true</code> if there are no <code>TransferDropTargetListeners</code> * <code>false</code> otherwise */ public boolean isEmpty() { return listeners.isEmpty(); } /** * Removes the given <code>TransferDropTargetListener</code>. * Listeners should not be removed while a drag and drop operation is in progress. * * @param listener the listener to remove */ public void removeDropTargetListener(TransferDropTargetListener listener) { if (currentListener == listener) { currentListener = null; } listeners.remove(listener); } /** * Sets the current listener to <code>listener</code>. Sends the given * <code>DropTargetEvent</code> if the current listener changes. * * @return <code>true</code> if the new listener is different than the previous * <code>false</code> otherwise */ private boolean setCurrentListener(TransferDropTargetListener listener, final DropTargetEvent event) { if (currentListener == listener) { return false; } if (currentListener != null) { SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { currentListener.dragLeave(event); } }); } currentListener = listener; // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Current drop listener: " + listener); //$NON-NLS-1$ if (currentListener != null) { SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { currentListener.dragEnter(event); } }); } return true; } /** * Updates the current listener to one that can handle the drop. There can be many * listeners and each listener may be able to handle many <code>TransferData</code> * types. The first listener found that can handle a drop of one of the given * <code>TransferData</code> types will be selected. * If no listener can handle the drag operation the <code>event.detail</code> field * is set to <code>DND.DROP_NONE</code> to indicate an invalid drop. * * @param event the drop target event */ private void updateCurrentListener(DropTargetEvent event) { int originalDetail = event.detail; // revert the detail to the "original" drop type that the User indicated. // this is necessary because the previous listener may have changed the detail // to something other than what the user indicated. event.detail = originalDropType; Iterator<TransferDropTargetListener> iter = listeners.iterator(); while (iter.hasNext()) { TransferDropTargetListener listener = iter .next(); TransferData dataType = getSupportedTransferType(event.dataTypes, listener); if (dataType != null) { TransferData originalDataType = event.currentDataType; // set the data type supported by the drop listener event.currentDataType = dataType; if (listener.isEnabled(event)) { // if the listener stays the same, set its previously determined // event detail if (!setCurrentListener(listener, event)) { event.detail = originalDetail; } return; } event.currentDataType = originalDataType; } } setCurrentListener(null, event); event.detail = DND.DROP_NONE; // -always- ensure that expand/scroll are on...otherwise // if a valid drop target is a child of an invalid one // you can't get there... event.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL; } }