|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include <stdio.h> |
|
7 #include "nsIDragService.h" |
|
8 #include "nsWidgetsCID.h" |
|
9 #include "nsNativeDragTarget.h" |
|
10 #include "nsDragService.h" |
|
11 #include "nsIServiceManager.h" |
|
12 #include "nsIDOMNode.h" |
|
13 #include "nsCOMPtr.h" |
|
14 |
|
15 #include "nsIWidget.h" |
|
16 #include "nsWindow.h" |
|
17 #include "nsClipboard.h" |
|
18 #include "KeyboardLayout.h" |
|
19 |
|
20 #include "mozilla/MouseEvents.h" |
|
21 |
|
22 using namespace mozilla; |
|
23 using namespace mozilla::widget; |
|
24 |
|
25 /* Define Interface IDs */ |
|
26 static NS_DEFINE_IID(kIDragServiceIID, NS_IDRAGSERVICE_IID); |
|
27 |
|
28 // This is cached for Leave notification |
|
29 static POINTL gDragLastPoint; |
|
30 |
|
31 /* |
|
32 * class nsNativeDragTarget |
|
33 */ |
|
34 nsNativeDragTarget::nsNativeDragTarget(nsIWidget * aWidget) |
|
35 : m_cRef(0), |
|
36 mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK), |
|
37 mEffectsPreferred(DROPEFFECT_NONE), |
|
38 mTookOwnRef(false), mWidget(aWidget), mDropTargetHelper(nullptr) |
|
39 { |
|
40 static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID); |
|
41 |
|
42 mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW); |
|
43 |
|
44 /* |
|
45 * Create/Get the DragService that we have implemented |
|
46 */ |
|
47 CallGetService(kCDragServiceCID, &mDragService); |
|
48 } |
|
49 |
|
50 nsNativeDragTarget::~nsNativeDragTarget() |
|
51 { |
|
52 NS_RELEASE(mDragService); |
|
53 |
|
54 if (mDropTargetHelper) { |
|
55 mDropTargetHelper->Release(); |
|
56 mDropTargetHelper = nullptr; |
|
57 } |
|
58 } |
|
59 |
|
60 // IUnknown methods - see iunknown.h for documentation |
|
61 STDMETHODIMP |
|
62 nsNativeDragTarget::QueryInterface(REFIID riid, void** ppv) |
|
63 { |
|
64 *ppv=nullptr; |
|
65 |
|
66 if (IID_IUnknown == riid || IID_IDropTarget == riid) |
|
67 *ppv=this; |
|
68 |
|
69 if (nullptr!=*ppv) { |
|
70 ((LPUNKNOWN)*ppv)->AddRef(); |
|
71 return S_OK; |
|
72 } |
|
73 |
|
74 return E_NOINTERFACE; |
|
75 } |
|
76 |
|
77 STDMETHODIMP_(ULONG) |
|
78 nsNativeDragTarget::AddRef(void) |
|
79 { |
|
80 ++m_cRef; |
|
81 NS_LOG_ADDREF(this, m_cRef, "nsNativeDragTarget", sizeof(*this)); |
|
82 return m_cRef; |
|
83 } |
|
84 |
|
85 STDMETHODIMP_(ULONG) nsNativeDragTarget::Release(void) |
|
86 { |
|
87 --m_cRef; |
|
88 NS_LOG_RELEASE(this, m_cRef, "nsNativeDragTarget"); |
|
89 if (0 != m_cRef) |
|
90 return m_cRef; |
|
91 |
|
92 delete this; |
|
93 return 0; |
|
94 } |
|
95 |
|
96 void |
|
97 nsNativeDragTarget::GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect, |
|
98 uint32_t * aGeckoAction) |
|
99 { |
|
100 // If a window is disabled or a modal window is on top of it |
|
101 // (which implies it is disabled), then we should not allow dropping. |
|
102 if (!mWidget->IsEnabled()) { |
|
103 *pdwEffect = DROPEFFECT_NONE; |
|
104 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE; |
|
105 return; |
|
106 } |
|
107 |
|
108 // If the user explicitly uses a modifier key, they want the associated action |
|
109 // Shift + Control -> LINK, Shift -> MOVE, Ctrl -> COPY |
|
110 DWORD desiredEffect = DROPEFFECT_NONE; |
|
111 if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) { |
|
112 desiredEffect = DROPEFFECT_LINK; |
|
113 } else if (grfKeyState & MK_SHIFT) { |
|
114 desiredEffect = DROPEFFECT_MOVE; |
|
115 } else if (grfKeyState & MK_CONTROL) { |
|
116 desiredEffect = DROPEFFECT_COPY; |
|
117 } |
|
118 |
|
119 // Determine the desired effect from what is allowed and preferred. |
|
120 if (!(desiredEffect &= mEffectsAllowed)) { |
|
121 // No modifier key effect is set which is also allowed, check |
|
122 // the preference of the data. |
|
123 desiredEffect = mEffectsPreferred & mEffectsAllowed; |
|
124 if (!desiredEffect) { |
|
125 // No preference is set, so just fall back to the allowed effect itself |
|
126 desiredEffect = mEffectsAllowed; |
|
127 } |
|
128 } |
|
129 |
|
130 // Otherwise we should specify the first available effect |
|
131 // from MOVE, COPY, or LINK. |
|
132 if (desiredEffect & DROPEFFECT_MOVE) { |
|
133 *pdwEffect = DROPEFFECT_MOVE; |
|
134 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_MOVE; |
|
135 } else if (desiredEffect & DROPEFFECT_COPY) { |
|
136 *pdwEffect = DROPEFFECT_COPY; |
|
137 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_COPY; |
|
138 } else if (desiredEffect & DROPEFFECT_LINK) { |
|
139 *pdwEffect = DROPEFFECT_LINK; |
|
140 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_LINK; |
|
141 } else { |
|
142 *pdwEffect = DROPEFFECT_NONE; |
|
143 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE; |
|
144 } |
|
145 } |
|
146 |
|
147 inline |
|
148 bool |
|
149 IsKeyDown(char key) |
|
150 { |
|
151 return GetKeyState(key) < 0; |
|
152 } |
|
153 |
|
154 void |
|
155 nsNativeDragTarget::DispatchDragDropEvent(uint32_t aEventType, POINTL aPT) |
|
156 { |
|
157 nsEventStatus status; |
|
158 WidgetDragEvent event(true, aEventType, mWidget); |
|
159 |
|
160 nsWindow * win = static_cast<nsWindow *>(mWidget); |
|
161 win->InitEvent(event); |
|
162 POINT cpos; |
|
163 |
|
164 cpos.x = aPT.x; |
|
165 cpos.y = aPT.y; |
|
166 |
|
167 if (mHWnd != nullptr) { |
|
168 ::ScreenToClient(mHWnd, &cpos); |
|
169 event.refPoint.x = cpos.x; |
|
170 event.refPoint.y = cpos.y; |
|
171 } else { |
|
172 event.refPoint.x = 0; |
|
173 event.refPoint.y = 0; |
|
174 } |
|
175 |
|
176 ModifierKeyState modifierKeyState; |
|
177 modifierKeyState.InitInputEvent(event); |
|
178 |
|
179 event.inputSource = static_cast<nsBaseDragService*>(mDragService)->GetInputSource(); |
|
180 |
|
181 mWidget->DispatchEvent(&event, status); |
|
182 } |
|
183 |
|
184 void |
|
185 nsNativeDragTarget::ProcessDrag(uint32_t aEventType, |
|
186 DWORD grfKeyState, |
|
187 POINTL ptl, |
|
188 DWORD* pdwEffect) |
|
189 { |
|
190 // Before dispatching the event make sure we have the correct drop action set |
|
191 uint32_t geckoAction; |
|
192 GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction); |
|
193 |
|
194 // Set the current action into the Gecko specific type |
|
195 nsCOMPtr<nsIDragSession> currSession; |
|
196 mDragService->GetCurrentSession(getter_AddRefs(currSession)); |
|
197 if (!currSession) { |
|
198 return; |
|
199 } |
|
200 |
|
201 currSession->SetDragAction(geckoAction); |
|
202 |
|
203 // Dispatch the event into Gecko |
|
204 DispatchDragDropEvent(aEventType, ptl); |
|
205 |
|
206 if (aEventType != NS_DRAGDROP_DROP) { |
|
207 // Get the cached drag effect from the drag service, the data member should |
|
208 // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on |
|
209 // drags. |
|
210 bool canDrop; |
|
211 currSession->GetCanDrop(&canDrop); |
|
212 if (!canDrop) { |
|
213 *pdwEffect = DROPEFFECT_NONE; |
|
214 } |
|
215 } |
|
216 |
|
217 // Clear the cached value |
|
218 currSession->SetCanDrop(false); |
|
219 } |
|
220 |
|
221 // IDropTarget methods |
|
222 STDMETHODIMP |
|
223 nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource, |
|
224 DWORD grfKeyState, |
|
225 POINTL ptl, |
|
226 DWORD* pdwEffect) |
|
227 { |
|
228 if (!mDragService) { |
|
229 return E_FAIL; |
|
230 } |
|
231 |
|
232 mEffectsAllowed = *pdwEffect; |
|
233 AddLinkSupportIfCanBeGenerated(pIDataSource); |
|
234 |
|
235 // Drag and drop image helper |
|
236 if (GetDropTargetHelper()) { |
|
237 POINT pt = { ptl.x, ptl.y }; |
|
238 GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect); |
|
239 } |
|
240 |
|
241 // save a ref to this, in case the window is destroyed underneath us |
|
242 NS_ASSERTION(!mTookOwnRef, "own ref already taken!"); |
|
243 this->AddRef(); |
|
244 mTookOwnRef = true; |
|
245 |
|
246 // tell the drag service about this drag (it may have come from an |
|
247 // outside app). |
|
248 mDragService->StartDragSession(); |
|
249 |
|
250 void* tempOutData = nullptr; |
|
251 uint32_t tempDataLen = 0; |
|
252 nsresult loadResult = nsClipboard::GetNativeDataOffClipboard( |
|
253 pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), nullptr, &tempOutData, &tempDataLen); |
|
254 if (NS_SUCCEEDED(loadResult) && tempOutData) { |
|
255 mEffectsPreferred = *((DWORD*)tempOutData); |
|
256 nsMemory::Free(tempOutData); |
|
257 } else { |
|
258 // We have no preference if we can't obtain it |
|
259 mEffectsPreferred = DROPEFFECT_NONE; |
|
260 } |
|
261 |
|
262 // Set the native data object into drag service |
|
263 // |
|
264 // This cast is ok because in the constructor we created a |
|
265 // the actual implementation we wanted, so we know this is |
|
266 // a nsDragService. It should be a private interface, though. |
|
267 nsDragService * winDragService = |
|
268 static_cast<nsDragService *>(mDragService); |
|
269 winDragService->SetIDataObject(pIDataSource); |
|
270 |
|
271 // Now process the native drag state and then dispatch the event |
|
272 ProcessDrag(NS_DRAGDROP_ENTER, grfKeyState, ptl, pdwEffect); |
|
273 |
|
274 return S_OK; |
|
275 } |
|
276 |
|
277 void |
|
278 nsNativeDragTarget::AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource) |
|
279 { |
|
280 // If we don't have a link effect, but we can generate one, fix the |
|
281 // drop effect to include it. |
|
282 if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) { |
|
283 if (S_OK == ::OleQueryLinkFromData(aIDataSource)) { |
|
284 mEffectsAllowed |= DROPEFFECT_LINK; |
|
285 } |
|
286 } |
|
287 } |
|
288 |
|
289 STDMETHODIMP |
|
290 nsNativeDragTarget::DragOver(DWORD grfKeyState, |
|
291 POINTL ptl, |
|
292 LPDWORD pdwEffect) |
|
293 { |
|
294 if (!mDragService) { |
|
295 return E_FAIL; |
|
296 } |
|
297 |
|
298 // If a LINK effect could be generated previously from a DragEnter(), |
|
299 // then we should include it as an allowed effect. |
|
300 mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK); |
|
301 |
|
302 nsCOMPtr<nsIDragSession> currentDragSession; |
|
303 mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); |
|
304 if (!currentDragSession) { |
|
305 return S_OK; // Drag was canceled. |
|
306 } |
|
307 |
|
308 // without the AddRef() |this| can get destroyed in an event handler |
|
309 this->AddRef(); |
|
310 |
|
311 // Drag and drop image helper |
|
312 if (GetDropTargetHelper()) { |
|
313 POINT pt = { ptl.x, ptl.y }; |
|
314 GetDropTargetHelper()->DragOver(&pt, *pdwEffect); |
|
315 } |
|
316 |
|
317 mDragService->FireDragEventAtSource(NS_DRAGDROP_DRAG); |
|
318 // Now process the native drag state and then dispatch the event |
|
319 ProcessDrag(NS_DRAGDROP_OVER, grfKeyState, ptl, pdwEffect); |
|
320 |
|
321 this->Release(); |
|
322 |
|
323 return S_OK; |
|
324 } |
|
325 |
|
326 STDMETHODIMP |
|
327 nsNativeDragTarget::DragLeave() |
|
328 { |
|
329 if (!mDragService) { |
|
330 return E_FAIL; |
|
331 } |
|
332 |
|
333 // Drag and drop image helper |
|
334 if (GetDropTargetHelper()) { |
|
335 GetDropTargetHelper()->DragLeave(); |
|
336 } |
|
337 |
|
338 // dispatch the event into Gecko |
|
339 DispatchDragDropEvent(NS_DRAGDROP_EXIT, gDragLastPoint); |
|
340 |
|
341 nsCOMPtr<nsIDragSession> currentDragSession; |
|
342 mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); |
|
343 |
|
344 if (currentDragSession) { |
|
345 nsCOMPtr<nsIDOMNode> sourceNode; |
|
346 currentDragSession->GetSourceNode(getter_AddRefs(sourceNode)); |
|
347 |
|
348 if (!sourceNode) { |
|
349 // We're leaving a window while doing a drag that was |
|
350 // initiated in a different app. End the drag session, since |
|
351 // we're done with it for now (until the user drags back into |
|
352 // mozilla). |
|
353 mDragService->EndDragSession(false); |
|
354 } |
|
355 } |
|
356 |
|
357 // release the ref that was taken in DragEnter |
|
358 NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); |
|
359 if (mTookOwnRef) { |
|
360 this->Release(); |
|
361 mTookOwnRef = false; |
|
362 } |
|
363 |
|
364 return S_OK; |
|
365 } |
|
366 |
|
367 void |
|
368 nsNativeDragTarget::DragCancel() |
|
369 { |
|
370 // Cancel the drag session if we did DragEnter. |
|
371 if (mTookOwnRef) { |
|
372 if (GetDropTargetHelper()) { |
|
373 GetDropTargetHelper()->DragLeave(); |
|
374 } |
|
375 if (mDragService) { |
|
376 mDragService->EndDragSession(false); |
|
377 } |
|
378 this->Release(); // matching the AddRef in DragEnter |
|
379 mTookOwnRef = false; |
|
380 } |
|
381 } |
|
382 |
|
383 STDMETHODIMP |
|
384 nsNativeDragTarget::Drop(LPDATAOBJECT pData, |
|
385 DWORD grfKeyState, |
|
386 POINTL aPT, |
|
387 LPDWORD pdwEffect) |
|
388 { |
|
389 if (!mDragService) { |
|
390 return E_FAIL; |
|
391 } |
|
392 |
|
393 mEffectsAllowed = *pdwEffect; |
|
394 AddLinkSupportIfCanBeGenerated(pData); |
|
395 |
|
396 // Drag and drop image helper |
|
397 if (GetDropTargetHelper()) { |
|
398 POINT pt = { aPT.x, aPT.y }; |
|
399 GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect); |
|
400 } |
|
401 |
|
402 // Set the native data object into the drag service |
|
403 // |
|
404 // This cast is ok because in the constructor we created a |
|
405 // the actual implementation we wanted, so we know this is |
|
406 // a nsDragService (but it should still be a private interface) |
|
407 nsDragService* winDragService = static_cast<nsDragService*>(mDragService); |
|
408 winDragService->SetIDataObject(pData); |
|
409 |
|
410 // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects. |
|
411 // We use strong refs to prevent it from destroying these: |
|
412 nsRefPtr<nsNativeDragTarget> kungFuDeathGrip = this; |
|
413 nsCOMPtr<nsIDragService> serv = mDragService; |
|
414 |
|
415 // Now process the native drag state and then dispatch the event |
|
416 ProcessDrag(NS_DRAGDROP_DROP, grfKeyState, aPT, pdwEffect); |
|
417 |
|
418 nsCOMPtr<nsIDragSession> currentDragSession; |
|
419 serv->GetCurrentSession(getter_AddRefs(currentDragSession)); |
|
420 if (!currentDragSession) { |
|
421 return S_OK; // DragCancel() was called. |
|
422 } |
|
423 |
|
424 // Let the win drag service know whether this session experienced |
|
425 // a drop event within the application. Drop will not oocur if the |
|
426 // drop landed outside the app. (used in tab tear off, bug 455884) |
|
427 winDragService->SetDroppedLocal(); |
|
428 |
|
429 // tell the drag service we're done with the session |
|
430 // Use GetMessagePos to get the position of the mouse at the last message |
|
431 // seen by the event loop. (Bug 489729) |
|
432 DWORD pos = ::GetMessagePos(); |
|
433 POINT cpos; |
|
434 cpos.x = GET_X_LPARAM(pos); |
|
435 cpos.y = GET_Y_LPARAM(pos); |
|
436 winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y)); |
|
437 serv->EndDragSession(true); |
|
438 |
|
439 // release the ref that was taken in DragEnter |
|
440 NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); |
|
441 if (mTookOwnRef) { |
|
442 this->Release(); |
|
443 mTookOwnRef = false; |
|
444 } |
|
445 |
|
446 return S_OK; |
|
447 } |
|
448 |
|
449 /** |
|
450 * By lazy loading mDropTargetHelper we save 50-70ms of startup time |
|
451 * which is ~5% of startup time. |
|
452 */ |
|
453 IDropTargetHelper* |
|
454 nsNativeDragTarget::GetDropTargetHelper() |
|
455 { |
|
456 if (!mDropTargetHelper) { |
|
457 CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, |
|
458 IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper); |
|
459 } |
|
460 |
|
461 return mDropTargetHelper; |
|
462 } |