< prev index next >
modules/javafx.swing/src/main/java/com/sun/javafx/embed/swing/FXDnD.java
Print this page
*** 21,31 ****
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
! package javafx.embed.swing;
import com.sun.javafx.tk.Toolkit;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
--- 21,31 ----
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
! package com.sun.javafx.embed.swing;
import com.sun.javafx.tk.Toolkit;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
*** 42,53 ****
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.dnd.InvalidDnDOperationException;
import java.awt.dnd.MouseDragGestureRecognizer;
- import java.awt.dnd.peer.DragSourceContextPeer;
- import java.awt.dnd.peer.DropTargetContextPeer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import javafx.application.Platform;
--- 42,51 ----
*** 56,564 ****
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
- import sun.awt.AWTAccessor;
- import sun.awt.dnd.SunDragSourceContextPeer;
- import sun.swing.JLightweightFrame;
/**
* A utility class to connect DnD mechanism of Swing and FX.
* It allows Swing content to use the FX machinery for performing DnD.
*/
! final class FXDnD {
! private final SwingNode node;
! private static boolean fxAppThreadIsDispatchThread;
private SwingNode getNode() { return node; }
static {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
fxAppThreadIsDispatchThread =
"true".equals(System.getProperty("javafx.embed.singleThread"));
return null;
}
});
- }
-
- FXDnD(SwingNode node) {
- this.node = node;
- }
-
- /**
- * Utility class that operates on Maps with Components as keys.
- * Useful when processing mouse events to choose an object from the map
- * based on the component located at the given coordinates.
- */
- private class ComponentMapper<T> {
- private int x, y;
- private T object = null;
-
- private ComponentMapper(Map<Component, T> map, int xArg, int yArg) {
- this.x = xArg;
- this.y = yArg;
-
- final JLightweightFrame lwFrame = node.getLightweightFrame();
- Component c = AWTAccessor.getContainerAccessor().findComponentAt(
- lwFrame, x, y, false);
- if (c == null) return;
-
- synchronized (c.getTreeLock()) {
- do {
- object = map.get(c);
- } while (object == null && (c = c.getParent()) != null);
-
- if (object != null) {
- // The object is either a DropTarget or a DragSource, so:
- //assert c == object.getComponent();
-
- // Translate x, y from lwFrame to component coordinates
- while (c != lwFrame && c != null) {
- x -= c.getX();
- y -= c.getY();
- c = c.getParent();
- }
- }
- }
- }
- }
- public <T> ComponentMapper<T> mapComponent(Map<Component, T> map, int x, int y) {
- return new ComponentMapper<T>(map, x, y);
- }
-
-
-
-
-
- ///////////////////////////////////////////////////////////////////////////
- // DRAG SOURCE IMPLEMENTATION
- ///////////////////////////////////////////////////////////////////////////
-
-
- private boolean isDragSourceListenerInstalled = false;
-
- // To keep track of where the DnD gesture actually started
- private MouseEvent pressEvent = null;
- private long pressTime = 0;
-
- private volatile SecondaryLoop loop;
-
- private final Map<Component, FXDragGestureRecognizer> recognizers = new HashMap<>();
-
- // Note that we don't really use the MouseDragGestureRecognizer facilities,
- // however some code in JDK may expect a descendant of this class rather
- // than a generic DragGestureRecognizer. So we inherit from it.
- private class FXDragGestureRecognizer extends MouseDragGestureRecognizer {
- FXDragGestureRecognizer(DragSource ds, Component c, int srcActions,
- DragGestureListener dgl)
- {
- super(ds, c, srcActions, dgl);
-
- if (c != null) recognizers.put(c, this);
- }
-
- @Override public void setComponent(Component c) {
- final Component old = getComponent();
- if (old != null) recognizers.remove(old);
- super.setComponent(c);
- if (c != null) recognizers.put(c, this);
- }
-
- protected void registerListeners() {
- SwingFXUtils.runOnFxThread(() -> {
- if (!isDragSourceListenerInstalled) {
- node.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressHandler);
- node.addEventHandler(MouseEvent.DRAG_DETECTED, onDragStartHandler);
- node.addEventHandler(DragEvent.DRAG_DONE, onDragDoneHandler);
-
- isDragSourceListenerInstalled = true;
- }
- });
- }
-
- protected void unregisterListeners() {
- SwingFXUtils.runOnFxThread(() -> {
- if (isDragSourceListenerInstalled) {
- node.removeEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressHandler);
- node.removeEventHandler(MouseEvent.DRAG_DETECTED, onDragStartHandler);
- node.removeEventHandler(DragEvent.DRAG_DONE, onDragDoneHandler);
-
- isDragSourceListenerInstalled = false;
- }
- });
- }
-
- private void fireEvent(int x, int y, long evTime, int modifiers) {
- // In theory we should register all the events that trigger the gesture (like PRESS, DRAG, DRAG, BINGO!)
- // But we can live with this hack for now.
- appendEvent(new java.awt.event.MouseEvent(getComponent(), java.awt.event.MouseEvent.MOUSE_PRESSED,
- evTime, modifiers, x, y, 0, false));
-
- // Also, the modifiers here should've actually come from the last known mouse event (last MOVE or DRAG).
- // But we're OK with using the initial PRESS modifiers for now
- int initialAction = SunDragSourceContextPeer.convertModifiersToDropAction(
- modifiers, getSourceActions());
-
- fireDragGestureRecognized(initialAction, new java.awt.Point(x, y));
- }
- }
-
- // Invoked on EDT
- private void fireEvent(int x, int y, long evTime, int modifiers) {
- ComponentMapper<FXDragGestureRecognizer> mapper = mapComponent(recognizers, x, y);
-
- final FXDragGestureRecognizer r = mapper.object;
- if (r != null) {
- r.fireEvent(mapper.x, mapper.y, evTime, modifiers);
- } else {
- // No recognizer, no DnD, no startDrag, so release the FX loop now
- SwingFXUtils.leaveFXNestedLoop(FXDnD.this);
- }
- }
-
- private MouseEvent getInitialGestureEvent() {
- return pressEvent;
- }
-
- private final EventHandler<MouseEvent> onMousePressHandler = (event) -> {
- // It would be nice to maintain a list of all the events that initiate
- // a DnD gesture (see a comment in FXDragGestureRecognizer.fireEvent().
- // For now, we simply use the initial PRESS event for this purpose.
- pressEvent = event;
- pressTime = System.currentTimeMillis();
- };
-
-
- private volatile FXDragSourceContextPeer activeDSContextPeer;
-
- private final EventHandler<MouseEvent> onDragStartHandler = (event) -> {
- // Call to AWT and determine the active DragSourceContextPeer
- activeDSContextPeer = null;
- final MouseEvent firstEv = getInitialGestureEvent();
- SwingFXUtils.runOnEDTAndWait(FXDnD.this, () -> fireEvent(
- (int)firstEv.getX(), (int)firstEv.getY(), pressTime,
- SwingEvents.fxMouseModsToMouseMods(firstEv)));
- if (activeDSContextPeer == null) return;
-
- // Since we're going to start DnD, consume the event.
- event.consume();
-
- Dragboard db = getNode().startDragAndDrop(SwingDnD.dropActionsToTransferModes(
- activeDSContextPeer.sourceActions).toArray(new TransferMode[1]));
-
- // At this point the activeDSContextPeer.transferable contains all the data from AWT
- Map<DataFormat, Object> fxData = new HashMap<>();
- for (String mt : activeDSContextPeer.transferable.getMimeTypes()) {
- DataFormat f = DataFormat.lookupMimeType(mt);
- //TODO: what to do if f == null?
- if (f != null) fxData.put(f, activeDSContextPeer.transferable.getData(mt));
- }
-
- final boolean hasContent = db.setContent(fxData);
- if (!hasContent) {
- // No data, no DnD, no onDragDoneHandler, so release the AWT loop now
- if (!fxAppThreadIsDispatchThread) {
- loop.exit();
- }
- }
- };
-
- private final EventHandler<DragEvent> onDragDoneHandler = (event) -> {
- event.consume();
! // Release FXDragSourceContextPeer.startDrag()
! if (!fxAppThreadIsDispatchThread) {
! loop.exit();
}
!
! if (activeDSContextPeer != null) {
! final TransferMode mode = event.getTransferMode();
! activeDSContextPeer.dragDone(
! mode == null ? 0 : SwingDnD.transferModeToDropAction(mode),
! (int)event.getX(), (int)event.getY());
! }
! };
!
!
! private final class FXDragSourceContextPeer extends SunDragSourceContextPeer {
! private volatile int sourceActions = 0;
!
! private final CachingTransferable transferable = new CachingTransferable();
!
! @Override public void startSecondaryEventLoop(){
! Toolkit.getToolkit().enterNestedEventLoop(this);
! }
! @Override public void quitSecondaryEventLoop(){
! assert !Platform.isFxApplicationThread();
! Platform.runLater(() -> Toolkit.getToolkit().exitNestedEventLoop(FXDragSourceContextPeer.this, null));
! }
!
! @Override protected void setNativeCursor(long nativeCtxt, Cursor c, int cType) {
! //TODO
}
!
! private void dragDone(int operation, int x, int y) {
! dragDropFinished(operation != 0, operation, x, y);
! }
!
! FXDragSourceContextPeer(DragGestureEvent dge) {
! super(dge);
}
!
! // It's Map<Long, DataFlavor> actually, but javac complains if the type isn't erased...
! @Override protected void startDrag(Transferable trans, long[] formats, Map formatMap)
! {
! activeDSContextPeer = this;
!
! // NOTE: we ignore the formats[] and the formatMap altogether.
! // AWT provides those to allow for more flexible representations of
! // e.g. text data (in various formats, encodings, etc.) However, FX
! // code isn't ready to handle those (e.g. it can't digest a
! // StringReader as data, etc.) So instead we perform our internal
! // translation.
! // Note that fetchData == true. FX doesn't support delayed data
! // callbacks yet anyway, so we have to fetch all the data from AWT upfront.
! transferable.updateData(trans, true);
!
! sourceActions = getDragSourceContext().getSourceActions();
!
! // Release the FX nested loop to allow onDragDetected to start the actual DnD operation,
! // and then start an AWT nested loop to wait until DnD finishes.
! if (!fxAppThreadIsDispatchThread) {
! loop = java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
! SwingFXUtils.leaveFXNestedLoop(FXDnD.this);
! if (!loop.enter()) {
! // An error occured, but there's little we can do here...
! }
! }
}
- };
public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
Class<T> abstractRecognizerClass,
DragSource ds, Component c, int srcActions,
DragGestureListener dgl)
{
! return (T) new FXDragGestureRecognizer(ds, c, srcActions, dgl);
! }
!
! public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException
! {
! return new FXDragSourceContextPeer(dge);
! }
!
!
!
!
!
! ///////////////////////////////////////////////////////////////////////////
! // DROP TARGET IMPLEMENTATION
! ///////////////////////////////////////////////////////////////////////////
!
!
! private boolean isDropTargetListenerInstalled = false;
! private volatile FXDropTargetContextPeer activeDTContextPeer = null;
! private final Map<Component, DropTarget> dropTargets = new HashMap<>();
!
! private final EventHandler<DragEvent> onDragEnteredHandler = (event) -> {
! if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
!
! int action = activeDTContextPeer.postDropTargetEvent(event);
!
! // If AWT doesn't accept anything, let parent nodes handle the event
! if (action != 0) event.consume();
! };
!
! private final EventHandler<DragEvent> onDragExitedHandler = (event) -> {
! if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
!
! activeDTContextPeer.postDropTargetEvent(event);
!
! activeDTContextPeer = null;
! };
!
! private final EventHandler<DragEvent> onDragOverHandler = (event) -> {
! if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
!
! int action = activeDTContextPeer.postDropTargetEvent(event);
!
! // If AWT doesn't accept anything, let parent nodes handle the event
! if (action != 0) {
! // NOTE: in FX the acceptTransferModes() may ONLY be called from DRAG_OVER.
! // If the AWT app always reports NONE and suddenly decides to accept the
! // data in its DRAG_DROPPED handler, this just won't work. There's no way
! // to workaround this other than by modifing the AWT application code.
! event.acceptTransferModes(SwingDnD.dropActionsToTransferModes(action).toArray(new TransferMode[1]));
! event.consume();
! }
! };
!
! private final EventHandler<DragEvent> onDragDroppedHandler = (event) -> {
! if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
!
! int action = activeDTContextPeer.postDropTargetEvent(event);
!
! if (action != 0) {
! // NOTE: the dropAction is ignored since we use the action last
! // reported from the DRAG_OVER handler.
! //
! // We might want to:
! //
! // assert activeDTContextPeer.dropAction == onDragDroppedHandler.currentAction;
! //
! // and maybe print a diagnostic message if they differ.
! event.setDropCompleted(activeDTContextPeer.success);
!
! event.consume();
! }
!
! activeDTContextPeer = null;
! };
!
! private final class FXDropTargetContextPeer implements DropTargetContextPeer {
!
! private int targetActions = DnDConstants.ACTION_NONE;
! private int currentAction = DnDConstants.ACTION_NONE;
! private DropTarget dt = null;
! private DropTargetContext ctx = null;
!
! private final CachingTransferable transferable = new CachingTransferable();
!
! // Drop result
! private boolean success = false;
! private int dropAction = 0;
!
! @Override public synchronized void setTargetActions(int actions) { targetActions = actions; }
! @Override public synchronized int getTargetActions() { return targetActions; }
!
! @Override public synchronized DropTarget getDropTarget() { return dt; }
!
! @Override public synchronized boolean isTransferableJVMLocal() { return false; }
!
! @Override public synchronized DataFlavor[] getTransferDataFlavors() { return transferable.getTransferDataFlavors(); }
! @Override public synchronized Transferable getTransferable() { return transferable; }
!
! @Override public synchronized void acceptDrag(int dragAction) { currentAction = dragAction; }
! @Override public synchronized void rejectDrag() { currentAction = DnDConstants.ACTION_NONE; }
!
! @Override public synchronized void acceptDrop(int dropAction) { this.dropAction = dropAction; }
! @Override public synchronized void rejectDrop() { dropAction = DnDConstants.ACTION_NONE; }
!
! @Override public synchronized void dropComplete(boolean success) { this.success = success; }
!
!
! private int postDropTargetEvent(DragEvent event)
! {
! ComponentMapper<DropTarget> mapper = mapComponent(dropTargets, (int)event.getX(), (int)event.getY());
!
! final EventType<?> fxEvType = event.getEventType();
!
! Dragboard db = event.getDragboard();
! transferable.updateData(db, DragEvent.DRAG_DROPPED.equals(fxEvType));
!
! final int sourceActions = SwingDnD.transferModesToDropActions(db.getTransferModes());
! final int userAction = event.getTransferMode() == null ? DnDConstants.ACTION_NONE
! : SwingDnD.transferModeToDropAction(event.getTransferMode());
!
! // A target for the AWT DnD event
! DropTarget target = mapper.object != null ? mapper.object : dt;
!
! SwingFXUtils.runOnEDTAndWait(FXDnD.this, () -> {
! if (target != dt) {
! if (ctx != null) {
! AWTAccessor.getDropTargetContextAccessor().reset(ctx);
! }
! ctx = null;
!
! currentAction = dropAction = DnDConstants.ACTION_NONE;
! }
!
! if (target != null) {
! if (ctx == null) {
! ctx = target.getDropTargetContext();
! AWTAccessor.getDropTargetContextAccessor()
! .setDropTargetContextPeer(ctx, FXDropTargetContextPeer.this);
! }
!
! DropTargetListener dtl = (DropTargetListener)target;
!
! if (DragEvent.DRAG_DROPPED.equals(fxEvType)) {
! DropTargetDropEvent awtEvent = new DropTargetDropEvent(
! ctx, new Point(mapper.x, mapper.y), userAction, sourceActions);
!
! dtl.drop(awtEvent);
! } else {
! DropTargetDragEvent awtEvent = new DropTargetDragEvent(
! ctx, new Point(mapper.x, mapper.y), userAction, sourceActions);
!
! if (DragEvent.DRAG_OVER.equals(fxEvType)) dtl.dragOver(awtEvent);
! else if (DragEvent.DRAG_ENTERED.equals(fxEvType)) dtl.dragEnter(awtEvent);
! else if (DragEvent.DRAG_EXITED.equals(fxEvType)) dtl.dragExit(awtEvent);
! }
! }
!
! dt = mapper.object;
! if (dt == null) {
! // FIXME: once we switch to JDK 9 as the boot JDK
! // we need to re-implement the following using
! // available API.
! /*
! if (ctx != null) ctx.removeNotify();
! */
! ctx = null;
!
! currentAction = dropAction = DnDConstants.ACTION_NONE;
! }
! if (DragEvent.DRAG_DROPPED.equals(fxEvType) || DragEvent.DRAG_EXITED.equals(fxEvType)) {
! // This must be done to ensure that the data isn't being
! // cached in AWT. Otherwise subsequent DnD operations will
! // see the old data only.
! // FIXME: once we switch to JDK 9 as the boot JDK
! // we need to re-implement the following using
! // available API.
! /*
! if (ctx != null) ctx.removeNotify();
! */
! ctx = null;
! }
!
! SwingFXUtils.leaveFXNestedLoop(FXDnD.this);
! });
!
! if (DragEvent.DRAG_DROPPED.equals(fxEvType)) return dropAction;
!
! return currentAction;
! }
}
public void addDropTarget(DropTarget dt) {
! dropTargets.put(dt.getComponent(), dt);
! Platform.runLater(() -> {
! if (!isDropTargetListenerInstalled) {
! node.addEventHandler(DragEvent.DRAG_ENTERED, onDragEnteredHandler);
! node.addEventHandler(DragEvent.DRAG_EXITED, onDragExitedHandler);
! node.addEventHandler(DragEvent.DRAG_OVER, onDragOverHandler);
! node.addEventHandler(DragEvent.DRAG_DROPPED, onDragDroppedHandler);
!
! isDropTargetListenerInstalled = true;
! }
! });
}
public void removeDropTarget(DropTarget dt) {
! dropTargets.remove(dt.getComponent());
! Platform.runLater(() -> {
! if (isDropTargetListenerInstalled && dropTargets.isEmpty()) {
! node.removeEventHandler(DragEvent.DRAG_ENTERED, onDragEnteredHandler);
! node.removeEventHandler(DragEvent.DRAG_EXITED, onDragExitedHandler);
! node.removeEventHandler(DragEvent.DRAG_OVER, onDragOverHandler);
! node.removeEventHandler(DragEvent.DRAG_DROPPED, onDragDroppedHandler);
!
! isDropTargetListenerInstalled = false;
! }
! });
}
}
--- 54,115 ----
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
+ import javafx.embed.swing.SwingNode;
/**
* A utility class to connect DnD mechanism of Swing and FX.
* It allows Swing content to use the FX machinery for performing DnD.
*/
! final public class FXDnD {
! private static SwingNode node;
! public static boolean fxAppThreadIsDispatchThread;
private SwingNode getNode() { return node; }
+ private static FXDnDInterop fxdndiop;
static {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
fxAppThreadIsDispatchThread =
"true".equals(System.getProperty("javafx.embed.singleThread"));
return null;
}
});
! InteropFactory instance = null;
! try {
! instance = InteropFactory.getInstance();
! } catch (Exception e) {
! throw new ExceptionInInitializerError(e);
}
! fxdndiop = instance.createFXDnDImpl();
}
! public FXDnD(SwingNode node) {
! this.node = node;
! fxdndiop.setNode(node);
}
! public Object createDragSourceContext(DragGestureEvent dge)
! throws InvalidDnDOperationException {
! return fxdndiop.createDragSourceContext(dge);
}
public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
Class<T> abstractRecognizerClass,
DragSource ds, Component c, int srcActions,
DragGestureListener dgl)
{
! return fxdndiop.createDragGestureRecognizer(ds, c, srcActions, dgl);
}
public void addDropTarget(DropTarget dt) {
! fxdndiop.addDropTarget(dt, node);
}
public void removeDropTarget(DropTarget dt) {
! fxdndiop.removeDropTarget(dt, node);
}
}
< prev index next >