diff options
author | Alexandr Miloslavskiy | 2020-05-13 18:14:06 +0000 |
---|---|---|
committer | Niraj Modi | 2020-05-15 07:03:02 +0000 |
commit | 0a02b26d9e63e6218f57c3699c2a225028a4a2ff (patch) | |
tree | 75c1c3bc390648a258af4991fd2ae7995af15968 | |
parent | 9d0bbcac1bc083b0e858a3590c8ac03fdab49e44 (diff) | |
download | eclipse.platform.swt-0a02b26d9e63e6218f57c3699c2a225028a4a2ff.tar.gz eclipse.platform.swt-0a02b26d9e63e6218f57c3699c2a225028a4a2ff.tar.xz eclipse.platform.swt-0a02b26d9e63e6218f57c3699c2a225028a4a2ff.zip |
Bug 560358 - [win32][Dark theme] Menu bar is not configurable
This approach has some drawbacks, see code comment near
`Display.MENUBAR_FOREGROUND_COLOR_KEY`. One the other hand, it's much
easier to achieve compared to fully owner drawn menus.
I also fixed `OS.HBMMENU_CALLBACK` which was wrong on 64-bit. Windows
seems to recognize the wrong value, though.
For API design goals, see recent patch for Bug 444560.
Change-Id: I738d74bf03b173f5e8149648b83b6b397da295f9
Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
6 files changed, 351 insertions, 6 deletions
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java index f8415726d3..3300c4c584 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java @@ -15,6 +15,7 @@ package org.eclipse.swt.internal.win32; +import org.eclipse.swt.graphics.Color; import org.eclipse.swt.internal.*; import org.eclipse.swt.widgets.Display; @@ -567,7 +568,7 @@ public class OS extends C { public static final int GW_HWNDNEXT = 0x2; public static final int GW_HWNDPREV = 0x3; public static final int GW_OWNER = 0x4; - public static final int HBMMENU_CALLBACK = 0xffffffff; + public static final long HBMMENU_CALLBACK = -1; public static final int HCBT_CREATEWND = 3; public static final int HCF_HIGHCONTRASTON = 0x1; public static final int HDF_BITMAP = 0x2000; @@ -2350,6 +2351,9 @@ public static final void setTheme(boolean isDarkTheme) { throw new NullPointerException("Display must be already created before you call OS.setTheme()"); display.setData("org.eclipse.swt.internal.win32.useDarkModeExplorerTheme", isDarkTheme); + display.setData("org.eclipse.swt.internal.win32.menuBarForegroundColor", isDarkTheme ? new Color(display, 0xD0, 0xD0, 0xD0) : null); + display.setData("org.eclipse.swt.internal.win32.menuBarBackgroundColor", isDarkTheme ? new Color(display, 0x30, 0x30, 0x30) : null); + display.setData("org.eclipse.swt.internal.win32.menuBarBorderColor", isDarkTheme ? new Color(display, 0x50, 0x50, 0x50) : null); } public static final boolean SetDllDirectory (TCHAR lpPathName) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java index 56beb9a299..e3d0c47607 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java @@ -183,6 +183,24 @@ public class Display extends Device { */ static final String USE_DARKMODE_EXPLORER_THEME_KEY = "org.eclipse.swt.internal.win32.useDarkModeExplorerTheme"; boolean useDarkModeExplorerTheme; + /** + * Configures background/foreground colors of Menu(SWT.BAR).<br> + * Side effects: + * <ul> + * <li>Menu items are no longer highlighted when mouse hovers over</li> + * </ul> + * Expects a <code>Color</code> value. + */ + static final String MENUBAR_FOREGROUND_COLOR_KEY = "org.eclipse.swt.internal.win32.menuBarForegroundColor"; //$NON-NLS-1$ + int menuBarForegroundPixel = -1; + static final String MENUBAR_BACKGROUND_COLOR_KEY = "org.eclipse.swt.internal.win32.menuBarBackgroundColor"; //$NON-NLS-1$ + int menuBarBackgroundPixel = -1; + /** + * Color of the 1px separator between Menu(SWT.BAR) and Shell's client area.<br> + * Expects a <code>Color</code> value. + */ + static final String MENUBAR_BORDER_COLOR_KEY = "org.eclipse.swt.internal.win32.menuBarBorderColor"; //$NON-NLS-1$ + long menuBarBorderPen; /* Custom icons */ long hIconSearch; @@ -3666,6 +3684,8 @@ void releaseDisplay () { if (hScrollBarTheme != 0) OS.CloseThemeData (hScrollBarTheme); if (hTabTheme != 0) OS.CloseThemeData (hTabTheme); hButtonTheme = hEditTheme = hExplorerBarTheme = hScrollBarTheme = hTabTheme = 0; + if (menuBarBorderPen != 0) OS.DeleteObject (menuBarBorderPen); + menuBarBorderPen = 0; /* Unhook the message hook */ if (msgHook != 0) OS.UnhookWindowsHookEx (msgHook); @@ -4256,6 +4276,11 @@ boolean _toBoolean (Object value) { return value != null && ((Boolean)value).booleanValue (); } +int _toColorPixel (Object value) { + if (value == null) return -1; + return ((Color)value).handle; +} + /** * Sets the application defined property of the receiver * with the specified name to the given argument. @@ -4315,6 +4340,22 @@ public void setData (String key, Object value) { !disableCustomThemeTweaks && isThemeAvailable_DarkModeExplorer (); return; + case MENUBAR_FOREGROUND_COLOR_KEY: + menuBarForegroundPixel = disableCustomThemeTweaks ? -1 : _toColorPixel(value); + return; + case MENUBAR_BACKGROUND_COLOR_KEY: + menuBarBackgroundPixel = disableCustomThemeTweaks ? -1 : _toColorPixel(value); + return; + case MENUBAR_BORDER_COLOR_KEY: + if (menuBarBorderPen != 0) + OS.DeleteObject(menuBarBorderPen); + + int pixel = _toColorPixel(value); + if (disableCustomThemeTweaks || (pixel == -1)) + menuBarBorderPen = 0; + else + menuBarBorderPen = OS.CreatePen (OS.PS_SOLID, 1, pixel); + return; } /* Remove the key/value pair */ diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java index 6d8cf654b9..a4d73580b1 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java @@ -334,6 +334,7 @@ void createHandle () { handle = OS.CreatePopupMenu (); } if (handle == 0) error (SWT.ERROR_NO_HANDLES); + updateBackground (); } void createItem (MenuItem item, int index) { @@ -368,11 +369,25 @@ void createItem (MenuItem item, int index) { display.removeMenuItem (item); error (SWT.ERROR_ITEM_NOT_ADDED); } + + if (needsMenuCallback()) { + /* + * Bug in Windows: when MIIM_BITMAP is used together with MFT_STRING, + * InsertMenuItem() fails. The workaround is to set MIIM_BITMAP with + * a separate SetMenuItemInfo(). + */ + + info.fMask = OS.MIIM_BITMAP; + info.hbmpItem = OS.HBMMENU_CALLBACK; + OS.SetMenuItemInfo (handle, index, true, info); + } + redraw (); } void createWidget () { checkOrientation (parent); + initThemeColors (); createHandle (); parent.addMenu (this); } @@ -831,6 +846,13 @@ public int indexOf (MenuItem item) { return -1; } +void initThemeColors () { + if ((style & SWT.BAR) != 0) { + foreground = display.menuBarForegroundPixel; + background = display.menuBarBackgroundPixel; + } +} + /** * Returns <code>true</code> if the receiver is enabled and all * of the receiver's ancestors are enabled, and <code>false</code> @@ -874,6 +896,34 @@ public boolean isVisible () { return getVisible (); } + +boolean needsMenuCallback() { + /* + * Note: using `HBMMENU_CALLBACK` disables XP theme for entire menu + * containing the menu item. This has at least the following side + * effects: + * 1) Menu bar: items are no longer highlighted when mouse hovers + * 2) Menu bar: text is now left-aligned without any margin + * 3) Popup menu: Images and checkboxes are no longer merged into a single column + */ + + if ((background != -1) || (backgroundImage != null)) { + /* + * Since XP theming, `MENUINFO.hbrBack` has two issues: + * 1) Menu bar completely ignores it + * 2) Popup menus ignore it for image/checkbox area + * The workaround is to disable XP theme via `HBMMENU_CALLBACK`. + */ + return true; + } + + /* + * Otherwise, if menu has foreground color configured, use + * `HBMMENU_CALLBACK` to set color in `MenuItem.wmDrawChild` callback. + */ + return (foreground != -1); +} + void redraw () { if (!isVisible ()) return; if ((style & SWT.BAR) != 0) { @@ -1271,11 +1321,12 @@ void update () { void updateBackground () { if (hBrush != 0) OS.DeleteObject (hBrush); hBrush = 0; - if (backgroundImage != null) { + + if (backgroundImage != null) hBrush = OS.CreatePatternBrush (backgroundImage.handle); - } else { - if (background != -1) hBrush = OS.CreateSolidBrush (background); - } + else if (background != -1) + hBrush = OS.CreateSolidBrush (background); + MENUINFO lpcmi = new MENUINFO (); lpcmi.cbSize = MENUINFO.sizeof; lpcmi.fMask = OS.MIM_BACKGROUND; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java index 8868199b13..7b0dd34fc2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java @@ -783,7 +783,7 @@ public void setImage (Image image) { MENUITEMINFO info = new MENUITEMINFO (); info.cbSize = MENUITEMINFO.sizeof; info.fMask = OS.MIIM_BITMAP; - if (parent.foreground != -1) { + if (parent.needsMenuCallback()) { info.hbmpItem = OS.HBMMENU_CALLBACK; } else { if (OS.IsAppThemed ()) { @@ -1145,6 +1145,25 @@ LRESULT wmDrawChild (long wParam, long lParam) { LRESULT wmMeasureChild (long wParam, long lParam) { MEASUREITEMSTRUCT struct = new MEASUREITEMSTRUCT (); OS.MoveMemory (struct, lParam, MEASUREITEMSTRUCT.sizeof); + + 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.autoScaleUpUsingNativeDPI(5); + OS.MoveMemory (lParam, struct, MEASUREITEMSTRUCT.sizeof); + return null; + } + } + int width = 0, height = 0; if (image != null) { Rectangle rect = image.getBoundsInPixels (); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java index 2b1d12fdfd..0399ede505 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java @@ -2060,6 +2060,37 @@ long windowProc () { return parent != null ? DialogProc : super.windowProc (); } +Rectangle getClientRectInWindow () { + RECT windowRect = new RECT (); + OS.GetWindowRect (handle, windowRect); + + POINT clientWindowLT = new POINT (); + OS.ClientToScreen (handle, clientWindowLT); + clientWindowLT.x -= windowRect.left; + clientWindowLT.y -= windowRect.top; + + RECT clientRect = new RECT (); + OS.GetClientRect (handle, clientRect); + + return new Rectangle( + clientWindowLT.x + clientRect.left, + clientWindowLT.y + clientRect.top, + clientRect.right - clientRect.left, + clientRect.bottom - clientRect.top); +} + +void overpaintMenuBorder () { + if ((menuBar == null) || (display.menuBarBorderPen == 0)) return; + + Rectangle clientArea = getClientRectInWindow(); + long dc = OS.GetWindowDC (handle); + long oldPen = OS.SelectObject (dc, display.menuBarBorderPen); + OS.MoveToEx (dc, clientArea.x, clientArea.y - 1, 0); + OS.LineTo (dc, clientArea.x + clientArea.width, clientArea.y - 1); + OS.SelectObject (dc, oldPen); + OS.ReleaseDC (handle, dc); +} + @Override long windowProc (long hwnd, int msg, long wParam, long lParam) { if (handle == 0) return 0; @@ -2097,6 +2128,17 @@ long windowProc (long hwnd, int msg, long wParam, long lParam) { } } } + + switch (msg) { + case OS.WM_NCACTIVATE: + case OS.WM_NCPAINT: + { + long ret = super.windowProc (hwnd, msg, wParam, lParam); + overpaintMenuBorder(); + return ret; + } + } + return super.windowProc (hwnd, msg, wParam, lParam); } diff --git a/tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/tests/win32/snippets/Bug560358_DarkMenuBar.java b/tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/tests/win32/snippets/Bug560358_DarkMenuBar.java new file mode 100644 index 0000000000..22b18eef60 --- /dev/null +++ b/tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/tests/win32/snippets/Bug560358_DarkMenuBar.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2020 Syntevo and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Syntevo - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.tests.win32.snippets; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.*; + +public class Bug560358_DarkMenuBar { + static Image createMenuImage(Device a_Device) { + Image result = new Image(a_Device, 16, 16); + GC gc = new GC(result); + + gc.setBackground(a_Device.getSystemColor(SWT.COLOR_BLUE)); + gc.fillRectangle(0, 0, 16, 16); + + gc.setForeground(a_Device.getSystemColor(SWT.COLOR_RED)); + gc.drawOval(4, 4, 8, 8); + + gc.dispose(); + return result; + } + + static void setMenuItemName(MenuItem item) { + StringBuilder itemText = new StringBuilder(); + + if ((item.getStyle() & SWT.SEPARATOR) != 0) + return; + else if ((item.getStyle() & SWT.CASCADE) != 0) + itemText.append("SWT.CASCADE"); + else if ((item.getStyle() & SWT.PUSH) != 0) + itemText.append("SWT.PUSH"); + else if ((item.getStyle() & SWT.CHECK) != 0) + itemText.append("SWT.CHECK"); + else if ((item.getStyle() & SWT.RADIO) != 0) + itemText.append("SWT.RADIO"); + else + throw new RuntimeException(); + + if (!item.getEnabled()) + itemText.append(" + disabled"); + + if (item.getImage() != null) + itemText.append(" + image"); + + if (item.getSelection()) + itemText.append(" + selection"); + + itemText.append("\tCtrl+A"); + + item.setText(itemText.toString()); + } + + static void createMenus(Menu a_Parent, Image menuImage, int a_Level) { + // SWT.CASCADE + if (a_Level < 1) { + for (int isEnabled = 0; isEnabled < 2; isEnabled++) { + for (int isImage = 0; isImage < 2; isImage++) { + MenuItem item = new MenuItem(a_Parent, SWT.CASCADE); + item.setEnabled(isEnabled != 0); + item.setImage((isImage != 0) ? menuImage : null); + setMenuItemName(item); + + Menu subMenu = new Menu(a_Parent.getShell(), SWT.DROP_DOWN); + item.setMenu(subMenu); + + createMenus(subMenu, menuImage, a_Level + 1); + } + } + + new MenuItem(a_Parent, SWT.SEPARATOR); + } + + // SWT.PUSH + { + for (int isEnabled = 0; isEnabled < 2; isEnabled++) { + for (int isImage = 0; isImage < 2; isImage++) { + MenuItem item = new MenuItem(a_Parent, SWT.PUSH); + item.setEnabled(isEnabled != 0); + item.setImage((isImage != 0) ? menuImage : null); + setMenuItemName(item); + } + } + + new MenuItem(a_Parent, SWT.SEPARATOR); + } + + // SWT.CHECK + { + for (int isEnabled = 0; isEnabled < 2; isEnabled++) { + for (int isChecked = 0; isChecked < 2; isChecked++) { + for (int isImage = 0; isImage < 2; isImage++) { + MenuItem item = new MenuItem(a_Parent, SWT.CHECK); + item.setEnabled(isEnabled != 0); + item.setImage((isImage != 0) ? menuImage : null); + item.setSelection(isChecked != 0); + setMenuItemName(item); + } + } + } + + new MenuItem(a_Parent, SWT.SEPARATOR); + } + + // SWT.RADIO + { + for (int isEnabled = 0; isEnabled < 2; isEnabled++) { + for (int isImage = 0; isImage < 2; isImage++) { + for (int isChecked = 0; isChecked < 2; isChecked++) { + MenuItem item = new MenuItem(a_Parent, SWT.RADIO); + item.setEnabled(isEnabled != 0); + item.setImage((isImage != 0) ? menuImage : null); + item.setSelection(isChecked != 0); + setMenuItemName(item); + } + } + } + } + } + + public static void main (String [] args) { + Display display = new Display (); + Shell shell = new Shell (display); + + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.marginHeight = 10; + layout.marginWidth = 10; + layout.spacing = 10; + shell.setLayout(layout); + + Color backColor = new Color(display, 0x30, 0x30, 0x30); + Color foreColor = new Color(display, 0xD0, 0xD0, 0xD0); + Color borderColor = new Color(display, 0x50, 0x50, 0x50); + display.setData("org.eclipse.swt.internal.win32.menuBarBackgroundColor", backColor); + display.setData("org.eclipse.swt.internal.win32.menuBarForegroundColor", foreColor); + display.setData("org.eclipse.swt.internal.win32.menuBarBorderColor", borderColor); + + final Text labelInfo = new Text(shell, SWT.READ_ONLY | SWT.MULTI); + labelInfo.setText("This snippet is for testing menubar coloring on Windows."); + + Image menuImage = createMenuImage(display); + + // Create menus + Menu rootMenu = new Menu(shell, SWT.BAR); + shell.setMenuBar(rootMenu); + for (int i = 0; i < 3; i++) + { + StringBuilder menuName = new StringBuilder("SWT.CASCADE"); + menuName.insert(i, '&'); + + MenuItem item = new MenuItem(rootMenu, SWT.CASCADE); + item.setText(menuName.toString()); + + Menu subMenu = new Menu(shell, SWT.DROP_DOWN); + item.setMenu(subMenu); + + createMenus(subMenu, menuImage, 0); + } + + // Set shell colors + shell.setBackground(backColor); + shell.setForeground(foreColor); + labelInfo.setBackground(backColor); + labelInfo.setForeground(foreColor); + + // Pack and show shell + shell.pack(); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch ()) display.sleep (); + } + + display.dispose (); + } +} |