1 /* 2 * Copyright (c) 2012, 2016, 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.swt; 27 28 import com.sun.glass.ui.Application; 29 import com.sun.glass.ui.Pixels; 30 import com.sun.javafx.cursor.CursorFrame; 31 import com.sun.javafx.cursor.CursorType; 32 import com.sun.javafx.embed.EmbeddedSceneDSInterface; 33 import com.sun.javafx.embed.EmbeddedSceneDTInterface; 34 import com.sun.javafx.embed.EmbeddedSceneInterface; 35 import com.sun.javafx.embed.EmbeddedStageInterface; 36 import com.sun.javafx.embed.HostInterface; 37 import com.sun.javafx.stage.EmbeddedWindow; 38 39 import java.lang.reflect.Field; 40 import java.lang.reflect.Method; 41 import java.nio.ByteBuffer; 42 import java.nio.IntBuffer; 43 import java.security.AccessController; 44 import java.security.PrivilegedAction; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.HashSet; 48 import java.util.Set; 49 import java.util.Stack; 50 import java.util.concurrent.CountDownLatch; 51 52 import com.sun.javafx.tk.FocusCause; 53 import javafx.application.Platform; 54 import javafx.beans.NamedArg; 55 import javafx.event.EventType; 56 import javafx.scene.Scene; 57 import javafx.scene.input.MouseButton; 58 import javafx.scene.input.RotateEvent; 59 import javafx.scene.input.ScrollEvent; 60 import javafx.scene.input.SwipeEvent; 61 import javafx.scene.input.ZoomEvent; 62 import javafx.scene.input.TransferMode; 63 import javafx.stage.Window; 64 import javafx.util.FXPermission; 65 66 import org.eclipse.swt.SWT; 67 import org.eclipse.swt.dnd.DND; 68 import org.eclipse.swt.dnd.DragSource; 69 import org.eclipse.swt.dnd.DragSourceListener; 70 import org.eclipse.swt.dnd.DropTarget; 71 import org.eclipse.swt.dnd.DropTargetEvent; 72 import org.eclipse.swt.dnd.DropTargetListener; 73 import org.eclipse.swt.dnd.FileTransfer; 74 import org.eclipse.swt.dnd.HTMLTransfer; 75 import org.eclipse.swt.dnd.ImageTransfer; 76 import org.eclipse.swt.dnd.RTFTransfer; 77 import org.eclipse.swt.dnd.TextTransfer; 78 import org.eclipse.swt.dnd.Transfer; 79 import org.eclipse.swt.dnd.TransferData; 80 import org.eclipse.swt.dnd.URLTransfer; 81 import org.eclipse.swt.events.ControlEvent; 82 import org.eclipse.swt.events.ControlListener; 83 import org.eclipse.swt.events.DisposeEvent; 84 import org.eclipse.swt.events.DisposeListener; 85 import org.eclipse.swt.events.FocusEvent; 86 import org.eclipse.swt.events.FocusListener; 87 import org.eclipse.swt.events.GestureEvent; 88 import org.eclipse.swt.events.KeyEvent; 89 import org.eclipse.swt.events.KeyListener; 90 import org.eclipse.swt.events.MenuDetectEvent; 91 import org.eclipse.swt.events.MouseEvent; 92 import org.eclipse.swt.events.MouseListener; 93 import org.eclipse.swt.events.MouseTrackListener; 94 import org.eclipse.swt.events.PaintEvent; 95 import org.eclipse.swt.graphics.Image; 96 import org.eclipse.swt.graphics.ImageData; 97 import org.eclipse.swt.graphics.PaletteData; 98 import org.eclipse.swt.graphics.Point; 99 import org.eclipse.swt.graphics.RGB; 100 import org.eclipse.swt.graphics.Rectangle; 101 import org.eclipse.swt.widgets.Canvas; 102 import org.eclipse.swt.widgets.Composite; 103 import org.eclipse.swt.widgets.Control; 104 import org.eclipse.swt.widgets.Display; 105 import org.eclipse.swt.widgets.Event; 106 import org.eclipse.swt.widgets.Listener; 107 import org.eclipse.swt.widgets.Shell; 108 109 /** 110 * {@code FXCanvas} is a component to embed JavaFX content into 111 * SWT applications. The content to be displayed is specified 112 * with the {@link #setScene} method that accepts an instance of 113 * JavaFX {@code Scene}. After the scene is attached, it gets 114 * repainted automatically. All the input and focus events are 115 * forwarded to the scene transparently to the developer. 116 * <p> 117 * Here is a typical pattern how {@code FXCanvas} can used: 118 * <pre> 119 * public class Test { 120 * private static Scene createScene() { 121 * Group group = new Group(); 122 * Scene scene = new Scene(group); 123 * Button button = new Button("JFX Button"); 124 * group.getChildren().add(button); 125 * return scene; 126 * } 127 * 128 * public static void main(String[] args) { 129 * Display display = new Display(); 130 * Shell shell = new Shell(display); 131 * shell.setLayout(new FillLayout()); 132 * FXCanvas canvas = new FXCanvas(shell, SWT.NONE); 133 * Scene scene = createScene(); 134 * canvas.setScene(scene); 135 * shell.open(); 136 * while (!shell.isDisposed()) { 137 * if (!display.readAndDispatch()) display.sleep(); 138 * } 139 * display.dispose(); 140 * } 141 * } 142 * </pre> 143 * 144 * 145 * @since JavaFX 2.0 146 */ 147 public class FXCanvas extends Canvas { 148 149 // Internal permission used by FXCanvas (SWT interop) 150 private static final FXPermission FXCANVAS_PERMISSION = 151 new FXPermission("accessFXCanvasInternals"); 152 153 private HostContainer hostContainer; 154 private volatile EmbeddedWindow stage; 155 private volatile Scene scene; 156 private EmbeddedStageInterface stagePeer; 157 private EmbeddedSceneInterface scenePeer; 158 159 private int pWidth = 0; 160 private int pHeight = 0; 161 162 private volatile int pPreferredWidth = -1; 163 private volatile int pPreferredHeight = -1; 164 165 private IntBuffer pixelsBuf = null; 166 167 // This filter runs when any widget is moved 168 Listener moveFilter = event -> { 169 // If a parent has moved, send a move event to FX 170 Control control = FXCanvas.this; 171 while (control != null) { 172 if (control == event.widget) { 173 sendMoveEventToFX(); 174 break; 175 } 176 control = control.getParent(); 177 }; 178 }; 179 180 private double getScaleFactor() { 181 if (SWT.getPlatform().equals("cocoa")) { 182 if (windowField == null || screenMethod == null || backingScaleFactorMethod == null) { 183 return 1.0; 184 } 185 try { 186 Object nsWindow = windowField.get(this.getShell()); 187 Object nsScreen = screenMethod.invoke(nsWindow); 188 Object bsFactor = backingScaleFactorMethod.invoke(nsScreen); 189 return ((Double) bsFactor).doubleValue(); 190 } catch (Exception e) { 191 // FAIL silently should the reflection fail 192 } 193 } 194 return 1.0; 195 } 196 197 private DropTarget dropTarget; 198 199 static Transfer [] StandardTransfers = new Transfer [] { 200 TextTransfer.getInstance(), 201 RTFTransfer.getInstance(), 202 HTMLTransfer.getInstance(), 203 URLTransfer.getInstance(), 204 ImageTransfer.getInstance(), 205 FileTransfer.getInstance(), 206 }; 207 static Transfer [] CustomTransfers = new Transfer [0]; 208 209 static Transfer [] getAllTransfers () { 210 Transfer [] transfers = new Transfer[StandardTransfers.length + CustomTransfers.length]; 211 System.arraycopy(StandardTransfers, 0, transfers, 0, StandardTransfers.length); 212 System.arraycopy(CustomTransfers, 0, transfers, StandardTransfers.length, CustomTransfers.length); 213 return transfers; 214 } 215 216 static Transfer getCustomTransfer(String mime) { 217 for (int i=0; i<CustomTransfers.length; i++) { 218 if (((CustomTransfer)CustomTransfers[i]).getMime().equals(mime)) { 219 return CustomTransfers[i]; 220 } 221 } 222 Transfer transfer = new CustomTransfer (mime, mime); 223 Transfer [] newCustom = new Transfer [CustomTransfers.length + 1]; 224 System.arraycopy(CustomTransfers, 0, newCustom, 0, CustomTransfers.length); 225 newCustom[CustomTransfers.length] = transfer; 226 CustomTransfers = newCustom; 227 return transfer; 228 } 229 230 private static Field windowField; 231 private static Method windowMethod; 232 private static Method screenMethod; 233 private static Method backingScaleFactorMethod; 234 235 static { 236 if (SWT.getPlatform().equals("cocoa")) { 237 try { 238 windowField = Shell.class.getDeclaredField("window"); 239 windowField.setAccessible(true); 240 241 Class nsViewClass = Class.forName("org.eclipse.swt.internal.cocoa.NSView"); 242 windowMethod = nsViewClass.getDeclaredMethod("window"); 243 windowMethod.setAccessible(true); 244 245 Class nsWindowClass = Class.forName("org.eclipse.swt.internal.cocoa.NSWindow"); 246 screenMethod = nsWindowClass.getDeclaredMethod("screen"); 247 screenMethod.setAccessible(true); 248 249 Class nsScreenClass = Class.forName("org.eclipse.swt.internal.cocoa.NSScreen"); 250 backingScaleFactorMethod = nsScreenClass.getDeclaredMethod("backingScaleFactor"); 251 backingScaleFactorMethod.setAccessible(true); 252 } catch (Exception e) { 253 //Fail silently. If we can't get the methods, then the current version of SWT has no retina support 254 } 255 } 256 initFx(); 257 } 258 259 /** 260 * @inheritDoc 261 */ 262 public FXCanvas(@NamedArg("parent") Composite parent, @NamedArg("style") int style) { 263 super(parent, style | SWT.NO_BACKGROUND); 264 setApplicationName(Display.getAppName()); 265 hostContainer = new HostContainer(); 266 registerEventListeners(); 267 Display display = parent.getDisplay(); 268 display.addFilter(SWT.Move, moveFilter); 269 } 270 271 /** 272 * Retrieves the {@code FXCanvas} embedding the given {@code Scene}, 273 * that is the {@code FXCanvas} to which the given {@code Scene} was 274 * attached using {@link #setScene}. 275 * 276 * @param scene the {@code Scene} whose embedding {@code FXCanvas} 277 * instance is to be retrieved 278 * @return the {@code FXCanvas} to which the given {@code Scene} is 279 * attached, or null if the given {@code Scene} is not attached to an 280 * {@code FXCanvas}. 281 * 282 * @since 9 283 */ 284 public static FXCanvas getFXCanvas(Scene scene) { 285 Window window = scene.getWindow(); 286 if (window != null && window instanceof EmbeddedWindow) { 287 HostInterface hostInterface = ((EmbeddedWindow) window).getHost(); 288 if (hostInterface instanceof HostContainer) { 289 // Obtain FXCanvas as the enclosing instance of the FXCanvas$HostContainer 290 // that is host of the embedded Scene's EmbeddedWindow. 291 return ((HostContainer)hostInterface).fxCanvas; 292 } 293 } 294 return null; 295 } 296 297 private static void initFx() { 298 // NOTE: no internal "com.sun.*" packages can be accessed until after 299 // the JavaFX platform is initialized. The list of needed internal 300 // packages is kept in the PlatformImpl class. 301 long eventProc = 0; 302 try { 303 Field field = Display.class.getDeclaredField("eventProc"); 304 field.setAccessible(true); 305 if (field.getType() == int.class) { 306 eventProc = field.getInt(Display.getDefault()); 307 } else { 308 if (field.getType() == long.class) { 309 eventProc = field.getLong(Display.getDefault()); 310 } 311 } 312 } catch (Throwable th) { 313 //Fail silently 314 } 315 final String eventProcStr = String.valueOf(eventProc); 316 317 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 318 System.setProperty("com.sun.javafx.application.type", "FXCanvas"); 319 System.setProperty("javafx.embed.isEventThread", "true"); 320 System.setProperty("glass.win.uiScale", "100%"); 321 System.setProperty("glass.win.renderScale", "100%"); 322 System.setProperty("javafx.embed.eventProc", eventProcStr); 323 return null; 324 }); 325 326 final CountDownLatch startupLatch = new CountDownLatch(1); 327 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 328 Platform.startup(() -> { 329 startupLatch.countDown(); 330 }); 331 return null; 332 }, null, FXCANVAS_PERMISSION); 333 334 try { 335 startupLatch.await(); 336 } catch (InterruptedException ex) { 337 throw new RuntimeException(ex); 338 } 339 } 340 341 private void setApplicationName(String name) { 342 Platform.runLater(()-> Application.GetApplication().setName(name)); 343 } 344 345 static ArrayList<DropTarget> targets = new ArrayList<>(); 346 347 DropTarget getDropTarget() { 348 return dropTarget; 349 } 350 351 void setDropTarget(DropTarget newTarget) { 352 if (dropTarget != null) { 353 targets.remove(dropTarget); 354 dropTarget.dispose(); 355 } 356 dropTarget = newTarget; 357 if (dropTarget != null) { 358 targets.add(dropTarget); 359 } 360 } 361 362 static void updateDropTarget() { 363 // Update all drop targets rather than just this target 364 // 365 // In order for a drop target to recognise a custom format, 366 // the format must be registered and the transfer type added 367 // to the list of transfers that the target accepts. This 368 // must happen before the drag and drop operations starts 369 // or the drop target will not accept the format. Therefore, 370 // set all transfers for all targets before any drag and drop 371 // operation starts 372 // 373 for (DropTarget target : targets) { 374 target.setTransfer(getAllTransfers()); 375 } 376 } 377 378 /** 379 * {@inheritDoc} 380 */ 381 public Point computeSize (int wHint, int hHint, boolean changed) { 382 checkWidget(); 383 if (wHint == -1 && hHint == -1) { 384 if (pPreferredWidth != -1 && pPreferredHeight != -1) { 385 return new Point (pPreferredWidth, pPreferredHeight); 386 } 387 } 388 return super.computeSize(wHint, hHint, changed); 389 } 390 391 /** 392 * Returns the JavaFX scene attached to this {@code FXCanvas}. 393 * 394 * @return the {@code Scene} attached to this {@code FXCanvas} 395 */ 396 public Scene getScene() { 397 checkWidget(); 398 return scene; 399 } 400 401 /** 402 * Attaches a {@code Scene} object to display in this {@code 403 * FXCanvas}. This method must called either on the JavaFX 404 * JavaFX application thread (which is the same as the SWT 405 * event dispatch thread). 406 * 407 * @param newScene a scene to display in this {@code FXCanvas} 408 * 409 * @see javafx.application.Platform#isFxApplicationThread() 410 */ 411 public void setScene(final Scene newScene) { 412 checkWidget(); 413 414 if ((stage == null) && (newScene != null)) { 415 stage = new EmbeddedWindow(hostContainer); 416 stage.show(); 417 } 418 scene = newScene; 419 if (stage != null) { 420 stage.setScene(newScene); 421 } 422 if ((stage != null) && (newScene == null)) { 423 stage.hide(); 424 stage = null; 425 } 426 } 427 428 // Note that removing the listeners is unnecessary 429 private void registerEventListeners() { 430 addDisposeListener(new DisposeListener() { 431 @Override 432 public void widgetDisposed(DisposeEvent de) { 433 Display display = getDisplay(); 434 display.removeFilter(SWT.Move, moveFilter); 435 FXCanvas.this.widgetDisposed(de); 436 } 437 }); 438 439 addPaintListener(pe -> { 440 FXCanvas.this.paintControl(pe); 441 }); 442 443 addMouseListener(new MouseListener() { 444 @Override 445 public void mouseDoubleClick(MouseEvent me) { 446 // Clicks and double-clicks are handled in FX 447 } 448 @Override 449 public void mouseDown(MouseEvent me) { 450 // FX only supports 3 buttons so don't send the event for other buttons 451 if (me.button > 3) return; 452 FXCanvas.this.sendMouseEventToFX(me, javafx.scene.input.MouseEvent.MOUSE_PRESSED); 453 } 454 @Override 455 public void mouseUp(MouseEvent me) { 456 // FX only supports 3 buttons so don't send the event for other buttons 457 if (me.button > 3) return; 458 FXCanvas.this.sendMouseEventToFX(me, javafx.scene.input.MouseEvent.MOUSE_RELEASED); 459 } 460 }); 461 462 addMouseMoveListener(me -> { 463 if ((me.stateMask & SWT.BUTTON_MASK) != 0) { 464 // FX only supports 3 buttons so don't send the event for other buttons 465 if ((me.stateMask & (SWT.BUTTON1 | SWT.BUTTON2 | SWT.BUTTON3)) != 0) { 466 FXCanvas.this.sendMouseEventToFX(me, javafx.scene.input.MouseEvent.MOUSE_DRAGGED); 467 } else { 468 FXCanvas.this.sendMouseEventToFX(me, javafx.scene.input.MouseEvent.MOUSE_MOVED); 469 } 470 } else { 471 FXCanvas.this.sendMouseEventToFX(me, javafx.scene.input.MouseEvent.MOUSE_MOVED); 472 } 473 }); 474 475 // SWT emulates mouse events from PAN gesture events. 476 // We need to suppress them while a gesture is active or inertia events are still processed. 477 addListener(SWT.MouseVerticalWheel, e -> { 478 if (!gestureActive && (!panGestureInertiaActive || lastGestureEvent == null || e.time != lastGestureEvent.time)) { 479 FXCanvas.this.sendMouseWheelEventToFX(0, SWTEvents.getWheelRotation(e), e.x, e.y, e.stateMask); 480 } 481 }); 482 addListener(SWT.MouseHorizontalWheel, e -> { 483 if (!gestureActive && (!panGestureInertiaActive || lastGestureEvent == null || e.time != lastGestureEvent.time)) { 484 FXCanvas.this.sendMouseWheelEventToFX(SWTEvents.getWheelRotation(e), 0, e.x, e.y, e.stateMask); 485 } 486 }); 487 488 addMouseTrackListener(new MouseTrackListener() { 489 @Override 490 public void mouseEnter(MouseEvent me) { 491 FXCanvas.this.sendMouseEventToFX(me, javafx.scene.input.MouseEvent.MOUSE_ENTERED); 492 } 493 @Override 494 public void mouseExit(MouseEvent me) { 495 FXCanvas.this.sendMouseEventToFX(me, javafx.scene.input.MouseEvent.MOUSE_EXITED); 496 } 497 @Override 498 public void mouseHover(MouseEvent me) { 499 // No mouse hovering in FX 500 } 501 }); 502 503 addControlListener(new ControlListener() { 504 @Override 505 public void controlMoved(ControlEvent ce) { 506 FXCanvas.this.sendMoveEventToFX(); 507 } 508 @Override 509 public void controlResized(ControlEvent ce) { 510 FXCanvas.this.sendResizeEventToFX(); 511 } 512 }); 513 514 addFocusListener(new FocusListener() { 515 @Override 516 public void focusGained(FocusEvent fe) { 517 FXCanvas.this.sendFocusEventToFX(fe, true); 518 } 519 @Override 520 public void focusLost(FocusEvent fe) { 521 FXCanvas.this.sendFocusEventToFX(fe, false); 522 } 523 }); 524 525 addKeyListener(new KeyListener() { 526 @Override 527 public void keyPressed(KeyEvent e) { 528 FXCanvas.this.sendKeyEventToFX(e, javafx.scene.input.KeyEvent.KEY_PRESSED); 529 530 } 531 @Override 532 public void keyReleased(KeyEvent e) { 533 FXCanvas.this.sendKeyEventToFX(e, javafx.scene.input.KeyEvent.KEY_RELEASED); 534 } 535 }); 536 537 addGestureListener(ge -> { 538 FXCanvas.this.sendGestureEventToFX(ge); 539 }); 540 541 addMenuDetectListener(e -> { 542 Runnable r = () -> { 543 if (isDisposed()) return; 544 FXCanvas.this.sendMenuEventToFX(e); 545 }; 546 // In SWT, MenuDetect comes before the equivalent mouse event 547 // On Mac, the order is MenuDetect, MouseDown, MouseUp. FX 548 // does not expect this order and when it gets the MouseDown, 549 // it closes the menu. The fix is to defer the MenuDetect 550 // notification until after the MouseDown is sent to FX. 551 if ("cocoa".equals(SWT.getPlatform())) { 552 getDisplay().asyncExec(r); 553 } else { 554 r.run(); 555 } 556 }); 557 } 558 559 private void widgetDisposed(DisposeEvent de) { 560 setDropTarget(null); 561 if (stage != null) { 562 stage.hide(); 563 } 564 } 565 566 double lastScaleFactor = 1.0; 567 int lastWidth, lastHeight; 568 IntBuffer lastPixelsBuf = null; 569 private void paintControl(PaintEvent pe) { 570 if ((scenePeer == null) || (pixelsBuf == null)) { 571 return; 572 } 573 574 double scaleFactor = getScaleFactor(); 575 if (lastScaleFactor != scaleFactor) { 576 resizePixelBuffer(scaleFactor); 577 lastScaleFactor = scaleFactor; 578 scenePeer.setPixelScaleFactors((float)scaleFactor, (float)scaleFactor); 579 } 580 581 // if we can't get the pixels, draw the bits that were there before 582 IntBuffer buffer = pixelsBuf; 583 int width = pWidth, height = pHeight; 584 if (scenePeer.getPixels(pixelsBuf, pWidth, pHeight)) { 585 width = lastWidth = pWidth; 586 height = lastHeight = pHeight; 587 buffer = lastPixelsBuf = pixelsBuf; 588 } else { 589 if (lastPixelsBuf == null) return; 590 width = lastWidth; 591 height = lastHeight; 592 buffer = lastPixelsBuf; 593 } 594 width = (int)Math.round(width * scaleFactor); 595 height = (int)Math.round(height * scaleFactor); 596 597 // Consider optimizing this 598 ImageData imageData = null; 599 if ("win32".equals(SWT.getPlatform())) { 600 PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000); 601 int scanline = width * 4; 602 byte[] dstData = new byte[scanline * height]; 603 int[] srcData = buffer.array(); 604 int dp = 0, sp = 0; 605 for (int y = 0; y < height; y++) { 606 for (int x = 0; x < width; x++) { 607 int p = srcData[sp++]; 608 dstData[dp++] = (byte) (p & 0xFF); //dst:blue 609 dstData[dp++] = (byte)((p >> 8) & 0xFF); //dst:green 610 dstData[dp++] = (byte)((p >> 16) & 0xFF); //dst:green 611 dstData[dp++] = (byte)0x00; //alpha 612 } 613 } 614 /*ImageData*/ imageData = new ImageData(width, height, 32, palette, 4, dstData); 615 } else { 616 if (width * height > buffer.array().length) { 617 // We shouldn't be here... 618 System.err.println("FXCanvas.paintControl: scale mismatch!"); 619 return; 620 } 621 PaletteData palette = new PaletteData(0x00ff0000, 0x0000ff00, 0x000000ff); 622 /*ImageData*/ imageData = new ImageData(width, height, 32, palette); 623 imageData.setPixels(0, 0,width * height, buffer.array(), 0); 624 } 625 626 Image image = new Image(Display.getDefault(), imageData); 627 pe.gc.drawImage(image, 0, 0, width, height, 0, 0, pWidth, pHeight); 628 image.dispose(); 629 } 630 631 private void sendMoveEventToFX() { 632 if ((stagePeer == null) /*|| !isShowing()*/) { 633 return; 634 } 635 Rectangle rect = getClientArea(); 636 Point los = toDisplay(rect.x, rect.y); 637 stagePeer.setLocation(los.x, los.y); 638 } 639 640 private void sendMouseEventToFX(MouseEvent me, EventType<javafx.scene.input.MouseEvent> type) { 641 if (scenePeer == null) { 642 return; 643 } 644 645 if (type == javafx.scene.input.MouseEvent.MOUSE_CLICKED) { 646 // Don't send click events to FX, as they are generated in Scene 647 return; 648 } 649 650 MouseButton button; 651 switch (me.button) { 652 case 1: 653 button = MouseButton.PRIMARY; 654 break; 655 case 2: 656 button = MouseButton.MIDDLE; 657 break; 658 case 3: 659 button = MouseButton.SECONDARY; 660 break; 661 default: 662 button = MouseButton.NONE; 663 if (type == javafx.scene.input.MouseEvent.MOUSE_MOVED 664 || type == javafx.scene.input.MouseEvent.MOUSE_DRAGGED 665 || type == javafx.scene.input.MouseEvent.MOUSE_ENTERED 666 || type == javafx.scene.input.MouseEvent.MOUSE_EXITED) { 667 // If this event was the result of mouse movement and has no 668 // button associated with it, then we look at the state to 669 // determine which button to report 670 if ((me.stateMask & SWT.BUTTON1) != 0) { 671 button = MouseButton.PRIMARY; 672 } else if ((me.stateMask & SWT.BUTTON2) != 0) { 673 button = MouseButton.MIDDLE; 674 } else if ((me.stateMask & SWT.BUTTON3) != 0) { 675 button = MouseButton.SECONDARY; 676 } 677 } 678 break; 679 } 680 681 boolean primaryBtnDown = (me.stateMask & SWT.BUTTON1) != 0; 682 boolean middleBtnDown = (me.stateMask & SWT.BUTTON2) != 0; 683 boolean secondaryBtnDown = (me.stateMask & SWT.BUTTON3) != 0; 684 if (type == javafx.scene.input.MouseEvent.MOUSE_PRESSED) { 685 primaryBtnDown |= me.button == 1; 686 middleBtnDown |= me.button == 2; 687 secondaryBtnDown |= me.button == 3; 688 } 689 else if (type == javafx.scene.input.MouseEvent.MOUSE_RELEASED) { 690 primaryBtnDown &= me.button != 1; 691 middleBtnDown &= me.button != 2; 692 secondaryBtnDown &= me.button != 3; 693 } 694 695 Point los = toDisplay(me.x, me.y); 696 697 scenePeer.mouseEvent( 698 type, 699 button, 700 primaryBtnDown, middleBtnDown, secondaryBtnDown, 701 me.x, me.y, 702 los.x, los.y, 703 (me.stateMask & SWT.SHIFT) != 0, 704 (me.stateMask & SWT.CONTROL) != 0, 705 (me.stateMask & SWT.ALT) != 0, 706 (me.stateMask & SWT.COMMAND) != 0, 707 false); // RT-32990: popup trigger not implemented 708 } 709 710 private void sendMouseWheelEventToFX(double scrollX, double scrollY, 711 int x, int y, int stateMask){ 712 if (scenePeer == null) { 713 return; 714 } 715 716 // granularity for mouse wheel scroll events is more coarse-grained than for pan gesture events; their 717 // total scroll is not accumulated, as they do not belong to a gesture 718 Point los = toDisplay(x, y); 719 scenePeer.scrollEvent(ScrollEvent.SCROLL, 720 scrollX, scrollY, 721 scrollX, scrollY, 722 40.0, 40.0, 723 x, y, 724 los.x, los.y, 725 (stateMask & SWT.SHIFT) != 0, 726 (stateMask & SWT.CONTROL) != 0, 727 (stateMask & SWT.ALT) != 0, 728 (stateMask & SWT.COMMAND) != 0, 729 false); 730 } 731 732 double totalScrollX = 0; 733 double totalScrollY = 0; 734 private void sendScrollEventToFX(EventType<ScrollEvent> type, double scrollX, double scrollY, 735 int x, int y, int stateMask, boolean inertia) { 736 // up to and including SWT 4.5, direction was inverted for pan gestures on the Mac 737 // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=481331) 738 final double multiplier = "cocoa".equals(SWT.getPlatform()) && SWT.getVersion() < 4600 ? -5.0 : 5.0; 739 740 if (type == ScrollEvent.SCROLL_STARTED) { 741 totalScrollX = 0; 742 totalScrollY = 0; 743 } else if (inertia) { 744 // inertia events do not belong to the gesture, 745 // thus total scroll is not accumulated 746 totalScrollX = scrollX; 747 totalScrollY = scrollY; 748 } else { 749 // accumulate total scroll as long as the gesture occurs 750 totalScrollX += scrollX; 751 totalScrollY += scrollY; 752 } 753 754 Point los = toDisplay(x, y); 755 scenePeer.scrollEvent(type, 756 scrollX, scrollY, 757 totalScrollX, totalScrollY, 758 multiplier, multiplier, 759 x, y, 760 los.x, los.y, 761 (stateMask & SWT.SHIFT) != 0, 762 (stateMask & SWT.CONTROL) != 0, 763 (stateMask & SWT.ALT) != 0, 764 (stateMask & SWT.COMMAND) != 0, 765 inertia); 766 } 767 768 private void sendKeyEventToFX(final KeyEvent e, EventType<javafx.scene.input.KeyEvent> type) { 769 if (scenePeer == null /*|| !isFxEnabled()*/) { 770 return; 771 } 772 int stateMask = e.stateMask; 773 if (type == javafx.scene.input.KeyEvent.KEY_PRESSED) { 774 if (e.keyCode == SWT.SHIFT) stateMask |= SWT.SHIFT; 775 if (e.keyCode == SWT.CONTROL) stateMask |= SWT.CONTROL; 776 if (e.keyCode == SWT.ALT) stateMask |= SWT.ALT; 777 if (e.keyCode == SWT.COMMAND) stateMask |= SWT.COMMAND; 778 } else { 779 if (e.keyCode == SWT.SHIFT) stateMask &= ~SWT.SHIFT; 780 if (e.keyCode == SWT.CONTROL) stateMask &= ~SWT.CONTROL; 781 if (e.keyCode == SWT.ALT) stateMask &= ~SWT.ALT; 782 if (e.keyCode == SWT.COMMAND) stateMask &= ~SWT.COMMAND; 783 } 784 boolean shift = (stateMask & SWT.SHIFT) != 0; 785 boolean ctrl = (stateMask & SWT.CONTROL) != 0; 786 boolean alt = (stateMask & SWT.ALT) != 0; 787 boolean meta = (stateMask & SWT.COMMAND) != 0; 788 789 int keyCode = SWTEvents.keyCodeToEmbedKeyCode(e.keyCode); 790 scenePeer.keyEvent(type, keyCode, new char[0], shift, ctrl, alt, meta); 791 if (e.character != '\0' && type == javafx.scene.input.KeyEvent.KEY_PRESSED) { 792 char[] chars = new char[] { e.character }; 793 scenePeer.keyEvent(javafx.scene.input.KeyEvent.KEY_TYPED, 794 e.keyCode, chars, shift, ctrl, alt, meta); 795 } 796 } 797 798 // true in between begin and end events of a (compound) gesture (not including inertia events) 799 private boolean gestureActive = false; 800 // true while inertia events of a pan gesture might be processed 801 private boolean panGestureInertiaActive = false; 802 // the last gesture event that was received (may also be an inertia event) 803 private GestureEvent lastGestureEvent; 804 // used to keep track of which (atomic) gestures are enclosed 805 private Stack<Integer> nestedGestures = new Stack<>(); 806 // data used to compute inertia values for pan gesture events (as SWT does not provide these) 807 private long inertiaTime = 0; 808 private double inertiaXScroll = 0.0; 809 private double inertiaYScroll = 0.0; 810 private void sendGestureEventToFX(GestureEvent gestureEvent) { 811 if (scenePeer == null) { 812 return; 813 } 814 815 // An SWT gesture may be compound, comprising several MAGNIFY, PAN, and ROTATE events, which are enclosed by a 816 // generic BEGIN and END event (while SWIPE events occur without being enclosed). 817 // In JavaFX, such a compound gesture is represented through (possibly nested) atomic gestures, which all 818 // (again excluding swipe) have their specific START and FINISH events. 819 // While a complex SWT gesture is active, we therefore have to generate START events for atomic gestures as 820 // needed, finishing them all when the compound SWT gesture ends (in the reverse order they were started), 821 // after which we still process inertia events (that only seem to occur for PAN). SWIPE events may simply be 822 // forwarded. 823 switch (gestureEvent.detail) { 824 case SWT.GESTURE_BEGIN: 825 // a (complex) gesture has started 826 gestureActive = true; 827 // we are within an active gesture, so no inertia processing now 828 panGestureInertiaActive = false; 829 break; 830 case SWT.GESTURE_MAGNIFY: 831 // emulate the start of an atomic gesture 832 if (gestureActive && !nestedGestures.contains(SWT.GESTURE_MAGNIFY)) { 833 sendZoomEventToFX(gestureEvent, ZoomEvent.ZOOM_STARTED); 834 nestedGestures.push(SWT.GESTURE_MAGNIFY); 835 } 836 sendZoomEventToFX(gestureEvent, ZoomEvent.ZOOM); 837 break; 838 case SWT.GESTURE_PAN: 839 // emulate the start of an atomic gesture 840 if (gestureActive && !nestedGestures.contains(SWT.GESTURE_PAN)) { 841 sendScrollEventToFX(ScrollEvent.SCROLL_STARTED, gestureEvent.xDirection, gestureEvent.yDirection, 842 gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, false); 843 nestedGestures.push(SWT.GESTURE_PAN); 844 } 845 846 // SWT does not flag inertia events and does not allow to distinguish emulated PAN gesture events 847 // (resulting from mouse wheel interaction) from native ones (resulting from touch device interaction); 848 // as it will always send both, mouse wheel as well as PAN gesture events when using the touch device or 849 // the mouse wheel, we can identify native PAN gesture inertia events only based on their temporal relationship 850 // to the preceding gesture event. 851 if(panGestureInertiaActive && gestureEvent.time > lastGestureEvent.time + 250) { 852 panGestureInertiaActive = false; 853 } 854 855 if(gestureActive || panGestureInertiaActive) { 856 double xDirection = gestureEvent.xDirection; 857 double yDirection = gestureEvent.yDirection; 858 859 if (panGestureInertiaActive) { 860 // calculate inertia values for scrollX and scrollY, as SWT (at least on MacOSX) provides zero values 861 if (xDirection == 0 && yDirection == 0) { 862 double delta = Math.max(0.0, Math.min(1.0, (gestureEvent.time - inertiaTime) / 1500.0)); 863 xDirection = (1.0 - delta) * inertiaXScroll; 864 yDirection = (1.0 - delta) * inertiaYScroll; 865 } 866 } 867 868 sendScrollEventToFX(ScrollEvent.SCROLL, xDirection, yDirection, 869 gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, panGestureInertiaActive); 870 } 871 break; 872 case SWT.GESTURE_ROTATE: 873 // emulate the start of an atomic gesture 874 if(gestureActive && !nestedGestures.contains(SWT.GESTURE_ROTATE)) { 875 sendRotateEventToFX(gestureEvent, RotateEvent.ROTATION_STARTED); 876 nestedGestures.push(SWT.GESTURE_ROTATE); 877 } 878 sendRotateEventToFX(gestureEvent, RotateEvent.ROTATE); 879 break; 880 case SWT.GESTURE_SWIPE: 881 sendSwipeEventToFX(gestureEvent); 882 break; 883 case SWT.GESTURE_END: 884 // finish atomic gesture(s) in reverse order of their start; SWIPE may be ignored, 885 // as JavaFX (like SWT) does not recognize it as a gesture 886 while (!nestedGestures.isEmpty()) { 887 switch (nestedGestures.pop()) { 888 case SWT.GESTURE_MAGNIFY: 889 sendZoomEventToFX(gestureEvent, ZoomEvent.ZOOM_FINISHED); 890 break; 891 case SWT.GESTURE_PAN: 892 sendScrollEventToFX(ScrollEvent.SCROLL_FINISHED, gestureEvent.xDirection, gestureEvent.yDirection, 893 gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, false); 894 // use the scroll values of the preceding scroll event to compute values for inertia events 895 inertiaXScroll = lastGestureEvent.xDirection; 896 inertiaYScroll = lastGestureEvent.yDirection; 897 inertiaTime = gestureEvent.time; 898 // from now on, inertia events may occur 899 panGestureInertiaActive = true; 900 break; 901 case SWT.GESTURE_ROTATE: 902 sendRotateEventToFX(gestureEvent, RotateEvent.ROTATION_FINISHED); 903 break; 904 } 905 } 906 // compound SWT gesture has ended 907 gestureActive = false; 908 break; 909 default: 910 // ignore 911 } 912 // keep track of currently received gesture event; this is needed to identify inertia events 913 lastGestureEvent = gestureEvent; 914 } 915 916 // used to compute zoom deltas, which are not provided by SWT 917 private double lastTotalZoom = 0.0; 918 private void sendZoomEventToFX(GestureEvent gestureEvent, EventType<ZoomEvent> type) { 919 Point los = toDisplay(gestureEvent.x, gestureEvent.y); 920 921 double totalZoom = gestureEvent.magnification; 922 if (type == ZoomEvent.ZOOM_STARTED) { 923 // ensure first event does not provide any zoom yet 924 totalZoom = lastTotalZoom = 1.0; 925 } else if (type == ZoomEvent.ZOOM_FINISHED) { 926 // SWT uses 0.0 for final event, while JavaFX still provides a (total) zoom value 927 totalZoom = lastTotalZoom; 928 } 929 double zoom = type == ZoomEvent.ZOOM_FINISHED ? 1.0 : totalZoom / lastTotalZoom; 930 lastTotalZoom = totalZoom; 931 932 scenePeer.zoomEvent(type, zoom, totalZoom, 933 gestureEvent.x, gestureEvent.y, los.x, los.y, 934 (gestureEvent.stateMask & SWT.SHIFT) != 0, 935 (gestureEvent.stateMask & SWT.CONTROL) != 0, 936 (gestureEvent.stateMask & SWT.ALT) != 0, 937 (gestureEvent.stateMask & SWT.COMMAND) != 0, 938 !gestureActive); 939 } 940 941 private double lastTotalAngle = 0.0; 942 private void sendRotateEventToFX(GestureEvent gestureEvent, EventType<RotateEvent> type) { 943 Point los = toDisplay(gestureEvent.x, gestureEvent.y); 944 945 double totalAngle = gestureEvent.rotation; 946 if (type == RotateEvent.ROTATION_STARTED) { 947 totalAngle = lastTotalAngle = 0.0; 948 } else if (type == RotateEvent.ROTATION_FINISHED) { 949 // SWT uses 0.0 for final event, while JavaFX still provides a (total) rotation value 950 totalAngle = lastTotalAngle; 951 } 952 double angle = type == RotateEvent.ROTATION_FINISHED ? 0.0 : totalAngle - lastTotalAngle; 953 lastTotalAngle = totalAngle; 954 955 scenePeer.rotateEvent(type, angle, totalAngle, 956 gestureEvent.x, gestureEvent.y, los.x, los.y, 957 (gestureEvent.stateMask & SWT.SHIFT) != 0, 958 (gestureEvent.stateMask & SWT.CONTROL) != 0, 959 (gestureEvent.stateMask & SWT.ALT) != 0, 960 (gestureEvent.stateMask & SWT.COMMAND) != 0, 961 !gestureActive); 962 } 963 964 private void sendSwipeEventToFX(GestureEvent gestureEvent) { 965 Point los = toDisplay(gestureEvent.x, gestureEvent.y); 966 EventType<SwipeEvent> type = null; 967 if(gestureEvent.yDirection > 0) { 968 type = SwipeEvent.SWIPE_DOWN; 969 } else if(gestureEvent.yDirection < 0) { 970 type = SwipeEvent.SWIPE_UP; 971 } else if(gestureEvent.xDirection > 0) { 972 type = SwipeEvent.SWIPE_RIGHT; 973 } else if(gestureEvent.xDirection < 0) { 974 type = SwipeEvent.SWIPE_LEFT; 975 } 976 scenePeer.swipeEvent(type, gestureEvent.x, gestureEvent.y, los.x, los.y, 977 (gestureEvent.stateMask & SWT.SHIFT) != 0, 978 (gestureEvent.stateMask & SWT.CONTROL) != 0, 979 (gestureEvent.stateMask & SWT.ALT) != 0, 980 (gestureEvent.stateMask & SWT.COMMAND) != 0); 981 } 982 983 private void sendMenuEventToFX(MenuDetectEvent me) { 984 if (scenePeer == null /*|| !isFxEnabled()*/) { 985 return; 986 } 987 Point pt = toControl(me.x, me.y); 988 scenePeer.menuEvent(pt.x, pt.y, me.x, me.y, false); 989 } 990 991 private void sendResizeEventToFX() { 992 993 // force the panel to draw right away (avoid black rectangle) 994 redraw(); 995 update(); 996 997 pWidth = getClientArea().width; 998 pHeight = getClientArea().height; 999 1000 resizePixelBuffer(lastScaleFactor); 1001 1002 if (scenePeer == null) { 1003 return; 1004 } 1005 1006 stagePeer.setSize(pWidth, pHeight); 1007 scenePeer.setSize(pWidth, pHeight); 1008 } 1009 1010 private void resizePixelBuffer(double newScaleFactor) { 1011 lastPixelsBuf = null; 1012 if ((pWidth <= 0) || (pHeight <= 0)) { 1013 pixelsBuf = null; 1014 } else { 1015 pixelsBuf = IntBuffer.allocate((int)Math.round(pWidth * newScaleFactor) * 1016 (int)Math.round(pHeight * newScaleFactor)); 1017 // The bg color may show through on resize. See RT-34380. 1018 RGB rgb = getBackground().getRGB(); 1019 Arrays.fill(pixelsBuf.array(), rgb.red << 16 | rgb.green << 8 | rgb.blue); 1020 } 1021 } 1022 1023 private void sendFocusEventToFX(FocusEvent fe, boolean focused) { 1024 if ((stage == null) || (stagePeer == null)) { 1025 return; 1026 } 1027 FocusCause focusCause = (focused ? FocusCause.ACTIVATED : FocusCause.DEACTIVATED); 1028 stagePeer.setFocused(focused, focusCause); 1029 } 1030 1031 private class HostContainer implements HostInterface { 1032 1033 final FXCanvas fxCanvas = FXCanvas.this; 1034 1035 @Override 1036 public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) { 1037 stagePeer = embeddedStage; 1038 if (stagePeer == null) { 1039 return; 1040 } 1041 if (pWidth > 0 && pHeight > 0) { 1042 stagePeer.setSize(pWidth, pHeight); 1043 } 1044 if (FXCanvas.this.isFocusControl()) { 1045 stagePeer.setFocused(true, FocusCause.ACTIVATED); 1046 } 1047 sendMoveEventToFX(); 1048 sendResizeEventToFX(); 1049 } 1050 1051 TransferMode getTransferMode(int bits) { 1052 switch (bits) { 1053 case DND.DROP_COPY: 1054 return TransferMode.COPY; 1055 case DND.DROP_MOVE: 1056 case DND.DROP_TARGET_MOVE: 1057 return TransferMode.MOVE; 1058 case DND.DROP_LINK: 1059 return TransferMode.LINK; 1060 default: 1061 return null; 1062 } 1063 } 1064 1065 Set<TransferMode> getTransferModes(int bits) { 1066 Set<TransferMode> set = new HashSet<TransferMode>(); 1067 if ((bits & DND.DROP_COPY) != 0) set.add(TransferMode.COPY); 1068 if ((bits & DND.DROP_MOVE) != 0) set.add(TransferMode.MOVE); 1069 if ((bits & DND.DROP_TARGET_MOVE) != 0) set.add(TransferMode.MOVE); 1070 if ((bits & DND.DROP_LINK) != 0) set.add(TransferMode.LINK); 1071 return set; 1072 } 1073 1074 ImageData createImageData(Pixels pixels) { 1075 if (pixels == null) return null; 1076 int width = pixels.getWidth(); 1077 int height = pixels.getHeight(); 1078 int bpr = width * 4; 1079 int dataSize = bpr * height; 1080 byte[] buffer = new byte[dataSize]; 1081 byte[] alphaData = new byte[width * height]; 1082 if (pixels.getBytesPerComponent() == 1) { 1083 // ByteBgraPre 1084 ByteBuffer pixbuf = (ByteBuffer) pixels.getPixels(); 1085 for (int y = 0, offset = 0, alphaOffset = 0; y < height; y++) { 1086 for (int x = 0; x < width; x++, offset += 4) { 1087 byte b = pixbuf.get(); 1088 byte g = pixbuf.get(); 1089 byte r = pixbuf.get(); 1090 byte a = pixbuf.get(); 1091 // non premultiplied ? 1092 alphaData[alphaOffset++] = a; 1093 buffer[offset] = b; 1094 buffer[offset + 1] = g; 1095 buffer[offset + 2] = r; 1096 buffer[offset + 3] = 0;// alpha 1097 } 1098 } 1099 } else if (pixels.getBytesPerComponent() == 4) { 1100 // IntArgbPre 1101 IntBuffer pixbuf = (IntBuffer) pixels.getPixels(); 1102 for (int y = 0, offset = 0, alphaOffset = 0; y < height; y++) { 1103 for (int x = 0; x < width; x++, offset += 4) { 1104 int pixel = pixbuf.get(); 1105 byte b = (byte) (pixel & 0xFF); 1106 byte g = (byte) ((pixel >> 8) & 0xFF); 1107 byte r = (byte) ((pixel >> 16) & 0xFF); 1108 byte a = (byte) ((pixel >> 24) & 0xFF); 1109 // non premultiplied ? 1110 alphaData[alphaOffset++] = a; 1111 buffer[offset] = b; 1112 buffer[offset + 1] = g; 1113 buffer[offset + 2] = r; 1114 buffer[offset + 3] = 0;// alpha 1115 } 1116 } 1117 } else { 1118 return null; 1119 } 1120 PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000); 1121 ImageData imageData = new ImageData(width, height, 32, palette, 4, buffer); 1122 imageData.alphaData = alphaData; 1123 return imageData; 1124 } 1125 1126 // Consider using dragAction 1127 private DragSource createDragSource(final EmbeddedSceneDSInterface fxDragSource, TransferMode dragAction) { 1128 Transfer [] transfers = getTransferTypes(fxDragSource.getMimeTypes()); 1129 if (transfers.length == 0) return null; 1130 int dragOperation = getDragActions(fxDragSource.getSupportedActions()); 1131 final DragSource dragSource = new DragSource(FXCanvas.this, dragOperation); 1132 dragSource.setTransfer(transfers); 1133 dragSource.addDragListener(new DragSourceListener() { 1134 public void dragFinished(org.eclipse.swt.dnd.DragSourceEvent event) { 1135 dragSource.dispose(); 1136 fxDragSource.dragDropEnd(getTransferMode(event.detail)); 1137 } 1138 public void dragSetData(org.eclipse.swt.dnd.DragSourceEvent event) { 1139 Transfer [] transfers = dragSource.getTransfer(); 1140 for (int i=0; i<transfers.length; i++) { 1141 if (transfers[i].isSupportedType(event.dataType)) { 1142 String mime = getMime(transfers[i]); 1143 if (mime != null) { 1144 event.doit = true; 1145 event.data = fxDragSource.getData(mime); 1146 if (event.data instanceof Pixels) { 1147 event.data = createImageData((Pixels)event.data); 1148 } 1149 return; 1150 } 1151 } 1152 event.doit = false; 1153 } 1154 } 1155 public void dragStart(org.eclipse.swt.dnd.DragSourceEvent event) { 1156 } 1157 }); 1158 return dragSource; 1159 } 1160 1161 int getDragAction(TransferMode tm) { 1162 if (tm == null) return DND.DROP_NONE; 1163 switch (tm) { 1164 case COPY: return DND.DROP_COPY; 1165 case MOVE: return DND.DROP_MOVE; 1166 case LINK: return DND.DROP_LINK; 1167 default: 1168 throw new IllegalArgumentException("Invalid transfer mode"); 1169 } 1170 } 1171 1172 int getDragActions(Set<TransferMode> set) { 1173 int result = 0; 1174 for (TransferMode mode : set) { 1175 result |= getDragAction(mode); 1176 } 1177 return result; 1178 } 1179 1180 Transfer getTransferType(String mime) { 1181 if (mime.equals("text/plain")) return TextTransfer.getInstance(); 1182 if (mime.equals("text/rtf")) return RTFTransfer.getInstance(); 1183 if (mime.equals("text/html")) return HTMLTransfer.getInstance(); 1184 if (mime.equals("text/uri-list")) return URLTransfer.getInstance(); 1185 if (mime.equals("application/x-java-rawimage")) return ImageTransfer.getInstance(); 1186 if (mime.equals("application/x-java-file-list") || mime.equals("java.file-list")) { 1187 return FileTransfer.getInstance(); 1188 } 1189 return getCustomTransfer(mime); 1190 } 1191 1192 Transfer [] getTransferTypes(String [] mimeTypes) { 1193 int count= 0; 1194 Transfer [] transfers = new Transfer [mimeTypes.length]; 1195 for (int i=0; i<mimeTypes.length; i++) { 1196 Transfer transfer = getTransferType(mimeTypes[i]); 1197 if (transfer != null) transfers [count++] = transfer; 1198 } 1199 if (count != mimeTypes.length) { 1200 Transfer [] newTransfers = new Transfer[count]; 1201 System.arraycopy(transfers, 0, newTransfers, 0, count); 1202 transfers = newTransfers; 1203 } 1204 return transfers; 1205 } 1206 1207 String getMime(Transfer transfer) { 1208 if (transfer.equals(TextTransfer.getInstance())) return "text/plain"; 1209 if (transfer.equals(RTFTransfer.getInstance())) return "text/rtf"; ; 1210 if (transfer.equals( HTMLTransfer.getInstance())) return "text/html"; 1211 if (transfer.equals(URLTransfer.getInstance())) return "text/uri-list"; 1212 if (transfer.equals( ImageTransfer.getInstance())) return "application/x-java-rawimage"; 1213 if (transfer.equals(FileTransfer.getInstance())) return "application/x-java-file-list"; 1214 if (transfer instanceof CustomTransfer) return ((CustomTransfer)transfer).getMime(); 1215 return null; 1216 } 1217 1218 String [] getMimes(Transfer [] transfers, TransferData data) { 1219 int count= 0; 1220 String [] result = new String [transfers.length]; 1221 for (int i=0; i<transfers.length; i++) { 1222 if (transfers[i].isSupportedType(data)) { 1223 result [count++] = getMime (transfers [i]); 1224 } 1225 } 1226 if (count != result.length) { 1227 String [] newResult = new String[count]; 1228 System.arraycopy(result, 0, newResult, 0, count); 1229 result = newResult; 1230 } 1231 return result; 1232 } 1233 1234 DropTarget createDropTarget(EmbeddedSceneInterface embeddedScene) { 1235 final DropTarget dropTarget = new DropTarget(FXCanvas.this, DND.DROP_COPY | DND.DROP_LINK | DND.DROP_MOVE); 1236 final EmbeddedSceneDTInterface fxDropTarget = embeddedScene.createDropTarget(); 1237 dropTarget.setTransfer(getAllTransfers()); 1238 dropTarget.addDropListener(new DropTargetListener() { 1239 Object data; 1240 // In SWT, the list of available types that the source can provide 1241 // is part of the event. FX queries this directly from the operating 1242 // system and bypasses SWT. This variable is commented out to remind 1243 // us of this potential inconsistency. 1244 // 1245 //TransferData [] transferData; 1246 TransferData currentTransferData; 1247 boolean ignoreLeave; 1248 int detail = DND.DROP_NONE, operations = DND.DROP_NONE; 1249 EmbeddedSceneDSInterface fxDragSource = new EmbeddedSceneDSInterface() { 1250 public Set<TransferMode> getSupportedActions() { 1251 return getTransferModes(operations); 1252 } 1253 public Object getData(String mimeType) { 1254 // NOTE: get the data for the requested mime type, not the default data 1255 return data; 1256 } 1257 public String[] getMimeTypes() { 1258 if (currentTransferData == null) return new String [0]; 1259 return getMimes(getAllTransfers(), currentTransferData); 1260 } 1261 public boolean isMimeTypeAvailable(String mimeType) { 1262 String [] mimes = getMimeTypes(); 1263 for (int i=0; i<mimes.length; i++) { 1264 if (mimes[i].equals(mimeType)) return true; 1265 } 1266 return false; 1267 } 1268 public void dragDropEnd(TransferMode performedAction) { 1269 data = null; 1270 //transferData = null; 1271 currentTransferData = null; 1272 } 1273 }; 1274 public void dragEnter(DropTargetEvent event) { 1275 ignoreLeave = false; 1276 dropTarget.setTransfer(getAllTransfers()); 1277 detail = event.detail; 1278 operations = event.operations; 1279 dragOver (event, true, detail); 1280 } 1281 public void dragLeave(DropTargetEvent event) { 1282 detail = operations = DND.DROP_NONE; 1283 data = null; 1284 //transferData = null; 1285 currentTransferData = null; 1286 getDisplay().asyncExec(() -> { 1287 if (ignoreLeave) return; 1288 fxDropTarget.handleDragLeave(); 1289 }); 1290 } 1291 public void dragOperationChanged(DropTargetEvent event) { 1292 detail = event.detail; 1293 operations = event.operations; 1294 dragOver(event, false, detail); 1295 } 1296 public void dragOver(DropTargetEvent event) { 1297 operations = event.operations; 1298 dragOver (event, false, detail); 1299 } 1300 public void dragOver(DropTargetEvent event, boolean enter, int detail) { 1301 //transferData = event.dataTypes; 1302 currentTransferData = event.currentDataType; 1303 Point pt = toControl(event.x, event.y); 1304 if (detail == DND.DROP_NONE) detail = DND.DROP_COPY; 1305 TransferMode acceptedMode, recommendedMode = getTransferMode(detail); 1306 if (enter) { 1307 acceptedMode = fxDropTarget.handleDragEnter(pt.x, pt.y, event.x, event.y, recommendedMode, fxDragSource); 1308 } else { 1309 acceptedMode = fxDropTarget.handleDragOver(pt.x, pt.y, event.x, event.y, recommendedMode); 1310 } 1311 event.detail = getDragAction(acceptedMode); 1312 } 1313 public void drop(DropTargetEvent event) { 1314 detail = event.detail; 1315 operations = event.operations; 1316 data = event.data; 1317 //transferData = event.dataTypes; 1318 currentTransferData = event.currentDataType; 1319 Point pt = toControl(event.x, event.y); 1320 TransferMode recommendedDropAction = getTransferMode(event.detail); 1321 TransferMode acceptedMode = fxDropTarget.handleDragDrop(pt.x, pt.y, event.x, event.y, recommendedDropAction); 1322 event.detail = getDragAction(acceptedMode); 1323 data = null; 1324 //transferData = null; 1325 currentTransferData = null; 1326 } 1327 public void dropAccept(DropTargetEvent event) { 1328 ignoreLeave = true; 1329 } 1330 }); 1331 return dropTarget; 1332 } 1333 1334 @Override 1335 public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) { 1336 scenePeer = embeddedScene; 1337 if (scenePeer == null) { 1338 return; 1339 } 1340 if (pWidth > 0 && pHeight > 0) { 1341 scenePeer.setSize(pWidth, pHeight); 1342 } 1343 double scaleFactor = getScaleFactor(); 1344 resizePixelBuffer(scaleFactor); 1345 lastScaleFactor = scaleFactor; 1346 scenePeer.setPixelScaleFactors((float)scaleFactor, (float)scaleFactor); 1347 scenePeer.setDragStartListener((fxDragSource, dragAction) -> { 1348 Platform.runLater(() -> { 1349 DragSource dragSource = createDragSource(fxDragSource, dragAction); 1350 if (dragSource == null) { 1351 fxDragSource.dragDropEnd(null); 1352 } else { 1353 updateDropTarget(); 1354 FXCanvas.this.notifyListeners(SWT.DragDetect, null); 1355 } 1356 }); 1357 }); 1358 //Force the old drop target to be disposed before creating a new one 1359 setDropTarget(null); 1360 setDropTarget(createDropTarget(embeddedScene)); 1361 } 1362 1363 @Override 1364 public boolean requestFocus() { 1365 Display.getDefault().asyncExec(() -> { 1366 if (isDisposed()) return; 1367 FXCanvas.this.forceFocus(); 1368 }); 1369 return true; 1370 } 1371 1372 @Override 1373 public boolean traverseFocusOut(boolean bln) { 1374 // RT-18085: not implemented 1375 return true; 1376 } 1377 1378 Object lock = new Object(); 1379 boolean queued = false; 1380 public void repaint() { 1381 synchronized (lock) { 1382 if (queued) return; 1383 queued = true; 1384 Display.getDefault().asyncExec(() -> { 1385 try { 1386 if (isDisposed()) return; 1387 FXCanvas.this.redraw(); 1388 } finally { 1389 synchronized (lock) { 1390 queued = false; 1391 } 1392 } 1393 }); 1394 } 1395 } 1396 1397 @Override 1398 public void setPreferredSize(int width, int height) { 1399 FXCanvas.this.pPreferredWidth = width; 1400 FXCanvas.this.pPreferredHeight = height; 1401 //FXCanvas.this.getShell().layout(new Control []{FXCanvas.this}, SWT.DEFER); 1402 } 1403 1404 @Override 1405 public void setEnabled(boolean bln) { 1406 FXCanvas.this.setEnabled(bln); 1407 } 1408 1409 @Override 1410 public void setCursor(CursorFrame cursorFrame) { 1411 FXCanvas.this.setCursor(getPlatformCursor(cursorFrame)); 1412 } 1413 1414 private org.eclipse.swt.graphics.Cursor getPlatformCursor(final CursorFrame cursorFrame) { 1415 /* 1416 * On the Mac, setting the cursor during drag and drop clears the move 1417 * and link indicators. The fix is to set the default cursor for the 1418 * control (which is null) when the FX explicitly requests the default 1419 * cursor. This will preserve the drag and drop indicators. 1420 */ 1421 if (cursorFrame.getCursorType() == CursorType.DEFAULT) { 1422 return null; 1423 } 1424 final org.eclipse.swt.graphics.Cursor cachedPlatformCursor = 1425 cursorFrame.getPlatformCursor(org.eclipse.swt.graphics.Cursor.class); 1426 if (cachedPlatformCursor != null) { 1427 // platform cursor already cached 1428 return cachedPlatformCursor; 1429 } 1430 1431 // platform cursor not cached yet 1432 final org.eclipse.swt.graphics.Cursor platformCursor = 1433 SWTCursors.embedCursorToCursor(cursorFrame); 1434 cursorFrame.setPlatforCursor(org.eclipse.swt.graphics.Cursor.class, platformCursor); 1435 1436 return platformCursor; 1437 } 1438 1439 @Override 1440 public boolean grabFocus() { 1441 // RT-27949: not implemented 1442 return true; 1443 } 1444 1445 @Override 1446 public void ungrabFocus() { 1447 // RT-27949: not implemented 1448 } 1449 } 1450 }