diff --git a/openide.explorer/src/org/openide/explorer/view/VisualizerNode.java b/openide.explorer/src/org/openide/explorer/view/VisualizerNode.java
--- a/openide.explorer/src/org/openide/explorer/view/VisualizerNode.java
+++ b/openide.explorer/src/org/openide/explorer/view/VisualizerNode.java
@@ -56,10 +56,10 @@
import java.util.*;
import javax.swing.Icon;
-import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
import javax.swing.tree.TreeNode;
+import org.openide.util.ImageUtilities;
/** Visual representation of one node. Holds necessary information about nodes
@@ -206,7 +206,14 @@
if (desc == UNKNOWN) {
shortDescription = desc = node.getShortDescription();
}
-
+ if (icon instanceof Image) {
+ String toolTip = ImageUtilities.getImageToolTip((Image) icon);
+ if (toolTip.length() > 0) {
+ StringBuilder str = new StringBuilder(128);
+ str.append("").append(desc).append(" ").append(toolTip).append("");
+ desc = str.toString();
+ }
+ }
return desc;
}
@@ -407,7 +414,7 @@
if (Node.PROP_NAME.equals(name) || Node.PROP_DISPLAY_NAME.equals(name) || isIconChange) {
if (isIconChange) {
//Ditch the cached icon type so the next call to getIcon() will
- //recreate the ImageIcon
+ //recreate the Icon
cachedIconType = -1;
}
@@ -558,7 +565,7 @@
icon = getDefaultIcon();
} else {
- icon = new ImageIcon(image);
+ icon = ImageUtilities.image2Icon(image);
}
}
@@ -577,9 +584,8 @@
/** Loads default icon if not loaded. */
private static Icon getDefaultIcon() {
if (defaultIcon == null) {
- defaultIcon = new ImageIcon(Utilities.loadImage(DEFAULT_ICON));
+ defaultIcon = ImageUtilities.image2Icon(ImageUtilities.loadImage(DEFAULT_ICON));
}
-
return defaultIcon;
}
diff --git a/openide.explorer/test/unit/src/org/openide/explorer/view/VisualizerNodeTest.java b/openide.explorer/test/unit/src/org/openide/explorer/view/VisualizerNodeTest.java
--- a/openide.explorer/test/unit/src/org/openide/explorer/view/VisualizerNodeTest.java
+++ b/openide.explorer/test/unit/src/org/openide/explorer/view/VisualizerNodeTest.java
@@ -89,4 +89,16 @@
assertEquals("Index is 1", 1, ta.getIndex(tm));
}
+
+ public void testIconsAreShared() {
+ AbstractNode a1 = new AbstractNode(Children.LEAF);
+ VisualizerNode v1 = VisualizerNode.getVisualizer(null, a1);
+ Icon icon1 = v1.getIcon(false, false);
+
+ AbstractNode a2 = new AbstractNode(Children.LEAF);
+ VisualizerNode v2 = VisualizerNode.getVisualizer(null, a2);
+ Icon icon2 = v2.getIcon(false, false);
+
+ assertSame("Icon instances should be same", icon1, icon2);
+ }
}
diff --git a/openide.util/apichanges.xml b/openide.util/apichanges.xml
--- a/openide.util/apichanges.xml
+++ b/openide.util/apichanges.xml
@@ -49,6 +49,41 @@
Actions API
+
+
+ Image methods were made deprecated in Utilities, replacement is
+ ImageUtilities class (with additional methods).
+
+
+
+
+
+
+ Image methods were made deprecated in Utilities. Replacement is
+ ImageUtilities
+ (renamed IconManager). It contains also new
+ methods for image tool tips manipulation:
+ New methods for tool tips manipulation:
+ Image assignToolTipToImage(Image image, String text);
+ String getImageToolTip(Image image);
+ Image addToolTipToImage(Image image, String text);
+
+
+ New method for conversion from Image to Icon:
+ Icon image2Icon(Image image);
+
+
+ Deprecated methods in Utilities:
+ Image icon2Image(Icon icon);
+ Image loadImage(String resourceID);
+ Image loadImage(String resource, boolean localized);
+ Image mergeImages(Image image1, Image image2, int x, int y);
+
+
+
+
+
+ Mutex made pluggable
diff --git a/openide.util/nbproject/project.properties b/openide.util/nbproject/project.properties
--- a/openide.util/nbproject/project.properties
+++ b/openide.util/nbproject/project.properties
@@ -41,7 +41,7 @@
javac.source=1.5
module.jar.dir=lib
-spec.version.base=7.13.0
+spec.version.base=7.14.0
# For XMLSerializer, needed for XMLUtil.write to work w/ namespaces under JDK 1.4:
diff --git a/openide.util/src/org/openide/util/IconManager.java b/openide.util/src/org/openide/util/ImageUtilities.java
rename from openide.util/src/org/openide/util/IconManager.java
rename to openide.util/src/org/openide/util/ImageUtilities.java
--- a/openide.util/src/org/openide/util/IconManager.java
+++ b/openide.util/src/org/openide/util/ImageUtilities.java
@@ -42,6 +42,7 @@
package org.openide.util;
import java.awt.Component;
+import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.MediaTracker;
@@ -50,10 +51,13 @@
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@@ -63,18 +67,26 @@
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
-/** Registers all loaded images into the AbstractNode, so nothing is loaded twice.
-*
-* @author Jaroslav Tulach
-*/
-final class IconManager extends Object {
+/**
+ * Useful static methods for manipulation with images/icons, results are cached.
+ *
+ * @author Jaroslav Tulach, Tomas Holy
+ * @since 7.14
+ */
+public final class ImageUtilities {
+ /** separator for individual parts of tool tip text */
+ static final String TOOLTIP_SEPAR = " "; // NOI18N
/** a value that indicates that the icon does not exists */
private static final ActiveRef NO_ICON = new ActiveRef(null, null, null);
private static final Map> cache = new HashMap>(128);
private static final Map> localizedCache = new HashMap>(128);
private static final Map> compositeCache = new HashMap>(128);
+ private static final Map> imageToolTipCache = new HashMap>(128);
/** Resource paths for which we have had to strip initial slash.
* @see "#20072"
@@ -84,7 +96,7 @@
private static Lookup.Result loaderQuery = null;
private static boolean noLoaderWarned = false;
private static final Component component = new Component() {
- };
+ };
private static final MediaTracker tracker = new MediaTracker(component);
private static int mediaTrackerID;
@@ -92,13 +104,161 @@
private static ImageReader PNG_READER;
// private static ImageReader GIF_READER;
- private static final Logger ERR = Logger.getLogger(IconManager.class.getName());
+ private static final Logger ERR = Logger.getLogger(ImageUtilities.class.getName());
+
+ private ImageUtilities() {
+ }
static {
ImageIO.setUseCache(false);
PNG_READER = ImageIO.getImageReadersByMIMEType("image/png").next();
// GIF_READER = ImageIO.getImageReadersByMIMEType("image/gif").next();
}
+
+ /**
+ * Loads an image from the specified resource ID. The image is loaded using the "system" classloader registered in
+ * Lookup.
+ * @param resourceID resource path of the icon (no initial slash)
+ * @return icon's Image, or null, if the icon cannot be loaded.
+ */
+ public static final Image loadImage(String resourceID) {
+ return getIcon(resourceID, false);
+ }
+
+ /**
+ * Loads an image based on resource path.
+ * Exactly like {@link #loadImage(String)} but may do a localized search.
+ * For example, requesting org/netbeans/modules/foo/resources/foo.gif
+ * might actually find org/netbeans/modules/foo/resources/foo_ja.gif
+ * or org/netbeans/modules/foo/resources/foo_mybranding.gif.
+ *
+ *
Caching of loaded images can be used internally to improve performance.
+ *
+ */
+ public static final Image loadImage(String resource, boolean localized) {
+ return getIcon(resource, localized);
+ }
+
+ /** This method merges two images into the new 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.
+ * @param image1 underlying image
+ * @param image2 second image
+ * @param x x position of top-left corner
+ * @param y y position of top-left corner
+ * @return new merged image
+ */
+ public static final Image mergeImages(Image image1, Image image2, int x, int y) {
+ if (image1 == null || image2 == null) {
+ throw new NullPointerException();
+ }
+
+ CompositeImageKey k = new CompositeImageKey(image1, image2, x, y);
+ Image cached;
+
+ synchronized (compositeCache) {
+ ActiveRef r = compositeCache.get(k);
+ if (r != null) {
+ cached = r.get();
+ if (cached != null) {
+ return cached;
+ }
+ }
+ cached = doMergeImages(image1, image2, x, y);
+ compositeCache.put(k, new ActiveRef(cached, compositeCache, k));
+ return cached;
+ }
+ }
+
+ /**
+ * Converts given image to a {@link javax.swing.Icon}.
+ * @param image {@link java.awt.Image} to be converted.
+ * @return icon corresponding {@link javax.swing.Icon}
+ */
+ public static final Icon image2Icon(Image image) {
+ if (image instanceof ToolTipImage) {
+ return (Icon) image;
+ } else {
+ return new ImageIcon(image);
+ }
+ }
+
+ /**
+ * Converts given icon to a {@link java.awt.Image}.
+ *
+ * @param icon {@link javax.swing.Icon} to be converted.
+ */
+ public static final Image icon2Image(Icon icon) {
+ if (icon instanceof ToolTipImage) {
+ return (Image) icon;
+ } else {
+ ToolTipImage image = new ToolTipImage("", icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics g = image.getGraphics();
+ icon.paintIcon(new JLabel(), g, 0, 0);
+ g.dispose();
+ return image;
+ }
+ }
+
+ /**
+ * 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
+ * @return Image with attached tool tip
+ */
+ public static final Image assignToolTipToImage(Image image, String text) {
+ ToolTipImageKey key = new ToolTipImageKey(image, text);
+ Image cached;
+ synchronized (imageToolTipCache) {
+ ActiveRef r = imageToolTipCache.get(key);
+ if (r != null) {
+ cached = r.get();
+ if (cached != null) {
+ return cached;
+ }
+ }
+ cached = ToolTipImage.createNew(text, image);
+ imageToolTipCache.put(key, new ActiveRef(cached, imageToolTipCache, key));
+ return cached;
+ }
+ }
+
+ /**
+ * Get tool tip text for given image
+ * @param image image which is asked for tool tip text
+ * @return String containing attached tool tip text
+ */
+ public static final String getImageToolTip(Image image) {
+ if (image instanceof ToolTipImage) {
+ return ((ToolTipImage) image).toolTipText;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 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
+ */
+ public static final Image addToolTipToImage(Image image, String text) {
+ if (image instanceof ToolTipImage) {
+ ToolTipImage tti = (ToolTipImage) image;
+ StringBuilder str = new StringBuilder(tti.toolTipText);
+ if (str.length() > 0 && text.length() > 0) {
+ str.append(TOOLTIP_SEPAR);
+ }
+ str.append(text);
+ return assignToolTipToImage(image, str.toString());
+ } else {
+ return assignToolTipToImage(image, text);
+ }
+ }
/**
* Get the class loader from lookup.
@@ -184,7 +344,7 @@
// #31008. [PENDING] remove in case package cache is precomputed
java.net.URL baseurl = (loader != null) ? loader.getResource(resource) // NOPMD
- : IconManager.class.getClassLoader().getResource(resource);
+ : ImageUtilities.class.getClassLoader().getResource(resource);
Iterator it = NbBundle.getLocalizingSuffixes();
while (it.hasNext()) {
@@ -199,13 +359,10 @@
if (i != null) {
localizedCache.put(resource, new ActiveRef(i, localizedCache, resource));
-
return i;
}
}
-
localizedCache.put(resource, NO_ICON);
-
return null;
}
} else {
@@ -270,7 +427,7 @@
// we have to load it
java.net.URL url = (loader != null) ? loader.getResource(n)
- : IconManager.class.getClassLoader().getResource(n);
+ : ImageUtilities.class.getClassLoader().getResource(n);
// img = (url == null) ? null : Toolkit.getDefaultToolkit().createImage(url);
Image result = null;
@@ -321,47 +478,17 @@
// Image img2 = toBufferedImage(result);
- ERR.log(Level.FINE,
- "loading icon {0} = {1}", new Object[] {n, result});
+ ERR.log(Level.FINE, "loading icon {0} = {1}", new Object[] {n, result});
name = new String(name).intern(); // NOPMD
-
+ result = ToolTipImage.createNew("", result);
cache.put(name, new ActiveRef(result, cache, name));
-
return result;
} else { // no icon found
-
if (!localizedQuery) {
cache.put(name, NO_ICON);
}
-
return null;
}
- }
- }
-
- /**
- * Method that attempts to find the merged image in the cache first, then
- * creates the image if it was not found.
- */
- static final Image mergeImages(Image im1, Image im2, int x, int y) {
- CompositeImageKey k = new CompositeImageKey(im1, im2, x, y);
- Image cached;
-
- synchronized (compositeCache) {
- ActiveRef r = compositeCache.get(k);
-
- if (r != null) {
- cached = r.get();
-
- if (cached != null) {
- return cached;
- }
- }
-
- cached = doMergeImages(im1, im2, x, y);
- compositeCache.put(k, new ActiveRef(cached, compositeCache, k));
-
- return cached;
}
}
@@ -394,7 +521,6 @@
synchronized (tracker) {
int id = ++mediaTrackerID;
-
tracker.addImage(image, id);
try {
@@ -407,7 +533,7 @@
tracker.removeImage(image, id);
}
}
-
+
private static final Image doMergeImages(Image image1, Image image2, int x, int y) {
ensureLoaded(image1);
ensureLoaded(image2);
@@ -417,8 +543,17 @@
boolean bitmask = (image1 instanceof Transparency) && ((Transparency)image1).getTransparency() != Transparency.TRANSLUCENT
&& (image2 instanceof Transparency) && ((Transparency)image2).getTransparency() != Transparency.TRANSLUCENT;
+ StringBuilder str = new StringBuilder(image1 instanceof ToolTipImage ? ((ToolTipImage)image1).toolTipText : "");
+ if (image2 instanceof ToolTipImage) {
+ String toolTip = ((ToolTipImage)image2).toolTipText;
+ if (str.length() > 0 && toolTip.length() > 0) {
+ str.append(TOOLTIP_SEPAR);
+ }
+ str.append(toolTip);
+ }
+
ColorModel model = colorModel(bitmask? Transparency.BITMASK: Transparency.TRANSLUCENT);
- java.awt.image.BufferedImage buffImage = new java.awt.image.BufferedImage(
+ ToolTipImage buffImage = new ToolTipImage(str.toString(),
model, model.createCompatibleWritableRaster(w, h), model.isAlphaPremultiplied(), null
);
@@ -454,7 +589,6 @@
catch(HeadlessException he) {
model = ColorModel.getRGBdefault();
}
-
return model;
}
@@ -474,6 +608,7 @@
this.overlayImage = overlay;
}
+ @Override
public boolean equals(Object other) {
if (!(other instanceof CompositeImageKey)) {
return false;
@@ -484,6 +619,7 @@
return (x == k.x) && (y == k.y) && (baseImage == k.baseImage) && (overlayImage == k.overlayImage);
}
+ @Override
public int hashCode() {
int hash = ((x << 3) ^ y) << 4;
hash = hash ^ baseImage.hashCode() ^ overlayImage.hashCode();
@@ -491,8 +627,42 @@
return hash;
}
+ @Override
public String toString() {
return "Composite key for " + baseImage + " + " + overlayImage + " at [" + x + ", " + y + "]"; // NOI18N
+ }
+ }
+
+ /**
+ * Key used for ToolTippedImage
+ */
+ private static class ToolTipImageKey {
+ Image image;
+ String str;
+
+ ToolTipImageKey(Image image, String str) {
+ this.image = image;
+ this.str = str;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof ToolTipImageKey)) {
+ return false;
+ }
+ ToolTipImageKey k = (ToolTipImageKey) other;
+ return (str.equals(k.str)) && (image == k.image);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = image.hashCode() ^ str.hashCode();
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return "ImageStringKey for " + image + " + " + str; // NOI18N
}
}
@@ -514,4 +684,58 @@
}
}
// end of ActiveRef
+
+ /**
+ * Image with tool tip text (for icons with badges)
+ */
+ private static class ToolTipImage extends BufferedImage implements Icon {
+ final String toolTipText;
+
+ public static ToolTipImage createNew(String toolTipText, Image image) {
+ ImageUtilities.ensureLoaded(image);
+ boolean bitmask = (image instanceof Transparency) && ((Transparency) image).getTransparency() != Transparency.TRANSLUCENT;
+ ColorModel model = colorModel(bitmask ? Transparency.BITMASK : Transparency.TRANSLUCENT);
+ int w = image.getWidth(null);
+ int h = image.getHeight(null);
+ ToolTipImage newImage = new ToolTipImage(toolTipText, model, model.createCompatibleWritableRaster(w, h), model.isAlphaPremultiplied(), null);
+
+ java.awt.Graphics g = newImage.createGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ return newImage;
+ }
+
+ public ToolTipImage(String toolTipText, ColorModel cm, WritableRaster raster, boolean isRasterPremultiplied, Hashtable, ?> properties) {
+ super(cm, raster, isRasterPremultiplied, properties);
+ this.toolTipText = toolTipText;
+ }
+
+ public ToolTipImage(String toolTipText, int width, int height, int imageType, IndexColorModel cm) {
+ super(width, height, imageType, cm);
+ this.toolTipText = toolTipText;
+ }
+
+ public ToolTipImage(String toolTipText, int width, int height, int imageType) {
+ super(width, height, imageType);
+ this.toolTipText = toolTipText;
+ }
+
+ public ToolTipImage(String toolTipText, BufferedImage image) {
+ super(image.getWidth(), image.getHeight(), image.getType());
+ this.toolTipText = toolTipText;
+ }
+
+
+ public int getIconHeight() {
+ return super.getHeight();
+ }
+
+ public int getIconWidth() {
+ return super.getWidth();
+ }
+
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ g.drawImage(this, x, y, null);
+ }
+ }
}
diff --git a/openide.util/src/org/openide/util/Utilities.java b/openide.util/src/org/openide/util/Utilities.java
--- a/openide.util/src/org/openide/util/Utilities.java
+++ b/openide.util/src/org/openide/util/Utilities.java
@@ -48,7 +48,6 @@
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
-import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
@@ -61,7 +60,6 @@
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
-import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
@@ -99,8 +97,6 @@
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.Icon;
-import javax.swing.ImageIcon;
-import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
@@ -2588,33 +2584,28 @@
* 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).
- * If both images are null, it makes default transparent 16x16 image.
* @param image1 underlying image
* @param image2 second image
* @param x x position of top-left corner
* @param y y position of top-left corner
* @return new merged image
+ * @deprecated Use {@link ImageUtilities#mergeImages}.
*/
+ @Deprecated
public static final Image mergeImages(Image image1, Image image2, int x, int y) {
- if (image1 == null) {
- throw new NullPointerException();
- }
-
- if (image2 == null) {
- throw new NullPointerException();
- }
-
- return IconManager.mergeImages(image1, image2, x, y);
+ return ImageUtilities.mergeImages(image1, image2, x, y);
}
-
+
/**
* Loads an image from the specified resource ID. The image is loaded using the "system" classloader registered in
* Lookup.
* @param resourceID resource path of the icon (no initial slash)
* @return icon's Image, or null, if the icon cannot be loaded.
+ * @deprecated Use {@link ImageUtilities#loadImage(java.lang.String)}.
*/
+ @Deprecated
public static final Image loadImage(String resourceID) {
- return IconManager.getIcon(resourceID, false);
+ return ImageUtilities.loadImage(resourceID);
}
/**
@@ -2622,17 +2613,11 @@
*
* @param icon {@link javax.swing.Icon} to be converted.
* @since 7.3
+ * @deprecated Use {@link ImageUtilities#icon2Image}.
*/
+ @Deprecated
public static final Image icon2Image(Icon icon) {
- if (icon instanceof ImageIcon) {
- return ((ImageIcon) icon).getImage();
- } else {
- BufferedImage bImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
- Graphics g = bImage.getGraphics();
- icon.paintIcon(new JLabel(), g, 0, 0);
- g.dispose();
- return bImage;
- }
+ return ImageUtilities.icon2Image(icon);
}
/** Builds a popup menu from actions for provided context specified by
@@ -2796,9 +2781,11 @@
*
Caching of loaded images can be used internally to improve performance.
*
* @since 3.24
+ * @deprecated Use {@link ImageUtilities#loadImage(java.lang.String, boolean)}.
*/
+ @Deprecated
public static final Image loadImage(String resource, boolean localized) {
- return IconManager.getIcon(resource, localized);
+ return ImageUtilities.loadImage(resource, localized);
}
/**
@@ -2865,7 +2852,7 @@
}
// need to resize the icon
- Image empty = IconManager.createBufferedImage(d.width, d.height);
+ Image empty = ImageUtilities.createBufferedImage(d.width, d.height);
i = Utilities.mergeImages(icon, empty, 0, 0);
}
@@ -3038,14 +3025,17 @@
this.deprecated = deprecated;
}
+ @Override
public Reference