Ticket #18694: hidpi-cursors-overlay.patch
File hidpi-cursors-overlay.patch, 26.2 KB (added by , 5 years ago) |
---|
-
src/org/openstreetmap/josm/tools/GuiSizesHelper.java
diff --git a/src/org/openstreetmap/josm/tools/GuiSizesHelper.java b/src/org/openstreetmap/josm/tools/GuiSizesHelper.java index 2e4d38691..29f54e6d3 100644
a b public final class GuiSizesHelper { 56 56 return getScreenDPI() / 96f; 57 57 } 58 58 59 /** 60 * Sets coefficient of monitor pixel density. 61 * @param pixelDensity coefficient of monitor pixel density to be set. 62 */ 63 public static void setPixelDensity(float pixelDensity) { 64 screenDPI = pixelDensity * 96f; 65 } 66 59 67 /** 60 68 * Check if a high DPI resolution is used 61 69 * @return <code>true</code> for HIDPI screens -
src/org/openstreetmap/josm/tools/HiDPISupport.java
diff --git a/src/org/openstreetmap/josm/tools/HiDPISupport.java b/src/org/openstreetmap/josm/tools/HiDPISupport.java index 169eea566..f54e3ff2e 100644
a b public final class HiDPISupport { 32 32 private static final Class<? extends Image> baseMultiResolutionImageClass; 33 33 private static final Constructor<? extends Image> baseMultiResolutionImageConstructor; 34 34 private static final Method resolutionVariantsMethod; 35 private static final Method resolutionVariantMethod; 35 36 36 37 static { 37 38 baseMultiResolutionImageClass = initBaseMultiResolutionImageClass(); 38 39 baseMultiResolutionImageConstructor = initBaseMultiResolutionImageConstructor(); 39 40 resolutionVariantsMethod = initResolutionVariantsMethod(); 41 resolutionVariantMethod = initResolutionVariantMethod(); 40 42 } 41 43 42 44 private HiDPISupport() { … … public final class HiDPISupport { 56 58 public static Image getMultiResolutionImage(Image base, ImageResource ir) { 57 59 double uiScale = getHiDPIScale(); 58 60 if (uiScale != 1.0 && baseMultiResolutionImageConstructor != null) { 59 ImageIcon zoomed = ir.getImageIcon (new Dimension(61 ImageIcon zoomed = ir.getImageIconAlreadyScaled(new Dimension( 60 62 (int) Math.round(base.getWidth(null) * uiScale), 61 (int) Math.round(base.getHeight(null) * uiScale)), false );63 (int) Math.round(base.getHeight(null) * uiScale)), false, true); 62 64 Image mrImg = getMultiResolutionImage(Arrays.asList(base, zoomed.getImage())); 63 65 if (mrImg != null) return mrImg; 64 66 } … … public final class HiDPISupport { 137 139 return Collections.singletonList(img); 138 140 } 139 141 142 /** 143 * Wrapper for method <code>java.awt.image.MultiResolutionImage#getResolutionVariant(double destImageWidth, double destImageHeight)</code>. 144 * <p> 145 * Will return the argument, in case it is not a multi-resolution image. 146 * @param img the image 147 * @return if <code>img</code> is a <code>java.awt.image.BaseMultiResolutionImage</code>, 148 * then the result of the method <code>#getResolutionVariant(destImageWidth, destImageHeight)</code>, 149 * otherwise the image itself 150 */ 151 public static Image getResolutionVariant(Image img, double destImageWidth, double destImageHeight) { 152 if (baseMultiResolutionImageClass == null || resolutionVariantsMethod == null) { 153 return img; 154 } 155 if (baseMultiResolutionImageClass.isInstance(img)) { 156 try { 157 return (Image) resolutionVariantMethod.invoke(img, destImageWidth, destImageHeight); 158 } catch (IllegalAccessException | InvocationTargetException ex) { 159 Logging.error("Unexpected error while calling method: " + ex); 160 } 161 } 162 return img; 163 } 164 140 165 /** 141 166 * Detect the GUI scale for HiDPI mode. 142 167 * <p> … … public final class HiDPISupport { 144 169 * only take the default screen device into account. 145 170 * @return the GUI scale for HiDPI mode, a value of 1.0 means standard mode. 146 171 */ 147 privatestatic double getHiDPIScale() {172 static double getHiDPIScale() { 148 173 if (GraphicsEnvironment.isHeadless()) 149 174 return 1.0; 150 175 GraphicsConfiguration gc = GraphicsEnvironment … … public final class HiDPISupport { 236 261 return null; 237 262 } 238 263 } 264 265 private static Method initResolutionVariantMethod() { 266 try { 267 return baseMultiResolutionImageClass != null 268 ? baseMultiResolutionImageClass.getMethod("getResolutionVariant", Double.TYPE, Double.TYPE) 269 : null; 270 } catch (NoSuchMethodException ex) { 271 Logging.error("Cannot find expected method: " + ex); 272 return null; 273 } 274 } 239 275 } -
src/org/openstreetmap/josm/tools/ImageOverlay.java
diff --git a/src/org/openstreetmap/josm/tools/ImageOverlay.java b/src/org/openstreetmap/josm/tools/ImageOverlay.java index 0bb61c68c..fd28eb770 100644
a b 2 2 package org.openstreetmap.josm.tools; 3 3 4 4 import java.awt.Dimension; 5 import java.awt.Image; 5 6 import java.awt.image.BufferedImage; 7 import java.util.List; 6 8 7 9 import javax.swing.ImageIcon; 8 10 … … public class ImageOverlay implements ImageProcessor { 66 68 */ 67 69 @Override 68 70 public BufferedImage process(BufferedImage ground) { 71 return process(ground, false); 72 } 73 74 /** 75 * Handle overlay. The image passed as argument is modified! 76 * 77 * @param ground the base image for the overlay (gets modified!) 78 * @param highResolution whether the high resolution variant should be used to overlay 79 * @return the modified image (same as argument) 80 */ 81 BufferedImage process(BufferedImage ground, boolean highResolution) { 69 82 /* get base dimensions for calculation */ 70 83 int w = ground.getWidth(); 71 84 int h = ground.getHeight(); … … public class ImageOverlay implements ImageProcessor { 77 90 if (offsetTop > 0 && offsetBottom > 0) { 78 91 height = (int) (h*(offsetBottom-offsetTop)); 79 92 } 80 ImageIcon overlay;81 93 image = new ImageProvider(image).setMaxSize(new Dimension(width, height)); 82 overlay = image.get(); 94 ImageIcon overlay = image.get(); 95 if (highResolution) { 96 List<Image> resolutionVariants = HiDPISupport.getResolutionVariants(overlay.getImage()); 97 if (resolutionVariants.size() >= 2) { 98 overlay = new ImageIcon(resolutionVariants.get(1)); 99 } 100 } 83 101 int x, y; 84 102 if (width == -1 && offsetLeft < 0) { 85 103 x = (int) (w*offsetRight) - overlay.getIconWidth(); -
src/org/openstreetmap/josm/tools/ImageProvider.java
diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java index 8b1882cb0..53ed29d45 100644
a b public class ImageProvider { 1317 1317 * @return cursor with a given file name, optionally decorated with an overlay image 1318 1318 */ 1319 1319 public static Cursor getCursor(String name, String overlay) { 1320 ImageIcon img = get("cursor", name);1321 if (overlay != null) {1322 img = new ImageProvider("cursor", name).setMaxSize(ImageSizes.CURSOR)1323 .addOverlay(new ImageOverlay(new ImageProvider("cursor/modifier/" + overlay)1324 .setMaxSize(ImageSizes.CURSOROVERLAY))).get();1325 }1326 1320 if (GraphicsEnvironment.isHeadless()) { 1327 1321 Logging.debug("Cursors are not available in headless mode. Returning null for ''{0}''", name); 1328 1322 return null; 1329 1323 } 1330 return Toolkit.getDefaultToolkit().createCustomCursor(img.getImage(), 1331 "crosshair".equals(name) ? new Point(10, 10) : new Point(3, 2), "Cursor"); 1324 1325 Point hotSpot = new Point(); 1326 Image image = getCursorImage(name, overlay, hotSpot); 1327 1328 return Toolkit.getDefaultToolkit().createCustomCursor(image, hotSpot, name); 1329 } 1330 1331 /** 1332 * Load a cursor image with a given file name, optionally decorated with an overlay image 1333 * 1334 * @param name the cursor image filename in "cursor" directory 1335 * @param overlay optional overlay image 1336 * @param hotSpot will be set to the properly scaled hotspot of the cursor 1337 * @return cursor with a given file name, optionally decorated with an overlay image 1338 */ 1339 static Image getCursorImage(String name, String overlay, /* out */ Point hotSpot) { 1340 ImageProvider imageProvider = new ImageProvider("cursor", name); 1341 if (overlay != null) { 1342 imageProvider 1343 .setMaxSize(ImageSizes.CURSOR) 1344 .addOverlay(new ImageOverlay(new ImageProvider("cursor/modifier/" + overlay) 1345 .setMaxSize(ImageSizes.CURSOROVERLAY))); 1346 } 1347 hotSpot.setLocation("crosshair".equals(name) ? new Point(10, 10) : new Point(3, 2)); 1348 ImageIcon imageIcon = imageProvider.get(); 1349 Image image = imageIcon.getImage(); 1350 int width = image.getWidth(null); 1351 int height = image.getHeight(null); 1352 1353 // AWT will resize the cursor to bestCursorSize internally anyway, but miss to scale the hotspot as well 1354 // (bug JDK-8238734). So let's do this ourselves, and also scale the hotspot accordingly. 1355 Dimension bestCursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(width, height); 1356 if (bestCursorSize.width != 0 && bestCursorSize.height != 0) { 1357 // In principle, we could pass the MultiResolutionImage itself to AWT, but due to bug JDK-8240568, 1358 // this results in bad alpha blending and thus jaggy edges. So let's select the best variant ourselves. 1359 image = HiDPISupport.getResolutionVariant(image, bestCursorSize.width, bestCursorSize.height); 1360 if (bestCursorSize.width != image.getWidth(null) || bestCursorSize.height != image.getHeight(null)) { 1361 image = image.getScaledInstance(bestCursorSize.width, bestCursorSize.height, Image.SCALE_DEFAULT); 1362 } 1363 1364 hotSpot.x = hotSpot.x * bestCursorSize.width / width; 1365 hotSpot.y = hotSpot.y * bestCursorSize.height / height; 1366 } 1367 1368 return image; 1332 1369 } 1333 1370 1334 1371 /** 90 degrees in radians units */ -
src/org/openstreetmap/josm/tools/ImageResource.java
diff --git a/src/org/openstreetmap/josm/tools/ImageResource.java b/src/org/openstreetmap/josm/tools/ImageResource.java index 831b7c71b..4c00e0b79 100644
a b public class ImageResource { 153 153 * @return ImageIcon object for the image of this resource, scaled according to dim 154 154 * @since 12722 155 155 */ 156 public ImageIcon getImageIcon(Dimension dim, boolean multiResolution) { 156 ImageIcon getImageIcon(Dimension dim, boolean multiResolution) { 157 return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false); 158 } 159 160 /** 161 * Get an ImageIcon object for the image of this resource. A potential UI scaling is assumed 162 * to be already taken care of, so dim is already scaled accordingly. 163 * @param dim The requested dimensions. Use (-1,-1) for the original size and (width, -1) 164 * to set the width, but otherwise scale the image proportionally. 165 * @param multiResolution If true, return a multi-resolution image 166 * (java.awt.image.MultiResolutionImage in Java 9), otherwise a plain {@link BufferedImage}. 167 * When running Java 8, this flag has no effect and a plain image will be returned in any case. 168 * @param highResolution whether the high resolution variant should be used for overlays 169 * @return ImageIcon object for the image of this resource, scaled according to dim 170 */ 171 ImageIcon getImageIconAlreadyScaled(Dimension dim, boolean multiResolution, boolean highResolution) { 157 172 CheckParameterUtil.ensureThat((dim.width > 0 || dim.width == -1) && (dim.height > 0 || dim.height == -1), 158 173 () -> dim + " is invalid"); 174 159 175 BufferedImage img = imgCache.get(dim); 160 176 if (img == null) { 161 177 if (svg != null) { 162 Dimension realDim = GuiSizesHelper.getDimensionDpiAdjusted(dim); 163 img = ImageProvider.createImageFromSvg(svg, realDim); 178 img = ImageProvider.createImageFromSvg(svg, dim); 164 179 if (img == null) { 165 180 return null; 166 181 } 167 182 } else { 168 183 if (baseImage == null) throw new AssertionError(); 169 184 170 int realWidth = GuiSizesHelper.getSizeDpiAdjusted(dim.width);171 int realHeight = GuiSizesHelper.getSizeDpiAdjusted(dim.height);172 185 ImageIcon icon = new ImageIcon(baseImage); 173 if ( realWidth == -1 && realHeight == -1) {174 realWidth = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconWidth());175 realHeight = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconHeight());176 } else if ( realWidth == -1) {177 realWidth = Math.max(1, icon.getIconWidth() * realHeight / icon.getIconHeight());178 } else if ( realHeight == -1) {179 realHeight = Math.max(1, icon.getIconHeight() * realWidth / icon.getIconWidth());186 if (dim.width == -1 && dim.height == -1) { 187 dim.width = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconWidth()); 188 dim.height = GuiSizesHelper.getSizeDpiAdjusted(icon.getIconHeight()); 189 } else if (dim.width == -1) { 190 dim.width = Math.max(1, icon.getIconWidth() * dim.height / icon.getIconHeight()); 191 } else if (dim.height == -1) { 192 dim.height = Math.max(1, icon.getIconHeight() * dim.width / icon.getIconWidth()); 180 193 } 181 Image i = icon.getImage().getScaledInstance( realWidth, realHeight, Image.SCALE_SMOOTH);182 img = new BufferedImage( realWidth, realHeight, BufferedImage.TYPE_INT_ARGB);194 Image i = icon.getImage().getScaledInstance(dim.width, dim.height, Image.SCALE_SMOOTH); 195 img = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); 183 196 img.getGraphics().drawImage(i, 0, 0, null); 184 197 } 185 198 if (overlayInfo != null) { 186 199 for (ImageOverlay o : overlayInfo) { 187 o.process(img );200 o.process(img, highResolution); 188 201 } 189 202 } 190 203 if (isDisabled) { -
test/unit/org/openstreetmap/josm/TestUtils.java
diff --git a/test/unit/org/openstreetmap/josm/TestUtils.java b/test/unit/org/openstreetmap/josm/TestUtils.java index 3f8f100a4..61700f688 100644
a b import org.openstreetmap.josm.gui.progress.ProgressTaskId; 50 50 import org.openstreetmap.josm.gui.util.GuiHelper; 51 51 import org.openstreetmap.josm.io.Compression; 52 52 import org.openstreetmap.josm.testutils.FakeGraphics; 53 import org.openstreetmap.josm.testutils.mockers.HeadlessToolkitMocker; 53 54 import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker; 54 55 import org.openstreetmap.josm.testutils.mockers.WindowMocker; 55 56 import org.openstreetmap.josm.tools.JosmRuntimeException; … … public final class TestUtils { 464 465 // Inspired by https://issues.apache.org/jira/browse/SOLR-11606 465 466 new WindowMocker(); 466 467 new JOptionPaneSimpleMocker(); 468 new HeadlessToolkitMocker(); 467 469 } catch (UnsupportedOperationException e) { 468 470 Assume.assumeNoException(e); 469 471 } finally { -
new file test/unit/org/openstreetmap/josm/testutils/mockers/HeadlessToolkitMocker.java
diff --git a/test/unit/org/openstreetmap/josm/testutils/mockers/HeadlessToolkitMocker.java b/test/unit/org/openstreetmap/josm/testutils/mockers/HeadlessToolkitMocker.java new file mode 100644 index 000000000..59fec5e4e
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.testutils.mockers; 3 4 import mockit.Mock; 5 import mockit.MockUp; 6 import sun.awt.HeadlessToolkit; 7 8 import java.awt.Dimension; 9 import java.awt.HeadlessException; 10 import java.awt.Toolkit; 11 12 /** 13 * MockUp for a {@link Toolkit} that allows to mock getBestCursorSize 14 */ 15 public class HeadlessToolkitMocker extends MockUp<HeadlessToolkit> { 16 public static Dimension bestCursorSize = new Dimension(32, 32); 17 18 @Mock 19 public Dimension getBestCursorSize(int preferredWidth, int preferredHeight) throws HeadlessException { 20 return bestCursorSize; 21 } 22 } -
test/unit/org/openstreetmap/josm/tools/ImageProviderTest.java
diff --git a/test/unit/org/openstreetmap/josm/tools/ImageProviderTest.java b/test/unit/org/openstreetmap/josm/tools/ImageProviderTest.java index b96320301..20f056aee 100644
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.tools; 3 3 4 import static java.awt.image.BufferedImage.TYPE_INT_ARGB; 4 5 import static org.junit.Assert.assertEquals; 5 6 import static org.junit.Assert.assertFalse; 6 7 import static org.junit.Assert.assertNotNull; 7 8 import static org.junit.Assert.assertNull; 9 import static org.junit.Assert.assertTrue; 8 10 11 import java.awt.Color; 9 12 import java.awt.Dimension; 13 import java.awt.GraphicsEnvironment; 14 import java.awt.GridLayout; 15 import java.awt.Graphics; 16 import java.awt.Image; 17 import java.awt.Point; 18 import java.awt.Toolkit; 10 19 import java.awt.Transparency; 20 import java.awt.event.MouseEvent; 21 import java.awt.event.MouseListener; 11 22 import java.awt.image.BufferedImage; 12 23 import java.io.File; 13 24 import java.io.IOException; 14 25 import java.util.EnumSet; 26 import java.util.List; 15 27 import java.util.logging.Handler; 16 28 import java.util.logging.LogRecord; 17 29 import java.util.logging.Logger; 18 30 19 31 import javax.swing.ImageIcon; 32 import javax.swing.JFrame; 33 import javax.swing.JPanel; 20 34 35 import mockit.integration.TestRunnerDecorator; 36 import org.junit.Before; 21 37 import org.junit.BeforeClass; 38 import org.junit.Ignore; 22 39 import org.junit.Rule; 23 40 import org.junit.Test; 24 41 import org.openstreetmap.josm.JOSMFixture; … … import org.openstreetmap.josm.TestUtils; 26 43 import org.openstreetmap.josm.data.osm.Node; 27 44 import org.openstreetmap.josm.data.osm.OsmUtils; 28 45 import org.openstreetmap.josm.testutils.JOSMTestRules; 46 import org.openstreetmap.josm.testutils.mockers.HeadlessToolkitMocker; 29 47 import org.openstreetmap.josm.tools.ImageProvider.GetPaddedOptions; 30 48 31 49 import com.kitfox.svg.SVGConst; … … public class ImageProviderTest { 71 89 JOSMFixture.createUnitTestFixture().init(); 72 90 } 73 91 92 @Before 93 public void resetPixelDensity() { 94 GuiSizesHelper.setPixelDensity(1.0f); 95 } 96 74 97 /** 75 98 * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/9984">#9984</a> 76 99 * @throws IOException if an error occurs during reading … … public class ImageProviderTest { 148 171 assertNotNull(ImageProvider.getPadded(OsmUtils.createPrimitive("way waterway=stream"), iconSize, noDefault)); 149 172 assertNotNull(ImageProvider.getPadded(OsmUtils.createPrimitive("relation type=route route=railway"), iconSize, noDefault)); 150 173 } 174 175 /** 176 * Test getting a bounded icon given some UI scaling configured. 177 */ 178 @Test 179 public void testGetImageIconBounded() { 180 int scale = 2; 181 GuiSizesHelper.setPixelDensity(scale); 182 183 ImageProvider imageProvider = new ImageProvider("open").setOptional(true); 184 ImageResource resource = imageProvider.getResource(); 185 Dimension iconDimension = ImageProvider.ImageSizes.SMALLICON.getImageDimension(); 186 ImageIcon icon = resource.getImageIconBounded(iconDimension); 187 Image image = icon.getImage(); 188 List<Image> resolutionVariants = HiDPISupport.getResolutionVariants(image); 189 if (resolutionVariants.size() > 1) { 190 assertEquals(2, resolutionVariants.size()); 191 int expectedVirtualWidth = ImageProvider.ImageSizes.SMALLICON.getVirtualWidth(); 192 assertEquals(expectedVirtualWidth * scale, resolutionVariants.get(0).getWidth(null)); 193 assertEquals((int) Math.round(expectedVirtualWidth * scale * HiDPISupport.getHiDPIScale()), 194 resolutionVariants.get(1).getWidth(null)); 195 } 196 } 197 198 public static final int ORIGINAL_CURSOR_SIZE = 32; 199 200 /** 201 * Test getting an image for a crosshair cursor. 202 */ 203 @Test 204 public void testGetCursorImageForCrosshair() { 205 Dimension[] bestCursorSizes = installHeadlessToolkitMock(); 206 207 for (Dimension cursorSize: bestCursorSizes) { 208 if (GraphicsEnvironment.isHeadless()) { 209 HeadlessToolkitMocker.bestCursorSize = cursorSize; 210 } 211 Point hotSpot = new Point(); 212 Image image = ImageProvider.getCursorImage("crosshair", null, hotSpot); 213 assertCursorDimensionsCorrect(new Point.Double(10.0, 10.0), image, hotSpot); 214 } 215 216 TestRunnerDecorator.cleanUpAllMocks(); 217 } 218 219 /** 220 * Test getting an image for a custom cursor with overlay. 221 */ 222 @Test 223 public void testGetCursorImageWithOverlay() { 224 Dimension[] bestCursorSizes = installHeadlessToolkitMock(); 225 226 for (Dimension cursorSize: bestCursorSizes) { 227 if (GraphicsEnvironment.isHeadless()) { 228 HeadlessToolkitMocker.bestCursorSize = cursorSize; 229 } 230 Point hotSpot = new Point(); 231 Image image = ImageProvider.getCursorImage("normal", "selection", hotSpot); 232 assertCursorDimensionsCorrect(new Point.Double(3.0, 2.0), image, hotSpot); 233 BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getWidth(null), TYPE_INT_ARGB); 234 bufferedImage.getGraphics().drawImage(image, 0, 0, null); 235 236 // check that the square of 1/4 size right lower to the center has some non-emtpy pixels 237 boolean nonEmptyPixelExistsRightLowerToCenter = false; 238 for (int x = image.getWidth(null) / 2; x < image.getWidth(null) * 3 / 4; ++x) { 239 for (int y = image.getHeight(null) / 2; y < image.getWidth(null) * 3 / 4; ++y) { 240 if (bufferedImage.getRGB(x, y) != 0) 241 nonEmptyPixelExistsRightLowerToCenter = true; 242 } 243 } 244 assertTrue(nonEmptyPixelExistsRightLowerToCenter); 245 } 246 TestRunnerDecorator.cleanUpAllMocks(); 247 } 248 249 private Dimension[] installHeadlessToolkitMock() { 250 if (GraphicsEnvironment.isHeadless()) { 251 TestUtils.assumeWorkingJMockit(); 252 new HeadlessToolkitMocker(); 253 // in headless mode, let's test many reasonable values 254 return new Dimension[]{new Dimension(32, 32), new Dimension(48, 48), 255 new Dimension(64, 64), new Dimension(96, 96)}; 256 } else { 257 // in normal mode, test the settings of the system run on 258 return new Dimension[]{new Dimension(-1, -1)}; // values disregarded, but trigger a single test run 259 } 260 } 261 262 private void assertCursorDimensionsCorrect(Point.Double originalHotspot, Image image, Point hotSpot) { 263 Dimension bestCursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(ORIGINAL_CURSOR_SIZE, ORIGINAL_CURSOR_SIZE); 264 Image bestCursorImage = HiDPISupport.getResolutionVariant(image, bestCursorSize.width, bestCursorSize.height); 265 int bestCursorImageWidth = bestCursorImage.getWidth(null); 266 assertEquals((int) Math.round(bestCursorSize.getWidth()), bestCursorImageWidth); 267 int bestCursorImageHeight = bestCursorImage.getHeight(null); 268 assertEquals((int) Math.round(bestCursorSize.getHeight()), bestCursorImageHeight); 269 assertEquals(originalHotspot.x / ORIGINAL_CURSOR_SIZE * bestCursorImageWidth, hotSpot.x, 1 /* at worst one pixel off */); 270 assertEquals(originalHotspot.y / ORIGINAL_CURSOR_SIZE * bestCursorImageHeight, hotSpot.y, 1 /* at worst one pixel off */); 271 } 272 273 274 /** 275 * Test getting a cursor 276 */ 277 @Ignore("manual execution only, as the look of the cursor cannot be checked automatedly") 278 @Test 279 public void testGetCursor() throws InterruptedException { 280 JFrame frame = new JFrame(); 281 frame.setSize(500, 500); 282 frame.setLayout(new GridLayout(2, 2)); 283 JPanel leftUpperPanel = new JPanel(), rightUpperPanel = new JPanel(), leftLowerPanel = new JPanel(), rightLowerPanel = new JPanel(); 284 leftUpperPanel.setBackground(Color.DARK_GRAY); 285 rightUpperPanel.setBackground(Color.DARK_GRAY); 286 leftLowerPanel.setBackground(Color.DARK_GRAY); 287 rightLowerPanel.setBackground(Color.DARK_GRAY); 288 frame.add(leftUpperPanel); 289 frame.add(rightUpperPanel); 290 frame.add(leftLowerPanel); 291 frame.add(rightLowerPanel); 292 293 leftUpperPanel.setCursor(ImageProvider.getCursor("normal", "select_add")); // contains diagonal sensitive to alpha blending 294 rightUpperPanel.setCursor(ImageProvider.getCursor("crosshair", "joinway")); // combination of overlay and hotspot not top left 295 leftLowerPanel.setCursor(ImageProvider.getCursor("hand", "parallel_remove")); // reasonably nice bitmap cursor 296 rightLowerPanel.setCursor(ImageProvider.getCursor("rotate", null)); // ugly bitmap cursor, cannot do much here 297 298 frame.setVisible(true); 299 300 // hover over the four quadrant to observe different cursors 301 302 // draw red dot at hotspot when clicking 303 frame.addMouseListener(new MouseListener() { 304 @Override 305 public void mouseClicked(MouseEvent e) { 306 Graphics graphics = frame.getGraphics(); 307 graphics.setColor(Color.RED); 308 graphics.drawRect(e.getX(), e.getY(), 1, 1); 309 } 310 311 @Override 312 public void mousePressed(MouseEvent e) { } 313 314 @Override 315 public void mouseReleased(MouseEvent e) { } 316 317 @Override 318 public void mouseEntered(MouseEvent e) { } 319 320 @Override 321 public void mouseExited(MouseEvent e) { } 322 }); 323 Thread.sleep(9000); // test would time out after 10s 324 } 151 325 }