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>
25 #include "idroptarget.hxx"
26 #include "globals.hxx"
27 #include "targetdropcontext.hxx"
28 #include "targetdragcontext.hxx"
29 #include <rtl/ustring.h>
30 #include <osl/thread.h>
34 using namespace com::sun::star::datatransfer
;
35 using namespace com::sun::star::datatransfer::dnd
;
36 using namespace com::sun::star::datatransfer::dnd::DNDConstants
;
38 #define WM_REGISTERDRAGDROP WM_USER + 1
39 #define WM_REVOKEDRAGDROP WM_USER + 2
40 extern Reference
< XTransferable
> g_XTransferable
;
42 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
);
44 DropTarget::DropTarget( const Reference
<XComponentContext
>& rxContext
):
45 WeakComponentImplHelper3
<XInitialization
,XDropTarget
, XServiceInfo
>(m_mutex
),
52 m_xContext( rxContext
),
54 m_nDefaultActions(ACTION_COPY
|ACTION_MOVE
|ACTION_LINK
|ACTION_DEFAULT
),
55 m_nCurrentDropAction( ACTION_NONE
),
57 m_bDropComplete(false)
61 DropTarget::~DropTarget()
64 // called from WeakComponentImplHelperX::dispose
65 // WeakComponentImplHelper calls disposing before it destroys
67 // NOTE: RevokeDragDrop decrements the ref count on the IDropTarget
68 // interface. (m_pDropTarget)
69 // If the HWND is invalid then it doesn't decrement and
70 // the IDropTarget object will live on. MEMORY LEAK
71 void SAL_CALL
DropTarget::disposing()
75 // Call RevokeDragDrop and wait for the OLE thread to die;
76 PostThreadMessage( m_threadIdTarget
, WM_REVOKEDRAGDROP
, (WPARAM
)this, 0);
77 WaitForSingleObject( m_hOleThread
, INFINITE
);
78 CloseHandle( m_hOleThread
);
79 //OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" );
83 RevokeDragDrop( m_hWnd
);
88 CoLockObjectExternal( m_pDropTarget
, FALSE
, TRUE
);
89 m_pDropTarget
->Release();
90 m_pDropTarget
= nullptr;
95 if( m_oleThreadId
== CoGetCurrentProcess() )
101 void SAL_CALL
DropTarget::initialize( const Sequence
< Any
>& aArguments
)
102 throw(Exception
, RuntimeException
)
104 // The window must be registered for Dnd by RegisterDragDrop. We must ensure
105 // that RegisterDragDrop is called from an STA ( OleInitialize) thread.
106 // As long as the window is registered we need to receive OLE messages in
107 // an OLE thread. That is to say, if DropTarget::initialize was called from an
108 // MTA thread then we create an OLE thread in which the window is registered.
109 // The thread will stay alive until aver RevokeDragDrop has been called.
111 // Additionally even if RegisterDragDrop is called from an STA thread we have
112 // to ensure that it is called from the same thread that created the Window
113 // otherwise meesages sent during DND won't reach the windows message queue.
114 // Calling AttachThreadInput first would resolve this problem but would block
115 // the message queue of the calling thread. So if the current thread
116 // (even if it's an STA thread) and the thread that created the window are not
117 // identical we need to create a new thread as we do when the calling thread is
120 if( aArguments
.getLength() > 0)
122 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
123 m_hWnd
= *(HWND
*)aArguments
[0].getValue();
124 OSL_ASSERT( IsWindow( m_hWnd
) );
126 // Obtain the id of the thread that created the window
127 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, NULL
);
129 HRESULT hr
= OleInitialize( NULL
);
131 // Current thread is MTA or Current thread and Window thread are not identical
132 if( hr
== RPC_E_CHANGED_MODE
|| GetCurrentThreadId() != m_threadIdWindow
)
134 OSL_ENSURE( ! m_threadIdTarget
,"initialize was called twice");
135 // create the IDropTargetImplementation
136 m_pDropTarget
= new IDropTargetImpl( *static_cast<DropTarget
*>( this) );
137 m_pDropTarget
->AddRef();
139 // Obtain the id of the thread that created the window
140 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, NULL
);
141 // The event is set by the thread that we will create momentarily.
142 // It indicates that the thread is ready to receive messages.
143 HANDLE m_evtThreadReady
= CreateEvent( NULL
, FALSE
, FALSE
, NULL
);
145 m_hOleThread
= CreateThread( NULL
, 0, (LPTHREAD_START_ROUTINE
)DndTargetOleSTAFunc
,
146 &m_evtThreadReady
, 0, &m_threadIdTarget
);
147 WaitForSingleObject( m_evtThreadReady
, INFINITE
);
148 CloseHandle( m_evtThreadReady
);
149 PostThreadMessage( m_threadIdTarget
, WM_REGISTERDRAGDROP
, (WPARAM
)static_cast<DropTarget
*>(this), 0);
151 else if( hr
== S_OK
|| hr
== S_FALSE
)
153 // current thread is STA
154 // If OleInitialize has been called by the caller then we must not call
158 // caller did not call OleInitialize, so we call OleUninitialize
159 // remember the thread that will call OleUninitialize
160 m_oleThreadId
= CoGetCurrentProcess(); // get a unique thread id
163 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
164 // create the IDropTargetImplementation
165 m_pDropTarget
= new IDropTargetImpl( *static_cast<DropTarget
*>( this) );
166 m_pDropTarget
->AddRef();
167 // CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count
168 if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget
, TRUE
, FALSE
)))
170 if( FAILED( RegisterDragDrop( m_hWnd
, m_pDropTarget
) ) )
172 // do clean up if drag and drop is not possible
173 CoLockObjectExternal( m_pDropTarget
, FALSE
, FALSE
);
174 m_pDropTarget
->Release();
175 m_pDropTarget
= nullptr;
186 // This function is called as extra thread from DragSource::startDrag.
187 // The function carries out a drag and drop operation by calling
188 // DoDragDrop. The thread also notifies all XSourceListener.
189 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
)
191 osl_setThreadName("DropTarget DndTargetOleSTAFunc");
193 HRESULT hr
= OleInitialize( NULL
);
197 // force the creation of a message queue
198 PeekMessage( &msg
, (HWND
)NULL
, 0, 0, PM_NOREMOVE
);
199 // Signal the creator ( DropTarget::initialize) that the thread is
200 // ready to receive messages.
201 SetEvent( *(HANDLE
*) pParams
);
202 // Thread id is needed for attaching this message queue to the one of the
203 // thread where the window was created.
204 DWORD threadId
= GetCurrentThreadId();
205 // We force the creation of a thread message queue. This is necessary
206 // for a later call to AttachThreadInput
207 while( GetMessage(&msg
, (HWND
)NULL
, 0, 0) )
209 if( msg
.message
== WM_REGISTERDRAGDROP
)
211 DropTarget
*pTarget
= (DropTarget
*)msg
.wParam
;
212 // This thread is attached to the thread that created the window. Hence
213 // this thread also receives all mouse and keyboard messages which are
215 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, TRUE
);
217 if( SUCCEEDED( CoLockObjectExternal(pTarget
-> m_pDropTarget
, TRUE
, FALSE
)))
219 if( FAILED( RegisterDragDrop( pTarget
-> m_hWnd
, pTarget
-> m_pDropTarget
) ) )
221 // do clean up if drag and drop is not possible
222 CoLockObjectExternal( pTarget
->m_pDropTarget
, FALSE
, FALSE
);
223 pTarget
->m_pDropTarget
->Release();
224 pTarget
->m_pDropTarget
= nullptr;
225 pTarget
->m_hWnd
= NULL
;
229 else if( msg
.message
== WM_REVOKEDRAGDROP
)
231 DropTarget
*pTarget
= (DropTarget
*)msg
.wParam
;
232 RevokeDragDrop( pTarget
-> m_hWnd
);
233 // Detach this thread from the window thread
234 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, FALSE
);
238 TranslateMessage( &msg
);
239 DispatchMessage( &msg
);
247 OUString SAL_CALL
DropTarget::getImplementationName( ) throw (RuntimeException
)
249 return OUString(DNDTARGET_IMPL_NAME
);
252 sal_Bool SAL_CALL
DropTarget::supportsService( const OUString
& ServiceName
) throw (RuntimeException
)
254 return cppu::supportsService(this, ServiceName
);
257 Sequence
< OUString
> SAL_CALL
DropTarget::getSupportedServiceNames( ) throw (RuntimeException
)
259 OUString names
[1]= {OUString(DNDTARGET_SERVICE_NAME
)};
260 return Sequence
<OUString
>(names
, 1);
264 void SAL_CALL
DropTarget::addDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
265 throw(RuntimeException
)
267 rBHelper
.addListener( cppu::UnoType
<decltype(dtl
)>::get(), dtl
);
270 void SAL_CALL
DropTarget::removeDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
271 throw(RuntimeException
)
273 rBHelper
.removeListener( cppu::UnoType
<decltype(dtl
)>::get(), dtl
);
276 sal_Bool SAL_CALL
DropTarget::isActive( ) throw(RuntimeException
)
278 return m_bActive
; //m_bDropTargetRegistered;
281 void SAL_CALL
DropTarget::setActive( sal_Bool _b
) throw(RuntimeException
)
283 MutexGuard
g(m_mutex
);
287 sal_Int8 SAL_CALL
DropTarget::getDefaultActions( ) throw(RuntimeException
)
289 return m_nDefaultActions
;
292 void SAL_CALL
DropTarget::setDefaultActions( sal_Int8 actions
) throw(RuntimeException
)
294 OSL_ENSURE( actions
< 8, "No valid default actions");
295 m_nDefaultActions
= actions
;
298 HRESULT
DropTarget::DragEnter( IDataObject
*pDataObj
,
303 #if defined DBG_CONSOLE_OUT
304 printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState
, *pdwEffect
);
308 // Intersection of pdwEffect and the allowed actions ( setDefaultActions)
309 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
310 // m_nLastDropAction has to be set by a listener. If no listener calls
311 //XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect
312 // will be DROPEFFECT_NONE throughout
313 m_nLastDropAction
= ACTION_DEFAULT
| ACTION_MOVE
;
315 m_currentDragContext
= static_cast<XDropTargetDragContext
*>( new TargetDragContext(
316 static_cast<DropTarget
*>(this) ) );
321 if ( g_XTransferable
.is( ) )
322 m_currentData
= g_XTransferable
;
325 // Convert the IDataObject to a XTransferable
326 m_currentData
= m_aDataConverter
.createTransferableFromDataObj(
327 m_xContext
, IDataObjectPtr(pDataObj
));
332 if( m_nCurrentDropAction
!= ACTION_NONE
)
334 DropTargetDragEnterEvent e
;
335 e
.SupportedDataFlavors
= m_currentData
->getTransferDataFlavors();
336 e
.DropAction
= m_nCurrentDropAction
;
337 e
.Source
= Reference
<XInterface
>( static_cast<XDropTarget
*>(this),UNO_QUERY
);
338 e
.Context
= m_currentDragContext
;
339 POINT point
={ pt
.x
, pt
.y
};
340 ScreenToClient( m_hWnd
, &point
);
341 e
.LocationX
= point
.x
;
342 e
.LocationY
= point
.y
;
343 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
346 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
347 // by the listener (m_nCurrentDropAction) is allowed by the source. Only a allowed action is set
348 // in pdwEffect. The listener notification is asynchron, that is we cannot expext that the listener
349 // has already reacted to the notification.
350 // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
351 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
352 // On drop the target should present the user a dialog from which the user may change the action.
353 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
354 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
358 *pdwEffect
= DROPEFFECT_NONE
;
364 HRESULT
DropTarget::DragOver( DWORD grfKeyState
,
370 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
372 if( m_nCurrentDropAction
)
374 DropTargetDragEvent e
;
375 e
.DropAction
= m_nCurrentDropAction
;
376 e
.Source
= Reference
<XInterface
>(static_cast<XDropTarget
*>(this),UNO_QUERY
);
377 e
.Context
= m_currentDragContext
;
378 POINT point
={ pt
.x
, pt
.y
};
379 ScreenToClient( m_hWnd
, &point
);
380 e
.LocationX
= point
.x
;
381 e
.LocationY
= point
.y
;
382 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
384 // if grfKeyState has changed since the last DragOver then fire events.
385 // A listener might change m_nCurrentDropAction by calling the
386 // XDropTargetDragContext::acceptDrag function. But this is not important
387 // because in the afterwards fired dragOver event the action reflects
388 // grgKeyState again.
389 if( m_nLastDropAction
!= m_nCurrentDropAction
)
390 fire_dropActionChanged( e
);
392 // The Event contains a XDropTargetDragContext implementation.
394 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
395 // by the listener (m_nCurrentDropAction) is allowed by the source. Only a allowed action is set
396 // in pdwEffect. The listener notification is asynchron, that is we cannot expext that the listener
397 // has already reacted to the notification.
398 // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
399 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
400 // On drop the target should present the user a dialog from which the user may change the action.
401 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
402 // set the last action to the current if listener has not changed the value yet
403 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
407 *pdwEffect
= DROPEFFECT_NONE
;
410 #if defined DBG_CONSOLE_OUT
411 printf("\nDropTarget::DragOver %d", *pdwEffect
);
416 HRESULT
DropTarget::DragLeave()
418 #if defined DBG_CONSOLE_OUT
419 printf("\nDropTarget::DragLeave");
425 m_currentDragContext
= 0;
426 m_currentDropContext
= 0;
427 m_nLastDropAction
= 0;
429 if( m_nDefaultActions
!= ACTION_NONE
)
432 e
.Source
= static_cast<XDropTarget
*>(this);
440 HRESULT
DropTarget::Drop( IDataObject
* /*pDataObj*/,
445 #if defined DBG_CONSOLE_OUT
446 printf("\nDropTarget::Drop");
451 m_bDropComplete
= sal_False
;
453 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
454 m_currentDropContext
= static_cast<XDropTargetDropContext
*>( new TargetDropContext( static_cast<DropTarget
*>(this )) );
455 if( m_nCurrentDropAction
)
457 DropTargetDropEvent e
;
458 e
.DropAction
= m_nCurrentDropAction
;
459 e
.Source
= Reference
<XInterface
>( static_cast<XDropTarget
*>(this), UNO_QUERY
);
460 e
.Context
= m_currentDropContext
;
461 POINT point
={ pt
.x
, pt
.y
};
462 ScreenToClient( m_hWnd
, &point
);
463 e
.LocationX
= point
.x
;
464 e
.LocationY
= point
.y
;
465 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
466 e
.Transferable
= m_currentData
;
469 //if fire_drop returns than a listener might have modified m_nCurrentDropAction
470 if( m_bDropComplete
== sal_True
)
472 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
473 *pdwEffect
= dndActionsToSingleDropEffect( m_nCurrentDropAction
& allowedActions
);
476 *pdwEffect
= DROPEFFECT_NONE
;
479 *pdwEffect
= DROPEFFECT_NONE
;
482 m_currentDragContext
= 0;
483 m_currentDropContext
= 0;
484 m_nLastDropAction
= 0;
489 void DropTarget::fire_drop( const DropTargetDropEvent
& dte
)
491 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
494 OInterfaceIteratorHelper
iter( *pContainer
);
495 while( iter
.hasMoreElements())
497 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
498 listener
->drop( dte
);
503 void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent
& e
)
505 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
508 OInterfaceIteratorHelper
iter( *pContainer
);
509 while( iter
.hasMoreElements())
511 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
512 listener
->dragEnter( e
);
517 void DropTarget::fire_dragExit( const DropTargetEvent
& dte
)
519 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
523 OInterfaceIteratorHelper
iter( *pContainer
);
524 while( iter
.hasMoreElements())
526 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
527 listener
->dragExit( dte
);
532 void DropTarget::fire_dragOver( const DropTargetDragEvent
& dtde
)
534 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
537 OInterfaceIteratorHelper
iter( *pContainer
);
538 while( iter
.hasMoreElements())
540 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
541 listener
->dragOver( dtde
);
546 void DropTarget::fire_dropActionChanged( const DropTargetDragEvent
& dtde
)
548 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( cppu::UnoType
<XDropTargetListener
>::get());
551 OInterfaceIteratorHelper
iter( *pContainer
);
552 while( iter
.hasMoreElements())
554 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
555 listener
->dropActionChanged( dtde
);
560 // Non - interface functions
561 // DropTarget fires events to XDropTargetListeners. The event object contains an
562 // XDropTargetDropContext implementaion. When the listener calls on that interface
563 // then the calls are delegated from DropContext (XDropTargetDropContext) to these
565 // Only one listener which visible area is affected is allowed to call on
566 // XDropTargetDropContext
567 // Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation
568 // to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed.
569 // return sal_False results in throwing a InvalidDNDOperationException in the caller.
571 void DropTarget::_acceptDrop(sal_Int8 dropOperation
, const Reference
<XDropTargetDropContext
>& context
)
573 if( context
== m_currentDropContext
)
575 m_nCurrentDropAction
= dropOperation
;
579 void DropTarget::_rejectDrop( const Reference
<XDropTargetDropContext
>& context
)
581 if( context
== m_currentDropContext
)
583 m_nCurrentDropAction
= ACTION_NONE
;
587 void DropTarget::_dropComplete(sal_Bool success
, const Reference
<XDropTargetDropContext
>& context
)
589 if(context
== m_currentDropContext
)
591 m_bDropComplete
= success
;
595 // DropTarget fires events to XDropTargetListeners. The event object can contains an
596 // XDropTargetDragContext implementaion. When the listener calls on that interface
597 // then the calls are delegated from DragContext (XDropTargetDragContext) to these
599 // Only one listener which visible area is affected is allowed to call on
600 // XDropTargetDragContext
601 void DropTarget::_acceptDrag( sal_Int8 dragOperation
, const Reference
<XDropTargetDragContext
>& context
)
603 if( context
== m_currentDragContext
)
605 m_nLastDropAction
= dragOperation
;
609 void DropTarget::_rejectDrag( const Reference
<XDropTargetDragContext
>& context
)
611 if(context
== m_currentDragContext
)
613 m_nLastDropAction
= ACTION_NONE
;
617 // This function determines the action dependent on the pressed
618 // key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result
619 // is then checked against the allowed actions which can be set through
620 // XDropTarget::setDefaultActions. Only those values which are also
621 // default actions are returned. If setDefaultActions has not been called
622 // beforehand the default actions comprise all possible actions.
623 // params: grfKeyState - the modifier keys and mouse buttons currently pressed
624 inline sal_Int8
DropTarget::getFilteredActions( DWORD grfKeyState
, DWORD dwEffect
)
626 sal_Int8 actions
= dndOleKeysToAction( grfKeyState
, dndOleDropEffectsToActions( dwEffect
));
627 return actions
& m_nDefaultActions
;
630 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */