Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 366452 - ImageProvider extension point could be more flexible
Summary: ImageProvider extension point could be more flexible
Status: CLOSED WORKSFORME
Alias: None
Product: Graphiti
Classification: Modeling
Component: Core (show other bugs)
Version: 0.8.0   Edit
Hardware: All All
: P3 enhancement (vote)
Target Milestone: ---   Edit
Assignee: Felix Velasco CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-12-12 14:46 EST by Rhett Hudson CLA
Modified: 2012-12-12 03:16 EST (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Rhett Hudson CLA 2011-12-12 14:46:21 EST
Build Identifier: Build id: 20110916-0149

The IImageProvider interface makes a couple of assumptions that could be more flexible.

One is that images come from the same plugin that provides the IImageProvider. I have a use case where a graphiti shape provided by my editor is customized into a large number of specialized shapes by plugins. One thing that these plugins may contribute is an icon displayed on the shape. I'd like to be able to provide an extension point to my plugin that gathers all of the images and supplies them to graphiti. That would eliminate a dependency between my client plugins and graphiti. It would eliminate code duplication for supplying images in all of my client plugins. To do that, I'd need to be able to tell graphiti which plugin the image was in rather than it telling me.

I could override getPluginId() to ignore the value set by createImageProvider() and just return what I want. I may do that for now if it works. But, that seems like an abuse of the interface contract.

The other assumption, maybe it's a requirement for reasons that I'm unaware of, is that images must be sourced from a plugin. Some of the specialized shapes in my editor are specialized by the user at run time. I'd like to be able to add, delete or modify an image in the provider map from a user supplied image that is available from a plain old file.

Reproducible: Always
Comment 1 Rhett Hudson CLA 2011-12-12 14:56:17 EST
Turns out getPluginId() is final, so overriding it won't work.

The lack of atomicity between getPluginId() and getImageFilePath() made it highly implementation dependent anyway.
Comment 2 Rhett Hudson CLA 2011-12-16 16:45:39 EST
As I try to work my way around this, I've realized that not only do the plugins that extend my editor have to depend on Graphiti to extend the image provider extension point, but my editor which extends diagramTypeProviders has to include a reference to the id of the image providers that they extend. That means that my editor plugin has to have prior knowledge of the things that extend it. Which breaks my use case.

I need to be able to register something like an IImageProvider that gets passed an id and is responsible for returning the Image.
Comment 3 Rob Cernich CLA 2012-07-18 15:17:31 EDT
I have a similar requirement.

One possible solution is to add a documentTypeExtension extension point that allows other plugins to contribute functionality within the scope of a specific document type.  I propose this extension point mirror documentTypeProvider in all aspects except the actual provider part.  This would allow the existing parse code to be reused.

I'm effectively using a HACKY version of this whereby additional plugins redeclare documentTypeProvider but do not provide an implementation.  This allows their image providers to be loaded when that specific document type is loaded.  It would be nice if there were a safer way to do this.

Best,
Rob
Comment 4 Hallvard Traetteberg CLA 2012-09-14 12:16:12 EDT
If you could support general URLs including platform:/plugin and platform:/resource instead of just paths relative to a plugin, I think most of these use cases would be handled. E.g. if you create your own extension point that lets other plugins provide new images, you can add a platform:/plugin/<bundleId> prefix to provided plugin-relative path.

This means the implementation may need a more powerful ImageDescriptor, in general reading from a URL. Here's one:

package org.ptolemy.graphiti.editor.jface;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.graphics.ImageData;

/**
 * ImageDescriptor that downloads the image data from a URL
 * Typically used by a LabelProvider with an ImageRegistry to provide icons.
 * @see LabelProvider
 * @see ImageRegistry
 * @author hal
 *
 */
public class URLImageDescriptor extends ImageDescriptor {

	private URL url;

	public URLImageDescriptor(URL url) {
		this.url = url;
	}
	public URLImageDescriptor(String urlString) throws MalformedURLException {
		this(new URL(urlString));
	}
	public URLImageDescriptor(URI uri) throws MalformedURLException {
		this(uri.toURL());
	}
	
	@Override
	public String toString() {
		return "[URLImageDescriptor @ " + getUrl() + "]";
	}
	
	public URL getUrl() {
		return url;
	}

	@Override
	public ImageData getImageData() {
		try {
			return getImageData(getUrl());
		} catch (Exception e) {
			return null;
		}
	}
	
	public ImageData getImageData(URL url) {
		InputStream inputStream = null;
		try {
			inputStream = url.openStream();
			return new ImageData(inputStream);
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException(e);
		} catch (IOException e) {
			throw new IllegalArgumentException(e);
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}
}
Comment 5 Michael Wenz CLA 2012-10-08 10:33:47 EDT
An alternative way to add images from other plugins could be to use PlatformGraphicsAlgorithms as described in http://www.eclipse.org/forums/index.php/mv/msg/389722/934103/#msg_934103
Comment 6 Rob Cernich CLA 2012-10-08 11:07:00 EDT
(In reply to comment #5)
> An alternative way to add images from other plugins could be to use
> PlatformGraphicsAlgorithms as described in
> http://www.eclipse.org/forums/index.php/mv/msg/389722/934103/#msg_934103

Wouldn't that require the plugin providing the editor to also provide an extension point for registering image providers?  The plugin providing the images won't be providing a DiagramTypeProvider.

Some background: My specific usecase relates to adding support for a custom task into the BPMN2 modeller.  To support this today, I provide a documentTypeProvider extension that defines no class.  This works, but it's definitely a hack.  My suggestion about create a documentTypeExtension, was to create an interface for adding features to an existing document type provider, without overriding the type provider itself (i.e. use all the existing code that processes the type provider, but igonore class and name attributes).
Comment 7 Hallvard Traetteberg CLA 2012-10-08 12:15:12 EDT
Just to a bit more specific about how to use PlatformGraphicsAlgorithms. I created a IGraphicsAlgorithmRendererFactory that retrieved the image from the id using ImageDescriptor's createFromURL method (jface has an internal ImageDescriptor class for URLs, and this is how you access it). From this I created an ImageFigure that I then wrapped in a Shape implementing IGraphicsAlgorithmRenderer. Since you can use platform:/plugin/ urls, this supports getting images from any plugin.

Here's the essential code:

public IGraphicsAlgorithmRenderer createGraphicsAlgorithmRenderer(IRendererContext rendererContext) {
  String imageKey = rendererContext.getPlatformGraphicsAlgorithm().getId();
  ImageRegistry imageRegistry = Activator.getDefault().getImageRegistry();
  Image image = imageRegistry.get(imageKey);
  if (image == null) {
    imageRegistry.put(imageKey, ImageDescriptor.createFromURL(new URL(imageKey)));
    image = imageRegistry.get(imageKey);
  }
  IFigure figure = null;
  if (image != null) {
    figure = new ImageFigure(image);
  else {
    figure = new Label(id);
  }
  return new GenericShapeRenderer(figure);
}

public class GenericShapeRenderer extends Shape implements IGraphicsAlgorithmRenderer {

  public GenericShapeRenderer(IFigure... childFigures) {
    for (IFigure childFigure : childFigures) {
      add(childFigure);
    }
  }
	
  @Override
  public void setBounds(Rectangle rect) {
    super.setBounds(rect);
    for (Object figure : getChildren()) {
      if (figure instanceof IFigure) {
        ((IFigure) figure).setSize(rect.width, rect.height);
      }
    }
  }

  @Override
  protected void fillShape(Graphics graphics) {
  }

  @Override
  protected void outlineShape(Graphics graphics) {
  }
}
Comment 8 Felix Velasco CLA 2012-12-10 06:05:23 EST
(In reply to comment #4)
> If you could support general URLs including platform:/plugin and
> platform:/resource instead of just paths relative to a plugin, I think most
> of these use cases would be handled. E.g. if you create your own extension
> point that lets other plugins provide new images, you can add a
> platform:/plugin/<bundleId> prefix to provided plugin-relative path.
> 
> This means the implementation may need a more powerful ImageDescriptor, in
> general reading from a URL. Here's one:
> 

The current implementation, without any change, already supports arbitrary URLs, including platform:/plugin, platform:/resource, and Bundle.getEntry urls. You can try any of this options:

URI myURI = URI.createPlatformPluginURI("plugin-id/icons/toad.png", true);
addImageFilePath("Toad", myURI.toString());

URI myURI2 = URI.createPlatformResourceURI("/Test/Frog.png", true);
addImageFilePath("Frog", myURI2.toString());

Bundle bundle = Platform.getBundle("plugin-in");
addImageFilePath("bird", bundle.getEntry("icons/bird.jpg").toString());

IResource res = ResourcesPlugin.getWorkspace().getRoot().findMember("/Test/mouse.png");
addImageFilePath("mouse", res.getLocationURI().toString());


Please note that URI in the two first examples is org.eclipse.emf.common.util.URI, from the EMF Framework, not the java.net one.
Comment 9 Felix Velasco CLA 2012-12-11 03:48:10 EST
Bug 396247 has been filled to review the relation between DTPs and ImageProviders. Further improvements will be tracked there.

If the workaround of comment #8 doesn't cover your use cases, please comment in the new bug.
Comment 10 Michael Wenz CLA 2012-12-12 03:16:22 EST
With Felix comment on this I would close this with worksforme rather than fixed, as no changes have been done.