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


The following commit(s) were added to refs/heads/master by this push:
     new 8d39338e2a2 Adjust the Icon implementation in ImageUtilities to make 
SVG/HiDPI icons work in the MacOS menu bar.
8d39338e2a2 is described below

commit 8d39338e2a243b03deefd27a7fa6a1da621be04f
Author: Eirik Bakke <[email protected]>
AuthorDate: Sat Dec 2 18:41:33 2023 -0500

    Adjust the Icon implementation in ImageUtilities to make SVG/HiDPI icons 
work in the MacOS menu bar.
---
 platform/openide.util.ui/manifest.mf               |  1 -
 .../openide.util.ui/nbproject/project.properties   |  3 +-
 .../src/org/openide/util/ImageUtilities.java       | 87 +++++++++++++++----
 .../unit/src/org/openide/util/UtilitiesTest.java   | 98 +---------------------
 4 files changed, 76 insertions(+), 113 deletions(-)

diff --git a/platform/openide.util.ui/manifest.mf 
b/platform/openide.util.ui/manifest.mf
index 1d8866d38e0..a2a1c97ac14 100644
--- a/platform/openide.util.ui/manifest.mf
+++ b/platform/openide.util.ui/manifest.mf
@@ -2,4 +2,3 @@ Manifest-Version: 1.0
 OpenIDE-Module: org.openide.util.ui
 OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties
 OpenIDE-Module-Specification-Version: 9.34
-
diff --git a/platform/openide.util.ui/nbproject/project.properties 
b/platform/openide.util.ui/nbproject/project.properties
index 5ba993f13de..d56fbb7147a 100644
--- a/platform/openide.util.ui/nbproject/project.properties
+++ b/platform/openide.util.ui/nbproject/project.properties
@@ -16,8 +16,7 @@
 # under the License.
 
 javac.compilerargs=-Xlint -Xlint:-serial -Xlint:-processing
-javac.source=1.8
-javac.target=1.8
+javac.release=11
 module.jar.dir=lib
 
 
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 d66dd82b633..526f659594a 100644
--- a/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
+++ b/platform/openide.util.ui/src/org/openide/util/ImageUtilities.java
@@ -34,6 +34,7 @@ import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.ImageObserver;
+import java.awt.image.MultiResolutionImage;
 import java.awt.image.RGBImageFilter;
 import java.awt.image.WritableRaster;
 import java.io.IOException;
@@ -46,6 +47,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -322,10 +324,9 @@ public final class ImageUtilities {
      * @return icon corresponding icon
      */    
     public static final Icon image2Icon(Image image) {
-        /* Make sure to always return a ToolTipImage, to take advantage of its 
rendering tweaks for
-        HiDPI screens. */
-        return (image instanceof ToolTipImage)
+        ToolTipImage ret = (image instanceof ToolTipImage)
                 ? (ToolTipImage) image : assignToolTipToImageInternal(image, 
"");
+        return ret.asImageIconIfRequiredForRetina();
     }
     
     /**
@@ -376,8 +377,9 @@ public final class ImageUtilities {
             // so let's try second most used one type, it satisfies 
AbstractButton, JCheckbox. Not all cases are
             // covered, however.
             icon.paintIcon(dummyIconComponentButton, g, 0, 0);
+        } finally {
+            g.dispose();
         }
-        g.dispose();
         return image;
     }
     
@@ -1049,17 +1051,12 @@ public final class ImageUtilities {
         it volatile instead, to be completely sure that the class is still 
thread-safe. */
         private volatile Icon delegate;
 
-        private IconImageIcon(Icon delegate) {
-            super(icon2Image(delegate));
+        IconImageIcon(ToolTipImage delegate) {
+            super(delegate);
             Parameters.notNull("delegate", delegate);
             this.delegate = delegate;
         }
 
-        private static ImageIcon create(Icon delegate) {
-            return (delegate instanceof ImageIcon)
-                    ? (ImageIcon) delegate : new IconImageIcon(delegate);
-        }
-
         @Override
         public synchronized void paintIcon(Component c, Graphics g, int x, int 
y) {
             delegate.paintIcon(c, g, x, y);
@@ -1089,9 +1086,15 @@ public final class ImageUtilities {
     }
 
     /**
-     * Image with tool tip text (for icons with badges)
+     * Image with tool tip text (for icons with badges).
+     *
+     * <p>On MacOS, HiDPI (Retina) support in JMenuItem.setIcon(Icon) requires 
the Icon argument to
+     * be an instance of ImageIcon wrapping a MultiResolutionImage (see
+     * com.apple.laf.ScreenMenuIcon.setIcon, 
com.apple.laf.AquaIcon.getImageForIcon, and
+     * sun.lwawt.macosx.CImage.Creator.createFromImage). Thus we have this 
class implement
+     * MultiResolutionImage, and use asImageIcon when needed via 
asImageIconIfRequiredForRetina.
      */
-    private static class ToolTipImage extends BufferedImage implements Icon {
+    private static class ToolTipImage extends BufferedImage implements Icon, 
MultiResolutionImage {
         final String toolTipText;
         // May be null.
         final Icon delegateIcon;
@@ -1099,6 +1102,8 @@ public final class ImageUtilities {
         final URL url;
         // May be null.
         ImageIcon imageIconVersion;
+        // May be null.
+        volatile BufferedImage doubleSizeVariant;
 
         public static ToolTipImage createNew(String toolTipText, Image image, 
URL url) {
             ImageUtilities.ensureLoaded(image);
@@ -1137,9 +1142,16 @@ public final class ImageUtilities {
         }
 
         public synchronized ImageIcon asImageIcon() {
-          if (imageIconVersion == null)
-            imageIconVersion = IconImageIcon.create(this);
-          return imageIconVersion;
+            if (imageIconVersion == null) {
+                imageIconVersion = new IconImageIcon(this);
+            }
+            return imageIconVersion;
+        }
+
+        public Icon asImageIconIfRequiredForRetina() {
+            /* We could choose to do this only on MacOS, but doing it on all 
platforms will lower
+            the chance of undetected platform-specific bugs. */
+            return delegateIcon != null ? asImageIcon() : this;
         }
 
         /**
@@ -1237,6 +1249,49 @@ public final class ImageUtilities {
             }
             return super.getProperty(name, observer);
         }
+
+        private Image getDoubleSizeVariant() {
+          if (delegateIcon == null) {
+              return null;
+          }
+          BufferedImage ret = doubleSizeVariant;
+          if (ret == null) {
+              int SCALE = 2;
+              ColorModel model = getColorModel();
+              int w = delegateIcon.getIconWidth()  * SCALE;
+              int h = delegateIcon.getIconHeight() * SCALE;
+              ret = new BufferedImage(
+                    model,
+                    model.createCompatibleWritableRaster(w, h),
+                    model.isAlphaPremultiplied(), null);
+              Graphics g = ret.createGraphics();
+              try {
+                  ((Graphics2D) 
g).transform(AffineTransform.getScaleInstance(SCALE, SCALE));
+                  delegateIcon.paintIcon(dummyIconComponentLabel, g, 0, 0);
+              } finally {
+                  g.dispose();
+              }
+              doubleSizeVariant = ret;
+          }
+          return ret;
+        }
+
+        @Override
+        public Image getResolutionVariant(double destImageWidth, double 
destImageHeight) {
+            if (destImageWidth <= getWidth(null) && destImageHeight <= 
getHeight(null)) {
+                /* Returning "this" should be safe here, as the same is done in
+                sun.awt.image.MultiResolutionToolkitImage. */
+                return this;
+            }
+            Image ds = getDoubleSizeVariant();
+            return ds != null ? ds : this;
+        }
+
+        @Override
+        public List<Image> getResolutionVariants() {
+            Image ds = getDoubleSizeVariant();
+            return ds == null ? List.of(this) : List.of(this, ds);
+        }
     }
 
     private static final class DisabledButtonFilter extends RGBImageFilter {
diff --git 
a/platform/openide.util.ui/test/unit/src/org/openide/util/UtilitiesTest.java 
b/platform/openide.util.ui/test/unit/src/org/openide/util/UtilitiesTest.java
index 421d39d1fe1..840a5e7b94c 100644
--- a/platform/openide.util.ui/test/unit/src/org/openide/util/UtilitiesTest.java
+++ b/platform/openide.util.ui/test/unit/src/org/openide/util/UtilitiesTest.java
@@ -55,7 +55,7 @@ import java.awt.Window;
 import java.awt.datatransfer.Clipboard;
 import java.awt.dnd.DragGestureEvent;
 import java.awt.dnd.InvalidDnDOperationException;
-import java.awt.dnd.peer.DragSourceContextPeer;
+//import java.awt.dnd.peer.DragSourceContextPeer;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
@@ -64,6 +64,7 @@ import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.ImageObserver;
 import java.awt.image.ImageProducer;
+/*
 import java.awt.peer.ButtonPeer;
 import java.awt.peer.CanvasPeer;
 import java.awt.peer.CheckboxMenuItemPeer;
@@ -86,6 +87,7 @@ import java.awt.peer.ScrollbarPeer;
 import java.awt.peer.TextAreaPeer;
 import java.awt.peer.TextFieldPeer;
 import java.awt.peer.WindowPeer;
+*/
 import java.beans.PropertyChangeListener;
 import java.net.URL;
 import java.util.ArrayList;
@@ -320,28 +322,12 @@ public class UtilitiesTest extends NbTestCase {
             return customToolkit;
         }
     }
-    
+
     private static class NoCustomCursorToolkit extends Toolkit {
         public FontMetrics getFontMetrics(Font font) {
             return Toolkit.getDefaultToolkit().getFontMetrics( font );
         }
 
-        protected TextFieldPeer createTextField(TextField target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected ListPeer createList(java.awt.List target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected MenuBarPeer createMenuBar(MenuBar target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        public DragSourceContextPeer 
createDragSourceContextPeer(DragGestureEvent dge) throws 
InvalidDnDOperationException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
         public boolean prepareImage(Image image, int width, int height, 
ImageObserver observer) {
             return Toolkit.getDefaultToolkit().prepareImage( image, width, 
height, observer );
         }
@@ -350,30 +336,14 @@ public class UtilitiesTest extends NbTestCase {
             return Toolkit.getDefaultToolkit().checkImage( image, width, 
height, observer );
         }
 
-        protected PopupMenuPeer createPopupMenu(PopupMenu target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
         public PrintJob getPrintJob(Frame frame, String jobtitle, Properties 
props) {
             return Toolkit.getDefaultToolkit().getPrintJob( frame, jobtitle, 
props );
         }
 
-        protected ButtonPeer createButton(Button target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
         public Image createImage(ImageProducer producer) {
             return Toolkit.getDefaultToolkit().createImage( producer );
         }
 
-        protected CanvasPeer createCanvas(Canvas target) {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected ScrollbarPeer createScrollbar(Scrollbar target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
         public Image getImage(String filename) {
             return Toolkit.getDefaultToolkit().getImage( filename );
         }
@@ -382,14 +352,6 @@ public class UtilitiesTest extends NbTestCase {
             return Toolkit.getDefaultToolkit().createImage( filename );
         }
 
-        protected MenuPeer createMenu(Menu target) throws HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected MenuItemPeer createMenuItem(MenuItem target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
         public Map mapInputMethodHighlight(InputMethodHighlight highlight) 
throws HeadlessException {
             return Toolkit.getDefaultToolkit().mapInputMethodHighlight( 
highlight );
         }
@@ -402,58 +364,10 @@ public class UtilitiesTest extends NbTestCase {
             return Toolkit.getDefaultToolkit().getImage( url );
         }
 
-        protected CheckboxPeer createCheckbox(Checkbox target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
         public Image createImage(URL url) {
             return Toolkit.getDefaultToolkit().createImage( url );
         }
 
-        protected TextAreaPeer createTextArea(TextArea target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected FileDialogPeer createFileDialog(FileDialog target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected ScrollPanePeer createScrollPane(ScrollPane target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected DialogPeer createDialog(Dialog target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected PanelPeer createPanel(Panel target) {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected ChoicePeer createChoice(Choice target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected FramePeer createFrame(Frame target) throws HeadlessException 
{
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected LabelPeer createLabel(Label target) throws HeadlessException 
{
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected FontPeer getFontPeer(String name, int style) {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected CheckboxMenuItemPeer createCheckboxMenuItem(CheckboxMenuItem 
target) throws HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
-        protected WindowPeer createWindow(Window target) throws 
HeadlessException {
-            throw new IllegalStateException("Method not implemented");
-        }
-
         public void sync() {
             Toolkit.getDefaultToolkit().sync();
         }
@@ -503,10 +417,6 @@ public class UtilitiesTest extends NbTestCase {
             return new Dimension(0,0);
         }
 
-        protected DesktopPeer createDesktopPeer(Desktop target) throws 
HeadlessException {
-            throw new UnsupportedOperationException("Not supported yet.");
-        }
-
         @Override
         public boolean isModalityTypeSupported(ModalityType modalityType) {
             throw new UnsupportedOperationException("Not supported yet.");


---------------------------------------------------------------------
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

Reply via email to