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>
25 #include "idroptarget.hxx"
26 #include "globals.hxx"
27 #include "targetdropcontext.hxx"
28 #include "targetdragcontext.hxx"
29 #include <rtl/ustring.h>
32 using namespace com::sun::star::datatransfer
;
33 using namespace com::sun::star::datatransfer::dnd
;
34 using namespace com::sun::star::datatransfer::dnd::DNDConstants
;
37 #define WM_REGISTERDRAGDROP WM_USER + 1
38 #define WM_REVOKEDRAGDROP WM_USER + 2
39 extern Reference
< XTransferable
> g_XTransferable
;
41 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
);
43 DropTarget::DropTarget( const Reference
<XComponentContext
>& rxContext
):
44 WeakComponentImplHelper3
<XInitialization
,XDropTarget
, XServiceInfo
>(m_mutex
),
51 m_xContext( rxContext
),
53 m_nDefaultActions(ACTION_COPY
|ACTION_MOVE
|ACTION_LINK
|ACTION_DEFAULT
),
54 m_nCurrentDropAction( ACTION_NONE
),
56 m_bDropComplete(false)
60 DropTarget::~DropTarget()
63 // called from WeakComponentImplHelperX::dispose
64 // WeakComponentImplHelper calls disposing before it destroys
66 // NOTE: RevokeDragDrop decrements the ref count on the IDropTarget
67 // interface. (m_pDropTarget)
68 // If the HWND is invalid then it doesn't decrement and
69 // the IDropTarget object will live on. MEMORY LEAK
70 void SAL_CALL
DropTarget::disposing()
74 // Call RevokeDragDrop and wait for the OLE thread to die;
75 PostThreadMessage( m_threadIdTarget
, WM_REVOKEDRAGDROP
, (WPARAM
)this, 0);
76 WaitForSingleObject( m_hOleThread
, INFINITE
);
77 CloseHandle( m_hOleThread
);
78 //OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" );
82 RevokeDragDrop( m_hWnd
);
87 CoLockObjectExternal( m_pDropTarget
, FALSE
, TRUE
);
88 m_pDropTarget
->Release();
93 if( m_oleThreadId
== CoGetCurrentProcess() )
99 void SAL_CALL
DropTarget::initialize( const Sequence
< Any
>& aArguments
)
100 throw(Exception
, RuntimeException
)
102 // The window must be registered for Dnd by RegisterDragDrop. We must ensure
103 // that RegisterDragDrop is called from an STA ( OleInitialize) thread.
104 // As long as the window is registered we need to receive OLE messages in
105 // an OLE thread. That is to say, if DropTarget::initialize was called from an
106 // MTA thread then we create an OLE thread in which the window is registered.
107 // The thread will stay alive until aver RevokeDragDrop has been called.
109 // Additionally even if RegisterDragDrop is called from an STA thread we have
110 // to ensure that it is called from the same thread that created the Window
111 // otherwise meesages sent during DND won't reach the windows message queue.
112 // Calling AttachThreadInput first would resolve this problem but would block
113 // the message queue of the calling thread. So if the current thread
114 // (even if it's an STA thread) and the thread that created the window are not
115 // identical we need to create a new thread as we do when the calling thread is
118 if( aArguments
.getLength() > 0)
120 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
121 m_hWnd
= *(HWND
*)aArguments
[0].getValue();
122 OSL_ASSERT( IsWindow( m_hWnd
) );
124 // Obtain the id of the thread that created the window
125 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, NULL
);
127 HRESULT hr
= OleInitialize( NULL
);
129 // Current thread is MTA or Current thread and Window thread are not identical
130 if( hr
== RPC_E_CHANGED_MODE
|| GetCurrentThreadId() != m_threadIdWindow
)
132 OSL_ENSURE( ! m_threadIdTarget
,"initialize was called twice");
133 // create the IDropTargetImplementation
134 m_pDropTarget
= new IDropTargetImpl( *static_cast<DropTarget
*>( this) );
135 m_pDropTarget
->AddRef();
138 // Obtain the id of the thread that created the window
139 m_threadIdWindow
= GetWindowThreadProcessId( m_hWnd
, NULL
);
140 // The event is set by the thread that we will create momentarily.
141 // It indicates that the thread is ready to receive messages.
142 HANDLE m_evtThreadReady
= CreateEvent( NULL
, FALSE
, FALSE
, NULL
);
144 m_hOleThread
= CreateThread( NULL
, 0, (LPTHREAD_START_ROUTINE
)DndTargetOleSTAFunc
,
145 &m_evtThreadReady
, 0, &m_threadIdTarget
);
146 WaitForSingleObject( m_evtThreadReady
, INFINITE
);
147 CloseHandle( m_evtThreadReady
);
148 PostThreadMessage( m_threadIdTarget
, WM_REGISTERDRAGDROP
, (WPARAM
)static_cast<DropTarget
*>(this), 0);
150 else if( hr
== S_OK
|| hr
== S_FALSE
)
152 // current thread is STA
153 // If OleInitialize has been called by the caller then we must not call
157 // caller did not call OleInitialize, so we call OleUninitialize
158 // remember the thread that will call OleUninitialize
159 m_oleThreadId
= CoGetCurrentProcess(); // get a unique thread id
162 // Get the window handle from aArgument. It is needed for RegisterDragDrop.
163 // create the IDropTargetImplementation
164 m_pDropTarget
= new IDropTargetImpl( *static_cast<DropTarget
*>( this) );
165 m_pDropTarget
->AddRef();
166 // CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count
167 if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget
, TRUE
, FALSE
)))
169 if( FAILED( RegisterDragDrop( m_hWnd
, m_pDropTarget
) ) )
171 // do clean up if drag and drop is not possible
172 CoLockObjectExternal( m_pDropTarget
, FALSE
, FALSE
);
173 m_pDropTarget
->Release();
184 // This function is called as extra thread from DragSource::startDrag.
185 // The function carries out a drag and drop operation by calling
186 // DoDragDrop. The thread also notifies all XSourceListener.
187 DWORD WINAPI
DndTargetOleSTAFunc(LPVOID pParams
)
189 HRESULT hr
= OleInitialize( NULL
);
193 // force the creation of a message queue
194 PeekMessage( &msg
, (HWND
)NULL
, 0, 0, PM_NOREMOVE
);
195 // Signal the creator ( DropTarget::initialize) that the thread is
196 // ready to receive messages.
197 SetEvent( *(HANDLE
*) pParams
);
198 // Thread id is needed for attaching this message queue to the one of the
199 // thread where the window was created.
200 DWORD threadId
= GetCurrentThreadId();
201 // We force the creation of a thread message queue. This is necessary
202 // for a later call to AttachThreadInput
203 while( GetMessage(&msg
, (HWND
)NULL
, 0, 0) )
205 if( msg
.message
== WM_REGISTERDRAGDROP
)
207 DropTarget
*pTarget
= (DropTarget
*)msg
.wParam
;
208 // This thread is attached to the thread that created the window. Hence
209 // this thread also receives all mouse and keyboard messages which are
211 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, TRUE
);
213 if( SUCCEEDED( CoLockObjectExternal(pTarget
-> m_pDropTarget
, TRUE
, FALSE
)))
215 if( FAILED( RegisterDragDrop( pTarget
-> m_hWnd
, pTarget
-> m_pDropTarget
) ) )
217 // do clean up if drag and drop is not possible
218 CoLockObjectExternal( pTarget
->m_pDropTarget
, FALSE
, FALSE
);
219 pTarget
->m_pDropTarget
->Release();
220 pTarget
->m_hWnd
= NULL
;
224 else if( msg
.message
== WM_REVOKEDRAGDROP
)
226 DropTarget
*pTarget
= (DropTarget
*)msg
.wParam
;
227 RevokeDragDrop( pTarget
-> m_hWnd
);
228 // Detach this thread from the window thread
229 AttachThreadInput( threadId
, pTarget
->m_threadIdWindow
, FALSE
);
233 TranslateMessage( &msg
);
234 DispatchMessage( &msg
);
245 OUString SAL_CALL
DropTarget::getImplementationName( ) throw (RuntimeException
)
247 return OUString(RTL_CONSTASCII_USTRINGPARAM(DNDTARGET_IMPL_NAME
));
250 sal_Bool SAL_CALL
DropTarget::supportsService( const OUString
& ServiceName
) throw (RuntimeException
)
252 if( ServiceName
== DNDTARGET_SERVICE_NAME
)
257 Sequence
< OUString
> SAL_CALL
DropTarget::getSupportedServiceNames( ) throw (RuntimeException
)
259 OUString names
[1]= {OUString(RTL_CONSTASCII_USTRINGPARAM(DNDTARGET_SERVICE_NAME
))};
260 return Sequence
<OUString
>(names
, 1);
264 // XDropTarget ----------------------------------------------------------------
265 void SAL_CALL
DropTarget::addDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
266 throw(RuntimeException
)
268 rBHelper
.addListener( ::getCppuType( &dtl
), dtl
);
271 void SAL_CALL
DropTarget::removeDropTargetListener( const Reference
< XDropTargetListener
>& dtl
)
272 throw(RuntimeException
)
274 rBHelper
.removeListener( ::getCppuType( &dtl
), dtl
);
277 sal_Bool SAL_CALL
DropTarget::isActive( ) throw(RuntimeException
)
279 return m_bActive
; //m_bDropTargetRegistered;
283 void SAL_CALL
DropTarget::setActive( sal_Bool _b
) throw(RuntimeException
)
285 MutexGuard
g(m_mutex
);
290 sal_Int8 SAL_CALL
DropTarget::getDefaultActions( ) throw(RuntimeException
)
292 return m_nDefaultActions
;
295 void SAL_CALL
DropTarget::setDefaultActions( sal_Int8 actions
) throw(RuntimeException
)
297 OSL_ENSURE( actions
< 8, "No valid default actions");
298 m_nDefaultActions
= actions
;
302 HRESULT
DropTarget::DragEnter( IDataObject
*pDataObj
,
307 #if defined DBG_CONSOLE_OUT
308 printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState
, *pdwEffect
);
312 // Intersection of pdwEffect and the allowed actions ( setDefaultActions)
313 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
314 // m_nLastDropAction has to be set by a listener. If no listener calls
315 //XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect
316 // will be DROPEFFECT_NONE throughout
317 m_nLastDropAction
= ACTION_DEFAULT
| ACTION_MOVE
;
319 m_currentDragContext
= static_cast<XDropTargetDragContext
*>( new TargetDragContext(
320 static_cast<DropTarget
*>(this) ) );
325 if ( g_XTransferable
.is( ) )
326 m_currentData
= g_XTransferable
;
329 // Convert the IDataObject to a XTransferable
330 m_currentData
= m_aDataConverter
.createTransferableFromDataObj(
331 m_xContext
, IDataObjectPtr(pDataObj
));
336 if( m_nCurrentDropAction
!= ACTION_NONE
)
338 DropTargetDragEnterEvent e
;
339 e
.SupportedDataFlavors
= m_currentData
->getTransferDataFlavors();
340 e
.DropAction
= m_nCurrentDropAction
;
341 e
.Source
= Reference
<XInterface
>( static_cast<XDropTarget
*>(this),UNO_QUERY
);
342 e
.Context
= m_currentDragContext
;
343 POINT point
={ pt
.x
, pt
.y
};
344 ScreenToClient( m_hWnd
, &point
);
345 e
.LocationX
= point
.x
;
346 e
.LocationY
= point
.y
;
347 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
350 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
351 // by the listener (m_nCurrentDropAction) is allowed by the source. Only a allowed action is set
352 // in pdwEffect. The listener notification is asynchron, that is we cannot expext that the listener
353 // has already reacted to the notification.
354 // If there is more then one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
355 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
356 // On drop the target should present the user a dialog from which the user may change the action.
357 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
358 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
362 *pdwEffect
= DROPEFFECT_NONE
;
368 HRESULT
DropTarget::DragOver( DWORD grfKeyState
,
374 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
376 if( m_nCurrentDropAction
)
378 DropTargetDragEvent e
;
379 e
.DropAction
= m_nCurrentDropAction
;
380 e
.Source
= Reference
<XInterface
>(static_cast<XDropTarget
*>(this),UNO_QUERY
);
381 e
.Context
= m_currentDragContext
;
382 POINT point
={ pt
.x
, pt
.y
};
383 ScreenToClient( m_hWnd
, &point
);
384 e
.LocationX
= point
.x
;
385 e
.LocationY
= point
.y
;
386 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
388 // if grfKeyState has changed since the last DragOver then fire events.
389 // A listener might change m_nCurrentDropAction by calling the
390 // XDropTargetDragContext::acceptDrag function. But this is not important
391 // because in the afterwards fired dragOver event the action reflects
392 // grgKeyState again.
393 if( m_nLastDropAction
!= m_nCurrentDropAction
)
394 fire_dropActionChanged( e
);
396 // The Event contains a XDropTargetDragContext implementation.
398 // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set
399 // by the listener (m_nCurrentDropAction) is allowed by the source. Only a allowed action is set
400 // in pdwEffect. The listener notification is asynchron, that is we cannot expext that the listener
401 // has already reacted to the notification.
402 // If there is more then one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed
403 // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed.
404 // On drop the target should present the user a dialog from which the user may change the action.
405 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
406 // set the last action to the current if listener has not changed the value yet
407 *pdwEffect
= dndActionsToSingleDropEffect( m_nLastDropAction
& allowedActions
);
411 *pdwEffect
= DROPEFFECT_NONE
;
414 #if defined DBG_CONSOLE_OUT
415 printf("\nDropTarget::DragOver %d", *pdwEffect
);
420 HRESULT
DropTarget::DragLeave( void)
422 #if defined DBG_CONSOLE_OUT
423 printf("\nDropTarget::DragLeave");
429 m_currentDragContext
= 0;
430 m_currentDropContext
= 0;
431 m_nLastDropAction
= 0;
433 if( m_nDefaultActions
!= ACTION_NONE
)
436 e
.Source
= static_cast<XDropTarget
*>(this);
444 HRESULT
DropTarget::Drop( IDataObject
* /*pDataObj*/,
449 #if defined DBG_CONSOLE_OUT
450 printf("\nDropTarget::Drop");
455 m_bDropComplete
= sal_False
;
457 m_nCurrentDropAction
= getFilteredActions( grfKeyState
, *pdwEffect
);
458 m_currentDropContext
= static_cast<XDropTargetDropContext
*>( new TargetDropContext( static_cast<DropTarget
*>(this )) );
459 if( m_nCurrentDropAction
)
461 DropTargetDropEvent e
;
462 e
.DropAction
= m_nCurrentDropAction
;
463 e
.Source
= Reference
<XInterface
>( static_cast<XDropTarget
*>(this), UNO_QUERY
);
464 e
.Context
= m_currentDropContext
;
465 POINT point
={ pt
.x
, pt
.y
};
466 ScreenToClient( m_hWnd
, &point
);
467 e
.LocationX
= point
.x
;
468 e
.LocationY
= point
.y
;
469 e
.SourceActions
= dndOleDropEffectsToActions( *pdwEffect
);
470 e
.Transferable
= m_currentData
;
473 //if fire_drop returns than a listener might have modified m_nCurrentDropAction
474 if( m_bDropComplete
== sal_True
)
476 sal_Int8 allowedActions
= dndOleDropEffectsToActions( *pdwEffect
);
477 *pdwEffect
= dndActionsToSingleDropEffect( m_nCurrentDropAction
& allowedActions
);
480 *pdwEffect
= DROPEFFECT_NONE
;
483 *pdwEffect
= DROPEFFECT_NONE
;
486 m_currentDragContext
= 0;
487 m_currentDropContext
= 0;
488 m_nLastDropAction
= 0;
495 void DropTarget::fire_drop( const DropTargetDropEvent
& dte
)
497 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( getCppuType( (Reference
<XDropTargetListener
>* )0 ) );
500 OInterfaceIteratorHelper
iter( *pContainer
);
501 while( iter
.hasMoreElements())
503 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
504 listener
->drop( dte
);
509 void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent
& e
)
511 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( getCppuType( (Reference
<XDropTargetListener
>* )0 ) );
514 OInterfaceIteratorHelper
iter( *pContainer
);
515 while( iter
.hasMoreElements())
517 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
518 listener
->dragEnter( e
);
523 void DropTarget::fire_dragExit( const DropTargetEvent
& dte
)
525 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( getCppuType( (Reference
<XDropTargetListener
>* )0 ) );
529 OInterfaceIteratorHelper
iter( *pContainer
);
530 while( iter
.hasMoreElements())
532 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
533 listener
->dragExit( dte
);
538 void DropTarget::fire_dragOver( const DropTargetDragEvent
& dtde
)
540 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( getCppuType( (Reference
<XDropTargetListener
>* )0 ) );
543 OInterfaceIteratorHelper
iter( *pContainer
);
544 while( iter
.hasMoreElements())
546 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
547 listener
->dragOver( dtde
);
552 void DropTarget::fire_dropActionChanged( const DropTargetDragEvent
& dtde
)
554 OInterfaceContainerHelper
* pContainer
= rBHelper
.getContainer( getCppuType( (Reference
<XDropTargetListener
>* )0 ) );
557 OInterfaceIteratorHelper
iter( *pContainer
);
558 while( iter
.hasMoreElements())
560 Reference
<XDropTargetListener
> listener( static_cast<XDropTargetListener
*>( iter
.next()));
561 listener
->dropActionChanged( dtde
);
566 // Non - interface functions ============================================================
567 // DropTarget fires events to XDropTargetListeners. The event object contains an
568 // XDropTargetDropContext implementaion. When the listener calls on that interface
569 // then the calls are delegated from DropContext (XDropTargetDropContext) to these
571 // Only one listener which visible area is affected is allowed to call on
572 // XDropTargetDropContext
573 // Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation
574 // to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed.
575 // return sal_False results in throwing a InvalidDNDOperationException in the caller.
577 void DropTarget::_acceptDrop(sal_Int8 dropOperation
, const Reference
<XDropTargetDropContext
>& context
)
579 if( context
== m_currentDropContext
)
581 m_nCurrentDropAction
= dropOperation
;
585 void DropTarget::_rejectDrop( const Reference
<XDropTargetDropContext
>& context
)
587 if( context
== m_currentDropContext
)
589 m_nCurrentDropAction
= ACTION_NONE
;
593 void DropTarget::_dropComplete(sal_Bool success
, const Reference
<XDropTargetDropContext
>& context
)
595 if(context
== m_currentDropContext
)
597 m_bDropComplete
= success
;
600 // --------------------------------------------------------------------------------------
601 // DropTarget fires events to XDropTargetListeners. The event object can contains an
602 // XDropTargetDragContext implementaion. When the listener calls on that interface
603 // then the calls are delegated from DragContext (XDropTargetDragContext) to these
605 // Only one listener which visible area is affected is allowed to call on
606 // XDropTargetDragContext
607 void DropTarget::_acceptDrag( sal_Int8 dragOperation
, const Reference
<XDropTargetDragContext
>& context
)
609 if( context
== m_currentDragContext
)
611 m_nLastDropAction
= dragOperation
;
615 void DropTarget::_rejectDrag( const Reference
<XDropTargetDragContext
>& context
)
617 if(context
== m_currentDragContext
)
619 m_nLastDropAction
= ACTION_NONE
;
624 //--------------------------------------------------------------------------------------
627 // This function determines the action dependend on the pressed
628 // key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result
629 // is then checked against the allowed actions which can be set through
630 // XDropTarget::setDefaultActions. Only those values which are also
631 // default actions are returned. If setDefaultActions has not been called
632 // beforehand the default actions comprise all possible actions.
633 // params: grfKeyState - the modifier keys and mouse buttons currently pressed
634 inline sal_Int8
DropTarget::getFilteredActions( DWORD grfKeyState
, DWORD dwEffect
)
636 sal_Int8 actions
= dndOleKeysToAction( grfKeyState
, dndOleDropEffectsToActions( dwEffect
));
637 return actions
& m_nDefaultActions
;
641 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */