Branch libreoffice-5-0-4
[LibreOffice.git] / dtrans / source / win32 / dnd / target.cxx
blob9f1132977a828db1001210e188b5b2bbf73154dc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <stdio.h>
24 #include "target.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>
32 using namespace cppu;
33 using namespace osl;
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),
46 m_hWnd( NULL),
47 m_threadIdWindow(0),
48 m_threadIdTarget(0),
49 m_hOleThread(0),
50 m_oleThreadId( 0),
51 m_pDropTarget( NULL),
52 m_xContext( rxContext ),
53 m_bActive(sal_True),
54 m_nDefaultActions(ACTION_COPY|ACTION_MOVE|ACTION_LINK|ACTION_DEFAULT),
55 m_nCurrentDropAction( ACTION_NONE),
56 m_nLastDropAction(0),
57 m_bDropComplete(false)
61 DropTarget::~DropTarget()
64 // called from WeakComponentImplHelperX::dispose
65 // WeakComponentImplHelper calls disposing before it destroys
66 // itself.
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()
73 if( m_threadIdTarget)
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!" );
81 else
83 RevokeDragDrop( m_hWnd);
84 m_hWnd= 0;
86 if( m_pDropTarget)
88 CoLockObjectExternal( m_pDropTarget, FALSE, TRUE);
89 m_pDropTarget->Release();
90 m_pDropTarget = nullptr;
93 if( m_oleThreadId)
95 if( m_oleThreadId == CoGetCurrentProcess() )
96 OleUninitialize();
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
118 // an MTA thread.
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
155 // OleUninitialize
156 if( hr == S_OK)
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;
176 m_hWnd= NULL;
180 else
181 throw Exception();
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);
194 if( SUCCEEDED( hr) )
196 MSG msg;
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
214 // needed
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);
235 pTarget->m_hWnd= 0;
236 break;
238 TranslateMessage( &msg);
239 DispatchMessage( &msg);
241 OleUninitialize();
243 return 0;
246 // XServiceInfo
247 OUString SAL_CALL DropTarget::getImplementationName( ) throw (RuntimeException)
249 return OUString(DNDTARGET_IMPL_NAME);
251 // XServiceInfo
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);
263 // XDropTarget
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);
284 m_bActive= _b;
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,
299 DWORD grfKeyState,
300 POINTL pt,
301 DWORD *pdwEffect)
303 #if defined DBG_CONSOLE_OUT
304 printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState, *pdwEffect);
305 #endif
306 if( m_bActive )
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) ) );
318 //--> TRA
320 // shortcut
321 if ( g_XTransferable.is( ) )
322 m_currentData = g_XTransferable;
323 else
325 // Convert the IDataObject to a XTransferable
326 m_currentData= m_aDataConverter.createTransferableFromDataObj(
327 m_xContext, IDataObjectPtr(pDataObj));
330 //<-- TRA
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);
345 fire_dragEnter( e);
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);
356 else
358 *pdwEffect= DROPEFFECT_NONE;
361 return S_OK;
364 HRESULT DropTarget::DragOver( DWORD grfKeyState,
365 POINTL pt,
366 DWORD *pdwEffect)
368 if( m_bActive)
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.
393 fire_dragOver( e);
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);
405 else
407 *pdwEffect= DROPEFFECT_NONE;
410 #if defined DBG_CONSOLE_OUT
411 printf("\nDropTarget::DragOver %d", *pdwEffect );
412 #endif
413 return S_OK;
416 HRESULT DropTarget::DragLeave()
418 #if defined DBG_CONSOLE_OUT
419 printf("\nDropTarget::DragLeave");
420 #endif
421 if( m_bActive)
424 m_currentData=0;
425 m_currentDragContext= 0;
426 m_currentDropContext= 0;
427 m_nLastDropAction= 0;
429 if( m_nDefaultActions != ACTION_NONE)
431 DropTargetEvent e;
432 e.Source= static_cast<XDropTarget*>(this);
434 fire_dragExit( e);
437 return S_OK;
440 HRESULT DropTarget::Drop( IDataObject * /*pDataObj*/,
441 DWORD grfKeyState,
442 POINTL pt,
443 DWORD *pdwEffect)
445 #if defined DBG_CONSOLE_OUT
446 printf("\nDropTarget::Drop");
447 #endif
448 if( m_bActive)
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;
467 fire_drop( e);
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);
475 else
476 *pdwEffect= DROPEFFECT_NONE;
478 else
479 *pdwEffect= DROPEFFECT_NONE;
481 m_currentData= 0;
482 m_currentDragContext= 0;
483 m_currentDropContext= 0;
484 m_nLastDropAction= 0;
486 return S_OK;
489 void DropTarget::fire_drop( const DropTargetDropEvent& dte)
491 OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
492 if( pContainer)
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());
506 if( pContainer)
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());
521 if( pContainer)
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());
535 if( pContainer)
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());
549 if( pContainer)
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
564 // functions.
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
598 // functions.
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: */