mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-04-01 22:08:37 +08:00
792 lines
28 KiB
C++
792 lines
28 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
#include <QtCore/qt_windows.h>
|
|
#include "qwindowsdrag.h"
|
|
#include "qwindowscontext.h"
|
|
#include "qwindowsscreen.h"
|
|
#if QT_CONFIG(clipboard)
|
|
# include "qwindowsclipboard.h"
|
|
#endif
|
|
#include "qwindowsintegration.h"
|
|
#include "qwindowsdropdataobject.h"
|
|
#include "qwindowswindow.h"
|
|
#include "qwindowsmousehandler.h"
|
|
#include "qwindowscursor.h"
|
|
#include "qwindowskeymapper.h"
|
|
|
|
#include <QtGui/qevent.h>
|
|
#include <QtGui/qpixmap.h>
|
|
#include <QtGui/qpainter.h>
|
|
#include <QtGui/qrasterwindow.h>
|
|
#include <QtGui/qguiapplication.h>
|
|
#include <qpa/qwindowsysteminterface_p.h>
|
|
#include <QtGui/private/qdnd_p.h>
|
|
#include <QtGui/private/qguiapplication_p.h>
|
|
#include <QtGui/private/qhighdpiscaling_p.h>
|
|
|
|
#include <QtCore/qdebug.h>
|
|
#include <QtCore/qbuffer.h>
|
|
#include <QtCore/qpoint.h>
|
|
#include <QtCore/qpointer.h>
|
|
#include <QtCore/private/qcomobject_p.h>
|
|
|
|
#include <shlobj.h>
|
|
|
|
#include "vxkex.h"
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
/*!
|
|
\class QWindowsDragCursorWindow
|
|
\brief A toplevel window showing the drag icon in case of touch drag.
|
|
|
|
\sa QWindowsOleDropSource
|
|
\internal
|
|
*/
|
|
|
|
class QWindowsDragCursorWindow : public QRasterWindow
|
|
{
|
|
public:
|
|
explicit QWindowsDragCursorWindow(QWindow *parent = nullptr);
|
|
|
|
void setPixmap(const QPixmap &p);
|
|
|
|
protected:
|
|
void paintEvent(QPaintEvent *) override
|
|
{
|
|
QPainter painter(this);
|
|
painter.drawPixmap(0, 0, m_pixmap);
|
|
}
|
|
|
|
private:
|
|
QPixmap m_pixmap;
|
|
};
|
|
|
|
QWindowsDragCursorWindow::QWindowsDragCursorWindow(QWindow *parent)
|
|
: QRasterWindow(parent)
|
|
{
|
|
QSurfaceFormat windowFormat = format();
|
|
windowFormat.setAlphaBufferSize(8);
|
|
setFormat(windowFormat);
|
|
setObjectName(QStringLiteral("QWindowsDragCursorWindow"));
|
|
setFlags(Qt::Popup | Qt::NoDropShadowWindowHint
|
|
| Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
|
|
| Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput);
|
|
}
|
|
|
|
void QWindowsDragCursorWindow::setPixmap(const QPixmap &p)
|
|
{
|
|
if (p.cacheKey() == m_pixmap.cacheKey())
|
|
return;
|
|
const QSize oldSize = m_pixmap.size();
|
|
QSize newSize = p.size();
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << p.cacheKey() << newSize;
|
|
m_pixmap = p;
|
|
if (oldSize != newSize) {
|
|
const qreal pixDevicePixelRatio = p.devicePixelRatio();
|
|
if (pixDevicePixelRatio > 1.0 && qFuzzyCompare(pixDevicePixelRatio, devicePixelRatio()))
|
|
newSize /= qRound(pixDevicePixelRatio);
|
|
resize(newSize);
|
|
}
|
|
if (isVisible())
|
|
update();
|
|
}
|
|
|
|
/*!
|
|
\class QWindowsDropMimeData
|
|
\brief Special mime data class for data retrieval from Drag operations.
|
|
|
|
Implementation of QWindowsInternalMimeDataBase which retrieves the
|
|
current drop data object from QWindowsDrag.
|
|
|
|
\sa QWindowsDrag
|
|
\internal
|
|
*/
|
|
|
|
IDataObject *QWindowsDropMimeData::retrieveDataObject() const
|
|
{
|
|
return QWindowsDrag::instance()->dropDataObject();
|
|
}
|
|
|
|
static inline Qt::DropActions translateToQDragDropActions(DWORD pdwEffects)
|
|
{
|
|
Qt::DropActions actions = Qt::IgnoreAction;
|
|
if (pdwEffects & DROPEFFECT_LINK)
|
|
actions |= Qt::LinkAction;
|
|
if (pdwEffects & DROPEFFECT_COPY)
|
|
actions |= Qt::CopyAction;
|
|
if (pdwEffects & DROPEFFECT_MOVE)
|
|
actions |= Qt::MoveAction;
|
|
return actions;
|
|
}
|
|
|
|
static inline Qt::DropAction translateToQDragDropAction(DWORD pdwEffect)
|
|
{
|
|
if (pdwEffect & DROPEFFECT_LINK)
|
|
return Qt::LinkAction;
|
|
if (pdwEffect & DROPEFFECT_COPY)
|
|
return Qt::CopyAction;
|
|
if (pdwEffect & DROPEFFECT_MOVE)
|
|
return Qt::MoveAction;
|
|
return Qt::IgnoreAction;
|
|
}
|
|
|
|
static inline DWORD translateToWinDragEffects(Qt::DropActions action)
|
|
{
|
|
DWORD effect = DROPEFFECT_NONE;
|
|
if (action & Qt::LinkAction)
|
|
effect |= DROPEFFECT_LINK;
|
|
if (action & Qt::CopyAction)
|
|
effect |= DROPEFFECT_COPY;
|
|
if (action & Qt::MoveAction)
|
|
effect |= DROPEFFECT_MOVE;
|
|
return effect;
|
|
}
|
|
|
|
static inline Qt::KeyboardModifiers toQtKeyboardModifiers(DWORD keyState)
|
|
{
|
|
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
|
|
|
|
if (keyState & MK_SHIFT)
|
|
modifiers |= Qt::ShiftModifier;
|
|
if (keyState & MK_CONTROL)
|
|
modifiers |= Qt::ControlModifier;
|
|
if (keyState & MK_ALT)
|
|
modifiers |= Qt::AltModifier;
|
|
|
|
return modifiers;
|
|
}
|
|
|
|
static Qt::KeyboardModifiers lastModifiers = Qt::NoModifier;
|
|
static Qt::MouseButtons lastButtons = Qt::NoButton;
|
|
|
|
/*!
|
|
\class QWindowsOleDropSource
|
|
\brief Implementation of IDropSource
|
|
|
|
Used for drag operations.
|
|
|
|
\sa QWindowsDrag
|
|
\internal
|
|
*/
|
|
|
|
class QWindowsOleDropSource : public QComObject<IDropSource>
|
|
{
|
|
public:
|
|
enum Mode {
|
|
MouseDrag,
|
|
TouchDrag // Mouse cursor suppressed, use window as cursor.
|
|
};
|
|
|
|
explicit QWindowsOleDropSource(QWindowsDrag *drag);
|
|
~QWindowsOleDropSource() override;
|
|
|
|
void createCursors();
|
|
|
|
// IDropSource methods
|
|
STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState) override;
|
|
STDMETHOD(GiveFeedback)(DWORD dwEffect) override;
|
|
|
|
private:
|
|
struct CursorEntry {
|
|
CursorEntry() : cacheKey(0) {}
|
|
CursorEntry(const QPixmap &p, qint64 cK, const CursorHandlePtr &c, const QPoint &h) :
|
|
pixmap(p), cacheKey(cK), cursor(c), hotSpot(h) {}
|
|
|
|
QPixmap pixmap;
|
|
qint64 cacheKey; // Cache key of cursor
|
|
CursorHandlePtr cursor;
|
|
QPoint hotSpot;
|
|
};
|
|
|
|
typedef QMap<Qt::DropAction, CursorEntry> ActionCursorMap;
|
|
|
|
Mode m_mode;
|
|
QWindowsDrag *m_drag;
|
|
QPointer<QWindow> m_windowUnderMouse;
|
|
Qt::MouseButtons m_currentButtons;
|
|
ActionCursorMap m_cursors;
|
|
QWindowsDragCursorWindow *m_touchDragWindow;
|
|
|
|
#ifndef QT_NO_DEBUG_STREAM
|
|
friend QDebug operator<<(QDebug, const QWindowsOleDropSource::CursorEntry &);
|
|
#endif
|
|
};
|
|
|
|
QWindowsOleDropSource::QWindowsOleDropSource(QWindowsDrag *drag)
|
|
: m_mode(QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed ? MouseDrag : TouchDrag)
|
|
, m_drag(drag)
|
|
, m_windowUnderMouse(QWindowsContext::instance()->windowUnderMouse())
|
|
, m_currentButtons(Qt::NoButton)
|
|
, m_touchDragWindow(nullptr)
|
|
{
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << m_mode;
|
|
}
|
|
|
|
QWindowsOleDropSource::~QWindowsOleDropSource()
|
|
{
|
|
m_cursors.clear();
|
|
delete m_touchDragWindow;
|
|
qCDebug(lcQpaMime) << __FUNCTION__;
|
|
}
|
|
|
|
#ifndef QT_NO_DEBUG_STREAM
|
|
QDebug operator<<(QDebug d, const QWindowsOleDropSource::CursorEntry &e)
|
|
{
|
|
d << "CursorEntry:" << e.pixmap.size() << '#' << e.cacheKey
|
|
<< "HCURSOR" << e.cursor->handle() << "hotspot:" << e.hotSpot;
|
|
return d;
|
|
}
|
|
#endif // !QT_NO_DEBUG_STREAM
|
|
|
|
/*!
|
|
\brief Blend custom pixmap with cursors.
|
|
*/
|
|
|
|
void QWindowsOleDropSource::createCursors()
|
|
{
|
|
const QDrag *drag = m_drag->currentDrag();
|
|
const QPixmap pixmap = drag->pixmap();
|
|
const bool hasPixmap = !pixmap.isNull();
|
|
|
|
// Find screen for drag. Could be obtained from QDrag::source(), but that might be a QWidget.
|
|
const QPlatformScreen *platformScreen = QWindowsContext::instance()->screenManager().screenAtDp(QWindowsCursor::mousePosition());
|
|
if (!platformScreen) {
|
|
if (const QScreen *primaryScreen = QGuiApplication::primaryScreen())
|
|
platformScreen = primaryScreen->handle();
|
|
}
|
|
QPlatformCursor *platformCursor = nullptr;
|
|
if (platformScreen)
|
|
platformCursor = platformScreen->cursor();
|
|
|
|
if (GetSystemMetrics (SM_REMOTESESSION) != 0) {
|
|
/* Workaround for RDP issues with large cursors.
|
|
* Touch drag window seems to work just fine...
|
|
* 96 pixel is a 'large' mouse cursor, according to RDP spec */
|
|
const int rdpLargeCursor = qRound(qreal(96) / QHighDpiScaling::factor(platformScreen));
|
|
if (pixmap.width() > rdpLargeCursor || pixmap.height() > rdpLargeCursor)
|
|
m_mode = TouchDrag;
|
|
}
|
|
|
|
qreal pixmapScaleFactor = 1;
|
|
qreal hotSpotScaleFactor = 1;
|
|
if (m_mode != TouchDrag) { // Touch drag: pixmap is shown in a separate QWindow, which will be scaled.)
|
|
hotSpotScaleFactor = QHighDpiScaling::factor(platformScreen);
|
|
pixmapScaleFactor = hotSpotScaleFactor / pixmap.devicePixelRatio();
|
|
}
|
|
QPixmap scaledPixmap = (!hasPixmap || qFuzzyCompare(pixmapScaleFactor, 1.0))
|
|
? pixmap
|
|
: pixmap.scaled((QSizeF(pixmap.size()) * pixmapScaleFactor).toSize(),
|
|
Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
scaledPixmap.setDevicePixelRatio(1);
|
|
|
|
Qt::DropAction actions[] = { Qt::MoveAction, Qt::CopyAction, Qt::LinkAction, Qt::IgnoreAction };
|
|
int actionCount = int(sizeof(actions) / sizeof(actions[0]));
|
|
if (!hasPixmap)
|
|
--actionCount; // No Qt::IgnoreAction unless pixmap
|
|
const QPoint hotSpot = qFuzzyCompare(hotSpotScaleFactor, 1.0)
|
|
? drag->hotSpot()
|
|
: (QPointF(drag->hotSpot()) * hotSpotScaleFactor).toPoint();
|
|
for (int cnum = 0; cnum < actionCount; ++cnum) {
|
|
const Qt::DropAction action = actions[cnum];
|
|
QPixmap cursorPixmap = drag->dragCursor(action);
|
|
if (cursorPixmap.isNull() && platformCursor)
|
|
cursorPixmap = static_cast<QWindowsCursor *>(platformCursor)->dragDefaultCursor(action);
|
|
const qint64 cacheKey = cursorPixmap.cacheKey();
|
|
const auto it = m_cursors.find(action);
|
|
if (it != m_cursors.end() && it.value().cacheKey == cacheKey)
|
|
continue;
|
|
if (cursorPixmap.isNull()) {
|
|
qWarning("%s: Unable to obtain drag cursor for %d.", __FUNCTION__, action);
|
|
continue;
|
|
}
|
|
|
|
QPoint newHotSpot(0, 0);
|
|
QPixmap newPixmap = cursorPixmap;
|
|
|
|
if (hasPixmap) {
|
|
const int x1 = qMin(-hotSpot.x(), 0);
|
|
const int x2 = qMax(scaledPixmap.width() - hotSpot.x(), cursorPixmap.width());
|
|
const int y1 = qMin(-hotSpot.y(), 0);
|
|
const int y2 = qMax(scaledPixmap.height() - hotSpot.y(), cursorPixmap.height());
|
|
QPixmap newCursor(x2 - x1 + 1, y2 - y1 + 1);
|
|
newCursor.fill(Qt::transparent);
|
|
QPainter p(&newCursor);
|
|
const QPoint pmDest = QPoint(qMax(0, -hotSpot.x()), qMax(0, -hotSpot.y()));
|
|
p.drawPixmap(pmDest, scaledPixmap);
|
|
p.drawPixmap(qMax(0, hotSpot.x()),qMax(0, hotSpot.y()), cursorPixmap);
|
|
newPixmap = newCursor;
|
|
newHotSpot = QPoint(qMax(0, hotSpot.x()), qMax(0, hotSpot.y()));
|
|
}
|
|
|
|
if (const HCURSOR sysCursor = QWindowsCursor::createPixmapCursor(newPixmap, newHotSpot)) {
|
|
const CursorEntry entry(newPixmap, cacheKey, CursorHandlePtr(new CursorHandle(sysCursor)), newHotSpot);
|
|
if (it == m_cursors.end())
|
|
m_cursors.insert(action, entry);
|
|
else
|
|
it.value() = entry;
|
|
}
|
|
}
|
|
#ifndef QT_NO_DEBUG_OUTPUT
|
|
if (lcQpaMime().isDebugEnabled())
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << "pixmap" << pixmap.size() << m_cursors.size() << "cursors:\n" << m_cursors;
|
|
#endif // !QT_NO_DEBUG_OUTPUT
|
|
}
|
|
|
|
/*!
|
|
\brief Check for cancel.
|
|
*/
|
|
|
|
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
|
QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
|
|
{
|
|
// In some rare cases, when a mouse button is released but the mouse is static,
|
|
// grfKeyState will not be updated with these released buttons until the mouse
|
|
// is moved. So we use the async key state given by queryMouseButtons() instead.
|
|
Qt::MouseButtons buttons = QWindowsMouseHandler::queryMouseButtons();
|
|
|
|
SCODE result = S_OK;
|
|
if (fEscapePressed || QWindowsDrag::isCanceled()) {
|
|
result = DRAGDROP_S_CANCEL;
|
|
buttons = Qt::NoButton;
|
|
} else {
|
|
if (buttons && !m_currentButtons) {
|
|
m_currentButtons = buttons;
|
|
} else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation.
|
|
result = DRAGDROP_S_DROP;
|
|
}
|
|
}
|
|
|
|
switch (result) {
|
|
case DRAGDROP_S_DROP:
|
|
case DRAGDROP_S_CANCEL:
|
|
if (!m_windowUnderMouse.isNull() && m_mode != TouchDrag && fEscapePressed == FALSE
|
|
&& buttons != lastButtons) {
|
|
// QTBUG 66447: Synthesize a mouse release to the window under mouse at
|
|
// start of the DnD operation as Windows does not send any.
|
|
const QPoint globalPos = QWindowsCursor::mousePosition();
|
|
const QPoint localPos = m_windowUnderMouse->handle()->mapFromGlobal(globalPos);
|
|
QWindowSystemInterface::handleMouseEvent(m_windowUnderMouse.data(),
|
|
QPointF(localPos), QPointF(globalPos),
|
|
QWindowsMouseHandler::queryMouseButtons(),
|
|
Qt::LeftButton, QEvent::MouseButtonRelease);
|
|
}
|
|
m_currentButtons = Qt::NoButton;
|
|
break;
|
|
|
|
default:
|
|
QGuiApplication::processEvents();
|
|
break;
|
|
}
|
|
|
|
if (QWindowsContext::verbose > 1 || result != S_OK) {
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << "fEscapePressed=" << fEscapePressed
|
|
<< "grfKeyState=" << grfKeyState << "buttons" << m_currentButtons
|
|
<< "returns 0x" << Qt::hex << int(result) << Qt::dec;
|
|
}
|
|
return ResultFromScode(result);
|
|
}
|
|
|
|
/*!
|
|
\brief Give feedback: Change cursor according to action.
|
|
*/
|
|
|
|
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
|
QWindowsOleDropSource::GiveFeedback(DWORD dwEffect)
|
|
{
|
|
const Qt::DropAction action = translateToQDragDropAction(dwEffect);
|
|
m_drag->updateAction(action);
|
|
|
|
const qint64 currentCacheKey = m_drag->currentDrag()->dragCursor(action).cacheKey();
|
|
auto it = m_cursors.constFind(action);
|
|
// If a custom drag cursor is set, check its cache key to detect changes.
|
|
if (it == m_cursors.constEnd() || (currentCacheKey && currentCacheKey != it.value().cacheKey)) {
|
|
createCursors();
|
|
it = m_cursors.constFind(action);
|
|
}
|
|
|
|
if (it != m_cursors.constEnd()) {
|
|
const CursorEntry &e = it.value();
|
|
switch (m_mode) {
|
|
case MouseDrag:
|
|
SetCursor(e.cursor->handle());
|
|
break;
|
|
case TouchDrag:
|
|
// "Touch drag" with an unsuppressed cursor may happen with RDP (see createCursors())
|
|
if (QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed)
|
|
SetCursor(nullptr);
|
|
if (!m_touchDragWindow)
|
|
m_touchDragWindow = new QWindowsDragCursorWindow;
|
|
m_touchDragWindow->setPixmap(e.pixmap);
|
|
m_touchDragWindow->setFramePosition(QCursor::pos() - e.hotSpot);
|
|
if (!m_touchDragWindow->isVisible())
|
|
m_touchDragWindow->show();
|
|
break;
|
|
}
|
|
return ResultFromScode(S_OK);
|
|
}
|
|
|
|
return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
|
|
}
|
|
|
|
/*!
|
|
\class QWindowsOleDropTarget
|
|
\brief Implementation of IDropTarget
|
|
|
|
To be registered for each window. Currently, drop sites
|
|
are enabled for top levels. The child window handling
|
|
(sending DragEnter/Leave, etc) is handled in here.
|
|
|
|
\sa QWindowsDrag
|
|
\internal
|
|
*/
|
|
|
|
QWindowsOleDropTarget::QWindowsOleDropTarget(QWindow *w) : m_window(w)
|
|
{
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << this << w;
|
|
}
|
|
|
|
QWindowsOleDropTarget::~QWindowsOleDropTarget()
|
|
{
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << this;
|
|
}
|
|
|
|
void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState,
|
|
const QPoint &point, LPDWORD pdwEffect)
|
|
{
|
|
Q_ASSERT(window);
|
|
m_lastPoint = point;
|
|
m_lastKeyState = grfKeyState;
|
|
|
|
QWindowsDrag *windowsDrag = QWindowsDrag::instance();
|
|
const Qt::DropActions actions = translateToQDragDropActions(*pdwEffect);
|
|
|
|
lastModifiers = toQtKeyboardModifiers(grfKeyState);
|
|
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
|
|
|
const QPlatformDragQtResponse response =
|
|
QWindowSystemInterface::handleDrag(window, windowsDrag->dropData(),
|
|
m_lastPoint, actions,
|
|
lastButtons, lastModifiers);
|
|
|
|
m_answerRect = response.answerRect();
|
|
const Qt::DropAction action = response.acceptedAction();
|
|
if (response.isAccepted()) {
|
|
m_chosenEffect = translateToWinDragEffects(action);
|
|
} else {
|
|
m_chosenEffect = DROPEFFECT_NONE;
|
|
}
|
|
*pdwEffect = m_chosenEffect;
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << m_window
|
|
<< windowsDrag->dropData() << " supported actions=" << actions
|
|
<< " mods=" << lastModifiers << " mouse=" << lastButtons
|
|
<< " accepted: " << response.isAccepted() << action
|
|
<< m_answerRect << " effect" << *pdwEffect;
|
|
}
|
|
|
|
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
|
QWindowsOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState,
|
|
POINTL pt, LPDWORD pdwEffect)
|
|
{
|
|
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
|
dh->DragEnter(reinterpret_cast<HWND>(m_window->winId()), pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
|
|
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << "widget=" << m_window << " key=" << grfKeyState
|
|
<< "pt=" << pt.x << pt.y;
|
|
|
|
QWindowsDrag::instance()->setDropDataObject(pDataObj);
|
|
pDataObj->AddRef();
|
|
const QPoint point = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
|
|
handleDrag(m_window, grfKeyState, point, pdwEffect);
|
|
return NOERROR;
|
|
}
|
|
|
|
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
|
QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
|
|
{
|
|
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
|
dh->DragOver(reinterpret_cast<POINT*>(&pt), *pdwEffect);
|
|
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << "m_window" << m_window << "key=" << grfKeyState
|
|
<< "pt=" << pt.x << pt.y;
|
|
const QPoint tmpPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
|
|
// see if we should compress this event
|
|
if ((tmpPoint == m_lastPoint || m_answerRect.contains(tmpPoint))
|
|
&& m_lastKeyState == grfKeyState) {
|
|
*pdwEffect = m_chosenEffect;
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << "compressed event";
|
|
return NOERROR;
|
|
}
|
|
|
|
handleDrag(m_window, grfKeyState, tmpPoint, pdwEffect);
|
|
return NOERROR;
|
|
}
|
|
|
|
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
|
QWindowsOleDropTarget::DragLeave()
|
|
{
|
|
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
|
dh->DragLeave();
|
|
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window;
|
|
|
|
const auto *keyMapper = QWindowsContext::instance()->keyMapper();
|
|
lastModifiers = keyMapper->queryKeyboardModifiers();
|
|
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
|
|
|
QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction,
|
|
Qt::NoButton, Qt::NoModifier);
|
|
|
|
if (!QDragManager::self()->source())
|
|
m_lastKeyState = 0;
|
|
QWindowsDrag::instance()->releaseDropDataObject();
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
#define KEY_STATE_BUTTON_MASK (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)
|
|
|
|
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
|
QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
|
|
POINTL pt, LPDWORD pdwEffect)
|
|
{
|
|
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
|
dh->Drop(pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
|
|
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window
|
|
<< "keys=" << grfKeyState << "pt=" << pt.x << ',' << pt.y;
|
|
|
|
m_lastPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
|
|
|
|
QWindowsDrag *windowsDrag = QWindowsDrag::instance();
|
|
|
|
lastModifiers = toQtKeyboardModifiers(grfKeyState);
|
|
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
|
|
|
const QPlatformDropQtResponse response =
|
|
QWindowSystemInterface::handleDrop(m_window, windowsDrag->dropData(),
|
|
m_lastPoint,
|
|
translateToQDragDropActions(*pdwEffect),
|
|
lastButtons,
|
|
lastModifiers);
|
|
|
|
m_lastKeyState = grfKeyState;
|
|
|
|
if (response.isAccepted()) {
|
|
const Qt::DropAction action = response.acceptedAction();
|
|
if (action == Qt::MoveAction || action == Qt::TargetMoveAction) {
|
|
if (action == Qt::MoveAction)
|
|
m_chosenEffect = DROPEFFECT_MOVE;
|
|
else
|
|
m_chosenEffect = DROPEFFECT_COPY;
|
|
HGLOBAL hData = GlobalAlloc(0, sizeof(DWORD));
|
|
if (hData) {
|
|
auto *moveEffect = reinterpret_cast<DWORD *>(GlobalLock(hData));
|
|
*moveEffect = DROPEFFECT_MOVE;
|
|
GlobalUnlock(hData);
|
|
STGMEDIUM medium;
|
|
memset(&medium, 0, sizeof(STGMEDIUM));
|
|
medium.tymed = TYMED_HGLOBAL;
|
|
medium.hGlobal = hData;
|
|
FORMATETC format;
|
|
format.cfFormat = CLIPFORMAT(RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT));
|
|
format.tymed = TYMED_HGLOBAL;
|
|
format.ptd = nullptr;
|
|
format.dwAspect = 1;
|
|
format.lindex = -1;
|
|
windowsDrag->dropDataObject()->SetData(&format, &medium, true);
|
|
}
|
|
} else {
|
|
m_chosenEffect = translateToWinDragEffects(action);
|
|
}
|
|
} else {
|
|
m_chosenEffect = DROPEFFECT_NONE;
|
|
}
|
|
*pdwEffect = m_chosenEffect;
|
|
|
|
windowsDrag->releaseDropDataObject();
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
/*!
|
|
\class QWindowsDrag
|
|
\brief Windows drag implementation.
|
|
\internal
|
|
*/
|
|
|
|
bool QWindowsDrag::m_canceled = false;
|
|
|
|
QWindowsDrag::QWindowsDrag() = default;
|
|
|
|
QWindowsDrag::~QWindowsDrag()
|
|
{
|
|
if (m_cachedDropTargetHelper)
|
|
m_cachedDropTargetHelper->Release();
|
|
}
|
|
|
|
/*!
|
|
\brief Return data for a drop in process. If it stems from a current drag, use a shortcut.
|
|
*/
|
|
|
|
QMimeData *QWindowsDrag::dropData()
|
|
{
|
|
if (const QDrag *drag = currentDrag())
|
|
return drag->mimeData();
|
|
return &m_dropData;
|
|
}
|
|
|
|
/*!
|
|
\brief May be used to handle extended cursors functionality for drags from outside the app.
|
|
*/
|
|
IDropTargetHelper* QWindowsDrag::dropHelper() {
|
|
if (!m_cachedDropTargetHelper) {
|
|
CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
|
|
IID_IDropTargetHelper,
|
|
reinterpret_cast<void**>(&m_cachedDropTargetHelper));
|
|
}
|
|
return m_cachedDropTargetHelper;
|
|
}
|
|
|
|
// Workaround for DoDragDrop() not working with touch/pen input, causing DnD to hang until the mouse is moved.
|
|
// We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop()
|
|
static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
|
|
{
|
|
QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse();
|
|
const bool hasMouseCapture = underMouse && static_cast<QWindowsWindow *>(underMouse->handle())->hasMouseCapture();
|
|
const HWND hwnd = hasMouseCapture ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus();
|
|
bool starting = false;
|
|
|
|
for (;;) {
|
|
MSG msg{};
|
|
if (::GetMessage(&msg, hwnd, 0, 0) > 0) {
|
|
|
|
if (msg.message == WM_MOUSEMOVE) {
|
|
|
|
// Only consider the first simulated event, or actual mouse messages.
|
|
if (!starting && (msg.wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0)
|
|
return E_FAIL;
|
|
|
|
return ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
|
|
}
|
|
|
|
if (msg.message == WM_POINTERUPDATE) {
|
|
|
|
const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
|
|
|
|
POINTER_INFO pointerInfo{};
|
|
BOOL bResultPointerInfo = QWindowsContext::user32dll.getPointerInfo ?
|
|
QWindowsContext::user32dll.getPointerInfo(pointerId, &pointerInfo) :
|
|
vxkex::GetPointerInfo(pointerId, &pointerInfo);
|
|
|
|
if (!bResultPointerInfo)
|
|
return E_FAIL;
|
|
|
|
if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
|
|
|
|
DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_MOVE;
|
|
if (IS_POINTER_FIRSTBUTTON_WPARAM(msg.wParam))
|
|
flags |= MOUSEEVENTF_LEFTDOWN;
|
|
if (IS_POINTER_SECONDBUTTON_WPARAM(msg.wParam))
|
|
flags |= MOUSEEVENTF_RIGHTDOWN;
|
|
if (IS_POINTER_THIRDBUTTON_WPARAM(msg.wParam))
|
|
flags |= MOUSEEVENTF_MIDDLEDOWN;
|
|
|
|
if (!starting) {
|
|
POINT pt{};
|
|
if (::GetCursorPos(&pt)) {
|
|
|
|
// Send mouse input that can generate a WM_MOUSEMOVE message.
|
|
if ((flags & MOUSEEVENTF_LEFTDOWN || flags & MOUSEEVENTF_RIGHTDOWN || flags & MOUSEEVENTF_MIDDLEDOWN)
|
|
&& (pt.x != pointerInfo.ptPixelLocation.x || pt.y != pointerInfo.ptPixelLocation.y)) {
|
|
|
|
const int origin_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
const int origin_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
const int virt_w = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
const int virt_h = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
const int virt_x = pointerInfo.ptPixelLocation.x - origin_x;
|
|
const int virt_y = pointerInfo.ptPixelLocation.y - origin_y;
|
|
|
|
INPUT input{};
|
|
input.type = INPUT_MOUSE;
|
|
input.mi.dx = static_cast<DWORD>(virt_x * (65535.0 / virt_w));
|
|
input.mi.dy = static_cast<DWORD>(virt_y * (65535.0 / virt_h));
|
|
input.mi.dwFlags = flags;
|
|
|
|
::SendInput(1, &input, sizeof(input));
|
|
starting = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Handle other messages.
|
|
qWindowsWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
|
|
|
|
if (msg.message == WM_POINTERLEAVE)
|
|
return E_FAIL;
|
|
}
|
|
} else {
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
Qt::DropAction QWindowsDrag::drag(QDrag *drag)
|
|
{
|
|
// TODO: Accessibility handling?
|
|
QMimeData *dropData = drag->mimeData();
|
|
Qt::DropAction dragResult = Qt::IgnoreAction;
|
|
|
|
DWORD resultEffect;
|
|
QWindowsDrag::m_canceled = false;
|
|
auto *windowDropSource = new QWindowsOleDropSource(this);
|
|
windowDropSource->createCursors();
|
|
auto *dropDataObject = new QWindowsDropDataObject(dropData);
|
|
const Qt::DropActions possibleActions = drag->supportedActions();
|
|
const DWORD allowedEffects = translateToWinDragEffects(possibleActions);
|
|
qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x"
|
|
<< Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec;
|
|
const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
|
|
const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect();
|
|
if (r == DRAGDROP_S_DROP) {
|
|
if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) {
|
|
dragResult = Qt::TargetMoveAction;
|
|
resultEffect = DROPEFFECT_MOVE;
|
|
} else {
|
|
dragResult = translateToQDragDropAction(resultEffect);
|
|
}
|
|
// Force it to be a copy if an unsupported operation occurred.
|
|
// This indicates a bug in the drop target.
|
|
if (resultEffect != DROPEFFECT_NONE && !(resultEffect & allowedEffects)) {
|
|
qWarning("%s: Forcing Qt::CopyAction", __FUNCTION__);
|
|
dragResult = Qt::CopyAction;
|
|
}
|
|
}
|
|
// clean up
|
|
dropDataObject->releaseQt();
|
|
dropDataObject->Release(); // Will delete obj if refcount becomes 0
|
|
windowDropSource->Release(); // Will delete src if refcount becomes 0
|
|
qCDebug(lcQpaMime) << '<' << __FUNCTION__ << Qt::hex << "allowedEffects=0x" << allowedEffects
|
|
<< "reportedPerformedEffect=0x" << reportedPerformedEffect
|
|
<< " resultEffect=0x" << resultEffect << "hr=0x" << int(r) << Qt::dec << "dropAction=" << dragResult;
|
|
return dragResult;
|
|
}
|
|
|
|
QWindowsDrag *QWindowsDrag::instance()
|
|
{
|
|
return static_cast<QWindowsDrag *>(QWindowsIntegration::instance()->drag());
|
|
}
|
|
|
|
void QWindowsDrag::releaseDropDataObject()
|
|
{
|
|
qCDebug(lcQpaMime) << __FUNCTION__ << m_dropDataObject;
|
|
if (m_dropDataObject) {
|
|
m_dropDataObject->Release();
|
|
m_dropDataObject = nullptr;
|
|
}
|
|
}
|
|
|
|
QT_END_NAMESPACE
|