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

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

gui: Fixed converting text from/to local code page during DnD.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 86.7 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 bool ok = file.remove();
999 Q_ASSERT((ok = ok));
1000 Q_UNUSED(ok);
1001 } else {
1002 Q_ASSERT(xfer->hstrRenderToName);
1003 renderOk = xfer->hstrRenderToName != 0;
1004 if (renderOk) {
1005 const char *ptr = (const char *) xfer->hstrRenderToName;
1006 ULONG size = ~0;
1007 ULONG flags = 0;
1008 APIRET rc = DosQueryMem((PVOID) ptr, &size, &flags);
1009 renderOk = rc == 0;
1010 if (renderOk) {
1011 DEBUG(("DefaultDropWorker: Got shared data %p size %lu "
1012 "(0x%08lX) flags 0x%08lX", ptr, size, size, flags));
1013 Q_ASSERT((flags & (PAG_COMMIT | PAG_READ | PAG_BASE)) ==
1014 (PAG_COMMIT | PAG_READ | PAG_BASE));
1015 renderOk = (flags & (PAG_COMMIT | PAG_READ | PAG_BASE)) ==
1016 (PAG_COMMIT | PAG_READ | PAG_BASE);
1017#ifndef QT_NO_DEBUG
1018 } else {
1019 qWarning("DefaultDropWorker: DosQueryMem failed with %ld", rc);
1020#endif
1021 }
1022 if (renderOk) {
1023 ULONG realSize = *(ULONG *) ptr;
1024 DEBUG(() << "DefaultDropWorker: realSize" << realSize);
1025 Q_ASSERT(realSize <= size);
1026 renderOk = realSize <= size;
1027 if (renderOk) {
1028 itemData.resize(realSize);
1029 memcpy(itemData.data(), ptr + sizeof(ULONG), realSize);
1030 }
1031 }
1032 // free memory only if it is given by another process,
1033 // otherwise DefaultDragWorker will free it
1034 if (flags & PAG_SHARED)
1035 DosFreeMem((PVOID) xfer->hstrRenderToName);
1036 }
1037 }
1038 }
1039
1040 if (renderOk)
1041 renderOk = provider->provide(mimeType, i, itemData, allData);
1042
1043 if (needToTalk) {
1044 // free the DRAGTRANSFER structure
1045 DrgFreeDragtransfer(xfer);
1046#if defined(QT_DEBUG_DND)
1047 {
1048 ULONG size = ~0, flags = 0;
1049 DosQueryMem(xfer, &size, &flags);
1050 DEBUG(("DefaultDropWorker: Freed DRAGTRANSFER: "
1051 "xfer=%p, size=%lu(0x%08lX), flags=0x%08lX",
1052 xfer, size, size, flags));
1053 }
1054#endif
1055 xfer = NULL;
1056 }
1057
1058 if (!renderOk)
1059 break;
1060 }
1061
1062 DEBUG(() << "DefaultDropWorker: renderOk" << renderOk);
1063
1064 DrgDeleteStrHandle(rmfSharedMem);
1065 DrgDeleteStrHandle(rmfOS2File);
1066 DrgDeleteStrHandle(hstrRenderToName);
1067
1068 if (renderOk)
1069 ret = allData;
1070
1071 return ret;
1072}
1073
1074MRESULT QPMMime::DefaultDropWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
1075{
1076 switch (msg) {
1077 case DM_RENDERCOMPLETE: {
1078 // sanity check
1079 Q_ASSERT(info());
1080 if (!info())
1081 return (MRESULT)FALSE;
1082
1083 DEBUG(("DefaultDropWorker: Got DM_RENDERCOMPLETE"));
1084 d->got_DM_RENDERCOMPLETE = true;
1085 d->flags_DM_RENDERCOMPLETE = SHORT1FROMMP(mp2);
1086
1087 if (d->sending_DM_RENDER)
1088 {
1089#ifndef QT_NO_DEBUG
1090 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
1091 qWarning("Drag item 0x%08lX sent DM_RENDERCOMPLETE w/o first "
1092 "replying to DM_RENDER!\n"
1093 "Contact the drag source developer.",
1094 xfer->pditem->hwndItem);
1095#endif
1096 return (MRESULT)FALSE;
1097 }
1098
1099 // stop synchronous waiting for DM_RENDERCOMPLETE
1100 if (d->eventLoop.isRunning())
1101 d->eventLoop.exit();
1102 return (MRESULT)FALSE;
1103 }
1104 default:
1105 break;
1106 }
1107
1108 return (MRESULT)FALSE;
1109}
1110
1111bool QPMMime::DefaultDropWorker::addProvider(const QString &mimeType,
1112 Provider *provider)
1113{
1114 Q_ASSERT(!mimeType.isEmpty() && provider);
1115 if (!mimeType.isEmpty() && provider && !d->exclusive) {
1116 // ensure there are no dups (several providers for the same mime)
1117 if (!d->providerFor(mimeType))
1118 d->providers.append(Data::MimeProvider(mimeType, provider));
1119 return true;
1120 }
1121 return false;
1122}
1123
1124bool QPMMime::DefaultDropWorker::addExclusiveProvider(const QString &mimeType,
1125 Provider *provider)
1126{
1127 Q_ASSERT(!mimeType.isEmpty() && provider);
1128 if (!mimeType.isEmpty() && provider && !d->exclusive) {
1129 d->exclusive = true;
1130 d->providers.clear();
1131 d->providers.append(Data::MimeProvider(mimeType, provider));
1132 return true;
1133 }
1134 return false;
1135}
1136
1137// static
1138bool QPMMime::DefaultDropWorker::canRender(DRAGITEM *item, const char *drf)
1139{
1140 return DrgVerifyRMF(item, "DRM_OS2FILE", drf) ||
1141 (DrgVerifyRMF(item, "DRM_SHAREDMEM", drf) &&
1142 DrgVerifyRMF(item, "DRM_SHAREDMEM", "DRF_POINTERDATA"));
1143}
1144
1145/*! \internal
1146
1147 Parses the rendering mechanism/format specification of the given \a item
1148 and stores only those mechanism branches in the given \a list that represent
1149 mechanisms supported by this worker. Returns false if fails to parse the
1150 RMF specification. Note that if no supported mechanisms are found, true is
1151 returned but the \a list will simply contain zero items.
1152
1153 \note The method clears the given \a list variable before proceeding.
1154
1155 \sa canRender(), PMMime::parseRMFs()
1156*/
1157// static
1158bool QPMMime::DefaultDropWorker::getSupportedRMFs(DRAGITEM *item,
1159 QList<QByteArrayList> &list)
1160{
1161 if (!parseRMFs(item->hstrRMF, list))
1162 return false;
1163
1164 for (QList<QByteArrayList>::iterator rmf = list.begin(); rmf != list.end();) {
1165 QByteArrayList::iterator mf = rmf->begin();
1166 Q_ASSERT(mf != rmf->end());
1167 const char *drm = *mf;
1168 if (qstrcmp(drm, "DRM_OS2FILE") == 0) {
1169 ++rmf;
1170 continue;
1171 }
1172 if (qstrcmp(drm, "DRM_SHAREDMEM") == 0) {
1173 // accept DRM_SHAREDMEM only if there is DRF_POINTERDATA
1174 for(; mf != rmf->end(); ++mf) {
1175 const char *drf = *mf;
1176 if (qstrcmp(drf, "DRF_POINTERDATA") == 0)
1177 break;
1178 }
1179 if (mf != rmf->end()) {
1180 ++rmf;
1181 continue;
1182 }
1183 }
1184 // remove the unsupported mechanism branch from the list
1185 rmf = list.erase(rmf);
1186 }
1187
1188 return true;
1189}
1190
1191#endif // !QT_NO_DRAGANDDROP
1192
1193//------------------------------------------------------------------------------
1194
1195class QPMMimeList
1196{
1197public:
1198 QPMMimeList();
1199 ~QPMMimeList();
1200 void addMime(QPMMime *mime);
1201 void removeMime(QPMMime *mime);
1202 QList<QPMMime*> mimes();
1203
1204private:
1205 void init();
1206 bool initialized;
1207 QList<QPMMime*> list;
1208};
1209
1210Q_GLOBAL_STATIC(QPMMimeList, theMimeList);
1211
1212
1213/*!
1214 \class QPMMime
1215 \brief The QMPMime class maps open-standard MIME to OS/2 PM Clipboard
1216 formats.
1217 \ingroup io
1218 \ingroup draganddrop
1219 \ingroup misc
1220
1221 Qt's drag-and-drop and clipboard facilities use the MIME standard.
1222 On X11, this maps trivially to the Xdnd protocol, but on OS/2
1223 although some applications use MIME types to describe clipboard
1224 formats, others use arbitrary non-standardized naming conventions,
1225 or unnamed built-in formats of the Presentation Manager.
1226
1227 By instantiating subclasses of QPMMime that provide conversions between OS/2
1228 PM Clipboard and MIME formats, you can convert proprietary clipboard formats
1229 to MIME formats.
1230
1231 Qt has predefined support for the following PM Clipboard formats (custom
1232 formats registered in the system atom table by name are given in double
1233 quotes):
1234
1235 \table
1236 \header \o PM Format \o Equivalent MIME type
1237 \row \o \c CF_TEXT \o \c text/plain (system codepage,
1238 zero-terminated string)
1239 \row \o \c "text/unicode" \o \c text/plain (16-bit Unicode,
1240 zero-terminated string, Mozilla-compatible)
1241 \row \o \c "text/html" \o \c text/html (16-bit Unicode,
1242 zero-terminated string, Mozilla-compatible)
1243 \row \o \c CF_BITMAP \o \c{image/xyz}, where \c xyz is
1244 a \l{QImageWriter::supportedImageFormats()}{Qt image format}
1245 \row \o \c "x-mime:<mime>" \o data in the format corresponding to the given
1246 MIME type \c <mime>
1247 \endtable
1248
1249 Note that all "x-mime:<mime>" formats use the CFI_POINTER storage type. That
1250 is, the clipboard contains a pointer to the memory block containing the MIME
1251 data in the corresponding format. The first 4 bytes of this memory block
1252 always contain the length of the subsequent MIME data array, in bytes.
1253
1254 An example use of this class by the user application would be to map the
1255 PM Metafile clipboard format (\c CF_METAFILE) to and from the MIME type
1256 \c{image/x-metafile}. This conversion might simply be adding or removing a
1257 header, or even just passing on the data. See \l{Drag and Drop} for more
1258 information on choosing and definition MIME types.
1259*/
1260
1261/*!
1262Constructs a new conversion object, adding it to the globally accessed
1263list of available converters.
1264*/
1265QPMMime::QPMMime()
1266{
1267 theMimeList()->addMime(this);
1268}
1269
1270/*!
1271Destroys a conversion object, removing it from the global
1272list of available converters.
1273*/
1274QPMMime::~QPMMime()
1275{
1276 theMimeList()->removeMime(this);
1277}
1278
1279/*!
1280 Registers the MIME type \a mime, and returns an ID number
1281 identifying the format on OS/2. Intended to be used by QPMMime
1282 implementations for registering custom clipboard formats they use.
1283*/
1284// static
1285ULONG QPMMime::registerMimeType(const QString &mime)
1286{
1287 ULONG cf = WinAddAtom(WinQuerySystemAtomTable(), mime.toLocal8Bit());
1288 if (!cf) {
1289#ifndef QT_NO_DEBUG
1290 qWarning("QPMMime: WinAddAtom failed with 0x%lX",
1291 WinGetLastError(NULLHANDLE));
1292#endif
1293 return 0;
1294 }
1295
1296 return cf;
1297}
1298
1299/*!
1300 Unregisters the MIME type identified by \a id which was previously
1301 registered with registerMimeType().
1302*/
1303// static
1304void QPMMime::unregisterMimeType(ULONG mimeId)
1305{
1306 WinDeleteAtom(WinQuerySystemAtomTable(), mimeId);
1307}
1308
1309/*!
1310 Returns a list of all currently defined QPMMime objects.
1311*/
1312// static
1313QList<QPMMime*> QPMMime::all()
1314{
1315 return theMimeList()->mimes();
1316}
1317
1318/*!
1319 Allocates a block of shared memory of the given size and returns the address
1320 of this block. This memory block may be then filled with data and returned
1321 by convertFromMimeData() as the value of the CFI_POINTER type.
1322*/
1323// static
1324ULONG QPMMime::allocateMemory(size_t size)
1325{
1326 if (size == 0)
1327 return 0;
1328
1329 ULONG data = 0;
1330
1331 // allocate giveable memory for the array
1332 APIRET arc = DosAllocSharedMem((PVOID *)&data, NULL, size,
1333 PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE);
1334 if (arc != NO_ERROR) {
1335#ifndef QT_NO_DEBUG
1336 qWarning("QPMMime::allocateMemory: DosAllocSharedMem failed with %lu", arc);
1337#endif
1338 return 0;
1339 }
1340
1341 return data;
1342}
1343
1344/*!
1345 Frees memory allocated by allocateMemory(). Normally, not used because the
1346 CFI_POINTER memory blocks are owned by the system after
1347 convertFromMimeData() returns.
1348*/
1349// static
1350void QPMMime::freeMemory(ULONG addr)
1351{
1352 DosFreeMem((PVOID)addr);
1353}
1354
1355/*!
1356 \fn QList<MimeCFPair> QPMMime::formatsForMimeData(const QMimeData *mimeData) const
1357
1358 Returns a list of ULONG values representing the different OS/2 PM
1359 clipboard formats that can be provided for the \a mimeData, in order of
1360 precedence (the most suitable format goes first), or an empty list if
1361 neither of the mime types provided by \a mimeData is supported by this
1362 converter. Note that each item in the returned list is actually a pair
1363 consisting of the mime type name and the corresponding format identifier.
1364
1365 All subclasses must reimplement this pure virtual function.
1366*/
1367
1368/*!
1369 \fn bool QPMMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1370 ULONG &flags, ULONG *data) const
1371
1372 Converts the \a mimeData to the specified \a format.
1373
1374 If \a data is not NULL, a handle to the converted data should be then placed
1375 in a variable pointed to by \a data and with the necessary flags describing
1376 the handle returned in the \a flags variable.
1377
1378 The following flags describing the data storage type are recognized:
1379
1380 \table
1381 \row \o \c CFI_POINTER \o \a data is a pointer to a block of memory
1382 allocated with QPMMime::allocateMemory()
1383 \row \o \c CFI_HANDLE \o \a data is a handle to the appropriate
1384 PM resource
1385 \endtable
1386
1387 If \a data is NULL then a delayed conversion is requested by the caller.
1388 The implementation should return the appropriate flags in the \a flags
1389 variable and may perform the real data conversion later when this method is
1390 called again with \a data being non-NULL.
1391
1392 Return true if the conversion was successful.
1393
1394 All subclasses must reimplement this pure virtual function.
1395*/
1396
1397/*!
1398 \fn QList<MimeCFPair> QPMMime::mimesForFormats(const QList<ULONG> &formats) const
1399
1400 Returns a list of mime types that will be created form the specified \a list
1401 of \a formats, in order of precedence (the most suitable mime type comes
1402 first), or an empty list if neither of the \a formats is supported by this
1403 converter. Note that each item in the returned list is actually a pair
1404 consisting of the mime type name and the corresponding format identifier.
1405
1406 All subclasses must reimplement this pure virtual function.
1407*/
1408
1409/*!
1410 \fn QVariant QPMMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
1411 const QString &mimeType,
1412 QVariant::Type preferredType) const
1413
1414 Returns a QVariant containing the converted from the \a data in the
1415 specified \a format with the given \a flags to the requested \a mimeType. If
1416 possible the QVariant should be of the \a preferredType to avoid needless
1417 conversions.
1418
1419 All subclasses must reimplement this pure virtual function.
1420*/
1421
1422// @todo add DnD interfaces docs
1423
1424// static
1425QList<QPMMime::Match> QPMMime::allConvertersFromFormats(const QList<ULONG> &formats)
1426{
1427 QList<Match> matches;
1428
1429 QList<QPMMime*> mimes = theMimeList()->mimes();
1430 foreach(QPMMime *mime, mimes) {
1431 QList<MimeCFPair> fmts = mime->mimesForFormats(formats);
1432 int priority = 0;
1433 foreach (MimeCFPair fmt, fmts) {
1434 ++priority;
1435 QList<Match>::iterator it = matches.begin();
1436 for (; it != matches.end(); ++it) {
1437 Match &match = *it;
1438 if (match.mime == fmt.mime) {
1439 // replace if priority is higher, ignore otherwise
1440 if (priority < match.priority) {
1441 match.converter = mime;
1442 match.format = fmt.format;
1443 match.priority = priority;
1444 }
1445 break;
1446 }
1447 }
1448 if (it == matches.end()) {
1449 matches += Match(mime, fmt.mime, fmt.format, priority);
1450 }
1451 }
1452 }
1453
1454 return matches;
1455}
1456
1457// static
1458QList<QPMMime::Match> QPMMime::allConvertersFromMimeData(const QMimeData *mimeData)
1459{
1460 QList<Match> matches;
1461
1462 QList<QPMMime*> mimes = theMimeList()->mimes();
1463 foreach(QPMMime *mime, mimes) {
1464 QList<MimeCFPair> fmts = mime->formatsForMimeData(mimeData);
1465 int priority = 0;
1466 foreach (MimeCFPair fmt, fmts) {
1467 ++priority;
1468 QList<Match>::iterator it = matches.begin();
1469 for (; it != matches.end(); ++it) {
1470 Match &match = *it;
1471 if (mime == mimes.last()) { // QPMMimeAnyMime?
1472 if (match.mime == fmt.mime){
1473 // we assume that specialized converters (that come
1474 // first) provide a more precise conversion than
1475 // QPMMimeAnyMime and don't let it get into the list in
1476 // order to avoid unnecessary duplicate representations
1477 break;
1478 }
1479 }
1480 if (match.format == fmt.format) {
1481 // replace if priority is higher, ignore otherwise
1482 if (priority < match.priority) {
1483 match.converter = mime;
1484 match.mime = fmt.mime;
1485 match.priority = priority;
1486 }
1487 break;
1488 }
1489 }
1490 if (it == matches.end()) {
1491 matches += Match(mime, fmt.mime, fmt.format, priority);
1492 }
1493 }
1494 }
1495
1496 return matches;
1497}
1498
1499QString QPMMime::formatName(ULONG format)
1500{
1501 QString name;
1502 HATOMTBL tbl = WinQuerySystemAtomTable();
1503 if (tbl != NULLHANDLE) {
1504 ULONG len = WinQueryAtomLength(tbl, format);
1505 QByteArray atom(len, '\0');
1506 WinQueryAtomName(tbl, format, atom.data(), atom.size() + 1);
1507 name = QString::fromLocal8Bit(atom);
1508 }
1509 return name;
1510}
1511
1512#if !defined(QT_NO_DRAGANDDROP)
1513
1514/*!
1515 Returns a string represented by \a hstr.
1516*/
1517// static
1518QByteArray QPMMime::queryHSTR(HSTR hstr)
1519{
1520 QByteArray str;
1521 ULONG len = DrgQueryStrNameLen(hstr);
1522 if (len) {
1523 str.resize(len);
1524 DrgQueryStrName(hstr, str.size() + 1 /* \0 */, str.data());
1525 }
1526 return str;
1527}
1528
1529/*!
1530 Returns a string that is a concatenation of \c hstrContainerName and
1531 \c hstrSourceName fileds of the given \a item structure.
1532*/
1533// static
1534QByteArray QPMMime::querySourceNameFull(DRAGITEM *item)
1535{
1536 QByteArray fullName;
1537 if (!item)
1538 return fullName;
1539
1540 ULONG pathLen = DrgQueryStrNameLen(item->hstrContainerName);
1541 ULONG nameLen = DrgQueryStrNameLen(item->hstrSourceName);
1542 if (!pathLen || !nameLen)
1543 return fullName;
1544
1545 fullName.resize(pathLen + nameLen);
1546 DrgQueryStrName(item->hstrContainerName, pathLen + 1, fullName.data());
1547 DrgQueryStrName(item->hstrSourceName, nameLen + 1, fullName.data() + pathLen);
1548
1549 return fullName;
1550}
1551
1552/*! \internal
1553
1554 Checks that the given drag \a item supports the DRM_OS2FILE rendering
1555 mechanism and can be rendered by a target w/o involving the source (i.e.,
1556 DRM_OS2FILE is the first supported format and a valid file name with full
1557 path is provided). If the function returns TRUE, \a fullName (if not NULL)
1558 will be assigned the item's full source file name (composed from
1559 \c hstrContainerName and \c hstrSourceName fields).
1560 */
1561// static
1562bool QPMMime::canTargetRenderAsOS2File(DRAGITEM *item, QByteArray *fullName /*= 0*/)
1563{
1564 if ( !item )
1565 return false;
1566
1567 if (item->fsControl & (DC_PREPARE | DC_PREPAREITEM))
1568 return false;
1569
1570 {
1571 // DrgVerifyNativeRMF doesn't work on my system (ECS 1.2.1 GA):
1572 // it always returns FALSE regardless of arguments. Use simplified
1573 // hstrRMF parsing to determine whether DRM_OS2FILE is the native
1574 // mechanism or not (i.e. "^\s*[\(<]\s*DRM_OS2FILE\s*,.*").
1575
1576 QByteArray rmf = queryHSTR(item->hstrRMF);
1577 bool ok = false;
1578 int i = rmf.indexOf("DRM_OS2FILE");
1579 if (i >= 1) {
1580 for (int j = i - 1; j >= 0; --j) {
1581 char ch = rmf[j];
1582 if (ch == ' ')
1583 continue;
1584 if (ch == '<' || ch == '(') {
1585 if (ok)
1586 return false;
1587 ok = true;
1588 } else {
1589 return false;
1590 }
1591 }
1592 }
1593 if (ok) {
1594 ok = false;
1595 int drmLen = strlen("DRM_OS2FILE");
1596 for (int j = i + drmLen; j < (int) rmf.size(); ++j) {
1597 char ch = rmf[j];
1598 if (ch == ' ')
1599 continue;
1600 if (ch == ',') {
1601 ok = true;
1602 break;
1603 }
1604 return false;
1605 }
1606 }
1607 if (!ok)
1608 return false;
1609 }
1610
1611 QByteArray srcFullName = querySourceNameFull(item);
1612 if (srcFullName.isEmpty())
1613 return false;
1614
1615 QByteArray srcFullName2(srcFullName.size(), '\0');
1616 APIRET rc = DosQueryPathInfo(srcFullName, FIL_QUERYFULLNAME,
1617 srcFullName2.data(), srcFullName2.size());
1618 if (rc != 0)
1619 return false;
1620
1621 QString s1 = QFile::decodeName(srcFullName);
1622 QString s2 = QFile::decodeName(srcFullName2);
1623
1624 if (s1.compare(s2, Qt::CaseInsensitive) != 0)
1625 return false;
1626
1627 if (fullName)
1628 *fullName = srcFullName;
1629 return true;
1630}
1631
1632/*! \internal
1633
1634 Parses the given \a rmfs list (full rendering mechanism/format specification)
1635 and builds a \a list of mechanism branches. Each mechanism branch is also a
1636 list, where the first item is the mechahism name and all subsequent items are
1637 formats supported by this mechanism. Returns false if fails to parse \a rmf.
1638
1639 \note The method clears the given \a list variable before proceeding.
1640*/
1641// static
1642bool QPMMime::parseRMFs(HSTR rmfs, QList<QByteArrayList> &list)
1643{
1644 // The format of the RMF list is "elem {,elem,elem...}"
1645 // where elem is "(mechanism{,mechanism...}) x (format{,format...})"
1646 // or "<mechanism,format>".
1647 // We use a simple FSM to parse it. In terms of FSM, the format is:
1648 //
1649 // STRT ( BCM m CMCH echanism CMCH , NCM m CMCH echanism CMCH ) ECM x
1650 // SCMF ( BCF f CFCH ormat CFCH , NCF f CFCH ormat CFCH ) ECF , STRT
1651 // STRT < BP m PMCH echanism PMCH , SPMF f PFCH ormat PFCH > EP , STRT
1652
1653 QByteArray str = queryHSTR(rmfs);
1654 uint len = str.length();
1655
1656 enum {
1657 // states
1658 STRT = 0, BCM, CMCH, NCM, ECM, SCMF, BCF, CFCH, NCF, ECF,
1659 BP, PMCH, SPMF, PFCH, EP,
1660 STATES_COUNT,
1661 // pseudo states
1662 Err, Skip,
1663 // inputs
1664 obr = 0, cbr, xx, lt, gt, cm, any, ws,
1665 INPUTS_COUNT,
1666 };
1667
1668 static const char Chars[] = { '(', ')', 'x', 'X', '<', '>', ',', ' ', 0 };
1669 static const char Inputs[] = { obr, cbr, xx, xx, lt, gt, cm, ws };
1670 static const uchar Fsm [STATES_COUNT] [INPUTS_COUNT] = {
1671 /* 0 obr 1 cbr 2 xx 3 lt 4 gt 5 cm 6 any 7 ws */
1672/* STRT 0 */ { BCM, Err, Err, BP, Err, Err, Err, Skip },
1673/* BCM 1 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1674/* CMCH 2 */ { Err, ECM, CMCH, Err, Err, NCM, CMCH, CMCH },
1675/* NCM 3 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
1676/* ECM 4 */ { Err, Err, SCMF, Err, Err, Err, Err, Skip },
1677/* SCMF 5 */ { BCF, Err, Err, Err, Err, Err, Err, Skip },
1678/* BCF 6 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1679/* CFCH 7 */ { Err, ECF, CFCH, Err, Err, NCF, CFCH, CFCH },
1680/* NCF 8 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
1681/* ECF 9 */ { Err, Err, Err, Err, Err, STRT, Err, Skip },
1682/* BP 10 */ { Err, Err, Err, Err, Err, Err, PMCH, Skip },
1683/* PMCH 11 */ { Err, Err, PMCH, Err, Err, SPMF, PMCH, PMCH },
1684/* SPMF 12 */ { Err, Err, Err, Err, Err, Err, PFCH, Skip },
1685/* PFCH 13 */ { Err, Err, PFCH, Err, EP, Err, PFCH, PFCH },
1686/* EP 14 */ { Err, Err, Err, Err, Err, STRT, Err, Skip }
1687 };
1688
1689 list.clear();
1690
1691 QList<QByteArrayList*> refList;
1692
1693 QByteArray buf;
1694 QList<QByteArrayList>::iterator rmf;
1695
1696 uint state = STRT;
1697 uint start = 0, end = 0, space = 0;
1698
1699 for (uint i = 0; i < len && state != Err ; ++i) {
1700 char ch = str[i];
1701 char *p = strchr(Chars, ch);
1702 uint input = p ? Inputs[p - Chars] : any;
1703 uint newState = Fsm[state][input];
1704 switch (newState) {
1705 case Skip:
1706 continue;
1707 case CMCH:
1708 case CFCH:
1709 case PMCH:
1710 case PFCH:
1711 if (state != newState)
1712 start = end = i;
1713 ++end;
1714 // accumulate trailing space for truncation
1715 if (input == ws) ++space;
1716 else space = 0;
1717 break;
1718 case NCM:
1719 case ECM:
1720 case SPMF:
1721 buf = QByteArray(str.data() + start, end - start - space);
1722 // find the mechanism branch in the output list
1723 for (rmf = list.begin(); rmf != list.end(); ++rmf) {
1724 if (rmf->first() == buf)
1725 break;
1726 }
1727 if (rmf == list.end()) {
1728 // append to the output list if not found
1729 QByteArrayList newRmf;
1730 newRmf.append(buf);
1731 rmf = list.insert(list.end(), newRmf);
1732 }
1733 // store a refecence in the helper list for making a cross product
1734 refList.append(&*rmf);
1735 start = end = 0;
1736 break;
1737 case NCF:
1738 case ECF:
1739 case EP:
1740 buf = QByteArray(str.data() + start, end - start - space);
1741 // make a cross product with all current mechanisms
1742 foreach(QByteArrayList *rmfRef, refList)
1743 rmfRef->append(buf);
1744 if (newState != NCF)
1745 refList.clear();
1746 start = end = 0;
1747 break;
1748 default:
1749 break;
1750 }
1751 state = newState;
1752 }
1753
1754 return state == ECF || state == EP;
1755}
1756
1757/*! \internal
1758
1759 Splits the given \a rmf (rendering mechanism/format pair) to a \a mechanism
1760 and a \a format string. Returns FALSE if fails to parse \a rmf.
1761 */
1762// static
1763bool QPMMime::parseRMF(HSTR rmf, QByteArray &mechanism, QByteArray &format)
1764{
1765 QList<QByteArrayList> list;
1766 if (!parseRMFs(rmf, list))
1767 return false;
1768
1769 if (list.count() != 1 || list.first().count() != 2)
1770 return false;
1771
1772 QByteArrayList first = list.first();
1773 mechanism = first.at(0);
1774 format = first.at(1);
1775
1776 return true;
1777}
1778
1779/*! \internal */
1780// static
1781QPMMime::DefaultDragWorker *QPMMime::defaultCoopDragWorker()
1782{
1783 static DefaultDragWorker defCoopDragWorker(false /* exclusive */);
1784 return &defCoopDragWorker;
1785}
1786
1787// static
1788/*! \internal */
1789QPMMime::DefaultDragWorker *QPMMime::defaultExclDragWorker()
1790{
1791 static DefaultDragWorker defExclDragWorker(true /* exclusive */);
1792 return &defExclDragWorker;
1793}
1794
1795/*! \internal */
1796// static
1797QPMMime::DefaultDropWorker *QPMMime::defaultDropWorker()
1798{
1799 static DefaultDropWorker defaultDropWorker;
1800 return &defaultDropWorker;
1801}
1802
1803#endif // !QT_NO_DRAGANDDROP
1804
1805////////////////////////////////////////////////////////////////////////////////
1806
1807class QPMMimeText : public QPMMime
1808{
1809public:
1810 QPMMimeText();
1811 ~QPMMimeText();
1812
1813 // for converting from Qt
1814 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
1815 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
1816 ULONG &flags, ULONG *data) const;
1817
1818 // for converting to Qt
1819 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
1820 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
1821 const QString &mimeType,
1822 QVariant::Type preferredType) const;
1823
1824#if !defined(QT_NO_DRAGANDDROP)
1825
1826 // Direct Manipulation (DND) converter interface
1827 DragWorker *dragWorkerFor(const QString &mimeType, QMimeData *mimeData);
1828 DropWorker *dropWorkerFor(DRAGINFO *info);
1829
1830 class NativeFileDrag : public DragWorker, public QPMObjectWindow
1831 {
1832 public:
1833 // DragWorker interface
1834 bool cleanup(bool isCancelled) { return true; } // always disallow Move
1835 bool isExclusive() const { return true; }
1836 ULONG itemCount() const { return 0; } // super exclusive
1837 HWND hwnd() const { return QPMObjectWindow::hwnd(); }
1838 DRAGINFO *createDragInfo(const QString &targetName, USHORT supportedOps);
1839 // QPMObjectWindow interface (dummy implementation, we don't need to interact)
1840 MRESULT message(ULONG msg, MPARAM mp1, MPARAM mp2) { return 0; }
1841 };
1842
1843 class NativeFileDrop : public DropWorker
1844 {
1845 public:
1846 // DropWorker interface
1847 bool isExclusive() const { return true; }
1848 bool hasFormat(const QString &mimeType) const;
1849 QStringList formats() const;
1850 QVariant retrieveData(const QString &mimeType,
1851 QVariant::Type preferredType) const;
1852 };
1853
1854 class TextDragProvider : public DefaultDragWorker::Provider
1855 {
1856 public:
1857 TextDragProvider() : exclusive(false) {}
1858 bool exclusive;
1859 // Provider interface
1860 QString format(const char *drf) const;
1861 bool provide(const char *drf, const QByteArray &allData,
1862 ULONG itemIndex, QByteArray &itemData);
1863 void fileType(const char *drf, const char *&type, const char *&ext);
1864 };
1865
1866 class TextDropProvider : public DefaultDropWorker::Provider
1867 {
1868 public:
1869 // Provider interface
1870 const char *drf(const QString &mimeType) const;
1871 bool provide(const QString &format, ULONG itemIndex,
1872 const QByteArray &itemData, QByteArray &allData);
1873 };
1874
1875#endif // !QT_NO_DRAGANDDROP
1876
1877 const ULONG CF_TextUnicode;
1878 const ULONG CF_TextHtml;
1879
1880#if !defined(QT_NO_DRAGANDDROP)
1881 NativeFileDrag nativeFileDrag;
1882 NativeFileDrop nativeFileDrop;
1883 TextDragProvider textDragProvider;
1884 TextDropProvider textDropProvider;
1885#endif // !QT_NO_DRAGANDDROP
1886};
1887
1888QPMMimeText::QPMMimeText()
1889 // "text/unicode" is what Mozilla uses to for unicode
1890 : CF_TextUnicode (registerMimeType(QLatin1String("text/unicode")))
1891 // "text/html" is what Mozilla uses to for HTML
1892 , CF_TextHtml (registerMimeType(QLatin1String("text/html")))
1893{
1894}
1895
1896QPMMimeText::~QPMMimeText()
1897{
1898 unregisterMimeType(CF_TextHtml);
1899 unregisterMimeType(CF_TextUnicode);
1900}
1901
1902QList<QPMMime::MimeCFPair> QPMMimeText::formatsForMimeData(const QMimeData *mimeData) const
1903{
1904 QList<MimeCFPair> fmts;
1905 // prefer HTML as it's reacher
1906 if (mimeData->hasHtml())
1907 fmts << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
1908 // prefer unicode over local8Bit
1909 if (mimeData->hasText())
1910 fmts << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode)
1911 << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
1912 return fmts;
1913}
1914
1915// text/plain is defined as using CRLF, but so many programs don't,
1916// and programmers just look for '\n' in strings.
1917// OS/2 really needs CRLF, so we ensure it here.
1918bool QPMMimeText::convertFromMimeData(const QMimeData *mimeData, ULONG format,
1919 ULONG &flags, ULONG *data) const
1920{
1921 if (!mimeData->hasText() ||
1922 (format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml))
1923 return false;
1924
1925 flags = CFI_POINTER;
1926
1927 if (data == NULL)
1928 return true; // delayed rendering, nothing to do
1929
1930 QByteArray r;
1931
1932 if (format == CF_TEXT) {
1933 QByteArray str = mimeData->text().toLocal8Bit();
1934 // Anticipate required space for CRLFs at 1/40
1935 int maxsize = str.size()+str.size()/40+1;
1936 r.fill('\0', maxsize);
1937 char *o = r.data();
1938 const char *d = str.data();
1939 const int s = str.size();
1940 bool cr = false;
1941 int j = 0;
1942 for (int i = 0; i < s; i++) {
1943 char c = d[i];
1944 if (c == '\r')
1945 cr = true;
1946 else {
1947 if (c == '\n') {
1948 if (!cr)
1949 o[j++] = '\r';
1950 }
1951 cr = false;
1952 }
1953 o[j++] = c;
1954 if (j+1 >= maxsize) {
1955 maxsize += maxsize/4;
1956 r.resize(maxsize);
1957 o = r.data();
1958 }
1959 }
1960 if (j < r.size())
1961 o[j] = '\0';
1962 } else if (format == CF_TextUnicode || CF_TextHtml) {
1963 QString str = format == CF_TextUnicode ?
1964 mimeData->text() : mimeData->html();
1965 const QChar *u = str.unicode();
1966 QString res;
1967 const int s = str.length();
1968 int maxsize = s + s/40 + 3;
1969 res.resize(maxsize);
1970 int ri = 0;
1971 bool cr = false;
1972 for (int i = 0; i < s; ++i) {
1973 if (*u == QLatin1Char('\r'))
1974 cr = true;
1975 else {
1976 if (*u == QLatin1Char('\n') && !cr)
1977 res[ri++] = QLatin1Char('\r');
1978 cr = false;
1979 }
1980 res[ri++] = *u;
1981 if (ri+3 >= maxsize) {
1982 maxsize += maxsize/4;
1983 res.resize(maxsize);
1984 }
1985 ++u;
1986 }
1987 res.truncate(ri);
1988 const int byteLength = res.length()*2;
1989 r.fill('\0', byteLength + 2);
1990 memcpy(r.data(), res.unicode(), byteLength);
1991 r[byteLength] = 0;
1992 r[byteLength+1] = 0;
1993 } else{
1994 return false;
1995 }
1996
1997 *data = QPMMime::allocateMemory(r.size());
1998 if (!*data)
1999 return false;
2000
2001 memcpy((void *)*data, r.data(), r.size());
2002 return true;
2003}
2004
2005QList<QPMMime::MimeCFPair> QPMMimeText::mimesForFormats(const QList<ULONG> &formats) const
2006{
2007 QList<MimeCFPair> mimes;
2008 // prefer HTML as it's reacher
2009 if (formats.contains(CF_TextHtml))
2010 mimes << MimeCFPair(QLatin1String("text/html"), CF_TextHtml);
2011 // prefer unicode over local8Bit
2012 if (formats.contains(CF_TextUnicode))
2013 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TextUnicode);
2014 if (formats.contains(CF_TEXT))
2015 mimes << MimeCFPair(QLatin1String("text/plain"), CF_TEXT);
2016 return mimes;
2017}
2018
2019QVariant QPMMimeText::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2020 const QString &mimeType,
2021 QVariant::Type preferredType) const
2022{
2023 QVariant ret;
2024
2025 if (!mimeType.startsWith(QLatin1String("text/plain")) &&
2026 !mimeType.startsWith(QLatin1String("text/html")))
2027 return ret;
2028 if ((format != CF_TEXT && format != CF_TextUnicode && format != CF_TextHtml) ||
2029 !(flags & CFI_POINTER) || !data)
2030 return ret;
2031
2032 QString str;
2033
2034 if (format == CF_TEXT) {
2035 const char *d = (const char *)data;
2036 QByteArray r("");
2037 if (*d) {
2038 const int s = qstrlen(d);
2039 r.fill('\0', s);
2040 char *o = r.data();
2041 int j = 0;
2042 for (int i = 0; i < s; i++) {
2043 char c = d[i];
2044 if (c != '\r')
2045 o[j++] = c;
2046 }
2047 }
2048 str = QString::fromLocal8Bit(r);
2049 } else if (format == CF_TextUnicode || CF_TextHtml) {
2050 str = QString::fromUtf16((const unsigned short *)data);
2051 str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
2052 }
2053
2054 if (preferredType == QVariant::String)
2055 ret = str;
2056 else
2057 ret = str.toUtf8();
2058
2059 return ret;
2060}
2061
2062#if !defined(QT_NO_DRAGANDDROP)
2063
2064DRAGINFO *QPMMimeText::NativeFileDrag::createDragInfo(const QString &targetName,
2065 USHORT supportedOps)
2066{
2067 Q_ASSERT(source());
2068 if (!source())
2069 return 0;
2070
2071 // obtain the list of files
2072 QList<QUrl> list;
2073 if (source()->hasUrls())
2074 list = source()->urls();
2075 ULONG itemCnt = list.count();
2076 Q_ASSERT(itemCnt);
2077 if (!itemCnt)
2078 return 0;
2079
2080 DEBUG(() << "QPMMimeText::NativeFileDrag: itemCnt" << itemCnt);
2081
2082 DRAGINFO *info = DrgAllocDraginfo(itemCnt);
2083 Q_ASSERT(info);
2084 if (!info)
2085 return 0;
2086
2087 bool ok = true;
2088 QList<QUrl>::iterator it = list.begin();
2089 for (ULONG i = 0; i < itemCnt; ++i, ++it) {
2090 DRAGITEM *item = DrgQueryDragitemPtr(info, i);
2091 Q_ASSERT(item);
2092 if (!item) {
2093 ok = false;
2094 break;
2095 }
2096
2097 QByteArray fileName = QFile::encodeName(QDir::convertSeparators(it->toLocalFile()));
2098
2099 int sep = fileName.lastIndexOf('\\');
2100 Q_ASSERT(sep > 0 && sep < fileName.length() - 1);
2101 if (sep <= 0 || sep >= fileName.length() - 1) {
2102 ok = false;
2103 break;
2104 }
2105
2106 item->hstrSourceName = DrgAddStrHandle(fileName.data() + sep + 1);
2107 fileName.truncate(sep + 1);
2108 item->hstrContainerName = DrgAddStrHandle(fileName);
2109
2110 DEBUG(() << "QPMMimeText::NativeFileDrag: item" << i
2111 << "dir" << queryHSTR(item->hstrContainerName)
2112 << "name" << queryHSTR(item->hstrSourceName));
2113
2114 item->hwndItem = hwnd();
2115 item->ulItemID = 0;
2116 item->hstrType = DrgAddStrHandle(DRT_UNKNOWN);
2117 item->hstrRMF = DrgAddStrHandle("<DRM_OS2FILE,DRF_UNKNOWN>");
2118 item->hstrTargetName = 0;
2119 item->cxOffset = 0;
2120 item->cyOffset = 0;
2121 item->fsControl = 0;
2122 item->fsSupportedOps = supportedOps;
2123 }
2124
2125 if (!ok) {
2126 DrgFreeDraginfo(info);
2127 info = 0;
2128 }
2129
2130 return info;
2131}
2132
2133bool QPMMimeText::NativeFileDrop::hasFormat(const QString &mimeType) const
2134{
2135 return mimeType == QLatin1String("text/uri-list");
2136}
2137
2138QStringList QPMMimeText::NativeFileDrop::formats() const
2139{
2140 QStringList mimes;
2141 mimes << QLatin1String("text/uri-list");
2142 return mimes;
2143}
2144
2145QVariant QPMMimeText::NativeFileDrop::retrieveData(const QString &mimeType,
2146 QVariant::Type preferredType) const
2147{
2148 QVariant result;
2149
2150 Q_ASSERT(info());
2151 if (!info())
2152 return result;
2153
2154 ULONG itemCount = DrgQueryDragitemCount(info());
2155 Q_ASSERT(itemCount);
2156 if (!itemCount)
2157 return result;
2158
2159 // sanity check
2160 if (mimeType != QLatin1String("text/uri-list"))
2161 return result;
2162
2163 QList<QVariant> urls;
2164
2165 for (ULONG i = 0; i < itemCount; ++i) {
2166 DRAGITEM *item = DrgQueryDragitemPtr(info(), i);
2167 Q_ASSERT(item);
2168 QByteArray fullName;
2169 if (!item || !canTargetRenderAsOS2File(item, &fullName))
2170 return result;
2171 QString fn = QFile::decodeName(fullName);
2172 urls += QUrl::fromLocalFile(fn).toString();
2173 }
2174
2175 if (preferredType == QVariant::Url && urls.size() == 1)
2176 result = urls.at(0);
2177 else if (!urls.isEmpty())
2178 result = urls;
2179
2180 return result;
2181}
2182
2183QString QPMMimeText::TextDragProvider::format(const char *drf) const
2184{
2185 QString result;
2186
2187 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2188 if (exclusive)
2189 result = QLatin1String("text/uri-list");
2190 else
2191 result = QLatin1String("text/plain");
2192 }
2193 return result;
2194}
2195
2196bool QPMMimeText::TextDragProvider::provide(const char *drf,
2197 const QByteArray &allData,
2198 ULONG itemIndex,
2199 QByteArray &itemData)
2200{
2201 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2202 if (exclusive) {
2203 // locate the required item
2204 int dataSize = allData.size();
2205 if (!dataSize)
2206 return false;
2207 int begin = 0, end = 0, next = 0;
2208 do {
2209 begin = next;
2210 end = allData.indexOf('\r', begin);
2211 if (end >= 0) {
2212 next = end + 1;
2213 if (next < dataSize && allData[next] == '\n')
2214 ++next;
2215 } else {
2216 end = allData.indexOf('\n', begin);
2217 if (end >= 0)
2218 next = end + 1;
2219 }
2220 } while (itemIndex-- && end >= 0 && next < dataSize);
2221 int urlLen = end - begin;
2222 if (urlLen <= 0)
2223 return false;
2224 QUrl url = QUrl(QString::fromUtf8(allData.data() + begin, urlLen));
2225 if (!url.isValid())
2226 return false;
2227 itemData = url.toEncoded();
2228 } else {
2229 itemData = QString::fromUtf8(allData).toLocal8Bit();
2230 }
2231 return true;
2232 }
2233 return false;
2234}
2235
2236void QPMMimeText::TextDragProvider::fileType(const char *drf, const char *&type,
2237 const char *&ext)
2238{
2239 if (qstrcmp(drf, "DRF_TEXT") == 0) {
2240 if (exclusive) {
2241 type = "UniformResourceLocator";
2242 // no extension for URLs
2243 } else {
2244 type = DRT_TEXT;
2245 ext = "txt";
2246 }
2247 }
2248};
2249
2250const char *QPMMimeText::TextDropProvider::drf(const QString &mimeType) const
2251{
2252 // sanity check
2253 if (mimeType == QLatin1String("text/plain") ||
2254 mimeType == QLatin1String("text/uri-list"))
2255 return "DRF_TEXT";
2256 return 0;
2257}
2258
2259bool QPMMimeText::TextDropProvider::provide(const QString &mimeType,
2260 ULONG itemIndex,
2261 const QByteArray &itemData,
2262 QByteArray &allData)
2263{
2264 if (mimeType == QLatin1String("text/plain")) {
2265 allData = QString::fromLocal8Bit(itemData).toUtf8();
2266 return true;
2267 }
2268
2269 if (mimeType == QLatin1String("text/uri-list")) {
2270 QUrl url = QUrl::fromEncoded(itemData);
2271 if (!url.isValid())
2272 return false;
2273 // append the URL to the list
2274 allData += url.toString().toUtf8();
2275 allData += "\r\n";
2276 return true;
2277 }
2278
2279 return false;
2280}
2281
2282QPMMime::DragWorker *QPMMimeText::dragWorkerFor(const QString &mimeType,
2283 QMimeData *mimeData)
2284{
2285 if (mimeType == QLatin1String("text/plain")) {
2286 // add a cooperative provider
2287 textDragProvider.exclusive = false;
2288 DefaultDragWorker *defWorker = defaultCoopDragWorker();
2289 defWorker->addProvider("DRF_TEXT", &textDragProvider);
2290 return defWorker;
2291 }
2292
2293 if (mimeType == QLatin1String("text/uri-list")) {
2294 // see what kind of items text/uri-list represents
2295 QList<QUrl> urls = mimeData->urls();
2296 int fileCnt = 0;
2297 foreach (const QUrl &url, urls) {
2298 if (url.scheme() == QLatin1String("file"))
2299 ++fileCnt;
2300 }
2301 if (fileCnt && fileCnt == urls.count()) {
2302 // all items are local files, return an exclusive file drag worker
2303 return &nativeFileDrag;
2304 }
2305 if (urls.count() && !fileCnt) {
2306 // all items are non-files, add an exclusive provider for the
2307 // specified item count
2308 textDragProvider.exclusive = true;
2309 DefaultDragWorker *defWorker = defaultExclDragWorker();
2310 bool ok = defWorker->addProvider("DRF_TEXT", &textDragProvider,
2311 urls.count());
2312 return ok ? defWorker : 0;
2313 }
2314 // if items are mixed, we return NULL to fallback to QPMMimeAnyMime
2315 }
2316
2317 return 0;
2318}
2319
2320QPMMime::DropWorker *QPMMimeText::dropWorkerFor(DRAGINFO *info)
2321{
2322 ULONG itemCount = DrgQueryDragitemCount(info);
2323 Q_ASSERT(itemCount);
2324 if (!itemCount)
2325 return 0;
2326
2327 if (itemCount == 1) {
2328 DRAGITEM *item = DrgQueryDragitemPtr(info, 0);
2329 Q_ASSERT(item);
2330 if (!item)
2331 return 0;
2332 // proceed only if the target cannot render DRM_OS2FILE on its own
2333 // and if the item type is not "UniformResourceLocator" (which will be
2334 // processed below)
2335 if (!canTargetRenderAsOS2File(item) &&
2336 !DrgVerifyType(item, "UniformResourceLocator")) {
2337 DefaultDropWorker *defWorker = defaultDropWorker();
2338 // check that we support one of DRMs and the format is DRF_TEXT
2339 if (defWorker->canRender(item, "DRF_TEXT")) {
2340 // add a cooperative provider (can coexist with others)
2341 defWorker->addProvider(QLatin1String("text/plain"),
2342 &textDropProvider);
2343 return defWorker;
2344 }
2345 return 0;
2346 }
2347 }
2348
2349 // Either the target can render DRM_OS2FILE on its own (so it's a valid
2350 // file/directory name), or it's an "UniformResourceLocator", or there is
2351 // more than one drag item. Check that all items are of either one type
2352 // or another. If so, we can represent them as 'text/uri-list'.
2353 bool allAreFiles = true;
2354 bool allAreURLs = true;
2355 DefaultDropWorker *defWorker = defaultDropWorker();
2356 for (ULONG i = 0; i < itemCount; ++i) {
2357 DRAGITEM *item = DrgQueryDragitemPtr(info, i);
2358 Q_ASSERT(item);
2359 if (!item)
2360 return 0;
2361 if (allAreFiles)
2362 allAreFiles &= canTargetRenderAsOS2File(item);
2363 if (allAreURLs)
2364 allAreURLs &= DrgVerifyType(item, "UniformResourceLocator") &&
2365 defWorker->canRender(item, "DRF_TEXT");
2366 if (!allAreFiles && !allAreURLs)
2367 return 0;
2368 }
2369
2370 if (allAreFiles) {
2371 // return an exclusive drop worker
2372 return &nativeFileDrop;
2373 }
2374
2375 // add an exclusive provider (can neither coexist with other workers
2376 // or providers)
2377 bool ok = defWorker->addExclusiveProvider(QLatin1String("text/uri-list"),
2378 &textDropProvider);
2379 return ok ? defWorker : 0;
2380}
2381
2382#endif // !QT_NO_DRAGANDDROP
2383
2384////////////////////////////////////////////////////////////////////////////////
2385
2386class QPMMimeImage : public QPMMime
2387{
2388public:
2389 QPMMimeImage();
2390
2391 // for converting from Qt
2392 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2393 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2394 ULONG &flags, ULONG *data) const;
2395 // for converting to Qt
2396 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2397 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2398 const QString &mimeType,
2399 QVariant::Type preferredType) const;
2400};
2401
2402QPMMimeImage::QPMMimeImage()
2403{
2404}
2405
2406QList<QPMMime::MimeCFPair> QPMMimeImage::formatsForMimeData(const QMimeData *mimeData) const
2407{
2408 QList<MimeCFPair> fmts;
2409 if (mimeData->hasImage()) {
2410 // "application/x-qt-image" seems to be used as a single name for all
2411 // "image/xxx" types in Qt
2412 fmts << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2413 }
2414 return fmts;
2415}
2416
2417bool QPMMimeImage::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2418 ULONG &flags, ULONG *data) const
2419{
2420 if (!mimeData->hasImage() || format != CF_BITMAP)
2421 return false;
2422
2423 flags = CFI_HANDLE;
2424
2425 if (data == NULL)
2426 return true; // delayed rendering, nothing to do
2427
2428 QImage img = qvariant_cast<QImage>(mimeData->imageData());
2429 if (img.isNull())
2430 return false;
2431
2432 QPixmap pm = QPixmap::fromImage(img);
2433 if (pm.isNull())
2434 return false;
2435
2436 HBITMAP bmp = pm.toPmHBITMAP(0, true);
2437 if (bmp == NULLHANDLE)
2438 return false;
2439
2440 *data = bmp;
2441 return true;
2442}
2443
2444QList<QPMMime::MimeCFPair> QPMMimeImage::mimesForFormats(const QList<ULONG> &formats) const
2445{
2446 QList<MimeCFPair> mimes;
2447 if (formats.contains(CF_BITMAP))
2448 mimes << MimeCFPair(QLatin1String("application/x-qt-image"), CF_BITMAP);
2449 return mimes;
2450}
2451
2452QVariant QPMMimeImage::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2453 const QString &mimeType,
2454 QVariant::Type preferredType) const
2455{
2456 Q_UNUSED(preferredType);
2457
2458 QVariant ret;
2459
2460 if (mimeType != QLatin1String("application/x-qt-image"))
2461 return ret;
2462 if (format != CF_BITMAP || !(flags & CFI_HANDLE) || !data)
2463 return ret;
2464
2465 QPixmap pm = QPixmap::fromPmHBITMAP((HBITMAP)data);
2466 if (pm.isNull())
2467 return ret;
2468
2469 ret = pm.toImage();
2470 return ret;
2471}
2472
2473////////////////////////////////////////////////////////////////////////////////
2474
2475class QPMMimeAnyMime : public QPMMime
2476{
2477public:
2478 QPMMimeAnyMime();
2479 ~QPMMimeAnyMime();
2480
2481 // for converting from Qt
2482 QList<MimeCFPair> formatsForMimeData(const QMimeData *mimeData) const;
2483 bool convertFromMimeData(const QMimeData *mimeData, ULONG format,
2484 ULONG &flags, ULONG *data) const;
2485 // for converting to Qt
2486 QList<MimeCFPair> mimesForFormats(const QList<ULONG> &formats) const;
2487 QVariant convertFromFormat(ULONG format, ULONG flags, ULONG data,
2488 const QString &mimeType,
2489 QVariant::Type preferredType) const;
2490
2491private:
2492 ULONG registerMimeType(const QString &mime) const;
2493 QString registerFormat(ULONG format) const;
2494
2495 mutable QMap<QString, ULONG> cfMap;
2496 mutable QMap<ULONG, QString> mimeMap;
2497
2498 static QStringList ianaTypes;
2499 static QString mimePrefix;
2500 static QString customPrefix;
2501};
2502
2503// static
2504QStringList QPMMimeAnyMime::ianaTypes;
2505QString QPMMimeAnyMime::mimePrefix;
2506QString QPMMimeAnyMime::customPrefix;
2507
2508QPMMimeAnyMime::QPMMimeAnyMime()
2509{
2510 //MIME Media-Types
2511 if (!ianaTypes.size()) {
2512 ianaTypes.append(QLatin1String("application/"));
2513 ianaTypes.append(QLatin1String("audio/"));
2514 ianaTypes.append(QLatin1String("example/"));
2515 ianaTypes.append(QLatin1String("image/"));
2516 ianaTypes.append(QLatin1String("message/"));
2517 ianaTypes.append(QLatin1String("model/"));
2518 ianaTypes.append(QLatin1String("multipart/"));
2519 ianaTypes.append(QLatin1String("text/"));
2520 ianaTypes.append(QLatin1String("video/"));
2521
2522 mimePrefix = QLatin1String("x-mime:");
2523 customPrefix = QLatin1String("application/x-qt-pm-mime;value=\"");
2524 }
2525}
2526
2527QPMMimeAnyMime::~QPMMimeAnyMime()
2528{
2529 foreach(ULONG cf, cfMap.values())
2530 unregisterMimeType(cf);
2531}
2532
2533QList<QPMMime::MimeCFPair> QPMMimeAnyMime::formatsForMimeData(const QMimeData *mimeData) const
2534{
2535 QList<MimeCFPair> fmts;
2536
2537 QStringList mimes = QInternalMimeData::formatsHelper(mimeData);
2538 foreach (QString mime, mimes) {
2539 ULONG cf = cfMap.value(mime);
2540 if (!cf)
2541 cf = registerMimeType(mime);
2542 if (cf)
2543 fmts << MimeCFPair(mime, cf);
2544 }
2545
2546 return fmts;
2547}
2548
2549bool QPMMimeAnyMime::convertFromMimeData(const QMimeData *mimeData, ULONG format,
2550 ULONG &flags, ULONG *data) const
2551{
2552 QString mime = mimeMap.value(format);
2553 if (mime.isNull())
2554 return false;
2555
2556 flags = CFI_POINTER;
2557
2558 if (data == NULL)
2559 return true; // delayed rendering, nothing to do
2560
2561 QByteArray r = QInternalMimeData::renderDataHelper(mime, mimeData);
2562 if (r.isNull())
2563 return false;
2564
2565 *data = QPMMime::allocateMemory(r.size() + sizeof(ULONG));
2566 if (!*data)
2567 return false;
2568
2569 *((ULONG *)(*data)) = r.size();
2570 memcpy((void *)(*data + sizeof(ULONG)), r.data(), r.size());
2571 return true;
2572}
2573
2574QList<QPMMime::MimeCFPair> QPMMimeAnyMime::mimesForFormats(const QList<ULONG> &formats) const
2575{
2576 QList<MimeCFPair> mimes;
2577
2578 foreach (ULONG format, formats) {
2579 QString mime = mimeMap.value(format);
2580 if (mime.isEmpty())
2581 mime = registerFormat(format);
2582 if (!mime.isEmpty())
2583 mimes << MimeCFPair(mime, format);
2584 }
2585
2586 return mimes;
2587}
2588
2589QVariant QPMMimeAnyMime::convertFromFormat(ULONG format, ULONG flags, ULONG data,
2590 const QString &mimeType,
2591 QVariant::Type preferredType) const
2592{
2593 Q_UNUSED(preferredType);
2594
2595 QVariant ret;
2596
2597 if (cfMap.value(mimeType) != format)
2598 return ret;
2599
2600 if (!(flags & CFI_POINTER) || !data)
2601 return ret;
2602
2603 // get the real block size (always rounded to the page boundary (4K))
2604 ULONG sz = ~0, fl = 0, arc;
2605 arc = DosQueryMem((PVOID)data, &sz, &fl);
2606 if (arc != NO_ERROR) {
2607#ifndef QT_NO_DEBUG
2608 qWarning("QPMMimeText::convertFromFormat: DosQueryMem failed with %lu", arc);
2609#endif
2610 return ret;
2611 }
2612 ULONG size = *((ULONG *)data);
2613 if (!size || size + sizeof(ULONG) > sz)
2614 return ret;
2615
2616 // it should be enough to return the data and let QMimeData do the rest.
2617 ret = QByteArray((const char *)(data + sizeof(ULONG)), size);
2618 return ret;
2619}
2620
2621ULONG QPMMimeAnyMime::registerMimeType(const QString &mime) const
2622{
2623 if (mime.isEmpty())
2624 return 0;
2625
2626 QString mimeToReg = mime;
2627
2628 bool ianaType = false;
2629 foreach(QString prefix, ianaTypes) {
2630 if (mime.startsWith(prefix)) {
2631 ianaType = true;
2632 break;
2633 }
2634 }
2635 if (!ianaType) {
2636 // prepend the non-standard type with the prefix that makes it comply
2637 // with the standard
2638 mimeToReg = customPrefix + mime + QLatin1Char('\"');
2639 }
2640
2641 mimeToReg = mimePrefix + mimeToReg;
2642 ULONG cf = QPMMime::registerMimeType(mimeToReg);
2643 if (cf) {
2644 cfMap[mime] = cf;
2645 mimeMap[cf] = mime;
2646 }
2647 return cf;
2648}
2649
2650QString QPMMimeAnyMime::registerFormat(ULONG format) const
2651{
2652 QString mime;
2653
2654 if (!format)
2655 return mime;
2656
2657 QString atomStr = formatName(format);
2658 if (atomStr.startsWith(mimePrefix)) {
2659 // the format represents the mime type we can recognize
2660 // increase the reference count
2661 ULONG cf = QPMMime::registerMimeType(atomStr);
2662 Q_ASSERT(cf == format);
2663 // extract the real mime type (w/o our prefix)
2664 mime = atomStr.mid(mimePrefix.size());
2665 if (!mime.isEmpty()) {
2666 cfMap[mime] = cf;
2667 mimeMap[cf] = mime;
2668 }
2669 }
2670 return mime;
2671}
2672
2673////////////////////////////////////////////////////////////////////////////////
2674
2675QPMMimeList::QPMMimeList()
2676 : initialized(false)
2677{
2678}
2679
2680QPMMimeList::~QPMMimeList()
2681{
2682 while (list.size())
2683 delete list.first();
2684}
2685
2686
2687void QPMMimeList::init()
2688{
2689 if (!initialized) {
2690 initialized = true;
2691 new QPMMimeAnyMime; // must be the first (used as a fallback)
2692 new QPMMimeImage;
2693 new QPMMimeText;
2694 }
2695}
2696
2697void QPMMimeList::addMime(QPMMime *mime)
2698{
2699 init();
2700 list.prepend(mime);
2701}
2702
2703void QPMMimeList::removeMime(QPMMime *mime)
2704{
2705 init();
2706 list.removeAll(mime);
2707}
2708
2709QList<QPMMime*> QPMMimeList::mimes()
2710{
2711 init();
2712 return list;
2713}
2714
2715QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.