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 }