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 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 }