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