public final class NativeDragAndDrop
- Object
- NativeDragAndDrop
Drag and drop through the operating system rather than inside the application.
Codename One has always had a lightweight drag and drop – Component#setDraggable(boolean)
and Component#setDropTarget(boolean) – which moves a rendered image around inside one
form. That never leaves the application, so it cannot drop a file on the desktop, cannot
carry text into another application’s window, and cannot receive anything from one.
This class is the other half: it hands the drag to the operating system’s own drag machinery,
using the same ClipboardContent a copy publishes as the payload. That is the whole idea –
a drag is a copy that the user aims with the pointer, so anything the application can already
put on the clipboard it can already drag out, and anything it can paste it can already accept
as a drop.
Dragging out
Label file = new Label("report.pdf");
file.setNativeDragOperation(NativeDragOperation.createFileDrag(
new String[] { FileSystemStorage.getInstance().getAppHomePath() + "report.pdf" }));
Dropping that on the desktop, on a mail composer or into a file manager copies the file,
because the receiving application asked for ClipboardContent#MIME_FILE and the drag
offered it. Offer several representations and every receiver takes the best one it
understands.
Receiving a drop
Container inbox = new Container();
inbox.setNativeDropTarget(true);
inbox.addNativeDropListener(e -> {
NativeDropEvent drop = (NativeDropEvent)e;
String[] files = drop.getFiles();
...
});
Where it works
Native drag and drop needs the platform to have it. Check #isSupported() before offering
the affordance, and #isDragOutsideApplicationSupported() before promising the user that a
drag can leave the application: a desktop can drop onto any other window, a tablet can drop
into another application beside it, and a phone in full screen has nowhere for a drag to go
even though drags within the application still work. Where nothing is supported the calls
here are harmless no-ops and the lightweight drag and drop is unaffected.
Threading
The gesture half runs on the event dispatch thread; the receiving half is called from whatever thread the platform hands the port. All of the shared state below is therefore guarded by one lock, and no callback into component or port code is ever made while holding it – the framework’s own event dispatch thread blocks on the platform’s UI thread to paint on some ports, so a lock held across a callback is a deadlock waiting for the first drag.
Fields
public static final int NO_HOVER_GENERATION = -1 | What a port that does not track the hover generation passes, which asks for the recovery to be attempted on whatever hover state is there – the behaviour every port had before one of them could tell. |
Methods
public static boolean isSupported() | Returns true when this platform can drag and drop through the operating system at all. |
public static boolean isDragOutsideApplicationSupported() | Returns true when a drag started here can be dropped outside the application: on the desktop, in a file manager or in another application’s window. |
public static boolean startDrag(Component source, NativeDragOperation op) | Starts a native drag immediately, for an application that decides on its own that a drag has begun – from a long press, or a menu item – rather than letting a component do it through Component#setNativeDragSource(boolean). |
public static NativeDragOperation dragSessionStarted() | Reports that the platform started a drag session on its own, for the operation the press prepared. |
public static Object produceDragValue(NativeDragOperation op, String mimeType) | Produces one representation of a drag for the session that is reading it, rather than for whichever transfer armed the operation last. |
public static void beginTransfer(ClipboardContent content) | The same, for a transfer that is not a drag: a clipboard a port keeps lazily, whose content the application may also be dragging. |
public static Object produceTransferValue(ClipboardContent content, String mimeType) | |
public static NativeDragOperation getActiveDrag() | Returns the drag this application is currently running through the operating system, or null when it is not dragging. |
public static void gestureCancelled() | Abandons whatever a press staged, because the gesture it belonged to is over or has turned into something else. |
public static int dragEnter(int windowId, int x, int y, ClipboardContent content, int allowedActions) | Reports that a native drag has entered one of the application’s surfaces. |
public static int dragOver(int windowId, int x, int y, ClipboardContent content, int allowedActions) | Reports that a native drag has moved over one of the application’s surfaces, and answers whether it would be accepted here. |
public static void dragExit(int windowId) | Reports that a native drag has left the application’s surfaces without dropping. |
public static int drop(int windowId, int x, int y, ClipboardContent content, int action) | Delivers a native drop. |
public static int drop(int windowId, int x, int y, ClipboardContent content, int action, int advertisedActions, boolean local) | Delivers a native drop whose origin the port knows. |
public static int deferredDrop(int windowId, int x, int y, ClipboardContent content, int action, int advertisedActions, boolean local) | Delivers a native drop whose action was already decided when the user released. |
public static int deferredDrop(int windowId, int x, int y, ClipboardContent content, int action, int advertisedActions, boolean local, int hoverGeneration) | The same, for a port that took a hover generation when the drop began. |
public static int hoverGeneration() | The generation of the hover state a drop assembled later can quote back; see #deferredDrop(int, int, int, com.codename1.ui.ClipboardContent, int, int, boolean, int). |
public static int plannedDropAction(int windowId, int x, int y, ClipboardContent content, int action) | The action a drop at this position would perform, without dispatching anything or disturbing the drag in progress. |
public static void dragCompleted(int performedAction) | Reports that the session started by #startDrag(com.codename1.ui.Component, com.codename1.ui.NativeDragOperation) has finished, whatever the outcome, so that a source offering NativeDragOperation#ACTION_MOVE learns whether to delete its copy. |
Inherited methods
Field details
NO_HOVER_GENERATION
public static final int NO_HOVER_GENERATION = -1Method details
isSupported
public static boolean isSupported()isDragOutsideApplicationSupported
public static boolean isDragOutsideApplicationSupported()Returns true when a drag started here can be dropped outside the application: on the desktop, in a file manager or in another application’s window.
This is narrower than #isSupported(). A platform can route drags between components,
and between this application’s own windows, while still refusing to let one leave –
which is the normal state of affairs on a phone.
startDrag
public static boolean startDrag(Component source, NativeDragOperation op)Starts a native drag immediately, for an application that decides on its own that a drag
has begun – from a long press, or a menu item – rather than letting a component do it
through Component#setNativeDragSource(boolean).
Call this on the event dispatch thread while the pointer is still down; a drag the user is not currently holding cannot be aimed and platforms reject it.
Parameters
sourceComponent- the component the drag comes from, used for the default drag image and
reported by
NativeDragOperation#getSource(). May be null. opNativeDragOperation- what is being dragged
Returns
dragSessionStarted
public static NativeDragOperation dragSessionStarted()com.codename1.impl.CodenameOneImplementation#startNativeDrag(com.codename1.ui.NativeDragOperation).Returns
produceDragValue
public static Object produceDragValue(NativeDragOperation op, String mimeType)Produces one representation of a drag for the session that is reading it, rather than for whichever transfer armed the operation last.
For a port whose platform keeps an older session readable while a newer one runs –
iOS does, for as long as a receiver holds one of its item providers – and which
therefore keeps a memo of its own, one per session. Reading through the operation
instead would hand that receiver the newer drag’s value, or produce a second one for
a drag that had already ended. Every other port reads through
ClipboardContent#getData(java.lang.String), whose memory is the running transfer’s
and is exactly right when only one session can be read at a time.
Parameters
opNativeDragOperation- the operation the session is carrying, which may be null
mimeTypeString- the representation being read
Returns
beginTransfer
public static void beginTransfer(ClipboardContent content)The same, for a transfer that is not a drag: a clipboard a port keeps lazily, whose content the application may also be dragging.
A copy and a drag can share one ClipboardContent, and arming a drag forgets what
its providers produced. Reading the content’s own memory then gave the clipboard
whatever the drag had most recently produced – for a provider that writes a file
per transfer, a path belonging to that drag, which its cleanup may since have
deleted. A port in that position produces its own value and remembers it itself.
Parameters
contentClipboardContent- the representations being transferred, which may be null
Returns
the value, or null when there is no such representation Ends a content’s memory of what its providers produced, because a new transfer of it is beginning.
A representation registered through
ClipboardContent#setDataProvider(java.lang.String, com.codename1.ui.ClipboardDataProvider)
is resolved once per transfer and remembered, so a consumer that asks twice does not
make the provider write its file twice. Display#copyToClipboard(ClipboardContent)
calls this as a copy is asked for. A port that assembles later, or on another thread,
needs more than this – two overlapping transfers of one content would share the memory
this resets – and reads through a memo of its own instead; see the Android port’s
clip assembly.
produceTransferValue
public static Object produceTransferValue(ClipboardContent content, String mimeType)getActiveDrag
public static NativeDragOperation getActiveDrag()NativeDropEvent#isLocal() reports.gestureCancelled
public static void gestureCancelled()Abandons whatever a press staged, because the gesture it belonged to is over or has turned into something else.
A release is the ordinary way that happens and the framework calls this itself. A port calls it for the ways that are not a release: a touch the platform cancels outright, which delivers no release at all, and anything else that ends a gesture without one. Leaving an operation staged past its gesture is what lets a later, unrelated movement start a drag nobody asked for.
dragEnter
public static int dragEnter(int windowId, int x, int y, ClipboardContent content, int allowedActions)Parameters
windowIdint- the id of the window the drag is over, or zero for the main surface
xint- the pointer position within that surface
yint- the pointer position within that surface
contentClipboardContent- the representations the drag is offering
allowedActionsint- the actions the source permits
Returns
NativeDragOperation#ACTION_NONE when
nothing under the pointer will take itdragOver
public static int dragOver(int windowId, int x, int y, ClipboardContent content, int allowedActions)Reports that a native drag has moved over one of the application’s surfaces, and answers whether it would be accepted here.
Threading
The operating system needs the answer synchronously, while the framework’s callbacks
have to run on the event dispatch thread – and blocking a native drag thread on the
event dispatch thread deadlocks, because on some ports the event dispatch thread is
itself waiting on that native thread to paint. So the target is resolved here, on the
calling thread, from state that does not change under it, while
Component#nativeDragOver(com.codename1.ui.NativeDropEvent) and the listeners are
dispatched asynchronously; the value returned is the one they produced for the
previous event on this same target. A target that changes its mind therefore shows the
user the new cursor one drag event late, which is a frame, and never blocks.
A target that refuses a drop outright should say so through
Component#canAcceptNativeDrop(com.codename1.ui.ClipboardContent) or the accepted MIME
list instead, both of which are consulted here and are therefore exact from the first
event – and from every event, including the drop itself. A NativeDropEvent#reject()
in a callback is a change of mind rather than a refusal: it is honoured from the next
event onward, and a drop landing before the callback has run reads what the target
declared. #drop(int, int, int, com.codename1.ui.ClipboardContent, int) says why that
cannot be closed without doing something worse.
Parameters
windowIdint- the id of the window the drag is over, or zero for the main surface
xint- the pointer position within that surface
yint- the pointer position within that surface
contentClipboardContent- the representations the drag is offering
allowedActionsint- the actions the source permits
Returns
NativeDragOperation#ACTION_NONEdragExit
public static void dragExit(int windowId)Parameters
windowIdint- the id of the window the drag left, or zero for the main surface
drop
public static int drop(int windowId, int x, int y, ClipboardContent content, int action)Delivers a native drop.
The content must be fully materialized before this is called: on most platforms the native transfer object is only readable inside the drop callback, so a port that hands over a lazy view of it delivers empty data by the time the event dispatch thread reads it.
Parameters
windowIdint- the id of the window dropped on, or zero for the main surface
xint- the pointer position within that surface
yint- the pointer position within that surface
contentClipboardContent- the dropped representations
actionint- the action the operating system settled on
Returns
NativeDragOperation#ACTION_NONE when nothing under
the pointer took the drop and the port should report the transfer as faileddrop
public static int drop(int windowId, int x, int y, ClipboardContent content, int action, int advertisedActions, boolean local)Delivers a native drop whose origin the port knows.
A port that assembles a drop asynchronously calls this one, because by the time the
assembly finishes the drag it belongs to may no longer be the one running: a drop that
arrived from another application, still loading when the user began a drag of their
own, would otherwise be reported to the target as local – and a target that uses
NativeDropEvent#isLocal() to tell reordering from importing would treat foreign
content as an internal move.
Parameters
windowIdint- Not documented.
xint- Not documented.
yint- Not documented.
contentClipboardContent- Not documented.
actionint- Not documented.
advertisedActionsint- the mask this drag offered, or
NativeDragOperation#ACTION_NONEto use whatever the last drag event advertised. Carried for the same reason as the locality beside it: a newer drag has since overwritten what the framework remembers, and giving this drop that newer mask made its event report an action the source never offered – or, when the newer drag is narrower, report nothing accepted at all while the platform had been told the drop succeeded. localboolean- true when the drag being dropped is one this application started
Returns
NativeDragOperation#ACTION_NONEdeferredDrop
public static int deferredDrop(int windowId, int x, int y, ClipboardContent content, int action, int advertisedActions, boolean local)Delivers a native drop whose action was already decided when the user released.
For a port that assembles a drop asynchronously and keeps that session’s decision with the session – iOS does both. The ordinary entry point prefers what the component hovering said last, because a port’s action is by construction one drag event behind; but a drop that has been loading is no longer the hovering session, and the framework keeps one hover state. Another drop hovering the same component in the meantime would otherwise lend this one its decision: its rejection would discard a drop the user had actually performed, and its acceptance would change the action this one reports.
So the caller’s action is taken as the answer here, narrowed only by what the target still permits. The hover state is cleared as it is for any drop – a component holding a highlight for a drag that has moved on is sent an exit and re-entered by its next update, which is a frame that repairs itself, where a discarded drop is work the user did and lost.
Parameters
windowIdint- Not documented.
xint- Not documented.
yint- Not documented.
contentClipboardContent- Not documented.
actionint- the action this drop’s own session settled on when it was released
advertisedActionsint- Not documented.
localboolean- Not documented.
Returns
NativeDragOperation#ACTION_NONEdeferredDrop
public static int deferredDrop(int windowId, int x, int y, ClipboardContent content, int action, int advertisedActions, boolean local, int hoverGeneration)The same, for a port that took a hover generation when the drop began.
The recovery for a target whose position moved reads the hover the drag left behind, and
by the time a slow provider has finished that hover can belong to a session that arrived
since – so the payload of one drop was handed to the target of another. A port that
quotes back what #hoverGeneration() answered when the user released tells this apart:
the hover is this drop’s while the number still matches, and is somebody else’s the
moment it does not.
Parameters
windowIdint- Not documented.
xint- Not documented.
yint- Not documented.
contentClipboardContent- Not documented.
actionint- Not documented.
advertisedActionsint- Not documented.
localboolean- Not documented.
hoverGenerationint- what
#hoverGeneration()answered when the drop began, or#NO_HOVER_GENERATIONfrom a port that does not track it
hoverGeneration
public static int hoverGeneration()#deferredDrop(int, int, int, com.codename1.ui.ClipboardContent, int, int, boolean, int).plannedDropAction
public static int plannedDropAction(int windowId, int x, int y, ClipboardContent content, int action)The action a drop at this position would perform, without dispatching anything or disturbing the drag in progress.
A port whose platform commits to an action before it can read the transferred data –
AWT does, because a drop has to be accepted before it becomes readable – asks here
first, so that what it commits to is what #drop(int, int, int, com.codename1.ui.ClipboardContent, int) will go on to report. Committing the platform’s
own stale action instead told the source a copy had happened while the target was handed
a move.
Parameters
windowIdint- the id of the window the drag is over, or zero for the main surface
xint- the pointer position within that surface
yint- the pointer position within that surface
contentClipboardContent- the representations the drag is offering, which may still be a description rather than the materialized payload
actionint- the action the platform is proposing
Returns
NativeDragOperation#ACTION_NONEdragCompleted
public static void dragCompleted(int performedAction)#startDrag(com.codename1.ui.Component, com.codename1.ui.NativeDragOperation) has finished, whatever the outcome, so that a
source offering NativeDragOperation#ACTION_MOVE learns whether to delete its copy.Parameters
performedActionint- the action the receiver performed, or
NativeDragOperation#ACTION_NONEwhen the drag was cancelled or refused