8294427: Check boxes and radio buttons have rendering issues on Windows in High DPI env

Reviewed-by: aivanov, achung
This commit is contained in:
Rajat Mahajan 2023-06-28 21:07:24 +00:00 committed by Alexey Ivanov
parent 3df36c4f10
commit a63afa4aa6
5 changed files with 161 additions and 97 deletions
src/java.desktop
share/classes/sun/swing
windows
classes
com/sun/java/swing/plaf/windows
sun/awt/windows
native/libawt/windows

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -314,8 +314,9 @@ public abstract class CachedPainter {
@Override
public Image getResolutionVariant(double destWidth, double destHeight) {
int w = (int) Math.ceil(destWidth);
int h = (int) Math.ceil(destHeight);
int w = (int) Math.floor(destWidth + 0.5);
int h = (int) Math.floor(destHeight + 0.5);
return getImage(PainterMultiResolutionCachedImage.class,
c, baseWidth, baseHeight, w, h, args);
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -40,10 +40,13 @@
package com.sun.java.swing.plaf.windows;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.util.EnumMap;
import javax.swing.JComponent;
import sun.awt.windows.ThemeReader;
@ -55,7 +58,7 @@ import sun.awt.windows.ThemeReader;
*
* @author Leif Samuelsson
*/
class TMSchema {
public final class TMSchema {
/**
* An enumeration of the various Windows controls (also known as

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -40,14 +40,41 @@
package com.sun.java.swing.plaf.windows;
import java.awt.*;
import java.awt.image.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.security.AccessController;
import java.util.*;
import java.util.HashMap;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.*;
import javax.swing.AbstractButton;
import javax.swing.CellRendererPane;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JRadioButton;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.text.JTextComponent;
import sun.awt.image.SunWritableRaster;
@ -55,8 +82,10 @@ import sun.awt.windows.ThemeReader;
import sun.security.action.GetPropertyAction;
import sun.swing.CachedPainter;
import static com.sun.java.swing.plaf.windows.TMSchema.*;
import static com.sun.java.swing.plaf.windows.TMSchema.Part;
import static com.sun.java.swing.plaf.windows.TMSchema.Prop;
import static com.sun.java.swing.plaf.windows.TMSchema.State;
import static com.sun.java.swing.plaf.windows.TMSchema.TypeEnum;
/**
* Implements Windows XP Styles for the Windows Look and Feel.
@ -675,6 +704,11 @@ class XPStyle {
w = bi.getWidth();
h = bi.getHeight();
// Get DPI to pass further to ThemeReader.paintBackground()
Graphics2D g2d = (Graphics2D) g;
AffineTransform at = g2d.getTransform();
int dpi = (int)(at.getScaleX() * 96);
WritableRaster raster = bi.getRaster();
DataBufferInt dbi = (DataBufferInt)raster.getDataBuffer();
// Note that stealData() requires a markDirty() afterwards
@ -682,7 +716,8 @@ class XPStyle {
ThemeReader.paintBackground(SunWritableRaster.stealData(dbi, 0),
part.getControlName(c), part.getValue(),
State.getValue(part, state),
0, 0, w, h, w);
0, 0, w, h, w, dpi);
SunWritableRaster.markDirty(dbi);
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -30,11 +30,14 @@ import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.sun.java.swing.plaf.windows.TMSchema.Part;
/**
* Implements Theme Support for Windows XP.
*
@ -44,7 +47,24 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
*/
public final class ThemeReader {
private static final Map<String, Long> widgetToTheme = new HashMap<>();
private static final int defaultDPI = 96;
/**
* List of widgets for which we need to get the part size for the current DPI.
*/
private static final List<String> partSizeWidgets =
List.of("MENU", "BUTTON");
/**
* List of widget parts for which we need to get the part size for the current DPI.
*/
private static final List<Integer> partSizeWidgetParts =
List.of(Part.BP_RADIOBUTTON.getValue(),
Part.BP_CHECKBOX.getValue(),
Part.MP_POPUPCHECK.getValue());
private static final Map<Integer, Map<String, Long>> dpiAwareWidgetToTheme
= new HashMap<>();
// lock for the cache
// reading should be done with readLock
@ -80,28 +100,30 @@ public final class ThemeReader {
return xpStyleEnabled;
}
private static Long openThemeImpl(String widget, int dpi) {
Long theme;
int i = widget.indexOf("::");
if (i > 0) {
// We're using the syntax "subAppName::controlName" here, as used by msstyles.
// See documentation for SetWindowTheme on MSDN.
setWindowTheme(widget.substring(0, i));
theme = openTheme(widget.substring(i + 2), dpi);
setWindowTheme(null);
} else {
theme = openTheme(widget, dpi);
}
return theme;
}
// this should be called only with writeLock held
private static Long getThemeImpl(String widget) {
Long theme = widgetToTheme.get(widget);
if (theme == null) {
int i = widget.indexOf("::");
if (i > 0) {
// We're using the syntax "subAppName::controlName" here, as used by msstyles.
// See documentation for SetWindowTheme on MSDN.
setWindowTheme(widget.substring(0, i));
theme = openTheme(widget.substring(i+2));
setWindowTheme(null);
} else {
theme = openTheme(widget);
}
widgetToTheme.put(widget, theme);
}
return theme;
private static Long getThemeImpl(String widget, int dpi) {
return dpiAwareWidgetToTheme.computeIfAbsent(dpi, key -> new HashMap<>())
.computeIfAbsent(widget, w -> openThemeImpl(widget, dpi));
}
// returns theme value
// this method should be invoked with readLock locked
private static Long getTheme(String widget) {
private static Long getTheme(String widget, int dpi) {
if (!isThemed) {
throw new IllegalStateException("Themes are not loaded");
}
@ -111,10 +133,12 @@ public final class ThemeReader {
try {
if (!valid) {
// Close old themes.
for (Long value : widgetToTheme.values()) {
closeTheme(value);
for (Map<String, Long> dpiVal : dpiAwareWidgetToTheme.values()) {
for (Long value : dpiVal.values()) {
closeTheme(value);
}
}
widgetToTheme.clear();
dpiAwareWidgetToTheme.clear();
valid = true;
}
} finally {
@ -123,13 +147,20 @@ public final class ThemeReader {
}
}
Long theme = null;
Map<String, Long> widgetToTheme = dpiAwareWidgetToTheme.get(dpi);
if (widgetToTheme != null) {
theme = widgetToTheme.get(widget);
}
// mostly copied from the javadoc for ReentrantReadWriteLock
Long theme = widgetToTheme.get(widget);
if (theme == null) {
readLock.unlock();
writeLock.lock();
try {
theme = getThemeImpl(widget);
theme = getThemeImpl(widget, dpi);
} finally {
readLock.lock();
writeLock.unlock();// Unlock write, still hold read
@ -139,14 +170,23 @@ public final class ThemeReader {
}
private static native void paintBackground(int[] buffer, long theme,
int part, int state, int x,
int y, int w, int h, int stride);
int part, int state,
int rectRight, int rectBottom,
int w, int h, int stride);
public static void paintBackground(int[] buffer, String widget,
int part, int state, int x, int y, int w, int h, int stride) {
int part, int state, int x, int y, int w, int h, int stride, int dpi) {
readLock.lock();
try {
paintBackground(buffer, getTheme(widget), part, state, x, y, w, h, stride);
/* For widgets and parts in the lists, we get the part size
for the current screen DPI to scale them better. */
Dimension d = (partSizeWidgets.contains(widget)
&& partSizeWidgetParts.contains(Integer.valueOf(part)))
? getPartSize(getTheme(widget, dpi), part, state)
: new Dimension(w, h);
paintBackground(buffer, getTheme(widget, dpi), part, state,
d.width, d.height, w, h, stride);
} finally {
readLock.unlock();
}
@ -158,7 +198,7 @@ public final class ThemeReader {
public static Insets getThemeMargins(String widget, int part, int state, int marginType) {
readLock.lock();
try {
return getThemeMargins(getTheme(widget), part, state, marginType);
return getThemeMargins(getTheme(widget, defaultDPI), part, state, marginType);
} finally {
readLock.unlock();
}
@ -169,7 +209,7 @@ public final class ThemeReader {
public static boolean isThemePartDefined(String widget, int part, int state) {
readLock.lock();
try {
return isThemePartDefined(getTheme(widget), part, state);
return isThemePartDefined(getTheme(widget, defaultDPI), part, state);
} finally {
readLock.unlock();
}
@ -181,7 +221,7 @@ public final class ThemeReader {
public static Color getColor(String widget, int part, int state, int property) {
readLock.lock();
try {
return getColor(getTheme(widget), part, state, property);
return getColor(getTheme(widget, defaultDPI), part, state, property);
} finally {
readLock.unlock();
}
@ -193,7 +233,7 @@ public final class ThemeReader {
public static int getInt(String widget, int part, int state, int property) {
readLock.lock();
try {
return getInt(getTheme(widget), part, state, property);
return getInt(getTheme(widget, defaultDPI), part, state, property);
} finally {
readLock.unlock();
}
@ -205,7 +245,7 @@ public final class ThemeReader {
public static int getEnum(String widget, int part, int state, int property) {
readLock.lock();
try {
return getEnum(getTheme(widget), part, state, property);
return getEnum(getTheme(widget, defaultDPI), part, state, property);
} finally {
readLock.unlock();
}
@ -218,7 +258,7 @@ public final class ThemeReader {
int property) {
readLock.lock();
try {
return getBoolean(getTheme(widget), part, state, property);
return getBoolean(getTheme(widget, defaultDPI), part, state, property);
} finally {
readLock.unlock();
}
@ -229,7 +269,7 @@ public final class ThemeReader {
public static boolean getSysBoolean(String widget, int property) {
readLock.lock();
try {
return getSysBoolean(getTheme(widget), property);
return getSysBoolean(getTheme(widget, defaultDPI), property);
} finally {
readLock.unlock();
}
@ -241,7 +281,7 @@ public final class ThemeReader {
public static Point getPoint(String widget, int part, int state, int property) {
readLock.lock();
try {
return getPoint(getTheme(widget), part, state, property);
return getPoint(getTheme(widget, defaultDPI), part, state, property);
} finally {
readLock.unlock();
}
@ -254,7 +294,7 @@ public final class ThemeReader {
int property) {
readLock.lock();
try {
return getPosition(getTheme(widget), part,state,property);
return getPosition(getTheme(widget, defaultDPI), part,state,property);
} finally {
readLock.unlock();
}
@ -266,13 +306,13 @@ public final class ThemeReader {
public static Dimension getPartSize(String widget, int part, int state) {
readLock.lock();
try {
return getPartSize(getTheme(widget), part, state);
return getPartSize(getTheme(widget, defaultDPI), part, state);
} finally {
readLock.unlock();
}
}
private static native long openTheme(String widget);
private static native long openTheme(String widget, int dpi);
private static native void closeTheme(long theme);
@ -285,8 +325,9 @@ public final class ThemeReader {
int stateFrom, int stateTo, int propId) {
readLock.lock();
try {
return getThemeTransitionDuration(getTheme(widget),
part, stateFrom, stateTo, propId);
return getThemeTransitionDuration(getTheme(widget, defaultDPI),
part, stateFrom, stateTo,
propId);
} finally {
readLock.unlock();
}
@ -299,8 +340,9 @@ public final class ThemeReader {
int part, int state, int boundingWidth, int boundingHeight) {
readLock.lock();
try {
return getThemeBackgroundContentMargins(getTheme(widget),
part, state, boundingWidth, boundingHeight);
return getThemeBackgroundContentMargins(getTheme(widget, defaultDPI),
part, state,
boundingWidth, boundingHeight);
} finally {
readLock.unlock();
}

@ -46,7 +46,7 @@ typedef HRESULT(__stdcall *PFNCLOSETHEMEDATA)(HTHEME hTheme);
typedef HRESULT(__stdcall *PFNDRAWTHEMEBACKGROUND)(HTHEME hTheme, HDC hdc,
int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect);
typedef HTHEME(__stdcall *PFNOPENTHEMEDATA)(HWND hwnd, LPCWSTR pszClassList);
typedef HTHEME(__stdcall *PFNOPENTHEMEDATAFORDPI)(HWND hwnd, LPCWSTR pszClassList, UINT dpi);
typedef HRESULT (__stdcall *PFNDRAWTHEMETEXT)(HTHEME hTheme, HDC hdc,
int iPartId, int iStateId, LPCWSTR pszText, int iCharCount,
@ -90,7 +90,7 @@ typedef HRESULT (__stdcall *PFNGETTHEMETRANSITIONDURATION)
(HTHEME hTheme, int iPartId, int iStateIdFrom, int iStateIdTo,
int iPropId, DWORD *pdwDuration);
static PFNOPENTHEMEDATA OpenThemeDataFunc = NULL;
static PFNOPENTHEMEDATAFORDPI OpenThemeDataForDpiFunc = NULL;
static PFNDRAWTHEMEBACKGROUND DrawThemeBackgroundFunc = NULL;
static PFNCLOSETHEMEDATA CloseThemeDataFunc = NULL;
static PFNDRAWTHEMETEXT DrawThemeTextFunc = NULL;
@ -116,8 +116,8 @@ BOOL InitThemes() {
DTRACE_PRINTLN1("InitThemes hModThemes = %x\n", hModThemes);
if(hModThemes) {
DTRACE_PRINTLN("Loaded UxTheme.dll\n");
OpenThemeDataFunc = (PFNOPENTHEMEDATA)GetProcAddress(hModThemes,
"OpenThemeData");
OpenThemeDataForDpiFunc = (PFNOPENTHEMEDATAFORDPI)GetProcAddress(
hModThemes, "OpenThemeDataForDpi");
DrawThemeBackgroundFunc = (PFNDRAWTHEMEBACKGROUND)GetProcAddress(
hModThemes, "DrawThemeBackground");
CloseThemeDataFunc = (PFNCLOSETHEMEDATA)GetProcAddress(
@ -152,7 +152,7 @@ BOOL InitThemes() {
(PFNGETTHEMETRANSITIONDURATION)GetProcAddress(hModThemes,
"GetThemeTransitionDuration");
if(OpenThemeDataFunc
if(OpenThemeDataForDpiFunc
&& DrawThemeBackgroundFunc
&& CloseThemeDataFunc
&& DrawThemeTextFunc
@ -171,9 +171,12 @@ BOOL InitThemes() {
&& GetThemeTransitionDurationFunc
) {
DTRACE_PRINTLN("Loaded function pointers.\n");
// We need to make sure we can load the Theme. This may not be
// the case on a WinXP machine with classic mode enabled.
HTHEME hTheme = OpenThemeDataFunc(AwtToolkit::GetInstance().GetHWnd(), L"Button");
// We need to make sure we can load the Theme.
// Use the default DPI value of 96 on windows.
constexpr unsigned int defaultDPI = 96;
HTHEME hTheme = OpenThemeDataForDpiFunc (
AwtToolkit::GetInstance().GetHWnd(),
L"Button", defaultDPI);
if(hTheme) {
DTRACE_PRINTLN("Loaded Theme data.\n");
CloseThemeDataFunc(hTheme);
@ -236,7 +239,7 @@ static void assert_result(HRESULT hres, JNIEnv *env) {
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL Java_sun_awt_windows_ThemeReader_openTheme
(JNIEnv *env, jclass klass, jstring widget) {
(JNIEnv *env, jclass klass, jstring widget, jint dpi) {
LPCTSTR str = (LPCTSTR) JNU_GetStringPlatformChars(env, widget, NULL);
if (str == NULL) {
@ -245,7 +248,9 @@ JNIEXPORT jlong JNICALL Java_sun_awt_windows_ThemeReader_openTheme
}
// We need to open the Theme on a Window that will stick around.
// The best one for that purpose is the Toolkit window.
HTHEME htheme = OpenThemeDataFunc(AwtToolkit::GetInstance().GetHWnd(), str);
HTHEME htheme = OpenThemeDataForDpiFunc(
AwtToolkit::GetInstance().GetHWnd(),
str, dpi);
JNU_ReleaseStringPlatformChars(env, widget, str);
return (jlong) htheme;
}
@ -378,7 +383,7 @@ static void copyDIBToBufferedImage(int *pDstBits, int *pSrcBits,
*/
JNIEXPORT void JNICALL Java_sun_awt_windows_ThemeReader_paintBackground
(JNIEnv *env, jclass klass, jintArray array, jlong theme, jint part, jint state,
jint x, jint y, jint w, jint h, jint stride) {
jint rectRight, jint rectBottom, jint w, jint h, jint stride) {
int *pDstBits=NULL;
int *pSrcBits=NULL;
@ -424,8 +429,8 @@ JNIEXPORT void JNICALL Java_sun_awt_windows_ThemeReader_paintBackground
rect.left = 0;
rect.top = 0;
rect.bottom = h;
rect.right = w;
rect.bottom = rectBottom;
rect.right = rectRight;
ZeroMemory(pSrcBits,(BITS_PER_PIXEL>>3)*w*h);
@ -714,27 +719,6 @@ JNIEXPORT jobject JNICALL Java_sun_awt_windows_ThemeReader_getPosition
return NULL;
}
void rescale(SIZE *size) {
static int dpiX = -1;
static int dpiY = -1;
if (dpiX == -1 || dpiY == -1) {
HWND hWnd = ::GetDesktopWindow();
HDC hDC = ::GetDC(hWnd);
dpiX = ::GetDeviceCaps(hDC, LOGPIXELSX);
dpiY = ::GetDeviceCaps(hDC, LOGPIXELSY);
::ReleaseDC(hWnd, hDC);
}
if (dpiX !=0 && dpiX != 96) {
float invScaleX = 96.0f / dpiX;
size->cx = (int) round(size->cx * invScaleX);
}
if (dpiY != 0 && dpiY != 96) {
float invScaleY = 96.0f / dpiY;
size->cy = (int) round(size->cy * invScaleY);
}
}
/*
* Class: sun_awt_windows_ThemeReader
* Method: getPartSize
@ -761,7 +745,6 @@ JNIEXPORT jobject JNICALL Java_sun_awt_windows_ThemeReader_getPartSize
CHECK_NULL_RETURN(dimMID, NULL);
}
rescale(&size);
jobject dimObj = env->NewObject(dimClassID, dimMID, size.cx, size.cy);
if (safe_ExceptionOccurred(env)) {
env->ExceptionDescribe();