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

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

Fixed a compiler warning.

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