Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsdrag.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <QtCore/qt_windows.h>
6#include "qwindowsdrag.h"
9#if QT_CONFIG(clipboard)
10# include "qwindowsclipboard.h"
11#endif
14#include "qwindowswindow.h"
16#include "qwindowscursor.h"
18
19#include <QtGui/qevent.h>
20#include <QtGui/qpixmap.h>
21#include <QtGui/qpainter.h>
22#include <QtGui/qrasterwindow.h>
23#include <QtGui/qguiapplication.h>
24#include <qpa/qwindowsysteminterface_p.h>
25#include <QtGui/private/qdnd_p.h>
26#include <QtGui/private/qguiapplication_p.h>
27#include <QtGui/private/qhighdpiscaling_p.h>
28
29#include <QtCore/qdebug.h>
30#include <QtCore/qbuffer.h>
31#include <QtCore/qpoint.h>
32#include <QtCore/qpointer.h>
33#include <QtCore/private/qcomobject_p.h>
34
35#include <shlobj.h>
36
38
39/*!
40 \class QWindowsDragCursorWindow
41 \brief A toplevel window showing the drag icon in case of touch drag.
42
43 \sa QWindowsOleDropSource
44 \internal
45*/
46
48{
49public:
50 explicit QWindowsDragCursorWindow(QWindow *parent = nullptr);
51
52 void setPixmap(const QPixmap &p);
53
54protected:
55 void paintEvent(QPaintEvent *) override
56 {
57 QPainter painter(this);
58 painter.drawPixmap(0, 0, m_pixmap);
59 }
60
61private:
62 QPixmap m_pixmap;
63};
64
67{
68 QSurfaceFormat windowFormat = format();
69 windowFormat.setAlphaBufferSize(8);
70 setFormat(windowFormat);
71 setObjectName(QStringLiteral("QWindowsDragCursorWindow"));
72 setFlags(Qt::Popup | Qt::NoDropShadowWindowHint
73 | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
74 | Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput);
75}
76
77void QWindowsDragCursorWindow::setPixmap(const QPixmap &p)
78{
79 if (p.cacheKey() == m_pixmap.cacheKey())
80 return;
81 const QSize oldSize = m_pixmap.size();
82 QSize newSize = p.size();
83 qCDebug(lcQpaMime) << __FUNCTION__ << p.cacheKey() << newSize;
84 m_pixmap = p;
85 if (oldSize != newSize) {
86 const qreal pixDevicePixelRatio = p.devicePixelRatio();
87 if (pixDevicePixelRatio > 1.0 && qFuzzyCompare(pixDevicePixelRatio, devicePixelRatio()))
88 newSize /= qRound(pixDevicePixelRatio);
89 resize(newSize);
90 }
91 if (isVisible())
92 update();
93}
94
95/*!
96 \class QWindowsDropMimeData
97 \brief Special mime data class for data retrieval from Drag operations.
98
99 Implementation of QWindowsInternalMimeDataBase which retrieves the
100 current drop data object from QWindowsDrag.
101
102 \sa QWindowsDrag
103 \internal
104*/
105
107{
108 return QWindowsDrag::instance()->dropDataObject();
109}
110
111static inline Qt::DropActions translateToQDragDropActions(DWORD pdwEffects)
112{
113 Qt::DropActions actions = Qt::IgnoreAction;
114 if (pdwEffects & DROPEFFECT_LINK)
115 actions |= Qt::LinkAction;
116 if (pdwEffects & DROPEFFECT_COPY)
117 actions |= Qt::CopyAction;
118 if (pdwEffects & DROPEFFECT_MOVE)
119 actions |= Qt::MoveAction;
120 return actions;
121}
122
123static inline Qt::DropAction translateToQDragDropAction(DWORD pdwEffect)
124{
125 if (pdwEffect & DROPEFFECT_LINK)
126 return Qt::LinkAction;
127 if (pdwEffect & DROPEFFECT_COPY)
128 return Qt::CopyAction;
129 if (pdwEffect & DROPEFFECT_MOVE)
130 return Qt::MoveAction;
131 return Qt::IgnoreAction;
132}
133
134static inline DWORD translateToWinDragEffects(Qt::DropActions action)
135{
136 DWORD effect = DROPEFFECT_NONE;
137 if (action & Qt::LinkAction)
138 effect |= DROPEFFECT_LINK;
139 if (action & Qt::CopyAction)
140 effect |= DROPEFFECT_COPY;
141 if (action & Qt::MoveAction)
142 effect |= DROPEFFECT_MOVE;
143 return effect;
144}
145
146static inline Qt::KeyboardModifiers toQtKeyboardModifiers(DWORD keyState)
147{
148 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
149
150 if (keyState & MK_SHIFT)
151 modifiers |= Qt::ShiftModifier;
152 if (keyState & MK_CONTROL)
153 modifiers |= Qt::ControlModifier;
154 if (keyState & MK_ALT)
155 modifiers |= Qt::AltModifier;
156
157 return modifiers;
158}
159
160static Qt::KeyboardModifiers lastModifiers = Qt::NoModifier;
161static Qt::MouseButtons lastButtons = Qt::NoButton;
162
163/*!
164 \class QWindowsOleDropSource
165 \brief Implementation of IDropSource
166
167 Used for drag operations.
168
169 \sa QWindowsDrag
170 \internal
171*/
172
174{
175public:
176 enum Mode {
178 TouchDrag // Mouse cursor suppressed, use window as cursor.
179 };
180
183
185
186 // IDropSource methods
187 STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState) noexcept override;
188 STDMETHOD(GiveFeedback)(DWORD dwEffect) noexcept override;
189
190private:
191 struct CursorEntry {
192 CursorEntry() : cacheKey(0) {}
193 CursorEntry(const QPixmap &p, qint64 cK, const CursorHandlePtr &c, const QPoint &h) :
194 pixmap(p), cacheKey(cK), cursor(c), hotSpot(h) {}
195
196 QPixmap pixmap;
197 qint64 cacheKey; // Cache key of cursor
198 CursorHandlePtr cursor;
199 QPoint hotSpot;
200 };
201
202 typedef QMap<Qt::DropAction, CursorEntry> ActionCursorMap;
203
204 Mode m_mode;
205 QWindowsDrag *m_drag;
206 QPointer<QWindow> m_windowUnderMouse;
207 Qt::MouseButtons m_currentButtons;
208 ActionCursorMap m_cursors;
209 QWindowsDragCursorWindow *m_touchDragWindow;
210
211#ifndef QT_NO_DEBUG_STREAM
212 friend QDebug operator<<(QDebug, const QWindowsOleDropSource::CursorEntry &);
213#endif
214};
215
218 , m_drag(drag)
221 , m_touchDragWindow(nullptr)
222{
223 qCDebug(lcQpaMime) << __FUNCTION__ << m_mode;
224}
225
227{
228 m_cursors.clear();
229 delete m_touchDragWindow;
230 qCDebug(lcQpaMime) << __FUNCTION__;
231}
232
233#ifndef QT_NO_DEBUG_STREAM
234QDebug operator<<(QDebug d, const QWindowsOleDropSource::CursorEntry &e)
235{
236 d << "CursorEntry:" << e.pixmap.size() << '#' << e.cacheKey
237 << "HCURSOR" << e.cursor->handle() << "hotspot:" << e.hotSpot;
238 return d;
239}
240#endif // !QT_NO_DEBUG_STREAM
241
242/*!
243 \brief Blend custom pixmap with cursors.
244*/
245
247{
248 const QDrag *drag = m_drag->currentDrag();
249 const QPixmap pixmap = drag->pixmap();
250 const bool hasPixmap = !pixmap.isNull();
251
252 // Find screen for drag. Could be obtained from QDrag::source(), but that might be a QWidget.
254 if (!platformScreen) {
255 if (const QScreen *primaryScreen = QGuiApplication::primaryScreen())
256 platformScreen = primaryScreen->handle();
257 }
258 QPlatformCursor *platformCursor = nullptr;
259 if (platformScreen)
260 platformCursor = platformScreen->cursor();
261
262 if (GetSystemMetrics (SM_REMOTESESSION) != 0) {
263 /* Workaround for RDP issues with large cursors.
264 * Touch drag window seems to work just fine...
265 * 96 pixel is a 'large' mouse cursor, according to RDP spec */
266 const int rdpLargeCursor = qRound(qreal(96) / QHighDpiScaling::factor(platformScreen));
267 if (pixmap.width() > rdpLargeCursor || pixmap.height() > rdpLargeCursor)
268 m_mode = TouchDrag;
269 }
270
271 qreal pixmapScaleFactor = 1;
272 qreal hotSpotScaleFactor = 1;
273 if (m_mode != TouchDrag) { // Touch drag: pixmap is shown in a separate QWindow, which will be scaled.)
274 hotSpotScaleFactor = QHighDpiScaling::factor(platformScreen);
275 pixmapScaleFactor = hotSpotScaleFactor / pixmap.devicePixelRatio();
276 }
277 QPixmap scaledPixmap = (!hasPixmap || qFuzzyCompare(pixmapScaleFactor, 1.0))
278 ? pixmap
279 : pixmap.scaled((QSizeF(pixmap.size()) * pixmapScaleFactor).toSize(),
280 Qt::KeepAspectRatio, Qt::SmoothTransformation);
281 scaledPixmap.setDevicePixelRatio(1);
282
283 Qt::DropAction actions[] = { Qt::MoveAction, Qt::CopyAction, Qt::LinkAction, Qt::IgnoreAction };
284 int actionCount = int(sizeof(actions) / sizeof(actions[0]));
285 if (!hasPixmap)
286 --actionCount; // No Qt::IgnoreAction unless pixmap
287 const QPoint hotSpot = qFuzzyCompare(hotSpotScaleFactor, 1.0)
288 ? drag->hotSpot()
289 : (QPointF(drag->hotSpot()) * hotSpotScaleFactor).toPoint();
290 for (int cnum = 0; cnum < actionCount; ++cnum) {
291 const Qt::DropAction action = actions[cnum];
292 QPixmap cursorPixmap = drag->dragCursor(action);
293 if (cursorPixmap.isNull() && platformCursor)
294 cursorPixmap = static_cast<QWindowsCursor *>(platformCursor)->dragDefaultCursor(action);
295 const qint64 cacheKey = cursorPixmap.cacheKey();
296 const auto it = m_cursors.find(action);
297 if (it != m_cursors.end() && it.value().cacheKey == cacheKey)
298 continue;
299 if (cursorPixmap.isNull()) {
300 qWarning("%s: Unable to obtain drag cursor for %d.", __FUNCTION__, action);
301 continue;
302 }
303
304 QPoint newHotSpot(0, 0);
305 QPixmap newPixmap = cursorPixmap;
306
307 if (hasPixmap) {
308 const int x1 = qMin(-hotSpot.x(), 0);
309 const int x2 = qMax(scaledPixmap.width() - hotSpot.x(), cursorPixmap.width());
310 const int y1 = qMin(-hotSpot.y(), 0);
311 const int y2 = qMax(scaledPixmap.height() - hotSpot.y(), cursorPixmap.height());
312 QPixmap newCursor(x2 - x1 + 1, y2 - y1 + 1);
313 newCursor.fill(Qt::transparent);
314 QPainter p(&newCursor);
315 const QPoint pmDest = QPoint(qMax(0, -hotSpot.x()), qMax(0, -hotSpot.y()));
316 p.drawPixmap(pmDest, scaledPixmap);
317 p.drawPixmap(qMax(0, hotSpot.x()),qMax(0, hotSpot.y()), cursorPixmap);
318 newPixmap = newCursor;
319 newHotSpot = QPoint(qMax(0, hotSpot.x()), qMax(0, hotSpot.y()));
320 }
321
322 if (const HCURSOR sysCursor = QWindowsCursor::createPixmapCursor(newPixmap, newHotSpot)) {
323 const CursorEntry entry(newPixmap, cacheKey, CursorHandlePtr(new CursorHandle(sysCursor)), newHotSpot);
324 if (it == m_cursors.end())
325 m_cursors.insert(action, entry);
326 else
327 it.value() = entry;
328 }
329 }
330#ifndef QT_NO_DEBUG_OUTPUT
331 if (lcQpaMime().isDebugEnabled())
332 qCDebug(lcQpaMime) << __FUNCTION__ << "pixmap" << pixmap.size() << m_cursors.size() << "cursors:\n" << m_cursors;
333#endif // !QT_NO_DEBUG_OUTPUT
334}
335
336/*!
337 \brief Check for cancel.
338*/
339
340QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
341QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) noexcept
342{
343 // In some rare cases, when a mouse button is released but the mouse is static,
344 // grfKeyState will not be updated with these released buttons until the mouse
345 // is moved. So we use the async key state given by queryMouseButtons() instead.
346 Qt::MouseButtons buttons = QWindowsPointerHandler::queryMouseButtons();
347
348 SCODE result = S_OK;
349 if (fEscapePressed || QWindowsDrag::isCanceled()) {
350 result = DRAGDROP_S_CANCEL;
351 buttons = Qt::NoButton;
352 } else {
353 if (buttons && !m_currentButtons) {
354 m_currentButtons = buttons;
355 } else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation.
356 result = DRAGDROP_S_DROP;
357 }
358 }
359
360 switch (result) {
361 case DRAGDROP_S_DROP:
362 case DRAGDROP_S_CANCEL:
363 if (!m_windowUnderMouse.isNull() && m_mode != TouchDrag && fEscapePressed == FALSE
364 && buttons != lastButtons) {
365 // QTBUG 66447: Synthesize a mouse release to the window under mouse at
366 // start of the DnD operation as Windows does not send any.
367 const QPoint globalPos = QWindowsCursor::mousePosition();
368 const QPoint localPos = m_windowUnderMouse->handle()->mapFromGlobal(globalPos);
369 QWindowSystemInterface::handleMouseEvent(m_windowUnderMouse.data(),
370 QPointF(localPos), QPointF(globalPos),
371 QWindowsPointerHandler::queryMouseButtons(),
372 Qt::LeftButton, QEvent::MouseButtonRelease);
373 }
374 m_currentButtons = Qt::NoButton;
375 break;
376
377 default:
378 QGuiApplication::processEvents();
379 break;
380 }
381
382 if (QWindowsContext::verbose > 1 || result != S_OK) {
383 qCDebug(lcQpaMime) << __FUNCTION__ << "fEscapePressed=" << fEscapePressed
384 << "grfKeyState=" << grfKeyState << "buttons" << m_currentButtons
385 << "returns 0x" << Qt::hex << int(result) << Qt::dec;
386 }
387 return ResultFromScode(result);
388}
389
390/*!
391 \brief Give feedback: Change cursor according to action.
392*/
393
394QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
395QWindowsOleDropSource::GiveFeedback(DWORD dwEffect) noexcept
396{
397 const Qt::DropAction action = translateToQDragDropAction(dwEffect);
398 m_drag->updateAction(action);
399
400 const qint64 currentCacheKey = m_drag->currentDrag()->dragCursor(action).cacheKey();
401 auto it = m_cursors.constFind(action);
402 // If a custom drag cursor is set, check its cache key to detect changes.
403 if (it == m_cursors.constEnd() || (currentCacheKey && currentCacheKey != it.value().cacheKey)) {
404 createCursors();
405 it = m_cursors.constFind(action);
406 }
407
408 if (it != m_cursors.constEnd()) {
409 const CursorEntry &e = it.value();
410 switch (m_mode) {
411 case MouseDrag:
412 SetCursor(e.cursor->handle());
413 break;
414 case TouchDrag:
415 // "Touch drag" with an unsuppressed cursor may happen with RDP (see createCursors())
416 if (QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed)
417 SetCursor(nullptr);
418 if (!m_touchDragWindow)
419 m_touchDragWindow = new QWindowsDragCursorWindow;
420 m_touchDragWindow->setPixmap(e.pixmap);
421 m_touchDragWindow->setFramePosition(QCursor::pos() - e.hotSpot);
422 if (!m_touchDragWindow->isVisible())
423 m_touchDragWindow->show();
424 break;
425 }
426 return ResultFromScode(S_OK);
427 }
428
429 return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
430}
431
432/*!
433 \class QWindowsOleDropTarget
434 \brief Implementation of IDropTarget
435
436 To be registered for each window. Currently, drop sites
437 are enabled for top levels. The child window handling
438 (sending DragEnter/Leave, etc) is handled in here.
439
440 \sa QWindowsDrag
441 \internal
442*/
443
444QWindowsOleDropTarget::QWindowsOleDropTarget(QWindow *w) : m_window(w)
445{
446 qCDebug(lcQpaMime) << __FUNCTION__ << this << w;
447}
448
450{
451 qCDebug(lcQpaMime) << __FUNCTION__ << this;
452}
453
454void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState,
455 const QPoint &point, LPDWORD pdwEffect)
456{
457 Q_ASSERT(window);
458 m_lastPoint = point;
459 m_lastKeyState = grfKeyState;
460
462 const Qt::DropActions actions = translateToQDragDropActions(*pdwEffect);
463
464 lastModifiers = toQtKeyboardModifiers(grfKeyState);
465 lastButtons = QWindowsPointerHandler::queryMouseButtons();
466
467 const QPlatformDragQtResponse response =
468 QWindowSystemInterface::handleDrag(window, windowsDrag->dropData(),
469 m_lastPoint, actions,
470 lastButtons, lastModifiers);
471
472 m_answerRect = response.answerRect();
473 const Qt::DropAction action = response.acceptedAction();
474 if (response.isAccepted()) {
475 m_chosenEffect = translateToWinDragEffects(action);
476 } else {
477 m_chosenEffect = DROPEFFECT_NONE;
478 }
479 *pdwEffect = m_chosenEffect;
480 qCDebug(lcQpaMime) << __FUNCTION__ << m_window
481 << windowsDrag->dropData() << " supported actions=" << actions
482 << " mods=" << lastModifiers << " mouse=" << lastButtons
483 << " accepted: " << response.isAccepted() << action
484 << m_answerRect << " effect" << *pdwEffect;
485}
486
487QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
488QWindowsOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState,
489 POINTL pt, LPDWORD pdwEffect) noexcept
490{
491 if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
492 dh->DragEnter(reinterpret_cast<HWND>(m_window->winId()), pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
493
494 qCDebug(lcQpaMime) << __FUNCTION__ << "widget=" << m_window << " key=" << grfKeyState
495 << "pt=" << pt.x << pt.y;
496
497 QWindowsDrag::instance()->setDropDataObject(pDataObj);
498 pDataObj->AddRef();
499 const QPoint point = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
500 handleDrag(m_window, grfKeyState, point, pdwEffect);
501 return NOERROR;
502}
503
504QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
505QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) noexcept
506{
507 if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
508 dh->DragOver(reinterpret_cast<POINT*>(&pt), *pdwEffect);
509
510 qCDebug(lcQpaMime) << __FUNCTION__ << "m_window" << m_window << "key=" << grfKeyState
511 << "pt=" << pt.x << pt.y;
512 const QPoint tmpPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
513 // see if we should compress this event
514 if ((tmpPoint == m_lastPoint || m_answerRect.contains(tmpPoint))
515 && m_lastKeyState == grfKeyState) {
516 *pdwEffect = m_chosenEffect;
517 qCDebug(lcQpaMime) << __FUNCTION__ << "compressed event";
518 return NOERROR;
519 }
520
521 handleDrag(m_window, grfKeyState, tmpPoint, pdwEffect);
522 return NOERROR;
523}
524
525QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
526QWindowsOleDropTarget::DragLeave() noexcept
527{
528 if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
529 dh->DragLeave();
530
531 qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window;
532
533 const auto *keyMapper = QWindowsContext::instance()->keyMapper();
534 lastModifiers = keyMapper->queryKeyboardModifiers();
535 lastButtons = QWindowsPointerHandler::queryMouseButtons();
536
537 QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction,
538 Qt::NoButton, Qt::NoModifier);
539
540 if (!QDragManager::self()->source())
541 m_lastKeyState = 0;
542 QWindowsDrag::instance()->releaseDropDataObject();
543
544 return NOERROR;
545}
546
547#define KEY_STATE_BUTTON_MASK (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)
548
549QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
550QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
551 POINTL pt, LPDWORD pdwEffect) noexcept
552{
553 if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
554 dh->Drop(pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
555
556 qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window
557 << "keys=" << grfKeyState << "pt=" << pt.x << ',' << pt.y;
558
559 m_lastPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
560
561 QWindowsDrag *windowsDrag = QWindowsDrag::instance();
562
563 lastModifiers = toQtKeyboardModifiers(grfKeyState);
564 lastButtons = QWindowsPointerHandler::queryMouseButtons();
565
566 const QPlatformDropQtResponse response =
567 QWindowSystemInterface::handleDrop(m_window, windowsDrag->dropData(),
568 m_lastPoint,
569 translateToQDragDropActions(*pdwEffect),
570 lastButtons,
571 lastModifiers);
572
573 m_lastKeyState = grfKeyState;
574
575 if (response.isAccepted()) {
576 const Qt::DropAction action = response.acceptedAction();
577 if (action == Qt::MoveAction || action == Qt::TargetMoveAction) {
578 if (action == Qt::MoveAction)
579 m_chosenEffect = DROPEFFECT_MOVE;
580 else
581 m_chosenEffect = DROPEFFECT_COPY;
582 HGLOBAL hData = GlobalAlloc(0, sizeof(DWORD));
583 if (hData) {
584 auto *moveEffect = reinterpret_cast<DWORD *>(GlobalLock(hData));
585 *moveEffect = DROPEFFECT_MOVE;
586 GlobalUnlock(hData);
587 STGMEDIUM medium;
588 memset(&medium, 0, sizeof(STGMEDIUM));
589 medium.tymed = TYMED_HGLOBAL;
590 medium.hGlobal = hData;
591 FORMATETC format;
592 format.cfFormat = CLIPFORMAT(RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT));
593 format.tymed = TYMED_HGLOBAL;
594 format.ptd = nullptr;
595 format.dwAspect = 1;
596 format.lindex = -1;
597 windowsDrag->dropDataObject()->SetData(&format, &medium, true);
598 }
599 } else {
600 m_chosenEffect = translateToWinDragEffects(action);
601 }
602 } else {
603 m_chosenEffect = DROPEFFECT_NONE;
604 }
605 *pdwEffect = m_chosenEffect;
606
607 windowsDrag->releaseDropDataObject();
608 return NOERROR;
609}
610
611
612/*!
613 \class QWindowsDrag
614 \brief Windows drag implementation.
615 \internal
616*/
617
618bool QWindowsDrag::m_canceled = false;
619
620QWindowsDrag::QWindowsDrag() = default;
621
623{
624 if (m_cachedDropTargetHelper)
625 m_cachedDropTargetHelper->Release();
626}
627
628/*!
629 \brief Return data for a drop in process. If it stems from a current drag, use a shortcut.
630*/
631
633{
634 if (const QDrag *drag = currentDrag())
635 return drag->mimeData();
636 return &m_dropData;
637}
638
639/*!
640 \brief May be used to handle extended cursors functionality for drags from outside the app.
641*/
642IDropTargetHelper* QWindowsDrag::dropHelper() {
643 if (!m_cachedDropTargetHelper) {
644 CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
645 IID_IDropTargetHelper,
646 reinterpret_cast<void**>(&m_cachedDropTargetHelper));
647 }
648 return m_cachedDropTargetHelper;
649}
650
651// Workaround for DoDragDrop() not working with touch/pen input, causing DnD to hang until the mouse is moved.
652// We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop()
653static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
654{
655 QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse();
656 const bool hasMouseCapture = underMouse && static_cast<QWindowsWindow *>(underMouse->handle())->hasMouseCapture();
657 const HWND hwnd = hasMouseCapture ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus();
658 bool starting = false;
659
660 for (;;) {
661 MSG msg{};
662 if (::GetMessage(&msg, hwnd, 0, 0) > 0) {
663
664 if (msg.message == WM_MOUSEMOVE) {
665
666 // Only consider the first simulated event, or actual mouse messages.
667 if (!starting && (msg.wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0)
668 return E_FAIL;
669
670 return ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
671 }
672
673 if (msg.message == WM_POINTERUPDATE) {
674
675 const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
676
677 POINTER_INFO pointerInfo{};
678 if (!GetPointerInfo(pointerId, &pointerInfo))
679 return E_FAIL;
680
681 if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
682
683 DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_MOVE;
684 if (IS_POINTER_FIRSTBUTTON_WPARAM(msg.wParam))
685 flags |= MOUSEEVENTF_LEFTDOWN;
686 if (IS_POINTER_SECONDBUTTON_WPARAM(msg.wParam))
687 flags |= MOUSEEVENTF_RIGHTDOWN;
688 if (IS_POINTER_THIRDBUTTON_WPARAM(msg.wParam))
689 flags |= MOUSEEVENTF_MIDDLEDOWN;
690
691 if (!starting) {
692 POINT pt{};
693 if (::GetCursorPos(&pt)) {
694
695 // Send mouse input that can generate a WM_MOUSEMOVE message.
696 if ((flags & MOUSEEVENTF_LEFTDOWN || flags & MOUSEEVENTF_RIGHTDOWN || flags & MOUSEEVENTF_MIDDLEDOWN)
697 && (pt.x != pointerInfo.ptPixelLocation.x || pt.y != pointerInfo.ptPixelLocation.y)) {
698
699 const int origin_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
700 const int origin_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
701 const int virt_w = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
702 const int virt_h = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
703 const int virt_x = pointerInfo.ptPixelLocation.x - origin_x;
704 const int virt_y = pointerInfo.ptPixelLocation.y - origin_y;
705
706 INPUT input{};
707 input.type = INPUT_MOUSE;
708 input.mi.dx = static_cast<DWORD>(virt_x * (65535.0 / virt_w));
709 input.mi.dy = static_cast<DWORD>(virt_y * (65535.0 / virt_h));
710 input.mi.dwFlags = flags;
711
712 ::SendInput(1, &input, sizeof(input));
713 starting = true;
714 }
715 }
716 }
717 }
718 } else {
719 // Handle other messages.
720 qWindowsWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
721
722 if (msg.message == WM_POINTERLEAVE)
723 return E_FAIL;
724 }
725 } else {
726 return E_FAIL;
727 }
728 }
729}
730
732{
733 // TODO: Accessibility handling?
734 QMimeData *dropData = drag->mimeData();
735 Qt::DropAction dragResult = Qt::IgnoreAction;
736
737 DWORD resultEffect;
738 QWindowsDrag::m_canceled = false;
739 auto *windowDropSource = new QWindowsOleDropSource(this);
740 windowDropSource->createCursors();
741 auto *dropDataObject = new QWindowsDropDataObject(dropData);
742 const Qt::DropActions possibleActions = drag->supportedActions();
743 const DWORD allowedEffects = translateToWinDragEffects(possibleActions);
744 qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x"
745 << Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec;
746 const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
747 const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect();
748 if (r == DRAGDROP_S_DROP) {
749 if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) {
750 dragResult = Qt::TargetMoveAction;
751 resultEffect = DROPEFFECT_MOVE;
752 } else {
753 dragResult = translateToQDragDropAction(resultEffect);
754 }
755 // Force it to be a copy if an unsupported operation occurred.
756 // This indicates a bug in the drop target.
757 if (resultEffect != DROPEFFECT_NONE && !(resultEffect & allowedEffects)) {
758 qWarning("%s: Forcing Qt::CopyAction", __FUNCTION__);
759 dragResult = Qt::CopyAction;
760 }
761 }
762 // clean up
763 dropDataObject->releaseQt();
764 dropDataObject->Release(); // Will delete obj if refcount becomes 0
765 windowDropSource->Release(); // Will delete src if refcount becomes 0
766 qCDebug(lcQpaMime) << '<' << __FUNCTION__ << Qt::hex << "allowedEffects=0x" << allowedEffects
767 << "reportedPerformedEffect=0x" << reportedPerformedEffect
768 << " resultEffect=0x" << resultEffect << "hr=0x" << int(r) << Qt::dec << "dropAction=" << dragResult;
769 return dragResult;
770}
771
773{
774 return static_cast<QWindowsDrag *>(QWindowsIntegration::instance()->drag());
775}
776
778{
779 qCDebug(lcQpaMime) << __FUNCTION__ << m_dropDataObject;
780 if (m_dropDataObject) {
781 m_dropDataObject->Release();
782 m_dropDataObject = nullptr;
783 }
784}
785
786QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:30
Singleton container for all relevant information.
QWindowsScreenManager & screenManager()
static QWindowsContext * instance()
Platform cursor implementation.
static QPoint mousePosition()
static State cursorState()
A toplevel window showing the drag icon in case of touch drag.
void setPixmap(const QPixmap &p)
void paintEvent(QPaintEvent *) override
Handles paint events passed in the event parameter.
QWindowsDragCursorWindow(QWindow *parent=nullptr)
Windows drag implementation.
virtual ~QWindowsDrag()
QMimeData * dropData()
Return data for a drop in process.
static QWindowsDrag * instance()
void releaseDropDataObject()
IDropTargetHelper * dropHelper()
May be used to handle extended cursors functionality for drags from outside the app.
Qt::DropAction drag(QDrag *drag) override
QWindowsOleDataObject subclass specialized for handling Drag&Drop.
QWindowsDropDataObject(QMimeData *mimeData)
Special mime data class for data retrieval from Drag operations.
IDataObject * retrieveDataObject() const override
static QWindowsIntegration * instance()
Implementation of IDropSource.
STDMETHOD GiveFeedback(DWORD dwEffect) noexcept override
Give feedback: Change cursor according to action.
~QWindowsOleDropSource() override
void createCursors()
Blend custom pixmap with cursors.
QWindowsOleDropSource(QWindowsDrag *drag)
STDMETHOD QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) noexcept override
Check for cancel.
Implementation of IDropTarget.
~QWindowsOleDropTarget() override
const QWindowsScreen * screenAtDp(const QPoint &p) const
Raster or OpenGL Window.
bool hasMouseCapture() const
Combined button and popup list for selecting options.
#define WM_POINTERUPDATE
#define WM_POINTERLEAVE
static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
static Qt::DropActions translateToQDragDropActions(DWORD pdwEffects)
static Qt::KeyboardModifiers toQtKeyboardModifiers(DWORD keyState)
static Qt::MouseButtons lastButtons
static Qt::DropAction translateToQDragDropAction(DWORD pdwEffect)
static DWORD translateToWinDragEffects(Qt::DropActions action)
static Qt::KeyboardModifiers lastModifiers