Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandr Miloslavskiy2020-05-13 18:14:06 +0000
committerNiraj Modi2020-05-15 07:03:02 +0000
commit0a02b26d9e63e6218f57c3699c2a225028a4a2ff (patch)
tree75c1c3bc390648a258af4991fd2ae7995af15968
parent9d0bbcac1bc083b0e858a3590c8ac03fdab49e44 (diff)
downloadeclipse.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>
-rw-r--r--bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java6
-rw-r--r--bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java41
-rw-r--r--bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Menu.java59
-rw-r--r--bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java21
-rw-r--r--bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java42
-rw-r--r--tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/tests/win32/snippets/Bug560358_DarkMenuBar.java188
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 ();
+ }
+}

Back to the top