diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java index d2e2d7245e..92b50e6468 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java @@ -47,7 +47,7 @@ public class Pattern extends Resource { private final Map zoomToHandle = new HashMap<>(); - private boolean isDestroyed; + private boolean disposed; /** * Constructs a new Pattern given an image. Drawing with the resulting @@ -167,6 +167,10 @@ public Pattern(Device device, float x1, float y1, float x2, float y2, Color colo */ public Pattern(Device device, float x1, float y1, float x2, float y2, Color color1, int alpha1, Color color2, int alpha2) { super(device); + if (color1 == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (color1.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (color2 == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (color2.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); this.baseX1 = x1; this.baseX2 = x2; this.baseY1 = y1; @@ -180,18 +184,8 @@ public Pattern(Device device, float x1, float y1, float x2, float y2, Color colo this.device.registerResourceWithZoomSupport(this); } -private PatternHandle newPatternHandle(int zoom) { - if (image != null) { - return new ImagePatternHandle(zoom); - } - return new BasePatternHandle(zoom); -} - private PatternHandle getPatternHandle(int zoom) { - if (!zoomToHandle.containsKey(zoom)) { - zoomToHandle.put(zoom, newPatternHandle(zoom)); - } - return zoomToHandle.get(zoom); + return zoomToHandle.computeIfAbsent(zoom, z -> image != null ? new ImagePatternHandle(z) : new BasePatternHandle(z)); } long getHandle(int zoom) { @@ -203,7 +197,7 @@ void destroy() { device.deregisterResourceWithZoomSupport(this); zoomToHandle.values().forEach(PatternHandle::destroy); zoomToHandle.clear(); - this.isDestroyed = true; + disposed = true; } @Override @@ -237,7 +231,7 @@ Pattern copy() { */ @Override public boolean isDisposed() { - return isDestroyed; + return disposed; } /** @@ -252,8 +246,16 @@ public String toString() { return "Pattern {" + zoomToHandle + "}"; } +/** + * Converts a Win32 COLORREF value (0x00BBGGRR) and an alpha byte into a + * GDI+ ARGB color value (0xAARRGGBB). + */ +private static int colorRefToArgb(int colorRef, int alpha) { + return ((alpha & 0xFF) << 24) | ((colorRef >> 16) & 0xFF) | (colorRef & 0xFF00) | ((colorRef & 0xFF) << 16); +} + private class BasePatternHandle extends PatternHandle { - public BasePatternHandle(int zoom) { + BasePatternHandle(int zoom) { super(zoom); } @@ -264,19 +266,17 @@ long createHandle(int zoom) { float y1 = Win32DPIUtils.pointToPixel(baseY1, zoom); float x2 = Win32DPIUtils.pointToPixel(baseX2, zoom); float y2 = Win32DPIUtils.pointToPixel(baseY2, zoom); - if (color1 == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (color1.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); - if (color2 == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (color2.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); device.checkGDIP(); int colorRef1 = color1.handle; - int foreColor = ((alpha1 & 0xFF) << 24) | ((colorRef1 >> 16) & 0xFF) | (colorRef1 & 0xFF00) | ((colorRef1 & 0xFF) << 16); + int foreColor = colorRefToArgb(colorRef1, alpha1); if (x1 == x2 && y1 == y2) { handle = Gdip.SolidBrush_new(foreColor); if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES); } else { int colorRef2 = color2.handle; - int backColor = ((alpha2 & 0xFF) << 24) | ((colorRef2 >> 16) & 0xFF) | (colorRef2 & 0xFF00) | ((colorRef2 & 0xFF) << 16); + int backColor = colorRefToArgb(colorRef2, alpha2); PointF p1 = new PointF(); p1.X = x1; p1.Y = y1; @@ -287,7 +287,7 @@ long createHandle(int zoom) { if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES); if (alpha1 != 0xFF || alpha2 != 0xFF) { int a = (int)((alpha1 & 0xFF) * 0.5f + (alpha2 & 0xFF) * 0.5f); - int r = (int)(((colorRef1 & 0xFF) >> 0) * 0.5f + ((colorRef2 & 0xFF) >> 0) * 0.5f); + int r = (int)((colorRef1 & 0xFF) * 0.5f + (colorRef2 & 0xFF) * 0.5f); int g = (int)(((colorRef1 & 0xFF00) >> 8) * 0.5f + ((colorRef2 & 0xFF00) >> 8) * 0.5f); int b = (int)(((colorRef1 & 0xFF0000) >> 16) * 0.5f + ((colorRef2 & 0xFF0000) >> 16) * 0.5f); int midColor = a << 24 | r << 16 | g << 8 | b; @@ -301,7 +301,7 @@ long createHandle(int zoom) { private class ImagePatternHandle extends PatternHandle { private Image.GdipImage gdipImage; - public ImagePatternHandle(int zoom) { + ImagePatternHandle(int zoom) { super(zoom); } @@ -336,27 +336,18 @@ private void cleanupBitmap() { private abstract class PatternHandle { private final long handle; - public PatternHandle(int zoom) { + PatternHandle(int zoom) { this.handle = createHandle(zoom); } abstract long createHandle(int zoom); protected void destroy() { - int type = Gdip.Brush_GetType(handle); - switch (type) { - case Gdip.BrushTypeSolidColor: - Gdip.SolidBrush_delete(handle); - break; - case Gdip.BrushTypeHatchFill: - Gdip.HatchBrush_delete(handle); - break; - case Gdip.BrushTypeLinearGradient: - Gdip.LinearGradientBrush_delete(handle); - break; - case Gdip.BrushTypeTextureFill: - Gdip.TextureBrush_delete(handle); - break; + switch (Gdip.Brush_GetType(handle)) { + case Gdip.BrushTypeSolidColor -> Gdip.SolidBrush_delete(handle); + case Gdip.BrushTypeHatchFill -> Gdip.HatchBrush_delete(handle); + case Gdip.BrushTypeLinearGradient -> Gdip.LinearGradientBrush_delete(handle); + case Gdip.BrushTypeTextureFill -> Gdip.TextureBrush_delete(handle); } } } diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllGraphicsTests.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllGraphicsTests.java index c1921d2e9b..87ca0c0207 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllGraphicsTests.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllGraphicsTests.java @@ -35,6 +35,7 @@ Test_org_eclipse_swt_graphics_ImageLoaderEvent.class, // Test_org_eclipse_swt_graphics_PaletteData.class, // Test_org_eclipse_swt_graphics_Path.class, // + Test_org_eclipse_swt_graphics_Pattern.class, // Test_org_eclipse_swt_graphics_Point.class, // Test_org_eclipse_swt_graphics_RGB.class, // Test_org_eclipse_swt_graphics_RGBA.class, // diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Pattern.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Pattern.java new file mode 100644 index 0000000000..ad71215f9c --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_Pattern.java @@ -0,0 +1,199 @@ +/******************************************************************************* + * Copyright (c) 2026 Vector Informatik GmbH 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Pattern; +import org.eclipse.swt.widgets.Display; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Automated Test Suite for class org.eclipse.swt.graphics.Pattern + * + * @see org.eclipse.swt.graphics.Pattern + */ +public class Test_org_eclipse_swt_graphics_Pattern { + + private Display display; + + @BeforeEach + public void setUp() { + display = Display.getDefault(); + } + + @AfterEach + public void tearDown() { + // display is shared; do not dispose + } + + // --- Image-based constructor --- + + @Test + public void test_Constructor_image_valid() { + Image image = new Image(display, 10, 10); + try { + Pattern pattern = new Pattern(display, image); + assertFalse(pattern.isDisposed(), "Newly constructed Pattern must not be disposed"); + pattern.dispose(); + } finally { + image.dispose(); + } + } + + @Test + public void test_Constructor_image_nullImage() { + assertThrows(IllegalArgumentException.class, () -> new Pattern(display, (Image) null)); + } + + @Test + public void test_Constructor_image_disposedImage() { + Image image = new Image(display, 10, 10); + image.dispose(); + assertThrows(IllegalArgumentException.class, () -> new Pattern(display, image)); + } + + // --- Gradient constructors --- + + @Test + public void test_Constructor_gradient_valid() { + Color red = display.getSystemColor(SWT.COLOR_RED); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue); + assertFalse(pattern.isDisposed(), "Newly constructed Pattern must not be disposed"); + pattern.dispose(); + } + + @Test + public void test_Constructor_gradientAlpha_valid() { + Color red = display.getSystemColor(SWT.COLOR_RED); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, 128, blue, 255); + assertFalse(pattern.isDisposed(), "Newly constructed Pattern must not be disposed"); + pattern.dispose(); + } + + @Test + public void test_Constructor_gradient_nullColor1() { + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, null, blue)); + } + + @Test + public void test_Constructor_gradient_nullColor2() { + Color red = display.getSystemColor(SWT.COLOR_RED); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, red, null)); + } + + @Test + public void test_Constructor_gradientAlpha_nullColor1() { + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, null, 255, blue, 255)); + } + + @Test + public void test_Constructor_gradientAlpha_nullColor2() { + Color red = display.getSystemColor(SWT.COLOR_RED); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, red, 255, null, 255)); + } + + @Test + public void test_Constructor_gradient_disposedColor1() { + Color red = new Color(display, 255, 0, 0); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + red.dispose(); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, red, blue)); + } + + @Test + public void test_Constructor_gradient_disposedColor2() { + Color red = display.getSystemColor(SWT.COLOR_RED); + Color blue = new Color(display, 0, 0, 255); + blue.dispose(); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, red, blue)); + } + + @Test + public void test_Constructor_gradientAlpha_disposedColor1() { + Color red = new Color(display, 255, 0, 0); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + red.dispose(); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, red, 255, blue, 255)); + } + + @Test + public void test_Constructor_gradientAlpha_disposedColor2() { + Color red = display.getSystemColor(SWT.COLOR_RED); + Color blue = new Color(display, 0, 0, 255); + blue.dispose(); + assertThrows(IllegalArgumentException.class, + () -> new Pattern(display, 0, 0, 100, 100, red, 255, blue, 255)); + } + + // --- isDisposed --- + + @Test + public void test_isDisposed() { + Color red = display.getSystemColor(SWT.COLOR_RED); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue); + + assertFalse(pattern.isDisposed(), "Pattern must not be disposed before dispose() is called"); + pattern.dispose(); + assertTrue(pattern.isDisposed(), "Pattern must be disposed after dispose() is called"); + } + + @Test + public void test_dispose_twice() { + Color red = display.getSystemColor(SWT.COLOR_RED); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue); + + // Disposing twice must not throw + pattern.dispose(); + pattern.dispose(); + assertTrue(pattern.isDisposed()); + } + + // --- toString --- + + @Test + public void test_toString() { + Color red = display.getSystemColor(SWT.COLOR_RED); + Color blue = display.getSystemColor(SWT.COLOR_BLUE); + Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue); + + String s = pattern.toString(); + assertNotNull(s, "toString() must not return null"); + assertFalse(s.isEmpty(), "toString() must not return an empty string"); + + pattern.dispose(); + s = pattern.toString(); + assertNotNull(s, "toString() on disposed Pattern must not return null"); + assertFalse(s.isEmpty(), "toString() on disposed Pattern must not return an empty string"); + } + +}