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