root/dwt/dnd/DropTarget.d

Revision 246:fd9c62a2998e, 27.3 kB (checked in by Frank Benoit <benoit@tionex.de>, 6 months ago)

Updater SWT 3.4M7 to 3.4

Line 
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 dwt.dnd.DropTarget;
14
15 import dwt.DWT;
16 import dwt.DWTError;
17 import dwt.DWTException;
18 import dwt.internal.C;
19 import dwt.internal.ole.win32.COM;
20 import dwt.internal.ole.win32.extras;
21 import dwt.internal.ole.win32.OBJIDL;
22 import dwt.internal.ole.win32.OLEIDL;
23 import dwt.internal.ole.win32.ifs;
24 import dwt.internal.win32.OS;
25 import dwt.widgets.Control;
26 import dwt.widgets.Event;
27 import dwt.widgets.Listener;
28 import dwt.widgets.Table;
29 import dwt.widgets.Tree;
30 import dwt.widgets.Widget;
31
32 import dwt.dnd.Transfer;
33 import dwt.dnd.DropTargetEffect;
34 import dwt.dnd.TransferData;
35 import dwt.dnd.DropTargetListener;
36 import dwt.dnd.DNDListener;
37 import dwt.dnd.DNDEvent;
38 import dwt.dnd.DND;
39 import dwt.dnd.TableDropTargetEffect;
40 import dwt.dnd.TreeDropTargetEffect;
41
42 import dwt.dwthelper.utils;
43
44 /**
45  *
46  * Class <code>DropTarget</code> defines the target object for a drag and drop transfer.
47  *
48  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
49  *
50  * <p>This class identifies the <code>Control</code> over which the user must position the cursor
51  * in order to drop the data being transferred.  It also specifies what data types can be dropped on
52  * this control and what operations can be performed.  You may have several DropTragets in an
53  * application but there can only be a one to one mapping between a <code>Control</code> and a <code>DropTarget</code>.
54  * The DropTarget can receive data from within the same application or from other applications
55  * (such as text dragged from a text editor like Word).</p>
56  *
57  * <code><pre>
58  *  int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
59  *  Transfer[] types = new Transfer[] {TextTransfer.getInstance()};
60  *  DropTarget target = new DropTarget(label, operations);
61  *  target.setTransfer(types);
62  * </code></pre>
63  *
64  * <p>The application is notified of data being dragged over this control and of when a drop occurs by
65  * implementing the interface <code>DropTargetListener</code> which uses the class
66  * <code>DropTargetEvent</code>.  The application can modify the type of drag being performed
67  * on this Control at any stage of the drag by modifying the <code>event.detail</code> field or the
68  * <code>event.currentDataType</code> field.  When the data is dropped, it is the responsibility of
69  * the application to copy this data for its own purposes.
70  *
71  * <code><pre>
72  *  target.addDropListener (new DropTargetListener() {
73  *      public void dragEnter(DropTargetEvent event) {};
74  *      public void dragOver(DropTargetEvent event) {};
75  *      public void dragLeave(DropTargetEvent event) {};
76  *      public void dragOperationChanged(DropTargetEvent event) {};
77  *      public void dropAccept(DropTargetEvent event) {}
78  *      public void drop(DropTargetEvent event) {
79  *          // A drop has occurred, copy over the data
80  *          if (event.data is null) { // no data to copy, indicate failure in event.detail
81  *              event.detail = DND.DROP_NONE;
82  *              return;
83  *          }
84  *          label.setText ((String) event.data); // data copied to label text
85  *      }
86  *  });
87  * </pre></code>
88  *
89  * <dl>
90  *  <dt><b>Styles</b></dt> <dd>DND.DROP_NONE, DND.DROP_COPY, DND.DROP_MOVE, DND.DROP_LINK</dd>
91  *  <dt><b>Events</b></dt> <dd>DND.DragEnter, DND.DragLeave, DND.DragOver, DND.DragOperationChanged,
92  *                             DND.DropAccept, DND.Drop </dd>
93  * </dl>
94  *
95  * @see <a href="http://www.eclipse.org/swt/snippets/#dnd">Drag and Drop snippets</a>
96  * @see <a href="http://www.eclipse.org/swt/examples.php">DWT Example: DNDExample</a>
97  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
98  */
99 public class DropTarget : Widget {
100
101     Control control;
102     Listener controlListener;
103     Transfer[] transferAgents;
104     DropTargetEffect dropEffect;
105
106     // Track application selections
107     TransferData selectedDataType;
108     int selectedOperation;
109
110     // workaround - There is no event for "operation changed" so track operation based on key state
111     int keyOperation = -1;
112
113     // workaround - The dataobject address is only passed as an argument in drag enter and drop.
114     // To allow applications to query the data values during the drag over operations,
115     // maintain a reference to it.
116     IDataObject iDataObject;
117
118     // interfaces
119     _IDropTargetImpl iDropTarget;
120     int refCount;
121
122     static final String DEFAULT_DROP_TARGET_EFFECT = "DEFAULT_DROP_TARGET_EFFECT"; //$NON-NLS-1$
123
124 /**
125  * Creates a new <code>DropTarget</code> to allow data to be dropped on the specified
126  * <code>Control</code>.
127  * Creating an instance of a DropTarget may cause system resources to be allocated
128  * depending on the platform.  It is therefore mandatory that the DropTarget instance
129  * be disposed when no longer required.
130  *
131  * @param control the <code>Control</code> over which the user positions the cursor to drop the data
132  * @param style the bitwise OR'ing of allowed operations; this may be a combination of any of
133  *         DND.DROP_NONE, DND.DROP_COPY, DND.DROP_MOVE, DND.DROP_LINK
134  *
135  * @exception DWTException <ul>
136  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
137  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
138  * </ul>
139  * @exception DWTError <ul>
140  *    <li>ERROR_CANNOT_INIT_DROP - unable to initiate drop target; this will occur if more than one
141  *        drop target is created for a control or if the operating system will not allow the creation
142  *        of the drop target</li>
143  * </ul>
144  *
145  * <p>NOTE: ERROR_CANNOT_INIT_DROP should be an DWTException, since it is a
146  * recoverable error, but can not be changed due to backward compatibility.</p>
147  *
148  * @see Widget#dispose
149  * @see DropTarget#checkSubclass
150  * @see DND#DROP_NONE
151  * @see DND#DROP_COPY
152  * @see DND#DROP_MOVE
153  * @see DND#DROP_LINK
154  */
155 public this(Control control, int style) {
156     super (control, checkStyle(style));
157     this.control = control;
158     if (control.getData(DND.DROP_TARGET_KEY) !is null) {
159         DND.error(DND.ERROR_CANNOT_INIT_DROP);
160     }
161     control.setData(DND.DROP_TARGET_KEY, this);
162     createCOMInterfaces();
163     this.AddRef();
164
165     if (COM.CoLockObjectExternal(iDropTarget, true, true) !is COM.S_OK)
166         DND.error(DND.ERROR_CANNOT_INIT_DROP);
167     if (COM.RegisterDragDrop( control.handle, iDropTarget) !is COM.S_OK)
168         DND.error(DND.ERROR_CANNOT_INIT_DROP);
169
170     controlListener = new class() Listener {
171         public void handleEvent (Event event) {
172             if (!this.outer.isDisposed()){
173                 this.outer.dispose();
174             }
175         }
176     };
177     control.addListener (DWT.Dispose, controlListener);
178
179     this.addListener(DWT.Dispose, new class() Listener {
180         public void handleEvent (Event event) {
181             onDispose();
182         }
183     });
184
185     Object effect = control.getData(DEFAULT_DROP_TARGET_EFFECT);
186     if ( auto dte = cast(DropTargetEffect) effect ) {
187         dropEffect = dte;
188     } else if ( auto table = cast(Table)control ) {
189         dropEffect = new TableDropTargetEffect(table);
190     } else if ( auto tree = cast(Tree)control ) {
191         dropEffect = new TreeDropTargetEffect(tree);
192     }
193 }
194
195 static int checkStyle (int style) {
196     if (style is DWT.NONE) return DND.DROP_MOVE;
197     return style;
198 }
199
200 /**
201  * Adds the listener to the collection of listeners who will
202  * be notified when a drag and drop operation is in progress, by sending
203  * it one of the messages defined in the <code>DropTargetListener</code>
204  * interface.
205  *
206  * <p><ul>
207  * <li><code>dragEnter</code> is called when the cursor has entered the drop target boundaries
208  * <li><code>dragLeave</code> is called when the cursor has left the drop target boundaries and just before
209  * the drop occurs or is cancelled.
210  * <li><code>dragOperationChanged</code> is called when the operation being performed has changed
211  * (usually due to the user changing the selected modifier key(s) while dragging)
212  * <li><code>dragOver</code> is called when the cursor is moving over the drop target
213  * <li><code>dropAccept</code> is called just before the drop is performed.  The drop target is given
214  * the chance to change the nature of the drop or veto the drop by setting the <code>event.detail</code> field
215  * <li><code>drop</code> is called when the data is being dropped
216  * </ul></p>
217  *
218  * @param listener the listener which should be notified
219  *
220  * @exception IllegalArgumentException <ul>
221  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
222  * </ul>
223  * @exception DWTException <ul>
224  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
225  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
226  * </ul>
227  *
228  * @see DropTargetListener
229  * @see #getDropListeners
230  * @see #removeDropListener
231  * @see DropTargetEvent
232  */
233 public void addDropListener(DropTargetListener listener) {
234     if (listener is null) DND.error (DWT.ERROR_NULL_ARGUMENT);
235     DNDListener typedListener = new DNDListener (listener);
236     typedListener.dndWidget = this;
237     addListener (DND.DragEnter, typedListener);
238     addListener (DND.DragLeave, typedListener);
239     addListener (DND.DragOver, typedListener);
240     addListener (DND.DragOperationChanged, typedListener);
241     addListener (DND.Drop, typedListener);
242     addListener (DND.DropAccept, typedListener);
243 }
244
245 ULONG AddRef() {
246     refCount++;
247     return refCount;
248 }
249
250 protected void checkSubclass () {
251     String name = this.classinfo.name;
252     String validName = DropTarget.classinfo.name;
253     if (validName!=/*eq*/name) {
254         DND.error (DWT.ERROR_INVALID_SUBCLASS);
255     }
256 }
257
258 void createCOMInterfaces() {
259     // register each of the interfaces that this object implements
260     iDropTarget = new _IDropTargetImpl(this);
261 }
262
263 void disposeCOMInterfaces() {
264     iDropTarget = null;
265 }
266
267 int DragEnter_64(IDataObject pDataObject, DWORD grfKeyState, long pt, DWORD* pdwEffect) {
268     POINTL point;
269     OS.MoveMemory( &point, &pt, 8);
270     return DragEnter(pDataObject, grfKeyState, point, pdwEffect);
271 }
272
273 HRESULT DragEnter(IDataObject pDataObject, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {
274     selectedDataType = null;
275     selectedOperation = DND.DROP_NONE;
276     if (iDataObject !is null) iDataObject.Release();
277     iDataObject = null;
278
279     DNDEvent event = new DNDEvent();
280     if (!setEventData(event, pDataObject, grfKeyState, pt, pdwEffect)) {
281         *pdwEffect = COM.DROPEFFECT_NONE;
282         return COM.S_FALSE;
283     }
284
285     // Remember the iDataObject because it is not passed into the DragOver callback
286     iDataObject = pDataObject;
287     iDataObject.AddRef();
288
289     int allowedOperations = event.operations;
290     TransferData[] allowedDataTypes = new TransferData[event.dataTypes.length];
291     System.arraycopy(event.dataTypes, 0, allowedDataTypes, 0, allowedDataTypes.length);
292     notifyListeners(DND.DragEnter, event);
293     refresh();
294     if (event.detail is DND.DROP_DEFAULT) {
295         event.detail = (allowedOperations & DND.DROP_MOVE) !is 0 ? DND.DROP_MOVE : DND.DROP_NONE;
296     }
297
298     selectedDataType = null;
299     for (int i = 0; i < allowedDataTypes.length; i++) {
300         if (TransferData.sameType(allowedDataTypes[i], event.dataType)){
301             selectedDataType = allowedDataTypes[i];
302             break;
303         }
304     }
305
306     selectedOperation = DND.DROP_NONE;
307     if (selectedDataType !is null && ((allowedOperations & event.detail) !is 0)) {
308         selectedOperation = event.detail;
309     }
310
311     *pdwEffect = opToOs(selectedOperation);
312     return COM.S_OK;
313 }
314
315 HRESULT DragLeave() {
316     keyOperation = -1;
317
318     if (iDataObject is null) return COM.S_FALSE;
319
320     DNDEvent event = new DNDEvent();
321     event.widget = this;
322     event.time = OS.GetMessageTime();
323     event.detail = DND.DROP_NONE;
324     notifyListeners(DND.DragLeave, event);
325     refresh();
326
327     iDataObject.Release();
328     iDataObject = null;
329     return COM.S_OK;
330 }
331
332 int DragOver_64(int grfKeyState, long pt, DWORD* pdwEffect) {
333     POINTL point;
334     OS.MoveMemory(&point, &pt, 8);
335     return DragOver(grfKeyState, point, pdwEffect);
336 }
337
338 HRESULT DragOver(int grfKeyState, POINTL pt, DWORD* pdwEffect) {
339     if (iDataObject is null) return COM.S_FALSE;
340     int oldKeyOperation = keyOperation;
341
342     DNDEvent event = new DNDEvent();
343     if (!setEventData(event, iDataObject, grfKeyState, pt, pdwEffect)) {
344         keyOperation = -1;
345         *pdwEffect = COM.DROPEFFECT_NONE;
346         return COM.S_FALSE;
347     }
348
349     int allowedOperations = event.operations;
350     TransferData[] allowedDataTypes = new TransferData[event.dataTypes.length];
351     System.arraycopy(event.dataTypes, 0, allowedDataTypes, 0, allowedDataTypes.length);
352
353     if (keyOperation is oldKeyOperation) {
354         event.type = DND.DragOver;
355         event.dataType = selectedDataType;
356         event.detail = selectedOperation;
357     } else {
358         event.type = DND.DragOperationChanged;
359         event.dataType = selectedDataType;
360     }
361     notifyListeners(event.type, event);
362     refresh();
363     if (event.detail is DND.DROP_DEFAULT) {
364         event.detail = (allowedOperations & DND.DROP_MOVE) !is 0 ? DND.DROP_MOVE : DND.DROP_NONE;
365     }
366
367     selectedDataType = null;
368     for (int i = 0; i < allowedDataTypes.length; i++) {
369         if (TransferData.sameType(allowedDataTypes[i], event.dataType)){
370             selectedDataType = allowedDataTypes[i];
371             break;
372         }
373     }
374
375     selectedOperation = DND.DROP_NONE;
376     if (selectedDataType !is null && ((allowedOperations & event.detail) is event.detail)) {
377         selectedOperation = event.detail;
378     }
379
380     *pdwEffect = opToOs(selectedOperation);
381     return COM.S_OK;
382 }
383
384 int Drop_64(IDataObject pDataObject, int grfKeyState, long pt, DWORD* pdwEffect) {
385     POINTL point;
386     OS.MoveMemory(&point, &pt, 8);
387     return Drop(pDataObject, grfKeyState, point, pdwEffect);
388 }
389
390 HRESULT Drop(IDataObject pDataObject, int grfKeyState, POINTL pt, DWORD* pdwEffect) {
391     DNDEvent event = new DNDEvent();
392     event.widget = this;
393     event.time = OS.GetMessageTime();
394     if (dropEffect !is null) {
395         event.item = dropEffect.getItem(pt.x, pt.y);
396     }
397     event.detail = DND.DROP_NONE;
398     notifyListeners(DND.DragLeave, event);
399     refresh();
400
401     event = new DNDEvent();
402     if (!setEventData(event, pDataObject, grfKeyState, pt, pdwEffect)) {
403         keyOperation = -1;
404         *pdwEffect = COM.DROPEFFECT_NONE;
405         return COM.S_FALSE;
406     }
407     keyOperation = -1;
408     int allowedOperations = event.operations;
409     TransferData[] allowedDataTypes = new TransferData[event.dataTypes.length];
410     System.arraycopy(event.dataTypes, 0, allowedDataTypes, 0, allowedDataTypes.length);
411     event.dataType = selectedDataType;
412     event.detail = selectedOperation;
413     notifyListeners(DND.DropAccept,event);
414     refresh();
415
416     selectedDataType = null;
417     for (int i = 0; i < allowedDataTypes.length; i++) {
418         if (TransferData.sameType(allowedDataTypes[i], event.dataType)){
419             selectedDataType = allowedDataTypes[i];
420             break;
421         }
422     }
423     selectedOperation = DND.DROP_NONE;
424     if (selectedDataType !is null && (allowedOperations & event.detail) is event.detail) {
425         selectedOperation = event.detail;
426     }
427
428     if (selectedOperation is DND.DROP_NONE){
429         *pdwEffect = COM.DROPEFFECT_NONE;
430         return COM.S_OK;
431     }
432
433     // Get Data in a Java format
434     Object object = null;
435     for (int i = 0; i < transferAgents.length; i++){
436         Transfer transfer = transferAgents[i];
437         if (transfer !is null && transfer.isSupportedType(selectedDataType)){
438             object = transfer.nativeToJava(selectedDataType);
439             break;
440         }
441     }
442     if (object is null){
443         selectedOperation = DND.DROP_NONE;
444     }
445
446     event.detail = selectedOperation;
447     event.dataType = selectedDataType;
448     event.data = object;
449     OS.ImageList_DragShowNolock(false);
450     try {
451         notifyListeners(DND.Drop,event);
452     } finally {
453         OS.ImageList_DragShowNolock(true);
454     }
455     refresh();
456     selectedOperation = DND.DROP_NONE;
457     if ((allowedOperations & event.detail) is event.detail) {
458         selectedOperation = event.detail;
459     }
460     //notify source of action taken
461     *pdwEffect = opToOs(selectedOperation);
462     return COM.S_OK;
463 }
464
465 /**
466  * Returns the Control which is registered for this DropTarget.  This is the control over which the
467  * user positions the cursor to drop the data.
468  *
469  * @return the Control which is registered for this DropTarget
470  */
471 public Control getControl () {
472     return control;
473 }
474
475 /**
476  * Returns an array of listeners who will be notified when a drag and drop
477  * operation is in progress, by sending it one of the messages defined in
478  * the <code>DropTargetListener</code> interface.
479  *
480  * @return the listeners who will be notified when a drag and drop
481  * operation is in progress
482  *
483  * @exception DWTException <ul>
484  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
485  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
486  * </ul>
487  *
488  * @see DropTargetListener
489  * @see #addDropListener
490  * @see #removeDropListener
491  * @see DropTargetEvent
492  *
493  * @since 3.4
494  */
495 public DropTargetListener[] getDropListeners() {
496     Listener[] listeners = getListeners(DND.DragEnter);
497     int length = listeners.length;
498     DropTargetListener[] dropListeners = new DropTargetListener[length];
499     int count = 0;
500     for (int i = 0; i < length; i++) {
501         Listener listener = listeners[i];
502         if (auto l = cast(DNDListener)listener ) {
503             dropListeners[count] = cast(DropTargetListener) (l.getEventListener());
504             count++;
505         }
506     }
507     if (count is length) return dropListeners;
508     DropTargetListener[] result = new DropTargetListener[count];
509     SimpleType!(DropTargetListener).arraycopy(dropListeners, 0, result, 0, count);
510     return result;
511 }
512
513 /**
514  * Returns the drop effect for this DropTarget.  This drop effect will be
515  * used during a drag and drop to display the drag under effect on the
516  * target widget.
517  *
518  * @return the drop effect that is registered for this DropTarget
519  *
520  * @since 3.3
521  */
522 public DropTargetEffect getDropTargetEffect() {
523     return dropEffect;
524 }
525
526 int getOperationFromKeyState(int grfKeyState) {
527     bool ctrl = (grfKeyState & OS.MK_CONTROL) !is 0;
528     bool shift = (grfKeyState & OS.MK_SHIFT) !is 0;
529     bool alt = (grfKeyState & OS.MK_ALT) !is 0;
530     if (alt) {
531         if (ctrl || shift) return DND.DROP_DEFAULT;
532         return DND.DROP_LINK;
533     }
534     if (ctrl && shift) return DND.DROP_LINK;
535     if (ctrl)return DND.DROP_COPY;
536     if (shift)return DND.DROP_MOVE;
537     return DND.DROP_DEFAULT;
538 }
539
540 /**
541  * Returns a list of the data types that can be transferred to this DropTarget.
542  *
543  * @return a list of the data types that can be transferred to this DropTarget
544  */
545 public Transfer[] getTransfer() {
546     return transferAgents;
547 }
548
549 void onDispose () {
550     if (control is null) return;
551
552     COM.RevokeDragDrop(control.handle);
553
554     if (controlListener !is null)
555         control.removeListener(DWT.Dispose, controlListener);
556     controlListener = null;
557     control.setData(DND.DROP_TARGET_KEY, null);
558     transferAgents = null;
559     control = null;
560
561     COM.CoLockObjectExternal(iDropTarget, false, true);
562
563     this.Release();
564
565     COM.CoFreeUnusedLibraries();
566 }
567
568 int opToOs(int operation) {
569     int osOperation = 0;
570     if ((operation & DND.DROP_COPY) !is 0){
571         osOperation |= COM.DROPEFFECT_COPY;
572     }
573     if ((operation & DND.DROP_LINK) !is 0) {
574         osOperation |= COM.DROPEFFECT_LINK;
575     }
576     if ((operation & DND.DROP_MOVE) !is 0) {
577         osOperation |= COM.DROPEFFECT_MOVE;
578     }
579     return osOperation;
580 }
581
582 int osToOp(int osOperation){
583     int operation = 0;
584     if ((osOperation & COM.DROPEFFECT_COPY) !is 0){
585         operation |= DND.DROP_COPY;
586     }
587     if ((osOperation & COM.DROPEFFECT_LINK) !is 0) {
588         operation |= DND.DROP_LINK;
589     }
590     if ((osOperation & COM.DROPEFFECT_MOVE) !is 0) {
591         operation |= DND.DROP_MOVE;
592     }
593     return operation;
594 }
595
596 /* QueryInterface([in] iid, [out] ppvObject)
597  * Ownership of ppvObject transfers from callee to caller so reference count on ppvObject
598  * must be incremented before returning.  Caller is responsible for releasing ppvObject.
599  */
600 HRESULT QueryInterface(REFIID riid, void ** ppvObject) {
601
602     if (riid is null || ppvObject is null)
603         return COM.E_INVALIDARG;
604     if (COM.IsEqualGUID(riid, &COM.IIDIUnknown) || COM.IsEqualGUID(riid, &COM.IIDIDropTarget)) {
605         *ppvObject = cast(void*)cast(IDropTarget) iDropTarget;
606         AddRef();
607         return COM.S_OK;
608     }
609
610     *ppvObject = null;
611     return COM.E_NOINTERFACE;
612 }
613
614 ULONG Release() {
615