Class NativeDragAndDrop

java.lang.Object
com.codename1.ui.NativeDragAndDrop

public final class NativeDragAndDrop extends Object

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.

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final int
    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.
  • Method Summary

    Modifier and Type
    Method
    Description
    static void
    The same, for a transfer that is not a drag: a clipboard a port keeps lazily, whose content the application may also be dragging.
    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.
    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.
    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.
    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.
    static void
    dragExit(int windowId)
    Reports that a native drag has left the application's surfaces without dropping.
    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.
    Reports that the platform started a drag session on its own, for the operation the press prepared.
    static int
    drop(int windowId, int x, int y, ClipboardContent content, int action)
    Delivers a native drop.
    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.
    static void
    Abandons whatever a press staged, because the gesture it belonged to is over or has turned into something else.
    Returns the drag this application is currently running through the operating system, or null when it is not dragging.
    static int
    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).
    static boolean
    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.
    static boolean
    Returns true when this platform can drag and drop through the operating system at all.
    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.
    static Object
    Produces one representation of a drag for the session that is reading it, rather than for whichever transfer armed the operation last.
    static Object
     
    static boolean
    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).

    Methods inherited from class Object

    clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • NO_HOVER_GENERATION

      public static final int NO_HOVER_GENERATION
      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.
      See Also:
  • Method Details

    • isSupported

      public static boolean isSupported()
      Returns true when this platform can drag and drop through the operating system at all. Where this is false every method here does nothing and reports failure, so no call site needs to be conditional -- but an application that shows a "drag me" affordance should hide it.
    • 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
      • source: the component the drag comes from, used for the default drag image and reported by NativeDragOperation#getSource(). May be null.

      • op: what is being dragged

      Returns

      true when the operating system took the drag; false when the platform has no native drag and drop, refused to start a session, or is already running one

    • dragSessionStarted

      public static NativeDragOperation dragSessionStarted()

      Reports that the platform started a drag session on its own, for the operation the press prepared. Ports whose operating system owns the drag gesture -- where a long press, not the framework's own threshold, is what begins a drag -- call this instead of returning true from com.codename1.impl.CodenameOneImplementation#startNativeDrag(com.codename1.ui.NativeDragOperation).

      Returns

      the operation the session is carrying, or null when nothing was prepared -- in which case the port should refuse to start a session

    • 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
      • op: the operation the session is carrying, which may be null

      • mimeType: the representation being read

      Returns

      the value, or null when there is no such operation or representation

    • 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
      • content: the representations being transferred, which may be null

      • mimeType: the representation being read

      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.

      Parameters
      • content: the content about to be published, which may be null
    • produceTransferValue

      public static Object produceTransferValue(ClipboardContent content, String mimeType)
    • getActiveDrag

      public static NativeDragOperation getActiveDrag()
      Returns the drag this application is currently running through the operating system, or null when it is not dragging. A drop target uses this to tell a drag it started itself from one that arrived from elsewhere, which 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)

      Reports that a native drag has entered one of the application's surfaces.

      Parameters
      • windowId: the id of the window the drag is over, or zero for the main surface

      • x: the pointer position within that surface

      • y: the pointer position within that surface

      • content: the representations the drag is offering

      • allowedActions: the actions the source permits

      Returns

      the action a drop would perform right now, or NativeDragOperation#ACTION_NONE when nothing under the pointer will take it

    • dragOver

      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
      • windowId: the id of the window the drag is over, or zero for the main surface

      • x: the pointer position within that surface

      • y: the pointer position within that surface

      • content: the representations the drag is offering

      • allowedActions: the actions the source permits

      Returns

      the action a drop would perform right now, or NativeDragOperation#ACTION_NONE

    • dragExit

      public static void dragExit(int windowId)

      Reports that a native drag has left the application's surfaces without dropping.

      Parameters
      • windowId: 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
      • windowId: the id of the window dropped on, or zero for the main surface

      • x: the pointer position within that surface

      • y: the pointer position within that surface

      • content: the dropped representations

      • action: the action the operating system settled on

      Returns

      the action actually accepted, or NativeDragOperation#ACTION_NONE when nothing under the pointer took the drop and the port should report the transfer as failed

    • 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.

      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
      • advertisedActions: the mask this drag offered, or NativeDragOperation#ACTION_NONE to 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.

      • local: true when the drag being dropped is one this application started

      Returns

      the action actually accepted, or NativeDragOperation#ACTION_NONE

    • deferredDrop

      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
      • action: the action this drop's own session settled on when it was released
      Returns

      the action actually accepted, or NativeDragOperation#ACTION_NONE

    • deferredDrop

      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
      • hoverGeneration: what #hoverGeneration() answered when the drop began, or #NO_HOVER_GENERATION from a port that does not track it
    • hoverGeneration

      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).
    • 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
      • windowId: the id of the window the drag is over, or zero for the main surface

      • x: the pointer position within that surface

      • y: the pointer position within that surface

      • content: the representations the drag is offering, which may still be a description rather than the materialized payload

      • action: the action the platform is proposing

      Returns

      the action the drop would perform, or NativeDragOperation#ACTION_NONE

    • dragCompleted

      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.

      Parameters
      • performedAction: the action the receiver performed, or NativeDragOperation#ACTION_NONE when the drag was cancelled or refused