diff --git a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF
index b2c427bf1af..142f59fae71 100644
--- a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.jface;singleton:=true
-Bundle-Version: 3.38.100.qualifier
+Bundle-Version: 3.39.0
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.jface,
@@ -34,7 +34,7 @@ Export-Package: org.eclipse.jface,
org.eclipse.jface.window,
org.eclipse.jface.wizard,
org.eclipse.jface.wizard.images
-Require-Bundle: org.eclipse.swt;bundle-version="[3.126.0,4.0.0)";visibility:=reexport,
+Require-Bundle: org.eclipse.swt;bundle-version="[3.132.0,3.134.0)";visibility:=reexport,
org.eclipse.core.commands;bundle-version="[3.4.0,4.0.0)";visibility:=reexport,
org.eclipse.equinox.common;bundle-version="[3.18.0,4.0.0)",
org.eclipse.equinox.bidi;bundle-version="[0.10.0,2.0.0)";resolution:=optional
diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLHintProvider.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLHintProvider.java
new file mode 100644
index 00000000000..4d5c176cf32
--- /dev/null
+++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLHintProvider.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jface.resource;
+
+import java.net.URL;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.swt.graphics.Point;
+
+class URLHintProvider implements Supplier
+ * This demonstrates two ways to control SVG rendering size:
+ *
+ *
+ *
+ * This allows using a single SVG file at different sizes without creating + * multiple scaled versions or restricting the SVG design size. + *
+ */ +public class Snippet083SVGImageSizeHints { + + public static void main(String[] args) { + Display display = new Display(); + Shell shell = new Shell(display); + shell.setText("SVG Image Size Hints Demo"); + GridLayoutFactory.fillDefaults().numColumns(2).margins(10, 10).spacing(10, 10).applyTo(shell); + + addSection(shell, "Path-Based Size Hints", """ + SVGs placed in folders with size patterns (e.g., /icons/16x16/, /icons/32x32/) + are automatically rendered at that size. + + Example: bundle://plugin.id/icons/16x16/icon.svg + """); + + addSection(shell, "Query Parameter Size Hints", """ + You can specify size using query parameters for maximum flexibility. + This allows using the same SVG at different sizes. + + Example: bundle://plugin.id/icons/icon.svg?size=16x16 + Example: bundle://plugin.id/icons/icon.svg?size=128x128 + """); + + addSection(shell, "Zoom Support", """ + Both methods work with high-DPI displays. The hint specifies the base size, + and JFace automatically scales for 150% and 200% zoom levels. + + 16x16 at 100% zoom → 16x16 pixels + 16x16 at 150% zoom → 24x24 pixels + 16x16 at 200% zoom → 32x32 pixels + """); + + // Demonstrate with actual images from test resources if available + try { + URL svgUrl = Snippet083SVGImageSizeHints.class + .getResource("/org/eclipse/jface/snippets/resources/test-demo.svg"); + if (svgUrl != null) { + addImageDemo(shell, "Default (no hint)", svgUrl.toString(), display); + addImageDemo(shell, "With ?size=32x32", svgUrl.toString() + "?size=32x32", display); + addImageDemo(shell, "With ?size=64x64", svgUrl.toString() + "?size=64x64", display); + } + } catch (Exception e) { + // Demo images not available, that's okay + Label note = new Label(shell, SWT.WRAP); + note.setText("Note: Visual demo requires test SVG files. See URLHintProviderTest for examples."); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(note); + } + + addSection(shell, "Use Cases", """ + 1. Toolbar icons: Use ?size=16x16 for consistent toolbar sizing + 2. Wizard images: Use ?size=128x128 for large wizard graphics + 3. View icons: Place in /icons/16x16/ folder structure + 4. Multi-resolution: One SVG serves all sizes without duplication + """); + + addSection(shell, "Code Example", """ + // Using query parameter + URL iconUrl = new URL("bundle://my.plugin/icons/search.svg?size=16x16"); + ImageDescriptor desc = ImageDescriptor.createFromURL(iconUrl); + Image icon = desc.createImage(); + + // Using path-based hint + URL iconUrl = FileLocator.find(bundle, new Path("icons/16x16/search.svg")); + ImageDescriptor desc = ImageDescriptor.createFromURL(iconUrl); + """); + + shell.pack(); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + } + + private static void addSection(Shell shell, String title, String text) { + Label titleLabel = new Label(shell, SWT.BOLD); + titleLabel.setText(title); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(titleLabel); + + Label textLabel = new Label(shell, SWT.WRAP); + textLabel.setText(text.trim()); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).hint(600, SWT.DEFAULT).applyTo(textLabel); + + Label separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL); + GridDataFactory.fillDefaults().span(2, 1).grab(true, false).applyTo(separator); + } + + private static void addImageDemo(Shell shell, String description, String urlString, Display display) { + try { + URL url = new URL(urlString); + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + Image image = descriptor.createImage(display); + + Label label = new Label(shell, SWT.NONE); + label.setText(description + ":"); + GridDataFactory.fillDefaults().applyTo(label); + + Canvas canvas = new Canvas(shell, SWT.BORDER); + canvas.addPaintListener(e -> { + if (!image.isDisposed()) { + e.gc.drawImage(image, 0, 0); + } + }); + GridDataFactory.swtDefaults().hint(image.getBounds().width + 2, image.getBounds().height + 2) + .applyTo(canvas); + + canvas.addDisposeListener(e -> { + if (!image.isDisposed()) { + image.dispose(); + } + }); + } catch (Exception e) { + Label error = new Label(shell, SWT.NONE); + error.setText(description + ": Error loading image"); + GridDataFactory.fillDefaults().span(2, 1).applyTo(error); + } + } +} diff --git a/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF b/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF index 5d7848897ab..6a368779768 100644 --- a/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF +++ b/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF @@ -15,5 +15,5 @@ Export-Package: org.eclipse.jface.snippets.dialogs, org.eclipse.jface.snippets.viewers, org.eclipse.jface.snippets.window, org.eclipse.jface.snippets.wizard -Bundle-RequiredExecutionEnvironment: JavaSE-17 +Bundle-RequiredExecutionEnvironment: JavaSE-21 Automatic-Module-Name: org.eclipse.jface.snippets diff --git a/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF index b8dc172d3e3..8cadbfa2c6c 100644 --- a/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.jface.tests -Bundle-Version: 1.4.1100.qualifier +Bundle-Version: 1.4.1200.qualifier Automatic-Module-Name: org.eclipse.jface.tests Bundle-RequiredExecutionEnvironment: JavaSE-17 Require-Bundle: org.junit;bundle-version="4.12.0", diff --git a/tests/org.eclipse.jface.tests/icons/imagetests/16x16/test-icon.svg b/tests/org.eclipse.jface.tests/icons/imagetests/16x16/test-icon.svg new file mode 100644 index 00000000000..61178e6910f --- /dev/null +++ b/tests/org.eclipse.jface.tests/icons/imagetests/16x16/test-icon.svg @@ -0,0 +1,6 @@ + + diff --git a/tests/org.eclipse.jface.tests/icons/imagetests/32x32/test-icon.svg b/tests/org.eclipse.jface.tests/icons/imagetests/32x32/test-icon.svg new file mode 100644 index 00000000000..61178e6910f --- /dev/null +++ b/tests/org.eclipse.jface.tests/icons/imagetests/32x32/test-icon.svg @@ -0,0 +1,6 @@ + + diff --git a/tests/org.eclipse.jface.tests/icons/imagetests/test-icon.svg b/tests/org.eclipse.jface.tests/icons/imagetests/test-icon.svg new file mode 100644 index 00000000000..4c8ecc392a1 --- /dev/null +++ b/tests/org.eclipse.jface.tests/icons/imagetests/test-icon.svg @@ -0,0 +1,6 @@ + + diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java index 7761d2aa846..d4fb86cacd9 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/AllImagesTests.java @@ -21,7 +21,8 @@ @Suite @SelectClasses({ ImageRegistryTest.class, ResourceManagerTest.class, FileImageDescriptorTest.class, - UrlImageDescriptorTest.class, DecorationOverlayIconTest.class, DeferredImageDescriptorTest.class }) + UrlImageDescriptorTest.class, URLHintProviderTest.class, DecorationOverlayIconTest.class, + DeferredImageDescriptorTest.class }) public class AllImagesTests { public static void main(String[] args) { diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/URLHintProviderTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/URLHintProviderTest.java new file mode 100644 index 00000000000..ae5b207e386 --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/URLHintProviderTest.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.tests.images; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.net.URL; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.ImageData; +import org.junit.jupiter.api.Test; + +/** + * Tests for URLHintProvider functionality that detects desired image sizes from + * URL paths and query parameters. + * + *+ * Note: SVG size hint support requires SWT 3.132+ with native SVG loading + * capabilities. Tests verify that hints are detected and passed to the loader, + * but actual rendering depends on platform SVG support. + *
+ */ +public class URLHintProviderTest { + + @Test + public void testPathBasedHintDetection16x16() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + // If SVG size hints are supported, should be 16x16, otherwise native 128x128 + assertEquals(16, imageData.width, "Width should be 16 based on path hint"); + assertEquals(16, imageData.height, "Height should be 16 based on path hint"); + } + + @Test + public void testPathBasedHintDetection32x32() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/32x32/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(32, imageData.width, "Width should be 32 based on path hint"); + assertEquals(32, imageData.height, "Height should be 32 based on path hint"); + } + + @Test + public void testPathBasedHintDetectionZoom200() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(200); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(32, imageData.width, "Width should be 32 (16*2) at 200% zoom"); + assertEquals(32, imageData.height, "Height should be 32 (16*2) at 200% zoom"); + } + + @Test + public void testPathBasedHintDetectionZoom150() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(150); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(24, imageData.width, "Width should be 24 (16*1.5) at 150% zoom"); + assertEquals(24, imageData.height, "Height should be 24 (16*1.5) at 150% zoom"); + } + + @Test + public void testQueryParameterHintDetection() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + // Create URL with query parameter - using jar: protocol from class loader + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=16x16"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(16, imageData.width, "Width should be 16 based on query parameter"); + assertEquals(16, imageData.height, "Height should be 16 based on query parameter"); + } + + @Test + public void testQueryParameterHintDetection64x64() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=64x64"); + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(64, imageData.width, "Width should be 64 based on query parameter"); + assertEquals(64, imageData.height, "Height should be 64 based on query parameter"); + } + + @Test + public void testQueryParameterWithZoom200() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=16x16"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(200); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(32, imageData.width, "Width should be 32 (16*2) at 200% zoom"); + assertEquals(32, imageData.height, "Height should be 32 (16*2) at 200% zoom"); + } + + @Test + public void testQueryParameterRectangularSize() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=48x32"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(48, imageData.width, "Width should be 48 based on query parameter"); + assertEquals(32, imageData.height, "Height should be 32 based on query parameter"); + } + + @Test + public void testNoHintDefaultSize() throws Exception { + URL url = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(url, "Test SVG not found"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + // Without hint, SVG should render at its native viewBox size (128x128) + assertEquals(128, imageData.width, "Width should be 128 (native SVG size)"); + assertEquals(128, imageData.height, "Height should be 128 (native SVG size)"); + } + + @Test + public void testQueryParameterPrecedenceOverPath() throws Exception { + // Query parameter should take precedence over path hint + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/16x16/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + URL url = new URL(urlString + "?size=64x64"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(64, imageData.width, "Width should be 64 from query parameter, not 16 from path"); + assertEquals(64, imageData.height, "Height should be 64 from query parameter, not 16 from path"); + } + + @Test + public void testQueryParameterWithMultipleParams() throws Exception { + URL baseUrl = URLHintProviderTest.class.getResource("/icons/imagetests/test-icon.svg"); + assertNotNull(baseUrl, "Test SVG not found"); + + String urlString = baseUrl.toExternalForm(); + // Test with multiple query parameters + URL url = new URL(urlString + "?foo=bar&size=24x24&other=value"); + + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + ImageData imageData = descriptor.getImageData(100); + + assertNotNull(imageData, "ImageData should not be null"); + assertEquals(24, imageData.width, "Width should be 24 from query parameter"); + assertEquals(24, imageData.height, "Height should be 24 from query parameter"); + } + +}