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

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

gui: DND: Implemented dropping text to Qt applications.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision Author Id
File size: 19.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information (qt-info@nokia.com)
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 qt-sales@nokia.com.
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 "qdnd_p.h"
55#include "qdebug.h"
56
57#include "qt_os2.h"
58
59//#define QDND_DEBUG // in pair with qmime_pm.cpp
60
61#ifdef QDND_DEBUG
62# define DEBUG(a) qDebug a
63#else
64# define DEBUG(a) do {} while(0)
65#endif
66
67QT_BEGIN_NAMESPACE
68
69#if !defined(QT_NO_DRAGANDDROP) && !defined(QT_NO_CLIPBOARD)
70
71/** \internal
72 * Data for QDragEnterEvent/QDragMoveEvent/QPMDropEvent.
73 */
74class QPMDragData
75{
76public:
77 QPMDragData();
78 ~QPMDragData();
79
80 void initialize(DRAGINFO *di);
81 void reset(bool isAccepted);
82 void reset() { reset(false); }
83
84 void setDropped(bool d) { dropped = d; }
85 bool isDropped() const { return dropped; }
86
87 DRAGINFO *info() const { return di; }
88
89 bool hasFormat_sys(const QString &mimeType);
90 QStringList formats_sys();
91 QVariant retrieveData_sys(const QString &mimeType,
92 QVariant::Type preferredType);
93
94private:
95
96 void initWorkers();
97
98 bool initialized : 1;
99 bool dropped : 1;
100 bool gotWorkers : 1;
101
102 DRAGINFO *di;
103 QList<QPMMime::DropWorker*> workers;
104};
105
106QPMDragData::QPMDragData()
107 : initialized(false), dropped(false)
108 , gotWorkers(false), di(NULL)
109{
110 QDragManager *manager = QDragManager::self();
111 Q_ASSERT(!manager->dropData->d);
112 manager->dropData->d = this;
113}
114
115QPMDragData::~QPMDragData()
116{
117 reset();
118
119 QDragManager *manager = QDragManager::self();
120 if (manager) {
121 Q_ASSERT(manager->dropData->d == this);
122 manager->dropData->d = 0;
123 }
124}
125
126void QPMDragData::initialize(DRAGINFO *info)
127{
128 Q_ASSERT(info);
129 if (initialized || !info)
130 return;
131
132 initialized = true;
133 di = info;
134}
135
136void QPMDragData::initWorkers()
137{
138 Q_ASSERT(initialized);
139 if (!initialized || gotWorkers)
140 return;
141
142 gotWorkers = true;
143
144 // go through all converters and collect DropWorkers to use
145 foreach (QPMMime *mime, QPMMime::all()) {
146 QPMMime::DropWorker *wrk = mime->dropWorkerFor(di);
147 if (wrk) {
148 if (wrk->isExclusive()) {
149 // ignore all other workers if some of them identified itself
150 // as exclusive
151 workers.clear();
152 workers.append(wrk);
153 break;
154 }
155 // ensure there are no duplicateseed
156 if (!workers.contains(wrk))
157 workers.append(wrk);
158 }
159 }
160
161 DEBUG(() << "QPMDragData:" << workers.count() << "drop workers for DRAGINFO" << di);
162
163 // init all workers
164 foreach (QPMMime::DropWorker *w, workers) {
165 w->nfo = di;
166 w->init();
167 }
168}
169
170void QPMDragData::reset(bool isAccepted)
171{
172 if (!initialized)
173 return;
174
175 // cleanup all workers
176 foreach (QPMMime::DropWorker *w, workers) {
177 w->cleanup(isAccepted);
178 w->nfo = 0;
179 }
180
181 workers.clear();
182 di = 0;
183 initialized = dropped = gotWorkers = false;
184}
185
186bool QPMDragData::hasFormat_sys(const QString &mimeType)
187{
188 if (!gotWorkers)
189 initWorkers();
190
191 foreach (QPMMime::DropWorker *w, workers)
192 if (w->hasFormat(mimeType))
193 return true;
194
195 return false;
196}
197
198QStringList QPMDragData::formats_sys()
199{
200 QStringList mimes;
201
202 if (!gotWorkers)
203 initWorkers();
204
205 foreach (QPMMime::DropWorker *w, workers)
206 mimes << w->formats();
207
208 return mimes;
209}
210
211QVariant QPMDragData::retrieveData_sys(const QString &mimeType,
212 QVariant::Type preferredType)
213{
214 QVariant result;
215
216 // we may only do data transfer after DM_DROP is sent. Return shortly.
217 if (!isDropped())
218 return result;
219
220 if (!gotWorkers)
221 initWorkers();
222
223 foreach (QPMMime::DropWorker *w, workers) {
224 if (w->hasFormat(mimeType)) {
225 result = w->retrieveData(mimeType, preferredType);
226 break;
227 }
228 }
229
230 return result;
231}
232
233static Qt::DropActions toQDragDropActions(USHORT ops)
234{
235 Qt::DropActions actions = Qt::IgnoreAction;
236 if (ops & DO_LINKABLE)
237 actions |= Qt::LinkAction;
238 if (ops & DO_COPYABLE)
239 actions |= Qt::CopyAction;
240 if (ops & DO_MOVEABLE)
241 actions |= Qt::MoveAction;
242 return actions;
243}
244
245static Qt::DropAction toQDragDropAction(USHORT op)
246{
247 if (op == DO_LINK)
248 return Qt::LinkAction;
249 if (op == DO_COPY)
250 return Qt::CopyAction;
251 if (op == DO_MOVE)
252 return Qt::MoveAction;
253 return Qt::IgnoreAction;
254}
255
256static USHORT toPmDragDropOp(Qt::DropActions action)
257{
258 if (action & Qt::LinkAction)
259 return DO_LINK;
260 if (action & Qt::CopyAction)
261 return DO_COPY;
262 if (action & Qt::MoveAction)
263 return DO_MOVE;
264 return DO_UNKNOWN;
265}
266
267/*!
268 * \internal
269 * Direct manipulation (Drag & Drop) event handler
270 */
271MRESULT qt_dispatchDragAndDrop(QWidget *widget, const QMSG &qmsg)
272{
273 static QWidget *lastDragOverWidget = 0; // last target widget
274 static USHORT lastDragOverOp = 0; // last DM_DRAGOVER operation
275
276 static USHORT supportedOps = 0; // operations supported by all items
277 static bool sourceAllowsOp = false; // does source allow requested operation
278
279 static USHORT lastDropReply = DOR_NEVERDROP; // last reply to DM_DRAGOVER
280 static USHORT lastOpRequest = DO_UNKNOWN; // last op requested in DM_DRAGOVER
281
282 static Qt::DropAction lastProposedAction = Qt::CopyAction; // last proposed action
283 static QRect lastAnswerRect; // last accepted rectangle from the target
284 // @todo use lastAnswerRect to compress DM_DRAGOVER events
285
286 static QPMDragData dragData;
287
288 Q_ASSERT(widget);
289
290 BOOL ok = FALSE;
291
292 QDragManager *manager = QDragManager::self();
293
294 switch (qmsg.msg) {
295 case DM_DRAGOVER: {
296 DEBUG(("DM_DRAGOVER: hwnd %lX di %p x %d y %d", qmsg.hwnd, qmsg.mp1,
297 SHORT1FROMMP(qmsg.mp2), SHORT2FROMMP(qmsg.mp2)));
298
299 DRAGINFO *info = (DRAGINFO *)qmsg.mp1;
300 ok = DrgAccessDraginfo(info);
301 Q_ASSERT(ok && info);
302 if (!ok || !info)
303 return MRFROM2SHORT(DOR_NEVERDROP, 0);
304
305 // flip y coordinate
306 QPoint pnt(info->xDrop, info->yDrop);
307 pnt.setY(QApplication::desktop()->height() - (pnt.y() + 1));
308 pnt = widget->mapFromGlobal(pnt);
309
310 QWidget *dragOverWidget = widget->childAt(pnt);
311 if (!dragOverWidget)
312 dragOverWidget = widget;
313
314 bool first = lastDragOverWidget != dragOverWidget;
315 if (first) {
316 // the first DM_DRAGOVER message
317 if (lastDragOverWidget != 0) {
318 // send drag leave to the old widget
319 dragData.reset();
320 QPointer<QWidget> dragOverWidgetGuard(dragOverWidget);
321 QDragLeaveEvent dle;
322 QApplication::sendEvent(lastDragOverWidget, &dle);
323 if (!dragOverWidgetGuard) {
324 dragOverWidget = widget->childAt(pnt);
325 if (!dragOverWidget)
326 dragOverWidget = widget;
327 }
328 }
329 lastDragOverWidget = dragOverWidget;
330 lastDragOverOp = 0;
331 supportedOps = DO_COPYABLE | DO_MOVEABLE | DO_LINKABLE;
332 sourceAllowsOp = false;
333 lastDropReply = DOR_NEVERDROP;
334 lastOpRequest = DO_UNKNOWN;
335 lastProposedAction = manager->defaultAction(toQDragDropActions(supportedOps),
336 Qt::NoModifier);
337 lastAnswerRect = QRect();
338 // ensure drag data is reset (just in case of a wrong msg flow...)
339 dragData.reset();
340 }
341
342 if (!dragOverWidget->acceptDrops()) {
343 // We don't reply with DOR_NEVERDROP here because in this
344 // case PM will stop sending DM_DRAGOVER to the given HWND but
345 // we may have another non-native child (that has the same HWND)
346 // at a different position that accepts drops
347 DrgFreeDraginfo(info);
348 return MRFROM2SHORT(DOR_NODROP, 0);
349 }
350
351 USHORT dropReply = DOR_DROP;
352
353 if (first) {
354 // determine the set of operations supported by *all* items
355 // (this implies that DRAGITEM::fsSupportedOps is a bit field)
356 ULONG itemCount = DrgQueryDragitemCount(info);
357 for (ULONG i = 0; i < itemCount; ++ i) {
358 PDRAGITEM item = DrgQueryDragitemPtr(info, i);
359 Q_ASSERT(item);
360 if (!item) {
361 dropReply = DOR_NEVERDROP;
362 break;
363 }
364 supportedOps &= item->fsSupportedOps;
365 }
366 if (dropReply != DOR_NEVERDROP) {
367 Q_ASSERT(itemCount);
368 if (!itemCount || !supportedOps) {
369 // items don't have even a single common operation...
370 dropReply = DOR_NEVERDROP;
371 }
372 }
373 }
374
375 if (dropReply != DOR_NEVERDROP) {
376
377 if (first || lastDragOverOp != info->usOperation) {
378 // the current drop operation was changed by a modifier key
379 lastDragOverOp = info->usOperation;
380 USHORT realOp = info->usOperation;
381 if (realOp == DO_DEFAULT || realOp == DO_UNKNOWN) {
382 // the default operation is requested
383 realOp = toPmDragDropOp(lastProposedAction);
384 }
385 sourceAllowsOp =
386 ((supportedOps & DO_MOVEABLE) && realOp == DO_MOVE) ||
387 ((supportedOps & DO_COPYABLE) && realOp == DO_COPY) ||
388 ((supportedOps & DO_LINKABLE) && realOp == DO_LINK);
389 }
390
391 // Note that if sourceAllowsOp = false here, we have to deliver
392 // events anyway (stealing them from Qt would be confusing), but
393 // we will silently ignore any accept commands and always reject
394 // the drop. Other platforms seem to do the same.
395
396 QMimeData *data = manager->source() ? manager->dragPrivate()->data : manager->dropData;
397
398 Qt::DropAction action = toQDragDropAction(lastOpRequest);
399
400 if (first) {
401 dragData.initialize(info);
402 QDragEnterEvent dee(pnt, toQDragDropActions(supportedOps), data,
403 QApplication::mouseButtons(),
404 QApplication::keyboardModifiers());
405 QApplication::sendEvent(dragOverWidget, &dee);
406 // if not allowed or not accepted, always reply DOR_NODROP
407 // to have DM_DRAGOVER delivered to us again for a new test
408 if (sourceAllowsOp && dee.isAccepted()) {
409 dropReply = DOR_DROP;
410 action = dee.dropAction();
411 lastProposedAction = dee.proposedAction();
412 lastAnswerRect = dee.answerRect();
413 } else {
414 dropReply = DOR_NODROP;
415 }
416 }
417
418 // note: according to the Qt documentation, the move event
419 // is sent immediately after the enter event, do so (only if
420 // the target accepts it)
421 if (!first || dropReply == DOR_DROP) {
422 QDragEnterEvent dme(pnt, toQDragDropActions(supportedOps), data,
423 QApplication::mouseButtons(),
424 QApplication::keyboardModifiers());
425 // accept by default, since enter event was accepted.
426 dme.setDropAction(action);
427 dme.accept();
428 QApplication::sendEvent(dragOverWidget, &dme);
429 if (sourceAllowsOp && dme.isAccepted()) {
430 dropReply = DOR_DROP;
431 action = dme.dropAction();
432 lastProposedAction = dme.proposedAction();
433 lastAnswerRect = dme.answerRect();
434 } else {
435 dropReply = DOR_NODROP;
436 }
437 }
438
439 lastDropReply = dropReply;
440 lastOpRequest = toPmDragDropOp(action);
441 }
442
443 DrgFreeDraginfo(info);
444
445 return MRFROM2SHORT(dropReply, lastOpRequest);
446 }
447 case DM_DRAGLEAVE: {
448 DEBUG(("DM_DRAGLEAVE: hwnd %lX di %p", qmsg.hwnd, qmsg.mp1));
449
450 // Odin32 apps produce incorrect message flow, ignore
451 Q_ASSERT(lastDragOverWidget != 0);
452 if (lastDragOverWidget == 0)
453 return 0;
454
455 QDragLeaveEvent de;
456 QApplication::sendEvent(lastDragOverWidget, &de);
457
458 lastDragOverWidget = 0;
459 dragData.reset();
460
461 return 0;
462 }
463 case DM_DROP: {
464 DEBUG(("DM_DROP: hwnd %lX di %p", qmsg.hwnd, qmsg.mp1));
465
466 // Odin32 apps produce incorrect message flow, ignore
467 Q_ASSERT(lastDragOverWidget != 0);
468 if (lastDragOverWidget == 0)
469 return 0;
470
471 // Odin32 apps send DM_DROP even if we replied DOR_NEVERDROP or
472 // DOR_NODROP, simulate DM_DRAGLEAVE
473 Q_ASSERT(lastDropReply == DOR_DROP);
474 if (lastDropReply != DOR_DROP) {
475 QMSG qmsg2 = qmsg;
476 qmsg2.msg = DM_DRAGLEAVE;
477 qt_dispatchDragAndDrop(widget, qmsg2);
478 return 0;
479 }
480
481 DRAGINFO *info = (DRAGINFO *)qmsg.mp1;
482 ok = DrgAccessDraginfo(info);
483 Q_ASSERT(ok && info);
484 if (!ok || !info)
485 return 0;
486
487 // flip y coordinate
488 QPoint pnt(info->xDrop, info->yDrop);
489 pnt.setY(QApplication::desktop()->height() - (pnt.y() + 1));
490 pnt = widget->mapFromGlobal(pnt);
491
492 Q_ASSERT(lastOpRequest == info->usOperation);
493
494 Q_ASSERT(lastDragOverWidget->acceptDrops());
495 if (!lastDragOverWidget->acceptDrops()) {
496 DrgDeleteDraginfoStrHandles(info);
497 DrgFreeDraginfo(info);
498 return 0;
499 }
500
501 QDragManager *manager = QDragManager::self();
502 QMimeData *data = manager->source() ? manager->dragPrivate()->data : manager->dropData;
503
504 Qt::DropAction action = toQDragDropAction(lastOpRequest);
505
506 dragData.setDropped(true);
507
508 QDropEvent de(pnt, action, data, QApplication::mouseButtons(),
509 QApplication::keyboardModifiers());
510 if (lastDropReply == DOR_DROP)
511 de.setDropAction(action);
512
513 QApplication::sendEvent(lastDragOverWidget, &de);
514
515 if (lastDropReply == DOR_DROP)
516 de.accept();
517
518 lastDragOverWidget = 0;
519 dragData.reset(de.isAccepted());
520
521 ULONG targetReply = de.isAccepted() ?
522 DMFL_TARGETSUCCESSFUL : DMFL_TARGETFAIL;
523
524 if (de.isAccepted() && de.dropAction() == Qt::TargetMoveAction) {
525 // Qt::TargetMoveAction means that the target will move the
526 // object (e.g. copy it to itself and delete from the source).
527 // In this case, we always send DMFL_TARGETFAIL to the source to
528 // prevent it from doing the same on its side.
529 targetReply = DMFL_TARGETFAIL;
530 }
531
532 // send DM_ENDCONVERSATION for every item
533 ULONG itemCount = DrgQueryDragitemCount(info);
534 for (ULONG i = 0; i < itemCount; ++ i) {
535 PDRAGITEM item = DrgQueryDragitemPtr(info, i);
536 Q_ASSERT(item);
537 if (!item)
538 continue;
539 // it is possible that this item required DM_RENDERPREPARE but
540 // returned false in reply to it (so hwndItem may be NULL)
541 if (!item->hwndItem)
542 continue;
543 DrgSendTransferMsg(item->hwndItem, DM_ENDCONVERSATION,
544 MPFROMLONG(item->ulItemID),
545 MPFROMLONG(targetReply));
546 }
547
548 DrgDeleteDraginfoStrHandles(info);
549 DrgFreeDraginfo(info);
550
551 return 0;
552 }
553 default:
554 break;
555 }
556
557 return WinDefWindowProc(qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2);
558}
559
560//---------------------------------------------------------------------
561// QDropData
562//---------------------------------------------------------------------
563
564bool QDropData::hasFormat_sys(const QString &mimeType) const
565{
566 Q_ASSERT(d);
567 if (d)
568 return d->hasFormat_sys(mimeType);
569 return false;
570}
571
572QStringList QDropData::formats_sys() const
573{
574 QStringList fmts;
575 Q_ASSERT(d);
576 if (d)
577 fmts = d->formats_sys();
578 return fmts;
579}
580
581QVariant QDropData::retrieveData_sys(const QString &mimeType, QVariant::Type type) const
582{
583 QVariant result;
584 Q_ASSERT(d);
585 if (d)
586 result = d->retrieveData_sys(mimeType, type);
587 return result;
588}
589
590Qt::DropAction QDragManager::drag(QDrag *o)
591
592{
593 DEBUG(() << "QDragManager::drag");
594
595 // @todo implement
596 return Qt::IgnoreAction;
597}
598
599void QDragManager::cancel(bool /* deleteSource */)
600{
601 // @todo implement
602}
603
604void QDragManager::updatePixmap()
605{
606 // not used in PM implementation
607}
608
609bool QDragManager::eventFilter(QObject *, QEvent *)
610{
611 // not used in PM implementation
612 return false;
613}
614
615void QDragManager::timerEvent(QTimerEvent*)
616{
617 // not used in PM implementation
618}
619
620void QDragManager::move(const QPoint &)
621{
622 // not used in PM implementation
623}
624
625void QDragManager::drop()
626{
627 // not used in PM implementation
628}
629
630QT_END_NAMESPACE
631
632#endif // QT_NO_DRAGANDDROP
Note: See TracBrowser for help on using the repository browser.