diff -r 000000000000 -r 1918ee327afb src/gui/kernel/qdnd_x11.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/kernel/qdnd_x11.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,2055 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include "qapplication.h" + +#ifndef QT_NO_DRAGANDDROP + +#include "qwidget.h" +#include "qpainter.h" +#include "qpixmap.h" +#include "qbitmap.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qdatetime.h" +#include "qiodevice.h" +#include "qpointer.h" +#include "qcursor.h" +#include "qvariant.h" +#include "qvector.h" +#include "qurl.h" +#include "qdebug.h" +#include "qimagewriter.h" +#include "qbuffer.h" +#include "qtextcodec.h" + +#include "qdnd_p.h" +#include "qt_x11_p.h" +#include "qx11info_x11.h" + +#include "qwidget_p.h" +#include "qcursor_p.h" + +QT_BEGIN_NAMESPACE + +// #define DND_DEBUG +#ifdef DND_DEBUG +#define DEBUG qDebug +#else +#define DEBUG if(0) qDebug +#endif + +#ifdef DND_DEBUG +#define DNDDEBUG qDebug() +#else +#define DNDDEBUG if(0) qDebug() +#endif + +static int findXdndDropTransactionByWindow(Window window) +{ + int at = -1; + for (int i = 0; i < X11->dndDropTransactions.count(); ++i) { + const QXdndDropTransaction &t = X11->dndDropTransactions.at(i); + if (t.target == window || t.proxy_target == window) { + at = i; + break; + } + } + return at; +} + +static int findXdndDropTransactionByTime(Time timestamp) +{ + int at = -1; + for (int i = 0; i < X11->dndDropTransactions.count(); ++i) { + const QXdndDropTransaction &t = X11->dndDropTransactions.at(i); + if (t.timestamp == timestamp) { + at = i; + break; + } + } + return at; +} + +// timer used to discard old XdndDrop transactions +static int transaction_expiry_timer = -1; +enum { XdndDropTransactionTimeout = 5000 }; // 5 seconds + +static void restartXdndDropExpiryTimer() +{ + if (transaction_expiry_timer != -1) + QDragManager::self()->killTimer(transaction_expiry_timer); + transaction_expiry_timer = QDragManager::self()->startTimer(XdndDropTransactionTimeout); +} + + +// find an ancestor with XdndAware on it +static Window findXdndAwareParent(Window window) +{ + Window target = 0; + forever { + // check if window has XdndAware + Atom type = 0; + int f; + unsigned long n, a; + unsigned char *data = 0; + if (XGetWindowProperty(X11->display, window, ATOM(XdndAware), 0, 0, False, + AnyPropertyType, &type, &f,&n,&a,&data) == Success) { + if (data) + XFree(data); + if (type) { + target = window; + break; + } + } + + // try window's parent + Window root; + Window parent; + Window *children; + uint unused; + if (!XQueryTree(X11->display, window, &root, &parent, &children, &unused)) + break; + if (children) + XFree(children); + if (window == root) + break; + window = parent; + } + return target; +} + + + + +// and all this stuff is copied -into- qapp_x11.cpp + +static void handle_xdnd_position(QWidget *, const XEvent *, bool); +static void handle_xdnd_status(QWidget * w, const XEvent * xe, bool /*passive*/); + +const int xdnd_version = 5; + +static Qt::DropAction xdndaction_to_qtaction(Atom atom) +{ + if (atom == ATOM(XdndActionCopy) || atom == 0) + return Qt::CopyAction; + if (atom == ATOM(XdndActionLink)) + return Qt::LinkAction; + if (atom == ATOM(XdndActionMove)) + return Qt::MoveAction; + return Qt::CopyAction; +} + +static int qtaction_to_xdndaction(Qt::DropAction a) +{ + switch (a) { + case Qt::CopyAction: + return ATOM(XdndActionCopy); + case Qt::LinkAction: + return ATOM(XdndActionLink); + case Qt::MoveAction: + case Qt::TargetMoveAction: + return ATOM(XdndActionMove); + case Qt::IgnoreAction: + return XNone; + default: + return ATOM(XdndActionCopy); + } +} + +// clean up the stuff used. +static void qt_xdnd_cleanup(); + +static void qt_xdnd_send_leave(); + +// real variables: +// xid of current drag source +static Atom qt_xdnd_dragsource_xid = 0; + +// the types in this drop. 100 is no good, but at least it's big. +const int qt_xdnd_max_type = 100; +static Atom qt_xdnd_types[qt_xdnd_max_type + 1]; + +// timer used when target wants "continuous" move messages (eg. scroll) +static int heartbeat = -1; +// rectangle in which the answer will be the same +static QRect qt_xdnd_source_sameanswer; +// top-level window we sent position to last. +static Window qt_xdnd_current_target; +// window to send events to (always valid if qt_xdnd_current_target) +static Window qt_xdnd_current_proxy_target; +static Time qt_xdnd_source_current_time; + +// widget we forwarded position to last, and local position +static QPointer qt_xdnd_current_widget; +static QPoint qt_xdnd_current_position; +// timestamp from the XdndPosition and XdndDrop +static Time qt_xdnd_target_current_time; +// screen number containing the pointer... -1 means default +static int qt_xdnd_current_screen = -1; +// state of dragging... true if dragging, false if not +bool qt_xdnd_dragging = false; + +static bool waiting_for_status = false; + +// used to preset each new QDragMoveEvent +static Qt::DropAction last_target_accepted_action = Qt::IgnoreAction; + +// Shift/Ctrl handling, and final drop status +static Qt::DropAction global_accepted_action = Qt::CopyAction; +static Qt::DropActions possible_actions = Qt::IgnoreAction; + +// for embedding only +static QWidget* current_embedding_widget = 0; +static XEvent last_enter_event; + +// cursors +static QCursor *noDropCursor = 0; +static QCursor *moveCursor = 0; +static QCursor *copyCursor = 0; +static QCursor *linkCursor = 0; + +static QPixmap *defaultPm = 0; + +static const int default_pm_hotx = -2; +static const int default_pm_hoty = -16; +static const char* const default_pm[] = { +"13 9 3 1", +". c None", +" c #000000", +"X c #FFFFFF", +"X X X X X X X", +" X X X X X X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X X X X X X ", +"X X X X X X X" +}; + +class QShapedPixmapWidget : public QWidget +{ + Q_OBJECT +public: + QShapedPixmapWidget(QWidget* w) : + QWidget(w, + Qt::Tool | Qt::FramelessWindowHint + | Qt::X11BypassWindowManagerHint + | Qt::BypassGraphicsProxyWidget) + { + setAttribute(Qt::WA_X11NetWmWindowTypeDND); + } + + void setPixmap(const QPixmap &pm) + { + QBitmap mask = pm.mask(); + if (!mask.isNull()) { + setMask(mask); + } else { + clearMask(); + } + resize(pm.width(),pm.height()); + pixmap = pm; + update(); + } + QPoint pm_hot; + +protected: + QPixmap pixmap; + void paintEvent(QPaintEvent*) + { + QPainter p(this); + p.drawPixmap(0, 0, pixmap); + } +}; + +#include "qdnd_x11.moc" + +struct XdndData { + QShapedPixmapWidget *deco; + QWidget* desktop_proxy; +}; + +static XdndData xdnd_data = { 0, 0 }; + +class QExtraWidget : public QWidget +{ + Q_DECLARE_PRIVATE(QWidget) +public: + inline QWExtra* extraData(); + inline QTLWExtra* topData(); +}; + +inline QWExtra* QExtraWidget::extraData() { return d_func()->extraData(); } +inline QTLWExtra* QExtraWidget::topData() { return d_func()->topData(); } + + +static WId xdndProxy(WId w) +{ + Atom type = XNone; + int f; + unsigned long n, a; + unsigned char *retval = 0; + XGetWindowProperty(X11->display, w, ATOM(XdndProxy), 0, 1, False, + XA_WINDOW, &type, &f,&n,&a,&retval); + WId *proxy_id_ptr = (WId *)retval; + WId proxy_id = 0; + if (type == XA_WINDOW && proxy_id_ptr) { + proxy_id = *proxy_id_ptr; + XFree(proxy_id_ptr); + proxy_id_ptr = 0; + // Already exists. Real? + X11->ignoreBadwindow(); + XGetWindowProperty(X11->display, proxy_id, ATOM(XdndProxy), 0, 1, False, + XA_WINDOW, &type, &f,&n,&a,&retval); + proxy_id_ptr = (WId *)retval; + if (X11->badwindow() || type != XA_WINDOW || !proxy_id_ptr || *proxy_id_ptr != proxy_id) + // Bogus - we will overwrite. + proxy_id = 0; + } + if (proxy_id_ptr) + XFree(proxy_id_ptr); + return proxy_id; +} + +static bool xdndEnable(QWidget* w, bool on) +{ + DNDDEBUG << "xdndEnable" << w << on; + if (on) { + QWidget * xdnd_widget = 0; + if ((w->windowType() == Qt::Desktop)) { + if (xdnd_data.desktop_proxy) // *WE* already have one. + return false; + + // As per Xdnd4, use XdndProxy + XGrabServer(X11->display); + Q_ASSERT(w->testAttribute(Qt::WA_WState_Created)); + WId proxy_id = xdndProxy(w->effectiveWinId()); + + if (!proxy_id) { + xdnd_widget = xdnd_data.desktop_proxy = new QWidget; + proxy_id = xdnd_data.desktop_proxy->effectiveWinId(); + XChangeProperty (X11->display, w->effectiveWinId(), ATOM(XdndProxy), + XA_WINDOW, 32, PropModeReplace, (unsigned char *)&proxy_id, 1); + XChangeProperty (X11->display, proxy_id, ATOM(XdndProxy), + XA_WINDOW, 32, PropModeReplace, (unsigned char *)&proxy_id, 1); + } + + XUngrabServer(X11->display); + } else { + xdnd_widget = w->window(); + } + if (xdnd_widget) { + DNDDEBUG << "setting XdndAware for" << xdnd_widget << xdnd_widget->effectiveWinId(); + Atom atm = (Atom)xdnd_version; + Q_ASSERT(xdnd_widget->testAttribute(Qt::WA_WState_Created)); + XChangeProperty(X11->display, xdnd_widget->effectiveWinId(), ATOM(XdndAware), + XA_ATOM, 32, PropModeReplace, (unsigned char *)&atm, 1); + return true; + } else { + return false; + } + } else { + if ((w->windowType() == Qt::Desktop)) { + XDeleteProperty(X11->display, w->internalWinId(), ATOM(XdndProxy)); + delete xdnd_data.desktop_proxy; + xdnd_data.desktop_proxy = 0; + } else { + DNDDEBUG << "not deleting XDndAware"; + } + return true; + } +} + +QByteArray QX11Data::xdndAtomToString(Atom a) +{ + if (!a) return 0; + + if (a == XA_STRING || a == ATOM(UTF8_STRING)) { + return "text/plain"; // some Xdnd clients are dumb + } + char *atom = XGetAtomName(display, a); + QByteArray result = atom; + XFree(atom); + return result; +} + +Atom QX11Data::xdndStringToAtom(const char *mimeType) +{ + if (!mimeType || !*mimeType) + return 0; + return XInternAtom(display, mimeType, False); +} + +//$$$ +QString QX11Data::xdndMimeAtomToString(Atom a) +{ + QString atomName; + if (a) { + char *atom = XGetAtomName(display, a); + atomName = QString::fromLatin1(atom); + XFree(atom); + } + return atomName; +} + +//$$$ +Atom QX11Data::xdndMimeStringToAtom(const QString &mimeType) +{ + if (mimeType.isEmpty()) + return 0; + return XInternAtom(display, mimeType.toLatin1().constData(), False); +} + +//$$$ replace ccxdndAtomToString() +QStringList QX11Data::xdndMimeFormatsForAtom(Atom a) +{ + QStringList formats; + if (a) { + QString atomName = xdndMimeAtomToString(a); + formats.append(atomName); + + // special cases for string type + if (a == ATOM(UTF8_STRING) || a == XA_STRING + || a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) + formats.append(QLatin1String("text/plain")); + + // special cases for uris + if (atomName == QLatin1String("text/x-moz-url")) + formats.append(QLatin1String("text/uri-list")); + + // special case for images + if (a == XA_PIXMAP) + formats.append(QLatin1String("image/ppm")); + } + return formats; +} + +//$$$ +bool QX11Data::xdndMimeDataForAtom(Atom a, QMimeData *mimeData, QByteArray *data, Atom *atomFormat, int *dataFormat) +{ + bool ret = false; + *atomFormat = a; + *dataFormat = 8; + QString atomName = xdndMimeAtomToString(a); + if (QInternalMimeData::hasFormatHelper(atomName, mimeData)) { + *data = QInternalMimeData::renderDataHelper(atomName, mimeData); + if (atomName == QLatin1String("application/x-color")) + *dataFormat = 16; + ret = true; + } else { + if ((a == ATOM(UTF8_STRING) || a == XA_STRING + || a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) + && QInternalMimeData::hasFormatHelper(QLatin1String("text/plain"), mimeData)) { + if (a == ATOM(UTF8_STRING)){ + *data = QInternalMimeData::renderDataHelper(QLatin1String("text/plain"), mimeData); + ret = true; + } else if (a == XA_STRING) { + *data = QString::fromUtf8(QInternalMimeData::renderDataHelper( + QLatin1String("text/plain"), mimeData)).toLocal8Bit(); + ret = true; + } else if (a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) { + // the ICCCM states that TEXT and COMPOUND_TEXT are in the + // encoding of choice, so we choose the encoding of the locale + QByteArray strData = QString::fromUtf8(QInternalMimeData::renderDataHelper( + QLatin1String("text/plain"), mimeData)).toLocal8Bit(); + char *list[] = { strData.data(), NULL }; + + XICCEncodingStyle style = (a == ATOM(COMPOUND_TEXT)) + ? XCompoundTextStyle : XStdICCTextStyle; + XTextProperty textprop; + if (list[0] != NULL + && XmbTextListToTextProperty(X11->display, list, 1, style, + &textprop) == Success) { + *atomFormat = textprop.encoding; + *dataFormat = textprop.format; + *data = QByteArray((const char *) textprop.value, textprop.nitems * textprop.format / 8); + ret = true; + + DEBUG(" textprop type %lx\n" + " textprop name '%s'\n" + " format %d\n" + " %ld items\n" + " %d bytes\n", + textprop.encoding, + X11->xdndMimeAtomToString(textprop.encoding).toLatin1().data(), + textprop.format, textprop.nitems, data->size()); + + XFree(textprop.value); + } + } + } else if (atomName == QLatin1String("text/x-moz-url") && + QInternalMimeData::hasFormatHelper(QLatin1String("text/uri-list"), mimeData)) { + QByteArray uri = QInternalMimeData::renderDataHelper( + QLatin1String("text/uri-list"), mimeData).split('\n').first(); + QString mozUri = QString::fromLatin1(uri, uri.size()); + mozUri += QLatin1Char('\n'); + *data = QByteArray(reinterpret_cast(mozUri.utf16()), mozUri.length() * 2); + ret = true; + } else if ((a == XA_PIXMAP || a == XA_BITMAP) && mimeData->hasImage()) { + QPixmap pm = qvariant_cast(mimeData->imageData()); + if (a == XA_BITMAP && pm.depth() != 1) { + QImage img = pm.toImage(); + img = img.convertToFormat(QImage::Format_MonoLSB); + pm = QPixmap::fromImage(img); + } + QDragManager *dm = QDragManager::self(); + if (dm) { + Pixmap handle = pm.handle(); + *data = QByteArray((const char *) &handle, sizeof(Pixmap)); + dm->xdndMimeTransferedPixmap[dm->xdndMimeTransferedPixmapIndex] = pm; + dm->xdndMimeTransferedPixmapIndex = + (dm->xdndMimeTransferedPixmapIndex + 1) % 2; + ret = true; + } + } else { + DEBUG("QClipboard: xdndMimeDataForAtom(): converting to type '%s' is not supported", qPrintable(atomName)); + } + } + return ret && data != 0; +} + +//$$$ +QList QX11Data::xdndMimeAtomsForFormat(const QString &format) +{ + QList atoms; + atoms.append(xdndMimeStringToAtom(format)); + + // special cases for strings + if (format == QLatin1String("text/plain")) { + atoms.append(ATOM(UTF8_STRING)); + atoms.append(XA_STRING); + atoms.append(ATOM(TEXT)); + atoms.append(ATOM(COMPOUND_TEXT)); + } + + // special cases for uris + if (format == QLatin1String("text/uri-list")) { + atoms.append(xdndMimeStringToAtom(QLatin1String("text/x-moz-url"))); + } + + //special cases for images + if (format == QLatin1String("image/ppm")) + atoms.append(XA_PIXMAP); + if (format == QLatin1String("image/pbm")) + atoms.append(XA_BITMAP); + + return atoms; +} + +//$$$ +QVariant QX11Data::xdndMimeConvertToFormat(Atom a, const QByteArray &data, const QString &format, QVariant::Type requestedType, const QByteArray &encoding) +{ + QString atomName = xdndMimeAtomToString(a); + if (atomName == format) + return data; + + if (!encoding.isEmpty() + && atomName == format + QLatin1String(";charset=") + QString::fromLatin1(encoding)) { + + if (requestedType == QVariant::String) { + QTextCodec *codec = QTextCodec::codecForName(encoding); + if (codec) + return codec->toUnicode(data); + } + + return data; + } + + // special cases for string types + if (format == QLatin1String("text/plain")) { + if (a == ATOM(UTF8_STRING)) + return QString::fromUtf8(data); + if (a == XA_STRING) + return QString::fromLatin1(data); + if (a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) + // #### might be wrong for COMPUND_TEXT + return QString::fromLocal8Bit(data, data.size()); + } + + // special case for uri types + if (format == QLatin1String("text/uri-list")) { + if (atomName == QLatin1String("text/x-moz-url")) { + // we expect this as utf16 + // the first part is a url that should only contain ascci char + // so it should be safe to check that the second char is 0 + // to verify that it is utf16 + if (data.size() > 1 && data.at(1) == 0) + return QString::fromUtf16(reinterpret_cast<const ushort *>(data.constData()), + data.size() / 2).split(QLatin1Char('\n')).first().toLatin1(); + } + } + + // special cas for images + if (format == QLatin1String("image/ppm")) { + if (a == XA_PIXMAP && data.size() == sizeof(Pixmap)) { + Pixmap xpm = *((Pixmap*)data.data()); + if (!xpm) + return QByteArray(); + QPixmap qpm = QPixmap::fromX11Pixmap(xpm); + QImageWriter imageWriter; + imageWriter.setFormat("PPMRAW"); + QImage imageToWrite = qpm.toImage(); + QBuffer buf; + buf.open(QIODevice::WriteOnly); + imageWriter.setDevice(&buf); + imageWriter.write(imageToWrite); + return buf.buffer(); + } + } + return QVariant(); +} + +//$$$ middle of xdndObtainData +Atom QX11Data::xdndMimeAtomForFormat(const QString &format, QVariant::Type requestedType, const QList<Atom> &atoms, QByteArray *encoding) +{ + encoding->clear(); + + // find matches for string types + if (format == QLatin1String("text/plain")) { + if (atoms.contains(ATOM(UTF8_STRING))) + return ATOM(UTF8_STRING); + if (atoms.contains(ATOM(COMPOUND_TEXT))) + return ATOM(COMPOUND_TEXT); + if (atoms.contains(ATOM(TEXT))) + return ATOM(TEXT); + if (atoms.contains(XA_STRING)) + return XA_STRING; + } + + // find matches for uri types + if (format == QLatin1String("text/uri-list")) { + Atom a = xdndMimeStringToAtom(format); + if (a && atoms.contains(a)) + return a; + a = xdndMimeStringToAtom(QLatin1String("text/x-moz-url")); + if (a && atoms.contains(a)) + return a; + } + + // find match for image + if (format == QLatin1String("image/ppm")) { + if (atoms.contains(XA_PIXMAP)) + return XA_PIXMAP; + } + + // for string/text requests try to use a format with a well-defined charset + // first to avoid encoding problems + if (requestedType == QVariant::String + && format.startsWith(QLatin1String("text/")) + && !format.contains(QLatin1String("charset="))) { + + QString formatWithCharset = format; + formatWithCharset.append(QLatin1String(";charset=utf-8")); + + Atom a = xdndMimeStringToAtom(formatWithCharset); + if (a && atoms.contains(a)) { + *encoding = "utf-8"; + return a; + } + } + + Atom a = xdndMimeStringToAtom(format); + if (a && atoms.contains(a)) + return a; + + return 0; +} + +void QX11Data::xdndSetup() { + QCursorData::initialize(); + qAddPostRoutine(qt_xdnd_cleanup); +} + + +void qt_xdnd_cleanup() +{ + delete noDropCursor; + noDropCursor = 0; + delete copyCursor; + copyCursor = 0; + delete moveCursor; + moveCursor = 0; + delete linkCursor; + linkCursor = 0; + delete defaultPm; + defaultPm = 0; + delete xdnd_data.desktop_proxy; + xdnd_data.desktop_proxy = 0; + delete xdnd_data.deco; + xdnd_data.deco = 0; +} + + +static QWidget *find_child(QWidget *tlw, QPoint & p) +{ + QWidget *widget = tlw; + + p = widget->mapFromGlobal(p); + bool done = false; + while (!done) { + done = true; + if (((QExtraWidget*)widget)->extraData() && + ((QExtraWidget*)widget)->extraData()->xDndProxy != 0) + break; // stop searching for widgets under the mouse cursor if found widget is a proxy. + QObjectList children = widget->children(); + if (!children.isEmpty()) { + for(int i = children.size(); i > 0;) { + --i; + QWidget *w = qobject_cast<QWidget *>(children.at(i)); + if (!w) + continue; + if (w->testAttribute(Qt::WA_TransparentForMouseEvents)) + continue; + if (w->isVisible() && + w->geometry().contains(p) && + !w->isWindow()) { + widget = w; + done = false; + p = widget->mapFromParent(p); + break; + } + } + } + } + return widget; +} + + +static bool checkEmbedded(QWidget* w, const XEvent* xe) +{ + if (!w) + return false; + + if (current_embedding_widget != 0 && current_embedding_widget != w) { + qt_xdnd_current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy; + qt_xdnd_current_proxy_target = qt_xdnd_current_target; + qt_xdnd_send_leave(); + qt_xdnd_current_target = 0; + qt_xdnd_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 (qt_xdnd_current_widget != w) { + qt_xdnd_current_widget = w; + } + return true; + } + current_embedding_widget = 0; + return false; +} + +void QX11Data::xdndHandleEnter(QWidget *, const XEvent * xe, bool /*passive*/) +{ + motifdnd_active = false; + + last_enter_event.xclient = xe->xclient; + + const long *l = xe->xclient.data.l; + int version = (int)(((unsigned long)(l[1])) >> 24); + + if (version > xdnd_version) + return; + + qt_xdnd_dragsource_xid = l[0]; + + int j = 0; + if (l[1] & 1) { + // get the types from XdndTypeList + Atom type = XNone; + int f; + unsigned long n, a; + unsigned char *retval = 0; + XGetWindowProperty(X11->display, qt_xdnd_dragsource_xid, ATOM(XdndTypelist), 0, + qt_xdnd_max_type, False, XA_ATOM, &type, &f,&n,&a,&retval); + if (retval) { + Atom *data = (Atom *)retval; + for (; j<qt_xdnd_max_type && j < (int)n; j++) { + qt_xdnd_types[j] = data[j]; + } + XFree((uchar*)data); + } + } else { + // get the types from the message + int i; + for(i=2; i < 5; i++) { + qt_xdnd_types[j++] = l[i]; + } + } + qt_xdnd_types[j] = 0; +} + +static void handle_xdnd_position(QWidget *w, const XEvent * xe, bool passive) +{ + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + QPoint p((l[2] & 0xffff0000) >> 16, l[2] & 0x0000ffff); + QWidget * c = find_child(w, p); // changes p to to c-local coordinates + + if (!passive && checkEmbedded(c, xe)) + return; + + if (!c || (!c->acceptDrops() && (c->windowType() == Qt::Desktop))) + return; + + if (l[0] != qt_xdnd_dragsource_xid) { + DEBUG("xdnd drag position from unexpected source (%08lx not %08lx)", l[0], qt_xdnd_dragsource_xid); + return; + } + + // timestamp from the source + if (l[3] != 0) { + // Some X server/client combination swallow the first 32 bit and + // interpret a set bit 31 as negative sign. + qt_xdnd_target_current_time = X11->userTime = + ((sizeof(Time) == 8 && xe->xclient.data.l[3] < 0) + ? uint(l[3]) + : l[3]); + } + + QDragManager *manager = QDragManager::self(); + QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; + + XClientMessageEvent response; + response.type = ClientMessage; + response.window = qt_xdnd_dragsource_xid; + response.format = 32; + response.message_type = ATOM(XdndStatus); + response.data.l[0] = w->effectiveWinId(); + response.data.l[1] = 0; // flags + response.data.l[2] = 0; // x, y + response.data.l[3] = 0; // w, h + response.data.l[4] = 0; // action + + if (!passive) { // otherwise just reject + while (c && !c->acceptDrops() && !c->isWindow()) { + p = c->mapToParent(p); + c = c->parentWidget(); + } + QWidget *target_widget = c && c->acceptDrops() ? c : 0; + + QRect answerRect(c->mapToGlobal(p), QSize(1,1)); + + if (manager->object) { + possible_actions = manager->dragPrivate()->possible_actions; + } else { + possible_actions = Qt::DropActions(xdndaction_to_qtaction(l[4])); +// possible_actions |= Qt::CopyAction; + } + QDragMoveEvent me(p, possible_actions, dropData, QApplication::mouseButtons(), QApplication::keyboardModifiers()); + + Qt::DropAction accepted_action = Qt::IgnoreAction; + + + if (target_widget != qt_xdnd_current_widget) { + if (qt_xdnd_current_widget) { + QDragLeaveEvent e; + QApplication::sendEvent(qt_xdnd_current_widget, &e); + } + if (qt_xdnd_current_widget != target_widget) { + qt_xdnd_current_widget = target_widget; + } + if (target_widget) { + qt_xdnd_current_position = p; + + last_target_accepted_action = Qt::IgnoreAction; + QDragEnterEvent de(p, possible_actions, dropData, QApplication::mouseButtons(), QApplication::keyboardModifiers()); + QApplication::sendEvent(target_widget, &de); + if (de.isAccepted() && de.dropAction() != Qt::IgnoreAction) + last_target_accepted_action = de.dropAction(); + } + } + + DEBUG() << "qt_handle_xdnd_position action=" << X11->xdndAtomToString(l[4]); + if (!target_widget) { + answerRect = QRect(p, QSize(1, 1)); + } else { + qt_xdnd_current_widget = c; + qt_xdnd_current_position = p; + + if (last_target_accepted_action != Qt::IgnoreAction) { + me.setDropAction(last_target_accepted_action); + me.accept(); + } + QApplication::sendEvent(c, &me); + if (me.isAccepted()) { + response.data.l[1] = 1; // yes + accepted_action = me.dropAction(); + last_target_accepted_action = accepted_action; + } else { + response.data.l[0] = 0; + last_target_accepted_action = Qt::IgnoreAction; + } + answerRect = me.answerRect().intersected(c->rect()); + } + answerRect = QRect(c->mapToGlobal(answerRect.topLeft()), answerRect.size()); + + 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); + + response.data.l[2] = (answerRect.x() << 16) + answerRect.y(); + response.data.l[3] = (answerRect.width() << 16) + answerRect.height(); + response.data.l[4] = qtaction_to_xdndaction(accepted_action); + } + + // reset + qt_xdnd_target_current_time = CurrentTime; + + QWidget * source = QWidget::find(qt_xdnd_dragsource_xid); + if (source && (source->windowType() == Qt::Desktop) && !source->acceptDrops()) + source = 0; + + DEBUG() << "sending XdndStatus"; + if (source) + handle_xdnd_status(source, (const XEvent *)&response, passive); + else + XSendEvent(X11->display, qt_xdnd_dragsource_xid, False, NoEventMask, (XEvent*)&response); +} + +static Bool xdnd_position_scanner(Display *, XEvent *event, XPointer) +{ + if (event->type != ClientMessage) + return false; + XClientMessageEvent *ev = &event->xclient; + + if (ev->message_type == ATOM(XdndPosition)) + return true; + + return false; +} + +void QX11Data::xdndHandlePosition(QWidget * w, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandlePosition"); + while (XCheckIfEvent(X11->display, (XEvent *)xe, xdnd_position_scanner, 0)) + ; + + handle_xdnd_position(w, xe, passive); +} + + +static void handle_xdnd_status(QWidget *, const XEvent * xe, bool) +{ + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + // ignore late status messages + if (l[0] && l[0] != qt_xdnd_current_proxy_target) + return; + Qt::DropAction newAction = (l[1] & 0x1) ? xdndaction_to_qtaction(l[4]) : Qt::IgnoreAction; + + if ((int)(l[1] & 2) == 0) { + QPoint p((l[2] & 0xffff0000) >> 16, l[2] & 0x0000ffff); + QSize s((l[3] & 0xffff0000) >> 16, l[3] & 0x0000ffff); + qt_xdnd_source_sameanswer = QRect(p, s); + } else { + qt_xdnd_source_sameanswer = QRect(); + } + QDragManager *manager = QDragManager::self(); + manager->willDrop = (l[1] & 0x1); + if (global_accepted_action != newAction) + manager->emitActionChanged(newAction); + global_accepted_action = newAction; + manager->updateCursor(); + waiting_for_status = false; +} + +static Bool xdnd_status_scanner(Display *, XEvent *event, XPointer) +{ + if (event->type != ClientMessage) + return false; + XClientMessageEvent *ev = &event->xclient; + + if (ev->message_type == ATOM(XdndStatus)) + return true; + + return false; +} + +void QX11Data::xdndHandleStatus(QWidget * w, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandleStatus"); + while (XCheckIfEvent(X11->display, (XEvent *)xe, xdnd_status_scanner, 0)) + ; + + handle_xdnd_status(w, xe, passive); + DEBUG("xdndHandleStatus end"); +} + +void QX11Data::xdndHandleLeave(QWidget *w, const XEvent * xe, bool /*passive*/) +{ + DEBUG("xdnd leave"); + if (!qt_xdnd_current_widget || + w->window() != qt_xdnd_current_widget->window()) { + return; // sanity + } + + if (checkEmbedded(current_embedding_widget, xe)) { + current_embedding_widget = 0; + qt_xdnd_current_widget = 0; + return; + } + + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + QDragLeaveEvent e; + QApplication::sendEvent(qt_xdnd_current_widget, &e); + + if (l[0] != qt_xdnd_dragsource_xid) { + // This often happens - leave other-process window quickly + DEBUG("xdnd drag leave from unexpected source (%08lx not %08lx", l[0], qt_xdnd_dragsource_xid); + qt_xdnd_current_widget = 0; + return; + } + + qt_xdnd_dragsource_xid = 0; + qt_xdnd_types[0] = 0; + qt_xdnd_current_widget = 0; +} + + +void qt_xdnd_send_leave() +{ + if (!qt_xdnd_current_target) + return; + + QDragManager *manager = QDragManager::self(); + + XClientMessageEvent leave; + leave.type = ClientMessage; + leave.window = qt_xdnd_current_target; + leave.format = 32; + leave.message_type = ATOM(XdndLeave); + leave.data.l[0] = manager->dragPrivate()->source->effectiveWinId(); + leave.data.l[1] = 0; // flags + leave.data.l[2] = 0; // x, y + leave.data.l[3] = 0; // w, h + leave.data.l[4] = 0; // just null + + QWidget * w = QWidget::find(qt_xdnd_current_proxy_target); + + if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops()) + w = 0; + + if (w) + X11->xdndHandleLeave(w, (const XEvent *)&leave, false); + else + XSendEvent(X11->display, qt_xdnd_current_proxy_target, False, + NoEventMask, (XEvent*)&leave); + + // reset the drag manager state + manager->willDrop = false; + if (global_accepted_action != Qt::IgnoreAction) + manager->emitActionChanged(Qt::IgnoreAction); + global_accepted_action = Qt::IgnoreAction; + manager->updateCursor(); + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + qt_xdnd_source_current_time = 0; + waiting_for_status = false; +} + + + +void QX11Data::xdndHandleDrop(QWidget *, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandleDrop"); + if (!qt_xdnd_current_widget) { + qt_xdnd_dragsource_xid = 0; + return; // sanity + } + + if (!passive && checkEmbedded(qt_xdnd_current_widget, xe)){ + current_embedding_widget = 0; + qt_xdnd_dragsource_xid = 0; + qt_xdnd_current_widget = 0; + return; + } + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + QDragManager *manager = QDragManager::self(); + DEBUG("xdnd drop"); + + if (l[0] != qt_xdnd_dragsource_xid) { + DEBUG("xdnd drop from unexpected source (%08lx not %08lx", l[0], qt_xdnd_dragsource_xid); + return; + } + + // update the "user time" from the timestamp in the event. + if (l[2] != 0) { + // Some X server/client combination swallow the first 32 bit and + // interpret a set bit 31 as negative sign. + qt_xdnd_target_current_time = X11->userTime = + ((sizeof(Time) == 8 && xe->xclient.data.l[2] < 0) + ? uint(l[2]) + : l[2]); + } + + if (!passive) { + // this could be a same-application drop, just proxied due to + // some XEMBEDding, so try to find the real QMimeData used + // based on the timestamp for this drop. + QMimeData *dropData = 0; + int at = findXdndDropTransactionByTime(qt_xdnd_target_current_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 + if (!dropData) + dropData = (manager->object) ? manager->dragPrivate()->data : manager->dropData; + + QDropEvent de(qt_xdnd_current_position, possible_actions, dropData, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + QApplication::sendEvent(qt_xdnd_current_widget, &de); + if (!de.isAccepted()) { + // Ignore a failed drag + global_accepted_action = Qt::IgnoreAction; + } else { + global_accepted_action = de.dropAction(); + } + XClientMessageEvent finished; + finished.type = ClientMessage; + finished.window = qt_xdnd_dragsource_xid; + finished.format = 32; + finished.message_type = ATOM(XdndFinished); + DNDDEBUG << "xdndHandleDrop" + << "qt_xdnd_current_widget" << qt_xdnd_current_widget + << (qt_xdnd_current_widget ? qt_xdnd_current_widget->effectiveWinId() : 0) + << "t_xdnd_current_widget->window()" + << (qt_xdnd_current_widget ? qt_xdnd_current_widget->window() : 0) + << (qt_xdnd_current_widget ? qt_xdnd_current_widget->window()->internalWinId() : 0); + finished.data.l[0] = qt_xdnd_current_widget?qt_xdnd_current_widget->window()->internalWinId():0; + finished.data.l[1] = de.isAccepted() ? 1 : 0; // flags + finished.data.l[2] = qtaction_to_xdndaction(global_accepted_action); + XSendEvent(X11->display, qt_xdnd_dragsource_xid, False, + NoEventMask, (XEvent*)&finished); + } else { + QDragLeaveEvent e; + QApplication::sendEvent(qt_xdnd_current_widget, &e); + } + qt_xdnd_dragsource_xid = 0; + qt_xdnd_current_widget = 0; + waiting_for_status = false; + + // reset + qt_xdnd_target_current_time = CurrentTime; +} + + +void QX11Data::xdndHandleFinished(QWidget *, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandleFinished"); + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + DNDDEBUG << "xdndHandleFinished, l[0]" << l[0] + << "qt_xdnd_current_target" << qt_xdnd_current_target + << "qt_xdnd_current_proxy_targe" << qt_xdnd_current_proxy_target; + + if (l[0]) { + int at = findXdndDropTransactionByWindow(l[0]); + if (at != -1) { + restartXdndDropExpiryTimer(); + + QXdndDropTransaction t = X11->dndDropTransactions.takeAt(at); + QDragManager *manager = QDragManager::self(); + + Window target = qt_xdnd_current_target; + Window proxy_target = qt_xdnd_current_proxy_target; + QWidget *embedding_widget = current_embedding_widget; + QDrag *currentObject = manager->object; + + qt_xdnd_current_target = t.target; + qt_xdnd_current_proxy_target = t.proxy_target; + current_embedding_widget = t.embedding_widget; + manager->object = t.object; + + if (!passive) + (void) checkEmbedded(qt_xdnd_current_widget, xe); + + current_embedding_widget = 0; + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + + if (t.object) + t.object->deleteLater(); + + qt_xdnd_current_target = target; + qt_xdnd_current_proxy_target = proxy_target; + current_embedding_widget = embedding_widget; + manager->object = currentObject; + } + } + waiting_for_status = false; +} + + +void QDragManager::timerEvent(QTimerEvent* e) +{ + if (e->timerId() == heartbeat && qt_xdnd_source_sameanswer.isNull()) { + move(QCursor::pos()); + } else if (e->timerId() == transaction_expiry_timer) { + for (int i = 0; i < X11->dndDropTransactions.count(); ++i) { + const QXdndDropTransaction &t = X11->dndDropTransactions.at(i); + if (t.targetWidget) { + // dnd within the same process, don't delete these + continue; + } + t.object->deleteLater(); + X11->dndDropTransactions.removeAt(i--); + } + + killTimer(transaction_expiry_timer); + transaction_expiry_timer = -1; + } +} + +bool QDragManager::eventFilter(QObject * o, QEvent * e) +{ + if (beingCancelled) { + if (e->type() == QEvent::KeyRelease && ((QKeyEvent*)e)->key() == Qt::Key_Escape) { + qApp->removeEventFilter(this); + Q_ASSERT(object == 0); + beingCancelled = false; + eventLoop->exit(); + return true; // block the key release + } + return false; + } + + Q_ASSERT(object != 0); + + if (!o->isWidgetType()) + return false; + + if (e->type() == QEvent::MouseMove) { + QMouseEvent* me = (QMouseEvent *)e; + move(me->globalPos()); + return true; + } else if (e->type() == QEvent::MouseButtonRelease) { + DEBUG("pre drop"); + qApp->removeEventFilter(this); + if (willDrop) + drop(); + else + cancel(); + DEBUG("drop, resetting object"); + beingCancelled = false; + eventLoop->exit(); + return true; + } + + if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { + QKeyEvent *ke = ((QKeyEvent*)e); + if (ke->key() == Qt::Key_Escape && e->type() == QEvent::KeyPress) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + eventLoop->exit(); + } else { + qt_xdnd_source_sameanswer = QRect(); // force move + move(QCursor::pos()); + } + return true; // Eat all key events + } + + // ### We bind modality to widgets, so we have to do this + // ### "manually". + // DnD is modal - eat all other interactive events + switch (e->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::Wheel: + case QEvent::ShortcutOverride: +#ifdef QT3_SUPPORT + case QEvent::Accel: + case QEvent::AccelAvailable: +#endif + return true; + default: + return false; + } +} + +void QDragManager::updateCursor() +{ + if (!noDropCursor) { +#ifndef QT_NO_CURSOR + noDropCursor = new QCursor(Qt::ForbiddenCursor); + moveCursor = new QCursor(dragCursor(Qt::MoveAction), 0,0); + copyCursor = new QCursor(dragCursor(Qt::CopyAction), 0,0); + linkCursor = new QCursor(dragCursor(Qt::LinkAction), 0,0); +#endif + } + + QCursor *c; + if (willDrop) { + if (global_accepted_action == Qt::CopyAction) { + c = copyCursor; + } else if (global_accepted_action == Qt::LinkAction) { + c = linkCursor; + } else { + c = moveCursor; + } + if (xdnd_data.deco) { + xdnd_data.deco->show(); + xdnd_data.deco->raise(); + } + } else { + c = noDropCursor; + //if (qt_xdnd_deco) + // qt_xdnd_deco->hide(); + } +#ifndef QT_NO_CURSOR + if (c) + qApp->changeOverrideCursor(*c); +#endif +} + + +void QDragManager::cancel(bool deleteSource) +{ + DEBUG("QDragManager::cancel"); + Q_ASSERT(heartbeat != -1); + killTimer(heartbeat); + heartbeat = -1; + beingCancelled = true; + qt_xdnd_dragging = false; + + if (qt_xdnd_current_target) + qt_xdnd_send_leave(); + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif + + if (deleteSource && object) + object->deleteLater(); + object = 0; + qDeleteInEventHandler(xdnd_data.deco); + xdnd_data.deco = 0; + + global_accepted_action = Qt::IgnoreAction; +} + +static +Window findRealWindow(const QPoint & pos, Window w, int md) +{ + if (xdnd_data.deco && w == xdnd_data.deco->effectiveWinId()) + return 0; + + if (md) { + X11->ignoreBadwindow(); + XWindowAttributes attr; + XGetWindowAttributes(X11->display, w, &attr); + if (X11->badwindow()) + return 0; + + if (attr.map_state == IsViewable + && QRect(attr.x,attr.y,attr.width,attr.height).contains(pos)) { + { + Atom type = XNone; + int f; + unsigned long n, a; + unsigned char *data; + + XGetWindowProperty(X11->display, w, ATOM(XdndAware), 0, 0, False, + AnyPropertyType, &type, &f,&n,&a,&data); + if (data) XFree(data); + if (type) + return w; + } + + Window r, p; + Window* c; + uint nc; + if (XQueryTree(X11->display, w, &r, &p, &c, &nc)) { + r=0; + for (uint i=nc; !r && i--;) { + r = findRealWindow(pos-QPoint(attr.x,attr.y), + c[i], md-1); + } + XFree(c); + if (r) + return r; + + // We didn't find a client window! Just use the + // innermost window. + } + + // No children! + return w; + } + } + return 0; +} + +void QDragManager::move(const QPoint & globalPos) +{ +#ifdef QT_NO_CURSOR + Q_UNUSED(globalPos); + return; +#else + DEBUG() << "QDragManager::move enter"; + if (!object) { + // perhaps the target crashed? + return; + } + + int screen = QCursor::x11Screen(); + if ((qt_xdnd_current_screen == -1 && screen != X11->defaultScreen) || (screen != qt_xdnd_current_screen)) { + // recreate the pixmap on the new screen... + delete xdnd_data.deco; + QWidget* parent = object->source()->window()->x11Info().screen() == screen + ? object->source()->window() : QApplication::desktop()->screen(screen); + xdnd_data.deco = new QShapedPixmapWidget(parent); + if (!QWidget::mouseGrabber()) { + updatePixmap(); + xdnd_data.deco->grabMouse(); + } + } + xdnd_data.deco->move(QCursor::pos() - xdnd_data.deco->pm_hot); + + if (qt_xdnd_source_sameanswer.contains(globalPos) && qt_xdnd_source_sameanswer.isValid()) + return; + + qt_xdnd_current_screen = screen; + Window rootwin = QX11Info::appRootWindow(qt_xdnd_current_screen); + Window target = 0; + int lx = 0, ly = 0; + if (!XTranslateCoordinates(X11->display, rootwin, rootwin, globalPos.x(), globalPos.y(), &lx, &ly, &target)) + // some weird error... + return; + + if (target == rootwin) { + // Ok. + } else if (target) { + //me + Window src = rootwin; + while (target != 0) { + DNDDEBUG << "checking target for XdndAware" << QWidget::find(target) << target; + int lx2, ly2; + Window t; + // translate coordinates + if (!XTranslateCoordinates(X11->display, src, target, lx, ly, &lx2, &ly2, &t)) { + target = 0; + break; + } + lx = lx2; + ly = ly2; + src = target; + + // check if it has XdndAware + Atom type = 0; + int f; + unsigned long n, a; + unsigned char *data = 0; + XGetWindowProperty(X11->display, target, ATOM(XdndAware), 0, 0, False, + AnyPropertyType, &type, &f,&n,&a,&data); + if (data) + XFree(data); + if (type) { + DNDDEBUG << "Found XdndAware on " << QWidget::find(target) << target; + break; + } + + // find child at the coordinates + if (!XTranslateCoordinates(X11->display, src, src, lx, ly, &lx2, &ly2, &target)) { + target = 0; + break; + } + } + if (xdnd_data.deco && (!target || target == xdnd_data.deco->effectiveWinId())) { + DNDDEBUG << "need to find real window"; + target = findRealWindow(globalPos, rootwin, 6); + DNDDEBUG << "real window found" << QWidget::find(target) << target; + } + } + + QWidget* w; + if (target) { + w = QWidget::find((WId)target); + if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops()) + w = 0; + } else { + w = 0; + target = rootwin; + } + + DNDDEBUG << "and the final target is " << QWidget::find(target) << target; + DNDDEBUG << "the widget w is" << w; + + WId proxy_target = xdndProxy(target); + if (!proxy_target) + proxy_target = target; + int target_version = 1; + + if (proxy_target) { + Atom type = XNone; + int r, f; + unsigned long n, a; + unsigned char *retval; + X11->ignoreBadwindow(); + r = XGetWindowProperty(X11->display, proxy_target, ATOM(XdndAware), 0, + 1, False, AnyPropertyType, &type, &f,&n,&a,&retval); + int *tv = (int *)retval; + if (r != Success || X11->badwindow()) { + target = 0; + } else { + target_version = qMin(xdnd_version,tv ? *tv : 1); + if (tv) + XFree(tv); +// if (!(!X11->badwindow() && type)) +// target = 0; + } + } + + if (target != qt_xdnd_current_target) { + if (qt_xdnd_current_target) + qt_xdnd_send_leave(); + + qt_xdnd_current_target = target; + qt_xdnd_current_proxy_target = proxy_target; + if (target) { + QVector<Atom> types; + int flags = target_version << 24; + QStringList fmts = QInternalMimeData::formatsHelper(dragPrivate()->data); + for (int i = 0; i < fmts.size(); ++i) { + QList<Atom> atoms = X11->xdndMimeAtomsForFormat(fmts.at(i)); + for (int j = 0; j < atoms.size(); ++j) { + if (!types.contains(atoms.at(j))) + types.append(atoms.at(j)); + } + } + if (types.size() > 3) { + XChangeProperty(X11->display, + dragPrivate()->source->effectiveWinId(), ATOM(XdndTypelist), + XA_ATOM, 32, PropModeReplace, + (unsigned char *)types.data(), + types.size()); + flags |= 0x0001; + } + XClientMessageEvent enter; + enter.type = ClientMessage; + enter.window = target; + enter.format = 32; + enter.message_type = ATOM(XdndEnter); + enter.data.l[0] = dragPrivate()->source->effectiveWinId(); + enter.data.l[1] = flags; + enter.data.l[2] = types.size()>0 ? types.at(0) : 0; + enter.data.l[3] = types.size()>1 ? types.at(1) : 0; + enter.data.l[4] = types.size()>2 ? types.at(2) : 0; + // provisionally set the rectangle to 5x5 pixels... + qt_xdnd_source_sameanswer = QRect(globalPos.x() - 2, + globalPos.y() -2 , 5, 5); + + DEBUG("sending Xdnd enter"); + if (w) + X11->xdndHandleEnter(w, (const XEvent *)&enter, false); + else if (target) + XSendEvent(X11->display, proxy_target, False, NoEventMask, (XEvent*)&enter); + waiting_for_status = false; + } + } + if (waiting_for_status) + return; + + if (target) { + waiting_for_status = true; + + XClientMessageEvent move; + move.type = ClientMessage; + move.window = target; + move.format = 32; + move.message_type = ATOM(XdndPosition); + move.window = target; + move.data.l[0] = dragPrivate()->source->effectiveWinId(); + move.data.l[1] = 0; // flags + move.data.l[2] = (globalPos.x() << 16) + globalPos.y(); + move.data.l[3] = X11->time; + move.data.l[4] = qtaction_to_xdndaction(defaultAction(dragPrivate()->possible_actions, QApplication::keyboardModifiers())); + DEBUG("sending Xdnd position"); + + qt_xdnd_source_current_time = X11->time; + + if (w) + handle_xdnd_position(w, (const XEvent *)&move, false); + else + XSendEvent(X11->display, proxy_target, False, NoEventMask, + (XEvent*)&move); + } else { + if (willDrop) { + willDrop = false; + updateCursor(); + } + } + DEBUG() << "QDragManager::move leave"; +#endif +} + + +void QDragManager::drop() +{ + Q_ASSERT(heartbeat != -1); + killTimer(heartbeat); + heartbeat = -1; + qt_xdnd_dragging = false; + + if (!qt_xdnd_current_target) + return; + + qDeleteInEventHandler(xdnd_data.deco); + xdnd_data.deco = 0; + + XClientMessageEvent drop; + drop.type = ClientMessage; + drop.window = qt_xdnd_current_target; + drop.format = 32; + drop.message_type = ATOM(XdndDrop); + drop.data.l[0] = dragPrivate()->source->effectiveWinId(); + drop.data.l[1] = 0; // flags + drop.data.l[2] = X11->time; + + drop.data.l[3] = 0; + drop.data.l[4] = 0; + + QWidget * w = QWidget::find(qt_xdnd_current_proxy_target); + + if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops()) + w = 0; + + QXdndDropTransaction t = { + X11->time, + qt_xdnd_current_target, + qt_xdnd_current_proxy_target, + w, + current_embedding_widget, + object + }; + X11->dndDropTransactions.append(t); + restartXdndDropExpiryTimer(); + + if (w) + X11->xdndHandleDrop(w, (const XEvent *)&drop, false); + else + XSendEvent(X11->display, qt_xdnd_current_proxy_target, False, + NoEventMask, (XEvent*)&drop); + + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + qt_xdnd_source_current_time = 0; + current_embedding_widget = 0; + object = 0; + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif +} + + + +bool QX11Data::xdndHandleBadwindow() +{ + if (qt_xdnd_current_target) { + QDragManager *manager = QDragManager::self(); + if (manager->object) { + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + manager->object->deleteLater(); + manager->object = 0; + delete xdnd_data.deco; + xdnd_data.deco = 0; + return true; + } + } + if (qt_xdnd_dragsource_xid) { + qt_xdnd_dragsource_xid = 0; + if (qt_xdnd_current_widget) { + QApplication::postEvent(qt_xdnd_current_widget, new QDragLeaveEvent); + qt_xdnd_current_widget = 0; + } + return true; + } + return false; +} + +void QX11Data::xdndHandleSelectionRequest(const XSelectionRequestEvent * req) +{ + if (!req) + return; + XEvent evt; + evt.xselection.type = SelectionNotify; + evt.xselection.display = req->display; + evt.xselection.requestor = req->requestor; + evt.xselection.selection = req->selection; + evt.xselection.target = XNone; + evt.xselection.property = XNone; + evt.xselection.time = req->time; + + QDragManager *manager = QDragManager::self(); + QDrag *currentObject = manager->object; + + // which transaction do we use? (note: -2 means use current manager->object) + int at = -1; + + // figure out which data the requestor is really interested in + if (manager->object && req->time == qt_xdnd_source_current_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 XConvertSelection() using the timestamp from the XdndDrop + at = findXdndDropTransactionByTime(req->time); + if (at == -1) { + // no dice, perhaps the client was nice enough to use the same window id in XConvertSelection() + // that we sent the XdndDrop event to. + at = findXdndDropTransactionByWindow(req->requestor); + } + if (at == -1 && req->time == CurrentTime) { + // previous Qt versions always requested the data on a child of the target window + // using CurrentTime... but it could be asking for either drop data or the current drag's data + Window target = findXdndAwareParent(req->requestor); + if (target) { + if (qt_xdnd_current_target && qt_xdnd_current_target == target) + at = -2; + else + at = findXdndDropTransactionByWindow(target); + } + } + } + if (at >= 0) { + restartXdndDropExpiryTimer(); + + // use the drag object from an XdndDrop tansaction + manager->object = X11->dndDropTransactions.at(at).object; + } else if (at != -2) { + // no transaction found, we'll have to reject the request + manager->object = 0; + } + if (manager->object) { + Atom atomFormat = req->target; + int dataFormat = 0; + QByteArray data; + if (X11->xdndMimeDataForAtom(req->target, manager->dragPrivate()->data, + &data, &atomFormat, &dataFormat)) { + int dataSize = data.size() / (dataFormat / 8); + XChangeProperty (X11->display, req->requestor, req->property, + atomFormat, dataFormat, PropModeReplace, + (unsigned char *)data.data(), dataSize); + evt.xselection.property = req->property; + evt.xselection.target = atomFormat; + } + } + + // reset manager->object in case we modified it above + manager->object = currentObject; + + // ### this can die if req->requestor crashes at the wrong + // ### moment + XSendEvent(X11->display, req->requestor, False, 0, &evt); +} + +static QVariant xdndObtainData(const char *format, QVariant::Type requestedType) +{ + QByteArray result; + + QWidget* w; + QDragManager *manager = QDragManager::self(); + if (qt_xdnd_dragsource_xid && manager->object && + (w=QWidget::find(qt_xdnd_dragsource_xid)) + && (!(w->windowType() == Qt::Desktop) || w->acceptDrops())) + { + QDragPrivate * o = QDragManager::self()->dragPrivate(); + if (o->data->hasFormat(QLatin1String(format))) + result = o->data->data(QLatin1String(format)); + return result; + } + + QList<Atom> atoms; + int i = 0; + while ((qt_xdnd_types[i])) { + atoms.append(qt_xdnd_types[i]); + ++i; + } + QByteArray encoding; + Atom a = X11->xdndMimeAtomForFormat(QLatin1String(format), requestedType, atoms, &encoding); + if (!a) + return result; + + if (XGetSelectionOwner(X11->display, ATOM(XdndSelection)) == XNone) + return result; // should never happen? + + QWidget* tw = qt_xdnd_current_widget; + if (!qt_xdnd_current_widget || (qt_xdnd_current_widget->windowType() == Qt::Desktop)) + tw = new QWidget; + + XConvertSelection(X11->display, ATOM(XdndSelection), a, ATOM(XdndSelection), tw->effectiveWinId(), + qt_xdnd_target_current_time); + XFlush(X11->display); + + XEvent xevent; + bool got=X11->clipboardWaitForEvent(tw->effectiveWinId(), SelectionNotify, &xevent, 5000); + if (got) { + Atom type; + + if (X11->clipboardReadProperty(tw->effectiveWinId(), ATOM(XdndSelection), true, &result, 0, &type, 0, false)) { + if (type == ATOM(INCR)) { + int nbytes = result.size() >= 4 ? *((int*)result.data()) : 0; + result = X11->clipboardReadIncrementalProperty(tw->effectiveWinId(), ATOM(XdndSelection), nbytes, false); + } else if (type != a && type != XNone) { + DEBUG("Qt clipboard: unknown atom %ld", type); + } + } + } + if (!qt_xdnd_current_widget || (qt_xdnd_current_widget->windowType() == Qt::Desktop)) + delete tw; + + return X11->xdndMimeConvertToFormat(a, result, QLatin1String(format), requestedType, encoding); +} + + +/* + Enable drag and drop for widget w by installing the proper + properties on w's toplevel widget. +*/ +bool QX11Data::dndEnable(QWidget* w, bool on) +{ + w = w->window(); + + if (bool(((QExtraWidget*)w)->topData()->dnd) == on) + return true; // been there, done that + ((QExtraWidget*)w)->topData()->dnd = on ? 1 : 0; + + motifdndEnable(w, on); + return xdndEnable(w, on); +} + +Qt::DropAction QDragManager::drag(QDrag * o) +{ + if (object == o || !o || !o->d_func()->source) + return Qt::IgnoreAction; + + if (object) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + } + + if (object) { + // the last drag and drop operation hasn't finished, so we are going to wait + // for one second to see if it does... if the finish message comes after this, + // then we could still have problems, but this is highly unlikely + QApplication::flush(); + + QTime started = QTime::currentTime(); + QTime now = started; + do { + XEvent event; + if (XCheckTypedEvent(X11->display, ClientMessage, &event)) + qApp->x11ProcessEvent(&event); + + now = QTime::currentTime(); + if (started > now) // crossed midnight + started = now; + + // sleep 50 ms, so we don't use up CPU cycles all the time. + struct timeval usleep_tv; + usleep_tv.tv_sec = 0; + usleep_tv.tv_usec = 50000; + select(0, 0, 0, 0, &usleep_tv); + } while (object && started.msecsTo(now) < 1000); + } + + object = o; + object->d_func()->target = 0; + xdnd_data.deco = new QShapedPixmapWidget(object->source()->window()); + + willDrop = false; + + updatePixmap(); + + qApp->installEventFilter(this); + XSetSelectionOwner(X11->display, ATOM(XdndSelection), dragPrivate()->source->window()->internalWinId(), X11->time); + global_accepted_action = Qt::CopyAction; + qt_xdnd_source_sameanswer = QRect(); +#ifndef QT_NO_CURSOR + // set the override cursor (must be done here, since it is updated + // in the call to move() below) + qApp->setOverrideCursor(Qt::ArrowCursor); + restoreCursor = true; +#endif + move(QCursor::pos()); + heartbeat = startTimer(200); + + qt_xdnd_dragging = true; + + if (!QWidget::mouseGrabber()) + xdnd_data.deco->grabMouse(); + + eventLoop = new QEventLoop; + (void) eventLoop->exec(); + delete eventLoop; + eventLoop = 0; + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + qApp->restoreOverrideCursor(); + restoreCursor = false; + } +#endif + + // delete cursors as they may be different next drag. + delete noDropCursor; + noDropCursor = 0; + delete copyCursor; + copyCursor = 0; + delete moveCursor; + moveCursor = 0; + delete linkCursor; + linkCursor = 0; + + delete xdnd_data.deco; + xdnd_data.deco = 0; + if (heartbeat != -1) + killTimer(heartbeat); + heartbeat = -1; + qt_xdnd_current_screen = -1; + qt_xdnd_dragging = false; + + return global_accepted_action; + // object persists until we get an xdnd_finish message +} + +void QDragManager::updatePixmap() +{ + if (xdnd_data.deco) { + QPixmap pm; + QPoint pm_hot(default_pm_hotx,default_pm_hoty); + if (object) { + pm = dragPrivate()->pixmap; + if (!pm.isNull()) + pm_hot = dragPrivate()->hotspot; + } + if (pm.isNull()) { + if (!defaultPm) + defaultPm = new QPixmap(default_pm); + pm = *defaultPm; + } + xdnd_data.deco->pm_hot = pm_hot; + xdnd_data.deco->setPixmap(pm); + xdnd_data.deco->move(QCursor::pos()-pm_hot); + xdnd_data.deco->show(); + } +} + +QVariant QDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const +{ + QByteArray mime = mimetype.toLatin1(); + QVariant data = X11->motifdnd_active + ? X11->motifdndObtainData(mime) + : xdndObtainData(mime, requestedType); + return data; +} + +bool QDropData::hasFormat_sys(const QString &format) const +{ + return formats().contains(format); +} + +QStringList QDropData::formats_sys() const +{ + QStringList formats; + if (X11->motifdnd_active) { + int i = 0; + QByteArray fmt; + while (!(fmt = X11->motifdndFormat(i)).isEmpty()) { + formats.append(QLatin1String(fmt)); + ++i; + } + } else { + int i = 0; + while ((qt_xdnd_types[i])) { + QStringList formatsForAtom = X11->xdndMimeFormatsForAtom(qt_xdnd_types[i]); + for (int j = 0; j < formatsForAtom.size(); ++j) { + if (!formats.contains(formatsForAtom.at(j))) + formats.append(formatsForAtom.at(j)); + } + ++i; + } + } + return formats; +} + +QT_END_NAMESPACE + +#endif // QT_NO_DRAGANDDROP