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

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

gui: Fixed assertion shown when dragging the "Computer" item in the side bar of the file dialog.

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