1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
21 #include <com/sun/star/datatransfer/XTransferable.hpp>
22 #include <cppuhelper/supportsservice.hxx>
23 #include <o3tl/any.hxx>
26 #include <win/dnd_target.hxx>
27 #include "idroptarget.hxx"
28 #include "globals.hxx"
29 #include "targetdropcontext.hxx"
30 #include "targetdragcontext.hxx"
31 #include <rtl/ustring.h>
32 #include <osl/thread.h>
33 #include <sal/log.hxx>
34 #include <comphelper/windowserrorstring.hxx>
36 #include "DOTransferable.hxx"
40 using namespace com::sun::star::datatransfer
;
41 using namespace com::sun::star::datatransfer::dnd
;
42 using namespace com::sun::star::datatransfer::dnd::DNDConstants
;
44 #define WM_REGISTERDRAGDROP WM_USER + 1
45 #define WM_REVOKEDRAGDROP WM_USER + 2
47 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
);
49 DropTarget::DropTarget( const Reference
<XComponentContext
>& rxContext
):
50 WeakComponentImplHelper
<XInitialization
,XDropTarget
, XServiceInfo
>(m_aMutex
),
54 m_hOleThread(nullptr),
56 m_pDropTarget( nullptr),
57 m_xContext( rxContext
),
59 m_nDefaultActions(ACTION_COPY
|ACTION_MOVE
|ACTION_LINK
|ACTION_DEFAULT
),
60 m_nCurrentDropAction( ACTION_NONE
),
62 m_bDropComplete(false)
66 DropTarget::~DropTarget()
69 // called from WeakComponentImplHelperX::dispose
70 // WeakComponentImplHelper calls disposing before it destroys
72 // NOTE: RevokeDragDrop decrements the ref count on the IDropTarget
73 // interface. (m_pDropTarget)
74 // If the HWND is invalid then it doesn't decrement and
75 // the IDropTarget object will live on. MEMORY LEAK
76 void SAL_CALL
DropTarget::disposing()
80 // Call RevokeDragDrop and wait for the OLE thread to die;
81 PostThreadMessageW( m_threadIdTarget
, WM_REVOKEDRAGDROP
, reinterpret_cast<WPARAM
>(this), 0);
82 WaitForSingleObject( m_hOleThread
, INFINITE
);
83 CloseHandle( m_hOleThread
);
84 //OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" );
88 RevokeDragDrop( m_hWnd
);
93 CoLockObjectExternal( m_pDropTarget
, FALSE
, TRUE
);
94 m_pDropTarget
->Release();
95 m_pDropTarget
= nullptr;
100 if( m_oleThreadId
== CoGetCurrentProcess() )
106 void SAL_CALL
DropTarget::initialize( const Sequence
< Any
>& aArguments
)
108 // The window must be registered for Dnd by RegisterDragDrop. We must ensure
109 // that RegisterDragDrop is called from an STA ( OleInitialize) thread.
110 // As long as the window is registered we need to receive OLE messages in
111 // an OLE thread. That is to say, if DropTarget::initialize was called from an
112 // MTA thread then we create an OLE thread in which the window is registered.
113 // The thread will stay alive until aver RevokeDragDrop has been called.
115 // Additionally even if RegisterDragDrop is called from an STA thread we have
116 // to ensure that it is called from the same thread that created the Window
117 // otherwise messages sent during DND won't reach the windows message queue.
118 // Calling AttachThreadInput first would resolve this problem but would block
119 // the message queue of the calling thread. So if the current thread
120 // (even if it's an STA thread) and the thread that created the window are not
121 // identical we need to create a new thread as we do when the calling thread is
124 if( aArguments
.getLength() > 0)
126 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
127 m_hWnd
= reinterpret_cast<HWND
>(static_cast<sal_uIntPtr
>(*o3tl::doAccess
<sal_uInt64
>(aArguments
[0])));
128 OSL_ASSERT( IsWindow( m_hWnd
) );
130 // Obtain the id of the thread that created the window
131 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, nullptr);
133 HRESULT hr
= OleInitialize( nullptr);
135 // Current thread is MTA or Current thread and Window thread are not identical
136 if( hr
== RPC_E_CHANGED_MODE
|| GetCurrentThreadId() != m_threadIdWindow
)
138 OSL_ENSURE( ! m_threadIdTarget
,"initialize was called twice");
139 // create the IDropTargetImplementation
140 m_pDropTarget
= new IDropTargetImpl( *this );
141 m_pDropTarget
->AddRef();
143 // Obtain the id of the thread that created the window
144 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, nullptr);
145 // The event is set by the thread that we will create momentarily.
146 // It indicates that the thread is ready to receive messages.
147 HANDLE m_evtThreadReady
= CreateEventW( nullptr, FALSE
, FALSE
, nullptr);
149 m_hOleThread
= CreateThread( nullptr, 0, DndTargetOleSTAFunc
,
150 &m_evtThreadReady
, 0, &m_threadIdTarget
);
151 WaitForSingleObject( m_evtThreadReady
, INFINITE
);
152 CloseHandle( m_evtThreadReady
);
153 PostThreadMessageW( m_threadIdTarget
, WM_REGISTERDRAGDROP
, reinterpret_cast<WPARAM
>(this), 0);
155 else if( hr
== S_OK
|| hr
== S_FALSE
)
157 // current thread is STA
158 // If OleInitialize has been called by the caller then we must not call
162 // caller did not call OleInitialize, so we call OleUninitialize
163 // remember the thread that will call OleUninitialize
164 m_oleThreadId
= CoGetCurrentProcess(); // get a unique thread id
167 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
168 // create the IDropTargetImplementation
169 m_pDropTarget
= new IDropTargetImpl( *this );
170 m_pDropTarget
->AddRef();
171 // CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count
172 if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget
, TRUE
, FALSE
)))
174 if( FAILED( RegisterDragDrop( m_hWnd
, m_pDropTarget
) ) )
176 // do clean up if drag and drop is not possible
177 CoLockObjectExternal( m_pDropTarget
, FALSE
, FALSE
);
178 m_pDropTarget
->Release();
179 m_pDropTarget
= nullptr;
185 throw Exception("OleInitialize failed with " + OUString::number(hr
), nullptr);
190 // This function is called as extra thread from DragSource::startDrag.
191 // The function carries out a drag and drop operation by calling
192 // DoDragDrop. The thread also notifies all XSourceListener.
193 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
)
195 osl_setThreadName("DropTarget DndTargetOleSTAFunc");
197 HRESULT hr
= OleInitialize( nullptr);
201 // force the creation of a message queue
202 PeekMessageW( &msg
, nullptr, 0, 0, PM_NOREMOVE
);
203 // Signal the creator ( DropTarget::initialize) that the thread is
204 // ready to receive messages.
205 SetEvent( *static_cast<HANDLE
*>(pParams
));
206 // Thread id is needed for attaching this message queue to the one of the
207 // thread where the window was created.
208 DWORD threadId
= GetCurrentThreadId();
209 // We force the creation of a thread message queue. This is necessary
210 // for a later call to AttachThreadInput
213 int const bRet
= GetMessageW(&msg
, nullptr, 0, 0);
220 SAL_WARN("vcl.win.dtrans", "GetMessageW failed: " << WindowsErrorString(GetLastError()));
223 if( msg
.message
== WM_REGISTERDRAGDROP
)
225 DropTarget
*pTarget
= reinterpret_cast<DropTarget
*>(msg
.wParam
);
226 // This thread is attached to the thread that created the window. Hence
227 // this thread also receives all mouse and keyboard messages which are
229 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, TRUE
);
231 if( SUCCEEDED( CoLockObjectExternal(pTarget
-> m_pDropTarget
, TRUE
, FALSE
)))
233 if( FAILED( RegisterDragDrop( pTarget
-> m_hWnd
, pTarget
-> m_pDropTarget
) ) )
235 // do clean up if drag and drop is not possible
236 CoLockObjectExternal( pTarget
->m_pDropTarget
, FALSE
, FALSE
);
237 pTarget
->m_pDropTarget
->Release();
238 pTarget
->m_pDropTarget
= nullptr;
239 pTarget
->m_hWnd
= nullptr;
243 else if( msg
.message
== WM_REVOKEDRAGDROP
)
245 DropTarget
*pTarget
= reinterpret_cast<DropTarget
*>(msg
.wParam
);
246 RevokeDragDrop( pTarget
-> m_hWnd
);
247 // Detach this thread from the window thread
248 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, FALSE
);
249 pTarget
->m_hWnd
= nullptr;
252 TranslateMessage( &msg
);
253 DispatchMessageW( &msg
);
261 OUString SAL_CALL
DropTarget::getImplementationName( )
263 return "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1";
266 sal_Bool SAL_CALL
DropTarget::supportsService( const OUString
& ServiceName
)
268 return cppu::supportsService(this, ServiceName
);
271 Sequence
< OUString
> SAL_CALL
DropTarget::getSupportedServiceNames( )
273 return { "com.sun.star.datatransfer.dnd.OleDropTarget" };
277 void SAL_CALL
DropTarget::addDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
279 rBHelper
.addListener( cppu::UnoType
<decltype(dtl
)>::get(), dtl
);
282 void SAL_CALL
DropTarget::removeDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
284 rBHelper
.removeListener( cppu::UnoType
<decltype(dtl
)>::get(), dtl
);
287 sal_Bool SAL_CALL
DropTarget::isActive( )
289 return m_bActive
; //m_bDropTargetRegistered;
292 void SAL_CALL
DropTarget::setActive( sal_Bool _b
)
294 MutexGuard
g(m_aMutex
);
298 sal_Int8 SAL_CALL
DropTarget::getDefaultActions( )
300 return m_nDefaultActions
;
303 void SAL_CALL
DropTarget::setDefaultActions( sal_Int8 actions
)
305 OSL_ENSURE( actions
< 8, "No valid default actions");
306 m_nDefaultActions
= actions
;
309 HRESULT
DropTarget::DragEnter( IDataObject
*pDataObj
,
314 #if defined DBG_CONSOLE_OUT
315 printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState
, *pdwEffect
);
319 // Intersection of pdwEffect and the allowed actions ( setDefaultActions)
320 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
321 // m_nLastDropAction has to be set by a listener. If no listener calls
322 //XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect
323 // will be DROPEFFECT_NONE throughout
324 m_nLastDropAction
= ACTION_DEFAULT
| ACTION_MOVE
;
326 m_currentDragContext
= new TargetDragContext(this);
331 if ( g_XTransferable
.is( ) )
332 m_currentData
= g_XTransferable
;
335 // Convert the IDataObject to a XTransferable
336 m_currentData
= new CDOTransferable(m_xContext
, IDataObjectPtr(pDataObj
));
341 if( m_nCurrentDropAction
!= ACTION_NONE
)
343 DropTargetDragEnterEvent e
;
344 e
.SupportedDataFlavors
= m_currentData
->getTransferDataFlavors();
345 e
.DropAction
= m_nCurrentDropAction
;
346 e
.Source
.set( static_cast<XDropTarget
*>(this),UNO_QUERY
);
347 e
.Context
= m_currentDragContext
;
348 POINT point
={ pt
.x
, pt
.y
};
349 ScreenToClient( m_hWnd
, &point
);
350 e
.LocationX
= point
.x
;
351 e
.LocationY
= point
.y
;
352 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
355 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
356 // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set
357 // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener
358 // has already reacted to the notification.
359 // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
360 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
361 // On drop the target should present the user a dialog from which the user may change the action.
362 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
363 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
367 *pdwEffect
= DROPEFFECT_NONE
;
373 HRESULT
DropTarget::DragOver( DWORD grfKeyState
,
379 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
381 if( m_nCurrentDropAction
)
383 DropTargetDragEvent e
;
384 e
.DropAction
= m_nCurrentDropAction
;
385 e
.Source
.set(static_cast<XDropTarget
*>(this),UNO_QUERY
);
386 e
.Context
= m_currentDragContext
;
387 POINT point
={ pt
.x
, pt
.y
};
388 ScreenToClient( m_hWnd
, &point
);
389 e
.LocationX
= point
.x
;
390 e
.LocationY
= point
.y
;
391 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
393 // if grfKeyState has changed since the last DragOver then fire events.
394 // A listener might change m_nCurrentDropAction by calling the
395 // XDropTargetDragContext::acceptDrag function. But this is not important
396 // because in the afterwards fired dragOver event the action reflects
397 // grgKeyState again.
398 if( m_nLastDropAction
!= m_nCurrentDropAction
)
399 fire_dropActionChanged( e
);
401 // The Event contains a XDropTargetDragContext implementation.
403 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
404 // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set
405 // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener
406 // has already reacted to the notification.
407 // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
408 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
409 // On drop the target should present the user a dialog from which the user may change the action.
410 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
411 // set the last action to the current if listener has not changed the value yet
412 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
416 *pdwEffect
= DROPEFFECT_NONE
;
419 #if defined DBG_CONSOLE_OUT
420 printf("\nDropTarget::DragOver %d", *pdwEffect
);
425 HRESULT
DropTarget::DragLeave()
427 #if defined DBG_CONSOLE_OUT
428 printf("\nDropTarget::DragLeave");
433 m_currentData
=nullptr;
434 m_currentDragContext
= nullptr;
435 m_currentDropContext
= nullptr;
436 m_nLastDropAction
= 0;
438 if( m_nDefaultActions
!= ACTION_NONE
)
441 e
.Source
= static_cast<XDropTarget
*>(this);
449 HRESULT
DropTarget::Drop( IDataObject
* /*pDataObj*/,
454 #if defined DBG_CONSOLE_OUT
455 printf("\nDropTarget::Drop");
460 m_bDropComplete
= false;
462 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
463 m_currentDropContext
= new TargetDropContext(this);
464 if( m_nCurrentDropAction
)
466 DropTargetDropEvent e
;
467 e
.DropAction
= m_nCurrentDropAction
;
468 e
.Source
.set( static_cast<XDropTarget
*>(this), UNO_QUERY
);
469 e
.Context
= m_currentDropContext
;
470 POINT point
={ pt
.x
, pt
.y
};
471 ScreenToClient( m_hWnd
, &point
);
472 e
.LocationX
= point
.x
;
473 e
.LocationY
= point
.y
;
474 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
475 e
.Transferable
= m_currentData
;
478 //if fire_drop returns than a listener might have modified m_nCurrentDropAction
479 if( m_bDropComplete
)
481 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
482 *pdwEffect
= dndActionsToSingleDropEffect( m_nCurrentDropAction
& allowedActions
);
485 *pdwEffect
= DROPEFFECT_NONE
;
488 *pdwEffect
= DROPEFFECT_NONE
;
490 m_currentData
= nullptr;
491 m_currentDragContext
= nullptr;
492 m_currentDropContext
= nullptr;
493 m_nLastDropAction
= 0;
498 void DropTarget::fire_drop( const DropTargetDropEvent
& dte
)
500 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
503 OInterfaceIteratorHelper
iter( *pContainer
);
504 while( iter
.hasMoreElements())
506 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
507 listener
->drop( dte
);
512 void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent
& e
)
514 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
517 OInterfaceIteratorHelper
iter( *pContainer
);
518 while( iter
.hasMoreElements())
520 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
521 listener
->dragEnter( e
);
526 void DropTarget::fire_dragExit( const DropTargetEvent
& dte
)
528 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
532 OInterfaceIteratorHelper
iter( *pContainer
);
533 while( iter
.hasMoreElements())
535 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
536 listener
->dragExit( dte
);
541 void DropTarget::fire_dragOver( const DropTargetDragEvent
& dtde
)
543 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
546 OInterfaceIteratorHelper
iter( *pContainer
);
547 while( iter
.hasMoreElements())
549 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
550 listener
->dragOver( dtde
);
555 void DropTarget::fire_dropActionChanged( const DropTargetDragEvent
& dtde
)
557 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
560 OInterfaceIteratorHelper
iter( *pContainer
);
561 while( iter
.hasMoreElements())
563 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
564 listener
->dropActionChanged( dtde
);
569 // Non - interface functions
570 // DropTarget fires events to XDropTargetListeners. The event object contains an
571 // XDropTargetDropContext implementation. When the listener calls on that interface
572 // then the calls are delegated from DropContext (XDropTargetDropContext) to these
574 // Only one listener which visible area is affected is allowed to call on
575 // XDropTargetDropContext
576 // Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation
577 // to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed.
578 // return sal_False results in throwing an InvalidDNDOperationException in the caller.
580 void DropTarget::_acceptDrop(sal_Int8 dropOperation
, const Reference
<XDropTargetDropContext
>& context
)
582 if( context
== m_currentDropContext
)
584 m_nCurrentDropAction
= dropOperation
;
588 void DropTarget::_rejectDrop( const Reference
<XDropTargetDropContext
>& context
)
590 if( context
== m_currentDropContext
)
592 m_nCurrentDropAction
= ACTION_NONE
;
596 void DropTarget::_dropComplete(bool success
, const Reference
<XDropTargetDropContext
>& context
)
598 if(context
== m_currentDropContext
)
600 m_bDropComplete
= success
;
604 // DropTarget fires events to XDropTargetListeners. The event object can contains an
605 // XDropTargetDragContext implementation. When the listener calls on that interface
606 // then the calls are delegated from DragContext (XDropTargetDragContext) to these
608 // Only one listener which visible area is affected is allowed to call on
609 // XDropTargetDragContext
610 void DropTarget::_acceptDrag( sal_Int8 dragOperation
, const Reference
<XDropTargetDragContext
>& context
)
612 if( context
== m_currentDragContext
)
614 m_nLastDropAction
= dragOperation
;
618 void DropTarget::_rejectDrag( const Reference
<XDropTargetDragContext
>& context
)
620 if(context
== m_currentDragContext
)
622 m_nLastDropAction
= ACTION_NONE
;
626 // This function determines the action dependent on the pressed
627 // key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result
628 // is then checked against the allowed actions which can be set through
629 // XDropTarget::setDefaultActions. Only those values which are also
630 // default actions are returned. If setDefaultActions has not been called
631 // beforehand the default actions comprise all possible actions.
632 // params: grfKeyState - the modifier keys and mouse buttons currently pressed
633 inline sal_Int8
DropTarget::getFilteredActions( DWORD grfKeyState
, DWORD dwEffect
)
635 sal_Int8 actions
= dndOleKeysToAction( grfKeyState
, dndOleDropEffectsToActions( dwEffect
));
636 return actions
& m_nDefaultActions
;
639 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
640 dtrans_DropTarget_get_implementation(
641 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const&)
643 return cppu::acquire(new DropTarget(context
));
646 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */