From 6fc216dff5fcd5734923a6ec5e03c46c5736e86d Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 29 Apr 2020 19:22:09 +0200 Subject: [PATCH] Menus: fixed text color of selected menu items that use HTML (issue #87) --- CHANGELOG.md | 1 + .../flatlaf/ui/FlatMenuItemRenderer.java | 68 ++- .../formdev/flatlaf/util/Graphics2DProxy.java | 479 ++++++++++++++++++ .../com/formdev/flatlaf/demo/DemoFrame.java | 5 + .../com/formdev/flatlaf/demo/DemoFrame.jfd | 4 + .../flatlaf/testing/FlatMenusTest.java | 7 +- .../formdev/flatlaf/testing/FlatMenusTest.jfd | 6 +- 7 files changed, 567 insertions(+), 3 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/util/Graphics2DProxy.java 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 ) {