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

Last change on this file was 1014, checked in by Dmitry A. Kuminov, 14 years ago

OS/2: Doc fixes.

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