1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.dnd.DropTarget;
14 
15 
16 
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.SWTError;
19 import org.eclipse.swt.SWTException;
20 import org.eclipse.swt.graphics.Point;
21 import org.eclipse.swt.internal.gtk.OS;
22 import org.eclipse.swt.widgets.Combo;
23 import org.eclipse.swt.widgets.Control;
24 import org.eclipse.swt.widgets.Display;
25 import org.eclipse.swt.widgets.Event;
26 import org.eclipse.swt.widgets.Listener;
27 import org.eclipse.swt.widgets.Table;
28 import org.eclipse.swt.widgets.Tree;
29 import org.eclipse.swt.widgets.Widget;
30 import org.eclipse.swt.dnd.DND;
31 import org.eclipse.swt.dnd.Transfer;
32 import org.eclipse.swt.dnd.DropTargetEffect;
33 import org.eclipse.swt.dnd.DNDEvent;
34 import org.eclipse.swt.dnd.DNDListener;
35 import org.eclipse.swt.dnd.TransferData;
36 import org.eclipse.swt.dnd.DropTargetListener;
37 import org.eclipse.swt.dnd.TableDropTargetEffect;
38 import org.eclipse.swt.dnd.TreeDropTargetEffect;
39 import java.lang.all;
40 
41 import java.lang.Thread;
42 version(Tango){
43 static import tango.stdc..string;
44 } else { // Phobos
45 }
46 
47 /**
48  *
49  * Class <code>DropTarget</code> defines the target object for a drag and drop transfer.
50  *
51  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
52  *
53  * <p>This class identifies the <code>Control</code> over which the user must position the cursor
54  * in order to drop the data being transferred.  It also specifies what data types can be dropped on
55  * this control and what operations can be performed.  You may have several DropTragets in an
56  * application but there can only be a one to one mapping between a <code>Control</code> and a <code>DropTarget</code>.
57  * The DropTarget can receive data from within the same application or from other applications
58  * (such as text dragged from a text editor like Word).</p>
59  *
60  * <code><pre>
61  *  int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
62  *  Transfer[] types = new Transfer[] {TextTransfer.getInstance()};
63  *  DropTarget target = new DropTarget(label, operations);
64  *  target.setTransfer(types);
65  * </code></pre>
66  *
67  * <p>The application is notified of data being dragged over this control and of when a drop occurs by
68  * implementing the interface <code>DropTargetListener</code> which uses the class
69  * <code>DropTargetEvent</code>.  The application can modify the type of drag being performed
70  * on this Control at any stage of the drag by modifying the <code>event.detail</code> field or the
71  * <code>event.currentDataType</code> field.  When the data is dropped, it is the responsibility of
72  * the application to copy this data for its own purposes.
73  *
74  * <code><pre>
75  *  target.addDropListener (new DropTargetListener() {
76  *      public void dragEnter(DropTargetEvent event) {};
77  *      public void dragOver(DropTargetEvent event) {};
78  *      public void dragLeave(DropTargetEvent event) {};
79  *      public void dragOperationChanged(DropTargetEvent event) {};
80  *      public void dropAccept(DropTargetEvent event) {}
81  *      public void drop(DropTargetEvent event) {
82  *          // A drop has occurred, copy over the data
83  *          if (event.data is null) { // no data to copy, indicate failure in event.detail
84  *              event.detail = DND.DROP_NONE;
85  *              return;
86  *          }
87  *          label.setText ((String) event.data); // data copied to label text
88  *      }
89  *  });
90  * </pre></code>
91  *
92  * <dl>
93  *  <dt><b>Styles</b></dt> <dd>DND.DROP_NONE, DND.DROP_COPY, DND.DROP_MOVE, DND.DROP_LINK</dd>
94  *  <dt><b>Events</b></dt> <dd>DND.DragEnter, DND.DragLeave, DND.DragOver, DND.DragOperationChanged,
95  *                             DND.DropAccept, DND.Drop </dd>
96  * </dl>
97  *
98  * @see <a href="http://www.eclipse.org/swt/snippets/#dnd">Drag and Drop snippets</a>
99  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: DNDExample</a>
100  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
101  */
102 public class DropTarget : Widget {
103 
104     Control control;
105     Listener controlListener;
106     Transfer[] transferAgents;
107     DropTargetEffect dropEffect;
108 
109     // Track application selections
110     TransferData selectedDataType;
111     int selectedOperation;
112 
113     // workaround - There is no event for "operation changed" so track operation based on key state
114     int keyOperation = -1;
115 
116     // workaround - Simulate events when the mouse is not moving
117     long dragOverStart;
118     Runnable dragOverHeartbeat;
119     DNDEvent dragOverEvent;
120 
121     ptrdiff_t drag_motion_handler;
122     ptrdiff_t drag_leave_handler;
123     ptrdiff_t drag_data_received_handler;
124     ptrdiff_t drag_drop_handler;
125 
126     static const String DEFAULT_DROP_TARGET_EFFECT = "DEFAULT_DROP_TARGET_EFFECT"; //$NON-NLS-1$
127     static const int DRAGOVER_HYSTERESIS = 50;
128 
129 //     static Callback Drag_Motion;
130 //     static Callback Drag_Leave;
131 //     static Callback Drag_Data_Received;
132 //     static Callback Drag_Drop;
133 //
134 //      static this(){
135 //         Drag_Motion = new Callback(DropTarget.class, "Drag_Motion", 5); //$NON-NLS-1$
136 //         if (Drag_Motion.getAddress() is 0) SWT.error(SWT.ERROR_NO_MORE_CALLBACKS);
137 //         Drag_Leave = new Callback(DropTarget.class, "Drag_Leave", 3); //$NON-NLS-1$
138 //         if (Drag_Leave.getAddress() is 0) SWT.error(SWT.ERROR_NO_MORE_CALLBACKS);
139 //         Drag_Data_Received = new Callback(DropTarget.class, "Drag_Data_Received", 7); //$NON-NLS-1$
140 //         if (Drag_Data_Received.getAddress() is 0) SWT.error(SWT.ERROR_NO_MORE_CALLBACKS);
141 //         Drag_Drop = new Callback(DropTarget.class, "Drag_Drop", 5); //$NON-NLS-1$
142 //         if (Drag_Drop.getAddress() is 0) SWT.error(SWT.ERROR_NO_MORE_CALLBACKS);
143 //     }
144 
145 /**
146  * Creates a new <code>DropTarget</code> to allow data to be dropped on the specified
147  * <code>Control</code>.
148  * Creating an instance of a DropTarget may cause system resources to be allocated
149  * depending on the platform.  It is therefore mandatory that the DropTarget instance
150  * be disposed when no longer required.
151  *
152  * @param control the <code>Control</code> over which the user positions the cursor to drop the data
153  * @param style the bitwise OR'ing of allowed operations; this may be a combination of any of
154  *         DND.DROP_NONE, DND.DROP_COPY, DND.DROP_MOVE, DND.DROP_LINK
155  *
156  * @exception SWTException <ul>
157  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
158  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
159  * </ul>
160  * @exception SWTError <ul>
161  *    <li>ERROR_CANNOT_INIT_DROP - unable to initiate drop target; this will occur if more than one
162  *        drop target is created for a control or if the operating system will not allow the creation
163  *        of the drop target</li>
164  * </ul>
165  *
166  * <p>NOTE: ERROR_CANNOT_INIT_DROP should be an SWTException, since it is a
167  * recoverable error, but can not be changed due to backward compatibility.</p>
168  *
169  * @see Widget#dispose
170  * @see DropTarget#checkSubclass
171  * @see DND#DROP_NONE
172  * @see DND#DROP_COPY
173  * @see DND#DROP_MOVE
174  * @see DND#DROP_LINK
175  */
176 public this(Control control, int style) {
177     super(control, checkStyle(style));
178     this.control = control;
179 //     if (Drag_Motion is null || Drag_Leave is null || Drag_Data_Received is null || Drag_Drop is null) {
180 //          DND.error(DND.ERROR_CANNOT_INIT_DROP);
181 //     }
182     if (control.getData(DND.DROP_TARGET_KEY) !is null) {
183         DND.error(DND.ERROR_CANNOT_INIT_DROP);
184     }
185     control.setData(DND.DROP_TARGET_KEY, this);
186 
187     drag_motion_handler = OS.g_signal_connect(control.handle, OS.drag_motion.ptr, cast(GCallback)&Drag_Motion, null);
188     drag_leave_handler = OS.g_signal_connect(control.handle, OS.drag_leave.ptr, cast(GCallback)&Drag_Leave, null);
189     drag_data_received_handler = OS.g_signal_connect(control.handle, OS.drag_data_received.ptr, cast(GCallback)&Drag_Data_Received, null);
190     drag_drop_handler = OS.g_signal_connect(control.handle, OS.drag_drop.ptr, cast(GCallback)&Drag_Drop, null);
191 
192     // Dispose listeners
193     controlListener = new class() Listener{
194         public void handleEvent(Event event){
195             if (!this.outer.isDisposed()){
196                 this.outer.dispose();
197             }
198         }
199     };
200     control.addListener(SWT.Dispose, controlListener);
201 
202     this.addListener(SWT.Dispose, new class() Listener {
203         public void handleEvent(Event event){
204             onDispose();
205         }
206     });
207 
208     Object effect = control.getData(DEFAULT_DROP_TARGET_EFFECT);
209     if ( auto de = cast(DropTargetEffect)effect ) {
210         dropEffect = de;
211     } else if ( auto table = cast(Table)control ) {
212         dropEffect = new TableDropTargetEffect(table);
213     } else if ( auto tree = cast(Tree) control ) {
214         dropEffect = new TreeDropTargetEffect(tree);
215     }
216 
217     dragOverHeartbeat = new class() Runnable {
218         public void run() {
219             Control control = this.outer.control;
220             if (control is null || control.isDisposed() || dragOverStart is 0) return;
221             long time = System.currentTimeMillis();
222             int delay = DRAGOVER_HYSTERESIS;
223             if (time < dragOverStart) {
224                 delay = cast(int)(dragOverStart - time);
225             } else {
226                 dragOverEvent.time += DRAGOVER_HYSTERESIS;
227                 int allowedOperations = dragOverEvent.operations;
228                 TransferData[] allowedTypes = dragOverEvent.dataTypes;
229                 //pass a copy of data types in to listeners in case application modifies it
230                 TransferData[] dataTypes = new TransferData[allowedTypes.length];
231                 System.arraycopy(allowedTypes, 0, dataTypes, 0, dataTypes.length);
232 
233                 DNDEvent event = new DNDEvent();
234                 event.widget = dragOverEvent.widget;
235                 event.x = dragOverEvent.x;
236                 event.y = dragOverEvent.y;
237                 event.time = dragOverEvent.time;
238                 event.feedback = DND.FEEDBACK_SELECT;
239                 event.dataTypes = dataTypes;
240                 event.dataType = selectedDataType;
241                 event.operations = dragOverEvent.operations;
242                 event.detail  = selectedOperation;
243                 if (dropEffect !is null) {
244                     event.item = dropEffect.getItem(dragOverEvent.x, dragOverEvent.y);
245                 }
246                 selectedDataType = null;
247                 selectedOperation = DND.DROP_NONE;
248                 notifyListeners(DND.DragOver, event);
249                 if (event.dataType !is null) {
250                     for (int i = 0; i < allowedTypes.length; i++) {
251                         if (allowedTypes[i].type is event.dataType.type) {
252                             selectedDataType = event.dataType;
253                             break;
254                         }
255                     }
256                 }
257                 if (selectedDataType !is null && (event.detail & allowedOperations) !is 0) {
258                     selectedOperation = event.detail;
259                 }
260             }
261             control = this.outer.control;
262             if (control is null || control.isDisposed()) return;
263             control.getDisplay().timerExec(delay, dragOverHeartbeat);
264         }
265     };
266 }
267 
268 static int checkStyle (int style) {
269     if (style is SWT.NONE) return DND.DROP_MOVE;
270     return style;
271 }
272 
273 private static extern(C) void Drag_Data_Received (
274     GtkWidget *widget,
275     GdkDragContext *context,
276     int x,
277     int y,
278     GtkSelectionData *data,
279     uint info,
280     uint time,
281     void* user_data)
282 {
283     DropTarget target = FindDropTarget(widget);
284     if (target is null) return;
285     target.drag_data_received (widget, context, x, y, data, info, time);
286 }
287 
288 private static extern(C) int Drag_Drop(
289     GtkWidget *widget,
290     GdkDragContext *context,
291     int x,
292     int y,
293     uint time,
294     void* user_data)
295 {
296     DropTarget target = FindDropTarget(widget);
297     if (target is null) return 0;
298     return target.drag_drop (widget, context, x, y, time) ? 1 : 0;
299 }
300 
301 private static extern(C) void Drag_Leave (
302     GtkWidget *widget,
303     GdkDragContext *context,
304     uint time,
305     void* user_data)
306 {
307     DropTarget target = FindDropTarget(widget);
308     if (target is null) return;
309     target.drag_leave (widget, context, time);
310 }
311 
312 private static extern(C) int Drag_Motion (
313     GtkWidget *widget,
314     GdkDragContext *context,
315     int x,
316     int y,
317     uint time,
318     void* user_data)
319 {
320     DropTarget target = FindDropTarget(widget);
321     if (target is null) return 0;
322     return target.drag_motion (widget, context, x, y, time) ? 1 : 0;
323 }
324 
325 static DropTarget FindDropTarget(GtkWidget* handle) {
326     Display display = Display.findDisplay(Thread.currentThread());
327     if (display is null || display.isDisposed()) return null;
328     Widget widget = display.findWidget(handle);
329     if (widget is null) return null;
330     return cast(DropTarget)widget.getData(DND.DROP_TARGET_KEY);
331 }
332 
333 /**
334  * Adds the listener to the collection of listeners who will
335  * be notified when a drag and drop operation is in progress, by sending
336  * it one of the messages defined in the <code>DropTargetListener</code>
337  * interface.
338  *
339  * <p><ul>
340  * <li><code>dragEnter</code> is called when the cursor has entered the drop target boundaries
341  * <li><code>dragLeave</code> is called when the cursor has left the drop target boundaries and just before
342  * the drop occurs or is cancelled.
343  * <li><code>dragOperationChanged</code> is called when the operation being performed has changed
344  * (usually due to the user changing the selected modifier key(s) while dragging)
345  * <li><code>dragOver</code> is called when the cursor is moving over the drop target
346  * <li><code>dropAccept</code> is called just before the drop is performed.  The drop target is given
347  * the chance to change the nature of the drop or veto the drop by setting the <code>event.detail</code> field
348  * <li><code>drop</code> is called when the data is being dropped
349  * </ul></p>
350  *
351  * @param listener the listener which should be notified
352  *
353  * @exception IllegalArgumentException <ul>
354  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
355  * </ul>
356  * @exception SWTException <ul>
357  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
358  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
359  * </ul>
360  *
361  * @see DropTargetListener
362  * @see #getDropListeners
363  * @see #removeDropListener
364  * @see DropTargetEvent
365  */
366 public void addDropListener(DropTargetListener listener) {
367     if (listener is null) DND.error (SWT.ERROR_NULL_ARGUMENT);
368     DNDListener typedListener = new DNDListener (listener);
369     typedListener.dndWidget = this;
370     addListener (DND.DragEnter, typedListener);
371     addListener (DND.DragLeave, typedListener);
372     addListener (DND.DragOver, typedListener);
373     addListener (DND.DragOperationChanged, typedListener);
374     addListener (DND.Drop, typedListener);
375     addListener (DND.DropAccept, typedListener);
376 }
377 
378 protected override void checkSubclass () {
379     String name = this.classinfo.name;
380     String validName = DropTarget.classinfo.name;
381     if ( validName !=/*eq*/ name ) {
382         DND.error (SWT.ERROR_INVALID_SUBCLASS);
383     }
384 }
385 
386 void drag_data_received (
387     GtkWidget *widget,
388     GdkDragContext *context,
389     int x,
390     int y,
391     GtkSelectionData *data,
392     uint info,
393     uint time )
394 {
395     DNDEvent event = new DNDEvent();
396     if (data is null || !setEventData(context, x, y, time, event)) {
397         keyOperation = -1;
398         return;
399     }
400     keyOperation = -1;
401 
402     int allowedOperations = event.operations;
403 
404     // Get data in a Java format
405     Object object = null;
406     TransferData transferData = new TransferData();
407     if (data.data !is null) {
408         transferData.type = data.type;
409         transferData.length = data.length;
410         transferData.pValue = data.data;
411         transferData.format = data.format;
412         for (int i = 0; i < transferAgents.length; i++) {
413             Transfer transfer = transferAgents[i];
414             if (transfer !is null && transfer.isSupportedType(transferData)) {
415                 object = transfer.nativeToJava(transferData);
416                 break;
417             }
418         }
419     }
420     if (object is null) {
421         selectedOperation = DND.DROP_NONE;
422     }
423 
424     event.detail = selectedOperation;
425     event.dataType = transferData;
426     event.data = object;
427     selectedOperation = DND.DROP_NONE;
428     notifyListeners(DND.Drop, event);
429     if ((allowedOperations & event.detail) is event.detail) {
430         selectedOperation = event.detail;
431     }
432     //stop native handler
433     OS.g_signal_stop_emission_by_name(widget, OS.drag_data_received.ptr);
434 
435     //notify source of action taken
436     OS.gtk_drag_finish(context, selectedOperation !is DND.DROP_NONE, selectedOperation is DND.DROP_MOVE, time);
437     return;
438 }
439 
440 bool drag_drop(
441     GtkWidget *widget,
442     GdkDragContext *context,
443     int x,
444     int y,
445     uint time)
446 {
447     DNDEvent event = new DNDEvent();
448     if (!setEventData(context, x, y, time, event)) {
449         keyOperation = -1;
450         return false;
451     }
452     keyOperation = -1;
453 
454     int allowedOperations = event.operations;
455     TransferData[] allowedDataTypes = new TransferData[event.dataTypes.length];
456     System.arraycopy(event.dataTypes, 0, allowedDataTypes, 0, allowedDataTypes.length);
457 
458     event.dataType = selectedDataType;
459     event.detail = selectedOperation;
460     selectedDataType = null;
461     selectedOperation = DND.DROP_NONE;
462     notifyListeners(DND.DropAccept,event);
463     if (event.dataType !is null) {
464         for (int i = 0; i < allowedDataTypes.length; i++) {
465             if (allowedDataTypes[i].type is event.dataType.type) {
466                 selectedDataType = allowedDataTypes[i];
467                 break;
468             }
469         }
470     }
471     if (selectedDataType !is null && ((event.detail & allowedOperations) is event.detail)) {
472         selectedOperation = event.detail;
473     }
474     if (selectedOperation is DND.DROP_NONE) {
475         // this was not a successful drop
476         return false;
477     }
478     // ask drag source for dropped data
479     OS.gtk_drag_get_data(widget, context, selectedDataType.type, time);
480     return true;
481 }
482 
483 void drag_leave(
484     GtkWidget *widget,
485     GdkDragContext *context,
486     uint time )
487 {
488     updateDragOverHover(0, null);
489 
490     if (keyOperation is -1) return;
491     keyOperation = -1;
492 
493     DNDEvent event = new DNDEvent();
494     event.widget = this;
495     event.time = time;
496     event.detail = DND.DROP_NONE;
497     notifyListeners(DND.DragLeave, event);
498 }
499 
500 bool drag_motion (
501     GtkWidget *widget,
502     GdkDragContext *context,
503     int x,
504     int y,
505     uint time)
506 {
507     int oldKeyOperation = keyOperation;
508 
509     if (oldKeyOperation is -1) { //drag enter
510         selectedDataType = null;
511         selectedOperation = DND.DROP_NONE;
512     }
513 
514     DNDEvent event = new DNDEvent();
515     if (!setEventData(context, x, y, time, event)) {
516         keyOperation = -1;
517         OS.gdk_drag_status(context, 0, time);
518         return false;
519     }
520 
521     int allowedOperations = event.operations;
522     TransferData[] allowedDataTypes = new TransferData[event.dataTypes.length];
523     System.arraycopy(event.dataTypes, 0, allowedDataTypes, 0, allowedDataTypes.length);
524 
525     if (oldKeyOperation is -1) {
526         event.type = DND.DragEnter;
527     } else {
528         if (keyOperation is oldKeyOperation) {
529             event.type = DND.DragOver;
530             event.dataType = selectedDataType;
531             event.detail = selectedOperation;
532         } else {
533             event.type = DND.DragOperationChanged;
534             event.dataType = selectedDataType;
535         }
536     }
537     updateDragOverHover(DRAGOVER_HYSTERESIS, event);
538     selectedDataType = null;
539     selectedOperation = DND.DROP_NONE;
540     notifyListeners(event.type, event);
541     if (event.detail is DND.DROP_DEFAULT) {
542         event.detail = (allowedOperations & DND.DROP_MOVE) !is 0 ? DND.DROP_MOVE : DND.DROP_NONE;
543     }
544     if (event.dataType !is null) {
545         for (int i = 0; i < allowedDataTypes.length; i++) {
546             if (allowedDataTypes[i].type is event.dataType.type) {
547                 selectedDataType = allowedDataTypes[i];
548                 break;
549             }
550         }
551     }
552     if (selectedDataType !is null && (allowedOperations & event.detail) !is 0) {
553         selectedOperation = event.detail;
554     }
555 
556     switch (selectedOperation) {
557         case DND.DROP_NONE:
558             OS.gdk_drag_status(context, 0, time);
559             break;
560         case DND.DROP_COPY:
561             OS.gdk_drag_status(context, OS.GDK_ACTION_COPY, time);
562             break;
563         case DND.DROP_MOVE:
564             OS.gdk_drag_status(context, OS.GDK_ACTION_MOVE, time);
565             break;
566         case DND.DROP_LINK:
567             OS.gdk_drag_status(context, OS.GDK_ACTION_LINK, time);
568             break;
569         default:
570     }
571 
572     if (oldKeyOperation is -1) {
573         dragOverHeartbeat.run();
574     }
575     return true;
576 }
577 
578 /**
579  * Returns the Control which is registered for this DropTarget.  This is the control over which the
580  * user positions the cursor to drop the data.
581  *
582  * @return the Control which is registered for this DropTarget
583  */
584 public Control getControl () {
585     return control;
586 }
587 
588 /**
589  * Returns an array of listeners who will be notified when a drag and drop
590  * operation is in progress, by sending it one of the messages defined in
591  * the <code>DropTargetListener</code> interface.
592  *
593  * @return the listeners who will be notified when a drag and drop 
594  * operation is in progress
595  *
596  * @exception SWTException <ul>
597  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
598  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
599  * </ul>
600  *
601  * @see DropTargetListener
602  * @see #addDropListener
603  * @see #removeDropListener
604  * @see DropTargetEvent
605  *
606  * @since 3.4
607  */
608 public DropTargetListener[] getDropListeners() {
609     Listener[] listeners = getListeners(DND.DragEnter);
610     auto length = listeners.length;
611     DropTargetListener[] dropListeners = new DropTargetListener[length];
612     int count = 0;
613     for (typeof(length) i = 0; i < length; i++) {
614         Listener listener = listeners[i];
615         if ( auto l = cast(DNDListener)listener ) {
616             dropListeners[count] = cast(DropTargetListener) (l.getEventListener());
617             count++;
618         }
619     }
620     if (count is length) return dropListeners;
621     DropTargetListener[] result = new DropTargetListener[count];
622     SimpleType!(DropTargetListener).arraycopy(dropListeners, 0, result, 0, count);
623     return result;
624 }
625 
626 /**
627  * Returns the drop effect for this DropTarget.  This drop effect will be
628  * used during a drag and drop to display the drag under effect on the
629  * target widget.
630  *
631  * @return the drop effect that is registered for this DropTarget
632  *
633  * @since 3.3
634  */
635 public DropTargetEffect getDropTargetEffect() {
636     return dropEffect;
637 }
638 
639 int getOperationFromKeyState() {
640     int state;
641     OS.gdk_window_get_pointer(null, null, null, &state);
642     bool ctrl = (state & OS.GDK_CONTROL_MASK) !is 0;
643     bool shift = (state & OS.GDK_SHIFT_MASK) !is 0;
644     if (ctrl && shift) return DND.DROP_LINK;
645     if (ctrl)return DND.DROP_COPY;
646     if (shift)return DND.DROP_MOVE;
647     return DND.DROP_DEFAULT;
648 }
649 
650 /**
651  * Returns a list of the data types that can be transferred to this DropTarget.
652  *
653  * @return a list of the data types that can be transferred to this DropTarget
654  */
655 public Transfer[] getTransfer() {
656     return transferAgents;
657 }
658 
659 void onDispose(){
660     if (control is null) return;
661     OS.g_signal_handler_disconnect(control.handle, drag_motion_handler);
662     OS.g_signal_handler_disconnect(control.handle, drag_leave_handler);
663     OS.g_signal_handler_disconnect(control.handle, drag_data_received_handler);
664     OS.g_signal_handler_disconnect(control.handle, drag_drop_handler);
665     if (transferAgents.length !is 0)
666         OS.gtk_drag_dest_unset(control.handle);
667     transferAgents = null;
668     if (controlListener !is null)
669         control.removeListener(SWT.Dispose, controlListener);
670     control.setData(DND.DROP_TARGET_KEY, null);
671     control = null;
672     controlListener = null;
673 }
674 
675 int opToOsOp(int operation){
676     int osOperation = 0;
677     if ((operation & DND.DROP_COPY) is DND.DROP_COPY)
678         osOperation |= OS.GDK_ACTION_COPY;
679     if ((operation & DND.DROP_MOVE) is DND.DROP_MOVE)
680         osOperation |= OS.GDK_ACTION_MOVE;
681     if ((operation & DND.DROP_LINK) is DND.DROP_LINK)
682         osOperation |= OS.GDK_ACTION_LINK;
683     return osOperation;
684 }
685 
686 int osOpToOp(int osOperation){
687     int operation = DND.DROP_NONE;
688     if ((osOperation & OS.GDK_ACTION_COPY) is OS.GDK_ACTION_COPY)
689         operation |= DND.DROP_COPY;
690     if ((osOperation & OS.GDK_ACTION_MOVE) is OS.GDK_ACTION_MOVE)
691         operation |= DND.DROP_MOVE;
692     if ((osOperation & OS.GDK_ACTION_LINK) is OS.GDK_ACTION_LINK)
693         operation |= DND.DROP_LINK;
694     return operation;
695 }
696 
697 /**
698  * Removes the listener from the collection of listeners who will
699  * be notified when a drag and drop operation is in progress.
700  *
701  * @param listener the listener which should no longer be notified
702  *
703  * @exception IllegalArgumentException <ul>
704  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
705  * </ul>
706  * @exception SWTException <ul>
707  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
708  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
709  * </ul>
710  *
711  * @see DropTargetListener
712  * @see #addDropListener
713  * @see #getDropListeners
714  */
715 public void removeDropListener(DropTargetListener listener) {
716     if (listener is null) DND.error (SWT.ERROR_NULL_ARGUMENT);
717     removeListener (DND.DragEnter, listener);
718     removeListener (DND.DragLeave, listener);
719     removeListener (DND.DragOver, listener);
720     removeListener (DND.DragOperationChanged, listener);
721     removeListener (DND.Drop, listener);
722     removeListener (DND.DropAccept, listener);
723 }
724 
725 /**
726  * Specifies the data types that can be transferred to this DropTarget.  If data is
727  * being dragged that does not match one of these types, the drop target will be notified of
728  * the drag and drop operation but the currentDataType will be null and the operation
729  * will be DND.NONE.
730  *
731  * @param transferAgents a list of Transfer objects which define the types of data that can be
732  *                       dropped on this target
733  *
734  * @exception IllegalArgumentException <ul>
735  *    <li>ERROR_NULL_ARGUMENT - if transferAgents is null</li>
736  * </ul>
737  */
738 public void setTransfer(Transfer[] transferAgents){
739     if (transferAgents is null) DND.error(SWT.ERROR_NULL_ARGUMENT);
740 
741     if (this.transferAgents.length !is 0) {
742         OS.gtk_drag_dest_unset(control.handle);
743     }
744     this.transferAgents = transferAgents;
745 
746     GtkTargetEntry*[] targets;
747     for (int i = 0; i < transferAgents.length; i++) {
748         Transfer transfer = transferAgents[i];
749         if (transfer !is null) {
750             int[] typeIds = transfer.getTypeIds();
751             String[] typeNames = transfer.getTypeNames();
752             for (int j = 0; j < typeIds.length; j++) {
753                 GtkTargetEntry* entry = new GtkTargetEntry();
754                 entry.target = cast(char*)
755                     OS.g_malloc(typeNames[j].length +1);
756                 entry.target[ 0 .. typeNames[j].length ] = typeNames[j];
757                 entry.target[ typeNames[j].length ] = '\0';
758                 entry.info = typeIds[j];
759                 GtkTargetEntry*[] newTargets = new GtkTargetEntry*[targets.length + 1];
760                 SimpleType!(GtkTargetEntry*).arraycopy(targets, 0, newTargets,
761                                              0, targets.length);
762                 newTargets[targets.length] = entry;
763                 targets = newTargets;
764             }
765         }
766     }
767 
768     auto pTargets = OS.g_malloc(targets.length * GtkTargetEntry.sizeof);
769     for (int i = 0; i < targets.length; i++) {
770         OS.memmove(pTargets + i*GtkTargetEntry.sizeof, targets[i], GtkTargetEntry.sizeof);
771     }
772 
773     int actions = opToOsOp(getStyle());
774     if ( auto c = cast(Combo)control ) {
775         if ((control.getStyle() & SWT.READ_ONLY) is 0) {
776             auto entryHandle = OS.gtk_bin_get_child (control.handle);
777             if (entryHandle !is null) {
778                 OS.gtk_drag_dest_unset(entryHandle);
779             }
780         }
781     }
782     OS.gtk_drag_dest_set(control.handle, 0, pTargets,
783                          cast(int)/*64bit*/targets.length, actions);
784 
785     for (int i = 0; i < targets.length; i++) {
786         OS.g_free(targets[i].target);
787     }
788 }
789 
790 /**
791  * Specifies the drop effect for this DropTarget.  This drop effect will be
792  * used during a drag and drop to display the drag under effect on the
793  * target widget.
794  *
795  * @param effect the drop effect that is registered for this DropTarget
796  *
797  * @since 3.3
798  */
799 public void setDropTargetEffect(DropTargetEffect effect) {
800     dropEffect = effect;
801 }
802 
803 bool setEventData(GdkDragContext* dragContext, int x, int y, int time, DNDEvent event) {
804     if (dragContext is null) return false;
805     if (dragContext.targets is null) return false;
806 
807     // get allowed operations
808     int style = getStyle();
809     int operations = osOpToOp(dragContext.actions) & style;
810     if (operations is DND.DROP_NONE) return false;
811 
812     // get current operation
813     int operation = getOperationFromKeyState();
814     keyOperation = operation;
815     if (operation is DND.DROP_DEFAULT) {
816         if ((style & DND.DROP_DEFAULT) is 0) {
817             operation = (operations & DND.DROP_MOVE) !is 0 ? DND.DROP_MOVE : DND.DROP_NONE;
818         }
819     } else {
820         if ((operation & operations) is 0) operation = DND.DROP_NONE;
821     }
822 
823     // Get allowed transfer types
824     int length = OS.g_list_length(dragContext.targets);
825     TransferData[] dataTypes = new TransferData[0];
826     for (int i = 0; i < length; i++) {
827         auto pData = OS.g_list_nth(dragContext.targets, i);
828         GtkTargetPair* gtkTargetPair = cast(GtkTargetPair*)pData;
829         TransferData data = new TransferData();
830         data.type = gtkTargetPair.target;
831         for (int j = 0; j < transferAgents.length; j++) {
832             Transfer transfer = transferAgents[j];
833             if (transfer !is null && transfer.isSupportedType(data)) {
834                 TransferData[] newDataTypes = new TransferData[dataTypes.length + 1];
835                 System.arraycopy(dataTypes, 0, newDataTypes, 0, dataTypes.length);
836                 newDataTypes[dataTypes.length] = data;
837                 dataTypes = newDataTypes;
838                 break;
839             }
840         }
841     }
842     if (dataTypes.length is 0) return false;
843 
844     auto window = OS.GTK_WIDGET_WINDOW(control.handle);
845     int origin_x, origin_y;
846     OS.gdk_window_get_origin(window, &origin_x, &origin_y);
847     Point coordinates = new Point(origin_x + x, origin_y + y);
848 
849     event.widget = this;
850     event.x = coordinates.x;
851     event.y = coordinates.y;
852     event.time = time;
853     event.feedback = DND.FEEDBACK_SELECT;
854     event.dataTypes = dataTypes;
855     event.dataType = dataTypes[0];
856     event.operations = operations;
857     event.detail = operation;
858     if (dropEffect !is null) {
859         event.item = dropEffect.getItem(coordinates.x, coordinates.y);
860     }
861     return true;
862 }
863 
864 void updateDragOverHover(long delay, DNDEvent event) {
865     if (delay is 0) {
866         dragOverStart = 0;
867         dragOverEvent = null;
868         return;
869     }
870     dragOverStart = System.currentTimeMillis() + delay;
871     if (dragOverEvent is null) dragOverEvent = new DNDEvent();
872     dragOverEvent.x = event.x;
873     dragOverEvent.y = event.y;
874     TransferData[] dataTypes = new TransferData[ event.dataTypes.length];
875     System.arraycopy( event.dataTypes, 0, dataTypes, 0, dataTypes.length);
876     dragOverEvent.dataTypes  = dataTypes;
877     dragOverEvent.operations = event.operations;
878     dragOverEvent.time = event.time;
879 }
880 
881 }