This is an automated email from the ASF dual-hosted git repository. ebakke pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
commit 55d123f50c06467a62d518f0682b8b40e5dc2912 Author: Eirik Bakke <[email protected]> AuthorDate: Sun Jan 5 19:32:35 2025 +0100 Add methods to ImageUtilities, to make migration from other image loading methods used throughout the codebase easier. (Additive API change.) Edit Javadoc to avoid repeating semantics that are common to many methods. Some other Javadoc cleanup. --- platform/openide.util.ui/apichanges.xml | 14 ++ .../src/org/openide/util/ImageUtilities.java | 203 +++++++++++++++++---- 2 files changed, 182 insertions(+), 35 deletions(-) diff --git a/platform/openide.util.ui/apichanges.xml b/platform/openide.util.ui/apichanges.xml index baed377a7a4..94169749edc 100644 --- a/platform/openide.util.ui/apichanges.xml +++ b/platform/openide.util.ui/apichanges.xml @@ -27,6 +27,20 @@ <apidef name="actions">Actions API</apidef> </apidefs> <changes> + <change id="moreImageUtilsForHiDPI"> + <api name="util"/> + <summary>Added methods to ImageUtilities, to aid migration from other icon loading methods.</summary> + <version major="9" minor="36"/> + <date day="5" month="1" year="2025"/> + <author login="ebakke"/> + <compatibility addition="yes" binary="compatible" source="compatible" semantic="compatible" deprecation="no" deletion="no" modification="yes"/> + <description> + <p>Added new utility methods to ImageUtilities to ease migration from other image loading methods used throughout the NetBeans codebase. + Loading icons via ImageUtilities is preferred as it permits alternative SVG versions to be loaded automatically when available.</p> + </description> + <class package="org.openide.util" name="ImageUtilities"/> + </change> + <change id="mouseKeyCodes"> <api name="util"/> <summary>Added methods to query mouse event pseudo-keycodes</summary> diff --git a/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java b/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java index 526f659594a..eddc1d968a9 100644 --- a/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java +++ b/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java @@ -42,6 +42,8 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.lang.ref.SoftReference; +import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.HashSet; @@ -161,8 +163,13 @@ public final class ImageUtilities { } /** - * Loads an image from the specified resource ID. The image is loaded using the "system" classloader registered in - * Lookup. + * Loads an image from the specified resource path. The image is loaded using the "system" + * classloader registered in Lookup. + * + * <p>If the current look and feel is 'dark' (<code>UIManager.getBoolean("nb.dark.theme")</code>) + * then the method first attempts to load image + * <i><original file name><b>_dark</b>.<original extension></i>. If such file + * doesn't exist the default one is loaded instead. * * <p>If the default lookup contains a service provider for the {@link SVGLoader} interface, and * there exists an SVG version of the requested image (e.g. "icon.svg" exists when "icon.png" @@ -177,6 +184,13 @@ public final class ImageUtilities { * When painting on HiDPI-capable {@code Graphics2D} instances provided by Swing, the * appropriate transform will already be in place. * + * <p>Since version 8.12 the returned image object responds to call + * <code>image.getProperty({@link #PROPERTY_URL}, null)</code> by returning the internal + * {@link URL} of the found and loaded <code>resource</code>. The convenience method + * {@link #findImageBaseURL} should be used in preference to direct property access. + * + * <p>Caching of loaded images can be used internally to improve performance. + * * @param resourceID resource path of the icon (no initial slash) * @return icon's Image, or null, if the icon cannot be loaded. */ @@ -185,31 +199,93 @@ public final class ImageUtilities { } /** - * Loads an image based on resource path. + * Loads an image based on a resource path. * Exactly like {@link #loadImage(String)} but may do a localized search. * For example, requesting <code>org/netbeans/modules/foo/resources/foo.gif</code> * might actually find <code>org/netbeans/modules/foo/resources/foo_ja.gif</code> * or <code>org/netbeans/modules/foo/resources/foo_mybranding.gif</code>. - * - * <p>Caching of loaded images can be used internally to improve performance. - * <p> Since version 8.12 the returned image object responds to call - * <code>image.getProperty({@link #PROPERTY_URL}, null)</code> by returning the internal - * {@link URL} of the found and loaded <code>resource</code>. Convenience method {@link #findImageBaseURL} - * should be used in preference to direct property access. - * - * <p>If the current look and feel is 'dark' (<code>UIManager.getBoolean("nb.dark.theme")</code>) - * then the method first attempts to load image <i><original file name><b>_dark</b>.<original extension></i>. - * If such file doesn't exist the default one is loaded instead. - * </p> - * + * * @param resource resource path of the image (no initial slash) * @param localized true for localized search - * @return icon's Image or null if the icon cannot be loaded + * @return the icon's Image, or null if the icon cannot be loaded */ public static final Image loadImage(String resource, boolean localized) { return loadImageInternal(resource, localized); } + /** + * Load an image from a URI/URL. If the URI uses the {@code nbresloc} protocol, it is loaded + * using the resource loading mechanism provided by {@link #loadImage(java.lang.String)}. This + * includes handling of SVG icons and dark mode variations. + * + * <p>This method is intended for use only when a URL or URI must be used instead of a resource + * path, e.g. in the implementation of pre-existing NetBeans APIs. External URLs should be + * avoided, as they may be disallowed in the future. Do not use this method for new code; prefer + * image loading by resource paths instead (e.g. {@link #loadImage(String)}). + * + * @param uri the URI of the image, possibly with the nbresloc protocol + * @return the loaded image, or either null or an uninitialized image if the image was not + * available + * @since 7.36 + */ + public static final Image loadImage(URI uri) { + Parameters.notNull("icon", uri); + String scheme = uri.getScheme(); + if (scheme.equals("nbresloc")) { // NOI18N + // Apply our dedicated handling logic. Omit the initial slash of the path. + return loadImage(uri.getPath().substring(1), false); + } else { + if (!(scheme.equals("file") || + scheme.equals("jar") && uri.toString().startsWith("jar:file:") || + scheme.equals("file"))) + { + LOGGER.log(Level.WARNING, "loadImage(URI) called with unusual URI: {0}", uri); + } + try { + /* Observed to return an image with size (-1, -1) if URL points to a non-existent + file (after ensureLoaded(Image) is called). */ + return Toolkit.getDefaultToolkit().createImage(uri.toURL()); + } catch (MalformedURLException e) { + LOGGER.log(Level.WARNING, "Malformed URL passed to loadImage(URI)", e); + return null; + } + } + } + + /** + * Convert an {@link Icon} instance to a delegating {@link ImageIcon} instance. If the supplied + * Icon instance is an SVG icon or has other HiDPI capabilities provided by the methods in this + * class, they are preserved by the conversion. + * + * <p>This method is intended for use only when existing APIs require the use of ImageIcon. In + * most other situations it is preferable to use the more general Icon type. + * + * @param icon the icon to be converted; may <em>not</em> be null + * @return the converted instance + * @since 7.36 + */ + public static final ImageIcon icon2ImageIcon(Icon icon) { + if (icon == null) { + /* Log a warning and return null rather than throwing an exception here. That way, + refactoring of existing code that wraps an existing expression in icon2ImageIcon will + not break existing functionality even if it accidentally relied on being able to pass + null in the past. */ + LOGGER.log(Level.WARNING, "Passing null to icon2ImageIcon is not permitted", + new NullPointerException()); + return null; + } + if (icon instanceof ImageIcon) { + return (ImageIcon) icon; + } else if (icon instanceof ToolTipImage) { + return ((ToolTipImage) icon).asImageIcon(); + } else { + Image image = icon2Image(icon); + ToolTipImage tti = (image instanceof ToolTipImage) + ? (ToolTipImage) image : assignToolTipToImageInternal(image, ""); + return tti.asImageIcon(); + } + } + /* Private version of the method showing the more specific return type. We always return a ToolTipImage, to take advantage of its rendering tweaks for HiDPI screens. */ private static ToolTipImage loadImageInternal(String resource, boolean localized) { @@ -235,16 +311,15 @@ public final class ImageUtilities { } /** - * Loads an icon based on resource path. - * Similar to {@link #loadImage(String, boolean)}, returns ImageIcon instead of Image. - * - * <p>If the current look and feel is 'dark' (<code>UIManager.getBoolean("nb.dark.theme")</code>) - * then the method first attempts to load image <i><original file name><b>_dark</b>.<original extension></i>. - * If such file doesn't exist the default one is loaded instead. - * </p> + * Loads an icon based on a resource path. Similar to {@link #loadImage(String, boolean)}, but + * returns {@link ImageIcon} instead of {@link Image}. + * + * <p>When a general Icon instance can be used rather than more specifically an ImageIcon, it is + * recommended to use {@link #loadIcon(java.lang.String, boolean)} instead. This method remains + * for compatibility. * * @param resource resource path of the icon (no initial slash) - * @param localized localized resource should be used + * @param localized true for localized search * @return ImageIcon or null, if the icon cannot be loaded. * @since 7.22 */ @@ -255,6 +330,35 @@ public final class ImageUtilities { } return image.asImageIcon(); } + + /** + * Loads an icon based on a resource path. Similar to {@link #loadImage(String, boolean)}, but + * returns {@link Icon} instead of {@link Image}. + * + * @param resource resource path of the icon (no initial slash) + * @param localized true for localized search + * @return ImageIcon or null, if the icon cannot be loaded. + * @since 7.36 + */ + public static final Icon loadIcon( String resource, boolean localized ) { + ToolTipImage image = loadImageInternal(resource, localized); + if( image == null ) { + return null; + } + return image.asImageIconIfRequiredForRetina(); + } + + /** + * Loads an icon based on a resource path. Similar to {@link #loadImage(String)}, but returns + * {@link Icon} instead of {@link Image}. + * + * @param resource resource path of the icon (no initial slash) + * @return ImageIcon or null, if the icon cannot be loaded. + * @since 7.36 + */ + public static final Icon loadIcon( String resource ) { + return loadIcon(resource, false); + } private static boolean isDarkLaF() { return UIManager.getBoolean("nb.dark.theme"); //NOI18N @@ -284,12 +388,14 @@ public final class ImageUtilities { return imageIconFilter; } - /** This method merges two images into the new one. The second image is drawn + /** This method merges two images into the a one. The second image is drawn * over the first one with its top-left corner at x, y. Images need not be of the same size. - * New image will have a size of max(second image size + top-left corner, first image size). - * Method is used mostly when second image contains transparent pixels (e.g. for badging). - * Method that attempts to find the merged image in the cache first, then - * creates the image if it was not found. + * The new image will have a size of max(second image size + top-left corner, first image size). + * This method is used mostly when second image contains transparent pixels (e.g. for badging). + * + * <p>The implementation attempts to find the merged image in the cache first, then creates the + * image if it was not found. + * * @param image1 underlying image * @param image2 second image * @param x x position of top-left corner @@ -316,8 +422,34 @@ public final class ImageUtilities { compositeCache.put(k, new ActiveRef<CompositeImageKey>(cached, compositeCache, k)); return cached; } - } - + } + + /** This method merges two icons into a new one. The second icon is drawn + * over the first one with its top-left corner at x, y. Icons need not be of the same size. + * The new icon will have a size of max(second icon size + top-left corner, first icon size). + * This method used mostly when second icon contains transparent pixels (e.g. for badging). + * + * <p>Similar to {@link #mergeImages(Image, Image, int, int)}, but on {@link Icon} instances + * rather than {@link Image} instances. This method is provided as a shortcut to avoid the need + * for conversions in client code. + * + * @param icon1 underlying icon + * @param icon2 second icon + * @param x x position of top-left corner + * @param y y position of top-left corner + * @return new merged icon + * @since 7.36 + */ + public static final Icon mergeIcons(Icon icon1, Icon icon2, int x, int y) { + if (icon1 == null || icon2 == null) { + throw new NullPointerException(); + } + /* Use the Image-based mergeImages implementation rather than just creating a new + MergedIcon instance, to take advantage of the former's cache. Icon instances tend to get + converted back and forth between Icon and Image anyway. */ + return image2Icon(mergeImages(icon2Image(icon1), icon2Image(icon2), x, y)); + } + /** * Converts given image to an icon. * @param image to be converted @@ -384,7 +516,7 @@ public final class ImageUtilities { } /** - * Assign tool tip text to given image (creates new or returns cached, original remains unmodified) + * Assign tool tip text to given image (creates new or returns cached, original remains unmodified). * Text can contain HTML tags e.g. "<b>my</b> text" * @param image image to which tool tip should be set * @param text tool tip text @@ -428,7 +560,7 @@ public final class ImageUtilities { } /** - * Add text to tool tip for given image (creates new or returns cached, original remains unmodified) + * Add text to tool tip for given image (creates new or returns cached, original remains unmodified). * Text can contain HTML tags e.g. "<b>my</b> text" * @param text text to add to tool tip * @return Image with attached tool tip @@ -449,7 +581,7 @@ public final class ImageUtilities { /** * Creates disabled (color saturation lowered) icon. - * Icon image conversion is performed lazily. + * * @param icon original icon used for conversion * @return less saturated Icon * @since 7.28 @@ -465,6 +597,7 @@ public final class ImageUtilities { /** * Creates disabled (color saturation lowered) image. + * * @param image original image used for conversion * @return less saturated Image * @since 7.28 @@ -849,7 +982,7 @@ public final class ImageUtilities { } } - private static final ToolTipImage doMergeImages(Image image1, Image image2, int x, int y) { + private static ToolTipImage doMergeImages(Image image1, Image image2, int x, int y) { ensureLoaded(image1); ensureLoaded(image2); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected] For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists
