Ignore:
Timestamp:
Dec 25, 2009, 12:21:47 AM (16 years ago)
Author:
Dmitry A. Kuminov
Message:

gui: More DnD code typed in.

File:
1 edited

Legend:

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

    r423 r439  
    6060#include "qdir.h"
    6161
    62 #define QMIME_DEBUG
     62#define QDND_DEBUG // in pair with qdnd_pm.cpp
     63
     64#ifdef QDND_DEBUG
     65#   define DEBUG(a) qDebug a
     66#else
     67#   define DEBUG(a) do {} while(0)
     68#endif
    6369
    6470QT_BEGIN_NAMESPACE
     71
     72#if !defined(QT_NO_DRAGANDDROP)
     73
     74// @todo remove
     75//#define DRT_URL "UniformResourceLocator"
     76//
     77//#define DRM_OS2FILE "DRM_OS2FILE"
     78//#define DRM_SHAREDMEM "DRM_SHAREDMEM"
     79//
     80//#define DRF_UNKNOWN "DRF_UNKNOWN"
     81//#define DRF_TEXT "DRF_TEXT"
     82//#define DRF_POINTERDATA "DRF_POINTERDATA"
     83//
     84//#define MIME_TEXT_PLAIN "text/plain"
     85//#define MIME_TEXT_PLAIN_CHARSET_SYSTEM "text/plain;charset=system"
     86//#define MIME_TEXT_URI_LIST "text/uri-list"
     87
     88/*! \internal
     89  According to my tests, DrgFreeDragtransfer() appears to be bogus: when the
     90  drag source attempts to free the DRAGTRANSFER structure passed to it in
     91  DM_RENDERPREPARE/DM_RENDER by another process, the shared memory object is not
     92  actually released until DrgFreeDragtransfer() is called for the second time.
     93  This method tries to fix this problem.
     94
     95  \note The problem (and the solution) was not tested on platforms other than
     96  eCS!
     97*/
     98void qt_DrgFreeDragtransfer(DRAGTRANSFER *xfer)
     99{
     100    Q_ASSERT(xfer);
     101    if (xfer) {
     102        BOOL ok = DrgFreeDragtransfer(xfer);
     103        Q_ASSERT(ok);
     104        if (ok) {
     105            ULONG size = ~0, flags = 0;
     106            APIRET rc = DosQueryMem(xfer, &size, &flags);
     107            Q_ASSERT(rc == 0);
     108            if (rc == 0 && !(flags & PAG_FREE)) {
     109                PID pid;
     110                TID tid;
     111                ok = WinQueryWindowProcess(xfer->hwndClient, &pid, &tid);
     112                Q_ASSERT(ok);
     113                if (ok) {
     114                    PPIB ppib = 0;
     115                    DosGetInfoBlocks(0, &ppib);
     116                    if (ppib->pib_ulpid != pid) {
     117                        DEBUG(() << "qt_DrgFreeDragtransfer: Will free xfer"
     118                                 << xfer << "TWICE (other process)!");
     119                        DrgFreeDragtransfer(xfer);
     120                    }
     121                }
     122            }
     123        }
     124    }
     125}
     126
     127#define SEA_TYPE ".TYPE"
     128
     129/*! \internal
     130  Sets a single .TYPE EA vaule on a given fle.
     131*/
     132static void qt_SetFileTypeEA(const char *name, const char *type)
     133{
     134    #pragma pack(1)
     135
     136    struct MY_FEA2 {
     137        ULONG   oNextEntryOffset;  /*  Offset to next entry. */
     138        BYTE    fEA;               /*  Extended attributes flag. */
     139        BYTE    cbName;            /*  Length of szName, not including NULL. */
     140        USHORT  cbValue;           /*  Value length. */
     141        CHAR    szName[0];         /*  Extended attribute name. */
     142        /* EA value follows here */
     143    };
     144
     145    struct MY_FEA2LIST {
     146        ULONG   cbList;            /*  Total bytes of structure including full list. */
     147        MY_FEA2 list[0];           /*  Variable-length FEA2 structures. */
     148    };
     149
     150    struct MY_FEA2_VAL {
     151        USHORT  usEAType;          /* EA value type (one of EAT_* constants) */
     152        USHORT  usValueLen;        /* Length of the value data following */
     153        CHAR    aValueData[0];     /* value data */
     154    };
     155
     156    struct MY_FEA2_MVMT {
     157        USHORT      usEAType;      /* Always EAT_MVMT */
     158        USHORT      usCodePage;    /* 0 - default */
     159        USHORT      cbNumEntries;  /* Number of MYFEA2_VAL structs following */
     160        MY_FEA2_VAL aValues[0];    /* MYFEA2_VAL value structures */
     161    };
     162
     163    #pragma pack()
     164
     165    uint typeLen = qstrlen(type);
     166    uint valLen = sizeof(MY_FEA2_VAL) + typeLen;
     167    uint mvmtLen = sizeof(MY_FEA2_MVMT) + valLen;
     168    uint fea2Len = sizeof(MY_FEA2) + sizeof(SEA_TYPE);
     169    uint fullLen = sizeof(MY_FEA2LIST) + fea2Len + mvmtLen;
     170
     171    uchar *eaData = new uchar[fullLen];
     172
     173    MY_FEA2LIST *fea2List = (MY_FEA2LIST *)eaData;
     174    fea2List->cbList = fullLen;
     175
     176    MY_FEA2 *fea2 = fea2List->list;
     177    fea2->oNextEntryOffset = 0;
     178    fea2->fEA = 0;
     179    fea2->cbName = sizeof(SEA_TYPE) - 1;
     180    fea2->cbValue = mvmtLen;
     181    strcpy(fea2->szName, SEA_TYPE);
     182
     183    MY_FEA2_MVMT *mvmt = (MY_FEA2_MVMT *)(fea2->szName + sizeof(SEA_TYPE));
     184    mvmt->usEAType = EAT_MVMT;
     185    mvmt->usCodePage = 0;
     186    mvmt->cbNumEntries = 1;
     187
     188    MY_FEA2_VAL *val = mvmt->aValues;
     189    val->usEAType = EAT_ASCII;
     190    val->usValueLen = typeLen;
     191    memcpy(val->aValueData, type, typeLen);
     192
     193    EAOP2 eaop2;
     194    eaop2.fpGEA2List = 0;
     195    eaop2.fpFEA2List = (FEA2LIST *)fea2List;
     196    eaop2.oError = 0;
     197
     198    APIRET rc = DosSetPathInfo(name, FIL_QUERYEASIZE,
     199                               &eaop2, sizeof(eaop2), 0);
     200#ifndef QT_NO_DEBUG
     201    if (rc)
     202        qWarning("qt_SetFileTypeEA: DosSetPathInfo failed with %ld", rc);
     203#endif
     204
     205    delete[] eaData;
     206}
     207
     208static int hex_to_int(uchar c)
     209{
     210    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
     211    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
     212    if (c >= '0' && c <= '9') return c - '0';
     213    return -1;
     214}
     215
     216static inline int hex_to_int(char c)
     217{
     218    return hex_to_int((uchar) c);
     219}
     220
     221// -----------------------------------------------------------------------------
     222
     223struct QPMMime::DefaultDragWorker::Data
     224{
     225    Data(bool excl) : exclusive(excl) {}
     226
     227    struct Request
     228    {
     229        Request(ULONG i, Provider *p, const char *m, const char *f)
     230            : index(i), provider(p), drm(m), drf(f)
     231            , xfer(0), rendered(false), sharedMem(0) {}
     232
     233        ~Request()
     234        {
     235            // free memory allocated for the target that requested DRM_SHAREDMEM
     236            if (sharedMem)
     237                DosFreeMem(sharedMem);
     238            Q_ASSERT(!xfer);
     239        }
     240
     241        ULONG index;
     242        Provider *provider;
     243        QCString drm;
     244        QCString drf;
     245        DRAGTRANSFER *xfer;
     246        bool rendered;
     247        PVOID sharedMem;
     248    };
     249
     250    inline bool isInitialized() { return providers.count() != 0; }
     251    void cleanupRequests();
     252
     253    struct DrfProvider
     254    {
     255        DrfProvider() : prov(0) {}
     256        DrfProvider(const char *d, Provider *p) : drf(d), prov(p) {}
     257        const QByteArray drf;
     258        Provider * const prov;
     259    };
     260
     261    typedef QList<DrfProvider> DrfProviderList;
     262
     263    Provider *providerFor(const char *drf)
     264    {
     265        foreach (const DrfProvider &dp, providers)
     266            if (qstrcmp(dp.drf, drf) == 0)
     267                return dp.prov;
     268        return 0;
     269    }
     270
     271    const bool exclusive : 1;
     272    DrfProviderList providers;
     273
     274    ULONG itemCnt;
     275    QHash<ULONG, Request*> requests;
     276    bool renderOk : 1;
     277};
     278
     279void QPMMime::DefaultDragWorker::Data::cleanupRequests()
     280{
     281    if (requests.count()) {
     282#ifndef QT_NO_DEBUG
     283        qWarning("In the previous DnD session, the drop target sent "
     284                 "DM_RENDERPREPARE/DM_RENDER\n"
     285                 "for some drag item but didn't send DM_ENDCONVERSATION!");
     286#endif
     287        qDeleteAll(requests);
     288        requests.clear();
     289    }
     290}
     291
     292QPMMime::DefaultDragWorker::DefaultDragWorker(bool exclusive)
     293    : d(new Data(exclusive))
     294{
     295    d->itemCnt = 0;
     296    d->renderOk = true;
     297}
     298
     299QPMMime::DefaultDragWorker::~DefaultDragWorker()
     300{
     301    d->cleanupRequests();
     302    delete d;
     303}
     304
     305bool QPMMime::DefaultDragWorker::cleanup(bool isCancelled)
     306{
     307    // the return value is: true if the source-side Move for the given
     308    // drag object should be *dis*allowed, false otherwise (including cases
     309    // when this DragWorker didn't participate to the conversation at all)
     310
     311    // sanity check
     312    Q_ASSERT(d->isInitialized());
     313    if (!d->isInitialized())
     314        return true;
     315
     316    bool moveDisallowed = false;
     317
     318    DEBUG(() << "DefaultDragWorker: Session ended (cancelled" << isCancelled
     319             << "requests.left" << d->requests.count() << ")");
     320
     321    if (d->requests.count()) {
     322        // always disallow Move if not all requests got DM_ENDCONVERSATION
     323        moveDisallowed = true;
     324    } else {
     325        // disallow Move if rendering of some item failed
     326        moveDisallowed = !d->renderOk;
     327    }
     328
     329    DEBUG(() << "DefaultDragWorker: moveDisallowed" << moveDisallowed);
     330
     331    // Note: remaining requests will be lazily deleted by cleanupRequests()
     332    // when a new DND session is started
     333
     334    d->renderOk = true;
     335    d->itemCnt = 0;
     336
     337    // Indicate we're cleaned up (i.e. the DND session is finished)
     338    d->providers.clear();
     339
     340    return moveDisallowed;
     341}
     342
     343bool QPMMime::DefaultDragWorker::isExclusive() const
     344{
     345    return d->exclusive;
     346}
     347
     348ULONG QPMMime::DefaultDragWorker::itemCount() const
     349{
     350    return d->itemCnt;
     351}
     352
     353QByteArray QPMMime::DefaultDragWorker::composeFormatString()
     354{
     355    QByteArray formats;
     356
     357    // sanity checks
     358    Q_ASSERT(d->isInitialized());
     359    if (!d->isInitialized())
     360        return formats;
     361
     362    bool first = true;
     363    foreach(const Data::DrfProvider &p, d->providers) {
     364        if (first)
     365            first = false;
     366        else
     367            formats += ",";
     368        formats += p.drf;
     369    }
     370
     371    Q_ASSERT(!formats.isNull());
     372    if (formats.isNull())
     373        return formats;
     374
     375    // DRM_SHAREDMEM comes first to prevent native DRM_OS2FILE
     376    // rendering on the target side w/o involving the source.
     377    // Also, we add <DRM_SHAREDMEM,DRF_POINTERDATA> just like WPS does it
     378    // (however, it doesn't help when dropping objects to it -- WPS still
     379    // chooses DRM_OS2FILE).
     380    formats = "("DRM_SHAREDMEM","DRM_OS2FILE")x(" + formats + "),"
     381              "<"DRM_SHAREDMEM","DRF_POINTERDATA">";
     382
     383    DEBUG(() << "DefaultDragWorker: formats" << formats
     384             << ", itemCnt" << d->itemCnt);
     385
     386    return formats;
     387}
     388
     389bool QPMMime::DefaultDragWorker::prepare(const char *drm, const char *drf,
     390                                          DRAGITEM *item, ULONG itemIndex)
     391{
     392    // sanity checks
     393    Q_ASSERT(d->isInitialized());
     394    if (!d->isInitialized())
     395        return false;
     396
     397    Q_ASSERT(item && itemIndex < d->itemCnt);
     398    if (!item || itemIndex >= d->itemCnt)
     399        return false;
     400
     401    DEBUG(() << "DefaultDragWorker: Preparing item" <<  itemIndex << "(id "
     402             << item->ulItemID << ") for <" << drm << "," << drf ">");
     403
     404    Provider *p = d->providerFor(drf);
     405
     406    if (!canRender(drm) || p == NULL) {
     407        DEBUG(() << "DefaultDragWorker: Cannot render the given RMF");
     408        return false;
     409    }
     410
     411    Data::Request *req = d->requests.value(item->ulItemID);
     412
     413    if (req) {
     414        // this item has been already prepared, ensure it has also been
     415        // rendered already
     416        Q_ASSERT(req->index == itemIndex);
     417        Q_ASSERT(req->rendered);
     418        if (req->index != itemIndex || !req->rendered)
     419            return false;
     420        // remove the old request to free resources
     421        delete d->requests.take(item->ulItemID);
     422    }
     423
     424    // store the request
     425    req = new Data::Request(itemIndex, p, drm, drf);
     426    d->requests.insert(item->ulItemID, req);
     427
     428    return true;
     429}
     430
     431void QPMMime::DefaultDragWorker::defaultFileType(const char *&type,
     432                                                 const char *&ext)
     433{
     434    Q_ASSERT(d->providers.count());
     435    if (d->providers.count()) {
     436        Provider *p = d->providers.first().prov;
     437        Q_ASSERT(p);
     438        if (p)
     439            p->fileType(d->providers.first().drf, type, ext);
     440    }
     441}
     442
     443MRESULT QPMMime::DefaultDragWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
     444{
     445    if (msg == DM_RENDER) {
     446        DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
     447
     448        // sanity checks
     449        Q_ASSERT(d->isInitialized());
     450        Q_ASSERT(xfer);
     451        if (!d->isInitialized() || !xfer)
     452            return (MRESULT)FALSE;
     453
     454        Q_ASSERT(xfer->hwndClient && xfer->pditem);
     455        if (!xfer->hwndClient || !xfer->pditem)
     456            return (MRESULT)FALSE;
     457
     458        Data::Request *req = d->requests.value(xfer->pditem->ulItemID);
     459
     460        // check that this item has been prepared (should always be the case
     461        // because the target would never know our hwnd() otherwise)
     462        Q_ASSERT(req); // prepared
     463        Q_ASSERT(!req->xfer); // no DM_RENDER requested
     464        if (!req || req->xfer)
     465            return (MRESULT)FALSE;
     466
     467        DEBUG(() << "DefaultDragWorker: Got DM_RENDER to"
     468                 << queryHSTR(xfer->hstrSelectedRMF) << "for item" << req->index
     469                 << "(id " << xfer->pditem->ulItemID << ")");
     470
     471        QCString drm, drf;
     472        if (!parseRMF(xfer->hstrSelectedRMF, drm, drf))
     473            Q_ASSERT(/* parseRMF() = */ FALSE);
     474
     475        if (req->drm != drm || req->drf != drf) {
     476            xfer->fsReply = DMFL_RENDERRETRY;
     477            return (MRESULT)FALSE;
     478        }
     479
     480        // indicate that DM_RENDER was requested
     481        req->xfer = xfer;
     482
     483        DEBUG(() << "DefaultDragWorker: Will render from ["
     484                 << req->provider->format(drf) << "] using provider"
     485                 << req->provider);
     486
     487        // We would lile to post WM_USER to ourselves to do actual rendering
     488        // after we return from DM_RENDER. But we are inside DrgDrag() at this
     489        // point (our DND implementation is fully synchronous by design), so
     490        // PM will not  deliver this message to us until we return from
     491        // DrgDrag(). Thus, we have to send it.
     492
     493        WinSendMsg(hwnd(), WM_USER, MPFROMLONG(xfer->pditem->ulItemID),
     494                                     MPFROMP(req));
     495
     496        return (MRESULT)TRUE;
     497    }
     498
     499    if (msg == WM_USER) {
     500        // sanity checks
     501        Q_ASSERT(d->isInitialized());
     502        if (!d->isInitialized())
     503            return (MRESULT)FALSE;
     504
     505        ULONG itemId = LONGFROMMP(mp1);
     506
     507        // sanity checks
     508        Data::Request *req = d->requests.value(itemId);
     509        Q_ASSERT(req); // prepared
     510        Q_ASSERT(req->xfer != NULL); // DM_RENDER requested
     511        Q_ASSERT(!req->rendered); // not yet rendered
     512        Q_ASSERT((Data::Request *) PVOIDFROMMP(mp2) == req);
     513        if (!req || req->xfer == NULL || req->rendered ||
     514             (Data::Request *) PVOIDFROMMP(mp2) != req)
     515            return (MRESULT)FALSE;
     516
     517        Q_ASSERT(source() && req->provider && req->index < d->itemCnt);
     518        if (!source() || !req->provider || req->index >= d->itemCnt)
     519            return (MRESULT)FALSE;
     520
     521        DEBUG(() << "DefaultDragWorker: Got DO_RENDER for item " << req->index
     522                 << "(id " << req->xfer->pditem->ulItemID << ")"
     523                 << "provider"<< req->provider << "drm" << req->drm.data()
     524                 << "drf" << req->drf.data());
     525
     526        bool renderOk = false;
     527        QByteArray allData =
     528            source()->encodedData(req->provider->format(req->drf));
     529        QByteArray itemData;
     530
     531        renderOk = req->provider->provide(req->drf, allData,
     532                                          req->index, itemData);
     533
     534        if (renderOk) {
     535            enum DRM { OS2File, SharedMem } drmType;
     536            if (qstrcmp(req->drm, DRM_SHAREDMEM) == 0) drmType = SharedMem;
     537            else drmType = OS2File;
     538
     539            if (drmType == OS2File) {
     540                QCString renderToName = queryHSTR(req->xfer->hstrRenderToName);
     541                Q_ASSERT(!renderToName.isEmpty());
     542                renderOk = !renderToName.isEmpty();
     543                if (renderOk) {
     544                    DEBUG(() << "DefaultDragWorker: Will write to" << renderToName);
     545                    QFile file(QFile::decodeName(renderToName));
     546                    renderOk = file.open(IO_WriteOnly);
     547                    if (renderOk) {
     548                        Q_LONG written =
     549                            file.writeBlock(itemData.data(), itemData.size());
     550                        renderOk = written == (Q_LONG) itemData.size() &&
     551                                   file.status() == IO_Ok;
     552                        file.close();
     553                        if (renderOk && req->xfer->pditem->hstrType) {
     554                            // since WPS ignores hstrType, write it manually
     555                            // to the .TYPE EA of the created file
     556                            qt_SetFileTypeEA(renderToName,
     557                                queryHSTR(req->xfer->pditem->hstrType));
     558                        }
     559                    }
     560                }
     561            } else {
     562                PID pid;
     563                TID tid;
     564                bool isSameProcess = false;
     565                renderOk = WinQueryWindowProcess(req->xfer->hwndClient,
     566                                                 &pid, &tid);
     567                if (renderOk) {
     568                    PPIB ppib = NULL;
     569                    DosGetInfoBlocks(NULL, &ppib);
     570                    isSameProcess = ppib->pib_ulpid == pid;
     571
     572                    ULONG sz = itemData.size() + sizeof (ULONG);
     573                    char *ptr = NULL;
     574                    APIRET rc = isSameProcess ?
     575                                DosAllocMem((PPVOID) &ptr, sz,
     576                                            PAG_COMMIT | PAG_READ | PAG_WRITE) :
     577                                DosAllocSharedMem((PPVOID) &ptr, NULL, sz,
     578                                                  OBJ_GIVEABLE | PAG_COMMIT |
     579                                                  PAG_READ | PAG_WRITE);
     580                    renderOk = rc == 0;
     581                    if (renderOk && !isSameProcess) {
     582                        rc = DosGiveSharedMem(ptr, pid, PAG_READ);
     583                        renderOk = rc == 0;
     584                    }
     585                    if (renderOk) {
     586                        *(ULONG *) ptr = itemData.size();
     587                        memcpy(ptr + sizeof (ULONG), itemData.data(),
     588                               itemData.size());
     589                        req->xfer->hstrRenderToName = (HSTR) ptr;
     590                        req->sharedMem = ptr;
     591                        DEBUG(() << "DefaultDragWorker: Created shared memory "
     592                                    "object" << ptr);
     593#ifndef QT_NO_DEBUG
     594                    } else {
     595                        qWarning("DefaultDragWorker: DosAllocSharedMem/"
     596                                 "DosGiveSharedMem failed with %ld", rc);
     597#endif
     598                    }
     599#ifndef QT_NO_DEBUG
     600                } else {
     601                    qWarning("DefaultDragWorker: WinQueryWindowProcess failed"
     602                             "with 0x%lX", WinGetLastError(NULLHANDLE));
     603#endif
     604                }
     605            }
     606        }
     607
     608        req->rendered = true;
     609        // cumulative render result
     610        d->renderOk &= renderOk;
     611
     612        DEBUG(() << "DefaultDragWorker: renderOk" << renderOk
     613                 << "overall.renderOk" << d->renderOk);
     614
     615        // note that we don't allow the target to retry
     616        USHORT reply = renderOk ? DMFL_RENDEROK : DMFL_RENDERFAIL;
     617        DrgPostTransferMsg(req->xfer->hwndClient, DM_RENDERCOMPLETE,
     618                           req->xfer, reply, 0, false);
     619
     620        // DRAGTRANSFER is no more necessary, free it early
     621        qt_DrgFreeDragtransfer(req->xfer);
     622#if defined(QT_DEBUG_DND)
     623        {
     624            ULONG size = ~0, flags = 0;
     625            DosQueryMem(req->xfer, &size, &flags);
     626            DEBUG(("DefaultDragWorker: Freed DRAGTRANSFER: "
     627                   "req->xfer %p size %lu (0x%08lX) flags 0x%08lX",
     628                   req->xfer, size, size, flags);
     629        }
     630#endif
     631        req->xfer = NULL;
     632
     633        return (MRESULT)FALSE;
     634    }
     635
     636    if (msg == DM_ENDCONVERSATION) {
     637        // we don't check that isInitialized() is true here, because WPS
     638        // (and probably some other apps) may send this message after
     639        // cleanup() is called up on return from DrgDrag
     640
     641        ULONG itemId = LONGFROMMP(mp1);
     642        ULONG flags = LONGFROMMP(mp2);
     643
     644        // sanity check (don't assert, see above)
     645        Data::Request *req = d->requests.value(itemId);
     646        Q_ASSERT(req);
     647        if (!req)
     648            return (MRESULT)FALSE;
     649
     650        DEBUG(() << "DefaultDragWorker: Got DM_ENDCONVERSATION for item " << req->index
     651                 << " (id " << itemId << ") provider" << req->provider
     652                 << "drm" << req->drm << "drf" << req->drf
     653                 << "rendered" << req->rendered << "outdated" << !d->isInitialized());
     654
     655        // proceed further only if it's not an outdated request
     656        // from the previous DND session
     657        if (d->isInitialized()) {
     658            if (!req->rendered) {
     659                // we treat cancelling the render request (for any reason)
     660                // as a failure
     661                d->renderOk = false;
     662            } else {
     663                // the overall success is true only if target says Okay
     664                d->renderOk &= flags == DMFL_TARGETSUCCESSFUL;
     665            }
     666        }
     667
     668        // delete the request
     669        delete d->requests.take(itemId);
     670
     671        return (MRESULT)FALSE;
     672    }
     673
     674    return (MRESULT)FALSE;
     675}
     676
     677bool QPMMime::DefaultDragWorker::addProvider(const char *drf, Provider *provider,
     678                                             ULONG itemCnt /* = 1 */)
     679{
     680    // make sure remaining requests from the previous DND session are deleted
     681    d->cleanupRequests();
     682
     683    Q_ASSERT(drf && provider && itemCnt >= 1);
     684    if (drf && provider && itemCnt >= 1) {
     685        if (d->providers.count() == 0) {
     686            // first provider
     687            d->itemCnt = itemCnt;
     688            d->providers.insert(Data::DrfProvider(drf, provider));
     689            return true;
     690        }
     691        // next provider, must not be exclusive and itemCnt must match
     692        if (!d->exclusive && d->itemCnt == itemCnt) {
     693            // ensure there are no dups (several providers for the same drf)
     694            if (!d->providerFor(drf))
     695                d->providers.insert(Data::DrfProvider(drf, provider));
     696            return true;
     697        }
     698    }
     699    return false;
     700}
     701
     702// static
     703bool QPMMime::DefaultDragWorker::canRender(const char *drm)
     704{
     705    return qstrcmp(drm, DRM_SHAREDMEM) == 0 ||
     706           qstrcmp(drm, DRM_OS2FILE) == 0;
     707}
     708
     709//------------------------------------------------------------------------------
     710
     711struct QPMMime::DefaultDropWorker::Data
     712{
     713    struct MimeProvider
     714    {
     715        MimeProvider() : prov(NULL) {}
     716        MimeProvider(const QString &m, Provider *p) : mime(m), prov(p) {}
     717        const QString mime;
     718        Provider * const prov;
     719    };
     720
     721    typedef QList<MimeProvider> MimeProviderList;
     722
     723    Provider *providerFor(const QString &mime)
     724    {
     725        foreach (const MimeProvider &p, providers) {
     726            if (p.mime == mime)
     727                return p.prov;
     728        }
     729        return NULL;
     730    }
     731
     732    bool exclusive : 1;
     733    MimeProviderList providers;
     734
     735    bool sending_DM_RENDER : 1;
     736    bool got_DM_RENDERCOMPLETE : 1;
     737    USHORT flags_DM_RENDERCOMPLETE;
     738    int waiting_DM_RENDERCOMPLETE;
     739};
     740
     741QPMMime::DefaultDropWorker::DefaultDropWorker() : d(new Data())
     742{
     743    d->exclusive = false;
     744    d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = false;
     745    d->flags_DM_RENDERCOMPLETE = 0;
     746    d->waiting_DM_RENDERCOMPLETE = 0;
     747}
     748
     749QPMMime::DefaultDropWorker::~DefaultDropWorker()
     750{
     751    delete d;
     752}
     753
     754void QPMMime::DefaultDropWorker::cleanup(bool isAccepted, bool isActAccepted)
     755{
     756    if (d->waiting_DM_RENDERCOMPLETE != 0) {
     757#ifndef QT_NO_DEBUG
     758        qWarning("The previous drag source didn't post DM_RENDERCOMPLETE!\n"
     759                 "Contact the drag source developer.");
     760#endif
     761        qApp->eventLoop()->exitLoop();
     762    }
     763
     764    d->providers.clear();
     765    d->exclusive = false;
     766    d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = false;
     767    d->flags_DM_RENDERCOMPLETE = 0;
     768    d->waiting_DM_RENDERCOMPLETE = 0;
     769}
     770
     771bool QPMMime::DefaultDropWorker::isExclusive() const
     772{
     773    return d->exclusive;
     774}
     775
     776bool QPMMime::DefaultDropWorker::provides(const char *format) const
     777{
     778    return d->providerFor(format) != NULL;
     779}
     780
     781int QPMMime::DefaultDropWorker::formatCount() const
     782{
     783    return d->providers.count();
     784}
     785
     786const char *QPMMime::DefaultDropWorker::format(int fn) const
     787{
     788    if (fn >= 0 && (uint) fn < d->providers.count())
     789        return d->providers[ fn ].mime;
     790    return NULL;
     791}
     792
     793static QCString composeTempFileName()
     794{
     795    static char defTmpDir[ 3 ] = { 0 };
     796    const char *tmpDir = getenv("TEMP");
     797    if (!tmpDir) tmpDir = getenv("TMP");
     798    if (!tmpDir || !QFile::exists(QFile::decodeName(tmpDir))) {
     799        if (!defTmpDir[ 0 ]) {
     800            ULONG bootDrive = 0;
     801            DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
     802                            &bootDrive, sizeof (bootDrive));
     803            defTmpDir[ 0 ] = bootDrive;
     804            defTmpDir[ 1 ] = ':';
     805        }
     806        tmpDir = defTmpDir;
     807    }
     808
     809    static bool srandDone = false;
     810    if (!srandDone) {
     811        srand(time(NULL));
     812        srandDone = true;
     813    }
     814
     815    ULONG num = rand();
     816    enum { Attempts = 100 };
     817    int attempts = Attempts;
     818
     819    QCString tmpName;
     820    do {
     821        tmpName.sprintf("%s\\%08lX.tmp", tmpDir, num);
     822        if (!QFile::exists(QFile::decodeName(tmpName)))
     823            break;
     824        num = rand();
     825    } while (--attempts > 0);
     826
     827    Q_ASSERT(attempts > 0);
     828    if (attempts <= 0)
     829        tmpName.resize(0);
     830
     831    return tmpName;
     832}
     833
     834QByteArray QPMMime::DefaultDropWorker::encodedData(const char *format) const
     835{
     836    DEBUG("DefaultDropWorker::encodedData(" << format << ")");
     837
     838    QByteArray data;
     839
     840    Q_ASSERT(info());
     841    if (!info())
     842        return data;
     843
     844    ULONG itemCount = DrgQueryDragitemCount(info());
     845    Q_ASSERT(itemCount);
     846    if (!itemCount)
     847        return data;
     848
     849    Provider *provider = d->providerFor(format);
     850    if (!provider)
     851        return data;
     852
     853    const char *drf = provider->drf(format);
     854    Q_ASSERT(drf);
     855    if (!drf)
     856        return data;
     857
     858    // Note: Allocating and freeing DRAGTRANSFER structures is a real mess. It's
     859    // absolutely unclear how they can be reused for multiple items and/or render
     860    // requests. My practice shows, that they cannot be reused at all, especially
     861    // when the source and the target are the same process: if we have multiple
     862    // items and use the same DRAGTRANSFER for all of them, the source will call
     863    // DrgFreeDragtransfer() every time that will eventually destroy the memory
     864    // object before the target finishes to work with it, so that the next
     865    // DrgFreeDragtransfer() will generate a segfault in PMCTLS. Also note that
     866    // using a number > 1 as an argument to DrgAllocDragtransfer() won't help
     867    // because that will still allocate a single memory object. Thus, we will
     868    // always allocate a new struct per every item. It seems to work.
     869
     870    QCString renderToName = composeTempFileName();
     871    HSTR hstrRenderToName = DrgAddStrHandle(renderToName);
     872
     873    HSTR rmfOS2File =
     874        DrgAddStrHandle(QCString().sprintf("<"DRM_OS2FILE",%s>", drf));
     875    HSTR rmfSharedMem =
     876        DrgAddStrHandle(QCString().sprintf("<"DRM_SHAREDMEM",%s>", drf));
     877
     878    MRESULT mrc;
     879    bool renderOk = false;
     880
     881    DRAGTRANSFER *xfer = NULL;
     882    QCString srcFileName;
     883
     884    QByteArray allData, itemData;
     885
     886    for (ULONG i = 0; i < itemCount; ++i) {
     887        DRAGITEM *item = DrgQueryDragitemPtr(info(), i);
     888        Q_ASSERT(item);
     889        if (!item) {
     890            renderOk = false;
     891            break;
     892        }
     893
     894        enum { None, OS2File, SharedMem } drm = None;
     895        bool needToTalk = true;
     896
     897        // determine the mechanism to use (prefer DRM_SHAREDMEM)
     898
     899        if (DrgVerifyRMF(item, DRM_SHAREDMEM, drf) &&
     900             DrgVerifyRMF(item, DRM_SHAREDMEM, DRF_POINTERDATA))
     901            drm = SharedMem;
     902        if (DrgVerifyRMF(item, DRM_OS2FILE, drf)) {
     903            srcFileName = querySourceNameFull(item);
     904            // If the source provides the full file name, we prefer DRM_OS2FILE
     905            // even if there is also DRM_SHAREDMEM available because we don't
     906            // need to do any communication in this case at all. This will help
     907            // with some native drag sources (such as DragText) that cannot send
     908            // DM_RENDERCOMPLETE synchronously (before we return from DM_DROP)
     909            // and would hang otherwise.
     910            if (!srcFileName.isEmpty()) {
     911                needToTalk = false;
     912                drm = OS2File;
     913            } else if (drm == None) {
     914                srcFileName = renderToName;
     915                drm = OS2File;
     916            }
     917        }
     918        Q_ASSERT(drm != None);
     919        if (drm == None) {
     920            renderOk = false;
     921            break;
     922        }
     923
     924        if (needToTalk) {
     925            // need to perform a conversation with the source,
     926            // allocate a new DRAGTRANSFER structure for each item
     927            xfer = DrgAllocDragtransfer(1);
     928            Q_ASSERT(xfer);
     929            if (!xfer) {
     930                renderOk = false;
     931                break;
     932            }
     933
     934            xfer->cb = sizeof(DRAGTRANSFER);
     935            xfer->hwndClient = hwnd();
     936            xfer->ulTargetInfo = (ULONG) info();
     937            xfer->usOperation = info()->usOperation;
     938
     939            xfer->pditem = item;
     940            if (drm == OS2File) {
     941                xfer->hstrSelectedRMF = rmfOS2File;
     942                xfer->hstrRenderToName = hstrRenderToName;
     943            } else {
     944                xfer->hstrSelectedRMF = rmfSharedMem;
     945                xfer->hstrRenderToName = 0;
     946            }
     947
     948            DEBUG(() << "DefaultDropWorker: Will use"
     949                     << queryHSTR(xfer->hstrSelectedRMF) << "to render item" << item);
     950
     951            mrc = (MRESULT)TRUE;
     952            if ((item->fsControl & DC_PREPARE) |
     953                 (item->fsControl & DC_PREPAREITEM)) {
     954                DEBUG(("DefaultDropWorker: Sending DM_RENDERPREPARE to 0x%08lX...",
     955                       info()->hwndSource));
     956                mrc = DrgSendTransferMsg(info()->hwndSource, DM_RENDERPREPARE,
     957                                         MPFROMP (xfer), 0);
     958                DEBUG(("DefaultDropWorker: Finisned sending DM_RENDERPREPARE\n"
     959                       " mrc %p xfer->fsReply 0x%08hX", mrc, xfer->fsReply);
     960                renderOk = (BOOL) mrc;
     961            }
     962
     963            if ((BOOL) mrc) {
     964                DEBUG(("DefaultDropWorker: Sending DM_RENDER to 0x%08lX...",
     965                       item->hwndItem));
     966                d->sending_DM_RENDER = true;
     967                mrc = DrgSendTransferMsg(item->hwndItem, DM_RENDER,
     968                                         MPFROMP (xfer), 0);
     969                d->sending_DM_RENDER = false;
     970                DEBUG(("DefaultDropWorker: Finisned Sending DM_RENDER\n"
     971                       " mrc %p xfer->fsReply 0x%hX got_DM_RENDERCOMPLETE %d",
     972                       mrc, xfer->fsReply, d->got_DM_RENDERCOMPLETE));
     973
     974                if (!(BOOL) mrc || d->got_DM_RENDERCOMPLETE) {
     975                    if (d->got_DM_RENDERCOMPLETE)
     976                        renderOk = (d->flags_DM_RENDERCOMPLETE & DMFL_RENDEROK);
     977                    else
     978                        renderOk = false;
     979                } else {
     980                    // synchronously wait for DM_RENDERCOMPLETE
     981                    d->waiting_DM_RENDERCOMPLETE = qApp->loopLevel() + 1;
     982                    DEBUG(() << "DefaultDropWorker: Waiting for DM_RENDERCOMPLETE...");
     983                    int level = qApp->eventLoop()->enterLoop();
     984                    DEBUG(("DefaultDropWorker: Finished waiting for "
     985                           "DM_RENDERCOMPLETE (%d)\n"
     986                           " got_DM_RENDERCOMPLETE %d usFS 0x%hX",
     987                           level, d->got_DM_RENDERCOMPLETE, d->flags_DM_RENDERCOMPLETE));
     988                    Q_UNUSED(level);
     989                    // JFTR: at this point, cleanup() might have been called,
     990                    // as a result of either program exit or getting another
     991                    // DM_DRAGOVER (if the source app has crashed) before getting
     992                    // DM_RENDERCOMPLETE from the source. Use data members with
     993                    // care!
     994                    d->waiting_DM_RENDERCOMPLETE = 0;
     995                    renderOk = d->got_DM_RENDERCOMPLETE &&
     996                               (d->flags_DM_RENDERCOMPLETE & DMFL_RENDEROK);
     997                }
     998
     999                d->got_DM_RENDERCOMPLETE = false;
     1000            }
     1001        } else {
     1002            DEBUG(() << "DefaultDropWorker: Source supports <" << DRM_OS2FILE
     1003                     << "," << drf << "> and provides a file" << srcFileName
     1004                     << "for item" << item << "(no need to render)");
     1005            renderOk = true;
     1006        }
     1007
     1008        if (renderOk) {
     1009            if (drm == OS2File) {
     1010                DEBUG(() << "DefaultDragWorker: Will read from" << srcFileName);
     1011                QFile file(QFile::decodeName(srcFileName));
     1012                renderOk = file.open(IO_ReadOnly);
     1013                if (renderOk) {
     1014                    itemData = file.readAll();
     1015                    renderOk = file.status() == IO_Ok;
     1016                    file.close();
     1017                }
     1018                bool ok = file.remove();
     1019                Q_ASSERT((ok = ok));
     1020            } else {
     1021                Q_ASSERT(xfer->hstrRenderToName);
     1022                renderOk = xfer->hstrRenderToName != 0;
     1023                if (renderOk) {
     1024                    const char *ptr = (const char *) xfer->hstrRenderToName;
     1025                    ULONG size = ~0;
     1026                    ULONG flags = 0;
     1027                    APIRET rc = DosQueryMem((PVOID) ptr, &size, &flags);
     1028                    renderOk = rc == 0;
     1029                    if (renderOk) {
     1030                        DEBUG(("DefaultDropWorker: Got shared data %p size %lu "
     1031                               "(0x%08lX) flags 0x%08lX", ptr, size, size, flags));
     1032                        Q_ASSERT(flags & (PAG_COMMIT | PAG_READ | PAG_BASE) ==
     1033                                 (PAG_COMMIT | PAG_READ | PAG_BASE));
     1034                        renderOk = flags & (PAG_COMMIT | PAG_READ | PAG_BASE) ==
     1035                                   (PAG_COMMIT | PAG_READ | PAG_BASE);
     1036#ifndef QT_NO_DEBUG
     1037                    } else {
     1038                        qWarning("DefaultDropWorker: DosQueryMem failed with %ld", rc);
     1039#endif
     1040                    }
     1041                    if (renderOk) {
     1042                        ULONG realSize = *(ULONG *) ptr;
     1043                        DEBUG(() << "DefaultDropWorker: realSize" << realSize);
     1044                        Q_ASSERT(realSize <= size);
     1045                        renderOk = realSize <= size;
     1046                        if (renderOk) {
     1047                            itemData.resize(realSize);
     1048                            memcpy(itemData.data(), ptr + sizeof (ULONG), realSize);
     1049                        }
     1050                    }
     1051                    // free memory only if it is given by another process,
     1052                    // otherwise DefaultDragWorker will free it
     1053                    if (flags & PAG_SHARED)
     1054                        DosFreeMem((PVOID) xfer->hstrRenderToName);
     1055                }
     1056            }
     1057        }
     1058
     1059        if (renderOk)
     1060            renderOk = provider->provide(format, i, itemData, allData);
     1061
     1062        if (needToTalk) {
     1063            // free the DRAGTRANSFER structure
     1064            DrgFreeDragtransfer(xfer);
     1065#if defined(QT_DEBUG_DND)
     1066            {
     1067                ULONG size = ~0, flags = 0;
     1068                DosQueryMem(xfer, &size, &flags);
     1069                DEBUG(("DefaultDropWorker: Freed DRAGTRANSFER: "
     1070                       "xfer=%p, size=%lu(0x%08lX), flags=0x%08lX",
     1071                       xfer, size, size, flags));
     1072            }
     1073#endif
     1074            xfer = NULL;
     1075        }
     1076
     1077        if (!renderOk)
     1078            break;
     1079    }
     1080
     1081    DEBUG(() << "DefaultDropWorker: renderOk" << renderOk);
     1082
     1083    DrgDeleteStrHandle(rmfSharedMem);
     1084    DrgDeleteStrHandle(rmfOS2File);
     1085    DrgDeleteStrHandle(hstrRenderToName);
     1086
     1087    if (renderOk)
     1088        data = allData;
     1089
     1090    return data;
     1091}
     1092
     1093MRESULT QPMMime::DefaultDropWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
     1094{
     1095    switch (msg) {
     1096        case DM_RENDERCOMPLETE: {
     1097            // sanity check
     1098            Q_ASSERT(info());
     1099            if (!info())
     1100                return (MRESULT)FALSE;
     1101
     1102            DEBUG("DefaultDropWorker: Got DM_RENDERCOMPLETE");
     1103            d->got_DM_RENDERCOMPLETE = true;
     1104            d->flags_DM_RENDERCOMPLETE = SHORT1FROMMP(mp2);
     1105
     1106            if (d->sending_DM_RENDER)
     1107            {
     1108                DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
     1109#ifndef QT_NO_DEBUG
     1110                qWarning("Drag item 0x%08lX sent DM_RENDERCOMPLETE w/o first "
     1111                         "replying to DM_RENDER!\n"
     1112                         "Contact the drag source developer.",
     1113                         xfer->pditem->hwndItem);
     1114#endif
     1115                return (MRESULT)FALSE;
     1116            }
     1117
     1118            // stop synchronous waiting for DM_RENDERCOMPLETE
     1119            if (d->waiting_DM_RENDERCOMPLETE != 0)
     1120                qApp->eventLoop()->exitLoop();
     1121            return (MRESULT)FALSE;
     1122        }
     1123        default:
     1124            break;
     1125    }
     1126
     1127    return (MRESULT)FALSE;
     1128}
     1129
     1130bool QPMMime::DefaultDropWorker::addProvider(const char *format,
     1131                                             Provider *provider)
     1132{
     1133    Q_ASSERT(format && provider);
     1134    if (format && provider && !d->exclusive) {
     1135        // ensure there are no dups (several providers for the same mime)
     1136        if (!d->providerFor(format))
     1137            d->providers.insert(Data::MimeProvider(format, provider));
     1138        return true;
     1139    }
     1140    return false;
     1141}
     1142
     1143bool QPMMime::DefaultDropWorker::addExclusiveProvider(const char *format,
     1144                                                      Provider *provider)
     1145{
     1146    Q_ASSERT(format && provider);
     1147    if (format && provider && !d->exclusive) {
     1148        d->exclusive = true;
     1149        d->providers.clear();
     1150        d->providers.insert(Data::MimeProvider(format, provider));
     1151        return true;
     1152    }
     1153    return false;
     1154}
     1155
     1156// static
     1157bool QPMMime::DefaultDropWorker::canRender(DRAGITEM *item, const char *drf)
     1158{
     1159    return DrgVerifyRMF(item, DRM_OS2FILE, drf) ||
     1160           (DrgVerifyRMF(item, DRM_SHAREDMEM, drf) &&
     1161            DrgVerifyRMF(item, DRM_SHAREDMEM, DRF_POINTERDATA));
     1162}
     1163
     1164// static
     1165/*! \internal
     1166  Parses the rendering mechanism/format specification of the given \a item
     1167  and stores only those mechanism branches in the given \a list that represent
     1168  mechanisms supported by this worker. Returns false if fails to parse the
     1169  RMF specification. Note that if no supported mechanisms are found, true is
     1170  returned but the \a list will simply contain zero items.
     1171  \note The method clears the given \a list variable before proceeding and sets
     1172  auto-deletion to true.
     1173  \sa canRender(), PMMime::parseRMFs()
     1174*/
     1175bool QPMMime::DefaultDropWorker::getSupportedRMFs(DRAGITEM *item,
     1176                                                  QPtrList<QStrList> &list)
     1177{
     1178    if (!parseRMFs(item->hstrRMF, list))
     1179        return false;
     1180
     1181    for (QStrList *mech = list.first(); mech;) {
     1182        const char *drm = mech->first();
     1183        if (qstrcmp(drm, DRM_OS2FILE) == 0) {
     1184            mech = list.next();
     1185            continue;
     1186        }
     1187        if (qstrcmp(drm, DRM_SHAREDMEM) == 0) {
     1188            const char *drf = mech->next();
     1189            // accept DRM_SHAREDMEM only if there is DRF_POINTERDATA
     1190            for(; drf; drf = mech->next()) {
     1191                if (qstrcmp(drf, DRF_POINTERDATA) == 0)
     1192                    break;
     1193            }
     1194            if (drf) {
     1195                mech = list.next();
     1196                continue;
     1197            }
     1198        }
     1199        // remove the unsupported mechanism branch from the list
     1200        bool wasLast = list.getLast() == mech;
     1201        list.removeRef(mech);
     1202        // after deleting the last item, the current one will be set to the new
     1203        // last item which was already analyzed earlier, so set to 0 to stop
     1204        mech = wasLast ? 0 : list.current();
     1205    }
     1206
     1207    return true;
     1208}
     1209
     1210#endif // !QT_NO_DRAGANDDROP
     1211
     1212//------------------------------------------------------------------------------
    651213
    661214class QPMMimeList
     
    1591307    if (!cf) {
    1601308#ifndef QT_NO_DEBUG
    161         qWarning("QPMMime: WinAddAtom failed with %lX",
     1309        qWarning("QPMMime: WinAddAtom failed with 0x%lX",
    1621310                 WinGetLastError(NULLHANDLE));
    1631311#endif
Note: See TracChangeset for help on using the changeset viewer.