# HG changeset patch
# User Alexander Nyßen <alexander@nyssen.org>
# Date 1468012123 -7200
#      Fri Jul 08 23:08:43 2016 +0200
# Node ID 9a52667aaba35825712de38cbd45b23221355304
# Parent  7a4c2a65b597fcc50fa3f016584207059aa498b8
JDK-8088147: Provide support for image cursors.

- Augmented implementation of SWTCursorsTest to handle image cursors properly.
- Ensured PlatformImpl exposes com.sun.javafx.tk to swt, as this is required to convert the cursor frame platform image to an javafx image.
- Ensured cursor frame is updated when synchronizing scene properties, so it does not get overwritten.
- Added an automated JUnit test (SWTCursorsTest), which is currently not executed on Mac (because -XstartOnFirstThread is not supported by Gradle test runner) and disabled for jigsaw tests (as SWT is no named module yet).
- Added SWT_TEST option to build.gradle, which is only evaluated when FULL_TEST is enabled.
- Enhanced gradle.properties.template to provide SWT_TEST property.
- Added a manual test class (SWTImageCursorTest) that can be used to validate the cursor change.

diff --git a/.idea/modules.xml b/.idea/modules.xml
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -35,5 +35,4 @@
       <module fileurl="file://$PROJECT_DIR$/.idea/web.iml" filepath="$PROJECT_DIR$/.idea/web.iml" />
     </modules>
   </component>
-</project>
-
+</project>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -425,6 +425,10 @@
 defineProperty("AWT_TEST", "true")
 ext.IS_AWT_TEST = Boolean.parseBoolean(AWT_TEST);
 
+// Specifies whether to run system tests that depend on SWT (only used when FULL_TEST is also enabled)
+defineProperty("SWT_TEST", "true")
+ext.IS_SWT_TEST = Boolean.parseBoolean(SWT_TEST);
+
 // Specifies whether to run unstable tests (true) - tests that don't run well with Hudson builds
 // These tests should be protected with :
 //    assumeTrue(Boolean.getBoolean("unstable.test"));
@@ -1849,6 +1853,22 @@
             }
         }
     }
+
+    test {
+        if (IS_JIGSAW_TEST) {
+            enabled = false // FIXME: JIGSAW -- support this with modules
+            logger.info("JIGSAW Testing disabled for swt")
+        } else {
+            enabled = IS_FULL_TEST && IS_SWT_TEST
+            if (IS_MAC) {
+                enabled = false
+                logger.info("SWT tests are disabled on MAC, because Gradle test runner does not handle -XstartOnFirstThread properly (https://issues.gradle.org/browse/GRADLE-3290).")
+            }
+            if (IS_LINUX) {
+                logger.info("SWT tests may cause 'Gtk-CRITICAL **: IA__gtk_main_quit: assertion 'main_loops != NULL' failed' LINUX (https://bugs.eclipse.org/bugs/show_bug.cgi?id=435066). This happens on shutdown, the tests are not affected.")
+            }
+        }
+    }
 }
 
 project(":fxml") {
diff --git a/gradle.properties.template b/gradle.properties.template
--- a/gradle.properties.template
+++ b/gradle.properties.template
@@ -115,6 +115,11 @@
 
 #AWT_TEST = false
 
+# Specifies whether to run system tests that depend on SWT.
+# This flag is ignored if FULL_TEST is false.
+
+#SWT_TEST = false
+
 # Specifies whether or not the results of the packager tests should be
 # retained.  If not they will be automatically deleted.
 
diff --git a/modules/graphics/src/main/java/com/sun/javafx/application/PlatformImpl.java b/modules/graphics/src/main/java/com/sun/javafx/application/PlatformImpl.java
--- a/modules/graphics/src/main/java/com/sun/javafx/application/PlatformImpl.java
+++ b/modules/graphics/src/main/java/com/sun/javafx/application/PlatformImpl.java
@@ -290,7 +290,8 @@
             "com.sun.glass.ui",
             "com.sun.javafx.cursor",
             "com.sun.javafx.embed",
-            "com.sun.javafx.stage"
+            "com.sun.javafx.stage",
+            "com.sun.javafx.tk"
         };
 
         if (DEBUG) {
diff --git a/modules/graphics/src/main/java/javafx/scene/Scene.java b/modules/graphics/src/main/java/javafx/scene/Scene.java
--- a/modules/graphics/src/main/java/javafx/scene/Scene.java
+++ b/modules/graphics/src/main/java/javafx/scene/Scene.java
@@ -2438,6 +2438,7 @@
 
             if (isDirty(DirtyBits.CURSOR_DIRTY)) {
                 mouseHandler.updateCursor(getCursor());
+                mouseHandler.updateCursorFrame();
             }
 
             clearDirty();
diff --git a/modules/swt/src/main/java/javafx/embed/swt/SWTCursors.java b/modules/swt/src/main/java/javafx/embed/swt/SWTCursors.java
--- a/modules/swt/src/main/java/javafx/embed/swt/SWTCursors.java
+++ b/modules/swt/src/main/java/javafx/embed/swt/SWTCursors.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,8 +25,11 @@
 
 package javafx.embed.swt;
 
+import com.sun.javafx.tk.Toolkit;
+import javafx.scene.image.Image;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.widgets.Display;
 
 import com.sun.javafx.cursor.CursorFrame;
@@ -39,31 +42,21 @@
  */
 class SWTCursors {
 
-    private static Cursor createCustomCursor(ImageCursorFrame cursorFrame) {
-        /*
-        Toolkit awtToolkit = Toolkit.getDefaultToolkit();
-
-        double imageWidth = cursorFrame.getWidth();
-        double imageHeight = cursorFrame.getHeight();
-        Dimension nativeSize = awtToolkit.getBestCursorSize((int)imageWidth, (int)imageHeight);
-
-        double scaledHotspotX = cursorFrame.getHotspotX() * nativeSize.getWidth() / imageWidth;
-        double scaledHotspotY = cursorFrame.getHotspotY() * nativeSize.getHeight() / imageHeight;
-        Point hotspot = new Point((int)scaledHotspotX, (int)scaledHotspotY);
-
-        final com.sun.javafx.tk.Toolkit fxToolkit =
-                com.sun.javafx.tk.Toolkit.getToolkit();
-        BufferedImage awtImage =
-                (BufferedImage) fxToolkit.toExternalImage(
-                                              cursorFrame.getPlatformImage(),
-                                              BufferedImage.class);
-
-        return awtToolkit.createCustomCursor(awtImage, hotspot, null);
-        */
-        return null;
+    private static Cursor createCustomCursor(Display display, ImageCursorFrame cursorFrame) {
+        // custom cursor, convert image
+        Image image = Toolkit.getImageAccessor().fromPlatformImage(cursorFrame.getPlatformImage());
+        ImageData imageData = SWTFXUtils.fromFXImage(image, null);
+        return new org.eclipse.swt.graphics.Cursor(
+                display, imageData, (int) cursorFrame.getHotspotX(), (int) cursorFrame.getHotspotY());
     }
 
     static Cursor embedCursorToCursor(CursorFrame cursorFrame) {
+        Display display = Display.getCurrent();
+
+        if (display == null) {
+            return  null;
+        }
+
         int id = SWT.CURSOR_ARROW;
         switch (cursorFrame.getCursorType()) {
             case DEFAULT:   id = SWT.CURSOR_ARROW; break;
@@ -88,12 +81,11 @@
             case H_RESIZE:  id = SWT.CURSOR_SIZEWE; break;
             case V_RESIZE:  id = SWT.CURSOR_SIZENS; break;
             case NONE:
+                // TODO: check if SWT.CURSOR_NO would be more appropriate here
                 return null;
             case IMAGE:
-                // RT-27939: custom cursors are not implemented
-                // return createCustomCursor((ImageCursorFrame) cursorFrame);
+                return createCustomCursor(display, (ImageCursorFrame) cursorFrame);
         }
-        Display display = Display.getCurrent();
-        return display != null ? display.getSystemCursor(id) : null;
+        return display.getSystemCursor(id);
     }
 }
diff --git a/modules/swt/src/test/java/test/javafx/embed/swt/SWTCursorsTest.java b/modules/swt/src/test/java/test/javafx/embed/swt/SWTCursorsTest.java
new file mode 100644
--- /dev/null
+++ b/modules/swt/src/test/java/test/javafx/embed/swt/SWTCursorsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javafx.embed.swt.FXCanvas;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.ImageCursor;
+import javafx.scene.Scene;
+import javafx.scene.control.TextArea;
+import javafx.scene.image.Image;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class SWTImageCursorTest {
+
+    static final String instructions =
+            "This tests that an image cursor applied to a scene embedded within an FXCanvas is properly transferred to SWT. " +
+                    "This test passes if the cursor changes to the provided cursor.png image when entering the rectangle, and back to the default cursor when leaving it again.";
+
+    private static TextArea createInfo(String msg) {
+        TextArea t = new TextArea(msg);
+        t.setWrapText(true);
+        t.setEditable(false);
+        t.setMaxWidth(400);
+        t.setMaxHeight(200);
+        return t;
+    }
+
+    public static void main(String[] args) {
+        final Display display = new Display();
+        final Shell shell = new Shell(display);
+        shell.setText("SWTImageCursorTest");
+        shell.setSize(400, 200);
+        shell.setLayout(new FillLayout());
+        final FXCanvas canvas = new FXCanvas(shell, SWT.NONE);
+        shell.open();
+
+        // create and hook scene
+        Group root = new Group();
+
+        TextArea info = createInfo(instructions);
+        root.getChildren().add(info);
+
+        Rectangle rect = new Rectangle(100, 100, 100, 50);
+        rect.setStroke(Color.BLACK);
+        rect.setFill(Color.WHITE);
+        root.getChildren().add(rect);
+
+        final Scene scene = new Scene(root, 200, 200);
+        rect.setOnMouseEntered(mouseEvent -> {
+            Image cursorImage = new Image("cursor.png");
+            scene.setCursor(new ImageCursor(cursorImage));
+        });
+        rect.setOnMouseExited(mouseEvent -> {
+            scene.setCursor(null);
+        });
+        canvas.setScene(scene);
+
+        while (!shell.isDisposed()) {
+            // run SWT event loop
+            if (!display.readAndDispatch()) {
+                display.sleep();
+            }
+        }
+        display.dispose();
+    }
+}
diff --git a/modules/swt/src/test/resources/test/javafx/embed/swt/cursor.png b/modules/swt/src/test/resources/test/javafx/embed/swt/cursor.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a802e46c82c34fb2b0320c33da5a93b9fbd9efd3
GIT binary patch
literal 912
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0WW
zg+Z8+Vb&Z8pn}NEkcg59UmvUF{9L`nl>DSry^7od1`x2RumUo3Q%e#RDspr3imfVa
zmB1>jfNYSkzLEl1NlCV?QiN}Sf^&XRs)DJWiJpOy9hZWFf=y9MnpKdC8&o@xXRDM^
zQc_^0uU}qXu2*iXmtT~wZ)j<0sc&GUZ)BtkRH0j3nOBlnp_^B%3^4>|j!SBBa#3bM
zNoIbY0*IfOlwVq6tE2=~0|5|=N;1<BHsuvVy#@53K1fF2P|rXgZZ^nxkf@b^QD!R8
zW{~-IhBo>bVlcPpqd5lRQ+=pc?YM08;lXCd<#R|q5*WxAJY5_^ECfq~b{E|?5QtGz
zaozZ(M_Sx-@=5+3+wAYH`^&m+(xG1YO+OeM7?d`1Pc#zkcbr>LzUOYV%tT*?`q!o1
za@TJ>6rcA|*nh_%`Itw7;uQ|Pzq__ZN4ZHi*6_yu$$wdUY}SAIeHTtAJ8Dh;o#}b$
zqT0LXaa=bm{p!OE0>hWc`uY3J+tjJ!VSW82hk5Y<H{r8=$~ip+h6gJ7?RhxPDN1#3
zeA;QtE|4zdR9x(9bd1T}OLzyjoULP<!fojt%iJr@uWWoB`d@0UhuV|;{o>PE*b_KZ
z6m}T0<z8QS`?^6W<MB^7g#Kim=bTr+Mx>)*Q}0&JJ=xzfPBF<U9%KD+>cZ_g!ZxwB
z0x~xo(zmtE&2H)z%GbI7yJ4T9gxM|z)wkBUc8Bhtp3E=Ua`0fOu2107bJgdPuYb*W
zCAIE~u2vdT-mHU<S-&ew9y?HM#XbN1rJ1<}FMOJ(J^1d+I(hYOwjYJHpW0`A+~Zc)
zxiRC?mgJm=xvh!28&`YHFmj)*|3LJhtco}1B4e)`Ri`Q*eoyiG92C5A-o{%ZUR(53
zl6l=&xON)xIfZs-#u-f&Q2lTzh-2AN)!EuF<_4W!9hw)bIlorJwlu!1t!V#-rCtd?
Z|1&(P_{CV><}VG(CZ4W-F6*2UngEmMR+#_*

diff --git a/tests/manual/swt/SWTImageCursorTest.java b/tests/manual/swt/SWTImageCursorTest.java
new file mode 100644
--- /dev/null
+++ b/tests/manual/swt/SWTImageCursorTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javafx.embed.swt.FXCanvas;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.ImageCursor;
+import javafx.scene.Scene;
+import javafx.scene.control.TextArea;
+import javafx.scene.image.Image;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class SWTImageCursorTest {
+
+    static final String instructions =
+            "This tests that an image cursor applied to a scene embedded within an FXCanvas is properly transferred to SWT. " +
+                    "This test passes if the cursor changes to the provided cursor.png image when entering the rectangle, and back to the default cursor when leaving it again.";
+
+    private static TextArea createInfo(String msg) {
+        TextArea t = new TextArea(msg);
+        t.setWrapText(true);
+        t.setEditable(false);
+        t.setMaxWidth(400);
+        t.setMaxHeight(200);
+        return t;
+    }
+
+    public static void main(String[] args) {
+        final Display display = new Display();
+        final Shell shell = new Shell(display);
+        shell.setText("SWTImageCursorTest");
+        shell.setSize(400, 200);
+        shell.setLayout(new FillLayout());
+        final FXCanvas canvas = new FXCanvas(shell, SWT.NONE);
+        shell.open();
+
+        // create and hook scene
+        Group root = new Group();
+
+        TextArea info = createInfo(instructions);
+        root.getChildren().add(info);
+
+        Rectangle rect = new Rectangle(100, 100, 100, 50);
+        rect.setStroke(Color.BLACK);
+        rect.setFill(Color.WHITE);
+        root.getChildren().add(rect);
+
+        final Scene scene = new Scene(root, 200, 200);
+        rect.setOnMouseEntered(mouseEvent -> {
+            Image cursorImage = new Image("cursor.png");
+            scene.setCursor(new ImageCursor(cursorImage));
+        });
+        rect.setOnMouseExited(mouseEvent -> {
+           scene.setCursor(null);
+        });
+        canvas.setScene(scene);
+        canvas.pack();
+
+        while (!shell.isDisposed()) {
+            // run SWT event loop
+            if (!display.readAndDispatch()) {
+                display.sleep();
+            }
+        }
+        display.dispose();
+    }
+}
diff --git a/tests/manual/swt/cursor.png b/tests/manual/swt/cursor.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a802e46c82c34fb2b0320c33da5a93b9fbd9efd3
GIT binary patch
literal 912
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0WW
zg+Z8+Vb&Z8pn}NEkcg59UmvUF{9L`nl>DSry^7od1`x2RumUo3Q%e#RDspr3imfVa
zmB1>jfNYSkzLEl1NlCV?QiN}Sf^&XRs)DJWiJpOy9hZWFf=y9MnpKdC8&o@xXRDM^
zQc_^0uU}qXu2*iXmtT~wZ)j<0sc&GUZ)BtkRH0j3nOBlnp_^B%3^4>|j!SBBa#3bM
zNoIbY0*IfOlwVq6tE2=~0|5|=N;1<BHsuvVy#@53K1fF2P|rXgZZ^nxkf@b^QD!R8
zW{~-IhBo>bVlcPpqd5lRQ+=pc?YM08;lXCd<#R|q5*WxAJY5_^ECfq~b{E|?5QtGz
zaozZ(M_Sx-@=5+3+wAYH`^&m+(xG1YO+OeM7?d`1Pc#zkcbr>LzUOYV%tT*?`q!o1
za@TJ>6rcA|*nh_%`Itw7;uQ|Pzq__ZN4ZHi*6_yu$$wdUY}SAIeHTtAJ8Dh;o#}b$
zqT0LXaa=bm{p!OE0>hWc`uY3J+tjJ!VSW82hk5Y<H{r8=$~ip+h6gJ7?RhxPDN1#3
zeA;QtE|4zdR9x(9bd1T}OLzyjoULP<!fojt%iJr@uWWoB`d@0UhuV|;{o>PE*b_KZ
z6m}T0<z8QS`?^6W<MB^7g#Kim=bTr+Mx>)*Q}0&JJ=xzfPBF<U9%KD+>cZ_g!ZxwB
z0x~xo(zmtE&2H)z%GbI7yJ4T9gxM|z)wkBUc8Bhtp3E=Ua`0fOu2107bJgdPuYb*W
zCAIE~u2vdT-mHU<S-&ew9y?HM#XbN1rJ1<}FMOJ(J^1d+I(hYOwjYJHpW0`A+~Zc)
zxiRC?mgJm=xvh!28&`YHFmj)*|3LJhtco}1B4e)`Ri`Q*eoyiG92C5A-o{%ZUR(53
zl6l=&xON)xIfZs-#u-f&Q2lTzh-2AN)!EuF<_4W!9hw)bIlorJwlu!1t!V#-rCtd?
Z|1&(P_{CV><}VG(CZ4W-F6*2UngEmMR+#_*

