Skip to content

Commit

Permalink
Drawing custom menu items for Menu Bar
Browse files Browse the repository at this point in the history
When switched to Dark Theme, using gc to draw menu item, moving the
responsibility from OS due to wrong scaling of menu bar vertically. Also
changing the text color from a grayish tone to white.
  • Loading branch information
ShahzaibIbrahim committed Sep 6, 2024
1 parent cd11f5e commit e0d0e5b
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,7 @@ public class OS extends C {
public static final int MFS_CHECKED = 0x8;
public static final int MFS_DISABLED = 0x3;
public static final int MFS_GRAYED = 0x3;
public static final int MFT_OWNERDRAW = 0x100;
public static final int MFT_RADIOCHECK = 0x200;
public static final int MFT_RIGHTJUSTIFY = 0x4000;
public static final int MFT_RIGHTORDER = 0x2000;
Expand Down Expand Up @@ -1019,6 +1020,8 @@ public class OS extends C {
public static final int OBJ_PEN = 0x1;
public static final int OBM_CHECKBOXES = 0x7ff7;
public static final int ODS_SELECTED = 0x1;
public static final int ODS_NOACCEL = 0x0100;
public static final int ODS_INACTIVE = 0x80;
public static final int ODT_MENU = 0x1;
public static final int OIC_BANG = 0x7F03;
public static final int OIC_HAND = 0x7F01;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,20 @@ void _setVisible (boolean visible) {
}
}

void _showMenu (boolean visible, MenuItem menuItem) {
long hwndParent = parent.handle;
if (visible) {
Rectangle r = DPIUtil.scaleDown(menuItem.getBounds(), menuItem.getZoom());
Point p = getDisplay().map(parent, null, new Point(r.x, r.y));
boolean success = OS.TrackPopupMenu (handle, 0, p.x, p.y, 0, hwndParent, null);
if (!success && OS.GetMenuItemCount (handle) == 0) {
OS.SendMessage (hwndParent, OS.WM_MENUSELECT, OS.MAKEWPARAM (menuItem.id, 0x00000010), 0);
}
} else {
OS.SendMessage (hwndParent, OS.WM_CANCELMODE, 0, 0);
}
}

/**
* Adds the listener to the collection of listeners who will
* be notified when help events are generated for the control,
Expand Down Expand Up @@ -338,6 +352,10 @@ void createItem (MenuItem item, int index) {
int count = OS.GetMenuItemCount (handle);
if (!(0 <= index && index <= count)) error (SWT.ERROR_INVALID_RANGE);
display.addMenuItem (item);

if((style & SWT.BAR) != 0 && needsMenuCallback()) {
this.getShell().addTraverseListener(e -> setFocusOnMnemonic(e, item));
}
/*
* Bug in Windows. For some reason, when InsertMenuItem()
* is used to insert an item without text, it is not possible
Expand All @@ -358,7 +376,7 @@ void createItem (MenuItem item, int index) {
info.fMask = OS.MIIM_ID | OS.MIIM_TYPE | OS.MIIM_DATA;
info.wID = item.id;
info.dwItemData = item.id;
info.fType = item.widgetStyle ();
info.fType = (style & SWT.BAR) != 0 && needsMenuCallback() ? OS.MFT_OWNERDRAW : item.widgetStyle ();
info.dwTypeData = pszText;
boolean success = OS.InsertMenuItem (handle, index, true, info);
if (pszText != 0) OS.HeapFree (hHeap, 0, pszText);
Expand All @@ -382,6 +400,22 @@ void createItem (MenuItem item, int index) {
redraw ();
}

private void setFocusOnMnemonic(TraverseEvent e, MenuItem menuItem) {
String text = menuItem.text;
if (text != null) {
int mnemonicIndex = text.indexOf('&');
if (e.detail == SWT.TRAVERSE_MNEMONIC) {
char pressedMnemonic = e.character;
if (Character.toUpperCase(pressedMnemonic) == Character.toUpperCase(text.charAt(mnemonicIndex + 1))) {
this.notifyListeners(SWT.Selection, new org.eclipse.swt.widgets.Event());
if (menuItem.getMenu() != null) {
menuItem.getMenu()._showMenu(true, menuItem);
}
}
}
}
}

void createWidget () {
checkOrientation (parent);
initThemeColors ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public class MenuItem extends Item {
/* Image margin. */
final static int MARGIN_WIDTH = 1;
final static int MARGIN_HEIGHT = 1;

final static int LEFT_TEXT_MARGIN = 20;
final static int ADDED_ITEM_WIDTH = 10;
static {
DPIZoomChangeRegistry.registerHandler(MenuItem::handleDPIChange, MenuItem.class);
}
Expand Down Expand Up @@ -1008,28 +1009,31 @@ public void setText (String string) {
super.setText (string);
long hHeap = OS.GetProcessHeap ();
long pszText = 0;
MENUITEMINFO info = new MENUITEMINFO ();
info.cbSize = MENUITEMINFO.sizeof;
long hMenu = parent.handle;

TCHAR buffer = new TCHAR (0, string, true);
int byteCount = buffer.length () * TCHAR.sizeof;
pszText = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
OS.MoveMemory (pszText, buffer, byteCount);
/*
* Bug in Windows 2000. For some reason, when MIIM_TYPE is set
* on a menu item that also has MIIM_BITMAP, the MIIM_TYPE clears
* the MIIM_BITMAP style. The fix is to use MIIM_STRING.
*/
info.fMask = OS.MIIM_STRING;
info.dwTypeData = pszText;
boolean success = OS.SetMenuItemInfo (hMenu, id, false, info);
if (pszText != 0) OS.HeapFree (hHeap, 0, pszText);
if (!success) {
int error = OS.GetLastError();
SWT.error (SWT.ERROR_CANNOT_SET_TEXT, null, " [GetLastError=0x" + Integer.toHexString(error) + "]");//$NON-NLS-1$ $NON-NLS-2$
if(!parent.needsMenuCallback()) {
MENUITEMINFO info = new MENUITEMINFO ();
info.cbSize = MENUITEMINFO.sizeof;
long hMenu = parent.handle;

TCHAR buffer = new TCHAR (0, string, true);
int byteCount = buffer.length () * TCHAR.sizeof;
pszText = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
OS.MoveMemory (pszText, buffer, byteCount);
/*
* Bug in Windows 2000. For some reason, when MIIM_TYPE is set
* on a menu item that also has MIIM_BITMAP, the MIIM_TYPE clears
* the MIIM_BITMAP style. The fix is to use MIIM_STRING.
*/
info.fMask = OS.MIIM_STRING;
info.dwTypeData = pszText;
boolean success = OS.SetMenuItemInfo (hMenu, id, false, info);
if (pszText != 0) OS.HeapFree (hHeap, 0, pszText);
if (!success) {
int error = OS.GetLastError();
SWT.error (SWT.ERROR_CANNOT_SET_TEXT, null, " [GetLastError=0x" + Integer.toHexString(error) + "]");//$NON-NLS-1$ $NON-NLS-2$
}
parent.redraw ();
}
parent.redraw ();
}

/**
Expand Down Expand Up @@ -1121,27 +1125,44 @@ LRESULT wmCommandChild (long wParam, long lParam) {
return null;
}

LRESULT wmDrawChild (long wParam, long lParam) {
DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT ();
OS.MoveMemory (struct, lParam, DRAWITEMSTRUCT.sizeof);
if (image != null) {
LRESULT wmDrawChild(long wParam, long lParam) {
DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT();
OS.MoveMemory(struct, lParam, DRAWITEMSTRUCT.sizeof);
if ((text != null || image != null)) {
GCData data = new GCData();
data.device = display;
GC gc = createNewGC(struct.hDC, data);
/*
* Bug in Windows. When a bitmap is included in the
* menu bar, the HDC seems to already include the left
* coordinate. The fix is to ignore this value when
* the item is in a menu bar.
*/
int x = (parent.style & SWT.BAR) != 0 ? MARGIN_WIDTH * 2 : struct.left;
Image image = getEnabled () ? this.image : new Image (display, this.image, SWT.IMAGE_DISABLE);

int x = (parent.style & SWT.BAR) == 0 ? MARGIN_WIDTH * 2 : struct.left;
int zoom = getZoom();
gc.drawImage (image, DPIUtil.scaleDown(x, zoom), DPIUtil.scaleDown(struct.top + MARGIN_HEIGHT, zoom));
if (this.image != image) image.dispose ();
gc.dispose ();
if (text != null) {
int flags = SWT.DRAW_MNEMONIC | SWT.DRAW_DELIMITER;
boolean isInactive = ((struct.itemState & OS.ODS_INACTIVE) != 0);
boolean isSelected = ((struct.itemState & OS.ODS_SELECTED) != 0);
boolean isNoAccel = ((struct.itemState & OS.ODS_NOACCEL) != 0);
Rectangle bounds = this.getBounds();
gc.setForeground(isInactive ? display.getSystemColor(SWT.COLOR_GRAY) : display.getSystemColor(SWT.COLOR_WHITE));
gc.setBackground(isSelected ? display.getSystemColor(SWT.COLOR_DARK_GRAY) : parent.getBackground());
int xMargin = DPIUtil.scaleDown(x + LEFT_TEXT_MARGIN, zoom) + (this.image != null ? this.image.getBounds().width : 0);
int yMargin = DPIUtil.scaleDown(struct.top + MARGIN_HEIGHT, zoom);

if(isNoAccel) {
gc.fillRectangle(DPIUtil.scaleDown(x, zoom), DPIUtil.scaleDown(struct.top + MARGIN_HEIGHT, zoom), DPIUtil.scaleDown(bounds.width + (image != null ? image.getBounds().width : 0), zoom), DPIUtil.scaleDown(bounds.height, zoom));
}

gc.drawText(this.text, xMargin, yMargin, flags);
}
if (image != null) {
Image image = getEnabled() ? this.image : new Image(display, this.image, SWT.IMAGE_DISABLE);
gc.drawImage(image, DPIUtil.scaleDown(x + LEFT_TEXT_MARGIN, zoom), DPIUtil.scaleDown(struct.top + MARGIN_HEIGHT, zoom));
if (this.image != image) {
image.dispose();
}
}
gc.dispose();
}
if (parent.foreground != -1) OS.SetTextColor (struct.hDC, parent.foreground);
if (parent.foreground != -1)
OS.SetTextColor(struct.hDC, parent.foreground);
return null;
}

Expand All @@ -1151,17 +1172,12 @@ LRESULT wmMeasureChild (long wParam, long lParam) {

if ((parent.style & SWT.BAR) != 0) {
if (parent.needsMenuCallback()) {
/*
* Weirdness in Windows. Setting `HBMMENU_CALLBACK` causes
* item sizes to mean something else. It seems that it is
* the size of left margin before the text. At the same time,
* if menu item has a mnemonic, it's always drawn at a fixed
* position. I have tested on Win7, Win8.1, Win10 and found
* that value of 5 works well in matching text to mnemonic.
* NOTE: autoScaleUpUsingNativeDPI() is used to avoid problems
* with applications that disable automatic scaling.
*/
struct.itemWidth = DPIUtil.scaleUp(5, nativeZoom);
GC gc = new GC (display);
String textWithoutMnemonicCharacter = getText().replace("&", "");
Point points = gc.textExtent(textWithoutMnemonicCharacter);
gc.dispose ();
struct.itemHeight = DPIUtil.scaleUp(points.y, nativeZoom);
struct.itemWidth = DPIUtil.scaleUp(points.x + ADDED_ITEM_WIDTH, nativeZoom);
OS.MoveMemory (lParam, struct, MEASUREITEMSTRUCT.sizeof);
return null;
}
Expand Down

0 comments on commit e0d0e5b

Please sign in to comment.