michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include "nsIDragService.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsNativeDragTarget.h" michael@0: #include "nsDragService.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsCOMPtr.h" michael@0: michael@0: #include "nsIWidget.h" michael@0: #include "nsWindow.h" michael@0: #include "nsClipboard.h" michael@0: #include "KeyboardLayout.h" michael@0: michael@0: #include "mozilla/MouseEvents.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: /* Define Interface IDs */ michael@0: static NS_DEFINE_IID(kIDragServiceIID, NS_IDRAGSERVICE_IID); michael@0: michael@0: // This is cached for Leave notification michael@0: static POINTL gDragLastPoint; michael@0: michael@0: /* michael@0: * class nsNativeDragTarget michael@0: */ michael@0: nsNativeDragTarget::nsNativeDragTarget(nsIWidget * aWidget) michael@0: : m_cRef(0), michael@0: mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK), michael@0: mEffectsPreferred(DROPEFFECT_NONE), michael@0: mTookOwnRef(false), mWidget(aWidget), mDropTargetHelper(nullptr) michael@0: { michael@0: static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID); michael@0: michael@0: mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW); michael@0: michael@0: /* michael@0: * Create/Get the DragService that we have implemented michael@0: */ michael@0: CallGetService(kCDragServiceCID, &mDragService); michael@0: } michael@0: michael@0: nsNativeDragTarget::~nsNativeDragTarget() michael@0: { michael@0: NS_RELEASE(mDragService); michael@0: michael@0: if (mDropTargetHelper) { michael@0: mDropTargetHelper->Release(); michael@0: mDropTargetHelper = nullptr; michael@0: } michael@0: } michael@0: michael@0: // IUnknown methods - see iunknown.h for documentation michael@0: STDMETHODIMP michael@0: nsNativeDragTarget::QueryInterface(REFIID riid, void** ppv) michael@0: { michael@0: *ppv=nullptr; michael@0: michael@0: if (IID_IUnknown == riid || IID_IDropTarget == riid) michael@0: *ppv=this; michael@0: michael@0: if (nullptr!=*ppv) { michael@0: ((LPUNKNOWN)*ppv)->AddRef(); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: STDMETHODIMP_(ULONG) michael@0: nsNativeDragTarget::AddRef(void) michael@0: { michael@0: ++m_cRef; michael@0: NS_LOG_ADDREF(this, m_cRef, "nsNativeDragTarget", sizeof(*this)); michael@0: return m_cRef; michael@0: } michael@0: michael@0: STDMETHODIMP_(ULONG) nsNativeDragTarget::Release(void) michael@0: { michael@0: --m_cRef; michael@0: NS_LOG_RELEASE(this, m_cRef, "nsNativeDragTarget"); michael@0: if (0 != m_cRef) michael@0: return m_cRef; michael@0: michael@0: delete this; michael@0: return 0; michael@0: } michael@0: michael@0: void michael@0: nsNativeDragTarget::GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect, michael@0: uint32_t * aGeckoAction) michael@0: { michael@0: // If a window is disabled or a modal window is on top of it michael@0: // (which implies it is disabled), then we should not allow dropping. michael@0: if (!mWidget->IsEnabled()) { michael@0: *pdwEffect = DROPEFFECT_NONE; michael@0: *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE; michael@0: return; michael@0: } michael@0: michael@0: // If the user explicitly uses a modifier key, they want the associated action michael@0: // Shift + Control -> LINK, Shift -> MOVE, Ctrl -> COPY michael@0: DWORD desiredEffect = DROPEFFECT_NONE; michael@0: if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) { michael@0: desiredEffect = DROPEFFECT_LINK; michael@0: } else if (grfKeyState & MK_SHIFT) { michael@0: desiredEffect = DROPEFFECT_MOVE; michael@0: } else if (grfKeyState & MK_CONTROL) { michael@0: desiredEffect = DROPEFFECT_COPY; michael@0: } michael@0: michael@0: // Determine the desired effect from what is allowed and preferred. michael@0: if (!(desiredEffect &= mEffectsAllowed)) { michael@0: // No modifier key effect is set which is also allowed, check michael@0: // the preference of the data. michael@0: desiredEffect = mEffectsPreferred & mEffectsAllowed; michael@0: if (!desiredEffect) { michael@0: // No preference is set, so just fall back to the allowed effect itself michael@0: desiredEffect = mEffectsAllowed; michael@0: } michael@0: } michael@0: michael@0: // Otherwise we should specify the first available effect michael@0: // from MOVE, COPY, or LINK. michael@0: if (desiredEffect & DROPEFFECT_MOVE) { michael@0: *pdwEffect = DROPEFFECT_MOVE; michael@0: *aGeckoAction = nsIDragService::DRAGDROP_ACTION_MOVE; michael@0: } else if (desiredEffect & DROPEFFECT_COPY) { michael@0: *pdwEffect = DROPEFFECT_COPY; michael@0: *aGeckoAction = nsIDragService::DRAGDROP_ACTION_COPY; michael@0: } else if (desiredEffect & DROPEFFECT_LINK) { michael@0: *pdwEffect = DROPEFFECT_LINK; michael@0: *aGeckoAction = nsIDragService::DRAGDROP_ACTION_LINK; michael@0: } else { michael@0: *pdwEffect = DROPEFFECT_NONE; michael@0: *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE; michael@0: } michael@0: } michael@0: michael@0: inline michael@0: bool michael@0: IsKeyDown(char key) michael@0: { michael@0: return GetKeyState(key) < 0; michael@0: } michael@0: michael@0: void michael@0: nsNativeDragTarget::DispatchDragDropEvent(uint32_t aEventType, POINTL aPT) michael@0: { michael@0: nsEventStatus status; michael@0: WidgetDragEvent event(true, aEventType, mWidget); michael@0: michael@0: nsWindow * win = static_cast(mWidget); michael@0: win->InitEvent(event); michael@0: POINT cpos; michael@0: michael@0: cpos.x = aPT.x; michael@0: cpos.y = aPT.y; michael@0: michael@0: if (mHWnd != nullptr) { michael@0: ::ScreenToClient(mHWnd, &cpos); michael@0: event.refPoint.x = cpos.x; michael@0: event.refPoint.y = cpos.y; michael@0: } else { michael@0: event.refPoint.x = 0; michael@0: event.refPoint.y = 0; michael@0: } michael@0: michael@0: ModifierKeyState modifierKeyState; michael@0: modifierKeyState.InitInputEvent(event); michael@0: michael@0: event.inputSource = static_cast(mDragService)->GetInputSource(); michael@0: michael@0: mWidget->DispatchEvent(&event, status); michael@0: } michael@0: michael@0: void michael@0: nsNativeDragTarget::ProcessDrag(uint32_t aEventType, michael@0: DWORD grfKeyState, michael@0: POINTL ptl, michael@0: DWORD* pdwEffect) michael@0: { michael@0: // Before dispatching the event make sure we have the correct drop action set michael@0: uint32_t geckoAction; michael@0: GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction); michael@0: michael@0: // Set the current action into the Gecko specific type michael@0: nsCOMPtr currSession; michael@0: mDragService->GetCurrentSession(getter_AddRefs(currSession)); michael@0: if (!currSession) { michael@0: return; michael@0: } michael@0: michael@0: currSession->SetDragAction(geckoAction); michael@0: michael@0: // Dispatch the event into Gecko michael@0: DispatchDragDropEvent(aEventType, ptl); michael@0: michael@0: if (aEventType != NS_DRAGDROP_DROP) { michael@0: // Get the cached drag effect from the drag service, the data member should michael@0: // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on michael@0: // drags. michael@0: bool canDrop; michael@0: currSession->GetCanDrop(&canDrop); michael@0: if (!canDrop) { michael@0: *pdwEffect = DROPEFFECT_NONE; michael@0: } michael@0: } michael@0: michael@0: // Clear the cached value michael@0: currSession->SetCanDrop(false); michael@0: } michael@0: michael@0: // IDropTarget methods michael@0: STDMETHODIMP michael@0: nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource, michael@0: DWORD grfKeyState, michael@0: POINTL ptl, michael@0: DWORD* pdwEffect) michael@0: { michael@0: if (!mDragService) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: mEffectsAllowed = *pdwEffect; michael@0: AddLinkSupportIfCanBeGenerated(pIDataSource); michael@0: michael@0: // Drag and drop image helper michael@0: if (GetDropTargetHelper()) { michael@0: POINT pt = { ptl.x, ptl.y }; michael@0: GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect); michael@0: } michael@0: michael@0: // save a ref to this, in case the window is destroyed underneath us michael@0: NS_ASSERTION(!mTookOwnRef, "own ref already taken!"); michael@0: this->AddRef(); michael@0: mTookOwnRef = true; michael@0: michael@0: // tell the drag service about this drag (it may have come from an michael@0: // outside app). michael@0: mDragService->StartDragSession(); michael@0: michael@0: void* tempOutData = nullptr; michael@0: uint32_t tempDataLen = 0; michael@0: nsresult loadResult = nsClipboard::GetNativeDataOffClipboard( michael@0: pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), nullptr, &tempOutData, &tempDataLen); michael@0: if (NS_SUCCEEDED(loadResult) && tempOutData) { michael@0: mEffectsPreferred = *((DWORD*)tempOutData); michael@0: nsMemory::Free(tempOutData); michael@0: } else { michael@0: // We have no preference if we can't obtain it michael@0: mEffectsPreferred = DROPEFFECT_NONE; michael@0: } michael@0: michael@0: // Set the native data object into drag service michael@0: // michael@0: // This cast is ok because in the constructor we created a michael@0: // the actual implementation we wanted, so we know this is michael@0: // a nsDragService. It should be a private interface, though. michael@0: nsDragService * winDragService = michael@0: static_cast(mDragService); michael@0: winDragService->SetIDataObject(pIDataSource); michael@0: michael@0: // Now process the native drag state and then dispatch the event michael@0: ProcessDrag(NS_DRAGDROP_ENTER, grfKeyState, ptl, pdwEffect); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: void michael@0: nsNativeDragTarget::AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource) michael@0: { michael@0: // If we don't have a link effect, but we can generate one, fix the michael@0: // drop effect to include it. michael@0: if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) { michael@0: if (S_OK == ::OleQueryLinkFromData(aIDataSource)) { michael@0: mEffectsAllowed |= DROPEFFECT_LINK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsNativeDragTarget::DragOver(DWORD grfKeyState, michael@0: POINTL ptl, michael@0: LPDWORD pdwEffect) michael@0: { michael@0: if (!mDragService) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // If a LINK effect could be generated previously from a DragEnter(), michael@0: // then we should include it as an allowed effect. michael@0: mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK); michael@0: michael@0: nsCOMPtr currentDragSession; michael@0: mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); michael@0: if (!currentDragSession) { michael@0: return S_OK; // Drag was canceled. michael@0: } michael@0: michael@0: // without the AddRef() |this| can get destroyed in an event handler michael@0: this->AddRef(); michael@0: michael@0: // Drag and drop image helper michael@0: if (GetDropTargetHelper()) { michael@0: POINT pt = { ptl.x, ptl.y }; michael@0: GetDropTargetHelper()->DragOver(&pt, *pdwEffect); michael@0: } michael@0: michael@0: mDragService->FireDragEventAtSource(NS_DRAGDROP_DRAG); michael@0: // Now process the native drag state and then dispatch the event michael@0: ProcessDrag(NS_DRAGDROP_OVER, grfKeyState, ptl, pdwEffect); michael@0: michael@0: this->Release(); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsNativeDragTarget::DragLeave() michael@0: { michael@0: if (!mDragService) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Drag and drop image helper michael@0: if (GetDropTargetHelper()) { michael@0: GetDropTargetHelper()->DragLeave(); michael@0: } michael@0: michael@0: // dispatch the event into Gecko michael@0: DispatchDragDropEvent(NS_DRAGDROP_EXIT, gDragLastPoint); michael@0: michael@0: nsCOMPtr currentDragSession; michael@0: mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); michael@0: michael@0: if (currentDragSession) { michael@0: nsCOMPtr sourceNode; michael@0: currentDragSession->GetSourceNode(getter_AddRefs(sourceNode)); michael@0: michael@0: if (!sourceNode) { michael@0: // We're leaving a window while doing a drag that was michael@0: // initiated in a different app. End the drag session, since michael@0: // we're done with it for now (until the user drags back into michael@0: // mozilla). michael@0: mDragService->EndDragSession(false); michael@0: } michael@0: } michael@0: michael@0: // release the ref that was taken in DragEnter michael@0: NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); michael@0: if (mTookOwnRef) { michael@0: this->Release(); michael@0: mTookOwnRef = false; michael@0: } michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: void michael@0: nsNativeDragTarget::DragCancel() michael@0: { michael@0: // Cancel the drag session if we did DragEnter. michael@0: if (mTookOwnRef) { michael@0: if (GetDropTargetHelper()) { michael@0: GetDropTargetHelper()->DragLeave(); michael@0: } michael@0: if (mDragService) { michael@0: mDragService->EndDragSession(false); michael@0: } michael@0: this->Release(); // matching the AddRef in DragEnter michael@0: mTookOwnRef = false; michael@0: } michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: nsNativeDragTarget::Drop(LPDATAOBJECT pData, michael@0: DWORD grfKeyState, michael@0: POINTL aPT, michael@0: LPDWORD pdwEffect) michael@0: { michael@0: if (!mDragService) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: mEffectsAllowed = *pdwEffect; michael@0: AddLinkSupportIfCanBeGenerated(pData); michael@0: michael@0: // Drag and drop image helper michael@0: if (GetDropTargetHelper()) { michael@0: POINT pt = { aPT.x, aPT.y }; michael@0: GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect); michael@0: } michael@0: michael@0: // Set the native data object into the drag service michael@0: // michael@0: // This cast is ok because in the constructor we created a michael@0: // the actual implementation we wanted, so we know this is michael@0: // a nsDragService (but it should still be a private interface) michael@0: nsDragService* winDragService = static_cast(mDragService); michael@0: winDragService->SetIDataObject(pData); michael@0: michael@0: // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects. michael@0: // We use strong refs to prevent it from destroying these: michael@0: nsRefPtr kungFuDeathGrip = this; michael@0: nsCOMPtr serv = mDragService; michael@0: michael@0: // Now process the native drag state and then dispatch the event michael@0: ProcessDrag(NS_DRAGDROP_DROP, grfKeyState, aPT, pdwEffect); michael@0: michael@0: nsCOMPtr currentDragSession; michael@0: serv->GetCurrentSession(getter_AddRefs(currentDragSession)); michael@0: if (!currentDragSession) { michael@0: return S_OK; // DragCancel() was called. michael@0: } michael@0: michael@0: // Let the win drag service know whether this session experienced michael@0: // a drop event within the application. Drop will not oocur if the michael@0: // drop landed outside the app. (used in tab tear off, bug 455884) michael@0: winDragService->SetDroppedLocal(); michael@0: michael@0: // tell the drag service we're done with the session michael@0: // Use GetMessagePos to get the position of the mouse at the last message michael@0: // seen by the event loop. (Bug 489729) michael@0: DWORD pos = ::GetMessagePos(); michael@0: POINT cpos; michael@0: cpos.x = GET_X_LPARAM(pos); michael@0: cpos.y = GET_Y_LPARAM(pos); michael@0: winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y)); michael@0: serv->EndDragSession(true); michael@0: michael@0: // release the ref that was taken in DragEnter michael@0: NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); michael@0: if (mTookOwnRef) { michael@0: this->Release(); michael@0: mTookOwnRef = false; michael@0: } michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: /** michael@0: * By lazy loading mDropTargetHelper we save 50-70ms of startup time michael@0: * which is ~5% of startup time. michael@0: */ michael@0: IDropTargetHelper* michael@0: nsNativeDragTarget::GetDropTargetHelper() michael@0: { michael@0: if (!mDropTargetHelper) { michael@0: CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, michael@0: IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper); michael@0: } michael@0: michael@0: return mDropTargetHelper; michael@0: }