@@ -47,7 +47,12 @@ public class MenuItem extends Item {
4747 /* Image margin. */
4848 final static int MARGIN_WIDTH = 1 ;
4949 final static int MARGIN_HEIGHT = 1 ;
50-
50+ private final static int LEFT_TEXT_MARGIN = 7 ;
51+ private final static int IMAGE_TEXT_GAP = 3 ;
52+ // There is a weird behavior in the Windows API with menus in OWENERDRAW mode that the returned
53+ // value in wmMeasureChild is increased by a fixed value (in points) when wmDrawChild is called
54+ // This static is used to mitigate this increase
55+ private final static int WINDOWS_OVERHEAD = 6 ;
5156 static {
5257 DPIZoomChangeRegistry .registerHandler (MenuItem ::handleDPIChange , MenuItem .class );
5358 }
@@ -1121,10 +1126,32 @@ LRESULT wmCommandChild (long wParam, long lParam) {
11211126 return null ;
11221127}
11231128
1124- LRESULT wmDrawChild (long wParam , long lParam ) {
1125- DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT ();
1126- OS .MoveMemory (struct , lParam , DRAWITEMSTRUCT .sizeof );
1127- if (image != null ) {
1129+ @ Override
1130+ GC createNewGC (long hDC , GCData data ) {
1131+ if (getDisplay ().isRescalingAtRuntime ()) {
1132+ return super .createNewGC (hDC , data );
1133+ } else {
1134+ data .nativeZoom = getMonitorZoom ();
1135+ return GC .win32_new (hDC , data );
1136+ }
1137+ }
1138+
1139+ private int getMonitorZoom () {
1140+ return getMenu ().getShell ().getMonitor ().zoom ;
1141+ }
1142+
1143+ private int getMenuZoom () {
1144+ if (getDisplay ().isRescalingAtRuntime ()) {
1145+ return super .getZoom ();
1146+ } else {
1147+ return DPIUtil .getZoomForAutoscaleProperty (getMonitorZoom ());
1148+ }
1149+ }
1150+
1151+ LRESULT wmDrawChild (long wParam , long lParam ) {
1152+ DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT ();
1153+ OS .MoveMemory (struct , lParam , DRAWITEMSTRUCT .sizeof );
1154+ if ((text != null || image != null )) {
11281155 GCData data = new GCData ();
11291156 data .device = display ;
11301157 GC gc = createNewGC (struct .hDC , data );
@@ -1134,14 +1161,50 @@ LRESULT wmDrawChild (long wParam, long lParam) {
11341161 * coordinate. The fix is to ignore this value when
11351162 * the item is in a menu bar.
11361163 */
1137- int x = (parent .style & SWT .BAR ) != 0 ? MARGIN_WIDTH * 2 : struct .left ;
1138- Image image = getEnabled () ? this .image : new Image (display , this .image , SWT .IMAGE_DISABLE );
1139- int zoom = getZoom ();
1140- gc .drawImage (image , DPIUtil .scaleDown (x , zoom ), DPIUtil .scaleDown (struct .top + MARGIN_HEIGHT , zoom ));
1141- if (this .image != image ) image .dispose ();
1142- gc .dispose ();
1164+ int x = (parent .style & SWT .BAR ) == 0 ? MARGIN_WIDTH * 2 : struct .left ;
1165+ int zoom = getMenuZoom ();
1166+ Rectangle menuItemArea = null ;
1167+ if (text != null ) {
1168+ this .getParent ().redraw ();
1169+ int flags = SWT .DRAW_DELIMITER ;
1170+ boolean isInactive = ((struct .itemState & OS .ODS_INACTIVE ) != 0 );
1171+ boolean isSelected = ((struct .itemState & OS .ODS_SELECTED ) != 0 );
1172+ boolean isNoAccel = ((struct .itemState & OS .ODS_NOACCEL ) != 0 );
1173+
1174+ String drawnText = "" ;
1175+ if (isNoAccel ) {
1176+ drawnText = this .text .replace ("&" , "" );
1177+ } else {
1178+ drawnText = this .text ;
1179+ flags |= SWT .DRAW_MNEMONIC ;
1180+ }
1181+ Rectangle menuItemBounds = this .getBounds ();
1182+
1183+ int fillMenuWidth = DPIUtil .scaleDown (menuItemBounds .width , zoom );
1184+ int fillMenuHeight = DPIUtil .scaleDown (menuItemBounds .height , zoom );
1185+ menuItemArea = new Rectangle (DPIUtil .scaleDown (x , zoom ), DPIUtil .scaleDown (struct .top , zoom ), fillMenuWidth , fillMenuHeight );
1186+
1187+ gc .setForeground (isInactive ? display .getSystemColor (SWT .COLOR_GRAY ) : display .getSystemColor (SWT .COLOR_WHITE ));
1188+ gc .setBackground (isSelected ? display .getSystemColor (SWT .COLOR_DARK_GRAY ) : parent .getBackground ());
1189+ gc .fillRectangle (menuItemArea );
1190+
1191+ int xPositionText = LEFT_TEXT_MARGIN + DPIUtil .scaleDown (x , zoom ) + (this .image != null ? this .image .getBounds ().width + IMAGE_TEXT_GAP : 0 );
1192+ int yPositionText = DPIUtil .scaleDown (struct .top , zoom ) + MARGIN_HEIGHT ;
1193+ gc .drawText (drawnText , xPositionText , yPositionText , flags );
1194+ }
1195+ if (image != null ) {
1196+ Image image = getEnabled () ? this .image : new Image (display , this .image , SWT .IMAGE_DISABLE );
1197+ int gap = (menuItemArea .height - image .getBounds ().height ) / 2 ;
1198+ gc .drawImage (image , LEFT_TEXT_MARGIN + DPIUtil .scaleDown (x , zoom ), gap + DPIUtil .scaleDown (struct .top , zoom ));
1199+ if (this .image != image ) {
1200+ image .dispose ();
1201+ }
1202+ }
1203+ gc .dispose ();
1204+ }
1205+ if (parent .foreground != -1 ) {
1206+ OS .SetTextColor (struct .hDC , parent .foreground );
11431207 }
1144- if (parent .foreground != -1 ) OS .SetTextColor (struct .hDC , parent .foreground );
11451208 return null ;
11461209}
11471210
@@ -1151,17 +1214,19 @@ LRESULT wmMeasureChild (long wParam, long lParam) {
11511214
11521215 if ((parent .style & SWT .BAR ) != 0 ) {
11531216 if (parent .needsMenuCallback ()) {
1217+ Point point = calculateRenderedTextSize ();
1218+ int menuZoom = getDisplay ().isRescalingAtRuntime () ? super .getZoom () : getMonitorZoom ();
1219+ struct .itemHeight = DPIUtil .scaleUp (point .y , menuZoom );
11541220 /*
11551221 * Weirdness in Windows. Setting `HBMMENU_CALLBACK` causes
11561222 * item sizes to mean something else. It seems that it is
11571223 * the size of left margin before the text. At the same time,
11581224 * if menu item has a mnemonic, it's always drawn at a fixed
11591225 * position. I have tested on Win7, Win8.1, Win10 and found
11601226 * that value of 5 works well in matching text to mnemonic.
1161- * NOTE: autoScaleUpUsingNativeDPI() is used to avoid problems
1162- * with applications that disable automatic scaling.
11631227 */
1164- struct .itemWidth = DPIUtil .scaleUp (5 , nativeZoom );
1228+ int horizontalSpaceImage = this .image != null ? this .image .getBounds ().width + IMAGE_TEXT_GAP : 0 ;
1229+ struct .itemWidth = DPIUtil .scaleUp (LEFT_TEXT_MARGIN + point .x - WINDOWS_OVERHEAD + horizontalSpaceImage , menuZoom );
11651230 OS .MoveMemory (lParam , struct , MEASUREITEMSTRUCT .sizeof );
11661231 return null ;
11671232 }
@@ -1205,6 +1270,32 @@ LRESULT wmMeasureChild (long wParam, long lParam) {
12051270 return null ;
12061271}
12071272
1273+ private Point calculateRenderedTextSize () {
1274+ GC gc = new GC (this .getMenu ().getShell ());
1275+ String textWithoutMnemonicCharacter = getText ().replace ("&" , "" );
1276+ Point points = gc .textExtent (textWithoutMnemonicCharacter );
1277+ gc .dispose ();
1278+
1279+ if (!getDisplay ().isRescalingAtRuntime ()) {
1280+ int primaryMonitorZoom = this .getDisplay ().getDeviceZoom ();
1281+ int adjustedPrimaryMonitorZoom = DPIUtil .getZoomForAutoscaleProperty (primaryMonitorZoom );
1282+ if (primaryMonitorZoom != adjustedPrimaryMonitorZoom ) {
1283+ // Windows will use a font matching the native primary monitor zoom for calculating the size in pixels,
1284+ // GC will use the native primary monitor zoom to scale down from pixels to points in this scenario
1285+ // Therefore we need to make sure adjust the points as if it would have been scaled down by the
1286+ // native primary monitor zoom.
1287+ // Example:
1288+ // Primary monitor on 150% with int200: native zoom 150%, adjusted zoom 100%
1289+ // Pixel height of font in this example is 15px
1290+ // GC calculated height of 15px, scales down with adjusted zoom of 100% and returns 15pt -> should be 10pt
1291+ // this calculation is corrected by the following line
1292+ // This is the only place, where the GC needs to use the native zoom to do that, therefore it is fixed only here
1293+ points = DPIUtil .scaleDown (DPIUtil .scaleUp (points , adjustedPrimaryMonitorZoom ), primaryMonitorZoom );
1294+ }
1295+ }
1296+ return points ;
1297+ }
1298+
12081299private static final class MenuItemToolTip extends ToolTip {
12091300
12101301 public MenuItemToolTip (Shell parent ) {
0 commit comments