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

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

gui: DnD: QPMAnyMime: Added fallback support for any mime type not supported by dedicated converters.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 93.6 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 lile 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, MPFROMLONG(xfer->pditem->ulItemID),
486 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// @todo add DnD interfaces docs
1428
1429// static
1430QList<QPMMime::Match> QPMMime::allConvertersFromFormats(const QList<ULONG> &formats)
1431{
1432 QList<Match> matches;
1433
1434 QList<QPMMime*> mimes = theMimeList()->mimes();
1435 foreach(QPMMime *mime, mimes) {
1436 QList<MimeCFPair> fmts = mime->mimesForFormats(formats);
1437 int priority = 0;
1438 foreach (MimeCFPair fmt, fmts) {
1439 ++priority;
1440 QList<Match>::iterator it = matches.begin();
1441 for (; it != matches.end(); ++it) {
1442 Match &match = *it;
1443 if (match.mime == fmt.mime) {
1444 // replace if priority is higher, ignore otherwise
1445 if (priority < match.priority) {
1446 match.converter = mime;
1447 match.format = fmt.format;
1448 match.priority = priority;
1449 }
1450 break;
1451 }
1452 }
1453 if (it == matches.end()) {
1454 matches += Match(mime, fmt.mime, fmt.format, priority);
1455 }
1456 }
1457 }
1458
1459 return matches;
1460}
1461
1462// static
1463QList<QPMMime::Match> QPMMime::allConvertersFromMimeData(const QMimeData *mimeData)
1464{
1465 QList<Match> matches;
1466
1467 QList<QPMMime*> mimes = theMimeList()->mimes();
1468 foreach(QPMMime *mime, mimes) {
1469 QList<MimeCFPair> fmts = mime->formatsForMimeData(mimeData);
1470 int priority = 0;
1471 foreach (MimeCFPair fmt, fmts) {
1472 ++priority;
1473 QList<Match>::iterator it = matches.begin();
1474 for (; it != matches.end(); ++it) {
1475 Match &match = *it;
1476 if (mime == mimes.last()) { // QPMMimeAnyMime?
1477 if (match.mime == fmt.mime){
1478 // we assume that specialized converters (that come
1479 // first) provide a more precise conversion than
1480 // QPMMimeAnyMime and don't let it get into the list in
1481 // order to avoid unnecessary duplicate representations
1482 break;
1483 }
1484 }
1485 if (match.format == fmt.format) {
1486 // replace if priority is higher, ignore otherwise
1487 if (priority < match.priority) {
1488 match.converter = mime;
1489 match.mime = fmt.mime;
1490 match.priority = priority;
1491 }
1492 break;
1493 }
1494 }
1495 if (it == matches.end()) {
1496 matches += Match(mime, fmt.mime, fmt.format, priority);
1497 }
1498 }
1499 }
1500
1501 return matches;
1502}
1503
1504QString QPMMime::formatName(ULONG format)
1505{
1506 QString name;
1507 HATOMTBL tbl = WinQuerySystemAtomTable();
1508 if (tbl != NULLHANDLE) {
1509 ULONG len = WinQueryAtomLength(tbl, format);
1510 QByteArray atom(len, '\0');
1511 WinQueryAtomName(tbl, format, atom.data(), atom.size() + 1);
1512 name = QString::fromLocal8Bit(atom);
1513 }
1514 return name;
1515}
1516
1517#if !defined(QT_NO_DRAGANDDROP)
1518
1519/*!
1520 Returns a string represented by \a hstr.
1521*/
1522// static
1523QByteArray QPMMime::queryHSTR(HSTR hstr)
1524{
1525 QByteArray str;
1526 ULONG len = DrgQueryStrNameLen(hstr);
1527 if (len) {
1528 str.resize(len);
1529 DrgQueryStrName(hstr, str.size() + 1 /* \0 */, str.data());
1530 }
1531 return str;
1532}
1533
1534/*!
1535 Returns a string that is a concatenation of \c hstrContainerName and
1536 \c hstrSourceName fileds of the given \a item structure.
1537*/
1538// static
1539QByteArray QPMMime::querySourceNameFull(DRAGITEM *item)
1540{
1541 QByteArray fullName;
1542 if (!item)
1543 return fullName;
1544
1545 ULONG pathLen = DrgQueryStrNameLen(item->hstrContainerName);
1546 ULONG nameLen = DrgQueryStrNameLen(item->hstrSourceName);
1547 if (!pathLen || !nameLen)
1548 return fullName;
1549
1550 // Take into account that the container name may lack the trailing slash
1551 fullName.resize(pathLen + nameLen + 1);
1552
1553 DrgQueryStrName(item->hstrContainerName, pathLen + 1, fullName.data());
1554 if (fullName.at(pathLen - 1) != '\\') {
1555 fullName[(size_t)pathLen] = '\\';
1556 ++pathLen;
1557 }
1558
1559 DrgQueryStrName(item->hstrSourceName, nameLen + 1, fullName.data() + pathLen);
1560
1561 fullName.truncate(qstrlen(fullName));
1562
1563 return fullName;
1564}
1565
1566/*! \internal
1567
1568 Checks that the given drag \a item supports the DRM_OS2FILE rendering
1569 mechanism and can be rendered by a target w/o involving the source (i.e.,
1570 DRM_OS2FILE is the first supported format and a valid file name with full
1571 path is provided). If the function returns TRUE, \a fullName (if not NULL)
1572 will be assigned the item's full source file name (composed from
1573 \c hstrContainerName and \c hstrSourceName fields).
1574 */
1575// static
1576bool QPMMime::canTargetRenderAsOS2File(DRAGITEM *item, QByteArray *fullName /*= 0*/)
1577{
1578 if (!item)
1579 return false;
1580
1581 if (item->fsControl & (DC_PREPARE | DC_PREPAREITEM))
1582 return false;
1583
1584 {
1585 // DrgVerifyNativeRMF doesn't work on my system (ECS 1.2.1 GA):
1586 // it always returns FALSE regardless of arguments. Use simplified
1587 // hstrRMF parsing to determine whether DRM_OS2FILE is the native
1588 // mechanism or not (i.e. "^\s*[\(<]\s*DRM_OS2FILE\s*,.*").
1589
1590 QByteArray rmf = queryHSTR(item->hstrRMF);
1591 bool ok = false;
1592 int i = rmf.indexOf("DRM_OS2FILE");
1593 if (i >= 1) {
1594 for (int j = i - 1; j >= 0; --j) {
1595 char ch = rmf[j];
1596 if (ch == ' ')
1597 continue;
1598 if (ch == '<' || ch == '(') {
1599 if (ok)
1600 return false;
1601 ok = true;
1602 } else {
1603 return false;
1604 }
1605 }
1606 }
1607 if (ok) {
1608 ok = false;
1609 int drmLen = strlen("DRM_OS2FILE");
1610 for (int j = i + drmLen; j < rmf.size(); ++j) {
1611 char ch = rmf[j];
1612 if (ch == ' ')
1613 continue;
1614 if (ch == ',') {
1615 ok = true;
1616 break;
1617 }
1618 return false;
1619 }
1620 }
1621 if (!ok)
1622 return false;
1623 }
1624
1625 QByteArray srcFullName = querySourceNameFull(item);
1626 if (srcFullName.isEmpty())
1627 return false;
1628
1629 QByteArray srcFullName2(srcFullName.size(), '\0');
1630 APIRET rc = DosQueryPathInfo(srcFullName, FIL_QUERYFULLNAME,
1631 srcFullName2.data(), srcFullName2.size() + 1);
1632 if (rc != 0)
1633 return false;
1634
1635 QString s1 = QFile::decodeName(srcFullName);
1636 QString s2 = QFile::decodeName(srcFullName2);
1637
1638 if (s1.compare(s2, Qt::CaseInsensitive) != 0)
1639 return false;
1640
1641 if (fullName)
1642 *fullName = srcFullName;
1643 return true;
1644}
1645
1646/*! \internal
1647
1648 Parses the given \a rmfs list (full rendering mechanism/format specification)
1649 and builds a \a list of mechanism branches. Each mechanism branch is also a
1650 list, where the first item is the mechahism name and all subsequent items are
1651 formats supported by this mechanism. Returns false if fails to parse \a rmf.
1652
1653 \note The method clears the given \a list variable before proceeding.
1654*/
1655// static
1656bool QPMMime::parseRMFs(HSTR rmfs, QList<QByteArrayList> &list)
1657{
1658 // The format of the RMF list is "elem {,elem,elem...}"
1659 // where elem is "(mechanism{,mechanism...}) x (format{,format...})"
1660 // or "<mechanism,format>".
1661 // We use a simple FSM to parse it. In terms of FSM, the format is:
1662 //
1663 // STRT ( BCM m CMCH echanism CMCH , NCM m CMCH echanism CMCH ) ECM x
1664 // SCMF ( BCF f CFCH ormat CFCH , NCF f CFCH ormat CFCH ) ECF , STRT
1665 // STRT < BP m PMCH echanism PMCH , SPMF f PFCH ormat PFCH > EP , STRT
1666
1667 QByteArray str = queryHSTR(rmfs);
1668 uint len = str.length();
1669
1670 enum {
1671 // states
1672 STRT = 0, BCM, CMCH, NCM, ECM, SCMF, BCF, CFCH, NCF, ECF,
1673 BP, PMCH, SPMF, PFCH, EP,
1674 STATES_COUNT,
1675 // pseudo states
1676 Err, Skip,
1677 // inputs
1678 obr = 0, cbr, xx, lt, gt, cm, any, ws,
1679 INPUTS_COUNT,
1680 };
1681
1682 static const char Chars[] = { '(', ')', 'x', 'X', '<', '>', ',', ' ', 0 };
1683 static const char Inputs[] = { obr, cbr, xx, xx, lt, gt, cm, ws };
1684 static const uchar Fsm [STATES_COUNT] [INPUTS_COUNT] = {
1685 /* 0 obr 1 cbr 2 xx 3 lt 4 gt 5 cm 6 any 7 ws */
1686/* STRT 0 */ { BCM, Err, Err, BP, Err, Err, Err, Skip },
1687/* BCM 1 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1688/* CMCH 2 */ { Err, ECM, CMCH, Err, Err, NCM, CMCH, CMCH },
1689/* NCM 3 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1690/* ECM 4 */ { Err, Err, SCMF, Err, Err, Err, Err, Skip },
1691/* SCMF 5 */ { BCF, Err, Err, Err, Err, Err, Err, Skip },
1692/* BCF 6 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1693/* CFCH 7 */ { Err, ECF, CFCH, Err, Err, NCF, CFCH, CFCH },
1694/* NCF 8 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1695/* ECF 9 */ { Err, Err, Err, Err, Err, STRT, Err, Skip },
1696/* BP 10 */ { Err, Err, Err, Err, Err, Err, PMCH, Skip },
1697/* PMCH 11 */ { Err, Err, PMCH, Err, Err, SPMF, PMCH, PMCH },
1698/* SPMF 12 */ { Err, Err, Err, Err, Err, Err, PFCH, Skip },
1699/* PFCH 13 */ { Err, Err, PFCH, Err, EP, Err, PFCH, PFCH },
1700/* EP 14 */ { Err, Err, Err, Err, Err, STRT, Err, Skip }
1701 };
1702
1703 list.clear();
1704
1705 QList<QByteArrayList*> refList;
1706
1707 QByteArray buf;
1708 QList<QByteArrayList>::iterator rmf;
1709
1710 uint state = STRT;
1711 uint start = 0, end = 0, space = 0;
1712
1713 for (uint i = 0; i < len && state != Err ; ++i) {
1714 char ch = str[i];
1715 char *p = strchr(Chars, ch);
1716 uint input = p ? Inputs[p - Chars] : any;
1717 uint newState = Fsm[state][input];
1718 switch (newState) {
1719 case Skip:
1720 continue;
1721 case CMCH:
1722 case CFCH:
1723 case PMCH:
1724 case PFCH:
1725 if (state != newState)
1726 start = end = i;
1727 ++end;
1728 // accumulate trailing space for truncation
1729 if (input == ws) ++space;
1730 else space = 0;
1731 break;
1732 case NCM:
1733 case ECM:
1734 case SPMF:
1735 buf = QByteArray(str.data() + start, end - start - space);
1736 // find the mechanism branch in the output list
1737 for (rmf = list.begin(); rmf != list.end(); ++rmf) {
1738 if (rmf->first() == buf)
1739 break;
1740 }
1741 if (rmf == list.end()) {
1742 // append to the output list if not found
1743 QByteArrayList newRmf;
1744 newRmf.append(buf);
1745 rmf = list.insert(list.end(), newRmf);
1746 }
1747 // store a refecence in the helper list for making a cross product
1748 refList.append(&*rmf);
1749 start = end = 0;
1750 break;
1751 case NCF:
1752 case ECF:
1753 case EP:
1754 buf = QByteArray(str.data() + start, end - start - space);
1755 // make a cross product with all current mechanisms
1756 foreach(QByteArrayList *rmfRef, refList)
1757 rmfRef->append(buf);
1758 if (newState != NCF)
1759 refList.clear();
1760 start = end = 0;
1761 break;
1762 default:
1763 break;
1764 }
1765 state = newState;
1766 }
1767
1768 return state == ECF || state == EP;
1769}
1770
1771/*! \internal
1772
1773 Splits the given \a rmf (rendering mechanism/format pair) to a \a mechanism
1774 and a \a format string. Returns FALSE if fails to parse \a rmf.
1775 */
1776// static
1777bool QPMMime::parseRMF(HSTR rmf, QByteArray &mechanism, QByteArray &format)
1778{
1779 QList<QByteArrayList> list;
1780 if (!parseRMFs(rmf, list))
1781 return false;
1782
1783 if (list.count() != 1 || list.first().count() != 2)
1784 return false;
1785
1786 QByteArrayList first = list.first();
1787 mechanism = first.at(0);
1788 format = first.at(1);
1789
1790 return true;
1791}
1792
1793/*! \internal */
1794// static
1795QPMMime::DefaultDragWorker *QPMMime::defaultCoopDragWorker()
1796{
1797 static DefaultDragWorker defCoopDragWorker(false /* exclusive */);
1798 return &defCoopDragWorker;
1799}
1800
1801// static
1802/*! \internal */
1803QPMMime::DefaultDragWorker *QPMMime::defaultExclDragWorker()
1804{
1805 static DefaultDragWorker defExclDragWorker(true /* exclusive */);
1806 return &defExclDragWorker;
1807}
1808
1809/*! \internal */
1810// static
1811QPMMime::DefaultDropWorker *QPMMime::defaultDropWorker()
1812{
1813 static DefaultDropWorker defaultDropWorker;
1814 return &defaultDropWorker;
1815}
1816
1817#endif // !QT_NO_DRAGANDDROP
1818
1819//------------------------------------------------------------------------------
1820
1821class QPMMimeText : public QPMMime
1822{
1823public:
1824 QPMMimeText();
1825 ~QPMMimeText();
1826
1827 // for converting from Qt
1828 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
1829 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
1830 ULONG &flags, ULONG *data) const;
1831
1832 // for converting to Qt
1833 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
1834 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
1835 const QString &mimeType,
1836 QVariant::Type preferredType) const;
1837
1838#if !defined(QT_NO_DRAGANDDROP)
1839
1840 // Direct Manipulation (DND) converter interface
1841 DragWorker *dragWorkerFor(const QString &mimeType, QMimeData *mimeData);
1842 DropWorker *dropWorkerFor(DRAGINFO *info);
1843
1844 class NativeFileDrag : public DragWorker, public QPMObjectWindow
1845 {
1846 public:
1847 // DragWorker interface
1848 bool cleanup(bool isCancelled) { return true; } // always disallow Move
1849 bool isExclusive() const { return true; }
1850 ULONG itemCount() const { return 0; } // super exclusive
1851 HWND hwnd() const { return QPMObjectWindow::hwnd(); }
1852 DRAGINFO *createDragInfo(const QString &targetName, USHORT supportedOps);
1853 // QPMObjectWindow interface (dummy implementation, we don't need to interact)
1854 MRESULT message(ULONG msg, MPARAM mp1, MPARAM mp2) { return 0; }
1855 };
1856
1857 class NativeFileDrop : public DropWorker
1858 {
1859 public:
1860 // DropWorker interface
1861 bool isExclusive() const { return true; }
1862 bool hasFormat(const QString &mimeType) const;
1863 QStringList formats() const;
1864 QVariant retrieveData(const QString &mimeType,
1865 QVariant::Type preferredType) const;
1866 };
1867
1868 class TextDragProvider : public DefaultDragWorker::Provider
1869 {
1870 public:
1871 TextDragProvider() : exclusive(false) {}
1872 bool exclusive;
1873 // Provider interface
1874 QString format(const char *drf) const;
1875 bool provide(const char *drf, const QByteArray &allData,
1876 ULONG itemIndex, QByteArray &itemData);
1877 void fileType(const char *drf, QString &type, QString &ext);
1878 };
1879
1880 class TextDropProvider : public DefaultDropWorker::Provider
1881 {
1882 public:
1883 // Provider interface
1884 QByteArray drf(const QString &mimeType) const;
1885 bool provide(const QString &mimeType, ULONG itemIndex,
1886 const QByteArray &itemData, QByteArray &allData);
1887 };
1888
1889#endif // !QT_NO_DRAGANDDROP
1890
1891 const ULONG CF_TextUnicode;
1892 const ULONG CF_TextHtml;
1893
1894#if !defined(QT_NO_DRAGANDDROP)
1895 NativeFileDrag nativeFileDrag;
1896 NativeFileDrop nativeFileDrop;
1897 TextDragProvider textDragProvider;
1898 TextDropProvider textDropProvider;
1899#endif // !QT_NO_DRAGANDDROP
1900};
1901
1902QPMMimeText::QPMMimeText()
1903 // "text/unicode" is what Mozilla uses to for unicode
1904 : CF_TextUnicode (registerMimeType(QLatin1String("text/unicode")))
1905 // "text/html" is what Mozilla uses to for HTML
1906 , CF_TextHtml (registerMimeType(QLatin1String("text/html")))
1907{
1908}
1909
1910QPMMimeText::~QPMMimeText()
1911{
1912 unregisterMimeType(CF_TextHtml);
1913 unregisterMimeType(CF_TextUnicode);
1914}
1915
1916QList<QPMMime::MimeCFPair> QPMMimeText::formatsForMimeData(const QMimeData *mimeData) const
1917{
1918 QList<MimeCFPair> fmts;
1919 // prefer HTML as it's reacher
1920 if (mimeData->hasHtml())
1921 fmts << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
1922 // prefer unicode over local8Bit
1923 if (mimeData->hasText())
1924 fmts << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode)
1925 << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
1926 return fmts;
1927}
1928
1929// text/plain is defined as using CRLF, but so many programs don't,
1930// and programmers just look for '\n' in strings.
1931// OS/2 really needs CRLF, so we ensure it here.
1932bool QPMMimeText::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1933 ULONG &flags, ULONG *data) const
1934{
1935 if (!mimeData->hasText() ||
1936 (format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml))
1937 return false;
1938
1939 flags = CFI_POINTER;
1940
1941 if (data == NULL)
1942 return true; // delayed rendering, nothing to do
1943
1944 QByteArray r;
1945
1946 if (format == CF_TEXT) {
1947 QByteArray str = mimeData->text().toLocal8Bit();
1948 // Anticipate required space for CRLFs at 1/40
1949 int maxsize = str.size()+str.size()/40+1;
1950 r.fill('\0', maxsize);
1951 char *o = r.data();
1952 const char *d = str.data();
1953 const int s = str.size();
1954 bool cr = false;
1955 int j = 0;
1956 for (int i = 0; i < s; i++) {
1957 char c = d[i];
1958 if (c == '\r')
1959 cr = true;
1960 else {
1961 if (c == '\n') {
1962 if (!cr)
1963 o[j++] = '\r';
1964 }
1965 cr = false;
1966 }
1967 o[j++] = c;
1968 if (j+1 >= maxsize) {
1969 maxsize += maxsize/4;
1970 r.resize(maxsize);
1971 o = r.data();
1972 }
1973 }
1974 if (j < r.size())
1975 o[j] = '\0';
1976 } else if (format == CF_TextUnicode || CF_TextHtml) {
1977 QString str = format == CF_TextUnicode ?
1978 mimeData->text() : mimeData->html();
1979 const QChar *u = str.unicode();
1980 QString res;
1981 const int s = str.length();
1982 int maxsize = s + s/40 + 3;
1983 res.resize(maxsize);
1984 int ri = 0;
1985 bool cr = false;
1986 for (int i = 0; i < s; ++i) {
1987 if (*u == QLatin1Char('\r'))
1988 cr = true;
1989 else {
1990 if (*u == QLatin1Char('\n') && !cr)
1991 res[ri++] = QLatin1Char('\r');
1992 cr = false;
1993 }
1994 res[ri++] = *u;
1995 if (ri+3 >= maxsize) {
1996 maxsize += maxsize/4;
1997 res.resize(maxsize);
1998 }
1999 ++u;
2000 }
2001 res.truncate(ri);
2002 const int byteLength = res.length()*2;
2003 r.fill('\0', byteLength + 2);
2004 memcpy(r.data(), res.unicode(), byteLength);
2005 r[byteLength] = 0;
2006 r[byteLength+1] = 0;
2007 } else{
2008 return false;
2009 }
2010
2011 *data = QPMMime::allocateMemory(r.size());
2012 if (!*data)
2013 return false;
2014
2015 memcpy((void *)*data, r.data(), r.size());
2016 return true;
2017}
2018
2019QList<QPMMime::MimeCFPair> QPMMimeText::mimesForFormats(const QList<ULONG> &formats) const
2020{
2021 QList<MimeCFPair> mimes;
2022 // prefer HTML as it's reacher
2023 if (formats.contains(CF_TextHtml))
2024 mimes << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
2025 // prefer unicode over local8Bit
2026 if (formats.contains(CF_TextUnicode))
2027 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode);
2028 if (formats.contains(CF_TEXT))
2029 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
2030 return mimes;
2031}
2032
2033QVariant QPMMimeText::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2034 const QString &mimeType,
2035 QVariant::Type preferredType) const
2036{
2037 QVariant ret;
2038
2039 if (!mimeType.startsWith(QLatin1String("text/plain")) &&
2040 !mimeType.startsWith(QLatin1String("text/html")))
2041 return ret;
2042 if ((format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml) ||
2043 !(flags & CFI_POINTER) || !data)
2044 return ret;
2045
2046 QString str;
2047
2048 if (format == CF_TEXT) {
2049 const char *d = (const char *)data;
2050 QByteArray r("");
2051 if (*d) {
2052 const int s = qstrlen(d);
2053 r.fill('\0', s);
2054 char *o = r.data();
2055 int j = 0;
2056 for (int i = 0; i < s; i++) {
2057 char c = d[i];
2058 if (c != '\r')
2059 o[j++] = c;
2060 }
2061 }
2062 str = QString::fromLocal8Bit(r);
2063 } else if (format == CF_TextUnicode || CF_TextHtml) {
2064 str = QString::fromUtf16((const unsigned short *)data);
2065 str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
2066 }
2067
2068 if (preferredType == QVariant::String)
2069 ret = str;
2070 else
2071 ret = str.toUtf8();
2072
2073 return ret;
2074}
2075
2076#if !defined(QT_NO_DRAGANDDROP)
2077
2078DRAGINFO *QPMMimeText::NativeFileDrag::createDragInfo(const QString &targetName,
2079 USHORT supportedOps)
2080{
2081 Q_ASSERT(source());
2082 if (!source())
2083 return 0;
2084
2085 // obtain the list of files
2086 QList<QUrl> list;
2087 if (source()->hasUrls())
2088 list = source()->urls();
2089 ULONG itemCnt = list.count();
2090 Q_ASSERT(itemCnt);
2091 if (!itemCnt)
2092 return 0;
2093
2094 DEBUG(() << "QPMMimeText::NativeFileDrag: itemCnt" << itemCnt);
2095
2096 DRAGINFO *info = DrgAllocDraginfo(itemCnt);
2097 Q_ASSERT(info);
2098 if (!info)
2099 return 0;
2100
2101 bool ok = true;
2102 QList<QUrl>::iterator it = list.begin();
2103 for (ULONG i = 0; i < itemCnt; ++i, ++it) {
2104 DRAGITEM *item = DrgQueryDragitemPtr(info, i);
2105 Q_ASSERT(item);
2106 if (!item) {
2107 ok = false;
2108 break;
2109 }
2110
2111 QByteArray fileName = QFile::encodeName(QDir::convertSeparators(it->toLocalFile()));
2112
2113 int sep = fileName.lastIndexOf('\\');
2114 Q_ASSERT(sep > 0 && sep < fileName.length() - 1);
2115 if (sep <= 0 || sep >= fileName.length() - 1) {
2116 ok = false;
2117 break;
2118 }
2119
2120 item->hstrSourceName = DrgAddStrHandle(fileName.data() + sep + 1);
2121 fileName.truncate(sep + 1);
2122 item->hstrContainerName = DrgAddStrHandle(fileName);
2123
2124 DEBUG(() << "QPMMimeText::NativeFileDrag: item" << i
2125 << "dir" << queryHSTR(item->hstrContainerName)
2126 << "name" << queryHSTR(item->hstrSourceName));
2127
2128 item->hwndItem = hwnd();
2129 item->ulItemID = 0;
2130 item->hstrType = DrgAddStrHandle(DRT_UNKNOWN);
2131 item->hstrRMF = DrgAddStrHandle("<DRM_OS2FILE,DRF_UNKNOWN>");
2132 item->hstrTargetName = 0;
2133 item->cxOffset = 0;
2134 item->cyOffset = 0;
2135 item->fsControl = 0;
2136 item->fsSupportedOps = supportedOps;
2137 }
2138
2139 if (!ok) {
2140 DrgFreeDraginfo(info);
2141 info = 0;
2142 }
2143
2144 return info;
2145}
2146
2147bool QPMMimeText::NativeFileDrop::hasFormat(const QString &mimeType) const
2148{
2149 return mimeType == QLatin1String("text/uri-list");
2150}
2151
2152QStringList QPMMimeText::NativeFileDrop::formats() const
2153{
2154 QStringList mimes;
2155 mimes << QLatin1String("text/uri-list");
2156 return mimes;
2157}
2158
2159QVariant QPMMimeText::NativeFileDrop::retrieveData(const QString &mimeType,
2160 QVariant::Type preferredType) const
2161{
2162 QVariant result;
2163
2164 Q_ASSERT(info());
2165 if (!info())
2166 return result;
2167
2168 ULONG itemCount = DrgQueryDragitemCount(info());
2169 Q_ASSERT(itemCount);
2170 if (!itemCount)
2171 return result;
2172
2173 // sanity check
2174 if (mimeType != QLatin1String("text/uri-list"))
2175 return result;
2176
2177 QList<QVariant> urls;
2178
2179 for (ULONG i = 0; i < itemCount; ++i) {
2180 DRAGITEM *item = DrgQueryDragitemPtr(info(), i);
2181 Q_ASSERT(item);
2182 QByteArray fullName;
2183 if (!item || !canTargetRenderAsOS2File(item, &fullName))
2184 return result;
2185 QString fn = QFile::decodeName(fullName);
2186 urls += QUrl::fromLocalFile(fn);
2187 }
2188
2189 if (preferredType == QVariant::Url && urls.size() == 1)
2190 result = urls.at(0);
2191 else if (!urls.isEmpty())
2192 result = urls;
2193
2194 return result;
2195}
2196
2197QString QPMMimeText::TextDragProvider::format(const char *drf) const
2198{
2199 QString result;
2200
2201 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2202 if (exclusive)
2203 result = QLatin1String("text/uri-list");
2204 else
2205 result = QLatin1String("text/plain");
2206 }
2207 return result;
2208}
2209
2210bool QPMMimeText::TextDragProvider::provide(const char *drf,
2211 const QByteArray &allData,
2212 ULONG itemIndex,
2213 QByteArray &itemData)
2214{
2215 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2216 if (exclusive) {
2217 // locate the required item
2218 int dataSize = allData.size();
2219 if (!dataSize)
2220 return false;
2221 int begin = 0, end = 0, next = 0;
2222 do {
2223 begin = next;
2224 end = allData.indexOf('\r', begin);
2225 if (end >= 0) {
2226 next = end + 1;
2227 if (next < dataSize && allData[next] == '\n')
2228 ++next;
2229 } else {
2230 end = allData.indexOf('\n', begin);
2231 if (end >= 0)
2232 next = end + 1;
2233 }
2234 } while (itemIndex-- && end >= 0 && next < dataSize);
2235 int urlLen = end - begin;
2236 if (urlLen <= 0)
2237 return false;
2238 QUrl url = QUrl(QString::fromUtf8(allData.data() + begin, urlLen));
2239 if (!url.isValid())
2240 return false;
2241 itemData = url.toEncoded();
2242 } else {
2243 itemData = QString::fromUtf8(allData).toLocal8Bit();
2244 }
2245 return true;
2246 }
2247 return false;
2248}
2249
2250void QPMMimeText::TextDragProvider::fileType(const char *drf,
2251 QString &type, QString &ext)
2252{
2253 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2254 if (exclusive) {
2255 type = QLatin1String("UniformResourceLocator");
2256 // no extension for URLs
2257 ext = QString::null;
2258 } else {
2259 type = QLatin1String(DRT_TEXT);
2260 ext = QLatin1String("txt");
2261 }
2262 }
2263};
2264
2265QByteArray QPMMimeText::TextDropProvider::drf(const QString &mimeType) const
2266{
2267 // sanity check
2268 if (mimeType == QLatin1String("text/plain") ||
2269 mimeType == QLatin1String("text/uri-list"))
2270 return QByteArray("DRF_TEXT");
2271 return 0;
2272}
2273
2274bool QPMMimeText::TextDropProvider::provide(const QString &mimeType,
2275 ULONG itemIndex,
2276 const QByteArray &itemData,
2277 QByteArray &allData)
2278{
2279 if (mimeType == QLatin1String("text/plain")) {
2280 allData = QString::fromLocal8Bit(itemData).toUtf8();
2281 return true;
2282 }
2283
2284 if (mimeType == QLatin1String("text/uri-list")) {
2285 QUrl url = QUrl::fromEncoded(itemData);
2286 if (!url.isValid())
2287 return false;
2288 // append the URL to the list
2289 allData += url.toString().toUtf8();
2290 allData += "\r\n";
2291 return true;
2292 }
2293
2294 return false;
2295}
2296
2297QPMMime::DragWorker *QPMMimeText::dragWorkerFor(const QString &mimeType,
2298 QMimeData *mimeData)
2299{
2300 if (mimeType == QLatin1String("text/plain")) {
2301 // add a cooperative provider
2302 textDragProvider.exclusive = false;
2303 DefaultDragWorker *defWorker = defaultCoopDragWorker();
2304 defWorker->addProvider("DRF_TEXT", &textDragProvider);
2305 return defWorker;
2306 }
2307
2308 if (mimeType == QLatin1String("text/uri-list")) {
2309 // see what kind of items text/uri-list represents
2310 QList<QUrl> urls = mimeData->urls();
2311 int fileCnt = 0;
2312 foreach (const QUrl &url, urls) {
2313 if (url.scheme() == QLatin1String("file"))
2314 ++fileCnt;
2315 }
2316 if (fileCnt && fileCnt == urls.count()) {
2317 // all items are local files, return an exclusive file drag worker
2318 return &nativeFileDrag;
2319 }
2320 if (urls.count() && !fileCnt) {
2321 // all items are non-files, add an exclusive provider for the
2322 // specified item count
2323 textDragProvider.exclusive = true;
2324 DefaultDragWorker *defWorker = defaultExclDragWorker();
2325 bool ok = defWorker->addProvider("DRF_TEXT", &textDragProvider,
2326 urls.count());
2327 return ok ? defWorker : 0;
2328 }
2329 // if items are mixed, we return NULL to fallback to QPMMimeAnyMime
2330 }
2331
2332 return 0;
2333}
2334
2335QPMMime::DropWorker *QPMMimeText::dropWorkerFor(DRAGINFO *info)
2336{
2337 ULONG itemCount = DrgQueryDragitemCount(info);
2338 Q_ASSERT(itemCount);
2339 if (!itemCount)
2340 return 0;
2341
2342 if (itemCount == 1) {
2343 DRAGITEM *item = DrgQueryDragitemPtr(info, 0);
2344 Q_ASSERT(item);
2345 if (!item)
2346 return 0;
2347 // proceed only if the target cannot render DRM_OS2FILE on its own
2348 // and if the item type is not "UniformResourceLocator" (which will be
2349 // processed below)
2350 if (!canTargetRenderAsOS2File(item) &&
2351 !DrgVerifyType(item, "UniformResourceLocator")) {
2352 DefaultDropWorker *defWorker = defaultDropWorker();
2353 // check that we support one of DRMs and the format is DRF_TEXT
2354 if (defWorker->canRender(item, "DRF_TEXT")) {
2355 // add a cooperative provider (can coexist with others)
2356 defWorker->addProvider(QLatin1String("text/plain"),
2357 &textDropProvider);
2358 return defWorker;
2359 }
2360 return 0;
2361 }
2362 }
2363
2364 // Either the target can render DRM_OS2FILE on its own (so it's a valid
2365 // file/directory name), or it's an "UniformResourceLocator", or there is
2366 // more than one drag item. Check that all items are of either one type
2367 // or another. If so, we can represent them as 'text/uri-list'.
2368 bool allAreFiles = true;
2369 bool allAreURLs = true;
2370 DefaultDropWorker *defWorker = defaultDropWorker();
2371 for (ULONG i = 0; i < itemCount; ++i) {
2372 DRAGITEM *item = DrgQueryDragitemPtr(info, i);
2373 Q_ASSERT(item);
2374 if (!item)
2375 return 0;
2376 if (allAreFiles)
2377 allAreFiles &= canTargetRenderAsOS2File(item);
2378 if (allAreURLs)
2379 allAreURLs &= DrgVerifyType(item, "UniformResourceLocator") &&
2380 defWorker->canRender(item, "DRF_TEXT");
2381 if (!allAreFiles && !allAreURLs)
2382 return 0;
2383 }
2384
2385 // Note: both allAreFiles and allAreURLs may be true here (e.g. a file on
2386 // the desktop that represents an URL object). In this case, we will treat
2387 // the list as files rather than as URLs for similarity with other platforms
2388 // (e.g. an Internet shortcut on Windows is interpreted as a local file as
2389 // well).
2390
2391 if (allAreFiles) {
2392 // return an exclusive drop worker
2393 return &nativeFileDrop;
2394 }
2395
2396 // add an exclusive provider (can neither coexist with other workers
2397 // or providers)
2398 bool ok = defWorker->addExclusiveProvider(QLatin1String("text/uri-list"),
2399 &textDropProvider);
2400 return ok ? defWorker : 0;
2401}
2402
2403#endif // !QT_NO_DRAGANDDROP
2404
2405//------------------------------------------------------------------------------
2406
2407class QPMMimeImage : public QPMMime
2408{
2409public:
2410 QPMMimeImage();
2411
2412 // for converting from Qt
2413 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2414 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2415 ULONG &flags, ULONG *data) const;
2416 // for converting to Qt
2417 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2418 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2419 const QString &mimeType,
2420 QVariant::Type preferredType) const;
2421};
2422
2423QPMMimeImage::QPMMimeImage()
2424{
2425}
2426
2427QList<QPMMime::MimeCFPair> QPMMimeImage::formatsForMimeData(const QMimeData *mimeData) const
2428{
2429 QList<MimeCFPair> fmts;
2430 if (mimeData->hasImage()) {
2431 // "application/x-qt-image" seems to be used as a single name for all
2432 // "image/xxx" types in Qt
2433 fmts << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2434 }
2435 return fmts;
2436}
2437
2438bool QPMMimeImage::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2439 ULONG &flags, ULONG *data) const
2440{
2441 if (!mimeData->hasImage() || format != CF_BITMAP)
2442 return false;
2443
2444 flags = CFI_HANDLE;
2445
2446 if (data == NULL)
2447 return true; // delayed rendering, nothing to do
2448
2449 QImage img = qvariant_cast<QImage>(mimeData->imageData());
2450 if (img.isNull())
2451 return false;
2452
2453 QPixmap pm = QPixmap::fromImage(img);
2454 if (pm.isNull())
2455 return false;
2456
2457 HBITMAP bmp = pm.toPmHBITMAP(0, true);
2458 if (bmp == NULLHANDLE)
2459 return false;
2460
2461 *data = bmp;
2462 return true;
2463}
2464
2465QList<QPMMime::MimeCFPair> QPMMimeImage::mimesForFormats(const QList<ULONG> &formats) const
2466{
2467 QList<MimeCFPair> mimes;
2468 if (formats.contains(CF_BITMAP))
2469 mimes << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2470 return mimes;
2471}
2472
2473QVariant QPMMimeImage::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2474 const QString &mimeType,
2475 QVariant::Type preferredType) const
2476{
2477 Q_UNUSED(preferredType);
2478
2479 QVariant ret;
2480
2481 if (mimeType != QLatin1String("application/x-qt-image"))
2482 return ret;
2483 if (format != CF_BITMAP || !(flags & CFI_HANDLE) || !data)
2484 return ret;
2485
2486 QPixmap pm = QPixmap::fromPmHBITMAP((HBITMAP)data);
2487 if (pm.isNull())
2488 return ret;
2489
2490 ret = pm.toImage();
2491 return ret;
2492}
2493
2494//------------------------------------------------------------------------------
2495
2496class QPMMimeAnyMime : public QPMMime
2497{
2498public:
2499 QPMMimeAnyMime();
2500 ~QPMMimeAnyMime();
2501
2502 // for converting from Qt
2503 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2504 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2505 ULONG &flags, ULONG *data) const;
2506 // for converting to Qt
2507 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2508 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2509 const QString &mimeType,
2510 QVariant::Type preferredType) const;
2511
2512#if !defined(QT_NO_DRAGANDDROP)
2513
2514 // Direct Manipulation (DND) converter interface
2515 DragWorker *dragWorkerFor(const QString &mimeType, QMimeData *mimeData);
2516 DropWorker *dropWorkerFor(DRAGINFO *info);
2517
2518 class AnyDragProvider : public DefaultDragWorker::Provider
2519 {
2520 public:
2521 AnyDragProvider(QPMMimeAnyMime *am) : anyMime(am) {}
2522 // Provider interface
2523 QString format(const char *drf) const;
2524 bool provide(const char *drf, const QByteArray &allData,
2525 ULONG itemIndex, QByteArray &itemData);
2526 void fileType(const char *drf, QString &type, QString &ext);
2527 private:
2528 QPMMimeAnyMime *anyMime;
2529 };
2530
2531 class AnyDropProvider : public DefaultDropWorker::Provider
2532 {
2533 public:
2534 AnyDropProvider(QPMMimeAnyMime *am) : anyMime(am) {}
2535 // Provider interface
2536 QByteArray drf(const QString &mimeType) const;
2537 bool provide(const QString &mimeType, ULONG itemIndex,
2538 const QByteArray &itemData, QByteArray &allData);
2539 private:
2540 QPMMimeAnyMime *anyMime;
2541 };
2542
2543#endif // !QT_NO_DRAGANDDROP
2544
2545private:
2546 ULONG registerMimeType(const QString &mime) const;
2547 QString registerFormat(ULONG format) const;
2548
2549 mutable QMap<QString, ULONG> cfMap;
2550 mutable QMap<ULONG, QString> mimeMap;
2551
2552 static QStringList ianaTypes;
2553 static QString mimePrefix;
2554 static QString customPrefix;
2555
2556#if !defined(QT_NO_DRAGANDDROP)
2557
2558 static ULONG drfToCf(const char *drf);
2559 static QByteArray cfToDrf(ULONG cf);
2560
2561 AnyDragProvider anyDragProvider;
2562 AnyDropProvider anyDropProvider;
2563
2564// friend class AnyDragProvider;
2565// friend class AnyDropProvider;
2566
2567#endif // !QT_NO_DRAGANDDROP
2568};
2569
2570// static
2571QStringList QPMMimeAnyMime::ianaTypes;
2572QString QPMMimeAnyMime::mimePrefix;
2573QString QPMMimeAnyMime::customPrefix;
2574
2575QPMMimeAnyMime::QPMMimeAnyMime()
2576#if !defined(QT_NO_DRAGANDDROP)
2577 : anyDragProvider(AnyDragProvider(this))
2578 , anyDropProvider(AnyDropProvider(this))
2579#endif // !QT_NO_DRAGANDDROP
2580{
2581 //MIME Media-Types
2582 if (!ianaTypes.size()) {
2583 ianaTypes.append(QLatin1String("application/"));
2584 ianaTypes.append(QLatin1String("audio/"));
2585 ianaTypes.append(QLatin1String("example/"));
2586 ianaTypes.append(QLatin1String("image/"));
2587 ianaTypes.append(QLatin1String("message/"));
2588 ianaTypes.append(QLatin1String("model/"));
2589 ianaTypes.append(QLatin1String("multipart/"));
2590 ianaTypes.append(QLatin1String("text/"));
2591 ianaTypes.append(QLatin1String("video/"));
2592
2593 mimePrefix = QLatin1String("x-mime:");
2594 customPrefix = QLatin1String("application/x-qt-pm-mime;value=\"");
2595 }
2596}
2597
2598QPMMimeAnyMime::~QPMMimeAnyMime()
2599{
2600 foreach(ULONG cf, cfMap.values())
2601 unregisterMimeType(cf);
2602}
2603
2604QList<QPMMime::MimeCFPair> QPMMimeAnyMime::formatsForMimeData(const QMimeData *mimeData) const
2605{
2606 QList<MimeCFPair> fmts;
2607
2608 QStringList mimes = QInternalMimeData::formatsHelper(mimeData);
2609 foreach (QString mime, mimes) {
2610 ULONG cf = cfMap.value(mime);
2611 if (!cf)
2612 cf = registerMimeType(mime);
2613 if (cf)
2614 fmts << MimeCFPair(mime, cf);
2615 }
2616
2617 return fmts;
2618}
2619
2620bool QPMMimeAnyMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2621 ULONG &flags, ULONG *data) const
2622{
2623 QString mime = mimeMap.value(format);
2624 if (mime.isNull())
2625 return false;
2626
2627 flags = CFI_POINTER;
2628
2629 if (data == NULL)
2630 return true; // delayed rendering, nothing to do
2631
2632 QByteArray r = QInternalMimeData::renderDataHelper(mime, mimeData);
2633 if (r.isNull())
2634 return false;
2635
2636 *data = QPMMime::allocateMemory(r.size() + sizeof(ULONG));
2637 if (!*data)
2638 return false;
2639
2640 *((ULONG *)(*data)) = r.size();
2641 memcpy((void *)(*data + sizeof(ULONG)), r.data(), r.size());
2642 return true;
2643}
2644
2645QList<QPMMime::MimeCFPair> QPMMimeAnyMime::mimesForFormats(const QList<ULONG> &formats) const
2646{
2647 QList<MimeCFPair> mimes;
2648
2649 foreach (ULONG format, formats) {
2650 QString mime = mimeMap.value(format);
2651 if (mime.isEmpty())
2652 mime = registerFormat(format);
2653 if (!mime.isEmpty())
2654 mimes << MimeCFPair(mime, format);
2655 }
2656
2657 return mimes;
2658}
2659
2660QVariant QPMMimeAnyMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2661 const QString &mimeType,
2662 QVariant::Type preferredType) const
2663{
2664 Q_UNUSED(preferredType);
2665
2666 QVariant ret;
2667
2668 if (cfMap.value(mimeType) != format)
2669 return ret;
2670
2671 if (!(flags & CFI_POINTER) || !data)
2672 return ret;
2673
2674 // get the real block size (always rounded to the page boundary (4K))
2675 ULONG sz = ~0, fl = 0, arc;
2676 arc = DosQueryMem((PVOID)data, &sz, &fl);
2677 if (arc != NO_ERROR) {
2678#ifndef QT_NO_DEBUG
2679 qWarning("QPMMimeText::convertFromFormat: DosQueryMem failed with %lu", arc);
2680#endif
2681 return ret;
2682 }
2683 ULONG size = *((ULONG *)data);
2684 if (!size || size + sizeof(ULONG) > sz)
2685 return ret;
2686
2687 // it should be enough to return the data and let QMimeData do the rest.
2688 ret = QByteArray((const char *)(data + sizeof(ULONG)), size);
2689 return ret;
2690}
2691
2692#if !defined(QT_NO_DRAGANDDROP)
2693
2694QString QPMMimeAnyMime::AnyDragProvider::format(const char *drf) const
2695{
2696 ULONG cf = drfToCf(drf);
2697 if (cf) {
2698 QString mime = anyMime->mimeMap.value(cf);
2699 if (!mime.isEmpty())
2700 return mime;
2701 }
2702
2703 // There must always be a match since the given drf is associated with this
2704 // provider by dragWorkerFor() and all necessary mappings are there.
2705 Q_ASSERT(false);
2706 return QString::null;
2707}
2708
2709bool QPMMimeAnyMime::AnyDragProvider::provide(const char *drf,
2710 const QByteArray &allData,
2711 ULONG itemIndex,
2712 QByteArray &itemData)
2713{
2714 Q_UNUSED(drf);
2715 Q_UNUSED(itemIndex);
2716
2717 // always straight through coversion
2718 itemData = allData;
2719 return true;
2720}
2721
2722void QPMMimeAnyMime::AnyDragProvider::fileType(const char *drf,
2723 QString &type, QString &ext)
2724{
2725 // file type = mime
2726 type = format(drf);
2727 Q_ASSERT(!type.isEmpty());
2728
2729 // no way to determine the extension
2730 ext = QString::null;
2731};
2732
2733QByteArray QPMMimeAnyMime::AnyDropProvider::drf(const QString &mimeType) const
2734{
2735 ULONG cf = anyMime->cfMap.value(mimeType);
2736 if (cf)
2737 return cfToDrf(cf);
2738
2739 // There must always be a match since the given drf is associated with this
2740 // provider by dragWorkerFor() and all necessary mappings are there.
2741 Q_ASSERT(false);
2742 return QByteArray();
2743}
2744
2745bool QPMMimeAnyMime::AnyDropProvider::provide(const QString &mimeType,
2746 ULONG itemIndex,
2747 const QByteArray &itemData,
2748 QByteArray &allData)
2749{
2750 Q_UNUSED(mimeType);
2751 Q_UNUSED(itemIndex);
2752
2753 // always straight through coversion
2754 allData = itemData;
2755 return true;
2756}
2757
2758QPMMime::DragWorker *QPMMimeAnyMime::dragWorkerFor(const QString &mimeType,
2759 QMimeData *mimeData)
2760{
2761 ULONG cf = cfMap.value(mimeType);
2762 if (!cf)
2763 cf = registerMimeType(mimeType);
2764 if (cf) {
2765 DefaultDragWorker *defWorker = defaultCoopDragWorker();
2766 // add a cooperative provider
2767 defWorker->addProvider(cfToDrf(cf), &anyDragProvider);
2768 return defWorker;
2769 }
2770
2771 Q_ASSERT(false);
2772 return 0;
2773}
2774
2775QPMMime::DropWorker *QPMMimeAnyMime::dropWorkerFor(DRAGINFO *info)
2776{
2777 ULONG itemCount = DrgQueryDragitemCount(info);
2778 Q_ASSERT(itemCount);
2779 if (!itemCount)
2780 return 0;
2781
2782 if (itemCount == 1) {
2783 DRAGITEM *item = DrgQueryDragitemPtr(info, 0);
2784 Q_ASSERT(item);
2785 if (!item)
2786 return 0;
2787
2788 DefaultDropWorker *defWorker = defaultDropWorker();
2789 bool atLeastOneSupported = false;
2790
2791 // check that we support one of DRMs and the format is CF_hhhhhhh
2792 QList<QByteArrayList> list;
2793 defWorker->getSupportedRMFs(item, list);
2794 foreach(const QByteArrayList &mech, list) {
2795 QByteArrayList::const_iterator it = mech.begin();
2796 Q_ASSERT(it != mech.end());
2797 DEBUG(() << "QPMMimeAnyMime: Supported drm:" << *it);
2798 for (++it; it != mech.end(); ++it) {
2799 const QByteArray &drf = *it;
2800 ULONG cf = drfToCf(drf);
2801 if (cf) {
2802 DEBUG(() << "QPMMimeAnyMime: Supported drf:" << drf);
2803 QString mime = mimeMap.value(cf);
2804 if (mime.isEmpty())
2805 mime = registerFormat(cf);
2806 Q_ASSERT(!mime.isEmpty());
2807 if (!mime.isEmpty()) {
2808 DEBUG(() << "QPMMimeAnyMime: Will provide [" << mime
2809 << "] for drf" << drf);
2810 // add a cooperative provider (can coexist with others)
2811 defWorker->addProvider(mime, &anyDropProvider);
2812 atLeastOneSupported = true;
2813 }
2814 }
2815 }
2816 }
2817
2818 if (atLeastOneSupported)
2819 return defWorker;
2820 }
2821
2822 return 0;
2823}
2824
2825#endif // !QT_NO_DRAGANDDROP
2826
2827ULONG QPMMimeAnyMime::registerMimeType(const QString &mime) const
2828{
2829 if (mime.isEmpty())
2830 return 0;
2831
2832 QString mimeToReg = mime;
2833
2834 bool ianaType = false;
2835 foreach(QString prefix, ianaTypes) {
2836 if (mime.startsWith(prefix)) {
2837 ianaType = true;
2838 break;
2839 }
2840 }
2841 if (!ianaType) {
2842 // prepend the non-standard type with the prefix that makes it comply
2843 // with the standard
2844 mimeToReg = customPrefix + mime + QLatin1Char('\"');
2845 }
2846
2847 mimeToReg = mimePrefix + mimeToReg;
2848 ULONG cf = QPMMime::registerMimeType(mimeToReg);
2849 if (cf) {
2850 cfMap[mime] = cf;
2851 mimeMap[cf] = mime;
2852 }
2853 return cf;
2854}
2855
2856QString QPMMimeAnyMime::registerFormat(ULONG format) const
2857{
2858 QString mime;
2859
2860 if (!format)
2861 return mime;
2862
2863 QString atomStr = formatName(format);
2864 if (atomStr.startsWith(mimePrefix)) {
2865 // the format represents the mime type we can recognize
2866 // increase the reference count
2867 ULONG cf = QPMMime::registerMimeType(atomStr);
2868 Q_ASSERT(cf == format);
2869 // extract the real mime type (w/o our prefix)
2870 mime = atomStr.mid(mimePrefix.size());
2871 if (!mime.isEmpty()) {
2872 cfMap[mime] = cf;
2873 mimeMap[cf] = mime;
2874 }
2875 }
2876 return mime;
2877}
2878
2879#if !defined(QT_NO_DRAGANDDROP)
2880
2881// static
2882ULONG QPMMimeAnyMime::drfToCf(const char *drf)
2883{
2884 if (qstrncmp(drf, "CF_", 3) == 0)
2885 return QString(QLatin1String(drf + 3)).toULong(0, 16);
2886 return 0;
2887}
2888
2889// static
2890QByteArray QPMMimeAnyMime::cfToDrf(ULONG cf)
2891{
2892 return QString().sprintf("CF_%08lX", cf).toLatin1();
2893}
2894
2895#endif // !QT_NO_DRAGANDDROP
2896
2897//------------------------------------------------------------------------------
2898
2899QPMMimeList::QPMMimeList()
2900 : initialized(false)
2901{
2902}
2903
2904QPMMimeList::~QPMMimeList()
2905{
2906 while (list.size())
2907 delete list.first();
2908}
2909
2910
2911void QPMMimeList::init()
2912{
2913 if (!initialized) {
2914 initialized = true;
2915 new QPMMimeAnyMime; // must be the first (used as a fallback)
2916 new QPMMimeImage;
2917 new QPMMimeText;
2918 }
2919}
2920
2921void QPMMimeList::addMime(QPMMime *mime)
2922{
2923 init();
2924 list.prepend(mime);
2925}
2926
2927void QPMMimeList::removeMime(QPMMime *mime)
2928{
2929 init();
2930 list.removeAll(mime);
2931}
2932
2933QList<QPMMime*> QPMMimeList::mimes()
2934{
2935 init();
2936 return list;
2937}
2938
2939QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.