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

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

gui: DnD: Fixed: The "container" part of the drag item representing the file name could lack the trailing slash.

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