source: trunk/src/kernel/qdnd_pm.cpp@ 114

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

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

  • Property svn:eol-style set to native
File size: 38.6 KB
Line 
1/****************************************************************************
2** $Id$
3**
4** Implementation of the PM direct manipulation protocol (drag and drop) for Qt.
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 "qapplication.h"
39
40#ifndef QT_NO_DRAGANDDROP
41
42#include "qapplication_p.h"
43#include "qeventloop.h"
44#include "qpainter.h"
45#include "qwidget.h"
46#include "qdragobject.h"
47#include "qimage.h"
48#include "qbuffer.h"
49#include "qdatastream.h"
50#include "qbitmap.h"
51#include "qptrlist.h"
52#include "qt_os2.h"
53
54#if defined(QT_ACCESSIBILITY_SUPPORT)
55#include "qaccessible.h"
56#endif
57
58// if you enable this, it makes sense to enable it in qmime_pm.cpp as well
59//#define QT_DEBUG_DND
60
61extern void qt_pmMouseButtonUp(); // defined in qapplication_pm.cpp
62
63bool QDragManager::eventFilter( QObject *, QEvent *)
64{
65 // not used in the PM implementation
66 return FALSE;
67}
68
69void QDragManager::timerEvent( QTimerEvent* )
70{
71 // not used in the PM implementation
72}
73
74void QDragManager::cancel( bool deleteSource /* = TRUE */ )
75{
76 // Note: currently, this method is called only by
77 // QDragObject::~QDragObject() with deleteSource = FALSE
78
79 Q_ASSERT( object && !beingCancelled );
80 if ( !object || beingCancelled )
81 return;
82
83 beingCancelled = TRUE;
84
85 dragSource = 0;
86
87 if ( !deleteSource ) {
88 object = 0;
89 } else {
90 QDragObject *obj = object;
91 object = 0;
92 delete obj;
93 }
94
95#ifndef QT_NO_CURSOR
96 if ( restoreCursor ) {
97 QApplication::restoreOverrideCursor();
98 restoreCursor = FALSE;
99 }
100#endif
101#if defined(QT_ACCESSIBILITY_SUPPORT)
102 QAccessible::updateAccessibility( this, 0, QAccessible::DragDropEnd );
103#endif
104}
105
106void QDragManager::move( const QPoint & )
107{
108 // not used in the PM implementation
109}
110
111
112void QDragManager::drop()
113{
114 // not used in the PM implementation
115}
116
117/** \internal
118 * Data for QDragEnterEvent/QDragMoveEvent/QPMDropEvent.
119 */
120class QPMDragData
121{
122public:
123 QPMDragData()
124 : initialized( FALSE ), dropped( FALSE )
125 , gotWorkers( FALSE ), di( NULL )
126 {}
127
128 ~QPMDragData() { reset(); }
129
130 void initialize( DRAGINFO *di );
131 void reset( bool isAccepted, bool isActAccepted );
132 void reset() { reset( FALSE, FALSE ); }
133
134 void setDropped( bool d ) { dropped = d; }
135 bool isDropped() const { return dropped; }
136
137 DRAGINFO *info() const { return di; }
138
139 bool provides( const char *format );
140 const char *format( int fn );
141 QByteArray encodedData( const char *format );
142
143private:
144
145 void initWorkers();
146
147 bool initialized : 1;
148 bool dropped : 1;
149 bool gotWorkers : 1;
150
151 DRAGINFO *di;
152 QPtrList<QPMMime::DropWorker> workers;
153};
154
155void QPMDragData::initialize( DRAGINFO *info )
156{
157 Q_ASSERT( info );
158 if ( initialized || !info )
159 return;
160
161 initialized = TRUE;
162 di = info;
163}
164
165void QPMDragData::initWorkers()
166{
167 Q_ASSERT( initialized );
168 if ( !initialized || gotWorkers )
169 return;
170
171 gotWorkers = TRUE;
172
173 // go through all convertors and collect DropWorkers to use
174 QPtrList<QPMMime> mimes = QPMMime::all();
175 for ( QPMMime *mime = mimes.first(); mime; mime = mimes.next() ) {
176 QPMMime::DropWorker *wrk = mime->dropWorkerFor( di );
177 if ( wrk ) {
178 if ( wrk->isExclusive() ) {
179 // ignore all other workers if some of them identified itself
180 // as exclusive
181 workers.clear();
182 workers.append( wrk );
183 break;
184 }
185 // ensure there are no duplicates
186 if ( !workers.containsRef( wrk ) )
187 workers.append( wrk );
188 }
189 }
190
191#if defined(QT_DEBUG_DND)
192 qDebug( "QPMDragData: %d drop workers for DRAGINFO %p",
193 workers.count(), di );
194#endif
195
196 // init all workers
197 for ( QPMMime::DropWorker *w = workers.first(); w; w = workers.next() ) {
198 w->nfo = di;
199 w->init();
200 }
201}
202
203void QPMDragData::reset( bool isAccepted, bool isActAccepted )
204{
205 if ( !initialized )
206 return;
207
208 // cleanup all workers
209 for ( QPMMime::DropWorker *w = workers.first(); w; w = workers.next() ) {
210 w->cleanup( isAccepted, isActAccepted );
211 w->nfo = NULL;
212 }
213
214 workers.clear();
215 di = NULL;
216 initialized = dropped = gotWorkers = FALSE;
217}
218
219bool QPMDragData::provides( const char *format )
220{
221 if ( !gotWorkers )
222 initWorkers();
223
224 bool result = FALSE;
225
226 for ( QPMMime::DropWorker *w = workers.first();
227 w && !result; w = workers.next() )
228 result = w->provides( format );
229
230 return result;
231}
232
233const char *QPMDragData::format( int fn )
234{
235 if ( !gotWorkers )
236 initWorkers();
237
238 for ( QPMMime::DropWorker *w = workers.first();
239 w; w = workers.next() ) {
240 int count = w->formatCount();
241 if ( fn < count )
242 return w->format( fn );
243 fn = fn - count;
244 }
245
246 return NULL;
247}
248
249QByteArray QPMDragData::encodedData( const char *format )
250{
251 if ( !gotWorkers )
252 initWorkers();
253
254 QByteArray result;
255
256 for ( QPMMime::DropWorker *w = workers.first();
257 w && result.isNull(); w = workers.next() )
258 result = w->encodedData( format );
259
260 return result;
261}
262
263class QPMDropEvent : public QDropEvent
264{
265public:
266 QPMDropEvent( const QPoint &pos, QPMDragData *data )
267 : QDropEvent( pos ) { d = data; }
268
269 inline bool isTrulyAccepted() const { return accpt; }
270 inline bool isActionTrulyAccepted() const { return accptact; }
271};
272
273class QPMDragEnterEvent : public QDragEnterEvent
274{
275public:
276 QPMDragEnterEvent( const QPoint &pos, QPMDragData *data )
277 : QDragEnterEvent( pos ) { d = data; }
278
279 inline bool isTrulyAccepted() const { return accpt; }
280 inline bool isActionTrulyAccepted() const { return accptact; }
281};
282
283class QPMDragMoveEvent : public QDragMoveEvent
284{
285public:
286 QPMDragMoveEvent( const QPoint &pos, QPMDragData *data )
287 : QDragMoveEvent( pos ) { d = data; }
288
289 inline bool isTrulyAccepted() const { return accpt; }
290 inline bool isActionTrulyAccepted() const { return accptact; }
291};
292
293bool QDropEvent::provides( const char* mimeType ) const
294{
295 QPMDragData *data = static_cast<QPMDragData *> (d);
296 Q_ASSERT( data );
297 if ( !data )
298 return FALSE;
299
300 return data->provides( mimeType );
301}
302
303const char *QDropEvent::format( int fn ) const
304{
305 QPMDragData *data = static_cast<QPMDragData *> (d);
306 Q_ASSERT( data );
307 if ( !data )
308 return NULL;
309
310 return data->format( fn );
311}
312
313QByteArray QDropEvent::encodedData( const char *format ) const
314{
315 QByteArray array;
316
317 QPMDragData *data = static_cast<QPMDragData *> (d);
318 Q_ASSERT( data );
319
320 if ( !data || !data->isDropped() ) {
321 // This is a QDragEnterEvent/QDragMoveEvent subclass; we cannot provide
322 // any MIME contents yet because it's impossible to do data transfers
323 // before DM_DROP is sent. Return shortly.
324 return array;
325 }
326
327 array = data->encodedData( format );
328 return array;
329}
330
331/*!
332 * \internal
333 * Direct manipulation (Drag & Drop) event handler
334 */
335MRESULT qt_dispatchDragAndDrop( QWidget *widget, const QMSG &qmsg )
336{
337 static HWND lastDragOverHwnd = 0; // last target window
338 static USHORT lastDragOverOp = 0; // last DM_DRAGOVER operation
339 static USHORT lastRealOp = 0; // last real op (never DO_DEFAULT or DO_UNKNOWN)
340 static USHORT defaultOp = 0; // default op for DO_DEFAULT or DO_UNKNOWN
341
342 static USHORT supportedOps = 0; // operations supported by all items
343 static bool sourceAllowsOp = FALSE; // does source allow requested operation
344
345 static bool lastAccept = FALSE; // last reply from the target
346 static bool lastAcceptAction = FALSE; // last action reply from the target
347
348 static QPMDragData dragData;
349
350 Q_ASSERT( widget );
351
352 BOOL ok = FALSE;
353
354 switch( qmsg.msg ) {
355 case DM_DRAGOVER: {
356 bool first = lastDragOverHwnd != qmsg.hwnd;
357 if ( first ) {
358 // the first DM_DRAGOVER message
359 lastDragOverHwnd = qmsg.hwnd;
360 lastAccept = lastAcceptAction = FALSE;
361 supportedOps = DO_COPYABLE | DO_MOVEABLE | DO_LINKABLE;
362 // ensure drag data is reset (just in case of a wrong msg flow...)
363 dragData.reset();
364 }
365
366 Q_ASSERT( first || widget->acceptDrops() );
367 if ( !widget->acceptDrops() ) {
368 if ( !first ) {
369 // Odin32 apps are dramatically bogus, they continue to send
370 // DM_DRAGOVER even if we reply DOR_NEVERDROP. Simulate
371 // DM_DRAGLEAVE
372 lastDragOverHwnd = 0;
373 dragData.reset();
374 }
375 return MRFROM2SHORT( DOR_NEVERDROP, 0 );
376 }
377
378 DRAGINFO *info = (DRAGINFO *) qmsg.mp1;
379 ok = DrgAccessDraginfo( info );
380 Q_ASSERT( ok && info );
381 if ( !ok || !info )
382 return MRFROM2SHORT( DOR_NEVERDROP, 0 );
383
384 USHORT dropReply = DOR_DROP;
385
386 if ( first ) {
387 // determine the set of operations supported by *all* items
388 // (this implies that DRAGITEM::fsSupportedOps is a bit field)
389 ULONG itemCount = DrgQueryDragitemCount( info );
390 for ( ULONG i = 0; i < itemCount; ++ i ) {
391 PDRAGITEM item = DrgQueryDragitemPtr( info, i );
392 Q_ASSERT( item );
393 if ( !item ) {
394 dropReply = DOR_NEVERDROP;
395 break;
396 }
397 supportedOps &= item->fsSupportedOps;
398 }
399 if ( dropReply != DOR_NEVERDROP ) {
400 Q_ASSERT( itemCount );
401 if ( !itemCount || !supportedOps ) {
402 // items don't have even a single common operation...
403 dropReply = DOR_NEVERDROP;
404 } else {
405 // determine the default operation
406 // (in order MOVE, COPY, LINK, for compatibility with Win32)
407 if ( supportedOps & DO_MOVEABLE ) defaultOp = DO_MOVE;
408 else if ( supportedOps & DO_COPYABLE ) defaultOp = DO_COPY;
409 else if ( supportedOps & DO_LINKABLE ) defaultOp = DO_LINK;
410 else Q_ASSERT( FALSE ); // should never happen
411 }
412 }
413 }
414
415 if ( dropReply != DOR_NEVERDROP ) {
416
417 if ( first || lastDragOverOp != info->usOperation ) {
418 // the current drop operation was changed by a modifier key
419 lastDragOverOp = info->usOperation;
420 lastRealOp = info->usOperation;
421 if ( lastRealOp == DO_DEFAULT || lastRealOp == DO_UNKNOWN ) {
422 // the default operation is requested
423 lastRealOp = defaultOp;
424 }
425 sourceAllowsOp =
426 ((supportedOps & DO_MOVEABLE) && lastRealOp == DO_MOVE) ||
427 ((supportedOps & DO_COPYABLE) && lastRealOp == DO_COPY) ||
428 ((supportedOps & DO_LINKABLE) && lastRealOp == DO_LINK);
429 }
430
431 // Note that if sourceAllowsOp = false here, we have to deliver
432 // events anyway (stealing them from Qt would be confusing), but
433 // we will silently ignore any accept commands and always reject
434 // the drop. Other platforms seem to do the same.
435
436 // convert the operation to an Action
437 QDropEvent::Action action = QDropEvent::Copy;
438 if ( lastRealOp == DO_COPY ) action = QDropEvent::Copy;
439 else if ( lastRealOp == DO_MOVE ) action = QDropEvent::Move;
440 else if ( lastRealOp == DO_LINK ) action = QDropEvent::Link;
441 else Q_ASSERT( FALSE ); // should never happen
442
443 // flip y coordinate
444 QPoint pnt( info->xDrop, info->yDrop );
445 pnt.setY( QApplication::desktop()->height() - (pnt.y() + 1) );
446 pnt = widget->mapFromGlobal( pnt );
447
448 // initialize drag data used in QPMDrag.../QPMDrop... events
449 if ( first )
450 dragData.initialize( info );
451
452 QDropEvent *de = NULL;
453 QPMDragEnterEvent *dee = NULL;
454 QPMDragMoveEvent *dme = NULL;
455
456 if ( first )
457 de = dee = new QPMDragEnterEvent( pnt, &dragData );
458 else
459 de = dme = new QPMDragMoveEvent( pnt, &dragData );
460
461 de->setAction( action );
462 de->accept( lastAccept );
463 de->acceptAction( lastAcceptAction );
464
465 QApplication::sendEvent( widget, de );
466
467 // if not allowed or not accepted, always reply DOR_NODROP
468 // to have DM_DRAGOVER delivered to us again in any case
469
470 dropReply = sourceAllowsOp && de->isAccepted() ?
471 DOR_DROP : DOR_NODROP;
472
473 lastAccept = dee ? dee->isTrulyAccepted() :
474 dme->isTrulyAccepted();
475 lastAcceptAction = dee ? dee->isActionTrulyAccepted() :
476 dme->isActionTrulyAccepted();
477
478 delete de;
479 }
480
481 DrgFreeDraginfo( info );
482
483 return MRFROM2SHORT( dropReply, lastRealOp );
484 }
485 case DM_DRAGLEAVE: {
486 // Odin32 apps produce incorrect message flow, ignore
487 Q_ASSERT( lastDragOverHwnd != 0 );
488 if ( lastDragOverHwnd == 0 )
489 return 0;
490
491 lastDragOverHwnd = 0;
492 dragData.reset();
493
494 if ( !widget->acceptDrops() )
495 return 0;
496
497 QDragLeaveEvent de;
498 QApplication::sendEvent( widget, &de );
499 return 0;
500 }
501 case DM_DROP: {
502 // Odin32 apps produce incorrect message flow, ignore
503 Q_ASSERT( lastDragOverHwnd != 0 );
504 if ( lastDragOverHwnd == 0 )
505 return 0;
506
507 // Odin32 apps send DM_DROP even if we replied DOR_NEVERDROP or
508 // DOR_NODROP, simulate DM_DRAGLEAVE
509 Q_ASSERT( lastAccept || lastAcceptAction );
510 if ( !lastAccept && !lastAcceptAction ) {
511 WinSendMsg( qmsg.hwnd, DM_DRAGLEAVE, 0, 0 );
512 return 0;
513 }
514
515 lastDragOverHwnd = 0;
516
517 Q_ASSERT( widget->acceptDrops() );
518 if ( !widget->acceptDrops() )
519 return 0;
520
521 DRAGINFO *info = (DRAGINFO *) qmsg.mp1;
522 ok = DrgAccessDraginfo( info );
523 Q_ASSERT( ok && info );
524 if ( !ok || !info )
525 return MRFROM2SHORT( DOR_NEVERDROP, 0 );
526
527 Q_ASSERT( lastRealOp == info->usOperation );
528
529 // convert the operation to an Action
530 QDropEvent::Action action = QDropEvent::Copy;
531 if ( lastRealOp == DO_COPY ) action = QDropEvent::Copy;
532 else if ( lastRealOp == DO_MOVE ) action = QDropEvent::Move;
533 else if ( lastRealOp == DO_LINK ) action = QDropEvent::Link;
534 else Q_ASSERT( FALSE ); // should never happen
535
536 // flip y coordinate
537 QPoint pnt( info->xDrop, info->yDrop );
538 pnt.setY( QApplication::desktop()->height() - (pnt.y() + 1) );
539
540 dragData.setDropped( TRUE );
541
542 QPMDropEvent de( widget->mapFromGlobal( pnt ), &dragData );
543 de.setAction( action );
544 de.accept( lastAccept );
545 de.acceptAction( lastAcceptAction );
546
547 QApplication::sendEvent( widget, &de );
548
549 dragData.reset( de.isTrulyAccepted(), de.isActionTrulyAccepted() );
550
551 // If the target has accepted the particular Drop action (using
552 // acceptAction() rather than just accept()), it means that it will
553 // perform the necessary operation on its own (for example, will
554 // delete the source if the accepted action is Move). In this case,
555 // we always send DMFL_TARGETFAIL to the source to prevent it from
556 // doing the same on its side.
557
558 ULONG targetReply =
559 de.isTrulyAccepted() && !de.isActionTrulyAccepted() ?
560 DMFL_TARGETSUCCESSFUL : DMFL_TARGETFAIL;
561
562 // send DM_ENDCONVERSATION for every item
563 ULONG itemCount = DrgQueryDragitemCount( info );
564 for ( ULONG i = 0; i < itemCount; ++ i ) {
565 PDRAGITEM item = DrgQueryDragitemPtr( info, i );
566 Q_ASSERT( item );
567 if ( !item )
568 continue;
569 // it is possible that this item required DM_RENDERPREPARE but
570 // returned FALSE in reply to it (so hwndItem may be NULL)
571 if ( !item->hwndItem )
572 continue;
573 DrgSendTransferMsg( item->hwndItem, DM_ENDCONVERSATION,
574 MPFROMLONG( item->ulItemID ),
575 MPFROMLONG( targetReply ) );
576 }
577
578 DrgDeleteDraginfoStrHandles( info );
579 DrgFreeDraginfo( info );
580
581 return 0;
582 }
583 default:
584 break;
585 }
586
587 return WinDefWindowProc( qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2 );
588}
589
590class QPMCoopDragWorker : public QPMMime::DragWorker, public QPMObjectWindow
591{
592public:
593 QPMCoopDragWorker() : info( NULL ) {}
594 bool collectWorkers( QDragObject *o );
595
596 // DragpWorker interface
597 void init();
598 bool cleanup( bool isCancelled );
599 bool isExclusive() const { return TRUE; }
600 ULONG itemCount() const { return 0; }
601 HWND hwnd() const;
602 DRAGINFO *createDragInfo( const char *name, USHORT supportedOps );
603
604 // QPMObjectWindow interface
605 MRESULT message( ULONG msg, MPARAM mp1, MPARAM mp2 );
606
607private:
608 QPtrList<DragWorker> workers;
609 DRAGINFO *info;
610};
611
612bool QPMCoopDragWorker::collectWorkers( QDragObject *o )
613{
614 Q_ASSERT( o );
615 if ( !o )
616 return FALSE;
617
618 bool gotExcl = FALSE; // got isExclusive() worker?
619 bool skipExcl = FALSE; // skip isExclusive() or itemCount() > 1 workers?
620 ULONG coopLevel = 0; // itemCount() level for !isExclusive() workers
621
622 bool gotExclForMime = FALSE;
623
624 // go through all formats and all convertors to collect DragWorkers
625 const char *fmt = NULL;
626 for ( int i = 0; (fmt = o->format( i )); ++i ) {
627#if defined(QT_DEBUG_DND)
628 qDebug( "QPMCoopDragWorker: Searching for worker for mime [%s]...", fmt );
629#endif
630 QPtrList<QPMMime> mimes = QPMMime::all();
631 for ( QPMMime *mime = mimes.first(); mime; mime = mimes.next() ) {
632 QPMMime::DragWorker *wrk = mime->dragWorkerFor( fmt, o );
633 if ( !wrk )
634 continue;
635#if defined(QT_DEBUG_DND)
636 qDebug( "QPMCoopDragWorker: Got worker %p (isExclusive()=%d, "
637 "itemCount()=%ld) from convertor '%s' (gotExclForMime=%d, "
638 "gotExcl=%d, skipExcl=%d, coopLevel=%ld)",
639 wrk, wrk->isExclusive(), wrk->itemCount(),
640 mime->convertorName(), gotExclForMime, gotExcl, skipExcl,
641 coopLevel );
642#endif
643 if ( wrk->isExclusive() ) {
644 if ( !skipExcl && !gotExclForMime ) {
645 gotExclForMime = TRUE;
646 if ( !gotExcl ) {
647 gotExcl = TRUE;
648 workers.append( wrk );
649 } else {
650 // skip everything exclusive unless it's exactly the
651 // same worker
652 skipExcl = !workers.containsRef( wrk );
653 }
654 }
655 // continue to search for a fall-back cooperative 1-item worker
656 // (like QPMMimeAnyMime) for the case if this worker quits
657 // the game
658 continue;
659 }
660 ULONG itemCnt = wrk->itemCount();
661 if ( itemCnt == 0 ) {
662#if defined(QT_CHECK_RANGE)
663 qWarning( "Cooperative DragWorker %p for mime [%s] has "
664 "itemCount() = 0!", wrk, fmt );
665#endif
666 continue;
667 }
668 if ( itemCnt > 1 ) {
669 // coop workers with item count > 1 are also considered exclusive
670 // here, because may not co-exist with 1-item workers that should
671 // always be able to contribute
672 if ( !gotExcl && !skipExcl && !gotExclForMime ) {
673 gotExclForMime = TRUE;
674 workers.append( wrk );
675 if ( !coopLevel )
676 coopLevel = itemCnt;
677 // only those for the same number of items can proceed
678 if ( itemCnt != coopLevel )
679 skipExcl = TRUE;
680 }
681 // continue to search for a fall-back cooperative 1-item worker
682 // (like QPMMimeAnyMime) for the case if this worker quits
683 // the game
684 continue;
685 }
686 workers.append( wrk );
687 // Don't search for other workrers for the same mime type --
688 // we've already got a drag worker for it and adding another
689 // one would just introduce mime type duplicates on the drop
690 // target's side (where the first encountered drop worker
691 // for the given RMF would be used for actual data transfer
692 // anyway). See also QClipboard::setData().
693 break;
694 }
695 if ( gotExclForMime ) {
696 // ensure we have a fall-back coop (should be the last added item)
697 DragWorker *w = workers.getLast();
698 if ( w->isExclusive() || w->itemCount() > 1 ) {
699#if defined(QT_CHECK_STATE)
700 qWarning( "DragWorker %p for [%s] (isExclusive()=%d, itemCount()"
701 "=%ld) has no fall-back cooperative 1-item worker!",
702 w, fmt, w->isExclusive(), w->itemCount() );
703#endif
704 workers.removeLast();
705 }
706 gotExclForMime = FALSE;
707 } else {
708 // got a regular (non-fall-back) 1-item coop, skip evreything else
709 skipExcl = TRUE;
710 }
711 }
712
713 // remove either all exclusive workers or all their fall-back workers
714 // (depending on skipExcl) and remove duplicates
715 for ( DragWorker *wrk = workers.first(); wrk; ) {
716 bool excl = wrk->isExclusive() || wrk->itemCount() > 1;
717 if ( skipExcl == excl || workers.containsRef( wrk ) > 1 ) {
718 workers.remove();
719 wrk = workers.current();
720 } else {
721 wrk = workers.next();
722 }
723 }
724
725#if defined(QT_DEBUG_DND)
726 for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
727 qDebug( "QPMCoopDragWorker: Will use worker %p (isExclusive()=%d, "
728 "itemCount()=%ld)", wrk, wrk->isExclusive(), wrk->itemCount() );
729 }
730#endif
731
732 Q_ASSERT( workers.count() > 0 );
733 return workers.count() > 0;
734}
735
736HWND QPMCoopDragWorker::hwnd() const
737{
738 DragWorker *firstWorker = workers.getFirst();
739 Q_ASSERT( firstWorker );
740 if ( !firstWorker )
741 return 0;
742
743 if ( firstWorker->isExclusive() && firstWorker->itemCount() == 0 ) {
744 // this is a super exclusive worker that will do everything on its own
745 return firstWorker->hwnd();
746 }
747
748 return QPMObjectWindow::hwnd();
749}
750
751void QPMCoopDragWorker::init()
752{
753 Q_ASSERT( source() );
754 for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
755 wrk->src = source();
756 wrk->init();
757 }
758}
759
760bool QPMCoopDragWorker::cleanup( bool isCancelled )
761{
762 bool moveDisallowed = FALSE;
763
764 for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
765 // disallow the Move operation if at least one worker asked so
766 moveDisallowed |= wrk->cleanup( isCancelled );
767 wrk->src = NULL;
768 }
769 workers.clear();
770 info = NULL;
771 return moveDisallowed;
772}
773
774DRAGINFO *QPMCoopDragWorker::createDragInfo( const char *name, USHORT supportedOps )
775{
776 Q_ASSERT( !info );
777 if ( info )
778 return NULL;
779
780 DragWorker *firstWorker = workers.getFirst();
781 Q_ASSERT( firstWorker );
782 if ( !firstWorker )
783 return NULL;
784
785 ULONG itemCnt = firstWorker->itemCount();
786
787 if ( firstWorker->isExclusive() && itemCnt == 0 ) {
788 // this is a super exclusive worker that will do everything on its own
789#if defined(QT_DEBUG_DND)
790 qDebug( "QPMCoopDragWorker: Will redirect to super worker %p",
791 firstWorker );
792#endif
793 return firstWorker->createDragInfo( name, supportedOps );
794 }
795
796 // note that all workers at this place require the same amount of items
797 // (guaranteed by collectWorkers())
798
799#if defined(QT_DEBUG_DND)
800 qDebug( "QPMCoopDragWorker: itemCnt=%ld", itemCnt );
801#endif
802
803 info = DrgAllocDraginfo( itemCnt );
804 Q_ASSERT( info );
805 if ( !info )
806 return NULL;
807
808 // collect all mechanism/format pairs
809 QCString allFormats;
810 for ( DragWorker *wrk = workers.first(); wrk; wrk = workers.next() ) {
811 QCString formats = wrk->composeFormatString();
812 Q_ASSERT( !formats.isNull() );
813 if ( !formats.isNull() ) {
814 if ( allFormats.isNull() )
815 allFormats = formats;
816 else {
817 allFormats += ",";
818 allFormats += formats;
819 }
820 }
821 }
822
823#if defined(QT_DEBUG_DND)
824 qDebug( "QPMCoopDragWorker: allFormats=%s", allFormats.data() );
825#endif
826
827 static ULONG itemID = 0;
828
829 const char *type = NULL;
830 const char *ext = NULL;
831 firstWorker->defaultFileType( type, ext );
832
833 bool ok = TRUE;
834 for ( ULONG i = 0; i < itemCnt; ++i ) {
835 DRAGITEM *item = DrgQueryDragitemPtr( info, i );
836 Q_ASSERT( item );
837 if ( !item ) {
838 ok = FALSE;
839 break;
840 }
841
842 QCString targetName;
843 if ( itemCnt == 1 ) targetName = name;
844 else targetName.sprintf( "%s %ld", name, i + 1 );
845
846 if ( ext ) {
847 targetName += ".";
848 targetName += ext;
849 }
850
851#if defined(QT_DEBUG_DND)
852 qDebug( "QPMCoopDragWorker: item %ld: type=[%s], name=\"%s\"",
853 i, type, targetName.data() );
854#endif
855
856 // Note 1: DRAGITEM::hstrType is actually ignored by WPS,
857 // only the target extension matters.
858
859 // Note 2: We're not required to fill in the hwndItem field because we
860 // use the DC_PREPARE flag (to indicate it will be filled later, after
861 // DM_RENDERPREPARE); however, Mozilla refuses to render if hwndItem
862 // is initially 0. Set it to our HWND instead (we'll issue a warning if
863 // DM_RENDER or DM_ENDCONVERSATION is erroneously sent to us)
864
865 item->hwndItem = hwnd();
866 item->ulItemID = itemID ++;
867 item->hstrType = DrgAddStrHandle( type ? type : DRT_UNKNOWN );
868 item->hstrRMF = DrgAddStrHandle( allFormats );
869 item->hstrContainerName = 0;
870 item->hstrSourceName = 0;
871 item->hstrTargetName = DrgAddStrHandle( targetName );
872 item->cxOffset = 0;
873 item->cyOffset = 0;
874 item->fsControl = DC_PREPARE; // require DM_RENDERPREPARE from target
875 item->fsSupportedOps = supportedOps;
876 }
877
878 if ( !ok ) {
879 DrgFreeDraginfo( info );
880 info = NULL;
881 }
882
883 return info;
884}
885
886extern void qt_DrgFreeDragtransfer( DRAGTRANSFER *xfer ); // defined in qmime_pm.cpp
887
888MRESULT QPMCoopDragWorker::message( ULONG msg, MPARAM mp1, MPARAM mp2 )
889{
890 if ( msg == DM_RENDERPREPARE ) {
891 if ( !info ) {
892#if defined(QT_CHECK_STATE)
893 qWarning( "Drop target sent DM_RENDERPREPARE after the DnD session "
894 "is over!" );
895#endif
896 // free the given DRAGTRANSFER structure to avoud memory leak
897 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
898 if ( xfer )
899 qt_DrgFreeDragtransfer( xfer );
900 return (MRESULT) FALSE;
901 }
902
903 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
904 Q_ASSERT( xfer && xfer->pditem );
905 if ( !xfer || !xfer->pditem )
906 return (MRESULT) FALSE;
907
908 // find the item's index (ordinal number)
909 ULONG itemCnt = DrgQueryDragitemCount( info );
910 ULONG index = 0;
911 for ( ; index < itemCnt; ++index )
912 if ( DrgQueryDragitemPtr( info, index ) == xfer->pditem )
913 break;
914
915 Q_ASSERT( index < itemCnt );
916 if ( index >= itemCnt )
917 return (MRESULT) FALSE;
918
919#if defined(QT_DEBUG_DND)
920 qDebug( "QPMCoopDragWorker: Got DM_RENDERPREPARE to %s for item %ld "
921 "(id=%ld)",
922 QPMMime::queryHSTR( xfer->hstrSelectedRMF ).data(),
923 index, xfer->pditem->ulItemID );
924#endif
925
926 QCString drm, drf;
927 if ( !QPMMime::parseRMF( xfer->hstrSelectedRMF, drm, drf ) ) {
928 Q_ASSERT( FALSE );
929 return (MRESULT) FALSE;
930 }
931
932 DragWorker *wrk = NULL;
933 for ( wrk = workers.first(); wrk; wrk = workers.next() )
934 if ( wrk->prepare( drm, drf, xfer->pditem, index ) )
935 break;
936 if ( !wrk ) {
937#if defined(QT_DEBUG_DND)
938 qDebug( "QPMCoopDragWorker: No suitable worker found" );
939#endif
940 return (MRESULT) FALSE;
941 }
942
943 xfer->pditem->hwndItem = wrk->hwnd();
944 Q_ASSERT( xfer->pditem->hwndItem );
945 return (MRESULT) ( xfer->pditem->hwndItem ? TRUE : FALSE );
946 }
947
948 if ( msg == DM_RENDER || msg == DM_ENDCONVERSATION ) {
949#if defined(QT_CHECK_STATE)
950 qWarning( "Drop target sent DM_RENDER or DM_ENDCONVERSATION to the "
951 "drag source window instead of the drag item window!" );
952#endif
953 if ( msg == DM_RENDER ) {
954 // free the given DRAGTRANSFER structure to avoud memory leak
955 DRAGTRANSFER *xfer = (DRAGTRANSFER *) mp1;
956 if ( xfer )
957 qt_DrgFreeDragtransfer( xfer );
958 }
959 }
960
961 return (MRESULT) FALSE;
962}
963
964bool QDragManager::drag( QDragObject *o, QDragObject::DragMode mode )
965{
966 // return shortly if drag is already in progress or being cancelled
967 if ( object || beingCancelled )
968 return FALSE;
969
970 QDragObject::setTarget( 0 );
971
972 // detect a mouse button to end dragging
973 LONG vkTerminate = 0;
974 {
975 ULONG msg = WinQuerySysValue( HWND_DESKTOP, SV_BEGINDRAG ) & 0xFFFF;
976 switch( msg ) {
977 case WM_BUTTON1MOTIONSTART: vkTerminate = VK_BUTTON1; break;
978 case WM_BUTTON2MOTIONSTART: vkTerminate = VK_BUTTON2; break;
979 case WM_BUTTON3MOTIONSTART: vkTerminate = VK_BUTTON3; break;
980 }
981
982 if ( WinGetKeyState( HWND_DESKTOP, vkTerminate ) & 0x8000 ) {
983 // prefer the default button if it is pressed
984 } else if ( WinGetKeyState( HWND_DESKTOP, VK_BUTTON2 ) & 0x8000 ) {
985 vkTerminate = VK_BUTTON2;
986 } else if ( WinGetKeyState( HWND_DESKTOP, VK_BUTTON1 ) & 0x8000 ) {
987 vkTerminate = VK_BUTTON1;
988 } else if ( WinGetKeyState( HWND_DESKTOP, VK_BUTTON3 ) & 0x8000 ) {
989 vkTerminate = VK_BUTTON3;
990 } else {
991 vkTerminate = 0;
992 }
993 }
994
995 if ( !vkTerminate ) {
996#if defined(QT_CHECK_STATE)
997 qWarning( "QDragManager: No valid mouse button pressed, "
998 "dragging cancelled!" );
999#endif
1000 delete o;
1001 return FALSE;
1002 }
1003
1004 USHORT supportedOps = 0;
1005
1006 switch ( mode ) {
1007 case QDragObject::DragDefault:
1008 supportedOps = DO_MOVEABLE | DO_COPYABLE | DO_LINKABLE;
1009 break;
1010 case QDragObject::DragMove:
1011 supportedOps = DO_MOVEABLE;
1012 break;
1013 case QDragObject::DragCopy:
1014 supportedOps = DO_COPYABLE;
1015 break;
1016 case QDragObject::DragCopyOrMove:
1017 supportedOps = DO_MOVEABLE | DO_COPYABLE;
1018 break;
1019 case QDragObject::DragLink:
1020 supportedOps = DO_LINKABLE;
1021 break;
1022 }
1023
1024 static QPMCoopDragWorker dragWorker;
1025
1026 bool ok = dragWorker.collectWorkers( o );
1027 Q_ASSERT( ok );
1028 Q_ASSERT( dragWorker.hwnd() );
1029 if ( !ok || ! dragWorker.hwnd() ) {
1030 delete o;
1031 return FALSE;
1032 }
1033
1034 object = o;
1035 dragSource = (QWidget *) (object->parent());
1036
1037 dragWorker.src = o;
1038 dragWorker.init();
1039 DRAGINFO *info = dragWorker.createDragInfo( o->name(), supportedOps );
1040
1041 Q_ASSERT( info );
1042 if ( !info ) {
1043 dragWorker.cleanup( TRUE /* isCancelled */);
1044 dragWorker.src = NULL;
1045 dragSource = 0;
1046 object = 0;
1047 delete o;
1048 return FALSE;
1049 }
1050
1051#if defined(QT_ACCESSIBILITY_SUPPORT)
1052 QAccessible::updateAccessibility( this, 0, QAccessible::DragDropStart );
1053#endif
1054
1055/// @todo (dmik)
1056// updatePixmap();
1057
1058 DRAGIMAGE img;
1059 img.cb = sizeof (DRAGIMAGE);
1060 img.hImage = WinQuerySysPointer( HWND_DESKTOP, SPTR_FILE, FALSE );
1061 img.fl = DRG_ICON;
1062 img.cxOffset = 0;
1063 img.cyOffset = 0;
1064
1065 // the mouse is most likely captured by Qt at this point, uncapture it
1066 // or DrgDrag() will definitely fail
1067 WinSetCapture( HWND_DESKTOP, 0 );
1068
1069 HWND target = DrgDrag( dragWorker.hwnd(), info, &img, 1, vkTerminate,
1070 (PVOID) 0x80000000L ); // don't lock the desktop PS
1071
1072#if defined(QT_DEBUG_DND)
1073 qDebug( "QDragManager: DrgDrag() returned %08lX (err=0x%08lX)",
1074 target, WinGetLastError( 0 ) );
1075#endif
1076
1077 // we won't get any mouse release event, so manually adjust qApp state
1078 qt_pmMouseButtonUp();
1079
1080 bool moveDisallowed = dragWorker.cleanup( beingCancelled || target == 0 );
1081 dragWorker.src = NULL;
1082
1083 moveDisallowed |= beingCancelled || target == 0 ||
1084 info->usOperation != DO_MOVE;
1085
1086#if defined(QT_DEBUG_DND)
1087 qDebug( "QDragManager: moveDisallowed=%d", moveDisallowed );
1088#endif
1089
1090 if ( target == 0 )
1091 DrgDeleteDraginfoStrHandles( info );
1092 DrgFreeDraginfo( info );
1093
1094 if ( !beingCancelled ) {
1095 QDragObject::setTarget( QWidget::find( target ) );
1096 cancel(); // this will delete o (object)
1097 }
1098
1099 beingCancelled = FALSE;
1100
1101/// @todo (dmik)
1102// updatePixmap();
1103
1104 return !moveDisallowed;
1105}
1106
1107void QDragManager::updatePixmap()
1108{
1109 /// @todo (dmik) later
1110
1111/// @todo (dmik) remove
1112// if ( object ) {
1113// if ( cursor ) {
1114//#ifndef Q_OS_TEMP
1115// for ( int i=0; i<n_cursor; i++ ) {
1116// DestroyCursor(cursor[i]);
1117// }
1118//#endif
1119// delete [] cursor;
1120// cursor = 0;
1121// }
1122//
1123// QPixmap pm = object->pixmap();
1124// if ( pm.isNull() ) {
1125// // None.
1126// } else {
1127// cursor = new HCURSOR[n_cursor];
1128// QPoint pm_hot = object->pixmapHotSpot();
1129// for (int cnum=0; cnum<n_cursor; cnum++) {
1130// QPixmap cpm = pm_cursor[cnum];
1131//
1132// int x1 = QMIN(-pm_hot.x(),0);
1133// int x2 = QMAX(pm.width()-pm_hot.x(),cpm.width());
1134// int y1 = QMIN(-pm_hot.y(),0);
1135// int y2 = QMAX(pm.height()-pm_hot.y(),cpm.height());
1136//
1137// int w = x2-x1+1;
1138// int h = y2-y1+1;
1139//
1140//#ifndef Q_OS_TEMP
1141// if ( qWinVersion() & WV_DOS_based ) {
1142// // Limited cursor size
1143// int reqw = GetSystemMetrics(SM_CXCURSOR);
1144// int reqh = GetSystemMetrics(SM_CYCURSOR);
1145// if ( reqw < w ) {
1146// // Not wide enough - move objectpm right
1147// pm_hot.setX(pm_hot.x()-w+reqw);
1148// }
1149// if ( reqh < h ) {
1150// // Not tall enough - move objectpm down
1151// pm_hot.setY(pm_hot.y()-h+reqh);
1152// }
1153// // Always use system cursor size
1154// w = reqw;
1155// h = reqh;
1156// }
1157//#endif
1158//
1159// QPixmap colorbits(w,h,-1,QPixmap::NormalOptim);
1160// {
1161// QPainter p(&colorbits);
1162// p.fillRect(0,0,w,h,color1);
1163// p.drawPixmap(QMAX(0,-pm_hot.x()),QMAX(0,-pm_hot.y()),pm);
1164// p.drawPixmap(QMAX(0,pm_hot.x()),QMAX(0,pm_hot.y()),cpm);
1165// }
1166//
1167// QBitmap maskbits(w,h,TRUE,QPixmap::NormalOptim);
1168// {
1169// QPainter p(&maskbits);
1170// if ( pm.mask() ) {
1171// QBitmap m(*pm.mask());
1172// m.setMask(m);
1173// p.drawPixmap(QMAX(0,-pm_hot.x()),QMAX(0,-pm_hot.y()),m);
1174// } else {
1175// p.fillRect(QMAX(0,-pm_hot.x()),QMAX(0,-pm_hot.y()),
1176// pm.width(),pm.height(),color1);
1177// }
1178// if ( cpm.mask() ) {
1179// QBitmap m(*cpm.mask());
1180// m.setMask(m);
1181// p.drawPixmap(QMAX(0,pm_hot.x()),QMAX(0,pm_hot.y()),m);
1182// } else {
1183// p.fillRect(QMAX(0,pm_hot.x()),QMAX(0,pm_hot.y()),
1184// cpm.width(),cpm.height(),
1185// color1);
1186// }
1187// }
1188//
1189// HBITMAP im = qt_createIconMask(maskbits);
1190// ICONINFO ii;
1191// ii.fIcon = FALSE;
1192// ii.xHotspot = QMAX(0,pm_hot.x());
1193// ii.yHotspot = QMAX(0,pm_hot.y());
1194// ii.hbmMask = im;
1195// ii.hbmColor = colorbits.hbm();
1196// cursor[cnum] = CreateIconIndirect(&ii);
1197// DeleteObject( im );
1198// }
1199// }
1200// }
1201}
1202
1203#endif // QT_NO_DRAGANDDROP
Note: See TracBrowser for help on using the repository browser.