Source code: org/eclipse/jface/util/DelegatingDropAdapter.java
1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jface.util;
12
13 import java.util.ArrayList;
14 import java.util.Iterator;
15 import java.util.List;
16
17 import org.eclipse.core.runtime.Platform;
18 import org.eclipse.swt.dnd.DND;
19 import org.eclipse.swt.dnd.DropTargetEvent;
20 import org.eclipse.swt.dnd.DropTargetListener;
21 import org.eclipse.swt.dnd.Transfer;
22 import org.eclipse.swt.dnd.TransferData;
23
24 /**
25 * A <code>DelegatingDropAdapter</code> is a <code>DropTargetListener</code> that
26 * maintains and delegates to a set of {@link TransferDropTargetListener}s. Each
27 * <code>TransferDropTargetListener</code> can then be implemented as if it were
28 * the DropTarget's only <code>DropTargetListener</code>.
29 * <p>
30 * On <code>dragEnter</code>, <code>dragOperationChanged</code>, <code>dragOver</code>
31 * and <code>drop</code>, a <i>current</i> listener is obtained from the set of all
32 * <code>TransferDropTargetListeners</code>. The current listener is the first listener
33 * to return <code>true</code> for
34 * {@link TransferDropTargetListener#isEnabled(DropTargetEvent)}.
35 * The current listener is forwarded all <code>DropTargetEvents</code> until some other
36 * listener becomes the current listener, or the drop terminates.
37 * </p>
38 * <p>
39 * After adding all <code>TransferDropTargetListeners</code> to the
40 * <code>DelegatingDropAdapter</code> the combined set of <code>Transfers</code> should
41 * be set in the SWT <code>DropTarget</code>. <code>#getTransfers()</code> provides the
42 * set of <code>Transfer</code> types of all <code>TransferDropTargetListeners</code>.
43 * </p>
44 * <p>
45 * The following example snippet shows a <code>DelegatingDropAdapter</code> with two
46 * <code>TransferDropTargetListeners</code>. One supports dropping resources and
47 * demonstrates how a listener can be disabled in the isEnabled method.
48 * The other listener supports text transfer.
49 * </p>
50 * <code><pre>
51 * final TreeViewer viewer = new TreeViewer(shell, SWT.NONE);
52 * DelegatingDropAdapter dropAdapter = new DelegatingDropAdapter();
53 * dropAdapter.addDropTargetListener(new TransferDropTargetListener() {
54 * public Transfer getTransfer() {
55 * return ResourceTransfer.getInstance();
56 * }
57 * public boolean isEnabled(DropTargetEvent event) {
58 * // disable drop listener if there is no viewer selection
59 * if (viewer.getSelection().isEmpty())
60 * return false;
61 * return true;
62 * }
63 * public void dragEnter(DropTargetEvent event) {}
64 * public void dragLeave(DropTargetEvent event) {}
65 * public void dragOperationChanged(DropTargetEvent event) {}
66 * public void dragOver(DropTargetEvent event) {}
67 * public void drop(DropTargetEvent event) {
68 * if (event.data == null)
69 * return;
70 * IResource[] resources = (IResource[]) event.data;
71 * if (event.detail == DND.DROP_COPY) {
72 * // copy resources
73 * } else {
74 * // move resources
75 * }
76 *
77 * }
78 * public void dropAccept(DropTargetEvent event) {}
79 * });
80 * dropAdapter.addDropTargetListener(new TransferDropTargetListener() {
81 * public Transfer getTransfer() {
82 * return TextTransfer.getInstance();
83 * }
84 * public boolean isEnabled(DropTargetEvent event) {
85 * return true;
86 * }
87 * public void dragEnter(DropTargetEvent event) {}
88 * public void dragLeave(DropTargetEvent event) {}
89 * public void dragOperationChanged(DropTargetEvent event) {}
90 * public void dragOver(DropTargetEvent event) {}
91 * public void drop(DropTargetEvent event) {
92 * if (event.data == null)
93 * return;
94 * System.out.println(event.data);
95 * }
96 * public void dropAccept(DropTargetEvent event) {}
97 * });
98 * viewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE, dropAdapter.getTransfers(), dropAdapter);
99 * </pre></code>
100 * <p>
101 * NOTE: This API is experimental and subject to change including removal.
102 * </p>
103 * @since 2.2
104 */
105 public class DelegatingDropAdapter implements DropTargetListener {
106 private List listeners = new ArrayList();
107 private TransferDropTargetListener currentListener;
108 private int originalDropType;
109
110 /**
111 * Adds the given <code>TransferDropTargetListener</code>.
112 *
113 * @param listener the new listener
114 */
115 public void addDropTargetListener(TransferDropTargetListener listener) {
116 listeners.add(listener);
117 }
118 /**
119 * The cursor has entered the drop target boundaries. The current listener is
120 * updated, and <code>#dragEnter()</code> is forwarded to the current listener.
121 *
122 * @param event the drop target event
123 * @see DropTargetListener#dragEnter(DropTargetEvent)
124 */
125 public void dragEnter(DropTargetEvent event) {
126 // if (Policy.DEBUG_DRAG_DROP)
127 // System.out.println("Drag Enter: " + toString()); //$NON-NLS-1$
128 originalDropType = event.detail;
129 updateCurrentListener(event);
130 }
131 /**
132 * The cursor has left the drop target boundaries. The event is forwarded to the
133 * current listener.
134 *
135 * @param event the drop target event
136 * @see DropTargetListener#dragLeave(DropTargetEvent)
137 */
138 public void dragLeave(final DropTargetEvent event) {
139 // if (Policy.DEBUG_DRAG_DROP)
140 // System.out.println("Drag Leave: " + toString()); //$NON-NLS-1$
141 setCurrentListener(null, event);
142 }
143 /**
144 * The operation being performed has changed (usually due to the user changing
145 * a drag modifier key while dragging). Updates the current listener and forwards
146 * this event to that listener.
147 *
148 * @param event the drop target event
149 * @see DropTargetListener#dragOperationChanged(DropTargetEvent)
150 */
151 public void dragOperationChanged(final DropTargetEvent event) {
152 // if (Policy.DEBUG_DRAG_DROP)
153 // System.out.println("Drag Operation Changed to: " + event.detail); //$NON-NLS-1$
154 originalDropType = event.detail;
155 TransferDropTargetListener oldListener = getCurrentListener();
156 updateCurrentListener(event);
157 final TransferDropTargetListener newListener = getCurrentListener();
158 // only notify the current listener if it hasn't changed based on the
159 // operation change. otherwise the new listener would get a dragEnter
160 // followed by a dragOperationChanged with the exact same event.
161 if (newListener != null && newListener == oldListener) {
162 Platform.run(new SafeRunnable() {
163 public void run() throws Exception {
164 newListener.dragOperationChanged(event);
165 }
166 });
167 }
168 }
169 /**
170 * The cursor is moving over the drop target. Updates the current listener and
171 * forwards this event to that listener. If no listener can handle the drag
172 * operation the <code>event.detail</code> field is set to <code>DND.DROP_NONE</code>
173 * to indicate an invalid drop.
174 *
175 * @param event the drop target event
176 * @see DropTargetListener#dragOver(DropTargetEvent)
177 */
178 public void dragOver(final DropTargetEvent event) {
179 TransferDropTargetListener oldListener = getCurrentListener();
180 updateCurrentListener(event);
181 final TransferDropTargetListener newListener = getCurrentListener();
182
183 // only notify the current listener if it hasn't changed based on the
184 // drag over. otherwise the new listener would get a dragEnter
185 // followed by a dragOver with the exact same event.
186 if (newListener != null && newListener == oldListener) {
187 Platform.run(new SafeRunnable() {
188 public void run() throws Exception {
189 newListener.dragOver(event);
190 }
191 });
192 }
193 }
194 /**
195 * Forwards this event to the current listener, if there is one. Sets the
196 * current listener to <code>null</code> afterwards.
197 *
198 * @param event the drop target event
199 * @see DropTargetListener#drop(DropTargetEvent)
200 */
201 public void drop(final DropTargetEvent event) {
202 // if (Policy.DEBUG_DRAG_DROP)
203 // System.out.println("Drop: " + toString()); //$NON-NLS-1$
204 updateCurrentListener(event);
205 if (getCurrentListener() != null) {
206 Platform.run(new SafeRunnable() {
207 public void run() throws Exception {
208 getCurrentListener().drop(event);
209 }
210 });
211 }
212 setCurrentListener(null, event);
213 }
214 /**
215 * Forwards this event to the current listener if there is one.
216 *
217 * @param event the drop target event
218 * @see DropTargetListener#dropAccept(DropTargetEvent)
219 */
220 public void dropAccept(final DropTargetEvent event) {
221 // if (Policy.DEBUG_DRAG_DROP)
222 // System.out.println("Drop Accept: " + toString()); //$NON-NLS-1$
223 if (getCurrentListener() != null) {
224 Platform.run(new SafeRunnable() {
225 public void run() throws Exception {
226 getCurrentListener().dropAccept(event);
227 }
228 });
229 }
230 }
231 /**
232 * Returns the listener which currently handles drop events.
233 *
234 * @return the <code>TransferDropTargetListener</code> which currently
235 * handles drop events.
236 */
237 private TransferDropTargetListener getCurrentListener() {
238 return currentListener;
239 }
240 /**
241 * Returns the transfer data type supported by the given listener.
242 * Returns <code>null</code> if the listener does not support any of the
243 * specified data types.
244 *
245 * @param dataTypes available data types
246 * @param listener <code>TransferDropTargetListener</code> to use for testing
247 * supported data types.
248 * @return the transfer data type supported by the given listener or
249 * <code>null</code>.
250 */
251 private TransferData getSupportedTransferType(TransferData[] dataTypes, TransferDropTargetListener listener) {
252 for (int i = 0; i < dataTypes.length; i++) {
253 if (listener.getTransfer().isSupportedType(dataTypes[i])) {
254 return dataTypes[i];
255 }
256 }
257 return null;
258 }
259 /**
260 * Returns the combined set of <code>Transfer</code> types of all
261 * <code>TransferDropTargetListeners</code>.
262 *
263 * @return the combined set of <code>Transfer</code> types
264 */
265 public Transfer[] getTransfers() {
266 Transfer[] types = new Transfer[listeners.size()];
267 for (int i = 0; i < listeners.size(); i++) {
268 TransferDropTargetListener listener = (TransferDropTargetListener)listeners.get(i);
269 types[i] = listener.getTransfer();
270 }
271 return types;
272 }
273 /**
274 * Returns <code>true</code> if there are no listeners to delegate events to.
275 *
276 * @return <code>true</code> if there are no <code>TransferDropTargetListeners</code>
277 * <code>false</code> otherwise
278 */
279 public boolean isEmpty() {
280 return listeners.isEmpty();
281 }
282 /**
283 * Removes the given <code>TransferDropTargetListener</code>.
284 * Listeners should not be removed while a drag and drop operation is in progress.
285 *
286 * @param listener the listener to remove
287 */
288 public void removeDropTargetListener(TransferDropTargetListener listener) {
289 if (currentListener == listener)
290 currentListener = null;
291 listeners.remove(listener);
292 }
293 /**
294 * Sets the current listener to <code>listener</code>. Sends the given
295 * <code>DropTargetEvent</code> if the current listener changes.
296 *
297 * @return <code>true</code> if the new listener is different than the previous
298 * <code>false</code> otherwise
299 */
300 private boolean setCurrentListener(TransferDropTargetListener listener, final DropTargetEvent event) {
301 if (currentListener == listener)
302 return false;
303 if (currentListener != null) {
304 Platform.run(new SafeRunnable() {
305 public void run() throws Exception {
306 currentListener.dragLeave(event);
307 }
308 });
309 }
310 currentListener = listener;
311 // if (Policy.DEBUG_DRAG_DROP)
312 // System.out.println("Current drop listener: " + listener); //$NON-NLS-1$
313 if (currentListener != null) {
314 Platform.run(new SafeRunnable() {
315 public void run() throws Exception {
316 currentListener.dragEnter(event);
317 }
318 });
319 }
320 return true;
321 }
322 /**
323 * Updates the current listener to one that can handle the drop. There can be many
324 * listeners and each listener may be able to handle many <code>TransferData</code>
325 * types. The first listener found that can handle a drop of one of the given
326 * <code>TransferData</code> types will be selected.
327 * If no listener can handle the drag operation the <code>event.detail</code> field
328 * is set to <code>DND.DROP_NONE</code> to indicate an invalid drop.
329 *
330 * @param event the drop target event
331 */
332 private void updateCurrentListener(DropTargetEvent event) {
333 int originalDetail = event.detail;
334 // revert the detail to the "original" drop type that the User indicated.
335 // this is necessary because the previous listener may have changed the detail
336 // to something other than what the user indicated.
337 event.detail = originalDropType;
338
339 Iterator iter = listeners.iterator();
340 while (iter.hasNext()) {
341 TransferDropTargetListener listener = (TransferDropTargetListener) iter.next();
342 TransferData dataType = getSupportedTransferType(event.dataTypes, listener);
343 if (dataType != null) {
344 TransferData originalDataType = event.currentDataType;
345 // set the data type supported by the drop listener
346 event.currentDataType = dataType;
347 if (listener.isEnabled(event)) {
348 // if the listener stays the same, set its previously determined
349 // event detail
350 if (!setCurrentListener(listener, event))
351 event.detail = originalDetail;
352 return;
353 } else {
354 event.currentDataType = originalDataType;
355 }
356 }
357 }
358 setCurrentListener(null, event);
359 event.detail = DND.DROP_NONE;
360 }
361 }