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

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

gui: QPMMime: Adding more DND support code, fixing the build.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 74.2 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#ifndef QT_NO_DEBUG
194 if (rc)
195 qWarning("qt_SetFileTypeEA: DosSetPathInfo failed with %ld", rc);
196#endif
197
198 delete[] eaData;
199}
200
201static int hex_to_int(uchar c)
202{
203 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
204 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
205 if (c >= '0' && c <= '9') return c - '0';
206 return -1;
207}
208
209static inline int hex_to_int(char c)
210{
211 return hex_to_int((uchar) c);
212}
213
214// -----------------------------------------------------------------------------
215
216struct QPMMime::DefaultDragWorker::Data
217{
218 Data(bool excl) : exclusive(excl) {}
219
220 struct Request
221 {
222 Request(ULONG i, Provider *p, const char *m, const char *f)
223 : index(i), provider(p), drm(m), drf(f)
224 , xfer(0), rendered(false), sharedMem(0) {}
225
226 ~Request()
227 {
228 // free memory allocated for the target that requested DRM_SHAREDMEM
229 if (sharedMem)
230 DosFreeMem(sharedMem);
231 Q_ASSERT(!xfer);
232 }
233
234 ULONG index;
235 Provider *provider;
236 QByteArray drm;
237 QByteArray drf;
238 DRAGTRANSFER *xfer;
239 bool rendered;
240 PVOID sharedMem;
241 };
242
243 inline bool isInitialized() { return providers.count() != 0; }
244 void cleanupRequests();
245
246 struct DrfProvider
247 {
248 DrfProvider() : prov(0) {}
249 DrfProvider(const char *d, Provider *p) : drf(d), prov(p) {}
250 QByteArray drf;
251 Provider *prov;
252 };
253
254 typedef QList<DrfProvider> DrfProviderList;
255
256 Provider *providerFor(const char *drf)
257 {
258 foreach (const DrfProvider &dp, providers)
259 if (qstrcmp(dp.drf, drf) == 0)
260 return dp.prov;
261 return 0;
262 }
263
264 const bool exclusive : 1;
265 DrfProviderList providers;
266
267 ULONG itemCnt;
268 QHash<ULONG, Request*> requests;
269 bool renderOk : 1;
270};
271
272void QPMMime::DefaultDragWorker::Data::cleanupRequests()
273{
274 if (requests.count()) {
275#ifndef QT_NO_DEBUG
276 qWarning("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#endif
280 qDeleteAll(requests);
281 requests.clear();
282 }
283}
284
285QPMMime::DefaultDragWorker::DefaultDragWorker(bool exclusive)
286 : d(new Data(exclusive))
287{
288 d->itemCnt = 0;
289 d->renderOk = true;
290}
291
292QPMMime::DefaultDragWorker::~DefaultDragWorker()
293{
294 d->cleanupRequests();
295 delete d;
296}
297
298bool QPMMime::DefaultDragWorker::cleanup(bool isCancelled)
299{
300 // the return value is: true if the source-side Move for the given
301 // drag object should be *dis*allowed, false otherwise (including cases
302 // when this DragWorker didn't participate to the conversation at all)
303
304 // sanity check
305 Q_ASSERT(d->isInitialized());
306 if (!d->isInitialized())
307 return true;
308
309 bool moveDisallowed = false;
310
311 DEBUG(() << "DefaultDragWorker: Session ended (cancelled" << isCancelled
312 << "requests.left" << d->requests.count() << ")");
313
314 if (d->requests.count()) {
315 // always disallow Move if not all requests got DM_ENDCONVERSATION
316 moveDisallowed = true;
317 } else {
318 // disallow Move if rendering of some item failed
319 moveDisallowed = !d->renderOk;
320 }
321
322 DEBUG(() << "DefaultDragWorker: moveDisallowed" << moveDisallowed);
323
324 // Note: remaining requests will be lazily deleted by cleanupRequests()
325 // when a new DND session is started
326
327 d->renderOk = true;
328 d->itemCnt = 0;
329
330 // Indicate we're cleaned up (i.e. the DND session is finished)
331 d->providers.clear();
332
333 return moveDisallowed;
334}
335
336bool QPMMime::DefaultDragWorker::isExclusive() const
337{
338 return d->exclusive;
339}
340
341ULONG QPMMime::DefaultDragWorker::itemCount() const
342{
343 return d->itemCnt;
344}
345
346QByteArray QPMMime::DefaultDragWorker::composeFormatString()
347{
348 QByteArray formats;
349
350 // sanity checks
351 Q_ASSERT(d->isInitialized());
352 if (!d->isInitialized())
353 return formats;
354
355 bool first = true;
356 foreach(const Data::DrfProvider &p, d->providers) {
357 if (first)
358 first = false;
359 else
360 formats += ",";
361 formats += p.drf;
362 }
363
364 Q_ASSERT(!formats.isNull());
365 if (formats.isNull())
366 return formats;
367
368 // DRM_SHAREDMEM comes first to prevent native DRM_OS2FILE
369 // rendering on the target side w/o involving the source.
370 // Also, we add <DRM_SHAREDMEM,DRF_POINTERDATA> just like WPS does it
371 // (however, it doesn't help when dropping objects to it -- WPS still
372 // chooses DRM_OS2FILE).
373 formats = "(DRM_SHAREDMEM,DRM_OS2FILE)x(" + formats + "),"
374 "<DRM_SHAREDMEM,DRF_POINTERDATA>";
375
376 DEBUG(() << "DefaultDragWorker: formats" << formats
377 << ", itemCnt" << d->itemCnt);
378
379 return formats;
380}
381
382bool QPMMime::DefaultDragWorker::prepare(const char *drm, const char *drf,
383 DRAGITEM *item, ULONG itemIndex)
384{
385 // sanity checks
386 Q_ASSERT(d->isInitialized());
387 if (!d->isInitialized())
388 return false;
389
390 Q_ASSERT(item && itemIndex < d->itemCnt);
391 if (!item || itemIndex >= d->itemCnt)
392 return false;
393
394 DEBUG(() << "DefaultDragWorker: Preparing item" << itemIndex << "(id "
395 << item->ulItemID << ") for <" << drm << "," << drf << ">");
396
397 Provider *p = d->providerFor(drf);
398
399 if (!canRender(drm) || p == NULL) {
400 DEBUG(() << "DefaultDragWorker: Cannot render the given RMF");
401 return false;
402 }
403
404 Data::Request *req = d->requests.value(item->ulItemID);
405
406 if (req) {
407 // this item has been already prepared, ensure it has also been
408 // rendered already
409 Q_ASSERT(req->index == itemIndex);
410 Q_ASSERT(req->rendered);
411 if (req->index != itemIndex || !req->rendered)
412 return false;
413 // remove the old request to free resources
414 delete d->requests.take(item->ulItemID);
415 }
416
417 // store the request
418 req = new Data::Request(itemIndex, p, drm, drf);
419 d->requests.insert(item->ulItemID, req);
420
421 return true;
422}
423
424void QPMMime::DefaultDragWorker::defaultFileType(const char *&type,
425 const char *&ext)
426{
427 Q_ASSERT(d->providers.count());
428 if (d->providers.count()) {
429 Provider *p = d->providers.first().prov;
430 Q_ASSERT(p);
431 if (p)
432 p->fileType(d->providers.first().drf, type, ext);
433 }
434}
435
436MRESULT QPMMime::DefaultDragWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
437{
438 if (msg == DM_RENDER) {
439 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
440
441 // sanity checks
442 Q_ASSERT(d->isInitialized());
443 Q_ASSERT(xfer);
444 if (!d->isInitialized() || !xfer)
445 return (MRESULT)FALSE;
446
447 Q_ASSERT(xfer->hwndClient && xfer->pditem);
448 if (!xfer->hwndClient || !xfer->pditem)
449 return (MRESULT)FALSE;
450
451 Data::Request *req = d->requests.value(xfer->pditem->ulItemID);
452
453 // check that this item has been prepared (should always be the case
454 // because the target would never know our hwnd() otherwise)
455 Q_ASSERT(req); // prepared
456 Q_ASSERT(!req->xfer); // no DM_RENDER requested
457 if (!req || req->xfer)
458 return (MRESULT)FALSE;
459
460 DEBUG(() << "DefaultDragWorker: Got DM_RENDER to"
461 << queryHSTR(xfer->hstrSelectedRMF) << "for item" << req->index
462 << "(id " << xfer->pditem->ulItemID << ")");
463
464 QByteArray drm, drf;
465 if (!parseRMF(xfer->hstrSelectedRMF, drm, drf))
466 Q_ASSERT(/* parseRMF() = */ FALSE);
467
468 if (req->drm != drm || req->drf != drf) {
469 xfer->fsReply = DMFL_RENDERRETRY;
470 return (MRESULT)FALSE;
471 }
472
473 // indicate that DM_RENDER was requested
474 req->xfer = xfer;
475
476 DEBUG(() << "DefaultDragWorker: Will render from ["
477 << req->provider->format(drf) << "] using provider"
478 << req->provider);
479
480 // We would lile to post WM_USER to ourselves to do actual rendering
481 // after we return from DM_RENDER. But we are inside DrgDrag() at this
482 // point (our DND implementation is fully synchronous by design), so
483 // PM will not deliver this message to us until we return from
484 // DrgDrag(). Thus, we have to send it.
485
486 WinSendMsg(hwnd(), WM_USER, MPFROMLONG(xfer->pditem->ulItemID),
487 MPFROMP(req));
488
489 return (MRESULT)TRUE;
490 }
491
492 if (msg == WM_USER) {
493 // sanity checks
494 Q_ASSERT(d->isInitialized());
495 if (!d->isInitialized())
496 return (MRESULT)FALSE;
497
498 ULONG itemId = LONGFROMMP(mp1);
499
500 // sanity checks
501 Data::Request *req = d->requests.value(itemId);
502 Q_ASSERT(req); // prepared
503 Q_ASSERT(req->xfer != NULL); // DM_RENDER requested
504 Q_ASSERT(!req->rendered); // not yet rendered
505 Q_ASSERT((Data::Request *) PVOIDFROMMP(mp2) == req);
506 if (!req || req->xfer == NULL || req->rendered ||
507 (Data::Request *) PVOIDFROMMP(mp2) != req)
508 return (MRESULT)FALSE;
509
510 Q_ASSERT(source() && req->provider && req->index < d->itemCnt);
511 if (!source() || !req->provider || req->index >= d->itemCnt)
512 return (MRESULT)FALSE;
513
514 DEBUG(() << "DefaultDragWorker: Got DO_RENDER for item " << req->index
515 << "(id " << req->xfer->pditem->ulItemID << ")"
516 << "provider"<< req->provider << "drm" << req->drm.data()
517 << "drf" << req->drf.data());
518
519 bool renderOk = false;
520
521 QByteArray allData = source()->data(req->provider->format(req->drf));
522 QByteArray itemData;
523
524 renderOk = req->provider->provide(req->drf, allData,
525 req->index, itemData);
526
527 if (renderOk) {
528 enum DRM { OS2File, SharedMem } drmType;
529 if (qstrcmp(req->drm, "DRM_SHAREDMEM") == 0) drmType = SharedMem;
530 else drmType = OS2File;
531
532 if (drmType == OS2File) {
533 QByteArray renderToName = queryHSTR(req->xfer->hstrRenderToName);
534 Q_ASSERT(!renderToName.isEmpty());
535 renderOk = !renderToName.isEmpty();
536 if (renderOk) {
537 DEBUG(() << "DefaultDragWorker: Will write to" << renderToName);
538 QFile file(QFile::decodeName(renderToName));
539 renderOk = file.open(QIODevice::WriteOnly);
540 if (renderOk) {
541 qint64 written = file.write(itemData, itemData.size());
542 renderOk = written == itemData.size();
543 file.close();
544 if (renderOk && req->xfer->pditem->hstrType) {
545 // since WPS ignores hstrType, write it manually
546 // to the .TYPE EA of the created file
547 qt_SetFileTypeEA(renderToName,
548 queryHSTR(req->xfer->pditem->hstrType));
549 }
550 }
551 }
552 } else {
553 PID pid;
554 TID tid;
555 bool isSameProcess = false;
556 renderOk = WinQueryWindowProcess(req->xfer->hwndClient,
557 &pid, &tid);
558 if (renderOk) {
559 PPIB ppib = NULL;
560 DosGetInfoBlocks(NULL, &ppib);
561 isSameProcess = ppib->pib_ulpid == pid;
562
563 ULONG sz = itemData.size() + sizeof (ULONG);
564 char *ptr = NULL;
565 APIRET rc = isSameProcess ?
566 DosAllocMem((PPVOID) &ptr, sz,
567 PAG_COMMIT | PAG_READ | PAG_WRITE) :
568 DosAllocSharedMem((PPVOID) &ptr, NULL, sz,
569 OBJ_GIVEABLE | PAG_COMMIT |
570 PAG_READ | PAG_WRITE);
571 renderOk = rc == 0;
572 if (renderOk && !isSameProcess) {
573 rc = DosGiveSharedMem(ptr, pid, PAG_READ);
574 renderOk = rc == 0;
575 }
576 if (renderOk) {
577 *(ULONG *) ptr = itemData.size();
578 memcpy(ptr + sizeof (ULONG), itemData.data(),
579 itemData.size());
580 req->xfer->hstrRenderToName = (HSTR) ptr;
581 req->sharedMem = ptr;
582 DEBUG(() << "DefaultDragWorker: Created shared memory "
583 "object" << ptr);
584#ifndef QT_NO_DEBUG
585 } else {
586 qWarning("DefaultDragWorker: DosAllocSharedMem/"
587 "DosGiveSharedMem failed with %ld", rc);
588#endif
589 }
590#ifndef QT_NO_DEBUG
591 } else {
592 qWarning("DefaultDragWorker: WinQueryWindowProcess failed"
593 "with 0x%lX", WinGetLastError(NULLHANDLE));
594#endif
595 }
596 }
597 }
598
599 req->rendered = true;
600 // cumulative render result
601 d->renderOk &= renderOk;
602
603 DEBUG(() << "DefaultDragWorker: renderOk" << renderOk
604 << "overall.renderOk" << d->renderOk);
605
606 // note that we don't allow the target to retry
607 USHORT reply = renderOk ? DMFL_RENDEROK : DMFL_RENDERFAIL;
608 DrgPostTransferMsg(req->xfer->hwndClient, DM_RENDERCOMPLETE,
609 req->xfer, reply, 0, false);
610
611 // DRAGTRANSFER is no more necessary, free it early
612 qt_DrgFreeDragtransfer(req->xfer);
613#if defined(QT_DEBUG_DND)
614 {
615 ULONG size = ~0, flags = 0;
616 DosQueryMem(req->xfer, &size, &flags);
617 DEBUG(("DefaultDragWorker: Freed DRAGTRANSFER: "
618 "req->xfer %p size %lu (0x%08lX) flags 0x%08lX",
619 req->xfer, size, size, flags));
620 }
621#endif
622 req->xfer = NULL;
623
624 return (MRESULT)FALSE;
625 }
626
627 if (msg == DM_ENDCONVERSATION) {
628 // we don't check that isInitialized() is true here, because WPS
629 // (and probably some other apps) may send this message after
630 // cleanup() is called up on return from DrgDrag
631
632 ULONG itemId = LONGFROMMP(mp1);
633 ULONG flags = LONGFROMMP(mp2);
634
635 // sanity check (don't assert, see above)
636 Data::Request *req = d->requests.value(itemId);
637 Q_ASSERT(req);
638 if (!req)
639 return (MRESULT)FALSE;
640
641 DEBUG(() << "DefaultDragWorker: Got DM_ENDCONVERSATION for item " << req->index
642 << " (id " << itemId << ") provider" << req->provider
643 << "drm" << req->drm << "drf" << req->drf
644 << "rendered" << req->rendered << "outdated" << !d->isInitialized());
645
646 // proceed further only if it's not an outdated request
647 // from the previous DND session
648 if (d->isInitialized()) {
649 if (!req->rendered) {
650 // we treat cancelling the render request (for any reason)
651 // as a failure
652 d->renderOk = false;
653 } else {
654 // the overall success is true only if target says Okay
655 d->renderOk &= flags == DMFL_TARGETSUCCESSFUL;
656 }
657 }
658
659 // delete the request
660 delete d->requests.take(itemId);
661
662 return (MRESULT)FALSE;
663 }
664
665 return (MRESULT)FALSE;
666}
667
668bool QPMMime::DefaultDragWorker::addProvider(const char *drf, Provider *provider,
669 ULONG itemCnt /* = 1 */)
670{
671 // make sure remaining requests from the previous DND session are deleted
672 d->cleanupRequests();
673
674 Q_ASSERT(drf && provider && itemCnt >= 1);
675 if (drf && provider && itemCnt >= 1) {
676 if (d->providers.count() == 0) {
677 // first provider
678 d->itemCnt = itemCnt;
679 d->providers.append(Data::DrfProvider(drf, provider));
680 return true;
681 }
682 // next provider, must not be exclusive and itemCnt must match
683 if (!d->exclusive && d->itemCnt == itemCnt) {
684 // ensure there are no dups (several providers for the same drf)
685 if (!d->providerFor(drf))
686 d->providers.append(Data::DrfProvider(drf, provider));
687 return true;
688 }
689 }
690 return false;
691}
692
693// static
694bool QPMMime::DefaultDragWorker::canRender(const char *drm)
695{
696 return qstrcmp(drm, "DRM_SHAREDMEM") == 0 ||
697 qstrcmp(drm, "DRM_OS2FILE") == 0;
698}
699
700//------------------------------------------------------------------------------
701
702struct QPMMime::DefaultDropWorker::Data
703{
704 struct MimeProvider
705 {
706 MimeProvider() : prov(NULL) {}
707 MimeProvider(const QString &m, Provider *p) : mime(m), prov(p) {}
708 QString mime;
709 Provider *prov;
710 };
711
712 typedef QList<MimeProvider> MimeProviderList;
713
714 Provider *providerFor(const QString &mime)
715 {
716 foreach (const MimeProvider &p, providers) {
717 if (p.mime == mime)
718 return p.prov;
719 }
720 return NULL;
721 }
722
723 bool exclusive : 1;
724 MimeProviderList providers;
725
726 bool sending_DM_RENDER : 1;
727 bool got_DM_RENDERCOMPLETE : 1;
728 USHORT flags_DM_RENDERCOMPLETE;
729
730 QEventLoop eventLoop;
731};
732
733QPMMime::DefaultDropWorker::DefaultDropWorker() : d(new Data())
734{
735 d->exclusive = false;
736 d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = false;
737 d->flags_DM_RENDERCOMPLETE = 0;
738}
739
740QPMMime::DefaultDropWorker::~DefaultDropWorker()
741{
742 delete d;
743}
744
745void QPMMime::DefaultDropWorker::cleanup(bool isAccepted, bool isActAccepted)
746{
747 if (d->eventLoop.isRunning()) {
748#ifndef QT_NO_DEBUG
749 qWarning("The previous drag source didn't post DM_RENDERCOMPLETE!\n"
750 "Contact the drag source developer.");
751#endif
752 d->eventLoop.exit(1);
753 }
754
755 d->providers.clear();
756 d->exclusive = false;
757 d->sending_DM_RENDER = d->got_DM_RENDERCOMPLETE = false;
758 d->flags_DM_RENDERCOMPLETE = 0;
759}
760
761bool QPMMime::DefaultDropWorker::isExclusive() const
762{
763 return d->exclusive;
764}
765
766bool QPMMime::DefaultDropWorker::hasFormat(const QString &mimeType) const
767{
768 return d->providerFor(mimeType) != NULL;
769}
770
771// @todo
772//int QPMMime::DefaultDropWorker::formatCount() const
773//{
774// return d->providers.count();
775//}
776
777QStringList QPMMime::DefaultDropWorker::formats() const
778{
779 QStringList mimes;
780 foreach(const Data::MimeProvider &p, d->providers)
781 mimes << p.mime;
782 return mimes;
783}
784
785static QByteArray composeTempFileName()
786{
787 QByteArray tmpDir =
788 QFile::encodeName(QDir::toNativeSeparators(QDir::tempPath()));
789
790 static bool srandDone = false;
791 if (!srandDone) {
792 srand(time(NULL));
793 srandDone = true;
794 }
795
796 ULONG num = rand();
797 enum { Attempts = 100 };
798 int attempts = Attempts;
799
800 QString tmpName;
801 do {
802 tmpName.sprintf("%s\\%08lX.tmp", tmpDir.constData(), num);
803 if (!QFile::exists(tmpName))
804 break;
805 num = rand();
806 } while (--attempts > 0);
807
808 Q_ASSERT(attempts > 0);
809 if (attempts <= 0)
810 tmpName.clear();
811
812 return QFile::encodeName(tmpName);
813}
814
815QVariant QPMMime::DefaultDropWorker::retrieveData(const QString &mimeType,
816 QVariant::Type type) const
817{
818 Q_UNUSED(type);
819
820 DEBUG(() << "DefaultDropWorker::retrieveData(" << mimeType << ")");
821
822 QVariant ret;
823
824 Q_ASSERT(info());
825 if (!info())
826 return ret;
827
828 ULONG itemCount = DrgQueryDragitemCount(info());
829 Q_ASSERT(itemCount);
830 if (!itemCount)
831 return ret;
832
833 Provider *provider = d->providerFor(mimeType);
834 if (!provider)
835 return ret;
836
837 const char *drf = provider->drf(mimeType);
838 Q_ASSERT(drf);
839 if (!drf)
840 return ret;
841
842 // Note: Allocating and freeing DRAGTRANSFER structures is a real mess. It's
843 // absolutely unclear how they can be reused for multiple items and/or render
844 // requests. My practice shows, that they cannot be reused at all, especially
845 // when the source and the target are the same process: if we have multiple
846 // items and use the same DRAGTRANSFER for all of them, the source will call
847 // DrgFreeDragtransfer() every time that will eventually destroy the memory
848 // object before the target finishes to work with it, so that the next
849 // DrgFreeDragtransfer() will generate a segfault in PMCTLS. Also note that
850 // using a number > 1 as an argument to DrgAllocDragtransfer() won't help
851 // because that will still allocate a single memory object. Thus, we will
852 // always allocate a new struct per every item. It seems to work.
853
854 QByteArray renderToName = composeTempFileName();
855 HSTR hstrRenderToName = DrgAddStrHandle(renderToName);
856
857 HSTR rmfOS2File =
858 DrgAddStrHandle(QString().sprintf("<DRM_OS2FILE,%s>", drf).toLocal8Bit());
859 HSTR rmfSharedMem =
860 DrgAddStrHandle(QString().sprintf("<DRM_SHAREDMEM,%s>", drf).toLocal8Bit());
861
862 MRESULT mrc;
863 bool renderOk = false;
864
865 DRAGTRANSFER *xfer = NULL;
866 QByteArray srcFileName;
867
868 QByteArray allData, itemData;
869
870 for (ULONG i = 0; i < itemCount; ++i) {
871 DRAGITEM *item = DrgQueryDragitemPtr(info(), i);
872 Q_ASSERT(item);
873 if (!item) {
874 renderOk = false;
875 break;
876 }
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 bool ok = file.remove();
1001 Q_ASSERT((ok = ok));
1002 } else {
1003 Q_ASSERT(xfer->hstrRenderToName);
1004 renderOk = xfer->hstrRenderToName != 0;
1005 if (renderOk) {
1006 const char *ptr = (const char *) xfer->hstrRenderToName;
1007 ULONG size = ~0;
1008 ULONG flags = 0;
1009 APIRET rc = DosQueryMem((PVOID) ptr, &size, &flags);
1010 renderOk = rc == 0;
1011 if (renderOk) {
1012 DEBUG(("DefaultDropWorker: Got shared data %p size %lu "
1013 "(0x%08lX) flags 0x%08lX", ptr, size, size, flags));
1014 Q_ASSERT((flags & (PAG_COMMIT | PAG_READ | PAG_BASE)) ==
1015 (PAG_COMMIT | PAG_READ | PAG_BASE));
1016 renderOk = (flags & (PAG_COMMIT | PAG_READ | PAG_BASE)) ==
1017 (PAG_COMMIT | PAG_READ | PAG_BASE);
1018#ifndef QT_NO_DEBUG
1019 } else {
1020 qWarning("DefaultDropWorker: DosQueryMem failed with %ld", rc);
1021#endif
1022 }
1023 if (renderOk) {
1024 ULONG realSize = *(ULONG *) ptr;
1025 DEBUG(() << "DefaultDropWorker: realSize" << realSize);
1026 Q_ASSERT(realSize <= size);
1027 renderOk = realSize <= size;
1028 if (renderOk) {
1029 itemData.resize(realSize);
1030 memcpy(itemData.data(), ptr + sizeof(ULONG), realSize);
1031 }
1032 }
1033 // free memory only if it is given by another process,
1034 // otherwise DefaultDragWorker will free it
1035 if (flags & PAG_SHARED)
1036 DosFreeMem((PVOID) xfer->hstrRenderToName);
1037 }
1038 }
1039 }
1040
1041 if (renderOk)
1042 renderOk = provider->provide(mimeType, i, itemData, allData);
1043
1044 if (needToTalk) {
1045 // free the DRAGTRANSFER structure
1046 DrgFreeDragtransfer(xfer);
1047#if defined(QT_DEBUG_DND)
1048 {
1049 ULONG size = ~0, flags = 0;
1050 DosQueryMem(xfer, &size, &flags);
1051 DEBUG(("DefaultDropWorker: Freed DRAGTRANSFER: "
1052 "xfer=%p, size=%lu(0x%08lX), flags=0x%08lX",
1053 xfer, size, size, flags));
1054 }
1055#endif
1056 xfer = NULL;
1057 }
1058
1059 if (!renderOk)
1060 break;
1061 }
1062
1063 DEBUG(() << "DefaultDropWorker: renderOk" << renderOk);
1064
1065 DrgDeleteStrHandle(rmfSharedMem);
1066 DrgDeleteStrHandle(rmfOS2File);
1067 DrgDeleteStrHandle(hstrRenderToName);
1068
1069 if (renderOk)
1070 ret = allData;
1071
1072 return ret;
1073}
1074
1075MRESULT QPMMime::DefaultDropWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
1076{
1077 switch (msg) {
1078 case DM_RENDERCOMPLETE: {
1079 // sanity check
1080 Q_ASSERT(info());
1081 if (!info())
1082 return (MRESULT)FALSE;
1083
1084 DEBUG(("DefaultDropWorker: Got DM_RENDERCOMPLETE"));
1085 d->got_DM_RENDERCOMPLETE = true;
1086 d->flags_DM_RENDERCOMPLETE = SHORT1FROMMP(mp2);
1087
1088 if (d->sending_DM_RENDER)
1089 {
1090 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
1091#ifndef QT_NO_DEBUG
1092 qWarning("Drag item 0x%08lX sent DM_RENDERCOMPLETE w/o first "
1093 "replying to DM_RENDER!\n"
1094 "Contact the drag source developer.",
1095 xfer->pditem->hwndItem);
1096#endif
1097 return (MRESULT)FALSE;
1098 }
1099
1100 // stop synchronous waiting for DM_RENDERCOMPLETE
1101 if (d->eventLoop.isRunning())
1102 d->eventLoop.exit();
1103 return (MRESULT)FALSE;
1104 }
1105 default:
1106 break;
1107 }
1108
1109 return (MRESULT)FALSE;
1110}
1111
1112bool QPMMime::DefaultDropWorker::addProvider(const QString &mimeType,
1113 Provider *provider)
1114{
1115 Q_ASSERT(!mimeType.isEmpty() && provider);
1116 if (!mimeType.isEmpty() && provider && !d->exclusive) {
1117 // ensure there are no dups (several providers for the same mime)
1118 if (!d->providerFor(mimeType))
1119 d->providers.append(Data::MimeProvider(mimeType, provider));
1120 return true;
1121 }
1122 return false;
1123}
1124
1125bool QPMMime::DefaultDropWorker::addExclusiveProvider(const QString &mimeType,
1126 Provider *provider)
1127{
1128 Q_ASSERT(!mimeType.isEmpty() && provider);
1129 if (!mimeType.isEmpty() && provider && !d->exclusive) {
1130 d->exclusive = true;
1131 d->providers.clear();
1132 d->providers.append(Data::MimeProvider(mimeType, provider));
1133 return true;
1134 }
1135 return false;
1136}
1137
1138// static
1139bool QPMMime::DefaultDropWorker::canRender(DRAGITEM *item, const char *drf)
1140{
1141 return DrgVerifyRMF(item, "DRM_OS2FILE", drf) ||
1142 (DrgVerifyRMF(item, "DRM_SHAREDMEM", drf) &&
1143 DrgVerifyRMF(item, "DRM_SHAREDMEM", "DRF_POINTERDATA"));
1144}
1145
1146/*! \internal
1147
1148 Parses the rendering mechanism/format specification of the given \a item
1149 and stores only those mechanism branches in the given \a list that represent
1150 mechanisms supported by this worker. Returns false if fails to parse the
1151 RMF specification. Note that if no supported mechanisms are found, true is
1152 returned but the \a list will simply contain zero items.
1153
1154 \note The method clears the given \a list variable before proceeding.
1155
1156 \sa canRender(), PMMime::parseRMFs()
1157*/
1158// static
1159bool QPMMime::DefaultDropWorker::getSupportedRMFs(DRAGITEM *item,
1160 QList<QByteArrayList> &list)
1161{
1162 if (!parseRMFs(item->hstrRMF, list))
1163 return false;
1164
1165 for (QList<QByteArrayList>::iterator rmf = list.begin(); rmf != list.end();) {
1166 QByteArrayList::iterator mf = rmf->begin();
1167 Q_ASSERT(mf != rmf->end());
1168 const char *drm = *mf;
1169 if (qstrcmp(drm, "DRM_OS2FILE") == 0) {
1170 ++rmf;
1171 continue;
1172 }
1173 if (qstrcmp(drm, "DRM_SHAREDMEM") == 0) {
1174 // accept DRM_SHAREDMEM only if there is DRF_POINTERDATA
1175 for(; mf != rmf->end(); ++mf) {
1176 const char *drf = *mf;
1177 if (qstrcmp(drf, "DRF_POINTERDATA") == 0)
1178 break;
1179 }
1180 if (mf != rmf->end()) {
1181 ++rmf;
1182 continue;
1183 }
1184 }
1185 // remove the unsupported mechanism branch from the list
1186 rmf = list.erase(rmf);
1187 }
1188
1189 return true;
1190}
1191
1192#endif // !QT_NO_DRAGANDDROP
1193
1194//------------------------------------------------------------------------------
1195
1196class QPMMimeList
1197{
1198public:
1199 QPMMimeList();
1200 ~QPMMimeList();
1201 void addMime(QPMMime *mime);
1202 void removeMime(QPMMime *mime);
1203 QList<QPMMime*> mimes();
1204
1205private:
1206 void init();
1207 bool initialized;
1208 QList<QPMMime*> list;
1209};
1210
1211Q_GLOBAL_STATIC(QPMMimeList, theMimeList);
1212
1213
1214/*!
1215 \class QPMMime
1216 \brief The QMPMime class maps open-standard MIME to OS/2 PM Clipboard
1217 formats.
1218 \ingroup io
1219 \ingroup draganddrop
1220 \ingroup misc
1221
1222 Qt's drag-and-drop and clipboard facilities use the MIME standard.
1223 On X11, this maps trivially to the Xdnd protocol, but on OS/2
1224 although some applications use MIME types to describe clipboard
1225 formats, others use arbitrary non-standardized naming conventions,
1226 or unnamed built-in formats of the Presentation Manager.
1227
1228 By instantiating subclasses of QPMMime that provide conversions between OS/2
1229 PM Clipboard and MIME formats, you can convert proprietary clipboard formats
1230 to MIME formats.
1231
1232 Qt has predefined support for the following PM Clipboard formats (custom
1233 formats registered in the system atom table by name are given in double
1234 quotes):
1235
1236 \table
1237 \header \o PM Format \o Equivalent MIME type
1238 \row \o \c CF_TEXT \o \c text/plain (system codepage,
1239 zero-terminated string)
1240 \row \o \c "text/unicode" \o \c text/plain (16-bit Unicode,
1241 zero-terminated string, Mozilla-compatible)
1242 \row \o \c "text/html" \o \c text/html (16-bit Unicode,
1243 zero-terminated string, Mozilla-compatible)
1244 \row \o \c CF_BITMAP \o \c{image/xyz}, where \c xyz is
1245 a \l{QImageWriter::supportedImageFormats()}{Qt image format}
1246 \row \o \c "x-mime:<mime>" \o data in the format corresponding to the given
1247 MIME type \c <mime>
1248 \endtable
1249
1250 Note that all "x-mime:<mime>" formats use the CFI_POINTER storage type. That
1251 is, the clipboard contains a pointer to the memory block containing the MIME
1252 data in the corresponding format. The first 4 bytes of this memory block
1253 always contain the length of the subsequent MIME data array, in bytes.
1254
1255 An example use of this class by the user application would be to map the
1256 PM Metafile clipboard format (\c CF_METAFILE) to and from the MIME type
1257 \c{image/x-metafile}. This conversion might simply be adding or removing a
1258 header, or even just passing on the data. See \l{Drag and Drop} for more
1259 information on choosing and definition MIME types.
1260*/
1261
1262/*!
1263Constructs a new conversion object, adding it to the globally accessed
1264list of available converters.
1265*/
1266QPMMime::QPMMime()
1267{
1268 theMimeList()->addMime(this);
1269}
1270
1271/*!
1272Destroys a conversion object, removing it from the global
1273list of available converters.
1274*/
1275QPMMime::~QPMMime()
1276{
1277 theMimeList()->removeMime(this);
1278}
1279
1280/*!
1281 Registers the MIME type \a mime, and returns an ID number
1282 identifying the format on OS/2. Intended to be used by QPMMime
1283 implementations for registering custom clipboard formats they use.
1284*/
1285// static
1286ULONG QPMMime::registerMimeType(const QString &mime)
1287{
1288 ULONG cf = WinAddAtom(WinQuerySystemAtomTable(), mime.toLocal8Bit());
1289 if (!cf) {
1290#ifndef QT_NO_DEBUG
1291 qWarning("QPMMime: WinAddAtom failed with 0x%lX",
1292 WinGetLastError(NULLHANDLE));
1293#endif
1294 return 0;
1295 }
1296
1297 return cf;
1298}
1299
1300/*!
1301 Unregisters the MIME type identified by \a id which was previously
1302 registered with registerMimeType().
1303*/
1304// static
1305void QPMMime::unregisterMimeType(ULONG mimeId)
1306{
1307 WinDeleteAtom(WinQuerySystemAtomTable(), mimeId);
1308}
1309
1310/*!
1311 Allocates a block of shared memory of the given size and returns the address
1312 of this block. This memory block may be then filled with data and returned
1313 by convertFromMimeData() as the value of the CFI_POINTER type.
1314*/
1315// static
1316ULONG QPMMime::allocateMemory(size_t size)
1317{
1318 if (size == 0)
1319 return 0;
1320
1321 ULONG data = 0;
1322
1323 // allocate giveable memory for the array
1324 APIRET arc = DosAllocSharedMem((PVOID *)&data, NULL, size,
1325 PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE);
1326 if (arc != NO_ERROR) {
1327#ifndef QT_NO_DEBUG
1328 qWarning("QPMMime::allocateMemory: DosAllocSharedMem failed with %lu", arc);
1329#endif
1330 return 0;
1331 }
1332
1333 return data;
1334}
1335
1336/*!
1337 Frees memory allocated by allocateMemory(). Normally, not used because the
1338 CFI_POINTER memory blocks are owned by the system after
1339 convertFromMimeData() returns.
1340*/
1341// static
1342void QPMMime::freeMemory(ULONG addr)
1343{
1344 DosFreeMem((PVOID)addr);
1345}
1346
1347/*!
1348 \fn QList<MimeCFPair> QPMMime::formatsForMimeData(const QMimeData *mimeData) const
1349
1350 Returns a list of ULONG values representing the different OS/2 PM
1351 clipboard formats that can be provided for the \a mimeData, in order of
1352 precedence (the most suitable format goes first), or an empty list if
1353 neither of the mime types provided by \a mimeData is supported by this
1354 converter. Note that each item in the returned list is actually a pair
1355 consisting of the mime type name and the corresponding format identifier.
1356
1357 All subclasses must reimplement this pure virtual function.
1358*/
1359
1360/*!
1361 \fn bool QPMMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1362 ULONG &flags, ULONG *data) const
1363
1364 Converts the \a mimeData to the specified \a format.
1365
1366 If \a data is not NULL, a handle to the converted data should be then placed
1367 in a variable pointed to by \a data and with the necessary flags describing
1368 the handle returned in the \a flags variable.
1369
1370 The following flags describing the data storage type are recognized:
1371
1372 \table
1373 \row \o \c CFI_POINTER \o \a data is a pointer to a block of memory
1374 allocated with QPMMime::allocateMemory()
1375 \row \o \c CFI_HANDLE \o \a data is a handle to the appropriate
1376 PM resource
1377 \endtable
1378
1379 If \a data is NULL then a delayed conversion is requested by the caller.
1380 The implementation should return the appropriate flags in the \a flags
1381 variable and may perform the real data conversion later when this method is
1382 called again with \a data being non-NULL.
1383
1384 Return true if the conversion was successful.
1385
1386 All subclasses must reimplement this pure virtual function.
1387*/
1388
1389/*!
1390 \fn QList<MimeCFPair> QPMMime::mimesForFormats(const QList<ULONG> &formats) const
1391
1392 Returns a list of mime types that will be created form the specified \a list
1393 of \a formats, in order of precedence (the most suitable mime type comes
1394 first), or an empty list if neither of the \a formats is supported by this
1395 converter. Note that each item in the returned list is actually a pair
1396 consisting of the mime type name and the corresponding format identifier.
1397
1398 All subclasses must reimplement this pure virtual function.
1399*/
1400
1401/*!
1402 \fn QVariant QPMMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
1403 const QString &mimeType,
1404 QVariant::Type preferredType) const
1405
1406 Returns a QVariant containing the converted from the \a data in the
1407 specified \a format with the given \a flags to the requested \a mimeType. If
1408 possible the QVariant should be of the \a preferredType to avoid needless
1409 conversions.
1410
1411 All subclasses must reimplement this pure virtual function.
1412*/
1413
1414// static
1415QList<QPMMime::Match> QPMMime::allConvertersFromFormats(const QList<ULONG> &formats)
1416{
1417 QList<Match> matches;
1418
1419 QList<QPMMime*> mimes = theMimeList()->mimes();
1420 foreach(QPMMime *mime, mimes) {
1421 QList<MimeCFPair> fmts = mime->mimesForFormats(formats);
1422 int priority = 0;
1423 foreach (MimeCFPair fmt, fmts) {
1424 ++priority;
1425 QList<Match>::iterator it = matches.begin();
1426 for (; it != matches.end(); ++it) {
1427 Match &match = *it;
1428 if (match.mime == fmt.mime) {
1429 // replace if priority is higher, ignore otherwise
1430 if (priority < match.priority) {
1431 match.converter = mime;
1432 match.format = fmt.format;
1433 match.priority = priority;
1434 }
1435 break;
1436 }
1437 }
1438 if (it == matches.end()) {
1439 matches += Match(mime, fmt.mime, fmt.format, priority);
1440 }
1441 }
1442 }
1443
1444 return matches;
1445}
1446
1447// static
1448QList<QPMMime::Match> QPMMime::allConvertersFromMimeData(const QMimeData *mimeData)
1449{
1450 QList<Match> matches;
1451
1452 QList<QPMMime*> mimes = theMimeList()->mimes();
1453 foreach(QPMMime *mime, mimes) {
1454 QList<MimeCFPair> fmts = mime->formatsForMimeData(mimeData);
1455 int priority = 0;
1456 foreach (MimeCFPair fmt, fmts) {
1457 ++priority;
1458 QList<Match>::iterator it = matches.begin();
1459 for (; it != matches.end(); ++it) {
1460 Match &match = *it;
1461 if (mime == mimes.last()) { // QPMMimeAnyMime?
1462 if (match.mime == fmt.mime){
1463 // we assume that specialized converters (that come
1464 // first) provide a more precise conversion than
1465 // QPMMimeAnyMime and don't let it get into the list in
1466 // order to avoid unnecessary duplicate representations
1467 break;
1468 }
1469 }
1470 if (match.format == fmt.format) {
1471 // replace if priority is higher, ignore otherwise
1472 if (priority < match.priority) {
1473 match.converter = mime;
1474 match.mime = fmt.mime;
1475 match.priority = priority;
1476 }
1477 break;
1478 }
1479 }
1480 if (it == matches.end()) {
1481 matches += Match(mime, fmt.mime, fmt.format, priority);
1482 }
1483 }
1484 }
1485
1486 return matches;
1487}
1488
1489QString QPMMime::formatName(ULONG format)
1490{
1491 QString name;
1492 HATOMTBL tbl = WinQuerySystemAtomTable();
1493 if (tbl != NULLHANDLE) {
1494 ULONG len = WinQueryAtomLength(tbl, format);
1495 QByteArray atom(len, '\0');
1496 WinQueryAtomName(tbl, format, atom.data(), atom.size() + 1);
1497 name = QString::fromLocal8Bit(atom);
1498 }
1499 return name;
1500}
1501
1502#if !defined(QT_NO_DRAGANDDROP)
1503
1504/*!
1505 Returns a string represented by \a hstr.
1506*/
1507// static
1508QByteArray QPMMime::queryHSTR(HSTR hstr)
1509{
1510 QByteArray str;
1511 ULONG len = DrgQueryStrNameLen(hstr);
1512 if (len) {
1513 str.resize(len);
1514 DrgQueryStrName(hstr, str.size() + 1 /* \0 */, str.data());
1515 }
1516 return str;
1517}
1518
1519/*!
1520 Returns a string that is a concatenation of \c hstrContainerName and
1521 \c hstrSourceName fileds of the given \a item structure.
1522*/
1523// static
1524QByteArray QPMMime::querySourceNameFull(DRAGITEM *item)
1525{
1526 QByteArray fullName;
1527 if (!item)
1528 return fullName;
1529
1530 ULONG pathLen = DrgQueryStrNameLen(item->hstrContainerName);
1531 ULONG nameLen = DrgQueryStrNameLen(item->hstrSourceName);
1532 if (!pathLen || !nameLen)
1533 return fullName;
1534
1535 fullName.resize(pathLen + nameLen);
1536 DrgQueryStrName(item->hstrContainerName, pathLen + 1, fullName.data());
1537 DrgQueryStrName(item->hstrSourceName, nameLen + 1, fullName.data() + pathLen);
1538
1539 return fullName;
1540}
1541
1542/*! \internal
1543
1544 Checks that the given drag \a item supports the DRM_OS2FILE rendering
1545 mechanism and can be rendered by a target w/o involving the source (i.e.,
1546 DRM_OS2FILE is the first supported format and a valid file name with full
1547 path is provided). If the function returns TRUE, \a fullName (if not NULL)
1548 will be assigned the item's full source file name (composed from
1549 \c hstrContainerName and \c hstrSourceName fields).
1550 */
1551// static
1552bool QPMMime::canTargetRenderAsOS2File(DRAGITEM *item, QByteArray *fullName /*= 0*/)
1553{
1554 if ( !item )
1555 return false;
1556
1557 if (item->fsControl & (DC_PREPARE | DC_PREPAREITEM))
1558 return false;
1559
1560 {
1561 // DrgVerifyNativeRMF doesn't work on my system (ECS 1.2.1 GA):
1562 // it always returns FALSE regardless of arguments. Use simplified
1563 // hstrRMF parsing to determine whether DRM_OS2FILE is the native
1564 // mechanism or not (i.e. "^\s*[\(<]\s*DRM_OS2FILE\s*,.*").
1565
1566 QByteArray rmf = queryHSTR(item->hstrRMF);
1567 bool ok = false;
1568 int i = rmf.indexOf("DRM_OS2FILE");
1569 if (i >= 1) {
1570 for (int j = i - 1; j >= 0; --j) {
1571 char ch = rmf[j];
1572 if (ch == ' ')
1573 continue;
1574 if (ch == '<' || ch == '(') {
1575 if (ok)
1576 return false;
1577 ok = true;
1578 } else {
1579 return false;
1580 }
1581 }
1582 }
1583 if (ok) {
1584 ok = false;
1585 int drmLen = strlen("DRM_OS2FILE");
1586 for (int j = i + drmLen; j < (int) rmf.size(); ++j) {
1587 char ch = rmf[j];
1588 if (ch == ' ')
1589 continue;
1590 if (ch == ',') {
1591 ok = true;
1592 break;
1593 }
1594 return false;
1595 }
1596 }
1597 if (!ok)
1598 return false;
1599 }
1600
1601 QByteArray srcFullName = querySourceNameFull(item);
1602 if (srcFullName.isEmpty())
1603 return false;
1604
1605 QByteArray srcFullName2(srcFullName.size(), '\0');
1606 APIRET rc = DosQueryPathInfo(srcFullName, FIL_QUERYFULLNAME,
1607 srcFullName2.data(), srcFullName2.size());
1608 if (rc != 0)
1609 return false;
1610
1611 QString s1 = QFile::decodeName(srcFullName);
1612 QString s2 = QFile::decodeName(srcFullName2);
1613
1614 if (s1.compare(s2, Qt::CaseInsensitive) != 0)
1615 return false;
1616
1617 if (fullName)
1618 *fullName = srcFullName;
1619 return true;
1620}
1621
1622/*! \internal
1623
1624 Parses the given \a rmfs list (full rendering mechanism/format specification)
1625 and builds a \a list of mechanism branches. Each mechanism branch is also a
1626 list, where the first item is the mechahism name and all subsequent items are
1627 formats supported by this mechanism. Returns false if fails to parse \a rmf.
1628
1629 \note The method clears the given \a list variable before proceeding.
1630*/
1631// static
1632bool QPMMime::parseRMFs(HSTR rmfs, QList<QByteArrayList> &list)
1633{
1634 // The format of the RMF list is "elem {,elem,elem...}"
1635 // where elem is "(mechanism{,mechanism...}) x (format{,format...})"
1636 // or "<mechanism,format>".
1637 // We use a simple FSM to parse it. In terms of FSM, the format is:
1638 //
1639 // STRT ( BCM m CMCH echanism CMCH , NCM m CMCH echanism CMCH ) ECM x
1640 // SCMF ( BCF f CFCH ormat CFCH , NCF f CFCH ormat CFCH ) ECF , STRT
1641 // STRT < BP m PMCH echanism PMCH , SPMF f PFCH ormat PFCH > EP , STRT
1642
1643 QByteArray str = queryHSTR(rmfs);
1644 uint len = str.length();
1645
1646 enum {
1647 // states
1648 STRT = 0, BCM, CMCH, NCM, ECM, SCMF, BCF, CFCH, NCF, ECF,
1649 BP, PMCH, SPMF, PFCH, EP,
1650 STATES_COUNT,
1651 // pseudo states
1652 Err, Skip,
1653 // inputs
1654 obr = 0, cbr, xx, lt, gt, cm, any, ws,
1655 INPUTS_COUNT,
1656 };
1657
1658 static const char Chars[] = { '(', ')', 'x', 'X', '<', '>', ',', ' ', 0 };
1659 static const char Inputs[] = { obr, cbr, xx, xx, lt, gt, cm, ws };
1660 static const uchar Fsm [STATES_COUNT] [INPUTS_COUNT] = {
1661 /* 0 obr 1 cbr 2 xx 3 lt 4 gt 5 cm 6 any 7 ws */
1662/* STRT 0 */ { BCM, Err, Err, BP, Err, Err, Err, Skip },
1663/* BCM 1 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1664/* CMCH 2 */ { Err, ECM, CMCH, Err, Err, NCM, CMCH, CMCH },
1665/* NCM 3 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1666/* ECM 4 */ { Err, Err, SCMF, Err, Err, Err, Err, Skip },
1667/* SCMF 5 */ { BCF, Err, Err, Err, Err, Err, Err, Skip },
1668/* BCF 6 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1669/* CFCH 7 */ { Err, ECF, CFCH, Err, Err, NCF, CFCH, CFCH },
1670/* NCF 8 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1671/* ECF 9 */ { Err, Err, Err, Err, Err, STRT, Err, Skip },
1672/* BP 10 */ { Err, Err, Err, Err, Err, Err, PMCH, Skip },
1673/* PMCH 11 */ { Err, Err, PMCH, Err, Err, SPMF, PMCH, PMCH },
1674/* SPMF 12 */ { Err, Err, Err, Err, Err, Err, PFCH, Skip },
1675/* PFCH 13 */ { Err, Err, PFCH, Err, EP, Err, PFCH, PFCH },
1676/* EP 14 */ { Err, Err, Err, Err, Err, STRT, Err, Skip }
1677 };
1678
1679 list.clear();
1680
1681 QList<QByteArrayList*> refList;
1682
1683 QByteArray buf;
1684 QList<QByteArrayList>::iterator rmf;
1685
1686 uint state = STRT;
1687 uint start = 0, end = 0, space = 0;
1688
1689 for (uint i = 0; i < len && state != Err ; ++i) {
1690 char ch = str[i];
1691 char *p = strchr(Chars, ch);
1692 uint input = p ? Inputs[p - Chars] : any;
1693 uint newState = Fsm[state][input];
1694 switch (newState) {
1695 case Skip:
1696 continue;
1697 case CMCH:
1698 case CFCH:
1699 case PMCH:
1700 case PFCH:
1701 if (state != newState)
1702 start = end = i;
1703 ++end;
1704 // accumulate trailing space for truncation
1705 if (input == ws) ++space;
1706 else space = 0;
1707 break;
1708 case NCM:
1709 case ECM:
1710 case SPMF:
1711 buf = QByteArray(str.data() + start, end - start - space);
1712 // find the mechanism branch in the output list
1713 for (rmf = list.begin(); rmf != list.end(); ++rmf) {
1714 if (rmf->first() == buf)
1715 break;
1716 }
1717 if (rmf == list.end()) {
1718 // append to the output list if not found
1719 QByteArrayList newRmf;
1720 newRmf.append(buf);
1721 rmf = list.insert(list.end(), newRmf);
1722 }
1723 // store a refecence in the helper list for making a cross product
1724 refList.append(&*rmf);
1725 start = end = 0;
1726 break;
1727 case NCF:
1728 case ECF:
1729 case EP:
1730 buf = QByteArray(str.data() + start, end - start - space);
1731 // make a cross product with all current mechanisms
1732 foreach(QByteArrayList *rmfRef, refList)
1733 rmfRef->append(buf);
1734 if (newState != NCF)
1735 refList.clear();
1736 start = end = 0;
1737 break;
1738 default:
1739 break;
1740 }
1741 state = newState;
1742 }
1743
1744 return state == ECF || state == EP;
1745}
1746
1747/*! \internal
1748
1749 Splits the given \a rmf (rendering mechanism/format pair) to a \a mechanism
1750 and a \a format string. Returns FALSE if fails to parse \a rmf.
1751 */
1752// static
1753bool QPMMime::parseRMF(HSTR rmf, QByteArray &mechanism, QByteArray &format)
1754{
1755 QList<QByteArrayList> list;
1756 if (!parseRMFs(rmf, list))
1757 return false;
1758
1759 if (list.count() != 1 || list.first().count() != 2)
1760 return false;
1761
1762 QByteArrayList first = list.first();
1763 mechanism = first.at(0);
1764 format = first.at(1);
1765
1766 return true;
1767}
1768
1769/*! \internal */
1770// static
1771QPMMime::DefaultDragWorker *QPMMime::defaultCoopDragWorker()
1772{
1773 static DefaultDragWorker defCoopDragWorker(false /* exclusive */);
1774 return &defCoopDragWorker;
1775}
1776
1777// static
1778/*! \internal */
1779QPMMime::DefaultDragWorker *QPMMime::defaultExclDragWorker()
1780{
1781 static DefaultDragWorker defExclDragWorker(false /* exclusive */);
1782 return &defExclDragWorker;
1783}
1784
1785/*! \internal */
1786// static
1787QPMMime::DefaultDropWorker *QPMMime::defaultDropWorker()
1788{
1789 static DefaultDropWorker defaultDropWorker;
1790 return &defaultDropWorker;
1791}
1792
1793#endif // !QT_NO_DRAGANDDROP
1794
1795////////////////////////////////////////////////////////////////////////////////
1796
1797class QPMMimeText : public QPMMime
1798{
1799public:
1800 QPMMimeText();
1801 ~QPMMimeText();
1802
1803 // for converting from Qt
1804 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
1805 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
1806 ULONG &flags, ULONG *data) const;
1807
1808 // for converting to Qt
1809 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
1810 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
1811 const QString &mimeType,
1812 QVariant::Type preferredType) const;
1813
1814 const ULONG CF_TextUnicode;
1815 const ULONG CF_TextHtml;
1816};
1817
1818QPMMimeText::QPMMimeText()
1819 // "text/unicode" is what Mozilla uses to for unicode
1820 : CF_TextUnicode (registerMimeType(QLatin1String("text/unicode")))
1821 // "text/html" is what Mozilla uses to for HTML
1822 , CF_TextHtml (registerMimeType(QLatin1String("text/html")))
1823{
1824}
1825
1826QPMMimeText::~QPMMimeText()
1827{
1828 unregisterMimeType(CF_TextHtml);
1829 unregisterMimeType(CF_TextUnicode);
1830}
1831
1832QList<QPMMime::MimeCFPair> QPMMimeText::formatsForMimeData(const QMimeData *mimeData) const
1833{
1834 QList<MimeCFPair> fmts;
1835 // prefer HTML as it's reacher
1836 if (mimeData->hasHtml())
1837 fmts << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
1838 // prefer unicode over local8Bit
1839 if (mimeData->hasText())
1840 fmts << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode)
1841 << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
1842 return fmts;
1843}
1844
1845// text/plain is defined as using CRLF, but so many programs don't,
1846// and programmers just look for '\n' in strings.
1847// OS/2 really needs CRLF, so we ensure it here.
1848bool QPMMimeText::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1849 ULONG &flags, ULONG *data) const
1850{
1851 if (!mimeData->hasText() ||
1852 (format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml))
1853 return false;
1854
1855 flags = CFI_POINTER;
1856
1857 if (data == NULL)
1858 return true; // delayed rendering, nothing to do
1859
1860 QByteArray r;
1861
1862 if (format == CF_TEXT) {
1863 QByteArray str = mimeData->text().toLocal8Bit();
1864 // Anticipate required space for CRLFs at 1/40
1865 int maxsize = str.size()+str.size()/40+1;
1866 r.fill('\0', maxsize);
1867 char *o = r.data();
1868 const char *d = str.data();
1869 const int s = str.size();
1870 bool cr = false;
1871 int j = 0;
1872 for (int i = 0; i < s; i++) {
1873 char c = d[i];
1874 if (c == '\r')
1875 cr = true;
1876 else {
1877 if (c == '\n') {
1878 if (!cr)
1879 o[j++] = '\r';
1880 }
1881 cr = false;
1882 }
1883 o[j++] = c;
1884 if (j+1 >= maxsize) {
1885 maxsize += maxsize/4;
1886 r.resize(maxsize);
1887 o = r.data();
1888 }
1889 }
1890 if (j < r.size())
1891 o[j] = '\0';
1892 } else if (format == CF_TextUnicode || CF_TextHtml) {
1893 QString str = format == CF_TextUnicode ?
1894 mimeData->text() : mimeData->html();
1895 const QChar *u = str.unicode();
1896 QString res;
1897 const int s = str.length();
1898 int maxsize = s + s/40 + 3;
1899 res.resize(maxsize);
1900 int ri = 0;
1901 bool cr = false;
1902 for (int i = 0; i < s; ++i) {
1903 if (*u == QLatin1Char('\r'))
1904 cr = true;
1905 else {
1906 if (*u == QLatin1Char('\n') && !cr)
1907 res[ri++] = QLatin1Char('\r');
1908 cr = false;
1909 }
1910 res[ri++] = *u;
1911 if (ri+3 >= maxsize) {
1912 maxsize += maxsize/4;
1913 res.resize(maxsize);
1914 }
1915 ++u;
1916 }
1917 res.truncate(ri);
1918 const int byteLength = res.length()*2;
1919 r.fill('\0', byteLength + 2);
1920 memcpy(r.data(), res.unicode(), byteLength);
1921 r[byteLength] = 0;
1922 r[byteLength+1] = 0;
1923 } else{
1924 return false;
1925 }
1926
1927 *data = QPMMime::allocateMemory(r.size());
1928 if (!*data)
1929 return false;
1930
1931 memcpy((void *)*data, r.data(), r.size());
1932 return true;
1933}
1934
1935QList<QPMMime::MimeCFPair> QPMMimeText::mimesForFormats(const QList<ULONG> &formats) const
1936{
1937 QList<MimeCFPair> mimes;
1938 // prefer HTML as it's reacher
1939 if (formats.contains(CF_TextHtml))
1940 mimes << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
1941 // prefer unicode over local8Bit
1942 if (formats.contains(CF_TextUnicode))
1943 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode);
1944 if (formats.contains(CF_TEXT))
1945 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
1946 return mimes;
1947}
1948
1949QVariant QPMMimeText::convertFromFormat(ULONG format, ULONG flags, ULONG data,
1950 const QString &mimeType,
1951 QVariant::Type preferredType) const
1952{
1953 QVariant ret;
1954
1955 if (!mimeType.startsWith(QLatin1String("text/plain")) &&
1956 !mimeType.startsWith(QLatin1String("text/html")))
1957 return ret;
1958 if ((format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml) ||
1959 !(flags & CFI_POINTER) || !data)
1960 return ret;
1961
1962 QString str;
1963
1964 if (format == CF_TEXT) {
1965 const char *d = (const char *)data;
1966 QByteArray r("");
1967 if (*d) {
1968 const int s = qstrlen(d);
1969 r.fill('\0', s);
1970 char *o = r.data();
1971 int j = 0;
1972 for (int i = 0; i < s; i++) {
1973 char c = d[i];
1974 if (c != '\r')
1975 o[j++] = c;
1976 }
1977 }
1978 str = QString::fromLocal8Bit(r);
1979 } else if (format == CF_TextUnicode || CF_TextHtml) {
1980 str = QString::fromUtf16((const unsigned short *)data);
1981 str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
1982 }
1983
1984 if (preferredType == QVariant::String)
1985 ret = str;
1986 else
1987 ret = str.toUtf8();
1988
1989 return ret;
1990}
1991
1992////////////////////////////////////////////////////////////////////////////////
1993
1994class QPMMimeImage : public QPMMime
1995{
1996public:
1997 QPMMimeImage();
1998
1999 // for converting from Qt
2000 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2001 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2002 ULONG &flags, ULONG *data) const;
2003 // for converting to Qt
2004 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2005 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2006 const QString &mimeType,
2007 QVariant::Type preferredType) const;
2008};
2009
2010QPMMimeImage::QPMMimeImage()
2011{
2012}
2013
2014QList<QPMMime::MimeCFPair> QPMMimeImage::formatsForMimeData(const QMimeData *mimeData) const
2015{
2016 QList<MimeCFPair> fmts;
2017 if (mimeData->hasImage()) {
2018 // "application/x-qt-image" seems to be used as a single name for all
2019 // "image/xxx" types in Qt
2020 fmts << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2021 }
2022 return fmts;
2023}
2024
2025bool QPMMimeImage::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2026 ULONG &flags, ULONG *data) const
2027{
2028 if (!mimeData->hasImage() || format != CF_BITMAP)
2029 return false;
2030
2031 flags = CFI_HANDLE;
2032
2033 if (data == NULL)
2034 return true; // delayed rendering, nothing to do
2035
2036 QImage img = qvariant_cast<QImage>(mimeData->imageData());
2037 if (img.isNull())
2038 return false;
2039
2040 QPixmap pm = QPixmap::fromImage(img);
2041 if (pm.isNull())
2042 return false;
2043
2044 HBITMAP bmp = pm.toPmHBITMAP(0, true);
2045 if (bmp == NULLHANDLE)
2046 return false;
2047
2048 *data = bmp;
2049 return true;
2050}
2051
2052QList<QPMMime::MimeCFPair> QPMMimeImage::mimesForFormats(const QList<ULONG> &formats) const
2053{
2054 QList<MimeCFPair> mimes;
2055 if (formats.contains(CF_BITMAP))
2056 mimes << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2057 return mimes;
2058}
2059
2060QVariant QPMMimeImage::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2061 const QString &mimeType,
2062 QVariant::Type preferredType) const
2063{
2064 Q_UNUSED(preferredType);
2065
2066 QVariant ret;
2067
2068 if (mimeType != QLatin1String("application/x-qt-image"))
2069 return ret;
2070 if (format != CF_BITMAP || !(flags & CFI_HANDLE) || !data)
2071 return ret;
2072
2073 QPixmap pm = QPixmap::fromPmHBITMAP((HBITMAP)data);
2074 if (pm.isNull())
2075 return ret;
2076
2077 ret = pm.toImage();
2078 return ret;
2079}
2080
2081////////////////////////////////////////////////////////////////////////////////
2082
2083class QPMMimeAnyMime : public QPMMime
2084{
2085public:
2086 QPMMimeAnyMime();
2087 ~QPMMimeAnyMime();
2088
2089 // for converting from Qt
2090 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2091 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2092 ULONG &flags, ULONG *data) const;
2093 // for converting to Qt
2094 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2095 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2096 const QString &mimeType,
2097 QVariant::Type preferredType) const;
2098
2099private:
2100 ULONG registerMimeType(const QString &mime) const;
2101 QString registerFormat(ULONG format) const;
2102
2103 mutable QMap<QString, ULONG> cfMap;
2104 mutable QMap<ULONG, QString> mimeMap;
2105
2106 static QStringList ianaTypes;
2107 static QString mimePrefix;
2108 static QString customPrefix;
2109};
2110
2111// static
2112QStringList QPMMimeAnyMime::ianaTypes;
2113QString QPMMimeAnyMime::mimePrefix;
2114QString QPMMimeAnyMime::customPrefix;
2115
2116QPMMimeAnyMime::QPMMimeAnyMime()
2117{
2118 //MIME Media-Types
2119 if (!ianaTypes.size()) {
2120 ianaTypes.append(QLatin1String("application/"));
2121 ianaTypes.append(QLatin1String("audio/"));
2122 ianaTypes.append(QLatin1String("example/"));
2123 ianaTypes.append(QLatin1String("image/"));
2124 ianaTypes.append(QLatin1String("message/"));
2125 ianaTypes.append(QLatin1String("model/"));
2126 ianaTypes.append(QLatin1String("multipart/"));
2127 ianaTypes.append(QLatin1String("text/"));
2128 ianaTypes.append(QLatin1String("video/"));
2129
2130 mimePrefix = QLatin1String("x-mime:");
2131 customPrefix = QLatin1String("application/x-qt-pm-mime;value=\"");
2132 }
2133}
2134
2135QPMMimeAnyMime::~QPMMimeAnyMime()
2136{
2137 foreach(ULONG cf, cfMap.values())
2138 unregisterMimeType(cf);
2139}
2140
2141QList<QPMMime::MimeCFPair> QPMMimeAnyMime::formatsForMimeData(const QMimeData *mimeData) const
2142{
2143 QList<MimeCFPair> fmts;
2144
2145 QStringList mimes = QInternalMimeData::formatsHelper(mimeData);
2146 foreach (QString mime, mimes) {
2147 ULONG cf = cfMap.value(mime);
2148 if (!cf)
2149 cf = registerMimeType(mime);
2150 if (cf)
2151 fmts << MimeCFPair(mime, cf);
2152 }
2153
2154 return fmts;
2155}
2156
2157bool QPMMimeAnyMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2158 ULONG &flags, ULONG *data) const
2159{
2160 QString mime = mimeMap.value(format);
2161 if (mime.isNull())
2162 return false;
2163
2164 flags = CFI_POINTER;
2165
2166 if (data == NULL)
2167 return true; // delayed rendering, nothing to do
2168
2169 QByteArray r = QInternalMimeData::renderDataHelper(mime, mimeData);
2170 if (r.isNull())
2171 return false;
2172
2173 *data = QPMMime::allocateMemory(r.size() + sizeof(ULONG));
2174 if (!*data)
2175 return false;
2176
2177 *((ULONG *)(*data)) = r.size();
2178 memcpy((void *)(*data + sizeof(ULONG)), r.data(), r.size());
2179 return true;
2180}
2181
2182QList<QPMMime::MimeCFPair> QPMMimeAnyMime::mimesForFormats(const QList<ULONG> &formats) const
2183{
2184 QList<MimeCFPair> mimes;
2185
2186 foreach (ULONG format, formats) {
2187 QString mime = mimeMap.value(format);
2188 if (mime.isEmpty())
2189 mime = registerFormat(format);
2190 if (!mime.isEmpty())
2191 mimes << MimeCFPair(mime, format);
2192 }
2193
2194 return mimes;
2195}
2196
2197QVariant QPMMimeAnyMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2198 const QString &mimeType,
2199 QVariant::Type preferredType) const
2200{
2201 Q_UNUSED(preferredType);
2202
2203 QVariant ret;
2204
2205 if (cfMap.value(mimeType) != format)
2206 return ret;
2207
2208 if (!(flags & CFI_POINTER) || !data)
2209 return ret;
2210
2211 // get the real block size (always rounded to the page boundary (4K))
2212 ULONG sz = ~0, fl = 0, arc;
2213 arc = DosQueryMem((PVOID)data, &sz, &fl);
2214 if (arc != NO_ERROR) {
2215#ifndef QT_NO_DEBUG
2216 qWarning("QPMMimeText::convertFromFormat: DosQueryMem failed with %lu", arc);
2217#endif
2218 return ret;
2219 }
2220 ULONG size = *((ULONG *)data);
2221 if (!size || size + sizeof(ULONG) > sz)
2222 return ret;
2223
2224 // it should be enough to return the data and let QMimeData do the rest.
2225 ret = QByteArray((const char *)(data + sizeof(ULONG)), size);
2226 return ret;
2227}
2228
2229ULONG QPMMimeAnyMime::registerMimeType(const QString &mime) const
2230{
2231 if (mime.isEmpty())
2232 return 0;
2233
2234 QString mimeToReg = mime;
2235
2236 bool ianaType = false;
2237 foreach(QString prefix, ianaTypes) {
2238 if (mime.startsWith(prefix)) {
2239 ianaType = true;
2240 break;
2241 }
2242 }
2243 if (!ianaType) {
2244 // prepend the non-standard type with the prefix that makes it comply
2245 // with the standard
2246 mimeToReg = customPrefix + mime + QLatin1Char('\"');
2247 }
2248
2249 mimeToReg = mimePrefix + mimeToReg;
2250 ULONG cf = QPMMime::registerMimeType(mimeToReg);
2251 if (cf) {
2252 cfMap[mime] = cf;
2253 mimeMap[cf] = mime;
2254 }
2255 return cf;
2256}
2257
2258QString QPMMimeAnyMime::registerFormat(ULONG format) const
2259{
2260 QString mime;
2261
2262 if (!format)
2263 return mime;
2264
2265 QString atomStr = formatName(format);
2266 if (atomStr.startsWith(mimePrefix)) {
2267 // the format represents the mime type we can recognize
2268 // increase the reference count
2269 ULONG cf = QPMMime::registerMimeType(atomStr);
2270 Q_ASSERT(cf == format);
2271 // extract the real mime type (w/o our prefix)
2272 mime = atomStr.mid(mimePrefix.size());
2273 if (!mime.isEmpty()) {
2274 cfMap[mime] = cf;
2275 mimeMap[cf] = mime;
2276 }
2277 }
2278 return mime;
2279}
2280
2281////////////////////////////////////////////////////////////////////////////////
2282
2283QPMMimeList::QPMMimeList()
2284 : initialized(false)
2285{
2286}
2287
2288QPMMimeList::~QPMMimeList()
2289{
2290 while (list.size())
2291 delete list.first();
2292}
2293
2294
2295void QPMMimeList::init()
2296{
2297 if (!initialized) {
2298 initialized = true;
2299 new QPMMimeAnyMime; // must be the first (used as a fallback)
2300 new QPMMimeImage;
2301 new QPMMimeText;
2302 }
2303}
2304
2305void QPMMimeList::addMime(QPMMime *mime)
2306{
2307 init();
2308 list.prepend(mime);
2309}
2310
2311void QPMMimeList::removeMime(QPMMime *mime)
2312{
2313 init();
2314 list.removeAll(mime);
2315}
2316
2317QList<QPMMime*> QPMMimeList::mimes()
2318{
2319 init();
2320 return list;
2321}
2322
2323QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.