# HG changeset patch # User Alexander Nyßen # Date 1468012123 -7200 # Fri Jul 08 23:08:43 2016 +0200 # Node ID 93968c5bb8879055295438063d5489cdc304085e # 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/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); } -} +} \ No newline at end of file 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,116 @@ +/* + * 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. + */ +package test.javafx.embed.swt; + +import javafx.embed.swt.FXCanvas; +import javafx.scene.Group; +import javafx.scene.ImageCursor; +import javafx.scene.Scene; +import javafx.scene.image.Image; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertNotNull; + +public class SWTCursorsTest { + + @Test + public void testImageCursor() throws Throwable { + // create display on first thread (required when using SWT on Mac OS X) + final Display display = new Display(); + final Shell shell = new Shell(display); + final FXCanvas canvas = new FXCanvas(shell, SWT.NONE); + shell.open(); + + // keep track of exceptions thrown in UI thread + final AtomicReference throwableRef = new AtomicReference<>(); + + final CountDownLatch latch = new CountDownLatch(2); + display.asyncExec(() -> { + try { + // create and hook scene + Scene scene = new Scene(new Group()); + canvas.setScene(scene); + + // set image cursor to scene + Image cursorImage = new Image("test/javafx/embed/swt/cursor.png"); + scene.setCursor(new ImageCursor(cursorImage)); + + } catch (Throwable throwable) { + throwableRef.set(throwable); + } finally { + latch.countDown(); + } + }); + + while (latch.getCount() > 1) { + // run SWT event loop + if (!display.readAndDispatch()) { + display.sleep(); + } + } + + rethrow(throwableRef); + Throwable thrown; + + // ensure at least one pulse has passed before asserting the cursor is set, + // as the scene property synchronization is triggered by the pulse listener + display.asyncExec(() -> { + try { + assertNotNull(canvas.getCursor()); + } catch (Throwable throwable) { + throwableRef.set(throwable); + } finally { + latch.countDown(); + } + }); + + while (latch.getCount() > 0) { + // run SWT event loop (again) + if (!display.readAndDispatch()) { + display.sleep(); + } + } + + shell.close(); + shell.dispose(); + display.dispose(); + + rethrow(throwableRef); + } + + private void rethrow(AtomicReference throwableRef) throws Throwable { + Throwable thrown = throwableRef.get(); + if (thrown != null) { + throw thrown; + } + } +} 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;1bVlcPpqd5lRQ+=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!VSW82hk5YPE*b_KZ z6m}T0)*Q}0&JJ=xzfPBFcZ_g!ZxwB z0x~xo(zmtE&2H)z%GbI7yJ4T9gxM|z)wkBUc8Bhtp3E=Ua`0fOu2107bJgdPuYb*W zCAIE~u2vdT-mHU<}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,98 @@ +/* + * 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(new EventHandler() { + @Override + public void handle(MouseEvent event) { + 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;1bVlcPpqd5lRQ+=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!VSW82hk5YPE*b_KZ z6m}T0)*Q}0&JJ=xzfPBFcZ_g!ZxwB z0x~xo(zmtE&2H)z%GbI7yJ4T9gxM|z)wkBUc8Bhtp3E=Ua`0fOu2107bJgdPuYb*W zCAIE~u2vdT-mHU<}VG(CZ4W-F6*2UngEmMR+#_*