1919import  org .eclipse .swt .graphics .*;
2020import  org .eclipse .swt .internal .*;
2121import  org .eclipse .swt .internal .win32 .*;
22+ import  org .eclipse .swt .internal .win32 .version .*;
2223
2324/** 
2425 * Instances of this class represent a selectable user interface object 
4243public  class  MenuItem  extends  Item  {
4344	Menu  parent , menu ;
4445	long  hBitmap ;
46+ 	Image  imageSelected ;
47+ 	long  hBitmapSelected ;
4548	int  id , accelerator , userId ;
4649	ToolTip  itemToolTip ;
4750	/* Image margin. */ 
@@ -53,6 +56,11 @@ public class MenuItem extends Item {
5356	// value in wmMeasureChild is increased by a fixed value (in points) when wmDrawChild is called 
5457	// This static is used to mitigate this increase 
5558	private  final  static  int  WINDOWS_OVERHEAD  = 6 ;
59+ 	// Workaround for: selection indicator is missing for menu item with image on Win11 (#501) 
60+ 	// 0= off/system behavior; 1= no image if selected; 2= with overlay marker (default) 
61+ 	private  final  static  int  CUSTOM_SELECTION_IMAGE  = (OsVersion .IS_WIN11_21H2 ) ?
62+ 			Integer .getInteger ("org.eclipse.swt.internal.win32.menu.customSelectionImage" , 2 ) : 0 ;
63+ 
5664	static  {
5765		DPIZoomChangeRegistry .registerHandler (MenuItem ::handleDPIChange , MenuItem .class );
5866	}
@@ -543,6 +551,12 @@ void releaseWidget () {
543551	super .releaseWidget  ();
544552	if  (hBitmap  != 0 ) OS .DeleteObject  (hBitmap );
545553	hBitmap  = 0 ;
554+ 	if  (hBitmapSelected  != 0 ) OS .DeleteObject  (hBitmapSelected );
555+ 	hBitmapSelected  = 0 ;
556+ 	if  (imageSelected  != null ) {
557+ 		imageSelected .dispose ();
558+ 		imageSelected  = null ;
559+ 	}
546560	if  (accelerator  != 0 ) {
547561		parent .destroyAccelerators  ();
548562	}
@@ -774,14 +788,34 @@ public void setImage (Image image) {
774788	if  (this .image  == image ) return ;
775789	if  ((style  & SWT .SEPARATOR ) != 0 ) return ;
776790	super .setImage  (image );
791+ 	if  (imageSelected  != null ) {
792+ 		imageSelected .dispose ();
793+ 		imageSelected  = null ;
794+ 	}
795+ 	if  ((style  & (SWT .CHECK  | SWT .RADIO )) != 0  && CUSTOM_SELECTION_IMAGE  > 1 
796+ 			&& image  != null  && getSelection ()) {
797+ 		initCustomSelectedImage ();
798+ 	}
799+ 	updateImage ();
800+ }
801+ 
802+ private  void  updateImage  () {
777803	MENUITEMINFO  info  = new  MENUITEMINFO  ();
778804	info .cbSize  = MENUITEMINFO .sizeof ;
779805	info .fMask  = OS .MIIM_BITMAP ;
780806	if  (parent .needsMenuCallback ()) {
781807		info .hbmpItem  = OS .HBMMENU_CALLBACK ;
782808	} else  {
783809		if  (OS .IsAppThemed  ()) {
784- 			info .hbmpItem  = hBitmap  = getMenuItemIconBitmapHandle (image );
810+ 			hBitmap  = getMenuItemIconBitmapHandle (image );
811+ 			if  ((style  & (SWT .CHECK  | SWT .RADIO )) != 0  && CUSTOM_SELECTION_IMAGE  > 0 ) {
812+ 				info .fMask  |= OS .MIIM_CHECKMARKS ;
813+ 				info .hbmpUnchecked  = hBitmap ;
814+ 				info .hbmpChecked  = getMenuItemIconSelectedBitmapHandle ();
815+ 			}
816+ 			else  {
817+ 				info .hbmpItem  = hBitmap ;
818+ 			}
785819		} else  {
786820			info .hbmpItem  = image  != null  ? OS .HBMMENU_CALLBACK  : 0 ;
787821		}
@@ -791,16 +825,92 @@ public void setImage (Image image) {
791825	parent .redraw  ();
792826}
793827
828+ private  void  initCustomSelectedImage () {
829+ 	Image  image  = this .image ;
830+ 	if  (image  == null ) {
831+ 		return ;
832+ 	}
833+ 	Rectangle  imageBounds  = image .getBounds ();
834+ 	Color  foregroundColor  = increaseContrast ((display .menuBarForegroundPixel  != -1 ) ? Color .win32_new  (this .display , display .menuBarForegroundPixel ) : parent .getForeground ());
835+ 	Color  backgroundColor  = increaseContrast ((display .menuBarBackgroundPixel  != -1 ) ? Color .win32_new  (this .display , display .menuBarBackgroundPixel ) : parent .getBackground ());
836+ 	ImageGcDrawer  drawer  = new  ImageGcDrawer () {
837+ 		@ Override 
838+ 		public  int  getGcStyle () {
839+ 			return  SWT .TRANSPARENT ;
840+ 		}
841+ 
842+ 		@ Override 
843+ 		public  void  drawOn (GC  gc , int  imageWidth , int  imageHeight ) {
844+ 			gc .setAdvanced (true );
845+ 			gc .drawImage (image , imageWidth  - imageBounds .width , (imageHeight  - imageBounds .height ) / 2 );
846+ 			gc .setAntialias (SWT .ON );
847+ 			int  x  = imageWidth  - 16 ;
848+ 			int  y  = imageHeight  / 2  - 8 ;
849+ 			if  ((style  & SWT .CHECK ) != 0 ) {
850+ 				drawCheck (gc , foregroundColor , backgroundColor , x , y );
851+ 			}
852+ 			else  {
853+ 				drawRadio (gc , foregroundColor , backgroundColor , x , y );
854+ 			}
855+ 		}
856+ 	};
857+ 	imageSelected  = new  Image (image .getDevice (), drawer ,
858+ 			Math .max (imageBounds .width , 16 ), Math .max (imageBounds .height , 16 ));
859+ }
860+ 
861+ private  void  drawCheck (GC  gc , Color  foregroundColor , Color  backgroundColor , int  x , int  y ) {
862+ 	int [] points  = new  int [] { x  + 4 , y  + 10 , x  + 6 , y  + 12 , x  + 12 , y  + 6  };
863+ 	gc .setLineStyle (SWT .LINE_SOLID );
864+ 	gc .setForeground (backgroundColor );
865+ 	gc .setLineCap (SWT .CAP_ROUND );
866+ 	gc .setLineJoin (SWT .JOIN_ROUND );
867+ 	gc .setAlpha (127 );
868+ 	gc .setLineWidth (6 );
869+ 	gc .drawPolyline (points );
870+ 	gc .setLineJoin (SWT .JOIN_MITER );
871+ 	gc .setAlpha (255 );
872+ 	gc .setLineWidth (3 );
873+ 	gc .drawPolyline (points );
874+ 	gc .setForeground (foregroundColor );
875+ 	gc .setLineWidth (1 );
876+ 	gc .setLineCap (SWT .CAP_FLAT );
877+ 	gc .drawPolyline (points );
878+ }
879+ 
880+ private  void  drawRadio (GC  gc , Color  foregroundColor , Color  backgroundColor , int  x , int  y ) {
881+ 	gc .setBackground (backgroundColor );
882+ 	gc .setAlpha (127 );
883+ 	gc .fillOval (x  + 4 , y  + 5 , 8 , 8 );
884+ 	gc .setAlpha (255 );
885+ 	gc .fillOval (x  + 5 , y  + 6 , 6 , 6 );
886+ 	gc .setBackground (foregroundColor );
887+ 	gc .fillOval (x  + 6 , y  + 7 , 4 , 4 );
888+ }
889+ 
890+ private  Color  increaseContrast (Color  color ) {
891+ 	return  (color .getRed () + color .getGreen () + color .getBlue () > 127  * 3 ) ? display .getSystemColor (SWT .COLOR_WHITE ) : color ;
892+ }
893+ 
794894private  long  getMenuItemIconBitmapHandle (Image  image ) {
795895	if  (image  == null ) {
796896		return  0 ;
797897	}
798898	if  (hBitmap  != 0 ) OS .DeleteObject  (hBitmap );
799- 	int  zoom  = adaptZoomForMenuItem (getZoom ());
899+ 	int  zoom  = adaptZoomForMenuItem (getZoom (),  image );
800900	return  Display .create32bitDIB  (image , zoom );
801901}
802902
803- private  int  adaptZoomForMenuItem (int  currentZoom ) {
903+ private  long  getMenuItemIconSelectedBitmapHandle () {
904+ 	Image  image  = imageSelected ;
905+ 	if  (image  == null ) {
906+ 		return  0 ;
907+ 	}
908+ 	if  (hBitmapSelected  != 0 ) OS .DeleteObject  (hBitmapSelected );
909+ 	int  zoom  = adaptZoomForMenuItem (getZoom (), image );
910+ 	return  hBitmapSelected  = Display .create32bitDIB  (image , zoom );
911+ }
912+ 
913+ private  int  adaptZoomForMenuItem (int  currentZoom , Image  image ) {
804914	int  primaryMonitorZoomAtAppStartUp  = getPrimaryMonitorZoomAtStartup ();
805915	/* 
806916	 * Windows has inconsistent behavior when setting the size of MenuItem image and 
@@ -985,6 +1095,14 @@ public void setSelection (boolean selected) {
9851095	if  (!success ) error  (SWT .ERROR_CANNOT_SET_SELECTION );
9861096	info .fState  &= ~OS .MFS_CHECKED ;
9871097	if  (selected ) info .fState  |= OS .MFS_CHECKED ;
1098+ 
1099+ 	if  (selected  && CUSTOM_SELECTION_IMAGE  > 1  && hBitmap  != 0  && imageSelected  == null ) {
1100+ 		initCustomSelectedImage ();
1101+ 		info .fMask  |= OS .MIIM_CHECKMARKS ;
1102+ 		info .hbmpUnchecked  = hBitmap ;
1103+ 		info .hbmpChecked  = getMenuItemIconSelectedBitmapHandle ();
1104+ 	}
1105+ 
9881106	success  = OS .SetMenuItemInfo  (hMenu , id , false , info );
9891107	if  (!success ) {
9901108		/* 
@@ -1350,12 +1468,9 @@ private static void handleDPIChange(Widget widget, int newZoom, float scalingFac
13501468	if  (!(widget  instanceof  MenuItem  menuItem )) {
13511469		return ;
13521470	}
1353- 	// Refresh the image 
1354- 	Image  menuItemImage  = menuItem .getImage ();
1355- 	if  (menuItemImage  != null ) {
1356- 		Image  currentImage  = menuItemImage ;
1357- 		menuItem .image  = null ;
1358- 		menuItem .setImage  (currentImage );
1471+ 	// Refresh the image(s) 
1472+ 	if  (menuItem .getImage () != null ) {
1473+ 		((MenuItem )menuItem ).updateImage ();
13591474	}
13601475	// Refresh the sub menu 
13611476	Menu  subMenu  = menuItem .getMenu ();
0 commit comments