1 /* 2 * Copyright (c) 2012, 2013, 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 javafx.embed.swing; 27 28 import java.io.IOException; 29 import java.util.Collections; 30 import java.util.ArrayList; 31 import java.util.EnumSet; 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.Set; 35 36 import javafx.scene.input.TransferMode; 37 38 import com.sun.javafx.embed.EmbeddedSceneInterface; 39 import com.sun.javafx.embed.EmbeddedSceneDragSourceInterface; 40 import com.sun.javafx.embed.EmbeddedSceneDragStartListenerInterface; 41 import com.sun.javafx.embed.EmbeddedSceneDropTargetInterface; 42 import com.sun.javafx.tk.Toolkit; 43 44 import javax.swing.JComponent; 45 import javax.swing.SwingUtilities; 46 47 import java.awt.Point; 48 49 import java.awt.datatransfer.DataFlavor; 50 import java.awt.datatransfer.Transferable; 51 import java.awt.datatransfer.UnsupportedFlavorException; 52 import java.awt.dnd.DnDConstants; 53 import java.awt.dnd.DragGestureEvent; 54 import java.awt.dnd.DragGestureRecognizer; 55 import java.awt.dnd.DragSource; 56 import java.awt.dnd.DragSourceAdapter; 57 import java.awt.dnd.DragSourceListener; 58 import java.awt.dnd.DragSourceDropEvent; 59 import java.awt.dnd.DropTarget; 60 import java.awt.dnd.DropTargetAdapter; 61 import java.awt.dnd.DropTargetDragEvent; 62 import java.awt.dnd.DropTargetDropEvent; 63 import java.awt.dnd.DropTargetEvent; 64 65 import java.awt.event.InputEvent; 66 import java.awt.event.MouseAdapter; 67 import java.awt.event.MouseEvent; 68 69 /** 70 * An utility class to connect DnD mechanism of Swing and FX. 71 */ 72 final class SwingDnD { 73 74 private final JFXPanelFacade facade; 75 private final Transferable dndTransferable = new DnDTransferable(); 76 private final DragSourceListener dragSourceListener; 77 private SwingDragSource swingDragSource; 78 private EmbeddedSceneDragSourceInterface fxDragSource; 79 private EmbeddedSceneDropTargetInterface dropTarget; 80 private MouseEvent me; 81 82 interface JFXPanelFacade { 83 84 EmbeddedSceneInterface getScene(); 85 } 86 87 SwingDnD(final JComponent comp, final JFXPanelFacade facade) { 88 this.facade = facade; 89 90 comp.addMouseListener(new MouseAdapter() { 91 92 @Override 93 public void mouseClicked(MouseEvent me) { 94 storeMouseEvent(me); 95 } 96 97 @Override 98 public void mouseDragged(MouseEvent me) { 99 storeMouseEvent(me); 100 } 101 102 @Override 103 public void mousePressed(MouseEvent me) { 104 storeMouseEvent(me); 105 } 106 107 @Override 108 public void mouseReleased(MouseEvent me) { 109 storeMouseEvent(me); 110 } 111 }); 112 113 dragSourceListener = new DragSourceAdapter() { 114 115 @Override 116 public void dragDropEnd(final DragSourceDropEvent dsde) { 117 // Fix for RT-21836 118 if (fxDragSource == null) { 119 return; 120 } 121 122 assert hasFxScene(); 123 124 try { 125 fxDragSource.dragDropEnd(dropActionToTransferMode(dsde. 126 getDropAction())); 127 } finally { 128 fxDragSource = null; 129 } 130 } 131 }; 132 133 new DropTarget(comp, DnDConstants.ACTION_COPY | DnDConstants.ACTION_MOVE | 134 DnDConstants.ACTION_LINK, new DropTargetAdapter() { 135 136 @Override 137 public void dragEnter(final DropTargetDragEvent e) { 138 if (!hasFxScene()) { 139 e.rejectDrag(); 140 return; 141 } 142 143 if (fxDragSource == null) { 144 // There is no FX drag source, create wrapper for external 145 // drag source. 146 assert swingDragSource == null; 147 swingDragSource = new SwingDragSource(e); 148 } 149 150 final Point orig = e.getLocation(); 151 final Point screen = new Point(orig); 152 SwingUtilities.convertPointToScreen(screen, comp); 153 applyDragResult(getDropTarget().handleDragEnter(orig.x, orig.y, 154 screen.x, 155 screen.y, 156 dropActionToTransferMode(e. 157 getDropAction()), getDragSource()), e); 158 } 159 160 @Override 161 public void dragExit(final DropTargetEvent e) { 162 if (!hasFxScene()) { 163 // The drag has been already rejected in dragEnter(), but it doesn't 164 // prevent dragExit(), dragOver() and drop() from being called 165 return; 166 } 167 168 try { 169 dropTarget.handleDragLeave(); 170 } finally { 171 endDnD(); 172 } 173 } 174 175 @Override 176 public void dragOver(final DropTargetDragEvent e) { 177 if (!hasFxScene()) { 178 // The drag has been already rejected in dragEnter(), but it doesn't 179 // prevent dragExit(), dragOver() and drop() from being called 180 return; 181 } 182 183 if (swingDragSource != null) { 184 swingDragSource.updateContents(e); 185 } 186 187 final Point orig = e.getLocation(); 188 final Point screen = new Point(orig); 189 SwingUtilities.convertPointToScreen(screen, comp); 190 applyDragResult(dropTarget.handleDragOver(orig.x, orig.y, 191 screen.x, screen.y, 192 dropActionToTransferMode(e. 193 getDropAction())), e); 194 } 195 196 @Override 197 public void drop(final DropTargetDropEvent e) { 198 if (!hasFxScene()) { 199 // The drag has been already rejected in dragEnter(), but it doesn't 200 // prevent dragExit(), dragOver() and drop() from being called 201 return; 202 } 203 204 final Point orig = e.getLocation(); 205 final Point screen = new Point(orig); 206 SwingUtilities.convertPointToScreen(screen, comp); 207 208 try { 209 final TransferMode dropResult = 210 dropTarget.handleDragDrop(orig.x, orig.y, screen.x, 211 screen.y, 212 dropActionToTransferMode(e. 213 getDropAction())); 214 applyDropResult(dropResult, e); 215 216 e.dropComplete(dropResult != null); 217 } finally { 218 endDnD(); 219 } 220 } 221 }); 222 } 223 224 void addNotify() { 225 DragSource.getDefaultDragSource().addDragSourceListener( 226 dragSourceListener); 227 } 228 229 void removeNotify() { 230 // RT-22049: Multi-JFrame/JFXPanel app leaks JFXPanels 231 // Don't forget to unregister drag source listener! 232 DragSource.getDefaultDragSource().removeDragSourceListener( 233 dragSourceListener); 234 } 235 236 EmbeddedSceneDragStartListenerInterface getDragStartListener() { 237 return new EmbeddedSceneDragStartListenerInterface() { 238 239 @Override 240 public void dragStarted( 241 final EmbeddedSceneDragSourceInterface dragSource, 242 final TransferMode dragAction) { 243 assert Toolkit.getToolkit().isFxUserThread(); 244 assert dragSource != null; 245 246 // 247 // The method is called from FX Scene just before entering 248 // nested event loop servicing DnD events. 249 // It should initialize DnD in AWT EDT. 250 // 251 252 SwingUtilities.invokeLater(new Runnable() { 253 254 @Override 255 public void run() { 256 assert fxDragSource == null; 257 assert swingDragSource == null; 258 assert dropTarget == null; 259 260 fxDragSource = dragSource; 261 262 startDrag(me, dndTransferable, dragSource. 263 getSupportedActions(), dragAction); 264 } 265 }); 266 } 267 }; 268 } 269 270 private static void startDrag(final MouseEvent e, final Transferable t, 271 final Set<TransferMode> sa, 272 final TransferMode dragAction) { 273 274 assert sa.contains(dragAction); 275 276 // 277 // This is a replacement for the default AWT drag gesture recognizer. 278 // Not sure DragGestureRecognizer was ever supposed to be used this way. 279 // 280 281 final class StubDragGestureRecognizer extends DragGestureRecognizer { 282 283 StubDragGestureRecognizer() { 284 super(DragSource.getDefaultDragSource(), e.getComponent()); 285 super.setSourceActions(transferModesToDropActions(sa)); 286 super.appendEvent(e); 287 } 288 289 @Override 290 protected void registerListeners() { 291 } 292 293 @Override 294 protected void unregisterListeners() { 295 } 296 } 297 298 final Point pt = new Point(e.getX(), e.getY()); 299 300 final int action = transferModeToDropAction(dragAction); 301 302 final DragGestureRecognizer dgs = new StubDragGestureRecognizer(); 303 304 final List<InputEvent> events = Arrays.asList(new InputEvent[]{dgs. 305 getTriggerEvent()}); 306 307 final DragGestureEvent dse = new DragGestureEvent(dgs, action, pt, 308 events); 309 310 dse.startDrag(null, t); 311 } 312 313 private boolean hasFxScene() { 314 assert SwingUtilities.isEventDispatchThread(); 315 return getFxScene() != null; 316 } 317 318 private EmbeddedSceneInterface getFxScene() { 319 return facade.getScene(); 320 } 321 322 private EmbeddedSceneDragSourceInterface getDragSource() { 323 assert hasFxScene(); 324 325 assert (swingDragSource == null) != (fxDragSource == null); 326 327 if (swingDragSource != null) { 328 return swingDragSource; 329 } 330 return fxDragSource; 331 } 332 333 private EmbeddedSceneDropTargetInterface getDropTarget() { 334 assert hasFxScene(); 335 336 if (dropTarget == null) { 337 dropTarget = getFxScene().createDropTarget(); 338 } 339 return dropTarget; 340 } 341 342 private void endDnD() { 343 assert dropTarget != null; 344 345 dropTarget = null; 346 if (swingDragSource != null) { 347 swingDragSource = null; 348 } 349 } 350 351 private void storeMouseEvent(final MouseEvent me) { 352 this.me = me; 353 } 354 355 private static void applyDragResult(final TransferMode dragResult, 356 final DropTargetDragEvent e) { 357 if (dragResult == null) { 358 e.rejectDrag(); 359 } else { 360 e.acceptDrag(transferModeToDropAction(dragResult)); 361 } 362 } 363 364 private static void applyDropResult(final TransferMode dropResult, 365 final DropTargetDropEvent e) { 366 if (dropResult == null) { 367 e.rejectDrop(); 368 } else { 369 e.acceptDrop(transferModeToDropAction(dropResult)); 370 } 371 } 372 373 static TransferMode dropActionToTransferMode(final int dropAction) { 374 switch (dropAction) { 375 case DnDConstants.ACTION_COPY: 376 return TransferMode.COPY; 377 case DnDConstants.ACTION_MOVE: 378 return TransferMode.MOVE; 379 case DnDConstants.ACTION_LINK: 380 return TransferMode.LINK; 381 case DnDConstants.ACTION_NONE: 382 return null; 383 default: 384 throw new IllegalArgumentException(); 385 } 386 } 387 388 static int transferModeToDropAction(final TransferMode tm) { 389 switch (tm) { 390 case COPY: 391 return DnDConstants.ACTION_COPY; 392 case MOVE: 393 return DnDConstants.ACTION_MOVE; 394 case LINK: 395 return DnDConstants.ACTION_LINK; 396 default: 397 throw new IllegalArgumentException(); 398 } 399 } 400 401 static Set<TransferMode> dropActionsToTransferModes( 402 final int dropActions) { 403 final Set<TransferMode> tms = EnumSet.noneOf(TransferMode.class); 404 if ((dropActions & DnDConstants.ACTION_COPY) != 0) { 405 tms.add(TransferMode.COPY); 406 } 407 if ((dropActions & DnDConstants.ACTION_MOVE) != 0) { 408 tms.add(TransferMode.MOVE); 409 } 410 if ((dropActions & DnDConstants.ACTION_LINK) != 0) { 411 tms.add(TransferMode.LINK); 412 } 413 return Collections.unmodifiableSet(tms); 414 } 415 416 static int transferModesToDropActions(final Set<TransferMode> tms) { 417 int dropActions = DnDConstants.ACTION_NONE; 418 for (TransferMode tm : tms) { 419 dropActions |= transferModeToDropAction(tm); 420 } 421 return dropActions; 422 } 423 424 // 425 // This is facade to export data from FX to outer world. 426 // 427 private final class DnDTransferable implements Transferable { 428 429 @Override 430 public Object getTransferData(final DataFlavor flavor) throws 431 UnsupportedFlavorException, IOException { 432 checkSwingEventDispatchThread(); 433 434 if (!hasFxScene()) { 435 return null; 436 } 437 438 final String mimeType = DataFlavorUtils.getFxMimeType(flavor); 439 440 return DataFlavorUtils.adjustFxData(flavor, getDragSource().getData( 441 mimeType)); 442 } 443 444 @Override 445 public DataFlavor[] getTransferDataFlavors() { 446 checkSwingEventDispatchThread(); 447 448 if (!hasFxScene()) { 449 return null; 450 } 451 452 final String mimeTypes[] = getDragSource().getMimeTypes(); 453 454 final ArrayList<DataFlavor> flavors = 455 new ArrayList<DataFlavor>(mimeTypes.length); 456 for (String mime : mimeTypes) { 457 DataFlavor flavor = null; 458 try { 459 flavor = new DataFlavor(mime); 460 } catch (ClassNotFoundException e) { 461 // FIXME: what to do? 462 continue; 463 } 464 flavors.add(flavor); 465 } 466 return flavors.toArray(new DataFlavor[0]); 467 } 468 469 @Override 470 public boolean isDataFlavorSupported(final DataFlavor flavor) { 471 checkSwingEventDispatchThread(); 472 473 if (!hasFxScene()) { 474 return false; 475 } 476 477 return getDragSource().isMimeTypeAvailable(DataFlavorUtils. 478 getFxMimeType(flavor)); 479 } 480 }; 481 482 private static void checkSwingEventDispatchThread() { 483 if (!SwingUtilities.isEventDispatchThread()) { 484 throw new IllegalStateException(); 485 } 486 } 487 }