/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module 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 "qxcbdrag.h"
#include <xcb/xcb.h>
#include "qxcbconnection.h"
#include "qxcbclipboard.h"
#include "qxcbmime.h"
#include "qxcbwindow.h"
#include "qxcbscreen.h"
#include "qwindow.h"
#include "qxcbcursor.h"
#include <private/qdnd_p.h>
#include <qdebug.h>
#include <qevent.h>
#include <qguiapplication.h>
#include <qrect.h>
#include <qpainter.h>
#include <qtimer.h>

#include <qpa/qwindowsysteminterface.h>

#include <private/qguiapplication_p.h>
#include <private/qshapedpixmapdndwindow_p.h>
#include <private/qsimpledrag_p.h>
#include <private/qhighdpiscaling_p.h>

QT_BEGIN_NAMESPACE

const int xdnd_version = 5;

static inline xcb_window_t xcb_window(QPlatformWindow *w)
{
    return static_cast<QXcbWindow *>(w)->xcb_window();
}

static inline xcb_window_t xcb_window(QWindow *w)
{
    return static_cast<QXcbWindow *>(w->handle())->xcb_window();
}

static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
{
    xcb_window_t proxy = XCB_NONE;

    auto reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
                             false, w, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);

    if (reply && reply->type == XCB_ATOM_WINDOW)
        proxy = *((xcb_window_t *)xcb_get_property_value(reply.get()));

    if (proxy == XCB_NONE)
        return proxy;

    // exists and is real?
    reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
                        false, proxy, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);

    if (reply && reply->type == XCB_ATOM_WINDOW) {
        xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply.get()));
        if (proxy != p)
            proxy = 0;
    } else {
        proxy = 0;
    }

    return proxy;
}

class QXcbDropData : public QXcbMime
{
public:
    QXcbDropData(QXcbDrag *d);
    ~QXcbDropData();

protected:
    bool hasFormat_sys(const QString &mimeType) const override;
    QStringList formats_sys() const override;
    QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const override;

    QVariant xdndObtainData(const QByteArray &format, QMetaType::Type requestedType) const;

    QXcbDrag *drag;
};


QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c)
{
    m_dropData = new QXcbDropData(this);

    init();
    cleanup_timer = -1;
}

QXcbDrag::~QXcbDrag()
{
    delete m_dropData;
}

void QXcbDrag::init()
{
    currentWindow.clear();

    accepted_drop_action = Qt::IgnoreAction;

    xdnd_dragsource = XCB_NONE;

    waiting_for_status = false;
    current_target = XCB_NONE;
    current_proxy_target = XCB_NONE;

    source_time = XCB_CURRENT_TIME;
    target_time = XCB_CURRENT_TIME;

    QXcbCursor::queryPointer(connection(), &current_virtual_desktop, nullptr);
    drag_types.clear();

    //current_embedding_widget = 0;

    dropped = false;
    canceled = false;

    source_sameanswer = QRect();
}

bool QXcbDrag::eventFilter(QObject *o, QEvent *e)
{
    /* We are setting a mouse grab on the QShapedPixmapWindow in order not to
     * lose the grab when the virtual desktop changes, but
     * QBasicDrag::eventFilter() expects the events to be coming from the
     * window where the drag was started. */
    if (initiatorWindow && o == shapedPixmapWindow())
        o = initiatorWindow.data();
    return QBasicDrag::eventFilter(o, e);
}

void QXcbDrag::startDrag()
{
    init();

#ifndef QT_NO_CLIPBOARD
    qCDebug(lcQpaXDnd) << "starting drag where source:" << connection()->clipboard()->owner();
    xcb_set_selection_owner(xcb_connection(), connection()->clipboard()->owner(),
                            atom(QXcbAtom::XdndSelection), connection()->time());
#endif

    QStringList fmts = QXcbMime::formatsHelper(drag()->mimeData());
    for (int i = 0; i < fmts.size(); ++i) {
        QVector<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection(), fmts.at(i));
        for (int j = 0; j < atoms.size(); ++j) {
            if (!drag_types.contains(atoms.at(j)))
                drag_types.append(atoms.at(j));
        }
    }
#ifndef QT_NO_CLIPBOARD
    if (drag_types.size() > 3)
        xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
                            atom(QXcbAtom::XdndTypelist),
                            XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData());
#endif

    setUseCompositing(current_virtual_desktop->compositingActive());
    setScreen(current_virtual_desktop->screens().constFirst()->screen());
    initiatorWindow = QGuiApplicationPrivate::currentMouseWindow;
    QBasicDrag::startDrag();
    if (connection()->mouseGrabber() == nullptr)
        shapedPixmapWindow()->setMouseGrabEnabled(true);

    auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), initiatorWindow.data());
    move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
}

void QXcbDrag::endDrag()
{
    QBasicDrag::endDrag();
    if (!dropped && !canceled && canDrop()) {
        // Set executed drop action when dropping outside application.
        setExecutedDropAction(accepted_drop_action);
    }
    initiatorWindow.clear();
}

Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const
{
    if (currentDrag() || drop_actions.isEmpty())
        return QBasicDrag::defaultAction(possibleActions, modifiers);

    return toDropAction(drop_actions.first());
}

void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
{
    if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::XdndActionList))
        return;

    readActionList();
}

static
bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType)
{
    bool interacts = false;
    auto reply = Q_XCB_REPLY(xcb_shape_get_rectangles, connection, w, shapeType);
    if (reply) {
        xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(reply.get());
        if (rectangles) {
            const int nRectangles = xcb_shape_get_rectangles_rectangles_length(reply.get());
            for (int i = 0; !interacts && i < nRectangles; ++i) {
                interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(pos);
            }
        }
    }

    return interacts;
}

xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md, bool ignoreNonXdndAwareWindows)
{
    if (w == shapedPixmapWindow()->handle()->winId())
        return 0;

    if (md) {
        auto reply = Q_XCB_REPLY(xcb_get_window_attributes, xcb_connection(), w);
        if (!reply)
            return 0;

        if (reply->map_state != XCB_MAP_STATE_VIEWABLE)
            return 0;

        auto greply = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), w);
        if (!greply)
            return 0;

        QRect windowRect(greply->x, greply->y, greply->width, greply->height);
        if (windowRect.contains(pos)) {
            bool windowContainsMouse = !ignoreNonXdndAwareWindows;
            {
                auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
                                         false, w, connection()->atom(QXcbAtom::XdndAware),
                                         XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
                bool isAware = reply && reply->type != XCB_NONE;
                if (isAware) {
                    const QPoint relPos = pos - windowRect.topLeft();
                    // When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we
                    // need to check both here so that in the case one is set and the other is not we still get the correct result.
                    if (connection()->hasInputShape())
                        windowContainsMouse = windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_INPUT);
                    if (windowContainsMouse && connection()->hasXShape())
                        windowContainsMouse = windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_BOUNDING);
                    if (!connection()->hasInputShape() && !connection()->hasXShape())
                        windowContainsMouse = true;
                    if (windowContainsMouse)
                        return w;
                }
            }

            auto reply = Q_XCB_REPLY(xcb_query_tree, xcb_connection(), w);
            if (!reply)
                return 0;
            int nc = xcb_query_tree_children_length(reply.get());
            xcb_window_t *c = xcb_query_tree_children(reply.get());

            xcb_window_t r = 0;
            for (uint i = nc; !r && i--;)
                r = findRealWindow(pos - windowRect.topLeft(), c[i], md-1, ignoreNonXdndAwareWindows);

            if (r)
                return r;

            // We didn't find a client window!  Just use the
            // innermost window.

            // No children!
            if (!windowContainsMouse)
                return 0;
            else
                return w;
        }
    }
    return 0;
}

bool QXcbDrag::findXdndAwareTarget(const QPoint &globalPos, xcb_window_t *target_out)
{
    xcb_window_t rootwin = current_virtual_desktop->root();
    auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
                                 rootwin, rootwin, globalPos.x(), globalPos.y());
    if (!translate)
        return false;

    xcb_window_t target = translate->child;
    int lx = translate->dst_x;
    int ly = translate->dst_y;

    if (target && target != rootwin) {
        xcb_window_t src = rootwin;
        while (target != 0) {
            qCDebug(lcQpaXDnd) << "checking target for XdndAware" << target;

            auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
                                         src, target, lx, ly);
            if (!translate) {
                target = 0;
                break;
            }
            lx = translate->dst_x;
            ly = translate->dst_y;
            src = target;
            xcb_window_t child = translate->child;

            auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, target,
                                     atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
            bool aware = reply && reply->type != XCB_NONE;
            if (aware) {
                qCDebug(lcQpaXDnd) << "found XdndAware on" << target;
                break;
            }

            target = child;
        }

        if (!target || target == shapedPixmapWindow()->handle()->winId()) {
            qCDebug(lcQpaXDnd) << "need to find real window";
            target = findRealWindow(globalPos, rootwin, 6, true);
            if (target == 0)
                target = findRealWindow(globalPos, rootwin, 6, false);
            qCDebug(lcQpaXDnd) << "real window found" << target;
        }
    }

    *target_out = target;
    return true;
}

void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
{
    // currentDrag() might be deleted while 'drag' is progressing
    if (!currentDrag()) {
        cancel();
        return;
    }
    // The source sends XdndEnter and XdndPosition to the target.
    if (source_sameanswer.contains(globalPos) && source_sameanswer.isValid())
        return;

    QXcbVirtualDesktop *virtualDesktop = nullptr;
    QPoint cursorPos;
    QXcbCursor::queryPointer(connection(), &virtualDesktop, &cursorPos);
    QXcbScreen *screen = virtualDesktop->screenAt(cursorPos);
    QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(globalPos, screen);

    if (virtualDesktop != current_virtual_desktop) {
        setUseCompositing(virtualDesktop->compositingActive());
        recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos);
        if (connection()->mouseGrabber() == nullptr)
            shapedPixmapWindow()->setMouseGrabEnabled(true);

        current_virtual_desktop = virtualDesktop;
    } else {
        QBasicDrag::moveShapedPixmapWindow(deviceIndependentPos);
    }

    xcb_window_t target;
    if (!findXdndAwareTarget(globalPos, &target))
        return;

    QXcbWindow *w = nullptr;
    if (target) {
        w = connection()->platformWindowFromId(target);
        if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
            w = nullptr;
    } else {
        w = nullptr;
        target = current_virtual_desktop->root();
    }

    xcb_window_t proxy_target = xdndProxy(connection(), target);
    if (!proxy_target)
        proxy_target = target;
    int target_version = 1;

    if (proxy_target) {
        auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
                                 false, proxy_target,
                                 atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
        if (!reply || reply->type == XCB_NONE) {
            target = 0;
        } else {
            target_version = *(uint32_t *)xcb_get_property_value(reply.get());
            target_version = qMin(xdnd_version, target_version ? target_version : 1);
        }
    }

    if (target != current_target) {
        if (current_target)
            send_leave();

        current_target = target;
        current_proxy_target = proxy_target;
        if (target) {
            int flags = target_version << 24;
            if (drag_types.size() > 3)
                flags |= 0x0001;

            xcb_client_message_event_t enter;
            enter.response_type = XCB_CLIENT_MESSAGE;
            enter.sequence = 0;
            enter.window = target;
            enter.format = 32;
            enter.type = atom(QXcbAtom::XdndEnter);
#ifndef QT_NO_CLIPBOARD
            enter.data.data32[0] = connection()->clipboard()->owner();
#else
            enter.data.data32[0] = 0;
#endif
            enter.data.data32[1] = flags;
            enter.data.data32[2] = drag_types.size() > 0 ? drag_types.at(0) : 0;
            enter.data.data32[3] = drag_types.size() > 1 ? drag_types.at(1) : 0;
            enter.data.data32[4] = drag_types.size() > 2 ? drag_types.at(2) : 0;
            // provisionally set the rectangle to 5x5 pixels...
            source_sameanswer = QRect(globalPos.x() - 2, globalPos.y() - 2 , 5, 5);

            qCDebug(lcQpaXDnd) << "sending XdndEnter to target:" << target;

            if (w)
                handleEnter(w, &enter, current_proxy_target);
            else if (target)
                xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&enter);
            waiting_for_status = false;
        }
    }

    if (waiting_for_status)
        return;

    if (target) {
        waiting_for_status = true;
        // The source sends a ClientMessage of type XdndPosition. This tells the target the
        // position of the mouse and the action that the user requested.
        xcb_client_message_event_t move;
        move.response_type = XCB_CLIENT_MESSAGE;
        move.sequence = 0;
        move.window = target;
        move.format = 32;
        move.type = atom(QXcbAtom::XdndPosition);
#ifndef QT_NO_CLIPBOARD
        move.data.data32[0] = connection()->clipboard()->owner();
#else
        move.data.data32[0] = 0;
#endif
        move.data.data32[1] = 0; // flags
        move.data.data32[2] = (globalPos.x() << 16) + globalPos.y();
        move.data.data32[3] = connection()->time();
        const auto supportedActions = currentDrag()->supportedActions();
        const auto requestedAction = defaultAction(supportedActions, mods);
        move.data.data32[4] = toXdndAction(requestedAction);

        qCDebug(lcQpaXDnd) << "sending XdndPosition to target:" << target;

        source_time = connection()->time();

        if (w) {
            handle_xdnd_position(w, &move, b, mods);
        } else {
            setActionList(requestedAction, supportedActions);
            xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&move);
        }
    }

    static const bool isUnity = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "unity";
    if (isUnity && xdndCollectionWindow == XCB_NONE) {
        QString name = QXcbWindow::windowTitle(connection(), target);
        if (name == QStringLiteral("XdndCollectionWindowImp"))
            xdndCollectionWindow = target;
    }
    if (target == xdndCollectionWindow) {
        setCanDrop(false);
        updateCursor(Qt::IgnoreAction);
    }
}

void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
{
    // XdndDrop is sent from source to target to complete the drop.
    QBasicDrag::drop(globalPos, b, mods);

    if (!current_target)
        return;

    xcb_client_message_event_t drop;
    drop.response_type = XCB_CLIENT_MESSAGE;
    drop.sequence = 0;
    drop.window = current_target;
    drop.format = 32;
    drop.type = atom(QXcbAtom::XdndDrop);
#ifndef QT_NO_CLIPBOARD
    drop.data.data32[0] = connection()->clipboard()->owner();
#else
    drop.data.data32[0] = 0;
#endif
    drop.data.data32[1] = 0; // flags
    drop.data.data32[2] = connection()->time();

    drop.data.data32[3] = 0;
    drop.data.data32[4] = currentDrag()->supportedActions();

    QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target);

    if (w && w->window()->type() == Qt::Desktop) // && !w->acceptDrops()
        w = nullptr;

    Transaction t = {
        connection()->time(),
        current_target,
        current_proxy_target,
        w,
//        current_embeddig_widget,
        currentDrag(),
        QTime::currentTime()
    };
    transactions.append(t);

    // timer is needed only for drops that came from other processes.
    if (!t.targetWindow && cleanup_timer == -1) {
        cleanup_timer = startTimer(XdndDropTransactionTimeout);
    }

    qCDebug(lcQpaXDnd) << "sending drop to target:" << current_target;

    if (w) {
        handleDrop(w, &drop, b, mods);
    } else {
        xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&drop);
    }
}

Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
{
    if (a == atom(QXcbAtom::XdndActionCopy) || a == 0)
        return Qt::CopyAction;
    if (a == atom(QXcbAtom::XdndActionLink))
        return Qt::LinkAction;
    if (a == atom(QXcbAtom::XdndActionMove))
        return Qt::MoveAction;
    return Qt::CopyAction;
}

Qt::DropActions QXcbDrag::toDropActions(const QVector<xcb_atom_t> &atoms) const
{
    Qt::DropActions actions;
    for (const auto actionAtom : atoms) {
        if (actionAtom != atom(QXcbAtom::XdndActionAsk))
            actions |= toDropAction(actionAtom);
    }
    return actions;
}

xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
{
    switch (a) {
    case Qt::CopyAction:
        return atom(QXcbAtom::XdndActionCopy);
    case Qt::LinkAction:
        return atom(QXcbAtom::XdndActionLink);
    case Qt::MoveAction:
    case Qt::TargetMoveAction:
        return atom(QXcbAtom::XdndActionMove);
    case Qt::IgnoreAction:
        return XCB_NONE;
    default:
        return atom(QXcbAtom::XdndActionCopy);
    }
}

void QXcbDrag::readActionList()
{
    drop_actions.clear();
    auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
                             atom(QXcbAtom::XdndActionList), XCB_ATOM_ATOM,
                             0, 1024);
    if (reply && reply->type != XCB_NONE && reply->format == 32) {
        int length = xcb_get_property_value_length(reply.get()) / 4;

        xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get());
        for (int i = 0; i < length; ++i)
            drop_actions.append(atoms[i]);
    }
}

void QXcbDrag::setActionList(Qt::DropAction requestedAction, Qt::DropActions supportedActions)
{
#ifndef QT_NO_CLIPBOARD
    QVector<xcb_atom_t> actions;
    if (requestedAction != Qt::IgnoreAction)
        actions.append(toXdndAction(requestedAction));

    auto checkAppend = [this, requestedAction, supportedActions, &actions](Qt::DropAction action) {
        if (requestedAction != action && supportedActions & action)
            actions.append(toXdndAction(action));
    };

    checkAppend(Qt::CopyAction);
    checkAppend(Qt::MoveAction);
    checkAppend(Qt::LinkAction);

    if (current_actions != actions) {
        xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
                            atom(QXcbAtom::XdndActionList),
                            XCB_ATOM_ATOM, 32, actions.size(), actions.constData());
        current_actions = actions;
    }
#endif
}

void QXcbDrag::startListeningForActionListChanges()
{
    connection()->addWindowEventListener(xdnd_dragsource, this);
    const uint32_t event_mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
    xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);
}

void QXcbDrag::stopListeningForActionListChanges()
{
    const uint32_t event_mask[] = { XCB_EVENT_MASK_NO_EVENT };
    xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);
    connection()->removeWindowEventListener(xdnd_dragsource);
}

int QXcbDrag::findTransactionByWindow(xcb_window_t window)
{
    int at = -1;
    for (int i = 0; i < transactions.count(); ++i) {
        const Transaction &t = transactions.at(i);
        if (t.target == window || t.proxy_target == window) {
            at = i;
            break;
        }
    }
    return at;
}

int QXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp)
{
    int at = -1;
    for (int i = 0; i < transactions.count(); ++i) {
        const Transaction &t = transactions.at(i);
        if (t.timestamp == timestamp) {
            at = i;
            break;
        }
    }
    return at;
}

#if 0
// for embedding only
static QWidget* current_embedding_widget  = 0;
static xcb_client_message_event_t last_enter_event;


static bool checkEmbedded(QWidget* w, const XEvent* xe)
{
    if (!w)
        return false;

    if (current_embedding_widget != 0 && current_embedding_widget != w) {
        current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy;
        current_proxy_target = current_target;
        qt_xdnd_send_leave();
        current_target = 0;
        current_proxy_target = 0;
        current_embedding_widget = 0;
    }

    QWExtra* extra = ((QExtraWidget*)w)->extraData();
    if (extra && extra->xDndProxy != 0) {

        if (current_embedding_widget != w) {

            last_enter_event.xany.window = extra->xDndProxy;
            XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event);
            current_embedding_widget = w;
        }

        ((XEvent*)xe)->xany.window = extra->xDndProxy;
        XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe);
        if (currentWindow != w) {
            currentWindow = w;
        }
        return true;
    }
    current_embedding_widget = 0;
    return false;
}
#endif

void QXcbDrag::handleEnter(QPlatformWindow *, const xcb_client_message_event_t *event, xcb_window_t proxy)
{
    // The target receives XdndEnter.
    qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndEnter";

    xdnd_types.clear();

    int version = (int)(event->data.data32[1] >> 24);
    if (version > xdnd_version)
        return;

    xdnd_dragsource = event->data.data32[0];
    startListeningForActionListChanges();
    readActionList();

    if (!proxy)
        proxy = xdndProxy(connection(), xdnd_dragsource);
    current_proxy_target = proxy ? proxy : xdnd_dragsource;

    if (event->data.data32[1] & 1) {
        // get the types from XdndTypeList
        auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
                                 atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM,
                                 0, xdnd_max_type);
        if (reply && reply->type != XCB_NONE && reply->format == 32) {
            int length = xcb_get_property_value_length(reply.get()) / 4;
            if (length > xdnd_max_type)
                length = xdnd_max_type;

            xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get());
            xdnd_types.reserve(length);
            for (int i = 0; i < length; ++i)
                xdnd_types.append(atoms[i]);
        }
    } else {
        // get the types from the message
        for(int i = 2; i < 5; i++) {
            if (event->data.data32[i])
                xdnd_types.append(event->data.data32[i]);
        }
    }
    for(int i = 0; i < xdnd_types.length(); ++i)
        qCDebug(lcQpaXDnd) << "    " << connection()->atomName(xdnd_types.at(i));
}

void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message_event_t *e,
                                    Qt::MouseButtons b, Qt::KeyboardModifiers mods)
{
    // The target receives XdndPosition. The target window must determine which widget the mouse
    // is in and ask it whether or not it will accept the drop.
    qCDebug(lcQpaXDnd) << "target:" << e->window << "received XdndPosition";

    QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff);
    Q_ASSERT(w);
    QRect geometry = w->geometry();
    p -= geometry.topLeft();

    if (!w || !w->window() || (w->window()->type() == Qt::Desktop))
        return;

    if (Q_UNLIKELY(e->data.data32[0] != xdnd_dragsource)) {
        qCDebug(lcQpaXDnd, "xdnd drag position from unexpected source (%x not %x)",
                e->data.data32[0], xdnd_dragsource);
        return;
    }

    currentPosition = p;
    currentWindow = w->window();

    // timestamp from the source
    if (e->data.data32[3] != XCB_NONE) {
        target_time = e->data.data32[3];
    }

    QMimeData *dropData = nullptr;
    Qt::DropActions supported_actions = Qt::IgnoreAction;
    if (currentDrag()) {
        dropData = currentDrag()->mimeData();
        supported_actions = currentDrag()->supportedActions();
    } else {
        dropData = m_dropData;
        supported_actions = toDropActions(drop_actions);
        if (e->data.data32[4] != atom(QXcbAtom::XdndActionAsk))
            supported_actions |= Qt::DropActions(toDropAction(e->data.data32[4]));
    }

    auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
    auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();

    QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
                w->window(), dropData, p, supported_actions, buttons, modifiers);

    // ### FIXME ? - answerRect appears to be unused.
    QRect answerRect(p + geometry.topLeft(), QSize(1,1));
    answerRect = qt_response.answerRect().translated(geometry.topLeft()).intersected(geometry);

    // The target sends a ClientMessage of type XdndStatus. This tells the source whether or not
    // it will accept the drop, and, if so, what action will be taken. It also includes a rectangle
    // that means "don't send another XdndPosition message until the mouse moves out of here".
    xcb_client_message_event_t response;
    response.response_type = XCB_CLIENT_MESSAGE;
    response.sequence = 0;
    response.window = xdnd_dragsource;
    response.format = 32;
    response.type = atom(QXcbAtom::XdndStatus);
    response.data.data32[0] = xcb_window(w);
    response.data.data32[1] = qt_response.isAccepted(); // flags
    response.data.data32[2] = 0; // x, y
    response.data.data32[3] = 0; // w, h
    response.data.data32[4] = toXdndAction(qt_response.acceptedAction()); // action

    accepted_drop_action = qt_response.acceptedAction();

    if (answerRect.left() < 0)
        answerRect.setLeft(0);
    if (answerRect.right() > 4096)
        answerRect.setRight(4096);
    if (answerRect.top() < 0)
        answerRect.setTop(0);
    if (answerRect.bottom() > 4096)
        answerRect.setBottom(4096);
    if (answerRect.width() < 0)
        answerRect.setWidth(0);
    if (answerRect.height() < 0)
        answerRect.setHeight(0);

    // reset
    target_time = XCB_CURRENT_TIME;

    qCDebug(lcQpaXDnd) << "sending XdndStatus to source:" << xdnd_dragsource;

#ifndef QT_NO_CLIPBOARD
    if (xdnd_dragsource == connection()->clipboard()->owner())
        handle_xdnd_status(&response);
    else
#endif
        xcb_send_event(xcb_connection(), false, current_proxy_target,
                       XCB_EVENT_MASK_NO_EVENT, (const char *)&response);
}

namespace
{
    class ClientMessageScanner {
    public:
        ClientMessageScanner(xcb_atom_t a) : atom(a) {}
        xcb_atom_t atom;
        bool operator() (xcb_generic_event_t *event, int type) const {
            if (type != XCB_CLIENT_MESSAGE)
                return false;
            auto clientMessage = reinterpret_cast<xcb_client_message_event_t *>(event);
            return clientMessage->type == atom;
        }
    };
}

void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_event_t *event)
{
    xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
    ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition));
    while (auto nextEvent = connection()->eventQueue()->peek(scanner)) {
        if (lastEvent != event)
            free(lastEvent);
        lastEvent = reinterpret_cast<xcb_client_message_event_t *>(nextEvent);
    }

    handle_xdnd_position(w, lastEvent);
    if (lastEvent != event)
        free(lastEvent);
}

void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event)
{
    // The source receives XdndStatus. It can use the action to change the cursor to indicate
    // whether or not the user's requested action will be performed.
    qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndStatus";
    waiting_for_status = false;
    // ignore late status messages
    if (event->data.data32[0] && event->data.data32[0] != current_target)
        return;

    const bool dropPossible = event->data.data32[1];
    setCanDrop(dropPossible);

    if (dropPossible) {
        accepted_drop_action = toDropAction(event->data.data32[4]);
        updateCursor(accepted_drop_action);
    } else {
        updateCursor(Qt::IgnoreAction);
    }

    if ((event->data.data32[1] & 2) == 0) {
        QPoint p((event->data.data32[2] & 0xffff0000) >> 16, event->data.data32[2] & 0x0000ffff);
        QSize s((event->data.data32[3] & 0xffff0000) >> 16, event->data.data32[3] & 0x0000ffff);
        source_sameanswer = QRect(p, s);
    } else {
        source_sameanswer = QRect();
    }
}

void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
{
    if (
#ifndef QT_NO_CLIPBOARD
            event->window != connection()->clipboard()->owner() ||
#endif
            !drag())
        return;

    xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
    xcb_generic_event_t *nextEvent;
    ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus));
    while ((nextEvent = connection()->eventQueue()->peek(scanner))) {
        if (lastEvent != event)
            free(lastEvent);
        lastEvent = (xcb_client_message_event_t *)nextEvent;
    }

    handle_xdnd_status(lastEvent);
    if (lastEvent != event)
        free(lastEvent);
}

void QXcbDrag::handleLeave(QPlatformWindow *w, const xcb_client_message_event_t *event)
{
    // If the target receives XdndLeave, it frees any cached data and forgets the whole incident.
    qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndLeave";

    if (!currentWindow || w != currentWindow.data()->handle()) {
        stopListeningForActionListChanges();
        return; // sanity
    }

    // ###
//    if (checkEmbedded(current_embedding_widget, event)) {
//        current_embedding_widget = 0;
//        currentWindow.clear();
//        return;
//    }

    if (event->data.data32[0] != xdnd_dragsource) {
        // This often happens - leave other-process window quickly
        qCDebug(lcQpaXDnd, "xdnd drag leave from unexpected source (%x not %x",
                event->data.data32[0], xdnd_dragsource);
    }

    stopListeningForActionListChanges();

    QWindowSystemInterface::handleDrag(w->window(), nullptr, QPoint(), Qt::IgnoreAction, { }, { });
}

void QXcbDrag::send_leave()
{
    // XdndLeave is sent from the source to the target to cancel the drop.
    if (!current_target)
        return;

    xcb_client_message_event_t leave;
    leave.response_type = XCB_CLIENT_MESSAGE;
    leave.sequence = 0;
    leave.window = current_target;
    leave.format = 32;
    leave.type = atom(QXcbAtom::XdndLeave);
#ifndef QT_NO_CLIPBOARD
    leave.data.data32[0] = connection()->clipboard()->owner();
#else
    leave.data.data32[0] = 0;
#endif
    leave.data.data32[1] = 0; // flags
    leave.data.data32[2] = 0; // x, y
    leave.data.data32[3] = 0; // w, h
    leave.data.data32[4] = 0; // just null

    QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target);

    if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
        w = nullptr;

    qCDebug(lcQpaXDnd) << "sending XdndLeave to target:" << current_target;

    if (w)
        handleLeave(w, (const xcb_client_message_event_t *)&leave);
    else
        xcb_send_event(xcb_connection(), false,current_proxy_target,
                       XCB_EVENT_MASK_NO_EVENT, (const char *)&leave);
}

void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *event,
                          Qt::MouseButtons b, Qt::KeyboardModifiers mods)
{
    // Target receives XdndDrop. Once it is finished processing the drop, it sends XdndFinished.
    qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndDrop";

    if (!currentWindow) {
        stopListeningForActionListChanges();
        xdnd_dragsource = 0;
        return; // sanity
    }

    const uint32_t *l = event->data.data32;

    if (l[0] != xdnd_dragsource) {
        qCDebug(lcQpaXDnd, "xdnd drop from unexpected source (%x not %x", l[0], xdnd_dragsource);
        return;
    }

    // update the "user time" from the timestamp in the event.
    if (l[2] != 0)
        target_time = l[2];

    Qt::DropActions supported_drop_actions;
    QMimeData *dropData = nullptr;
    if (currentDrag()) {
        dropData = currentDrag()->mimeData();
        supported_drop_actions = Qt::DropActions(l[4]);
    } else {
        dropData = m_dropData;
        supported_drop_actions = accepted_drop_action | toDropActions(drop_actions);
    }

    if (!dropData)
        return;
    // ###
    //        int at = findXdndDropTransactionByTime(target_time);
    //        if (at != -1)
    //            dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data;
    // if we can't find it, then use the data in the drag manager

    auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
    auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();

    QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
                currentWindow.data(), dropData, currentPosition, supported_drop_actions,
                buttons, modifiers);

    setExecutedDropAction(response.acceptedAction());

    xcb_client_message_event_t finished = {};
    finished.response_type = XCB_CLIENT_MESSAGE;
    finished.sequence = 0;
    finished.window = xdnd_dragsource;
    finished.format = 32;
    finished.type = atom(QXcbAtom::XdndFinished);
    finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE;
    finished.data.data32[1] = response.isAccepted(); // flags
    finished.data.data32[2] = toXdndAction(response.acceptedAction());

    qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource;

    xcb_send_event(xcb_connection(), false, current_proxy_target,
                   XCB_EVENT_MASK_NO_EVENT, (char *)&finished);

    stopListeningForActionListChanges();

    dropped = true;
}

void QXcbDrag::handleFinished(const xcb_client_message_event_t *event)
{
    // Source receives XdndFinished when target is done processing the drop data.
    qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndFinished";

#ifndef QT_NO_CLIPBOARD
    if (event->window != connection()->clipboard()->owner())
        return;
#endif

    const unsigned long *l = (const unsigned long *)event->data.data32;
    if (l[0]) {
        int at = findTransactionByWindow(l[0]);
        if (at != -1) {

            Transaction t = transactions.takeAt(at);
            if (t.drag)
                t.drag->deleteLater();
//            QDragManager *manager = QDragManager::self();

//            Window target = current_target;
//            Window proxy_target = current_proxy_target;
//            QWidget *embedding_widget = current_embedding_widget;
//            QDrag *currentObject = manager->object;

//            current_target = t.target;
//            current_proxy_target = t.proxy_target;
//            current_embedding_widget = t.embedding_widget;
//            manager->object = t.object;

//            if (!passive)
//                (void) checkEmbedded(currentWindow, xe);

//            current_embedding_widget = 0;
//            current_target = 0;
//            current_proxy_target = 0;

//            current_target = target;
//            current_proxy_target = proxy_target;
//            current_embedding_widget = embedding_widget;
//            manager->object = currentObject;
        } else {
            qWarning("QXcbDrag::handleFinished - drop data has expired");
        }
    }
    waiting_for_status = false;
}

void QXcbDrag::timerEvent(QTimerEvent* e)
{
    if (e->timerId() == cleanup_timer) {
        bool stopTimer = true;
        for (int i = 0; i < transactions.count(); ++i) {
            const Transaction &t = transactions.at(i);
            if (t.targetWindow) {
                // dnd within the same process, don't delete, these are taken care of
                // in handleFinished()
                continue;
            }
            QTime currentTime = QTime::currentTime();
            int delta = t.time.msecsTo(currentTime);
            if (delta > XdndDropTransactionTimeout) {
                /* delete transactions which are older than XdndDropTransactionTimeout. It could mean
                 one of these:
                 - client has crashed and as a result we have never received XdndFinished
                 - showing dialog box on drop event where user's response takes more time than XdndDropTransactionTimeout (QTBUG-14493)
                 - dnd takes unusually long time to process data
                 */
                if (t.drag)
                    t.drag->deleteLater();
                transactions.removeAt(i--);
            } else {
                stopTimer = false;
            }

        }
        if (stopTimer && cleanup_timer != -1) {
            killTimer(cleanup_timer);
            cleanup_timer = -1;
        }
    }
}

void QXcbDrag::cancel()
{
    qCDebug(lcQpaXDnd) << "dnd was canceled";

    QBasicDrag::cancel();
    if (current_target)
        send_leave();

    // remove canceled object
    if (currentDrag())
        currentDrag()->deleteLater();

    canceled = true;
}

static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window)
{
    xcb_window_t target = 0;
    forever {
        // check if window has XdndAware
        auto gpReply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, window,
                                   c->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
        bool aware = gpReply && gpReply->type != XCB_NONE;
        if (aware) {
            target = window;
            break;
        }

        // try window's parent
        auto qtReply = Q_XCB_REPLY_UNCHECKED(xcb_query_tree, c->xcb_connection(), window);
        if (!qtReply)
            break;
        xcb_window_t root = qtReply->root;
        xcb_window_t parent = qtReply->parent;
        if (window == root)
            break;
        window = parent;
    }
    return target;
}

void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event)
{
    qCDebug(lcQpaXDnd) << "handle selection request from target:" << event->requestor;
    q_padded_xcb_event<xcb_selection_notify_event_t> notify = {};
    notify.response_type = XCB_SELECTION_NOTIFY;
    notify.requestor = event->requestor;
    notify.selection = event->selection;
    notify.target = XCB_NONE;
    notify.property = XCB_NONE;
    notify.time = event->time;

    // which transaction do we use? (note: -2 means use current currentDrag())
    int at = -1;

    // figure out which data the requestor is really interested in
    if (currentDrag() && event->time == source_time) {
        // requestor wants the current drag data
        at = -2;
    } else {
        // if someone has requested data in response to XdndDrop, find the corresponding transaction. the
        // spec says to call xcb_convert_selection() using the timestamp from the XdndDrop
        at = findTransactionByTime(event->time);
        if (at == -1) {
            // no dice, perhaps the client was nice enough to use the same window id in
            // xcb_convert_selection() that we sent the XdndDrop event to.
            at = findTransactionByWindow(event->requestor);
        }

        if (at == -1) {
            xcb_window_t target = findXdndAwareParent(connection(), event->requestor);
            if (target) {
                if (event->time == XCB_CURRENT_TIME && current_target == target)
                    at = -2;
                else
                    at = findTransactionByWindow(target);
            }
        }
    }

    QDrag *transactionDrag = nullptr;
    if (at >= 0) {
        transactionDrag = transactions.at(at).drag;
    } else if (at == -2) {
        transactionDrag = currentDrag();
    }

    if (transactionDrag) {
        xcb_atom_t atomFormat = event->target;
        int dataFormat = 0;
        QByteArray data;
        if (QXcbMime::mimeDataForAtom(connection(), event->target, transactionDrag->mimeData(),
                                     &data, &atomFormat, &dataFormat)) {
            int dataSize = data.size() / (dataFormat / 8);
            xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, event->requestor, event->property,
                                atomFormat, dataFormat, dataSize, (const void *)data.constData());
            notify.property = event->property;
            notify.target = atomFormat;
        }
    }

    xcb_window_t proxy_target = xdndProxy(connection(), event->requestor);
    if (!proxy_target)
        proxy_target = event->requestor;

    xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&notify);
}


bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
{
    // Windows announce that they support the XDND protocol by creating a window property XdndAware.
    if (on) {
        QXcbWindow *window = nullptr;
        if (w->window()->type() == Qt::Desktop) {
            if (desktop_proxy) // *WE* already have one.
                return false;

            QXcbConnectionGrabber grabber(connection());

            // As per Xdnd4, use XdndProxy
            xcb_window_t proxy_id = xdndProxy(connection(), w->xcb_window());

            if (!proxy_id) {
                desktop_proxy = new QWindow;
                window = static_cast<QXcbWindow *>(desktop_proxy->handle());
                proxy_id = window->xcb_window();
                xcb_atom_t xdnd_proxy = atom(QXcbAtom::XdndProxy);
                xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, w->xcb_window(), xdnd_proxy,
                                    XCB_ATOM_WINDOW, 32, 1, &proxy_id);
                xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, proxy_id, xdnd_proxy,
                                    XCB_ATOM_WINDOW, 32, 1, &proxy_id);
            }

        } else {
            window = w;
        }
        if (window) {
            qCDebug(lcQpaXDnd) << "setting XdndAware for" << window->xcb_window();
            xcb_atom_t atm = xdnd_version;
            xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window->xcb_window(),
                                atom(QXcbAtom::XdndAware), XCB_ATOM_ATOM, 32, 1, &atm);
            return true;
        } else {
            return false;
        }
    } else {
        if (w->window()->type() == Qt::Desktop) {
            xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::XdndProxy));
            delete desktop_proxy;
            desktop_proxy = nullptr;
        } else {
            qCDebug(lcQpaXDnd) << "not deleting XDndAware";
        }
        return true;
    }
}

bool QXcbDrag::ownsDragObject() const
{
    return true;
}

QXcbDropData::QXcbDropData(QXcbDrag *d)
    : QXcbMime(),
      drag(d)
{
}

QXcbDropData::~QXcbDropData()
{
}

QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const
{
    QByteArray mime = mimetype.toLatin1();
    QVariant data = xdndObtainData(mime, QMetaType::Type(requestedType));
    return data;
}

QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType::Type requestedType) const
{
    QByteArray result;

    QXcbConnection *c = drag->connection();
    QXcbWindow *xcb_window = c->platformWindowFromId(drag->xdnd_dragsource);
    if (xcb_window && drag->currentDrag() && xcb_window->window()->type() != Qt::Desktop) {
        QMimeData *data = drag->currentDrag()->mimeData();
        if (data->hasFormat(QLatin1String(format)))
            result = data->data(QLatin1String(format));
        return result;
    }

    QVector<xcb_atom_t> atoms = drag->xdnd_types;
    QByteArray encoding;
    xcb_atom_t a = mimeAtomForFormat(c, QLatin1String(format), requestedType, atoms, &encoding);
    if (a == XCB_NONE)
        return result;

#ifndef QT_NO_CLIPBOARD
    if (c->clipboard()->getSelectionOwner(drag->atom(QXcbAtom::XdndSelection)) == XCB_NONE)
        return result; // should never happen?

    xcb_atom_t xdnd_selection = c->atom(QXcbAtom::XdndSelection);
    result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime());
#endif

    return mimeConvertToFormat(c, a, result, QLatin1String(format), requestedType, encoding);
}

bool QXcbDropData::hasFormat_sys(const QString &format) const
{
    return formats().contains(format);
}

QStringList QXcbDropData::formats_sys() const
{
    QStringList formats;
    for (int i = 0; i < drag->xdnd_types.size(); ++i) {
        QString f = mimeAtomToString(drag->connection(), drag->xdnd_types.at(i));
        if (!formats.contains(f))
            formats.append(f);
    }
    return formats;
}

QT_END_NAMESPACE
