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