Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Bundle-Activator: org.eclipse.e4.ui.internal.workbench.swt.WorkbenchSWTActivator
Require-Bundle: org.eclipse.e4.ui.workbench;bundle-version="0.10.0",
org.eclipse.e4.core.services;bundle-version="1.0.0",
org.eclipse.e4.ui.services;bundle-version="0.1.0",
org.eclipse.jface;bundle-version="[3.39.0,4.0.0)",
org.eclipse.jface;bundle-version="[3.41.0,4.0.0)",
org.eclipse.e4.ui.dialogs;bundle-version="1.1.600",
org.eclipse.core.databinding;bundle-version="[1.2.0,2.0.0)",
org.eclipse.jface.databinding;bundle-version="[1.3.0,2.0.0)",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*******************************************************************************
* Copyright (c) 2026 vogella 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
*
* Contributors:
* Lars Vogel <Lars.Vogel@vogella.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.e4.ui.internal.workbench.swt;

import org.eclipse.jface.resource.IImageURLModifier;
import org.eclipse.jface.resource.ImageDescriptor;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

/**
* Installs the highest ranked {@link IImageURLModifier} service in JFace.
* Tracked because the contributing bundle may start after the workbench.
*/
class ImageURLModifierTracker extends ServiceTracker<IImageURLModifier, IImageURLModifier> {

private ServiceReference<IImageURLModifier> installed;

ImageURLModifierTracker(BundleContext context) {
super(context, IImageURLModifier.class, null);
}

@Override
public synchronized IImageURLModifier addingService(ServiceReference<IImageURLModifier> reference) {
IImageURLModifier modifier = super.addingService(reference);
// not tracked until this returns, so it cannot be picked by installBest()
if (modifier != null && (installed == null || reference.compareTo(installed) > 0)) {
installed = reference;
ImageDescriptor.setURLModifier(modifier);
}
return modifier;
}

@Override
public synchronized void modifiedService(ServiceReference<IImageURLModifier> reference,
IImageURLModifier service) {
installBest();
}

@Override
public synchronized void removedService(ServiceReference<IImageURLModifier> reference,
IImageURLModifier service) {
super.removedService(reference, service);
if (reference.equals(installed)) {
installBest();
}
}

private void installBest() {
installed = getServiceReference();
ImageDescriptor.setURLModifier(installed == null ? null : getService(installed));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.DialogSettings;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.resource.IImageURLModifier;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
Expand All @@ -62,6 +63,7 @@ public class WorkbenchSWTActivator implements BundleActivator, DebugOptionsListe

private BundleContext context;
private ServiceTracker<?, Location> locationTracker;
private ServiceTracker<IImageURLModifier, IImageURLModifier> imageURLModifierTracker;
private static WorkbenchSWTActivator activator;
private DebugTrace trace;

Expand Down Expand Up @@ -89,11 +91,17 @@ public void start(BundleContext context) throws Exception {
Hashtable<String, String> props = new Hashtable<>(2);
props.put(DebugOptions.LISTENER_SYMBOLICNAME, PI_RENDERERS);
context.registerService(DebugOptionsListener.class, this, props);
imageURLModifierTracker = new ImageURLModifierTracker(context);
imageURLModifierTracker.open();
}

@Override
public void stop(BundleContext context) throws Exception {
saveDialogSettings();
if (imageURLModifierTracker != null) {
imageURLModifierTracker.close();
imageURLModifierTracker = null;
}
}

public Bundle getBundle() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2026 vogella 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
*
* Contributors:
* Lars Vogel <Lars.Vogel@vogella.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.resource;

import java.net.URL;

/**
* Rewrites image URLs before they are loaded, so icons can be substituted
* without changing the code that creates the image descriptors. The E4
* workbench installs the highest ranked OSGi service of this type.
* <p>
* <strong>EXPERIMENTAL</strong>. This interface has been added as part of a
* work in progress. There is no guarantee that this API will remain the same.
* </p>
*
* @see ImageDescriptor#setURLModifier(IImageURLModifier)
* @since 3.41
*/
@FunctionalInterface
public interface IImageURLModifier {

/**
* Returns the URL to load instead of the given one, or <code>null</code> to
* keep it. Called for every image load, so it must be fast and thread-safe.
*
* @param originalURL the URL the image would be loaded from
* @return the replacement URL, or <code>null</code> to keep the original
*/
URL modifyURL(URL originalURL);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ protected ImageDescriptor() {
super(shouldBeCached);
}

/** Set from OSGi service events, read from any thread. */
static volatile IImageURLModifier urlModifier;

/**
* Installs the modifier consulted when an image is loaded from a URL. Images
* created before are not reloaded.
* <p>
* <strong>EXPERIMENTAL</strong>. This method has been added as part of a work
* in progress. There is no guarantee that this API will remain the same.
* </p>
*
* @param modifier the modifier, or <code>null</code> to remove it
* @since 3.41
*/
public static void setURLModifier(IImageURLModifier modifier) {
urlModifier = modifier;
}

private static final ImageDescriptor NULL_IMAGE = createFromImageDataProvider(z -> null);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private ImageFileNameProvider createURLImageFileNameProvider() {
// The calling image will do that itself!
return getFilePath(tempURL, logIOException);
}
return getZoomedImageSource(tempURL, url, zoom, u -> getFilePath(u, logIOException));
return getZoomedImageSource(tempURL, zoom, u -> getFilePath(u, logIOException));
}
return null;
};
Expand Down Expand Up @@ -107,22 +107,23 @@ public ImageData getImageData(int zoom) {
if (zoom == 100 || canLoadAtZoom(tempURL, zoom)) {
return getImageData(tempURL, 100, zoom);
}
return getZoomedImageSource(tempURL, url, zoom, u -> getImageData(u, zoom, zoom));
return getZoomedImageSource(tempURL, zoom, u -> getImageData(u, zoom, zoom));
}
return null;
}

private static <R> R getZoomedImageSource(URL url, String urlString, int zoom, Function<URL, R> getImage) {
private static <R> R getZoomedImageSource(URL url, int zoom, Function<URL, R> getImage) {
URL xUrl = getxURL(url, zoom);
if (xUrl != null) {
R xdata = getImage.apply(xUrl);
if (xdata != null) {
return xdata;
}
}
String xpath = getxPath(urlString, zoom);
// derived from the already modified URL, so the modifier must not run again
String xpath = getxPath(url.toExternalForm(), zoom);
if (xpath != null) {
URL xPathUrl = getURL(xpath);
URL xPathUrl = toURL(xpath);
if (xPathUrl != null) {
return getImage.apply(xPathUrl);
}
Expand Down Expand Up @@ -352,14 +353,30 @@ public Image createImage(boolean returnMissingImageOnError, Device device) {
}
}

/**
* Resolves the given URL string, applying the URL modifier. Every code path of
* this descriptor resolves its URL here.
*/
private static URL getURL(String urlString) {
URL result = null;
URL result = toURL(urlString);
IImageURLModifier modifier = urlModifier;
if (result != null && modifier != null) {
URL modified = modifier.modifyURL(result);
if (modified != null) {
result = modified;
}
}
return result;
}

@SuppressWarnings("deprecation") // keeps the lenient parsing of new URL(String)
private static URL toURL(String urlString) {
try {
result = new URL(urlString);
return new URL(urlString);
} catch (MalformedURLException e) {
Policy.logException(e);
return null;
}
return result;
}

@Override
Expand Down
7 changes: 7 additions & 0 deletions examples/org.eclipse.ui.examples.urlmodifier/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
33 changes: 33 additions & 0 deletions examples/org.eclipse.ui.examples.urlmodifier/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.ui.examples.urlmodifier</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ds.core.builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
Loading
Loading