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