Index: trunk/src/kernel/qapplication_pm.cpp
===================================================================
--- trunk/src/kernel/qapplication_pm.cpp	(revision 96)
+++ trunk/src/kernel/qapplication_pm.cpp	(revision 97)
@@ -657,8 +657,4 @@
     // there is no need to unregister private window classes -- it is done
     // automatically upon process termination.
-//    QAsciiDictIterator<int> it(*winclassNames);
-//    const char *k;
-//    while ( (k = it.currentKey()) ) {
-//    }
     delete winclassNames;
     winclassNames = 0;
@@ -913,4 +909,8 @@
 extern uint qGlobalPostedEventsCount();
 
+#ifndef QT_NO_DRAGANDDROP
+extern MRESULT qt_dispatchDragAndDrop( QWidget *, const QMSG & ); // qdnd_pm.cpp
+#endif
+
 /*!
     The message procedure calls this function for every message
@@ -945,5 +945,4 @@
     }
 }
-
 
 //
@@ -1169,4 +1168,8 @@
 	result = widget->translateWheelEvent( qmsg );
         rc = (MRESULT) result;
+#endif
+#ifndef QT_NO_DRAGANDDROP
+    } else if ( msg >= WM_DRAGFIRST && msg <= WM_DRAGLAST ) {
+        RETURN( qt_dispatchDragAndDrop( widget, qmsg ) );
 #endif
     } else {
@@ -1589,5 +1592,5 @@
             break;
 
-/// @todo (dmik) later
+/// @todo (dmik) remove?
 //	case WM_IME_STARTCOMPOSITION:
 //    	    result = QInputContext::startComposition();
@@ -2182,6 +2185,17 @@
 static int qt_extraKeyState = 0;
 
-// State holder for mouse buttons
-static int qt_buttonState = 0;
+static int mouseButtonState()
+{
+    int state = 0;
+
+    if ( WinGetKeyState( HWND_DESKTOP, VK_BUTTON1 ) & 0x8000 )
+        state |= Qt::LeftButton;
+    if ( WinGetKeyState( HWND_DESKTOP, VK_BUTTON2 ) & 0x8000 )
+        state |= Qt::RightButton;
+    if ( WinGetKeyState( HWND_DESKTOP, VK_BUTTON3 ) & 0x8000 )
+        state |= Qt::MidButton;
+
+    return state;
+}
 
 //
@@ -2195,5 +2209,5 @@
     autoCaptureWnd = h;
 
-    if ( !qt_buttonState ) {
+    if ( !mouseButtonState() ) {
         // all buttons released, we don't actually capture the mouse
         // (see QWidget::translateMouseEvent())
@@ -2242,10 +2256,6 @@
 static int translateButtonState( USHORT s, int type, int button )
 {
-    if ( type == QEvent::MouseButtonPress )
-        qt_buttonState |= ( button & Qt::MouseButtonMask );
-    if ( type == QEvent::MouseButtonRelease )
-        qt_buttonState &= ~( button & Qt::MouseButtonMask );
-
-    int bst = qt_buttonState;
+    int bst = mouseButtonState();
+
     if ( type == QEvent::ContextMenu ) {
         if ( WinGetKeyState( HWND_DESKTOP, VK_SHIFT ) & 0x8000 )
@@ -2279,13 +2289,15 @@
 }
 
-//@@TODO (dmik): later
-//// In DnD, the mouse release event never appears, so the
-//// mouse button state machine must be manually reset
-///*! \internal */
-//void QApplication::winMouseButtonUp()
-//{
-//    qt_button_down = 0;
-//    releaseAutoCapture();
-//}
+/*! \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();
+}
 
 bool QETWidget::translateMouseEvent( const QMSG &qmsg )
@@ -2358,5 +2370,5 @@
     type   = (QEvent::Type)mouseTbl[++i];	// event type
     button = mouseTbl[++i];			// which button
-//@@TODO (dmik): later (extra buttons)
+/// @todo (dmik) later (extra buttons)
 //    if ( button > Qt::MidButton ) {
 //	switch( GET_XBUTTON_WPARAM( msg.wParam ) ) {
@@ -2424,5 +2436,6 @@
 #endif
 	if ( curWin != winId() ) {		// new current window
-//@@TODO (dmik): add CS_HITTEST to our window classes and handle WM_HITTEST,
+/// @todo (dmik)            
+//  add CS_HITTEST to our window classes and handle WM_HITTEST,
 //  otherwise disabled windows will not get mouse events?
 	    qt_dispatchEnterLeave( this, QWidget::find(curWin) );
Index: trunk/src/kernel/qdnd_pm.cpp
===================================================================
--- trunk/src/kernel/qdnd_pm.cpp	(revision 97)
+++ trunk/src/kernel/qdnd_pm.cpp	(revision 97)
@@ -0,0 +1,1203 @@
+/****************************************************************************
+** $Id$
+**
+** Implementation of the PM direct manipulation protocol (drag and drop) for Qt.
+**
+** Copyright (C) 1992-2002 Trolltech AS.  All rights reserved.
+** Copyright (C) 2004 Norman ASA.  Initial OS/2 Port.
+** Copyright (C) 2005 netlabs.org.  Further OS/2 Development.
+**
+** This file is part of the kernel module of the Qt GUI Toolkit.
+**
+** This file may be distributed under the terms of the Q Public License
+** as defined by Trolltech AS of Norway and appearing in the file
+** LICENSE.QPL included in the packaging of this file.
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU General Public License version 2 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.
+**
+** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+** licenses may use this file in accordance with the Qt Commercial License
+** Agreement provided with the Software.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
+**   information about Qt Commercial License Agreements.
+** See http://www.trolltech.com/qpl/ for QPL licensing information.
+** See http://www.trolltech.com/gpl/ for GPL licensing information.
+**
+** Contact info@trolltech.com if any conditions of this licensing are
+** not clear to you.
+**
+**********************************************************************/
+
+#include "qapplication.h"
+
+#ifndef QT_NO_DRAGANDDROP
+
+#include "qapplication_p.h"
+#include "qeventloop.h"
+#include "qpainter.h"
+#include "qwidget.h"
+#include "qdragobject.h"
+#include "qimage.h"
+#include "qbuffer.h"
+#include "qdatastream.h"
+#include "qbitmap.h"
+#include "qptrlist.h"
+#include "qt_os2.h"
+
+#if defined(QT_ACCESSIBILITY_SUPPORT)
+#include "qaccessible.h"
+#endif
+
+// if you enable this, it makes sense to enable it in qmime_pm.cpp as well
+//#define QT_DEBUG_DND
+
+extern void qt_pmMouseButtonUp(); // defined in qapplication_pm.cpp
+
+bool QDragManager::eventFilter( QObject *, QEvent *)
+{
+    // not used in the PM implementation
+    return FALSE;
+}
+
+void QDragManager::timerEvent( QTimerEvent* )
+{
+    // not used in the PM implementation
+}
+
+void QDragManager::cancel( bool deleteSource /* = TRUE */ )
+{
+    // Note: currently, this method is called only by
+    // QDragObject::~QDragObject() with deleteSource = FALSE 
+    
+    Q_ASSERT( object && !beingCancelled );
+    if ( !object || beingCancelled )
+        return;
+    
+    beingCancelled = TRUE;
+    
+    dragSource = 0;
+    
+    if ( !deleteSource ) {
+        object = 0;
+    } else {
+        QDragObject *obj = object;
+        object = 0;
+        delete obj;
+    }
+
+#ifndef QT_NO_CURSOR
+    if ( restoreCursor ) {
+	QApplication::restoreOverrideCursor();
+	restoreCursor = FALSE;
+    }
+#endif
+#if defined(QT_ACCESSIBILITY_SUPPORT)
+    QAccessible::updateAccessibility( this, 0, QAccessible::DragDropEnd );
+#endif
+}
+
+void QDragManager::move( const QPoint & )
+{
+    // not used in the PM implementation
+}
+
+
+void QDragManager::drop()
+{
+    // not used in the PM implementation
+}
+
+/** \internal
+ *  Data for QDragEnterEvent/QDragMoveEvent/QPMDropEvent.
+ */
+class QPMDragData
+{
+public:    
+    QPMDragData()
+        : initialized( FALSE ), dropped( FALSE )
+        , gotWorkers( FALSE ), di( NULL )
+    {}
+
+    ~QPMDragData() { reset(); }
+    
+    void initialize( DRAGINFO *di );
+    void reset( bool isAccepted, bool isActAccepted );
+    void reset() { reset( FALSE, FALSE ); }
+        
+    void setDropped( bool d ) { dropped = d; }
+    bool isDropped() const { return dropped; }
+    
+    DRAGINFO *info() const { return di; }
+    
+    bool provides( const char *format );
+    const char *format( int fn );
+    QByteArray encodedData( const char *format );
+    
+private:        
+
+    void initWorkers();
+
+    bool initialized : 1;
+    bool dropped : 1;
+    bool gotWorkers : 1;
+
+    DRAGINFO *di;
+    QPtrList<QPMMime::DropWorker> workers;
+};
+
+void QPMDragData::initialize( DRAGINFO *info )
+{
+    Q_ASSERT( info );
+    if ( initialized || !info )
+        return;
+
+    initialized = TRUE;
+    di = info;
+}
+
+void QPMDragData::initWorkers()
+{
+    Q_ASSERT( initialized );
+    if ( !initialized || gotWorkers )
+        return;
+
+    gotWorkers = TRUE;
+
+    // go through all convertors and collect DropWorkers to use    
+    QPtrList<QPMMime> mimes = QPMMime::all();
+    for ( QPMMime *mime = mimes.first(); mime; mime = mimes.next() ) {
+        QPMMime::DropWorker *wrk = mime->dropWorkerFor( di );
+        if ( wrk ) {
+            if ( wrk->isExclusive() ) {
+                // ignore all other workers if some of them identified itself
+                // as exclusive
+                workers.clear();
+                workers.append( wrk );
+                break;
+            }
+            // ensure there are no duplicates
+            if ( !workers.containsRef( wrk ) )
+                workers.append( wrk );
+        }
+    }
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "QPMDragData: %d drop workers for DRAGINFO %p",
+            workers.count(), di );
+#endif
+
+    // init all workers
+    for ( QPMMime::DropWorker *w = workers.first(); w; w = workers.next() ) {
+        w->nfo = di;
+        w->init();
+    }
+}
+
+void QPMDragData::reset( bool isAccepted, bool isActAccepted )
+{
+    if ( !initialized )
+        return;
+    
+    // cleanup all workers
+    for ( QPMMime::DropWorker *w = workers.first(); w; w = workers.next() ) {
+        w->cleanup( isAccepted, isActAccepted );
+        w->nfo = NULL;
+    }
+
+    workers.clear();
+    di = NULL; 
+    initialized = dropped = gotWorkers = FALSE;
+}
+
+bool QPMDragData::provides( const char *format )
+{
+    if ( !gotWorkers )
+        initWorkers();
+
+    bool result = FALSE;
+
+    for ( QPMMime::DropWorker *w = workers.first();
+          w && !result; w = workers.next() )
+        result = w->provides( format );
+
+    return result;
+}
+
+const char *QPMDragData::format( int fn )
+{
+    if ( !gotWorkers )
+        initWorkers();
+
+    for ( QPMMime::DropWorker *w = workers.first();
+          w; w = workers.next() ) {
+        int count = w->formatCount();
+        if ( fn < count )
+            return w->format( fn );
+        fn = fn - count;   
+    }
+    
+    return NULL;    
+}
+
+QByteArray QPMDragData::encodedData( const char *format )
+{
+    if ( !gotWorkers )
+        initWorkers();
+
+    QByteArray result;
+
+    for ( QPMMime::DropWorker *w = workers.first();
+          w && result.isNull(); w = workers.next() )
+        result = w->encodedData( format );
+
+    return result;
+}
+
+class QPMDropEvent : public QDropEvent
+{
+public:
+    QPMDropEvent( const QPoint &pos, QPMDragData *data )
+        : QDropEvent( pos ) { d = data; }
+       
+    inline bool isTrulyAccepted() const { return accpt; }
+    inline bool isActionTrulyAccepted() const { return accptact; }
+};
+
+class QPMDragEnterEvent : public QDragEnterEvent
+{
+public:
+    QPMDragEnterEvent( const QPoint &pos, QPMDragData *data )
+        : QDragEnterEvent( pos ) { d = data; }
+
+    inline bool isTrulyAccepted() const { return accpt; }
+    inline bool isActionTrulyAccepted() const { return accptact; }
+};
+
+class QPMDragMoveEvent : public QDragMoveEvent
+{
+public:
+    QPMDragMoveEvent( const QPoint &pos, QPMDragData *data )
+        : QDragMoveEvent( pos ) { d = data; }
+
+    inline bool isTrulyAccepted() const { return accpt; }
+    inline bool isActionTrulyAccepted() const { return accptact; }
+};
+
+bool QDropEvent::provides( const char* mimeType ) const
+{
+    QPMDragData *data = static_cast<QPMDragData *> (d);
+    Q_ASSERT( data );
+    if ( !data )
+        return FALSE;
+    
+    return data->provides( mimeType );
+}
+
+const char *QDropEvent::format( int fn ) const
+{
+    QPMDragData *data = static_cast<QPMDragData *> (d);
+    Q_ASSERT( data );
+    if ( !data )
+        return NULL;
+    
+    return data->format( fn );
+}
+
+QByteArray QDropEvent::encodedData( const char *format ) const
+{
+    QByteArray array;
+
+    QPMDragData *data = static_cast<QPMDragData *> (d);
+    Q_ASSERT( data );
+    
+    if ( !data || !data->isDropped() ) {
+        // This is a QDragEnterEvent/QDragMoveEvent subclass; we cannot provide
+        // any MIME contents yet because it's impossible to do data transfers
+        // before DM_DROP is sent. Return shortly.
+        return array;
+    }
+
+    array = data->encodedData( format );
+    return array;
+}
+
+/*!
+ *  \internal
+ *  Direct manipulation (Drag & Drop) event handler 
+ */
+MRESULT qt_dispatchDragAndDrop( QWidget *widget, const QMSG &qmsg )
+{
+    static HWND lastDragOverHwnd = 0; // last target window
+    static USHORT lastDragOverOp = 0; // last DM_DRAGOVER operation
+    static USHORT lastRealOp = 0; // last real op (never DO_DEFAULT or DO_UNKNOWN)
+    static USHORT defaultOp = 0; // default op for DO_DEFAULT or DO_UNKNOWN
+
+    static USHORT supportedOps = 0; // operations supported by all items
+    static bool sourceAllowsOp = FALSE; // does source allow requested operation
+
+    static bool lastAccept = FALSE; // last reply from the target
+    static bool lastAcceptAction = FALSE; // last action reply from the target
+    
+    static QPMDragData dragData;
+    
+    Q_ASSERT( widget );
+    
+    BOOL ok = FALSE;
+
+    switch( qmsg.msg ) {
+        case DM_DRAGOVER: {
+            bool first = lastDragOverHwnd != qmsg.hwnd;
+            if ( first ) {
+                // the first DM_DRAGOVER message
+                lastDragOverHwnd = qmsg.hwnd;
+                lastAccept = lastAcceptAction = FALSE;
+                supportedOps = DO_COPYABLE | DO_MOVEABLE | DO_LINKABLE;
+                // ensure drag data is reset (just in case of a wrong msg flow...)
+                dragData.reset();
+            }
+
+            Q_ASSERT( first || widget->acceptDrops() );
+            if ( !widget->acceptDrops() ) {
+                if ( !first ) {
+                    // Odin32 apps are dramatically bogus, they continue to send
+                    // DM_DRAGOVER even if we reply DOR_NEVERDROP. Simulate
+                    // DM_DRAGLEAVE
+                    lastDragOverHwnd = 0;
+                    dragData.reset();
+                }
+                return MRFROM2SHORT( DOR_NEVERDROP, 0 );
+            }
+            
+            DRAGINFO *info = (DRAGINFO *) qmsg.mp1;
+            ok = DrgAccessDraginfo( info );
+            Q_ASSERT( ok && info );
+            if ( !ok || !info )
+                return MRFROM2SHORT( DOR_NEVERDROP, 0 );
+
+            USHORT dropReply = DOR_DROP;
+
+            if ( first  ) {
+                // determine the set of operations supported by *all* items
+                // (this implies that DRAGITEM::fsSupportedOps is a bit field) 
+                ULONG itemCount = DrgQueryDragitemCount( info );
+                for ( ULONG i = 0; i < itemCount; ++ i ) {
+                    PDRAGITEM item = DrgQueryDragitemPtr( info, i );
+                    Q_ASSERT( item );
+                    if ( !item ) {
+                        dropReply = DOR_NEVERDROP;
+                        break;
+                    }
+                    supportedOps &= item->fsSupportedOps;
+                }
+                if ( dropReply != DOR_NEVERDROP ) {
+                    Q_ASSERT( itemCount );
+                    if ( !itemCount || !supportedOps ) {
+                        // items don't have even a single common operation...
+                        dropReply = DOR_NEVERDROP;
+                    } else {
+                        // determine the default operation
+                        // (in order MOVE, COPY, LINK, for compatibility with Win32)
+                        if ( supportedOps & DO_MOVEABLE ) defaultOp = DO_MOVE;
+                        else if ( supportedOps & DO_COPYABLE ) defaultOp = DO_COPY;
+                        else if ( supportedOps & DO_LINKABLE ) defaultOp = DO_LINK;
+                        else Q_ASSERT( FALSE ); // should never happen
+                    }
+                }
+            }
+            
+            if ( dropReply != DOR_NEVERDROP ) {
+                
+                if ( first || lastDragOverOp != info->usOperation ) {
+                    // the current drop operation was changed by a modifier key
+                    lastDragOverOp = info->usOperation;
+                    lastRealOp = info->usOperation;
+                    if ( lastRealOp == DO_DEFAULT || lastRealOp == DO_UNKNOWN ) {
+                        // the default operation is requested
+                        lastRealOp = defaultOp;
+                    }
+                    sourceAllowsOp =
+                        ((supportedOps & DO_MOVEABLE) && lastRealOp == DO_MOVE) ||
+                        ((supportedOps & DO_COPYABLE) && lastRealOp == DO_COPY) ||
+                        ((supportedOps & DO_LINKABLE) && lastRealOp == DO_LINK);
+                }
+
+                // Note that if sourceAllowsOp = false here, we have to deliver
+                // events anyway (stealing them from Qt would be confusing), but
+                // we will silently ignore any accept commands and always reject
+                // the drop. Other platforms seem to do the same.
+                
+                // convert the operation to an Action
+                QDropEvent::Action action = QDropEvent::Copy; 
+                if ( lastRealOp == DO_COPY ) action = QDropEvent::Copy;
+                else if ( lastRealOp == DO_MOVE ) action = QDropEvent::Move;
+                else if ( lastRealOp == DO_LINK ) action = QDropEvent::Link;
+                else Q_ASSERT( FALSE ); // should never happen
+            
+                // flip y coordinate
+                QPoint pnt( info->xDrop, info->yDrop );
+                pnt.setY( QApplication::desktop()->height() - (pnt.y() + 1) );
+                pnt = widget->mapFromGlobal( pnt );
+
+                // initialize drag data used in QPMDrag.../QPMDrop... events
+                if ( first )
+                    dragData.initialize( info ); 
+                
+                QDropEvent *de = NULL;
+                QPMDragEnterEvent *dee = NULL;
+                QPMDragMoveEvent *dme = NULL;
+                
+                if ( first )
+                    de = dee = new QPMDragEnterEvent( pnt, &dragData );
+                else
+                    de = dme = new QPMDragMoveEvent( pnt, &dragData );
+                
+                de->setAction( action );
+                de->accept( lastAccept );
+                de->acceptAction( lastAcceptAction );
+                
+                QApplication::sendEvent( widget, de );
+
+                // if not allowed or not accepted, always reply DOR_NODROP
+                // to have DM_DRAGOVER delivered to us again in any case
+                
+                dropReply = sourceAllowsOp && de->isAccepted() ?
+                            DOR_DROP : DOR_NODROP;
+                
+                lastAccept = dee ? dee->isTrulyAccepted() :
+                                   dme->isTrulyAccepted();
+                lastAcceptAction = dee ? dee->isActionTrulyAccepted() :
+                                         dme->isActionTrulyAccepted();
+
+                delete de;
+            }
+            
+            DrgFreeDraginfo( info );
+            
+            return MRFROM2SHORT( dropReply, lastRealOp );
+        }
+        case DM_DRAGLEAVE: {
+            // Odin32 apps produce incorrect message flow, ignore
+            Q_ASSERT( lastDragOverHwnd != 0 );
+            if ( lastDragOverHwnd == 0 )
+                return 0;
+
+            lastDragOverHwnd = 0;
+            dragData.reset();
+            
+            if ( !widget->acceptDrops() )
+                return 0;
+
+            QDragLeaveEvent de;
+            QApplication::sendEvent( widget, &de );
+            return 0;
+        }
+        case DM_DROP: {
+            // Odin32 apps produce incorrect message flow, ignore
+            Q_ASSERT( lastDragOverHwnd != 0 );
+            if ( lastDragOverHwnd == 0 )
+                return 0;
+
+            // Odin32 apps send DM_DROP even if we replied DOR_NEVERDROP or
+            // DOR_NODROP, simulate DM_DRAGLEAVE
+            Q_ASSERT( lastAccept || lastAcceptAction );
+            if ( !lastAccept && !lastAcceptAction ) {
+                WinSendMsg( qmsg.hwnd, DM_DRAGLEAVE, 0, 0 );
+                return 0;
+            }
+
+            lastDragOverHwnd = 0;
+            
+            Q_ASSERT( widget->acceptDrops() );
+            if ( !widget->acceptDrops() )
+                return 0;
+            
+            DRAGINFO *info = (DRAGINFO *) qmsg.mp1;
+            ok = DrgAccessDraginfo( info );
+            Q_ASSERT( ok && info );
+            if ( !ok || !info )
+                return MRFROM2SHORT( DOR_NEVERDROP, 0 );
+            
+            Q_ASSERT( lastRealOp == info->usOperation );
+            
+            // convert the operation to an Action
+            QDropEvent::Action action = QDropEvent::Copy; 
+            if ( lastRealOp == DO_COPY ) action = QDropEvent::Copy;
+            else if ( lastRealOp == DO_MOVE ) action = QDropEvent::Move;
+            else if ( lastRealOp == DO_LINK ) action = QDropEvent::Link;
+            else Q_ASSERT( FALSE ); // should never happen
+            
+            // flip y coordinate
+            QPoint pnt( info->xDrop, info->yDrop );
+            pnt.setY( QApplication::desktop()->height() - (pnt.y() + 1) );
+
+            dragData.setDropped( TRUE );
+            
+            QPMDropEvent de( widget->mapFromGlobal( pnt ), &dragData );
+            de.setAction( action );
+            de.accept( lastAccept );
+            de.acceptAction( lastAcceptAction );
+            
+            QApplication::sendEvent( widget, &de );
+
+            dragData.reset( de.isTrulyAccepted(), de.isActionTrulyAccepted() );
+            
+            // If the target has accepted the particular Drop action (using
+            // acceptAction() rather than just accept()), it means that it will
+            // perform the necessary operation on its own (for example, will
+            // delete the source if the accepted action is Move). In this case,
+            // we always send DMFL_TARGETFAIL to the source to prevent it from
+            // doing the same on its side.
+            
+            ULONG targetReply =
+                de.isTrulyAccepted() && !de.isActionTrulyAccepted() ?
+                DMFL_TARGETSUCCESSFUL : DMFL_TARGETFAIL;
+         
+            // send DM_ENDCONVERSATION for every item
+            ULONG itemCount = DrgQueryDragitemCount( info );
+            for ( ULONG i = 0; i < itemCount; ++ i ) {
+                PDRAGITEM item = DrgQueryDragitemPtr( info, i );
+                Q_ASSERT( item );
+                if ( !item )
+                    continue;
+                // it is possible that this item required DM_RENDERPREPARE but
+                // returned FALSE in reply to it (so hwndItem may be NULL)
+                if ( !item->hwndItem )
+                    continue;
+                DrgSendTransferMsg( item->hwndItem, DM_ENDCONVERSATION,
+                                    MPFROMLONG( item->ulItemID ),
+                                    MPFROMLONG( targetReply ) );
+            }
+            
+            DrgDeleteDraginfoStrHandles( info );
+            DrgFreeDraginfo( info );
+            
+            return 0;
+        }
+        default:
+            break;
+    }
+    
+    return WinDefWindowProc( qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2 );
+}
+
+class QPMCoopDragWorker : public QPMMime::DragWorker, QPMMime::Messenger
+{
+public:
+    QPMCoopDragWorker() : info( NULL ) {}
+    bool collectWorkers( QDragObject *o );
+
+    // DragpWorker interface
+    void init();
+    bool cleanup( bool isCancelled );
+    bool isExclusive() const { return TRUE; }
+    ULONG itemCount() const { return 0; }
+    HWND hwnd() const;
+    DRAGINFO *createDragInfo( const char *name, USHORT supportedOps );
+
+    // Messenger interface
+    MRESULT message( ULONG msg, MPARAM mp1, MPARAM mp2 );
+    
+private:
+    QPtrList<DragWorker> workers;
+    DRAGINFO *info;
+};
+
+bool QPMCoopDragWorker::collectWorkers( QDragObject *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 convertors to collect DragWorkers
+    const char *fmt = NULL;    
+    for ( int i = 0; (fmt = o->format( i )); ++i ) {
+#if defined(QT_DEBUG_DND)            
+        qDebug( "QPMCoopDragWorker: Searching for worker for mime [%s]...", fmt );
+#endif        
+        QPtrList<QPMMime> mimes = QPMMime::all();
+        for ( QPMMime *mime = mimes.first(); mime; mime = mimes.next() ) {
+            QPMMime::DragWorker *wrk = mime->dragWorkerFor( fmt, o );
+            if ( !wrk )
+                continue;
+#if defined(QT_DEBUG_DND)            
+            qDebug( "QPMCoopDragWorker: Got worker %p (isExclusive()=%d, "
+                    "itemCount()=%ld) from convertor '%s' (gotExclForMime=%d, "
+                    "gotExcl=%d, skipExcl=%d, coopLevel=%ld)",
+                    wrk, wrk->isExclusive(), wrk->itemCount(),
+                    mime->convertorName(), gotExclForMime, gotExcl, skipExcl,
+                    coopLevel );
+#endif            
+            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.containsRef( 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 ) {
+#if defined(QT_CHECK_RANGE)
+                qWarning( "Cooperative DragWorker %p for mime [%s] has "
+                          "itemCount() = 0!", wrk, fmt );
+#endif                
+                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.getLast();
+            if ( w->isExclusive() || w->itemCount() > 1 ) {
+#if defined(QT_CHECK_STATE)
+                qWarning( "DragWorker %p for [%s] (isExclusive()=%d, itemCount()"
+                          "=%ld) has no fall-back cooperative 1-item worker!",
+                          w, fmt, w->isExclusive(), w->itemCount() );
+#endif                
+                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 ( DragWorker *wrk = workers.first(); wrk; ) {
+        bool excl = wrk->isExclusive() || wrk->itemCount() > 1;
+        if ( skipExcl == excl || workers.containsRef( wrk ) > 1 ) {
+            workers.remove();
+            wrk = workers.current();
+        } else { 
+            wrk = workers.next();
+        }
+    }
+    
+#if defined(QT_DEBUG_DND)            
+    for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
+        qDebug( "QPMCoopDragWorker: Will use worker %p (isExclusive()=%d, "
+                "itemCount()=%ld)", wrk, wrk->isExclusive(), wrk->itemCount() ); 
+    }
+#endif
+
+    Q_ASSERT( workers.count() > 0 );
+    return workers.count() > 0;
+}
+
+HWND QPMCoopDragWorker::hwnd() const
+{
+    DragWorker *firstWorker = workers.getFirst();
+    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 QPMMime::Messenger::hwnd();
+}
+
+void QPMCoopDragWorker::init()
+{
+    Q_ASSERT( source() );
+    for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
+        wrk->src = source();
+        wrk->init();
+    }
+}
+
+bool QPMCoopDragWorker::cleanup( bool isCancelled )
+{
+    bool moveDisallowed = FALSE;
+    
+    for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
+        // disallow the Move operation if at least one worker asked so
+        moveDisallowed |= wrk->cleanup( isCancelled );
+        wrk->src = NULL;
+    }
+    workers.clear();
+    info = NULL;
+    return moveDisallowed;
+}
+
+DRAGINFO *QPMCoopDragWorker::createDragInfo( const char *name, USHORT supportedOps )
+{
+    Q_ASSERT( !info );
+    if ( info )
+        return NULL;
+
+    DragWorker *firstWorker = workers.getFirst();
+    Q_ASSERT( firstWorker );
+    if ( !firstWorker )
+        return NULL;
+
+    ULONG itemCnt = firstWorker->itemCount();
+
+    if ( firstWorker->isExclusive() && itemCnt == 0 ) {
+        // this is a super exclusive worker that will do everything on its own
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "QPMCoopDragWorker: Will redirect to super worker %p",
+                firstWorker );
+#endif        
+        return firstWorker->createDragInfo( name, supportedOps );
+    }
+    
+    // note that all workers at this place require the same amount of items
+    // (guaranteed by collectWorkers())
+    
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "QPMCoopDragWorker: itemCnt=%ld", itemCnt );
+#endif
+
+    info = DrgAllocDraginfo( itemCnt );
+    Q_ASSERT( info );
+    if ( !info )
+        return NULL;
+    
+    // collect all mechanism/format pairs
+    QCString allFormats;
+    for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
+        QCString formats = wrk->composeFormatString();
+        Q_ASSERT( !formats.isNull() );
+        if ( !formats.isNull() ) {
+            if ( allFormats.isNull() )
+                allFormats = formats;
+            else {
+                allFormats += ",";
+                allFormats += formats;
+            }
+        }
+    }
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "QPMCoopDragWorker: allFormats=%s", allFormats.data() );
+#endif
+
+    static ULONG itemID = 0;
+    
+    const char *type = NULL;
+    const char *ext = NULL;
+    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;
+        }
+
+        QCString targetName;
+        if ( itemCnt == 1 ) targetName = name;
+        else targetName.sprintf( "%s %ld", name, i + 1 );
+        
+        if ( ext ) {
+            targetName += ".";
+            targetName += ext;
+        }
+
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "QPMCoopDragWorker: item %ld: type=[%s], name=\"%s\"",
+                i, type, targetName.data() );
+#endif
+
+        // 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( targetName );
+        item->cxOffset = 0;
+        item->cyOffset = 0;
+        item->fsControl = DC_PREPARE; // require DM_RENDERPREPARE from target
+        item->fsSupportedOps = supportedOps;
+    }
+   
+    if ( !ok ) {
+        DrgFreeDraginfo( info );
+        info = NULL;
+    }
+    
+    return info;
+}
+
+extern void qt_DrgFreeDragtransfer( DRAGTRANSFER *xfer ); // defined in qmime_pm.cpp
+
+MRESULT QPMCoopDragWorker::message( ULONG msg, MPARAM mp1, MPARAM mp2 )
+{
+    if ( msg == DM_RENDERPREPARE ) {
+        if ( !info ) {
+#if defined(QT_CHECK_STATE)
+            qWarning( "Drop target sent DM_RENDERPREPARE after the DnD session "
+                      "is over!" );
+#endif            
+            // 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;
+
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "QPMCoopDragWorker: Got DM_RENDERPREPARE to %s for item %ld "
+                "(id=%ld)",
+                QPMMime::queryHSTR( xfer->hstrSelectedRMF ).data(),
+                index, xfer->pditem->ulItemID );
+#endif
+        
+        QCString drm, drf;
+        if ( !QPMMime::parseRMF( xfer->hstrSelectedRMF, drm, drf ) ) {
+            Q_ASSERT( FALSE );
+            return (MRESULT) FALSE;
+        }
+
+        DragWorker *wrk = NULL;
+        for ( wrk = workers.first(); wrk; wrk = workers.next() )
+            if ( wrk->prepare( drm, drf, xfer->pditem, index ) )
+                break;
+        if ( !wrk ) {
+#if defined(QT_DEBUG_DND)                        
+            qDebug( "QPMCoopDragWorker: No suitable worker found" );
+#endif            
+            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 ) {
+#if defined(QT_CHECK_STATE)
+        qWarning( "Drop target sent DM_RENDER or DM_ENDCONVERSATION to the "
+                  "drag source window instead of the drag item window!" );
+#endif        
+        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;
+}
+
+bool QDragManager::drag( QDragObject *o, QDragObject::DragMode mode )
+{
+    // return shortly if drag is already in progress or being cancelled
+    if ( object || beingCancelled )
+        return FALSE;
+
+    QDragObject::setTarget( 0 );
+    
+    // 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 ) {
+#if defined(QT_CHECK_STATE)
+        qWarning( "QDragManager: No valid mouse button pressed, "
+                  "dragging cancelled!" );
+#endif        
+        delete o;
+        return FALSE;
+    }
+
+    USHORT supportedOps = 0;
+    
+    switch ( mode ) {
+        case QDragObject::DragDefault:
+            supportedOps = DO_MOVEABLE | DO_COPYABLE | DO_LINKABLE;
+            break;
+        case QDragObject::DragMove:
+            supportedOps = DO_MOVEABLE;
+            break;
+        case QDragObject::DragCopy:
+            supportedOps = DO_COPYABLE;
+            break;
+        case QDragObject::DragCopyOrMove:
+            supportedOps = DO_MOVEABLE | DO_COPYABLE;
+            break;
+        case QDragObject::DragLink:
+            supportedOps = DO_LINKABLE;
+            break;
+    }
+    
+    static QPMCoopDragWorker dragWorker;
+    
+    bool ok = dragWorker.collectWorkers( o );
+    Q_ASSERT( ok );
+    Q_ASSERT( dragWorker.hwnd() );
+    if ( !ok || ! dragWorker.hwnd() ) {
+        delete o;
+        return FALSE;
+    }
+    
+    object = o;
+    dragSource = (QWidget *) (object->parent());
+    
+    dragWorker.src = o;
+    dragWorker.init();
+    DRAGINFO *info = dragWorker.createDragInfo( o->name(), supportedOps );
+
+    Q_ASSERT( info );
+    if ( !info ) {
+        dragWorker.cleanup( TRUE /* isCancelled */);
+        dragWorker.src = NULL;
+        dragSource = 0;
+        object = 0;
+        delete o;
+        return FALSE;
+    }
+
+#if defined(QT_ACCESSIBILITY_SUPPORT)
+    QAccessible::updateAccessibility( this, 0, QAccessible::DragDropStart );
+#endif
+    
+/// @todo (dmik)
+//    updatePixmap();
+
+    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
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "QDragManager: DrgDrag() returned %08lX (err=0x%08lX)",
+            target, WinGetLastError( 0 ) );
+#endif    
+    
+    // we won't get any mouse release event, so manually adjust qApp state
+    qt_pmMouseButtonUp();
+
+    bool moveDisallowed = dragWorker.cleanup( beingCancelled || target == 0 );
+    dragWorker.src = NULL;
+    
+    moveDisallowed |= beingCancelled || target == 0 ||
+                      info->usOperation != DO_MOVE;
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "QDragManager: moveDisallowed=%d", moveDisallowed );
+#endif
+
+    if ( target == 0 )
+        DrgDeleteDraginfoStrHandles( info );
+    DrgFreeDraginfo( info );
+
+    if ( !beingCancelled ) {
+        QDragObject::setTarget( QWidget::find( target ) );
+        cancel(); // this will delete o (object)
+    }
+    
+    beingCancelled = FALSE;
+
+/// @todo (dmik)
+//    updatePixmap();
+    
+    return !moveDisallowed;
+}
+
+void QDragManager::updatePixmap()
+{
+    /// @todo (dmik) later
+  
+/// @todo (dmik) remove    
+//    if ( object ) {
+//	if ( cursor ) {
+//#ifndef Q_OS_TEMP
+//	    for ( int i=0; i<n_cursor; i++ ) {
+//		DestroyCursor(cursor[i]);
+//	    }
+//#endif
+//	    delete [] cursor;
+//	    cursor = 0;
+//	}
+//
+//	QPixmap pm = object->pixmap();
+//	if ( pm.isNull() ) {
+//	    // None.
+//	} else {
+//	    cursor = new HCURSOR[n_cursor];
+//	    QPoint pm_hot = object->pixmapHotSpot();
+//	    for (int cnum=0; cnum<n_cursor; cnum++) {
+//		QPixmap cpm = pm_cursor[cnum];
+//
+//		int x1 = QMIN(-pm_hot.x(),0);
+//		int x2 = QMAX(pm.width()-pm_hot.x(),cpm.width());
+//		int y1 = QMIN(-pm_hot.y(),0);
+//		int y2 = QMAX(pm.height()-pm_hot.y(),cpm.height());
+//
+//		int w = x2-x1+1;
+//		int h = y2-y1+1;
+//
+//#ifndef Q_OS_TEMP
+//		if ( qWinVersion() & WV_DOS_based ) {
+//		    // Limited cursor size
+//		    int reqw = GetSystemMetrics(SM_CXCURSOR);
+//		    int reqh = GetSystemMetrics(SM_CYCURSOR);
+//		    if ( reqw < w ) {
+//			// Not wide enough - move objectpm right
+//			pm_hot.setX(pm_hot.x()-w+reqw);
+//		    }
+//		    if ( reqh < h ) {
+//			// Not tall enough - move objectpm down
+//			pm_hot.setY(pm_hot.y()-h+reqh);
+//		    }
+//		    // Always use system cursor size
+//		    w = reqw;
+//		    h = reqh;
+//		}
+//#endif
+//
+//		QPixmap colorbits(w,h,-1,QPixmap::NormalOptim);
+//		{
+//		    QPainter p(&colorbits);
+//		    p.fillRect(0,0,w,h,color1);
+//		    p.drawPixmap(QMAX(0,-pm_hot.x()),QMAX(0,-pm_hot.y()),pm);
+//		    p.drawPixmap(QMAX(0,pm_hot.x()),QMAX(0,pm_hot.y()),cpm);
+//		}
+//
+//		QBitmap maskbits(w,h,TRUE,QPixmap::NormalOptim);
+//		{
+//		    QPainter p(&maskbits);
+//		    if ( pm.mask() ) {
+//			QBitmap m(*pm.mask());
+//			m.setMask(m);
+//			p.drawPixmap(QMAX(0,-pm_hot.x()),QMAX(0,-pm_hot.y()),m);
+//		    } else {
+//			p.fillRect(QMAX(0,-pm_hot.x()),QMAX(0,-pm_hot.y()),
+//			    pm.width(),pm.height(),color1);
+//		    }
+//		    if ( cpm.mask() ) {
+//			QBitmap m(*cpm.mask());
+//			m.setMask(m);
+//			p.drawPixmap(QMAX(0,pm_hot.x()),QMAX(0,pm_hot.y()),m);
+//		    } else {
+//			p.fillRect(QMAX(0,pm_hot.x()),QMAX(0,pm_hot.y()),
+//			    cpm.width(),cpm.height(),
+//			    color1);
+//		    }
+//		}
+//
+//		HBITMAP im = qt_createIconMask(maskbits);
+//		ICONINFO ii;
+//		ii.fIcon     = FALSE;
+//		ii.xHotspot  = QMAX(0,pm_hot.x());
+//		ii.yHotspot  = QMAX(0,pm_hot.y());
+//		ii.hbmMask   = im;
+//		ii.hbmColor  = colorbits.hbm();
+//		cursor[cnum] = CreateIconIndirect(&ii);
+//		DeleteObject( im );
+//	    }
+//	}
+//    }
+}
+
+#endif // QT_NO_DRAGANDDROP
Index: trunk/src/kernel/qmime_pm.cpp
===================================================================
--- trunk/src/kernel/qmime_pm.cpp	(revision 96)
+++ trunk/src/kernel/qmime_pm.cpp	(revision 97)
@@ -40,4 +40,7 @@
 #ifndef QT_NO_MIME
 
+#include <stdlib.h>
+#include <time.h>
+
 #include "qstrlist.h"
 #include "qimage.h"
@@ -45,9 +48,1283 @@
 #include "qdragobject.h"
 #include "qbuffer.h"
+#include "qapplication.h"
 #include "qapplication_p.h"
+#include "qeventloop.h"
 #include "qtextcodec.h"
-#include "qregexp.h"
+#include "qdir.h"
+#include "qfile.h"
+#include "qurl.h"
+#include "qvaluelist.h"
+#include "qintdict.h"
+#include "qptrlist.h"
 #include "qt_os2.h"
 
+// if you enable this, it makes sense to enable it in qdnd_pm.cpp as well
+//#define QT_DEBUG_DND
+
+#if !defined(QT_NO_DRAGANDDROP)
+
+#define DRT_URL "UniformResourceLocator"
+
+#define DRM_OS2FILE "DRM_OS2FILE"
+#define DRM_SHAREDMEM "DRM_SHAREDMEM"
+
+#define DRF_UNKNOWN "DRF_UNKNOWN"
+#define DRF_TEXT "DRF_TEXT"
+#define DRF_POINTERDATA "DRF_POINTERDATA"
+
+#define MIME_TEXT_PLAIN "text/plain"
+#define MIME_TEXT_PLAIN_CHARSET_SYSTEM "text/plain;charset=system"
+#define MIME_TEXT_URI_LIST "text/uri-list"
+
+/*! \internal
+  According to my tests, DrgFreeDragtransfer() appears to be bogus: when the
+  drag source attempts to free the DRAGTRANSFER structure passed to it in
+  DM_RENDERPREPARE/DM_RENDER by another process, the shared memory object is not
+  actually released until DrgFreeDragtransfer() is called for the second time.
+  This method tries to fix this problem.
+  
+  \note The problem (and the solution) was not tested on platforms other than
+  eCS 1.2.1 GA!
+*/
+void qt_DrgFreeDragtransfer( DRAGTRANSFER *xfer )
+{
+    Q_ASSERT( xfer );
+    if ( xfer ) {
+        BOOL ok = DrgFreeDragtransfer( xfer );
+        Q_ASSERT( ok );
+        if ( ok ) {
+            ULONG size = ~0, flags = 0;
+            APIRET rc = DosQueryMem( xfer, &size, &flags );
+            Q_ASSERT( rc == 0 );
+            if ( rc == 0 && !(flags & PAG_FREE) ) {
+                PID pid;
+                TID tid;
+                ok = WinQueryWindowProcess( xfer->hwndClient, &pid, &tid );
+                Q_ASSERT( ok );
+                if ( ok ) {
+                    PPIB ppib = NULL;
+                    DosGetInfoBlocks( NULL, &ppib );
+                    if ( ppib->pib_ulpid != pid ) {
+#if defined(QT_DEBUG_DND)                        
+                        qDebug( "qt_DrgFreeDragtransfer: Will free xfer %p "
+                                "TWICE (other process)!", xfer );
+#endif                        
+                        DrgFreeDragtransfer( xfer );
+                    }
+                }
+            }
+        }
+    }
+}
+
+#define SEA_TYPE ".TYPE"
+
+/*! \internal
+  Sets a single .TYPE EA vaule on a given fle.
+*/ 
+static void qt_SetFileTypeEA( const char *name, const char *type)
+{
+    #pragma pack(1)
+    
+    struct MY_FEA2 {
+        ULONG   oNextEntryOffset;  /*  Offset to next entry. */
+        BYTE    fEA;               /*  Extended attributes flag. */
+        BYTE    cbName;            /*  Length of szName, not including NULL. */
+        USHORT  cbValue;           /*  Value length. */
+        CHAR    szName[0];         /*  Extended attribute name. */
+        /* EA value follows here */
+    };
+    
+    struct MY_FEA2LIST {
+        ULONG   cbList;            /*  Total bytes of structure including full list. */
+        MY_FEA2 list[0];           /*  Variable-length FEA2 structures. */
+    };
+    
+    struct MY_FEA2_VAL {
+        USHORT  usEAType;          /* EA value type (one of EAT_* constants) */
+        USHORT  usValueLen;        /* Length of the value data following */
+        CHAR    aValueData[0];     /* value data */
+    };
+    
+    struct MY_FEA2_MVMT {
+        USHORT      usEAType;      /* Always EAT_MVMT */
+        USHORT      usCodePage;    /* 0 - default */
+        USHORT      cbNumEntries;  /* Number of MYFEA2_VAL structs following */
+        MY_FEA2_VAL aValues[0];    /* MYFEA2_VAL value structures */
+    };
+    
+    #pragma pack()
+    
+    uint typeLen = qstrlen( type );
+    uint valLen = sizeof( MY_FEA2_VAL ) + typeLen;
+    uint mvmtLen = sizeof( MY_FEA2_MVMT ) + valLen;
+    uint fea2Len = sizeof( MY_FEA2 ) + sizeof( SEA_TYPE );
+    uint fullLen = sizeof( MY_FEA2LIST ) + fea2Len + mvmtLen;
+    
+    uchar *eaData = new uchar[ fullLen ];
+
+    MY_FEA2LIST *fea2List = (MY_FEA2LIST *) eaData;
+    fea2List->cbList = fullLen;
+    
+    MY_FEA2 *fea2 = fea2List->list;
+    fea2->oNextEntryOffset = 0;
+    fea2->fEA = 0;
+    fea2->cbName = sizeof( SEA_TYPE ) - 1;
+    fea2->cbValue = mvmtLen;
+    strcpy( fea2->szName, SEA_TYPE );
+    
+    MY_FEA2_MVMT *mvmt = (MY_FEA2_MVMT *) (fea2->szName + sizeof( SEA_TYPE ));
+    mvmt->usEAType = EAT_MVMT;
+    mvmt->usCodePage = 0;
+    mvmt->cbNumEntries = 1;
+    
+    MY_FEA2_VAL *val = mvmt->aValues;
+    val->usEAType = EAT_ASCII;
+    val->usValueLen = typeLen;
+    memcpy( val->aValueData, type, typeLen );
+
+    EAOP2 eaop2;
+    eaop2.fpGEA2List = 0;
+    eaop2.fpFEA2List = (FEA2LIST *) fea2List;
+    eaop2.oError = 0;
+
+    APIRET rc = DosSetPathInfo( name, FIL_QUERYEASIZE,
+                                &eaop2, sizeof( eaop2 ), 0 );
+#ifndef QT_NO_DEBUG
+    if ( rc ) 
+        qSystemWarning( "Could not set file EAs", rc );
+#endif    
+
+    delete[] eaData;   
+}
+
+static int hex_to_int( uchar c )
+{
+    if ( c >= 'A' && c <= 'F' ) return c - 'A' + 10;
+    if ( c >= 'a' && c <= 'f' ) return c - 'a' + 10;
+    if ( c >= '0' && c <= '9' ) return c - '0';
+    return -1;
+}
+
+static inline int hex_to_int( char c )
+{
+    return hex_to_int( (uchar) c );
+}
+
+// -----------------------------------------------------------------------------
+
+static QPtrList<HWND> msgWindows;
+
+static void destroyAllMessengerWindows()
+{
+    for ( HWND *w = msgWindows.first(); w; w = msgWindows.next() ) {
+        Q_ASSERT( *w );
+        WinDestroyWindow( *w );
+        // tell Messenger the window has been destroyed
+        *w = 0; 
+    }
+}
+
+QPMMime::Messenger::Messenger() : w( 0 )
+{
+    static const char *ClassName = "QPMMime::Messenger";
+    static bool classRegistered = FALSE;
+    if ( !classRegistered ) {
+        WinRegisterClass( 0, ClassName, windowProc, 0, QWL_USER + sizeof(PVOID) );
+        classRegistered = TRUE;
+        // make sure windows are destroyed before the message queue
+        qAddPostRoutine ( destroyAllMessengerWindows );
+    }
+
+    w = WinCreateWindow( HWND_OBJECT, ClassName,
+                         NULL, 0, 0, 0, 0, 0, NULL,
+                         HWND_BOTTOM, 0, this, NULL );
+    Q_ASSERT( w );
+    if ( w )
+        msgWindows.append( &w );
+}
+
+QPMMime::Messenger::~Messenger()
+{
+    if ( w ) {
+        msgWindows.removeRef( &w );
+        HWND h = w;
+        w = 0; // tell windowProc() we're unsafe
+        WinDestroyWindow( h );
+    }
+}
+
+//static
+MRESULT EXPENTRY QPMMime::Messenger::windowProc( HWND hwnd, ULONG msg,
+                                                 MPARAM mp1, MPARAM mp2 )
+{
+    if ( msg == WM_CREATE ) {
+        Messenger *that = (Messenger *) mp1;
+        if ( !that )
+            return (MRESULT) TRUE;
+        WinSetWindowPtr( hwnd, QWL_USER, that );
+        return (MRESULT) FALSE;
+    }
+
+    Messenger *that = (Messenger *) WinQueryWindowPtr( hwnd, QWL_USER );
+    Q_ASSERT( that );
+    
+    // Note: before WinCreateWindow() returns to the constructor or after the
+    // destructor has been called, w is 0. We use this to determine that the
+    // object is in the unsafe state (VTBL is not yet initialized or has been
+    // already uninitialized), so message() points to never-never land.
+    if ( !that || !that->w )
+        return (MRESULT) FALSE;
+    
+    return that->message( msg, mp1, mp2 );
+}
+
+// -----------------------------------------------------------------------------
+
+struct QPMMime::DefaultDragWorker::Data
+{
+    Data( bool excl ) : exclusive( excl ) {} 
+    
+    struct Request
+    {
+        Request( ULONG i, Provider *p, const char *m, const char *f )
+            : index( i ), provider( p ), drm( m ), drf( f )
+            , xfer( NULL ), rendered( FALSE ), sharedMem( NULL ) {}
+
+        ~Request()
+        {
+            // free memory allocated for the target that requested DRM_SHAREDMEM
+            if ( sharedMem )
+                DosFreeMem( sharedMem );
+            Q_ASSERT( !xfer );
+        }
+        
+        ULONG index;
+        Provider *provider;
+        QCString drm;
+        QCString drf;
+        DRAGTRANSFER *xfer;
+        bool rendered;
+        PVOID sharedMem;
+    };
+    
+    inline bool isInitialized() { return providers.count() != 0; } 
+    void cleanupRequests();
+    
+    struct DrfProvider
+    {
+        DrfProvider() : prov( NULL ) {}
+        DrfProvider( const char *d, Provider *p ) : drf( d ), prov( p ) {}
+        const QCString drf;
+        Provider * const prov;
+    };
+
+    typedef QValueList<DrfProvider> DrfProviderList;
+    
+    Provider *providerFor( const char *drf )
+    {
+        for ( DrfProviderList::const_iterator it = providers.begin();
+              it != providers.end(); ++it )
+            if ( qstrcmp( (*it).drf, drf ) == 0 )
+                return (*it).prov;
+        return NULL;
+    }
+    
+    const bool exclusive : 1;
+    DrfProviderList providers;
+    
+    ULONG itemCnt;
+    QIntDict<Request> requests;
+    bool renderOk : 1;
+};
+
+void QPMMime::DefaultDragWorker::Data::cleanupRequests()
+{
+    if ( requests.count() ) {
+#if defined(QT_CHECK_STATE)
+        qWarning( "In the previous DnD session, the drop target sent "
+                  "DM_RENDERPREPARE/DM_RENDER\n"
+                  "for some drag item but didn't send DM_ENDCONVERSATION!" );
+#endif        
+        requests.clear();
+    }
+}
+
+QPMMime::DefaultDragWorker::DefaultDragWorker( bool exclusive )
+    : d( new Data( exclusive ) )
+{
+    d->itemCnt = 0;
+    d->requests.setAutoDelete( TRUE );
+    d->renderOk = TRUE;
+}
+
+QPMMime::DefaultDragWorker::~DefaultDragWorker()
+{
+    d->cleanupRequests();
+    delete d;
+}
+
+bool QPMMime::DefaultDragWorker::cleanup( bool isCancelled )
+{
+    // the return value is: TRUE if the source-side Move for the given
+    // drag object should be *dis*allowed, FALSE otherwise (including cases
+    // when this DragWorker didn't participate to the conversation at all)
+    
+    // sanity check
+    Q_ASSERT( d->isInitialized() );
+    if ( !d->isInitialized() )
+        return TRUE;
+    
+    bool moveDisallowed = FALSE;
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "DefaultDragWorker: Session ended (cancelled=%d, requests.left=%d)",
+            isCancelled, d->requests.count() );
+#endif
+
+    if ( d->requests.count() ) {
+        // always disallow Move if not all requests got DM_ENDCONVERSATION
+        moveDisallowed = TRUE;
+    } else {
+        // disallow Move if rendering of some item failed
+        moveDisallowed = !d->renderOk;
+    }
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "DefaultDragWorker: moveDisallowed=%d", moveDisallowed );
+#endif
+    
+    // Note: remaining requests will be lazily deleted by cleanupRequests()
+    // when a new DND session is started
+    
+    d->renderOk = TRUE;
+    d->itemCnt = 0;
+        
+    // Indicate we're cleaned up (i.e. the DND session is finished)
+    d->providers.clear();
+
+    return moveDisallowed;
+}
+
+bool QPMMime::DefaultDragWorker::isExclusive() const
+{
+    return d->exclusive;
+}       
+
+ULONG QPMMime::DefaultDragWorker::itemCount() const
+{
+    return d->itemCnt;
+}
+
+QCString QPMMime::DefaultDragWorker::composeFormatString()
+{
+    QCString formats;
+
+    // sanity checks
+    Q_ASSERT( d->isInitialized() );
+    if ( !d->isInitialized() )
+        return formats;
+
+    bool first = TRUE;
+    for ( Data::DrfProviderList::const_iterator it = d->providers.begin();
+          it != d->providers.end(); ++it ) {
+        if ( first )
+            first = FALSE;
+        else
+            formats += ",";
+        formats += (*it).drf;
+    }
+
+    Q_ASSERT( !formats.isNull() );
+    if ( formats.isNull() )
+        return formats;
+    
+    // DRM_SHAREDMEM comes first to prevent native DRM_OS2FILE
+    // rendering on the target side w/o involving the source.
+    // Also, we add <DRM_SHAREDMEM,DRF_POINTERDATA> just like WPS does it
+    // (however, it doesn't help when dropping objects to it -- WPS still
+    // chooses DRM_OS2FILE).
+    formats = "("DRM_SHAREDMEM","DRM_OS2FILE")x(" + formats + "),"
+              "<"DRM_SHAREDMEM","DRF_POINTERDATA">"; 
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "DefaultDragWorker: formats=%s, itemCnt=%ld",
+            formats.data(), d->itemCnt );
+#endif
+
+    return formats;
+}
+
+bool QPMMime::DefaultDragWorker::prepare( const char *drm, const char *drf,
+                                          DRAGITEM *item, ULONG itemIndex )
+{
+    // sanity checks
+    Q_ASSERT( d->isInitialized() );
+    if ( !d->isInitialized() )
+        return FALSE;
+
+    Q_ASSERT( item && itemIndex < d->itemCnt );
+    if ( !item || itemIndex >= d->itemCnt )
+        return FALSE;
+    
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "DefaultDragWorker: Preparing item %ld (id=%ld) for <%s,%s>",
+            itemIndex, item->ulItemID, drm, drf );
+#endif
+
+    Provider *p = d->providerFor( drf );
+    
+    if ( !canRender( drm ) || p == NULL ) {
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "DefaultDragWorker: Cannot render the given RMF" );
+#endif
+        return FALSE;
+    }
+
+    Data::Request *req = d->requests[ item->ulItemID ];
+
+    if ( req ) {
+        // this item has been already prepared, ensure it has also been
+        // rendered already 
+        Q_ASSERT( req->index == itemIndex );
+        Q_ASSERT( req->rendered );
+        if ( req->index != itemIndex || !req->rendered )
+            return FALSE;
+        // remove the old request to free resources
+        d->requests.remove( item->ulItemID );
+    }
+
+    // store the request
+    req = new Data::Request( itemIndex, p, drm, drf );
+    d->requests.insert( item->ulItemID, req );
+    
+    return TRUE;
+}
+
+void QPMMime::DefaultDragWorker::defaultFileType( const char *&type,
+                                                  const char *&ext )
+{
+    Q_ASSERT( d->providers.count() );
+    if ( d->providers.count() ) {
+        Provider *p = d->providers.front().prov;
+        Q_ASSERT( p );
+        if ( p )
+            p->fileType( d->providers.front().drf, type, ext );
+    }
+}
+
+MRESULT QPMMime::DefaultDragWorker::message( ULONG msg, MPARAM mp1, MPARAM mp2 )
+{
+    if ( msg == DM_RENDER ) {
+        DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
+
+        // sanity checks
+        Q_ASSERT( d->isInitialized() );
+        Q_ASSERT( xfer );
+        if ( !d->isInitialized() || !xfer )
+            return (MRESULT) FALSE;
+
+        Q_ASSERT( xfer->hwndClient && xfer->pditem );
+        if ( !xfer->hwndClient || !xfer->pditem )
+            return (MRESULT) FALSE;
+
+        Data::Request *req = d->requests[ xfer->pditem->ulItemID ];
+
+        // check that this item has been prepared (should always be the case
+        // because the target would never know our hwnd() otherwise)
+        Q_ASSERT( req ); // prepared
+        Q_ASSERT( !req->xfer ); // no DM_RENDER requested
+        if ( !req || req->xfer )
+            return (MRESULT) FALSE;
+
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "DefaultDragWorker: Got DM_RENDER to %s for item %ld (id=%ld)",
+                queryHSTR( xfer->hstrSelectedRMF ).data(),
+                req->index, xfer->pditem->ulItemID );
+#endif
+
+        QCString drm, drf;
+        if ( !parseRMF( xfer->hstrSelectedRMF, drm, drf ) )
+            Q_ASSERT( /* parseRMF() = */ FALSE );
+        
+        if ( req->drm != drm || req->drf != drf ) { 
+            xfer->fsReply = DMFL_RENDERRETRY;
+            return (MRESULT) FALSE;
+        }
+        
+        // indicate that DM_RENDER was requested
+        req->xfer = xfer;
+        
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "DefaultDragWorker: Will render from [%s] using provider %p",
+                req->provider->format( drf ), req->provider );
+#endif
+
+        // We would lile to post WM_USER to ourselves to do actual rendering
+        // after we return from DM_RENDER. But we are inside DrgDrag() at this
+        // point (our DND implementation is fully synchronous by design), so
+        // PM will not  deliver this message to us until we return from
+        // DrgDrag(). Thus, we have to send it.
+        
+        WinSendMsg( hwnd(), WM_USER, MPFROMLONG( xfer->pditem->ulItemID ),
+                                     MPFROMP( req ) );
+        
+        return (MRESULT) TRUE;
+    }
+
+    if ( msg == WM_USER ) {
+        // sanity checks
+        Q_ASSERT( d->isInitialized() );
+        if ( !d->isInitialized() )
+            return (MRESULT) FALSE;
+
+        ULONG itemId = LONGFROMMP( mp1 );
+        
+        // sanity checks
+        Data::Request *req = d->requests[ itemId ];
+        Q_ASSERT( req ); // prepared
+        Q_ASSERT( req->xfer != NULL ); // DM_RENDER requested
+        Q_ASSERT( !req->rendered ); // not yet rendered
+        Q_ASSERT( (Data::Request *) PVOIDFROMMP( mp2 ) == req );
+        if ( !req || req->xfer == NULL || req->rendered ||
+             (Data::Request *) PVOIDFROMMP( mp2 ) != req )
+            return (MRESULT) FALSE;
+
+        Q_ASSERT( source() && req->provider && req->index < d->itemCnt );
+        if ( !source() || !req->provider || req->index >= d->itemCnt )
+            return (MRESULT) FALSE;
+            
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "DefaultDragWorker: Got DO_RENDER for item %ld (id=%ld), "
+                "provider=%p, drm=%s, drf=%s",
+                req->index, req->xfer->pditem->ulItemID,
+                req->provider, req->drm.data(), req->drf.data() );
+#endif
+
+        bool renderOk = FALSE;
+        QByteArray allData =
+            source()->encodedData( req->provider->format( req->drf ) );
+        QByteArray itemData;
+
+        renderOk = req->provider->provide( req->drf, allData,
+                                           req->index, itemData );
+
+        if ( renderOk ) {
+            enum DRM { OS2File, SharedMem } drmType;
+            if ( qstrcmp( req->drm, DRM_SHAREDMEM ) == 0 ) drmType = SharedMem;
+            else drmType = OS2File;
+            
+            if ( drmType == OS2File ) {
+                QCString renderToName = queryHSTR( req->xfer->hstrRenderToName );
+                Q_ASSERT( !renderToName.isEmpty() );
+                renderOk = !renderToName.isEmpty();
+                if ( renderOk ) {
+#if defined(QT_DEBUG_DND)                        
+                    qDebug( "DefaultDragWorker: Will write to \"%s\"",
+                            renderToName.data() );
+#endif
+                    QFile file( QFile::decodeName( renderToName ) );
+                    renderOk = file.open( IO_WriteOnly );
+                    if ( renderOk ) {
+                        Q_LONG written =
+                            file.writeBlock( itemData.data(), itemData.size() );
+                        renderOk = written == ( Q_LONG ) itemData.size() &&
+                                   file.status() == IO_Ok;
+                        file.close();
+                        if ( renderOk && req->xfer->pditem->hstrType ) {
+                            // since WPS ignores hstrType, write it manually
+                            // to the .TYPE EA of the created file
+                            qt_SetFileTypeEA( renderToName,
+                                queryHSTR( req->xfer->pditem->hstrType ) ); 
+                        }
+                    }
+                }
+            } else {
+                PID pid;
+                TID tid;
+                bool isSameProcess = FALSE;
+                renderOk = WinQueryWindowProcess( req->xfer->hwndClient,
+                                                  &pid, &tid );
+                if ( renderOk ) {
+                    PPIB ppib = NULL;
+                    DosGetInfoBlocks( NULL, &ppib );
+                    isSameProcess = ppib->pib_ulpid == pid;
+                    
+                    ULONG sz = itemData.size() + sizeof (ULONG);
+                    char *ptr = NULL;
+                    APIRET rc = isSameProcess ?
+                                DosAllocMem( (PPVOID) &ptr, sz,
+                                              PAG_COMMIT | PAG_READ | PAG_WRITE ) :                
+                                DosAllocSharedMem( (PPVOID) &ptr, NULL, sz,
+                                                    OBJ_GIVEABLE | PAG_COMMIT |
+                                                    PAG_READ | PAG_WRITE );                
+                    renderOk = rc == 0;
+                    if ( renderOk && !isSameProcess ) {
+                        rc = DosGiveSharedMem( ptr, pid, PAG_READ );
+                        renderOk = rc == 0;
+                    }
+                    if ( renderOk ) {
+                        *(ULONG *) ptr = itemData.size();
+                        memcpy( ptr + sizeof (ULONG), itemData.data(),
+                                itemData.size() );
+                        req->xfer->hstrRenderToName = (HSTR) ptr;
+                        req->sharedMem = ptr;
+#if defined(QT_DEBUG_DND)                        
+                        qDebug( "DefaultDragWorker: Created shared memory object %p",
+                                ptr );
+#endif
+#ifndef QT_NO_DEBUG
+                    } else {
+                        qSystemWarning( "DosAllocSharedMem/DosGiveSharedMem "
+                                        "failed", rc );
+#endif                        
+                    }
+#ifndef QT_NO_DEBUG
+                } else {
+                    qSystemWarning( "WinQueryWindowProcess failed" );
+#endif                    
+                }
+            }
+        }
+
+        req->rendered = TRUE;
+        // cumulative render result
+        d->renderOk &= renderOk;
+
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "DefaultDragWorker: renderOk=%d, overall.renderOk=%d",
+                renderOk, d->renderOk );
+#endif
+
+        // note that we don't allow the target to retry
+        USHORT reply = renderOk ? DMFL_RENDEROK : DMFL_RENDERFAIL;
+        DrgPostTransferMsg( req->xfer->hwndClient, DM_RENDERCOMPLETE,
+                            req->xfer, reply, 0, FALSE );
+
+        // DRAGTRANSFER is no more necessary, free it early
+        qt_DrgFreeDragtransfer( req->xfer );
+#if defined(QT_DEBUG_DND)                        
+        {
+            ULONG size = ~0, flags = 0;
+            DosQueryMem( req->xfer, &size, &flags );
+            qDebug( "DefaultDragWorker: Freed DRAGTRANSFER: "
+                    "req->xfer=%p, size=%lu(0x%08lX), flags=0x%08lX",
+                    req->xfer, size, size, flags );
+        }
+#endif        
+        req->xfer = NULL;
+        
+        return (MRESULT) FALSE;
+    }
+    
+    if ( msg == DM_ENDCONVERSATION ) {
+        // we don't check that isInitialized() is TRUE here, because WPS
+        // (and probably some other apps) may send this message after
+        // cleanup() is called up on return from DrgDrag
+        
+        ULONG itemId = LONGFROMMP( mp1 );
+        ULONG flags = LONGFROMMP( mp2 );
+        
+        // sanity check (don't assert, see above)
+        Data::Request *req = d->requests[ itemId ];
+        Q_ASSERT( req );
+        if ( !req )
+            return (MRESULT) FALSE;
+
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "DefaultDragWorker: Got DM_ENDCONVERSATION for item %ld (id=%ld), "
+                "provider=%p, drm=%s, drf=%s, rendered=%d, outdated=%d",
+                req->index, itemId, req->provider,
+                req->drm.data(), req->drf.data(), req->rendered,
+                !d->isInitialized() );
+#endif
+
+        // proceed further only if it's not an outdated request
+        // from the previous DND session
+        if ( d->isInitialized() ) {
+            if ( !req->rendered ) {
+                // we treat cancelling the render request (for any reason)
+                // as a failure
+                d->renderOk = FALSE;
+            } else {
+                // the overall success is TRUE only if target says Okay
+                d->renderOk &= flags == DMFL_TARGETSUCCESSFUL;
+            }
+        }
+        
+        // delete the request
+        d->requests.remove( itemId );
+        
+        return (MRESULT) FALSE;
+    }
+    
+    return (MRESULT) FALSE;
+}
+
+bool QPMMime::DefaultDragWorker::addProvider( const char *drf, Provider *provider,
+                                              ULONG itemCnt /* = 1 */ )
+{
+    // make sure remaining requests from the previous DND session are deleted
+    d->cleanupRequests();
+
+    Q_ASSERT( drf && provider && itemCnt >= 1 );
+    if ( drf && provider && itemCnt >= 1 ) {
+        if ( d->providers.count() == 0 ) {
+            // first provider
+            d->itemCnt = itemCnt;
+            d->providers.push_back( Data::DrfProvider( drf, provider ) );
+            return TRUE;
+        }
+        // next provider, must not be exclusive and itemCnt must match
+        if ( !d->exclusive && d->itemCnt == itemCnt ) {
+            // ensure there are no dups (several providers for the same drf)
+            if ( !d->providerFor( drf ) )
+                d->providers.push_back( Data::DrfProvider( drf, provider ) );
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+// static
+bool QPMMime::DefaultDragWorker::canRender( const char *drm )
+{
+    return qstrcmp( drm, DRM_SHAREDMEM ) == 0 ||
+           qstrcmp( drm, DRM_OS2FILE ) == 0;
+}
+
+//------------------------------------------------------------------------------
+
+struct QPMMime::DefaultDropWorker::Data
+{
+    struct MimeProvider
+    {
+        MimeProvider() : prov( NULL ) {}
+        MimeProvider( const char *m, Provider *p ) : mime( m ), prov( p ) {}
+        const QCString mime;
+        Provider * const prov;
+    };
+
+    typedef QValueList<MimeProvider> MimeProviderList;
+    
+    Provider *providerFor( const char *mime )
+    {
+        for ( MimeProviderList::const_iterator it = providers.begin();
+              it != providers.end(); ++it )
+            if ( qstrcmp( (*it).mime, mime ) == 0 )
+                return (*it).prov;
+        return NULL;
+    }
+    
+    bool exclusive : 1;
+    MimeProviderList providers;
+
+    bool sending_DM_RENDER : 1;
+    bool got_DM_RENDERCOMPLETE : 1;
+    USHORT flags_DM_RENDERCOMPLETE;
+    int waiting_DM_RENDERCOMPLETE;
+};
+
+QPMMime::DefaultDropWorker::DefaultDropWorker() : d( new Data() )
+{
+    d->exclusive = FALSE;
+    d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = FALSE;
+    d->flags_DM_RENDERCOMPLETE = 0;
+    d->waiting_DM_RENDERCOMPLETE = 0;
+}
+
+QPMMime::DefaultDropWorker::~DefaultDropWorker()
+{
+    delete d;
+}
+
+void QPMMime::DefaultDropWorker::cleanup( bool isAccepted, bool isActAccepted )
+{
+    if ( d->waiting_DM_RENDERCOMPLETE != 0 ) {
+#if defined(QT_CHECK_STATE)
+        qWarning( "The previous drag source didn't post DM_RENDERCOMPLETE!\n"
+                  "Contact the drag source developer." );
+#endif        
+        qApp->eventLoop()->exitLoop();
+    }
+
+    d->providers.clear();
+    d->exclusive = FALSE;
+    d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = FALSE;
+    d->flags_DM_RENDERCOMPLETE = 0;
+    d->waiting_DM_RENDERCOMPLETE = 0;
+}
+
+bool QPMMime::DefaultDropWorker::isExclusive() const
+{
+    return d->exclusive;
+}
+
+bool QPMMime::DefaultDropWorker::provides( const char *format ) const
+{
+    return d->providerFor( format ) != NULL;
+}
+
+int QPMMime::DefaultDropWorker::formatCount() const
+{
+    return d->providers.count();
+}
+
+const char *QPMMime::DefaultDropWorker::format( int fn ) const
+{
+    if ( fn >= 0 && (uint) fn < d->providers.count() )
+        return d->providers[ fn ].mime;
+    return NULL;    
+}
+
+static QCString composeTempFileName()
+{
+    static char defTmpDir[ 3 ] = { 0 };
+    const char *tmpDir = getenv( "TEMP" );
+    if ( !tmpDir ) tmpDir = getenv( "TMP" );
+    if ( !tmpDir || !QFile::exists( QFile::decodeName( tmpDir ) ) ) {
+        if ( !defTmpDir[ 0 ] ) { 
+            ULONG bootDrive = 0;
+            DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
+                             &bootDrive, sizeof (bootDrive) );
+            defTmpDir[ 0 ] = bootDrive;
+            defTmpDir[ 1 ] = ':';
+        }
+        tmpDir = defTmpDir;
+    }
+
+    static bool srandDone = FALSE;
+    if ( !srandDone ) {
+        srand( time( NULL ) );
+        srandDone = TRUE;
+    }
+    
+    ULONG num = rand();
+    enum { Attempts = 100 };
+    int attempts = Attempts;
+    
+    QCString tmpName;
+    do {
+        tmpName.sprintf( "%s\\%08lX.tmp", tmpDir, num );
+        if ( !QFile::exists( QFile::decodeName( tmpName ) ) )
+            break;
+        num = rand();
+    } while ( --attempts > 0 );
+    
+    Q_ASSERT( attempts > 0 );
+    if ( attempts <= 0 )
+        tmpName.resize( 0 );
+    
+    return tmpName;
+}
+
+QByteArray QPMMime::DefaultDropWorker::encodedData( const char *format ) const
+{
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "DefaultDropWorker::encodedData(%s)", format );
+#endif
+
+    QByteArray data;
+
+    Q_ASSERT( info() );
+    if ( !info() )
+        return data;
+    
+    ULONG itemCount = DrgQueryDragitemCount( info() );
+    Q_ASSERT( itemCount );
+    if ( !itemCount )
+        return data;
+    
+    Provider *provider = d->providerFor( format );
+    if ( !provider )
+        return data;
+
+    const char *drf = provider->drf( format );
+    Q_ASSERT( drf );
+    if ( !drf )
+        return data;
+
+    // Note: Allocating and freeing DRAGTRANSFER structures is a real mess. It's
+    // absolutely unclear how they can be reused for multiple items and/or render
+    // requests. My practice shows, that they cannot be reused at all, especially
+    // when the source and the target are the same process: if we have multiple
+    // items and use the same DRAGTRANSFER for all of them, the source will call
+    // DrgFreeDragtransfer() every time that will eventually destroy the memory
+    // object before the target finishes to work with it, so that the next
+    // DrgFreeDragtransfer() will generate a segfault in PMCTLS. Also note that
+    // using a number > 1 as an argument to DrgAllocDragtransfer() won't help
+    // because that will still allocate a single memory object. Thus, we will
+    // always allocate a new struct per every item. It seems to work.
+
+    QCString renderToName = composeTempFileName();
+    HSTR hstrRenderToName = DrgAddStrHandle( renderToName );
+    
+    HSTR rmfOS2File =
+        DrgAddStrHandle( QCString().sprintf( "<"DRM_OS2FILE",%s>", drf ) );
+    HSTR rmfSharedMem =
+        DrgAddStrHandle( QCString().sprintf( "<"DRM_SHAREDMEM",%s>", drf ) );
+    
+    MRESULT mrc;
+    bool renderOk = FALSE;
+    
+    DRAGTRANSFER *xfer = NULL;
+    QCString srcFileName;
+
+    QByteArray allData, itemData;
+    
+    for ( ULONG i = 0; i < itemCount; ++i ) {
+        DRAGITEM *item = DrgQueryDragitemPtr( info(), i );
+        Q_ASSERT( item );
+        if ( !item ) {
+            renderOk = FALSE;
+            break;
+        }
+
+        enum { None, OS2File, SharedMem } drm = None;
+        bool needToTalk = TRUE;
+        
+        // determine the mechanism to use (prefer DRM_SHAREDMEM)
+        
+        if ( DrgVerifyRMF( item, DRM_SHAREDMEM, drf ) &&
+             DrgVerifyRMF( item, DRM_SHAREDMEM, DRF_POINTERDATA ) )
+            drm = SharedMem;
+        if ( DrgVerifyRMF( item, DRM_OS2FILE, drf ) ) {
+            srcFileName = querySourceNameFull( item );
+            // If the source provides the full file name, we prefer DRM_OS2FILE
+            // even if there is also DRM_SHAREDMEM available because we don't
+            // need to do any communication in this case at all. This will help
+            // with some native drag sources (such as DragText) that cannot send
+            // DM_RENDERCOMPLETE synchronously (before we return from DM_DROP)
+            // and would hang otherwise.
+            if ( !srcFileName.isEmpty() ) {
+                needToTalk = FALSE;
+                drm = OS2File;
+            } else if ( drm == None ) {
+                srcFileName = renderToName;
+                drm = OS2File;
+            }
+        }
+        Q_ASSERT( drm != None );
+        if ( drm == None ) {
+            renderOk = FALSE;
+            break;
+        }
+        
+        if ( needToTalk ) {
+            // need to perform a conversation with the source,
+            // allocate a new DRAGTRANSFER structure for each item
+            xfer = DrgAllocDragtransfer( 1 );
+            Q_ASSERT( xfer );
+            if ( !xfer ) {
+                renderOk = FALSE;
+                break;
+            }
+    
+            xfer->cb = sizeof( DRAGTRANSFER );
+            xfer->hwndClient = hwnd();
+            xfer->ulTargetInfo = (ULONG) info();
+            xfer->usOperation = info()->usOperation;
+            
+            xfer->pditem = item;
+            if ( drm == OS2File ) {
+                xfer->hstrSelectedRMF = rmfOS2File;
+                xfer->hstrRenderToName = hstrRenderToName;
+            } else {
+                xfer->hstrSelectedRMF = rmfSharedMem;
+                xfer->hstrRenderToName = 0;
+            }
+    
+#if defined(QT_DEBUG_DND)                        
+            qDebug( "DefaultDropWorker: Will use %s to render item %p",
+                    queryHSTR( xfer->hstrSelectedRMF ).data(), item );
+#endif
+
+            mrc = (MRESULT) TRUE;
+            if ( (item->fsControl & DC_PREPARE) |
+                 (item->fsControl & DC_PREPAREITEM) ) {
+#if defined(QT_DEBUG_DND)                        
+                qDebug( "DefaultDropWorker: Sending DM_RENDERPREPARE to 0x%08lX...",
+                        info()->hwndSource );
+#endif                
+                mrc = DrgSendTransferMsg( info()->hwndSource, DM_RENDERPREPARE,
+                                          MPFROMP (xfer), 0 );
+#if defined(QT_DEBUG_DND)                        
+                qDebug( "DefaultDropWorker: Finisned sending DM_RENDERPREPARE\n"
+                        " mrc=%p, xfer->fsReply=0x%08hX", mrc, xfer->fsReply );
+#endif                
+                renderOk = (BOOL) mrc;
+            }
+    
+            if ( (BOOL) mrc ) {
+#if defined(QT_DEBUG_DND)                        
+                qDebug( "DefaultDropWorker: Sending DM_RENDER to 0x%08lX...",
+                        item->hwndItem );
+#endif
+                d->sending_DM_RENDER = TRUE;
+                mrc = DrgSendTransferMsg( item->hwndItem, DM_RENDER,
+                                          MPFROMP (xfer), 0 );
+                d->sending_DM_RENDER = FALSE;
+#if defined(QT_DEBUG_DND)                        
+                qDebug( "DefaultDropWorker: Finisned Sending DM_RENDER\n"
+                        " mrc=%p, xfer->fsReply=0x%hX, got_DM_RENDERCOMPLETE=%d",
+                        mrc, xfer->fsReply, d->got_DM_RENDERCOMPLETE );
+#endif
+
+                if ( !(BOOL) mrc || d->got_DM_RENDERCOMPLETE ) {
+                    if ( d->got_DM_RENDERCOMPLETE )
+                        renderOk = (d->flags_DM_RENDERCOMPLETE & DMFL_RENDEROK);
+                    else
+                        renderOk = FALSE;
+                } else {
+                    // synchronously wait for DM_RENDERCOMPLETE
+                    d->waiting_DM_RENDERCOMPLETE = qApp->loopLevel() + 1;
+#if defined(QT_DEBUG_DND)                        
+                    qDebug( "DefaultDropWorker: Waiting for DM_RENDERCOMPLETE..." );
+#endif
+                    int level = qApp->eventLoop()->enterLoop();
+#if defined(QT_DEBUG_DND)                        
+                    qDebug( "DefaultDropWorker: Finished waiting for "
+                            "DM_RENDERCOMPLETE (%d)\n"
+                            " got_DM_RENDERCOMPLETE=%d, usFS=0x%hX",
+                            level, d->got_DM_RENDERCOMPLETE, d->flags_DM_RENDERCOMPLETE );
+#endif
+                    // JFTR: at this point, cleanup() might have been called,
+                    // as a result of either program exit or getting another        
+                    // DM_DRAGOVER (if the source app has crashed) before getting 
+                    // DM_RENDERCOMPLETE from the source. Use data members with
+                    // care!
+                    d->waiting_DM_RENDERCOMPLETE = 0;
+                    renderOk = d->got_DM_RENDERCOMPLETE &&
+                               (d->flags_DM_RENDERCOMPLETE & DMFL_RENDEROK);
+                }
+    
+                d->got_DM_RENDERCOMPLETE = FALSE;
+            }
+        } else {
+#if defined(QT_DEBUG_DND)                        
+            qDebug( "DefaultDropWorker: Source supports <"DRM_OS2FILE",%s> and "
+                    "provides a file \"%s\" for item %p (no need to render)",
+                    drf, srcFileName.data(), item );
+#endif
+            renderOk = TRUE;
+        }
+
+        if ( renderOk ) {
+            if ( drm == OS2File ) {
+#if defined(QT_DEBUG_DND)                        
+                qDebug( "DefaultDragWorker: Will read from \"%s\"",
+                        srcFileName.data() );
+#endif
+                QFile file( QFile::decodeName( srcFileName ) );
+                renderOk = file.open( IO_ReadOnly );
+                if ( renderOk ) {
+                    itemData = file.readAll();
+                    renderOk = file.status() == IO_Ok;
+                    file.close();
+                }
+                bool ok = file.remove();
+                Q_ASSERT( (ok = ok) );
+            } else {
+                Q_ASSERT( xfer->hstrRenderToName );
+                renderOk = xfer->hstrRenderToName != 0;
+                if ( renderOk ) {
+                    const char *ptr = (const char *) xfer->hstrRenderToName;
+                    ULONG size = ~0;
+                    ULONG flags = 0;
+                    APIRET rc = DosQueryMem( (PVOID) ptr, &size, &flags );
+                    renderOk = rc == 0;
+                    if ( renderOk ) {
+#if defined(QT_DEBUG_DND)                        
+                        qDebug( "DefaultDropWorker: Got shared data=%p, size=%lu "
+                                "(0x%08lX), flags=0x%08lX", ptr, size, size, flags );
+#endif
+                        Q_ASSERT( flags & (PAG_COMMIT | PAG_READ | PAG_BASE) ==
+                                  (PAG_COMMIT | PAG_READ | PAG_BASE) ); 
+                        renderOk = flags & (PAG_COMMIT | PAG_READ | PAG_BASE) ==
+                                   (PAG_COMMIT | PAG_READ | PAG_BASE);
+#ifndef QT_NO_DEBUG
+                    } else {
+                        qSystemWarning( "DosQueryMem failed", rc );
+#endif                        
+                    }
+                    if ( renderOk ) {
+                        ULONG realSize = *(ULONG *) ptr;
+#if defined(QT_DEBUG_DND)                        
+                        qDebug( "DefaultDropWorker: realSize=%lu", realSize );
+#endif
+                        Q_ASSERT( realSize <= size );
+                        renderOk = realSize <= size;
+                        if ( renderOk ) {
+                            itemData.resize( realSize );
+                            memcpy( itemData.data(), ptr + sizeof (ULONG), realSize );
+                        }
+                    }
+                    // free memory only if it is given by another process,
+                    // otherwise DefaultDragWorker will free it
+                    if ( flags & PAG_SHARED )
+                        DosFreeMem( (PVOID) xfer->hstrRenderToName );
+                }
+            }
+        }
+
+        if ( renderOk )
+            renderOk = provider->provide( format, i, itemData, allData );
+
+        if ( needToTalk ) {
+            // free the DRAGTRANSFER structure
+            DrgFreeDragtransfer( xfer );
+#if defined(QT_DEBUG_DND)                        
+            {
+                ULONG size = ~0, flags = 0;
+                DosQueryMem( xfer, &size, &flags );
+                qDebug( "DefaultDropWorker: Freed DRAGTRANSFER: "
+                        "xfer=%p, size=%lu(0x%08lX), flags=0x%08lX",
+                        xfer, size, size, flags );
+            }
+#endif            
+            xfer = NULL;
+        }
+        
+        if ( !renderOk )
+            break;
+    }
+
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "DefaultDropWorker: renderOk=%d", renderOk );
+#endif
+
+    DrgDeleteStrHandle( rmfSharedMem );
+    DrgDeleteStrHandle( rmfOS2File );
+    DrgDeleteStrHandle( hstrRenderToName );
+
+    if ( renderOk )
+        data = allData;
+
+    return data;
+}
+
+MRESULT QPMMime::DefaultDropWorker::message( ULONG msg, MPARAM mp1, MPARAM mp2 )
+{
+    switch ( msg ) {
+        case DM_RENDERCOMPLETE: {
+            // sanity check
+            Q_ASSERT( info() );
+            if ( !info() )
+                return (MRESULT) FALSE;
+
+#if defined(QT_DEBUG_DND)                        
+            qDebug( "DefaultDropWorker: Got DM_RENDERCOMPLETE" );
+#endif            
+            d->got_DM_RENDERCOMPLETE = TRUE;
+            d->flags_DM_RENDERCOMPLETE = SHORT1FROMMP( mp2 );
+
+            if (d->sending_DM_RENDER)
+            {
+                DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
+#if defined(QT_CHECK_STATE)
+                qWarning( "Drag item 0x%08lX sent DM_RENDERCOMPLETE w/o first "
+                          "replying to DM_RENDER!\n"
+                          "Contact the drag source developer.",
+                          xfer->pditem->hwndItem );
+#endif                
+                return (MRESULT) FALSE;
+            }
+            
+            // stop synchronous waiting for DM_RENDERCOMPLETE
+            if ( d->waiting_DM_RENDERCOMPLETE != 0 )
+                qApp->eventLoop()->exitLoop();
+            return (MRESULT) FALSE;
+        }
+        default:
+            break;
+    }
+    
+    return (MRESULT) FALSE;
+}
+
+bool QPMMime::DefaultDropWorker::addProvider( const char *format,
+                                              Provider *provider )
+{
+    Q_ASSERT( format && provider );
+    if ( format && provider && !d->exclusive ) {
+        // ensure there are no dups (several providers for the same mime)
+        if ( !d->providerFor( format ) )
+            d->providers.push_back( Data::MimeProvider( format, provider ) );
+        return TRUE;
+    }
+    return FALSE;
+}
+
+bool QPMMime::DefaultDropWorker::addExclusiveProvider( const char *format,
+                                                       Provider *provider )
+{
+    Q_ASSERT( format && provider );
+    if ( format && provider && !d->exclusive ) {
+        d->exclusive = TRUE;
+        d->providers.clear();
+        d->providers.push_back( Data::MimeProvider( format, provider ) );
+        return TRUE;
+    }
+    return FALSE;
+}
+
+// static
+bool QPMMime::DefaultDropWorker::canRender( DRAGITEM *item, const char *drf )
+{
+    return DrgVerifyRMF( item, DRM_OS2FILE, drf ) ||
+           (DrgVerifyRMF( item, DRM_SHAREDMEM, drf ) &&
+            DrgVerifyRMF( item, DRM_SHAREDMEM, DRF_POINTERDATA ));
+}
+
+// static
+/*! \internal
+  Parses the rendering mechanism/format specification of the given \a item
+  and stores only those mechanism branches in the given \a list that represent
+  mechanisms supported by this worker. Returns FALSE if fails to parse the
+  RMF specification. Note that if no supported mechanisms are found, TRUE is
+  returned but the \a list will simply contain zero items.  
+  \note The method clears the given \a list variable before proceeding and sets
+  auto-deletion to TRUE.
+  \sa canRender(), PMMime::parseRMFs()
+*/
+bool QPMMime::DefaultDropWorker::getSupportedRMFs( DRAGITEM *item, 
+                                                   QPtrList<QStrList> &list )
+{
+    if ( !parseRMFs( item->hstrRMF, list ) )
+        return FALSE;
+    
+    for ( QStrList *mech = list.first(); mech; ) {
+        const char *drm = mech->first();
+        if ( qstrcmp( drm, DRM_OS2FILE ) == 0 ) {
+            mech = list.next();
+            continue;
+        }
+        if ( qstrcmp( drm, DRM_SHAREDMEM ) == 0 ) {
+            const char *drf = mech->next();
+            // accept DRM_SHAREDMEM only if there is DRF_POINTERDATA
+            for( ; drf; drf = mech->next() ) {
+                if ( qstrcmp( drf, DRF_POINTERDATA ) == 0 )
+                    break;
+            }
+            if ( drf ) {
+                mech = list.next();
+                continue;
+            }
+        }
+        // remove the unsupported mechanism branch from the list
+        bool wasLast = list.getLast() == mech;
+        list.removeRef( mech );
+        // after deleting the last item, the current one will be set to the new
+        // last item which was already analyzed earlier, so set to 0 to stop 
+        mech = wasLast ? 0 : list.current();
+    }
+    
+    return TRUE;
+}
+
+#endif // !QT_NO_DRAGANDDROP
+
+//------------------------------------------------------------------------------
 
 static QPtrList<QPMMime> mimes;
@@ -96,5 +1373,6 @@
 QPMMime::QPMMime()
 {
-    mimes.append( this );
+    // we prepend the convertor to let it override other convertors
+    mimes.prepend( this );
 }
 
@@ -115,21 +1393,92 @@
     ulong cf;
     QCString mime;
+#if !defined(QT_NO_DRAGANDDROP)
+    QCString drf;
+#endif // !QT_NO_DRAGANDDROP
 };
-
-static QPtrList<QPMRegisteredMimeType> mimetypes;
 
 
 class QPMMimeAnyMime : public QPMMime {
 public:
-    const char* convertorName();
+    QPMMimeAnyMime();
+    ~QPMMimeAnyMime();
+    
+    const char *convertorName();
     int countCf();
     ulong cf( int index );
     ulong flFor( ulong cf );
     ulong cfFor( const char* mime );
-    const char* mimeFor( ulong cf );
+    const char *mimeFor( ulong cf );
     bool canConvert( const char* mime, ulong cf );
     QByteArray convertToMime( QByteArray data, const char *, ulong );
     QByteArray convertFromMime( QByteArray data, const char *, ulong );
+
+#if !defined(QT_NO_DRAGANDDROP)
+    
+    DragWorker *dragWorkerFor( const char *mime, QMimeSource *src );
+    DropWorker *dropWorkerFor( DRAGINFO *info );
+
+    class AnyDragProvider : public DefaultDragWorker::Provider
+    {
+    public:
+        AnyDragProvider( QPMMimeAnyMime *am ) : anyMime( am ) {}
+        // Provider interface
+        const char *format( const char *drf ) const;
+        bool provide( const char *drf, const QByteArray &allData,
+                      ULONG itemIndex, QByteArray &itemData );
+        void fileType( const char *drf, const char *&type, const char *&ext );
+    private:
+        QPMMimeAnyMime *anyMime;
+    };
+
+    class AnyDropProvider : public DefaultDropWorker::Provider
+    {
+    public:
+        AnyDropProvider( QPMMimeAnyMime *am ) : anyMime( am ) {}
+        // Provider interface
+        const char *drf( const char *format ) const;
+        bool provide( const char *format, ULONG itemIndex,
+                      const QByteArray &itemData, QByteArray &allData );
+    private:
+        QPMMimeAnyMime *anyMime;
+    };
+
+#endif // !QT_NO_DRAGANDDROP
+    
+private:
+    ulong registerMimeType( const char *mime );
+    const char *lookupMimeType( ulong cf );
+
+    QPtrList<QPMRegisteredMimeType> mimetypes;
+    
+#if !defined(QT_NO_DRAGANDDROP)
+
+    AnyDragProvider anyDragProvider;
+    AnyDropProvider anyDropProvider;
+
+    friend class AnyDragProvider;
+    friend class AnyDropProvider;
+    
+#endif // !QT_NO_DRAGANDDROP
 };
+
+QPMMimeAnyMime::QPMMimeAnyMime()
+#if !defined(QT_NO_DRAGANDDROP)
+    : anyDragProvider( AnyDragProvider( this ) )
+    , anyDropProvider( AnyDropProvider( this ) )
+#endif // !QT_NO_DRAGANDDROP
+{
+    mimetypes.setAutoDelete( TRUE );
+}
+
+QPMMimeAnyMime::~QPMMimeAnyMime()
+{
+    // dereference all atoms we've referenced
+    HATOMTBL tbl = WinQuerySystemAtomTable();
+    for ( QPMRegisteredMimeType *mt = mimetypes.first();
+          mt; mt = mimetypes.next() ) {
+        WinDeleteAtom( tbl, 0xFFFF0000 | mt->cf );
+    }
+}
 
 const char* QPMMimeAnyMime::convertorName()
@@ -176,24 +1525,11 @@
 	if ( mt->cf == cf )
 	    return mt->mime;
-    return 0;
+    // try to determine the mime type from the clipboard format ID
+    return lookupMimeType( cf );
 }
 
 bool QPMMimeAnyMime::canConvert( const char* mime, ulong cf )
 {
-    QPMRegisteredMimeType *mt = mimetypes.current();
-    do {
-        if ( mt ) // quick check with most-recent
-            if ( mt->cf == cf )
-                break;
-        for ( mt = mimetypes.first(); mt; mt = mimetypes.next() )
-            if ( mt->cf == cf )
-                break;
-        if ( !mt ) {
-            ulong f = registerMimeType( mime );
-            return f && f == cf;
-        }
-    } while ( 0 );
-
-    return qstricmp( mt->mime, mime ) == 0;
+    return mime && cf && qstricmp( mimeFor( cf ), mime ) == 0;
 }
 
@@ -208,9 +1544,198 @@
 }
 
-
+#if !defined(QT_NO_DRAGANDDROP)
+
+const char *QPMMimeAnyMime::AnyDragProvider::format( const char *drf ) const
+{
+    QPMRegisteredMimeType *mt = anyMime->mimetypes.current();
+    if ( mt ) // quick check with most-recent
+	if ( qstricmp( mt->drf, drf ) == 0 )
+            return mt->mime;
+    for ( mt = anyMime->mimetypes.first(); mt; mt = anyMime->mimetypes.next() )
+	if ( qstricmp( mt->drf, drf ) == 0 )
+	    return mt->mime;
+        
+    Q_ASSERT( /* mt->mime != */ NULL );
+    return NULL;
+}
+
+bool QPMMimeAnyMime::AnyDragProvider::provide( const char *drf,
+                                               const QByteArray &allData,
+                                               ULONG itemIndex,
+                                               QByteArray &itemData )
+{
+    // always straight through coversion
+    itemData = allData;
+    return TRUE;
+}
+
+void QPMMimeAnyMime::AnyDragProvider::fileType( const char *drf, const char *&type,
+                                                const char *&ext )
+{
+    // file type = mime
+    type = format( drf );
+    Q_ASSERT( type );
+    
+    // no way to determine the extension
+    ext = NULL;
+
+    /// @todo (dmik) later, we can add a hack method to qmime.cpp that returns
+    //  the extension->mime map of the current QMimeSourceFactory and use it
+    //  to determine the extension.
+};
+
+const char *QPMMimeAnyMime::AnyDropProvider::drf( const char *format ) const
+{
+    ulong cf = anyMime->cfFor( format );
+    if ( cf ) {
+        // current is what was found by cfFor()
+        Q_ASSERT( anyMime->mimetypes.current()->drf );
+        return anyMime->mimetypes.current()->drf;
+    }
+        
+    Q_ASSERT( FALSE );
+    return NULL;
+}
+
+bool QPMMimeAnyMime::AnyDropProvider::provide( const char *format,
+                                             ULONG /* itemIndex */,
+                                             const QByteArray &itemData,
+                                             QByteArray &allData )
+{
+    // always straight through coversion
+    allData = itemData;
+    return TRUE;
+}
+
+QPMMime::DragWorker *QPMMimeAnyMime::dragWorkerFor( const char *mime, QMimeSource *src )
+{
+    ulong cf = cfFor( mime );
+    if ( cf ) {
+        DefaultDragWorker *defWorker = defaultCoopDragWorker();
+        QCString drf;
+        drf.sprintf( "CF_%04lX", cf ); 
+        // current is what was found by cfFor()
+        mimetypes.current()->drf = drf;
+        // add a cooperative provider
+        defWorker->addProvider( drf, &anyDragProvider );
+        return defWorker;
+    }
+    
+    return NULL;
+}
+
+QPMMime::DropWorker *QPMMimeAnyMime::dropWorkerFor( DRAGINFO *info )
+{
+    ULONG itemCount = DrgQueryDragitemCount( info );
+    Q_ASSERT( itemCount );
+    if ( !itemCount )
+        return NULL;
+
+    if ( itemCount == 1 ) {
+        DRAGITEM *item = DrgQueryDragitemPtr( info, 0 );
+        Q_ASSERT( item );
+        if ( !item )
+            return NULL;
+
+        DefaultDropWorker *defWorker = defaultDropWorker();
+        bool atLeastOneSupported = FALSE;
+        
+        // check that we support one of DRMs and the format is MIME_hhhh
+        QPtrList<QStrList> list;
+        defWorker->getSupportedRMFs( item, list );
+        for( QStrList *mech = list.first(); mech; mech = list.next() ) {
+#if defined(QT_DEBUG_DND)                        
+            qDebug( "QPMMimeAnyMime: Supported drm: %s", mech->getFirst() );            
+#endif
+            for( const char *drf = mech->at( 1 ); drf; drf = mech->next() ) {
+                if ( qstrncmp( drf, "CF_", 3 ) == 0 ) {
+                    // get the atom ID
+                    ulong cf = 0; 
+                    uint len = qstrlen ( drf );
+                    for ( uint i = 3; i < len; ++i ) {
+                        int hex = hex_to_int( drf[ i ] );
+                        if ( hex < 0 ) { cf = 0; break; }
+                        cf = (cf << 4) | (uint) hex;
+                    }
+                    Q_ASSERT( cf );
+                    if ( !cf )
+                        continue;
+#if defined(QT_DEBUG_DND)                        
+                    qDebug( "QPMMimeAnyMime: drf %s is atom 0x%08lX", drf, cf );
+#endif                    
+                    const char *mime = mimeFor( cf );
+                    if ( mime ) {
+#if defined(QT_DEBUG_DND)                        
+                        qDebug( "QPMMimeAnyMime: Will provide [%s] for drf %s",
+                                mime, drf );
+#endif                        
+                        // current is what was found by mimeFor()
+                        mimetypes.current()->drf = drf;
+                        // add a cooperative provider (can coexist with others)
+                        defWorker->addProvider( mime, &anyDropProvider );
+                        atLeastOneSupported = TRUE;
+                    }
+                }
+            }
+        }
+        
+        if ( atLeastOneSupported )
+            return defWorker;
+    }
+    
+    return NULL;
+}
+
+#endif // !QT_NO_DRAGANDDROP
+
+ulong QPMMimeAnyMime::registerMimeType( const char *mime )
+{
+    QCString atom;
+    atom.sprintf( "mime:%s", mime );
+    
+    ulong f = WinAddAtom( WinQuerySystemAtomTable(), atom );
+    if ( !f ) {
+#ifndef QT_NO_DEBUG
+	qSystemWarning( "QPMMime: Failed to register the clipboard format" );
+#endif
+        return 0;
+    }
+
+    mimetypes.append( new QPMRegisteredMimeType ( f, mime ) );
+    Q_ASSERT( mimetypes.current()->cf == f ); 
+    return mimetypes.current()->cf == f ? f : 0;
+}
+
+const char *QPMMimeAnyMime::lookupMimeType( ulong cf )
+{
+    HATOMTBL tbl = WinQuerySystemAtomTable();
+    ULONG len = WinQueryAtomLength( tbl, cf );
+    QCString atom( len + 1 );
+    WinQueryAtomName( tbl, cf, atom.data(), atom.size() );
+
+    // see if this atom represents a mime type    
+    if ( qstrncmp( atom, "mime:", 5 ) == 0 ) {
+        atom = atom.right( atom.length() - 5 );
+        if ( !atom.isEmpty() ) {
+            ulong f = WinAddAtom( tbl, (PSZ) (0xFFFF0000 | cf) );
+            if ( !f ) {
+#ifndef QT_NO_DEBUG
+	        qSystemWarning( "QPMMime: Failed to reference the clipboard format" );
+#endif
+                return 0;
+            }
+            mimetypes.append( new QPMRegisteredMimeType ( cf, atom ) );
+            Q_ASSERT( mimetypes.current()->cf == cf ); 
+            return mimetypes.current()->cf == cf ? mimetypes.current()->mime : NULL;
+        }
+    }
+    
+    return NULL;
+}
 
 class QPMMimeText : public QPMMime {
 public:
     QPMMimeText();
+    ~QPMMimeText();
     const char* convertorName();
     int countCf();
@@ -222,6 +1747,65 @@
     QByteArray convertToMime( QByteArray data, const char *, ulong cf );
     QByteArray convertFromMime( QByteArray data, const char *, ulong cf );
+
+#if !defined(QT_NO_DRAGANDDROP)
+    
+    DragWorker *dragWorkerFor( const char *mime, QMimeSource *src );
+    DropWorker *dropWorkerFor( DRAGINFO *info );
+
+    class NativeFileDrag : public DragWorker, public Messenger
+    {
+    public:
+        // DragWorker interface
+        bool cleanup( bool isCancelled ) { return TRUE; } // always disallow Move
+        bool isExclusive() const { return TRUE; }
+        ULONG itemCount() const { return 0; } // super exclusive
+        HWND hwnd() const { return Messenger::hwnd(); }
+        DRAGINFO *createDragInfo( const char *name, USHORT supportedOps );
+        // Messenger interface (dummy implementation, we don't need to interact)
+        MRESULT message( ULONG msg, MPARAM mp1, MPARAM mp2 ) { return 0; }
+    };
+    
+    class NativeFileDrop : public DropWorker
+    {
+    public:
+        // DropWorker interface
+        bool isExclusive() const { return TRUE; }
+        bool provides( const char *format ) const;
+        int formatCount() const;
+        const char *format( int fn ) const;
+        QByteArray encodedData( const char *format ) const;
+    };
+    
+    class TextDragProvider : public DefaultDragWorker::Provider
+    {
+    public:
+        TextDragProvider() : exclusive( FALSE ) {}
+        bool exclusive;
+        // Provider interface
+        const char *format( const char *drf ) const;
+        bool provide( const char *drf, const QByteArray &allData,
+                      ULONG itemIndex, QByteArray &itemData );
+        void fileType( const char *drf, const char *&type, const char *&ext );
+    };
+
+    class TextDropProvider : public DefaultDropWorker::Provider
+    {
+    public:
+        // Provider interface
+        const char *drf( const char *format ) const;
+        bool provide( const char *format, ULONG itemIndex,
+                      const QByteArray &itemData, QByteArray &allData );
+    };
+    
+#endif // !QT_NO_DRAGANDDROP
+    
 private:
     const ulong CF_TextUnicode;
+#if !defined(QT_NO_DRAGANDDROP)
+    NativeFileDrag nativeFileDrag;
+    NativeFileDrop nativeFileDrop;
+    TextDragProvider textDragProvider;
+    TextDropProvider textDropProvider;
+#endif // !QT_NO_DRAGANDDROP
 };
 
@@ -234,4 +1818,9 @@
 }
 
+QPMMimeText::~QPMMimeText()
+{
+    WinDeleteAtom( WinQuerySystemAtomTable(), 0xFFFF0000 | CF_TextUnicode );
+}
+
 const char* QPMMimeText::convertorName()
 {
@@ -261,7 +1850,7 @@
 ulong QPMMimeText::cfFor( const char* mime )
 {
-/// @todo (dmik) do we want to accept "text/plain" w/o "charset="?
-//    if ( qstricmp( mime, "text/plain" ) == 0 )
-//	return CF_TEXT;
+    if ( qstrnicmp( mime, MIME_TEXT_PLAIN, qstrlen( MIME_TEXT_PLAIN ) ) != 0 ||
+         (mime[10] != 0 && mime[10] != ' ' && mime[10] != ';') )
+        return 0;
 
     QCString m( mime );
@@ -276,6 +1865,10 @@
 	if ( cs == "ISO-10646-UCS-2" )
 	    return CF_TextUnicode;
-    }
-    return 0;
+        // any other 'charset' spec 
+        return 0;
+    }
+    
+    // no 'charset' spec
+    return CF_TEXT;
 }
 
@@ -283,5 +1876,5 @@
 {
     if ( cf == CF_TEXT )
-	return "text/plain;charset=system";
+	return MIME_TEXT_PLAIN_CHARSET_SYSTEM;
     else if ( cf == CF_TextUnicode )
         return "text/plain;charset=ISO-10646-UCS-2";
@@ -407,4 +2000,374 @@
 }
 
+#if !defined(QT_NO_DRAGANDDROP)
+
+DRAGINFO *QPMMimeText::NativeFileDrag::createDragInfo( const char *name,
+                                                       USHORT supportedOps )
+{
+    Q_ASSERT( source() );
+    if ( !source() )
+        return NULL;
+    
+    // obtain the list of files
+    QStringList list;
+    QUriDrag::decodeLocalFiles( source(), list );
+    ULONG itemCnt = list.count();
+    Q_ASSERT( itemCnt );
+    if ( !itemCnt )
+        return NULL;
+    
+#if defined(QT_DEBUG_DND)                        
+    qDebug( "QPMMimeText::NativeFileDrag: itemCnt=%ld", itemCnt );
+#endif
+    
+    DRAGINFO *info = DrgAllocDraginfo( itemCnt );
+    Q_ASSERT( info );
+    if ( !info )
+        return NULL;
+    
+    bool ok = TRUE;
+    QStringList::Iterator it = list.begin();
+    for ( ULONG i = 0; i < itemCnt; ++i, ++it ) {
+        DRAGITEM *item = DrgQueryDragitemPtr( info, i );
+        Q_ASSERT( item );
+        if ( !item ) {
+            ok = FALSE;
+            break;
+        }
+
+        QCString fileName = QDir::convertSeparators( *it ).local8Bit();
+
+        int sep = fileName.findRev( '\\' );
+        Q_ASSERT( sep > 0 && sep < int( fileName.length() ) - 1 );
+        if ( sep <= 0 || sep >= int( fileName.length() ) - 1 ) {
+            ok = FALSE;
+            break;
+        }
+
+        item->hstrSourceName = DrgAddStrHandle( fileName.data() + sep + 1 );
+        fileName.truncate( sep + 1 );
+        item->hstrContainerName = DrgAddStrHandle( fileName );
+
+#if defined(QT_DEBUG_DND)                        
+        qDebug( "QPMMimeText::NativeFileDrag: item %ld: dir=\"%s\", name=\"%s\"",
+                i, queryHSTR( item->hstrContainerName ).data(),
+                   queryHSTR( item->hstrSourceName ).data() );
+#endif
+        
+        item->hwndItem = hwnd();
+        item->ulItemID = 0;
+        item->hstrType = DrgAddStrHandle( DRT_UNKNOWN );
+        item->hstrRMF = DrgAddStrHandle( "<"DRM_OS2FILE","DRF_UNKNOWN">" );
+        item->hstrTargetName = 0;
+        item->cxOffset = 0;
+        item->cyOffset = 0;
+        item->fsControl = 0;
+        item->fsSupportedOps = supportedOps;
+    }    
+
+    if ( !ok ) {
+        DrgFreeDraginfo( info );
+        info = NULL;
+    }
+    
+    return info;
+}
+
+bool QPMMimeText::NativeFileDrop::provides( const char *format ) const
+{
+    return qstricmp( format, MIME_TEXT_URI_LIST ) == 0;
+}
+
+int QPMMimeText::NativeFileDrop::formatCount() const
+{
+    return 1;
+}
+
+const char *QPMMimeText::NativeFileDrop::format( int fn ) const
+{
+    return fn == 0 ? MIME_TEXT_URI_LIST : NULL;
+}
+
+QByteArray QPMMimeText::NativeFileDrop::encodedData( const char *format ) const
+{
+    QByteArray data;
+
+    Q_ASSERT( info() );
+    if ( !info() )
+        return data;
+    
+    ULONG itemCount = DrgQueryDragitemCount( info() );
+    Q_ASSERT( itemCount );
+    if ( !itemCount )
+        return data;
+    
+    // sanity check
+    if ( qstricmp( format, MIME_TEXT_URI_LIST ) != 0 )
+        return data;
+    
+    QCString texturi;
+
+    for ( ULONG i = 0; i < itemCount; ++i ) {
+        DRAGITEM *item = DrgQueryDragitemPtr( info(), i );
+        Q_ASSERT( item );
+        QCString fullName;
+        if ( !item || !canTargetRenderAsOS2File( item, &fullName ) )
+            return data;
+        QString fn = QFile::decodeName( fullName );
+        texturi += QUriDrag::localFileToUri( fn );
+        texturi += "\r\n";
+    }
+    
+    data = texturi;
+    return data;
+}
+
+const char *QPMMimeText::TextDragProvider::format( const char *drf ) const
+{
+    if ( qstrcmp( drf, DRF_TEXT ) == 0 ) {
+        if ( exclusive )
+            return MIME_TEXT_URI_LIST;
+        else
+            return MIME_TEXT_PLAIN_CHARSET_SYSTEM;
+    }
+    return NULL;
+}
+
+bool QPMMimeText::TextDragProvider::provide( const char *drf,
+                                             const QByteArray &allData,
+                                             ULONG itemIndex,
+                                             QByteArray &itemData )
+{
+    if ( qstrcmp( drf, DRF_TEXT ) == 0 ) {
+        if ( exclusive ) {
+            // locate the required item
+            int dataSize = allData.size();
+            if ( !dataSize )
+                return FALSE;
+            int begin = 0, end = 0, next = 0;
+            do {
+                begin = next;
+                end = allData.find( '\r', begin );
+                if ( end >= 0 ) {
+                    next = end + 1;
+                    if ( next < dataSize && allData[ next ] == '\n' )
+                        ++ next;
+                } else {
+                    end = allData.find( '\n', begin );
+                    if ( end >= 0 )
+                        next = end + 1;
+                }
+            } while ( itemIndex-- && end >= 0 && next < dataSize );
+            int urlLen = end - begin;
+            if ( urlLen <= 0 )
+                return FALSE;
+            QCString urlStr( urlLen + 1 );
+            memcpy( urlStr.data(), allData.data() + begin, urlLen );
+            // decode from UTF-8
+            urlStr = QString::fromUtf8( urlStr ).local8Bit(); 
+            // encode special/national chars to escaped %XX within urlStr
+            // (see QUrl::encode(), but special chars a bit differ here: for
+            // example, Mozilla doesn't like when ':' is encoded after 'http').
+            const QCString special( "<>#@\"&%$,;?={}|^~[]\'`\\ \n\t\r" );
+            QCString itemStr( urlLen * 3 + 1 );
+            int itemLen = 0;
+            for ( int i = 0; i < urlLen; ++i ) {
+                uchar ch = urlStr[ i ];
+                if ( ch >= 128 || special.contains( ch ) ) {
+                    itemStr[ itemLen++ ] = '%';
+                    uchar c = ch / 16;
+                    c += c > 9 ? 'A' - 10 : '0';
+                    itemStr[ itemLen++ ] = c;
+                    c = ch % 16;
+                    c += c > 9 ? 'A' - 10 : '0';
+                    itemStr[ itemLen++ ] = c;
+                } else {
+                    itemStr[ itemLen++ ] = ch;
+                }
+            }
+            itemData = itemStr;
+            // resize on QByteArray to ensure the trailing \0 is stripped
+            itemData.resize( itemLen );
+        } else {
+            itemData = allData;
+        }
+        return TRUE;
+    }
+    return FALSE;
+}
+
+void QPMMimeText::TextDragProvider::fileType( const char *drf, const char *&type,
+                                              const char *&ext )
+{
+    if ( qstrcmp( drf, DRF_TEXT ) == 0 ) {
+        if ( exclusive ) {
+            type = DRT_URL;
+            // no extension for URLs
+        } else {
+            type = DRT_TEXT;
+            ext = "txt";
+        }
+    }
+};
+
+const char *QPMMimeText::TextDropProvider::drf( const char *format ) const
+{
+    // sanity check
+    if ( qstricmp( format, MIME_TEXT_PLAIN_CHARSET_SYSTEM ) == 0 ||
+         qstricmp( format, MIME_TEXT_URI_LIST ) == 0 )
+        return DRF_TEXT;
+    return NULL;
+}
+
+bool QPMMimeText::TextDropProvider::provide( const char *format,
+                                             ULONG /* itemIndex */,
+                                             const QByteArray &itemData,
+                                             QByteArray &allData )
+{
+    if ( qstricmp( format, MIME_TEXT_PLAIN_CHARSET_SYSTEM ) == 0 ) {
+        allData = itemData;
+        return TRUE;
+    }
+
+    if ( qstricmp( format, MIME_TEXT_URI_LIST ) == 0 ) {
+        // decode escaped %XX within itemData (see QUrl::decode())
+        size_t dataSize = itemData.size();
+        size_t strLen = 0;
+        QCString itemStr( dataSize + 1 );
+        for ( size_t i = 0; i < dataSize; ) {
+            uchar ch = itemData[ i++ ];
+            if ( ch == '%' && (i + 2 <= dataSize) ) {
+                int hi = hex_to_int( itemData[ i ] );
+                if ( hi >= 0 ) {
+                    int lo = hex_to_int( itemData[ i + 1 ] );
+                    if ( lo >= 0 ) {
+                        ch = (hi << 4) + lo;
+                        i += 2;
+                    }
+                }
+            }
+            itemStr[ strLen++ ] = ch; 
+        }
+        itemStr.truncate( strLen );
+        // check that itemData is a valid URL
+        QUrl url( QString::fromLocal8Bit( itemStr ) );
+        if ( !url.isValid() )
+            return FALSE;
+        // oops, QUrl incorrectly handles the 'file:' protocol, do it for it
+        QString urlStr;
+        if ( url.isLocalFile() )
+            urlStr = url.protocol() + ":///" + url.path();
+        else
+            urlStr = url.toString();
+        // append the URL to the list
+        QCString str ( allData );
+        str += QUriDrag::unicodeUriToUri( urlStr );
+        str += "\r\n";
+        allData = str;
+        return TRUE;
+    }
+    
+    return FALSE;
+}
+
+QPMMime::DragWorker *QPMMimeText::dragWorkerFor( const char *mime, QMimeSource *src )
+{
+    if ( cfFor( mime ) == CF_TEXT ) {
+        // add a cooperative provider
+        textDragProvider.exclusive = FALSE;
+        DefaultDragWorker *defWorker = defaultCoopDragWorker();
+        defWorker->addProvider( DRF_TEXT, &textDragProvider );
+        return defWorker;
+    }
+    
+    if ( qstricmp( mime, MIME_TEXT_URI_LIST ) == 0 ) {
+        // see what kind of items text/uri-list represents
+        QStrList uriList;
+        QStringList fileList;
+        QUriDrag::decode( src, uriList );
+        QUriDrag::decodeLocalFiles( src, fileList );
+        if ( fileList.count() && fileList.count() == uriList.count() ) {
+            // all items are local files, return an exclusive file drag worker
+            return &nativeFileDrag;
+        }
+        if ( uriList.count() && !fileList.count() ) {
+            // all items are non-files, add an exclusive provider for the
+            // specified item count
+            textDragProvider.exclusive = TRUE;
+            DefaultDragWorker *defWorker = defaultExclDragWorker();
+            bool ok = defWorker->addProvider( DRF_TEXT, &textDragProvider,
+                                              uriList.count() );
+            return ok ? defWorker : NULL;
+        }
+        // if items are mixed, we return NULL to fallback to QPMMimeAnyMime 
+    }
+    
+    return NULL;
+}
+
+QPMMime::DropWorker *QPMMimeText::dropWorkerFor( DRAGINFO *info )
+{
+    ULONG itemCount = DrgQueryDragitemCount( info );
+    Q_ASSERT( itemCount );
+    if ( !itemCount )
+        return NULL;
+    
+    if ( itemCount == 1 ) {
+        DRAGITEM *item = DrgQueryDragitemPtr( info, 0 );
+        Q_ASSERT( item );
+        if ( !item )
+            return NULL;
+        // proceed only if the target cannot render DRM_OS2FILE on its own
+        // and if the item type is not "UniformResourceLocator" (which will be
+        // processed below) 
+        if ( !canTargetRenderAsOS2File( item ) &&
+             !DrgVerifyType( item, DRT_URL ) ) {
+            DefaultDropWorker *defWorker = defaultDropWorker();
+            // check that we support one of DRMs and the format is DRF_TEXT
+            if ( defWorker->canRender( item, DRF_TEXT ) ) {
+                // add a cooperative provider (can coexist with others)
+                defWorker->addProvider( MIME_TEXT_PLAIN_CHARSET_SYSTEM,
+                                        &textDropProvider );
+                return defWorker;
+            }
+            return NULL;
+        }
+    }
+
+    // Either the target can render DRM_OS2FILE on its own (so it's a valid
+    // file/directory name), or it's an "UniformResourceLocator", or there is
+    // more than one drag item. Check that all items are of either one type
+    // or another. If so, we can represent them as 'text/uri-list'.
+    bool allAreFiles = TRUE;
+    bool allAreURLs = TRUE; 
+    DefaultDropWorker *defWorker = defaultDropWorker();
+    for ( ULONG i = 0; i < itemCount; ++i ) {
+        DRAGITEM *item = DrgQueryDragitemPtr( info, i );
+        Q_ASSERT( item );
+        if ( !item )
+            return NULL;
+        if (allAreFiles)
+            allAreFiles &= canTargetRenderAsOS2File( item );
+        if (allAreURLs)
+            allAreURLs &= DrgVerifyType( item, DRT_URL ) &&
+                          defWorker->canRender( item, DRF_TEXT );
+        if (!allAreFiles && !allAreURLs)
+            return NULL;
+    }
+
+    if (allAreFiles) {
+        // return an exclusive drop worker
+        return &nativeFileDrop;
+    }
+    
+    // add an exclusive provider (can neither coexist with other workers
+    // or providers)
+    bool ok = defWorker->addExclusiveProvider( MIME_TEXT_URI_LIST,
+                                               &textDropProvider );
+    return ok ? defWorker : NULL;
+}
+
+#endif // !QT_NO_DRAGANDDROP
+
 
 class QPMMimeImage : public QPMMime {
@@ -519,6 +2482,4 @@
 
 
-static QPMMimeAnyMime *anymime = 0;
-
 static
 void cleanup_mimes()
@@ -528,7 +2489,4 @@
 	delete wm;
     }
-    mimetypes.setAutoDelete( TRUE );
-    mimetypes.clear();
-    anymime = 0;
 }
 
@@ -539,47 +2497,10 @@
 {
     if ( mimes.isEmpty() ) {
+        // add standard convertors so that QPMMimeAnyMime is the last in the list
+        new QPMMimeAnyMime;
 	new QPMMimeImage;
 	new QPMMimeText;
-	anymime = new QPMMimeAnyMime;
-
 	qAddPostRoutine( cleanup_mimes );
     }
-}
-
-/*!
-    \internal
-  This is an internal function.
-*/
-ulong QPMMime::registerMimeType( const char *mime )
-{
-    // first, look if other non-QPMMimeAnyMime convertors support this mime,
-    // to avoid double handling of this mime type by another (probably,
-    // system-integrated and most likely more specialized) convertor and the
-    // dummy "pass-through" QPMMimeAnyMime convertor (which will handle the
-    // given mime type after once we call mimetyes.append (...)).
-    QPtrListIterator<QPMMime> it( mimes );
-    for ( QPMMime* c; (c = *it); ++ it )
-        if ( c != anymime && c->cfFor( mime ) )
-            return 0;
-
-    ulong f = WinAddAtom( WinQuerySystemAtomTable(), mime );
-    if ( !f ) {
-#ifndef QT_NO_DEBUG
-	qSystemWarning( "QPMMime: Failed to register clipboard format" );
-#endif
-        return 0;
-    }
-    QPMRegisteredMimeType *mt = mimetypes.current();
-    if ( !mt || mt->cf != f ) {
-	for ( mt = mimetypes.first(); mt && mt->cf != f; mt = mimetypes.next() )
-	    ;
-	if ( !mt ) {
-	    mimetypes.append( new QPMRegisteredMimeType ( f, mime ) );
-            // successful memory allocation check
-            if ( !(mt = mimetypes.current()) || mt->cf != f )
-                return 0;
-	}
-    }
-    return f;
 }
 
@@ -627,4 +2548,288 @@
 }
 
+#if !defined(QT_NO_DRAGANDDROP)
+
+/*!
+  Returns a string represented by \a hstr.
+*/
+QCString QPMMime::queryHSTR( HSTR hstr )
+{
+    QCString str;
+    ULONG len = DrgQueryStrNameLen( hstr );
+    if ( len ) {
+        str.resize( len + 1 );
+        DrgQueryStrName( hstr, str.size(), str.data() );
+    }
+    return str;
+}
+
+/*!
+  Returns a string that is a concatenation of \c hstrContainerName and
+  \c hstrSourceName fileds of the given \a item structure.
+*/
+QCString QPMMime::querySourceNameFull( DRAGITEM *item )
+{
+    QCString fullName;
+    if ( !item )
+        return fullName;
+
+    ULONG pathLen = DrgQueryStrNameLen( item->hstrContainerName );
+    ULONG nameLen = DrgQueryStrNameLen( item->hstrSourceName );
+    if ( !pathLen || !nameLen )
+        return fullName;
+    
+    fullName.resize( pathLen + nameLen + 1 );
+    DrgQueryStrName( item->hstrContainerName, pathLen + 1, fullName.data() );
+    DrgQueryStrName( item->hstrSourceName, nameLen + 1, fullName.data() + pathLen );
+    
+    return fullName;
+}
+
+/*! \internal
+  Checks that the given drag \a item supports the DRM_OS2FILE rendering
+  mechanism and can be rendered by a target w/o involving the source (i.e.,
+  DRM_OS2FILE is the first supported format and a valid file name with full
+  path is provided). If the function returns TRUE, \a fullName (if not NULL)
+  will be assigned the item's full source file name (composed from
+  \c hstrContainerName and \c hstrSourceName fields).
+ */
+bool QPMMime::canTargetRenderAsOS2File( DRAGITEM *item, QCString *fullName /* = NULL */ )
+{
+    if ( !item )
+        return FALSE;
+    
+    if ( item->fsControl & (DC_PREPARE | DC_PREPAREITEM) )
+        return FALSE;
+
+    {
+        // DrgVerifyNativeRMF doesn't work on my system (ECS 1.2.1 GA):
+        // it always returns FALSE regardless of arguments. Use simplified
+        // hstrRMF parsing to determine whether DRM_OS2FILE is the native
+        // mechanism or not (i.e. "^\s*[\(<]\s*DRM_OS2FILE\s*,.*").
+            
+        QCString rmf = queryHSTR( item->hstrRMF );
+        bool ok = FALSE;
+        int i = rmf.find( DRM_OS2FILE );
+        if ( i >= 1 ) {
+            for( int j = i - 1; j >= 0; --j ) {
+                char ch = rmf.data()[j];
+                if ( ch == ' ' ) continue;
+                if ( ch == '<' || ch == '(' ) {
+                    if ( ok ) return FALSE;
+                    ok = TRUE;
+                } else {
+                    return FALSE;
+                }
+            }
+        }
+        if ( ok ) {
+            ok = FALSE;
+            int drmLen = strlen( DRM_OS2FILE );
+            for( int j = i + drmLen; j < (int) rmf.length(); ++j ) {
+                char ch = rmf.data()[j];
+                if ( ch == ' ' ) continue;
+                if ( ch == ',' ) {
+                    ok = TRUE;
+                    break;
+                }
+                return FALSE;
+            }
+        }
+        if ( !ok )
+            return FALSE;
+    }
+
+    QCString srcFullName = querySourceNameFull( item );
+    if ( srcFullName.isEmpty() )
+        return FALSE;
+    
+    QCString srcFullName2( srcFullName.length() + 1 );
+    APIRET rc = DosQueryPathInfo( srcFullName, FIL_QUERYFULLNAME,
+                                  srcFullName2.data(), srcFullName2.size() );
+    if ( rc != 0 )
+        return FALSE;
+    
+    QString s1 = QFile::decodeName( srcFullName.data() );
+    QString s2 = QFile::decodeName( srcFullName2.data() );
+    
+    if ( s1.lower() != s2.lower() )
+        return FALSE;
+
+    if ( fullName )
+        *fullName = srcFullName;
+    return TRUE;
+}
+
+// static
+/*! \internal
+  Parses the given \a rmfs list (full rendering mechanism/format specification)
+  and builds a \a list of mechanism branches. Each mechanism branch is also a
+  list, where the first item is the mechahism name and all subsequent items are
+  formats supported by this mechanism. Returns FALSE if fails to parse \a rmf.
+  \note The method clears the given \a list variable before proceeding and sets
+  auto-deletion to TRUE.
+*/
+bool QPMMime::parseRMFs( HSTR rmfs, QPtrList<QStrList> &list )
+{
+    // The format of the RMF list is "elem {,elem,elem...}"
+    // where elem is "(mechanism{,mechanism...}) x (format{,format...})"
+    // or "<mechanism,format>".
+    // We use a simple FSM to parse it. In terms of FSM, the format is:
+    //
+    // STRT ( BCM m CMCH echanism CMCH , NCM m CMCH echanism CMCH ) ECM x
+    //     SCMF ( BCF f CFCH ormat CFCH , NCF f CFCH ormat CFCH ) ECF , STRT
+    // STRT < BP m PMCH echanism PMCH , SPMF f PFCH ormat PFCH > EP , STRT
+
+    QCString str = queryHSTR( rmfs );
+    uint len = str.length();
+
+    enum {
+        // states
+        STRT = 0, BCM, CMCH, NCM, ECM, SCMF, BCF, CFCH, NCF, ECF,
+        BP, PMCH, SPMF, PFCH, EP,
+        STATES_COUNT,
+        // pseudo states
+        Err, Skip,
+        // inputs
+        obr = 0, cbr, xx, lt, gt, cm, any, ws,
+        INPUTS_COUNT,
+    };
+
+    static const char Chars[] =  { '(', ')', 'x', 'X', '<', '>', ',', ' ', 0 };
+    static const char Inputs[] = { obr, cbr, xx,  xx,  lt,  gt,  cm,  ws };
+    static const uchar Fsm [STATES_COUNT] [INPUTS_COUNT] = {
+             /* 0 obr  1 cbr  2 xx   3 lt   4 gt   5 cm   6 any  7 ws */
+/* STRT 0  */ { BCM,   Err,   Err,   BP,    Err,   Err,   Err,   Skip },
+/* BCM  1  */ { Err,   Err,   Err,   Err,   Err,   Err,   CMCH,  Skip },
+/* CMCH 2  */ { Err,   ECM,   CMCH,  Err,   Err,   NCM,   CMCH,  CMCH },
+/* NCM  3  */ { Err,   Err,   Err,   Err,   Err,   Err,   CMCH,  Skip },
+/* ECM  4  */ { Err,   Err,   SCMF,  Err,   Err,   Err,   Err,   Skip },
+/* SCMF 5  */ { BCF,   Err,   Err,   Err,   Err,   Err,   Err,   Skip },  
+/* BCF  6  */ { Err,   Err,   Err,   Err,   Err,   Err,   CFCH,  Skip },
+/* CFCH 7  */ { Err,   ECF,   CFCH,  Err,   Err,   NCF,   CFCH,  CFCH },
+/* NCF  8  */ { Err,   Err,   Err,   Err,   Err,   Err,   CFCH,  Skip },
+/* ECF  9  */ { Err,   Err,   Err,   Err,   Err,   STRT,  Err,   Skip },
+/* BP   10 */ { Err,   Err,   Err,   Err,   Err,   Err,   PMCH,  Skip },
+/* PMCH 11 */ { Err,   Err,   PMCH,  Err,   Err,   SPMF,  PMCH,  PMCH  },
+/* SPMF 12 */ { Err,   Err,   Err,   Err,   Err,   Err,   PFCH,  Skip },
+/* PFCH 13 */ { Err,   Err,   PFCH,  Err,   EP,    Err,   PFCH,  PFCH },
+/* EP   14 */ { Err,   Err,   Err,   Err,   Err,   STRT,  Err,   Skip }
+    };
+    
+    list.clear();
+    list.setAutoDelete( TRUE );
+    
+    QPtrList<QStrList> mech;
+    QCString buf;
+    QStrList *m = NULL;
+    
+    uint state = STRT;
+    uint start = 0, end = 0, space = 0;
+    
+    for ( uint i = 0; i < len && state != Err ; ++i ) {
+        char ch = str[i];
+        char *p = strchr( Chars, ch );
+        uint input = p ? Inputs[ p - Chars ] : any;
+        uint new_state = Fsm[ state ][ input ];
+        switch ( new_state ) {
+            case Skip:
+                continue;
+            case CMCH:
+            case CFCH:
+            case PMCH:
+            case PFCH:
+                if ( state != new_state )
+                    start = end = i;
+                ++end;
+                // accumulate trailing space for truncation
+                if ( input == ws ) ++space;
+                else space = 0;
+                break;
+            case NCM:
+            case ECM:
+            case SPMF:
+                buf = QCString( str.data() + start, end - start - space + 1 );
+                // find the mechanism branch in the output list
+                for ( m = list.first(); m; m = list.next() ) {
+                    if ( qstrcmp( m->getFirst(), buf ) == 0 )
+                        break;
+                }
+                if ( !m ) {
+                    // append to the output list if not found
+                    m = new QStrList();
+                    m->append( buf );
+                    list.append( m );
+                }
+                // store in the current list for making a cross product
+                mech.append( m );
+                start = end = 0;
+                break;
+            case NCF:
+            case ECF:
+            case EP:
+                buf = QCString( str.data() + start, end - start - space + 1 );
+                // make a cross product with all current mechanisms
+                for ( m = mech.first(); m; m = mech.next() )
+                    m->append( buf );
+                if ( new_state != NCF )
+                    mech.clear();
+                start = end = 0;
+                break;
+            default:
+                break;
+        }
+        state = new_state;
+    }
+    
+    return state == ECF || state == EP;
+}
+
+// static
+/*! \internal
+  Splits the given \a rmf (rendering mechanism/format pair) to a \a mechanism
+  and a \a format string. Returns FALSE if fails to parse \a rmf.  
+ */
+bool QPMMime::parseRMF( HSTR rmf, QCString &mechanism, QCString &format )
+{
+    QPtrList<QStrList> list;
+    if ( !parseRMFs( rmf, list ) )
+        return FALSE;
+    
+    if ( list.count() != 1 || list.getFirst()->count() != 2 )
+        return FALSE;
+    
+    QStrList *m = list.getFirst();
+    mechanism = m->first();
+    format = m->next();
+    
+    return TRUE;
+}
+
+// static
+/*! \internal */
+QPMMime::DefaultDragWorker *QPMMime::defaultCoopDragWorker()
+{
+    static DefaultDragWorker defCoopDragWorker( FALSE /* exclusive */ );
+    return &defCoopDragWorker;
+}
+
+// static
+/*! \internal */
+QPMMime::DefaultDragWorker *QPMMime::defaultExclDragWorker()
+{
+    static DefaultDragWorker defExclDragWorker( TRUE /* exclusive */ );
+    return &defExclDragWorker;
+}
+
+// static
+/*! \internal */
+QPMMime::DefaultDropWorker *QPMMime::defaultDropWorker()
+{
+    static DefaultDropWorker defaultDropWorker;
+    return &defaultDropWorker;
+}
+
+#endif // !QT_NO_DRAGANDDROP
+
 /*!
   \fn const char* QPMMime::convertorName()
@@ -723,3 +2928,86 @@
 */
 
+/*!
+  \class QPMMime::DragWorker
+  
+  \internal
+  This class is responsible for the sources's part of the Direct
+  Manipulation conversation after the drop event occurs on a target.
+  
+  Drag workers can be super exclusive (solely responsible for converting the
+  given mime type to a set of DRAGITEM structures), exclusive (cannot coexist
+  with other workers but don't manage the DRAGINFO/DRAGITEM creation), or 
+  or cooperative (can coexist with other drag and share the same set of DRAGITEM
+  structures in order to represent different mime data types). As opposed to
+  super exclusive workers (identified by isExclusive() returning TRUE and by
+  itemCount() returning zero), exclusive and cooperative workers do not
+  create DRAGINFO/DRAGITEM structures on their own, they implement a subset
+  of methods that is used by the drag manager to fill drag structures it creates.
+
+  If a super exlusive or an exclusive worker is encoundered when starting the
+  object drag, it will be used only if there are no any other workers found for
+  \b other mime types of the object being dragged. If a cooperative worker
+  with item count greater than one is encountered, it will be used only if all
+  other found workers are also cooperative and require the same number of items.
+  In both cases, if the above conditions are broken, the respective workers
+  are discarded (ignored). Instead, a special fall-back cooperative worker
+  (that requires a single DRAGITEM, supports any mime type and can coexist with
+  other one-item cooperative workers) will be used for the given mime type.
+
+  \note Subclasses must NOT free the DRAGINFO structure they allocated and
+  returned by createDragInfo().
+  \note Every exclusive drag worker must implement createDragInfo() and must not
+  implement composeFormatSting()/canRender()/prepare()/defaultFileType(). And
+  vice versa, every cooperative drag worker must implement the latter three
+  functions but not the former two.
+  \note The return value of cleanup() is whether the Move operation is
+  disallowed by this worker or not (if the worker doesn't participate in the
+  DND session, it should return FALSE, to let other workers allow Move).
+  \note If you need more details, please contact the developers of Qt for OS/2.
+*/
+
+/*!
+  \class QPMMime::DefaultDragWorker
+  
+  \internal
+  This class is a Drag Worker that supports standard DRM_SHAREDMEM and
+  DRM_OS2FILE and rendering mechanisms. It uses
+  QPMMime::DefaultDragWorker::Provider subclasses to map mime types of the
+  object being dragged to rendering formats and apply preprocessing of data
+  before rendering.
+  
+  \note If you need more details, please contact the developers of Qt for OS/2.
+*/
+
+/*!
+  \class QPMMime::DropWorker
+  
+  \internal
+  This class is responsible for the target's part of the Direct
+  Manipulation conversation after the drop event occurs on a target.
+  
+  Drop workers can be exclusive (solely responsible for converting the given
+  set of DRAGITEM structures) or cooperative (can coexist with other drop
+  workers in order to produce different mime data types from the same set of
+  DRAGITEM structures). If an exclusive drop worker is encountered when
+  processing the drop event, all other workers are silently ignored.
+  
+  \note Subclasses must NOT free the DRAGINFO structure pointed to by info().
+  \note Subclasses must NOT send DM_ENDCONVERSATION to the source.
+  \note If you need more details, please contact the developers of Qt for OS/2.
+*/
+
+/*!
+  \class QPMMime::DefaultDropWorker
+  
+  \internal
+  This class is a Drop Worker that supports standard DRM_SHAREDMEM and
+  DRM_OS2FILE and rendering mechanisms. It uses
+  QPMMime::DefaultDropWorker::Provider subclasses to map various rendering
+  formats to particular mime types and apply postprocessing of data after
+  rendering.
+  
+  \note If you need more details, please contact the developers of Qt for OS/2.
+*/
+
 #endif // QT_NO_MIME
Index: trunk/src/kernel/qt_kernel.pri
===================================================================
--- trunk/src/kernel/qt_kernel.pri	(revision 96)
+++ trunk/src/kernel/qt_kernel.pri	(revision 97)
@@ -128,4 +128,5 @@
 		  $$KERNEL_CPP/qcursor_pm.cpp \
 		  $$KERNEL_CPP/qdesktopwidget_pm.cpp \
+		  $$KERNEL_CPP/qdnd_pm.cpp \
 		  $$KERNEL_CPP/qeventloop_pm.cpp \
 		  $$KERNEL_CPP/qfont_pm.cpp \
Index: trunk/src/kernel/qwidget_pm.cpp
===================================================================
--- trunk/src/kernel/qwidget_pm.cpp	(revision 96)
+++ trunk/src/kernel/qwidget_pm.cpp	(revision 97)
@@ -1051,7 +1051,4 @@
     }
 
-    bool accept_drops = acceptDrops();
-    if ( accept_drops )
-	setAcceptDrops( FALSE ); // ole dnd unregister (we will register again below)
     if ( testWFlags(WType_Desktop) )
 	old_winfid = 0;
@@ -1144,16 +1141,4 @@
 
     reparentFocusWidgets( oldtlw );		// fix focus chains
-
-    if ( accept_drops )
-	setAcceptDrops( TRUE );
-
-//@@TODO (dmik): remove?
-//#ifdef Q_OS_TEMP
-//    // Show borderless toplevel windows in tasklist & NavBar
-//    if ( !parent ) {
-//	QString txt = caption().isEmpty()?qAppName():caption();
-//	SetWindowText( winId(), (TCHAR*)txt.ucs2() );
-//    }
-//#endif
 }
 
@@ -2504,11 +2489,8 @@
 void QWidget::createSysExtra()
 {
-//@@TODO (dmik): later
-//    extra->dropTarget = 0;
 }
 
 void QWidget::deleteSysExtra()
 {
-    setAcceptDrops( FALSE );
 }
 
@@ -2532,28 +2514,15 @@
 bool QWidget::acceptDrops() const
 {
-    return 0;
-/// @todo (dmik) later
-//    return ( extra && extra->dropTarget );
+    return testWState( WState_DND );    
 }
 
 void QWidget::setAcceptDrops( bool on )
 {
-/// @todo (dmik) later
-//    // Enablement is defined by extra->dropTarget != 0.
-//
-//    if ( on ) {
-//	// Turn on.
-//	createExtra();
-//	QWExtra *extra = extraData();
-//	if ( !extra->dropTarget )
-//	    extra->dropTarget = qt_olednd_register( this );
-//    } else {
-//	// Turn off.
-//	QWExtra *extra = extraData();
-//	if ( extra && extra->dropTarget ) {
-//	    qt_olednd_unregister(this, extra->dropTarget);
-//	    extra->dropTarget = 0;
-//	}
-//    }
+    if ( testWState(WState_DND) != on ) {
+        if ( on )
+            setWState( WState_DND );
+        else
+            clearWState( WState_DND );
+    }
 }
 
Index: trunk/src/qt.pro
===================================================================
--- trunk/src/qt.pro	(revision 96)
+++ trunk/src/qt.pro	(revision 97)
@@ -203,5 +203,5 @@
 os2 {
 	# this section should be removed one day
-    # when an OS/2 version of every module is ready
+	# when an OS/2 version of every module is ready
 	include($$KERNEL_CPP/qt_kernel.pri)
 	include($$WIDGETS_CPP/qt_widgets.pri)
@@ -240,5 +240,4 @@
 
 #install directives
-#@@todo(dmik): need this on OS/2?
 !os2:include(qt_install.pri)
 !staticlib:PRL_EXPORT_DEFINES += QT_SHARED
