diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab2fa1ea..f3d25df1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@ FlatLaf Change Log
## Unreleased
+- Menus: Fixed text color of selected menu items that use HTML. (issue #87)
- Hide mnemonics if window is deactivated (e.g. Alt+Tab to another
window). (issue #43)
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java
index c5f31ebf..0c43583a 100644
--- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatMenuItemRenderer.java
@@ -23,10 +23,13 @@ import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
+import java.awt.Graphics2D;
import java.awt.Insets;
+import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
+import java.text.AttributedCharacterIterator;
import javax.swing.Icon;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
@@ -36,6 +39,7 @@ import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.formdev.flatlaf.FlatLaf;
+import com.formdev.flatlaf.util.Graphics2DProxy;
/**
* Renderer for menu items.
@@ -273,7 +277,7 @@ debug*/
protected void paintText( Graphics g, Rectangle textRect, String text, Color selectionForeground, Color disabledForeground ) {
View htmlView = (View) menuItem.getClientProperty( BasicHTML.propertyKey );
if( htmlView != null ) {
- htmlView.paint( g, textRect );
+ paintHTMLText( g, menuItem, textRect, htmlView, isUnderlineSelection() ? null : selectionForeground );
return;
}
@@ -330,6 +334,15 @@ debug*/
g.setFont( oldFont );
}
+ protected static void paintHTMLText( Graphics g, JMenuItem menuItem,
+ Rectangle textRect, View htmlView, Color selectionForeground )
+ {
+ if( isArmedOrSelected( menuItem ) && selectionForeground != null )
+ g = new GraphicsProxyWithTextColor( (Graphics2D) g, selectionForeground );
+
+ htmlView.paint( g, textRect );
+ }
+
protected static boolean isArmedOrSelected( JMenuItem menuItem ) {
return menuItem.isArmed() || (menuItem instanceof JMenu && menuItem.isSelected());
}
@@ -427,4 +440,57 @@ debug*/
public void paintIcon( Component c, Graphics g, int x, int y ) {
}
}
+
+ //---- class GraphicsProxyWithTextColor -----------------------------------
+
+ private static class GraphicsProxyWithTextColor
+ extends Graphics2DProxy
+ {
+ private final Color textColor;
+
+ GraphicsProxyWithTextColor( Graphics2D delegate, Color textColor ) {
+ super( delegate );
+ this.textColor = textColor;
+ }
+
+ @Override
+ public void drawString( String str, int x, int y ) {
+ Paint oldPaint = getPaint();
+ setPaint( textColor );
+ super.drawString( str, x, y );
+ setPaint( oldPaint );
+ }
+
+ @Override
+ public void drawString( String str, float x, float y ) {
+ Paint oldPaint = getPaint();
+ setPaint( textColor );
+ super.drawString( str, x, y );
+ setPaint( oldPaint );
+ }
+
+ @Override
+ public void drawString( AttributedCharacterIterator iterator, int x, int y ) {
+ Paint oldPaint = getPaint();
+ setPaint( textColor );
+ super.drawString( iterator, x, y );
+ setPaint( oldPaint );
+ }
+
+ @Override
+ public void drawString( AttributedCharacterIterator iterator, float x, float y ) {
+ Paint oldPaint = getPaint();
+ setPaint( textColor );
+ super.drawString( iterator, x, y );
+ setPaint( oldPaint );
+ }
+
+ @Override
+ public void drawChars( char[] data, int offset, int length, int x, int y ) {
+ Paint oldPaint = getPaint();
+ setPaint( textColor );
+ super.drawChars( data, offset, length, x, y );
+ setPaint( oldPaint );
+ }
+ }
}
diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/Graphics2DProxy.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/Graphics2DProxy.java
new file mode 100644
index 00000000..b68d2c69
--- /dev/null
+++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/Graphics2DProxy.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2020 FormDev Software GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.formdev.flatlaf.util;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.RenderingHints.Key;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImageObserver;
+import java.awt.image.RenderedImage;
+import java.awt.image.renderable.RenderableImage;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+/**
+ * A proxy for {@link Graphics2D}.
+ *
+ * @author Karl Tauber
+ */
+public class Graphics2DProxy
+ extends Graphics2D
+{
+ private final Graphics2D delegate;
+
+ public Graphics2DProxy( Graphics2D delegate ) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Graphics create() {
+ return delegate.create();
+ }
+
+ @Override
+ public Graphics create( int x, int y, int width, int height ) {
+ return delegate.create( x, y, width, height );
+ }
+
+ @Override
+ public Color getColor() {
+ return delegate.getColor();
+ }
+
+ @Override
+ public void setColor( Color c ) {
+ delegate.setColor( c );
+ }
+
+ @Override
+ public void setPaintMode() {
+ delegate.setPaintMode();
+ }
+
+ @Override
+ public void setXORMode( Color c1 ) {
+ delegate.setXORMode( c1 );
+ }
+
+ @Override
+ public Font getFont() {
+ return delegate.getFont();
+ }
+
+ @Override
+ public void setFont( Font font ) {
+ delegate.setFont( font );
+ }
+
+ @Override
+ public FontMetrics getFontMetrics() {
+ return delegate.getFontMetrics();
+ }
+
+ @Override
+ public FontMetrics getFontMetrics( Font f ) {
+ return delegate.getFontMetrics( f );
+ }
+
+ @Override
+ public Rectangle getClipBounds() {
+ return delegate.getClipBounds();
+ }
+
+ @Override
+ public void clipRect( int x, int y, int width, int height ) {
+ delegate.clipRect( x, y, width, height );
+ }
+
+ @Override
+ public void setClip( int x, int y, int width, int height ) {
+ delegate.setClip( x, y, width, height );
+ }
+
+ @Override
+ public Shape getClip() {
+ return delegate.getClip();
+ }
+
+ @Override
+ public void setClip( Shape clip ) {
+ delegate.setClip( clip );
+ }
+
+ @Override
+ public void copyArea( int x, int y, int width, int height, int dx, int dy ) {
+ delegate.copyArea( x, y, width, height, dx, dy );
+ }
+
+ @Override
+ public void drawLine( int x1, int y1, int x2, int y2 ) {
+ delegate.drawLine( x1, y1, x2, y2 );
+ }
+
+ @Override
+ public void fillRect( int x, int y, int width, int height ) {
+ delegate.fillRect( x, y, width, height );
+ }
+
+ @Override
+ public void drawRect( int x, int y, int width, int height ) {
+ delegate.drawRect( x, y, width, height );
+ }
+
+ @Override
+ public void clearRect( int x, int y, int width, int height ) {
+ delegate.clearRect( x, y, width, height );
+ }
+
+ @Override
+ public void drawRoundRect( int x, int y, int width, int height, int arcWidth, int arcHeight ) {
+ delegate.drawRoundRect( x, y, width, height, arcWidth, arcHeight );
+ }
+
+ @Override
+ public void fillRoundRect( int x, int y, int width, int height, int arcWidth, int arcHeight ) {
+ delegate.fillRoundRect( x, y, width, height, arcWidth, arcHeight );
+ }
+
+ @Override
+ public void drawOval( int x, int y, int width, int height ) {
+ delegate.drawOval( x, y, width, height );
+ }
+
+ @Override
+ public void fillOval( int x, int y, int width, int height ) {
+ delegate.fillOval( x, y, width, height );
+ }
+
+ @Override
+ public void drawArc( int x, int y, int width, int height, int startAngle, int arcAngle ) {
+ delegate.drawArc( x, y, width, height, startAngle, arcAngle );
+ }
+
+ @Override
+ public void fillArc( int x, int y, int width, int height, int startAngle, int arcAngle ) {
+ delegate.fillArc( x, y, width, height, startAngle, arcAngle );
+ }
+
+ @Override
+ public void drawPolyline( int[] xPoints, int[] yPoints, int nPoints ) {
+ delegate.drawPolyline( xPoints, yPoints, nPoints );
+ }
+
+ @Override
+ public void drawPolygon( int[] xPoints, int[] yPoints, int nPoints ) {
+ delegate.drawPolygon( xPoints, yPoints, nPoints );
+ }
+
+ @Override
+ public void drawPolygon( Polygon p ) {
+ delegate.drawPolygon( p );
+ }
+
+ @Override
+ public void fillPolygon( int[] xPoints, int[] yPoints, int nPoints ) {
+ delegate.fillPolygon( xPoints, yPoints, nPoints );
+ }
+
+ @Override
+ public void fillPolygon( Polygon p ) {
+ delegate.fillPolygon( p );
+ }
+
+ @Override
+ public void drawChars( char[] data, int offset, int length, int x, int y ) {
+ delegate.drawChars( data, offset, length, x, y );
+ }
+
+ @Override
+ public void drawBytes( byte[] data, int offset, int length, int x, int y ) {
+ delegate.drawBytes( data, offset, length, x, y );
+ }
+
+ @Override
+ public boolean drawImage( Image img, int x, int y, ImageObserver observer ) {
+ return delegate.drawImage( img, x, y, observer );
+ }
+
+ @Override
+ public boolean drawImage( Image img, int x, int y, int width, int height, ImageObserver observer ) {
+ return delegate.drawImage( img, x, y, width, height, observer );
+ }
+
+ @Override
+ public boolean drawImage( Image img, int x, int y, Color bgcolor, ImageObserver observer ) {
+ return delegate.drawImage( img, x, y, bgcolor, observer );
+ }
+
+ @Override
+ public boolean drawImage( Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer ) {
+ return delegate.drawImage( img, x, y, width, height, bgcolor, observer );
+ }
+
+ @Override
+ public boolean drawImage( Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer ) {
+ return delegate.drawImage( img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer );
+ }
+
+ @Override
+ public boolean drawImage( Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer ) {
+ return delegate.drawImage( img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer );
+ }
+
+ @Override
+ public void dispose() {
+ delegate.dispose();
+ }
+
+ @Override
+ public void finalize() {
+ delegate.finalize();
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+ @SuppressWarnings( "deprecation" )
+ @Override
+ public Rectangle getClipRect() {
+ return delegate.getClipRect();
+ }
+
+ @Override
+ public boolean hitClip( int x, int y, int width, int height ) {
+ return delegate.hitClip( x, y, width, height );
+ }
+
+ @Override
+ public Rectangle getClipBounds( Rectangle r ) {
+ return delegate.getClipBounds( r );
+ }
+
+ @Override
+ public void draw3DRect( int x, int y, int width, int height, boolean raised ) {
+ delegate.draw3DRect( x, y, width, height, raised );
+ }
+
+ @Override
+ public void fill3DRect( int x, int y, int width, int height, boolean raised ) {
+ delegate.fill3DRect( x, y, width, height, raised );
+ }
+
+ @Override
+ public void draw( Shape s ) {
+ delegate.draw( s );
+ }
+
+ @Override
+ public boolean drawImage( Image img, AffineTransform xform, ImageObserver obs ) {
+ return delegate.drawImage( img, xform, obs );
+ }
+
+ @Override
+ public void drawImage( BufferedImage img, BufferedImageOp op, int x, int y ) {
+ delegate.drawImage( img, op, x, y );
+ }
+
+ @Override
+ public void drawRenderedImage( RenderedImage img, AffineTransform xform ) {
+ delegate.drawRenderedImage( img, xform );
+ }
+
+ @Override
+ public void drawRenderableImage( RenderableImage img, AffineTransform xform ) {
+ delegate.drawRenderableImage( img, xform );
+ }
+
+ @Override
+ public void drawString( String str, int x, int y ) {
+ delegate.drawString( str, x, y );
+ }
+
+ @Override
+ public void drawString( String str, float x, float y ) {
+ delegate.drawString( str, x, y );
+ }
+
+ @Override
+ public void drawString( AttributedCharacterIterator iterator, int x, int y ) {
+ delegate.drawString( iterator, x, y );
+ }
+
+ @Override
+ public void drawString( AttributedCharacterIterator iterator, float x, float y ) {
+ delegate.drawString( iterator, x, y );
+ }
+
+ @Override
+ public void drawGlyphVector( GlyphVector g, float x, float y ) {
+ delegate.drawGlyphVector( g, x, y );
+ }
+
+ @Override
+ public void fill( Shape s ) {
+ delegate.fill( s );
+ }
+
+ @Override
+ public boolean hit( Rectangle rect, Shape s, boolean onStroke ) {
+ return delegate.hit( rect, s, onStroke );
+ }
+
+ @Override
+ public GraphicsConfiguration getDeviceConfiguration() {
+ return delegate.getDeviceConfiguration();
+ }
+
+ @Override
+ public void setComposite( Composite comp ) {
+ delegate.setComposite( comp );
+ }
+
+ @Override
+ public void setPaint( Paint paint ) {
+ delegate.setPaint( paint );
+ }
+
+ @Override
+ public void setStroke( Stroke s ) {
+ delegate.setStroke( s );
+ }
+
+ @Override
+ public void setRenderingHint( Key hintKey, Object hintValue ) {
+ delegate.setRenderingHint( hintKey, hintValue );
+ }
+
+ @Override
+ public Object getRenderingHint( Key hintKey ) {
+ return delegate.getRenderingHint( hintKey );
+ }
+
+ @Override
+ public void setRenderingHints( Map, ?> hints ) {
+ delegate.setRenderingHints( hints );
+ }
+
+ @Override
+ public void addRenderingHints( Map, ?> hints ) {
+ delegate.addRenderingHints( hints );
+ }
+
+ @Override
+ public RenderingHints getRenderingHints() {
+ return delegate.getRenderingHints();
+ }
+
+ @Override
+ public void translate( int x, int y ) {
+ delegate.translate( x, y );
+ }
+
+ @Override
+ public void translate( double tx, double ty ) {
+ delegate.translate( tx, ty );
+ }
+
+ @Override
+ public void rotate( double theta ) {
+ delegate.rotate( theta );
+ }
+
+ @Override
+ public void rotate( double theta, double x, double y ) {
+ delegate.rotate( theta, x, y );
+ }
+
+ @Override
+ public void scale( double sx, double sy ) {
+ delegate.scale( sx, sy );
+ }
+
+ @Override
+ public void shear( double shx, double shy ) {
+ delegate.shear( shx, shy );
+ }
+
+ @Override
+ public void transform( AffineTransform Tx ) {
+ delegate.transform( Tx );
+ }
+
+ @Override
+ public void setTransform( AffineTransform Tx ) {
+ delegate.setTransform( Tx );
+ }
+
+ @Override
+ public AffineTransform getTransform() {
+ return delegate.getTransform();
+ }
+
+ @Override
+ public Paint getPaint() {
+ return delegate.getPaint();
+ }
+
+ @Override
+ public Composite getComposite() {
+ return delegate.getComposite();
+ }
+
+ @Override
+ public void setBackground( Color color ) {
+ delegate.setBackground( color );
+ }
+
+ @Override
+ public Color getBackground() {
+ return delegate.getBackground();
+ }
+
+ @Override
+ public Stroke getStroke() {
+ return delegate.getStroke();
+ }
+
+ @Override
+ public void clip( Shape s ) {
+ delegate.clip( s );
+ }
+
+ @Override
+ public FontRenderContext getFontRenderContext() {
+ return delegate.getFontRenderContext();
+ }
+}
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
index a4498c86..0d0329f0 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java
@@ -129,6 +129,7 @@ class DemoFrame
JMenuItem projectViewMenuItem = new JMenuItem();
JMenuItem structureViewMenuItem = new JMenuItem();
JMenuItem propertiesViewMenuItem = new JMenuItem();
+ JMenuItem menuItem1 = new JMenuItem();
JRadioButtonMenuItem radioButtonMenuItem1 = new JRadioButtonMenuItem();
JRadioButtonMenuItem radioButtonMenuItem2 = new JRadioButtonMenuItem();
JRadioButtonMenuItem radioButtonMenuItem3 = new JRadioButtonMenuItem();
@@ -314,6 +315,10 @@ class DemoFrame
menu1.add(propertiesViewMenuItem);
}
viewMenu.add(menu1);
+
+ //---- menuItem1 ----
+ menuItem1.setText("some HTML text");
+ viewMenu.add(menuItem1);
viewMenu.addSeparator();
//---- radioButtonMenuItem1 ----
diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
index c3df8d44..a6268309 100644
--- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
+++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd
@@ -259,6 +259,10 @@ new FormModel {
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
} )
} )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "menuItem1"
+ "text": "some HTML text"
+ } )
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
name: "separator8"
} )
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java
index 1750ef69..a8436742 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.java
@@ -132,6 +132,7 @@ public class FlatMenusTest
JMenuItem menuItem7 = new JMenuItem();
JMenuItem menuItem34 = new JMenuItem();
JMenuItem menuItem8 = new JMenuItem();
+ JMenuItem menuItem38 = new JMenuItem();
JMenu menu11 = new JMenu();
JMenuItem menuItem36 = new JMenuItem();
JMenuItem menuItem37 = new JMenuItem();
@@ -254,6 +255,10 @@ public class FlatMenusTest
menuItem8.setMnemonic('E');
menu5.add(menuItem8);
+ //---- menuItem38 ----
+ menuItem38.setText("some HTML text");
+ menu5.add(menuItem38);
+
//======== menu11 ========
{
menu11.setText("sub menu");
@@ -546,7 +551,7 @@ public class FlatMenusTest
panel1.add(radioButtonMenuItemLabel, "cell 0 3");
//---- radioButtonMenuItem1 ----
- radioButtonMenuItem1.setText("enabled");
+ radioButtonMenuItem1.setText("enabled");
radioButtonMenuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
radioButtonMenuItem1.setMnemonic('B');
panel1.add(radioButtonMenuItem1, "cell 1 3");
diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd
index 5e30b3da..d4eb8f32 100644
--- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd
+++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatMenusTest.jfd
@@ -47,6 +47,10 @@ new FormModel {
"text": "longer text longer text longer"
"mnemonic": 69
} )
+ add( new FormComponent( "javax.swing.JMenuItem" ) {
+ name: "menuItem38"
+ "text": "some HTML text"
+ } )
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "menu11"
"text": "sub menu"
@@ -354,7 +358,7 @@ new FormModel {
} )
add( new FormComponent( "javax.swing.JRadioButtonMenuItem" ) {
name: "radioButtonMenuItem1"
- "text": "enabled"
+ "text": "enabled"
"accelerator": #KeyStroke0
"mnemonic": 66
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {