diff --git a/src/macosx/classes/sun/lwawt/macosx/CCustomCursor.java b/src/macosx/classes/sun/lwawt/macosx/CCustomCursor.java index 70a0874a49a6d905020903f8e7513be18f9d69d1..8e0ded6b3ebc87c1464be16ca8778bfc3a4e3bf8 100644 --- a/src/macosx/classes/sun/lwawt/macosx/CCustomCursor.java +++ b/src/macosx/classes/sun/lwawt/macosx/CCustomCursor.java @@ -38,6 +38,8 @@ public class CCustomCursor extends Cursor { Image fImage; Point fHotspot; + int fWidth; + int fHeight; public CCustomCursor(final Image cursor, final Point hotSpot, final String name) throws IndexOutOfBoundsException, HeadlessException { super(name); @@ -50,6 +52,7 @@ public class CCustomCursor extends Cursor { // Make sure image is fully loaded. final Component c = new Canvas(); // for its imageUpdate method final MediaTracker tracker = new MediaTracker(c); + // MediaTracker loads resolution variants from MultiResolution Toolkit image tracker.addImage(fImage, 0); try { tracker.waitForAll(); @@ -67,15 +70,15 @@ public class CCustomCursor extends Cursor { width = height = 1; fImage = createTransparentImage(width, height); } else { - // Scale image to nearest supported size + // Get the nearest supported cursor size final Dimension nativeSize = toolkit.getBestCursorSize(width, height); - if (nativeSize.width != width || nativeSize.height != height) { - fImage = fImage.getScaledInstance(nativeSize.width, nativeSize.height, Image.SCALE_DEFAULT); - width = nativeSize.width; - height = nativeSize.height; - } + width = nativeSize.width; + height = nativeSize.height; } + fWidth = width; + fHeight = height; + // NOTE: this was removed for 3169146, but in 1.5 the JCK tests for an exception and fails if one isn't thrown. // See what JBuilder does. // Verify that the hotspot is within cursor bounds. @@ -138,6 +141,7 @@ public class CCustomCursor extends Cursor { // failed to do its job. Return null to keep the cursor unchanged. return 0L; } else { + fCImage.resizeRepresentations(fWidth, fHeight); return fCImage.ptr; } } catch (IllegalArgumentException iae) { diff --git a/src/macosx/classes/sun/lwawt/macosx/CImage.java b/src/macosx/classes/sun/lwawt/macosx/CImage.java index a861133bbccb50477d4aac64b260289b77097327..5ea172b3e23e1d89bae1d799387cb4bf8a98af45 100644 --- a/src/macosx/classes/sun/lwawt/macosx/CImage.java +++ b/src/macosx/classes/sun/lwawt/macosx/CImage.java @@ -31,6 +31,7 @@ import java.awt.image.*; import java.util.Arrays; import java.util.List; +import sun.awt.image.MultiResolutionImage; import sun.awt.image.SunWritableRaster; @@ -44,6 +45,7 @@ public class CImage extends CFRetainedResource { private static native void nativeCopyNSImageIntoArray(long image, int[] buffer, int w, int h); private static native Dimension2D nativeGetNSImageSize(long image); private static native void nativeSetNSImageSize(long image, double w, double h); + private static native void nativeResizeNSImageRepresentations(long image, double w, double h); static Creator creator = new Creator(); static Creator getCreator() { @@ -145,6 +147,12 @@ public class CImage extends CFRetainedResource { // This is used to create a CImage from a Image public CImage createFromImage(final Image image) { + if (image instanceof MultiResolutionImage) { + List resolutionVariants + = ((MultiResolutionImage) image).getResolutionVariants(); + return createFromImages(resolutionVariants); + } + int[] buffer = imageToArray(image, true); if (buffer == null) { return null; @@ -223,4 +231,8 @@ public class CImage extends CFRetainedResource { if (ptr != 0) nativeSetNSImageSize(ptr, w, h); return this; } + + void resizeRepresentations(double w, double h) { + if (ptr != 0) nativeResizeNSImageRepresentations(ptr, w, h); + } } diff --git a/src/macosx/native/sun/awt/CImage.m b/src/macosx/native/sun/awt/CImage.m index f92b272bad3d7b0f8f58550966568e364f2ae824..de7319896c4425864863efe1c02df5d6f1389538 100644 --- a/src/macosx/native/sun/awt/CImage.m +++ b/src/macosx/native/sun/awt/CImage.m @@ -320,3 +320,26 @@ JNF_COCOA_ENTER(env); JNF_COCOA_EXIT(env); } + +/* + * Class: sun_lwawt_macosx_CImage + * Method: nativeResizeNSImageRepresentations + * Signature: (JDD)V + */ +JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CImage_nativeResizeNSImageRepresentations +(JNIEnv *env, jclass clazz, jlong image, jdouble w, jdouble h) +{ + if (!image) return; + NSImage *i = (NSImage *)jlong_to_ptr(image); + +JNF_COCOA_ENTER(env); + + NSImageRep *imageRep = nil; + NSArray *imageRepresentations = [i representations]; + NSEnumerator *imageEnumerator = [imageRepresentations objectEnumerator]; + while ((imageRep = [imageEnumerator nextObject]) != nil) { + [imageRep setSize:NSMakeSize(w, h)]; + } + +JNF_COCOA_EXIT(env); +} diff --git a/test/java/awt/Cursor/MultiResolutionCursorTest/MultiResolutionCursorTest.html b/test/java/awt/Cursor/MultiResolutionCursorTest/MultiResolutionCursorTest.html new file mode 100644 index 0000000000000000000000000000000000000000..85e187dfe02890a8d0bbce6e9d20caf4cf6c1584 --- /dev/null +++ b/test/java/awt/Cursor/MultiResolutionCursorTest/MultiResolutionCursorTest.html @@ -0,0 +1,32 @@ + + + + + High resolution custom cursor test, bug ID 8028212 + + + +

See the dialog box (usually in upper left corner) for instructions

+ + \ No newline at end of file diff --git a/test/java/awt/Cursor/MultiResolutionCursorTest/MultiResolutionCursorTest.java b/test/java/awt/Cursor/MultiResolutionCursorTest/MultiResolutionCursorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9507bb40948db20dcc648f63816c8aa4a6934519 --- /dev/null +++ b/test/java/awt/Cursor/MultiResolutionCursorTest/MultiResolutionCursorTest.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2013, 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. + * + * 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 java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Label; +import java.awt.Point; +import java.awt.TextArea; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.util.LinkedList; +import java.util.List; +import javax.swing.JApplet; +import sun.awt.OSInfo; +import sun.awt.image.MultiResolutionImage; + +/** + * @test + * @bug 8028212 + * @summary [macosx] Custom Cursor HiDPI support + * @author Alexander Scherbatiy + * @run applet/manual=yesno MultiResolutionCursorTest.html + */ +public class MultiResolutionCursorTest extends JApplet { + //Declare things used in the test, like buttons and labels here + + static final int sizes[] = {16, 32, 128}; + static final Color colors[] = {Color.WHITE, Color.RED, Color.GREEN, Color.BLUE}; + + public void init() { + //Create instructions for the user here, as well as set up + // the environment -- set the layout manager, add buttons, + // etc. + this.setLayout(new BorderLayout()); + + if (OSInfo.getOSType().equals(OSInfo.OSType.MACOSX)) { + String[] instructions = { + "Verify that high resolution custom cursor is used" + + " on HiDPI displays.", + "1) Run the test on Retina display or enable the Quartz Debug" + + " and select the screen resolution with (HiDPI) label", + "2) Move the cursor to the Test Frame", + "3) Check that cursor has red, green or blue color", + "If so, press PASS, else press FAIL." + }; + Sysout.createDialogWithInstructions(instructions); + + } else { + String[] instructions = { + "This test is not applicable to the current platform. Press PASS." + }; + Sysout.createDialogWithInstructions(instructions); + } + }//End init() + + public void start() { + //Get things going. Request focus, set size, et cetera + setSize(200, 200); + setVisible(true); + validate(); + + final Image image = new MultiResolutionCursor(); + + int center = sizes[0] / 2; + Cursor cursor = Toolkit.getDefaultToolkit().createCustomCursor( + image, new Point(center, center), "multi-resolution cursor"); + + Frame frame = new Frame("Test Frame"); + frame.setSize(300, 300); + frame.setLocation(300, 50); + frame.add(new Label("Move cursor here")); + frame.setCursor(cursor); + frame.setVisible(true); + }// start() + + + static class MultiResolutionCursor extends BufferedImage implements MultiResolutionImage { + + List highResolutionImages; + + public MultiResolutionCursor() { + super(sizes[0], sizes[0], BufferedImage.TYPE_INT_RGB); + + draw(getGraphics(), 0); + highResolutionImages = new LinkedList<>(); + highResolutionImages.add(this); + + for (int i = 1; i < sizes.length; i++) { + BufferedImage highResolutionImage = + new BufferedImage(sizes[i], sizes[i], BufferedImage.TYPE_INT_RGB); + draw(highResolutionImage.getGraphics(), i); + highResolutionImages.add(highResolutionImage); + } + } + + @Override + public Image getResolutionVariant(int width, int height) { + + for (int i = 0; i < sizes.length; i++) { + Image image = highResolutionImages.get(i); + int w = image.getWidth(null); + int h = image.getHeight(null); + + if (width <= w && height <= h) { + return image; + } + } + + return highResolutionImages.get(highResolutionImages.size() - 1); + } + + void draw(Graphics graphics, int index) { + Graphics2D g2 = (Graphics2D) graphics; + Color color = colors[index]; + g2.setColor(color); + g2.fillRect(0, 0, sizes[index], sizes[index]); + } + + @Override + public List getResolutionVariants() { + return highResolutionImages; + } + } +}// class BlockedWindowTest + +/* Place other classes related to the test after this line */ +/** + * ************************************************** + * Standard Test Machinery DO NOT modify anything below -- it's a standard chunk + * of code whose purpose is to make user interaction uniform, and thereby make + * it simpler to read and understand someone else's test. + * ************************************************** + */ +/** + * This is part of the standard test machinery. It creates a dialog (with the + * instructions), and is the interface for sending text messages to the user. To + * print the instructions, send an array of strings to Sysout.createDialog + * WithInstructions method. Put one line of instructions per array entry. To + * display a message for the tester to see, simply call Sysout.println with the + * string to be displayed. This mimics System.out.println but works within the + * test harness as well as standalone. + */ +class Sysout { + + private static TestDialog dialog; + + public static void createDialogWithInstructions(String[] instructions) { + dialog = new TestDialog(new Frame(), "Instructions"); + dialog.printInstructions(instructions); + dialog.setVisible(true); + println("Any messages for the tester will display here."); + } + + public static void createDialog() { + dialog = new TestDialog(new Frame(), "Instructions"); + String[] defInstr = {"Instructions will appear here. ", ""}; + dialog.printInstructions(defInstr); + dialog.setVisible(true); + println("Any messages for the tester will display here."); + } + + public static void printInstructions(String[] instructions) { + dialog.printInstructions(instructions); + } + + public static void println(String messageIn) { + dialog.displayMessage(messageIn); + } +}// Sysout class + +/** + * This is part of the standard test machinery. It provides a place for the test + * instructions to be displayed, and a place for interactive messages to the + * user to be displayed. To have the test instructions displayed, see Sysout. To + * have a message to the user be displayed, see Sysout. Do not call anything in + * this dialog directly. + */ +class TestDialog extends Dialog { + + TextArea instructionsText; + TextArea messageText; + int maxStringLength = 80; + + //DO NOT call this directly, go through Sysout + public TestDialog(Frame frame, String name) { + super(frame, name); + int scrollBoth = TextArea.SCROLLBARS_BOTH; + instructionsText = new TextArea("", 15, maxStringLength, scrollBoth); + add("North", instructionsText); + + messageText = new TextArea("", 5, maxStringLength, scrollBoth); + add("Center", messageText); + + pack(); + + setVisible(true); + }// TestDialog() + + //DO NOT call this directly, go through Sysout + public void printInstructions(String[] instructions) { + //Clear out any current instructions + instructionsText.setText(""); + + //Go down array of instruction strings + + String printStr, remainingStr; + for (int i = 0; i < instructions.length; i++) { + //chop up each into pieces maxSringLength long + remainingStr = instructions[ i]; + while (remainingStr.length() > 0) { + //if longer than max then chop off first max chars to print + if (remainingStr.length() >= maxStringLength) { + //Try to chop on a word boundary + int posOfSpace = remainingStr.lastIndexOf(' ', maxStringLength - 1); + + if (posOfSpace <= 0) { + posOfSpace = maxStringLength - 1; + } + + printStr = remainingStr.substring(0, posOfSpace + 1); + remainingStr = remainingStr.substring(posOfSpace + 1); + } //else just print + else { + printStr = remainingStr; + remainingStr = ""; + } + + instructionsText.append(printStr + "\n"); + + }// while + + }// for + + }//printInstructions() + + //DO NOT call this directly, go through Sysout + public void displayMessage(String messageIn) { + messageText.append(messageIn + "\n"); + System.out.println(messageIn); + } +}// Te \ No newline at end of file