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