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