/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include "idroptarget.hxx" #include "globals.hxx" #include "targetdropcontext.hxx" #include "targetdragcontext.hxx" #include #include #include #include #include "DOTransferable.hxx" using namespace cppu; using namespace osl; using namespace com::sun::star::datatransfer; using namespace com::sun::star::datatransfer::dnd; using namespace com::sun::star::datatransfer::dnd::DNDConstants; #define WM_REGISTERDRAGDROP WM_USER + 1 #define WM_REVOKEDRAGDROP WM_USER + 2 DWORD WINAPI DndTargetOleSTAFunc(LPVOID pParams); DropTarget::DropTarget( const Reference& rxContext): WeakComponentImplHelper(m_aMutex), m_hWnd( nullptr), m_threadIdWindow(0), m_threadIdTarget(0), m_hOleThread(nullptr), m_oleThreadId( 0), m_pDropTarget( nullptr), m_xContext( rxContext ), m_bActive(true), m_nDefaultActions(ACTION_COPY|ACTION_MOVE|ACTION_LINK|ACTION_DEFAULT), m_nCurrentDropAction( ACTION_NONE), m_nLastDropAction(0), m_bDropComplete(false) { } DropTarget::~DropTarget() { } // called from WeakComponentImplHelperX::dispose // WeakComponentImplHelper calls disposing before it destroys // itself. // NOTE: RevokeDragDrop decrements the ref count on the IDropTarget // interface. (m_pDropTarget) // If the HWND is invalid then it doesn't decrement and // the IDropTarget object will live on. MEMORY LEAK void SAL_CALL DropTarget::disposing() { if( m_threadIdTarget) { // Call RevokeDragDrop and wait for the OLE thread to die; PostThreadMessageW( m_threadIdTarget, WM_REVOKEDRAGDROP, reinterpret_cast(this), 0); WaitForSingleObject( m_hOleThread, INFINITE); CloseHandle( m_hOleThread); //OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" ); } else { RevokeDragDrop( m_hWnd); m_hWnd= nullptr; } if( m_pDropTarget) { CoLockObjectExternal( m_pDropTarget, FALSE, TRUE); m_pDropTarget->Release(); m_pDropTarget = nullptr; } if( m_oleThreadId) { if( m_oleThreadId == CoGetCurrentProcess() ) OleUninitialize(); } } void SAL_CALL DropTarget::initialize( const Sequence< Any >& aArguments ) { // The window must be registered for Dnd by RegisterDragDrop. We must ensure // that RegisterDragDrop is called from an STA ( OleInitialize) thread. // As long as the window is registered we need to receive OLE messages in // an OLE thread. That is to say, if DropTarget::initialize was called from an // MTA thread then we create an OLE thread in which the window is registered. // The thread will stay alive until aver RevokeDragDrop has been called. // Additionally even if RegisterDragDrop is called from an STA thread we have // to ensure that it is called from the same thread that created the Window // otherwise messages sent during DND won't reach the windows message queue. // Calling AttachThreadInput first would resolve this problem but would block // the message queue of the calling thread. So if the current thread // (even if it's an STA thread) and the thread that created the window are not // identical we need to create a new thread as we do when the calling thread is // an MTA thread. if( aArguments.getLength() > 0) { // Get the window handle from aArgument. It is needed for RegisterDragDrop. m_hWnd= reinterpret_cast(static_cast(*o3tl::doAccess(aArguments[0]))); OSL_ASSERT( IsWindow( m_hWnd) ); // Obtain the id of the thread that created the window m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, nullptr); HRESULT hr= OleInitialize( nullptr); // Current thread is MTA or Current thread and Window thread are not identical if( hr == RPC_E_CHANGED_MODE || GetCurrentThreadId() != m_threadIdWindow ) { OSL_ENSURE( ! m_threadIdTarget,"initialize was called twice"); // create the IDropTargetImplementation m_pDropTarget= new IDropTargetImpl( *this ); m_pDropTarget->AddRef(); // Obtain the id of the thread that created the window m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, nullptr); // The event is set by the thread that we will create momentarily. // It indicates that the thread is ready to receive messages. HANDLE m_evtThreadReady= CreateEventW( nullptr, FALSE, FALSE, nullptr); m_hOleThread= CreateThread( nullptr, 0, DndTargetOleSTAFunc, &m_evtThreadReady, 0, &m_threadIdTarget); WaitForSingleObject( m_evtThreadReady, INFINITE); CloseHandle( m_evtThreadReady); PostThreadMessageW( m_threadIdTarget, WM_REGISTERDRAGDROP, reinterpret_cast(this), 0); } else if( hr == S_OK || hr == S_FALSE) { // current thread is STA // If OleInitialize has been called by the caller then we must not call // OleUninitialize if( hr == S_OK) { // caller did not call OleInitialize, so we call OleUninitialize // remember the thread that will call OleUninitialize m_oleThreadId= CoGetCurrentProcess(); // get a unique thread id } // Get the window handle from aArgument. It is needed for RegisterDragDrop. // create the IDropTargetImplementation m_pDropTarget= new IDropTargetImpl( *this ); m_pDropTarget->AddRef(); // CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget, TRUE, FALSE))) { if( FAILED( RegisterDragDrop( m_hWnd, m_pDropTarget) ) ) { // do clean up if drag and drop is not possible CoLockObjectExternal( m_pDropTarget, FALSE, FALSE); m_pDropTarget->Release(); m_pDropTarget = nullptr; m_hWnd= nullptr; } } } else throw Exception("OleInitialize failed with " + OUString::number(hr), nullptr); } } // This function is called as extra thread from DragSource::startDrag. // The function carries out a drag and drop operation by calling // DoDragDrop. The thread also notifies all XSourceListener. DWORD WINAPI DndTargetOleSTAFunc(LPVOID pParams) { osl_setThreadName("DropTarget DndTargetOleSTAFunc"); HRESULT hr= OleInitialize( nullptr); if( SUCCEEDED( hr) ) { MSG msg; // force the creation of a message queue PeekMessageW( &msg, nullptr, 0, 0, PM_NOREMOVE); // Signal the creator ( DropTarget::initialize) that the thread is // ready to receive messages. SetEvent( *static_cast(pParams)); // Thread id is needed for attaching this message queue to the one of the // thread where the window was created. DWORD threadId= GetCurrentThreadId(); // We force the creation of a thread message queue. This is necessary // for a later call to AttachThreadInput for (;;) { int const bRet = GetMessageW(&msg, nullptr, 0, 0); if (bRet == 0) { break; } if (-1 == bRet) { SAL_WARN("vcl.win.dtrans", "GetMessageW failed: " << WindowsErrorString(GetLastError())); break; } if( msg.message == WM_REGISTERDRAGDROP) { DropTarget *pTarget= reinterpret_cast(msg.wParam); // This thread is attached to the thread that created the window. Hence // this thread also receives all mouse and keyboard messages which are // needed AttachThreadInput( threadId , pTarget->m_threadIdWindow, TRUE ); if( SUCCEEDED( CoLockObjectExternal(pTarget-> m_pDropTarget, TRUE, FALSE))) { if( FAILED( RegisterDragDrop( pTarget-> m_hWnd, pTarget-> m_pDropTarget) ) ) { // do clean up if drag and drop is not possible CoLockObjectExternal( pTarget->m_pDropTarget, FALSE, FALSE); pTarget->m_pDropTarget->Release(); pTarget->m_pDropTarget = nullptr; pTarget->m_hWnd= nullptr; } } } else if( msg.message == WM_REVOKEDRAGDROP) { DropTarget *pTarget= reinterpret_cast(msg.wParam); RevokeDragDrop( pTarget-> m_hWnd); // Detach this thread from the window thread AttachThreadInput( threadId, pTarget->m_threadIdWindow, FALSE); pTarget->m_hWnd= nullptr; break; } TranslateMessage( &msg); DispatchMessageW( &msg); } OleUninitialize(); } return 0; } // XServiceInfo OUString SAL_CALL DropTarget::getImplementationName( ) { return "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1"; } // XServiceInfo sal_Bool SAL_CALL DropTarget::supportsService( const OUString& ServiceName ) { return cppu::supportsService(this, ServiceName); } Sequence< OUString > SAL_CALL DropTarget::getSupportedServiceNames( ) { return { "com.sun.star.datatransfer.dnd.OleDropTarget" }; } // XDropTarget void SAL_CALL DropTarget::addDropTargetListener( const Reference< XDropTargetListener >& dtl ) { rBHelper.addListener( cppu::UnoType::get(), dtl ); } void SAL_CALL DropTarget::removeDropTargetListener( const Reference< XDropTargetListener >& dtl ) { rBHelper.removeListener( cppu::UnoType::get(), dtl ); } sal_Bool SAL_CALL DropTarget::isActive( ) { return m_bActive; //m_bDropTargetRegistered; } void SAL_CALL DropTarget::setActive( sal_Bool _b ) { MutexGuard g(m_aMutex); m_bActive= _b; } sal_Int8 SAL_CALL DropTarget::getDefaultActions( ) { return m_nDefaultActions; } void SAL_CALL DropTarget::setDefaultActions( sal_Int8 actions ) { OSL_ENSURE( actions < 8, "No valid default actions"); m_nDefaultActions= actions; } HRESULT DropTarget::DragEnter( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { #if defined DBG_CONSOLE_OUT printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState, *pdwEffect); #endif if( m_bActive ) { // Intersection of pdwEffect and the allowed actions ( setDefaultActions) m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect); // m_nLastDropAction has to be set by a listener. If no listener calls //XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect // will be DROPEFFECT_NONE throughout m_nLastDropAction= ACTION_DEFAULT | ACTION_MOVE; m_currentDragContext = new TargetDragContext(this); //--> TRA // shortcut if ( g_XTransferable.is( ) ) m_currentData = g_XTransferable; else { // Convert the IDataObject to a XTransferable m_currentData = new CDOTransferable(m_xContext, IDataObjectPtr(pDataObj)); } //<-- TRA if( m_nCurrentDropAction != ACTION_NONE) { DropTargetDragEnterEvent e; e.SupportedDataFlavors= m_currentData->getTransferDataFlavors(); e.DropAction= m_nCurrentDropAction; e.Source.set( static_cast(this),UNO_QUERY); e.Context= m_currentDragContext; POINT point={ pt.x, pt.y}; ScreenToClient( m_hWnd, &point); e.LocationX= point.x; e.LocationY= point.y; e.SourceActions= dndOleDropEffectsToActions( *pdwEffect); fire_dragEnter( e); // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener // has already reacted to the notification. // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed. // On drop the target should present the user a dialog from which the user may change the action. sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect); *pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions); } else { *pdwEffect= DROPEFFECT_NONE; } } return S_OK; } HRESULT DropTarget::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { if( m_bActive) { m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect); if( m_nCurrentDropAction) { DropTargetDragEvent e; e.DropAction= m_nCurrentDropAction; e.Source.set(static_cast(this),UNO_QUERY); e.Context= m_currentDragContext; POINT point={ pt.x, pt.y}; ScreenToClient( m_hWnd, &point); e.LocationX= point.x; e.LocationY= point.y; e.SourceActions= dndOleDropEffectsToActions( *pdwEffect); // if grfKeyState has changed since the last DragOver then fire events. // A listener might change m_nCurrentDropAction by calling the // XDropTargetDragContext::acceptDrag function. But this is not important // because in the afterwards fired dragOver event the action reflects // grgKeyState again. if( m_nLastDropAction != m_nCurrentDropAction) fire_dropActionChanged( e); // The Event contains a XDropTargetDragContext implementation. fire_dragOver( e); // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener // has already reacted to the notification. // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed. // On drop the target should present the user a dialog from which the user may change the action. sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect); // set the last action to the current if listener has not changed the value yet *pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions); } else { *pdwEffect= DROPEFFECT_NONE; } } #if defined DBG_CONSOLE_OUT printf("\nDropTarget::DragOver %d", *pdwEffect ); #endif return S_OK; } HRESULT DropTarget::DragLeave() { #if defined DBG_CONSOLE_OUT printf("\nDropTarget::DragLeave"); #endif if( m_bActive) { m_currentData=nullptr; m_currentDragContext= nullptr; m_currentDropContext= nullptr; m_nLastDropAction= 0; if( m_nDefaultActions != ACTION_NONE) { DropTargetEvent e; e.Source= static_cast(this); fire_dragExit( e); } } return S_OK; } HRESULT DropTarget::Drop( IDataObject * /*pDataObj*/, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { #if defined DBG_CONSOLE_OUT printf("\nDropTarget::Drop"); #endif if( m_bActive) { m_bDropComplete= false; m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect); m_currentDropContext = new TargetDropContext(this); if( m_nCurrentDropAction) { DropTargetDropEvent e; e.DropAction= m_nCurrentDropAction; e.Source.set( static_cast(this), UNO_QUERY); e.Context= m_currentDropContext; POINT point={ pt.x, pt.y}; ScreenToClient( m_hWnd, &point); e.LocationX= point.x; e.LocationY= point.y; e.SourceActions= dndOleDropEffectsToActions( *pdwEffect); e.Transferable= m_currentData; fire_drop( e); //if fire_drop returns than a listener might have modified m_nCurrentDropAction if( m_bDropComplete ) { sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect); *pdwEffect= dndActionsToSingleDropEffect( m_nCurrentDropAction & allowedActions); } else *pdwEffect= DROPEFFECT_NONE; } else *pdwEffect= DROPEFFECT_NONE; m_currentData= nullptr; m_currentDragContext= nullptr; m_currentDropContext= nullptr; m_nLastDropAction= 0; } return S_OK; } void DropTarget::fire_drop( const DropTargetDropEvent& dte) { OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType::get()); if( pContainer) { OInterfaceIteratorHelper iter( *pContainer); while( iter.hasMoreElements()) { Reference listener( static_cast( iter.next())); listener->drop( dte); } } } void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent& e ) { OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType::get()); if( pContainer) { OInterfaceIteratorHelper iter( *pContainer); while( iter.hasMoreElements()) { Reference listener( static_cast( iter.next())); listener->dragEnter( e); } } } void DropTarget::fire_dragExit( const DropTargetEvent& dte ) { OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType::get()); if( pContainer) { OInterfaceIteratorHelper iter( *pContainer); while( iter.hasMoreElements()) { Reference listener( static_cast( iter.next())); listener->dragExit( dte); } } } void DropTarget::fire_dragOver( const DropTargetDragEvent& dtde ) { OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType::get()); if( pContainer) { OInterfaceIteratorHelper iter( *pContainer ); while( iter.hasMoreElements()) { Reference listener( static_cast( iter.next())); listener->dragOver( dtde); } } } void DropTarget::fire_dropActionChanged( const DropTargetDragEvent& dtde ) { OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType::get()); if( pContainer) { OInterfaceIteratorHelper iter( *pContainer); while( iter.hasMoreElements()) { Reference listener( static_cast( iter.next())); listener->dropActionChanged( dtde); } } } // Non - interface functions // DropTarget fires events to XDropTargetListeners. The event object contains an // XDropTargetDropContext implementation. When the listener calls on that interface // then the calls are delegated from DropContext (XDropTargetDropContext) to these // functions. // Only one listener which visible area is affected is allowed to call on // XDropTargetDropContext // Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation // to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed. // return sal_False results in throwing an InvalidDNDOperationException in the caller. void DropTarget::_acceptDrop(sal_Int8 dropOperation, const Reference& context) { if( context == m_currentDropContext) { m_nCurrentDropAction= dropOperation; } } void DropTarget::_rejectDrop( const Reference& context) { if( context == m_currentDropContext) { m_nCurrentDropAction= ACTION_NONE; } } void DropTarget::_dropComplete(bool success, const Reference& context) { if(context == m_currentDropContext) { m_bDropComplete= success; } } // DropTarget fires events to XDropTargetListeners. The event object can contains an // XDropTargetDragContext implementation. When the listener calls on that interface // then the calls are delegated from DragContext (XDropTargetDragContext) to these // functions. // Only one listener which visible area is affected is allowed to call on // XDropTargetDragContext void DropTarget::_acceptDrag( sal_Int8 dragOperation, const Reference& context) { if( context == m_currentDragContext) { m_nLastDropAction= dragOperation; } } void DropTarget::_rejectDrag( const Reference& context) { if(context == m_currentDragContext) { m_nLastDropAction= ACTION_NONE; } } // This function determines the action dependent on the pressed // key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result // is then checked against the allowed actions which can be set through // XDropTarget::setDefaultActions. Only those values which are also // default actions are returned. If setDefaultActions has not been called // beforehand the default actions comprise all possible actions. // params: grfKeyState - the modifier keys and mouse buttons currently pressed inline sal_Int8 DropTarget::getFilteredActions( DWORD grfKeyState, DWORD dwEffect) { sal_Int8 actions= dndOleKeysToAction( grfKeyState, dndOleDropEffectsToActions( dwEffect)); return actions & m_nDefaultActions; } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* dtrans_DropTarget_get_implementation( css::uno::XComponentContext* context, css::uno::Sequence const&) { return cppu::acquire(new DropTarget(context)); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */