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

Last change on this file since 443 was 442, checked in by Dmitry A. Kuminov, 16 years ago

gui: A bunch of updates to the DND code (the drop site party).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 74.3 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)
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 Returns a list of all currently defined QPMMime objects.
1312*/
1313// static
1314QList<QPMMime*> QPMMime::all()
1315{
1316 return theMimeList()->mimes();
1317}
1318
1319/*!
1320 Allocates a block of shared memory of the given size and returns the address
1321 of this block. This memory block may be then filled with data and returned
1322 by convertFromMimeData() as the value of the CFI_POINTER type.
1323*/
1324// static
1325ULONG QPMMime::allocateMemory(size_t size)
1326{
1327 if (size == 0)
1328 return 0;
1329
1330 ULONG data = 0;
1331
1332 // allocate giveable memory for the array
1333 APIRET arc = DosAllocSharedMem((PVOID *)&data, NULL, size,
1334 PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE);
1335 if (arc != NO_ERROR) {
1336#ifndef QT_NO_DEBUG
1337 qWarning("QPMMime::allocateMemory: DosAllocSharedMem failed with %lu", arc);
1338#endif
1339 return 0;
1340 }
1341
1342 return data;
1343}
1344
1345/*!
1346 Frees memory allocated by allocateMemory(). Normally, not used because the
1347 CFI_POINTER memory blocks are owned by the system after
1348 convertFromMimeData() returns.
1349*/
1350// static
1351void QPMMime::freeMemory(ULONG addr)
1352{
1353 DosFreeMem((PVOID)addr);
1354}
1355
1356/*!
1357 \fn QList<MimeCFPair> QPMMime::formatsForMimeData(const QMimeData *mimeData) const
1358
1359 Returns a list of ULONG values representing the different OS/2 PM
1360 clipboard formats that can be provided for the \a mimeData, in order of
1361 precedence (the most suitable format goes first), or an empty list if
1362 neither of the mime types provided by \a mimeData is supported by this
1363 converter. Note that each item in the returned list is actually a pair
1364 consisting of the mime type name and the corresponding format identifier.
1365
1366 All subclasses must reimplement this pure virtual function.
1367*/
1368
1369/*!
1370 \fn bool QPMMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1371 ULONG &flags, ULONG *data) const
1372
1373 Converts the \a mimeData to the specified \a format.
1374
1375 If \a data is not NULL, a handle to the converted data should be then placed
1376 in a variable pointed to by \a data and with the necessary flags describing
1377 the handle returned in the \a flags variable.
1378
1379 The following flags describing the data storage type are recognized:
1380
1381 \table
1382 \row \o \c CFI_POINTER \o \a data is a pointer to a block of memory
1383 allocated with QPMMime::allocateMemory()
1384 \row \o \c CFI_HANDLE \o \a data is a handle to the appropriate
1385 PM resource
1386 \endtable
1387
1388 If \a data is NULL then a delayed conversion is requested by the caller.
1389 The implementation should return the appropriate flags in the \a flags
1390 variable and may perform the real data conversion later when this method is
1391 called again with \a data being non-NULL.
1392
1393 Return true if the conversion was successful.
1394
1395 All subclasses must reimplement this pure virtual function.
1396*/
1397
1398/*!
1399 \fn QList<MimeCFPair> QPMMime::mimesForFormats(const QList<ULONG> &formats) const
1400
1401 Returns a list of mime types that will be created form the specified \a list
1402 of \a formats, in order of precedence (the most suitable mime type comes
1403 first), or an empty list if neither of the \a formats is supported by this
1404 converter. Note that each item in the returned list is actually a pair
1405 consisting of the mime type name and the corresponding format identifier.
1406
1407 All subclasses must reimplement this pure virtual function.
1408*/
1409
1410/*!
1411 \fn QVariant QPMMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
1412 const QString &mimeType,
1413 QVariant::Type preferredType) const
1414
1415 Returns a QVariant containing the converted from the \a data in the
1416 specified \a format with the given \a flags to the requested \a mimeType. If
1417 possible the QVariant should be of the \a preferredType to avoid needless
1418 conversions.
1419
1420 All subclasses must reimplement this pure virtual function.
1421*/
1422
1423// static
1424QList<QPMMime::Match> QPMMime::allConvertersFromFormats(const QList<ULONG> &formats)
1425{
1426 QList<Match> matches;
1427
1428 QList<QPMMime*> mimes = theMimeList()->mimes();
1429 foreach(QPMMime *mime, mimes) {
1430 QList<MimeCFPair> fmts = mime->mimesForFormats(formats);
1431 int priority = 0;
1432 foreach (MimeCFPair fmt, fmts) {
1433 ++priority;
1434 QList<Match>::iterator it = matches.begin();
1435 for (; it != matches.end(); ++it) {
1436 Match &match = *it;
1437 if (match.mime == fmt.mime) {
1438 // replace if priority is higher, ignore otherwise
1439 if (priority < match.priority) {
1440 match.converter = mime;
1441 match.format = fmt.format;
1442 match.priority = priority;
1443 }
1444 break;
1445 }
1446 }
1447 if (it == matches.end()) {
1448 matches += Match(mime, fmt.mime, fmt.format, priority);
1449 }
1450 }
1451 }
1452
1453 return matches;
1454}
1455
1456// static
1457QList<QPMMime::Match> QPMMime::allConvertersFromMimeData(const QMimeData *mimeData)
1458{
1459 QList<Match> matches;
1460
1461 QList<QPMMime*> mimes = theMimeList()->mimes();
1462 foreach(QPMMime *mime, mimes) {
1463 QList<MimeCFPair> fmts = mime->formatsForMimeData(mimeData);
1464 int priority = 0;
1465 foreach (MimeCFPair fmt, fmts) {
1466 ++priority;
1467 QList<Match>::iterator it = matches.begin();
1468 for (; it != matches.end(); ++it) {
1469 Match &match = *it;
1470 if (mime == mimes.last()) { // QPMMimeAnyMime?
1471 if (match.mime == fmt.mime){
1472 // we assume that specialized converters (that come
1473 // first) provide a more precise conversion than
1474 // QPMMimeAnyMime and don't let it get into the list in
1475 // order to avoid unnecessary duplicate representations
1476 break;
1477 }
1478 }
1479 if (match.format == fmt.format) {
1480 // replace if priority is higher, ignore otherwise
1481 if (priority < match.priority) {
1482 match.converter = mime;
1483 match.mime = fmt.mime;
1484 match.priority = priority;
1485 }
1486 break;
1487 }
1488 }
1489 if (it == matches.end()) {
1490 matches += Match(mime, fmt.mime, fmt.format, priority);
1491 }
1492 }
1493 }
1494
1495 return matches;
1496}
1497
1498QString QPMMime::formatName(ULONG format)
1499{
1500 QString name;
1501 HATOMTBL tbl = WinQuerySystemAtomTable();
1502 if (tbl != NULLHANDLE) {
1503 ULONG len = WinQueryAtomLength(tbl, format);
1504 QByteArray atom(len, '\0');
1505 WinQueryAtomName(tbl, format, atom.data(), atom.size() + 1);
1506 name = QString::fromLocal8Bit(atom);
1507 }
1508 return name;
1509}
1510
1511#if !defined(QT_NO_DRAGANDDROP)
1512
1513/*!
1514 Returns a string represented by \a hstr.
1515*/
1516// static
1517QByteArray QPMMime::queryHSTR(HSTR hstr)
1518{
1519 QByteArray str;
1520 ULONG len = DrgQueryStrNameLen(hstr);
1521 if (len) {
1522 str.resize(len);
1523 DrgQueryStrName(hstr, str.size() + 1 /* \0 */, str.data());
1524 }
1525 return str;
1526}
1527
1528/*!
1529 Returns a string that is a concatenation of \c hstrContainerName and
1530 \c hstrSourceName fileds of the given \a item structure.
1531*/
1532// static
1533QByteArray QPMMime::querySourceNameFull(DRAGITEM *item)
1534{
1535 QByteArray fullName;
1536 if (!item)
1537 return fullName;
1538
1539 ULONG pathLen = DrgQueryStrNameLen(item->hstrContainerName);
1540 ULONG nameLen = DrgQueryStrNameLen(item->hstrSourceName);
1541 if (!pathLen || !nameLen)
1542 return fullName;
1543
1544 fullName.resize(pathLen + nameLen);
1545 DrgQueryStrName(item->hstrContainerName, pathLen + 1, fullName.data());
1546 DrgQueryStrName(item->hstrSourceName, nameLen + 1, fullName.data() + pathLen);
1547
1548 return fullName;
1549}
1550
1551/*! \internal
1552
1553 Checks that the given drag \a item supports the DRM_OS2FILE rendering
1554 mechanism and can be rendered by a target w/o involving the source (i.e.,
1555 DRM_OS2FILE is the first supported format and a valid file name with full
1556 path is provided). If the function returns TRUE, \a fullName (if not NULL)
1557 will be assigned the item's full source file name (composed from
1558 \c hstrContainerName and \c hstrSourceName fields).
1559 */
1560// static
1561bool QPMMime::canTargetRenderAsOS2File(DRAGITEM *item, QByteArray *fullName /*= 0*/)
1562{
1563 if ( !item )
1564 return false;
1565
1566 if (item->fsControl & (DC_PREPARE | DC_PREPAREITEM))
1567 return false;
1568
1569 {
1570 // DrgVerifyNativeRMF doesn't work on my system (ECS 1.2.1 GA):
1571 // it always returns FALSE regardless of arguments. Use simplified
1572 // hstrRMF parsing to determine whether DRM_OS2FILE is the native
1573 // mechanism or not (i.e. "^\s*[\(<]\s*DRM_OS2FILE\s*,.*").
1574
1575 QByteArray rmf = queryHSTR(item->hstrRMF);
1576 bool ok = false;
1577 int i = rmf.indexOf("DRM_OS2FILE");
1578 if (i >= 1) {
1579 for (int j = i - 1; j >= 0; --j) {
1580 char ch = rmf[j];
1581 if (ch == ' ')
1582 continue;
1583 if (ch == '<' || ch == '(') {
1584 if (ok)
1585 return false;
1586 ok = true;
1587 } else {
1588 return false;
1589 }
1590 }
1591 }
1592 if (ok) {
1593 ok = false;
1594 int drmLen = strlen("DRM_OS2FILE");
1595 for (int j = i + drmLen; j < (int) rmf.size(); ++j) {
1596 char ch = rmf[j];
1597 if (ch == ' ')
1598 continue;
1599 if (ch == ',') {
1600 ok = true;
1601 break;
1602 }
1603 return false;
1604 }
1605 }
1606 if (!ok)
1607 return false;
1608 }
1609
1610 QByteArray srcFullName = querySourceNameFull(item);
1611 if (srcFullName.isEmpty())
1612 return false;
1613
1614 QByteArray srcFullName2(srcFullName.size(), '\0');
1615 APIRET rc = DosQueryPathInfo(srcFullName, FIL_QUERYFULLNAME,
1616 srcFullName2.data(), srcFullName2.size());
1617 if (rc != 0)
1618 return false;
1619
1620 QString s1 = QFile::decodeName(srcFullName);
1621 QString s2 = QFile::decodeName(srcFullName2);
1622
1623 if (s1.compare(s2, Qt::CaseInsensitive) != 0)
1624 return false;
1625
1626 if (fullName)
1627 *fullName = srcFullName;
1628 return true;
1629}
1630
1631/*! \internal
1632
1633 Parses the given \a rmfs list (full rendering mechanism/format specification)
1634 and builds a \a list of mechanism branches. Each mechanism branch is also a
1635 list, where the first item is the mechahism name and all subsequent items are
1636 formats supported by this mechanism. Returns false if fails to parse \a rmf.
1637
1638 \note The method clears the given \a list variable before proceeding.
1639*/
1640// static
1641bool QPMMime::parseRMFs(HSTR rmfs, QList<QByteArrayList> &list)
1642{
1643 // The format of the RMF list is "elem {,elem,elem...}"
1644 // where elem is "(mechanism{,mechanism...}) x (format{,format...})"
1645 // or "<mechanism,format>".
1646 // We use a simple FSM to parse it. In terms of FSM, the format is:
1647 //
1648 // STRT ( BCM m CMCH echanism CMCH , NCM m CMCH echanism CMCH ) ECM x
1649 // SCMF ( BCF f CFCH ormat CFCH , NCF f CFCH ormat CFCH ) ECF , STRT
1650 // STRT < BP m PMCH echanism PMCH , SPMF f PFCH ormat PFCH > EP , STRT
1651
1652 QByteArray str = queryHSTR(rmfs);
1653 uint len = str.length();
1654
1655 enum {
1656 // states
1657 STRT = 0, BCM, CMCH, NCM, ECM, SCMF, BCF, CFCH, NCF, ECF,
1658 BP, PMCH, SPMF, PFCH, EP,
1659 STATES_COUNT,
1660 // pseudo states
1661 Err, Skip,
1662 // inputs
1663 obr = 0, cbr, xx, lt, gt, cm, any, ws,
1664 INPUTS_COUNT,
1665 };
1666
1667 static const char Chars[] = { '(', ')', 'x', 'X', '<', '>', ',', ' ', 0 };
1668 static const char Inputs[] = { obr, cbr, xx, xx, lt, gt, cm, ws };
1669 static const uchar Fsm [STATES_COUNT] [INPUTS_COUNT] = {
1670 /* 0 obr 1 cbr 2 xx 3 lt 4 gt 5 cm 6 any 7 ws */
1671/* STRT 0 */ { BCM, Err, Err, BP, Err, Err, Err, Skip },
1672/* BCM 1 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1673/* CMCH 2 */ { Err, ECM, CMCH, Err, Err, NCM, CMCH, CMCH },
1674/* NCM 3 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1675/* ECM 4 */ { Err, Err, SCMF, Err, Err, Err, Err, Skip },
1676/* SCMF 5 */ { BCF, Err, Err, Err, Err, Err, Err, Skip },
1677/* BCF 6 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1678/* CFCH 7 */ { Err, ECF, CFCH, Err, Err, NCF, CFCH, CFCH },
1679/* NCF 8 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1680/* ECF 9 */ { Err, Err, Err, Err, Err, STRT, Err, Skip },
1681/* BP 10 */ { Err, Err, Err, Err, Err, Err, PMCH, Skip },
1682/* PMCH 11 */ { Err, Err, PMCH, Err, Err, SPMF, PMCH, PMCH },
1683/* SPMF 12 */ { Err, Err, Err, Err, Err, Err, PFCH, Skip },
1684/* PFCH 13 */ { Err, Err, PFCH, Err, EP, Err, PFCH, PFCH },
1685/* EP 14 */ { Err, Err, Err, Err, Err, STRT, Err, Skip }
1686 };
1687
1688 list.clear();
1689
1690 QList<QByteArrayList*> refList;
1691
1692 QByteArray buf;
1693 QList<QByteArrayList>::iterator rmf;
1694
1695 uint state = STRT;
1696 uint start = 0, end = 0, space = 0;
1697
1698 for (uint i = 0; i < len && state != Err ; ++i) {
1699 char ch = str[i];
1700 char *p = strchr(Chars, ch);
1701 uint input = p ? Inputs[p - Chars] : any;
1702 uint newState = Fsm[state][input];
1703 switch (newState) {
1704 case Skip:
1705 continue;
1706 case CMCH:
1707 case CFCH:
1708 case PMCH:
1709 case PFCH:
1710 if (state != newState)
1711 start = end = i;
1712 ++end;
1713 // accumulate trailing space for truncation
1714 if (input == ws) ++space;
1715 else space = 0;
1716 break;
1717 case NCM:
1718 case ECM:
1719 case SPMF:
1720 buf = QByteArray(str.data() + start, end - start - space);
1721 // find the mechanism branch in the output list
1722 for (rmf = list.begin(); rmf != list.end(); ++rmf) {
1723 if (rmf->first() == buf)
1724 break;
1725 }
1726 if (rmf == list.end()) {
1727 // append to the output list if not found
1728 QByteArrayList newRmf;
1729 newRmf.append(buf);
1730 rmf = list.insert(list.end(), newRmf);
1731 }
1732 // store a refecence in the helper list for making a cross product
1733 refList.append(&*rmf);
1734 start = end = 0;
1735 break;
1736 case NCF:
1737 case ECF:
1738 case EP:
1739 buf = QByteArray(str.data() + start, end - start - space);
1740 // make a cross product with all current mechanisms
1741 foreach(QByteArrayList *rmfRef, refList)
1742 rmfRef->append(buf);
1743 if (newState != NCF)
1744 refList.clear();
1745 start = end = 0;
1746 break;
1747 default:
1748 break;
1749 }
1750 state = newState;
1751 }
1752
1753 return state == ECF || state == EP;
1754}
1755
1756/*! \internal
1757
1758 Splits the given \a rmf (rendering mechanism/format pair) to a \a mechanism
1759 and a \a format string. Returns FALSE if fails to parse \a rmf.
1760 */
1761// static
1762bool QPMMime::parseRMF(HSTR rmf, QByteArray &mechanism, QByteArray &format)
1763{
1764 QList<QByteArrayList> list;
1765 if (!parseRMFs(rmf, list))
1766 return false;
1767
1768 if (list.count() != 1 || list.first().count() != 2)
1769 return false;
1770
1771 QByteArrayList first = list.first();
1772 mechanism = first.at(0);
1773 format = first.at(1);
1774
1775 return true;
1776}
1777
1778/*! \internal */
1779// static
1780QPMMime::DefaultDragWorker *QPMMime::defaultCoopDragWorker()
1781{
1782 static DefaultDragWorker defCoopDragWorker(false /* exclusive */);
1783 return &defCoopDragWorker;
1784}
1785
1786// static
1787/*! \internal */
1788QPMMime::DefaultDragWorker *QPMMime::defaultExclDragWorker()
1789{
1790 static DefaultDragWorker defExclDragWorker(false /* exclusive */);
1791 return &defExclDragWorker;
1792}
1793
1794/*! \internal */
1795// static
1796QPMMime::DefaultDropWorker *QPMMime::defaultDropWorker()
1797{
1798 static DefaultDropWorker defaultDropWorker;
1799 return &defaultDropWorker;
1800}
1801
1802#endif // !QT_NO_DRAGANDDROP
1803
1804////////////////////////////////////////////////////////////////////////////////
1805
1806class QPMMimeText : public QPMMime
1807{
1808public:
1809 QPMMimeText();
1810 ~QPMMimeText();
1811
1812 // for converting from Qt
1813 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
1814 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
1815 ULONG &flags, ULONG *data) const;
1816
1817 // for converting to Qt
1818 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
1819 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
1820 const QString &mimeType,
1821 QVariant::Type preferredType) const;
1822
1823 const ULONG CF_TextUnicode;
1824 const ULONG CF_TextHtml;
1825};
1826
1827QPMMimeText::QPMMimeText()
1828 // "text/unicode" is what Mozilla uses to for unicode
1829 : CF_TextUnicode (registerMimeType(QLatin1String("text/unicode")))
1830 // "text/html" is what Mozilla uses to for HTML
1831 , CF_TextHtml (registerMimeType(QLatin1String("text/html")))
1832{
1833}
1834
1835QPMMimeText::~QPMMimeText()
1836{
1837 unregisterMimeType(CF_TextHtml);
1838 unregisterMimeType(CF_TextUnicode);
1839}
1840
1841QList<QPMMime::MimeCFPair> QPMMimeText::formatsForMimeData(const QMimeData *mimeData) const
1842{
1843 QList<MimeCFPair> fmts;
1844 // prefer HTML as it's reacher
1845 if (mimeData->hasHtml())
1846 fmts << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
1847 // prefer unicode over local8Bit
1848 if (mimeData->hasText())
1849 fmts << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode)
1850 << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
1851 return fmts;
1852}
1853
1854// text/plain is defined as using CRLF, but so many programs don't,
1855// and programmers just look for '\n' in strings.
1856// OS/2 really needs CRLF, so we ensure it here.
1857bool QPMMimeText::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1858 ULONG &flags, ULONG *data) const
1859{
1860 if (!mimeData->hasText() ||
1861 (format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml))
1862 return false;
1863
1864 flags = CFI_POINTER;
1865
1866 if (data == NULL)
1867 return true; // delayed rendering, nothing to do
1868
1869 QByteArray r;
1870
1871 if (format == CF_TEXT) {
1872 QByteArray str = mimeData->text().toLocal8Bit();
1873 // Anticipate required space for CRLFs at 1/40
1874 int maxsize = str.size()+str.size()/40+1;
1875 r.fill('\0', maxsize);
1876 char *o = r.data();
1877 const char *d = str.data();
1878 const int s = str.size();
1879 bool cr = false;
1880 int j = 0;
1881 for (int i = 0; i < s; i++) {
1882 char c = d[i];
1883 if (c == '\r')
1884 cr = true;
1885 else {
1886 if (c == '\n') {
1887 if (!cr)
1888 o[j++] = '\r';
1889 }
1890 cr = false;
1891 }
1892 o[j++] = c;
1893 if (j+1 >= maxsize) {
1894 maxsize += maxsize/4;
1895 r.resize(maxsize);
1896 o = r.data();
1897 }
1898 }
1899 if (j < r.size())
1900 o[j] = '\0';
1901 } else if (format == CF_TextUnicode || CF_TextHtml) {
1902 QString str = format == CF_TextUnicode ?
1903 mimeData->text() : mimeData->html();
1904 const QChar *u = str.unicode();
1905 QString res;
1906 const int s = str.length();
1907 int maxsize = s + s/40 + 3;
1908 res.resize(maxsize);
1909 int ri = 0;
1910 bool cr = false;
1911 for (int i = 0; i < s; ++i) {
1912 if (*u == QLatin1Char('\r'))
1913 cr = true;
1914 else {
1915 if (*u == QLatin1Char('\n') && !cr)
1916 res[ri++] = QLatin1Char('\r');
1917 cr = false;
1918 }
1919 res[ri++] = *u;
1920 if (ri+3 >= maxsize) {
1921 maxsize += maxsize/4;
1922 res.resize(maxsize);
1923 }
1924 ++u;
1925 }
1926 res.truncate(ri);
1927 const int byteLength = res.length()*2;
1928 r.fill('\0', byteLength + 2);
1929 memcpy(r.data(), res.unicode(), byteLength);
1930 r[byteLength] = 0;
1931 r[byteLength+1] = 0;
1932 } else{
1933 return false;
1934 }
1935
1936 *data = QPMMime::allocateMemory(r.size());
1937 if (!*data)
1938 return false;
1939
1940 memcpy((void *)*data, r.data(), r.size());
1941 return true;
1942}
1943
1944QList<QPMMime::MimeCFPair> QPMMimeText::mimesForFormats(const QList<ULONG> &formats) const
1945{
1946 QList<MimeCFPair> mimes;
1947 // prefer HTML as it's reacher
1948 if (formats.contains(CF_TextHtml))
1949 mimes << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
1950 // prefer unicode over local8Bit
1951 if (formats.contains(CF_TextUnicode))
1952 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode);
1953 if (formats.contains(CF_TEXT))
1954 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
1955 return mimes;
1956}
1957
1958QVariant QPMMimeText::convertFromFormat(ULONG format, ULONG flags, ULONG data,
1959 const QString &mimeType,
1960 QVariant::Type preferredType) const
1961{
1962 QVariant ret;
1963
1964 if (!mimeType.startsWith(QLatin1String("text/plain")) &&
1965 !mimeType.startsWith(QLatin1String("text/html")))
1966 return ret;
1967 if ((format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml) ||
1968 !(flags & CFI_POINTER) || !data)
1969 return ret;
1970
1971 QString str;
1972
1973 if (format == CF_TEXT) {
1974 const char *d = (const char *)data;
1975 QByteArray r("");
1976 if (*d) {
1977 const int s = qstrlen(d);
1978 r.fill('\0', s);
1979 char *o = r.data();
1980 int j = 0;
1981 for (int i = 0; i < s; i++) {
1982 char c = d[i];
1983 if (c != '\r')
1984 o[j++] = c;
1985 }
1986 }
1987 str = QString::fromLocal8Bit(r);
1988 } else if (format == CF_TextUnicode || CF_TextHtml) {
1989 str = QString::fromUtf16((const unsigned short *)data);
1990 str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
1991 }
1992
1993 if (preferredType == QVariant::String)
1994 ret = str;
1995 else
1996 ret = str.toUtf8();
1997
1998 return ret;
1999}
2000
2001////////////////////////////////////////////////////////////////////////////////
2002
2003class QPMMimeImage : public QPMMime
2004{
2005public:
2006 QPMMimeImage();
2007
2008 // for converting from Qt
2009 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2010 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2011 ULONG &flags, ULONG *data) const;
2012 // for converting to Qt
2013 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2014 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2015 const QString &mimeType,
2016 QVariant::Type preferredType) const;
2017};
2018
2019QPMMimeImage::QPMMimeImage()
2020{
2021}
2022
2023QList<QPMMime::MimeCFPair> QPMMimeImage::formatsForMimeData(const QMimeData *mimeData) const
2024{
2025 QList<MimeCFPair> fmts;
2026 if (mimeData->hasImage()) {
2027 // "application/x-qt-image" seems to be used as a single name for all
2028 // "image/xxx" types in Qt
2029 fmts << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2030 }
2031 return fmts;
2032}
2033
2034bool QPMMimeImage::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2035 ULONG &flags, ULONG *data) const
2036{
2037 if (!mimeData->hasImage() || format != CF_BITMAP)
2038 return false;
2039
2040 flags = CFI_HANDLE;
2041
2042 if (data == NULL)
2043 return true; // delayed rendering, nothing to do
2044
2045 QImage img = qvariant_cast<QImage>(mimeData->imageData());
2046 if (img.isNull())
2047 return false;
2048
2049 QPixmap pm = QPixmap::fromImage(img);
2050 if (pm.isNull())
2051 return false;
2052
2053 HBITMAP bmp = pm.toPmHBITMAP(0, true);
2054 if (bmp == NULLHANDLE)
2055 return false;
2056
2057 *data = bmp;
2058 return true;
2059}
2060
2061QList<QPMMime::MimeCFPair> QPMMimeImage::mimesForFormats(const QList<ULONG> &formats) const
2062{
2063 QList<MimeCFPair> mimes;
2064 if (formats.contains(CF_BITMAP))
2065 mimes << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2066 return mimes;
2067}
2068
2069QVariant QPMMimeImage::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2070 const QString &mimeType,
2071 QVariant::Type preferredType) const
2072{
2073 Q_UNUSED(preferredType);
2074
2075 QVariant ret;
2076
2077 if (mimeType != QLatin1String("application/x-qt-image"))
2078 return ret;
2079 if (format != CF_BITMAP || !(flags & CFI_HANDLE) || !data)
2080 return ret;
2081
2082 QPixmap pm = QPixmap::fromPmHBITMAP((HBITMAP)data);
2083 if (pm.isNull())
2084 return ret;
2085
2086 ret = pm.toImage();
2087 return ret;
2088}
2089
2090////////////////////////////////////////////////////////////////////////////////
2091
2092class QPMMimeAnyMime : public QPMMime
2093{
2094public:
2095 QPMMimeAnyMime();
2096 ~QPMMimeAnyMime();
2097
2098 // for converting from Qt
2099 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2100 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2101 ULONG &flags, ULONG *data) const;
2102 // for converting to Qt
2103 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2104 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2105 const QString &mimeType,
2106 QVariant::Type preferredType) const;
2107
2108private:
2109 ULONG registerMimeType(const QString &mime) const;
2110 QString registerFormat(ULONG format) const;
2111
2112 mutable QMap<QString, ULONG> cfMap;
2113 mutable QMap<ULONG, QString> mimeMap;
2114
2115 static QStringList ianaTypes;
2116 static QString mimePrefix;
2117 static QString customPrefix;
2118};
2119
2120// static
2121QStringList QPMMimeAnyMime::ianaTypes;
2122QString QPMMimeAnyMime::mimePrefix;
2123QString QPMMimeAnyMime::customPrefix;
2124
2125QPMMimeAnyMime::QPMMimeAnyMime()
2126{
2127 //MIME Media-Types
2128 if (!ianaTypes.size()) {
2129 ianaTypes.append(QLatin1String("application/"));
2130 ianaTypes.append(QLatin1String("audio/"));
2131 ianaTypes.append(QLatin1String("example/"));
2132 ianaTypes.append(QLatin1String("image/"));
2133 ianaTypes.append(QLatin1String("message/"));
2134 ianaTypes.append(QLatin1String("model/"));
2135 ianaTypes.append(QLatin1String("multipart/"));
2136 ianaTypes.append(QLatin1String("text/"));
2137 ianaTypes.append(QLatin1String("video/"));
2138
2139 mimePrefix = QLatin1String("x-mime:");
2140 customPrefix = QLatin1String("application/x-qt-pm-mime;value=\"");
2141 }
2142}
2143
2144QPMMimeAnyMime::~QPMMimeAnyMime()
2145{
2146 foreach(ULONG cf, cfMap.values())
2147 unregisterMimeType(cf);
2148}
2149
2150QList<QPMMime::MimeCFPair> QPMMimeAnyMime::formatsForMimeData(const QMimeData *mimeData) const
2151{
2152 QList<MimeCFPair> fmts;
2153
2154 QStringList mimes = QInternalMimeData::formatsHelper(mimeData);
2155 foreach (QString mime, mimes) {
2156 ULONG cf = cfMap.value(mime);
2157 if (!cf)
2158 cf = registerMimeType(mime);
2159 if (cf)
2160 fmts << MimeCFPair(mime, cf);
2161 }
2162
2163 return fmts;
2164}
2165
2166bool QPMMimeAnyMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2167 ULONG &flags, ULONG *data) const
2168{
2169 QString mime = mimeMap.value(format);
2170 if (mime.isNull())
2171 return false;
2172
2173 flags = CFI_POINTER;
2174
2175 if (data == NULL)
2176 return true; // delayed rendering, nothing to do
2177
2178 QByteArray r = QInternalMimeData::renderDataHelper(mime, mimeData);
2179 if (r.isNull())
2180 return false;
2181
2182 *data = QPMMime::allocateMemory(r.size() + sizeof(ULONG));
2183 if (!*data)
2184 return false;
2185
2186 *((ULONG *)(*data)) = r.size();
2187 memcpy((void *)(*data + sizeof(ULONG)), r.data(), r.size());
2188 return true;
2189}
2190
2191QList<QPMMime::MimeCFPair> QPMMimeAnyMime::mimesForFormats(const QList<ULONG> &formats) const
2192{
2193 QList<MimeCFPair> mimes;
2194
2195 foreach (ULONG format, formats) {
2196 QString mime = mimeMap.value(format);
2197 if (mime.isEmpty())
2198 mime = registerFormat(format);
2199 if (!mime.isEmpty())
2200 mimes << MimeCFPair(mime, format);
2201 }
2202
2203 return mimes;
2204}
2205
2206QVariant QPMMimeAnyMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2207 const QString &mimeType,
2208 QVariant::Type preferredType) const
2209{
2210 Q_UNUSED(preferredType);
2211
2212 QVariant ret;
2213
2214 if (cfMap.value(mimeType) != format)
2215 return ret;
2216
2217 if (!(flags & CFI_POINTER) || !data)
2218 return ret;
2219
2220 // get the real block size (always rounded to the page boundary (4K))
2221 ULONG sz = ~0, fl = 0, arc;
2222 arc = DosQueryMem((PVOID)data, &sz, &fl);
2223 if (arc != NO_ERROR) {
2224#ifndef QT_NO_DEBUG
2225 qWarning("QPMMimeText::convertFromFormat: DosQueryMem failed with %lu", arc);
2226#endif
2227 return ret;
2228 }
2229 ULONG size = *((ULONG *)data);
2230 if (!size || size + sizeof(ULONG) > sz)
2231 return ret;
2232
2233 // it should be enough to return the data and let QMimeData do the rest.
2234 ret = QByteArray((const char *)(data + sizeof(ULONG)), size);
2235 return ret;
2236}
2237
2238ULONG QPMMimeAnyMime::registerMimeType(const QString &mime) const
2239{
2240 if (mime.isEmpty())
2241 return 0;
2242
2243 QString mimeToReg = mime;
2244
2245 bool ianaType = false;
2246 foreach(QString prefix, ianaTypes) {
2247 if (mime.startsWith(prefix)) {
2248 ianaType = true;
2249 break;
2250 }
2251 }
2252 if (!ianaType) {
2253 // prepend the non-standard type with the prefix that makes it comply
2254 // with the standard
2255 mimeToReg = customPrefix + mime + QLatin1Char('\"');
2256 }
2257
2258 mimeToReg = mimePrefix + mimeToReg;
2259 ULONG cf = QPMMime::registerMimeType(mimeToReg);
2260 if (cf) {
2261 cfMap[mime] = cf;
2262 mimeMap[cf] = mime;
2263 }
2264 return cf;
2265}
2266
2267QString QPMMimeAnyMime::registerFormat(ULONG format) const
2268{
2269 QString mime;
2270
2271 if (!format)
2272 return mime;
2273
2274 QString atomStr = formatName(format);
2275 if (atomStr.startsWith(mimePrefix)) {
2276 // the format represents the mime type we can recognize
2277 // increase the reference count
2278 ULONG cf = QPMMime::registerMimeType(atomStr);
2279 Q_ASSERT(cf == format);
2280 // extract the real mime type (w/o our prefix)
2281 mime = atomStr.mid(mimePrefix.size());
2282 if (!mime.isEmpty()) {
2283 cfMap[mime] = cf;
2284 mimeMap[cf] = mime;
2285 }
2286 }
2287 return mime;
2288}
2289
2290////////////////////////////////////////////////////////////////////////////////
2291
2292QPMMimeList::QPMMimeList()
2293 : initialized(false)
2294{
2295}
2296
2297QPMMimeList::~QPMMimeList()
2298{
2299 while (list.size())
2300 delete list.first();
2301}
2302
2303
2304void QPMMimeList::init()
2305{
2306 if (!initialized) {
2307 initialized = true;
2308 new QPMMimeAnyMime; // must be the first (used as a fallback)
2309 new QPMMimeImage;
2310 new QPMMimeText;
2311 }
2312}
2313
2314void QPMMimeList::addMime(QPMMime *mime)
2315{
2316 init();
2317 list.prepend(mime);
2318}
2319
2320void QPMMimeList::removeMime(QPMMime *mime)
2321{
2322 init();
2323 list.removeAll(mime);
2324}
2325
2326QList<QPMMime*> QPMMimeList::mimes()
2327{
2328 init();
2329 return list;
2330}
2331
2332QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.