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

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

kernel: Created the initial implementation of the Drag'n'Drop support for OS/2 (not fully functiona yet: updating widget contents under the drag pointer will cause screen corruption, setting the drag object's pixmap is not possible). See ticket:22 for more info.

  • Property svn:keywords set to Id
File size: 99.7 KB
Line 
1/****************************************************************************
2** $Id: qmime_pm.cpp 97 2006-07-06 21:25:56Z 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#endif
1085 int level = qApp->eventLoop()->enterLoop();
1086#if defined(QT_DEBUG_DND)
1087 qDebug( "DefaultDropWorker: Finished waiting for "
1088 "DM_RENDERCOMPLETE (%d)\n"
1089 " got_DM_RENDERCOMPLETE=%d, usFS=0x%hX",
1090 level, d->got_DM_RENDERCOMPLETE, d->flags_DM_RENDERCOMPLETE );
1091#endif
1092 // JFTR: at this point, cleanup() might have been called,
1093 // as a result of either program exit or getting another
1094 // DM_DRAGOVER (if the source app has crashed) before getting
1095 // DM_RENDERCOMPLETE from the source. Use data members with
1096 // care!
1097 d->waiting_DM_RENDERCOMPLETE = 0;
1098 renderOk = d->got_DM_RENDERCOMPLETE &&
1099 (d->flags_DM_RENDERCOMPLETE & DMFL_RENDEROK);
1100 }
1101
1102 d->got_DM_RENDERCOMPLETE = FALSE;
1103 }
1104 } else {
1105#if defined(QT_DEBUG_DND)
1106 qDebug( "DefaultDropWorker: Source supports <"DRM_OS2FILE",%s> and "
1107 "provides a file \"%s\" for item %p (no need to render)",
1108 drf, srcFileName.data(), item );
1109#endif
1110 renderOk = TRUE;
1111 }
1112
1113 if ( renderOk ) {
1114 if ( drm == OS2File ) {
1115#if defined(QT_DEBUG_DND)
1116 qDebug( "DefaultDragWorker: Will read from \"%s\"",
1117 srcFileName.data() );
1118#endif
1119 QFile file( QFile::decodeName( srcFileName ) );
1120 renderOk = file.open( IO_ReadOnly );
1121 if ( renderOk ) {
1122 itemData = file.readAll();
1123 renderOk = file.status() == IO_Ok;
1124 file.close();
1125 }
1126 bool ok = file.remove();
1127 Q_ASSERT( (ok = ok) );
1128 } else {
1129 Q_ASSERT( xfer->hstrRenderToName );
1130 renderOk = xfer->hstrRenderToName != 0;
1131 if ( renderOk ) {
1132 const char *ptr = (const char *) xfer->hstrRenderToName;
1133 ULONG size = ~0;
1134 ULONG flags = 0;
1135 APIRET rc = DosQueryMem( (PVOID) ptr, &size, &flags );
1136 renderOk = rc == 0;
1137 if ( renderOk ) {
1138#if defined(QT_DEBUG_DND)
1139 qDebug( "DefaultDropWorker: Got shared data=%p, size=%lu "
1140 "(0x%08lX), flags=0x%08lX", ptr, size, size, flags );
1141#endif
1142 Q_ASSERT( flags & (PAG_COMMIT | PAG_READ | PAG_BASE) ==
1143 (PAG_COMMIT | PAG_READ | PAG_BASE) );
1144 renderOk = flags & (PAG_COMMIT | PAG_READ | PAG_BASE) ==
1145 (PAG_COMMIT | PAG_READ | PAG_BASE);
1146#ifndef QT_NO_DEBUG
1147 } else {
1148 qSystemWarning( "DosQueryMem failed", rc );
1149#endif
1150 }
1151 if ( renderOk ) {
1152 ULONG realSize = *(ULONG *) ptr;
1153#if defined(QT_DEBUG_DND)
1154 qDebug( "DefaultDropWorker: realSize=%lu", realSize );
1155#endif
1156 Q_ASSERT( realSize <= size );
1157 renderOk = realSize <= size;
1158 if ( renderOk ) {
1159 itemData.resize( realSize );
1160 memcpy( itemData.data(), ptr + sizeof (ULONG), realSize );
1161 }
1162 }
1163 // free memory only if it is given by another process,
1164 // otherwise DefaultDragWorker will free it
1165 if ( flags & PAG_SHARED )
1166 DosFreeMem( (PVOID) xfer->hstrRenderToName );
1167 }
1168 }
1169 }
1170
1171 if ( renderOk )
1172 renderOk = provider->provide( format, i, itemData, allData );
1173
1174 if ( needToTalk ) {
1175 // free the DRAGTRANSFER structure
1176 DrgFreeDragtransfer( xfer );
1177#if defined(QT_DEBUG_DND)
1178 {
1179 ULONG size = ~0, flags = 0;
1180 DosQueryMem( xfer, &size, &flags );
1181 qDebug( "DefaultDropWorker: Freed DRAGTRANSFER: "
1182 "xfer=%p, size=%lu(0x%08lX), flags=0x%08lX",
1183 xfer, size, size, flags );
1184 }
1185#endif
1186 xfer = NULL;
1187 }
1188
1189 if ( !renderOk )
1190 break;
1191 }
1192
1193#if defined(QT_DEBUG_DND)
1194 qDebug( "DefaultDropWorker: renderOk=%d", renderOk );
1195#endif
1196
1197 DrgDeleteStrHandle( rmfSharedMem );
1198 DrgDeleteStrHandle( rmfOS2File );
1199 DrgDeleteStrHandle( hstrRenderToName );
1200
1201 if ( renderOk )
1202 data = allData;
1203
1204 return data;
1205}
1206
1207MRESULT QPMMime::DefaultDropWorker::message( ULONG msg, MPARAM mp1, MPARAM mp2 )
1208{
1209 switch ( msg ) {
1210 case DM_RENDERCOMPLETE: {
1211 // sanity check
1212 Q_ASSERT( info() );
1213 if ( !info() )
1214 return (MRESULT) FALSE;
1215
1216#if defined(QT_DEBUG_DND)
1217 qDebug( "DefaultDropWorker: Got DM_RENDERCOMPLETE" );
1218#endif
1219 d->got_DM_RENDERCOMPLETE = TRUE;
1220 d->flags_DM_RENDERCOMPLETE = SHORT1FROMMP( mp2 );
1221
1222 if (d->sending_DM_RENDER)
1223 {
1224 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
1225#if defined(QT_CHECK_STATE)
1226 qWarning( "Drag item 0x%08lX sent DM_RENDERCOMPLETE w/o first "
1227 "replying to DM_RENDER!\n"
1228 "Contact the drag source developer.",
1229 xfer->pditem->hwndItem );
1230#endif
1231 return (MRESULT) FALSE;
1232 }
1233
1234 // stop synchronous waiting for DM_RENDERCOMPLETE
1235 if ( d->waiting_DM_RENDERCOMPLETE != 0 )
1236 qApp->eventLoop()->exitLoop();
1237 return (MRESULT) FALSE;
1238 }
1239 default:
1240 break;
1241 }
1242
1243 return (MRESULT) FALSE;
1244}
1245
1246bool QPMMime::DefaultDropWorker::addProvider( const char *format,
1247 Provider *provider )
1248{
1249 Q_ASSERT( format && provider );
1250 if ( format && provider && !d->exclusive ) {
1251 // ensure there are no dups (several providers for the same mime)
1252 if ( !d->providerFor( format ) )
1253 d->providers.push_back( Data::MimeProvider( format, provider ) );
1254 return TRUE;
1255 }
1256 return FALSE;
1257}
1258
1259bool QPMMime::DefaultDropWorker::addExclusiveProvider( const char *format,
1260 Provider *provider )
1261{
1262 Q_ASSERT( format && provider );
1263 if ( format && provider && !d->exclusive ) {
1264 d->exclusive = TRUE;
1265 d->providers.clear();
1266 d->providers.push_back( Data::MimeProvider( format, provider ) );
1267 return TRUE;
1268 }
1269 return FALSE;
1270}
1271
1272// static
1273bool QPMMime::DefaultDropWorker::canRender( DRAGITEM *item, const char *drf )
1274{
1275 return DrgVerifyRMF( item, DRM_OS2FILE, drf ) ||
1276 (DrgVerifyRMF( item, DRM_SHAREDMEM, drf ) &&
1277 DrgVerifyRMF( item, DRM_SHAREDMEM, DRF_POINTERDATA ));
1278}
1279
1280// static
1281/*! \internal
1282 Parses the rendering mechanism/format specification of the given \a item
1283 and stores only those mechanism branches in the given \a list that represent
1284 mechanisms supported by this worker. Returns FALSE if fails to parse the
1285 RMF specification. Note that if no supported mechanisms are found, TRUE is
1286 returned but the \a list will simply contain zero items.
1287 \note The method clears the given \a list variable before proceeding and sets
1288 auto-deletion to TRUE.
1289 \sa canRender(), PMMime::parseRMFs()
1290*/
1291bool QPMMime::DefaultDropWorker::getSupportedRMFs( DRAGITEM *item,
1292 QPtrList<QStrList> &list )
1293{
1294 if ( !parseRMFs( item->hstrRMF, list ) )
1295 return FALSE;
1296
1297 for ( QStrList *mech = list.first(); mech; ) {
1298 const char *drm = mech->first();
1299 if ( qstrcmp( drm, DRM_OS2FILE ) == 0 ) {
1300 mech = list.next();
1301 continue;
1302 }
1303 if ( qstrcmp( drm, DRM_SHAREDMEM ) == 0 ) {
1304 const char *drf = mech->next();
1305 // accept DRM_SHAREDMEM only if there is DRF_POINTERDATA
1306 for( ; drf; drf = mech->next() ) {
1307 if ( qstrcmp( drf, DRF_POINTERDATA ) == 0 )
1308 break;
1309 }
1310 if ( drf ) {
1311 mech = list.next();
1312 continue;
1313 }
1314 }
1315 // remove the unsupported mechanism branch from the list
1316 bool wasLast = list.getLast() == mech;
1317 list.removeRef( mech );
1318 // after deleting the last item, the current one will be set to the new
1319 // last item which was already analyzed earlier, so set to 0 to stop
1320 mech = wasLast ? 0 : list.current();
1321 }
1322
1323 return TRUE;
1324}
1325
1326#endif // !QT_NO_DRAGANDDROP
1327
1328//------------------------------------------------------------------------------
1329
1330static QPtrList<QPMMime> mimes;
1331
1332/*!
1333 \class QPMMime qmime.h
1334 \brief The QPMMime class maps open-standard MIME to OS/2 PM Clipboard formats.
1335 \ingroup io
1336 \ingroup draganddrop
1337 \ingroup misc
1338
1339 Qt's drag-and-drop and clipboard facilities use the MIME standard.
1340 On X11, this maps trivially to the Xdnd protocol, but on OS/2
1341 although some applications use MIME types to describe clipboard
1342 formats, others use arbitrary non-standardized naming conventions,
1343 or unnamed built-in formats of OS/2.
1344
1345 By instantiating subclasses of QPMMime that provide conversions
1346 between OS/2 PM Clipboard and MIME formats, you can convert
1347 proprietary clipboard formats to MIME formats.
1348
1349 Qt has predefined support for the following OS/2 PM Clipboard formats:
1350 \list
1351 \i CF_TEXT - converted to "text/plain;charset=system" or "text/plain"
1352 and supported by QTextDrag.
1353 \i CF_BITMAP - converted to "image/fmt", where fmt is
1354 a \link QImage::outputFormats() Qt image format\endlink,
1355 and supported by QImageDrag.
1356 \endlist
1357
1358 An example use of this class would be to map the OS/2 Metafile
1359 clipboard format (CF_METAFILE) to and from the MIME type "image/x-metafile".
1360 This conversion might simply be adding or removing a header, or even
1361 just passing on the data. See the
1362 \link dnd.html Drag-and-Drop documentation\endlink for more information
1363 on choosing and definition MIME types.
1364
1365 You can check if a MIME type is convertible using canConvert() and
1366 can perform conversions with convertToMime() and convertFromMime().
1367*/
1368
1369/*!
1370 Constructs a new conversion object, adding it to the globally accessed
1371 list of available convertors.
1372*/
1373QPMMime::QPMMime()
1374{
1375 // we prepend the convertor to let it override other convertors
1376 mimes.prepend( this );
1377}
1378
1379/*!
1380 Destroys a conversion object, removing it from the global
1381 list of available convertors.
1382*/
1383QPMMime::~QPMMime()
1384{
1385 mimes.remove( this );
1386}
1387
1388
1389
1390struct QPMRegisteredMimeType {
1391 QPMRegisteredMimeType( ulong c, const char *m ) :
1392 cf( c ), mime( m ) {}
1393 ulong cf;
1394 QCString mime;
1395#if !defined(QT_NO_DRAGANDDROP)
1396 QCString drf;
1397#endif // !QT_NO_DRAGANDDROP
1398};
1399
1400
1401class QPMMimeAnyMime : public QPMMime {
1402public:
1403 QPMMimeAnyMime();
1404 ~QPMMimeAnyMime();
1405
1406 const char *convertorName();
1407 int countCf();
1408 ulong cf( int index );
1409 ulong flFor( ulong cf );
1410 ulong cfFor( const char* mime );
1411 const char *mimeFor( ulong cf );
1412 bool canConvert( const char* mime, ulong cf );
1413 QByteArray convertToMime( QByteArray data, const char *, ulong );
1414 QByteArray convertFromMime( QByteArray data, const char *, ulong );
1415
1416#if !defined(QT_NO_DRAGANDDROP)
1417
1418 DragWorker *dragWorkerFor( const char *mime, QMimeSource *src );
1419 DropWorker *dropWorkerFor( DRAGINFO *info );
1420
1421 class AnyDragProvider : public DefaultDragWorker::Provider
1422 {
1423 public:
1424 AnyDragProvider( QPMMimeAnyMime *am ) : anyMime( am ) {}
1425 // Provider interface
1426 const char *format( const char *drf ) const;
1427 bool provide( const char *drf, const QByteArray &allData,
1428 ULONG itemIndex, QByteArray &itemData );
1429 void fileType( const char *drf, const char *&type, const char *&ext );
1430 private:
1431 QPMMimeAnyMime *anyMime;
1432 };
1433
1434 class AnyDropProvider : public DefaultDropWorker::Provider
1435 {
1436 public:
1437 AnyDropProvider( QPMMimeAnyMime *am ) : anyMime( am ) {}
1438 // Provider interface
1439 const char *drf( const char *format ) const;
1440 bool provide( const char *format, ULONG itemIndex,
1441 const QByteArray &itemData, QByteArray &allData );
1442 private:
1443 QPMMimeAnyMime *anyMime;
1444 };
1445
1446#endif // !QT_NO_DRAGANDDROP
1447
1448private:
1449 ulong registerMimeType( const char *mime );
1450 const char *lookupMimeType( ulong cf );
1451
1452 QPtrList<QPMRegisteredMimeType> mimetypes;
1453
1454#if !defined(QT_NO_DRAGANDDROP)
1455
1456 AnyDragProvider anyDragProvider;
1457 AnyDropProvider anyDropProvider;
1458
1459 friend class AnyDragProvider;
1460 friend class AnyDropProvider;
1461
1462#endif // !QT_NO_DRAGANDDROP
1463};
1464
1465QPMMimeAnyMime::QPMMimeAnyMime()
1466#if !defined(QT_NO_DRAGANDDROP)
1467 : anyDragProvider( AnyDragProvider( this ) )
1468 , anyDropProvider( AnyDropProvider( this ) )
1469#endif // !QT_NO_DRAGANDDROP
1470{
1471 mimetypes.setAutoDelete( TRUE );
1472}
1473
1474QPMMimeAnyMime::~QPMMimeAnyMime()
1475{
1476 // dereference all atoms we've referenced
1477 HATOMTBL tbl = WinQuerySystemAtomTable();
1478 for ( QPMRegisteredMimeType *mt = mimetypes.first();
1479 mt; mt = mimetypes.next() ) {
1480 WinDeleteAtom( tbl, 0xFFFF0000 | mt->cf );
1481 }
1482}
1483
1484const char* QPMMimeAnyMime::convertorName()
1485{
1486 return "Any-Mime";
1487}
1488
1489int QPMMimeAnyMime::countCf()
1490{
1491 return mimetypes.count();
1492}
1493
1494ulong QPMMimeAnyMime::cf( int index )
1495{
1496 return mimetypes.at( index )->cf;
1497}
1498
1499ulong QPMMimeAnyMime::flFor( ulong cf )
1500{
1501 // all formats in this converter assume CFI_POINTER data storage type
1502 return CFI_POINTER;
1503}
1504
1505ulong QPMMimeAnyMime::cfFor( const char* mime )
1506{
1507 QPMRegisteredMimeType *mt = mimetypes.current();
1508 if ( mt ) // quick check with most-recent
1509 if ( qstricmp( mt->mime, mime ) == 0 )
1510 return mt->cf;
1511 for ( mt = mimetypes.first(); mt; mt = mimetypes.next() )
1512 if ( qstricmp( mt->mime, mime ) == 0 )
1513 return mt->cf;
1514 // try to register the mime type
1515 return registerMimeType( mime );
1516}
1517
1518const char* QPMMimeAnyMime::mimeFor( ulong cf )
1519{
1520 QPMRegisteredMimeType *mt = mimetypes.current();
1521 if ( mt ) // quick check with most-recent
1522 if ( mt->cf == cf )
1523 return mt->mime;
1524 for ( mt = mimetypes.first(); mt; mt = mimetypes.next() )
1525 if ( mt->cf == cf )
1526 return mt->mime;
1527 // try to determine the mime type from the clipboard format ID
1528 return lookupMimeType( cf );
1529}
1530
1531bool QPMMimeAnyMime::canConvert( const char* mime, ulong cf )
1532{
1533 return mime && cf && qstricmp( mimeFor( cf ), mime ) == 0;
1534}
1535
1536QByteArray QPMMimeAnyMime::convertToMime( QByteArray data, const char *, ulong )
1537{
1538 return data;
1539}
1540
1541QByteArray QPMMimeAnyMime::convertFromMime( QByteArray data, const char *, ulong )
1542{
1543 return data;
1544}
1545
1546#if !defined(QT_NO_DRAGANDDROP)
1547
1548const char *QPMMimeAnyMime::AnyDragProvider::format( const char *drf ) const
1549{
1550 QPMRegisteredMimeType *mt = anyMime->mimetypes.current();
1551 if ( mt ) // quick check with most-recent
1552 if ( qstricmp( mt->drf, drf ) == 0 )
1553 return mt->mime;
1554 for ( mt = anyMime->mimetypes.first(); mt; mt = anyMime->mimetypes.next() )
1555 if ( qstricmp( mt->drf, drf ) == 0 )
1556 return mt->mime;
1557
1558 Q_ASSERT( /* mt->mime != */ NULL );
1559 return NULL;
1560}
1561
1562bool QPMMimeAnyMime::AnyDragProvider::provide( const char *drf,
1563 const QByteArray &allData,
1564 ULONG itemIndex,
1565 QByteArray &itemData )
1566{
1567 // always straight through coversion
1568 itemData = allData;
1569 return TRUE;
1570}
1571
1572void QPMMimeAnyMime::AnyDragProvider::fileType( const char *drf, const char *&type,
1573 const char *&ext )
1574{
1575 // file type = mime
1576 type = format( drf );
1577 Q_ASSERT( type );
1578
1579 // no way to determine the extension
1580 ext = NULL;
1581
1582 /// @todo (dmik) later, we can add a hack method to qmime.cpp that returns
1583 // the extension->mime map of the current QMimeSourceFactory and use it
1584 // to determine the extension.
1585};
1586
1587const char *QPMMimeAnyMime::AnyDropProvider::drf( const char *format ) const
1588{
1589 ulong cf = anyMime->cfFor( format );
1590 if ( cf ) {
1591 // current is what was found by cfFor()
1592 Q_ASSERT( anyMime->mimetypes.current()->drf );
1593 return anyMime->mimetypes.current()->drf;
1594 }
1595
1596 Q_ASSERT( FALSE );
1597 return NULL;
1598}
1599
1600bool QPMMimeAnyMime::AnyDropProvider::provide( const char *format,
1601 ULONG /* itemIndex */,
1602 const QByteArray &itemData,
1603 QByteArray &allData )
1604{
1605 // always straight through coversion
1606 allData = itemData;
1607 return TRUE;
1608}
1609
1610QPMMime::DragWorker *QPMMimeAnyMime::dragWorkerFor( const char *mime, QMimeSource *src )
1611{
1612 ulong cf = cfFor( mime );
1613 if ( cf ) {
1614 DefaultDragWorker *defWorker = defaultCoopDragWorker();
1615 QCString drf;
1616 drf.sprintf( "CF_%04lX", cf );
1617 // current is what was found by cfFor()
1618 mimetypes.current()->drf = drf;
1619 // add a cooperative provider
1620 defWorker->addProvider( drf, &anyDragProvider );
1621 return defWorker;
1622 }
1623
1624 return NULL;
1625}
1626
1627QPMMime::DropWorker *QPMMimeAnyMime::dropWorkerFor( DRAGINFO *info )
1628{
1629 ULONG itemCount = DrgQueryDragitemCount( info );
1630 Q_ASSERT( itemCount );
1631 if ( !itemCount )
1632 return NULL;
1633
1634 if ( itemCount == 1 ) {
1635 DRAGITEM *item = DrgQueryDragitemPtr( info, 0 );
1636 Q_ASSERT( item );
1637 if ( !item )
1638 return NULL;
1639
1640 DefaultDropWorker *defWorker = defaultDropWorker();
1641 bool atLeastOneSupported = FALSE;
1642
1643 // check that we support one of DRMs and the format is MIME_hhhh
1644 QPtrList<QStrList> list;
1645 defWorker->getSupportedRMFs( item, list );
1646 for( QStrList *mech = list.first(); mech; mech = list.next() ) {
1647#if defined(QT_DEBUG_DND)
1648 qDebug( "QPMMimeAnyMime: Supported drm: %s", mech->getFirst() );
1649#endif
1650 for( const char *drf = mech->at( 1 ); drf; drf = mech->next() ) {
1651 if ( qstrncmp( drf, "CF_", 3 ) == 0 ) {
1652 // get the atom ID
1653 ulong cf = 0;
1654 uint len = qstrlen ( drf );
1655 for ( uint i = 3; i < len; ++i ) {
1656 int hex = hex_to_int( drf[ i ] );
1657 if ( hex < 0 ) { cf = 0; break; }
1658 cf = (cf << 4) | (uint) hex;
1659 }
1660 Q_ASSERT( cf );
1661 if ( !cf )
1662 continue;
1663#if defined(QT_DEBUG_DND)
1664 qDebug( "QPMMimeAnyMime: drf %s is atom 0x%08lX", drf, cf );
1665#endif
1666 const char *mime = mimeFor( cf );
1667 if ( mime ) {
1668#if defined(QT_DEBUG_DND)
1669 qDebug( "QPMMimeAnyMime: Will provide [%s] for drf %s",
1670 mime, drf );
1671#endif
1672 // current is what was found by mimeFor()
1673 mimetypes.current()->drf = drf;
1674 // add a cooperative provider (can coexist with others)
1675 defWorker->addProvider( mime, &anyDropProvider );
1676 atLeastOneSupported = TRUE;
1677 }
1678 }
1679 }
1680 }
1681
1682 if ( atLeastOneSupported )
1683 return defWorker;
1684 }
1685
1686 return NULL;
1687}
1688
1689#endif // !QT_NO_DRAGANDDROP
1690
1691ulong QPMMimeAnyMime::registerMimeType( const char *mime )
1692{
1693 QCString atom;
1694 atom.sprintf( "mime:%s", mime );
1695
1696 ulong f = WinAddAtom( WinQuerySystemAtomTable(), atom );
1697 if ( !f ) {
1698#ifndef QT_NO_DEBUG
1699 qSystemWarning( "QPMMime: Failed to register the clipboard format" );
1700#endif
1701 return 0;
1702 }
1703
1704 mimetypes.append( new QPMRegisteredMimeType ( f, mime ) );
1705 Q_ASSERT( mimetypes.current()->cf == f );
1706 return mimetypes.current()->cf == f ? f : 0;
1707}
1708
1709const char *QPMMimeAnyMime::lookupMimeType( ulong cf )
1710{
1711 HATOMTBL tbl = WinQuerySystemAtomTable();
1712 ULONG len = WinQueryAtomLength( tbl, cf );
1713 QCString atom( len + 1 );
1714 WinQueryAtomName( tbl, cf, atom.data(), atom.size() );
1715
1716 // see if this atom represents a mime type
1717 if ( qstrncmp( atom, "mime:", 5 ) == 0 ) {
1718 atom = atom.right( atom.length() - 5 );
1719 if ( !atom.isEmpty() ) {
1720 ulong f = WinAddAtom( tbl, (PSZ) (0xFFFF0000 | cf) );
1721 if ( !f ) {
1722#ifndef QT_NO_DEBUG
1723 qSystemWarning( "QPMMime: Failed to reference the clipboard format" );
1724#endif
1725 return 0;
1726 }
1727 mimetypes.append( new QPMRegisteredMimeType ( cf, atom ) );
1728 Q_ASSERT( mimetypes.current()->cf == cf );
1729 return mimetypes.current()->cf == cf ? mimetypes.current()->mime : NULL;
1730 }
1731 }
1732
1733 return NULL;
1734}
1735
1736class QPMMimeText : public QPMMime {
1737public:
1738 QPMMimeText();
1739 ~QPMMimeText();
1740 const char* convertorName();
1741 int countCf();
1742 ulong cf( int index );
1743 ulong flFor( ulong cf );
1744 ulong cfFor( const char* mime );
1745 const char* mimeFor( ulong cf );
1746 bool canConvert( const char* mime, ulong cf );
1747 QByteArray convertToMime( QByteArray data, const char *, ulong cf );
1748 QByteArray convertFromMime( QByteArray data, const char *, ulong cf );
1749
1750#if !defined(QT_NO_DRAGANDDROP)
1751
1752 DragWorker *dragWorkerFor( const char *mime, QMimeSource *src );
1753 DropWorker *dropWorkerFor( DRAGINFO *info );
1754
1755 class NativeFileDrag : public DragWorker, public Messenger
1756 {
1757 public:
1758 // DragWorker interface
1759 bool cleanup( bool isCancelled ) { return TRUE; } // always disallow Move
1760 bool isExclusive() const { return TRUE; }
1761 ULONG itemCount() const { return 0; } // super exclusive
1762 HWND hwnd() const { return Messenger::hwnd(); }
1763 DRAGINFO *createDragInfo( const char *name, USHORT supportedOps );
1764 // Messenger interface (dummy implementation, we don't need to interact)
1765 MRESULT message( ULONG msg, MPARAM mp1, MPARAM mp2 ) { return 0; }
1766 };
1767
1768 class NativeFileDrop : public DropWorker
1769 {
1770 public:
1771 // DropWorker interface
1772 bool isExclusive() const { return TRUE; }
1773 bool provides( const char *format ) const;
1774 int formatCount() const;
1775 const char *format( int fn ) const;
1776 QByteArray encodedData( const char *format ) const;
1777 };
1778
1779 class TextDragProvider : public DefaultDragWorker::Provider
1780 {
1781 public:
1782 TextDragProvider() : exclusive( FALSE ) {}
1783 bool exclusive;
1784 // Provider interface
1785 const char *format( const char *drf ) const;
1786 bool provide( const char *drf, const QByteArray &allData,
1787 ULONG itemIndex, QByteArray &itemData );
1788 void fileType( const char *drf, const char *&type, const char *&ext );
1789 };
1790
1791 class TextDropProvider : public DefaultDropWorker::Provider
1792 {
1793 public:
1794 // Provider interface
1795 const char *drf( const char *format ) const;
1796 bool provide( const char *format, ULONG itemIndex,
1797 const QByteArray &itemData, QByteArray &allData );
1798 };
1799
1800#endif // !QT_NO_DRAGANDDROP
1801
1802private:
1803 const ulong CF_TextUnicode;
1804#if !defined(QT_NO_DRAGANDDROP)
1805 NativeFileDrag nativeFileDrag;
1806 NativeFileDrop nativeFileDrop;
1807 TextDragProvider textDragProvider;
1808 TextDropProvider textDropProvider;
1809#endif // !QT_NO_DRAGANDDROP
1810};
1811
1812QPMMimeText::QPMMimeText() :
1813 // register a clipboard format for unicode text
1814 // ("text/unicode" is what Mozilla uses to for unicode, so Qt apps will
1815 // be able to interchange unicode text with Mozilla apps)
1816 CF_TextUnicode ( WinAddAtom( WinQuerySystemAtomTable(), "text/unicode" ) )
1817{
1818}
1819
1820QPMMimeText::~QPMMimeText()
1821{
1822 WinDeleteAtom( WinQuerySystemAtomTable(), 0xFFFF0000 | CF_TextUnicode );
1823}
1824
1825const char* QPMMimeText::convertorName()
1826{
1827 return "Text";
1828}
1829
1830int QPMMimeText::countCf()
1831{
1832 return 2;
1833}
1834
1835ulong QPMMimeText::cf( int index )
1836{
1837 if ( index == 0 )
1838 return CF_TextUnicode;
1839 else if ( index == 1 )
1840 return CF_TEXT;
1841 return 0;
1842}
1843
1844ulong QPMMimeText::flFor( ulong cf )
1845{
1846 // both CF_TEXT and CF_TextUnicode use CFI_POINTER
1847 return cf == CF_TEXT || cf == CF_TextUnicode ? CFI_POINTER : 0;
1848}
1849
1850ulong QPMMimeText::cfFor( const char* mime )
1851{
1852 if ( qstrnicmp( mime, MIME_TEXT_PLAIN, qstrlen( MIME_TEXT_PLAIN ) ) != 0 ||
1853 (mime[10] != 0 && mime[10] != ' ' && mime[10] != ';') )
1854 return 0;
1855
1856 QCString m( mime );
1857 int i = m.find( "charset=" );
1858 if ( i >= 0 ) {
1859 QCString cs( m.data() + i + 8 );
1860 i = cs.find( ";" );
1861 if ( i >= 0 )
1862 cs = cs.left( i );
1863 if ( cs == "system" )
1864 return CF_TEXT;
1865 if ( cs == "ISO-10646-UCS-2" )
1866 return CF_TextUnicode;
1867 // any other 'charset' spec
1868 return 0;
1869 }
1870
1871 // no 'charset' spec
1872 return CF_TEXT;
1873}
1874
1875const char* QPMMimeText::mimeFor( ulong cf )
1876{
1877 if ( cf == CF_TEXT )
1878 return MIME_TEXT_PLAIN_CHARSET_SYSTEM;
1879 else if ( cf == CF_TextUnicode )
1880 return "text/plain;charset=ISO-10646-UCS-2";
1881 return 0;
1882}
1883
1884bool QPMMimeText::canConvert( const char* mime, ulong cf )
1885{
1886 if ( !cf )
1887 return FALSE;
1888
1889 return cfFor( mime ) == cf;
1890}
1891
1892/*
1893 text/plain is defined as using CRLF, but so many programs don't,
1894 and programmers just look for '\n' in strings.
1895 OS/2 really needs CRLF, so we ensure it here.
1896*/
1897
1898QByteArray QPMMimeText::convertToMime( QByteArray data, const char* , ulong cf )
1899{
1900 if ( cf == CF_TEXT ) {
1901 const char* d = data.data();
1902 const int s = qstrlen( d );
1903 QByteArray r( data.size() + 1 );
1904 char* o = r.data();
1905 int j = 0;
1906 for ( int i = 0; i < s; i++ ) {
1907 char c = d[i];
1908 if ( c != '\r' )
1909 o[j++] = c;
1910 }
1911 o[j] = 0;
1912 return r;
1913 } else if ( cf == CF_TextUnicode ) {
1914 // Mozilla uses un-marked little-endian nul-terminated Unicode
1915 // for "text/unicode"
1916 int sz = data.size();
1917 int len = 0;
1918 // Find NUL
1919 for ( ; len < sz - 1 && (data[len+0] || data[len+1]); len += 2 )
1920 ;
1921
1922 QByteArray r( len + 2 );
1923 r[0] = uchar( 0xff ); // BOM
1924 r[1] = uchar( 0xfe );
1925 memcpy( r.data() + 2, data.data(), len );
1926 return r;
1927 }
1928
1929 return QByteArray();
1930}
1931
1932extern QTextCodec* qt_findcharset( const QCString& mimetype );
1933
1934QByteArray QPMMimeText::convertFromMime( QByteArray data, const char *mime, ulong cf )
1935{
1936 if ( cf == CF_TEXT ) {
1937 // Anticipate required space for CRLFs at 1/40
1938 int maxsize = data.size() + data.size() / 40 + 3;
1939 QByteArray r( maxsize );
1940 char* o = r.data();
1941 const char* d = data.data();
1942 const int s = data.size();
1943 bool cr = FALSE;
1944 int j = 0;
1945 for ( int i = 0; i < s; i++ ) {
1946 char c = d[i];
1947 if ( c == '\r' )
1948 cr = TRUE;
1949 else {
1950 if ( c == '\n' ) {
1951 if ( !cr )
1952 o[j++]='\r';
1953 }
1954 cr = FALSE;
1955 }
1956 o[j++] = c;
1957 if ( j + 3 >= maxsize ) {
1958 maxsize += maxsize / 4;
1959 r.resize( maxsize );
1960 o = r.data();
1961 }
1962 }
1963 o[j] = 0;
1964 return r;
1965 } else if ( cf == CF_TextUnicode ) {
1966 QTextCodec *codec = qt_findcharset( mime );
1967 QString str = codec->toUnicode( data );
1968 const QChar *u = str.unicode();
1969 QString res;
1970 const int s = str.length();
1971 int maxsize = s + s / 40 + 3;
1972 res.setLength( maxsize );
1973 int ri = 0;
1974 bool cr = FALSE;
1975 for ( int i = 0; i < s; ++ i ) {
1976 if ( *u == '\r' )
1977 cr = TRUE;
1978 else {
1979 if ( *u == '\n' && !cr )
1980 res[ri++] = QChar('\r');
1981 cr = FALSE;
1982 }
1983 res[ri++] = *u;
1984 if ( ri + 3 >= maxsize ) {
1985 maxsize += maxsize / 4;
1986 res.setLength( maxsize );
1987 }
1988 ++u;
1989 }
1990 res.truncate( ri );
1991 const int byteLength = res.length() * 2;
1992 QByteArray r( byteLength + 2 );
1993 memcpy( r.data(), res.unicode(), byteLength );
1994 r[byteLength] = 0;
1995 r[byteLength+1] = 0;
1996 return r;
1997 }
1998
1999 return QByteArray();
2000}
2001
2002#if !defined(QT_NO_DRAGANDDROP)
2003
2004DRAGINFO *QPMMimeText::NativeFileDrag::createDragInfo( const char *name,
2005 USHORT supportedOps )
2006{
2007 Q_ASSERT( source() );
2008 if ( !source() )
2009 return NULL;
2010
2011 // obtain the list of files
2012 QStringList list;
2013 QUriDrag::decodeLocalFiles( source(), list );
2014 ULONG itemCnt = list.count();
2015 Q_ASSERT( itemCnt );
2016 if ( !itemCnt )
2017 return NULL;
2018
2019#if defined(QT_DEBUG_DND)
2020 qDebug( "QPMMimeText::NativeFileDrag: itemCnt=%ld", itemCnt );
2021#endif
2022
2023 DRAGINFO *info = DrgAllocDraginfo( itemCnt );
2024 Q_ASSERT( info );
2025 if ( !info )
2026 return NULL;
2027
2028 bool ok = TRUE;
2029 QStringList::Iterator it = list.begin();
2030 for ( ULONG i = 0; i < itemCnt; ++i, ++it ) {
2031 DRAGITEM *item = DrgQueryDragitemPtr( info, i );
2032 Q_ASSERT( item );
2033 if ( !item ) {
2034 ok = FALSE;
2035 break;
2036 }
2037
2038 QCString fileName = QDir::convertSeparators( *it ).local8Bit();
2039
2040 int sep = fileName.findRev( '\\' );
2041 Q_ASSERT( sep > 0 && sep < int( fileName.length() ) - 1 );
2042 if ( sep <= 0 || sep >= int( fileName.length() ) - 1 ) {
2043 ok = FALSE;
2044 break;
2045 }
2046
2047 item->hstrSourceName = DrgAddStrHandle( fileName.data() + sep + 1 );
2048 fileName.truncate( sep + 1 );
2049 item->hstrContainerName = DrgAddStrHandle( fileName );
2050
2051#if defined(QT_DEBUG_DND)
2052 qDebug( "QPMMimeText::NativeFileDrag: item %ld: dir=\"%s\", name=\"%s\"",
2053 i, queryHSTR( item->hstrContainerName ).data(),
2054 queryHSTR( item->hstrSourceName ).data() );
2055#endif
2056
2057 item->hwndItem = hwnd();
2058 item->ulItemID = 0;
2059 item->hstrType = DrgAddStrHandle( DRT_UNKNOWN );
2060 item->hstrRMF = DrgAddStrHandle( "<"DRM_OS2FILE","DRF_UNKNOWN">" );
2061 item->hstrTargetName = 0;
2062 item->cxOffset = 0;
2063 item->cyOffset = 0;
2064 item->fsControl = 0;
2065 item->fsSupportedOps = supportedOps;
2066 }
2067
2068 if ( !ok ) {
2069 DrgFreeDraginfo( info );
2070 info = NULL;
2071 }
2072
2073 return info;
2074}
2075
2076bool QPMMimeText::NativeFileDrop::provides( const char *format ) const
2077{
2078 return qstricmp( format, MIME_TEXT_URI_LIST ) == 0;
2079}
2080
2081int QPMMimeText::NativeFileDrop::formatCount() const
2082{
2083 return 1;
2084}
2085
2086const char *QPMMimeText::NativeFileDrop::format( int fn ) const
2087{
2088 return fn == 0 ? MIME_TEXT_URI_LIST : NULL;
2089}
2090
2091QByteArray QPMMimeText::NativeFileDrop::encodedData( const char *format ) const
2092{
2093 QByteArray data;
2094
2095 Q_ASSERT( info() );
2096 if ( !info() )
2097 return data;
2098
2099 ULONG itemCount = DrgQueryDragitemCount( info() );
2100 Q_ASSERT( itemCount );
2101 if ( !itemCount )
2102 return data;
2103
2104 // sanity check
2105 if ( qstricmp( format, MIME_TEXT_URI_LIST ) != 0 )
2106 return data;
2107
2108 QCString texturi;
2109
2110 for ( ULONG i = 0; i < itemCount; ++i ) {
2111 DRAGITEM *item = DrgQueryDragitemPtr( info(), i );
2112 Q_ASSERT( item );
2113 QCString fullName;
2114 if ( !item || !canTargetRenderAsOS2File( item, &fullName ) )
2115 return data;
2116 QString fn = QFile::decodeName( fullName );
2117 texturi += QUriDrag::localFileToUri( fn );
2118 texturi += "\r\n";
2119 }
2120
2121 data = texturi;
2122 return data;
2123}
2124
2125const char *QPMMimeText::TextDragProvider::format( const char *drf ) const
2126{
2127 if ( qstrcmp( drf, DRF_TEXT ) == 0 ) {
2128 if ( exclusive )
2129 return MIME_TEXT_URI_LIST;
2130 else
2131 return MIME_TEXT_PLAIN_CHARSET_SYSTEM;
2132 }
2133 return NULL;
2134}
2135
2136bool QPMMimeText::TextDragProvider::provide( const char *drf,
2137 const QByteArray &allData,
2138 ULONG itemIndex,
2139 QByteArray &itemData )
2140{
2141 if ( qstrcmp( drf, DRF_TEXT ) == 0 ) {
2142 if ( exclusive ) {
2143 // locate the required item
2144 int dataSize = allData.size();
2145 if ( !dataSize )
2146 return FALSE;
2147 int begin = 0, end = 0, next = 0;
2148 do {
2149 begin = next;
2150 end = allData.find( '\r', begin );
2151 if ( end >= 0 ) {
2152 next = end + 1;
2153 if ( next < dataSize && allData[ next ] == '\n' )
2154 ++ next;
2155 } else {
2156 end = allData.find( '\n', begin );
2157 if ( end >= 0 )
2158 next = end + 1;
2159 }
2160 } while ( itemIndex-- && end >= 0 && next < dataSize );
2161 int urlLen = end - begin;
2162 if ( urlLen <= 0 )
2163 return FALSE;
2164 QCString urlStr( urlLen + 1 );
2165 memcpy( urlStr.data(), allData.data() + begin, urlLen );
2166 // decode from UTF-8
2167 urlStr = QString::fromUtf8( urlStr ).local8Bit();
2168 // encode special/national chars to escaped %XX within urlStr
2169 // (see QUrl::encode(), but special chars a bit differ here: for
2170 // example, Mozilla doesn't like when ':' is encoded after 'http').
2171 const QCString special( "<>#@\"&%$,;?={}|^~[]\'`\\ \n\t\r" );
2172 QCString itemStr( urlLen * 3 + 1 );
2173 int itemLen = 0;
2174 for ( int i = 0; i < urlLen; ++i ) {
2175 uchar ch = urlStr[ i ];
2176 if ( ch >= 128 || special.contains( ch ) ) {
2177 itemStr[ itemLen++ ] = '%';
2178 uchar c = ch / 16;
2179 c += c > 9 ? 'A' - 10 : '0';
2180 itemStr[ itemLen++ ] = c;
2181 c = ch % 16;
2182 c += c > 9 ? 'A' - 10 : '0';
2183 itemStr[ itemLen++ ] = c;
2184 } else {
2185 itemStr[ itemLen++ ] = ch;
2186 }
2187 }
2188 itemData = itemStr;
2189 // resize on QByteArray to ensure the trailing \0 is stripped
2190 itemData.resize( itemLen );
2191 } else {
2192 itemData = allData;
2193 }
2194 return TRUE;
2195 }
2196 return FALSE;
2197}
2198
2199void QPMMimeText::TextDragProvider::fileType( const char *drf, const char *&type,
2200 const char *&ext )
2201{
2202 if ( qstrcmp( drf, DRF_TEXT ) == 0 ) {
2203 if ( exclusive ) {
2204 type = DRT_URL;
2205 // no extension for URLs
2206 } else {
2207 type = DRT_TEXT;
2208 ext = "txt";
2209 }
2210 }
2211};
2212
2213const char *QPMMimeText::TextDropProvider::drf( const char *format ) const
2214{
2215 // sanity check
2216 if ( qstricmp( format, MIME_TEXT_PLAIN_CHARSET_SYSTEM ) == 0 ||
2217 qstricmp( format, MIME_TEXT_URI_LIST ) == 0 )
2218 return DRF_TEXT;
2219 return NULL;
2220}
2221
2222bool QPMMimeText::TextDropProvider::provide( const char *format,
2223 ULONG /* itemIndex */,
2224 const QByteArray &itemData,
2225 QByteArray &allData )
2226{
2227 if ( qstricmp( format, MIME_TEXT_PLAIN_CHARSET_SYSTEM ) == 0 ) {
2228 allData = itemData;
2229 return TRUE;
2230 }
2231
2232 if ( qstricmp( format, MIME_TEXT_URI_LIST ) == 0 ) {
2233 // decode escaped %XX within itemData (see QUrl::decode())
2234 size_t dataSize = itemData.size();
2235 size_t strLen = 0;
2236 QCString itemStr( dataSize + 1 );
2237 for ( size_t i = 0; i < dataSize; ) {
2238 uchar ch = itemData[ i++ ];
2239 if ( ch == '%' && (i + 2 <= dataSize) ) {
2240 int hi = hex_to_int( itemData[ i ] );
2241 if ( hi >= 0 ) {
2242 int lo = hex_to_int( itemData[ i + 1 ] );
2243 if ( lo >= 0 ) {
2244 ch = (hi << 4) + lo;
2245 i += 2;
2246 }
2247 }
2248 }
2249 itemStr[ strLen++ ] = ch;
2250 }
2251 itemStr.truncate( strLen );
2252 // check that itemData is a valid URL
2253 QUrl url( QString::fromLocal8Bit( itemStr ) );
2254 if ( !url.isValid() )
2255 return FALSE;
2256 // oops, QUrl incorrectly handles the 'file:' protocol, do it for it
2257 QString urlStr;
2258 if ( url.isLocalFile() )
2259 urlStr = url.protocol() + ":///" + url.path();
2260 else
2261 urlStr = url.toString();
2262 // append the URL to the list
2263 QCString str ( allData );
2264 str += QUriDrag::unicodeUriToUri( urlStr );
2265 str += "\r\n";
2266 allData = str;
2267 return TRUE;
2268 }
2269
2270 return FALSE;
2271}
2272
2273QPMMime::DragWorker *QPMMimeText::dragWorkerFor( const char *mime, QMimeSource *src )
2274{
2275 if ( cfFor( mime ) == CF_TEXT ) {
2276 // add a cooperative provider
2277 textDragProvider.exclusive = FALSE;
2278 DefaultDragWorker *defWorker = defaultCoopDragWorker();
2279 defWorker->addProvider( DRF_TEXT, &textDragProvider );
2280 return defWorker;
2281 }
2282
2283 if ( qstricmp( mime, MIME_TEXT_URI_LIST ) == 0 ) {
2284 // see what kind of items text/uri-list represents
2285 QStrList uriList;
2286 QStringList fileList;
2287 QUriDrag::decode( src, uriList );
2288 QUriDrag::decodeLocalFiles( src, fileList );
2289 if ( fileList.count() && fileList.count() == uriList.count() ) {
2290 // all items are local files, return an exclusive file drag worker
2291 return &nativeFileDrag;
2292 }
2293 if ( uriList.count() && !fileList.count() ) {
2294 // all items are non-files, add an exclusive provider for the
2295 // specified item count
2296 textDragProvider.exclusive = TRUE;
2297 DefaultDragWorker *defWorker = defaultExclDragWorker();
2298 bool ok = defWorker->addProvider( DRF_TEXT, &textDragProvider,
2299 uriList.count() );
2300 return ok ? defWorker : NULL;
2301 }
2302 // if items are mixed, we return NULL to fallback to QPMMimeAnyMime
2303 }
2304
2305 return NULL;
2306}
2307
2308QPMMime::DropWorker *QPMMimeText::dropWorkerFor( DRAGINFO *info )
2309{
2310 ULONG itemCount = DrgQueryDragitemCount( info );
2311 Q_ASSERT( itemCount );
2312 if ( !itemCount )
2313 return NULL;
2314
2315 if ( itemCount == 1 ) {
2316 DRAGITEM *item = DrgQueryDragitemPtr( info, 0 );
2317 Q_ASSERT( item );
2318 if ( !item )
2319 return NULL;
2320 // proceed only if the target cannot render DRM_OS2FILE on its own
2321 // and if the item type is not "UniformResourceLocator" (which will be
2322 // processed below)
2323 if ( !canTargetRenderAsOS2File( item ) &&
2324 !DrgVerifyType( item, DRT_URL ) ) {
2325 DefaultDropWorker *defWorker = defaultDropWorker();
2326 // check that we support one of DRMs and the format is DRF_TEXT
2327 if ( defWorker->canRender( item, DRF_TEXT ) ) {
2328 // add a cooperative provider (can coexist with others)
2329 defWorker->addProvider( MIME_TEXT_PLAIN_CHARSET_SYSTEM,
2330 &textDropProvider );
2331 return defWorker;
2332 }
2333 return NULL;
2334 }
2335 }
2336
2337 // Either the target can render DRM_OS2FILE on its own (so it's a valid
2338 // file/directory name), or it's an "UniformResourceLocator", or there is
2339 // more than one drag item. Check that all items are of either one type
2340 // or another. If so, we can represent them as 'text/uri-list'.
2341 bool allAreFiles = TRUE;
2342 bool allAreURLs = TRUE;
2343 DefaultDropWorker *defWorker = defaultDropWorker();
2344 for ( ULONG i = 0; i < itemCount; ++i ) {
2345 DRAGITEM *item = DrgQueryDragitemPtr( info, i );
2346 Q_ASSERT( item );
2347 if ( !item )
2348 return NULL;
2349 if (allAreFiles)
2350 allAreFiles &= canTargetRenderAsOS2File( item );
2351 if (allAreURLs)
2352 allAreURLs &= DrgVerifyType( item, DRT_URL ) &&
2353 defWorker->canRender( item, DRF_TEXT );
2354 if (!allAreFiles && !allAreURLs)
2355 return NULL;
2356 }
2357
2358 if (allAreFiles) {
2359 // return an exclusive drop worker
2360 return &nativeFileDrop;
2361 }
2362
2363 // add an exclusive provider (can neither coexist with other workers
2364 // or providers)
2365 bool ok = defWorker->addExclusiveProvider( MIME_TEXT_URI_LIST,
2366 &textDropProvider );
2367 return ok ? defWorker : NULL;
2368}
2369
2370#endif // !QT_NO_DRAGANDDROP
2371
2372
2373class QPMMimeImage : public QPMMime {
2374public:
2375 const char* convertorName();
2376 int countCf();
2377 ulong cf( int index );
2378 ulong flFor( ulong cf );
2379 ulong cfFor( const char* mime );
2380 const char* mimeFor( ulong cf );
2381 bool canConvert( const char* mime, ulong cf );
2382 QByteArray convertToMime( QByteArray data, const char *mime, ulong cf );
2383 QByteArray convertFromMime( QByteArray data, const char *mime, ulong cf );
2384};
2385
2386const char* QPMMimeImage::convertorName()
2387{
2388 return "Image";
2389}
2390
2391int QPMMimeImage::countCf()
2392{
2393 return 1;
2394}
2395
2396ulong QPMMimeImage::cf( int index )
2397{
2398 return index == 0 ? CF_BITMAP : 0;
2399}
2400
2401ulong QPMMimeImage::flFor( ulong cf )
2402{
2403 // CF_BITMAP uses CFI_HANDLE
2404 return cf == CF_BITMAP ? CFI_HANDLE : 0;
2405}
2406
2407ulong QPMMimeImage::cfFor( const char *mime )
2408{
2409 if ( qstrnicmp( mime, "image/", 6 ) == 0 ) {
2410 QStrList ofmts = QImage::outputFormats();
2411 for ( const char *fmt = ofmts.first(); fmt; fmt = ofmts.next() )
2412 if ( qstricmp( fmt, mime + 6 ) == 0 )
2413 return CF_BITMAP;
2414 }
2415 return 0;
2416}
2417
2418const char* QPMMimeImage::mimeFor( ulong cf )
2419{
2420 if ( cf == CF_BITMAP )
2421 return "image/bmp";
2422 return 0;
2423}
2424
2425bool QPMMimeImage::canConvert( const char* mime, ulong cf )
2426{
2427 if ( cf == CF_BITMAP && qstrnicmp( mime, "image/", 6 ) == 0 ) {
2428 QStrList ofmts = QImage::outputFormats();
2429 for ( const char* fmt = ofmts.first(); fmt; fmt = ofmts.next() )
2430 if ( qstricmp( fmt, mime + 6 ) == 0 )
2431 return TRUE;
2432 }
2433 return FALSE;
2434}
2435
2436QByteArray QPMMimeImage::convertToMime( QByteArray data, const char *mime, ulong cf )
2437{
2438 if ( qstrnicmp( mime, "image/", 6 ) != 0 || cf != CF_BITMAP ) // Sanity
2439 return QByteArray();
2440
2441 HBITMAP hbm = (HBITMAP) *(ULONG *) data.data();
2442
2443 QPixmap pm;
2444 pm.attachHandle( hbm );
2445 QImage img = pm.convertToImage();
2446 pm.detachHandle(); // prevent hbm from being deleted
2447
2448 QCString ofmt = mime + 6;
2449 QByteArray ba;
2450 QBuffer iod( ba );
2451 iod.open( IO_WriteOnly );
2452 QImageIO iio( &iod, ofmt.upper() );
2453 iio.setImage( img );
2454 if ( iio.write() ) {
2455 iod.close();
2456 return ba;
2457 }
2458
2459 // Failed
2460 return QByteArray();
2461}
2462
2463QByteArray QPMMimeImage::convertFromMime( QByteArray data, const char *mime, ulong cf )
2464{
2465 if ( qstrnicmp( mime, "image/", 6 ) != 0 || cf != CF_BITMAP ) // Sanity
2466 return QByteArray();
2467
2468 QImage img;
2469 img.loadFromData( (unsigned char *) data.data(), data.size() );
2470 if ( !img.isNull() ) {
2471 HBITMAP hbm = QPixmap( img ).detachHandle();
2472 if ( hbm ) {
2473 QByteArray ba ( sizeof(HBITMAP) );
2474 *(HBITMAP *) ba.data() = hbm;
2475 return ba;
2476 }
2477 }
2478
2479 // Failed
2480 return QByteArray();
2481}
2482
2483
2484static
2485void cleanup_mimes()
2486{
2487 QPMMime* wm;
2488 while ( (wm = mimes.first()) ) {
2489 delete wm;
2490 }
2491}
2492
2493/*!
2494 This is an internal function.
2495*/
2496void QPMMime::initialize()
2497{
2498 if ( mimes.isEmpty() ) {
2499 // add standard convertors so that QPMMimeAnyMime is the last in the list
2500 new QPMMimeAnyMime;
2501 new QPMMimeImage;
2502 new QPMMimeText;
2503 qAddPostRoutine( cleanup_mimes );
2504 }
2505}
2506
2507/*!
2508 Returns the most-recently created QPMMime that can convert
2509 between the \a mime and \a cf formats. Returns 0 if no such convertor
2510 exists.
2511*/
2512QPMMime *
2513QPMMime::convertor( const char *mime, ulong cf )
2514{
2515 // return nothing for illegal requests
2516 if ( !cf )
2517 return 0;
2518
2519 QPMMime* wm;
2520 for ( wm = mimes.first(); wm; wm = mimes.next() ) {
2521 if ( wm->canConvert( mime, cf ) ) {
2522 return wm;
2523 }
2524 }
2525 return 0;
2526}
2527
2528
2529/*!
2530 Returns a MIME type for \a cf, or 0 if none exists.
2531*/
2532const char* QPMMime::cfToMime( ulong cf )
2533{
2534 const char* m = 0;
2535 QPMMime* wm;
2536 for ( wm = mimes.first(); wm && !m; wm = mimes.next() ) {
2537 m = wm->mimeFor( cf );
2538 }
2539 return m;
2540}
2541
2542/*!
2543 Returns a list of all currently defined QPMMime objects.
2544*/
2545QPtrList<QPMMime> QPMMime::all()
2546{
2547 return mimes;
2548}
2549
2550#if !defined(QT_NO_DRAGANDDROP)
2551
2552/*!
2553 Returns a string represented by \a hstr.
2554*/
2555QCString QPMMime::queryHSTR( HSTR hstr )
2556{
2557 QCString str;
2558 ULONG len = DrgQueryStrNameLen( hstr );
2559 if ( len ) {
2560 str.resize( len + 1 );
2561 DrgQueryStrName( hstr, str.size(), str.data() );
2562 }
2563 return str;
2564}
2565
2566/*!
2567 Returns a string that is a concatenation of \c hstrContainerName and
2568 \c hstrSourceName fileds of the given \a item structure.
2569*/
2570QCString QPMMime::querySourceNameFull( DRAGITEM *item )
2571{
2572 QCString fullName;
2573 if ( !item )
2574 return fullName;
2575
2576 ULONG pathLen = DrgQueryStrNameLen( item->hstrContainerName );
2577 ULONG nameLen = DrgQueryStrNameLen( item->hstrSourceName );
2578 if ( !pathLen || !nameLen )
2579 return fullName;
2580
2581 fullName.resize( pathLen + nameLen + 1 );
2582 DrgQueryStrName( item->hstrContainerName, pathLen + 1, fullName.data() );
2583 DrgQueryStrName( item->hstrSourceName, nameLen + 1, fullName.data() + pathLen );
2584
2585 return fullName;
2586}
2587
2588/*! \internal
2589 Checks that the given drag \a item supports the DRM_OS2FILE rendering
2590 mechanism and can be rendered by a target w/o involving the source (i.e.,
2591 DRM_OS2FILE is the first supported format and a valid file name with full
2592 path is provided). If the function returns TRUE, \a fullName (if not NULL)
2593 will be assigned the item's full source file name (composed from
2594 \c hstrContainerName and \c hstrSourceName fields).
2595 */
2596bool QPMMime::canTargetRenderAsOS2File( DRAGITEM *item, QCString *fullName /* = NULL */ )
2597{
2598 if ( !item )
2599 return FALSE;
2600
2601 if ( item->fsControl & (DC_PREPARE | DC_PREPAREITEM) )
2602 return FALSE;
2603
2604 {
2605 // DrgVerifyNativeRMF doesn't work on my system (ECS 1.2.1 GA):
2606 // it always returns FALSE regardless of arguments. Use simplified
2607 // hstrRMF parsing to determine whether DRM_OS2FILE is the native
2608 // mechanism or not (i.e. "^\s*[\(<]\s*DRM_OS2FILE\s*,.*").
2609
2610 QCString rmf = queryHSTR( item->hstrRMF );
2611 bool ok = FALSE;
2612 int i = rmf.find( DRM_OS2FILE );
2613 if ( i >= 1 ) {
2614 for( int j = i - 1; j >= 0; --j ) {
2615 char ch = rmf.data()[j];
2616 if ( ch == ' ' ) continue;
2617 if ( ch == '<' || ch == '(' ) {
2618 if ( ok ) return FALSE;
2619 ok = TRUE;
2620 } else {
2621 return FALSE;
2622 }
2623 }
2624 }
2625 if ( ok ) {
2626 ok = FALSE;
2627 int drmLen = strlen( DRM_OS2FILE );
2628 for( int j = i + drmLen; j < (int) rmf.length(); ++j ) {
2629 char ch = rmf.data()[j];
2630 if ( ch == ' ' ) continue;
2631 if ( ch == ',' ) {
2632 ok = TRUE;
2633 break;
2634 }
2635 return FALSE;
2636 }
2637 }
2638 if ( !ok )
2639 return FALSE;
2640 }
2641
2642 QCString srcFullName = querySourceNameFull( item );
2643 if ( srcFullName.isEmpty() )
2644 return FALSE;
2645
2646 QCString srcFullName2( srcFullName.length() + 1 );
2647 APIRET rc = DosQueryPathInfo( srcFullName, FIL_QUERYFULLNAME,
2648 srcFullName2.data(), srcFullName2.size() );
2649 if ( rc != 0 )
2650 return FALSE;
2651
2652 QString s1 = QFile::decodeName( srcFullName.data() );
2653 QString s2 = QFile::decodeName( srcFullName2.data() );
2654
2655 if ( s1.lower() != s2.lower() )
2656 return FALSE;
2657
2658 if ( fullName )
2659 *fullName = srcFullName;
2660 return TRUE;
2661}
2662
2663// static
2664/*! \internal
2665 Parses the given \a rmfs list (full rendering mechanism/format specification)
2666 and builds a \a list of mechanism branches. Each mechanism branch is also a
2667 list, where the first item is the mechahism name and all subsequent items are
2668 formats supported by this mechanism. Returns FALSE if fails to parse \a rmf.
2669 \note The method clears the given \a list variable before proceeding and sets
2670 auto-deletion to TRUE.
2671*/
2672bool QPMMime::parseRMFs( HSTR rmfs, QPtrList<QStrList> &list )
2673{
2674 // The format of the RMF list is "elem {,elem,elem...}"
2675 // where elem is "(mechanism{,mechanism...}) x (format{,format...})"
2676 // or "<mechanism,format>".
2677 // We use a simple FSM to parse it. In terms of FSM, the format is:
2678 //
2679 // STRT ( BCM m CMCH echanism CMCH , NCM m CMCH echanism CMCH ) ECM x
2680 // SCMF ( BCF f CFCH ormat CFCH , NCF f CFCH ormat CFCH ) ECF , STRT
2681 // STRT < BP m PMCH echanism PMCH , SPMF f PFCH ormat PFCH > EP , STRT
2682
2683 QCString str = queryHSTR( rmfs );
2684 uint len = str.length();
2685
2686 enum {
2687 // states
2688 STRT = 0, BCM, CMCH, NCM, ECM, SCMF, BCF, CFCH, NCF, ECF,
2689 BP, PMCH, SPMF, PFCH, EP,
2690 STATES_COUNT,
2691 // pseudo states
2692 Err, Skip,
2693 // inputs
2694 obr = 0, cbr, xx, lt, gt, cm, any, ws,
2695 INPUTS_COUNT,
2696 };
2697
2698 static const char Chars[] = { '(', ')', 'x', 'X', '<', '>', ',', ' ', 0 };
2699 static const char Inputs[] = { obr, cbr, xx, xx, lt, gt, cm, ws };
2700 static const uchar Fsm [STATES_COUNT] [INPUTS_COUNT] = {
2701 /* 0 obr 1 cbr 2 xx 3 lt 4 gt 5 cm 6 any 7 ws */
2702/* STRT 0 */ { BCM, Err, Err, BP, Err, Err, Err, Skip },
2703/* BCM 1 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
2704/* CMCH 2 */ { Err, ECM, CMCH, Err, Err, NCM, CMCH, CMCH },
2705/* NCM 3 */ { Err, Err, Err, Err, Err, Err, CMCH, Skip },
2706/* ECM 4 */ { Err, Err, SCMF, Err, Err, Err, Err, Skip },
2707/* SCMF 5 */ { BCF, Err, Err, Err, Err, Err, Err, Skip },
2708/* BCF 6 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
2709/* CFCH 7 */ { Err, ECF, CFCH, Err, Err, NCF, CFCH, CFCH },
2710/* NCF 8 */ { Err, Err, Err, Err, Err, Err, CFCH, Skip },
2711/* ECF 9 */ { Err, Err, Err, Err, Err, STRT, Err, Skip },
2712/* BP 10 */ { Err, Err, Err, Err, Err, Err, PMCH, Skip },
2713/* PMCH 11 */ { Err, Err, PMCH, Err, Err, SPMF, PMCH, PMCH },
2714/* SPMF 12 */ { Err, Err, Err, Err, Err, Err, PFCH, Skip },
2715/* PFCH 13 */ { Err, Err, PFCH, Err, EP, Err, PFCH, PFCH },
2716/* EP 14 */ { Err, Err, Err, Err, Err, STRT, Err, Skip }
2717 };
2718
2719 list.clear();
2720 list.setAutoDelete( TRUE );
2721
2722 QPtrList<QStrList> mech;
2723 QCString buf;
2724 QStrList *m = NULL;
2725
2726 uint state = STRT;
2727 uint start = 0, end = 0, space = 0;
2728
2729 for ( uint i = 0; i < len && state != Err ; ++i ) {
2730 char ch = str[i];
2731 char *p = strchr( Chars, ch );
2732 uint input = p ? Inputs[ p - Chars ] : any;
2733 uint new_state = Fsm[ state ][ input ];
2734 switch ( new_state ) {
2735 case Skip:
2736 continue;
2737 case CMCH:
2738 case CFCH:
2739 case PMCH:
2740 case PFCH:
2741 if ( state != new_state )
2742 start = end = i;
2743 ++end;
2744 // accumulate trailing space for truncation
2745 if ( input == ws ) ++space;
2746 else space = 0;
2747 break;
2748 case NCM:
2749 case ECM:
2750 case SPMF:
2751 buf = QCString( str.data() + start, end - start - space + 1 );
2752 // find the mechanism branch in the output list
2753 for ( m = list.first(); m; m = list.next() ) {
2754 if ( qstrcmp( m->getFirst(), buf ) == 0 )
2755 break;
2756 }
2757 if ( !m ) {
2758 // append to the output list if not found
2759 m = new QStrList();
2760 m->append( buf );
2761 list.append( m );
2762 }
2763 // store in the current list for making a cross product
2764 mech.append( m );
2765 start = end = 0;
2766 break;
2767 case NCF:
2768 case ECF:
2769 case EP:
2770 buf = QCString( str.data() + start, end - start - space + 1 );
2771 // make a cross product with all current mechanisms
2772 for ( m = mech.first(); m; m = mech.next() )
2773 m->append( buf );
2774 if ( new_state != NCF )
2775 mech.clear();
2776 start = end = 0;
2777 break;
2778 default:
2779 break;
2780 }
2781 state = new_state;
2782 }
2783
2784 return state == ECF || state == EP;
2785}
2786
2787// static
2788/*! \internal
2789 Splits the given \a rmf (rendering mechanism/format pair) to a \a mechanism
2790 and a \a format string. Returns FALSE if fails to parse \a rmf.
2791 */
2792bool QPMMime::parseRMF( HSTR rmf, QCString &mechanism, QCString &format )
2793{
2794 QPtrList<QStrList> list;
2795 if ( !parseRMFs( rmf, list ) )
2796 return FALSE;
2797
2798 if ( list.count() != 1 || list.getFirst()->count() != 2 )
2799 return FALSE;
2800
2801 QStrList *m = list.getFirst();
2802 mechanism = m->first();
2803 format = m->next();
2804
2805 return TRUE;
2806}
2807
2808// static
2809/*! \internal */
2810QPMMime::DefaultDragWorker *QPMMime::defaultCoopDragWorker()
2811{
2812 static DefaultDragWorker defCoopDragWorker( FALSE /* exclusive */ );
2813 return &defCoopDragWorker;
2814}
2815
2816// static
2817/*! \internal */
2818QPMMime::DefaultDragWorker *QPMMime::defaultExclDragWorker()
2819{
2820 static DefaultDragWorker defExclDragWorker( TRUE /* exclusive */ );
2821 return &defExclDragWorker;
2822}
2823
2824// static
2825/*! \internal */
2826QPMMime::DefaultDropWorker *QPMMime::defaultDropWorker()
2827{
2828 static DefaultDropWorker defaultDropWorker;
2829 return &defaultDropWorker;
2830}
2831
2832#endif // !QT_NO_DRAGANDDROP
2833
2834/*!
2835 \fn const char* QPMMime::convertorName()
2836
2837 Returns a name for the convertor.
2838
2839 All subclasses must reimplement this pure virtual function.
2840*/
2841
2842/*!
2843 \fn int QPMMime::countCf()
2844
2845 Returns the number of OS/2 PM Clipboard formats supported by this
2846 convertor.
2847
2848 All subclasses must reimplement this pure virtual function.
2849*/
2850
2851/*!
2852 \fn ulong QPMMime::cf(int index)
2853
2854 Returns the OS/2 PM Clipboard format supported by this convertor
2855 that is ordinarily at position \a index. This means that cf(0)
2856 returns the first OS/2 PM Clipboard format supported, and
2857 cf(countCf()-1) returns the last. If \a index is out of range the
2858 return value is undefined.
2859
2860 All subclasses must reimplement this pure virtual function.
2861*/
2862
2863/*!
2864 \fn ulong QPMMime::flFor(ulong cf)
2865
2866 Returns the data storage flag for the OS/2 PM Clipboard type \a cf
2867 (either \c CFI_POINTER or \c CFI_HANDLE) used by this convertor,
2868 or 0 if invalid \a cf is specified.
2869
2870 All subclasses must reimplement this pure virtual function.
2871*/
2872
2873/*!
2874 \fn bool QPMMime::canConvert( const char* mime, ulong cf )
2875
2876 Returns TRUE if the convertor can convert (both ways) between
2877 \a mime and \a cf; otherwise returns FALSE.
2878
2879 All subclasses must reimplement this pure virtual function.
2880*/
2881
2882/*!
2883 \fn const char* QPMMime::mimeFor(ulong cf)
2884
2885 Returns the MIME type used for OS/2 PM Clipboard format \a cf, or
2886 0 if this convertor does not support \a cf.
2887
2888 All subclasses must reimplement this pure virtual function.
2889*/
2890
2891/*!
2892 \fn ulong QPMMime::cfFor(const char* mime)
2893
2894 Returns the OS/2 PM Clipboard type used for MIME type \a mime, or
2895 0 if this convertor does not support \a mime.
2896
2897 All subclasses must reimplement this pure virtual function.
2898*/
2899
2900/*!
2901 \fn QByteArray QPMMime::convertToMime( QByteArray data, const char* mime, ulong cf )
2902
2903 Returns \a data converted from OS/2 PM Clipboard format \a cf
2904 to MIME type \a mime.
2905
2906 Note that OS/2 PM Clipboard formats must all be self-terminating. The
2907 input \a data may contain trailing data. If flFor(ulong) for the given \a cf
2908 has the \c CFI_HANDLE flag set, then first 4 bytes of \a data represent a
2909 valid OS/2 handle of the appropriate type, otherwise \a data contains data
2910 itself.
2911
2912 All subclasses must reimplement this pure virtual function.
2913*/
2914
2915/*!
2916 \fn QByteArray QPMMime::convertFromMime( QByteArray data, const char* mime, ulong cf )
2917
2918 Returns \a data converted from MIME type \a mime
2919 to OS/2 PM Clipboard format \a cf.
2920
2921 Note that OS/2 PM Clipboard formats must all be self-terminating. The
2922 return value may contain trailing data. If flFor(ulong) for the given \a cf
2923 has the \c CFI_HANDLE flag set, then first 4 bytes of the returned array
2924 must represent a valid OS/2 handle of the appropriate type, otherwise the array
2925 contains data itself.
2926
2927 All subclasses must reimplement this pure virtual function.
2928*/
2929
2930/*!
2931 \class QPMMime::DragWorker
2932
2933 \internal
2934 This class is responsible for the sources's part of the Direct
2935 Manipulation conversation after the drop event occurs on a target.
2936
2937 Drag workers can be super exclusive (solely responsible for converting the
2938 given mime type to a set of DRAGITEM structures), exclusive (cannot coexist
2939 with other workers but don't manage the DRAGINFO/DRAGITEM creation), or
2940 or cooperative (can coexist with other drag and share the same set of DRAGITEM
2941 structures in order to represent different mime data types). As opposed to
2942 super exclusive workers (identified by isExclusive() returning TRUE and by
2943 itemCount() returning zero), exclusive and cooperative workers do not
2944 create DRAGINFO/DRAGITEM structures on their own, they implement a subset
2945 of methods that is used by the drag manager to fill drag structures it creates.
2946
2947 If a super exlusive or an exclusive worker is encoundered when starting the
2948 object drag, it will be used only if there are no any other workers found for
2949 \b other mime types of the object being dragged. If a cooperative worker
2950 with item count greater than one is encountered, it will be used only if all
2951 other found workers are also cooperative and require the same number of items.
2952 In both cases, if the above conditions are broken, the respective workers
2953 are discarded (ignored). Instead, a special fall-back cooperative worker
2954 (that requires a single DRAGITEM, supports any mime type and can coexist with
2955 other one-item cooperative workers) will be used for the given mime type.
2956
2957 \note Subclasses must NOT free the DRAGINFO structure they allocated and
2958 returned by createDragInfo().
2959 \note Every exclusive drag worker must implement createDragInfo() and must not
2960 implement composeFormatSting()/canRender()/prepare()/defaultFileType(). And
2961 vice versa, every cooperative drag worker must implement the latter three
2962 functions but not the former two.
2963 \note The return value of cleanup() is whether the Move operation is
2964 disallowed by this worker or not (if the worker doesn't participate in the
2965 DND session, it should return FALSE, to let other workers allow Move).
2966 \note If you need more details, please contact the developers of Qt for OS/2.
2967*/
2968
2969/*!
2970 \class QPMMime::DefaultDragWorker
2971
2972 \internal
2973 This class is a Drag Worker that supports standard DRM_SHAREDMEM and
2974 DRM_OS2FILE and rendering mechanisms. It uses
2975 QPMMime::DefaultDragWorker::Provider subclasses to map mime types of the
2976 object being dragged to rendering formats and apply preprocessing of data
2977 before rendering.
2978
2979 \note If you need more details, please contact the developers of Qt for OS/2.
2980*/
2981
2982/*!
2983 \class QPMMime::DropWorker
2984
2985 \internal
2986 This class is responsible for the target's part of the Direct
2987 Manipulation conversation after the drop event occurs on a target.
2988
2989 Drop workers can be exclusive (solely responsible for converting the given
2990 set of DRAGITEM structures) or cooperative (can coexist with other drop
2991 workers in order to produce different mime data types from the same set of
2992 DRAGITEM structures). If an exclusive drop worker is encountered when
2993 processing the drop event, all other workers are silently ignored.
2994
2995 \note Subclasses must NOT free the DRAGINFO structure pointed to by info().
2996 \note Subclasses must NOT send DM_ENDCONVERSATION to the source.
2997 \note If you need more details, please contact the developers of Qt for OS/2.
2998*/
2999
3000/*!
3001 \class QPMMime::DefaultDropWorker
3002
3003 \internal
3004 This class is a Drop Worker that supports standard DRM_SHAREDMEM and
3005 DRM_OS2FILE and rendering mechanisms. It uses
3006 QPMMime::DefaultDropWorker::Provider subclasses to map various rendering
3007 formats to particular mime types and apply postprocessing of data after
3008 rendering.
3009
3010 \note If you need more details, please contact the developers of Qt for OS/2.
3011*/
3012
3013#endif // QT_NO_MIME
Note: See TracBrowser for help on using the repository browser.