1 /*
   2  * Copyright (c) 2012, 2017, 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.Stack;
  51 import java.util.concurrent.CountDownLatch;
  52 
  53 import javafx.application.Platform;
  54 import javafx.beans.NamedArg;
  55 import javafx.scene.Scene;
  56 import javafx.scene.input.TransferMode;
  57 import javafx.stage.Window;
  58 import javafx.util.FXPermission;
  59 
  60 import org.eclipse.swt.SWT;
  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.ControlListener;
  77 import org.eclipse.swt.events.DisposeEvent;
  78 import org.eclipse.swt.events.DisposeListener;
  79 import org.eclipse.swt.events.FocusEvent;
  80 import org.eclipse.swt.events.FocusListener;
  81 import org.eclipse.swt.events.GestureEvent;
  82 import org.eclipse.swt.events.KeyEvent;
  83 import org.eclipse.swt.events.KeyListener;
  84 import org.eclipse.swt.events.MenuDetectEvent;
  85 import org.eclipse.swt.events.MouseEvent;
  86 import org.eclipse.swt.events.MouseListener;
  87 import org.eclipse.swt.events.MouseTrackListener;
  88 import org.eclipse.swt.events.PaintEvent;
  89 import org.eclipse.swt.graphics.Image;
  90 import org.eclipse.swt.graphics.ImageData;
  91 import org.eclipse.swt.graphics.PaletteData;
  92 import org.eclipse.swt.graphics.Point;
  93 import org.eclipse.swt.graphics.RGB;
  94 import org.eclipse.swt.graphics.Rectangle;
  95 import org.eclipse.swt.widgets.Canvas;
  96 import org.eclipse.swt.widgets.Composite;
  97 import org.eclipse.swt.widgets.Control;
  98 import org.eclipse.swt.widgets.Display;
  99 import org.eclipse.swt.widgets.Event;
 100 import org.eclipse.swt.widgets.Listener;
 101 import org.eclipse.swt.widgets.Shell;
 102 
 103 /**
 104  * {@code FXCanvas} is a component to embed JavaFX content into
 105  * SWT applications. The content to be displayed is specified
 106  * with the {@link #setScene} method that accepts an instance of
 107  * JavaFX {@code Scene}. After the scene is attached, it gets
 108  * repainted automatically. All the input and focus events are
 109  * forwarded to the scene transparently to the developer.
 110  * <p>
 111  * Here is a typical pattern how {@code FXCanvas} can used:
 112  * <pre>
 113  *    public class Test {
 114  *        private static Scene createScene() {
 115  *            Group group = new Group();
 116  *            Scene scene = new Scene(group);
 117  *            Button button = new Button("JFX Button");
 118  *            group.getChildren().add(button);
 119  *            return scene;
 120  *        }
 121  *
 122  *        public static void main(String[] args) {
 123  *            Display display = new Display();
 124  *            Shell shell = new Shell(display);
 125  *            shell.setLayout(new FillLayout());
 126  *            FXCanvas canvas = new FXCanvas(shell, SWT.NONE);
 127  *            Scene scene = createScene();
 128  *            canvas.setScene(scene);
 129  *            shell.open();
 130  *            while (!shell.isDisposed()) {
 131  *                if (!display.readAndDispatch()) display.sleep();
 132  *            }
 133  *            display.dispose();
 134  *        }
 135  *    }
 136  * </pre>
 137  *
 138  *
 139  * @since JavaFX 2.0
 140  */
 141 public class FXCanvas extends Canvas {
 142 
 143     // Internal permission used by FXCanvas (SWT interop)
 144     private static final FXPermission FXCANVAS_PERMISSION =
 145             new FXPermission("accessFXCanvasInternals");
 146 
 147     private HostContainer hostContainer;
 148     private volatile EmbeddedWindow stage;
 149     private volatile Scene scene;
 150     private EmbeddedStageInterface stagePeer;
 151     private EmbeddedSceneInterface scenePeer;
 152 
 153     private int pWidth = 0;
 154     private int pHeight = 0;
 155 
 156     private volatile int pPreferredWidth = -1;
 157     private volatile int pPreferredHeight = -1;
 158 
 159     private IntBuffer pixelsBuf = null;
 160 
 161     // This filter runs when any widget is moved
 162     Listener moveFilter = event -> {
 163         // If a parent has moved, send a move event to FX
 164         Control control = FXCanvas.this;
 165         while (control != null) {
 166             if (control == event.widget) {
 167                 sendMoveEventToFX();
 168                 break;
 169             }
 170             control = control.getParent();
 171         };
 172     };
 173 
 174     private double getScaleFactor() {
 175         if (SWT.getPlatform().equals("cocoa")) {
 176             if (windowField == null || screenMethod == null || backingScaleFactorMethod == null) {
 177                 return 1.0;
 178             }
 179             try {
 180                 Object nsWindow = windowField.get(this.getShell());
 181                 Object nsScreen = screenMethod.invoke(nsWindow);
 182                 Object bsFactor = backingScaleFactorMethod.invoke(nsScreen);
 183                 return ((Double) bsFactor).doubleValue();
 184             } catch (Exception e) {
 185                 // FAIL silently should the reflection fail
 186             }
 187         } else if (SWT.getPlatform().equals("win32")) {
 188             if (swtDPIUtilMethod == null) {
 189                 return 1.0;
 190             }
 191             try {
 192                 Integer value = (Integer) swtDPIUtilMethod.invoke(null);
 193                 return value.intValue() / 100.0;
 194             } catch (Exception e) {
 195                 // FAIL silently should the reflection fail
 196             }
 197         }
 198         return 1.0;
 199     }
 200 
 201     private DropTarget dropTarget;
 202 
 203     static Transfer [] StandardTransfers = new Transfer [] {
 204         TextTransfer.getInstance(),
 205         RTFTransfer.getInstance(),
 206         HTMLTransfer.getInstance(),
 207         URLTransfer.getInstance(),
 208         ImageTransfer.getInstance(),
 209         FileTransfer.getInstance(),
 210     };
 211     static Transfer [] CustomTransfers = new Transfer [0];
 212 
 213     static Transfer [] getAllTransfers () {
 214         Transfer [] transfers = new Transfer[StandardTransfers.length + CustomTransfers.length];
 215         System.arraycopy(StandardTransfers, 0, transfers, 0, StandardTransfers.length);
 216         System.arraycopy(CustomTransfers, 0, transfers, StandardTransfers.length, CustomTransfers.length);
 217         return transfers;
 218     }
 219 
 220     static Transfer getCustomTransfer(String mime) {
 221         for (int i=0; i<CustomTransfers.length; i++) {
 222             if (((CustomTransfer)CustomTransfers[i]).getMime().equals(mime)) {
 223                 return CustomTransfers[i];
 224             }
 225         }
 226         Transfer transfer = new CustomTransfer (mime, mime);
 227         Transfer [] newCustom = new Transfer [CustomTransfers.length + 1];
 228         System.arraycopy(CustomTransfers, 0, newCustom, 0, CustomTransfers.length);
 229         newCustom[CustomTransfers.length] = transfer;
 230         CustomTransfers = newCustom;
 231         return transfer;
 232     }
 233 
 234     private static Field windowField;
 235     private static Method windowMethod;
 236     private static Method screenMethod;
 237     private static Method backingScaleFactorMethod;
 238     private static Method swtDPIUtilMethod;
 239 
 240     static {
 241         if (SWT.getPlatform().equals("cocoa")) {
 242             try {
 243                 windowField = Shell.class.getDeclaredField("window");
 244                 windowField.setAccessible(true);
 245 
 246                 Class nsViewClass = Class.forName("org.eclipse.swt.internal.cocoa.NSView");
 247                 windowMethod = nsViewClass.getDeclaredMethod("window");
 248                 windowMethod.setAccessible(true);
 249 
 250                 Class nsWindowClass = Class.forName("org.eclipse.swt.internal.cocoa.NSWindow");
 251                 screenMethod = nsWindowClass.getDeclaredMethod("screen");
 252                 screenMethod.setAccessible(true);
 253 
 254                 Class nsScreenClass = Class.forName("org.eclipse.swt.internal.cocoa.NSScreen");
 255                 backingScaleFactorMethod = nsScreenClass.getDeclaredMethod("backingScaleFactor");
 256                 backingScaleFactorMethod.setAccessible(true);
 257             } catch (Exception e) {
 258                 //Fail silently.  If we can't get the methods, then the current version of SWT has no retina support
 259             }
 260         } else if (SWT.getPlatform().equals("win32")) {
 261             try {
 262                 String autoScale = AccessController.doPrivileged((PrivilegedAction<String>)() -> System.getProperty("swt.autoScale"));
 263                 if (autoScale == null || ! "false".equalsIgnoreCase(autoScale)) {
 264                     Class dpiUtilClass = Class.forName("org.eclipse.swt.internal.DPIUtil");
 265                     swtDPIUtilMethod = dpiUtilClass.getMethod("getDeviceZoom");
 266                 }
 267             } catch (Exception e) {
 268                 //Fail silently.  If we can't get the methods, then the current version of SWT has no retina support
 269             }
 270         }
 271         initFx();
 272     }
 273 
 274     /**
 275      * @inheritDoc
 276      */
 277     public FXCanvas(@NamedArg("parent") Composite parent, @NamedArg("style") int style) {
 278         super(parent, style | SWT.NO_BACKGROUND);
 279         setApplicationName(Display.getAppName());
 280         hostContainer = new HostContainer();
 281         registerEventListeners();
 282         Display display = parent.getDisplay();
 283         display.addFilter(SWT.Move, moveFilter);
 284     }
 285 
 286     /**
 287      * Retrieves the {@code FXCanvas} embedding the given {@code Scene},
 288      * that is the {@code FXCanvas} to which the given {@code Scene} was
 289      * attached using {@link #setScene}.
 290      *
 291      * @param scene the {@code Scene} whose embedding {@code FXCanvas}
 292      *              instance is to be retrieved
 293      * @return the {@code FXCanvas} to which the given {@code Scene} is
 294      * attached, or null if the given {@code Scene} is not attached to an
 295      * {@code FXCanvas}.
 296      *
 297      * @since 9
 298      */
 299     public static FXCanvas getFXCanvas(Scene scene) {
 300         Window window = scene.getWindow();
 301         if (window != null && window instanceof EmbeddedWindow) {
 302             HostInterface hostInterface = ((EmbeddedWindow) window).getHost();
 303             if (hostInterface instanceof HostContainer) {
 304                 // Obtain FXCanvas as the enclosing instance of the FXCanvas$HostContainer
 305                 // that is host of the embedded Scene's EmbeddedWindow.
 306                 return ((HostContainer)hostInterface).fxCanvas;
 307             }
 308         }
 309         return null;
 310     }
 311 
 312     private static void initFx() {
 313         // NOTE: no internal "com.sun.*" packages can be accessed until after
 314         // the JavaFX platform is initialized. The list of needed internal
 315         // packages is kept in the PlatformImpl class.
 316         long eventProc = 0;
 317         try {
 318             Field field = Display.class.getDeclaredField("eventProc");
 319             field.setAccessible(true);
 320             if (field.getType() == int.class) {
 321                 eventProc = field.getInt(Display.getDefault());
 322             } else {
 323                 if (field.getType() == long.class) {
 324                     eventProc = field.getLong(Display.getDefault());
 325                 }
 326             }
 327         } catch (Throwable th) {
 328             //Fail silently
 329         }
 330         final String eventProcStr = String.valueOf(eventProc);
 331 
 332         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 333             System.setProperty("com.sun.javafx.application.type", "FXCanvas");
 334             System.setProperty("javafx.embed.isEventThread", "true");
 335             if (swtDPIUtilMethod == null) {
 336                 System.setProperty("glass.win.uiScale", "100%");
 337                 System.setProperty("glass.win.renderScale", "100%");
 338             } else {
 339                 Integer scale = 100;
 340                 try {
 341                     scale = (Integer) swtDPIUtilMethod.invoke(null);
 342                 } catch (Exception e) {
 343                     //Fail silently
 344                 }
 345                 System.setProperty("glass.win.uiScale", scale + "%");
 346                 System.setProperty("glass.win.renderScale", scale + "%");
 347             }
 348             System.setProperty("javafx.embed.eventProc", eventProcStr);
 349             return null;
 350         });
 351 
 352         final CountDownLatch startupLatch = new CountDownLatch(1);
 353         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 354             Platform.startup(() -> {
 355                 startupLatch.countDown();
 356             });
 357             return null;
 358         }, null, FXCANVAS_PERMISSION);
 359 
 360         try {
 361             startupLatch.await();
 362         } catch (InterruptedException ex) {
 363             throw new RuntimeException(ex);
 364         }
 365     }
 366 
 367     private void setApplicationName(String name) {
 368         Platform.runLater(()-> Application.GetApplication().setName(name));
 369     }
 370 
 371     // Work around because SWT does not send reparent events but Control#setParent
 372     // calls reskin(SWT.ALL) so this implementation detail can be used to update
 373     // the current x/y position of the embedded stage
 374     //
 375     // There are other situations where reskin() is called but they are not frequent
 376     // and the only harm is that we potentially recompute the location although it did
 377     // not change in reality
 378     @Override
 379     public void reskin(int flags) {
 380         super.reskin(flags);
 381         if (flags == SWT.ALL) {
 382             sendMoveEventToFX();
 383         }
 384     }
 385 
 386     static ArrayList<DropTarget> targets = new ArrayList<>();
 387 
 388     DropTarget getDropTarget() {
 389         return dropTarget;
 390     }
 391 
 392     void setDropTarget(DropTarget newTarget) {
 393         if (dropTarget != null) {
 394             targets.remove(dropTarget);
 395             dropTarget.dispose();
 396         }
 397         dropTarget = newTarget;
 398         if (dropTarget != null) {
 399             targets.add(dropTarget);
 400         }
 401     }
 402 
 403     static void updateDropTarget() {
 404         // Update all drop targets rather than just this target
 405         //
 406         // In order for a drop target to recognise a custom format,
 407         // the format must be registered and the transfer type added
 408         // to the list of transfers that the target accepts.  This
 409         // must happen before the drag and drop operations starts
 410         // or the drop target will not accept the format.  Therefore,
 411         // set all transfers for all targets before any drag and drop
 412         // operation starts
 413         //
 414         for (DropTarget target : targets) {
 415             target.setTransfer(getAllTransfers());
 416         }
 417     }
 418 
 419     /**
 420      * {@inheritDoc}
 421      */
 422     public Point computeSize (int wHint, int hHint, boolean changed) {
 423         checkWidget();
 424         if (wHint == -1 && hHint == -1) {
 425             if (pPreferredWidth != -1 && pPreferredHeight != -1) {
 426                 return new Point (pPreferredWidth, pPreferredHeight);
 427             }
 428         }
 429         return super.computeSize(wHint, hHint, changed);
 430     }
 431 
 432     /**
 433      * Returns the JavaFX scene attached to this {@code FXCanvas}.
 434      *
 435      * @return the {@code Scene} attached to this {@code FXCanvas}
 436      */
 437     public Scene getScene() {
 438         checkWidget();
 439         return scene;
 440     }
 441 
 442     /**
 443      * Attaches a {@code Scene} object to display in this {@code
 444      * FXCanvas}. This method must called either on the JavaFX
 445      * JavaFX application thread (which is the same as the SWT
 446      * event dispatch thread).
 447      *
 448      * @param newScene a scene to display in this {@code FXCanvas}
 449      *
 450      * @see javafx.application.Platform#isFxApplicationThread()
 451      */
 452     public void setScene(final Scene newScene) {
 453         checkWidget();
 454 
 455         if ((stage == null) && (newScene != null)) {
 456             stage = new EmbeddedWindow(hostContainer);
 457             stage.show();
 458         }
 459         scene = newScene;
 460         if (stage != null) {
 461             stage.setScene(newScene);
 462         }
 463         if ((stage != null) && (newScene == null)) {
 464             stage.hide();
 465             stage = null;
 466         }
 467     }
 468 
 469     // Note that removing the listeners is unnecessary
 470     private void registerEventListeners() {
 471         addDisposeListener(new DisposeListener() {
 472             @Override
 473             public void widgetDisposed(DisposeEvent de) {
 474                 Display display = getDisplay();
 475                 display.removeFilter(SWT.Move, moveFilter);
 476                 FXCanvas.this.widgetDisposed(de);
 477             }
 478         });
 479 
 480         addPaintListener(pe -> {
 481             FXCanvas.this.paintControl(pe);
 482         });
 483 
 484         addMouseListener(new MouseListener() {
 485             @Override
 486             public void mouseDoubleClick(MouseEvent me) {
 487                 // Clicks and double-clicks are handled in FX
 488             }
 489             @Override
 490             public void mouseDown(MouseEvent me) {
 491                 // FX only supports 3 buttons so don't send the event for other buttons
 492                 if (me.button > 3) return;
 493                 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_PRESSED);
 494             }
 495             @Override
 496             public void mouseUp(MouseEvent me) {
 497                 // FX only supports 3 buttons so don't send the event for other buttons
 498                 if (me.button > 3) return;
 499                 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_RELEASED);
 500             }
 501         });
 502 
 503         addMouseMoveListener(me -> {
 504             if ((me.stateMask & SWT.BUTTON_MASK) != 0) {
 505                 // FX only supports 3 buttons so don't send the event for other buttons
 506                 if ((me.stateMask & (SWT.BUTTON1 | SWT.BUTTON2 | SWT.BUTTON3)) != 0) {
 507                     FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_DRAGGED);
 508                 } else {
 509                     FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED);
 510                 }
 511             } else {
 512                 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED);
 513             }
 514         });
 515 
 516         // SWT emulates mouse events from PAN gesture events.
 517         // We need to suppress them while a gesture is active or inertia events are still processed.
 518         addListener(SWT.MouseVerticalWheel, e -> {
 519             if (!gestureActive && (!panGestureInertiaActive || lastGestureEvent == null || e.time != lastGestureEvent.time)) {
 520                 FXCanvas.this.sendScrollEventToFX(AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL,
 521                         0, SWTEvents.getWheelRotation(e), e.x, e.y, e.stateMask, false);
 522             }
 523         });
 524         addListener(SWT.MouseHorizontalWheel, e -> {
 525             if (!gestureActive && (!panGestureInertiaActive || lastGestureEvent == null || e.time != lastGestureEvent.time)) {
 526                 FXCanvas.this.sendScrollEventToFX(AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL,
 527                         SWTEvents.getWheelRotation(e), 0, e.x, e.y, e.stateMask, false);
 528             }
 529         });
 530 
 531         addMouseTrackListener(new MouseTrackListener() {
 532             @Override
 533             public void mouseEnter(MouseEvent me) {
 534                 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_ENTERED);
 535             }
 536             @Override
 537             public void mouseExit(MouseEvent me) {
 538                 FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_EXITED);
 539             }
 540             @Override
 541             public void mouseHover(MouseEvent me) {
 542                 // No mouse hovering in FX
 543             }
 544         });
 545 
 546         addControlListener(new ControlListener() {
 547             @Override
 548             public void controlMoved(ControlEvent ce) {
 549                 FXCanvas.this.sendMoveEventToFX();
 550             }
 551             @Override
 552             public void controlResized(ControlEvent ce) {
 553                 FXCanvas.this.sendResizeEventToFX();
 554             }
 555         });
 556 
 557         addFocusListener(new FocusListener() {
 558             @Override
 559             public void focusGained(FocusEvent fe) {
 560                 FXCanvas.this.sendFocusEventToFX(fe, true);
 561             }
 562             @Override
 563             public void focusLost(FocusEvent fe) {
 564                 FXCanvas.this.sendFocusEventToFX(fe, false);
 565             }
 566         });
 567 
 568         addKeyListener(new KeyListener() {
 569             @Override
 570             public void keyPressed(KeyEvent e) {
 571                 FXCanvas.this.sendKeyEventToFX(e, SWT.KeyDown);
 572 
 573             }
 574             @Override
 575             public void keyReleased(KeyEvent e) {
 576                 FXCanvas.this.sendKeyEventToFX(e, SWT.KeyUp);
 577             }
 578         });
 579 
 580         addGestureListener(ge -> {
 581             FXCanvas.this.sendGestureEventToFX(ge);
 582         });
 583 
 584         addMenuDetectListener(e -> {
 585             Runnable r = () -> {
 586                 if (isDisposed()) return;
 587                 FXCanvas.this.sendMenuEventToFX(e);
 588             };
 589             // In SWT, MenuDetect comes before the equivalent mouse event
 590             // On Mac, the order is MenuDetect, MouseDown, MouseUp.  FX
 591             // does not expect this order and when it gets the MouseDown,
 592             // it closes the menu.  The fix is to defer the MenuDetect
 593             // notification until after the MouseDown is sent to FX.
 594             if ("cocoa".equals(SWT.getPlatform())) {
 595                 getDisplay().asyncExec(r);
 596             } else {
 597                 r.run();
 598             }
 599         });
 600     }
 601 
 602     private void widgetDisposed(DisposeEvent de) {
 603         setDropTarget(null);
 604         if (stage != null) {
 605             stage.hide();
 606         }
 607     }
 608 
 609     double lastScaleFactor = 1.0;
 610     int lastWidth, lastHeight;
 611     IntBuffer lastPixelsBuf =  null;
 612     private void paintControl(PaintEvent pe) {
 613         if ((scenePeer == null) || (pixelsBuf == null)) {
 614             return;
 615         }
 616 
 617         double scaleFactor = getScaleFactor();
 618         if (lastScaleFactor != scaleFactor) {
 619             resizePixelBuffer(scaleFactor);
 620             lastScaleFactor = scaleFactor;
 621             scenePeer.setPixelScaleFactors((float)scaleFactor, (float)scaleFactor);
 622         }
 623 
 624         // if we can't get the pixels, draw the bits that were there before
 625         IntBuffer buffer = pixelsBuf;
 626         int width = pWidth, height = pHeight;
 627         if (scenePeer.getPixels(pixelsBuf, pWidth, pHeight)) {
 628             width = lastWidth = pWidth;
 629             height = lastHeight = pHeight;
 630             buffer = lastPixelsBuf = pixelsBuf;
 631         } else {
 632             if (lastPixelsBuf == null) return;
 633             width = lastWidth;
 634             height = lastHeight;
 635             buffer = lastPixelsBuf;
 636         }
 637         width = (int)Math.round(width * scaleFactor);
 638         height = (int)Math.round(height * scaleFactor);
 639 
 640         // Consider optimizing this
 641         ImageData imageData = null;
 642         if ("win32".equals(SWT.getPlatform())) {
 643             PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
 644             int scanline = width * 4;
 645             byte[] dstData = new byte[scanline * height];
 646             int[] srcData = buffer.array();
 647             int dp = 0, sp = 0;
 648             for (int y = 0; y < height; y++) {
 649                 for (int x = 0; x < width; x++) {
 650                     int p = srcData[sp++];
 651                     dstData[dp++] = (byte) (p & 0xFF); //dst:blue
 652                     dstData[dp++] = (byte)((p >> 8) & 0xFF); //dst:green
 653                     dstData[dp++] = (byte)((p >> 16) & 0xFF); //dst:green
 654                     dstData[dp++] = (byte)0x00; //alpha
 655                 }
 656             }
 657             /*ImageData*/ imageData = new ImageData(width, height, 32, palette, 4, dstData);
 658         } else {
 659             if (width * height > buffer.array().length) {
 660                 // We shouldn't be here...
 661                 System.err.println("FXCanvas.paintControl: scale mismatch!");
 662                 return;
 663             }
 664             PaletteData palette = new PaletteData(0x00ff0000, 0x0000ff00, 0x000000ff);
 665             /*ImageData*/  imageData = new ImageData(width, height, 32, palette);
 666             imageData.setPixels(0, 0,width * height, buffer.array(), 0);
 667         }
 668 
 669         Image image = new Image(Display.getDefault(), imageData);
 670         pe.gc.drawImage(image, 0, 0, width, height, 0, 0, pWidth, pHeight);
 671         image.dispose();
 672     }
 673 
 674     private void sendMoveEventToFX() {
 675         if ((stagePeer == null) /*|| !isShowing()*/) {
 676             return;
 677         }
 678         Rectangle rect = getClientArea();
 679         Point los = toDisplay(rect.x, rect.y);
 680         stagePeer.setLocation(los.x, los.y);
 681     }
 682 
 683     private void sendMouseEventToFX(MouseEvent me, int embedMouseType) {
 684         if (scenePeer == null) {
 685             return;
 686         }
 687 
 688         Point los = toDisplay(me.x, me.y);
 689         boolean primaryBtnDown = (me.stateMask & SWT.BUTTON1) != 0;
 690         boolean middleBtnDown = (me.stateMask & SWT.BUTTON2) != 0;
 691         boolean secondaryBtnDown = (me.stateMask & SWT.BUTTON3) != 0;
 692         boolean shift = (me.stateMask & SWT.SHIFT) != 0;
 693         boolean control = (me.stateMask & SWT.CONTROL) != 0;
 694         boolean alt = (me.stateMask & SWT.ALT) != 0;
 695         boolean meta = (me.stateMask & SWT.COMMAND) != 0;
 696         int button = me.button;
 697         switch (embedMouseType) {
 698             case AbstractEvents.MOUSEEVENT_PRESSED:
 699                 primaryBtnDown |= me.button == 1;
 700                 middleBtnDown |= me.button == 2;
 701                 secondaryBtnDown |= me.button == 3;
 702                 break;
 703             case AbstractEvents.MOUSEEVENT_RELEASED:
 704                 primaryBtnDown &= me.button != 1;
 705                 middleBtnDown &= me.button != 2;
 706                 secondaryBtnDown &= me.button != 3;
 707                 break;
 708             case AbstractEvents.MOUSEEVENT_CLICKED:
 709                 // Don't send click events to FX, as they are generated in Scene
 710                 return;
 711 
 712             case AbstractEvents.MOUSEEVENT_MOVED:
 713             case AbstractEvents.MOUSEEVENT_DRAGGED:
 714             case AbstractEvents.MOUSEEVENT_ENTERED:
 715             case AbstractEvents.MOUSEEVENT_EXITED:
 716                 // If this event was the result of mouse movement and has no
 717                 // button associated with it, then we look at the state to
 718                 // determine which button to report
 719                 if (button == 0) {
 720                     if ((me.stateMask & SWT.BUTTON1) != 0) {
 721                         button = 1;
 722                     } else if ((me.stateMask & SWT.BUTTON2) != 0) {
 723                         button = 2;
 724                     } else if ((me.stateMask & SWT.BUTTON3) != 0) {
 725                         button = 3;
 726                     }
 727                 }
 728                 break;
 729 
 730             default:
 731                 break;
 732         }
 733 
 734         scenePeer.mouseEvent(
 735                 embedMouseType,
 736                 SWTEvents.mouseButtonToEmbedMouseButton(button, me.stateMask),
 737                 primaryBtnDown, middleBtnDown, secondaryBtnDown,
 738                 me.x, me.y,
 739                 los.x, los.y,
 740                 shift, control, alt, meta,
 741                 false);  // RT-32990: popup trigger not implemented
 742     }
 743 
 744     double totalScrollX = 0;
 745     double totalScrollY = 0;
 746     private void sendScrollEventToFX(int type, double scrollX, double scrollY, int x, int y, int stateMask, boolean inertia) {
 747         if (scenePeer == null) {
 748             return;
 749         }
 750 
 751         double multiplier = 5.0;
 752         if (type == AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL  || type == AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL) {
 753             // granularity for mouse wheel scroll events is more coarse-grained than for pan gesture events
 754             multiplier = 40.0;
 755 
 756             // mouse wheel scroll events do not belong to a gesture,
 757             // so total scroll is not accumulated
 758             totalScrollX = scrollX;
 759             totalScrollY = scrollY;
 760         } else {
 761             // up to and including SWT 4.5, direction was inverted for pan gestures on the Mac
 762             // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=481331)
 763             if ("cocoa".equals(SWT.getPlatform()) && SWT.getVersion() < 4600) {
 764                 multiplier  *= -1.0;
 765             }
 766 
 767             if (type == AbstractEvents.SCROLLEVENT_STARTED) {
 768                 totalScrollX = 0;
 769                 totalScrollY = 0;
 770             } else if (inertia) {
 771                 // inertia events do not belong to the gesture,
 772                 // thus total scroll is not accumulated
 773                 totalScrollX = scrollX;
 774                 totalScrollY = scrollY;
 775             } else {
 776                 // accumulate total scroll as long as the gesture occurs
 777                 totalScrollX += scrollX;
 778                 totalScrollY += scrollY;
 779             }
 780         }
 781 
 782         Point los = toDisplay(x, y);
 783         scenePeer.scrollEvent(type,
 784                 scrollX, scrollY,
 785                 totalScrollX, totalScrollY,
 786                 multiplier, multiplier,
 787                 x, y,
 788                 los.x, los.y,
 789                 (stateMask & SWT.SHIFT) != 0,
 790                 (stateMask & SWT.CONTROL) != 0,
 791                 (stateMask & SWT.ALT) != 0,
 792                 (stateMask & SWT.COMMAND) != 0,
 793                 inertia);
 794     }
 795 
 796     private void sendKeyEventToFX(final KeyEvent e, int type) {
 797         if (scenePeer == null /*|| !isFxEnabled()*/) {
 798             return;
 799         }
 800         int stateMask = e.stateMask;
 801         if (type == SWT.KeyDown) {
 802             if (e.keyCode == SWT.SHIFT) stateMask |= SWT.SHIFT;
 803             if (e.keyCode == SWT.CONTROL) stateMask |= SWT.CONTROL;
 804             if (e.keyCode == SWT.ALT) stateMask |= SWT.ALT;
 805             if (e.keyCode == SWT.COMMAND) stateMask |= SWT.COMMAND;
 806         } else {
 807             if (e.keyCode == SWT.SHIFT) stateMask &= ~SWT.SHIFT;
 808             if (e.keyCode == SWT.CONTROL) stateMask &= ~SWT.CONTROL;
 809             if (e.keyCode == SWT.ALT) stateMask &= ~SWT.ALT;
 810             if (e.keyCode == SWT.COMMAND) stateMask &= ~SWT.COMMAND;
 811         }
 812         int keyCode = SWTEvents.keyCodeToEmbedKeyCode(e.keyCode);
 813         scenePeer.keyEvent(
 814                 SWTEvents.keyIDToEmbedKeyType(type),
 815                 keyCode, new char[0],
 816                 SWTEvents.keyModifiersToEmbedKeyModifiers(stateMask));
 817         if (e.character != '\0' && type == SWT.KeyDown) {
 818             char[] chars = new char[] { e.character };
 819             scenePeer.keyEvent(
 820                     AbstractEvents.KEYEVENT_TYPED,
 821                     e.keyCode, chars,
 822                     SWTEvents.keyModifiersToEmbedKeyModifiers(stateMask));
 823         }
 824     }
 825 
 826     // true in between begin and end events of a (compound) gesture (not including inertia events)
 827     private boolean gestureActive = false;
 828     // true while inertia events of a pan gesture might be processed
 829     private boolean panGestureInertiaActive = false;
 830     // the last gesture event that was received (may also be an inertia event)
 831     private GestureEvent lastGestureEvent;
 832     // used to keep track of which (atomic) gestures are enclosed
 833     private Stack<Integer> nestedGestures = new Stack<>();
 834     // data used to compute inertia values for pan gesture events (as SWT does not provide these)
 835     private long inertiaTime = 0;
 836     private double inertiaXScroll = 0.0;
 837     private double inertiaYScroll = 0.0;
 838     private void sendGestureEventToFX(GestureEvent gestureEvent) {
 839         if (scenePeer == null) {
 840             return;
 841         }
 842 
 843         // An SWT gesture may be compound, comprising several MAGNIFY, PAN, and ROTATE events, which are enclosed by a
 844         // generic BEGIN and END event (while SWIPE events occur without being enclosed).
 845         // In JavaFX, such a compound gesture is represented through (possibly nested) atomic gestures, which all
 846         // (again excluding swipe) have their specific START and FINISH events.
 847         // While a complex SWT gesture is active, we therefore have to generate START events for atomic gestures as
 848         // needed, finishing them all when the compound SWT gesture ends (in the reverse order they were started),
 849         // after which we still process inertia events (that only seem to occur for PAN). SWIPE events may simply be
 850         // forwarded.
 851         switch (gestureEvent.detail) {
 852             case SWT.GESTURE_BEGIN:
 853                 // a (complex) gesture has started
 854                 gestureActive = true;
 855                 // we are within an active gesture, so no inertia processing now
 856                 panGestureInertiaActive = false;
 857                 break;
 858             case SWT.GESTURE_MAGNIFY:
 859                 // emulate the start of an atomic gesture
 860                 if (gestureActive && !nestedGestures.contains(SWT.GESTURE_MAGNIFY)) {
 861                     sendZoomEventToFX(AbstractEvents.ZOOMEVENT_STARTED, gestureEvent);
 862                     nestedGestures.push(SWT.GESTURE_MAGNIFY);
 863                 }
 864                 sendZoomEventToFX(AbstractEvents.ZOOMEVENT_ZOOM, gestureEvent);
 865                 break;
 866             case SWT.GESTURE_PAN:
 867                 // emulate the start of an atomic gesture
 868                 if (gestureActive && !nestedGestures.contains(SWT.GESTURE_PAN)) {
 869                     sendScrollEventToFX(AbstractEvents.SCROLLEVENT_STARTED, gestureEvent.xDirection, gestureEvent.yDirection,
 870                             gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, false);
 871                     nestedGestures.push(SWT.GESTURE_PAN);
 872                 }
 873 
 874                 // SWT does not flag inertia events and does not allow to distinguish emulated PAN gesture events
 875                 // (resulting from mouse wheel interaction) from native ones (resulting from touch device interaction);
 876                 // as it will always send both, mouse wheel as well as PAN gesture events when using the touch device or
 877                 // the mouse wheel, we can identify native PAN gesture inertia events only based on their temporal relationship
 878                 // to the preceding gesture event.
 879                 if(panGestureInertiaActive && gestureEvent.time > lastGestureEvent.time + 250) {
 880                     panGestureInertiaActive = false;
 881                 }
 882 
 883                 if(gestureActive || panGestureInertiaActive) {
 884                     double xDirection = gestureEvent.xDirection;
 885                     double yDirection = gestureEvent.yDirection;
 886 
 887                     if (panGestureInertiaActive) {
 888                         // calculate inertia values for scrollX and scrollY, as SWT (at least on MacOSX) provides zero values
 889                         if (xDirection == 0 && yDirection == 0) {
 890                             double delta = Math.max(0.0, Math.min(1.0, (gestureEvent.time - inertiaTime) / 1500.0));
 891                             xDirection = (1.0 - delta) * inertiaXScroll;
 892                             yDirection = (1.0 - delta) * inertiaYScroll;
 893                         }
 894                     }
 895 
 896                     sendScrollEventToFX(AbstractEvents.SCROLLEVENT_SCROLL, xDirection, yDirection,
 897                             gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, panGestureInertiaActive);
 898                 }
 899                 break;
 900             case SWT.GESTURE_ROTATE:
 901                 // emulate the start of an atomic gesture
 902                 if(gestureActive && !nestedGestures.contains(SWT.GESTURE_ROTATE)) {
 903                     sendRotateEventToFX(AbstractEvents.ROTATEEVENT_STARTED, gestureEvent);
 904                     nestedGestures.push(SWT.GESTURE_ROTATE);
 905                 }
 906                 sendRotateEventToFX(AbstractEvents.ROTATEEVENT_ROTATE, gestureEvent);
 907                 break;
 908             case SWT.GESTURE_SWIPE:
 909                 sendSwipeEventToFX(gestureEvent);
 910                 break;
 911             case SWT.GESTURE_END:
 912                 // finish atomic gesture(s) in reverse order of their start; SWIPE may be ignored,
 913                 // as JavaFX (like SWT) does not recognize it as a gesture
 914                 while (!nestedGestures.isEmpty()) {
 915                     switch (nestedGestures.pop()) {
 916                         case SWT.GESTURE_MAGNIFY:
 917                             sendZoomEventToFX(AbstractEvents.ZOOMEVENT_FINISHED, gestureEvent);
 918                             break;
 919                         case SWT.GESTURE_PAN:
 920                             sendScrollEventToFX(AbstractEvents.SCROLLEVENT_FINISHED, gestureEvent.xDirection, gestureEvent.yDirection,
 921                                     gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, false);
 922                             // use the scroll values of the preceding scroll event to compute values for inertia events
 923                             inertiaXScroll = lastGestureEvent.xDirection;
 924                             inertiaYScroll = lastGestureEvent.yDirection;
 925                             inertiaTime = gestureEvent.time;
 926                             // from now on, inertia events may occur
 927                             panGestureInertiaActive = true;
 928                             break;
 929                         case SWT.GESTURE_ROTATE:
 930                             sendRotateEventToFX(AbstractEvents.ROTATEEVENT_FINISHED, gestureEvent);
 931                             break;
 932                     }
 933                 }
 934                 // compound SWT gesture has ended
 935                 gestureActive = false;
 936                 break;
 937             default:
 938                 // ignore
 939         }
 940         // keep track of currently received gesture event; this is needed to identify inertia events
 941         lastGestureEvent = gestureEvent;
 942     }
 943 
 944     // used to compute zoom deltas, which are not provided by SWT
 945     private double lastTotalZoom = 0.0;
 946     private void sendZoomEventToFX(int type, GestureEvent gestureEvent) {
 947         Point los = toDisplay(gestureEvent.x, gestureEvent.y);
 948 
 949         double totalZoom = gestureEvent.magnification;
 950         if (type == AbstractEvents.ZOOMEVENT_STARTED) {
 951             // ensure first event does not provide any zoom yet
 952             totalZoom = lastTotalZoom = 1.0;
 953         } else if (type == AbstractEvents.ZOOMEVENT_FINISHED) {
 954             // SWT uses 0.0 for final event, while JavaFX still provides a (total) zoom value
 955             totalZoom = lastTotalZoom;
 956         }
 957         double zoom = type == AbstractEvents.ZOOMEVENT_FINISHED ? 1.0 : totalZoom / lastTotalZoom;
 958         lastTotalZoom = totalZoom;
 959 
 960         scenePeer.zoomEvent(type, zoom, totalZoom,
 961                 gestureEvent.x, gestureEvent.y, los.x, los.y,
 962                 (gestureEvent.stateMask & SWT.SHIFT) != 0,
 963                 (gestureEvent.stateMask & SWT.CONTROL) != 0,
 964                 (gestureEvent.stateMask & SWT.ALT) != 0,
 965                 (gestureEvent.stateMask & SWT.COMMAND) != 0,
 966                 !gestureActive);
 967     }
 968 
 969     private double lastTotalAngle = 0.0;
 970     private void sendRotateEventToFX(int type, GestureEvent gestureEvent) {
 971         Point los = toDisplay(gestureEvent.x, gestureEvent.y);
 972 
 973         // SWT uses negative angle values to indicate clockwise rotation, while JavaFX uses positive ones.
 974         // We thus have to invert the values here
 975         double totalAngle = -gestureEvent.rotation;
 976         if (type == AbstractEvents.ROTATEEVENT_STARTED) {
 977             totalAngle = lastTotalAngle = 0.0;
 978         } else if (type == AbstractEvents.ROTATEEVENT_FINISHED) {
 979             // SWT uses 0.0 for final event, while JavaFX still provides a (total) rotation value
 980             totalAngle = lastTotalAngle;
 981         }
 982         double angle = type == AbstractEvents.ROTATEEVENT_FINISHED ? 0.0 : totalAngle - lastTotalAngle;
 983         lastTotalAngle = totalAngle;
 984 
 985         scenePeer.rotateEvent(type, angle, totalAngle,
 986                 gestureEvent.x, gestureEvent.y, los.x, los.y,
 987                 (gestureEvent.stateMask & SWT.SHIFT) != 0,
 988                 (gestureEvent.stateMask & SWT.CONTROL) != 0,
 989                 (gestureEvent.stateMask & SWT.ALT) != 0,
 990                 (gestureEvent.stateMask & SWT.COMMAND) != 0,
 991                 !gestureActive);
 992     }
 993 
 994     private void sendSwipeEventToFX(GestureEvent gestureEvent) {
 995         Point los = toDisplay(gestureEvent.x, gestureEvent.y);
 996         int type = -1;
 997         if(gestureEvent.yDirection > 0) {
 998             type = AbstractEvents.SWIPEEVENT_DOWN;
 999         } else if(gestureEvent.yDirection < 0) {
1000             type = AbstractEvents.SWIPEEVENT_UP;
1001         } else if(gestureEvent.xDirection > 0) {
1002             type = AbstractEvents.SWIPEEVENT_RIGHT;
1003         } else if(gestureEvent.xDirection < 0) {
1004             type = AbstractEvents.SWIPEEVENT_LEFT;
1005         }
1006         scenePeer.swipeEvent(type, gestureEvent.x, gestureEvent.y, los.x, los.y,
1007                 (gestureEvent.stateMask & SWT.SHIFT) != 0,
1008                 (gestureEvent.stateMask & SWT.CONTROL) != 0,
1009                 (gestureEvent.stateMask & SWT.ALT) != 0,
1010                 (gestureEvent.stateMask & SWT.COMMAND) != 0);
1011     }
1012 
1013     private void sendMenuEventToFX(MenuDetectEvent me) {
1014         if (scenePeer == null /*|| !isFxEnabled()*/) {
1015             return;
1016         }
1017         Point pt = toControl(me.x, me.y);
1018         scenePeer.menuEvent(pt.x, pt.y, me.x, me.y, false);
1019     }
1020 
1021     private void sendResizeEventToFX() {
1022 
1023         // force the panel to draw right away (avoid black rectangle)
1024         redraw();
1025         update();
1026 
1027         pWidth = getClientArea().width;
1028         pHeight = getClientArea().height;
1029 
1030         resizePixelBuffer(lastScaleFactor);
1031 
1032         if (scenePeer == null) {
1033             return;
1034         }
1035 
1036         stagePeer.setSize(pWidth, pHeight);
1037         scenePeer.setSize(pWidth, pHeight);
1038     }
1039 
1040     private void resizePixelBuffer(double newScaleFactor) {
1041         lastPixelsBuf = null;
1042         if ((pWidth <= 0) || (pHeight <= 0)) {
1043             pixelsBuf = null;
1044         } else {
1045             pixelsBuf = IntBuffer.allocate((int)Math.round(pWidth * newScaleFactor) *
1046                                            (int)Math.round(pHeight * newScaleFactor));
1047             // The bg color may show through on resize. See RT-34380.
1048             RGB rgb = getBackground().getRGB();
1049             Arrays.fill(pixelsBuf.array(), rgb.red << 16 | rgb.green << 8 | rgb.blue);
1050         }
1051     }
1052 
1053     private void sendFocusEventToFX(FocusEvent fe, boolean focused) {
1054         if ((stage == null) || (stagePeer == null)) {
1055             return;
1056         }
1057         int focusCause = (focused ?
1058                           AbstractEvents.FOCUSEVENT_ACTIVATED :
1059                           AbstractEvents.FOCUSEVENT_DEACTIVATED);
1060         stagePeer.setFocused(focused, focusCause);
1061     }
1062 
1063     private class HostContainer implements HostInterface {
1064 
1065         final FXCanvas fxCanvas = FXCanvas.this;
1066 
1067         @Override
1068         public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) {
1069             stagePeer = embeddedStage;
1070             if (stagePeer == null) {
1071                 return;
1072             }
1073             if (pWidth > 0 && pHeight > 0) {
1074                 stagePeer.setSize(pWidth, pHeight);
1075             }
1076             if (FXCanvas.this.isFocusControl()) {
1077                 stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
1078             }
1079             sendMoveEventToFX();
1080             sendResizeEventToFX();
1081         }
1082 
1083         TransferMode getTransferMode(int bits) {
1084             switch (bits) {
1085                 case DND.DROP_COPY:
1086                     return TransferMode.COPY;
1087                 case DND.DROP_MOVE:
1088                 case DND.DROP_TARGET_MOVE:
1089                     return TransferMode.MOVE;
1090                 case DND.DROP_LINK:
1091                     return TransferMode.LINK;
1092                 default:
1093                    return null;
1094             }
1095         }
1096 
1097         Set<TransferMode> getTransferModes(int bits) {
1098             Set<TransferMode> set = new HashSet<TransferMode>();
1099             if ((bits & DND.DROP_COPY) != 0) set.add(TransferMode.COPY);
1100             if ((bits & DND.DROP_MOVE) != 0) set.add(TransferMode.MOVE);
1101             if ((bits & DND.DROP_TARGET_MOVE) != 0) set.add(TransferMode.MOVE);
1102             if ((bits & DND.DROP_LINK) != 0) set.add(TransferMode.LINK);
1103             return set;
1104         }
1105 
1106         ImageData createImageData(Pixels pixels) {
1107             if (pixels == null) return null;
1108             int width = pixels.getWidth();
1109             int height = pixels.getHeight();
1110             int bpr = width * 4;
1111             int dataSize = bpr * height;
1112             byte[] buffer = new byte[dataSize];
1113             byte[] alphaData = new byte[width * height];
1114             if (pixels.getBytesPerComponent() == 1) {
1115                 // ByteBgraPre
1116                 ByteBuffer pixbuf = (ByteBuffer) pixels.getPixels();
1117                 for (int y = 0, offset = 0, alphaOffset = 0; y < height; y++) {
1118                     for (int x = 0; x < width; x++, offset += 4) {
1119                         byte b = pixbuf.get();
1120                         byte g = pixbuf.get();
1121                         byte r = pixbuf.get();
1122                         byte a = pixbuf.get();
1123                         // non premultiplied ?
1124                         alphaData[alphaOffset++] = a;
1125                         buffer[offset] = b;
1126                         buffer[offset + 1] = g;
1127                         buffer[offset + 2] = r;
1128                         buffer[offset + 3] = 0;// alpha
1129                     }
1130                 }
1131             } else if (pixels.getBytesPerComponent() == 4) {
1132                 // IntArgbPre
1133                 IntBuffer pixbuf = (IntBuffer) pixels.getPixels();
1134                 for (int y = 0, offset = 0, alphaOffset = 0; y < height; y++) {
1135                     for (int x = 0; x < width; x++, offset += 4) {
1136                         int pixel = pixbuf.get();
1137                         byte b = (byte) (pixel & 0xFF);
1138                         byte g = (byte) ((pixel >> 8) & 0xFF);
1139                         byte r = (byte) ((pixel >> 16) & 0xFF);
1140                         byte a = (byte) ((pixel >> 24) & 0xFF);
1141                         // non premultiplied ?
1142                         alphaData[alphaOffset++] = a;
1143                         buffer[offset] = b;
1144                         buffer[offset + 1] = g;
1145                         buffer[offset + 2] = r;
1146                         buffer[offset + 3] = 0;// alpha
1147                     }
1148                 }
1149             } else {
1150                 return null;
1151             }
1152             PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
1153             ImageData imageData = new ImageData(width, height, 32, palette, 4, buffer);
1154             imageData.alphaData = alphaData;
1155             return imageData;
1156         }
1157 
1158         // Consider using dragAction
1159         private DragSource createDragSource(final EmbeddedSceneDSInterface fxDragSource, TransferMode dragAction) {
1160             Transfer [] transfers = getTransferTypes(fxDragSource.getMimeTypes());
1161             if (transfers.length == 0) return null;
1162             int dragOperation = getDragActions(fxDragSource.getSupportedActions());
1163             final DragSource dragSource = new DragSource(FXCanvas.this, dragOperation);
1164             dragSource.setTransfer(transfers);
1165             dragSource.addDragListener(new DragSourceListener() {
1166                 public void dragFinished(org.eclipse.swt.dnd.DragSourceEvent event) {
1167                     dragSource.dispose();
1168                     fxDragSource.dragDropEnd(getTransferMode(event.detail));
1169                 }
1170                 public void dragSetData(org.eclipse.swt.dnd.DragSourceEvent event) {
1171                     Transfer [] transfers = dragSource.getTransfer();
1172                     for (int i=0; i<transfers.length; i++) {
1173                         if (transfers[i].isSupportedType(event.dataType)) {
1174                             String mime = getMime(transfers[i]);
1175                             if (mime != null) {
1176                                 event.doit = true;
1177                                 event.data = fxDragSource.getData(mime);
1178                                 if (event.data instanceof Pixels) {
1179                                     event.data = createImageData((Pixels)event.data);
1180                                 }
1181                                 return;
1182                             }
1183                         }
1184                         event.doit = false;
1185                     }
1186                 }
1187                 public void dragStart(org.eclipse.swt.dnd.DragSourceEvent event) {
1188                 }
1189             });
1190             return dragSource;
1191         }
1192 
1193         int getDragAction(TransferMode tm) {
1194             if (tm == null) return DND.DROP_NONE;
1195             switch (tm) {
1196                 case COPY: return DND.DROP_COPY;
1197                 case MOVE: return DND.DROP_MOVE;
1198                 case LINK: return DND.DROP_LINK;
1199                 default:
1200                     throw new IllegalArgumentException("Invalid transfer mode");
1201             }
1202         }
1203 
1204         int getDragActions(Set<TransferMode> set) {
1205             int result = 0;
1206             for (TransferMode mode : set) {
1207                 result |= getDragAction(mode);
1208             }
1209             return result;
1210         }
1211 
1212         Transfer getTransferType(String mime) {
1213             if (mime.equals("text/plain")) return TextTransfer.getInstance();
1214             if (mime.equals("text/rtf")) return RTFTransfer.getInstance();
1215             if (mime.equals("text/html")) return HTMLTransfer.getInstance();
1216             if (mime.equals("text/uri-list")) return URLTransfer.getInstance();
1217             if (mime.equals("application/x-java-rawimage")) return ImageTransfer.getInstance();
1218             if (mime.equals("application/x-java-file-list") || mime.equals("java.file-list")) {
1219                 return FileTransfer.getInstance();
1220             }
1221             return getCustomTransfer(mime);
1222         }
1223 
1224         Transfer [] getTransferTypes(String [] mimeTypes) {
1225             int count= 0;
1226             Transfer [] transfers = new Transfer [mimeTypes.length];
1227             for (int i=0; i<mimeTypes.length; i++) {
1228                 Transfer transfer = getTransferType(mimeTypes[i]);
1229                 if (transfer != null) transfers [count++] = transfer;
1230             }
1231             if (count != mimeTypes.length) {
1232                 Transfer [] newTransfers = new Transfer[count];
1233                 System.arraycopy(transfers, 0, newTransfers, 0, count);
1234                 transfers = newTransfers;
1235             }
1236             return transfers;
1237         }
1238 
1239         String getMime(Transfer transfer) {
1240             if (transfer.equals(TextTransfer.getInstance())) return "text/plain";
1241             if (transfer.equals(RTFTransfer.getInstance())) return "text/rtf"; ;
1242             if (transfer.equals( HTMLTransfer.getInstance())) return "text/html";
1243             if (transfer.equals(URLTransfer.getInstance())) return "text/uri-list";
1244             if (transfer.equals( ImageTransfer.getInstance())) return "application/x-java-rawimage";
1245             if (transfer.equals(FileTransfer.getInstance())) return "application/x-java-file-list";
1246             if (transfer instanceof CustomTransfer) return ((CustomTransfer)transfer).getMime();
1247             return null;
1248         }
1249 
1250         String [] getMimes(Transfer [] transfers, TransferData data) {
1251             int count= 0;
1252             String [] result = new String [transfers.length];
1253             for (int i=0; i<transfers.length; i++) {
1254                 if (transfers[i].isSupportedType(data)) {
1255                     result [count++] = getMime (transfers [i]);
1256                 }
1257             }
1258             if (count != result.length) {
1259                 String [] newResult = new String[count];
1260                 System.arraycopy(result, 0, newResult, 0, count);
1261                 result = newResult;
1262             }
1263             return result;
1264         }
1265 
1266         DropTarget createDropTarget(EmbeddedSceneInterface embeddedScene) {
1267             final DropTarget dropTarget = new DropTarget(FXCanvas.this, DND.DROP_COPY | DND.DROP_LINK | DND.DROP_MOVE);
1268             final EmbeddedSceneDTInterface fxDropTarget = embeddedScene.createDropTarget();
1269             dropTarget.setTransfer(getAllTransfers());
1270             dropTarget.addDropListener(new DropTargetListener() {
1271                 Object data;
1272                 // In SWT, the list of available types that the source can provide
1273                 // is part of the event.  FX queries this directly from the operating
1274                 // system and bypasses SWT.  This variable is commented out to remind
1275                 // us of this potential inconsistency.
1276                 //
1277                 //TransferData [] transferData;
1278                 TransferData currentTransferData;
1279                 boolean ignoreLeave;
1280                 int detail = DND.DROP_NONE, operations = DND.DROP_NONE;
1281                 EmbeddedSceneDSInterface fxDragSource = new EmbeddedSceneDSInterface() {
1282                     public Set<TransferMode> getSupportedActions() {
1283                         return getTransferModes(operations);
1284                     }
1285                     public Object getData(String mimeType) {
1286                         // NOTE: get the data for the requested mime type, not the default data
1287                         return data;
1288                     }
1289                     public String[] getMimeTypes() {
1290                         if (currentTransferData == null) return new String [0];
1291                         return getMimes(getAllTransfers(), currentTransferData);
1292                     }
1293                     public boolean isMimeTypeAvailable(String mimeType) {
1294                         String [] mimes = getMimeTypes();
1295                         for (int i=0; i<mimes.length; i++) {
1296                             if (mimes[i].equals(mimeType)) return true;
1297                         }
1298                         return false;
1299                     }
1300                     public void dragDropEnd(TransferMode performedAction) {
1301                         data = null;
1302                         //transferData = null;
1303                         currentTransferData = null;
1304                     }
1305                 };
1306                 public void dragEnter(DropTargetEvent event) {
1307                     ignoreLeave = false;
1308                     dropTarget.setTransfer(getAllTransfers());
1309                     detail = event.detail;
1310                     operations = event.operations;
1311                     dragOver (event, true, detail);
1312                 }
1313                 public void dragLeave(DropTargetEvent event) {
1314                     detail = operations = DND.DROP_NONE;
1315                     data = null;
1316                     //transferData = null;
1317                     currentTransferData = null;
1318                     getDisplay().asyncExec(() -> {
1319                         if (ignoreLeave) return;
1320                         fxDropTarget.handleDragLeave();
1321                     });
1322                 }
1323                 public void dragOperationChanged(DropTargetEvent event) {
1324                     detail = event.detail;
1325                     operations = event.operations;
1326                     dragOver(event, false, detail);
1327                 }
1328                 public void dragOver(DropTargetEvent event) {
1329                     operations = event.operations;
1330                     dragOver (event, false, detail);
1331                 }
1332                 public void dragOver(DropTargetEvent event, boolean enter, int detail) {
1333                     //transferData = event.dataTypes;
1334                     currentTransferData = event.currentDataType;
1335                     Point pt = toControl(event.x, event.y);
1336                     if (detail == DND.DROP_NONE) detail = DND.DROP_COPY;
1337                     TransferMode acceptedMode, recommendedMode = getTransferMode(detail);
1338                     if (enter) {
1339                         acceptedMode = fxDropTarget.handleDragEnter(pt.x, pt.y, event.x, event.y, recommendedMode, fxDragSource);
1340                     } else {
1341                         acceptedMode = fxDropTarget.handleDragOver(pt.x, pt.y, event.x, event.y, recommendedMode);
1342                     }
1343                     event.detail = getDragAction(acceptedMode);
1344                 }
1345                 public void drop(DropTargetEvent event) {
1346                     detail = event.detail;
1347                     operations = event.operations;
1348                     data = event.data;
1349                     //transferData = event.dataTypes;
1350                     currentTransferData = event.currentDataType;
1351                     Point pt = toControl(event.x, event.y);
1352                     TransferMode recommendedDropAction = getTransferMode(event.detail);
1353                     TransferMode acceptedMode = fxDropTarget.handleDragDrop(pt.x, pt.y, event.x, event.y, recommendedDropAction);
1354                     event.detail = getDragAction(acceptedMode);
1355                     data = null;
1356                     //transferData = null;
1357                     currentTransferData = null;
1358                 }
1359                 public void dropAccept(DropTargetEvent event) {
1360                     ignoreLeave = true;
1361                 }
1362             });
1363             return dropTarget;
1364         }
1365 
1366         @Override
1367         public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) {
1368             scenePeer = embeddedScene;
1369             if (scenePeer == null) {
1370                 return;
1371             }
1372             if (pWidth > 0 && pHeight > 0) {
1373                 scenePeer.setSize(pWidth, pHeight);
1374             }
1375             double scaleFactor = getScaleFactor();
1376             resizePixelBuffer(scaleFactor);
1377             lastScaleFactor = scaleFactor;
1378             scenePeer.setPixelScaleFactors((float)scaleFactor, (float)scaleFactor);
1379             scenePeer.setDragStartListener((fxDragSource, dragAction) -> {
1380                 Platform.runLater(() -> {
1381                     DragSource dragSource = createDragSource(fxDragSource, dragAction);
1382                     if (dragSource == null) {
1383                         fxDragSource.dragDropEnd(null);
1384                     } else {
1385                         updateDropTarget();
1386                         FXCanvas.this.notifyListeners(SWT.DragDetect, null);
1387                     }
1388                 });
1389             });
1390             //Force the old drop target to be disposed before creating a new one
1391             setDropTarget(null);
1392             setDropTarget(createDropTarget(embeddedScene));
1393         }
1394 
1395         @Override
1396         public boolean requestFocus() {
1397             Display.getDefault().asyncExec(() -> {
1398                 if (isDisposed()) return;
1399                 FXCanvas.this.forceFocus();
1400             });
1401             return true;
1402         }
1403 
1404         @Override
1405         public boolean traverseFocusOut(boolean bln) {
1406             // RT-18085: not implemented
1407             return true;
1408         }
1409 
1410         Object lock = new Object();
1411         boolean queued = false;
1412         public void repaint() {
1413             synchronized (lock) {
1414                 if (queued) return;
1415                 queued = true;
1416                 Display.getDefault().asyncExec(() -> {
1417                     try {
1418                         if (isDisposed()) return;
1419                         FXCanvas.this.redraw();
1420                     } finally {
1421                         synchronized (lock) {
1422                             queued = false;
1423                         }
1424                     }
1425                 });
1426             }
1427         }
1428 
1429         @Override
1430         public void setPreferredSize(int width, int height) {
1431             FXCanvas.this.pPreferredWidth = width;
1432             FXCanvas.this.pPreferredHeight = height;
1433             //FXCanvas.this.getShell().layout(new Control []{FXCanvas.this}, SWT.DEFER);
1434         }
1435 
1436         @Override
1437         public void setEnabled(boolean bln) {
1438             FXCanvas.this.setEnabled(bln);
1439         }
1440 
1441         @Override
1442         public void setCursor(CursorFrame cursorFrame) {
1443             FXCanvas.this.setCursor(getPlatformCursor(cursorFrame));
1444         }
1445 
1446         private org.eclipse.swt.graphics.Cursor getPlatformCursor(final CursorFrame cursorFrame) {
1447             /*
1448              * On the Mac, setting the cursor during drag and drop clears the move
1449              * and link indicators.  The fix is to set the default cursor for the
1450              * control (which is null) when the FX explicitly requests the default
1451              * cursor.  This will preserve the drag and drop indicators.
1452              */
1453             if (cursorFrame.getCursorType() == CursorType.DEFAULT) {
1454                 return null;
1455             }
1456             final org.eclipse.swt.graphics.Cursor cachedPlatformCursor =
1457                     cursorFrame.getPlatformCursor(org.eclipse.swt.graphics.Cursor.class);
1458             if (cachedPlatformCursor != null) {
1459                 // platform cursor already cached
1460                 return cachedPlatformCursor;
1461             }
1462 
1463             // platform cursor not cached yet
1464             final org.eclipse.swt.graphics.Cursor platformCursor =
1465                     SWTCursors.embedCursorToCursor(cursorFrame);
1466             cursorFrame.setPlatforCursor(org.eclipse.swt.graphics.Cursor.class, platformCursor);
1467 
1468             return platformCursor;
1469         }
1470 
1471         @Override
1472         public boolean grabFocus() {
1473             // RT-27949: not implemented
1474             return true;
1475         }
1476 
1477         @Override
1478         public void ungrabFocus() {
1479             // RT-27949: not implemented
1480         }
1481     }
1482 }