diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c index d1c9397ff64..f6c8dbdce79 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2022 IBM Corporation and others. + * Copyright (c) 2000, 2023 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -3258,6 +3258,18 @@ JNIEXPORT jint JNICALL OS_NATIVE(GetSystemMetrics) } #endif +#ifndef NO_GetSystemMetricsForDpi +JNIEXPORT jint JNICALL OS_NATIVE(GetSystemMetricsForDpi) + (JNIEnv *env, jclass that, jint arg0, jint arg1) +{ + jint rc = 0; + OS_NATIVE_ENTER(env, that, GetSystemMetricsForDpi_FUNC); + rc = (jint)GetSystemMetricsForDpi(arg0, arg1); + OS_NATIVE_EXIT(env, that, GetSystemMetricsForDpi_FUNC); + return rc; +} +#endif + #ifndef NO_GetTextColor JNIEXPORT jint JNICALL OS_NATIVE(GetTextColor) (JNIEnv *env, jclass that, jlong arg0) @@ -9128,6 +9140,22 @@ JNIEXPORT jboolean JNICALL OS_NATIVE(SystemParametersInfo__II_3II) } #endif +#ifndef NO_SystemParametersInfoForDpi +JNIEXPORT jboolean JNICALL OS_NATIVE(SystemParametersInfoForDpi) + (JNIEnv *env, jclass that, jint arg0, jint arg1, jobject arg2, jint arg3, jint arg4) +{ + NONCLIENTMETRICS _arg2, *lparg2=NULL; + jboolean rc = 0; + OS_NATIVE_ENTER(env, that, SystemParametersInfoForDpi_FUNC); + if (arg2) if ((lparg2 = getNONCLIENTMETRICSFields(env, arg2, &_arg2)) == NULL) goto fail; + rc = (jboolean)SystemParametersInfoForDpi(arg0, arg1, lparg2, arg3, arg4); +fail: + if (arg2 && lparg2) setNONCLIENTMETRICSFields(env, arg2, lparg2); + OS_NATIVE_EXIT(env, that, SystemParametersInfoForDpi_FUNC); + return rc; +} +#endif + #ifndef NO_TBBUTTONINFO_1sizeof JNIEXPORT jint JNICALL OS_NATIVE(TBBUTTONINFO_1sizeof) (JNIEnv *env, jclass that) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h index d8bf36e349f..6fbcd0fc91e 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_stats.h @@ -257,6 +257,7 @@ typedef enum { GetSysColorBrush_FUNC, GetSystemMenu_FUNC, GetSystemMetrics_FUNC, + GetSystemMetricsForDpi_FUNC, GetTextColor_FUNC, GetTextExtentPoint32_FUNC, GetTextMetrics_FUNC, @@ -680,6 +681,7 @@ typedef enum { SystemParametersInfo__IILorg_eclipse_swt_internal_win32_NONCLIENTMETRICS_2I_FUNC, SystemParametersInfo__IILorg_eclipse_swt_internal_win32_RECT_2I_FUNC, SystemParametersInfo__II_3II_FUNC, + SystemParametersInfoForDpi_FUNC, TBBUTTONINFO_1sizeof_FUNC, TBBUTTON_1sizeof_FUNC, TCHITTESTINFO_1sizeof_FUNC, 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 c3cfd904f28..f32d0ea3a8e 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 @@ -39,6 +39,7 @@ public class OS extends C { /** * Values taken from https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions */ + public static final int WIN32_BUILD_WIN10_1607 = 14393; // "Windows 10 August 2016 Update" public static final int WIN32_BUILD_WIN10_1809 = 17763; // "Windows 10 October 2018 Update" public static final int WIN32_BUILD_WIN10_2004 = 19041; // "Windows 10 May 2020 Update" public static final int WIN32_BUILD_WIN11_21H2 = 22000; // Initial Windows 11 release @@ -2975,6 +2976,7 @@ public static int HRESULT_FROM_WIN32(int x) { /** @param hWnd cast=(HWND) */ public static final native long GetSystemMenu (long hWnd, boolean bRevert); public static final native int GetSystemMetrics (int nIndex); +public static final native int GetSystemMetricsForDpi (int nIndex, int dpi); /** @param hDC cast=(HDC) */ public static final native int GetTextColor (long hDC); /** @@ -4436,6 +4438,7 @@ public static int HRESULT_FROM_WIN32(int x) { public static final native boolean SystemParametersInfo (int uiAction, int uiParam, RECT pvParam, int fWinIni); public static final native boolean SystemParametersInfo (int uiAction, int uiParam, NONCLIENTMETRICS pvParam, int fWinIni); public static final native boolean SystemParametersInfo (int uiAction, int uiParam, int [] pvParam, int fWinIni); +public static final native boolean SystemParametersInfoForDpi (int uiAction, int uiParam, NONCLIENTMETRICS pvParam, int fWinIni, int dpi); /** * @param lpKeyState cast=(PBYTE) * @param pwszBuff cast=(LPWSTR) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java index c4b9ffb0a08..c241c0d664c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java @@ -44,6 +44,7 @@ public class DPIUtil { private static enum AutoScaleMethod { AUTO, NEAREST, SMOOTH } private static AutoScaleMethod autoScaleMethodSetting = AutoScaleMethod.AUTO; private static AutoScaleMethod autoScaleMethod = AutoScaleMethod.NEAREST; + private static boolean autoScaleOnRuntime = false; private static String autoScaleValue; private static boolean useCairoAutoScale = false; @@ -84,6 +85,16 @@ private static enum AutoScaleMethod { AUTO, NEAREST, SMOOTH } * bug 493455. */ private static final String SWT_AUTOSCALE_METHOD = "swt.autoScale.method"; + + /** + * System property to enable to scale the applicaiton on runtime + * when a DPI change is detected. + * + */ + private static final String SWT_AUTOSCALE_UPDATE_ON_RUNTIME = "swt.autoScale.updateOnRuntime"; static { autoScaleValue = System.getProperty (SWT_AUTOSCALE); @@ -95,6 +106,9 @@ private static enum AutoScaleMethod { AUTO, NEAREST, SMOOTH } autoScaleMethod = autoScaleMethodSetting = AutoScaleMethod.SMOOTH; } } + + String updateOnRuntimeValue = System.getProperty (SWT_AUTOSCALE_UPDATE_ON_RUNTIME); + autoScaleOnRuntime = Boolean.parseBoolean(updateOnRuntimeValue); } /** @@ -227,6 +241,11 @@ public static ImageData autoScaleImageData (Device device, final ImageData image return autoScaleImageData(device, imageData, scaleFactor); } + +public static ImageData autoScaleImageData (Device device, final ElementAtZoom elementAtZoom, int targetZoom) { + return autoScaleImageData(device, elementAtZoom.element(), targetZoom, elementAtZoom.zoom()); +} + private static ImageData autoScaleImageData (Device device, final ImageData imageData, float scaleFactor) { // Guards are already implemented in callers: if (deviceZoom == 100 || imageData == null || scaleFactor == 1.0f) return imageData; int width = imageData.width; @@ -261,7 +280,7 @@ private static ImageData autoScaleImageData (Device device, final ImageData imag * Returns a new rectangle as per the scaleFactor. */ public static Rectangle autoScaleBounds (Rectangle rect, int targetZoom, int currentZoom) { - if (deviceZoom == 100 || rect == null || targetZoom == currentZoom) return rect; + if (rect == null || targetZoom == currentZoom) return rect; float scaleFactor = ((float)targetZoom) / (float)currentZoom; Rectangle returnRect = new Rectangle (0,0,0,0); returnRect.x = Math.round (rect.x * scaleFactor); @@ -287,6 +306,10 @@ public static ImageData autoScaleUp (Device device, final ImageData imageData) { return autoScaleImageData(device, imageData, 100); } +public static ImageData autoScaleUp (Device device, final ElementAtZoom elementAtZoom) { + return autoScaleImageData(device, elementAtZoom.element(), elementAtZoom.zoom()); +} + public static int[] autoScaleUp(int[] pointArray) { if (deviceZoom == 100 || pointArray == null) return pointArray; float scaleFactor = getScalingFactor (); @@ -311,6 +334,14 @@ public static int autoScaleUp (int size) { return Math.round (size * scaleFactor); } +/** + * Auto-scale up int dimensions to match the given zoom level + */ +public static int autoScaleUp (int size, int zoom) { + float scaleFactor = getScalingFactor (zoom); + return Math.round (size * scaleFactor); +} + /** * Auto-scale up int dimensions using Native DPI */ @@ -388,10 +419,18 @@ public static Rectangle autoScaleUp (Drawable drawable, Rectangle rect) { * @return float scaling factor */ private static float getScalingFactor () { + return getScalingFactor(deviceZoom); +} + +/** + * Returns scaling factor from the given device zoom + * @return float scaling factor + */ +private static float getScalingFactor (int shellDeviceZoom) { if (useCairoAutoScale) { return 1; } - return deviceZoom / 100f; + return shellDeviceZoom / 100f; } /** @@ -405,6 +444,17 @@ public static int mapDPIToZoom (int dpi) { return roundedZoom; } +/** + * Compute the DPI value value based on the zoom. + * + * @return DPI + */ +public static int mapZoomToDPI (int zoom) { + double dpi = (double) zoom / 100 * DPI_ZOOM_100; + int roundedDpi = (int) Math.round (dpi); + return roundedDpi; +} + /** * Represents an element, such as some image data, at a specific zoom level. * @@ -479,6 +529,10 @@ private static ElementAtZoom getElementAtZoom(Function elemen return null; } +public static int getNativeDeviceZoom() { + return nativeDeviceZoom; +} + public static int getDeviceZoom() { return deviceZoom; } @@ -535,6 +589,10 @@ public static int getZoomForAutoscaleProperty (int nativeDeviceZoom) { return zoom; } +public static boolean isAutoScaleOnRuntimeActive() { + return autoScaleOnRuntime; +} + /** * AutoScale ImageDataProvider. */ diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java index a3cc4f554a1..9e83aa6ee0e 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Device.java @@ -264,6 +264,10 @@ int computePixels(float height) { } float computePoints(LOGFONT logFont, long hFont) { + return computePoints(logFont, hFont, -1); +} + +float computePoints(LOGFONT logFont, long hFont, int currentFontDPI) { long hDC = internal_new_GC (null); int logPixelsY = OS.GetDeviceCaps(hDC, OS.LOGPIXELSY); int pixels = 0; @@ -284,7 +288,14 @@ float computePoints(LOGFONT logFont, long hFont) { pixels = -logFont.lfHeight; } internal_dispose_GC (hDC, null); - return pixels * 72f / logPixelsY; + float adjustedZoomFactor = 1.0f; + if (currentFontDPI > 0) { + // as Device::computePoints will always return point on the basis of the + // primary monitor zoom, a custom zoomFactor must be calculated if the font + // is used for a different zoom level + adjustedZoomFactor *= (float) logPixelsY / (float) currentFontDPI; + } + return adjustedZoomFactor * pixels * 72f / logPixelsY; } /** @@ -905,6 +916,7 @@ protected void release () { fontCollection = 0; Gdip.GdiplusShutdown (gdipToken[0]); } + SWTFontProvider.disposeFontRegistry(this); gdipToken = null; scripts = null; logFonts = null; @@ -946,5 +958,4 @@ void setEnableAutoScaling(boolean value) { protected int getDeviceZoom () { return DPIUtil.mapDPIToZoom ( _getDPIx ()); } - } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java index 63492501df4..ae595fbd5ca 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Font.java @@ -15,6 +15,7 @@ import org.eclipse.swt.*; +import org.eclipse.swt.internal.*; import org.eclipse.swt.internal.win32.*; /** @@ -49,11 +50,17 @@ public final class Font extends Resource { */ public long handle; + /** + * The zoom in % of the standard resolution used for conversion of point height to pixel height + * (Warning: This field is platform dependent) + */ + private int zoom; /** * Prevents uninitialized instances from being created outside the package. */ Font(Device device) { super(device); + this.zoom = extractZoom(this.device); } /** @@ -78,6 +85,14 @@ public final class Font extends Resource { */ public Font(Device device, FontData fd) { super(device); + this.zoom = extractZoom(this.device); + init(fd); + init(); +} + +private Font(Device device, FontData fd, int zoom) { + super(device); + this.zoom = zoom; init(fd); init(); } @@ -114,6 +129,7 @@ public Font(Device device, FontData[] fds) { for (FontData fd : fds) { if (fd == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT); } + this.zoom = extractZoom(this.device); init(fds[0]); init(); } @@ -145,6 +161,7 @@ public Font(Device device, FontData[] fds) { public Font(Device device, String name, int height, int style) { super(device); if (name == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + this.zoom = extractZoom(this.device); init(new FontData (name, height, style)); init(); } @@ -152,6 +169,7 @@ public Font(Device device, String name, int height, int style) { /*public*/ Font(Device device, String name, float height, int style) { super(device); if (name == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + this.zoom = extractZoom(this.device); init(new FontData (name, height, style)); init(); } @@ -195,7 +213,8 @@ public FontData[] getFontData() { if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); LOGFONT logFont = new LOGFONT (); OS.GetObject(handle, LOGFONT.sizeof, logFont); - return new FontData[] {FontData.win32_new(logFont, device.computePoints(logFont, handle))}; + float heightInPoints = device.computePoints(logFont, handle, DPIUtil.mapZoomToDPI(zoom)); + return new FontData[] {FontData.win32_new(logFont, heightInPoints)}; } /** @@ -218,6 +237,13 @@ void init (FontData fd) { LOGFONT logFont = fd.data; int lfHeight = logFont.lfHeight; logFont.lfHeight = device.computePixels(fd.height); + + int primaryZoom = extractZoom(device); + if (zoom != primaryZoom) { + float scaleFactor = 1f * zoom / primaryZoom; + logFont.lfHeight *= scaleFactor; + } + handle = OS.CreateFontIndirect(logFont); logFont.lfHeight = lfHeight; if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES); @@ -250,6 +276,13 @@ public String toString () { return "Font {" + handle + "}"; } +private static int extractZoom(Device device) { + if (device == null) { + return DPIUtil.getNativeDeviceZoom(); + } + return DPIUtil.mapDPIToZoom(device._getDPIx()); +} + /** * Invokes platform specific functionality to allocate a new font. *

@@ -268,6 +301,7 @@ public String toString () { */ public static Font win32_new(Device device, long handle) { Font font = new Font(device); + font.zoom = extractZoom(font.device); font.handle = handle; /* * When created this way, Font doesn't own its .handle, and @@ -278,4 +312,75 @@ public static Font win32_new(Device device, long handle) { return font; } +/** + * Invokes platform specific functionality to allocate a new font. + *

+ * IMPORTANT: This method is not part of the public + * API for Font. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param device the device on which to allocate the font + * @param handle the handle for the font + * @param zoom zoom in % of the standard resolution + * @return a new font object containing the specified device and handle + * + * @noreference This method is not intended to be referenced by clients. + * @since 3.126 + */ +public static Font win32_new(Device device, long handle, int zoom) { + Font font = win32_new(device, handle); + font.zoom = zoom; + return font; +} + +/** + * Invokes platform specific private constructor to allocate a new font. + *

+ * IMPORTANT: This method is not part of the public + * API for Font. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param device the device on which to allocate the font + * @param fontData font data to create the font for + * @param zoom zoom in % of the standard resolution + * @return a new font object using the specified font data with the + * specified zoom as factor for the font data + * + * @noreference This method is not intended to be referenced by clients. + * @since 3.126 + */ +public static Font win32_new(Device device, FontData fontData, int zoom) { + return new Font(device, fontData, zoom); +} + +/** + * Used to receive a font for the given zoom in the context + * of the current configuration of SWT at runtime. + *

+ * IMPORTANT: This method is not part of the public + * API for Font. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param font font to create a font for the given target zoom + * @param targetZoom zoom in % of the standard resolution + * @return a font matching the specified font and zoom in % + * + * @noreference This method is not intended to be referenced by clients. + * @since 3.126 + */ +public static Font win32_new(Font font, int targetZoom) { + if (targetZoom == font.zoom) { + return font; + } + return SWTFontProvider.getFont(font.getDevice(), font.getFontData()[0], targetZoom); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index 5cb0091d5cd..937a9015367 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java @@ -980,7 +980,7 @@ public void drawImage (Image image, int srcX, int srcY, int srcWidth, int srcHei * the coordinates may be slightly off. The workaround is to restrict * coordinates to the allowed bounds. */ - Rectangle b = image.getBoundsInPixels(); + Rectangle b = image.getBounds(deviceZoom); int errX = src.x + src.width - b.width; int errY = src.y + src.height - b.height; if (errX != 0 || errY != 0) { @@ -996,8 +996,7 @@ public void drawImage (Image image, int srcX, int srcY, int srcWidth, int srcHei void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, boolean simple) { /* Refresh Image as per zoom level, if required. */ - srcImage.refreshImageForZoom (); - + srcImage.handleDPIChange(DPIUtil.getDeviceZoom()); if (data.gdipGraphics != 0) { //TODO - cache bitmap long [] gdipImage = srcImage.createGdipImage(); @@ -4387,7 +4386,7 @@ public void setFillRule(int rule) { public void setFont (Font font) { if (handle == 0) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); if (font != null && font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); - data.font = font != null ? font : data.device.systemFont; + data.font = font != null ? Font.win32_new(font, DPIUtil.getNativeDeviceZoom()) : data.device.systemFont; data.state &= ~FONT; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java index 72e6c52e562..333e0ed2be5 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java @@ -15,6 +15,7 @@ import java.io.*; +import java.util.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; @@ -112,6 +113,12 @@ public final class Image extends Resource implements Drawable { */ GC memGC; + /** + * Base image data at given zoom in % of the standard resolution. It will be used for + * scaled variants of this image + */ + private ElementAtZoom dataAtBaseZoom; + /** * ImageFileNameProvider to provide file names at various Zoom levels */ @@ -248,6 +255,7 @@ public Image(Device device, Image srcImage, int flag) { this.imageFileNameProvider = srcImage.imageFileNameProvider; this.styleFlag = srcImage.styleFlag | flag; this.currentDeviceZoom = srcImage.currentDeviceZoom; + this.dataAtBaseZoom = srcImage.dataAtBaseZoom; switch (flag) { case SWT.IMAGE_COPY: { switch (type) { @@ -285,7 +293,7 @@ public Image(Device device, Image srcImage, int flag) { break; } case SWT.IMAGE_DISABLE: { - ImageData data = srcImage.getImageDataAtCurrentZoom(); + ImageData data = srcImage.getImageData(srcImage.currentDeviceZoom); PaletteData palette = data.palette; RGB[] rgbs = new RGB[3]; rgbs[0] = device.getSystemColor(SWT.COLOR_BLACK).getRGB(); @@ -344,7 +352,7 @@ public Image(Device device, Image srcImage, int flag) { break; } case SWT.IMAGE_GRAY: { - ImageData data = srcImage.getImageDataAtCurrentZoom(); + ImageData data = srcImage.getImageData(srcImage.currentDeviceZoom); PaletteData palette = data.palette; ImageData newData = data; if (!palette.isDirect) { @@ -484,7 +492,8 @@ public Image(Device device, ImageData data) { super(device); if (data == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); currentDeviceZoom = DPIUtil.getDeviceZoom (); - data = DPIUtil.autoScaleUp (device, data); + this.dataAtBaseZoom = new ElementAtZoom<>(data, 100); + data = DPIUtil.autoScaleUp(device, this.dataAtBaseZoom); init(data); init(); } @@ -527,6 +536,7 @@ public Image(Device device, ImageData source, ImageData mask) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } currentDeviceZoom = DPIUtil.getDeviceZoom (); + this.dataAtBaseZoom = new ElementAtZoom<>(applyMask(source, ImageData.convertMask(mask)), 100); source = DPIUtil.autoScaleUp(device, source); mask = DPIUtil.autoScaleUp(device, mask); mask = ImageData.convertMask(mask); @@ -590,7 +600,8 @@ public Image(Device device, ImageData source, ImageData mask) { public Image (Device device, InputStream stream) { super(device); currentDeviceZoom = DPIUtil.getDeviceZoom (); - ImageData data = DPIUtil.autoScaleUp(device, new ImageData(stream)); + this.dataAtBaseZoom = new ElementAtZoom<>(new ImageData (stream), 100); + ImageData data = DPIUtil.autoScaleUp(device, this.dataAtBaseZoom); init(data); init(); } @@ -631,7 +642,8 @@ public Image (Device device, String filename) { super(device); if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); currentDeviceZoom = DPIUtil.getDeviceZoom (); - ImageData data = DPIUtil.autoScaleUp(device, new ImageData(filename)); + this.dataAtBaseZoom = new ElementAtZoom<>(new ImageData (filename), 100); + ImageData data = DPIUtil.autoScaleUp(device, this.dataAtBaseZoom); init(data); init(); } @@ -720,17 +732,18 @@ public Image(Device device, ImageDataProvider imageDataProvider) { } /** - * Refresh the Image based on the zoom level, if required. + * Update zoom and refresh the Image based on the zoom level, if required. + * + * @param deviceZoom zoom in % of the standard resolution the image shall be scaled for * * @return true if image is refreshed */ -boolean refreshImageForZoom () { +boolean handleDPIChange (int deviceZoom) { boolean refreshed = false; - int deviceZoomLevel = DPIUtil.getDeviceZoom(); if (imageFileNameProvider != null) { - if (deviceZoomLevel != currentDeviceZoom) { - ElementAtZoom filename = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, deviceZoomLevel); - if (filename.zoom() == deviceZoomLevel) { + if (deviceZoom != currentDeviceZoom) { + ElementAtZoom filename = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, deviceZoom); + if (filename.zoom() == deviceZoom) { /* Release current native resources */ destroy (); initNative(filename.element()); @@ -745,28 +758,27 @@ boolean refreshImageForZoom () { init (); refreshed = true; } - setCurrentDeviceZoom(deviceZoomLevel); + setCurrentDeviceZoom(deviceZoom); } } else if (imageDataProvider != null) { - if (deviceZoomLevel != currentDeviceZoom) { - ElementAtZoom data = DPIUtil.validateAndGetImageDataAtZoom (imageDataProvider, deviceZoomLevel); + if (deviceZoom != currentDeviceZoom) { + ElementAtZoom data = DPIUtil.validateAndGetImageDataAtZoom (imageDataProvider, deviceZoom); /* Release current native resources */ destroy (); ImageData resizedData = DPIUtil.autoScaleImageData (device, data.element(), data.zoom()); init(resizedData); init(); refreshed = true; - setCurrentDeviceZoom(deviceZoomLevel); + setCurrentDeviceZoom(deviceZoom); } - } else { - if (deviceZoomLevel != currentDeviceZoom) { - ImageData data = getImageDataAtCurrentZoom(); + } else if (this.dataAtBaseZoom != null) { // Resizing is only possible with a cached base image + if (deviceZoom != currentDeviceZoom) { + ImageData resizedData = getImageData(deviceZoom); destroy (); - ImageData resizedData = DPIUtil.autoScaleImageData(device, data, deviceZoomLevel, currentDeviceZoom); init(resizedData); init(); refreshed = true; - setCurrentDeviceZoom(deviceZoomLevel); + setCurrentDeviceZoom(deviceZoom); } } return refreshed; @@ -1134,7 +1146,7 @@ public boolean equals (Object object) { if (object == this) return true; if (!(object instanceof Image)) return false; Image image = (Image) object; - if (device != image.device || transparentPixel != image.transparentPixel) return false; + if (device != image.device || transparentPixel != image.transparentPixel || currentDeviceZoom != image.currentDeviceZoom) return false; if (imageDataProvider != null && image.imageDataProvider != null) { return (styleFlag == image.styleFlag) && imageDataProvider.equals (image.imageDataProvider); } else if (imageFileNameProvider != null && image.imageFileNameProvider != null) { @@ -1228,7 +1240,9 @@ public Rectangle getBounds() { } Rectangle getBounds(int zoom) { - Rectangle bounds = getBoundsInPixels(); + if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); + // Read the bounds in pixels from native layer. + Rectangle bounds = getBoundsInPixelsFromNative(); if (bounds != null && zoom != currentDeviceZoom) { bounds = DPIUtil.autoScaleBounds(bounds, zoom, currentDeviceZoom); } @@ -1256,6 +1270,10 @@ public Rectangle getBoundsInPixels() { if (width != -1 && height != -1) { return new Rectangle(0, 0, width, height); } + return getBoundsInPixelsFromNative(); +} + +private Rectangle getBoundsInPixelsFromNative() { switch (type) { case SWT.BITMAP: BITMAP bm = new BITMAP(); @@ -1338,6 +1356,19 @@ public ImageData getImageData (int zoom) { } else if (imageFileNameProvider != null) { ElementAtZoom fileName = DPIUtil.validateAndGetImagePathAtZoom (imageFileNameProvider, zoom); return DPIUtil.autoScaleImageData (device, new ImageData (fileName.element()), zoom, fileName.zoom()); + } + + // if a GC is initialized with an Image (memGC != null), the image data must not be resized, because it would + // be a destructive operation. Therefor, always the current image data must be returned + if (memGC != null) { + return getImageDataAtCurrentZoom(); + } + if (this.dataAtBaseZoom == null) { + // Cache data at base zoom before resizing it. + this.dataAtBaseZoom = new ElementAtZoom<>(getImageData(this.currentDeviceZoom), this.currentDeviceZoom); + } + if (this.dataAtBaseZoom != null) { + return DPIUtil.autoScaleImageData(device, this.dataAtBaseZoom, zoom); } else { return DPIUtil.autoScaleImageData (device, getImageDataAtCurrentZoom (), zoom, currentDeviceZoom); } @@ -1654,7 +1685,7 @@ public int hashCode () { if (imageDataProvider != null) { return imageDataProvider.hashCode(); } else if (imageFileNameProvider != null) { - return imageFileNameProvider.hashCode(); + return Objects.hash(imageFileNameProvider, styleFlag, transparentPixel, currentDeviceZoom); } else { return (int)handle; } @@ -1997,6 +2028,11 @@ else if (i.alphaData != null) { } static long [] init(Device device, Image image, ImageData source, ImageData mask) { + ImageData imageData = applyMask(source, mask); + return init(device, image, imageData); +} + +private static ImageData applyMask(ImageData source, ImageData mask) { /* Create a temporary image and locate the black pixel */ ImageData imageData; int blackIndex = 0; @@ -2067,8 +2103,9 @@ else if (i.alphaData != null) { } imageData.maskPad = mask.scanlinePad; imageData.maskData = mask.data; - return init(device, image, imageData); + return imageData; } + void init(ImageData i) { if (i == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); init(device, this, i); @@ -2116,7 +2153,7 @@ public long internal_new_GC (GCData data) { } data.device = device; data.image = this; - data.font = device.systemFont; + data.font = Font.win32_new(device.getSystemFont(), DPIUtil.getNativeDeviceZoom()); } return imageDC; } @@ -2264,4 +2301,27 @@ public static Image win32_new(Device device, int type, long handle) { return image; } +/** + * Invokes platform specific functionality to adapt an image for + * the correct zoom. + *

+ * IMPORTANT: This method is not part of the public + * API for Image. It is marked public only so that it + * can be shared within the packages provided by SWT. It is not + * available on all platforms, and should never be called from + * application code. + *

+ * + * @param image the image to adapt for the provided zoom + * @param targetZoom zoom in % of the standard resolution + * @return an image object equal to the specified image scaled to the provided targetZoom + * + * @noreference This method is not intended to be referenced by clients. + */ +public static Image win32_new(Image image, int targetZoom) { + if (targetZoom != image.currentDeviceZoom) { + image.handleDPIChange(targetZoom); + } + return image; +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/CommonWidgetsDPIChangeHandlers.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/CommonWidgetsDPIChangeHandlers.java new file mode 100644 index 00000000000..d0bf8647420 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/CommonWidgetsDPIChangeHandlers.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This class is used in the win32 implementation only to support + * adjusting widgets in the common package to DPI changes + *

+ * IMPORTANT: This class is not part of the public + * API for SWT. It is marked public only so that it can be shared + * within the packages provided by SWT. It is not available on all + * platforms, and should never be called from application code. + *

+ * @noreference This class is not intended to be referenced by clients + */ +public class CommonWidgetsDPIChangeHandlers { + + public static void registerCommonHandlers() { + DPIZoomChangeRegistry.registerHandler(CommonWidgetsDPIChangeHandlers::handleItemDPIChange, Item.class); + } + + private static void handleItemDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Item item)) { + return; + } + // Refresh the image + Image image = item.getImage(); + if (image != null) { + item.setImage(Image.win32_new(image, newZoom)); + } + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DPIZoomChangeHandler.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DPIZoomChangeHandler.java new file mode 100644 index 00000000000..ff406d0e040 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DPIZoomChangeHandler.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import org.eclipse.swt.widgets.*; + +@FunctionalInterface +public interface DPIZoomChangeHandler { + public void handleDPIChange(Widget widget, int newZoom, float scalingFactor); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DPIZoomChangeRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DPIZoomChangeRegistry.java new file mode 100644 index 00000000000..63d7eed4826 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DPIZoomChangeRegistry.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import java.util.*; +import java.util.Map.*; + +import org.eclipse.swt.widgets.*; + +public class DPIZoomChangeRegistry { + + private static Map, DPIZoomChangeHandler> dpiZoomChangeHandlers = new TreeMap<>( + (o1, o2) -> { + if(o1.isAssignableFrom(o2)) { + return -1; + } + if(o2.isAssignableFrom(o1)) { + return 1; + } + return o1.getName().compareTo(o2.getName()); + }); + + /** + * Calling this method will propagate the zoom change to all registered handlers that are responsible for the + * class of the provided widget or one of its super classes or interfaces. Usually there will be multiple handlers + * called per widget. To have a reliable and consistent execution order, the handler responsible for the most + * general class in the class hierarchy is called first, e.g. if a {@code Composite} is updated, the handlers are + * executed like ({@code Widget} -> {@code Control} -> {@code Scrollable} -> {@code Composite}). Each handler + * should only take care to update the attributes the class, it is registered for, adds to the hierarchy. + * + * @param widget widget the zoom change shall be applied to + * @param newZoom zoom in % of the standard resolution to be applied to the widget + * @param scalingFactor factor as division between new zoom and old zoom, e.g. 1.5 for a scaling from 100% to 150% + */ + public static void applyChange(Widget widget, int newZoom, float scalingFactor) { + for (Entry, DPIZoomChangeHandler> entry : dpiZoomChangeHandlers.entrySet()) { + Class clazz = entry.getKey(); + DPIZoomChangeHandler handler = entry.getValue(); + if (clazz.isInstance(widget)) { + handler.handleDPIChange(widget, newZoom, scalingFactor); + } + } + } + + public static void registerHandler(DPIZoomChangeHandler zoomChangeVisitor, Class clazzToRegisterFor) { + dpiZoomChangeHandlers.put(clazzToRegisterFor, zoomChangeVisitor); + } +} \ No newline at end of file diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DefaultSWTFontRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DefaultSWTFontRegistry.java new file mode 100644 index 00000000000..5113b14108d --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/DefaultSWTFontRegistry.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import java.util.*; + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.win32.*; + +/** + * This class is used in the win32 implementation only to support + * unscaled fonts in multiple DPI zoom levels. + * + * As this class is only intended to be used internally via {@code SWTFontProvider}, + * it should neither be instantiated nor referenced in a client application. + * The behavior can change any time in a future release. + * + * @noreference This class is not intended to be referenced by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class DefaultSWTFontRegistry implements SWTFontRegistry { + private static FontData KEY_SYSTEM_FONTS = new FontData(); + private Map fontsMap = new HashMap<>(); + private Device device; + + public DefaultSWTFontRegistry(Device device) { + this.device = device; + } + + @Override + public Font getSystemFont(int zoom) { + if (fontsMap.containsKey(KEY_SYSTEM_FONTS)) { + return fontsMap.get(KEY_SYSTEM_FONTS); + } + + long hFont = 0; + NONCLIENTMETRICS info = new NONCLIENTMETRICS (); + info.cbSize = NONCLIENTMETRICS.sizeof; + if (OS.SystemParametersInfo (OS.SPI_GETNONCLIENTMETRICS, 0, info, 0)) { + hFont = OS.CreateFontIndirect (info.lfMessageFont); + } + if (hFont == 0) hFont = OS.GetStockObject (OS.DEFAULT_GUI_FONT); + if (hFont == 0) hFont = OS.GetStockObject (OS.SYSTEM_FONT); + Font font = Font.win32_new(device, hFont); + registerFont(KEY_SYSTEM_FONTS, font); + return font; + } + + @Override + public Font getFont(FontData fontData, int zoom) { + if (fontsMap.containsKey(fontData)) { + Font font = fontsMap.get(fontData); + if (font.isDisposed()) { + // Remove disposed cached fonts + fontsMap.remove(fontData); + } else { + return font; + } + } + Font font = new Font(device, fontData); + registerFont(fontData, font); + return font; + } + + private Font registerFont(FontData fontData, Font font) { + fontsMap.put(fontData, font); + return font; + } + + @Override + public void dispose() { + for (Font font : fontsMap.values()) { + if (font != null) { + font.dispose(); + } + } + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java index 34ccb923d23..c5e0fee69fb 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ImageList.java @@ -46,7 +46,7 @@ public int add (Image image) { index++; } if (count == 0) { - Rectangle rect = image.getBoundsInPixels (); + Rectangle rect = image.getBoundsInPixels(); OS.ImageList_SetIconSize (handle, rect.width, rect.height); } set (index, image, count); @@ -369,7 +369,7 @@ void set (int index, Image image, int count) { * Note that the image size has to match the image list icon size. */ long hBitmap = 0, hMask = 0; - ImageData data = image.getImageData (DPIUtil.getDeviceZoom ()); + ImageData data = image.getImageDataAtCurrentZoom(); switch (data.getTransparencyType ()) { case SWT.TRANSPARENCY_ALPHA: /* diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontProvider.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontProvider.java new file mode 100644 index 00000000000..40de9888d40 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontProvider.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import java.util.*; + +import org.eclipse.swt.graphics.*; + +/** + * This internal class is used to provide and cache fonts scaled for different zoom levels in the win32 + * implementation. Depending on the configuration of the SWT application, either a default behavior or + * the scaling behavior is used. The default behavior mimics the existing behavior that fonts are scaled + * to the zoom of the primary monitor and are not updated on runtime. The scaling behavior will always + * take the provided values for the zoom into consideration and return scaled variant of a font if necessary. + */ +public class SWTFontProvider { + private static Map fontRegistries = new HashMap<>(); + + private static SWTFontRegistry getFontRegistry(Device device) { + return fontRegistries.computeIfAbsent(device, SWTFontProvider::newFontRegistry); + } + + public static Font getSystemFont(Device device, int zoom) { + return getFontRegistry(device).getSystemFont(zoom); + } + + public static Font getFont(Device device, FontData fontData, int zoom) { + return getFontRegistry(device).getFont(fontData, zoom); + } + + public static void disposeFontRegistry(Device device) { + SWTFontRegistry fontRegistry = fontRegistries.remove(device); + if (fontRegistry != null) { + fontRegistry.dispose(); + } + } + + private static SWTFontRegistry newFontRegistry(Device device) { + if (DPIUtil.isAutoScaleOnRuntimeActive()) { + return new ScalingSWTFontRegistry(device); + } + return new DefaultSWTFontRegistry(device); + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontRegistry.java new file mode 100644 index 00000000000..02a19ee2a1d --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/SWTFontRegistry.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import org.eclipse.swt.graphics.*; + +/** + * This class is used in the win32 implementation only to support + * re-usage of fonts. + *

+ * IMPORTANT: This class is not part of the public + * API for SWT. It is marked public only so that it can be shared + * within the packages provided by SWT. It is not available on all + * platforms, and should never be called from application code. + *

+ * @noreference This class is not intended to be referenced by clients + */ +public interface SWTFontRegistry { + + /** + * Returns a system font optimally suited for the specified zoom. + * + * @param zoom zoom in % of the standard resolution to determine the appropriate system font + * @return the system font best suited for the specified zoom + */ + Font getSystemFont(int zoom); + + /** + * Provides a font optimally suited for the specified zoom. Fonts created in this manner + * are managed by the font registry and should not be disposed of externally. + * + * @param fontData the data used to create the font + * @param zoom zoom in % of the standard resolution to determine the appropriate font + * @return the font best suited for the specified zoom + */ + Font getFont(FontData fontData, int zoom); + + /** + * Disposes all fonts managed by the font registry. + */ + void dispose(); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ScalingSWTFontRegistry.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ScalingSWTFontRegistry.java new file mode 100644 index 00000000000..0b7a5a6f6dd --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/internal/ScalingSWTFontRegistry.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import java.util.*; +import java.util.Map.*; + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.win32.*; + +/** + * This class is used in the win32 implementation only to support + * scaling of fonts in multiple DPI zoom levels. + * + * As this class is only intended to be used internally via {@code SWTFontProvider}, + * it should neither be instantiated nor referenced in a client application. + * The behavior can change any time in a future release. + * + * @noreference This class is not intended to be referenced by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class ScalingSWTFontRegistry implements SWTFontRegistry { + private class ScaledFontContainer { + // the first (unknown) font to be requested as scaled variant + // usually it is scaled to the primary monitor zoom, but that is not guaranteed + private Font baseFont; + private Map scaledFonts = new HashMap<>(); + + ScaledFontContainer(Font baseFont, int fontZoom) { + this.baseFont = baseFont; + scaledFonts.put(fontZoom, baseFont); + } + + private Font getScaledFont(int targetZoom) { + if (scaledFonts.containsKey(targetZoom)) { + Font font = scaledFonts.get(targetZoom); + if (font.isDisposed()) { + scaledFonts.remove(targetZoom); + return null; + } + return font; + } + return null; + } + + private Font scaleFont(int zoom) { + FontData fontData = baseFont.getFontData()[0]; + fontData.data.lfHeight = computePixels(zoom, fontData); + Font scaledFont = Font.win32_new(device, fontData, zoom); + addScaledFont(zoom, scaledFont); + return scaledFont; + } + + private void addScaledFont(int targetZoom, Font scaledFont) { + scaledFonts.put(targetZoom, scaledFont); + } + } + + private static FontData KEY_SYSTEM_FONTS = new FontData(); + private Map fontHandleMap = new HashMap<>(); + private Map fontKeyMap = new HashMap<>(); + private Device device; + + public ScalingSWTFontRegistry(Device device) { + this.device = device; + } + + @Override + public Font getSystemFont(int zoom) { + ScaledFontContainer container = getOrCreateBaseSystemFontContainer(device); + + Font systemFont = container.getScaledFont(zoom); + if (systemFont != null) { + return systemFont; + } + long systemFontHandle = createSystemFont(zoom); + systemFont = Font.win32_new(device, systemFontHandle, zoom); + container.addScaledFont(zoom, systemFont); + return systemFont; + } + + private ScaledFontContainer getOrCreateBaseSystemFontContainer(Device device) { + ScaledFontContainer systemFontContainer = fontKeyMap.get(KEY_SYSTEM_FONTS); + if (systemFontContainer == null) { + int targetZoom = DPIUtil.mapDPIToZoom(device.getDPI().x); + long systemFontHandle = createSystemFont(targetZoom); + Font systemFont = Font.win32_new(device, systemFontHandle); + systemFontContainer = new ScaledFontContainer(systemFont, targetZoom); + fontHandleMap.put(systemFont.handle, systemFontContainer); + fontKeyMap.put(KEY_SYSTEM_FONTS, systemFontContainer); + } + return systemFontContainer; + } + + private long createSystemFont(int targetZoom) { + long hFont = 0; + NONCLIENTMETRICS info = new NONCLIENTMETRICS(); + info.cbSize = NONCLIENTMETRICS.sizeof; + if (fetchSystemParametersInfo(info, targetZoom)) { + LOGFONT logFont = info.lfMessageFont; + hFont = OS.CreateFontIndirect(logFont); + } + if (hFont == 0) + hFont = OS.GetStockObject(OS.DEFAULT_GUI_FONT); + if (hFont == 0) + hFont = OS.GetStockObject(OS.SYSTEM_FONT); + return hFont; + } + + private static boolean fetchSystemParametersInfo(NONCLIENTMETRICS info, int targetZoom) { + if (OS.WIN32_BUILD >= OS.WIN32_BUILD_WIN10_1607) { + return OS.SystemParametersInfoForDpi(OS.SPI_GETNONCLIENTMETRICS, NONCLIENTMETRICS.sizeof, info, 0, + DPIUtil.mapZoomToDPI(targetZoom)); + } else { + return OS.SystemParametersInfo(OS.SPI_GETNONCLIENTMETRICS, 0, info, 0); + } + } + + @Override + public Font getFont(FontData fontData, int zoom) { + ScaledFontContainer container; + if (fontKeyMap.containsKey(fontData)) { + container = fontKeyMap.get(fontData); + } else { + int calculatedZoom = computeZoom(fontData); + Font newFont = Font.win32_new(device, fontData, calculatedZoom); + container = new ScaledFontContainer(newFont, calculatedZoom); + fontHandleMap.put(newFont.handle, container); + fontKeyMap.put(fontData, container); + } + return getOrCreateFont(container, zoom); + } + + @Override + public void dispose() { + for (Entry fontContainerEntry : fontKeyMap.entrySet()) { + if (KEY_SYSTEM_FONTS.equals(fontContainerEntry.getKey())) { + // do not dispose the system fonts here, they are not tied to the device of this registry + continue; + } + ScaledFontContainer scaledFontContainer = fontContainerEntry.getValue(); + for (Font font : scaledFontContainer.scaledFonts.values()) { + font.dispose(); + } + } + fontKeyMap.clear(); + } + + private Font getOrCreateFont(ScaledFontContainer container, int zoom) { + Font scaledFont = container.getScaledFont(zoom); + if (scaledFont == null) { + scaledFont = container.scaleFont(zoom); + fontHandleMap.put(scaledFont.handle, container); + fontKeyMap.put(scaledFont.getFontData()[0], container); + } + return scaledFont; + } + + private int computeZoom(FontData fontData) { + int dpi = device.getDPI().x; + int pixelsAtPrimaryMonitorZoom = computePixels(fontData.height); + int value = DPIUtil.mapDPIToZoom(dpi) * fontData.data.lfHeight / pixelsAtPrimaryMonitorZoom; + return value; + } + + private int computePixels(int zoom, FontData fontData) { + int dpi = device.getDPI().x; + int adjustedLogFontHeight = computePixels(fontData.height); + int primaryZoom = DPIUtil.mapDPIToZoom(dpi); + if (zoom != primaryZoom) { + adjustedLogFontHeight *= (1f * zoom / primaryZoom); + } + return adjustedLogFontHeight; + } + + private int computePixels(float height) { + int dpi = device.getDPI().x; + return -(int)(0.5f + (height * dpi / 72f)); + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Button.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Button.java index 631846cdb06..60511ea33b2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Button.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Button.java @@ -75,6 +75,8 @@ public class Button extends Control { WNDCLASS lpWndClass = new WNDCLASS (); OS.GetClassInfo (0, ButtonClass, lpWndClass); ButtonProc = lpWndClass.lpfnWndProc; + + DPIZoomChangeRegistry.registerHandler(Button::handleDPIChange, Button.class); } /** @@ -281,7 +283,7 @@ int computeLeftMargin () { if ((style & (SWT.PUSH | SWT.TOGGLE)) == 0) return MARGIN; int margin = 0; if (image != null && text.length () != 0) { - Rectangle bounds = image.getBoundsInPixels (); + Rectangle bounds = DPIUtil.autoScaleBounds(image.getBounds(), this.getZoom(), 100); margin += bounds.width + MARGIN * 2; long oldFont = 0; long hDC = OS.GetDC (handle); @@ -343,7 +345,7 @@ else if ((style & SWT.RIGHT) != 0) { boolean hasImage = image != null, hasText = true; if (hasImage) { if (image != null) { - Rectangle rect = image.getBoundsInPixels (); + Rectangle rect = DPIUtil.autoScaleBounds(image.getBounds(), this.getZoom(), 100); width = rect.width; if (hasText && text.length () != 0) { width += MARGIN * 2; @@ -1376,11 +1378,12 @@ LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) { GC gc = GC.win32_new (nmcd.hdc, data); int margin = computeLeftMargin(); - int imageWidth = image.getBoundsInPixels().width; + Rectangle imageBounds = DPIUtil.autoScaleBounds(image.getBounds(), this.getZoom(), 100); + int imageWidth = imageBounds.width; left += (imageWidth + (isRadioOrCheck() ? 2 * MARGIN : MARGIN)); // for SWT.RIGHT_TO_LEFT right and left are inverted int x = margin + (isRadioOrCheck() ? radioOrCheckTextPadding : 3); - int y = Math.max (0, (nmcd.bottom - image.getBoundsInPixels().height) / 2); + int y = Math.max (0, (nmcd.bottom - imageBounds.height) / 2); gc.drawImage (image, DPIUtil.autoScaleDown(x), DPIUtil.autoScaleDown(y)); gc.dispose (); } @@ -1543,4 +1546,14 @@ LRESULT wmDrawChild (long wParam, long lParam) { return null; } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Button button)) { + return; + } + // Refresh the image + if (button.image != null) { + button._setImage(Image.win32_new(button.image, newZoom)); + button.updateImageList(); + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Caret.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Caret.java index 5b5342b9404..07a10815f61 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Caret.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Caret.java @@ -49,6 +49,10 @@ public class Caret extends Widget { Font font; LOGFONT oldFont; +static { + DPIZoomChangeRegistry.registerHandler(Caret::handleDPIChange, Caret.class); +} + /** * Constructs a new instance of this class given its parent * and a style value describing its behavior and appearance. @@ -148,7 +152,7 @@ public Font getFont () { checkWidget(); if (font == null) { long hFont = defaultFont (); - return Font.win32_new (display, hFont); + return Font.win32_new (display, hFont, getZoom()); } return font; } @@ -470,7 +474,8 @@ public void setFont (Font font) { if (font != null && font.isDisposed ()) { error (SWT.ERROR_INVALID_ARGUMENT); } - this.font = font; + Shell shell = parent.getShell(); + this.font = font == null ? null : Font.win32_new(font, shell.getNativeZoom()); if (hasFocus ()) setIMEFont (); } @@ -647,4 +652,18 @@ public void setVisible (boolean visible) { } } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Caret caret)) { + return; + } + + Image image = caret.getImage(); + if (image != null) { + caret.setImage(Image.win32_new(image, newZoom)); + } + + if (caret.font != null) { + caret.setFont(caret.font); + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java index e5d65b25346..5317e5c9368 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java @@ -104,6 +104,7 @@ public class Combo extends Composite { WNDCLASS lpWndClass = new WNDCLASS (); OS.GetClassInfo (0, ComboClass, lpWndClass); ComboProc = lpWndClass.lpfnWndProc; + DPIZoomChangeRegistry.registerHandler(Combo::handleDPIChange, Combo.class); } /* Undocumented values. Remained the same at least between Win7 and Win10 */ @@ -3358,4 +3359,13 @@ LRESULT wmSysKeyDown (long hwnd, long wParam, long lParam) { return result; } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Combo combo)) { + return; + } + if ((combo.style & SWT.H_SCROLL) != 0) { + combo.scrollWidth = 0; + combo.setScrollWidth(); + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java index b0b2274656f..47b6d9fa997 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java @@ -57,6 +57,10 @@ public class Composite extends Scrollable { static final int TOOLTIP_LIMIT = 4096; + static { + DPIZoomChangeRegistry.registerHandler(Composite::handleDPIChange, Composite.class); + } + /** * Prevents uninitialized instances from being created outside the package. */ @@ -1966,4 +1970,13 @@ public String toString() { return super.toString() + " [layout=" + layout + "]"; } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Composite composite)) { + return; + } + for (Control child : composite.getChildren()) { + DPIZoomChangeRegistry.applyChange(child, newZoom, scalingFactor); + } + composite.redrawInPixels (null, true); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java index 832b5cee72e..3be70d9f163 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java @@ -23,6 +23,7 @@ import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.internal.gdip.*; +import org.eclipse.swt.internal.ole.win32.*; import org.eclipse.swt.internal.win32.*; /** @@ -50,6 +51,10 @@ */ public abstract class Control extends Widget implements Drawable { + static { + DPIZoomChangeRegistry.registerHandler(Control::handleDPIChange, Control.class); + } + /** * the handle to the OS resource * (Warning: This field is platform dependent) @@ -711,8 +716,8 @@ int defaultBackground () { return OS.GetSysColor (OS.COLOR_BTNFACE); } -long defaultFont () { - return display.getSystemFont ().handle; +long defaultFont() { + return display.getSystemFont(getShell().getNativeZoom()).handle; } int defaultForeground () { @@ -1304,7 +1309,7 @@ public Font getFont () { if (font != null) return font; long hFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); if (hFont == 0) hFont = defaultFont (); - return Font.win32_new (display, hFont); + return Font.win32_new (display, hFont, getShell().getNativeZoom()); } /** @@ -3309,7 +3314,7 @@ public void setCursor (Cursor cursor) { } void setDefaultFont () { - long hFont = display.getSystemFont ().handle; + long hFont = display.getSystemFont (getShell().getNativeZoom()).handle; OS.SendMessage (handle, OS.WM_SETFONT, hFont, 0); } @@ -3408,6 +3413,11 @@ public boolean setFocus () { */ public void setFont (Font font) { checkWidget (); + Font newFont = font; + if (newFont != null) { + if (newFont.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + newFont = Font.win32_new(newFont, getShell().getNativeZoom()); + } long hFont = 0; if (font != null) { if (font.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); @@ -4675,6 +4685,13 @@ public boolean setParent (Composite parent) { long topHandle = topHandle (); if (OS.SetParent (topHandle, parent.handle) == 0) return false; this.parent = parent; + // If parent changed, zoom level might need to be adjusted + if (parent.getZoom() != getZoom()) { + int oldZoom = getZoom(); + int newZoom = parent.getZoom(); + float scalingFactor = 1f * newZoom / oldZoom; + DPIZoomChangeRegistry.applyChange(this, newZoom, scalingFactor); + } int flags = OS.SWP_NOSIZE | OS.SWP_NOMOVE | OS.SWP_NOACTIVATE; OS.SetWindowPos (topHandle, OS.HWND_BOTTOM, 0, 0, 0, 0, flags); reskin (SWT.ALL); @@ -4869,7 +4886,7 @@ LRESULT WM_DPICHANGED (long wParam, long lParam) { // Map DPI to Zoom and compare int nativeZoom = DPIUtil.mapDPIToZoom (OS.HIWORD (wParam)); int newSWTZoom = DPIUtil.getZoomForAutoscaleProperty (nativeZoom); - int oldSWTZoom = DPIUtil.getDeviceZoom(); + int oldSWTZoom = getShell().getZoom(); // Throw the DPI change event if zoom value changes if (newSWTZoom != oldSWTZoom) { @@ -4878,7 +4895,19 @@ LRESULT WM_DPICHANGED (long wParam, long lParam) { event.widget = this; event.detail = newSWTZoom; event.doit = true; + + if (DPIUtil.isAutoScaleOnRuntimeActive()) { + DPIUtil.setDeviceZoom (nativeZoom); + getShell().setNativeZoom(nativeZoom); + } + notifyListeners(SWT.ZoomChanged, event); + + if (DPIUtil.isAutoScaleOnRuntimeActive()) { + RECT rect = new RECT (); + COM.MoveMemory(rect, lParam, RECT.sizeof); + this.setBoundsInPixels(rect.left, rect.top, rect.right - rect.left, rect.bottom-rect.top); + } return LRESULT.ZERO; } return LRESULT.ONE; @@ -5757,5 +5786,36 @@ LRESULT wmScrollChild (long wParam, long lParam) { return null; } + +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Control control)) { + return; + } + resizeFont(control, control.getShell().getNativeZoom()); + + Image image = control.getBackgroundImage(); + if (image != null) { + if (image.isDisposed()) { + control.setBackgroundImage(null); + } else { + control.setBackgroundImage(Image.win32_new(image, newZoom)); + } + } +} + +private static void resizeFont(Control control, int newZoom) { + Display display = control.getDisplay(); + Font font = control.font; + if (font == null) { + long currentFontHandle = OS.SendMessage (control.handle, OS.WM_GETFONT, 0, 0); + if (currentFontHandle != 0) { + Font newFont = display.getSystemFont(newZoom); + long newFontHandle = newFont.handle; + OS.SendMessage(control.handle, OS.WM_SETFONT, newFontHandle, 1); + } + } else { + control.setFont(Font.win32_new(font, newZoom)); + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoolBar.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoolBar.java index ec12e6c5043..b115bd37707 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoolBar.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/CoolBar.java @@ -58,6 +58,7 @@ public class CoolBar extends Composite { WNDCLASS lpWndClass = new WNDCLASS (); OS.GetClassInfo (0, ReBarClass, lpWndClass); ReBarProc = lpWndClass.lpfnWndProc; + DPIZoomChangeRegistry.registerHandler(CoolBar::handleDPIChange, CoolBar.class); } static final int SEPARATOR_WIDTH = 2; static final int MAX_WIDTH = 0x7FFF; @@ -1198,4 +1199,47 @@ LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) { } return super.wmNotifyChild (hdr, wParam, lParam); } + +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof CoolBar coolBar)) { + return; + } + Point[] sizes = coolBar.getItemSizesInPixels(); + Point[] scaledSizes = new Point[sizes.length]; + Point[] prefSizes = new Point[sizes.length]; + Point[] minSizes = new Point[sizes.length]; + int[] indices = coolBar.getWrapIndices(); + int[] itemOrder = coolBar.getItemOrder(); + + CoolItem[] items = coolBar.getItems(); + for (int index = 0; index < sizes.length; index++) { + minSizes[index] = items[index].getMinimumSizeInPixels(); + prefSizes[index] = items[index].getPreferredSizeInPixels(); + } + + for (int index = 0; index < sizes.length; index++) { + CoolItem item = items[index]; + + Control control = item.control; + if (control != null) { + DPIZoomChangeRegistry.applyChange(control, newZoom, scalingFactor); + item.setControl(control); + } + + Point preferredControlSize = item.getControl().computeSizeInPixels(SWT.DEFAULT, SWT.DEFAULT, true); + int controlWidth = preferredControlSize.x; + int controlHeight = preferredControlSize.y; + if (((coolBar.style & SWT.VERTICAL) != 0)) { + scaledSizes[index] = new Point(Math.round((sizes[index].x)*scalingFactor), Math.max(Math.round((sizes[index].y)*scalingFactor),0)); + item.setMinimumSizeInPixels(Math.round(minSizes[index].x*scalingFactor), Math.max(Math.round((minSizes[index].y)*scalingFactor),controlWidth)); + item.setPreferredSizeInPixels(Math.round(prefSizes[index].x*scalingFactor), Math.max(Math.round((prefSizes[index].y)*scalingFactor),controlWidth)); + } else { + scaledSizes[index] = new Point(Math.round((sizes[index].x)*scalingFactor),Math.max(Math.round((sizes[index].y)*scalingFactor),0)); + item.setMinimumSizeInPixels(Math.round(minSizes[index].x*scalingFactor), controlHeight); + item.setPreferredSizeInPixels(Math.round(prefSizes[index].x*scalingFactor), controlHeight); + } + } + coolBar.setItemLayoutInPixels(itemOrder, indices, scaledSizes); + coolBar.updateLayout(true); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Decorations.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Decorations.java index fc31029f76b..092daff515d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Decorations.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Decorations.java @@ -112,6 +112,10 @@ public class Decorations extends Canvas { int oldWidth = OS.CW_USEDEFAULT, oldHeight = OS.CW_USEDEFAULT; RECT maxRect = new RECT(); + static { + DPIZoomChangeRegistry.registerHandler(Decorations::handleDPIChange, Decorations.class); + } + /** * Prevents uninitialized instances from being created outside the package. */ @@ -895,7 +899,7 @@ void setImages (Image image, Image [] images) { System.arraycopy (images, 0, bestImages, 0, images.length); datas = new ImageData [images.length]; for (int i=0; iMenu) and a style value @@ -1213,4 +1217,21 @@ long hwndToolTip() { } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof MenuItem menuItem)) { + return; + } + // Refresh the image + Image menuItemImage = menuItem.getImage(); + if (menuItemImage != null) { + Image currentImage = menuItemImage; + menuItem.image = null; + menuItem.setImage (Image.win32_new(currentImage, newZoom)); + } + // Refresh the sub menu + Menu subMenu = menuItem.getMenu(); + if (subMenu != null) { + DPIZoomChangeRegistry.applyChange(subMenu, newZoom, scalingFactor); + } +} } 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 a006d157a43..dd082283883 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 @@ -125,6 +125,7 @@ public class Shell extends Decorations { ToolTip [] toolTips; long hwndMDIClient, lpstrTip, toolTipHandle, balloonTipHandle, menuItemToolTipHandle; int minWidth = SWT.DEFAULT, minHeight = SWT.DEFAULT, maxWidth = SWT.DEFAULT, maxHeight = SWT.DEFAULT; + private int nativeZoom; long [] brushes; boolean showWithParent, fullScreen, wasMaximized, modified, center; String toolTitle, balloonTitle; @@ -147,6 +148,7 @@ public class Shell extends Decorations { WNDCLASS lpWndClass = new WNDCLASS (); OS.GetClassInfo (0, DialogClass, lpWndClass); DialogProc = lpWndClass.lpfnWndProc; + DPIZoomChangeRegistry.registerHandler(Shell::handleDPIChange, Shell.class); } /** @@ -298,8 +300,27 @@ public Shell (Display display, int style) { if (handle != 0 && !embedded) { state |= FOREIGN_HANDLE; } + + int shellZoom; + int shellNativeZoom; + if (parent != null) { + shellZoom = parent.getZoom(); + shellNativeZoom = parent.getNativeZoom(); + } else { + int mappedDPIZoom = getMonitor().getZoom(); + shellZoom = DPIUtil.getZoomForAutoscaleProperty(mappedDPIZoom); + shellNativeZoom = mappedDPIZoom; + } + this.setZoom(shellZoom); + this.setNativeZoom(shellNativeZoom); + reskinWidget(); createWidget (); + + + if (DPIUtil.isAutoScaleOnRuntimeActive()) { + addListener(SWT.ZoomChanged, this::handleZoomEvent); + } } /** @@ -2628,4 +2649,27 @@ LRESULT WM_WINDOWPOSCHANGING (long wParam, long lParam) { } return result; } + +/** + * The native zoom in % of the standard resolution the shell is scaled for + */ +int getNativeZoom() { + return nativeZoom; +} + +void setNativeZoom(int nativeZoom) { + this.nativeZoom = nativeZoom; +} + +private void handleZoomEvent(Event event) { + float scalingFactor = 1f * event.detail / getZoom(); + DPIZoomChangeRegistry.applyChange(this, event.detail, scalingFactor); +} + +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Shell shell)) { + return; + } + shell.layout (null, SWT.DEFER | SWT.ALL | SWT.CHANGED); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TabFolder.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TabFolder.java index a72b87151e6..29cd9b58ea1 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TabFolder.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TabFolder.java @@ -90,6 +90,7 @@ public class TabFolder extends Composite { lpWndClass.hInstance = OS.GetModuleHandle (null); lpWndClass.style &= ~(OS.CS_HREDRAW | OS.CS_VREDRAW | OS.CS_GLOBALCLASS); OS.RegisterClass (TabFolderClass, lpWndClass); + DPIZoomChangeRegistry.registerHandler(TabFolder::handleDPIChange, TabFolder.class); } /** @@ -454,7 +455,7 @@ int imageIndex (Image image) { */ if (image == null) return -1; if (imageList == null) { - Rectangle bounds = image.getBoundsInPixels (); + Rectangle bounds = DPIUtil.autoScaleBounds(image.getBounds(), this.getZoom(), 100); imageList = display.getImageList (style & SWT.RIGHT_TO_LEFT, bounds.width, bounds.height); int index = imageList.add (image); long hImageList = imageList.getHandle (); @@ -1126,4 +1127,18 @@ LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) { return super.wmNotifyChild (hdr, wParam, lParam); } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof TabFolder tabFolder)) { + return; + } + Display display = tabFolder.getDisplay(); + if (tabFolder.imageList != null) { + display.releaseImageList (tabFolder.imageList); + tabFolder.imageList = null; + } + for (int i = 0; i < tabFolder.getItemCount(); i++) { + DPIZoomChangeRegistry.applyChange(tabFolder.items[i], newZoom, scalingFactor); + } + tabFolder.layout(true, true); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java index 31399a97f2b..f4739b8b6f6 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Table.java @@ -113,6 +113,7 @@ public class Table extends Composite { TableProc = lpWndClass.lpfnWndProc; OS.GetClassInfo (0, HeaderClass, lpWndClass); HeaderProc = lpWndClass.lpfnWndProc; + DPIZoomChangeRegistry.registerHandler(Table::handleDPIChange, Table.class); } /** @@ -7327,4 +7328,44 @@ LRESULT wmNotifyToolTip (NMTTCUSTOMDRAW nmcd, long lParam) { } return null; } + +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Table table)) { + return; + } + table.settingItemHeight = true; + var scrollWidth = 0; + // Request ScrollWidth + if (table.getColumns().length == 0) { + scrollWidth = Math.round(OS.SendMessage (table.handle, OS.LVM_GETCOLUMNWIDTH, 0, 0)*scalingFactor); + } + + Display display = table.getDisplay(); + ImageList headerImageList = table.headerImageList; + // Reset ImageList + if (headerImageList != null) { + display.releaseImageList(headerImageList); + table.headerImageList = null; + } + + ImageList imageList = table.imageList; + if (imageList != null) { + display.releaseImageList(imageList); + table.imageList = null; + } + + for (TableItem item : table.getItems()) { + DPIZoomChangeRegistry.applyChange(item, newZoom, scalingFactor); + } + for (TableColumn tableColumn : table.getColumns()) { + DPIZoomChangeRegistry.applyChange(tableColumn, newZoom, scalingFactor); + } + + if (table.getColumns().length == 0 && scrollWidth != 0) { + // Update scrollbar width if no columns are available + table.setScrollWidth(scrollWidth); + } + table.fixCheckboxImageListColor (true); + table.settingItemHeight = false; +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableColumn.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableColumn.java index ce6d1490492..9289c61d414 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableColumn.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableColumn.java @@ -44,6 +44,10 @@ public class TableColumn extends Item { String toolTipText; int id; + static { + DPIZoomChangeRegistry.registerHandler(TableColumn::handleDPIChange, TableColumn.class); + } + /** * Constructs a new instance of this class given its parent * (which must be a Table) and a style value @@ -883,4 +887,15 @@ void updateToolTip (int index) { } } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof TableColumn tableColumn)) { + return; + } + final int newColumnWidth = Math.round(tableColumn.getWidthInPixels() * scalingFactor); + tableColumn.setWidthInPixels(newColumnWidth); + Image image = tableColumn.getImage(); + if (image != null) { + tableColumn.setImage(Image.win32_new(image, newZoom)); + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableItem.java index 0206d30cb39..a827c0fe9a6 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TableItem.java @@ -46,6 +46,10 @@ public class TableItem extends Item { int imageIndent, background = -1, foreground = -1; int [] cellBackground, cellForeground; + static { + DPIZoomChangeRegistry.registerHandler(TableItem::handleDPIChange, TableItem.class); + } + /** * Constructs a new instance of this class given its parent * (which must be a Table) and a style value @@ -866,9 +870,11 @@ public void setFont (Font font){ error (SWT.ERROR_INVALID_ARGUMENT); } Font oldFont = this.font; - if (oldFont == font) return; - this.font = font; - if (oldFont != null && oldFont.equals (font)) return; + Shell shell = parent.getShell(); + Font newFont = (font == null ? font : Font.win32_new(font, shell.getNativeZoom())); + if (oldFont == newFont) return; + this.font = newFont; + if (oldFont != null && oldFont.equals (newFont)) return; if (font != null) parent.setCustomDraw (true); if ((parent.style & SWT.VIRTUAL) != 0) cached = true; /* @@ -1266,4 +1272,29 @@ public void setText (String string) { setText (0, string); } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof TableItem tableItem)) { + return; + } + Image[] images = tableItem.images; + if (images != null) { + for (Image innerImage : images) { + if (innerImage != null) { + Image.win32_new(innerImage, newZoom); + } + } + } + Font font = tableItem.font; + if (font != null) { + tableItem.setFont(tableItem.font); + } + Font[] cellFonts = tableItem.cellFont; + if (cellFonts != null) { + Shell shell = tableItem.parent.getShell(); + for (int index = 0; index < cellFonts.length; index++) { + Font cellFont = cellFonts[index]; + cellFonts[index] = cellFont == null ? null : Font.win32_new(cellFont, shell.getNativeZoom()); + } + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Text.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Text.java index f1f403786dd..a63116b280f 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Text.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Text.java @@ -112,6 +112,7 @@ public class Text extends Scrollable { WNDCLASS lpWndClass = new WNDCLASS (); OS.GetClassInfo (0, EditClass, lpWndClass); EditProc = lpWndClass.lpfnWndProc; + DPIZoomChangeRegistry.registerHandler(Text::handleDPIChange, Text.class); } /** @@ -336,6 +337,10 @@ void createHandle () { state |= THEME_BACKGROUND; } } + addIcons(); +} + +private void addIcons() { if ((style & SWT.SEARCH) != 0) { if (display.hIconSearch == 0) { long [] phicon = new long [1]; @@ -3166,4 +3171,11 @@ LRESULT wmKeyDown (long hwnd, long wParam, long lParam) { return result; } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Text text)) { + return; + } + text.addIcons(); + text.setMargins(); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java index a9b35172594..d45121fe5cc 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolBar.java @@ -60,6 +60,7 @@ public class ToolBar extends Composite { WNDCLASS lpWndClass = new WNDCLASS (); OS.GetClassInfo (0, ToolBarClass, lpWndClass); ToolBarProc = lpWndClass.lpfnWndProc; + DPIZoomChangeRegistry.registerHandler(ToolBar::handleDPIChange, ToolBar.class); } /* @@ -1736,4 +1737,79 @@ LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) { return super.wmNotifyChild (hdr, wParam, lParam); } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof ToolBar toolBar)) { + return; + } + ToolItem[] toolItems = toolBar._getItems (); + // Only Items with SWT.Sepreator Style have an own width assigned to them + var seperatorWidth = new int[toolItems.length]; + var enabledState = new boolean[toolItems.length]; + var selectedState = new boolean[toolItems.length]; + for (int i = 0; i < toolItems.length; i++) { + ToolItem item = toolItems[i]; + if((item.style & SWT.SEPARATOR) != 0) { + // Take note of widths, so we can re-apply them later + seperatorWidth[i] = item.getWidth(); + } + // Remember states of ToolItem to apply them later + enabledState[i] = item.getEnabled(); + selectedState[i] = item.getSelection(); + + } + for (ToolItem item : toolItems) { + toolBar.destroyItem(item); + // Resize after, as zoom update changes references to imageLists + DPIZoomChangeRegistry.applyChange(item, newZoom, scalingFactor); + } + + for (int i = 0; i < toolItems.length; i++) { + ToolItem toolItem = toolItems[i]; + + toolBar.createItem(toolItem, i); + String currentText = toolItem.getText(); + toolItem.setText(" "); + toolItem.setText(currentText); + + // Refresh images (upscaling already performed by toolItem) + Image image = toolItem.getImage(); + toolItem.setImage(null); + toolItem.setImage(image); + + Image hotImage = toolItem.getHotImage(); + toolItem.setHotImage(null); + toolItem.setHotImage(hotImage); + + Image disabledImage = toolItem.getDisabledImage(); + toolItem.setDisabledImage(null); + toolItem.setDisabledImage(disabledImage); + + var content = toolItem.getControl(); + toolItem.setControl(null); + toolItem.setControl(content); + + // In SWT, Width can only be set for Separators + if ((toolItem.style & SWT.SEPARATOR) != 0) { + var width = (int)((float)(seperatorWidth[i]) * scalingFactor); + toolItem.setWidth(width); + toolItem.resizeControl(); + } + + toolItem.setEnabled(enabledState[i]); + toolItem.setSelection(selectedState[i]); + } + + // Force a refresh of the toolbar by resetting the Font + toolBar.setDropDownItems(false); + long hFont = OS.SendMessage(toolBar.handle, OS.WM_GETFONT, 0, 0); + OS.SendMessage(toolBar.handle, OS.WM_SETFONT, hFont, 0); + if((toolBar.style & SWT.VERTICAL) != 0) { + // Reset row count to prevent wrapping of buttons + toolBar.setRowCount((int)OS.SendMessage (toolBar.handle, OS.TB_BUTTONCOUNT, 0, 0)); + } + toolBar.setDropDownItems(true); + toolBar.layout(true); + toolBar.sendResize(); + toolBar.redraw(); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolItem.java index 87a0cb80cb6..f99adecca7b 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/ToolItem.java @@ -50,6 +50,10 @@ public class ToolItem extends Item { short cx; int foreground = -1, background = -1; + static { + DPIZoomChangeRegistry.registerHandler(ToolItem::handleDPIChange, ToolItem.class); + } + /** * Constructs a new instance of this class given its parent * (which must be a ToolBar) and a style value @@ -1103,7 +1107,7 @@ void updateImages (boolean enabled) { ImageList hotImageList = parent.getHotImageList (); ImageList disabledImageList = parent.getDisabledImageList(); if (info.iImage == OS.I_IMAGENONE) { - Rectangle bounds = image.getBoundsInPixels (); + Rectangle bounds = DPIUtil.autoScaleBounds(image.getBounds(), getParent().getZoom(), 100); int listStyle = parent.style & SWT.RIGHT_TO_LEFT; if (imageList == null) { imageList = display.getImageListToolBar (listStyle, bounds.width, bounds.height); @@ -1167,7 +1171,9 @@ void updateImages (boolean enabled) { if ((style & (SWT.CHECK | SWT.RADIO)) != 0) { if (!enabled) image2 = hot = disabled; } - if (imageList != null) imageList.put (info.iImage, image2); + if (imageList != null) { + imageList.put (info.iImage, image2); + } if (hotImageList != null) { hotImageList.put (info.iImage, hot != null ? hot : image2); } @@ -1213,4 +1219,38 @@ LRESULT wmCommandChild (long wParam, long lParam) { return null; } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof ToolItem item)) { + return; + } + Image image = item.getImage(); + if (image != null) { + ToolBar parent = item.getParent(); + Display display = item.getDisplay(); + int listStyle = parent.style & SWT.RIGHT_TO_LEFT; + + Rectangle bounds = DPIUtil.autoScaleBounds(image.getBounds(), newZoom, 100); + if (parent.getImageList() == null) { + parent.setImageList (display.getImageListToolBar (listStyle, bounds.width, bounds.height)); + } + if (parent.getDisabledImageList() == null) { + parent.setDisabledImageList (display.getImageListToolBarDisabled (listStyle, bounds.width, bounds.height)); + } + if (parent.getHotImageList() == null) { + parent.setHotImageList (display.getImageListToolBarHot (listStyle, bounds.width, bounds.height)); + } + Image.win32_new(image, newZoom); + + Image disabledImage = item.getDisabledImage(); + if (disabledImage != null && !disabledImage.isDisposed()) { + Image.win32_new(disabledImage, newZoom); + } + + Image hotImage = item.getHotImage(); + if (hotImage != null) { + Image.win32_new(hotImage, newZoom); + } + } + item.setWidthInPixels(0); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java index 29cc4eaefa5..faeb117ac77 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Tree.java @@ -120,6 +120,8 @@ public class Tree extends Composite { static final int INCREMENT = 5; static final int EXPLORER_EXTRA = 2; static final int DRAG_IMAGE_SIZE = 301; + // The default Indent at 100 dpi + static final int DEFAULT_INDENT = 16; static final long TreeProc; static final TCHAR TreeClass = new TCHAR (0, OS.WC_TREEVIEW, true); static final long HeaderProc; @@ -130,6 +132,7 @@ public class Tree extends Composite { TreeProc = lpWndClass.lpfnWndProc; OS.GetClassInfo (0, HeaderClass, lpWndClass); HeaderProc = lpWndClass.lpfnWndProc; + DPIZoomChangeRegistry.registerHandler(Tree::handleDPIChange, Tree.class); } /** @@ -314,7 +317,10 @@ void _setBackgroundPixel (int newPixel) { } /* Set the checkbox image list */ - if ((style & SWT.CHECK) != 0) setCheckboxImageList (); + if ((style & SWT.CHECK) != 0) { + setCheckboxImageList (); + } + updateImageList(); } } @@ -1906,8 +1912,7 @@ void createHandle () { * scale with DPI resulting in distorted glyph image * at higher DPI settings. */ - int indent = DPIUtil.autoScaleUpUsingNativeDPI(16); - OS.SendMessage(handle, OS.TVM_SETINDENT, indent, 0); + calculateAndApplyIndentSize(); createdAsRTL = (style & SWT.RIGHT_TO_LEFT) != 0; } @@ -3734,7 +3739,7 @@ boolean hitTestSelection (long hItem, int x, int y) { int imageIndex (Image image, int index) { if (image == null) return OS.I_IMAGENONE; if (imageList == null) { - Rectangle bounds = image.getBoundsInPixels (); + Rectangle bounds = DPIUtil.autoScaleBounds(image.getBounds(), this.getZoom(), 100); imageList = display.getImageList (style & SWT.RIGHT_TO_LEFT, bounds.width, bounds.height); } int imageIndex = imageList.indexOf (image); @@ -3758,7 +3763,7 @@ int imageIndex (Image image, int index) { int imageIndexHeader (Image image) { if (image == null) return OS.I_IMAGENONE; if (headerImageList == null) { - Rectangle bounds = image.getBoundsInPixels (); + Rectangle bounds = DPIUtil.autoScaleBounds(image.getBounds(), this.getZoom(), 100); headerImageList = display.getImageList (style & SWT.RIGHT_TO_LEFT, bounds.width, bounds.height); int index = headerImageList.indexOf (image); if (index == -1) index = headerImageList.add (image); @@ -5360,6 +5365,15 @@ public void setTopItem (TreeItem item) { updateScrollBar (); } +/** + * Set indent for Tree; + * In a Tree without imageList, the indent also controls the chevron (glyph) size. + */ +private void calculateAndApplyIndentSize() { + int indent = DPIUtil.autoScaleUpUsingNativeDPI(DEFAULT_INDENT); + OS.SendMessage(handle, OS.TVM_SETINDENT, indent, 0); +} + void showItem (long hItem) { /* * Bug in Windows. When TVM_ENSUREVISIBLE is used to ensure @@ -7526,7 +7540,7 @@ LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) { } case OS.NM_CUSTOMDRAW: { if (hdr.hwndFrom == hwndHeader) break; - if (hooks (SWT.MeasureItem)) { + if (hooks (SWT.MeasureItem)) { if (hwndHeader == 0) createParent (); } if (!customDraw && findImageControl () == null) { @@ -7912,9 +7926,10 @@ LRESULT wmNotifyHeader (NMHDR hdr, long wParam, long lParam) { GCData data = new GCData(); data.device = display; GC gc = GC.win32_new (nmcd.hdc, data); - int y = Math.max (0, (nmcd.bottom - columns[i].image.getBoundsInPixels().height) / 2); + Rectangle imageBounds = DPIUtil.autoScaleBounds(columns[i].image.getBounds(), this.getZoom(), 100); + int y = Math.max (0, (nmcd.bottom - imageBounds.height) / 2); gc.drawImage (columns[i].image, DPIUtil.autoScaleDown(x), DPIUtil.autoScaleDown(y)); - x += columns[i].image.getBoundsInPixels().width + 12; + x += imageBounds.width + 12; gc.dispose (); } @@ -8248,4 +8263,38 @@ LRESULT wmNotifyToolTip (NMTTCUSTOMDRAW nmcd, long lParam) { return null; } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof Tree tree)) { + return; + } + Display display = tree.getDisplay(); + // Reset ImageList + if (tree.headerImageList != null) { + display.releaseImageList(tree.headerImageList); + tree.headerImageList = null; + } + if (tree.imageList != null) { + display.releaseImageList(tree.imageList); + // Reset the Imagelist of the OS as well; Will be recalculated when updating items + OS.SendMessage (tree.handle, OS.TVM_SETIMAGELIST, 0, 0); + tree.imageList = null; + } + + if (tree.hooks(SWT.MeasureItem)) { + // with the measure item hook, the height must be programmatically recalculated + var itemHeight = tree.getItemHeightInPixels(); + tree.setItemHeight(Math.round(itemHeight * scalingFactor)); + } + for (TreeColumn treeColumn : tree.getColumns()) { + DPIZoomChangeRegistry.applyChange(treeColumn, newZoom, scalingFactor); + } + for (TreeItem item : tree.getItems()) { + DPIZoomChangeRegistry.applyChange(item, newZoom, scalingFactor); + } + + tree.updateOrientation(); + tree.setScrollWidth(); + // Reset of CheckBox Size required (if SWT.Check is not set, this is a no-op) + tree.setCheckboxImageList(); +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeColumn.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeColumn.java index 285d1b8a661..d5cf637382f 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeColumn.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeColumn.java @@ -46,6 +46,10 @@ public class TreeColumn extends Item { String toolTipText; int id; + static { + DPIZoomChangeRegistry.registerHandler(TreeColumn::handleDPIChange, TreeColumn.class); + } + /** * Constructs a new instance of this class given its parent * (which must be a Tree) and a style value @@ -755,4 +759,15 @@ void updateToolTip (int index) { } } } + +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof TreeColumn treeColumn)) { + return; + } + treeColumn.setWidth(Math.round(treeColumn.getWidth() * scalingFactor)); + Image image = treeColumn.image; + if (image != null) { + treeColumn.setImage (Image.win32_new(image, newZoom)); + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeItem.java index 4758478318f..a5cbc6caaef 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TreeItem.java @@ -60,6 +60,10 @@ public class TreeItem extends Item { int background = -1, foreground = -1; int [] cellBackground, cellForeground; + static { + DPIZoomChangeRegistry.registerHandler(TreeItem::handleDPIChange, TreeItem.class); + } + /** * Constructs TreeItem and inserts it into Tree. * Item is inserted as last direct child of the tree. @@ -1385,7 +1389,8 @@ public void setFont (Font font){ } Font oldFont = this.font; if (oldFont == font) return; - this.font = font; + Shell shell = parent.getShell(); + this.font = (font == null ? font : Font.win32_new(font, shell.getNativeZoom())); if (oldFont != null && oldFont.equals (font)) return; if (font != null) parent.customDraw = true; if ((parent.style & SWT.VIRTUAL) != 0) cached = true; @@ -1439,7 +1444,8 @@ public void setFont (int index, Font font) { } Font oldFont = cellFont [index]; if (oldFont == font) return; - cellFont [index] = font; + Shell shell = parent.getShell(); + cellFont [index] = font == null ? font : Font.win32_new(font, shell.getNativeZoom()); if (oldFont != null && oldFont.equals (font)) return; if (font != null) parent.customDraw = true; if ((parent.style & SWT.VIRTUAL) != 0) cached = true; @@ -1811,4 +1817,32 @@ String getNameText () { return super.getNameText (); } +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + if (!(widget instanceof TreeItem treeItem)) { + return; + } + Image[] images = treeItem.images; + if (images != null) { + for (Image innerImage : images) { + if (innerImage != null) { + Image.win32_new(innerImage, newZoom); + } + } + } + Font font = treeItem.font; + if (font != null) { + treeItem.setFont(font); + } + Font[] cellFonts = treeItem.cellFont; + if (cellFonts != null) { + Shell shell = treeItem.parent.getShell(); + for (int index = 0; index < cellFonts.length; index++) { + Font cellFont = cellFonts[index]; + cellFonts[index] = cellFont == null ? null : Font.win32_new(cellFont, shell.getNativeZoom()); + } + } + for (TreeItem item : treeItem.getItems()) { + DPIZoomChangeRegistry.applyChange(item, newZoom, scalingFactor); + } +} } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java index 4eabdd6e78c..1fcf440f448 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java @@ -51,6 +51,8 @@ * @see Sample code and further information */ public abstract class Widget { + + private int zoom; int style, state; Display display; EventTable eventTable; @@ -124,6 +126,7 @@ public abstract class Widget { icce.dwSize = INITCOMMONCONTROLSEX.sizeof; icce.dwICC = 0xffff; OS.InitCommonControlsEx (icce); + DPIZoomChangeRegistry.registerHandler(Widget::handleDPIChange, Widget.class); } /** @@ -166,6 +169,7 @@ public Widget (Widget parent, int style) { checkSubclass (); checkParent (parent); this.style = style; + this.zoom = parent != null ? parent.getZoom() : DPIUtil.getDeviceZoom(); display = parent.display; reskinWidget (); notifyCreationTracker(); @@ -2631,4 +2635,19 @@ void notifyDisposalTracker() { } } + +/** + * The current DPI zoom level the widget is scaled for + */ +int getZoom() { + return zoom; +} + +void setZoom(int zoom) { + this.zoom = zoom; +} + +private static void handleDPIChange(Widget widget, int newZoom, float scalingFactor) { + widget.setZoom(newZoom); +} } diff --git a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/graphics/ImageWin32Tests.java b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/graphics/ImageWin32Tests.java new file mode 100644 index 00000000000..00bbeb2c69b --- /dev/null +++ b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/graphics/ImageWin32Tests.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.graphics; + + +import static org.junit.Assert.assertEquals; + +import org.eclipse.swt.internal.DPIUtil; +import org.eclipse.swt.widgets.Display; +import org.junit.Before; +import org.junit.Test; + +/** + * Automated Tests for class org.eclipse.swt.graphics.Image + * for Windows specific behavior + * + * @see org.eclipse.swt.graphics.Image + */ +public class ImageWin32Tests { + private Display display; + + @Before + public void setUp() { + display = Display.getDefault(); + } + + @Test + public void imageMustBeRescaledOnZoomChange() { + int zoom = DPIUtil.getDeviceZoom(); + Image image = new Image(display, 10, 10); + + try { + ImageData baseImageData = image.getImageData(zoom); + assertEquals("Width should equal the initial width on the same zoom", 10, baseImageData.width); + Image scaledImage = Image.win32_new(image, zoom); + ImageData scaledImageData = scaledImage.getImageData(zoom*2); + assertEquals("Width should be doubled on doubled zoom", 10*2, scaledImageData.width); + scaledImage = Image.win32_new(image, zoom*2); + baseImageData = scaledImage.getImageData(zoom); + assertEquals("Width of ImageData must not be affected by a zoom change", 10, baseImageData.width); + } finally { + image.dispose(); + } +} +} diff --git a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/DefaultSWTFontRegistryTests.java b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/DefaultSWTFontRegistryTests.java new file mode 100644 index 00000000000..ce5ebb55c8f --- /dev/null +++ b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/DefaultSWTFontRegistryTests.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.widgets.Display; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class DefaultSWTFontRegistryTests { + private static String TEST_FONT = "Helvetica"; + private Display display; + private SWTFontRegistry fontRegistry; + + @Before + public void setUp() { + this.display = Display.getDefault(); + this.fontRegistry = new DefaultSWTFontRegistry(display); + } + + @After + public void tearDown() { + if (this.fontRegistry != null) { + this.fontRegistry.dispose(); + } + } + + @Test + public void systemFontsAreCached() { + Font font1 = fontRegistry.getSystemFont(100); + Font font2 = fontRegistry.getSystemFont(100); + assertTrue("System fonts for same zoom factor must be reused", font1 == font2); + } + + @Test + public void systemFontsAlwaysDependOnPrimaryZoom() { + int primaryZoom = display.getPrimaryMonitor().getZoom(); + FontData fontPrimary = fontRegistry.getSystemFont(primaryZoom).getFontData()[0]; + FontData font100 = fontRegistry.getSystemFont(100).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font100.getHeight()); + FontData font200 = fontRegistry.getSystemFont(200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font200.getHeight()); + + int heightFontPrimary = fontPrimary.data.lfHeight; + int heightFont100 = font100.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 100% zoom", heightFontPrimary, heightFont100); + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 200% zoom", heightFontPrimary, heightFont200); + } + + @Test + public void fontsAreCached() { + int primaryZoom = display.getPrimaryMonitor().getZoom(); + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font1 = fontRegistry.getFont(fontData, primaryZoom); + FontData fontData2 = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font2 = fontRegistry.getFont(fontData2, primaryZoom); + assertTrue("Fonts for same font data and zoom levels must be reused", font1 == font2); + } + + @Test + public void fontsAlwaysDependOnPrimaryZoom() { + int primaryZoom = display.getPrimaryMonitor().getZoom(); + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + FontData fontPrimary = fontRegistry.getFont(fontData, primaryZoom).getFontData()[0]; + FontData font100 = fontRegistry.getFont(fontData, 100).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font100.getHeight()); + FontData font200 = fontRegistry.getFont(fontData, 200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom levels", fontPrimary.getHeight(), font200.getHeight()); + + int heightFontPrimary = fontPrimary.data.lfHeight; + int heightFont100 = font100.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 100% zoom", heightFontPrimary, heightFont100); + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must not differ between primary monitor and 200% zoom", heightFontPrimary, heightFont200); + } +} diff --git a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/ScalingSWTFontRegistryTests.java b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/ScalingSWTFontRegistryTests.java new file mode 100644 index 00000000000..dcaf0e709e0 --- /dev/null +++ b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/internal/ScalingSWTFontRegistryTests.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * 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: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.widgets.Display; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ScalingSWTFontRegistryTests { + private static String TEST_FONT = "Helvetica"; + private SWTFontRegistry fontRegistry; + + @Before + public void setUp() { + this.fontRegistry = new ScalingSWTFontRegistry(Display.getDefault()); + } + + @After + public void tearDown() { + if (this.fontRegistry != null) { + this.fontRegistry.dispose(); + } + } + + @Test + public void systemFontsAreCached() { + Font font100_1 = fontRegistry.getSystemFont(100); + Font font100_2 = fontRegistry.getSystemFont(100); + assertTrue("System fonts for same zoom factor must be reused", font100_1 == font100_2); + } + + @Test + public void systemFontsAreScaled() { + FontData font100 = fontRegistry.getSystemFont(100).getFontData()[0]; + FontData font200 = fontRegistry.getSystemFont(200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom factors", font100.getHeight(), font200.getHeight()); + + int heightFont100 = font100.data.lfHeight; + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must be doubled between 100% and 200% zoom factor", heightFont100 * 2, heightFont200); + } + + @Test + public void fontsAreCached() { + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font100_1 = fontRegistry.getFont(fontData, 100); + FontData fontData2 = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font100_2 = fontRegistry.getFont(fontData2, 100); + assertTrue("Fonts for same font data and zoom factor must be reused", font100_1 == font100_2); + } + + @Test + public void fontsAreScaled() { + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + FontData font100 = fontRegistry.getFont(fontData, 100).getFontData()[0]; + FontData font200 = fontRegistry.getFont(fontData, 200).getFontData()[0]; + assertEquals("Point height must be equal for all zoom factors", font100.getHeight(), font200.getHeight()); + + int heightFont100 = font100.data.lfHeight; + int heightFont200 = font200.data.lfHeight; + assertEquals("Pixel height must be doubled between 100% and 200% zoom factor", heightFont100 * 2, heightFont200); + } + + @Test + public void recreateDisposedFonts() { + FontData fontData = new FontData(TEST_FONT, 10, SWT.NORMAL); + Font font200 = fontRegistry.getFont(fontData, 200); + assertFalse("Font must not be disposed", font200.isDisposed()); + + font200.dispose(); + Font font200New = fontRegistry.getFont(fontData, 200); + assertFalse("Disposed fonts must not be reused in the font registry", font200 == font200New); + assertFalse("Font must not be disposed", font200New.isDisposed()); + } +} diff --git a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java index 4d0df294284..ec36dc88fac 100644 --- a/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java +++ b/tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java @@ -14,6 +14,9 @@ */ package org.eclipse.swt.tests.win32; +import org.eclipse.swt.graphics.ImageWin32Tests; +import org.eclipse.swt.internal.DefaultSWTFontRegistryTests; +import org.eclipse.swt.internal.ScalingSWTFontRegistryTests; import org.eclipse.swt.tests.win32.widgets.TestTreeColumn; import org.eclipse.swt.tests.win32.widgets.Test_org_eclipse_swt_widgets_Display; import org.junit.runner.JUnitCore; @@ -23,6 +26,9 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ + DefaultSWTFontRegistryTests.class, + ImageWin32Tests.class, + ScalingSWTFontRegistryTests.class, Test_org_eclipse_swt_dnd_DND.class, Test_org_eclipse_swt_events_KeyEvent.class, Test_org_eclipse_swt_widgets_Display.class, diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java index 88a6303492a..69e2637fda9 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Image.java @@ -875,6 +875,14 @@ void getImageData_int(int zoom) { Rectangle boundsAtZoom = new Rectangle(0, 0, imageDataAtZoom.width, imageDataAtZoom.height); assertEquals(":a: Size of ImageData returned from Image.getImageData(int) method doesn't return matches with bounds in Pixel values.", scaleBounds(bounds, zoom, 100), boundsAtZoom); + // creates second bitmap image and compare size of imageData + image = new Image(display, bounds); + imageDataAtZoom = image.getImageData(zoom); + boundsAtZoom = new Rectangle(0, 0, imageDataAtZoom.width, imageDataAtZoom.height); + bounds = image.getBounds(); + image.dispose(); + assertEquals(":a: Size of ImageData returned from Image.getImageData(int) method doesn't return matches with bounds in Pixel values.", scaleBounds(bounds, zoom, 100), boundsAtZoom); + // create icon image and compare size of imageData ImageData imageData = new ImageData(bounds.width, bounds.height, 1, new PaletteData(new RGB[] {new RGB(0, 0, 0)})); image = new Image(display, imageData);