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