Index: /trunk/src/gui/kernel/qapplication_pm.cpp
===================================================================
--- /trunk/src/gui/kernel/qapplication_pm.cpp	(revision 447)
+++ /trunk/src/gui/kernel/qapplication_pm.cpp	(revision 448)
@@ -117,5 +117,5 @@
 
 static HWND autoCaptureWnd = NULLHANDLE;
-static bool autoCaptureReleased = FALSE;
+static bool autoCaptureReleased = false;
 static void setAutoCapture(HWND); // automatic capture
 static void releaseAutoCapture();
@@ -2117,4 +2117,17 @@
 }
 #endif
+
+/*!
+    \internal
+    In DnD, the mouse release event never appears, so the
+    mouse button state machine must be manually reset.
+*/
+void qt_pmMouseButtonUp()
+{
+    // release any stored mouse capture
+    qt_button_down = 0;
+    autoCaptureReleased = true;
+    releaseAutoCapture();
+}
 
 //
Index: /trunk/src/gui/kernel/qdnd_pm.cpp
===================================================================
--- /trunk/src/gui/kernel/qdnd_pm.cpp	(revision 447)
+++ /trunk/src/gui/kernel/qdnd_pm.cpp	(revision 448)
@@ -52,4 +52,5 @@
 #include "qcursor.h"
 #include "qdesktopwidget.h"
+#include "qfile.h"
 #include "qdnd_p.h"
 #include "qdebug.h"
@@ -68,4 +69,7 @@
 
 #if !defined(QT_NO_DRAGANDDROP) && !defined(QT_NO_CLIPBOARD)
+
+extern void qt_pmMouseButtonUp(); // defined in qapplication_pm.cpp
+extern void qt_DrgFreeDragtransfer(DRAGTRANSFER *xfer); // defined in qmime_pm.cpp
 
 /** \internal
@@ -271,4 +275,16 @@
         return DO_MOVE;
     return DO_UNKNOWN;
+}
+
+static USHORT toPmDragDropOps(Qt::DropActions actions)
+{
+    USHORT op = DO_UNKNOWN;
+    if (actions & Qt::LinkAction)
+        op |= DO_LINKABLE;
+    if (actions & Qt::CopyAction)
+        op |= DO_COPYABLE;
+    if (actions & Qt::MoveAction)
+        op |= DO_MOVEABLE;
+    return op;
 }
 
@@ -617,4 +633,368 @@
 }
 
+//---------------------------------------------------------------------
+//                    QPMCoopDragWorker
+//---------------------------------------------------------------------
+
+class QPMCoopDragWorker : public QPMMime::DragWorker, public QPMObjectWindow
+{
+public:
+    QPMCoopDragWorker() : info(0) {}
+    bool collectWorkers(QDrag *o);
+
+    // DragWorker interface
+    void init();
+    bool cleanup(bool isCancelled);
+    bool isExclusive() const { return true; }
+    ULONG itemCount() const { return 0; }
+    HWND hwnd() const;
+    DRAGINFO *createDragInfo(const QString &targetName, USHORT supportedOps);
+
+    // QPMObjectWindow interface
+    MRESULT message(ULONG msg, MPARAM mp1, MPARAM mp2);
+    
+private:
+    QList<DragWorker*> workers;
+    // todo check workers!
+    //QPtrList<DragWorker> workers;
+    DRAGINFO *info;
+};
+
+bool QPMCoopDragWorker::collectWorkers(QDrag *o)
+{
+    Q_ASSERT(o);
+    if (!o)
+        return false;
+
+    bool gotExcl = false; // got isExclusive() worker?
+    bool skipExcl = false; // skip isExclusive() or itemCount() > 1 workers?
+    ULONG coopLevel = 0; // itemCount() level for !isExclusive() workers 
+
+    bool gotExclForMime = false;
+
+    // go through all formats and all converters to collect DragWorkers
+    QMimeData *mimeData = o->mimeData();
+    foreach (const QString &fmt, mimeData->formats()) {
+        DEBUG(() << "QPMCoopDragWorker: Searching for worker for mime" << fmt);
+        foreach (QPMMime *mime, QPMMime::all()) {
+            DragWorker *wrk = mime->dragWorkerFor(fmt, mimeData);
+            if (!wrk)
+                continue;
+            DEBUG(() << "QPMCoopDragWorker: Got worker" << wrk
+                     << "(isExclusive" << wrk->isExclusive() << ", "
+                     << "itemCount" << wrk->itemCount() << ") from convertor"
+                     << mime->convertorName() << " (gotExclForMime"
+                     << gotExclForMime << ", " << "gotExcl" << gotExcl
+                     << ", skipExcl" << skipExcl << ", coopLevel"
+                     << coopLevel << ")");
+            if (wrk->isExclusive()) {
+                if (!skipExcl && !gotExclForMime) {
+                    gotExclForMime = true;
+                    if (!gotExcl) {
+                        gotExcl = true;
+                        workers.append(wrk);
+                    } else {
+                        // skip everything exclusive unless it's exactly the
+                        // same worker
+                        skipExcl = !workers.contains(wrk);
+                    }
+                }
+                // continue to search for a fall-back cooperative 1-item worker
+                // (like QPMMimeAnyMime) for the case if this worker quits
+                // the game
+                continue;
+            }
+            ULONG itemCnt = wrk->itemCount();
+            if (itemCnt == 0) {
+                DEBUG(() << "QPMCoopDragWorker: Cooperative DragWorker"
+                         << wrk << "for mime " << fmt << " has "
+                         << "itemCount = 0!");
+                continue;
+            }
+            if (itemCnt > 1) {
+                // coop workers with item count > 1 are also considered exclusive
+                // here, because may not co-exist with 1-item workers that should
+                // always be able to contribute
+                if (!gotExcl && !skipExcl && !gotExclForMime) {
+                    gotExclForMime = true;
+                    workers.append(wrk);
+                    if (!coopLevel)
+                        coopLevel = itemCnt;
+                    // only those for the same number of items can proceed
+                    if (itemCnt != coopLevel)
+                        skipExcl = true;
+                }
+                // continue to search for a fall-back cooperative 1-item worker
+                // (like QPMMimeAnyMime) for the case if this worker quits
+                // the game
+                continue;
+            }
+            workers.append(wrk);
+            // Don't search for other workrers for the same mime type --
+            // we've already got a drag worker for it and adding another
+            // one would just introduce mime type duplicates on the drop
+            // target's side (where the first encountered drop worker
+            // for the given RMF would be used for actual data transfer
+            // anyway). See also QClipboard::setData(). 
+            break;
+        }
+        if (gotExclForMime) {
+            // ensure we have a fall-back coop (should be the last added item)
+            DragWorker *w = workers.last();
+            if (w->isExclusive() || w->itemCount() > 1) {
+                DEBUG(() << "QPMCoopDragWorker: DragWorker" << w
+                         << "for" << fmt << "(isExclusive" << w->isExclusive()
+                         << ", itemCount" << w->itemCount()
+                         <<") has no fall-back cooperative 1-item worker!");
+                workers.removeLast();
+            }
+            gotExclForMime = false;
+        } else {
+            // got a regular (non-fall-back) 1-item coop, skip evreything else
+            skipExcl = true;
+        }
+    }
+
+    // remove either all exclusive workers or all their fall-back workers
+    // (depending on skipExcl) and remove duplicates 
+    for (QList<DragWorker*>::iterator it = workers.begin();
+         it <= workers.end();) {
+        DragWorker *wrk = *it;
+        bool excl = wrk->isExclusive() || wrk->itemCount() > 1;
+        if (skipExcl == excl || workers.count(wrk) > 1) {
+            it = workers.erase(it);
+        } else {
+            ++it;
+        }
+    }
+
+#if defined(QDND_DEBUG)            
+    foreach (DragWorker *wrk, workers) {
+        DEBUG(() << "QPMCoopDragWorker: Will use worker" << wrk
+                 << "(isExclusive" << wrk->isExclusive()
+                 << ", itemCount" << wrk->itemCount() << ")"); 
+    }
+#endif
+
+    Q_ASSERT(workers.count() > 0);
+    return workers.count() > 0;
+}
+
+HWND QPMCoopDragWorker::hwnd() const
+{
+    DragWorker *firstWorker = workers.first();
+    Q_ASSERT(firstWorker);
+    if (!firstWorker)
+        return 0;
+    
+    if (firstWorker->isExclusive() && firstWorker->itemCount() == 0) {
+        // this is a super exclusive worker that will do everything on its own
+        return firstWorker->hwnd();
+    }
+    
+    return QPMObjectWindow::hwnd();
+}
+
+void QPMCoopDragWorker::init()
+{
+    Q_ASSERT(source());
+    foreach(DragWorker *wrk, workers) {
+        wrk->src = source();
+        wrk->init();
+    }
+}
+
+bool QPMCoopDragWorker::cleanup(bool isCancelled)
+{
+    bool moveDisallowed = false;
+    
+    foreach(DragWorker *wrk, workers) {
+        // disallow the Move operation if at least one worker asked so
+        moveDisallowed |= wrk->cleanup(isCancelled);
+        wrk->src = 0;
+    }
+    workers.clear();
+    info = 0;
+    return moveDisallowed;
+}
+
+DRAGINFO *QPMCoopDragWorker::createDragInfo(const QString &targetName,
+                                            USHORT supportedOps)
+{
+    Q_ASSERT(!info);
+    if (info)
+        return 0;
+
+    DragWorker *firstWorker = workers.first();
+    Q_ASSERT(firstWorker);
+    if (!firstWorker)
+        return 0;
+
+    ULONG itemCnt = firstWorker->itemCount();
+
+    if (firstWorker->isExclusive() && itemCnt == 0) {
+        // this is a super exclusive worker that will do everything on its own
+        DEBUG(() << "QPMCoopDragWorker: Will redirect to super worker"
+                 << firstWorker);
+        return firstWorker->createDragInfo(targetName, supportedOps);
+    }
+    
+    // note that all workers at this place require the same amount of items
+    // (guaranteed by collectWorkers())
+    
+    DEBUG(() << "QPMCoopDragWorker: itemCnt" << itemCnt);
+
+    info = DrgAllocDraginfo(itemCnt);
+    Q_ASSERT(info);
+    if (!info)
+        return 0;
+    
+    // collect all mechanism/format pairs
+    QByteArray allFormats;
+    foreach (DragWorker *wrk, workers) {
+        QByteArray formats = wrk->composeFormatString();
+        Q_ASSERT(!formats.isNull());
+        if (!formats.isNull()) {
+            if (allFormats.isNull())
+                allFormats = formats;
+            else {
+                allFormats += ",";
+                allFormats += formats;
+            }
+        }
+    }
+
+    DEBUG(() << "QPMCoopDragWorker: allFormats" << allFormats);
+
+    static ULONG itemID = 0;
+    
+    const char *type = 0;
+    const char *ext = 0;
+    firstWorker->defaultFileType(type, ext);
+
+    bool ok = true;
+    for (ULONG i = 0; i < itemCnt; ++i) {
+        DRAGITEM *item = DrgQueryDragitemPtr(info, i);
+        Q_ASSERT(item);
+        if (!item) {
+            ok = false;
+            break;
+        }
+
+        QString name;
+        if (itemCnt == 1)
+            name = targetName;
+        else
+            name = QString(QLatin1String("%1 %2")).arg(targetName).arg(i + 1);
+        
+        if (ext) {
+            name += QLatin1Char('.');
+            name += QFile::decodeName(QByteArray(ext));
+        }
+
+        DEBUG(() << "QPMCoopDragWorker: item" << i << ": type" << type
+                 << " name" << name);
+
+        // Note 1: DRAGITEM::hstrType is actually ignored by WPS,
+        // only the target extension matters.
+        
+        // Note 2: We're not required to fill in the hwndItem field because we
+        // use the DC_PREPARE flag (to indicate it will be filled later, after
+        // DM_RENDERPREPARE); however, Mozilla refuses to render if hwndItem
+        // is initially 0. Set it to our HWND instead (we'll issue a warning if
+        // DM_RENDER or DM_ENDCONVERSATION is erroneously sent to us) 
+
+        item->hwndItem = hwnd();
+        item->ulItemID = itemID ++;
+        item->hstrType = DrgAddStrHandle(type ? type : DRT_UNKNOWN);
+        item->hstrRMF = DrgAddStrHandle(allFormats);
+        item->hstrContainerName = 0;
+        item->hstrSourceName = 0;
+        item->hstrTargetName = DrgAddStrHandle(QFile::encodeName(name));
+        item->cxOffset = 0;
+        item->cyOffset = 0;
+        item->fsControl = DC_PREPARE; // require DM_RENDERPREPARE from target
+        item->fsSupportedOps = supportedOps;
+    }
+   
+    if (!ok) {
+        DrgFreeDraginfo(info);
+        info = 0;
+    }
+    
+    return info;
+}
+
+MRESULT QPMCoopDragWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+    if (msg == DM_RENDERPREPARE) {
+        if (!info) {
+            qWarning("Drop target sent DM_RENDERPREPARE after the DnD session "
+                     "is over!");
+            // free the given DRAGTRANSFER structure to avoud memory leak 
+            DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
+            if (xfer)
+                qt_DrgFreeDragtransfer(xfer);
+            return (MRESULT)FALSE;
+        }
+    
+        DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
+        Q_ASSERT(xfer && xfer->pditem);
+        if (!xfer || !xfer->pditem)
+            return (MRESULT)FALSE;
+        
+        // find the item's index (ordinal number)
+        ULONG itemCnt = DrgQueryDragitemCount(info);
+        ULONG index = 0;
+        for (; index <  itemCnt; ++index)
+            if (DrgQueryDragitemPtr(info, index) == xfer->pditem)
+                break;
+        
+        Q_ASSERT(index < itemCnt);
+        if (index >= itemCnt)
+            return (MRESULT)FALSE;
+
+        DEBUG(() << "QPMCoopDragWorker: Got DM_RENDERPREPARE to"
+                 << QPMMime::queryHSTR(xfer->hstrSelectedRMF) << "for item"
+                 << index << "(id" << xfer->pditem->ulItemID << ")");
+        
+        QByteArray drm, drf;
+        if (!QPMMime::parseRMF(xfer->hstrSelectedRMF, drm, drf)) {
+            Q_ASSERT(false);
+            return (MRESULT)FALSE;
+        }
+
+        DragWorker *wrk = 0;
+        foreach(wrk, workers)
+            if (wrk->prepare(drm, drf, xfer->pditem, index))
+                break;
+        if (!wrk) {
+            DEBUG(() << "QPMCoopDragWorker: No suitable worker found");
+            return (MRESULT)FALSE;
+        }
+
+        xfer->pditem->hwndItem = wrk->hwnd();
+        Q_ASSERT(xfer->pditem->hwndItem);
+        return (MRESULT)(xfer->pditem->hwndItem ? TRUE : FALSE);
+    }
+    
+    if (msg == DM_RENDER || msg == DM_ENDCONVERSATION) {
+        qWarning("Drop target sent DM_RENDER or DM_ENDCONVERSATION to the "
+                 "drag source window instead of the drag item window!");
+        if (msg == DM_RENDER) {
+            // free the given DRAGTRANSFER structure to avoud memory leak 
+            DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
+            if (xfer)
+                qt_DrgFreeDragtransfer(xfer);
+        }
+    }
+    
+    return (MRESULT)FALSE;
+}
+
+//---------------------------------------------------------------------
+//                    QDragManager
+//---------------------------------------------------------------------
+
 Qt::DropAction QDragManager::drag(QDrag *o)
 
@@ -622,11 +1002,163 @@
     DEBUG(() << "QDragManager::drag");
 
-    // @todo implement
-    return Qt::IgnoreAction;
-}
-
-void QDragManager::cancel(bool /* deleteSource */)
-{
-    // @todo implement
+    if (object == o || !o || !o->d_func()->source)
+        return Qt::IgnoreAction;
+
+    if (object) {
+        cancel();
+        qApp->removeEventFilter(this);
+        beingCancelled = false;
+    }
+
+    // detect a mouse button to end dragging
+    LONG vkTerminate = 0;
+    {
+        ULONG msg = WinQuerySysValue(HWND_DESKTOP, SV_BEGINDRAG) & 0xFFFF;
+        switch(msg) {
+            case WM_BUTTON1MOTIONSTART: vkTerminate = VK_BUTTON1; break; 
+            case WM_BUTTON2MOTIONSTART: vkTerminate = VK_BUTTON2; break; 
+            case WM_BUTTON3MOTIONSTART: vkTerminate = VK_BUTTON3; break; 
+        }
+
+        if (WinGetKeyState(HWND_DESKTOP, vkTerminate) & 0x8000) {
+            // prefer the default button if it is pressed
+        } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON2) & 0x8000) {
+            vkTerminate = VK_BUTTON2;
+        } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON1) & 0x8000) {
+            vkTerminate = VK_BUTTON1;
+        } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON3) & 0x8000) {
+            vkTerminate = VK_BUTTON3;
+        } else {
+            vkTerminate = 0;
+        }
+    }
+
+    if (!vkTerminate) {
+        DEBUG(() << "QDragManager::drag: No valid mouse button pressed, "
+                    "dragging cancelled!");
+        o->deleteLater();
+        return Qt::IgnoreAction;
+    }
+
+    USHORT supportedOps = toPmDragDropOps(dragPrivate()->possible_actions);
+
+    static QPMCoopDragWorker dragWorker;
+    
+    bool ok = dragWorker.collectWorkers(o);
+    Q_ASSERT(ok);
+    Q_ASSERT(dragWorker.hwnd());
+    if (!ok || !dragWorker.hwnd()) {
+        o->deleteLater();
+        return Qt::IgnoreAction;
+    }
+
+    dragWorker.src = o->mimeData();
+    dragWorker.init();
+    DRAGINFO *info = dragWorker.createDragInfo(o->objectName(), supportedOps);
+
+    Q_ASSERT(info);
+    if (!info) {
+        dragWorker.cleanup(true /* isCancelled */);
+        dragWorker.src = 0;
+        o->deleteLater();
+        return Qt::IgnoreAction;
+    }
+
+    object = o;
+
+    DEBUG(() << "QDragManager::drag: actions"
+             << dragActionsToString(dragPrivate()->possible_actions));
+
+    dragPrivate()->target = 0;
+
+#ifndef QT_NO_ACCESSIBILITY
+    QAccessible::updateAccessibility(this, 0, QAccessible::DragDropStart);
+#endif
+
+    // @todo custom drag pixmap?
+
+    DRAGIMAGE img;
+    img.cb = sizeof(DRAGIMAGE);
+    img.hImage = WinQuerySysPointer(HWND_DESKTOP, SPTR_FILE, FALSE);
+    img.fl = DRG_ICON;
+    img.cxOffset = 0;
+    img.cyOffset = 0;
+
+    // the mouse is most likely captured by Qt at this point, uncapture it
+    // or DrgDrag() will definitely fail
+    WinSetCapture(HWND_DESKTOP, 0);
+
+    HWND target = DrgDrag(dragWorker.hwnd(), info, &img, 1, vkTerminate,
+                          (PVOID)0x80000000L); // don't lock the desktop PS
+
+    DEBUG(("QDragManager::drag: DrgDrag() returned %08lX (error 0x%08lX)",
+            target, WinGetLastError(0)));
+
+    // we won't get any mouse release event, so manually adjust qApp state
+    qt_pmMouseButtonUp();
+
+    bool moveDisallowed = dragWorker.cleanup(beingCancelled || target == 0);
+    dragWorker.src = 0;
+    
+    moveDisallowed |= beingCancelled || target == 0 ||
+                      info->usOperation != DO_MOVE;
+
+    DEBUG(() << "QDragManager::drag: moveDisallowed" << moveDisallowed);
+
+    Qt::DropAction ret = Qt::IgnoreAction;
+    if (target != 0) {
+        ret = toQDragDropAction(info->usOperation);
+        if (moveDisallowed && info->usOperation == DO_MOVE)
+            ret = Qt::TargetMoveAction;
+    }
+
+    DEBUG(() << "QDragManager::drag: result" << dragActionsToString(ret));
+
+    if (target == 0)
+        DrgDeleteDraginfoStrHandles(info);
+    DrgFreeDraginfo(info);
+
+    if (!beingCancelled) {
+        dragPrivate()->target = QWidget::find(target);
+        cancel(); // this will delete o (object)
+    }
+
+    beingCancelled = false;
+
+#ifndef QT_NO_ACCESSIBILITY
+    QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd);
+#endif
+
+    return ret;
+}
+
+void QDragManager::cancel(bool deleteSource)
+{
+    // Note: the only place where this function is called with
+    // deleteSource = false so far is QDrag::~QDrag()
+
+    Q_ASSERT(object && !beingCancelled);
+    if (!object || beingCancelled)
+        return;
+    
+    beingCancelled = true;
+
+    object->setMimeData(0);
+
+    if (deleteSource)
+        object->deleteLater();
+    object = 0;
+
+#ifndef QT_NO_CURSOR
+    // insert cancel code here ######## todo
+
+    if (restoreCursor) {
+        QApplication::restoreOverrideCursor();
+        restoreCursor = false;
+    }
+#endif
+#ifndef QT_NO_ACCESSIBILITY
+    QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd);
+#endif
 }
 
Index: /trunk/src/gui/kernel/qmime.h
===================================================================
--- /trunk/src/gui/kernel/qmime.h	(revision 447)
+++ /trunk/src/gui/kernel/qmime.h	(revision 448)
@@ -322,4 +322,5 @@
     friend class QClipboard;
     friend class QPMDragData;
+    friend class QPMCoopDragWorker;
 
     struct Match
