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