source: trunk/src/gui/kernel/qmime_pm.cpp@ 474

Last change on this file since 474 was 474, checked in by Dmitry A. Kuminov, 15 years ago

gui: DnD: Added some documentation for important QPMMime classes and members.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 98.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** Copyright (C) 2009 netlabs.org. OS/2 parts.
7**
8** This file is part of the QtGui module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial Usage
12** Licensees holding valid Qt Commercial licenses may use this file in
13** accordance with the Qt Commercial License Agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and Nokia.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Nokia gives you certain
26** additional rights. These rights are described in the Nokia Qt LGPL
27** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
28** package.
29**
30** GNU General Public License Usage
31** Alternatively, this file may be used under the terms of the GNU
32** General Public License version 3.0 as published by the Free Software
33** Foundation and appearing in the file LICENSE.GPL included in the
34** packaging of this file. Please review the following information to
35** ensure the GNU General Public License version 3.0 requirements will be
36** met: http://www.gnu.org/copyleft/gpl.html.
37**
38** If you are unsure which license is appropriate for your use, please
39** contact the sales department at [email protected].
40** $QT_END_LICENSE$
41**
42****************************************************************************/
43
44#include "qmime.h"
45
46#include "qimagereader.h"
47#include "qimagewriter.h"
48#include "qdatastream.h"
49#include "qbuffer.h"
50#include "qt_os2.h"
51#include "qapplication_p.h"
52#include "qtextcodec.h"
53#include "qregexp.h"
54#include "qalgorithms.h"
55#include "qmap.h"
56#include "qdnd_p.h"
57#include "qurl.h"
58#include "qvariant.h"
59#include "qtextdocument.h"
60#include "qdir.h"
61
62//#define QDND_DEBUG // in pair with qdnd_pm.cpp
63
64#ifdef QDND_DEBUG
65# include "qdebug.h"
66# define DEBUG(a) qDebug a
67#else
68# define DEBUG(a) do {} while(0)
69#endif
70
71QT_BEGIN_NAMESPACE
72
73#if !defined(QT_NO_DRAGANDDROP)
74
75// Undoc'd DC_PREPAREITEM, see
76// http://lxr.mozilla.org/seamonkey/source/widget/src/os2/nsDragService.cpp
77#if !defined (DC_PREPAREITEM)
78#define DC_PREPAREITEM 0x40
79#endif
80
81/*! \internal
82 According to my tests, DrgFreeDragtransfer() appears to be bogus: when the
83 drag source attempts to free the DRAGTRANSFER structure passed to it in
84 DM_RENDERPREPARE/DM_RENDER by another process, the shared memory object is not
85 actually released until DrgFreeDragtransfer() is called for the second time.
86 This method tries to fix this problem.
87
88 \note The problem (and the solution) was not tested on platforms other than
89 eCS!
90*/
91void qt_DrgFreeDragtransfer(DRAGTRANSFER *xfer)
92{
93 Q_ASSERT(xfer);
94 if (xfer) {
95 BOOL ok = DrgFreeDragtransfer(xfer);
96 Q_ASSERT(ok);
97 if (ok) {
98 ULONG size = ~0, flags = 0;
99 APIRET rc = DosQueryMem(xfer, &size, &flags);
100 Q_ASSERT(rc == 0);
101 if (rc == 0 && !(flags & PAG_FREE)) {
102 PID pid;
103 TID tid;
104 ok = WinQueryWindowProcess(xfer->hwndClient, &pid, &tid);
105 Q_ASSERT(ok);
106 if (ok) {
107 PPIB ppib = 0;
108 DosGetInfoBlocks(0, &ppib);
109 if (ppib->pib_ulpid != pid) {
110 DEBUG(() << "qt_DrgFreeDragtransfer: Will free xfer"
111 << xfer << "TWICE (other process)!");
112 DrgFreeDragtransfer(xfer);
113 }
114 }
115 }
116 }
117 }
118}
119
120#define SEA_TYPE ".TYPE"
121
122/*! \internal
123 Sets a single .TYPE EA vaule on a given fle.
124*/
125static void qt_SetFileTypeEA(const char *name, const char *type)
126{
127 #pragma pack(1)
128
129 struct MY_FEA2 {
130 ULONG oNextEntryOffset; /* Offset to next entry. */
131 BYTE fEA; /* Extended attributes flag. */
132 BYTE cbName; /* Length of szName, not including NULL. */
133 USHORT cbValue; /* Value length. */
134 CHAR szName[0]; /* Extended attribute name. */
135 /* EA value follows here */
136 };
137
138 struct MY_FEA2LIST {
139 ULONG cbList; /* Total bytes of structure including full list. */
140 MY_FEA2 list[0]; /* Variable-length FEA2 structures. */
141 };
142
143 struct MY_FEA2_VAL {
144 USHORT usEAType; /* EA value type (one of EAT_* constants) */
145 USHORT usValueLen; /* Length of the value data following */
146 CHAR aValueData[0]; /* value data */
147 };
148
149 struct MY_FEA2_MVMT {
150 USHORT usEAType; /* Always EAT_MVMT */
151 USHORT usCodePage; /* 0 - default */
152 USHORT cbNumEntries; /* Number of MYFEA2_VAL structs following */
153 MY_FEA2_VAL aValues[0]; /* MYFEA2_VAL value structures */
154 };
155
156 #pragma pack()
157
158 uint typeLen = qstrlen(type);
159 uint valLen = sizeof(MY_FEA2_VAL) + typeLen;
160 uint mvmtLen = sizeof(MY_FEA2_MVMT) + valLen;
161 uint fea2Len = sizeof(MY_FEA2) + sizeof(SEA_TYPE);
162 uint fullLen = sizeof(MY_FEA2LIST) + fea2Len + mvmtLen;
163
164 uchar *eaData = new uchar[fullLen];
165
166 MY_FEA2LIST *fea2List = (MY_FEA2LIST *)eaData;
167 fea2List->cbList = fullLen;
168
169 MY_FEA2 *fea2 = fea2List->list;
170 fea2->oNextEntryOffset = 0;
171 fea2->fEA = 0;
172 fea2->cbName = sizeof(SEA_TYPE) - 1;
173 fea2->cbValue = mvmtLen;
174 strcpy(fea2->szName, SEA_TYPE);
175
176 MY_FEA2_MVMT *mvmt = (MY_FEA2_MVMT *)(fea2->szName + sizeof(SEA_TYPE));
177 mvmt->usEAType = EAT_MVMT;
178 mvmt->usCodePage = 0;
179 mvmt->cbNumEntries = 1;
180
181 MY_FEA2_VAL *val = mvmt->aValues;
182 val->usEAType = EAT_ASCII;
183 val->usValueLen = typeLen;
184 memcpy(val->aValueData, type, typeLen);
185
186 EAOP2 eaop2;
187 eaop2.fpGEA2List = 0;
188 eaop2.fpFEA2List = (FEA2LIST *)fea2List;
189 eaop2.oError = 0;
190
191 APIRET rc = DosSetPathInfo(name, FIL_QUERYEASIZE,
192 &eaop2, sizeof(eaop2), 0);
193 Q_UNUSED(rc);
194#ifndef QT_NO_DEBUG
195 if (rc)
196 qWarning("qt_SetFileTypeEA: DosSetPathInfo failed with %ld", rc);
197#endif
198
199 delete[] eaData;
200}
201
202static int hex_to_int(uchar c)
203{
204 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
205 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
206 if (c >= '0' && c <= '9') return c - '0';
207 return -1;
208}
209
210static inline int hex_to_int(char c)
211{
212 return hex_to_int((uchar) c);
213}
214
215//------------------------------------------------------------------------------
216
217struct QPMMime::DefaultDragWorker::Data
218{
219 Data(bool excl) : exclusive(excl) {}
220
221 struct Request
222 {
223 Request(ULONG i, Provider *p, const char *m, const char *f)
224 : index(i), provider(p), drm(m), drf(f)
225 , xfer(0), rendered(false), sharedMem(0) {}
226
227 ~Request()
228 {
229 // free memory allocated for the target that requested DRM_SHAREDMEM
230 if (sharedMem)
231 DosFreeMem(sharedMem);
232 Q_ASSERT(!xfer);
233 }
234
235 ULONG index;
236 Provider *provider;
237 QByteArray drm;
238 QByteArray drf;
239 DRAGTRANSFER *xfer;
240 bool rendered;
241 PVOID sharedMem;
242 };
243
244 inline bool isInitialized() { return providers.count() != 0; }
245 void cleanupRequests();
246
247 struct DrfProvider
248 {
249 DrfProvider() : prov(0) {}
250 DrfProvider(const QByteArray &d, Provider *p) : drf(d), prov(p) {}
251 QByteArray drf;
252 Provider *prov;
253 };
254
255 typedef QList<DrfProvider> DrfProviderList;
256
257 Provider *providerFor(const char *drf)
258 {
259 foreach (const DrfProvider &dp, providers)
260 if (qstrcmp(dp.drf, drf) == 0)
261 return dp.prov;
262 return 0;
263 }
264
265 const bool exclusive : 1;
266 DrfProviderList providers;
267
268 ULONG itemCnt;
269 QHash<ULONG, Request*> requests;
270 bool renderOk : 1;
271};
272
273void QPMMime::DefaultDragWorker::Data::cleanupRequests()
274{
275 if (requests.count()) {
276 DEBUG(("In the previous DnD session, the drop target sent "
277 "DM_RENDERPREPARE/DM_RENDER\n"
278 "for some drag item but didn't send DM_ENDCONVERSATION!"));
279 qDeleteAll(requests);
280 requests.clear();
281 }
282}
283
284QPMMime::DefaultDragWorker::DefaultDragWorker(bool exclusive)
285 : d(new Data(exclusive))
286{
287 d->itemCnt = 0;
288 d->renderOk = true;
289}
290
291QPMMime::DefaultDragWorker::~DefaultDragWorker()
292{
293 d->cleanupRequests();
294 delete d;
295}
296
297bool QPMMime::DefaultDragWorker::cleanup(bool isCancelled)
298{
299 // the return value is: true if the source-side Move for the given
300 // drag object should be *dis*allowed, false otherwise (including cases
301 // when this DragWorker didn't participate to the conversation at all)
302
303 // sanity check
304 Q_ASSERT(d->isInitialized());
305 if (!d->isInitialized())
306 return true;
307
308 bool moveDisallowed = false;
309
310 DEBUG(() << "DefaultDragWorker: Session ended ( cancelled" << isCancelled
311 << "requests.left" << d->requests.count() << ")");
312
313 if (d->requests.count()) {
314 // always disallow Move if not all requests got DM_ENDCONVERSATION
315 moveDisallowed = true;
316 } else {
317 // disallow Move if rendering of some item failed
318 moveDisallowed = !d->renderOk;
319 }
320
321 DEBUG(() << "DefaultDragWorker: moveDisallowed" << moveDisallowed);
322
323 // Note: remaining requests will be lazily deleted by cleanupRequests()
324 // when a new DND session is started
325
326 d->renderOk = true;
327 d->itemCnt = 0;
328
329 // Indicate we're cleaned up (i.e. the DND session is finished)
330 d->providers.clear();
331
332 return moveDisallowed;
333}
334
335bool QPMMime::DefaultDragWorker::isExclusive() const
336{
337 return d->exclusive;
338}
339
340ULONG QPMMime::DefaultDragWorker::itemCount() const
341{
342 return d->itemCnt;
343}
344
345QByteArray QPMMime::DefaultDragWorker::composeFormatString()
346{
347 QByteArray formats;
348
349 // sanity checks
350 Q_ASSERT(d->isInitialized());
351 if (!d->isInitialized())
352 return formats;
353
354 bool first = true;
355 foreach(const Data::DrfProvider &p, d->providers) {
356 if (first)
357 first = false;
358 else
359 formats += ",";
360 formats += p.drf;
361 }
362
363 Q_ASSERT(!formats.isNull());
364 if (formats.isNull())
365 return formats;
366
367 // DRM_SHAREDMEM comes first to prevent native DRM_OS2FILE
368 // rendering on the target side w/o involving the source.
369 // Also, we add <DRM_SHAREDMEM,DRF_POINTERDATA> just like WPS does it
370 // (however, it doesn't help when dropping objects to it -- WPS still
371 // chooses DRM_OS2FILE).
372 formats = "(DRM_SHAREDMEM,DRM_OS2FILE)x(" + formats + "),"
373 "<DRM_SHAREDMEM,DRF_POINTERDATA>";
374
375 DEBUG(() << "DefaultDragWorker: formats" << formats
376 << ", itemCnt" << d->itemCnt);
377
378 return formats;
379}
380
381bool QPMMime::DefaultDragWorker::prepare(const char *drm, const char *drf,
382 DRAGITEM *item, ULONG itemIndex)
383{
384 // sanity checks
385 Q_ASSERT(d->isInitialized());
386 if (!d->isInitialized())
387 return false;
388
389 Q_ASSERT(item && itemIndex < d->itemCnt);
390 if (!item || itemIndex >= d->itemCnt)
391 return false;
392
393 DEBUG(() << "DefaultDragWorker: Preparing item" << itemIndex << "( id "
394 << item->ulItemID << ") for <" << drm << "," << drf << ">");
395
396 Provider *p = d->providerFor(drf);
397
398 if (!canRender(drm) || p == NULL) {
399 DEBUG(() << "DefaultDragWorker: Cannot render the given RMF");
400 return false;
401 }
402
403 Data::Request *req = d->requests.value(item->ulItemID);
404
405 if (req) {
406 // this item has been already prepared, ensure it has also been
407 // rendered already
408 Q_ASSERT(req->index == itemIndex);
409 Q_ASSERT(req->rendered);
410 if (req->index != itemIndex || !req->rendered)
411 return false;
412 // remove the old request to free resources
413 delete d->requests.take(item->ulItemID);
414 }
415
416 // store the request
417 req = new Data::Request(itemIndex, p, drm, drf);
418 d->requests.insert(item->ulItemID, req);
419
420 return true;
421}
422
423void QPMMime::DefaultDragWorker::defaultFileType(QString &type,
424 QString &ext)
425{
426 Q_ASSERT(d->providers.count());
427 if (d->providers.count()) {
428 Provider *p = d->providers.first().prov;
429 Q_ASSERT(p);
430 if (p)
431 p->fileType(d->providers.first().drf, type, ext);
432 }
433}
434
435MRESULT QPMMime::DefaultDragWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
436{
437 if (msg == DM_RENDER) {
438 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
439
440 // sanity checks
441 Q_ASSERT(d->isInitialized());
442 Q_ASSERT(xfer);
443 if (!d->isInitialized() || !xfer)
444 return (MRESULT)FALSE;
445
446 Q_ASSERT(xfer->hwndClient && xfer->pditem);
447 if (!xfer->hwndClient || !xfer->pditem)
448 return (MRESULT)FALSE;
449
450 Data::Request *req = d->requests.value(xfer->pditem->ulItemID);
451
452 // check that this item has been prepared (should always be the case
453 // because the target would never know our hwnd() otherwise)
454 Q_ASSERT(req); // prepared
455 Q_ASSERT(!req->xfer); // no DM_RENDER requested
456 if (!req || req->xfer)
457 return (MRESULT)FALSE;
458
459 DEBUG(() << "DefaultDragWorker: Got DM_RENDER to"
460 << queryHSTR(xfer->hstrSelectedRMF) << "for item" << req->index
461 << "( id " << xfer->pditem->ulItemID << ")");
462
463 QByteArray drm, drf;
464 if (!parseRMF(xfer->hstrSelectedRMF, drm, drf))
465 Q_ASSERT(/* parseRMF() = */ FALSE);
466
467 if (req->drm != drm || req->drf != drf) {
468 xfer->fsReply = DMFL_RENDERRETRY;
469 return (MRESULT)FALSE;
470 }
471
472 // indicate that DM_RENDER was requested
473 req->xfer = xfer;
474
475 DEBUG(() << "DefaultDragWorker: Will render from ["
476 << req->provider->format(drf) << "] using provider"
477 << req->provider);
478
479 // We would like to post WM_USER to ourselves to do actual rendering
480 // after we return from DM_RENDER. But we are inside DrgDrag() at this
481 // point (our DND implementation is fully synchronous by design), so
482 // PM will not deliver this message to us until we return from
483 // DrgDrag(). Thus, we have to send it.
484
485 WinSendMsg(hwnd(), WM_USER,
486 MPFROMLONG(xfer->pditem->ulItemID), MPFROMP(req));
487
488 return (MRESULT)TRUE;
489 }
490
491 if (msg == WM_USER) {
492 // sanity checks
493 Q_ASSERT(d->isInitialized());
494 if (!d->isInitialized())
495 return (MRESULT)FALSE;
496
497 ULONG itemId = LONGFROMMP(mp1);
498
499 // sanity checks
500 Data::Request *req = d->requests.value(itemId);
501 Q_ASSERT(req); // prepared
502 Q_ASSERT(req->xfer != NULL); // DM_RENDER requested
503 Q_ASSERT(!req->rendered); // not yet rendered
504 Q_ASSERT((Data::Request *) PVOIDFROMMP(mp2) == req);
505 if (!req || req->xfer == NULL || req->rendered ||
506 (Data::Request *) PVOIDFROMMP(mp2) != req)
507 return (MRESULT)FALSE;
508
509 Q_ASSERT(source() && req->provider && req->index < d->itemCnt);
510 if (!source() || !req->provider || req->index >= d->itemCnt)
511 return (MRESULT)FALSE;
512
513 DEBUG(() << "DefaultDragWorker: Got DO_RENDER for item " << req->index
514 << "( id " << req->xfer->pditem->ulItemID << ")"
515 << "provider"<< req->provider << "drm" << req->drm.data()
516 << "drf" << req->drf.data());
517
518 bool renderOk = false;
519
520 QByteArray allData = source()->data(req->provider->format(req->drf));
521 QByteArray itemData;
522
523 renderOk = req->provider->provide(req->drf, allData,
524 req->index, itemData);
525
526 if (renderOk) {
527 enum DRM { OS2File, SharedMem } drmType;
528 if (qstrcmp(req->drm, "DRM_SHAREDMEM") == 0) drmType = SharedMem;
529 else drmType = OS2File;
530
531 if (drmType == OS2File) {
532 QByteArray renderToName = queryHSTR(req->xfer->hstrRenderToName);
533 Q_ASSERT(!renderToName.isEmpty());
534 renderOk = !renderToName.isEmpty();
535 if (renderOk) {
536 DEBUG(() << "DefaultDragWorker: Will write to" << renderToName);
537 QFile file(QFile::decodeName(renderToName));
538 renderOk = file.open(QIODevice::WriteOnly);
539 if (renderOk) {
540 qint64 written = file.write(itemData, itemData.size());
541 renderOk = written == itemData.size();
542 file.close();
543 if (renderOk && req->xfer->pditem->hstrType) {
544 // since WPS ignores hstrType, write it manually
545 // to the .TYPE EA of the created file
546 qt_SetFileTypeEA(renderToName,
547 queryHSTR(req->xfer->pditem->hstrType));
548 }
549 }
550 }
551 } else {
552 PID pid;
553 TID tid;
554 bool isSameProcess = false;
555 renderOk = WinQueryWindowProcess(req->xfer->hwndClient,
556 &pid, &tid);
557 if (renderOk) {
558 PPIB ppib = NULL;
559 DosGetInfoBlocks(NULL, &ppib);
560 isSameProcess = ppib->pib_ulpid == pid;
561
562 ULONG sz = itemData.size() + sizeof (ULONG);
563 char *ptr = NULL;
564 APIRET rc = isSameProcess ?
565 DosAllocMem((PPVOID) &ptr, sz,
566 PAG_COMMIT | PAG_READ | PAG_WRITE) :
567 DosAllocSharedMem((PPVOID) &ptr, NULL, sz,
568 OBJ_GIVEABLE | PAG_COMMIT |
569 PAG_READ | PAG_WRITE);
570 renderOk = rc == 0;
571 if (renderOk && !isSameProcess) {
572 rc = DosGiveSharedMem(ptr, pid, PAG_READ);
573 renderOk = rc == 0;
574 }
575 if (renderOk) {
576 *(ULONG *) ptr = itemData.size();
577 memcpy(ptr + sizeof (ULONG), itemData.data(),
578 itemData.size());
579 req->xfer->hstrRenderToName = (HSTR) ptr;
580 req->sharedMem = ptr;
581 DEBUG(() << "DefaultDragWorker: Created shared memory "
582 "object" << (void *)ptr);
583#ifndef QT_NO_DEBUG
584 } else {
585 qWarning("DefaultDragWorker: DosAllocSharedMem/"
586 "DosGiveSharedMem failed with %ld", rc);
587#endif
588 }
589#ifndef QT_NO_DEBUG
590 } else {
591 qWarning("DefaultDragWorker: WinQueryWindowProcess failed"
592 "with 0x%lX", WinGetLastError(NULLHANDLE));
593#endif
594 }
595 }
596 }
597
598 req->rendered = true;
599 // cumulative render result
600 d->renderOk &= renderOk;
601
602 DEBUG(() << "DefaultDragWorker: renderOk" << renderOk
603 << "overall.renderOk" << d->renderOk);
604
605 // note that we don't allow the target to retry
606 USHORT reply = renderOk ? DMFL_RENDEROK : DMFL_RENDERFAIL;
607 DrgPostTransferMsg(req->xfer->hwndClient, DM_RENDERCOMPLETE,
608 req->xfer, reply, 0, false);
609
610 // DRAGTRANSFER is no more necessary, free it early
611 qt_DrgFreeDragtransfer(req->xfer);
612#if defined(QT_DEBUG_DND)
613 {
614 ULONG size = ~0, flags = 0;
615 DosQueryMem(req->xfer, &size, &flags);
616 DEBUG(("DefaultDragWorker: Freed DRAGTRANSFER: "
617 "req->xfer %p size %lu (0x%08lX) flags 0x%08lX",
618 req->xfer, size, size, flags));
619 }
620#endif
621 req->xfer = NULL;
622
623 return (MRESULT)FALSE;
624 }
625
626 if (msg == DM_ENDCONVERSATION) {
627 // we don't check that isInitialized() is true here, because WPS
628 // (and probably some other apps) may send this message after
629 // cleanup() is called up on return from DrgDrag
630
631 ULONG itemId = LONGFROMMP(mp1);
632 ULONG flags = LONGFROMMP(mp2);
633
634 // sanity check (don't assert, see above)
635 Data::Request *req = d->requests.value(itemId);
636 Q_ASSERT(req);
637 if (!req)
638 return (MRESULT)FALSE;
639
640 DEBUG(() << "DefaultDragWorker: Got DM_ENDCONVERSATION for item" << req->index
641 << "(id " << itemId << ") provider" << req->provider
642 << "drm" << req->drm << "drf" << req->drf
643 << "rendered" << req->rendered << "outdated" << !d->isInitialized());
644
645 // proceed further only if it's not an outdated request
646 // from the previous DND session
647 if (d->isInitialized()) {
648 if (!req->rendered) {
649 // we treat cancelling the render request (for any reason)
650 // as a failure
651 d->renderOk = false;
652 } else {
653 // the overall success is true only if target says Okay
654 d->renderOk &= flags == DMFL_TARGETSUCCESSFUL;
655 }
656 }
657
658 // delete the request
659 delete d->requests.take(itemId);
660
661 return (MRESULT)FALSE;
662 }
663
664 return (MRESULT)FALSE;
665}
666
667bool QPMMime::DefaultDragWorker::addProvider(const QByteArray &drf, Provider *provider,
668 ULONG itemCnt /* = 1 */)
669{
670 // make sure remaining requests from the previous DND session are deleted
671 d->cleanupRequests();
672
673 Q_ASSERT(!drf.isEmpty() && provider && itemCnt >= 1);
674 if (!drf.isEmpty() && provider && itemCnt >= 1) {
675 if (d->providers.count() == 0) {
676 // first provider
677 d->itemCnt = itemCnt;
678 d->providers.append(Data::DrfProvider(drf, provider));
679 return true;
680 }
681 // next provider, must not be exclusive and itemCnt must match
682 if (!d->exclusive && d->itemCnt == itemCnt) {
683 // ensure there are no dups (several providers for the same drf)
684 if (!d->providerFor(drf))
685 d->providers.append(Data::DrfProvider(drf, provider));
686 return true;
687 }
688 }
689 return false;
690}
691
692// static
693bool QPMMime::DefaultDragWorker::canRender(const char *drm)
694{
695 return qstrcmp(drm, "DRM_SHAREDMEM") == 0 ||
696 qstrcmp(drm, "DRM_OS2FILE") == 0;
697}
698
699//------------------------------------------------------------------------------
700
701struct QPMMime::DefaultDropWorker::Data
702{
703 struct MimeProvider
704 {
705 MimeProvider() : prov(NULL) {}
706 MimeProvider(const QString &m, Provider *p) : mime(m), prov(p) {}
707 QString mime;
708 Provider *prov;
709 };
710
711 typedef QList<MimeProvider> MimeProviderList;
712
713 Provider *providerFor(const QString &mime)
714 {
715 foreach (const MimeProvider &p, providers) {
716 if (p.mime == mime)
717 return p.prov;
718 }
719 return NULL;
720 }
721
722 bool exclusive : 1;
723 MimeProviderList providers;
724
725 bool sending_DM_RENDER : 1;
726 bool got_DM_RENDERCOMPLETE : 1;
727 USHORT flags_DM_RENDERCOMPLETE;
728
729 QEventLoop eventLoop;
730};
731
732QPMMime::DefaultDropWorker::DefaultDropWorker() : d(new Data())
733{
734 d->exclusive = false;
735 d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = false;
736 d->flags_DM_RENDERCOMPLETE = 0;
737}
738
739QPMMime::DefaultDropWorker::~DefaultDropWorker()
740{
741 delete d;
742}
743
744void QPMMime::DefaultDropWorker::cleanup(bool isAccepted)
745{
746 if (d->eventLoop.isRunning()) {
747#ifndef QT_NO_DEBUG
748 qWarning("The previous drag source didn't post DM_RENDERCOMPLETE!\n"
749 "Contact the drag source developer.");
750#endif
751 d->eventLoop.exit(1);
752 }
753
754 d->providers.clear();
755 d->exclusive = false;
756 d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = false;
757 d->flags_DM_RENDERCOMPLETE = 0;
758}
759
760bool QPMMime::DefaultDropWorker::isExclusive() const
761{
762 return d->exclusive;
763}
764
765bool QPMMime::DefaultDropWorker::hasFormat(const QString &mimeType) const
766{
767 return d->providerFor(mimeType) != NULL;
768}
769
770QStringList QPMMime::DefaultDropWorker::formats() const
771{
772 QStringList mimes;
773 foreach(const Data::MimeProvider &p, d->providers)
774 mimes << p.mime;
775 return mimes;
776}
777
778static QByteArray composeTempFileName()
779{
780 QByteArray tmpDir =
781 QFile::encodeName(QDir::toNativeSeparators(QDir::tempPath()));
782
783 static bool srandDone = false;
784 if (!srandDone) {
785 srand(time(NULL));
786 srandDone = true;
787 }
788
789 ULONG num = rand();
790 enum { Attempts = 100 };
791 int attempts = Attempts;
792
793 QString tmpName;
794 do {
795 tmpName.sprintf("%s\\%08lX.tmp", tmpDir.constData(), num);
796 if (!QFile::exists(tmpName))
797 break;
798 num = rand();
799 } while (--attempts > 0);
800
801 Q_ASSERT(attempts > 0);
802 if (attempts <= 0)
803 tmpName.clear();
804
805 return QFile::encodeName(tmpName);
806}
807
808QVariant QPMMime::DefaultDropWorker::retrieveData(const QString &mimeType,
809 QVariant::Type preferredType) const
810{
811 Q_UNUSED(preferredType);
812
813 DEBUG(() << "DefaultDropWorker::retrieveData: mimeType" << mimeType);
814
815 QVariant ret;
816
817 Q_ASSERT(info());
818 if (!info())
819 return ret;
820
821 ULONG itemCount = DrgQueryDragitemCount(info());
822 Q_ASSERT(itemCount);
823 if (!itemCount)
824 return ret;
825
826 Provider *provider = d->providerFor(mimeType);
827 if (!provider)
828 return ret;
829
830 QByteArray drf = provider->drf(mimeType);
831 Q_ASSERT(!drf.isEmpty());
832 if (drf.isEmpty())
833 return ret;
834
835 // Note: Allocating and freeing DRAGTRANSFER structures is a real mess. It's
836 // absolutely unclear how they can be reused for multiple items and/or render
837 // requests. My practice shows, that they cannot be reused at all, especially
838 // when the source and the target are the same process: if we have multiple
839 // items and use the same DRAGTRANSFER for all of them, the source will call
840 // DrgFreeDragtransfer() every time that will eventually destroy the memory
841 // object before the target finishes to work with it, so that the next
842 // DrgFreeDragtransfer() will generate a segfault in PMCTLS. Also note that
843 // using a number > 1 as an argument to DrgAllocDragtransfer() won't help
844 // because that will still allocate a single memory object. Thus, we will
845 // always allocate a new struct per every item. It seems to work.
846
847 QByteArray renderToName = composeTempFileName();
848 HSTR hstrRenderToName = DrgAddStrHandle(renderToName);
849
850 HSTR rmfOS2File =
851 DrgAddStrHandle(QString().sprintf("<DRM_OS2FILE,%s>",
852 drf.data()).toLocal8Bit());
853 HSTR rmfSharedMem =
854 DrgAddStrHandle(QString().sprintf("<DRM_SHAREDMEM,%s>",
855 drf.data()).toLocal8Bit());
856
857 MRESULT mrc;
858 bool renderOk = false;
859
860 DRAGTRANSFER *xfer = NULL;
861 QByteArray srcFileName;
862
863 QByteArray allData, itemData;
864
865 DEBUG(() << "DefaultDropWorker::retrieveData: itemCount" << itemCount);
866
867 for (ULONG i = 0; i < itemCount; ++i) {
868 DRAGITEM *item = DrgQueryDragitemPtr(info(), i);
869 Q_ASSERT(item);
870 if (!item) {
871 renderOk = false;
872 break;
873 }
874
875 DEBUG(() << "DefaultDropWorker::retrieveData: item" << i
876 << "hstrRMF" << queryHSTR(item->hstrRMF));
877
878 enum { None, OS2File, SharedMem } drm = None;
879 bool needToTalk = true;
880
881 // determine the mechanism to use (prefer DRM_SHAREDMEM)
882
883 if (DrgVerifyRMF(item, "DRM_SHAREDMEM", drf) &&
884 DrgVerifyRMF(item, "DRM_SHAREDMEM", "DRF_POINTERDATA"))
885 drm = SharedMem;
886 if (DrgVerifyRMF(item, "DRM_OS2FILE", drf)) {
887 srcFileName = querySourceNameFull(item);
888 // If the source provides the full file name, we prefer DRM_OS2FILE
889 // even if there is also DRM_SHAREDMEM available because we don't
890 // need to do any communication in this case at all. This will help
891 // with some native drag sources (such as DragText) that cannot send
892 // DM_RENDERCOMPLETE synchronously (before we return from DM_DROP)
893 // and would hang otherwise.
894 if (!srcFileName.isEmpty()) {
895 needToTalk = false;
896 drm = OS2File;
897 } else if (drm == None) {
898 srcFileName = renderToName;
899 drm = OS2File;
900 }
901 }
902 Q_ASSERT(drm != None);
903 if (drm == None) {
904 renderOk = false;
905 break;
906 }
907
908 if (needToTalk) {
909 // need to perform a conversation with the source,
910 // allocate a new DRAGTRANSFER structure for each item
911 xfer = DrgAllocDragtransfer(1);
912 Q_ASSERT(xfer);
913 if (!xfer) {
914 renderOk = false;
915 break;
916 }
917
918 xfer->cb = sizeof(DRAGTRANSFER);
919 xfer->hwndClient = hwnd();
920 xfer->ulTargetInfo = (ULONG) info();
921 xfer->usOperation = info()->usOperation;
922
923 xfer->pditem = item;
924 if (drm == OS2File) {
925 xfer->hstrSelectedRMF = rmfOS2File;
926 xfer->hstrRenderToName = hstrRenderToName;
927 } else {
928 xfer->hstrSelectedRMF = rmfSharedMem;
929 xfer->hstrRenderToName = 0;
930 }
931
932 DEBUG(() << "DefaultDropWorker: Will use"
933 << queryHSTR(xfer->hstrSelectedRMF) << "to render item" << item);
934
935 mrc = (MRESULT)TRUE;
936 if ((item->fsControl & DC_PREPARE) ||
937 (item->fsControl & DC_PREPAREITEM)) {
938 DEBUG(("DefaultDropWorker: Sending DM_RENDERPREPARE to 0x%08lX...",
939 info()->hwndSource));
940 mrc = DrgSendTransferMsg(info()->hwndSource, DM_RENDERPREPARE,
941 MPFROMP (xfer), 0);
942 DEBUG(("DefaultDropWorker: Finisned sending DM_RENDERPREPARE\n"
943 " mrc %p xfer->fsReply 0x%08hX", mrc, xfer->fsReply));
944 renderOk = (BOOL) mrc;
945 }
946
947 if ((BOOL) mrc) {
948 DEBUG(("DefaultDropWorker: Sending DM_RENDER to 0x%08lX...",
949 item->hwndItem));
950 d->sending_DM_RENDER = true;
951 mrc = DrgSendTransferMsg(item->hwndItem, DM_RENDER,
952 MPFROMP(xfer), 0);
953 d->sending_DM_RENDER = false;
954 DEBUG(("DefaultDropWorker: Finisned Sending DM_RENDER\n"
955 " mrc %p xfer->fsReply 0x%hX got_DM_RENDERCOMPLETE %d",
956 mrc, xfer->fsReply, d->got_DM_RENDERCOMPLETE));
957
958 if (!(BOOL) mrc || d->got_DM_RENDERCOMPLETE) {
959 if (d->got_DM_RENDERCOMPLETE)
960 renderOk = (d->flags_DM_RENDERCOMPLETE & DMFL_RENDEROK);
961 else
962 renderOk = false;
963 } else {
964 // synchronously wait for DM_RENDERCOMPLETE
965 DEBUG(() << "DefaultDropWorker: Waiting for DM_RENDERCOMPLETE...");
966 int result = d->eventLoop.exec();
967 DEBUG(("DefaultDropWorker: Finished waiting for "
968 "DM_RENDERCOMPLETE (result %d)\n"
969 " got_DM_RENDERCOMPLETE %d usFS 0x%hX",
970 result, d->got_DM_RENDERCOMPLETE, d->flags_DM_RENDERCOMPLETE));
971 Q_UNUSED(result);
972 // JFTR: at this point, cleanup() might have been called,
973 // as a result of either program exit or getting another
974 // DM_DRAGOVER (if the source app has crashed) before getting
975 // DM_RENDERCOMPLETE from the source. Use data members with
976 // care!
977 renderOk = d->got_DM_RENDERCOMPLETE &&
978 (d->flags_DM_RENDERCOMPLETE & DMFL_RENDEROK);
979 }
980
981 d->got_DM_RENDERCOMPLETE = false;
982 }
983 } else {
984 DEBUG(() << "DefaultDropWorker: Source supports < DRM_OS2FILE,"
985 << drf << "> and provides a file" << srcFileName
986 << "for item" << item << "(no need to render)");
987 renderOk = true;
988 }
989
990 if (renderOk) {
991 if (drm == OS2File) {
992 DEBUG(() << "DefaultDragWorker: Will read from" << srcFileName);
993 QFile file(QFile::decodeName(srcFileName));
994 renderOk = file.open(QIODevice::ReadOnly);
995 if (renderOk) {
996 itemData = file.readAll();
997 renderOk = file.error() == QFile::NoError;
998 file.close();
999 }
1000 if (needToTalk) {
1001 // only delete the file if we provided it for rendering
1002 bool ok = file.remove();
1003 Q_ASSERT((ok = ok));
1004 Q_UNUSED(ok);
1005 }
1006 } else {
1007 Q_ASSERT(xfer->hstrRenderToName);
1008 renderOk = xfer->hstrRenderToName != 0;
1009 if (renderOk) {
1010 const char *ptr = (const char *) xfer->hstrRenderToName;
1011 ULONG size = ~0;
1012 ULONG flags = 0;
1013 APIRET rc = DosQueryMem((PVOID) ptr, &size, &flags);
1014 renderOk = rc == 0;
1015 if (renderOk) {
1016 DEBUG(("DefaultDropWorker: Got shared data %p size %lu "
1017 "(0x%08lX) flags 0x%08lX", ptr, size, size, flags));
1018 Q_ASSERT((flags & (PAG_COMMIT | PAG_READ | PAG_BASE)) ==
1019 (PAG_COMMIT | PAG_READ | PAG_BASE));
1020 renderOk = (flags & (PAG_COMMIT | PAG_READ | PAG_BASE)) ==
1021 (PAG_COMMIT | PAG_READ | PAG_BASE);
1022#ifndef QT_NO_DEBUG
1023 } else {
1024 qWarning("DefaultDropWorker: DosQueryMem failed with %ld", rc);
1025#endif
1026 }
1027 if (renderOk) {
1028 ULONG realSize = *(ULONG *) ptr;
1029 DEBUG(() << "DefaultDropWorker: realSize" << realSize);
1030 Q_ASSERT(realSize <= size);
1031 renderOk = realSize <= size;
1032 if (renderOk) {
1033 itemData.resize(realSize);
1034 memcpy(itemData.data(), ptr + sizeof(ULONG), realSize);
1035 }
1036 }
1037 // free memory only if it is given by another process,
1038 // otherwise DefaultDragWorker will free it
1039 if (flags & PAG_SHARED)
1040 DosFreeMem((PVOID) xfer->hstrRenderToName);
1041 }
1042 }
1043 }
1044
1045 if (renderOk)
1046 renderOk = provider->provide(mimeType, i, itemData, allData);
1047
1048 if (needToTalk) {
1049 // free the DRAGTRANSFER structure
1050 DrgFreeDragtransfer(xfer);
1051#if defined(QT_DEBUG_DND)
1052 {
1053 ULONG size = ~0, flags = 0;
1054 DosQueryMem(xfer, &size, &flags);
1055 DEBUG(("DefaultDropWorker: Freed DRAGTRANSFER: "
1056 "xfer=%p, size=%lu(0x%08lX), flags=0x%08lX",
1057 xfer, size, size, flags));
1058 }
1059#endif
1060 xfer = NULL;
1061 }
1062
1063 if (!renderOk)
1064 break;
1065 }
1066
1067 DEBUG(() << "DefaultDropWorker: renderOk" << renderOk);
1068
1069 DrgDeleteStrHandle(rmfSharedMem);
1070 DrgDeleteStrHandle(rmfOS2File);
1071 DrgDeleteStrHandle(hstrRenderToName);
1072
1073 if (renderOk)
1074 ret = allData;
1075
1076 return ret;
1077}
1078
1079MRESULT QPMMime::DefaultDropWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
1080{
1081 switch (msg) {
1082 case DM_RENDERCOMPLETE: {
1083 // sanity check
1084 Q_ASSERT(info());
1085 if (!info())
1086 return (MRESULT)FALSE;
1087
1088 DEBUG(("DefaultDropWorker: Got DM_RENDERCOMPLETE"));
1089 d->got_DM_RENDERCOMPLETE = true;
1090 d->flags_DM_RENDERCOMPLETE = SHORT1FROMMP(mp2);
1091
1092 if (d->sending_DM_RENDER)
1093 {
1094#ifndef QT_NO_DEBUG
1095 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
1096 qWarning("Drag item 0x%08lX sent DM_RENDERCOMPLETE w/o first "
1097 "replying to DM_RENDER!\n"
1098 "Contact the drag source developer.",
1099 xfer->pditem->hwndItem);
1100#endif
1101 return (MRESULT)FALSE;
1102 }
1103
1104 // stop synchronous waiting for DM_RENDERCOMPLETE
1105 if (d->eventLoop.isRunning())
1106 d->eventLoop.exit();
1107 return (MRESULT)FALSE;
1108 }
1109 default:
1110 break;
1111 }
1112
1113 return (MRESULT)FALSE;
1114}
1115
1116bool QPMMime::DefaultDropWorker::addProvider(const QString &mimeType,
1117 Provider *provider)
1118{
1119 Q_ASSERT(!mimeType.isEmpty() && provider);
1120 if (!mimeType.isEmpty() && provider && !d->exclusive) {
1121 // ensure there are no dups (several providers for the same mime)
1122 if (!d->providerFor(mimeType))
1123 d->providers.append(Data::MimeProvider(mimeType, provider));
1124 return true;
1125 }
1126 return false;
1127}
1128
1129bool QPMMime::DefaultDropWorker::addExclusiveProvider(const QString &mimeType,
1130 Provider *provider)
1131{
1132 Q_ASSERT(!mimeType.isEmpty() && provider);
1133 if (!mimeType.isEmpty() && provider && !d->exclusive) {
1134 d->exclusive = true;
1135 d->providers.clear();
1136 d->providers.append(Data::MimeProvider(mimeType, provider));
1137 return true;
1138 }
1139 return false;
1140}
1141
1142// static
1143bool QPMMime::DefaultDropWorker::canRender(DRAGITEM *item, const char *drf)
1144{
1145 return DrgVerifyRMF(item, "DRM_OS2FILE", drf) ||
1146 (DrgVerifyRMF(item, "DRM_SHAREDMEM", drf) &&
1147 DrgVerifyRMF(item, "DRM_SHAREDMEM", "DRF_POINTERDATA"));
1148}
1149
1150/*! \internal
1151
1152 Parses the rendering mechanism/format specification of the given \a item
1153 and stores only those mechanism branches in the given \a list that represent
1154 mechanisms supported by this worker. Returns false if fails to parse the
1155 RMF specification. Note that if no supported mechanisms are found, true is
1156 returned but the \a list will simply contain zero items.
1157
1158 \note The method clears the given \a list variable before proceeding.
1159
1160 \sa canRender(), PMMime::parseRMFs()
1161*/
1162// static
1163bool QPMMime::DefaultDropWorker::getSupportedRMFs(DRAGITEM *item,
1164 QList<QByteArrayList> &list)
1165{
1166 if (!parseRMFs(item->hstrRMF, list))
1167 return false;
1168
1169 for (QList<QByteArrayList>::iterator rmf = list.begin(); rmf != list.end();) {
1170 QByteArrayList::iterator mf = rmf->begin();
1171 Q_ASSERT(mf != rmf->end());
1172 const char *drm = *mf;
1173 if (qstrcmp(drm, "DRM_OS2FILE") == 0) {
1174 ++rmf;
1175 continue;
1176 }
1177 if (qstrcmp(drm, "DRM_SHAREDMEM") == 0) {
1178 // accept DRM_SHAREDMEM only if there is DRF_POINTERDATA
1179 for(; mf != rmf->end(); ++mf) {
1180 const char *drf = *mf;
1181 if (qstrcmp(drf, "DRF_POINTERDATA") == 0)
1182 break;
1183 }
1184 if (mf != rmf->end()) {
1185 ++rmf;
1186 continue;
1187 }
1188 }
1189 // remove the unsupported mechanism branch from the list
1190 rmf = list.erase(rmf);
1191 }
1192
1193 return true;
1194}
1195
1196#endif // !QT_NO_DRAGANDDROP
1197
1198//------------------------------------------------------------------------------
1199
1200class QPMMimeList
1201{
1202public:
1203 QPMMimeList();
1204 ~QPMMimeList();
1205 void addMime(QPMMime *mime);
1206 void removeMime(QPMMime *mime);
1207 QList<QPMMime*> mimes();
1208
1209private:
1210 void init();
1211 bool initialized;
1212 QList<QPMMime*> list;
1213};
1214
1215Q_GLOBAL_STATIC(QPMMimeList, theMimeList);
1216
1217
1218/*!
1219 \class QPMMime
1220 \brief The QMPMime class maps open-standard MIME to OS/2 PM Clipboard
1221 formats.
1222 \ingroup io
1223 \ingroup draganddrop
1224 \ingroup misc
1225
1226 Qt's drag-and-drop and clipboard facilities use the MIME standard.
1227 On X11, this maps trivially to the Xdnd protocol, but on OS/2
1228 although some applications use MIME types to describe clipboard
1229 formats, others use arbitrary non-standardized naming conventions,
1230 or unnamed built-in formats of the Presentation Manager.
1231
1232 By instantiating subclasses of QPMMime that provide conversions between OS/2
1233 PM Clipboard and MIME formats, you can convert proprietary clipboard formats
1234 to MIME formats.
1235
1236 Qt has predefined support for the following PM Clipboard formats (custom
1237 formats registered in the system atom table by name are given in double
1238 quotes):
1239
1240 \table
1241 \header \o PM Format \o Equivalent MIME type
1242 \row \o \c CF_TEXT \o \c text/plain (system codepage,
1243 zero-terminated string)
1244 \row \o \c "text/unicode" \o \c text/plain (16-bit Unicode,
1245 zero-terminated string, Mozilla-compatible)
1246 \row \o \c "text/html" \o \c text/html (16-bit Unicode,
1247 zero-terminated string, Mozilla-compatible)
1248 \row \o \c CF_BITMAP \o \c{image/xyz}, where \c xyz is
1249 a \l{QImageWriter::supportedImageFormats()}{Qt image format}
1250 \row \o \c "x-mime:<mime>" \o data in the format corresponding to the given
1251 MIME type \c <mime>
1252 \endtable
1253
1254 Note that all "x-mime:<mime>" formats use the CFI_POINTER storage type. That
1255 is, the clipboard contains a pointer to the memory block containing the MIME
1256 data in the corresponding format. The first 4 bytes of this memory block
1257 always contain the length of the subsequent MIME data array, in bytes.
1258
1259 An example use of this class by the user application would be to map the
1260 PM Metafile clipboard format (\c CF_METAFILE) to and from the MIME type
1261 \c{image/x-metafile}. This conversion might simply be adding or removing a
1262 header, or even just passing on the data. See \l{Drag and Drop} for more
1263 information on choosing and definition MIME types.
1264*/
1265
1266/*!
1267Constructs a new conversion object, adding it to the globally accessed
1268list of available converters.
1269*/
1270QPMMime::QPMMime()
1271{
1272 theMimeList()->addMime(this);
1273}
1274
1275/*!
1276Destroys a conversion object, removing it from the global
1277list of available converters.
1278*/
1279QPMMime::~QPMMime()
1280{
1281 theMimeList()->removeMime(this);
1282}
1283
1284/*!
1285 Registers the MIME type \a mime, and returns an ID number
1286 identifying the format on OS/2. Intended to be used by QPMMime
1287 implementations for registering custom clipboard formats they use.
1288*/
1289// static
1290ULONG QPMMime::registerMimeType(const QString &mime)
1291{
1292 ULONG cf = WinAddAtom(WinQuerySystemAtomTable(), mime.toLocal8Bit());
1293 if (!cf) {
1294#ifndef QT_NO_DEBUG
1295 qWarning("QPMMime: WinAddAtom failed with 0x%lX",
1296 WinGetLastError(NULLHANDLE));
1297#endif
1298 return 0;
1299 }
1300
1301 return cf;
1302}
1303
1304/*!
1305 Unregisters the MIME type identified by \a id which was previously
1306 registered with registerMimeType().
1307*/
1308// static
1309void QPMMime::unregisterMimeType(ULONG mimeId)
1310{
1311 WinDeleteAtom(WinQuerySystemAtomTable(), mimeId);
1312}
1313
1314/*!
1315 Returns a list of all currently defined QPMMime objects.
1316*/
1317// static
1318QList<QPMMime*> QPMMime::all()
1319{
1320 return theMimeList()->mimes();
1321}
1322
1323/*!
1324 Allocates a block of shared memory of the given size and returns the address
1325 of this block. This memory block may be then filled with data and returned
1326 by convertFromMimeData() as the value of the CFI_POINTER type.
1327*/
1328// static
1329ULONG QPMMime::allocateMemory(size_t size)
1330{
1331 if (size == 0)
1332 return 0;
1333
1334 ULONG data = 0;
1335
1336 // allocate giveable memory for the array
1337 APIRET arc = DosAllocSharedMem((PVOID *)&data, NULL, size,
1338 PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE);
1339 if (arc != NO_ERROR) {
1340#ifndef QT_NO_DEBUG
1341 qWarning("QPMMime::allocateMemory: DosAllocSharedMem failed with %lu", arc);
1342#endif
1343 return 0;
1344 }
1345
1346 return data;
1347}
1348
1349/*!
1350 Frees memory allocated by allocateMemory(). Normally, not used because the
1351 CFI_POINTER memory blocks are owned by the system after
1352 convertFromMimeData() returns.
1353*/
1354// static
1355void QPMMime::freeMemory(ULONG addr)
1356{
1357 DosFreeMem((PVOID)addr);
1358}
1359
1360/*!
1361 \fn QList<MimeCFPair> QPMMime::formatsForMimeData(const QMimeData *mimeData) const
1362
1363 Returns a list of ULONG values representing the different OS/2 PM
1364 clipboard formats that can be provided for the \a mimeData, in order of
1365 precedence (the most suitable format goes first), or an empty list if
1366 neither of the mime types provided by \a mimeData is supported by this
1367 converter. Note that each item in the returned list is actually a pair
1368 consisting of the mime type name and the corresponding format identifier.
1369
1370 All subclasses must reimplement this pure virtual function.
1371*/
1372
1373/*!
1374 \fn bool QPMMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1375 ULONG &flags, ULONG *data) const
1376
1377 Converts the \a mimeData to the specified \a format.
1378
1379 If \a data is not NULL, a handle to the converted data should be then placed
1380 in a variable pointed to by \a data and with the necessary flags describing
1381 the handle returned in the \a flags variable.
1382
1383 The following flags describing the data storage type are recognized:
1384
1385 \table
1386 \row \o \c CFI_POINTER \o \a data is a pointer to a block of memory
1387 allocated with QPMMime::allocateMemory()
1388 \row \o \c CFI_HANDLE \o \a data is a handle to the appropriate
1389 PM resource
1390 \endtable
1391
1392 If \a data is NULL then a delayed conversion is requested by the caller.
1393 The implementation should return the appropriate flags in the \a flags
1394 variable and may perform the real data conversion later when this method is
1395 called again with \a data being non-NULL.
1396
1397 Return true if the conversion was successful.
1398
1399 All subclasses must reimplement this pure virtual function.
1400*/
1401
1402/*!
1403 \fn QList<MimeCFPair> QPMMime::mimesForFormats(const QList<ULONG> &formats) const
1404
1405 Returns a list of mime types that will be created form the specified \a list
1406 of \a formats, in order of precedence (the most suitable mime type comes
1407 first), or an empty list if neither of the \a formats is supported by this
1408 converter. Note that each item in the returned list is actually a pair
1409 consisting of the mime type name and the corresponding format identifier.
1410
1411 All subclasses must reimplement this pure virtual function.
1412*/
1413
1414/*!
1415 \fn QVariant QPMMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
1416 const QString &mimeType,
1417 QVariant::Type preferredType) const
1418
1419 Returns a QVariant containing the converted from the \a data in the
1420 specified \a format with the given \a flags to the requested \a mimeType. If
1421 possible the QVariant should be of the \a preferredType to avoid needless
1422 conversions.
1423
1424 All subclasses must reimplement this pure virtual function.
1425*/
1426
1427/*!
1428 \fn DragWorker *QPMMime::dragWorkerFor(const QString &mimeType,
1429 QMimeData *mimeData)
1430
1431 Returns a DragWorker instance suitable for converting \a mimeType to a set
1432 of drag items for the Direct Manipulation (Drag And Drop) session. If this
1433 converter does not support the given MIME type, this method should return 0.
1434
1435 See the QPMMime::DragWorker class description for more information.
1436
1437 The default implementation of this method returns 0.
1438*/
1439
1440/*!
1441 \fn DropWorker *QPMMime::dropWorkerFor(DRAGINFO *info)
1442
1443 Returns a DropWorker instance suitable for converting drag items represented
1444 by the \a info structure to MIME data when these items are dropped on a Qt
1445 widget at the end of the Direct manipulation session. If this converter does
1446 not support the given set of drag items, this method should return 0.
1447
1448 See the QPMMime::DropWorker class description for more information.
1449
1450 The default implementation of this method returns 0.
1451*/
1452
1453/*!
1454 \class QPMMime::DragWorker
1455
1456 This class is responsible for providing the drag items for the Direct
1457 Manipulation session.
1458
1459 Drag workers can be super exclusive (solely responsible for converting the
1460 given mime type to a set of DRAGITEM structures), exclusive (cannot coexist
1461 with other workers but don't manage the DRAGINFO/DRAGITEM creation), or
1462 cooperative (can coexist with other drag workers and share the same set of
1463 DRAGITEM structures in order to represent different mime data types). As
1464 opposed to super exclusive workers (identified by isExclusive() returning
1465 TRUE and by itemCount() returning zero), exclusive and cooperative workers
1466 do not create DRAGINFO/DRAGITEM structures on their own, they implement a
1467 subset of methods that is used by the drag manager to fill drag structures
1468 it creates.
1469
1470 If a super exlusive or an exclusive worker is encoundered when starting the
1471 drag session, it will be used only if there are no any other workers found
1472 for \b other mime types of the object being dragged. If a cooperative worker
1473 with the item count greater than one is encountered, it will be used only if
1474 all other found workers are also cooperative and require the same number of
1475 items. In both cases, if the above conditions are broken, the respective
1476 workers are discarded (ignored). Instead, a special fall-back cooperative
1477 worker (that requires a single DRAGITEM, supports any mime type and can
1478 coexist with other one-item cooperative workers) will be used for the given
1479 mime type.
1480
1481 \note Subclasses must NOT free the DRAGINFO structure they allocated and
1482 returned by createDragInfo().
1483
1484 \note Every exclusive drag worker must implement createDragInfo() and must
1485 not implement composeFormatSting()/canRender()/prepare()/defaultFileType().
1486 And vice versa, every cooperative drag worker must implement the latter
1487 three functions but not the former two.
1488
1489 \note The return value of cleanup() is whether the Move operation is
1490 disallowed by this worker or not (if the worker doesn't participate in the
1491 DND session, it should return FALSE, to let other workers allow Move).
1492*/
1493
1494/*!
1495 \class QPMMime::DefaultDragWorker
1496
1497 This class is a DragWorker implementation that supports standard
1498 DRM_SHAREDMEM and DRM_OS2FILE and rendering mechanisms. It uses
1499 QPMMime::DefaultDragWorker::Provider subclasses to map mime types of the
1500 object being dragged to rendering formats and apply preprocessing of data
1501 before rendering.
1502*/
1503
1504/*!
1505 \class QPMMime::DropWorker
1506
1507 This class is responsible for interpreting the drag items after the Direct
1508 Manipulation session ends up in a drop.
1509
1510 Drop workers can be exclusive (solely responsible for converting the given
1511 set of DRAGITEM structures) or cooperative (can coexist with other drop
1512 workers in order to produce different mime data types from the same set of
1513 DRAGITEM structures). If an exclusive drop worker is encountered when
1514 processing the drop event, all other workers are silently ignored.
1515
1516 \note Subclasses must NOT free the DRAGINFO structure pointed to by info().
1517
1518 \note Subclasses must NOT send DM_ENDCONVERSATION to the source.
1519*/
1520
1521/*!
1522 \class QPMMime::DefaultDropWorker
1523
1524 This class is a DropWorker implementation that supports standard
1525 DRM_SHAREDMEM and DRM_OS2FILE and rendering mechanisms. It uses
1526 QPMMime::DefaultDropWorker::Provider subclasses to map various rendering
1527 formats to particular mime types and apply postprocessing of data after
1528 rendering.
1529*/
1530
1531// static
1532QList<QPMMime::Match> QPMMime::allConvertersFromFormats(const QList<ULONG> &formats)
1533{
1534 QList<Match> matches;
1535
1536 QList<QPMMime*> mimes = theMimeList()->mimes();
1537 foreach(QPMMime *mime, mimes) {
1538 QList<MimeCFPair> fmts = mime->mimesForFormats(formats);
1539 int priority = 0;
1540 foreach (MimeCFPair fmt, fmts) {
1541 ++priority;
1542 QList<Match>::iterator it = matches.begin();
1543 for (; it != matches.end(); ++it) {
1544 Match &match = *it;
1545 if (match.mime == fmt.mime) {
1546 // replace if priority is higher, ignore otherwise
1547 if (priority < match.priority) {
1548 match.converter = mime;
1549 match.format = fmt.format;
1550 match.priority = priority;
1551 }
1552 break;
1553 }
1554 }
1555 if (it == matches.end()) {
1556 matches += Match(mime, fmt.mime, fmt.format, priority);
1557 }
1558 }
1559 }
1560
1561 return matches;
1562}
1563
1564// static
1565QList<QPMMime::Match> QPMMime::allConvertersFromMimeData(const QMimeData *mimeData)
1566{
1567 QList<Match> matches;
1568
1569 QList<QPMMime*> mimes = theMimeList()->mimes();
1570 foreach(QPMMime *mime, mimes) {
1571 QList<MimeCFPair> fmts = mime->formatsForMimeData(mimeData);
1572 int priority = 0;
1573 foreach (MimeCFPair fmt, fmts) {
1574 ++priority;
1575 QList<Match>::iterator it = matches.begin();
1576 for (; it != matches.end(); ++it) {
1577 Match &match = *it;
1578 if (mime == mimes.last()) { // QPMMimeAnyMime?
1579 if (match.mime == fmt.mime){
1580 // we assume that specialized converters (that come
1581 // first) provide a more precise conversion than
1582 // QPMMimeAnyMime and don't let it get into the list in
1583 // order to avoid unnecessary duplicate representations
1584 break;
1585 }
1586 }
1587 if (match.format == fmt.format) {
1588 // replace if priority is higher, ignore otherwise
1589 if (priority < match.priority) {
1590 match.converter = mime;
1591 match.mime = fmt.mime;
1592 match.priority = priority;
1593 }
1594 break;
1595 }
1596 }
1597 if (it == matches.end()) {
1598 matches += Match(mime, fmt.mime, fmt.format, priority);
1599 }
1600 }
1601 }
1602
1603 return matches;
1604}
1605
1606
1607/*!
1608 Returns a string representation of the given clipboard format.
1609 The string representation is obtained by querying the system atom table.
1610*/
1611// static
1612QString QPMMime::formatName(ULONG format)
1613{
1614 QString name;
1615 HATOMTBL tbl = WinQuerySystemAtomTable();
1616 if (tbl != NULLHANDLE) {
1617 ULONG len = WinQueryAtomLength(tbl, format);
1618 QByteArray atom(len, '\0');
1619 WinQueryAtomName(tbl, format, atom.data(), atom.size() + 1);
1620 name = QString::fromLocal8Bit(atom);
1621 }
1622 return name;
1623}
1624
1625#if !defined(QT_NO_DRAGANDDROP)
1626
1627/*!
1628 Returns a string represented by \a hstr.
1629*/
1630// static
1631QByteArray QPMMime::queryHSTR(HSTR hstr)
1632{
1633 QByteArray str;
1634 ULONG len = DrgQueryStrNameLen(hstr);
1635 if (len) {
1636 str.resize(len);
1637 DrgQueryStrName(hstr, str.size() + 1 /* \0 */, str.data());
1638 }
1639 return str;
1640}
1641
1642/*!
1643 Returns a string that is a concatenation of \c hstrContainerName and
1644 \c hstrSourceName fileds of the given \a item structure.
1645*/
1646// static
1647QByteArray QPMMime::querySourceNameFull(DRAGITEM *item)
1648{
1649 QByteArray fullName;
1650 if (!item)
1651 return fullName;
1652
1653 ULONG pathLen = DrgQueryStrNameLen(item->hstrContainerName);
1654 ULONG nameLen = DrgQueryStrNameLen(item->hstrSourceName);
1655 if (!pathLen || !nameLen)
1656 return fullName;
1657
1658 // Take into account that the container name may lack the trailing slash
1659 fullName.resize(pathLen + nameLen + 1);
1660
1661 DrgQueryStrName(item->hstrContainerName, pathLen + 1, fullName.data());
1662 if (fullName.at(pathLen - 1) != '\\') {
1663 fullName[(size_t)pathLen] = '\\';
1664 ++pathLen;
1665 }
1666
1667 DrgQueryStrName(item->hstrSourceName, nameLen + 1, fullName.data() + pathLen);
1668
1669 fullName.truncate(qstrlen(fullName));
1670
1671 return fullName;
1672}
1673
1674/*!
1675 Checks that the given drag \a item supports the DRM_OS2FILE rendering
1676 mechanism and can be rendered by a target w/o involving the source (i.e.,
1677 DRM_OS2FILE is the first supported format and a valid file name with full
1678 path is provided). If the function returns TRUE, \a fullName (if not NULL)
1679 will be assigned the item's full source file name (composed from
1680 \c hstrContainerName and \c hstrSourceName fields).
1681 */
1682// static
1683bool QPMMime::canTargetRenderAsOS2File(DRAGITEM *item, QByteArray *fullName /*= 0*/)
1684{
1685 if (!item)
1686 return false;
1687
1688 if (item->fsControl & (DC_PREPARE | DC_PREPAREITEM))
1689 return false;
1690
1691 {
1692 // DrgVerifyNativeRMF doesn't work on my system (ECS 1.2.1 GA):
1693 // it always returns FALSE regardless of arguments. Use simplified
1694 // hstrRMF parsing to determine whether DRM_OS2FILE is the native
1695 // mechanism or not (i.e. "^\s*[\(<]\s*DRM_OS2FILE\s*,.*").
1696
1697 QByteArray rmf = queryHSTR(item->hstrRMF);
1698 bool ok = false;
1699 int i = rmf.indexOf("DRM_OS2FILE");
1700 if (i >= 1) {
1701 for (int j = i - 1; j >= 0; --j) {
1702 char ch = rmf[j];
1703 if (ch == ' ')
1704 continue;
1705 if (ch == '<' || ch == '(') {
1706 if (ok)
1707 return false;
1708 ok = true;
1709 } else {
1710 return false;
1711 }
1712 }
1713 }
1714 if (ok) {
1715 ok = false;
1716 int drmLen = strlen("DRM_OS2FILE");
1717 for (int j = i + drmLen; j < rmf.size(); ++j) {
1718 char ch = rmf[j];
1719 if (ch == ' ')
1720 continue;
1721 if (ch == ',') {
1722 ok = true;
1723 break;
1724 }
1725 return false;
1726 }
1727 }
1728 if (!ok)
1729 return false;
1730 }
1731
1732 QByteArray srcFullName = querySourceNameFull(item);
1733 if (srcFullName.isEmpty())
1734 return false;
1735
1736 QByteArray srcFullName2(srcFullName.size(), '\0');
1737 APIRET rc = DosQueryPathInfo(srcFullName, FIL_QUERYFULLNAME,
1738 srcFullName2.data(), srcFullName2.size() + 1);
1739 if (rc != 0)
1740 return false;
1741
1742 QString s1 = QFile::decodeName(srcFullName);
1743 QString s2 = QFile::decodeName(srcFullName2);
1744
1745 if (s1.compare(s2, Qt::CaseInsensitive) != 0)
1746 return false;
1747
1748 if (fullName)
1749 *fullName = srcFullName;
1750 return true;
1751}
1752
1753/*!
1754 Parses the given \a rmfs list (full rendering mechanism/format specification)
1755 and builds a \a list of mechanism branches. Each mechanism branch is also a
1756 list, where the first item is the mechahism name and all subsequent items are
1757 formats supported by this mechanism. Returns false if fails to parse \a rmf.
1758
1759 \note The method clears the given \a list variable before proceeding.
1760*/
1761// static
1762bool QPMMime::parseRMFs(HSTR rmfs, QList<QByteArrayList> &list)
1763{
1764 // The format of the RMF list is "elem {,elem,elem...}"
1765 // where elem is "(mechanism{,mechanism...}) x (format{,format...})"
1766 // or "<mechanism,format>".
1767 // We use a simple FSM to parse it. In terms of FSM, the format is:
1768 //
1769 // STRT ( BCM m CMCH echanism CMCH , NCM m CMCH echanism CMCH ) ECM x
1770 // SCMF ( BCF f CFCH ormat CFCH , NCF f CFCH ormat CFCH ) ECF , STRT
1771 // STRT < BP m PMCH echanism PMCH , SPMF f PFCH ormat PFCH > EP , STRT
1772
1773 QByteArray str = queryHSTR(rmfs);
1774 uint len = str.length();
1775
1776 enum {
1777 // states
1778 STRT = 0, BCM, CMCH, NCM, ECM, SCMF, BCF, CFCH, NCF, ECF,
1779 BP, PMCH, SPMF, PFCH, EP,
1780 STATES_COUNT,
1781 // pseudo states
1782 Err, Skip,
1783 // inputs
1784 obr = 0, cbr, xx, lt, gt, cm, any, ws,
1785 INPUTS_COUNT,
1786 };
1787
1788 static const char Chars[] = { '(', ')', 'x', 'X', '<', '>', ',', ' ', 0 };
1789 static const char Inputs[] = { obr, cbr, xx, xx, lt, gt, cm, ws };
1790 static const uchar Fsm [STATES_COUNT] [INPUTS_COUNT] = {
1791 /* 0 obr 1 cbr 2 xx 3 lt 4 gt 5 cm 6 any 7 ws */
1792/* STRT 0 */ { BCM, Err, Err, BP, Err, Err, Err, Skip },
1793/* BCM 1 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1794/* CMCH 2 */ { Err, ECM, CMCH, Err, Err, NCM, CMCH, CMCH },
1795/* NCM 3 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1796/* ECM 4 */ { Err, Err, SCMF, Err, Err, Err, Err, Skip },
1797/* SCMF 5 */ { BCF, Err, Err, Err, Err, Err, Err, Skip },
1798/* BCF 6 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1799/* CFCH 7 */ { Err, ECF, CFCH, Err, Err, NCF, CFCH, CFCH },
1800/* NCF 8 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1801/* ECF 9 */ { Err, Err, Err, Err, Err, STRT, Err, Skip },
1802/* BP 10 */ { Err, Err, Err, Err, Err, Err, PMCH, Skip },
1803/* PMCH 11 */ { Err, Err, PMCH, Err, Err, SPMF, PMCH, PMCH },
1804/* SPMF 12 */ { Err, Err, Err, Err, Err, Err, PFCH, Skip },
1805/* PFCH 13 */ { Err, Err, PFCH, Err, EP, Err, PFCH, PFCH },
1806/* EP 14 */ { Err, Err, Err, Err, Err, STRT, Err, Skip }
1807 };
1808
1809 list.clear();
1810
1811 QList<QByteArrayList*> refList;
1812
1813 QByteArray buf;
1814 QList<QByteArrayList>::iterator rmf;
1815
1816 uint state = STRT;
1817 uint start = 0, end = 0, space = 0;
1818
1819 for (uint i = 0; i < len && state != Err ; ++i) {
1820 char ch = str[i];
1821 char *p = strchr(Chars, ch);
1822 uint input = p ? Inputs[p - Chars] : any;
1823 uint newState = Fsm[state][input];
1824 switch (newState) {
1825 case Skip:
1826 continue;
1827 case CMCH:
1828 case CFCH:
1829 case PMCH:
1830 case PFCH:
1831 if (state != newState)
1832 start = end = i;
1833 ++end;
1834 // accumulate trailing space for truncation
1835 if (input == ws) ++space;
1836 else space = 0;
1837 break;
1838 case NCM:
1839 case ECM:
1840 case SPMF:
1841 buf = QByteArray(str.data() + start, end - start - space);
1842 // find the mechanism branch in the output list
1843 for (rmf = list.begin(); rmf != list.end(); ++rmf) {
1844 if (rmf->first() == buf)
1845 break;
1846 }
1847 if (rmf == list.end()) {
1848 // append to the output list if not found
1849 QByteArrayList newRmf;
1850 newRmf.append(buf);
1851 rmf = list.insert(list.end(), newRmf);
1852 }
1853 // store a refecence in the helper list for making a cross product
1854 refList.append(&*rmf);
1855 start = end = 0;
1856 break;
1857 case NCF:
1858 case ECF:
1859 case EP:
1860 buf = QByteArray(str.data() + start, end - start - space);
1861 // make a cross product with all current mechanisms
1862 foreach(QByteArrayList *rmfRef, refList)
1863 rmfRef->append(buf);
1864 if (newState != NCF)
1865 refList.clear();
1866 start = end = 0;
1867 break;
1868 default:
1869 break;
1870 }
1871 state = newState;
1872 }
1873
1874 return state == ECF || state == EP;
1875}
1876
1877/*!
1878 Splits the given \a rmf (rendering mechanism/format pair) to a \a mechanism
1879 and a \a format string. Returns FALSE if fails to parse \a rmf.
1880 */
1881// static
1882bool QPMMime::parseRMF(HSTR rmf, QByteArray &mechanism, QByteArray &format)
1883{
1884 QList<QByteArrayList> list;
1885 if (!parseRMFs(rmf, list))
1886 return false;
1887
1888 if (list.count() != 1 || list.first().count() != 2)
1889 return false;
1890
1891 QByteArrayList first = list.first();
1892 mechanism = first.at(0);
1893 format = first.at(1);
1894
1895 return true;
1896}
1897
1898/*!
1899 Returns the default drag worker that works in cooperative mode.
1900
1901 See the DefaultDragWorker class description for more information.
1902 */
1903// static
1904QPMMime::DefaultDragWorker *QPMMime::defaultCoopDragWorker()
1905{
1906 static DefaultDragWorker defCoopDragWorker(false /* exclusive */);
1907 return &defCoopDragWorker;
1908}
1909
1910/*!
1911 Returns the default drag worker that works in exclusive mode.
1912
1913 See the DefaultDragWorker class description for more information.
1914 */
1915// static
1916QPMMime::DefaultDragWorker *QPMMime::defaultExclDragWorker()
1917{
1918 static DefaultDragWorker defExclDragWorker(true /* exclusive */);
1919 return &defExclDragWorker;
1920}
1921
1922/*!
1923 Returns the default drop worker.
1924
1925 See the DefaultDropWorker class description for more information.
1926 */
1927// static
1928QPMMime::DefaultDropWorker *QPMMime::defaultDropWorker()
1929{
1930 static DefaultDropWorker defaultDropWorker;
1931 return &defaultDropWorker;
1932}
1933
1934#endif // !QT_NO_DRAGANDDROP
1935
1936//------------------------------------------------------------------------------
1937
1938class QPMMimeText : public QPMMime
1939{
1940public:
1941 QPMMimeText();
1942 ~QPMMimeText();
1943
1944 // for converting from Qt
1945 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
1946 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
1947 ULONG &flags, ULONG *data) const;
1948
1949 // for converting to Qt
1950 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
1951 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
1952 const QString &mimeType,
1953 QVariant::Type preferredType) const;
1954
1955#if !defined(QT_NO_DRAGANDDROP)
1956
1957 // Direct Manipulation (DND) converter interface
1958 DragWorker *dragWorkerFor(const QString &mimeType, QMimeData *mimeData);
1959 DropWorker *dropWorkerFor(DRAGINFO *info);
1960
1961 class NativeFileDrag : public DragWorker, public QPMObjectWindow
1962 {
1963 public:
1964 // DragWorker interface
1965 bool cleanup(bool isCancelled) { return true; } // always disallow Move
1966 bool isExclusive() const { return true; }
1967 ULONG itemCount() const { return 0; } // super exclusive
1968 HWND hwnd() const { return QPMObjectWindow::hwnd(); }
1969 DRAGINFO *createDragInfo(const QString &targetName, USHORT supportedOps);
1970 // QPMObjectWindow interface (dummy implementation, we don't need to interact)
1971 MRESULT message(ULONG msg, MPARAM mp1, MPARAM mp2) { return 0; }
1972 };
1973
1974 class NativeFileDrop : public DropWorker
1975 {
1976 public:
1977 // DropWorker interface
1978 bool isExclusive() const { return true; }
1979 bool hasFormat(const QString &mimeType) const;
1980 QStringList formats() const;
1981 QVariant retrieveData(const QString &mimeType,
1982 QVariant::Type preferredType) const;
1983 };
1984
1985 class TextDragProvider : public DefaultDragWorker::Provider
1986 {
1987 public:
1988 TextDragProvider() : exclusive(false) {}
1989 bool exclusive;
1990 // Provider interface
1991 QString format(const char *drf) const;
1992 bool provide(const char *drf, const QByteArray &allData,
1993 ULONG itemIndex, QByteArray &itemData);
1994 void fileType(const char *drf, QString &type, QString &ext);
1995 };
1996
1997 class TextDropProvider : public DefaultDropWorker::Provider
1998 {
1999 public:
2000 // Provider interface
2001 QByteArray drf(const QString &mimeType) const;
2002 bool provide(const QString &mimeType, ULONG itemIndex,
2003 const QByteArray &itemData, QByteArray &allData);
2004 };
2005
2006#endif // !QT_NO_DRAGANDDROP
2007
2008 const ULONG CF_TextUnicode;
2009 const ULONG CF_TextHtml;
2010
2011#if !defined(QT_NO_DRAGANDDROP)
2012 NativeFileDrag nativeFileDrag;
2013 NativeFileDrop nativeFileDrop;
2014 TextDragProvider textDragProvider;
2015 TextDropProvider textDropProvider;
2016#endif // !QT_NO_DRAGANDDROP
2017};
2018
2019QPMMimeText::QPMMimeText()
2020 // "text/unicode" is what Mozilla uses to for unicode
2021 : CF_TextUnicode (registerMimeType(QLatin1String("text/unicode")))
2022 // "text/html" is what Mozilla uses to for HTML
2023 , CF_TextHtml (registerMimeType(QLatin1String("text/html")))
2024{
2025}
2026
2027QPMMimeText::~QPMMimeText()
2028{
2029 unregisterMimeType(CF_TextHtml);
2030 unregisterMimeType(CF_TextUnicode);
2031}
2032
2033QList<QPMMime::MimeCFPair> QPMMimeText::formatsForMimeData(const QMimeData *mimeData) const
2034{
2035 QList<MimeCFPair> fmts;
2036 // prefer HTML as it's reacher
2037 if (mimeData->hasHtml())
2038 fmts << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
2039 // prefer unicode over local8Bit
2040 if (mimeData->hasText())
2041 fmts << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode)
2042 << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
2043 return fmts;
2044}
2045
2046// text/plain is defined as using CRLF, but so many programs don't,
2047// and programmers just look for '\n' in strings.
2048// OS/2 really needs CRLF, so we ensure it here.
2049bool QPMMimeText::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2050 ULONG &flags, ULONG *data) const
2051{
2052 if (!mimeData->hasText() ||
2053 (format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml))
2054 return false;
2055
2056 flags = CFI_POINTER;
2057
2058 if (data == NULL)
2059 return true; // delayed rendering, nothing to do
2060
2061 QByteArray r;
2062
2063 if (format == CF_TEXT) {
2064 QByteArray str = mimeData->text().toLocal8Bit();
2065 // Anticipate required space for CRLFs at 1/40
2066 int maxsize = str.size()+str.size()/40+1;
2067 r.fill('\0', maxsize);
2068 char *o = r.data();
2069 const char *d = str.data();
2070 const int s = str.size();
2071 bool cr = false;
2072 int j = 0;
2073 for (int i = 0; i < s; i++) {
2074 char c = d[i];
2075 if (c == '\r')
2076 cr = true;
2077 else {
2078 if (c == '\n') {
2079 if (!cr)
2080 o[j++] = '\r';
2081 }
2082 cr = false;
2083 }
2084 o[j++] = c;
2085 if (j+1 >= maxsize) {
2086 maxsize += maxsize/4;
2087 r.resize(maxsize);
2088 o = r.data();
2089 }
2090 }
2091 if (j < r.size())
2092 o[j] = '\0';
2093 } else if (format == CF_TextUnicode || CF_TextHtml) {
2094 QString str = format == CF_TextUnicode ?
2095 mimeData->text() : mimeData->html();
2096 const QChar *u = str.unicode();
2097 QString res;
2098 const int s = str.length();
2099 int maxsize = s + s/40 + 3;
2100 res.resize(maxsize);
2101 int ri = 0;
2102 bool cr = false;
2103 for (int i = 0; i < s; ++i) {
2104 if (*u == QLatin1Char('\r'))
2105 cr = true;
2106 else {
2107 if (*u == QLatin1Char('\n') && !cr)
2108 res[ri++] = QLatin1Char('\r');
2109 cr = false;
2110 }
2111 res[ri++] = *u;
2112 if (ri+3 >= maxsize) {
2113 maxsize += maxsize/4;
2114 res.resize(maxsize);
2115 }
2116 ++u;
2117 }
2118 res.truncate(ri);
2119 const int byteLength = res.length()*2;
2120 r.fill('\0', byteLength + 2);
2121 memcpy(r.data(), res.unicode(), byteLength);
2122 r[byteLength] = 0;
2123 r[byteLength+1] = 0;
2124 } else{
2125 return false;
2126 }
2127
2128 *data = QPMMime::allocateMemory(r.size());
2129 if (!*data)
2130 return false;
2131
2132 memcpy((void *)*data, r.data(), r.size());
2133 return true;
2134}
2135
2136QList<QPMMime::MimeCFPair> QPMMimeText::mimesForFormats(const QList<ULONG> &formats) const
2137{
2138 QList<MimeCFPair> mimes;
2139 // prefer HTML as it's reacher
2140 if (formats.contains(CF_TextHtml))
2141 mimes << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
2142 // prefer unicode over local8Bit
2143 if (formats.contains(CF_TextUnicode))
2144 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode);
2145 if (formats.contains(CF_TEXT))
2146 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
2147 return mimes;
2148}
2149
2150QVariant QPMMimeText::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2151 const QString &mimeType,
2152 QVariant::Type preferredType) const
2153{
2154 QVariant ret;
2155
2156 if (!mimeType.startsWith(QLatin1String("text/plain")) &&
2157 !mimeType.startsWith(QLatin1String("text/html")))
2158 return ret;
2159 if ((format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml) ||
2160 !(flags & CFI_POINTER) || !data)
2161 return ret;
2162
2163 QString str;
2164
2165 if (format == CF_TEXT) {
2166 const char *d = (const char *)data;
2167 QByteArray r("");
2168 if (*d) {
2169 const int s = qstrlen(d);
2170 r.fill('\0', s);
2171 char *o = r.data();
2172 int j = 0;
2173 for (int i = 0; i < s; i++) {
2174 char c = d[i];
2175 if (c != '\r')
2176 o[j++] = c;
2177 }
2178 }
2179 str = QString::fromLocal8Bit(r);
2180 } else if (format == CF_TextUnicode || CF_TextHtml) {
2181 str = QString::fromUtf16((const unsigned short *)data);
2182 str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
2183 }
2184
2185 if (preferredType == QVariant::String)
2186 ret = str;
2187 else
2188 ret = str.toUtf8();
2189
2190 return ret;
2191}
2192
2193#if !defined(QT_NO_DRAGANDDROP)
2194
2195DRAGINFO *QPMMimeText::NativeFileDrag::createDragInfo(const QString &targetName,
2196 USHORT supportedOps)
2197{
2198 Q_ASSERT(source());
2199 if (!source())
2200 return 0;
2201
2202 // obtain the list of files
2203 QList<QUrl> list;
2204 if (source()->hasUrls())
2205 list = source()->urls();
2206 ULONG itemCnt = list.count();
2207 Q_ASSERT(itemCnt);
2208 if (!itemCnt)
2209 return 0;
2210
2211 DEBUG(() << "QPMMimeText::NativeFileDrag: itemCnt" << itemCnt);
2212
2213 DRAGINFO *info = DrgAllocDraginfo(itemCnt);
2214 Q_ASSERT(info);
2215 if (!info)
2216 return 0;
2217
2218 bool ok = true;
2219 QList<QUrl>::iterator it = list.begin();
2220 for (ULONG i = 0; i < itemCnt; ++i, ++it) {
2221 DRAGITEM *item = DrgQueryDragitemPtr(info, i);
2222 Q_ASSERT(item);
2223 if (!item) {
2224 ok = false;
2225 break;
2226 }
2227
2228 QByteArray fileName = QFile::encodeName(QDir::convertSeparators(it->toLocalFile()));
2229
2230 int sep = fileName.lastIndexOf('\\');
2231 Q_ASSERT(sep > 0 && sep < fileName.length() - 1);
2232 if (sep <= 0 || sep >= fileName.length() - 1) {
2233 ok = false;
2234 break;
2235 }
2236
2237 item->hstrSourceName = DrgAddStrHandle(fileName.data() + sep + 1);
2238 fileName.truncate(sep + 1);
2239 item->hstrContainerName = DrgAddStrHandle(fileName);
2240
2241 DEBUG(() << "QPMMimeText::NativeFileDrag: item" << i
2242 << "dir" << queryHSTR(item->hstrContainerName)
2243 << "name" << queryHSTR(item->hstrSourceName));
2244
2245 item->hwndItem = hwnd();
2246 item->ulItemID = 0;
2247 item->hstrType = DrgAddStrHandle(DRT_UNKNOWN);
2248 item->hstrRMF = DrgAddStrHandle("<DRM_OS2FILE,DRF_UNKNOWN>");
2249 item->hstrTargetName = 0;
2250 item->cxOffset = 0;
2251 item->cyOffset = 0;
2252 item->fsControl = 0;
2253 item->fsSupportedOps = supportedOps;
2254 }
2255
2256 if (!ok) {
2257 DrgFreeDraginfo(info);
2258 info = 0;
2259 }
2260
2261 return info;
2262}
2263
2264bool QPMMimeText::NativeFileDrop::hasFormat(const QString &mimeType) const
2265{
2266 return mimeType == QLatin1String("text/uri-list");
2267}
2268
2269QStringList QPMMimeText::NativeFileDrop::formats() const
2270{
2271 QStringList mimes;
2272 mimes << QLatin1String("text/uri-list");
2273 return mimes;
2274}
2275
2276QVariant QPMMimeText::NativeFileDrop::retrieveData(const QString &mimeType,
2277 QVariant::Type preferredType) const
2278{
2279 QVariant result;
2280
2281 Q_ASSERT(info());
2282 if (!info())
2283 return result;
2284
2285 ULONG itemCount = DrgQueryDragitemCount(info());
2286 Q_ASSERT(itemCount);
2287 if (!itemCount)
2288 return result;
2289
2290 // sanity check
2291 if (mimeType != QLatin1String("text/uri-list"))
2292 return result;
2293
2294 QList<QVariant> urls;
2295
2296 for (ULONG i = 0; i < itemCount; ++i) {
2297 DRAGITEM *item = DrgQueryDragitemPtr(info(), i);
2298 Q_ASSERT(item);
2299 QByteArray fullName;
2300 if (!item || !canTargetRenderAsOS2File(item, &fullName))
2301 return result;
2302 QString fn = QFile::decodeName(fullName);
2303 urls += QUrl::fromLocalFile(fn);
2304 }
2305
2306 if (preferredType == QVariant::Url && urls.size() == 1)
2307 result = urls.at(0);
2308 else if (!urls.isEmpty())
2309 result = urls;
2310
2311 return result;
2312}
2313
2314QString QPMMimeText::TextDragProvider::format(const char *drf) const
2315{
2316 QString result;
2317
2318 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2319 if (exclusive)
2320 result = QLatin1String("text/uri-list");
2321 else
2322 result = QLatin1String("text/plain");
2323 }
2324 return result;
2325}
2326
2327bool QPMMimeText::TextDragProvider::provide(const char *drf,
2328 const QByteArray &allData,
2329 ULONG itemIndex,
2330 QByteArray &itemData)
2331{
2332 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2333 if (exclusive) {
2334 // locate the required item
2335 int dataSize = allData.size();
2336 if (!dataSize)
2337 return false;
2338 int begin = 0, end = 0, next = 0;
2339 do {
2340 begin = next;
2341 end = allData.indexOf('\r', begin);
2342 if (end >= 0) {
2343 next = end + 1;
2344 if (next < dataSize && allData[next] == '\n')
2345 ++next;
2346 } else {
2347 end = allData.indexOf('\n', begin);
2348 if (end >= 0)
2349 next = end + 1;
2350 }
2351 } while (itemIndex-- && end >= 0 && next < dataSize);
2352 int urlLen = end - begin;
2353 if (urlLen <= 0)
2354 return false;
2355 QUrl url = QUrl(QString::fromUtf8(allData.data() + begin, urlLen));
2356 if (!url.isValid())
2357 return false;
2358 itemData = url.toEncoded();
2359 } else {
2360 itemData = QString::fromUtf8(allData).toLocal8Bit();
2361 }
2362 return true;
2363 }
2364 return false;
2365}
2366
2367void QPMMimeText::TextDragProvider::fileType(const char *drf,
2368 QString &type, QString &ext)
2369{
2370 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2371 if (exclusive) {
2372 type = QLatin1String("UniformResourceLocator");
2373 // no extension for URLs
2374 ext = QString::null;
2375 } else {
2376 type = QLatin1String(DRT_TEXT);
2377 ext = QLatin1String("txt");
2378 }
2379 }
2380};
2381
2382QByteArray QPMMimeText::TextDropProvider::drf(const QString &mimeType) const
2383{
2384 // sanity check
2385 if (mimeType == QLatin1String("text/plain") ||
2386 mimeType == QLatin1String("text/uri-list"))
2387 return QByteArray("DRF_TEXT");
2388 return 0;
2389}
2390
2391bool QPMMimeText::TextDropProvider::provide(const QString &mimeType,
2392 ULONG itemIndex,
2393 const QByteArray &itemData,
2394 QByteArray &allData)
2395{
2396 if (mimeType == QLatin1String("text/plain")) {
2397 allData = QString::fromLocal8Bit(itemData).toUtf8();
2398 return true;
2399 }
2400
2401 if (mimeType == QLatin1String("text/uri-list")) {
2402 QUrl url = QUrl::fromEncoded(itemData);
2403 if (!url.isValid())
2404 return false;
2405 // append the URL to the list
2406 allData += url.toString().toUtf8();
2407 allData += "\r\n";
2408 return true;
2409 }
2410
2411 return false;
2412}
2413
2414QPMMime::DragWorker *QPMMimeText::dragWorkerFor(const QString &mimeType,
2415 QMimeData *mimeData)
2416{
2417 if (mimeType == QLatin1String("text/plain")) {
2418 // add a cooperative provider
2419 textDragProvider.exclusive = false;
2420 DefaultDragWorker *defWorker = defaultCoopDragWorker();
2421 defWorker->addProvider("DRF_TEXT", &textDragProvider);
2422 return defWorker;
2423 }
2424
2425 if (mimeType == QLatin1String("text/uri-list")) {
2426 // see what kind of items text/uri-list represents
2427 QList<QUrl> urls = mimeData->urls();
2428 int fileCnt = 0;
2429 foreach (const QUrl &url, urls) {
2430 if (url.scheme() == QLatin1String("file"))
2431 ++fileCnt;
2432 }
2433 if (fileCnt && fileCnt == urls.count()) {
2434 // all items are local files, return an exclusive file drag worker
2435 return &nativeFileDrag;
2436 }
2437 if (urls.count() && !fileCnt) {
2438 // all items are non-files, add an exclusive provider for the
2439 // specified item count
2440 textDragProvider.exclusive = true;
2441 DefaultDragWorker *defWorker = defaultExclDragWorker();
2442 bool ok = defWorker->addProvider("DRF_TEXT", &textDragProvider,
2443 urls.count());
2444 return ok ? defWorker : 0;
2445 }
2446 // if items are mixed, we return NULL to fallback to QPMMimeAnyMime
2447 }
2448
2449 return 0;
2450}
2451
2452QPMMime::DropWorker *QPMMimeText::dropWorkerFor(DRAGINFO *info)
2453{
2454 ULONG itemCount = DrgQueryDragitemCount(info);
2455 Q_ASSERT(itemCount);
2456 if (!itemCount)
2457 return 0;
2458
2459 if (itemCount == 1) {
2460 DRAGITEM *item = DrgQueryDragitemPtr(info, 0);
2461 Q_ASSERT(item);
2462 if (!item)
2463 return 0;
2464 // proceed only if the target cannot render DRM_OS2FILE on its own
2465 // and if the item type is not "UniformResourceLocator" (which will be
2466 // processed below)
2467 if (!canTargetRenderAsOS2File(item) &&
2468 !DrgVerifyType(item, "UniformResourceLocator")) {
2469 DefaultDropWorker *defWorker = defaultDropWorker();
2470 // check that we support one of DRMs and the format is DRF_TEXT
2471 if (defWorker->canRender(item, "DRF_TEXT")) {
2472 // add a cooperative provider (can coexist with others)
2473 defWorker->addProvider(QLatin1String("text/plain"),
2474 &textDropProvider);
2475 return defWorker;
2476 }
2477 return 0;
2478 }
2479 }
2480
2481 // Either the target can render DRM_OS2FILE on its own (so it's a valid
2482 // file/directory name), or it's an "UniformResourceLocator", or there is
2483 // more than one drag item. Check that all items are of either one type
2484 // or another. If so, we can represent them as 'text/uri-list'.
2485 bool allAreFiles = true;
2486 bool allAreURLs = true;
2487 DefaultDropWorker *defWorker = defaultDropWorker();
2488 for (ULONG i = 0; i < itemCount; ++i) {
2489 DRAGITEM *item = DrgQueryDragitemPtr(info, i);
2490 Q_ASSERT(item);
2491 if (!item)
2492 return 0;
2493 if (allAreFiles)
2494 allAreFiles &= canTargetRenderAsOS2File(item);
2495 if (allAreURLs)
2496 allAreURLs &= DrgVerifyType(item, "UniformResourceLocator") &&
2497 defWorker->canRender(item, "DRF_TEXT");
2498 if (!allAreFiles && !allAreURLs)
2499 return 0;
2500 }
2501
2502 // Note: both allAreFiles and allAreURLs may be true here (e.g. a file on
2503 // the desktop that represents an URL object). In this case, we will treat
2504 // the list as files rather than as URLs for similarity with other platforms
2505 // (e.g. an Internet shortcut on Windows is interpreted as a local file as
2506 // well).
2507
2508 if (allAreFiles) {
2509 // return an exclusive drop worker
2510 return &nativeFileDrop;
2511 }
2512
2513 // add an exclusive provider (can neither coexist with other workers
2514 // or providers)
2515 bool ok = defWorker->addExclusiveProvider(QLatin1String("text/uri-list"),
2516 &textDropProvider);
2517 return ok ? defWorker : 0;
2518}
2519
2520#endif // !QT_NO_DRAGANDDROP
2521
2522//------------------------------------------------------------------------------
2523
2524class QPMMimeImage : public QPMMime
2525{
2526public:
2527 QPMMimeImage();
2528
2529 // for converting from Qt
2530 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2531 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2532 ULONG &flags, ULONG *data) const;
2533 // for converting to Qt
2534 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2535 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2536 const QString &mimeType,
2537 QVariant::Type preferredType) const;
2538};
2539
2540QPMMimeImage::QPMMimeImage()
2541{
2542}
2543
2544QList<QPMMime::MimeCFPair> QPMMimeImage::formatsForMimeData(const QMimeData *mimeData) const
2545{
2546 QList<MimeCFPair> fmts;
2547 if (mimeData->hasImage()) {
2548 // "application/x-qt-image" seems to be used as a single name for all
2549 // "image/xxx" types in Qt
2550 fmts << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2551 }
2552 return fmts;
2553}
2554
2555bool QPMMimeImage::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2556 ULONG &flags, ULONG *data) const
2557{
2558 if (!mimeData->hasImage() || format != CF_BITMAP)
2559 return false;
2560
2561 flags = CFI_HANDLE;
2562
2563 if (data == NULL)
2564 return true; // delayed rendering, nothing to do
2565
2566 QImage img = qvariant_cast<QImage>(mimeData->imageData());
2567 if (img.isNull())
2568 return false;
2569
2570 QPixmap pm = QPixmap::fromImage(img);
2571 if (pm.isNull())
2572 return false;
2573
2574 HBITMAP bmp = pm.toPmHBITMAP(0, true);
2575 if (bmp == NULLHANDLE)
2576 return false;
2577
2578 *data = bmp;
2579 return true;
2580}
2581
2582QList<QPMMime::MimeCFPair> QPMMimeImage::mimesForFormats(const QList<ULONG> &formats) const
2583{
2584 QList<MimeCFPair> mimes;
2585 if (formats.contains(CF_BITMAP))
2586 mimes << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2587 return mimes;
2588}
2589
2590QVariant QPMMimeImage::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2591 const QString &mimeType,
2592 QVariant::Type preferredType) const
2593{
2594 Q_UNUSED(preferredType);
2595
2596 QVariant ret;
2597
2598 if (mimeType != QLatin1String("application/x-qt-image"))
2599 return ret;
2600 if (format != CF_BITMAP || !(flags & CFI_HANDLE) || !data)
2601 return ret;
2602
2603 QPixmap pm = QPixmap::fromPmHBITMAP((HBITMAP)data);
2604 if (pm.isNull())
2605 return ret;
2606
2607 ret = pm.toImage();
2608 return ret;
2609}
2610
2611//------------------------------------------------------------------------------
2612
2613class QPMMimeAnyMime : public QPMMime
2614{
2615public:
2616 QPMMimeAnyMime();
2617 ~QPMMimeAnyMime();
2618
2619 // for converting from Qt
2620 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2621 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2622 ULONG &flags, ULONG *data) const;
2623 // for converting to Qt
2624 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2625 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2626 const QString &mimeType,
2627 QVariant::Type preferredType) const;
2628
2629#if !defined(QT_NO_DRAGANDDROP)
2630
2631 // Direct Manipulation (DND) converter interface
2632 DragWorker *dragWorkerFor(const QString &mimeType, QMimeData *mimeData);
2633 DropWorker *dropWorkerFor(DRAGINFO *info);
2634
2635 class AnyDragProvider : public DefaultDragWorker::Provider
2636 {
2637 public:
2638 AnyDragProvider(QPMMimeAnyMime *am) : anyMime(am) {}
2639 // Provider interface
2640 QString format(const char *drf) const;
2641 bool provide(const char *drf, const QByteArray &allData,
2642 ULONG itemIndex, QByteArray &itemData);
2643 void fileType(const char *drf, QString &type, QString &ext);
2644 private:
2645 QPMMimeAnyMime *anyMime;
2646 };
2647
2648 class AnyDropProvider : public DefaultDropWorker::Provider
2649 {
2650 public:
2651 AnyDropProvider(QPMMimeAnyMime *am) : anyMime(am) {}
2652 // Provider interface
2653 QByteArray drf(const QString &mimeType) const;
2654 bool provide(const QString &mimeType, ULONG itemIndex,
2655 const QByteArray &itemData, QByteArray &allData);
2656 private:
2657 QPMMimeAnyMime *anyMime;
2658 };
2659
2660#endif // !QT_NO_DRAGANDDROP
2661
2662private:
2663 ULONG registerMimeType(const QString &mime) const;
2664 QString registerFormat(ULONG format) const;
2665
2666 mutable QMap<QString, ULONG> cfMap;
2667 mutable QMap<ULONG, QString> mimeMap;
2668
2669 static QStringList ianaTypes;
2670 static QString mimePrefix;
2671 static QString customPrefix;
2672
2673#if !defined(QT_NO_DRAGANDDROP)
2674
2675 static ULONG drfToCf(const char *drf);
2676 static QByteArray cfToDrf(ULONG cf);
2677
2678 AnyDragProvider anyDragProvider;
2679 AnyDropProvider anyDropProvider;
2680
2681// friend class AnyDragProvider;
2682// friend class AnyDropProvider;
2683
2684#endif // !QT_NO_DRAGANDDROP
2685};
2686
2687// static
2688QStringList QPMMimeAnyMime::ianaTypes;
2689QString QPMMimeAnyMime::mimePrefix;
2690QString QPMMimeAnyMime::customPrefix;
2691
2692QPMMimeAnyMime::QPMMimeAnyMime()
2693#if !defined(QT_NO_DRAGANDDROP)
2694 : anyDragProvider(AnyDragProvider(this))
2695 , anyDropProvider(AnyDropProvider(this))
2696#endif // !QT_NO_DRAGANDDROP
2697{
2698 //MIME Media-Types
2699 if (!ianaTypes.size()) {
2700 ianaTypes.append(QLatin1String("application/"));
2701 ianaTypes.append(QLatin1String("audio/"));
2702 ianaTypes.append(QLatin1String("example/"));
2703 ianaTypes.append(QLatin1String("image/"));
2704 ianaTypes.append(QLatin1String("message/"));
2705 ianaTypes.append(QLatin1String("model/"));
2706 ianaTypes.append(QLatin1String("multipart/"));
2707 ianaTypes.append(QLatin1String("text/"));
2708 ianaTypes.append(QLatin1String("video/"));
2709
2710 mimePrefix = QLatin1String("x-mime:");
2711 customPrefix = QLatin1String("application/x-qt-pm-mime;value=\"");
2712 }
2713}
2714
2715QPMMimeAnyMime::~QPMMimeAnyMime()
2716{
2717 foreach(ULONG cf, cfMap.values())
2718 unregisterMimeType(cf);
2719}
2720
2721QList<QPMMime::MimeCFPair> QPMMimeAnyMime::formatsForMimeData(const QMimeData *mimeData) const
2722{
2723 QList<MimeCFPair> fmts;
2724
2725 QStringList mimes = QInternalMimeData::formatsHelper(mimeData);
2726 foreach (QString mime, mimes) {
2727 ULONG cf = cfMap.value(mime);
2728 if (!cf)
2729 cf = registerMimeType(mime);
2730 if (cf)
2731 fmts << MimeCFPair(mime, cf);
2732 }
2733
2734 return fmts;
2735}
2736
2737bool QPMMimeAnyMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2738 ULONG &flags, ULONG *data) const
2739{
2740 QString mime = mimeMap.value(format);
2741 if (mime.isNull())
2742 return false;
2743
2744 flags = CFI_POINTER;
2745
2746 if (data == NULL)
2747 return true; // delayed rendering, nothing to do
2748
2749 QByteArray r = QInternalMimeData::renderDataHelper(mime, mimeData);
2750 if (r.isNull())
2751 return false;
2752
2753 *data = QPMMime::allocateMemory(r.size() + sizeof(ULONG));
2754 if (!*data)
2755 return false;
2756
2757 *((ULONG *)(*data)) = r.size();
2758 memcpy((void *)(*data + sizeof(ULONG)), r.data(), r.size());
2759 return true;
2760}
2761
2762QList<QPMMime::MimeCFPair> QPMMimeAnyMime::mimesForFormats(const QList<ULONG> &formats) const
2763{
2764 QList<MimeCFPair> mimes;
2765
2766 foreach (ULONG format, formats) {
2767 QString mime = mimeMap.value(format);
2768 if (mime.isEmpty())
2769 mime = registerFormat(format);
2770 if (!mime.isEmpty())
2771 mimes << MimeCFPair(mime, format);
2772 }
2773
2774 return mimes;
2775}
2776
2777QVariant QPMMimeAnyMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2778 const QString &mimeType,
2779 QVariant::Type preferredType) const
2780{
2781 Q_UNUSED(preferredType);
2782
2783 QVariant ret;
2784
2785 if (cfMap.value(mimeType) != format)
2786 return ret;
2787
2788 if (!(flags & CFI_POINTER) || !data)
2789 return ret;
2790
2791 // get the real block size (always rounded to the page boundary (4K))
2792 ULONG sz = ~0, fl = 0, arc;
2793 arc = DosQueryMem((PVOID)data, &sz, &fl);
2794 if (arc != NO_ERROR) {
2795#ifndef QT_NO_DEBUG
2796 qWarning("QPMMimeText::convertFromFormat: DosQueryMem failed with %lu", arc);
2797#endif
2798 return ret;
2799 }
2800 ULONG size = *((ULONG *)data);
2801 if (!size || size + sizeof(ULONG) > sz)
2802 return ret;
2803
2804 // it should be enough to return the data and let QMimeData do the rest.
2805 ret = QByteArray((const char *)(data + sizeof(ULONG)), size);
2806 return ret;
2807}
2808
2809#if !defined(QT_NO_DRAGANDDROP)
2810
2811QString QPMMimeAnyMime::AnyDragProvider::format(const char *drf) const
2812{
2813 ULONG cf = drfToCf(drf);
2814 if (cf) {
2815 QString mime = anyMime->mimeMap.value(cf);
2816 if (!mime.isEmpty())
2817 return mime;
2818 }
2819
2820 // There must always be a match since the given drf is associated with this
2821 // provider by dragWorkerFor() and all necessary mappings are there.
2822 Q_ASSERT(false);
2823 return QString::null;
2824}
2825
2826bool QPMMimeAnyMime::AnyDragProvider::provide(const char *drf,
2827 const QByteArray &allData,
2828 ULONG itemIndex,
2829 QByteArray &itemData)
2830{
2831 Q_UNUSED(drf);
2832 Q_UNUSED(itemIndex);
2833
2834 // always straight through coversion
2835 itemData = allData;
2836 return true;
2837}
2838
2839void QPMMimeAnyMime::AnyDragProvider::fileType(const char *drf,
2840 QString &type, QString &ext)
2841{
2842 // file type = mime
2843 type = format(drf);
2844 Q_ASSERT(!type.isEmpty());
2845
2846 // no way to determine the extension
2847 ext = QString::null;
2848};
2849
2850QByteArray QPMMimeAnyMime::AnyDropProvider::drf(const QString &mimeType) const
2851{
2852 ULONG cf = anyMime->cfMap.value(mimeType);
2853 if (cf)
2854 return cfToDrf(cf);
2855
2856 // There must always be a match since the given drf is associated with this
2857 // provider by dragWorkerFor() and all necessary mappings are there.
2858 Q_ASSERT(false);
2859 return QByteArray();
2860}
2861
2862bool QPMMimeAnyMime::AnyDropProvider::provide(const QString &mimeType,
2863 ULONG itemIndex,
2864 const QByteArray &itemData,
2865 QByteArray &allData)
2866{
2867 Q_UNUSED(mimeType);
2868 Q_UNUSED(itemIndex);
2869
2870 // always straight through coversion
2871 allData = itemData;
2872 return true;
2873}
2874
2875QPMMime::DragWorker *QPMMimeAnyMime::dragWorkerFor(const QString &mimeType,
2876 QMimeData *mimeData)
2877{
2878 ULONG cf = cfMap.value(mimeType);
2879 if (!cf)
2880 cf = registerMimeType(mimeType);
2881 if (cf) {
2882 DefaultDragWorker *defWorker = defaultCoopDragWorker();
2883 // add a cooperative provider
2884 defWorker->addProvider(cfToDrf(cf), &anyDragProvider);
2885 return defWorker;
2886 }
2887
2888 Q_ASSERT(false);
2889 return 0;
2890}
2891
2892QPMMime::DropWorker *QPMMimeAnyMime::dropWorkerFor(DRAGINFO *info)
2893{
2894 ULONG itemCount = DrgQueryDragitemCount(info);
2895 Q_ASSERT(itemCount);
2896 if (!itemCount)
2897 return 0;
2898
2899 if (itemCount == 1) {
2900 DRAGITEM *item = DrgQueryDragitemPtr(info, 0);
2901 Q_ASSERT(item);
2902 if (!item)
2903 return 0;
2904
2905 DefaultDropWorker *defWorker = defaultDropWorker();
2906 bool atLeastOneSupported = false;
2907
2908 // check that we support one of DRMs and the format is CF_hhhhhhh
2909 QList<QByteArrayList> list;
2910 defWorker->getSupportedRMFs(item, list);
2911 foreach(const QByteArrayList &mech, list) {
2912 QByteArrayList::const_iterator it = mech.begin();
2913 Q_ASSERT(it != mech.end());
2914 DEBUG(() << "QPMMimeAnyMime: Supported drm:" << *it);
2915 for (++it; it != mech.end(); ++it) {
2916 const QByteArray &drf = *it;
2917 ULONG cf = drfToCf(drf);
2918 if (cf) {
2919 DEBUG(() << "QPMMimeAnyMime: Supported drf:" << drf);
2920 QString mime = mimeMap.value(cf);
2921 if (mime.isEmpty())
2922 mime = registerFormat(cf);
2923 Q_ASSERT(!mime.isEmpty());
2924 if (!mime.isEmpty()) {
2925 DEBUG(() << "QPMMimeAnyMime: Will provide [" << mime
2926 << "] for drf" << drf);
2927 // add a cooperative provider (can coexist with others)
2928 defWorker->addProvider(mime, &anyDropProvider);
2929 atLeastOneSupported = true;
2930 }
2931 }
2932 }
2933 }
2934
2935 if (atLeastOneSupported)
2936 return defWorker;
2937 }
2938
2939 return 0;
2940}
2941
2942#endif // !QT_NO_DRAGANDDROP
2943
2944ULONG QPMMimeAnyMime::registerMimeType(const QString &mime) const
2945{
2946 if (mime.isEmpty())
2947 return 0;
2948
2949 QString mimeToReg = mime;
2950
2951 bool ianaType = false;
2952 foreach(QString prefix, ianaTypes) {
2953 if (mime.startsWith(prefix)) {
2954 ianaType = true;
2955 break;
2956 }
2957 }
2958 if (!ianaType) {
2959 // prepend the non-standard type with the prefix that makes it comply
2960 // with the standard
2961 mimeToReg = customPrefix + mime + QLatin1Char('\"');
2962 }
2963
2964 mimeToReg = mimePrefix + mimeToReg;
2965 ULONG cf = QPMMime::registerMimeType(mimeToReg);
2966 if (cf) {
2967 cfMap[mime] = cf;
2968 mimeMap[cf] = mime;
2969 }
2970 return cf;
2971}
2972
2973QString QPMMimeAnyMime::registerFormat(ULONG format) const
2974{
2975 QString mime;
2976
2977 if (!format)
2978 return mime;
2979
2980 QString atomStr = formatName(format);
2981 if (atomStr.startsWith(mimePrefix)) {
2982 // the format represents the mime type we can recognize
2983 // increase the reference count
2984 ULONG cf = QPMMime::registerMimeType(atomStr);
2985 Q_ASSERT(cf == format);
2986 // extract the real mime type (w/o our prefix)
2987 mime = atomStr.mid(mimePrefix.size());
2988 if (!mime.isEmpty()) {
2989 cfMap[mime] = cf;
2990 mimeMap[cf] = mime;
2991 }
2992 }
2993 return mime;
2994}
2995
2996#if !defined(QT_NO_DRAGANDDROP)
2997
2998// static
2999ULONG QPMMimeAnyMime::drfToCf(const char *drf)
3000{
3001 if (qstrncmp(drf, "CF_", 3) == 0)
3002 return QString(QLatin1String(drf + 3)).toULong(0, 16);
3003 return 0;
3004}
3005
3006// static
3007QByteArray QPMMimeAnyMime::cfToDrf(ULONG cf)
3008{
3009 return QString().sprintf("CF_%08lX", cf).toLatin1();
3010}
3011
3012#endif // !QT_NO_DRAGANDDROP
3013
3014//------------------------------------------------------------------------------
3015
3016QPMMimeList::QPMMimeList()
3017 : initialized(false)
3018{
3019}
3020
3021QPMMimeList::~QPMMimeList()
3022{
3023 while (list.size())
3024 delete list.first();
3025}
3026
3027
3028void QPMMimeList::init()
3029{
3030 if (!initialized) {
3031 initialized = true;
3032 new QPMMimeAnyMime; // must be the first (used as a fallback)
3033 new QPMMimeImage;
3034 new QPMMimeText;
3035 }
3036}
3037
3038void QPMMimeList::addMime(QPMMime *mime)
3039{
3040 init();
3041 list.prepend(mime);
3042}
3043
3044void QPMMimeList::removeMime(QPMMime *mime)
3045{
3046 init();
3047 list.removeAll(mime);
3048}
3049
3050QList<QPMMime*> QPMMimeList::mimes()
3051{
3052 init();
3053 return list;
3054}
3055
3056QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.