1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/ole/droptgt.cpp
3 // Purpose:     wxDropTarget implementation
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:
7 // RCS-ID:      $Id: droptgt.cpp 54398 2008-06-28 01:40:42Z VZ $
8 // Copyright:   (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence:     wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11 
12 // ============================================================================
13 // Declarations
14 // ============================================================================
15 
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19 
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22 
23 #if defined(__BORLANDC__)
24     #pragma hdrstop
25 #endif
26 
27 #if wxUSE_OLE && wxUSE_DRAG_AND_DROP
28 
29 #ifndef WX_PRECOMP
30     #include "wx/msw/wrapwin.h"
31     #include "wx/log.h"
32 #endif
33 
34 #include "wx/msw/private.h"
35 
36 #ifdef __WXWINCE__
37     #include <winreg.h>
38     #include <ole2.h>
39 #endif
40 
41 #ifdef __WIN32__
42     #if !defined(__GNUWIN32__) || wxUSE_NORLANDER_HEADERS
43         #include <shlobj.h>            // for DROPFILES structure
44     #endif
45 #else
46     #include <shellapi.h>
47 #endif
48 
49 #include "wx/dnd.h"
50 
51 #include "wx/msw/ole/oleutils.h"
52 
53 // ----------------------------------------------------------------------------
54 // IDropTarget interface: forward all interesting things to wxDropTarget
55 // (the name is unfortunate, but wx_I_DropTarget is not at all the same thing
56 //  as wxDropTarget which is 'public' class, while this one is private)
57 // ----------------------------------------------------------------------------
58 
59 class wxIDropTarget : public IDropTarget
60 {
61 public:
62     wxIDropTarget(wxDropTarget *p);
63     virtual ~wxIDropTarget();
64 
65     // accessors for wxDropTarget
SetHwnd(HWND hwnd)66     void SetHwnd(HWND hwnd) { m_hwnd = hwnd; }
67 
68     // IDropTarget methods
69     STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
70     STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
71     STDMETHODIMP DragLeave();
72     STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
73 
74     DECLARE_IUNKNOWN_METHODS;
75 
76 protected:
77     IDataObject  *m_pIDataObject; // !NULL between DragEnter and DragLeave/Drop
78     wxDropTarget *m_pTarget;      // the real target (we're just a proxy)
79 
80     HWND          m_hwnd;         // window we're associated with
81 
82     // get default drop effect for given keyboard flags
83     static DWORD GetDropEffect(DWORD flags, wxDragResult defaultAction, DWORD pdwEffect);
84 
85     DECLARE_NO_COPY_CLASS(wxIDropTarget)
86 };
87 
88 // ----------------------------------------------------------------------------
89 // private functions
90 // ----------------------------------------------------------------------------
91 
92 static wxDragResult ConvertDragEffectToResult(DWORD dwEffect);
93 static DWORD ConvertDragResultToEffect(wxDragResult result);
94 
95 // ============================================================================
96 // wxIDropTarget implementation
97 // ============================================================================
98 
99 // Name    : static wxIDropTarget::GetDropEffect
100 // Purpose : determine the drop operation from keyboard/mouse state.
101 // Returns : DWORD combined from DROPEFFECT_xxx constants
102 // Params  : [in] DWORD flags       kbd & mouse flags as passed to
103 //                                  IDropTarget methods
104 // Notes   : We do "move" normally and "copy" if <Ctrl> is pressed,
105 //           which is the standard behaviour (currently there is no
106 //           way to redefine it)
GetDropEffect(DWORD flags,wxDragResult defaultAction,DWORD pdwEffect)107 DWORD wxIDropTarget::GetDropEffect(DWORD flags,
108                                    wxDragResult defaultAction,
109                                    DWORD pdwEffect)
110 {
111     DWORD effectiveAction;
112     if ( defaultAction == wxDragCopy )
113         effectiveAction = flags & MK_SHIFT ? DROPEFFECT_MOVE : DROPEFFECT_COPY;
114     else
115         effectiveAction = flags & MK_CONTROL ? DROPEFFECT_COPY : DROPEFFECT_MOVE;
116 
117     if ( !(effectiveAction & pdwEffect) )
118     {
119         // the action is not supported by drag source, fall back to something
120         // that it does support
121         if ( pdwEffect & DROPEFFECT_MOVE )
122             effectiveAction = DROPEFFECT_MOVE;
123         else if ( pdwEffect & DROPEFFECT_COPY )
124             effectiveAction = DROPEFFECT_COPY;
125         else if ( pdwEffect & DROPEFFECT_LINK )
126             effectiveAction = DROPEFFECT_LINK;
127         else
128             effectiveAction = DROPEFFECT_NONE;
129     }
130 
131     return effectiveAction;
132 }
133 
wxIDropTarget(wxDropTarget * pTarget)134 wxIDropTarget::wxIDropTarget(wxDropTarget *pTarget)
135 {
136   m_pTarget      = pTarget;
137   m_pIDataObject = NULL;
138 }
139 
~wxIDropTarget()140 wxIDropTarget::~wxIDropTarget()
141 {
142 }
143 
144 BEGIN_IID_TABLE(wxIDropTarget)
145   ADD_IID(Unknown)
146   ADD_IID(DropTarget)
147 END_IID_TABLE;
148 
IMPLEMENT_IUNKNOWN_METHODS(wxIDropTarget)149 IMPLEMENT_IUNKNOWN_METHODS(wxIDropTarget)
150 
151 // Name    : wxIDropTarget::DragEnter
152 // Purpose : Called when the mouse enters the window (dragging something)
153 // Returns : S_OK
154 // Params  : [in] IDataObject *pIDataSource : source data
155 //           [in] DWORD        grfKeyState  : kbd & mouse state
156 //           [in] POINTL       pt           : mouse coordinates
157 //           [out]DWORD       *pdwEffect    : effect flag
158 // Notes   :
159 STDMETHODIMP wxIDropTarget::DragEnter(IDataObject *pIDataSource,
160                                       DWORD        grfKeyState,
161                                       POINTL       pt,
162                                       DWORD       *pdwEffect)
163 {
164     wxLogTrace(wxTRACE_OleCalls, wxT("IDropTarget::DragEnter"));
165 
166     wxASSERT_MSG( m_pIDataObject == NULL,
167                   _T("drop target must have data object") );
168 
169     // show the list of formats supported by the source data object for the
170     // debugging purposes, this is quite useful sometimes - please don't remove
171 #if 0
172     IEnumFORMATETC *penumFmt;
173     if ( SUCCEEDED(pIDataSource->EnumFormatEtc(DATADIR_GET, &penumFmt)) )
174     {
175         FORMATETC fmt;
176         while ( penumFmt->Next(1, &fmt, NULL) == S_OK )
177         {
178             wxLogDebug(_T("Drop source supports format %s"),
179                        wxDataObject::GetFormatName(fmt.cfFormat));
180         }
181 
182         penumFmt->Release();
183     }
184     else
185     {
186         wxLogLastError(_T("IDataObject::EnumFormatEtc"));
187     }
188 #endif // 0
189 
190     if ( !m_pTarget->IsAcceptedData(pIDataSource) ) {
191         // we don't accept this kind of data
192         *pdwEffect = DROPEFFECT_NONE;
193 
194         return S_OK;
195     }
196 
197     // get hold of the data object
198     m_pIDataObject = pIDataSource;
199     m_pIDataObject->AddRef();
200 
201     // we need client coordinates to pass to wxWin functions
202     if ( !ScreenToClient(m_hwnd, (POINT *)&pt) )
203     {
204         wxLogLastError(wxT("ScreenToClient"));
205     }
206 
207     // give some visual feedback
208     *pdwEffect = ConvertDragResultToEffect(
209         m_pTarget->OnEnter(pt.x, pt.y, ConvertDragEffectToResult(
210             GetDropEffect(grfKeyState, m_pTarget->GetDefaultAction(), *pdwEffect))
211                     )
212                  );
213 
214     return S_OK;
215 }
216 
217 // Name    : wxIDropTarget::DragOver
218 // Purpose : Indicates that the mouse was moved inside the window represented
219 //           by this drop target.
220 // Returns : S_OK
221 // Params  : [in] DWORD   grfKeyState     kbd & mouse state
222 //           [in] POINTL  pt              mouse coordinates
223 //           [out]LPDWORD pdwEffect       effect flag
224 // Notes   : We're called on every WM_MOUSEMOVE, so this function should be
225 //           very efficient.
DragOver(DWORD grfKeyState,POINTL pt,LPDWORD pdwEffect)226 STDMETHODIMP wxIDropTarget::DragOver(DWORD   grfKeyState,
227                                      POINTL  pt,
228                                      LPDWORD pdwEffect)
229 {
230     // there are too many of them... wxLogDebug("IDropTarget::DragOver");
231 
232     wxDragResult result;
233     if ( m_pIDataObject ) {
234         result = ConvertDragEffectToResult(
235             GetDropEffect(grfKeyState, m_pTarget->GetDefaultAction(), *pdwEffect));
236     }
237     else {
238         // can't accept data anyhow normally
239         result = wxDragNone;
240     }
241 
242     if ( result != wxDragNone ) {
243         // we need client coordinates to pass to wxWin functions
244         if ( !ScreenToClient(m_hwnd, (POINT *)&pt) )
245         {
246             wxLogLastError(wxT("ScreenToClient"));
247         }
248 
249         *pdwEffect = ConvertDragResultToEffect(
250                         m_pTarget->OnDragOver(pt.x, pt.y, result)
251                      );
252     }
253     else {
254         *pdwEffect = DROPEFFECT_NONE;
255     }
256 
257     return S_OK;
258 }
259 
260 // Name    : wxIDropTarget::DragLeave
261 // Purpose : Informs the drop target that the operation has left its window.
262 // Returns : S_OK
263 // Notes   : good place to do any clean-up
DragLeave()264 STDMETHODIMP wxIDropTarget::DragLeave()
265 {
266   wxLogTrace(wxTRACE_OleCalls, wxT("IDropTarget::DragLeave"));
267 
268   // remove the UI feedback
269   m_pTarget->OnLeave();
270 
271   // release the held object
272   RELEASE_AND_NULL(m_pIDataObject);
273 
274   return S_OK;
275 }
276 
277 // Name    : wxIDropTarget::Drop
278 // Purpose : Instructs the drop target to paste data that was just now
279 //           dropped on it.
280 // Returns : S_OK
281 // Params  : [in] IDataObject *pIDataSource     the data to paste
282 //           [in] DWORD        grfKeyState      kbd & mouse state
283 //           [in] POINTL       pt               where the drop occurred?
284 //           [ouy]DWORD       *pdwEffect        operation effect
285 // Notes   :
Drop(IDataObject * pIDataSource,DWORD grfKeyState,POINTL pt,DWORD * pdwEffect)286 STDMETHODIMP wxIDropTarget::Drop(IDataObject *pIDataSource,
287                                  DWORD        grfKeyState,
288                                  POINTL       pt,
289                                  DWORD       *pdwEffect)
290 {
291     wxLogTrace(wxTRACE_OleCalls, wxT("IDropTarget::Drop"));
292 
293     // TODO I don't know why there is this parameter, but so far I assume
294     //      that it's the same we've already got in DragEnter
295     wxASSERT( m_pIDataObject == pIDataSource );
296 
297     // we need client coordinates to pass to wxWin functions
298     if ( !ScreenToClient(m_hwnd, (POINT *)&pt) )
299     {
300         wxLogLastError(wxT("ScreenToClient"));
301     }
302 
303     // first ask the drop target if it wants data
304     if ( m_pTarget->OnDrop(pt.x, pt.y) ) {
305         // it does, so give it the data source
306         m_pTarget->SetDataSource(pIDataSource);
307 
308         // and now it has the data
309         wxDragResult rc = ConvertDragEffectToResult(
310             GetDropEffect(grfKeyState, m_pTarget->GetDefaultAction(), *pdwEffect));
311         rc = m_pTarget->OnData(pt.x, pt.y, rc);
312         if ( wxIsDragResultOk(rc) ) {
313             // operation succeeded
314             *pdwEffect = ConvertDragResultToEffect(rc);
315         }
316         else {
317             *pdwEffect = DROPEFFECT_NONE;
318         }
319     }
320     else {
321         // OnDrop() returned false, no need to copy data
322         *pdwEffect = DROPEFFECT_NONE;
323     }
324 
325     // release the held object
326     RELEASE_AND_NULL(m_pIDataObject);
327 
328     return S_OK;
329 }
330 
331 // ============================================================================
332 // wxDropTarget implementation
333 // ============================================================================
334 
335 // ----------------------------------------------------------------------------
336 // ctor/dtor
337 // ----------------------------------------------------------------------------
338 
wxDropTarget(wxDataObject * dataObj)339 wxDropTarget::wxDropTarget(wxDataObject *dataObj)
340             : wxDropTargetBase(dataObj)
341 {
342     // create an IDropTarget implementation which will notify us about d&d
343     // operations.
344     m_pIDropTarget = new wxIDropTarget(this);
345     m_pIDropTarget->AddRef();
346 }
347 
~wxDropTarget()348 wxDropTarget::~wxDropTarget()
349 {
350     ReleaseInterface(m_pIDropTarget);
351 }
352 
353 // ----------------------------------------------------------------------------
354 // [un]register drop handler
355 // ----------------------------------------------------------------------------
356 
Register(WXHWND hwnd)357 bool wxDropTarget::Register(WXHWND hwnd)
358 {
359     // FIXME
360     // RegisterDragDrop not available on Windows CE >= 400?
361     // Or maybe we can dynamically load them from ceshell.dll
362     // or similar.
363 #if defined(__WXWINCE__) && _WIN32_WCE >= 400
364     wxUnusedVar(hwnd);
365     return false;
366 #else
367     HRESULT hr;
368 
369     // May exist in later WinCE versions
370 #ifndef __WXWINCE__
371     hr = ::CoLockObjectExternal(m_pIDropTarget, TRUE, FALSE);
372     if ( FAILED(hr) ) {
373         wxLogApiError(wxT("CoLockObjectExternal"), hr);
374         return false;
375     }
376 #endif
377 
378     hr = ::RegisterDragDrop((HWND) hwnd, m_pIDropTarget);
379     if ( FAILED(hr) ) {
380     // May exist in later WinCE versions
381 #ifndef __WXWINCE__
382         ::CoLockObjectExternal(m_pIDropTarget, FALSE, FALSE);
383 #endif
384         wxLogApiError(wxT("RegisterDragDrop"), hr);
385         return false;
386     }
387 
388     // we will need the window handle for coords transformation later
389     m_pIDropTarget->SetHwnd((HWND)hwnd);
390 
391     return true;
392 #endif
393 }
394 
Revoke(WXHWND hwnd)395 void wxDropTarget::Revoke(WXHWND hwnd)
396 {
397 #if defined(__WXWINCE__) && _WIN32_WCE >= 400
398     // Not available, see note above
399     wxUnusedVar(hwnd);
400 #else
401     HRESULT hr = ::RevokeDragDrop((HWND) hwnd);
402 
403     if ( FAILED(hr) ) {
404         wxLogApiError(wxT("RevokeDragDrop"), hr);
405     }
406 
407     // May exist in later WinCE versions
408 #ifndef __WXWINCE__
409     ::CoLockObjectExternal(m_pIDropTarget, FALSE, TRUE);
410 #endif
411 
412     m_pIDropTarget->SetHwnd(0);
413 #endif
414 }
415 
416 // ----------------------------------------------------------------------------
417 // base class pure virtuals
418 // ----------------------------------------------------------------------------
419 
420 // OnDrop() is called only if we previously returned true from
421 // IsAcceptedData(), so no need to check anything here
OnDrop(wxCoord WXUNUSED (x),wxCoord WXUNUSED (y))422 bool wxDropTarget::OnDrop(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y))
423 {
424     return true;
425 }
426 
427 // copy the data from the data source to the target data object
GetData()428 bool wxDropTarget::GetData()
429 {
430     wxDataFormat format = GetSupportedFormat(m_pIDataSource);
431     if ( format == wxDF_INVALID ) {
432         // this is strange because IsAcceptedData() succeeded previously!
433         wxFAIL_MSG(wxT("strange - did supported formats list change?"));
434 
435         return false;
436     }
437 
438     STGMEDIUM stm;
439     FORMATETC fmtMemory;
440     fmtMemory.cfFormat  = format;
441     fmtMemory.ptd       = NULL;
442     fmtMemory.dwAspect  = DVASPECT_CONTENT;
443     fmtMemory.lindex    = -1;
444     fmtMemory.tymed     = TYMED_HGLOBAL;  // TODO to add other media
445 
446     bool rc = false;
447 
448     HRESULT hr = m_pIDataSource->GetData(&fmtMemory, &stm);
449     if ( SUCCEEDED(hr) ) {
450         IDataObject *dataObject = m_dataObject->GetInterface();
451 
452         hr = dataObject->SetData(&fmtMemory, &stm, TRUE);
453         if ( SUCCEEDED(hr) ) {
454             rc = true;
455         }
456         else {
457             wxLogApiError(wxT("IDataObject::SetData()"), hr);
458         }
459     }
460     else {
461         wxLogApiError(wxT("IDataObject::GetData()"), hr);
462     }
463 
464     return rc;
465 }
466 
467 // ----------------------------------------------------------------------------
468 // callbacks used by wxIDropTarget
469 // ----------------------------------------------------------------------------
470 
471 // we need a data source, so wxIDropTarget gives it to us using this function
SetDataSource(IDataObject * pIDataSource)472 void wxDropTarget::SetDataSource(IDataObject *pIDataSource)
473 {
474     m_pIDataSource = pIDataSource;
475 }
476 
477 // determine if we accept data of this type
IsAcceptedData(IDataObject * pIDataSource) const478 bool wxDropTarget::IsAcceptedData(IDataObject *pIDataSource) const
479 {
480     return GetSupportedFormat(pIDataSource) != wxDF_INVALID;
481 }
482 
483 // ----------------------------------------------------------------------------
484 // helper functions
485 // ----------------------------------------------------------------------------
486 
GetSupportedFormat(IDataObject * pIDataSource) const487 wxDataFormat wxDropTarget::GetSupportedFormat(IDataObject *pIDataSource) const
488 {
489     // this strucutre describes a data of any type (first field will be
490     // changing) being passed through global memory block.
491     static FORMATETC s_fmtMemory = {
492         0,
493         NULL,
494         DVASPECT_CONTENT,
495         -1,
496         TYMED_HGLOBAL       // TODO is it worth supporting other tymeds here?
497     };
498 
499     // get the list of supported formats
500     size_t nFormats = m_dataObject->GetFormatCount(wxDataObject::Set);
501     wxDataFormat format;
502     wxDataFormat *formats;
503     formats = nFormats == 1 ? &format :  new wxDataFormat[nFormats];
504 
505     m_dataObject->GetAllFormats(formats, wxDataObject::Set);
506 
507     // cycle through all supported formats
508     size_t n;
509     for ( n = 0; n < nFormats; n++ ) {
510         s_fmtMemory.cfFormat = formats[n];
511 
512         // NB: don't use SUCCEEDED macro here: QueryGetData returns S_FALSE
513         //     for file drag and drop (format == CF_HDROP)
514         if ( pIDataSource->QueryGetData(&s_fmtMemory) == S_OK ) {
515             format = formats[n];
516 
517             break;
518         }
519     }
520 
521     if ( formats != &format ) {
522         // free memory if we allocated it
523         delete [] formats;
524     }
525 
526     return n < nFormats ? format : wxFormatInvalid;
527 }
528 
529 // ----------------------------------------------------------------------------
530 // private functions
531 // ----------------------------------------------------------------------------
532 
ConvertDragEffectToResult(DWORD dwEffect)533 static wxDragResult ConvertDragEffectToResult(DWORD dwEffect)
534 {
535     switch ( dwEffect ) {
536         case DROPEFFECT_COPY:
537             return wxDragCopy;
538 
539         case DROPEFFECT_LINK:
540             return wxDragLink;
541 
542         case DROPEFFECT_MOVE:
543             return wxDragMove;
544 
545         default:
546             wxFAIL_MSG(wxT("invalid value in ConvertDragEffectToResult"));
547             // fall through
548 
549         case DROPEFFECT_NONE:
550             return wxDragNone;
551     }
552 }
553 
ConvertDragResultToEffect(wxDragResult result)554 static DWORD ConvertDragResultToEffect(wxDragResult result)
555 {
556     switch ( result ) {
557         case wxDragCopy:
558             return DROPEFFECT_COPY;
559 
560         case wxDragLink:
561             return DROPEFFECT_LINK;
562 
563         case wxDragMove:
564             return DROPEFFECT_MOVE;
565 
566         default:
567             wxFAIL_MSG(wxT("invalid value in ConvertDragResultToEffect"));
568             // fall through
569 
570         case wxDragNone:
571             return DROPEFFECT_NONE;
572     }
573 }
574 
575 #endif // wxUSE_OLE && wxUSE_DRAG_AND_DROP
576