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.concurrent.CountDownLatch; 51 52 import javafx.application.Platform; 53 import javafx.beans.NamedArg; 54 import javafx.scene.Scene; 55 import javafx.scene.input.TransferMode; 56 import javafx.stage.Window; 57 import javafx.util.FXPermission; 58 59 import org.eclipse.swt.SWT; 60 import org.eclipse.swt.dnd.DND; 61 import org.eclipse.swt.dnd.DragSource; 62 import org.eclipse.swt.dnd.DragSourceListener; 63 import org.eclipse.swt.dnd.DropTarget; 64 import org.eclipse.swt.dnd.DropTargetEvent; 65 import org.eclipse.swt.dnd.DropTargetListener; 66 import org.eclipse.swt.dnd.FileTransfer; 67 import org.eclipse.swt.dnd.HTMLTransfer; 68 import org.eclipse.swt.dnd.ImageTransfer; 69 import org.eclipse.swt.dnd.RTFTransfer; 70 import org.eclipse.swt.dnd.TextTransfer; 71 import org.eclipse.swt.dnd.Transfer; 72 import org.eclipse.swt.dnd.TransferData; 73 import org.eclipse.swt.dnd.URLTransfer; 74 import org.eclipse.swt.events.ControlEvent; 75 import org.eclipse.swt.events.ControlListener; 76 import org.eclipse.swt.events.DisposeEvent; 77 import org.eclipse.swt.events.DisposeListener; 78 import org.eclipse.swt.events.FocusEvent; 79 import org.eclipse.swt.events.FocusListener; 80 import org.eclipse.swt.events.KeyEvent; 81 import org.eclipse.swt.events.KeyListener; 82 import org.eclipse.swt.events.MenuDetectEvent; 83 import org.eclipse.swt.events.MouseEvent; 84 import org.eclipse.swt.events.MouseListener; 85 import org.eclipse.swt.events.MouseTrackListener; 86 import org.eclipse.swt.events.PaintEvent; 87 import org.eclipse.swt.graphics.Image; 88 import org.eclipse.swt.graphics.ImageData; 89 import org.eclipse.swt.graphics.PaletteData; 90 import org.eclipse.swt.graphics.Point; 91 import org.eclipse.swt.graphics.RGB; 92 import org.eclipse.swt.graphics.Rectangle; 93 import org.eclipse.swt.widgets.Canvas; 94 import org.eclipse.swt.widgets.Composite; 95 import org.eclipse.swt.widgets.Control; 96 import org.eclipse.swt.widgets.Display; 97 import org.eclipse.swt.widgets.Event; 98 import org.eclipse.swt.widgets.Listener; 99 import org.eclipse.swt.widgets.Shell; 100 101 /** 102 * {@code FXCanvas} is a component to embed JavaFX content into 103 * SWT applications. The content to be displayed is specified 104 * with the {@link #setScene} method that accepts an instance of 105 * JavaFX {@code Scene}. After the scene is attached, it gets 106 * repainted automatically. All the input and focus events are 107 * forwarded to the scene transparently to the developer. 108 * <p> 109 * Here is a typical pattern how {@code FXCanvas} can used: 110 * <pre> 111 * public class Test { 112 * private static Scene createScene() { 113 * Group group = new Group(); 114 * Scene scene = new Scene(group); 115 * Button button = new Button("JFX Button"); 116 * group.getChildren().add(button); 117 * return scene; 118 * } 119 * 120 * public static void main(String[] args) { 121 * Display display = new Display(); 122 * Shell shell = new Shell(display); 123 * shell.setLayout(new FillLayout()); 124 * FXCanvas canvas = new FXCanvas(shell, SWT.NONE); 125 * Scene scene = createScene(); 126 * canvas.setScene(scene); 127 * shell.open(); 128 * while (!shell.isDisposed()) { 129 * if (!display.readAndDispatch()) display.sleep(); 130 * } 131 * display.dispose(); 132 * } 133 * } 134 * </pre> 135 * 136 * 137 * @since JavaFX 2.0 138 */ 139 public class FXCanvas extends Canvas { 140 141 // Internal permission used by FXCanvas (SWT interop) 142 private static final FXPermission FXCANVAS_PERMISSION = 143 new FXPermission("accessFXCanvasInternals"); 144 145 private HostContainer hostContainer; 146 private volatile EmbeddedWindow stage; 147 private volatile Scene scene; 148 private EmbeddedStageInterface stagePeer; 149 private EmbeddedSceneInterface scenePeer; 150 151 private int pWidth = 0; 152 private int pHeight = 0; 153 154 private volatile int pPreferredWidth = -1; 155 private volatile int pPreferredHeight = -1; 156 157 private IntBuffer pixelsBuf = null; 158 159 // This filter runs when any widget is moved 160 Listener moveFilter = event -> { 161 // If a parent has moved, send a move event to FX 162 Control control = FXCanvas.this; 163 while (control != null) { 164 if (control == event.widget) { 165 sendMoveEventToFX(); 166 break; 167 } 168 control = control.getParent(); 169 }; 170 }; 171 172 private double getScaleFactor() { 173 if (SWT.getPlatform().equals("cocoa")) { 174 if (windowField == null || screenMethod == null || backingScaleFactorMethod == null) { 175 return 1.0; 176 } 177 try { 178 Object nsWindow = windowField.get(this.getShell()); 179 Object nsScreen = screenMethod.invoke(nsWindow); 180 Object bsFactor = backingScaleFactorMethod.invoke(nsScreen); 181 return ((Double) bsFactor).doubleValue(); 182 } catch (Exception e) { 183 // FAIL silently should the reflection fail 184 } 185 } 186 return 1.0; 187 } 188 189 private DropTarget dropTarget; 190 191 static Transfer [] StandardTransfers = new Transfer [] { 192 TextTransfer.getInstance(), 193 RTFTransfer.getInstance(), 194 HTMLTransfer.getInstance(), 195 URLTransfer.getInstance(), 196 ImageTransfer.getInstance(), 197 FileTransfer.getInstance(), 198 }; 199 static Transfer [] CustomTransfers = new Transfer [0]; 200 201 static Transfer [] getAllTransfers () { 202 Transfer [] transfers = new Transfer[StandardTransfers.length + CustomTransfers.length]; 203 System.arraycopy(StandardTransfers, 0, transfers, 0, StandardTransfers.length); 204 System.arraycopy(CustomTransfers, 0, transfers, StandardTransfers.length, CustomTransfers.length); 205 return transfers; 206 } 207 208 static Transfer getCustomTransfer(String mime) { 209 for (int i=0; i<CustomTransfers.length; i++) { 210 if (((CustomTransfer)CustomTransfers[i]).getMime().equals(mime)) { 211 return CustomTransfers[i]; 212 } 213 } 214 Transfer transfer = new CustomTransfer (mime, mime); 215 Transfer [] newCustom = new Transfer [CustomTransfers.length + 1]; 216 System.arraycopy(CustomTransfers, 0, newCustom, 0, CustomTransfers.length); 217 newCustom[CustomTransfers.length] = transfer; 218 CustomTransfers = newCustom; 219 return transfer; 220 } 221 222 private static Field windowField; 223 private static Method windowMethod; 224 private static Method screenMethod; 225 private static Method backingScaleFactorMethod; 226 227 static { 228 if (SWT.getPlatform().equals("cocoa")) { 229 try { 230 windowField = Shell.class.getDeclaredField("window"); 231 windowField.setAccessible(true); 232 233 Class nsViewClass = Class.forName("org.eclipse.swt.internal.cocoa.NSView"); 234 windowMethod = nsViewClass.getDeclaredMethod("window"); 235 windowMethod.setAccessible(true); 236 237 Class nsWindowClass = Class.forName("org.eclipse.swt.internal.cocoa.NSWindow"); 238 screenMethod = nsWindowClass.getDeclaredMethod("screen"); 239 screenMethod.setAccessible(true); 240 241 Class nsScreenClass = Class.forName("org.eclipse.swt.internal.cocoa.NSScreen"); 242 backingScaleFactorMethod = nsScreenClass.getDeclaredMethod("backingScaleFactor"); 243 backingScaleFactorMethod.setAccessible(true); 244 } catch (Exception e) { 245 //Fail silently. If we can't get the methods, then the current version of SWT has no retina support 246 } 247 } 248 initFx(); 249 } 250 251 /** 252 * @inheritDoc 253 */ 254 public FXCanvas(@NamedArg("parent") Composite parent, @NamedArg("style") int style) { 255 super(parent, style | SWT.NO_BACKGROUND); 256 setApplicationName(Display.getAppName()); 257 hostContainer = new HostContainer(); 258 registerEventListeners(); 259 Display display = parent.getDisplay(); 260 display.addFilter(SWT.Move, moveFilter); 261 } 262 263 /** 264 * Retrieves the {@code FXCanvas} embedding the given {@code Scene}, 265 * that is the {@code FXCanvas} to which the given {@code Scene} was 266 * attached using {@link #setScene}. 267 * 268 * @param scene the {@code Scene} whose embedding {@code FXCanvas} 269 * instance is to be retrieved 270 * @return the {@code FXCanvas} to which the given {@code Scene} is 271 * attached, or null if the given {@code Scene} is not attached to an 272 * {@code FXCanvas}. 273 * 274 * @since 9 275 */ 276 public static FXCanvas getFXCanvas(Scene scene) { 277 Window window = scene.getWindow(); 278 if (window != null && window instanceof EmbeddedWindow) { 279 HostInterface hostInterface = ((EmbeddedWindow) window).getHost(); 280 if (hostInterface instanceof HostContainer) { 281 // Obtain FXCanvas as the enclosing instance of the FXCanvas$HostContainer 282 // that is host of the embedded Scene's EmbeddedWindow. 283 return ((HostContainer)hostInterface).fxCanvas; 284 } 285 } 286 return null; 287 } 288 289 private static void initFx() { 290 // NOTE: no internal "com.sun.*" packages can be accessed until after 291 // the JavaFX platform is initialized. The list of needed internal 292 // packages is kept in the PlatformImpl class. 293 long eventProc = 0; 294 try { 295 Field field = Display.class.getDeclaredField("eventProc"); 296 field.setAccessible(true); 297 if (field.getType() == int.class) { 298 eventProc = field.getInt(Display.getDefault()); 299 } else { 300 if (field.getType() == long.class) { 301 eventProc = field.getLong(Display.getDefault()); 302 } 303 } 304 } catch (Throwable th) { 305 //Fail silently 306 } 307 final String eventProcStr = String.valueOf(eventProc); 308 309 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 310 System.setProperty("com.sun.javafx.application.type", "FXCanvas"); 311 System.setProperty("javafx.embed.isEventThread", "true"); 312 System.setProperty("glass.win.uiScale", "100%"); 313 System.setProperty("glass.win.renderScale", "100%"); 314 System.setProperty("javafx.embed.eventProc", eventProcStr); 315 return null; 316 }); 317 318 final CountDownLatch startupLatch = new CountDownLatch(1); 319 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 320 Platform.startup(() -> { 321 startupLatch.countDown(); 322 }); 323 return null; 324 }, null, FXCANVAS_PERMISSION); 325 326 try { 327 startupLatch.await(); 328 } catch (InterruptedException ex) { 329 throw new RuntimeException(ex); 330 } 331 } 332 333 private void setApplicationName(String name) { 334 Platform.runLater(()-> Application.GetApplication().setName(name)); 335 } 336 337 static ArrayList<DropTarget> targets = new ArrayList<>(); 338 339 DropTarget getDropTarget() { 340 return dropTarget; 341 } 342 343 void setDropTarget(DropTarget newTarget) { 344 if (dropTarget != null) { 345 targets.remove(dropTarget); 346 dropTarget.dispose(); 347 } 348 dropTarget = newTarget; 349 if (dropTarget != null) { 350 targets.add(dropTarget); 351 } 352 } 353 354 static void updateDropTarget() { 355 // Update all drop targets rather than just this target 356 // 357 // In order for a drop target to recognise a custom format, 358 // the format must be registered and the transfer type added 359 // to the list of transfers that the target accepts. This 360 // must happen before the drag and drop operations starts 361 // or the drop target will not accept the format. Therefore, 362 // set all transfers for all targets before any drag and drop 363 // operation starts 364 // 365 for (DropTarget target : targets) { 366 target.setTransfer(getAllTransfers()); 367 } 368 } 369 370 /** 371 * {@inheritDoc} 372 */ 373 public Point computeSize (int wHint, int hHint, boolean changed) { 374 checkWidget(); 375 if (wHint == -1 && hHint == -1) { 376 if (pPreferredWidth != -1 && pPreferredHeight != -1) { 377 return new Point (pPreferredWidth, pPreferredHeight); 378 } 379 } 380 return super.computeSize(wHint, hHint, changed); 381 } 382 383 /** 384 * Returns the JavaFX scene attached to this {@code FXCanvas}. 385 * 386 * @return the {@code Scene} attached to this {@code FXCanvas} 387 */ 388 public Scene getScene() { 389 checkWidget(); 390 return scene; 391 } 392 393 /** 394 * Attaches a {@code Scene} object to display in this {@code 395 * FXCanvas}. This method must called either on the JavaFX 396 * JavaFX application thread (which is the same as the SWT 397 * event dispatch thread). 398 * 399 * @param newScene a scene to display in this {@code FXCanvas} 400 * 401 * @see javafx.application.Platform#isFxApplicationThread() 402 */ 403 public void setScene(final Scene newScene) { 404 checkWidget(); 405 406 if ((stage == null) && (newScene != null)) { 407 stage = new EmbeddedWindow(hostContainer); 408 stage.show(); 409 } 410 scene = newScene; 411 if (stage != null) { 412 stage.setScene(newScene); 413 } 414 if ((stage != null) && (newScene == null)) { 415 stage.hide(); 416 stage = null; 417 } 418 } 419 420 // Note that removing the listeners is unnecessary 421 private void registerEventListeners() { 422 addDisposeListener(new DisposeListener() { 423 @Override 424 public void widgetDisposed(DisposeEvent de) { 425 Display display = getDisplay(); 426 display.removeFilter(SWT.Move, moveFilter); 427 FXCanvas.this.widgetDisposed(de); 428 } 429 }); 430 431 addPaintListener(pe -> { 432 FXCanvas.this.paintControl(pe); 433 }); 434 435 addMouseListener(new MouseListener() { 436 @Override 437 public void mouseDoubleClick(MouseEvent me) { 438 // Clicks and double-clicks are handled in FX 439 } 440 @Override 441 public void mouseDown(MouseEvent me) { 442 // FX only supports 3 buttons so don't send the event for other buttons 443 if (me.button > 3) return; 444 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_PRESSED); 445 } 446 @Override 447 public void mouseUp(MouseEvent me) { 448 // FX only supports 3 buttons so don't send the event for other buttons 449 if (me.button > 3) return; 450 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_RELEASED); 451 } 452 }); 453 454 addMouseMoveListener(me -> { 455 if ((me.stateMask & SWT.BUTTON_MASK) != 0) { 456 // FX only supports 3 buttons so don't send the event for other buttons 457 if ((me.stateMask & (SWT.BUTTON1 | SWT.BUTTON2 | SWT.BUTTON3)) != 0) { 458 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_DRAGGED); 459 } else { 460 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED); 461 } 462 } else { 463 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED); 464 } 465 }); 466 467 addListener(SWT.MouseVerticalWheel, e -> { 468 FXCanvas.this.sendScrollEventToFX(e, AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL); 469 }); 470 addListener(SWT.MouseHorizontalWheel, e -> { 471 FXCanvas.this.sendScrollEventToFX(e, AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL); 472 }); 473 474 addMouseTrackListener(new MouseTrackListener() { 475 @Override 476 public void mouseEnter(MouseEvent me) { 477 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_ENTERED); 478 } 479 @Override 480 public void mouseExit(MouseEvent me) { 481 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_EXITED); 482 } 483 @Override 484 public void mouseHover(MouseEvent me) { 485 // No mouse hovering in FX 486 } 487 }); 488 489 addControlListener(new ControlListener() { 490 @Override 491 public void controlMoved(ControlEvent ce) { 492 FXCanvas.this.sendMoveEventToFX(); 493 } 494 @Override 495 public void controlResized(ControlEvent ce) { 496 FXCanvas.this.sendResizeEventToFX(); 497 } 498 }); 499 500 addFocusListener(new FocusListener() { 501 @Override 502 public void focusGained(FocusEvent fe) { 503 FXCanvas.this.sendFocusEventToFX(fe, true); 504 } 505 @Override 506 public void focusLost(FocusEvent fe) { 507 FXCanvas.this.sendFocusEventToFX(fe, false); 508 } 509 }); 510 511 addKeyListener(new KeyListener() { 512 @Override 513 public void keyPressed(KeyEvent e) { 514 FXCanvas.this.sendKeyEventToFX(e, SWT.KeyDown); 515 516 } 517 @Override 518 public void keyReleased(KeyEvent e) { 519 FXCanvas.this.sendKeyEventToFX(e, SWT.KeyUp); 520 } 521 }); 522 523 addMenuDetectListener(e -> { 524 Runnable r = () -> { 525 if (isDisposed()) return; 526 FXCanvas.this.sendMenuEventToFX(e); 527 }; 528 // In SWT, MenuDetect comes before the equivalent mouse event 529 // On Mac, the order is MenuDetect, MouseDown, MouseUp. FX 530 // does not expect this order and when it gets the MouseDown, 531 // it closes the menu. The fix is to defer the MenuDetect 532 // notification until after the MouseDown is sent to FX. 533 if ("cocoa".equals(SWT.getPlatform())) { 534 getDisplay().asyncExec(r); 535 } else { 536 r.run(); 537 } 538 }); 539 } 540 541 private void widgetDisposed(DisposeEvent de) { 542 setDropTarget(null); 543 if (stage != null) { 544 stage.hide(); 545 } 546 } 547 548 double lastScaleFactor = 1.0; 549 int lastWidth, lastHeight; 550 IntBuffer lastPixelsBuf = null; 551 private void paintControl(PaintEvent pe) { 552 if ((scenePeer == null) || (pixelsBuf == null)) { 553 return; 554 } 555 556 double scaleFactor = getScaleFactor(); 557 if (lastScaleFactor != scaleFactor) { 558 resizePixelBuffer(scaleFactor); 559 lastScaleFactor = scaleFactor; 560 scenePeer.setPixelScaleFactors((float)scaleFactor, (float)scaleFactor); 561 } 562 563 // if we can't get the pixels, draw the bits that were there before 564 IntBuffer buffer = pixelsBuf; 565 int width = pWidth, height = pHeight; 566 if (scenePeer.getPixels(pixelsBuf, pWidth, pHeight)) { 567 width = lastWidth = pWidth; 568 height = lastHeight = pHeight; 569 buffer = lastPixelsBuf = pixelsBuf; 570 } else { 571 if (lastPixelsBuf == null) return; 572 width = lastWidth; 573 height = lastHeight; 574 buffer = lastPixelsBuf; 575 } 576 width = (int)Math.round(width * scaleFactor); 577 height = (int)Math.round(height * scaleFactor); 578 579 // Consider optimizing this 580 ImageData imageData = null; 581 if ("win32".equals(SWT.getPlatform())) { 582 PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000); 583 int scanline = width * 4; 584 byte[] dstData = new byte[scanline * height]; 585 int[] srcData = buffer.array(); 586 int dp = 0, sp = 0; 587 for (int y = 0; y < height; y++) { 588 for (int x = 0; x < width; x++) { 589 int p = srcData[sp++]; 590 dstData[dp++] = (byte) (p & 0xFF); //dst:blue 591 dstData[dp++] = (byte)((p >> 8) & 0xFF); //dst:green 592 dstData[dp++] = (byte)((p >> 16) & 0xFF); //dst:green 593 dstData[dp++] = (byte)0x00; //alpha 594 } 595 } 596 /*ImageData*/ imageData = new ImageData(width, height, 32, palette, 4, dstData); 597 } else { 598 if (width * height > buffer.array().length) { 599 // We shouldn't be here... 600 System.err.println("FXCanvas.paintControl: scale mismatch!"); 601 return; 602 } 603 PaletteData palette = new PaletteData(0x00ff0000, 0x0000ff00, 0x000000ff); 604 /*ImageData*/ imageData = new ImageData(width, height, 32, palette); 605 imageData.setPixels(0, 0,width * height, buffer.array(), 0); 606 } 607 608 Image image = new Image(Display.getDefault(), imageData); 609 pe.gc.drawImage(image, 0, 0, width, height, 0, 0, pWidth, pHeight); 610 image.dispose(); 611 } 612 613 private void sendMoveEventToFX() { 614 if ((stagePeer == null) /*|| !isShowing()*/) { 615 return; 616 } 617 Rectangle rect = getClientArea(); 618 Point los = toDisplay(rect.x, rect.y); 619 stagePeer.setLocation(los.x, los.y); 620 } 621 622 private void sendMouseEventToFX(MouseEvent me, int embedMouseType) { 623 if (scenePeer == null) { 624 return; 625 } 626 627 Point los = toDisplay(me.x, me.y); 628 boolean primaryBtnDown = (me.stateMask & SWT.BUTTON1) != 0; 629 boolean middleBtnDown = (me.stateMask & SWT.BUTTON2) != 0; 630 boolean secondaryBtnDown = (me.stateMask & SWT.BUTTON3) != 0; 631 boolean shift = (me.stateMask & SWT.SHIFT) != 0; 632 boolean control = (me.stateMask & SWT.CONTROL) != 0; 633 boolean alt = (me.stateMask & SWT.ALT) != 0; 634 boolean meta = (me.stateMask & SWT.COMMAND) != 0; 635 int button = me.button; 636 switch (embedMouseType) { 637 case AbstractEvents.MOUSEEVENT_PRESSED: 638 primaryBtnDown |= me.button == 1; 639 middleBtnDown |= me.button == 2; 640 secondaryBtnDown |= me.button == 3; 641 break; 642 case AbstractEvents.MOUSEEVENT_RELEASED: 643 primaryBtnDown &= me.button != 1; 644 middleBtnDown &= me.button != 2; 645 secondaryBtnDown &= me.button != 3; 646 break; 647 case AbstractEvents.MOUSEEVENT_CLICKED: 648 // Don't send click events to FX, as they are generated in Scene 649 return; 650 651 case AbstractEvents.MOUSEEVENT_MOVED: 652 case AbstractEvents.MOUSEEVENT_DRAGGED: 653 case AbstractEvents.MOUSEEVENT_ENTERED: 654 case AbstractEvents.MOUSEEVENT_EXITED: 655 // If this event was the result of mouse movement and has no 656 // button associated with it, then we look at the state to 657 // determine which button to report 658 if (button == 0) { 659 if ((me.stateMask & SWT.BUTTON1) != 0) { 660 button = 1; 661 } else if ((me.stateMask & SWT.BUTTON2) != 0) { 662 button = 2; 663 } else if ((me.stateMask & SWT.BUTTON3) != 0) { 664 button = 3; 665 } 666 } 667 break; 668 669 default: 670 break; 671 } 672 673 scenePeer.mouseEvent( 674 embedMouseType, 675 SWTEvents.mouseButtonToEmbedMouseButton(button, me.stateMask), 676 primaryBtnDown, middleBtnDown, secondaryBtnDown, 677 me.x, me.y, 678 los.x, los.y, 679 shift, control, alt, meta, 680 false); // RT-32990: popup trigger not implemented 681 } 682 683 private void sendScrollEventToFX(Event event, int type){ 684 if (scenePeer == null) { 685 return; 686 } 687 Point los = toDisplay(event.x, event.y); 688 scenePeer.scrollEvent(type, 689 (type == AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL) ? -SWTEvents.getWheelRotation(event) : 0, 690 (type == AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL) ? -SWTEvents.getWheelRotation(event) : 0, 691 event.x, event.y, 692 los.x, los.y, 693 (event.stateMask & SWT.SHIFT) != 0, 694 (event.stateMask & SWT.CONTROL) != 0, 695 (event.stateMask & SWT.ALT) != 0, 696 (event.stateMask & SWT.COMMAND) != 0); 697 } 698 699 private void sendKeyEventToFX(final KeyEvent e, int type) { 700 if (scenePeer == null /*|| !isFxEnabled()*/) { 701 return; 702 } 703 int stateMask = e.stateMask; 704 if (type == SWT.KeyDown) { 705 if (e.keyCode == SWT.SHIFT) stateMask |= SWT.SHIFT; 706 if (e.keyCode == SWT.CONTROL) stateMask |= SWT.CONTROL; 707 if (e.keyCode == SWT.ALT) stateMask |= SWT.ALT; 708 if (e.keyCode == SWT.COMMAND) stateMask |= SWT.COMMAND; 709 } else { 710 if (e.keyCode == SWT.SHIFT) stateMask &= ~SWT.SHIFT; 711 if (e.keyCode == SWT.CONTROL) stateMask &= ~SWT.CONTROL; 712 if (e.keyCode == SWT.ALT) stateMask &= ~SWT.ALT; 713 if (e.keyCode == SWT.COMMAND) stateMask &= ~SWT.COMMAND; 714 } 715 int keyCode = SWTEvents.keyCodeToEmbedKeyCode(e.keyCode); 716 scenePeer.keyEvent( 717 SWTEvents.keyIDToEmbedKeyType(type), 718 keyCode, new char[0], 719 SWTEvents.keyModifiersToEmbedKeyModifiers(stateMask)); 720 if (e.character != '\0' && type == SWT.KeyDown) { 721 char[] chars = new char[] { e.character }; 722 scenePeer.keyEvent( 723 AbstractEvents.KEYEVENT_TYPED, 724 e.keyCode, chars, 725 SWTEvents.keyModifiersToEmbedKeyModifiers(stateMask)); 726 } 727 } 728 729 private void sendMenuEventToFX(MenuDetectEvent me) { 730 if (scenePeer == null /*|| !isFxEnabled()*/) { 731 return; 732 } 733 Point pt = toControl(me.x, me.y); 734 scenePeer.menuEvent(pt.x, pt.y, me.x, me.y, false); 735 } 736 737 private void sendResizeEventToFX() { 738 739 // force the panel to draw right away (avoid black rectangle) 740 redraw(); 741 update(); 742 743 pWidth = getClientArea().width; 744 pHeight = getClientArea().height; 745 746 resizePixelBuffer(lastScaleFactor); 747 748 if (scenePeer == null) { 749 return; 750 } 751 752 stagePeer.setSize(pWidth, pHeight); 753 scenePeer.setSize(pWidth, pHeight); 754 } 755 756 private void resizePixelBuffer(double newScaleFactor) { 757 lastPixelsBuf = null; 758 if ((pWidth <= 0) || (pHeight <= 0)) { 759 pixelsBuf = null; 760 } else { 761 pixelsBuf = IntBuffer.allocate((int)Math.round(pWidth * newScaleFactor) * 762 (int)Math.round(pHeight * newScaleFactor)); 763 // The bg color may show through on resize. See RT-34380. 764 RGB rgb = getBackground().getRGB(); 765 Arrays.fill(pixelsBuf.array(), rgb.red << 16 | rgb.green << 8 | rgb.blue); 766 } 767 } 768 769 private void sendFocusEventToFX(FocusEvent fe, boolean focused) { 770 if ((stage == null) || (stagePeer == null)) { 771 return; 772 } 773 int focusCause = (focused ? 774 AbstractEvents.FOCUSEVENT_ACTIVATED : 775 AbstractEvents.FOCUSEVENT_DEACTIVATED); 776 stagePeer.setFocused(focused, focusCause); 777 } 778 779 private class HostContainer implements HostInterface { 780 781 final FXCanvas fxCanvas = FXCanvas.this; 782 783 @Override 784 public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) { 785 stagePeer = embeddedStage; 786 if (stagePeer == null) { 787 return; 788 } 789 if (pWidth > 0 && pHeight > 0) { 790 stagePeer.setSize(pWidth, pHeight); 791 } 792 if (FXCanvas.this.isFocusControl()) { 793 stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED); 794 } 795 sendMoveEventToFX(); 796 sendResizeEventToFX(); 797 } 798 799 TransferMode getTransferMode(int bits) { 800 switch (bits) { 801 case DND.DROP_COPY: 802 return TransferMode.COPY; 803 case DND.DROP_MOVE: 804 case DND.DROP_TARGET_MOVE: 805 return TransferMode.MOVE; 806 case DND.DROP_LINK: 807 return TransferMode.LINK; 808 default: 809 return null; 810 } 811 } 812 813 Set<TransferMode> getTransferModes(int bits) { 814 Set<TransferMode> set = new HashSet<TransferMode>(); 815 if ((bits & DND.DROP_COPY) != 0) set.add(TransferMode.COPY); 816 if ((bits & DND.DROP_MOVE) != 0) set.add(TransferMode.MOVE); 817 if ((bits & DND.DROP_TARGET_MOVE) != 0) set.add(TransferMode.MOVE); 818 if ((bits & DND.DROP_LINK) != 0) set.add(TransferMode.LINK); 819 return set; 820 } 821 822 ImageData createImageData(Pixels pixels) { 823 if (pixels == null) return null; 824 int width = pixels.getWidth(); 825 int height = pixels.getHeight(); 826 int bpr = width * 4; 827 int dataSize = bpr * height; 828 byte[] buffer = new byte[dataSize]; 829 byte[] alphaData = new byte[width * height]; 830 if (pixels.getBytesPerComponent() == 1) { 831 // ByteBgraPre 832 ByteBuffer pixbuf = (ByteBuffer) pixels.getPixels(); 833 for (int y = 0, offset = 0, alphaOffset = 0; y < height; y++) { 834 for (int x = 0; x < width; x++, offset += 4) { 835 byte b = pixbuf.get(); 836 byte g = pixbuf.get(); 837 byte r = pixbuf.get(); 838 byte a = pixbuf.get(); 839 // non premultiplied ? 840 alphaData[alphaOffset++] = a; 841 buffer[offset] = b; 842 buffer[offset + 1] = g; 843 buffer[offset + 2] = r; 844 buffer[offset + 3] = 0;// alpha 845 } 846 } 847 } else if (pixels.getBytesPerComponent() == 4) { 848 // IntArgbPre 849 IntBuffer pixbuf = (IntBuffer) pixels.getPixels(); 850 for (int y = 0, offset = 0, alphaOffset = 0; y < height; y++) { 851 for (int x = 0; x < width; x++, offset += 4) { 852 int pixel = pixbuf.get(); 853 byte b = (byte) (pixel & 0xFF); 854 byte g = (byte) ((pixel >> 8) & 0xFF); 855 byte r = (byte) ((pixel >> 16) & 0xFF); 856 byte a = (byte) ((pixel >> 24) & 0xFF); 857 // non premultiplied ? 858 alphaData[alphaOffset++] = a; 859 buffer[offset] = b; 860 buffer[offset + 1] = g; 861 buffer[offset + 2] = r; 862 buffer[offset + 3] = 0;// alpha 863 } 864 } 865 } else { 866 return null; 867 } 868 PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000); 869 ImageData imageData = new ImageData(width, height, 32, palette, 4, buffer); 870 imageData.alphaData = alphaData; 871 return imageData; 872 } 873 874 // Consider using dragAction 875 private DragSource createDragSource(final EmbeddedSceneDSInterface fxDragSource, TransferMode dragAction) { 876 Transfer [] transfers = getTransferTypes(fxDragSource.getMimeTypes()); 877 if (transfers.length == 0) return null; 878 int dragOperation = getDragActions(fxDragSource.getSupportedActions()); 879 final DragSource dragSource = new DragSource(FXCanvas.this, dragOperation); 880 dragSource.setTransfer(transfers); 881 dragSource.addDragListener(new DragSourceListener() { 882 public void dragFinished(org.eclipse.swt.dnd.DragSourceEvent event) { 883 dragSource.dispose(); 884 fxDragSource.dragDropEnd(getTransferMode(event.detail)); 885 } 886 public void dragSetData(org.eclipse.swt.dnd.DragSourceEvent event) { 887 Transfer [] transfers = dragSource.getTransfer(); 888 for (int i=0; i<transfers.length; i++) { 889 if (transfers[i].isSupportedType(event.dataType)) { 890 String mime = getMime(transfers[i]); 891 if (mime != null) { 892 event.doit = true; 893 event.data = fxDragSource.getData(mime); 894 if (event.data instanceof Pixels) { 895 event.data = createImageData((Pixels)event.data); 896 } 897 return; 898 } 899 } 900 event.doit = false; 901 } 902 } 903 public void dragStart(org.eclipse.swt.dnd.DragSourceEvent event) { 904 } 905 }); 906 return dragSource; 907 } 908 909 int getDragAction(TransferMode tm) { 910 if (tm == null) return DND.DROP_NONE; 911 switch (tm) { 912 case COPY: return DND.DROP_COPY; 913 case MOVE: return DND.DROP_MOVE; 914 case LINK: return DND.DROP_LINK; 915 default: 916 throw new IllegalArgumentException("Invalid transfer mode"); 917 } 918 } 919 920 int getDragActions(Set<TransferMode> set) { 921 int result = 0; 922 for (TransferMode mode : set) { 923 result |= getDragAction(mode); 924 } 925 return result; 926 } 927 928 Transfer getTransferType(String mime) { 929 if (mime.equals("text/plain")) return TextTransfer.getInstance(); 930 if (mime.equals("text/rtf")) return RTFTransfer.getInstance(); 931 if (mime.equals("text/html")) return HTMLTransfer.getInstance(); 932 if (mime.equals("text/uri-list")) return URLTransfer.getInstance(); 933 if (mime.equals("application/x-java-rawimage")) return ImageTransfer.getInstance(); 934 if (mime.equals("application/x-java-file-list") || mime.equals("java.file-list")) { 935 return FileTransfer.getInstance(); 936 } 937 return getCustomTransfer(mime); 938 } 939 940 Transfer [] getTransferTypes(String [] mimeTypes) { 941 int count= 0; 942 Transfer [] transfers = new Transfer [mimeTypes.length]; 943 for (int i=0; i<mimeTypes.length; i++) { 944 Transfer transfer = getTransferType(mimeTypes[i]); 945 if (transfer != null) transfers [count++] = transfer; 946 } 947 if (count != mimeTypes.length) { 948 Transfer [] newTransfers = new Transfer[count]; 949 System.arraycopy(transfers, 0, newTransfers, 0, count); 950 transfers = newTransfers; 951 } 952 return transfers; 953 } 954 955 String getMime(Transfer transfer) { 956 if (transfer.equals(TextTransfer.getInstance())) return "text/plain"; 957 if (transfer.equals(RTFTransfer.getInstance())) return "text/rtf"; ; 958 if (transfer.equals( HTMLTransfer.getInstance())) return "text/html"; 959 if (transfer.equals(URLTransfer.getInstance())) return "text/uri-list"; 960 if (transfer.equals( ImageTransfer.getInstance())) return "application/x-java-rawimage"; 961 if (transfer.equals(FileTransfer.getInstance())) return "application/x-java-file-list"; 962 if (transfer instanceof CustomTransfer) return ((CustomTransfer)transfer).getMime(); 963 return null; 964 } 965 966 String [] getMimes(Transfer [] transfers, TransferData data) { 967 int count= 0; 968 String [] result = new String [transfers.length]; 969 for (int i=0; i<transfers.length; i++) { 970 if (transfers[i].isSupportedType(data)) { 971 result [count++] = getMime (transfers [i]); 972 } 973 } 974 if (count != result.length) { 975 String [] newResult = new String[count]; 976 System.arraycopy(result, 0, newResult, 0, count); 977 result = newResult; 978 } 979 return result; 980 } 981 982 DropTarget createDropTarget(EmbeddedSceneInterface embeddedScene) { 983 final DropTarget dropTarget = new DropTarget(FXCanvas.this, DND.DROP_COPY | DND.DROP_LINK | DND.DROP_MOVE); 984 final EmbeddedSceneDTInterface fxDropTarget = embeddedScene.createDropTarget(); 985 dropTarget.setTransfer(getAllTransfers()); 986 dropTarget.addDropListener(new DropTargetListener() { 987 Object data; 988 // In SWT, the list of available types that the source can provide 989 // is part of the event. FX queries this directly from the operating 990 // system and bypasses SWT. This variable is commented out to remind 991 // us of this potential inconsistency. 992 // 993 //TransferData [] transferData; 994 TransferData currentTransferData; 995 boolean ignoreLeave; 996 int detail = DND.DROP_NONE, operations = DND.DROP_NONE; 997 EmbeddedSceneDSInterface fxDragSource = new EmbeddedSceneDSInterface() { 998 public Set<TransferMode> getSupportedActions() { 999 return getTransferModes(operations); 1000 } 1001 public Object getData(String mimeType) { 1002 // NOTE: get the data for the requested mime type, not the default data 1003 return data; 1004 } 1005 public String[] getMimeTypes() { 1006 if (currentTransferData == null) return new String [0]; 1007 return getMimes(getAllTransfers(), currentTransferData); 1008 } 1009 public boolean isMimeTypeAvailable(String mimeType) { 1010 String [] mimes = getMimeTypes(); 1011 for (int i=0; i<mimes.length; i++) { 1012 if (mimes[i].equals(mimeType)) return true; 1013 } 1014 return false; 1015 } 1016 public void dragDropEnd(TransferMode performedAction) { 1017 data = null; 1018 //transferData = null; 1019 currentTransferData = null; 1020 } 1021 }; 1022 public void dragEnter(DropTargetEvent event) { 1023 ignoreLeave = false; 1024 dropTarget.setTransfer(getAllTransfers()); 1025 detail = event.detail; 1026 operations = event.operations; 1027 dragOver (event, true, detail); 1028 } 1029 public void dragLeave(DropTargetEvent event) { 1030 detail = operations = DND.DROP_NONE; 1031 data = null; 1032 //transferData = null; 1033 currentTransferData = null; 1034 getDisplay().asyncExec(() -> { 1035 if (ignoreLeave) return; 1036 fxDropTarget.handleDragLeave(); 1037 }); 1038 } 1039 public void dragOperationChanged(DropTargetEvent event) { 1040 detail = event.detail; 1041 operations = event.operations; 1042 dragOver(event, false, detail); 1043 } 1044 public void dragOver(DropTargetEvent event) { 1045 operations = event.operations; 1046 dragOver (event, false, detail); 1047 } 1048 public void dragOver(DropTargetEvent event, boolean enter, int detail) { 1049 //transferData = event.dataTypes; 1050 currentTransferData = event.currentDataType; 1051 Point pt = toControl(event.x, event.y); 1052 if (detail == DND.DROP_NONE) detail = DND.DROP_COPY; 1053 TransferMode acceptedMode, recommendedMode = getTransferMode(detail); 1054 if (enter) { 1055 acceptedMode = fxDropTarget.handleDragEnter(pt.x, pt.y, event.x, event.y, recommendedMode, fxDragSource); 1056 } else { 1057 acceptedMode = fxDropTarget.handleDragOver(pt.x, pt.y, event.x, event.y, recommendedMode); 1058 } 1059 event.detail = getDragAction(acceptedMode); 1060 } 1061 public void drop(DropTargetEvent event) { 1062 detail = event.detail; 1063 operations = event.operations; 1064 data = event.data; 1065 //transferData = event.dataTypes; 1066 currentTransferData = event.currentDataType; 1067 Point pt = toControl(event.x, event.y); 1068 TransferMode recommendedDropAction = getTransferMode(event.detail); 1069 TransferMode acceptedMode = fxDropTarget.handleDragDrop(pt.x, pt.y, event.x, event.y, recommendedDropAction); 1070 event.detail = getDragAction(acceptedMode); 1071 data = null; 1072 //transferData = null; 1073 currentTransferData = null; 1074 } 1075 public void dropAccept(DropTargetEvent event) { 1076 ignoreLeave = true; 1077 } 1078 }); 1079 return dropTarget; 1080 } 1081 1082 @Override 1083 public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) { 1084 scenePeer = embeddedScene; 1085 if (scenePeer == null) { 1086 return; 1087 } 1088 if (pWidth > 0 && pHeight > 0) { 1089 scenePeer.setSize(pWidth, pHeight); 1090 } 1091 double scaleFactor = getScaleFactor(); 1092 resizePixelBuffer(scaleFactor); 1093 lastScaleFactor = scaleFactor; 1094 scenePeer.setPixelScaleFactors((float)scaleFactor, (float)scaleFactor); 1095 scenePeer.setDragStartListener((fxDragSource, dragAction) -> { 1096 Platform.runLater(() -> { 1097 DragSource dragSource = createDragSource(fxDragSource, dragAction); 1098 if (dragSource == null) { 1099 fxDragSource.dragDropEnd(null); 1100 } else { 1101 updateDropTarget(); 1102 FXCanvas.this.notifyListeners(SWT.DragDetect, null); 1103 } 1104 }); 1105 }); 1106 //Force the old drop target to be disposed before creating a new one 1107 setDropTarget(null); 1108 setDropTarget(createDropTarget(embeddedScene)); 1109 } 1110 1111 @Override 1112 public boolean requestFocus() { 1113 Display.getDefault().asyncExec(() -> { 1114 if (isDisposed()) return; 1115 FXCanvas.this.forceFocus(); 1116 }); 1117 return true; 1118 } 1119 1120 @Override 1121 public boolean traverseFocusOut(boolean bln) { 1122 // RT-18085: not implemented 1123 return true; 1124 } 1125 1126 Object lock = new Object(); 1127 boolean queued = false; 1128 public void repaint() { 1129 synchronized (lock) { 1130 if (queued) return; 1131 queued = true; 1132 Display.getDefault().asyncExec(() -> { 1133 try { 1134 if (isDisposed()) return; 1135 FXCanvas.this.redraw(); 1136 } finally { 1137 synchronized (lock) { 1138 queued = false; 1139 } 1140 } 1141 }); 1142 } 1143 } 1144 1145 @Override 1146 public void setPreferredSize(int width, int height) { 1147 FXCanvas.this.pPreferredWidth = width; 1148 FXCanvas.this.pPreferredHeight = height; 1149 //FXCanvas.this.getShell().layout(new Control []{FXCanvas.this}, SWT.DEFER); 1150 } 1151 1152 @Override 1153 public void setEnabled(boolean bln) { 1154 FXCanvas.this.setEnabled(bln); 1155 } 1156 1157 @Override 1158 public void setCursor(CursorFrame cursorFrame) { 1159 FXCanvas.this.setCursor(getPlatformCursor(cursorFrame)); 1160 } 1161 1162 private org.eclipse.swt.graphics.Cursor getPlatformCursor(final CursorFrame cursorFrame) { 1163 /* 1164 * On the Mac, setting the cursor during drag and drop clears the move 1165 * and link indicators. The fix is to set the default cursor for the 1166 * control (which is null) when the FX explicitly requests the default 1167 * cursor. This will preserve the drag and drop indicators. 1168 */ 1169 if (cursorFrame.getCursorType() == CursorType.DEFAULT) { 1170 return null; 1171 } 1172 final org.eclipse.swt.graphics.Cursor cachedPlatformCursor = 1173 cursorFrame.getPlatformCursor(org.eclipse.swt.graphics.Cursor.class); 1174 if (cachedPlatformCursor != null) { 1175 // platform cursor already cached 1176 return cachedPlatformCursor; 1177 } 1178 1179 // platform cursor not cached yet 1180 final org.eclipse.swt.graphics.Cursor platformCursor = 1181 SWTCursors.embedCursorToCursor(cursorFrame); 1182 cursorFrame.setPlatforCursor(org.eclipse.swt.graphics.Cursor.class, platformCursor); 1183 1184 return platformCursor; 1185 } 1186 1187 @Override 1188 public boolean grabFocus() { 1189 // RT-27949: not implemented 1190 return true; 1191 } 1192 1193 @Override 1194 public void ungrabFocus() { 1195 // RT-27949: not implemented 1196 } 1197 } 1198 }