source: trunk/src/gui/kernel/qdnd_pm.cpp@ 450

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

kernel: DnD: Made Copy operation work. Fixed several small bugs.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision Author Id
File size: 38.0 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** Copyright (C) 2009 netlabs.org. OS/2 parts.
7**
8** This file is part of the QtGui module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial Usage
12** Licensees holding valid Qt Commercial licenses may use this file in
13** accordance with the Qt Commercial License Agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and Nokia.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Nokia gives you certain
26** additional rights. These rights are described in the Nokia Qt LGPL
27** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
28** package.
29**
30** GNU General Public License Usage
31** Alternatively, this file may be used under the terms of the GNU
32** General Public License version 3.0 as published by the Free Software
33** Foundation and appearing in the file LICENSE.GPL included in the
34** packaging of this file. Please review the following information to
35** ensure the GNU General Public License version 3.0 requirements will be
36** met: http://www.gnu.org/copyleft/gpl.html.
37**
38** If you are unsure which license is appropriate for your use, please
39** contact the sales department at [email protected].
40** $QT_END_LICENSE$
41**
42****************************************************************************/
43
44#include "qapplication.h"
45
46#include "qapplication_p.h"
47#include "qevent.h"
48#include "qpainter.h"
49#include "qwidget.h"
50#include "qbuffer.h"
51#include "qdatastream.h"
52#include "qcursor.h"
53#include "qdesktopwidget.h"
54#include "qfile.h"
55#include "qdnd_p.h"
56#include "qdebug.h"
57
58#include "qt_os2.h"
59
60//#define QDND_DEBUG // in pair with qmime_pm.cpp
61
62#ifdef QDND_DEBUG
63# define DEBUG(a) qDebug a
64#else
65# define DEBUG(a) do {} while(0)
66#endif
67
68QT_BEGIN_NAMESPACE
69
70#if !defined(QT_NO_DRAGANDDROP) && !defined(QT_NO_CLIPBOARD)
71
72extern void qt_pmMouseButtonUp(); // defined in qapplication_pm.cpp
73extern void qt_DrgFreeDragtransfer(DRAGTRANSFER *xfer); // defined in qmime_pm.cpp
74
75#ifdef QDND_DEBUG
76extern QString dragActionsToString(Qt::DropActions actions);
77#endif
78
79/** \internal
80 * Data for QDragEnterEvent/QDragMoveEvent/QPMDropEvent.
81 */
82class QPMDragData
83{
84public:
85 QPMDragData();
86 ~QPMDragData();
87
88 void initialize(DRAGINFO *di);
89 void reset(bool isAccepted);
90 void reset() { reset(false); }
91
92 void setDropped(bool d) { dropped = d; }
93 bool isDropped() const { return dropped; }
94
95 DRAGINFO *info() const { return di; }
96
97 bool hasFormat_sys(const QString &mimeType);
98 QStringList formats_sys();
99 QVariant retrieveData_sys(const QString &mimeType,
100 QVariant::Type preferredType);
101
102private:
103 friend class QDragManager;
104
105 void initWorkers();
106
107 bool initialized : 1;
108 bool dropped : 1;
109 bool gotWorkers : 1;
110
111 DRAGINFO *di;
112 QList<QPMMime::DropWorker*> workers;
113
114 QWidget *lastDragOverWidget; // last target widget
115 USHORT lastDragOverOp; // last DM_DRAGOVER operation
116
117 USHORT supportedOps; // operations supported by all items
118 bool sourceAllowsOp : 1; // does source allow requested operation
119
120 USHORT lastDropReply; // last reply to DM_DRAGOVER
121
122 Qt::DropAction lastAction; // last target-accepted action
123 QRect lastAnswerRect; // last accepted rectangle from the target
124};
125
126QPMDragData::QPMDragData()
127 : initialized(false), dropped(false)
128 , gotWorkers(false), di(NULL)
129 , lastDragOverWidget(0), lastDragOverOp(0)
130 , supportedOps(0), sourceAllowsOp(false)
131 , lastDropReply(DOR_NEVERDROP)
132 , lastAction(Qt::CopyAction)
133{
134}
135
136QPMDragData::~QPMDragData()
137{
138 reset();
139}
140
141void QPMDragData::initialize(DRAGINFO *info)
142{
143 Q_ASSERT(info);
144 if (initialized || !info)
145 return;
146
147 initialized = true;
148 di = info;
149}
150
151void QPMDragData::initWorkers()
152{
153 Q_ASSERT(initialized);
154 if (!initialized || gotWorkers)
155 return;
156
157 gotWorkers = true;
158
159 // go through all converters and collect DropWorkers to use
160 foreach (QPMMime *mime, QPMMime::all()) {
161 QPMMime::DropWorker *wrk = mime->dropWorkerFor(di);
162 if (wrk) {
163 if (wrk->isExclusive()) {
164 // ignore all other workers if some of them identified itself
165 // as exclusive
166 workers.clear();
167 workers.append(wrk);
168 break;
169 }
170 // ensure there are no duplicateseed
171 if (!workers.contains(wrk))
172 workers.append(wrk);
173 }
174 }
175
176 DEBUG(() << "QPMDragData:" << workers.count() << "drop workers for DRAGINFO" << di);
177
178 // init all workers
179 foreach (QPMMime::DropWorker *w, workers) {
180 w->nfo = di;
181 w->init();
182 }
183}
184
185void QPMDragData::reset(bool isAccepted)
186{
187 if (!initialized)
188 return;
189
190 // cleanup all workers
191 foreach (QPMMime::DropWorker *w, workers) {
192 w->cleanup(isAccepted);
193 w->nfo = 0;
194 }
195
196 workers.clear();
197 di = 0;
198 initialized = dropped = gotWorkers = false;
199}
200
201bool QPMDragData::hasFormat_sys(const QString &mimeType)
202{
203 if (!gotWorkers)
204 initWorkers();
205
206 foreach (QPMMime::DropWorker *w, workers)
207 if (w->hasFormat(mimeType))
208 return true;
209
210 return false;
211}
212
213QStringList QPMDragData::formats_sys()
214{
215 QStringList mimes;
216
217 if (!gotWorkers)
218 initWorkers();
219
220 foreach (QPMMime::DropWorker *w, workers)
221 mimes << w->formats();
222
223 return mimes;
224}
225
226QVariant QPMDragData::retrieveData_sys(const QString &mimeType,
227 QVariant::Type preferredType)
228{
229 QVariant result;
230
231 // we may only do data transfer after DM_DROP is sent. Return shortly.
232 if (!isDropped())
233 return result;
234
235 if (!gotWorkers)
236 initWorkers();
237
238 foreach (QPMMime::DropWorker *w, workers) {
239 if (w->hasFormat(mimeType)) {
240 result = w->retrieveData(mimeType, preferredType);
241 break;
242 }
243 }
244
245 return result;
246}
247
248static Qt::DropActions toQDragDropActions(USHORT ops)
249{
250 Qt::DropActions actions = Qt::IgnoreAction;
251 if (ops & DO_LINKABLE)
252 actions |= Qt::LinkAction;
253 if (ops & DO_COPYABLE)
254 actions |= Qt::CopyAction;
255 if (ops & DO_MOVEABLE)
256 actions |= Qt::MoveAction;
257 return actions;
258}
259
260static Qt::DropAction toQDragDropAction(USHORT op)
261{
262 if (op == DO_LINK)
263 return Qt::LinkAction;
264 if (op == DO_COPY)
265 return Qt::CopyAction;
266 if (op == DO_MOVE)
267 return Qt::MoveAction;
268 return Qt::IgnoreAction;
269}
270
271static USHORT toPmDragDropOp(Qt::DropActions action)
272{
273 if (action & Qt::LinkAction)
274 return DO_LINK;
275 if (action & Qt::CopyAction)
276 return DO_COPY;
277 if (action & Qt::MoveAction)
278 return DO_MOVE;
279 return DO_UNKNOWN;
280}
281
282static USHORT toPmDragDropOps(Qt::DropActions actions)
283{
284 USHORT op = DO_UNKNOWN;
285 if (actions & Qt::LinkAction)
286 op |= DO_LINKABLE;
287 if (actions & Qt::CopyAction)
288 op |= DO_COPYABLE;
289 if (actions & Qt::MoveAction)
290 op |= DO_MOVEABLE;
291 return op;
292}
293
294void QDragManager::init_sys()
295{
296 inDrag = false;
297
298 Q_ASSERT(dropData);
299 Q_ASSERT(!dropData->d);
300 dropData->d = new QPMDragData();
301}
302
303void QDragManager::uninit_sys()
304{
305 Q_ASSERT(dropData);
306 Q_ASSERT(dropData->d);
307 delete dropData->d;
308 dropData->d = 0;
309}
310
311void QDragManager::sendDropEvent(QWidget *receiver, QEvent *event)
312{
313 Q_ASSERT(!inDrag);
314 inDrag = true;
315 QApplication::sendEvent(receiver, event);
316 // make sure that all issued update requests are handled before we
317 // return from the DM_DRAGOVER/DM_DRAGLEAVE/DM_DROP message; otherwise
318 // we'll get screen corruption due to unexpected screen updates while
319 // dragging
320 QApplication::sendPostedEvents(0, QEvent::UpdateRequest);
321 inDrag = false;
322}
323
324/*!
325 * \internal
326 * Direct manipulation (Drag & Drop) event handler
327 */
328MRESULT QDragManager::dispatchDragAndDrop(QWidget *widget, const QMSG &qmsg)
329{
330 Q_ASSERT(widget);
331
332 BOOL ok = FALSE;
333
334 QDragManager *manager = QDragManager::self();
335 QPMDragData *dragData = manager->dropData->d;
336 Q_ASSERT(dragData);
337
338 // @todo use dragData->lastAnswerRect to compress DM_DRAGOVER events
339
340 switch (qmsg.msg) {
341 case DM_DRAGOVER: {
342 DEBUG(("DM_DRAGOVER: hwnd %lX di %p x %d y %d", qmsg.hwnd, qmsg.mp1,
343 SHORT1FROMMP(qmsg.mp2), SHORT2FROMMP(qmsg.mp2)));
344
345 DRAGINFO *info = (DRAGINFO *)qmsg.mp1;
346 ok = DrgAccessDraginfo(info);
347 Q_ASSERT(ok && info);
348 if (!ok || !info)
349 return MRFROM2SHORT(DOR_NEVERDROP, 0);
350
351 // flip y coordinate
352 QPoint pnt(info->xDrop, info->yDrop);
353 pnt.setY(QApplication::desktop()->height() - (pnt.y() + 1));
354 pnt = widget->mapFromGlobal(pnt);
355
356 QWidget *dragOverWidget = widget->childAt(pnt);
357 if (!dragOverWidget)
358 dragOverWidget = widget;
359
360 bool first = dragData->lastDragOverWidget != dragOverWidget;
361 if (first) {
362 // the first DM_DRAGOVER message
363 if (dragData->lastDragOverWidget != 0) {
364 // send drag leave to the old widget
365 dragData->reset();
366 QPointer<QWidget> dragOverWidgetGuard(dragOverWidget);
367 QDragLeaveEvent dle;
368 sendDropEvent(dragData->lastDragOverWidget, &dle);
369 if (!dragOverWidgetGuard) {
370 dragOverWidget = widget->childAt(pnt);
371 if (!dragOverWidget)
372 dragOverWidget = widget;
373 }
374 }
375 dragData->lastDragOverWidget = dragOverWidget;
376 dragData->lastDragOverOp = 0;
377 dragData->supportedOps = DO_COPYABLE | DO_MOVEABLE | DO_LINKABLE;
378 dragData->sourceAllowsOp = false;
379 dragData->lastDropReply = DOR_NEVERDROP;
380 dragData->lastAction =
381 manager->defaultAction(toQDragDropActions(dragData->supportedOps),
382 Qt::NoModifier);
383 dragData->lastAnswerRect = QRect();
384 // ensure drag data is reset (just in case of a wrong msg flow...)
385 dragData->reset();
386 }
387
388 if (!dragOverWidget->acceptDrops()) {
389 // We don't reply with DOR_NEVERDROP here because in this
390 // case PM will stop sending DM_DRAGOVER to the given HWND but
391 // we may have another non-native child (that has the same HWND)
392 // at a different position that accepts drops
393 DrgFreeDraginfo(info);
394 return MRFROM2SHORT(DOR_NODROP, 0);
395 }
396
397 USHORT dropReply = DOR_DROP;
398
399 if (first) {
400 // determine the set of operations supported by *all* items
401 // (this implies that DRAGITEM::fsSupportedOps is a bit field)
402 ULONG itemCount = DrgQueryDragitemCount(info);
403 for (ULONG i = 0; i < itemCount; ++ i) {
404 PDRAGITEM item = DrgQueryDragitemPtr(info, i);
405 Q_ASSERT(item);
406 if (!item) {
407 dropReply = DOR_NEVERDROP;
408 break;
409 }
410 dragData->supportedOps &= item->fsSupportedOps;
411 }
412 if (dropReply != DOR_NEVERDROP) {
413 Q_ASSERT(itemCount);
414 if (!itemCount || !dragData->supportedOps) {
415 // items don't have even a single common operation...
416 dropReply = DOR_NEVERDROP;
417 }
418 }
419 }
420
421 if (dropReply != DOR_NEVERDROP) {
422
423 if (first || dragData->lastDragOverOp != info->usOperation) {
424 // the current drop operation was changed by a modifier key
425 USHORT realOp = info->usOperation;
426 if (realOp == DO_DEFAULT || realOp == DO_UNKNOWN) {
427 // the default operation is requested
428 realOp = toPmDragDropOp(dragData->lastAction);
429 } else {
430 dragData->lastAction = toQDragDropAction(realOp);
431 }
432 dragData->sourceAllowsOp =
433 ((dragData->supportedOps & DO_MOVEABLE) && realOp == DO_MOVE) ||
434 ((dragData->supportedOps & DO_COPYABLE) && realOp == DO_COPY) ||
435 ((dragData->supportedOps & DO_LINKABLE) && realOp == DO_LINK);
436 dragData->lastDragOverOp = realOp;
437 }
438
439 // Note that if sourceAllowsOp = false here, we have to deliver
440 // events anyway (stealing them from Qt would be confusing), but
441 // we will silently ignore any accept commands and always reject
442 // the drop. Other platforms seem to do the same.
443
444 QMimeData *data = manager->source() ? manager->dragPrivate()->data : manager->dropData;
445
446 if (first) {
447 dragData->initialize(info);
448 QDragEnterEvent dee(pnt,
449 toQDragDropActions(dragData->supportedOps),
450 data, QApplication::mouseButtons(),
451 QApplication::keyboardModifiers());
452 dee.setDropAction(dragData->lastAction);
453 sendDropEvent(dragOverWidget, &dee);
454 // if not allowed or not accepted, always reply DOR_NODROP
455 // to have DM_DRAGOVER delivered to us again for a new test
456 if (dragData->sourceAllowsOp && dee.isAccepted()) {
457 dropReply = DOR_DROP;
458 dragData->lastAction = dee.dropAction();
459 dragData->lastAnswerRect = dee.answerRect();
460 } else {
461 dropReply = DOR_NODROP;
462 }
463 }
464
465 // note: according to the Qt documentation, the move event
466 // is sent immediately after the enter event, do so (only if
467 // the target accepts it)
468 if (!first || dropReply == DOR_DROP) {
469 QDragMoveEvent dme(pnt,
470 toQDragDropActions(dragData->supportedOps),
471 data, QApplication::mouseButtons(),
472 QApplication::keyboardModifiers());
473 // accept by default, since enter event was accepted.
474 dme.setDropAction(dragData->lastAction);
475 dme.accept();
476 sendDropEvent(dragOverWidget, &dme);
477 if (dragData->sourceAllowsOp && dme.isAccepted()) {
478 dropReply = DOR_DROP;
479 dragData->lastAction = dme.dropAction();
480 dragData->lastAnswerRect = dme.answerRect();
481 } else {
482 dropReply = DOR_NODROP;
483 }
484 }
485
486 dragData->lastDropReply = dropReply;
487 }
488
489 DrgFreeDraginfo(info);
490
491 return MRFROM2SHORT(dropReply, toPmDragDropOp(dragData->lastAction));
492 }
493 case DM_DRAGLEAVE: {
494 DEBUG(("DM_DRAGLEAVE: hwnd %lX di %p", qmsg.hwnd, qmsg.mp1));
495
496 // Odin32 apps produce incorrect message flow, ignore
497 Q_ASSERT(dragData->lastDragOverWidget != 0);
498 if (dragData->lastDragOverWidget == 0)
499 return 0;
500
501 QDragLeaveEvent de;
502 sendDropEvent(dragData->lastDragOverWidget, &de);
503
504 dragData->lastDragOverWidget = 0;
505 dragData->reset();
506
507 return 0;
508 }
509 case DM_DROP: {
510 DEBUG(("DM_DROP: hwnd %lX di %p", qmsg.hwnd, qmsg.mp1));
511
512 // Odin32 apps produce incorrect message flow, ignore
513 Q_ASSERT(dragData->lastDragOverWidget != 0);
514 if (dragData->lastDragOverWidget == 0)
515 return 0;
516
517 // Odin32 apps send DM_DROP even if we replied DOR_NEVERDROP or
518 // DOR_NODROP, simulate DM_DRAGLEAVE
519 Q_ASSERT(dragData->lastDropReply == DOR_DROP);
520 if (dragData->lastDropReply != DOR_DROP) {
521 QMSG qmsg2 = qmsg;
522 qmsg2.msg = DM_DRAGLEAVE;
523 dispatchDragAndDrop(widget, qmsg2);
524 return 0;
525 }
526
527 DRAGINFO *info = (DRAGINFO *)qmsg.mp1;
528 ok = DrgAccessDraginfo(info);
529 Q_ASSERT(ok && info);
530 if (!ok || !info)
531 return 0;
532
533 // flip y coordinate
534 QPoint pnt(info->xDrop, info->yDrop);
535 pnt.setY(QApplication::desktop()->height() - (pnt.y() + 1));
536 pnt = widget->mapFromGlobal(pnt);
537
538 Q_ASSERT(dragData->lastDragOverOp == info->usOperation);
539
540 Q_ASSERT(dragData->lastDragOverWidget->acceptDrops());
541 if (!dragData->lastDragOverWidget->acceptDrops()) {
542 DrgDeleteDraginfoStrHandles(info);
543 DrgFreeDraginfo(info);
544 return 0;
545 }
546
547 QMimeData *data = manager->source() ? manager->dragPrivate()->data : manager->dropData;
548
549 dragData->setDropped(true);
550
551 QDropEvent de(pnt, dragData->lastAction, data, QApplication::mouseButtons(),
552 QApplication::keyboardModifiers());
553 if (dragData->lastDropReply == DOR_DROP)
554 de.setDropAction(dragData->lastAction);
555
556 sendDropEvent(dragData->lastDragOverWidget, &de);
557
558 if (dragData->lastDropReply == DOR_DROP)
559 de.accept();
560
561 dragData->lastDragOverWidget = 0;
562 dragData->reset(de.isAccepted());
563
564 ULONG targetReply = de.isAccepted() ?
565 DMFL_TARGETSUCCESSFUL : DMFL_TARGETFAIL;
566
567 if (de.isAccepted() && de.dropAction() == Qt::TargetMoveAction) {
568 // Qt::TargetMoveAction means that the target will move the
569 // object (e.g. copy it to itself and delete from the source).
570 // In this case, we always send DMFL_TARGETFAIL to the source to
571 // prevent it from doing the same on its side.
572 targetReply = DMFL_TARGETFAIL;
573 }
574
575 // send DM_ENDCONVERSATION for every item
576 ULONG itemCount = DrgQueryDragitemCount(info);
577 for (ULONG i = 0; i < itemCount; ++ i) {
578 PDRAGITEM item = DrgQueryDragitemPtr(info, i);
579 Q_ASSERT(item);
580 if (!item)
581 continue;
582 // it is possible that this item required DM_RENDERPREPARE but
583 // returned false in reply to it (so hwndItem may be NULL)
584 if (!item->hwndItem)
585 continue;
586 DrgSendTransferMsg(item->hwndItem, DM_ENDCONVERSATION,
587 MPFROMLONG(item->ulItemID),
588 MPFROMLONG(targetReply));
589 }
590
591 DrgDeleteDraginfoStrHandles(info);
592 DrgFreeDraginfo(info);
593
594 return 0;
595 }
596 default:
597 break;
598 }
599
600 return WinDefWindowProc(qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2);
601}
602
603//---------------------------------------------------------------------
604// QDropData
605//---------------------------------------------------------------------
606
607bool QDropData::hasFormat_sys(const QString &mimeType) const
608{
609 Q_ASSERT(d);
610 if (d)
611 return d->hasFormat_sys(mimeType);
612 return false;
613}
614
615QStringList QDropData::formats_sys() const
616{
617 QStringList fmts;
618 Q_ASSERT(d);
619 if (d)
620 fmts = d->formats_sys();
621 return fmts;
622}
623
624QVariant QDropData::retrieveData_sys(const QString &mimeType, QVariant::Type type) const
625{
626 QVariant result;
627 Q_ASSERT(d);
628 if (d)
629 result = d->retrieveData_sys(mimeType, type);
630 return result;
631}
632
633//---------------------------------------------------------------------
634// QPMCoopDragWorker
635//---------------------------------------------------------------------
636
637class QPMCoopDragWorker : public QPMMime::DragWorker, public QPMObjectWindow
638{
639public:
640 QPMCoopDragWorker() : info(0) {}
641 bool collectWorkers(QDrag *o);
642
643 // DragWorker interface
644 void init();
645 bool cleanup(bool isCancelled);
646 bool isExclusive() const { return true; }
647 ULONG itemCount() const { return 0; }
648 HWND hwnd() const;
649 DRAGINFO *createDragInfo(const QString &targetName, USHORT supportedOps);
650
651 // QPMObjectWindow interface
652 MRESULT message(ULONG msg, MPARAM mp1, MPARAM mp2);
653
654private:
655 QList<DragWorker*> workers;
656 // todo check workers!
657 //QPtrList<DragWorker> workers;
658 DRAGINFO *info;
659};
660
661bool QPMCoopDragWorker::collectWorkers(QDrag *o)
662{
663 Q_ASSERT(o);
664 if (!o)
665 return false;
666
667 bool gotExcl = false; // got isExclusive() worker?
668 bool skipExcl = false; // skip isExclusive() or itemCount() > 1 workers?
669 ULONG coopLevel = 0; // itemCount() level for !isExclusive() workers
670
671 bool gotExclForMime = false;
672
673 // go through all formats and all converters to collect DragWorkers
674 QMimeData *mimeData = o->mimeData();
675 foreach (const QString &fmt, mimeData->formats()) {
676 DEBUG(() << "QPMCoopDragWorker: Searching for worker for mime" << fmt);
677 foreach (QPMMime *mime, QPMMime::all()) {
678 DragWorker *wrk = mime->dragWorkerFor(fmt, mimeData);
679 if (!wrk)
680 continue;
681 DEBUG(() << "QPMCoopDragWorker: Got worker" << wrk
682 << "( isExclusive" << wrk->isExclusive()
683 << "itemCount" << wrk->itemCount() << ") from convertor"
684 << mime << "( gotExclForMime" << gotExclForMime
685 << "gotExcl" << gotExcl
686 << "skipExcl" << skipExcl << "coopLevel"
687 << coopLevel << ")");
688 if (wrk->isExclusive()) {
689 if (!skipExcl && !gotExclForMime) {
690 gotExclForMime = true;
691 if (!gotExcl) {
692 gotExcl = true;
693 workers.append(wrk);
694 } else {
695 // skip everything exclusive unless it's exactly the
696 // same worker
697 skipExcl = !workers.contains(wrk);
698 }
699 }
700 // continue to search for a fall-back cooperative 1-item worker
701 // (like QPMMimeAnyMime) for the case if this worker quits
702 // the game
703 continue;
704 }
705 ULONG itemCnt = wrk->itemCount();
706 if (itemCnt == 0) {
707 DEBUG(() << "QPMCoopDragWorker: Cooperative DragWorker"
708 << wrk << "for mime " << fmt << " has "
709 << "itemCount = 0!");
710 continue;
711 }
712 if (itemCnt > 1) {
713 // coop workers with item count > 1 are also considered exclusive
714 // here, because may not co-exist with 1-item workers that should
715 // always be able to contribute
716 if (!gotExcl && !skipExcl && !gotExclForMime) {
717 gotExclForMime = true;
718 workers.append(wrk);
719 if (!coopLevel)
720 coopLevel = itemCnt;
721 // only those for the same number of items can proceed
722 if (itemCnt != coopLevel)
723 skipExcl = true;
724 }
725 // continue to search for a fall-back cooperative 1-item worker
726 // (like QPMMimeAnyMime) for the case if this worker quits
727 // the game
728 continue;
729 }
730 workers.append(wrk);
731 // Don't search for other workrers for the same mime type --
732 // we've already got a drag worker for it and adding another
733 // one would just introduce mime type duplicates on the drop
734 // target's side (where the first encountered drop worker
735 // for the given RMF would be used for actual data transfer
736 // anyway). See also QClipboard::setData().
737 break;
738 }
739 if (gotExclForMime) {
740 // ensure we have a fall-back coop (should be the last added item)
741 DragWorker *w = workers.last();
742 if (w->isExclusive() || w->itemCount() > 1) {
743 DEBUG(() << "QPMCoopDragWorker: DragWorker" << w
744 << "for" << fmt << "( isExclusive" << w->isExclusive()
745 << "itemCount" << w->itemCount()
746 <<") has no fall-back cooperative 1-item worker!");
747 workers.removeLast();
748 }
749 gotExclForMime = false;
750 } else {
751 // got a regular (non-fall-back) 1-item coop, skip evreything else
752 skipExcl = true;
753 }
754 }
755
756 // remove either all exclusive workers or all their fall-back workers
757 // (depending on skipExcl) and remove duplicates
758 for (QList<DragWorker*>::iterator it = workers.begin();
759 it < workers.end();) {
760 DragWorker *wrk = *it;
761 bool excl = wrk->isExclusive() || wrk->itemCount() > 1;
762 if (skipExcl == excl || workers.count(wrk) > 1) {
763 it = workers.erase(it);
764 } else {
765 ++it;
766 }
767 }
768
769#if defined(QDND_DEBUG)
770 foreach (DragWorker *wrk, workers) {
771 DEBUG(() << "QPMCoopDragWorker: Will use worker" << wrk
772 << "( isExclusive" << wrk->isExclusive()
773 << "itemCount" << wrk->itemCount() << ")");
774 }
775#endif
776
777 Q_ASSERT(workers.count() > 0);
778 return workers.count() > 0;
779}
780
781HWND QPMCoopDragWorker::hwnd() const
782{
783 DragWorker *firstWorker = workers.first();
784 Q_ASSERT(firstWorker);
785 if (!firstWorker)
786 return 0;
787
788 if (firstWorker->isExclusive() && firstWorker->itemCount() == 0) {
789 // this is a super exclusive worker that will do everything on its own
790 return firstWorker->hwnd();
791 }
792
793 return QPMObjectWindow::hwnd();
794}
795
796void QPMCoopDragWorker::init()
797{
798 Q_ASSERT(source());
799 foreach(DragWorker *wrk, workers) {
800 wrk->src = source();
801 wrk->init();
802 }
803}
804
805bool QPMCoopDragWorker::cleanup(bool isCancelled)
806{
807 bool moveDisallowed = false;
808
809 foreach(DragWorker *wrk, workers) {
810 // disallow the Move operation if at least one worker asked so
811 moveDisallowed |= wrk->cleanup(isCancelled);
812 wrk->src = 0;
813 }
814 workers.clear();
815 info = 0;
816 return moveDisallowed;
817}
818
819DRAGINFO *QPMCoopDragWorker::createDragInfo(const QString &targetName,
820 USHORT supportedOps)
821{
822 Q_ASSERT(!info);
823 if (info)
824 return 0;
825
826 DragWorker *firstWorker = workers.first();
827 Q_ASSERT(firstWorker);
828 if (!firstWorker)
829 return 0;
830
831 ULONG itemCnt = firstWorker->itemCount();
832
833 if (firstWorker->isExclusive() && itemCnt == 0) {
834 // this is a super exclusive worker that will do everything on its own
835 DEBUG(() << "QPMCoopDragWorker: Will redirect to super worker"
836 << firstWorker);
837 return firstWorker->createDragInfo(targetName, supportedOps);
838 }
839
840 // note that all workers at this place require the same amount of items
841 // (guaranteed by collectWorkers())
842
843 DEBUG(() << "QPMCoopDragWorker: itemCnt" << itemCnt);
844
845 info = DrgAllocDraginfo(itemCnt);
846 Q_ASSERT(info);
847 if (!info)
848 return 0;
849
850 // collect all mechanism/format pairs
851 QByteArray allFormats;
852 foreach (DragWorker *wrk, workers) {
853 QByteArray formats = wrk->composeFormatString();
854 Q_ASSERT(!formats.isNull());
855 if (!formats.isNull()) {
856 if (allFormats.isNull())
857 allFormats = formats;
858 else {
859 allFormats += ",";
860 allFormats += formats;
861 }
862 }
863 }
864
865 DEBUG(() << "QPMCoopDragWorker: allFormats" << allFormats);
866
867 static ULONG itemID = 0;
868
869 const char *type = 0;
870 const char *ext = 0;
871 firstWorker->defaultFileType(type, ext);
872
873 bool ok = true;
874 for (ULONG i = 0; i < itemCnt; ++i) {
875 DRAGITEM *item = DrgQueryDragitemPtr(info, i);
876 Q_ASSERT(item);
877 if (!item) {
878 ok = false;
879 break;
880 }
881
882 QString name;
883 if (itemCnt == 1)
884 name = targetName;
885 else
886 name = QString(QLatin1String("%1 %2")).arg(targetName).arg(i + 1);
887
888 if (ext) {
889 name += QLatin1Char('.');
890 name += QFile::decodeName(QByteArray(ext));
891 }
892
893 DEBUG(() << "QPMCoopDragWorker: item" << i << ": type" << type
894 << " name" << name);
895
896 // Note 1: DRAGITEM::hstrType is actually ignored by WPS,
897 // only the target extension matters.
898
899 // Note 2: We're not required to fill in the hwndItem field because we
900 // use the DC_PREPARE flag (to indicate it will be filled later, after
901 // DM_RENDERPREPARE); however, Mozilla refuses to render if hwndItem
902 // is initially 0. Set it to our HWND instead (we'll issue a warning if
903 // DM_RENDER or DM_ENDCONVERSATION is erroneously sent to us)
904
905 item->hwndItem = hwnd();
906 item->ulItemID = itemID ++;
907 item->hstrType = DrgAddStrHandle(type ? type : DRT_UNKNOWN);
908 item->hstrRMF = DrgAddStrHandle(allFormats);
909 item->hstrContainerName = 0;
910 item->hstrSourceName = 0;
911 item->hstrTargetName = DrgAddStrHandle(QFile::encodeName(name));
912 item->cxOffset = 0;
913 item->cyOffset = 0;
914 item->fsControl = DC_PREPARE; // require DM_RENDERPREPARE from target
915 item->fsSupportedOps = supportedOps;
916 }
917
918 if (!ok) {
919 DrgFreeDraginfo(info);
920 info = 0;
921 }
922
923 return info;
924}
925
926MRESULT QPMCoopDragWorker::message(ULONG msg, MPARAM mp1, MPARAM mp2)
927{
928 if (msg == DM_RENDERPREPARE) {
929 if (!info) {
930 DEBUG(() << "Drop target sent DM_RENDERPREPARE after the DnD "
931 "session is over!");
932 // free the given DRAGTRANSFER structure to avoud memory leak
933 DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
934 if (xfer)
935 qt_DrgFreeDragtransfer(xfer);
936 return (MRESULT)FALSE;
937 }
938
939 DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
940 Q_ASSERT(xfer && xfer->pditem);
941 if (!xfer || !xfer->pditem)
942 return (MRESULT)FALSE;
943
944 // find the item's index (ordinal number)
945 ULONG itemCnt = DrgQueryDragitemCount(info);
946 ULONG index = 0;
947 for (; index < itemCnt; ++index)
948 if (DrgQueryDragitemPtr(info, index) == xfer->pditem)
949 break;
950
951 Q_ASSERT(index < itemCnt);
952 if (index >= itemCnt)
953 return (MRESULT)FALSE;
954
955 DEBUG(() << "QPMCoopDragWorker: Got DM_RENDERPREPARE to"
956 << QPMMime::queryHSTR(xfer->hstrSelectedRMF) << "for item"
957 << index << "( id" << xfer->pditem->ulItemID << ")");
958
959 QByteArray drm, drf;
960 if (!QPMMime::parseRMF(xfer->hstrSelectedRMF, drm, drf)) {
961 Q_ASSERT(false);
962 return (MRESULT)FALSE;
963 }
964
965 DragWorker *wrk = 0;
966 foreach(wrk, workers)
967 if (wrk->prepare(drm, drf, xfer->pditem, index))
968 break;
969 if (!wrk) {
970 DEBUG(() << "QPMCoopDragWorker: No suitable worker found");
971 return (MRESULT)FALSE;
972 }
973
974 xfer->pditem->hwndItem = wrk->hwnd();
975 Q_ASSERT(xfer->pditem->hwndItem);
976 return (MRESULT)(xfer->pditem->hwndItem ? TRUE : FALSE);
977 }
978
979 if (msg == DM_RENDER || msg == DM_ENDCONVERSATION) {
980 DEBUG(() << "Drop target sent DM_RENDER or DM_ENDCONVERSATION to the "
981 "drag source window instead of the drag item window!");
982 if (msg == DM_RENDER) {
983 // free the given DRAGTRANSFER structure to avoud memory leak
984 DRAGTRANSFER *xfer = (DRAGTRANSFER *)mp1;
985 if (xfer)
986 qt_DrgFreeDragtransfer(xfer);
987 }
988 }
989
990 return (MRESULT)FALSE;
991}
992
993//---------------------------------------------------------------------
994// QDragManager
995//---------------------------------------------------------------------
996
997Qt::DropAction QDragManager::drag(QDrag *o)
998
999{
1000 DEBUG(() << "QDragManager::drag");
1001
1002 if (object == o || !o || !o->d_func()->source)
1003 return Qt::IgnoreAction;
1004
1005 if (object) {
1006 cancel();
1007 qApp->removeEventFilter(this);
1008 beingCancelled = false;
1009 }
1010
1011 // detect a mouse button to end dragging
1012 LONG vkTerminate = 0;
1013 {
1014 ULONG msg = WinQuerySysValue(HWND_DESKTOP, SV_BEGINDRAG) & 0xFFFF;
1015 switch(msg) {
1016 case WM_BUTTON1MOTIONSTART: vkTerminate = VK_BUTTON1; break;
1017 case WM_BUTTON2MOTIONSTART: vkTerminate = VK_BUTTON2; break;
1018 case WM_BUTTON3MOTIONSTART: vkTerminate = VK_BUTTON3; break;
1019 }
1020
1021 if (WinGetKeyState(HWND_DESKTOP, vkTerminate) & 0x8000) {
1022 // prefer the default button if it is pressed
1023 } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON2) & 0x8000) {
1024 vkTerminate = VK_BUTTON2;
1025 } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON1) & 0x8000) {
1026 vkTerminate = VK_BUTTON1;
1027 } else if (WinGetKeyState(HWND_DESKTOP, VK_BUTTON3) & 0x8000) {
1028 vkTerminate = VK_BUTTON3;
1029 } else {
1030 vkTerminate = 0;
1031 }
1032 }
1033
1034 if (!vkTerminate) {
1035 DEBUG(() << "QDragManager::drag: No valid mouse button pressed, "
1036 "dragging cancelled!");
1037 o->deleteLater();
1038 return Qt::IgnoreAction;
1039 }
1040
1041 USHORT supportedOps = toPmDragDropOps(o->d_func()->possible_actions);
1042
1043 static QPMCoopDragWorker dragWorker;
1044
1045 bool ok = dragWorker.collectWorkers(o);
1046 Q_ASSERT(ok);
1047 Q_ASSERT(dragWorker.hwnd());
1048 if (!ok || !dragWorker.hwnd()) {
1049 o->deleteLater();
1050 return Qt::IgnoreAction;
1051 }
1052
1053 dragWorker.src = o->mimeData();
1054 dragWorker.init();
1055 DRAGINFO *info = dragWorker.createDragInfo(o->objectName(), supportedOps);
1056
1057 Q_ASSERT(info);
1058 if (!info) {
1059 dragWorker.cleanup(true /* isCancelled */);
1060 dragWorker.src = 0;
1061 o->deleteLater();
1062 return Qt::IgnoreAction;
1063 }
1064
1065 object = o;
1066
1067 DEBUG(() << "QDragManager::drag: actions"
1068 << dragActionsToString(dragPrivate()->possible_actions));
1069
1070 dragPrivate()->target = 0;
1071
1072#ifndef QT_NO_ACCESSIBILITY
1073 QAccessible::updateAccessibility(this, 0, QAccessible::DragDropStart);
1074#endif
1075
1076 // @todo custom drag pixmap?
1077
1078 DRAGIMAGE img;
1079 img.cb = sizeof(DRAGIMAGE);
1080 img.hImage = WinQuerySysPointer(HWND_DESKTOP, SPTR_FILE, FALSE);
1081 img.fl = DRG_ICON;
1082 img.cxOffset = 0;
1083 img.cyOffset = 0;
1084
1085 // the mouse is most likely captured by Qt at this point, uncapture it
1086 // or DrgDrag() will definitely fail
1087 WinSetCapture(HWND_DESKTOP, 0);
1088
1089 HWND target = DrgDrag(dragWorker.hwnd(), info, &img, 1, vkTerminate,
1090 (PVOID)0x80000000L); // don't lock the desktop PS
1091
1092 DEBUG(("QDragManager::drag: DrgDrag() returned %08lX (error 0x%08lX)",
1093 target, WinGetLastError(0)));
1094
1095 // we won't get any mouse release event, so manually adjust qApp state
1096 qt_pmMouseButtonUp();
1097
1098 bool moveDisallowed = dragWorker.cleanup(beingCancelled || target == 0);
1099 dragWorker.src = 0;
1100
1101 moveDisallowed |= beingCancelled || target == 0 ||
1102 info->usOperation != DO_MOVE;
1103
1104 DEBUG(() << "QDragManager::drag: moveDisallowed" << moveDisallowed);
1105
1106 Qt::DropAction ret = Qt::IgnoreAction;
1107 if (target != 0) {
1108 ret = toQDragDropAction(info->usOperation);
1109 if (moveDisallowed && info->usOperation == DO_MOVE)
1110 ret = Qt::TargetMoveAction;
1111 }
1112
1113 DEBUG(() << "QDragManager::drag: result" << dragActionsToString(ret));
1114
1115 if (target == 0)
1116 DrgDeleteDraginfoStrHandles(info);
1117 DrgFreeDraginfo(info);
1118
1119 if (!beingCancelled) {
1120 dragPrivate()->target = QWidget::find(target);
1121 cancel(); // this will delete o (object)
1122 }
1123
1124 beingCancelled = false;
1125
1126#ifndef QT_NO_ACCESSIBILITY
1127 QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd);
1128#endif
1129
1130 return ret;
1131}
1132
1133void QDragManager::cancel(bool deleteSource)
1134{
1135 // Note: the only place where this function is called with
1136 // deleteSource = false so far is QDrag::~QDrag()
1137
1138 Q_ASSERT(object && !beingCancelled);
1139 if (!object || beingCancelled)
1140 return;
1141
1142 beingCancelled = true;
1143
1144 object->setMimeData(0);
1145
1146 if (deleteSource)
1147 object->deleteLater();
1148 object = 0;
1149
1150#ifndef QT_NO_CURSOR
1151 // insert cancel code here ######## todo
1152
1153 if (restoreCursor) {
1154 QApplication::restoreOverrideCursor();
1155 restoreCursor = false;
1156 }
1157#endif
1158#ifndef QT_NO_ACCESSIBILITY
1159 QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd);
1160#endif
1161}
1162
1163void QDragManager::updatePixmap()
1164{
1165 // not used in PM implementation
1166}
1167
1168bool QDragManager::eventFilter(QObject *, QEvent *)
1169{
1170 // not used in PM implementation
1171 return false;
1172}
1173
1174void QDragManager::timerEvent(QTimerEvent*)
1175{
1176 // not used in PM implementation
1177}
1178
1179void QDragManager::move(const QPoint &)
1180{
1181 // not used in PM implementation
1182}
1183
1184void QDragManager::drop()
1185{
1186 // not used in PM implementation
1187}
1188
1189QT_END_NAMESPACE
1190
1191#endif // QT_NO_DRAGANDDROP
Note: See TracBrowser for help on using the repository browser.