Menus: fixed text color of selected menu items that use HTML (issue #87)

This commit is contained in:
Karl Tauber
2020-04-29 19:22:09 +02:00
parent 3f3961d255
commit 6fc216dff5
7 changed files with 567 additions and 3 deletions

View File

@@ -3,6 +3,7 @@ FlatLaf Change Log
## Unreleased ## Unreleased
- Menus: Fixed text color of selected menu items that use HTML. (issue #87)
- Hide mnemonics if window is deactivated (e.g. <kbd>Alt+Tab</kbd> to another - Hide mnemonics if window is deactivated (e.g. <kbd>Alt+Tab</kbd> to another
window). (issue #43) window). (issue #43)

View File

@@ -23,10 +23,13 @@ import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Paint;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.event.InputEvent; import java.awt.event.InputEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.text.AttributedCharacterIterator;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
@@ -36,6 +39,7 @@ import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML; import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View; import javax.swing.text.View;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.Graphics2DProxy;
/** /**
* Renderer for menu items. * Renderer for menu items.
@@ -273,7 +277,7 @@ debug*/
protected void paintText( Graphics g, Rectangle textRect, String text, Color selectionForeground, Color disabledForeground ) { protected void paintText( Graphics g, Rectangle textRect, String text, Color selectionForeground, Color disabledForeground ) {
View htmlView = (View) menuItem.getClientProperty( BasicHTML.propertyKey ); View htmlView = (View) menuItem.getClientProperty( BasicHTML.propertyKey );
if( htmlView != null ) { if( htmlView != null ) {
htmlView.paint( g, textRect ); paintHTMLText( g, menuItem, textRect, htmlView, isUnderlineSelection() ? null : selectionForeground );
return; return;
} }
@@ -330,6 +334,15 @@ debug*/
g.setFont( oldFont ); 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 ) { protected static boolean isArmedOrSelected( JMenuItem menuItem ) {
return menuItem.isArmed() || (menuItem instanceof JMenu && menuItem.isSelected()); return menuItem.isArmed() || (menuItem instanceof JMenu && menuItem.isSelected());
} }
@@ -427,4 +440,57 @@ debug*/
public void paintIcon( Component c, Graphics g, int x, int y ) { 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 );
}
}
} }

View File

@@ -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();
}
}

View File

@@ -129,6 +129,7 @@ class DemoFrame
JMenuItem projectViewMenuItem = new JMenuItem(); JMenuItem projectViewMenuItem = new JMenuItem();
JMenuItem structureViewMenuItem = new JMenuItem(); JMenuItem structureViewMenuItem = new JMenuItem();
JMenuItem propertiesViewMenuItem = new JMenuItem(); JMenuItem propertiesViewMenuItem = new JMenuItem();
JMenuItem menuItem1 = new JMenuItem();
JRadioButtonMenuItem radioButtonMenuItem1 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem1 = new JRadioButtonMenuItem();
JRadioButtonMenuItem radioButtonMenuItem2 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem2 = new JRadioButtonMenuItem();
JRadioButtonMenuItem radioButtonMenuItem3 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem3 = new JRadioButtonMenuItem();
@@ -314,6 +315,10 @@ class DemoFrame
menu1.add(propertiesViewMenuItem); menu1.add(propertiesViewMenuItem);
} }
viewMenu.add(menu1); viewMenu.add(menu1);
//---- menuItem1 ----
menuItem1.setText("<html>some <b color=\"red\">HTML</b> <i color=\"blue\">text</i></html>");
viewMenu.add(menuItem1);
viewMenu.addSeparator(); viewMenu.addSeparator();
//---- radioButtonMenuItem1 ---- //---- radioButtonMenuItem1 ----

View File

@@ -259,6 +259,10 @@ new FormModel {
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) )
} ) } )
} ) } )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "menuItem1"
"text": "<html>some <b color=\"red\">HTML</b> <i color=\"blue\">text</i></html>"
} )
add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) { add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) {
name: "separator8" name: "separator8"
} ) } )

View File

@@ -132,6 +132,7 @@ public class FlatMenusTest
JMenuItem menuItem7 = new JMenuItem(); JMenuItem menuItem7 = new JMenuItem();
JMenuItem menuItem34 = new JMenuItem(); JMenuItem menuItem34 = new JMenuItem();
JMenuItem menuItem8 = new JMenuItem(); JMenuItem menuItem8 = new JMenuItem();
JMenuItem menuItem38 = new JMenuItem();
JMenu menu11 = new JMenu(); JMenu menu11 = new JMenu();
JMenuItem menuItem36 = new JMenuItem(); JMenuItem menuItem36 = new JMenuItem();
JMenuItem menuItem37 = new JMenuItem(); JMenuItem menuItem37 = new JMenuItem();
@@ -254,6 +255,10 @@ public class FlatMenusTest
menuItem8.setMnemonic('E'); menuItem8.setMnemonic('E');
menu5.add(menuItem8); menu5.add(menuItem8);
//---- menuItem38 ----
menuItem38.setText("<html>some <b color=\"red\">HTML</b> <i color=\"blue\">text</i></html>");
menu5.add(menuItem38);
//======== menu11 ======== //======== menu11 ========
{ {
menu11.setText("sub menu"); menu11.setText("sub menu");
@@ -546,7 +551,7 @@ public class FlatMenusTest
panel1.add(radioButtonMenuItemLabel, "cell 0 3"); panel1.add(radioButtonMenuItemLabel, "cell 0 3");
//---- radioButtonMenuItem1 ---- //---- radioButtonMenuItem1 ----
radioButtonMenuItem1.setText("enabled"); radioButtonMenuItem1.setText("<html>en<b color=\"red\">abl</b><i color=\"blue\">ed</i></html>");
radioButtonMenuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0)); radioButtonMenuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
radioButtonMenuItem1.setMnemonic('B'); radioButtonMenuItem1.setMnemonic('B');
panel1.add(radioButtonMenuItem1, "cell 1 3"); panel1.add(radioButtonMenuItem1, "cell 1 3");

View File

@@ -47,6 +47,10 @@ new FormModel {
"text": "longer text longer text longer" "text": "longer text longer text longer"
"mnemonic": 69 "mnemonic": 69
} ) } )
add( new FormComponent( "javax.swing.JMenuItem" ) {
name: "menuItem38"
"text": "<html>some <b color=\"red\">HTML</b> <i color=\"blue\">text</i></html>"
} )
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
name: "menu11" name: "menu11"
"text": "sub menu" "text": "sub menu"
@@ -354,7 +358,7 @@ new FormModel {
} ) } )
add( new FormComponent( "javax.swing.JRadioButtonMenuItem" ) { add( new FormComponent( "javax.swing.JRadioButtonMenuItem" ) {
name: "radioButtonMenuItem1" name: "radioButtonMenuItem1"
"text": "enabled" "text": "<html>en<b color=\"red\">abl</b><i color=\"blue\">ed</i></html>"
"accelerator": #KeyStroke0 "accelerator": #KeyStroke0
"mnemonic": 66 "mnemonic": 66
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {