Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/kernel/qsimpledrag.cpp
Line
Count
Source
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 "qsimpledrag_p.h"
6
7
#include "qbitmap.h"
8
#include "qdrag.h"
9
#include "qpixmap.h"
10
#include "qevent.h"
11
#include "qfile.h"
12
#include "qguiapplication.h"
13
#include "qpoint.h"
14
#include "qbuffer.h"
15
#include "qimage.h"
16
#include "qdir.h"
17
#include "qimagereader.h"
18
#include "qimagewriter.h"
19
#include "qplatformscreen.h"
20
#include "qplatformwindow.h"
21
22
#include <QtCore/QEventLoop>
23
#include <QtCore/QDebug>
24
#include <QtCore/QLoggingCategory>
25
26
#include <private/qguiapplication_p.h>
27
#include <private/qdnd_p.h>
28
29
#include <private/qshapedpixmapdndwindow_p.h>
30
#include <private/qhighdpiscaling_p.h>
31
32
QT_BEGIN_NAMESPACE
33
34
Q_STATIC_LOGGING_CATEGORY(lcDnd, "qt.gui.dnd")
35
36
static QWindow* topLevelAt(const QPoint &pos)
37
0
{
38
0
    const QWindowList list = QGuiApplication::topLevelWindows();
39
0
    const auto crend = list.crend();
40
0
    for (auto it = list.crbegin(); it != crend; ++it) {
41
0
        QWindow *w = *it;
42
0
        if (w->isVisible() && w->handle() && w->geometry().contains(pos) && !qobject_cast<QShapedPixmapWindow*>(w))
43
0
            return w;
44
0
    }
45
0
    return nullptr;
46
0
}
47
48
/*!
49
    \class QBasicDrag
50
    \brief QBasicDrag is a base class for implementing platform drag and drop.
51
    \since 5.0
52
    \internal
53
    \ingroup qpa
54
55
    QBasicDrag implements QPlatformDrag::drag() by running a local event loop in which
56
    it tracks mouse movements and moves the drag icon (QShapedPixmapWindow) accordingly.
57
    It provides new virtuals allowing for querying whether the receiving window
58
    (within the Qt application or outside) accepts the drag and sets the state accordingly.
59
*/
60
61
QBasicDrag::QBasicDrag()
62
0
{
63
0
}
64
65
QBasicDrag::~QBasicDrag()
66
0
{
67
0
    delete m_drag_icon_window;
68
0
}
69
70
void QBasicDrag::enableEventFilter()
71
0
{
72
0
    qApp->installEventFilter(this);
73
0
}
74
75
void QBasicDrag::disableEventFilter()
76
0
{
77
0
    qApp->removeEventFilter(this);
78
0
}
79
80
81
static inline QPoint getNativeMousePos(QEvent *e, QWindow *window)
82
0
{
83
0
    return QHighDpi::toNativePixels(static_cast<QMouseEvent *>(e)->globalPosition().toPoint(), window);
84
0
}
85
86
bool QBasicDrag::eventFilter(QObject *o, QEvent *e)
87
0
{
88
0
    Q_UNUSED(o);
89
90
0
    if (!m_drag) {
91
0
        if (e->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
92
0
            disableEventFilter();
93
0
            exitDndEventLoop();
94
0
            return true; // block the key release
95
0
        }
96
0
        return false;
97
0
    }
98
99
0
    switch (e->type()) {
100
0
        case QEvent::ShortcutOverride:
101
            // prevent accelerators from firing while dragging
102
0
            e->accept();
103
0
            return true;
104
105
0
        case QEvent::KeyPress:
106
0
        case QEvent::KeyRelease:
107
0
        {
108
0
            QKeyEvent *ke = static_cast<QKeyEvent *>(e);
109
0
            if (ke->key() == Qt::Key_Escape && e->type() == QEvent::KeyPress) {
110
0
                cancel();
111
0
                disableEventFilter();
112
0
                exitDndEventLoop();
113
114
0
            } else if (ke->modifiers() != QGuiApplication::keyboardModifiers()) {
115
0
                move(m_lastPos, QGuiApplication::mouseButtons(), ke->modifiers());
116
0
            }
117
0
            return true; // Eat all key events
118
0
        }
119
120
0
        case QEvent::MouseMove:
121
0
        {
122
0
            m_lastPos = getNativeMousePos(e, m_drag_icon_window);
123
0
            auto mouseMove = static_cast<QMouseEvent *>(e);
124
0
            move(m_lastPos, mouseMove->buttons(), mouseMove->modifiers());
125
0
            return true; // Eat all mouse move events
126
0
        }
127
0
        case QEvent::MouseButtonRelease:
128
0
        {
129
0
            QPointer<QObject> objGuard(o);
130
0
            disableEventFilter();
131
0
            if (canDrop()) {
132
0
                QPoint nativePosition = getNativeMousePos(e, m_drag_icon_window);
133
0
                auto mouseRelease = static_cast<QMouseEvent *>(e);
134
0
                drop(nativePosition, mouseRelease->buttons(), mouseRelease->modifiers());
135
0
            } else {
136
0
                cancel();
137
0
            }
138
0
            exitDndEventLoop();
139
0
            if (!objGuard)
140
0
                return true;
141
142
            // If a QShapedPixmapWindow (drag feedback) is being dragged along, the
143
            // mouse event's localPos() will be relative to that, which is useless.
144
            // We want a position relative to the window where the drag ends, if possible (?).
145
            // If there is no such window (belonging to this Qt application),
146
            // make the event relative to the window where the drag started. (QTBUG-66103)
147
0
            const QMouseEvent *release = static_cast<QMouseEvent *>(e);
148
0
            const QWindow *releaseWindow = topLevelAt(release->globalPosition().toPoint());
149
0
            qCDebug(lcDnd) << "mouse released over" << releaseWindow << "after drag from" << m_sourceWindow << "globalPos" << release->globalPosition().toPoint();
150
0
            if (!releaseWindow)
151
0
                releaseWindow = m_sourceWindow;
152
0
            QPointF releaseWindowPos = (releaseWindow ? releaseWindow->mapFromGlobal(release->globalPosition()) : release->globalPosition());
153
0
            QMouseEvent *newRelease = new QMouseEvent(release->type(),
154
0
                releaseWindowPos, releaseWindowPos, release->globalPosition(),
155
0
                release->button(), release->buttons(),
156
0
                release->modifiers(), release->source(), release->pointingDevice());
157
0
            QCoreApplication::postEvent(o, newRelease);
158
0
            return true; // defer mouse release events until drag event loop has returned
159
0
        }
160
0
        case QEvent::MouseButtonDblClick:
161
0
        case QEvent::Wheel:
162
0
            return true;
163
0
        default:
164
0
             break;
165
0
    }
166
0
    return false;
167
0
}
168
169
Qt::DropAction QBasicDrag::drag(QDrag *o)
170
0
{
171
0
    m_drag = o;
172
0
    m_executed_drop_action = Qt::IgnoreAction;
173
0
    m_can_drop = false;
174
175
0
    startDrag();
176
0
    m_eventLoop = new QEventLoop;
177
0
    m_eventLoop->exec();
178
0
    delete m_eventLoop;
179
0
    m_eventLoop = nullptr;
180
0
    m_drag = nullptr;
181
0
    endDrag();
182
183
0
    return m_executed_drop_action;
184
0
}
185
186
void QBasicDrag::cancelDrag()
187
0
{
188
0
    if (m_eventLoop) {
189
0
        cancel();
190
0
        m_eventLoop->quit();
191
0
    }
192
0
}
193
194
void QBasicDrag::startDrag()
195
0
{
196
0
    QPoint pos;
197
0
#ifndef QT_NO_CURSOR
198
0
    pos = QCursor::pos();
199
0
    static constexpr QGuiApplicationPrivate::QLastCursorPosition uninitializedCursorPosition;
200
0
    if (pos == uninitializedCursorPosition) {
201
        // ### fixme: no mouse pos registered. Get pos from touch...
202
0
        pos = QPoint();
203
0
    }
204
0
#endif
205
0
    m_lastPos = pos;
206
0
    recreateShapedPixmapWindow(m_screen, pos);
207
0
    enableEventFilter();
208
0
}
209
210
void QBasicDrag::endDrag()
211
0
{
212
0
}
213
214
void QBasicDrag::recreateShapedPixmapWindow(QScreen *screen, const QPoint &pos)
215
0
{
216
0
    delete m_drag_icon_window;
217
    // ### TODO Check if its really necessary to have m_drag_icon_window
218
    // when QDrag is used without a pixmap - QDrag::setPixmap()
219
0
    m_drag_icon_window = new QShapedPixmapWindow(screen);
220
221
0
    m_drag_icon_window->setUseCompositing(m_useCompositing);
222
0
    m_drag_icon_window->setPixmap(m_drag->pixmap());
223
0
    m_drag_icon_window->setHotspot(m_drag->hotSpot());
224
0
    m_drag_icon_window->updateGeometry(pos);
225
0
    m_drag_icon_window->setVisible(true);
226
0
}
227
228
void QBasicDrag::cancel()
229
0
{
230
0
    disableEventFilter();
231
0
    restoreCursor();
232
0
    m_drag_icon_window->setVisible(false);
233
0
}
234
235
/*!
236
  Move the drag label to \a globalPos, which is
237
  interpreted in device independent coordinates. Typically called from reimplementations of move().
238
 */
239
240
void QBasicDrag::moveShapedPixmapWindow(const QPoint &globalPos)
241
0
{
242
0
    if (m_drag)
243
0
        m_drag_icon_window->updateGeometry(globalPos);
244
0
}
245
246
void QBasicDrag::drop(const QPoint &, Qt::MouseButtons, Qt::KeyboardModifiers)
247
0
{
248
0
    disableEventFilter();
249
0
    restoreCursor();
250
0
    m_drag_icon_window->setVisible(false);
251
0
}
252
253
void  QBasicDrag::exitDndEventLoop()
254
0
{
255
0
    if (m_eventLoop && m_eventLoop->isRunning())
256
0
        m_eventLoop->exit();
257
0
}
258
259
void QBasicDrag::updateCursor(Qt::DropAction action)
260
0
{
261
0
#ifndef QT_NO_CURSOR
262
0
    Qt::CursorShape cursorShape = Qt::ForbiddenCursor;
263
0
    if (canDrop()) {
264
0
        switch (action) {
265
0
        case Qt::CopyAction:
266
0
            cursorShape = Qt::DragCopyCursor;
267
0
            break;
268
0
        case Qt::LinkAction:
269
0
            cursorShape = Qt::DragLinkCursor;
270
0
            break;
271
0
        default:
272
0
            cursorShape = Qt::DragMoveCursor;
273
0
            break;
274
0
        }
275
0
    }
276
277
0
    QPixmap pixmap = m_drag->dragCursor(action);
278
279
0
    if (!m_dndHasSetOverrideCursor) {
280
0
        QCursor newCursor = !pixmap.isNull() ? QCursor(pixmap) : QCursor(cursorShape);
281
0
        QGuiApplication::setOverrideCursor(newCursor);
282
0
        m_dndHasSetOverrideCursor = true;
283
0
    } else {
284
0
        QCursor *cursor = QGuiApplication::overrideCursor();
285
0
        if (!cursor) {
286
0
            QGuiApplication::changeOverrideCursor(pixmap.isNull() ? QCursor(cursorShape) : QCursor(pixmap));
287
0
        } else {
288
0
            if (!pixmap.isNull()) {
289
0
                if (cursor->pixmap().cacheKey() != pixmap.cacheKey())
290
0
                    QGuiApplication::changeOverrideCursor(QCursor(pixmap));
291
0
            } else if (cursorShape != cursor->shape()) {
292
0
                QGuiApplication::changeOverrideCursor(QCursor(cursorShape));
293
0
            }
294
0
        }
295
0
    }
296
0
#endif
297
0
    updateAction(action);
298
0
}
299
300
void QBasicDrag::restoreCursor()
301
0
{
302
0
#ifndef QT_NO_CURSOR
303
0
    if (m_dndHasSetOverrideCursor) {
304
0
        QGuiApplication::restoreOverrideCursor();
305
0
        m_dndHasSetOverrideCursor = false;
306
0
    }
307
0
#endif
308
0
}
309
310
static inline QPoint fromNativeGlobalPixels(const QPoint &point)
311
0
{
312
0
#ifndef QT_NO_HIGHDPISCALING
313
0
    QPoint res = point;
314
0
    if (QHighDpiScaling::isActive()) {
315
0
        for (const QScreen *s : std::as_const(QGuiApplicationPrivate::screen_list)) {
316
0
            if (s->handle()->geometry().contains(point)) {
317
0
                res = QHighDpi::fromNativePixels(point, s);
318
0
                break;
319
0
            }
320
0
        }
321
0
    }
322
0
    return res;
323
#else
324
    return point;
325
#endif
326
0
}
327
328
/*!
329
    \class QSimpleDrag
330
    \brief QSimpleDrag implements QBasicDrag for Drag and Drop operations within the Qt Application itself.
331
    \since 5.0
332
    \internal
333
    \ingroup qpa
334
335
    The class checks whether the receiving window is a window of the Qt application
336
    and sets the state accordingly. It does not take windows of other applications
337
    into account.
338
*/
339
340
QSimpleDrag::QSimpleDrag()
341
0
{
342
0
}
343
344
void QSimpleDrag::startDrag()
345
0
{
346
0
    setExecutedDropAction(Qt::IgnoreAction);
347
348
0
    QBasicDrag::startDrag();
349
    // Here we can be fairly sure that QGuiApplication::mouseButtons/keyboardModifiers() will
350
    // contain sensible values as startDrag() normally is called from mouse event handlers
351
    // by QDrag::exec(). A better API would be if we could pass something like "input device
352
    // pointer" to QDrag::exec(). My guess is that something like that might be required for
353
    // QTBUG-52430.
354
0
    m_sourceWindow = topLevelAt(QCursor::pos());
355
0
    m_windowUnderCursor = m_sourceWindow;
356
0
    if (m_sourceWindow) {
357
0
        auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), m_sourceWindow);
358
0
        move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
359
0
    } else {
360
0
        setCanDrop(false);
361
0
        updateCursor(Qt::IgnoreAction);
362
0
    }
363
364
0
    qCDebug(lcDnd) << "drag began from" << m_sourceWindow << "cursor pos" << QCursor::pos() << "can drop?" << canDrop();
365
0
}
366
367
static void sendDragLeave(QWindow *window)
368
0
{
369
0
    QWindowSystemInterface::handleDrag(window, nullptr, QPoint(), Qt::IgnoreAction, { }, { });
370
0
}
371
372
void QSimpleDrag::cancel()
373
0
{
374
0
    QBasicDrag::cancel();
375
0
    if (drag() && m_sourceWindow) {
376
0
        sendDragLeave(m_sourceWindow);
377
0
        m_sourceWindow = nullptr;
378
0
    }
379
0
}
380
381
void QSimpleDrag::move(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons,
382
                       Qt::KeyboardModifiers modifiers)
383
0
{
384
0
    QPoint globalPos = fromNativeGlobalPixels(nativeGlobalPos);
385
0
    moveShapedPixmapWindow(globalPos);
386
0
    QWindow *window = topLevelAt(globalPos);
387
388
0
    if (!window || window != m_windowUnderCursor) {
389
0
        if (m_windowUnderCursor)
390
0
            sendDragLeave(m_windowUnderCursor);
391
0
        m_windowUnderCursor = window;
392
0
        if (!window) {
393
            // QSimpleDrag supports only in-process dnd, we can't drop anywhere else.
394
0
            setCanDrop(false);
395
0
            updateCursor(Qt::IgnoreAction);
396
0
            return;
397
0
        }
398
0
    }
399
400
0
    const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft();
401
0
    const QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
402
0
                window, drag()->mimeData(), pos, drag()->supportedActions(),
403
0
                buttons, modifiers);
404
405
0
    setCanDrop(qt_response.isAccepted());
406
0
    updateCursor(qt_response.acceptedAction());
407
0
}
408
409
void QSimpleDrag::drop(const QPoint &nativeGlobalPos, Qt::MouseButtons buttons,
410
                       Qt::KeyboardModifiers modifiers)
411
0
{
412
0
    QPoint globalPos = fromNativeGlobalPixels(nativeGlobalPos);
413
414
0
    QBasicDrag::drop(nativeGlobalPos, buttons, modifiers);
415
0
    QWindow *window = topLevelAt(globalPos);
416
0
    if (!window)
417
0
        return;
418
419
0
    const QPoint pos = nativeGlobalPos - window->handle()->geometry().topLeft();
420
0
    const QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
421
0
                window, drag()->mimeData(), pos, drag()->supportedActions(),
422
0
                buttons, modifiers);
423
0
    if (response.isAccepted()) {
424
0
        setExecutedDropAction(response.acceptedAction());
425
0
    } else {
426
0
        setExecutedDropAction(Qt::IgnoreAction);
427
0
    }
428
0
}
429
430
QT_END_NAMESPACE