1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.embed.swing.newimpl;
  27 
  28 import com.sun.javafx.tk.Toolkit;
  29 import java.awt.Component;
  30 import java.awt.Cursor;
  31 import java.awt.Point;
  32 import java.awt.SecondaryLoop;
  33 import java.awt.datatransfer.DataFlavor;
  34 import java.awt.datatransfer.Transferable;
  35 import java.awt.dnd.DnDConstants;
  36 import java.awt.dnd.DragGestureEvent;
  37 import java.awt.dnd.DragGestureListener;
  38 import java.awt.dnd.DragGestureRecognizer;
  39 import java.awt.dnd.DragSource;
  40 import java.awt.dnd.DropTarget;
  41 import java.awt.dnd.DropTargetContext;
  42 import java.awt.dnd.DropTargetDragEvent;
  43 import java.awt.dnd.DropTargetDropEvent;
  44 import java.awt.dnd.DropTargetListener;
  45 import java.awt.dnd.InvalidDnDOperationException;
  46 import java.awt.dnd.MouseDragGestureRecognizer;
  47 import java.security.AccessController;
  48 import java.security.PrivilegedAction;
  49 import java.util.HashMap;
  50 import java.util.Map;
  51 import javafx.application.Platform;
  52 import javafx.event.EventHandler;
  53 import javafx.event.EventType;
  54 import javafx.scene.input.DataFormat;
  55 import javafx.scene.input.DragEvent;
  56 import javafx.scene.input.Dragboard;
  57 import javafx.scene.input.MouseEvent;
  58 import javafx.scene.input.TransferMode;
  59 
  60 import com.sun.javafx.embed.swing.FXDnDInterop;
  61 import com.sun.javafx.embed.swing.CachingTransferable;
  62 import com.sun.javafx.embed.swing.SwingEvents;
  63 import com.sun.javafx.embed.swing.SwingDnD;
  64 import com.sun.javafx.embed.swing.FXDnD;
  65 import com.sun.javafx.embed.swing.SwingNodeHelper;
  66 import javafx.embed.swing.SwingNode;
  67 import jdk.swing.interop.LightweightFrameWrapper;
  68 import jdk.swing.interop.SwingInterOpUtils;
  69 import jdk.swing.interop.DragSourceContextWrapper;
  70 import jdk.swing.interop.DropTargetContextWrapper;
  71 
  72 public class FXDnDInteropN extends FXDnDInterop {
  73 
  74     public Component findComponentAt(Object frame, int x, int y,
  75                                               boolean ignoreEnabled) {
  76         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper) frame;
  77         return lwFrame.findComponentAt(lwFrame, x, y, false);
  78     }
  79 
  80     public boolean isCompEqual(Component c, Object frame) {
  81         LightweightFrameWrapper lwFrame = (LightweightFrameWrapper) frame;
  82         return lwFrame.isCompEqual(c,lwFrame);
  83     }
  84 
  85     public int convertModifiersToDropAction(int modifiers,
  86                                                      int supportedActions) {
  87         return DragSourceContextWrapper.convertModifiersToDropAction(modifiers,
  88                 supportedActions);
  89     }
  90 
  91     public Object createDragSourceContext(DragGestureEvent dge)
  92                       throws InvalidDnDOperationException {
  93         return new FXDragSourceContextPeer(dge);
  94     }
  95 
  96     public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
  97                         DragSource ds, Component c, int srcActions,
  98                            DragGestureListener dgl) {
  99         return (T) new FXDragGestureRecognizer(ds, c, srcActions, dgl);
 100     }
 101 
 102     private void runOnFxThread(Runnable runnable) {
 103         if (Platform.isFxApplicationThread()) {
 104             runnable.run();
 105         } else {
 106             Platform.runLater(runnable);
 107         }
 108     }
 109 
 110     private SwingNode getNode() {
 111         return node;
 112     }
 113 
 114     public void setNode(SwingNode swnode) {
 115         node = swnode;
 116     }
 117 
 118     private SwingNode node = null;
 119 
 120     /**
 121      * Utility class that operates on Maps with Components as keys.
 122      * Useful when processing mouse events to choose an object from the map
 123      * based on the component located at the given coordinates.
 124      */
 125     private class ComponentMapper<T> {
 126         public int x, y;
 127         public T object = null;
 128 
 129         private ComponentMapper(Map<Component, T> map, int xArg, int yArg) {
 130             this.x = xArg;
 131             this.y = yArg;
 132 
 133             final LightweightFrameWrapper lwFrame = (LightweightFrameWrapper)SwingNodeHelper.getLightweightFrame(node);
 134             Component c = lwFrame.findComponentAt(lwFrame, x, y, false);
 135             if (c == null) return;
 136 
 137             synchronized (c.getTreeLock()) {
 138                 do {
 139                     object = map.get(c);
 140                 } while (object == null && (c = c.getParent()) != null);
 141 
 142                 if (object != null) {
 143                     // The object is either a DropTarget or a DragSource, so:
 144                     //assert c == object.getComponent();
 145 
 146                     // Translate x, y from lwFrame to component coordinates
 147                     while ((lwFrame.isCompEqual(c,lwFrame)) && c != null) {
 148                         x -= c.getX();
 149                         y -= c.getY();
 150                         c = c.getParent();
 151                     }
 152                 }
 153             }
 154         }
 155     }
 156 
 157     public <T> ComponentMapper<T> mapComponent(Map<Component, T> map, int x, int y) {
 158         return new ComponentMapper<T>(map, x, y);
 159     }
 160 
 161     ///////////////////////////////////////////////////////////////////////////
 162     //     DRAG SOURCE IMPLEMENTATION
 163     ///////////////////////////////////////////////////////////////////////////
 164 
 165 
 166     private boolean isDragSourceListenerInstalled = false;
 167 
 168     // To keep track of where the DnD gesture actually started
 169     private MouseEvent pressEvent = null;
 170     private long pressTime = 0;
 171 
 172     private volatile SecondaryLoop loop;
 173 
 174     private final Map<Component, FXDragGestureRecognizer> recognizers = new HashMap<>();
 175 
 176     // Note that we don't really use the MouseDragGestureRecognizer facilities,
 177     // however some code in JDK may expect a descendant of this class rather
 178     // than a generic DragGestureRecognizer. So we inherit from it.
 179     private class FXDragGestureRecognizer extends MouseDragGestureRecognizer {
 180         FXDragGestureRecognizer(DragSource ds, Component c, int srcActions,
 181             DragGestureListener dgl)
 182         {
 183             super(ds, c, srcActions, dgl);
 184 
 185             if (c != null) recognizers.put(c, this);
 186         }
 187 
 188         @Override public void setComponent(Component c) {
 189             final Component old = getComponent();
 190             if (old != null) recognizers.remove(old);
 191             super.setComponent(c);
 192             if (c != null) recognizers.put(c, this);
 193         }
 194 
 195         protected void registerListeners() {
 196             runOnFxThread(() -> {
 197                 if (!isDragSourceListenerInstalled) {
 198                     node.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressHandler);
 199                     node.addEventHandler(MouseEvent.DRAG_DETECTED, onDragStartHandler);
 200                     node.addEventHandler(DragEvent.DRAG_DONE, onDragDoneHandler);
 201 
 202                     isDragSourceListenerInstalled = true;
 203                 }
 204             });
 205         }
 206 
 207         protected void unregisterListeners() {
 208             runOnFxThread(() -> {
 209                 if (isDragSourceListenerInstalled) {
 210                     node.removeEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressHandler);
 211                     node.removeEventHandler(MouseEvent.DRAG_DETECTED, onDragStartHandler);
 212                     node.removeEventHandler(DragEvent.DRAG_DONE, onDragDoneHandler);
 213 
 214                     isDragSourceListenerInstalled = false;
 215                 }
 216             });
 217         }
 218 
 219         private void fireEvent(int x, int y, long evTime, int modifiers) {
 220             // In theory we should register all the events that trigger the gesture (like PRESS, DRAG, DRAG, BINGO!)
 221             // But we can live with this hack for now.
 222             appendEvent(new java.awt.event.MouseEvent(getComponent(), java.awt.event.MouseEvent.MOUSE_PRESSED,
 223                         evTime, modifiers, x, y, 0, false));
 224 
 225             // Also, the modifiers here should've actually come from the last known mouse event (last MOVE or DRAG).
 226             // But we're OK with using the initial PRESS modifiers for now
 227             int initialAction = DragSourceContextWrapper.convertModifiersToDropAction(
 228                     modifiers, getSourceActions());
 229 
 230             fireDragGestureRecognized(initialAction, new java.awt.Point(x, y));
 231         }
 232     }
 233 
 234     // Invoked on EDT
 235     private void fireEvent(int x, int y, long evTime, int modifiers) {
 236         ComponentMapper<FXDragGestureRecognizer> mapper = mapComponent(recognizers, x, y);
 237 
 238         final FXDragGestureRecognizer r = mapper.object;
 239         if (r != null) {
 240             r.fireEvent(mapper.x, mapper.y, evTime, modifiers);
 241         } else {
 242             // No recognizer, no DnD, no startDrag, so release the FX loop now
 243             SwingNodeHelper.leaveFXNestedLoop(this);
 244         }
 245     }
 246 
 247     private MouseEvent getInitialGestureEvent() {
 248         return pressEvent;
 249     }
 250 
 251     private final EventHandler<MouseEvent> onMousePressHandler = (event) -> {
 252         // It would be nice to maintain a list of all the events that initiate
 253         // a DnD gesture (see a comment in FXDragGestureRecognizer.fireEvent().
 254         // For now, we simply use the initial PRESS event for this purpose.
 255         pressEvent = event;
 256         pressTime = System.currentTimeMillis();
 257     };
 258 
 259 
 260     private volatile FXDragSourceContextPeer activeDSContextPeer;
 261 
 262     private final EventHandler<MouseEvent> onDragStartHandler = (event) -> {
 263         // Call to AWT and determine the active DragSourceContextPeer
 264         activeDSContextPeer = null;
 265         final MouseEvent firstEv = getInitialGestureEvent();
 266         SwingNodeHelper.runOnEDTAndWait(FXDnDInteropN.this, () -> fireEvent(
 267                     (int)firstEv.getX(), (int)firstEv.getY(), pressTime,
 268                     SwingEvents.fxMouseModsToMouseMods(firstEv)));
 269         if (activeDSContextPeer == null) return;
 270 
 271         // Since we're going to start DnD, consume the event.
 272         event.consume();
 273 
 274         Dragboard db = getNode().startDragAndDrop(SwingDnD.dropActionsToTransferModes(
 275                     activeDSContextPeer.sourceActions).toArray(new TransferMode[1]));
 276 
 277         // At this point the activeDSContextPeer.transferable contains all the data from AWT
 278         Map<DataFormat, Object> fxData = new HashMap<>();
 279         for (String mt : activeDSContextPeer.transferable.getMimeTypes()) {
 280             DataFormat f = DataFormat.lookupMimeType(mt);
 281             //TODO: what to do if f == null?
 282             if (f != null) fxData.put(f, activeDSContextPeer.transferable.getData(mt));
 283         }
 284 
 285         final boolean hasContent = db.setContent(fxData);
 286         if (!hasContent) {
 287             // No data, no DnD, no onDragDoneHandler, so release the AWT loop now
 288             if (!FXDnD.fxAppThreadIsDispatchThread) {
 289                 loop.exit();
 290             }
 291         }
 292     };
 293 
 294     private final EventHandler<DragEvent> onDragDoneHandler = (event) -> {
 295         event.consume();
 296 
 297         // Release FXDragSourceContextPeer.startDrag()
 298         if (!FXDnD.fxAppThreadIsDispatchThread) {
 299             loop.exit();
 300         }
 301 
 302         if (activeDSContextPeer != null) {
 303             final TransferMode mode = event.getTransferMode();
 304             activeDSContextPeer.dragDone(
 305                     mode == null ? 0 : SwingDnD.transferModeToDropAction(mode),
 306                     (int)event.getX(), (int)event.getY());
 307         }
 308     };
 309 
 310 
 311     private final class FXDragSourceContextPeer extends DragSourceContextWrapper {
 312         private volatile int sourceActions = 0;
 313 
 314         private final CachingTransferable transferable = new CachingTransferable();
 315 
 316         @Override public void startSecondaryEventLoop(){
 317             Toolkit.getToolkit().enterNestedEventLoop(this);
 318         }
 319         @Override public void quitSecondaryEventLoop(){
 320             assert !Platform.isFxApplicationThread();
 321             Platform.runLater(() -> Toolkit.getToolkit().exitNestedEventLoop(FXDragSourceContextPeer.this, null));
 322         }
 323 
 324         @Override protected void setNativeCursor(Cursor c, int cType) {
 325             //TODO
 326         }
 327 
 328 
 329         private void dragDone(int operation, int x, int y) {
 330             dragDropFinished(operation != 0, operation, x, y);
 331         }
 332 
 333         FXDragSourceContextPeer(DragGestureEvent dge) {
 334             super(dge);
 335         }
 336 
 337 
 338         // It's Map<Long, DataFlavor> actually, but javac complains if the type isn't erased...
 339         @Override protected void startDrag(Transferable trans, long[] formats, Map formatMap)
 340         {
 341             activeDSContextPeer = this;
 342 
 343             // NOTE: we ignore the formats[] and the formatMap altogether.
 344             // AWT provides those to allow for more flexible representations of
 345             // e.g. text data (in various formats, encodings, etc.) However, FX
 346             // code isn't ready to handle those (e.g. it can't digest a
 347             // StringReader as data, etc.) So instead we perform our internal
 348             // translation.
 349             // Note that fetchData == true. FX doesn't support delayed data
 350             // callbacks yet anyway, so we have to fetch all the data from AWT upfront.
 351             transferable.updateData(trans, true);
 352 
 353             sourceActions = getDragSourceContext().getSourceActions();
 354 
 355             // Release the FX nested loop to allow onDragDetected to start the actual DnD operation,
 356             // and then start an AWT nested loop to wait until DnD finishes.
 357             if (!FXDnD.fxAppThreadIsDispatchThread) {
 358                 loop = java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
 359                 SwingNodeHelper.leaveFXNestedLoop(FXDnDInteropN.this);
 360                 if (!loop.enter()) {
 361                     // An error occured, but there's little we can do here...
 362                 }
 363             }
 364         }
 365     };
 366 
 367     ///////////////////////////////////////////////////////////////////////////
 368     //     DROP TARGET IMPLEMENTATION
 369     ///////////////////////////////////////////////////////////////////////////
 370 
 371 
 372     private boolean isDropTargetListenerInstalled = false;
 373     private volatile FXDropTargetContextPeer activeDTContextPeer = null;
 374     private final Map<Component, DropTarget> dropTargets = new HashMap<>();
 375 
 376     private final EventHandler<DragEvent> onDragEnteredHandler = (event) -> {
 377         if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
 378 
 379         int action = activeDTContextPeer.postDropTargetEvent(event);
 380 
 381         // If AWT doesn't accept anything, let parent nodes handle the event
 382         if (action != 0) event.consume();
 383     };
 384 
 385     private final EventHandler<DragEvent> onDragExitedHandler = (event) -> {
 386         if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
 387 
 388         activeDTContextPeer.postDropTargetEvent(event);
 389 
 390         activeDTContextPeer = null;
 391     };
 392 
 393     private final EventHandler<DragEvent> onDragOverHandler = (event) -> {
 394         if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
 395 
 396         int action = activeDTContextPeer.postDropTargetEvent(event);
 397 
 398         // If AWT doesn't accept anything, let parent nodes handle the event
 399         if (action != 0) {
 400             // NOTE: in FX the acceptTransferModes() may ONLY be called from DRAG_OVER.
 401             // If the AWT app always reports NONE and suddenly decides to accept the
 402             // data in its DRAG_DROPPED handler, this just won't work. There's no way
 403             // to workaround this other than by modifing the AWT application code.
 404             event.acceptTransferModes(SwingDnD.dropActionsToTransferModes(action).toArray(new TransferMode[1]));
 405             event.consume();
 406         }
 407     };
 408 
 409     private final EventHandler<DragEvent> onDragDroppedHandler = (event) -> {
 410         if (activeDTContextPeer == null) activeDTContextPeer = new FXDropTargetContextPeer();
 411 
 412         int action = activeDTContextPeer.postDropTargetEvent(event);
 413 
 414         if (action != 0) {
 415             // NOTE: the dropAction is ignored since we use the action last
 416             // reported from the DRAG_OVER handler.
 417             //
 418             // We might want to:
 419             //
 420             //    assert activeDTContextPeer.dropAction == onDragDroppedHandler.currentAction;
 421             //
 422             // and maybe print a diagnostic message if they differ.
 423             event.setDropCompleted(activeDTContextPeer.success);
 424 
 425             event.consume();
 426         }
 427 
 428         activeDTContextPeer = null;
 429     };
 430 
 431     private final class FXDropTargetContextPeer extends DropTargetContextWrapper {
 432 
 433         private int targetActions = DnDConstants.ACTION_NONE;
 434         private int currentAction = DnDConstants.ACTION_NONE;
 435         private DropTarget dt = null;
 436         private DropTargetContext ctx = null;
 437 
 438         private final CachingTransferable transferable = new CachingTransferable();
 439 
 440         // Drop result
 441         private boolean success = false;
 442         private int dropAction = 0;
 443 
 444         @Override public synchronized void setTargetActions(int actions) { targetActions = actions; }
 445         @Override public synchronized int getTargetActions() { return targetActions; }
 446 
 447         @Override public synchronized DropTarget getDropTarget() { return dt; }
 448 
 449         @Override public synchronized boolean isTransferableJVMLocal() { return false; }
 450 
 451         @Override public synchronized DataFlavor[] getTransferDataFlavors() { return transferable.getTransferDataFlavors(); }
 452         @Override public synchronized Transferable getTransferable() { return transferable; }
 453 
 454         @Override public synchronized void acceptDrag(int dragAction) { currentAction = dragAction; }
 455         @Override public synchronized void rejectDrag() { currentAction = DnDConstants.ACTION_NONE; }
 456 
 457         @Override public synchronized void acceptDrop(int dropAction) { this.dropAction = dropAction; }
 458         @Override public synchronized void rejectDrop() { dropAction = DnDConstants.ACTION_NONE; }
 459 
 460         @Override public synchronized void dropComplete(boolean success) { this.success = success; }
 461 
 462 
 463         private int postDropTargetEvent(DragEvent event)
 464         {
 465             ComponentMapper<DropTarget> mapper = mapComponent(dropTargets, (int)event.getX(), (int)event.getY());
 466 
 467             final EventType<?> fxEvType = event.getEventType();
 468 
 469             Dragboard db = event.getDragboard();
 470             transferable.updateData(db, DragEvent.DRAG_DROPPED.equals(fxEvType));
 471 
 472             final int sourceActions = SwingDnD.transferModesToDropActions(db.getTransferModes());
 473             final int userAction = event.getTransferMode() == null ? DnDConstants.ACTION_NONE
 474                 : SwingDnD.transferModeToDropAction(event.getTransferMode());
 475 
 476             // A target for the AWT DnD event
 477             DropTarget target = mapper.object != null ? mapper.object : dt;
 478 
 479             SwingNodeHelper.runOnEDTAndWait(FXDnDInteropN.this, () -> {
 480                 if (target != dt) {
 481                     if (ctx != null) {
 482                         this.reset(ctx);
 483                     }
 484                     ctx = null;
 485 
 486                     currentAction = dropAction = DnDConstants.ACTION_NONE;
 487                 }
 488 
 489                 if (target != null) {
 490                     if (ctx == null) {
 491                         ctx = target.getDropTargetContext();
 492                         this.setDropTargetContext(ctx,
 493                      FXDropTargetContextPeer.this);
 494                     }
 495 
 496                     DropTargetListener dtl = (DropTargetListener)target;
 497 
 498                     if (DragEvent.DRAG_DROPPED.equals(fxEvType)) {
 499                         DropTargetDropEvent awtEvent = new DropTargetDropEvent(
 500                             ctx, new Point(mapper.x, mapper.y), userAction, sourceActions);
 501 
 502                         dtl.drop(awtEvent);
 503                     } else {
 504                         DropTargetDragEvent awtEvent = new DropTargetDragEvent(
 505                             ctx, new Point(mapper.x, mapper.y), userAction, sourceActions);
 506 
 507                         if (DragEvent.DRAG_OVER.equals(fxEvType)) dtl.dragOver(awtEvent);
 508                         else if (DragEvent.DRAG_ENTERED.equals(fxEvType)) dtl.dragEnter(awtEvent);
 509                         else if (DragEvent.DRAG_EXITED.equals(fxEvType)) dtl.dragExit(awtEvent);
 510                     }
 511                 }
 512 
 513                 dt = mapper.object;
 514                 if (dt == null) {
 515                     // FIXME: once we switch to JDK 9 as the boot JDK
 516                     // we need to re-implement the following using
 517                     // available API.
 518                     /*
 519                     if (ctx != null) ctx.removeNotify();
 520                     */
 521                     ctx = null;
 522 
 523                     currentAction = dropAction = DnDConstants.ACTION_NONE;
 524                 }
 525                 if (DragEvent.DRAG_DROPPED.equals(fxEvType) || DragEvent.DRAG_EXITED.equals(fxEvType)) {
 526                     // This must be done to ensure that the data isn't being
 527                     // cached in AWT. Otherwise subsequent DnD operations will
 528                     // see the old data only.
 529                     // FIXME: once we switch to JDK 9 as the boot JDK
 530                     // we need to re-implement the following using
 531                     // available API.
 532                     /*
 533                     if (ctx != null) ctx.removeNotify();
 534                     */
 535                     ctx = null;
 536                 }
 537 
 538                 SwingNodeHelper.leaveFXNestedLoop(FXDnDInteropN.this);
 539             });
 540 
 541             if (DragEvent.DRAG_DROPPED.equals(fxEvType)) return dropAction;
 542 
 543             return currentAction;
 544         }
 545     }
 546 
 547     public void addDropTarget(DropTarget dt, SwingNode node) {
 548         dropTargets.put(dt.getComponent(), dt);
 549         Platform.runLater(() -> {
 550             if (!isDropTargetListenerInstalled) {
 551                 node.addEventHandler(DragEvent.DRAG_ENTERED, onDragEnteredHandler);
 552                 node.addEventHandler(DragEvent.DRAG_EXITED, onDragExitedHandler);
 553                 node.addEventHandler(DragEvent.DRAG_OVER, onDragOverHandler);
 554                 node.addEventHandler(DragEvent.DRAG_DROPPED, onDragDroppedHandler);
 555 
 556                 isDropTargetListenerInstalled = true;
 557             }
 558         });
 559     }
 560 
 561     public void removeDropTarget(DropTarget dt, SwingNode node) {
 562         dropTargets.remove(dt.getComponent());
 563         Platform.runLater(() -> {
 564             if (isDropTargetListenerInstalled && dropTargets.isEmpty()) {
 565                 node.removeEventHandler(DragEvent.DRAG_ENTERED, onDragEnteredHandler);
 566                 node.removeEventHandler(DragEvent.DRAG_EXITED, onDragExitedHandler);
 567                 node.removeEventHandler(DragEvent.DRAG_OVER, onDragOverHandler);
 568                 node.removeEventHandler(DragEvent.DRAG_DROPPED, onDragDroppedHandler);
 569 
 570                 isDropTargetListenerInstalled = false;
 571             }
 572         });
 573     }
 574 }