source: trunk/src/kernel/qmime_pm.cpp@ 127

Last change on this file since 127 was 114, checked in by dmik, 19 years ago

Kernel: Updated QPMMime to use QPMObjectWindow instead of its own QPMMime::Messenger.

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