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>
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>
34 #include "../dtobj/DOTransferable.hxx"
38 using namespace com::sun::star::datatransfer
;
39 using namespace com::sun::star::datatransfer::dnd
;
40 using namespace com::sun::star::datatransfer::dnd::DNDConstants
;
42 #define WM_REGISTERDRAGDROP WM_USER + 1
43 #define WM_REVOKEDRAGDROP WM_USER + 2
45 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
);
47 DropTarget::DropTarget( const Reference
<XComponentContext
>& rxContext
):
48 WeakComponentImplHelper
<XInitialization
,XDropTarget
, XServiceInfo
>(m_mutex
),
52 m_hOleThread(nullptr),
54 m_pDropTarget( nullptr),
55 m_xContext( rxContext
),
57 m_nDefaultActions(ACTION_COPY
|ACTION_MOVE
|ACTION_LINK
|ACTION_DEFAULT
),
58 m_nCurrentDropAction( ACTION_NONE
),
60 m_bDropComplete(false)
64 DropTarget::~DropTarget()
67 // called from WeakComponentImplHelperX::dispose
68 // WeakComponentImplHelper calls disposing before it destroys
70 // NOTE: RevokeDragDrop decrements the ref count on the IDropTarget
71 // interface. (m_pDropTarget)
72 // If the HWND is invalid then it doesn't decrement and
73 // the IDropTarget object will live on. MEMORY LEAK
74 void SAL_CALL
DropTarget::disposing()
78 // Call RevokeDragDrop and wait for the OLE thread to die;
79 PostThreadMessageW( m_threadIdTarget
, WM_REVOKEDRAGDROP
, reinterpret_cast<WPARAM
>(this), 0);
80 WaitForSingleObject( m_hOleThread
, INFINITE
);
81 CloseHandle( m_hOleThread
);
82 //OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" );
86 RevokeDragDrop( m_hWnd
);
91 CoLockObjectExternal( m_pDropTarget
, FALSE
, TRUE
);
92 m_pDropTarget
->Release();
93 m_pDropTarget
= nullptr;
98 if( m_oleThreadId
== CoGetCurrentProcess() )
104 void SAL_CALL
DropTarget::initialize( const Sequence
< Any
>& aArguments
)
106 // The window must be registered for Dnd by RegisterDragDrop. We must ensure
107 // that RegisterDragDrop is called from an STA ( OleInitialize) thread.
108 // As long as the window is registered we need to receive OLE messages in
109 // an OLE thread. That is to say, if DropTarget::initialize was called from an
110 // MTA thread then we create an OLE thread in which the window is registered.
111 // The thread will stay alive until aver RevokeDragDrop has been called.
113 // Additionally even if RegisterDragDrop is called from an STA thread we have
114 // to ensure that it is called from the same thread that created the Window
115 // otherwise messages sent during DND won't reach the windows message queue.
116 // Calling AttachThreadInput first would resolve this problem but would block
117 // the message queue of the calling thread. So if the current thread
118 // (even if it's an STA thread) and the thread that created the window are not
119 // identical we need to create a new thread as we do when the calling thread is
122 if( aArguments
.getLength() > 0)
124 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
125 m_hWnd
= reinterpret_cast<HWND
>(static_cast<sal_uIntPtr
>(*o3tl::doAccess
<sal_uInt64
>(aArguments
[0])));
126 OSL_ASSERT( IsWindow( m_hWnd
) );
128 // Obtain the id of the thread that created the window
129 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, nullptr);
131 HRESULT hr
= OleInitialize( nullptr);
133 // Current thread is MTA or Current thread and Window thread are not identical
134 if( hr
== RPC_E_CHANGED_MODE
|| GetCurrentThreadId() != m_threadIdWindow
)
136 OSL_ENSURE( ! m_threadIdTarget
,"initialize was called twice");
137 // create the IDropTargetImplementation
138 m_pDropTarget
= new IDropTargetImpl( *this );
139 m_pDropTarget
->AddRef();
141 // Obtain the id of the thread that created the window
142 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, nullptr);
143 // The event is set by the thread that we will create momentarily.
144 // It indicates that the thread is ready to receive messages.
145 HANDLE m_evtThreadReady
= CreateEventW( nullptr, FALSE
, FALSE
, nullptr);
147 m_hOleThread
= CreateThread( nullptr, 0, DndTargetOleSTAFunc
,
148 &m_evtThreadReady
, 0, &m_threadIdTarget
);
149 WaitForSingleObject( m_evtThreadReady
, INFINITE
);
150 CloseHandle( m_evtThreadReady
);
151 PostThreadMessageW( m_threadIdTarget
, WM_REGISTERDRAGDROP
, reinterpret_cast<WPARAM
>(this), 0);
153 else if( hr
== S_OK
|| hr
== S_FALSE
)
155 // current thread is STA
156 // If OleInitialize has been called by the caller then we must not call
160 // caller did not call OleInitialize, so we call OleUninitialize
161 // remember the thread that will call OleUninitialize
162 m_oleThreadId
= CoGetCurrentProcess(); // get a unique thread id
165 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
166 // create the IDropTargetImplementation
167 m_pDropTarget
= new IDropTargetImpl( *this );
168 m_pDropTarget
->AddRef();
169 // CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count
170 if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget
, TRUE
, FALSE
)))
172 if( FAILED( RegisterDragDrop( m_hWnd
, m_pDropTarget
) ) )
174 // do clean up if drag and drop is not possible
175 CoLockObjectExternal( m_pDropTarget
, FALSE
, FALSE
);
176 m_pDropTarget
->Release();
177 m_pDropTarget
= nullptr;
183 throw Exception("OleInitialize failed with " + OUString::number(hr
), nullptr);
188 // This function is called as extra thread from DragSource::startDrag.
189 // The function carries out a drag and drop operation by calling
190 // DoDragDrop. The thread also notifies all XSourceListener.
191 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
)
193 osl_setThreadName("DropTarget DndTargetOleSTAFunc");
195 HRESULT hr
= OleInitialize( nullptr);
199 // force the creation of a message queue
200 PeekMessageW( &msg
, nullptr, 0, 0, PM_NOREMOVE
);
201 // Signal the creator ( DropTarget::initialize) that the thread is
202 // ready to receive messages.
203 SetEvent( *static_cast<HANDLE
*>(pParams
));
204 // Thread id is needed for attaching this message queue to the one of the
205 // thread where the window was created.
206 DWORD threadId
= GetCurrentThreadId();
207 // We force the creation of a thread message queue. This is necessary
208 // for a later call to AttachThreadInput
209 while( GetMessageW(&msg
, nullptr, 0, 0) )
211 if( msg
.message
== WM_REGISTERDRAGDROP
)
213 DropTarget
*pTarget
= reinterpret_cast<DropTarget
*>(msg
.wParam
);
214 // This thread is attached to the thread that created the window. Hence
215 // this thread also receives all mouse and keyboard messages which are
217 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, TRUE
);
219 if( SUCCEEDED( CoLockObjectExternal(pTarget
-> m_pDropTarget
, TRUE
, FALSE
)))
221 if( FAILED( RegisterDragDrop( pTarget
-> m_hWnd
, pTarget
-> m_pDropTarget
) ) )
223 // do clean up if drag and drop is not possible
224 CoLockObjectExternal( pTarget
->m_pDropTarget
, FALSE
, FALSE
);
225 pTarget
->m_pDropTarget
->Release();
226 pTarget
->m_pDropTarget
= nullptr;
227 pTarget
->m_hWnd
= nullptr;
231 else if( msg
.message
== WM_REVOKEDRAGDROP
)
233 DropTarget
*pTarget
= reinterpret_cast<DropTarget
*>(msg
.wParam
);
234 RevokeDragDrop( pTarget
-> m_hWnd
);
235 // Detach this thread from the window thread
236 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, FALSE
);
237 pTarget
->m_hWnd
= nullptr;
240 TranslateMessage( &msg
);
241 DispatchMessageW( &msg
);
249 OUString SAL_CALL
DropTarget::getImplementationName( )
251 return DNDTARGET_IMPL_NAME
;
254 sal_Bool SAL_CALL
DropTarget::supportsService( const OUString
& ServiceName
)
256 return cppu::supportsService(this, ServiceName
);
259 Sequence
< OUString
> SAL_CALL
DropTarget::getSupportedServiceNames( )
261 return { DNDTARGET_SERVICE_NAME
};
265 void SAL_CALL
DropTarget::addDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
267 rBHelper
.addListener( cppu::UnoType
<decltype(dtl
)>::get(), dtl
);
270 void SAL_CALL
DropTarget::removeDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
272 rBHelper
.removeListener( cppu::UnoType
<decltype(dtl
)>::get(), dtl
);
275 sal_Bool SAL_CALL
DropTarget::isActive( )
277 return m_bActive
; //m_bDropTargetRegistered;
280 void SAL_CALL
DropTarget::setActive( sal_Bool _b
)
282 MutexGuard
g(m_mutex
);
286 sal_Int8 SAL_CALL
DropTarget::getDefaultActions( )
288 return m_nDefaultActions
;
291 void SAL_CALL
DropTarget::setDefaultActions( sal_Int8 actions
)
293 OSL_ENSURE( actions
< 8, "No valid default actions");
294 m_nDefaultActions
= actions
;
297 HRESULT
DropTarget::DragEnter( IDataObject
*pDataObj
,
302 #if defined DBG_CONSOLE_OUT
303 printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState
, *pdwEffect
);
307 // Intersection of pdwEffect and the allowed actions ( setDefaultActions)
308 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
309 // m_nLastDropAction has to be set by a listener. If no listener calls
310 //XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect
311 // will be DROPEFFECT_NONE throughout
312 m_nLastDropAction
= ACTION_DEFAULT
| ACTION_MOVE
;
314 m_currentDragContext
= new TargetDragContext(this);
319 if ( g_XTransferable
.is( ) )
320 m_currentData
= g_XTransferable
;
323 // Convert the IDataObject to a XTransferable
324 m_currentData
= CDOTransferable::create(
325 m_xContext
, IDataObjectPtr(pDataObj
));
330 if( m_nCurrentDropAction
!= ACTION_NONE
)
332 DropTargetDragEnterEvent e
;
333 e
.SupportedDataFlavors
= m_currentData
->getTransferDataFlavors();
334 e
.DropAction
= m_nCurrentDropAction
;
335 e
.Source
.set( static_cast<XDropTarget
*>(this),UNO_QUERY
);
336 e
.Context
= m_currentDragContext
;
337 POINT point
={ pt
.x
, pt
.y
};
338 ScreenToClient( m_hWnd
, &point
);
339 e
.LocationX
= point
.x
;
340 e
.LocationY
= point
.y
;
341 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
344 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
345 // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set
346 // in pdwEffect. The listener notification is asynchron, that is we cannot expect that the listener
347 // has already reacted to the notification.
348 // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
349 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
350 // On drop the target should present the user a dialog from which the user may change the action.
351 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
352 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
356 *pdwEffect
= DROPEFFECT_NONE
;
362 HRESULT
DropTarget::DragOver( DWORD grfKeyState
,
368 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
370 if( m_nCurrentDropAction
)
372 DropTargetDragEvent e
;
373 e
.DropAction
= m_nCurrentDropAction
;
374 e
.Source
.set(static_cast<XDropTarget
*>(this),UNO_QUERY
);
375 e
.Context
= m_currentDragContext
;
376 POINT point
={ pt
.x
, pt
.y
};
377 ScreenToClient( m_hWnd
, &point
);
378 e
.LocationX
= point
.x
;
379 e
.LocationY
= point
.y
;
380 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
382 // if grfKeyState has changed since the last DragOver then fire events.
383 // A listener might change m_nCurrentDropAction by calling the
384 // XDropTargetDragContext::acceptDrag function. But this is not important
385 // because in the afterwards fired dragOver event the action reflects
386 // grgKeyState again.
387 if( m_nLastDropAction
!= m_nCurrentDropAction
)
388 fire_dropActionChanged( e
);
390 // The Event contains a XDropTargetDragContext implementation.
392 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
393 // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set
394 // in pdwEffect. The listener notification is asynchron, that is we cannot expect that the listener
395 // has already reacted to the notification.
396 // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
397 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
398 // On drop the target should present the user a dialog from which the user may change the action.
399 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
400 // set the last action to the current if listener has not changed the value yet
401 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
405 *pdwEffect
= DROPEFFECT_NONE
;
408 #if defined DBG_CONSOLE_OUT
409 printf("\nDropTarget::DragOver %d", *pdwEffect
);
414 HRESULT
DropTarget::DragLeave()
416 #if defined DBG_CONSOLE_OUT
417 printf("\nDropTarget::DragLeave");
422 m_currentData
=nullptr;
423 m_currentDragContext
= nullptr;
424 m_currentDropContext
= nullptr;
425 m_nLastDropAction
= 0;
427 if( m_nDefaultActions
!= ACTION_NONE
)
430 e
.Source
= static_cast<XDropTarget
*>(this);
438 HRESULT
DropTarget::Drop( IDataObject
* /*pDataObj*/,
443 #if defined DBG_CONSOLE_OUT
444 printf("\nDropTarget::Drop");
449 m_bDropComplete
= false;
451 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
452 m_currentDropContext
= new TargetDropContext(this);
453 if( m_nCurrentDropAction
)
455 DropTargetDropEvent e
;
456 e
.DropAction
= m_nCurrentDropAction
;
457 e
.Source
.set( static_cast<XDropTarget
*>(this), UNO_QUERY
);
458 e
.Context
= m_currentDropContext
;
459 POINT point
={ pt
.x
, pt
.y
};
460 ScreenToClient( m_hWnd
, &point
);
461 e
.LocationX
= point
.x
;
462 e
.LocationY
= point
.y
;
463 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
464 e
.Transferable
= m_currentData
;
467 //if fire_drop returns than a listener might have modified m_nCurrentDropAction
468 if( m_bDropComplete
)
470 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
471 *pdwEffect
= dndActionsToSingleDropEffect( m_nCurrentDropAction
& allowedActions
);
474 *pdwEffect
= DROPEFFECT_NONE
;
477 *pdwEffect
= DROPEFFECT_NONE
;
479 m_currentData
= nullptr;
480 m_currentDragContext
= nullptr;
481 m_currentDropContext
= nullptr;
482 m_nLastDropAction
= 0;
487 void DropTarget::fire_drop( const DropTargetDropEvent
& dte
)
489 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
492 OInterfaceIteratorHelper
iter( *pContainer
);
493 while( iter
.hasMoreElements())
495 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
496 listener
->drop( dte
);
501 void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent
& e
)
503 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
506 OInterfaceIteratorHelper
iter( *pContainer
);
507 while( iter
.hasMoreElements())
509 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
510 listener
->dragEnter( e
);
515 void DropTarget::fire_dragExit( const DropTargetEvent
& dte
)
517 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
521 OInterfaceIteratorHelper
iter( *pContainer
);
522 while( iter
.hasMoreElements())
524 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
525 listener
->dragExit( dte
);
530 void DropTarget::fire_dragOver( const DropTargetDragEvent
& dtde
)
532 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
535 OInterfaceIteratorHelper
iter( *pContainer
);
536 while( iter
.hasMoreElements())
538 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
539 listener
->dragOver( dtde
);
544 void DropTarget::fire_dropActionChanged( const DropTargetDragEvent
& dtde
)
546 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
549 OInterfaceIteratorHelper
iter( *pContainer
);
550 while( iter
.hasMoreElements())
552 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
553 listener
->dropActionChanged( dtde
);
558 // Non - interface functions
559 // DropTarget fires events to XDropTargetListeners. The event object contains an
560 // XDropTargetDropContext implementation. When the listener calls on that interface
561 // then the calls are delegated from DropContext (XDropTargetDropContext) to these
563 // Only one listener which visible area is affected is allowed to call on
564 // XDropTargetDropContext
565 // Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation
566 // to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed.
567 // return sal_False results in throwing an InvalidDNDOperationException in the caller.
569 void DropTarget::_acceptDrop(sal_Int8 dropOperation
, const Reference
<XDropTargetDropContext
>& context
)
571 if( context
== m_currentDropContext
)
573 m_nCurrentDropAction
= dropOperation
;
577 void DropTarget::_rejectDrop( const Reference
<XDropTargetDropContext
>& context
)
579 if( context
== m_currentDropContext
)
581 m_nCurrentDropAction
= ACTION_NONE
;
585 void DropTarget::_dropComplete(bool success
, const Reference
<XDropTargetDropContext
>& context
)
587 if(context
== m_currentDropContext
)
589 m_bDropComplete
= success
;
593 // DropTarget fires events to XDropTargetListeners. The event object can contains an
594 // XDropTargetDragContext implementation. When the listener calls on that interface
595 // then the calls are delegated from DragContext (XDropTargetDragContext) to these
597 // Only one listener which visible area is affected is allowed to call on
598 // XDropTargetDragContext
599 void DropTarget::_acceptDrag( sal_Int8 dragOperation
, const Reference
<XDropTargetDragContext
>& context
)
601 if( context
== m_currentDragContext
)
603 m_nLastDropAction
= dragOperation
;
607 void DropTarget::_rejectDrag( const Reference
<XDropTargetDragContext
>& context
)
609 if(context
== m_currentDragContext
)
611 m_nLastDropAction
= ACTION_NONE
;
615 // This function determines the action dependent on the pressed
616 // key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result
617 // is then checked against the allowed actions which can be set through
618 // XDropTarget::setDefaultActions. Only those values which are also
619 // default actions are returned. If setDefaultActions has not been called
620 // beforehand the default actions comprise all possible actions.
621 // params: grfKeyState - the modifier keys and mouse buttons currently pressed
622 inline sal_Int8
DropTarget::getFilteredActions( DWORD grfKeyState
, DWORD dwEffect
)
624 sal_Int8 actions
= dndOleKeysToAction( grfKeyState
, dndOleDropEffectsToActions( dwEffect
));
625 return actions
& m_nDefaultActions
;
628 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */