Ignore:
Timestamp:
Jan 13, 2010, 9:14:29 PM (16 years ago)
Author:
Dmitry A. Kuminov
Message:

gui: DnD: Drag party code in progress.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/gui/kernel/qdnd_pm.cpp

    r447 r448  
    5252#include "qcursor.h"
    5353#include "qdesktopwidget.h"
     54#include "qfile.h"
    5455#include "qdnd_p.h"
    5556#include "qdebug.h"
     
    6869
    6970#if !defined(QT_NO_DRAGANDDROP) && !defined(QT_NO_CLIPBOARD)
     71
     72extern void qt_pmMouseButtonUp(); // defined in qapplication_pm.cpp
     73extern void qt_DrgFreeDragtransfer(DRAGTRANSFER *xfer); // defined in qmime_pm.cpp
    7074
    7175/** \internal
     
    271275        return DO_MOVE;
    272276    return DO_UNKNOWN;
     277}
     278
     279static USHORT toPmDragDropOps(Qt::DropActions actions)
     280{
     281    USHORT op = DO_UNKNOWN;
     282    if (actions & Qt::LinkAction)
     283        op |= DO_LINKABLE;
     284    if (actions & Qt::CopyAction)
     285        op |= DO_COPYABLE;
     286    if (actions & Qt::MoveAction)
     287        op |= DO_MOVEABLE;
     288    return op;
    273289}
    274290
     
    617633}
    618634
     635//---------------------------------------------------------------------
     636//                    QPMCoopDragWorker
     637//---------------------------------------------------------------------
     638
     639class QPMCoopDragWorker : public QPMMime::DragWorker, public QPMObjectWindow
     640{
     641public:
     642    QPMCoopDragWorker() : info(0) {}
     643    bool collectWorkers(QDrag *o);
     644
     645    // DragWorker interface
     646    void init();
     647    bool cleanup(bool isCancelled);
     648    bool isExclusive() const { return true; }
     649    ULONG itemCount() const { return 0; }
     650    HWND hwnd() const;
     651    DRAGINFO *createDragInfo(const QString &targetName, USHORT supportedOps);
     652
     653    // QPMObjectWindow interface
     654    MRESULT message(ULONG msg, MPARAM mp1, MPARAM mp2);
     655   
     656private:
     657    QList<DragWorker*> workers;
     658    // todo check workers!
     659    //QPtrList<DragWorker> workers;
     660    DRAGINFO *info;
     661};
     662
     663bool QPMCoopDragWorker::collectWorkers(QDrag *o)
     664{
     665    Q_ASSERT(o);
     666    if (!o)
     667        return false;
     668
     669    bool gotExcl = false; // got isExclusive() worker?
     670    bool skipExcl = false; // skip isExclusive() or itemCount() > 1 workers?
     671    ULONG coopLevel = 0; // itemCount() level for !isExclusive() workers
     672
     673    bool gotExclForMime = false;
     674
     675    // go through all formats and all converters to collect DragWorkers
     676    QMimeData *mimeData = o->mimeData();
     677    foreach (const QString &fmt, mimeData->formats()) {
     678        DEBUG(() << "QPMCoopDragWorker: Searching for worker for mime" << fmt);
     679        foreach (QPMMime *mime, QPMMime::all()) {
     680            DragWorker *wrk = mime->dragWorkerFor(fmt, mimeData);
     681            if (!wrk)
     682                continue;
     683            DEBUG(() << "QPMCoopDragWorker: Got worker" << wrk
     684                     << "(isExclusive" << wrk->isExclusive() << ", "
     685                     << "itemCount" << wrk->itemCount() << ") from convertor"
     686                     << mime->convertorName() << " (gotExclForMime"
     687                     << gotExclForMime << ", " << "gotExcl" << gotExcl
     688                     << ", skipExcl" << skipExcl << ", coopLevel"
     689                     << coopLevel << ")");
     690            if (wrk->isExclusive()) {
     691                if (!skipExcl && !gotExclForMime) {
     692                    gotExclForMime = true;
     693                    if (!gotExcl) {
     694                        gotExcl = true;
     695                        workers.append(wrk);
     696                    } else {
     697                        // skip everything exclusive unless it's exactly the
     698                        // same worker
     699                        skipExcl = !workers.contains(wrk);
     700                    }
     701                }
     702                // continue to search for a fall-back cooperative 1-item worker
     703                // (like QPMMimeAnyMime) for the case if this worker quits
     704                // the game
     705                continue;
     706            }
     707            ULONG itemCnt = wrk->itemCount();
     708            if (itemCnt == 0) {
     709                DEBUG(() << "QPMCoopDragWorker: Cooperative DragWorker"
     710                         << wrk << "for mime " << fmt << " has "
     711                         << "itemCount = 0!");
     712                continue;
     713            }
     714            if (itemCnt > 1) {
     715                // coop workers with item count > 1 are also considered exclusive
     716                // here, because may not co-exist with 1-item workers that should
     717                // always be able to contribute
     718                if (!gotExcl && !skipExcl && !gotExclForMime) {
     719                    gotExclForMime = true;
     720                    workers.append(wrk);
     721                    if (!coopLevel)
     722                        coopLevel = itemCnt;
     723                    // only those for the same number of items can proceed
     724                    if (itemCnt != coopLevel)
     725                        skipExcl = true;
     726                }
     727                // continue to search for a fall-back cooperative 1-item worker
     728                // (like QPMMimeAnyMime) for the case if this worker quits
     729                // the game
     730                continue;
     731            }
     732            workers.append(wrk);
     733            // Don't search for other workrers for the same mime type --
     734            // we've already got a drag worker for it and adding another
     735            // one would just introduce mime type duplicates on the drop
     736            // target's side (where the first encountered drop worker
     737            // for the given RMF would be used for actual data transfer
     738            // anyway). See also QClipboard::setData().
     739            break;
     740        }
     741        if (gotExclForMime) {
     742            // ensure we have a fall-back coop (should be the last added item)
     743            DragWorker *w = workers.last();
     744            if (w->isExclusive() || w->itemCount() > 1) {
     745                DEBUG(() << "QPMCoopDragWorker: DragWorker" << w
     746                         << "for" << fmt << "(isExclusive" << w->isExclusive()
     747                         << ", itemCount" << w->itemCount()
     748                         <<") has no fall-back cooperative 1-item worker!");
     749                workers.removeLast();
     750            }
     751            gotExclForMime = false;
     752        } else {
     753            // got a regular (non-fall-back) 1-item coop, skip evreything else
     754            skipExcl = true;
     755        }
     756    }
     757
     758    // remove either all exclusive workers or all their fall-back workers
     759    // (depending on skipExcl) and remove duplicates
     760    for (QList<DragWorker*>::iterator it = workers.begin();
     761         it <= workers.end();) {
     762        DragWorker *wrk = *it;
     763        bool excl = wrk->isExclusive() || wrk->itemCount() > 1;
     764        if (skipExcl == excl || workers.count(wrk) > 1) {
     765            it = workers.erase(it);
     766        } else {
     767            ++it;
     768        }
     769    }
     770
     771#if defined(QDND_DEBUG)           
     772    foreach (DragWorker *wrk, workers) {
     773        DEBUG(() << "QPMCoopDragWorker: Will use worker" << wrk
     774                 << "(isExclusive" << wrk->isExclusive()
     775                 << ", itemCount" << wrk->itemCount() << ")");
     776    }
     777#endif
     778
     779    Q_ASSERT(workers.count() > 0);
     780    return workers.count() > 0;
     781}
     782
     783HWND QPMCoopDragWorker::hwnd() const
     784{
     785    DragWorker *firstWorker = workers.first();
     786    Q_ASSERT(firstWorker);
     787    if (!firstWorker)
     788        return 0;
     789   
     790    if (firstWorker->isExclusive() && firstWorker->itemCount() == 0) {
     791        // this is a super exclusive worker that will do everything on its own
     792        return firstWorker->hwnd();
     793    }
     794   
     795    return QPMObjectWindow::hwnd();
     796}
     797
     798void QPMCoopDragWorker::init()
     799{
     800    Q_ASSERT(source());
     801    foreach(DragWorker *wrk, workers) {
     802        wrk->src = source();
     803        wrk->init();
     804    }
     805}
     806
     807bool QPMCoopDragWorker::cleanup(bool isCancelled)
     808{
     809    bool moveDisallowed = false;
     810   
     811    foreach(DragWorker *wrk, workers) {
     812        // disallow the Move operation if at least one worker asked so
     813        moveDisallowed |= wrk->cleanup(isCancelled);
     814        wrk->src = 0;
     815    }
     816    workers.clear();
     817    info = 0;
     818    return moveDisallowed;
     819}
     820
     821DRAGINFO *QPMCoopDragWorker::createDragInfo(const QString &targetName,
     822                                            USHORT supportedOps)
     823{
     824    Q_ASSERT(!info);
     825    if (info)
     826        return 0;
     827
     828    DragWorker *firstWorker = workers.first();
     829    Q_ASSERT(firstWorker);
     830    if (!firstWorker)
     831        return 0;
     832
     833    ULONG itemCnt = firstWorker->itemCount();
     834
     835    if (firstWorker->isExclusive() && itemCnt == 0) {
     836        // this is a super exclusive worker that will do everything on its own
     837        DEBUG(() << "QPMCoopDragWorker: Will redirect to super worker"
     838                 << firstWorker);
     839        return firstWorker->createDragInfo(targetName, supportedOps);
     840    }
     841   
     842    // note that all workers at this place require the same amount of items
     843    // (guaranteed by collectWorkers())
     844   
     845    DEBUG(() << "QPMCoopDragWorker: itemCnt" << itemCnt);
     846
     847    info = DrgAllocDraginfo(itemCnt);
     848    Q_ASSERT(info);
     849    if (!info)
     850        return 0;
     851   
     852    // collect all mechanism/format pairs
     853    QByteArray allFormats;
     854    foreach (DragWorker *wrk, workers) {
     855        QByteArray formats = wrk->composeFormatString();
     856        Q_ASSERT(!formats.isNull());
     857        if (!formats.isNull()) {
     858            if (allFormats.isNull())
     859                allFormats = formats;
     860            else {
     861                allFormats += ",";
     862                allFormats += formats;
     863            }
     864        }
     865    }
     866
     867    DEBUG(() << "QPMCoopDragWorker: allFormats" << allFormats);
     868
     869    static ULONG itemID = 0;
     870   
     871    const char *type = 0;
     872    const char *ext = 0;
     873    firstWorker->defaultFileType(type, ext);
     874
     875    bool ok = true;
     876    for (ULONG i = 0; i < itemCnt; ++i) {
     877        DRAGITEM *item = DrgQueryDragitemPtr(info, i);
     878        Q_ASSERT(item);
     879        if (!item) {
     880            ok = false;
     881            break;
     882        }
     883
     884        QString name;
     885        if (itemCnt == 1)
     886            name = targetName;
     887        else
     888            name = QString(QLatin1String("%1 %2")).arg(targetName).arg(i + 1);
     889       
     890        if (ext) {
     891            name += QLatin1Char('.');
     892            name += QFile::decodeName(QByteArray(ext));
     893        }
     894
     895        DEBUG(() << "QPMCoopDragWorker: item" << i << ": type" << type
     896                 << " name" << name);
     897
     898        // Note 1: DRAGITEM::hstrType is actually ignored by WPS,
     899        // only the target extension matters.
     900       
     901        // Note 2: We're not required to fill in the hwndItem field because we
     902        // use the DC_PREPARE flag (to indicate it will be filled later, after
     903        // DM_RENDERPREPARE); however, Mozilla refuses to render if hwndItem
     904        // is initially 0. Set it to our HWND instead (we'll issue a warning if
     905        // DM_RENDER or DM_ENDCONVERSATION is erroneously sent to us)
     906
     907        item->hwndItem = hwnd();
     908        item->ulItemID = itemID ++;
     909        item->hstrType = DrgAddStrHandle(type ? type : DRT_UNKNOWN);
     910        item->hstrRMF = DrgAddStrHandle(allFormats);
     911        item->hstrContainerName = 0;
     912        item->hstrSourceName = 0;
     913        item->hstrTargetName = DrgAddStrHandle(QFile::encodeName(name));
     914        item->cxOffset = 0;
     915        item->cyOffset = 0;
     916        item->fsControl = DC_PREPARE; // require DM_RENDERPREPARE from target
     917        item->fsSupportedOps = supportedOps;
     918    }
     919   
     920    if (!ok) {
     921        DrgFreeDraginfo(info);
     922        info = 0;
     923    }
     924   
     925    return info;
     926}
     927
     928MRESULT QPMCoopDragWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
     929{
     930    if (msg == DM_RENDERPREPARE) {
     931        if (!info) {
     932            qWarning("Drop target sent DM_RENDERPREPARE after the DnD session "
     933                     "is over!");
     934            // free the given DRAGTRANSFER structure to avoud memory leak
     935            DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
     936            if (xfer)
     937                qt_DrgFreeDragtransfer(xfer);
     938            return (MRESULT)FALSE;
     939        }
     940   
     941        DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
     942        Q_ASSERT(xfer && xfer->pditem);
     943        if (!xfer || !xfer->pditem)
     944            return (MRESULT)FALSE;
     945       
     946        // find the item's index (ordinal number)
     947        ULONG itemCnt = DrgQueryDragitemCount(info);
     948        ULONG index = 0;
     949        for (; index <  itemCnt; ++index)
     950            if (DrgQueryDragitemPtr(info, index) == xfer->pditem)
     951                break;
     952       
     953        Q_ASSERT(index < itemCnt);
     954        if (index >= itemCnt)
     955            return (MRESULT)FALSE;
     956
     957        DEBUG(() << "QPMCoopDragWorker: Got DM_RENDERPREPARE to"
     958                 << QPMMime::queryHSTR(xfer->hstrSelectedRMF) << "for item"
     959                 << index << "(id" << xfer->pditem->ulItemID << ")");
     960       
     961        QByteArray drm, drf;
     962        if (!QPMMime::parseRMF(xfer->hstrSelectedRMF, drm, drf)) {
     963            Q_ASSERT(false);
     964            return (MRESULT)FALSE;
     965        }
     966
     967        DragWorker *wrk = 0;
     968        foreach(wrk, workers)
     969            if (wrk->prepare(drm, drf, xfer->pditem, index))
     970                break;
     971        if (!wrk) {
     972            DEBUG(() << "QPMCoopDragWorker: No suitable worker found");
     973            return (MRESULT)FALSE;
     974        }
     975
     976        xfer->pditem->hwndItem = wrk->hwnd();
     977        Q_ASSERT(xfer->pditem->hwndItem);
     978        return (MRESULT)(xfer->pditem->hwndItem ? TRUE : FALSE);
     979    }
     980   
     981    if (msg == DM_RENDER || msg == DM_ENDCONVERSATION) {
     982        qWarning("Drop target sent DM_RENDER or DM_ENDCONVERSATION to the "
     983                 "drag source window instead of the drag item window!");
     984        if (msg == DM_RENDER) {
     985            // free the given DRAGTRANSFER structure to avoud memory leak
     986            DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
     987            if (xfer)
     988                qt_DrgFreeDragtransfer(xfer);
     989        }
     990    }
     991   
     992    return (MRESULT)FALSE;
     993}
     994
     995//---------------------------------------------------------------------
     996//                    QDragManager
     997//---------------------------------------------------------------------
     998
    619999Qt::DropAction QDragManager::drag(QDrag *o)
    6201000
     
    6221002    DEBUG(() << "QDragManager::drag");
    6231003
    624     // @todo implement
    625     return Qt::IgnoreAction;
    626 }
    627 
    628 void QDragManager::cancel(bool /* deleteSource */)
    629 {
    630     // @todo implement
     1004    if (object == o || !o || !o->d_func()->source)
     1005        return Qt::IgnoreAction;
     1006
     1007    if (object) {
     1008        cancel();
     1009        qApp->removeEventFilter(this);
     1010        beingCancelled = false;
     1011    }
     1012
     1013    // detect a mouse button to end dragging
     1014    LONG vkTerminate = 0;
     1015    {
     1016        ULONG msg = WinQuerySysValue(HWND_DESKTOP, SV_BEGINDRAG) & 0xFFFF;
     1017        switch(msg) {
     1018            case WM_BUTTON1MOTIONSTART: vkTerminate = VK_BUTTON1; break;
     1019            case WM_BUTTON2MOTIONSTART: vkTerminate = VK_BUTTON2; break;
     1020            case WM_BUTTON3MOTIONSTART: vkTerminate = VK_BUTTON3; break;
     1021        }
     1022
     1023        if (WinGetKeyState(HWND_DESKTOP, vkTerminate) & 0x8000) {
     1024            // prefer the default button if it is pressed
     1025        } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON2) & 0x8000) {
     1026            vkTerminate = VK_BUTTON2;
     1027        } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON1) & 0x8000) {
     1028            vkTerminate = VK_BUTTON1;
     1029        } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON3) & 0x8000) {
     1030            vkTerminate = VK_BUTTON3;
     1031        } else {
     1032            vkTerminate = 0;
     1033        }
     1034    }
     1035
     1036    if (!vkTerminate) {
     1037        DEBUG(() << "QDragManager::drag: No valid mouse button pressed, "
     1038                    "dragging cancelled!");
     1039        o->deleteLater();
     1040        return Qt::IgnoreAction;
     1041    }
     1042
     1043    USHORT supportedOps = toPmDragDropOps(dragPrivate()->possible_actions);
     1044
     1045    static QPMCoopDragWorker dragWorker;
     1046   
     1047    bool ok = dragWorker.collectWorkers(o);
     1048    Q_ASSERT(ok);
     1049    Q_ASSERT(dragWorker.hwnd());
     1050    if (!ok || !dragWorker.hwnd()) {
     1051        o->deleteLater();
     1052        return Qt::IgnoreAction;
     1053    }
     1054
     1055    dragWorker.src = o->mimeData();
     1056    dragWorker.init();
     1057    DRAGINFO *info = dragWorker.createDragInfo(o->objectName(), supportedOps);
     1058
     1059    Q_ASSERT(info);
     1060    if (!info) {
     1061        dragWorker.cleanup(true /* isCancelled */);
     1062        dragWorker.src = 0;
     1063        o->deleteLater();
     1064        return Qt::IgnoreAction;
     1065    }
     1066
     1067    object = o;
     1068
     1069    DEBUG(() << "QDragManager::drag: actions"
     1070             << dragActionsToString(dragPrivate()->possible_actions));
     1071
     1072    dragPrivate()->target = 0;
     1073
     1074#ifndef QT_NO_ACCESSIBILITY
     1075    QAccessible::updateAccessibility(this, 0, QAccessible::DragDropStart);
     1076#endif
     1077
     1078    // @todo custom drag pixmap?
     1079
     1080    DRAGIMAGE img;
     1081    img.cb = sizeof(DRAGIMAGE);
     1082    img.hImage = WinQuerySysPointer(HWND_DESKTOP, SPTR_FILE, FALSE);
     1083    img.fl = DRG_ICON;
     1084    img.cxOffset = 0;
     1085    img.cyOffset = 0;
     1086
     1087    // the mouse is most likely captured by Qt at this point, uncapture it
     1088    // or DrgDrag() will definitely fail
     1089    WinSetCapture(HWND_DESKTOP, 0);
     1090
     1091    HWND target = DrgDrag(dragWorker.hwnd(), info, &img, 1, vkTerminate,
     1092                          (PVOID)0x80000000L); // don't lock the desktop PS
     1093
     1094    DEBUG(("QDragManager::drag: DrgDrag() returned %08lX (error 0x%08lX)",
     1095            target, WinGetLastError(0)));
     1096
     1097    // we won't get any mouse release event, so manually adjust qApp state
     1098    qt_pmMouseButtonUp();
     1099
     1100    bool moveDisallowed = dragWorker.cleanup(beingCancelled || target == 0);
     1101    dragWorker.src = 0;
     1102   
     1103    moveDisallowed |= beingCancelled || target == 0 ||
     1104                      info->usOperation != DO_MOVE;
     1105
     1106    DEBUG(() << "QDragManager::drag: moveDisallowed" << moveDisallowed);
     1107
     1108    Qt::DropAction ret = Qt::IgnoreAction;
     1109    if (target != 0) {
     1110        ret = toQDragDropAction(info->usOperation);
     1111        if (moveDisallowed && info->usOperation == DO_MOVE)
     1112            ret = Qt::TargetMoveAction;
     1113    }
     1114
     1115    DEBUG(() << "QDragManager::drag: result" << dragActionsToString(ret));
     1116
     1117    if (target == 0)
     1118        DrgDeleteDraginfoStrHandles(info);
     1119    DrgFreeDraginfo(info);
     1120
     1121    if (!beingCancelled) {
     1122        dragPrivate()->target = QWidget::find(target);
     1123        cancel(); // this will delete o (object)
     1124    }
     1125
     1126    beingCancelled = false;
     1127
     1128#ifndef QT_NO_ACCESSIBILITY
     1129    QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd);
     1130#endif
     1131
     1132    return ret;
     1133}
     1134
     1135void QDragManager::cancel(bool deleteSource)
     1136{
     1137    // Note: the only place where this function is called with
     1138    // deleteSource = false so far is QDrag::~QDrag()
     1139
     1140    Q_ASSERT(object && !beingCancelled);
     1141    if (!object || beingCancelled)
     1142        return;
     1143   
     1144    beingCancelled = true;
     1145
     1146    object->setMimeData(0);
     1147
     1148    if (deleteSource)
     1149        object->deleteLater();
     1150    object = 0;
     1151
     1152#ifndef QT_NO_CURSOR
     1153    // insert cancel code here ######## todo
     1154
     1155    if (restoreCursor) {
     1156        QApplication::restoreOverrideCursor();
     1157        restoreCursor = false;
     1158    }
     1159#endif
     1160#ifndef QT_NO_ACCESSIBILITY
     1161    QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd);
     1162#endif
    6311163}
    6321164
Note: See TracChangeset for help on using the changeset viewer.