When is the signal QDrag::targetChanged emitted?
I asked about when QDrag::targetChanged was emitted, and also I asked why, following a drop, the result of QDrag::exec() is 0 (Qt.IgnoreAction) and the target() method returned null (Python None).
After simplifying my test case I found the following:
QDrag::targetChanged is emitted, but only when the drag enters an accepting Qt Widget in the same Qt app, that is, a Widget which
- has setAcceptDrops(True)
- implements dragEnterEvent()
- AND calls event->acceptProposedAction on the enter event to accept the drop
Under these conditions the signal is emitted with a "target" argument of the accepting widget. Once the drag has entered the boundary of an accepting Widget, if you continue to drag out of that Widget, targetChanged is also emitted as the drag leaves, passing a "target" of a null QObject (Python None).
To summarize, it appears that QDrag::targetChanged is emitted exactly and only when an accepting widget in the same app executes its dragEnterEvent() or its dragLeaveEvent(). Note if the accepting widget is in a different Qt app, it will execute its dragEnterEvent and dropEvent but targetChanged will not be issued.
This means that you cannot use this signal to detect when a drag has crossed your window boundary over to the desktop or to a different application -- even if that application or desktop will accept the drop, even if it is a Qt app! For example I have tested a successful drop onto the Mac OS desktop (the dropped mime data appears as a Mac "clipping" file) or onto another app like an editor, the data is dropped correctly, but targetChanged is not issued -- presumably because these non-Qt targets do not execute dragEnterEvent?
I have also found that if the dragEnterEvent() code does not accept the proposed action, neither the dragMoveEvent() nor the dragLeaveEvent() will ever be called! This means that you cannot use dragEnterEvent to change the appearance of a widget to indicate it refuses the drop, expecting to clear the visual indication on dragLeaveEvent. For example, you cannot make your widget turn red when an unwanted drop enters it, because if you do, it will remain red forever because your dragLeaveEvent will not be executed.
If a drop is successful on a Qt Widget (that is, its dropEvent() method is executed) then the value of QDrag::target() will be that widget after QDrag::exec() completes. However, if the drop is rejected, or if the recipient is a different app, target() returns a null pointer or Python None. Thus you cannot use QDrag::target() to find out for certain whether a drop succeeded. It might have succeeded off-app, or it might not.
In all cases of drops, successful and rejected, to Qt widgets or to foreign widgets, the returned value of QDrag::exec() has always been 0 (Qt.IgnoreAction) in my tests. So far I cannot find out when, if ever, QDrag::exec() returns some action value other than 0.
Oh and in case anyone wants to try my test case, here it is: http://pastebin.com/LUMYyWCZ
You can start two copies, e.g.
and drag from one to the other.
I have now put EVERYTHING that I have learned about drag and drop in a series of blog posts that can be read here: http://thispageintentionally.blogspot.com/2014/01/ts-drag-and-drop-architecture-for.html
My findings regarding the targetChanged signal for anyone who wanders in here:
(a) If drag cursor dragEnterEvent()s a Widget and it acceptProposedAction()s, targetChanged(Widget) is emitted.
(b) If the Widget ignore()s the dragEnterEvent, targetChanged(nullptr) is emitted and it is not emitted anymore unless (a) happens again.
My strong but unverified belief: if drag cursor leaves the Window that acceptProposedAction()s in dragEnterEvent, targetChanged(nullptr) is emitted.
cool. thx for sharing.