blob: 3e4c93d47a470ed98e3dcfa66f78ad0f0519c389 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qwindowsdrag.h"
#include "qwindowscontext.h"
#include "qwindowsscreen.h"
#if QT_CONFIG(clipboard)
# include "qwindowsclipboard.h"
#endif
#include "qwindowsintegration.h"
#include "qwindowsdropdataobject.h"
#include <QtCore/qt_windows.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 <shlobj.h>
QT_BEGIN_NAMESPACE
/*!
\class QWindowsDragCursorWindow
\brief A toplevel window showing the drag icon in case of touch drag.
\sa QWindowsOleDropSource
\internal
\ingroup qt-lighthouse-win
*/
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
\ingroup qt-lighthouse-win
*/
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 inline Qt::MouseButtons toQtMouseButtons(DWORD keyState)
{
Qt::MouseButtons buttons = Qt::NoButton;
if (keyState & MK_LBUTTON)
buttons |= Qt::LeftButton;
if (keyState & MK_RBUTTON)
buttons |= Qt::RightButton;
if (keyState & MK_MBUTTON)
buttons |= Qt::MidButton;
return buttons;
}
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
\ingroup qt-lighthouse-win
*/
class QWindowsOleDropSource : public QWindowsComBase<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);
STDMETHOD(GiveFeedback)(DWORD dwEffect);
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();
}
Q_ASSERT(platformScreen);
QPlatformCursor *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 = 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)
{
Qt::MouseButtons buttons = toQtMouseButtons(grfKeyState);
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 accoding 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
\ingroup qt-lighthouse-win
*/
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 = toQtMouseButtons(grfKeyState);
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;
lastModifiers = QWindowsKeyMapper::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 = toQtMouseButtons(grfKeyState);
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
\ingroup qt-lighthouse-win
*/
bool QWindowsDrag::m_canceled = false;
bool QWindowsDrag::m_dragging = 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;
}
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;
// Indicate message handlers we are in DoDragDrop() event loop.
QWindowsDrag::m_dragging = true;
const HRESULT r = DoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
QWindowsDrag::m_dragging = false;
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