/******************************************************************************* * 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.DragSource; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TransferData; /** * A <code>DelegatingDragAdapter</code> is a <code>DragSourceListener</code> that * maintains and delegates to a set of {@link TransferDragSourceListener}s. Each * TransferDragSourceListener can then be implemented as if it were the * <code>DragSource's</code> only DragSourceListener. * <p> * When a drag is started, a subset of all <code>TransferDragSourceListeners</code> * is generated and stored in a list of <i>active</i> listeners. This subset is * calculated by forwarding {@link DragSourceListener#dragStart(DragSourceEvent)} to * every listener, and checking if the {@link DragSourceEvent#doit doit} field is left * set to <code>true</code>. * </p> * The <code>DragSource</code>'s set of supported Transfer types ({@link * DragSource#setTransfer(Transfer[])}) is updated to reflect the Transfer types * corresponding to the active listener subset. * <p> * If and when {@link #dragSetData(DragSourceEvent)} is called, a single * <code>TransferDragSourceListener</code> is chosen, and only it is allowed to set the * drag data. The chosen listener is the first listener in the subset of active listeners * whose Transfer supports ({@link Transfer#isSupportedType(TransferData)}) the * <code>dataType</code> in the <code>DragSourceEvent</code>. * </p> * <p> * The following example snippet shows a <code>DelegatingDragAdapter</code> with two * <code>TransferDragSourceListeners</code>. One implements drag of text strings, * the other supports file transfer and demonstrates how a listener can be disabled using * the dragStart method. * </p> * <code><pre> * final TreeViewer viewer = new TreeViewer(shell, SWT.NONE); * * DelegatingDragAdapter dragAdapter = new DelegatingDragAdapter(); * dragAdapter.addDragSourceListener(new TransferDragSourceListener() { * public Transfer getTransfer() { * return TextTransfer.getInstance(); * } * public void dragStart(DragSourceEvent event) { * // always enabled, can control enablement based on selection etc. * } * public void dragSetData(DragSourceEvent event) { * event.data = "Transfer data"; * } * public void dragFinished(DragSourceEvent event) { * // no clean-up required * } * }); * dragAdapter.addDragSourceListener(new TransferDragSourceListener() { * public Transfer getTransfer() { * return FileTransfer.getInstance(); * } * public void dragStart(DragSourceEvent event) { * // enable drag listener if there is a viewer selection * event.doit = !viewer.getSelection().isEmpty(); * } * public void dragSetData(DragSourceEvent event) { * File file1 = new File("C:/temp/file1"); * File file2 = new File("C:/temp/file2"); * event.data = new String[] {file1.getAbsolutePath(), file2.getAbsolutePath()}; * } * public void dragFinished(DragSourceEvent event) { * // no clean-up required * } * }); * viewer.addDragSupport(DND.DROP_COPY | DND.DROP_MOVE, dragAdapter.getTransfers(), dragAdapter); * </pre></code> * @since 3.0 */ public class DelegatingDragAdapter implements DragSourceListener { private List<TransferDragSourceListener> listeners = new ArrayList<>(); private List<TransferDragSourceListener> activeListeners = new ArrayList<>(); private TransferDragSourceListener currentListener; /** * Adds the given <code>TransferDragSourceListener</code>. * * @param listener the new listener */ public void addDragSourceListener(TransferDragSourceListener listener) { listeners.add(listener); } /** * The drop has successfully completed. This event is forwarded to the current * drag listener. * Doesn't update the current listener, since the current listener is already the one * that completed the drag operation. * * @param event the drag source event * @see DragSourceListener#dragFinished(DragSourceEvent) */ @Override public void dragFinished(final DragSourceEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drag Finished: " + toString()); //$NON-NLS-1$ SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { if (currentListener != null) { // there is a listener that can handle the drop, delegate the event currentListener.dragFinished(event); } else { // The drag was canceled and currentListener was never set, so send the // dragFinished event to all the active listeners. event.doit = false; Iterator<TransferDragSourceListener> iterator = activeListeners.iterator(); while (iterator.hasNext()) { iterator.next() .dragFinished(event); } } } }); currentListener = null; activeListeners.clear(); } /** * The drop data is requested. * Updates the current listener and then forwards the event to it. * * @param event the drag source event * @see DragSourceListener#dragSetData(DragSourceEvent) */ @Override public void dragSetData(final DragSourceEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drag Set Data: " + toString()); //$NON-NLS-1$ updateCurrentListener(event); // find a listener that can provide the given data type if (currentListener != null) { SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { currentListener.dragSetData(event); } }); } } /** * A drag operation has started. * Forwards this event to each listener. A listener must set <code>event.doit</code> * to <code>false</code> if it cannot handle the drag operation. If a listener can * handle the drag, it is added to the list of active listeners. * The drag is aborted if there are no listeners that can handle it. * * @param event the drag source event * @see DragSourceListener#dragStart(DragSourceEvent) */ @Override public void dragStart(final DragSourceEvent event) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Drag Start: " + toString()); //$NON-NLS-1$ boolean doit = false; // true if any one of the listeners can handle the drag List<Transfer> transfers = new ArrayList<>(listeners.size()); activeListeners.clear(); for (int i = 0; i < listeners.size(); i++) { final TransferDragSourceListener listener = listeners .get(i); event.doit = true; // restore event.doit SafeRunnable.run(new SafeRunnable() { @Override public void run() throws Exception { listener.dragStart(event); } }); if (event.doit) { // the listener can handle this drag transfers.add(listener.getTransfer()); activeListeners.add(listener); } doit |= event.doit; } if (doit) { ((DragSource) event.widget).setTransfer(transfers .toArray(new Transfer[transfers.size()])); } event.doit = doit; } /** * Returns the <code>Transfer<code>s from every <code>TransferDragSourceListener</code>. * * @return the combined <code>Transfer</code>s */ public Transfer[] getTransfers() { Transfer[] types = new Transfer[listeners.size()]; for (int i = 0; i < listeners.size(); i++) { TransferDragSourceListener listener = listeners .get(i); types[i] = listener.getTransfer(); } return types; } /** * Returns <code>true</code> if there are no listeners to delegate drag events to. * * @return <code>true</code> if there are no <code>TransferDragSourceListeners</code> * <code>false</code> otherwise. */ public boolean isEmpty() { return listeners.isEmpty(); } /** * Removes the given <code>TransferDragSourceListener</code>. * Listeners should not be removed while a drag and drop operation is in progress. * * @param listener the <code>TransferDragSourceListener</code> to remove */ public void removeDragSourceListener(TransferDragSourceListener listener) { listeners.remove(listener); if (currentListener == listener) { currentListener = null; } if (activeListeners.contains(listener)) { activeListeners.remove(listener); } } /** * Updates the current listener to one that can handle the drag. There can * be many listeners and each listener may be able to handle many <code>TransferData</code> * types. The first listener found that supports one of the <code>TransferData</ode> * types specified in the <code>DragSourceEvent</code> will be selected. * * @param event the drag source event */ private void updateCurrentListener(DragSourceEvent event) { currentListener = null; if (event.dataType == null) { return; } Iterator<TransferDragSourceListener> iterator = activeListeners.iterator(); while (iterator.hasNext()) { TransferDragSourceListener listener = iterator .next(); if (listener.getTransfer().isSupportedType(event.dataType)) { // if (Policy.DEBUG_DRAG_DROP) // System.out.println("Current drag listener: " + listener); //$NON-NLS-1$ currentListener = listener; return; } } } }