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