Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / dtrans / source / win32 / dnd / target.cxx
blob7592eb390962f448b8b5ec878eded84ee44471dc
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 <o3tl/any.hxx>
25 #include <stdio.h>
26 #include "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>
34 #include "../dtobj/DOTransferable.hxx"
36 using namespace cppu;
37 using namespace osl;
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),
49 m_hWnd( nullptr),
50 m_threadIdWindow(0),
51 m_threadIdTarget(0),
52 m_hOleThread(nullptr),
53 m_oleThreadId( 0),
54 m_pDropTarget( nullptr),
55 m_xContext( rxContext ),
56 m_bActive(true),
57 m_nDefaultActions(ACTION_COPY|ACTION_MOVE|ACTION_LINK|ACTION_DEFAULT),
58 m_nCurrentDropAction( ACTION_NONE),
59 m_nLastDropAction(0),
60 m_bDropComplete(false)
64 DropTarget::~DropTarget()
67 // called from WeakComponentImplHelperX::dispose
68 // WeakComponentImplHelper calls disposing before it destroys
69 // itself.
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()
76 if( m_threadIdTarget)
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!" );
84 else
86 RevokeDragDrop( m_hWnd);
87 m_hWnd= nullptr;
89 if( m_pDropTarget)
91 CoLockObjectExternal( m_pDropTarget, FALSE, TRUE);
92 m_pDropTarget->Release();
93 m_pDropTarget = nullptr;
96 if( m_oleThreadId)
98 if( m_oleThreadId == CoGetCurrentProcess() )
99 OleUninitialize();
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
120 // an MTA thread.
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
157 // OleUninitialize
158 if( hr == S_OK)
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;
178 m_hWnd= nullptr;
182 else
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);
196 if( SUCCEEDED( hr) )
198 MSG msg;
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
216 // needed
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;
238 break;
240 TranslateMessage( &msg);
241 DispatchMessageW( &msg);
243 OleUninitialize();
245 return 0;
248 // XServiceInfo
249 OUString SAL_CALL DropTarget::getImplementationName( )
251 return DNDTARGET_IMPL_NAME;
253 // XServiceInfo
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 };
264 // XDropTarget
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);
283 m_bActive= _b;
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,
298 DWORD grfKeyState,
299 POINTL pt,
300 DWORD *pdwEffect)
302 #if defined DBG_CONSOLE_OUT
303 printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState, *pdwEffect);
304 #endif
305 if( m_bActive )
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);
316 //--> TRA
318 // shortcut
319 if ( g_XTransferable.is( ) )
320 m_currentData = g_XTransferable;
321 else
323 // Convert the IDataObject to a XTransferable
324 m_currentData= CDOTransferable::create(
325 m_xContext, IDataObjectPtr(pDataObj));
328 //<-- TRA
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);
343 fire_dragEnter( e);
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);
354 else
356 *pdwEffect= DROPEFFECT_NONE;
359 return S_OK;
362 HRESULT DropTarget::DragOver( DWORD grfKeyState,
363 POINTL pt,
364 DWORD *pdwEffect)
366 if( m_bActive)
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.
391 fire_dragOver( e);
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);
403 else
405 *pdwEffect= DROPEFFECT_NONE;
408 #if defined DBG_CONSOLE_OUT
409 printf("\nDropTarget::DragOver %d", *pdwEffect );
410 #endif
411 return S_OK;
414 HRESULT DropTarget::DragLeave()
416 #if defined DBG_CONSOLE_OUT
417 printf("\nDropTarget::DragLeave");
418 #endif
419 if( m_bActive)
422 m_currentData=nullptr;
423 m_currentDragContext= nullptr;
424 m_currentDropContext= nullptr;
425 m_nLastDropAction= 0;
427 if( m_nDefaultActions != ACTION_NONE)
429 DropTargetEvent e;
430 e.Source= static_cast<XDropTarget*>(this);
432 fire_dragExit( e);
435 return S_OK;
438 HRESULT DropTarget::Drop( IDataObject * /*pDataObj*/,
439 DWORD grfKeyState,
440 POINTL pt,
441 DWORD *pdwEffect)
443 #if defined DBG_CONSOLE_OUT
444 printf("\nDropTarget::Drop");
445 #endif
446 if( m_bActive)
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;
465 fire_drop( e);
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);
473 else
474 *pdwEffect= DROPEFFECT_NONE;
476 else
477 *pdwEffect= DROPEFFECT_NONE;
479 m_currentData= nullptr;
480 m_currentDragContext= nullptr;
481 m_currentDropContext= nullptr;
482 m_nLastDropAction= 0;
484 return S_OK;
487 void DropTarget::fire_drop( const DropTargetDropEvent& dte)
489 OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
490 if( pContainer)
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());
504 if( pContainer)
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());
519 if( pContainer)
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());
533 if( pContainer)
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());
547 if( pContainer)
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
562 // functions.
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
596 // functions.
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: */