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
59class wxIDropTarget : public IDropTarget
60{
61public:
62    wxIDropTarget(wxDropTarget *p);
63    virtual ~wxIDropTarget();
64
65    // accessors for wxDropTarget
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
76protected:
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
92static wxDragResult ConvertDragEffectToResult(DWORD dwEffect);
93static 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)
107DWORD 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
134wxIDropTarget::wxIDropTarget(wxDropTarget *pTarget)
135{
136  m_pTarget      = pTarget;
137  m_pIDataObject = NULL;
138}
139
140wxIDropTarget::~wxIDropTarget()
141{
142}
143
144BEGIN_IID_TABLE(wxIDropTarget)
145  ADD_IID(Unknown)
146  ADD_IID(DropTarget)
147END_IID_TABLE;
148
149IMPLEMENT_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   :
159STDMETHODIMP 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.
226STDMETHODIMP 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
264STDMETHODIMP 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   :
286STDMETHODIMP 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
339wxDropTarget::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
348wxDropTarget::~wxDropTarget()
349{
350    ReleaseInterface(m_pIDropTarget);
351}
352
353// ----------------------------------------------------------------------------
354// [un]register drop handler
355// ----------------------------------------------------------------------------
356
357bool 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
395void 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
422bool 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
428bool 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
472void wxDropTarget::SetDataSource(IDataObject *pIDataSource)
473{
474    m_pIDataSource = pIDataSource;
475}
476
477// determine if we accept data of this type
478bool wxDropTarget::IsAcceptedData(IDataObject *pIDataSource) const
479{
480    return GetSupportedFormat(pIDataSource) != wxDF_INVALID;
481}
482
483// ----------------------------------------------------------------------------
484// helper functions
485// ----------------------------------------------------------------------------
486
487wxDataFormat 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
533static 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
554static 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