From a63afa4aa62863d1a199a0fb7d2f56ff8fcd04fd Mon Sep 17 00:00:00 2001 From: Rajat Mahajan Date: Wed, 28 Jun 2023 21:07:24 +0000 Subject: [PATCH] 8294427: Check boxes and radio buttons have rendering issues on Windows in High DPI env Reviewed-by: aivanov, achung --- .../classes/sun/swing/CachedPainter.java | 7 +- .../sun/java/swing/plaf/windows/TMSchema.java | 15 +- .../sun/java/swing/plaf/windows/XPStyle.java | 55 ++++++-- .../classes/sun/awt/windows/ThemeReader.java | 128 ++++++++++++------ .../native/libawt/windows/ThemeReader.cpp | 53 +++----- 5 files changed, 161 insertions(+), 97 deletions(-) diff --git a/src/java.desktop/share/classes/sun/swing/CachedPainter.java b/src/java.desktop/share/classes/sun/swing/CachedPainter.java index ae69dd29cc6..cee78c507f9 100644 --- a/src/java.desktop/share/classes/sun/swing/CachedPainter.java +++ b/src/java.desktop/share/classes/sun/swing/CachedPainter.java @@ -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); } diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/TMSchema.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/TMSchema.java index c4df0bf581d..90c7f797043 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/TMSchema.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/TMSchema.java @@ -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 diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/XPStyle.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/XPStyle.java index c2c11c896cf..a215277ba43 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/XPStyle.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/XPStyle.java @@ -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); } diff --git a/src/java.desktop/windows/classes/sun/awt/windows/ThemeReader.java b/src/java.desktop/windows/classes/sun/awt/windows/ThemeReader.java index 63e9c8de698..61972bf4ed5 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/ThemeReader.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/ThemeReader.java @@ -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 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 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 partSizeWidgetParts = + List.of(Part.BP_RADIOBUTTON.getValue(), + Part.BP_CHECKBOX.getValue(), + Part.MP_POPUPCHECK.getValue()); + + private static final Map> 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 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 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(); } diff --git a/src/java.desktop/windows/native/libawt/windows/ThemeReader.cpp b/src/java.desktop/windows/native/libawt/windows/ThemeReader.cpp index c5428aedda5..de1f02070c1 100644 --- a/src/java.desktop/windows/native/libawt/windows/ThemeReader.cpp +++ b/src/java.desktop/windows/native/libawt/windows/ThemeReader.cpp @@ -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();