// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.widgets;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Shape;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.text.AttributeSet;
import javax.swing.text.Element;
import javax.swing.text.html.ImageView;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Utils;
/**
* Specialized Image View allowing to display SVG images.
* @since 8933
*/
public class JosmImageView extends ImageView {
private static final int LOADING_FLAG = 1;
private static final int WIDTH_FLAG = 4;
private static final int HEIGHT_FLAG = 8;
private static final int RELOAD_FLAG = 16;
private static final int RELOAD_IMAGE_FLAG = 32;
private final Field imageField;
private final Field stateField;
private final Field widthField;
private final Field heightField;
/**
* Constructs a new {@code JosmImageView}.
* @param elem the element to create a view for
* @throws SecurityException see {@link Class#getDeclaredField} for details
* @throws NoSuchFieldException see {@link Class#getDeclaredField} for details
*/
public JosmImageView(Element elem) throws NoSuchFieldException {
super(elem);
imageField = ImageView.class.getDeclaredField("image");
stateField = ImageView.class.getDeclaredField("state");
widthField = ImageView.class.getDeclaredField("width");
heightField = ImageView.class.getDeclaredField("height");
Utils.setObjectsAccessible(imageField, stateField, widthField, heightField);
}
/**
* Makes sure the necessary properties and image is loaded.
*/
private void doSync() {
try {
int s = (int) stateField.get(this);
if ((s & RELOAD_IMAGE_FLAG) != 0) {
doRefreshImage();
}
s = (int) stateField.get(this);
if ((s & RELOAD_FLAG) != 0) {
synchronized (this) {
stateField.set(this, ((int) stateField.get(this) | RELOAD_FLAG) ^ RELOAD_FLAG);
}
setPropertiesFromAttributes();
}
} catch (IllegalArgumentException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException | SecurityException e) {
Main.error(e);
}
}
/**
* Loads the image and updates the size accordingly. This should be
* invoked instead of invoking <code>loadImage</code> or
* <code>updateImageSize</code> directly.
* @throws IllegalAccessException see {@link Field#set} and {@link Method#invoke} for details
* @throws IllegalArgumentException see {@link Field#set} and {@link Method#invoke} for details
* @throws InvocationTargetException see {@link Method#invoke} for details
* @throws NoSuchMethodException see {@link Class#getDeclaredMethod} for details
* @throws SecurityException see {@link Class#getDeclaredMethod} for details
*/
private void doRefreshImage() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
synchronized (this) {
// clear out width/height/reloadimage flag and set loading flag
stateField.set(this, ((int) stateField.get(this) | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG |
HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG |
RELOAD_IMAGE_FLAG));
imageField.set(this, null);
widthField.set(this, 0);
heightField.set(this, 0);
}
try {
// Load the image
doLoadImage();
// And update the size params
Method updateImageSize = ImageView.class.getDeclaredMethod("updateImageSize");
Utils.setObjectsAccessible(updateImageSize);
updateImageSize.invoke(this);
} finally {
synchronized (this) {
// Clear out state in case someone threw an exception.
stateField.set(this, ((int) stateField.get(this) | LOADING_FLAG) ^ LOADING_FLAG);
}
}
}
/**
* Loads the image from the URL <code>getImageURL</code>. This should
* only be invoked from <code>refreshImage</code>.
* @throws IllegalAccessException see {@link Field#set} and {@link Method#invoke} for details
* @throws IllegalArgumentException see {@link Field#set} and {@link Method#invoke} for details
* @throws InvocationTargetException see {@link Method#invoke} for details
* @throws NoSuchMethodException see {@link Class#getDeclaredMethod} for details
* @throws SecurityException see {@link Class#getDeclaredMethod} for details
*/
private void doLoadImage() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
URL src = getImageURL();
if (src != null) {
String urlStr = src.toExternalForm();
if (urlStr.endsWith(".svg") || urlStr.endsWith(".svg?format=raw")) {
ImageIcon imgIcon = new ImageProvider(urlStr).setOptional(true).get();
imageField.set(this, imgIcon != null ? imgIcon.getImage() : null);
} else {
Method loadImage = ImageView.class.getDeclaredMethod("loadImage");
Utils.setObjectsAccessible(loadImage);
loadImage.invoke(this);
}
} else {
imageField.set(this, null);
}
}
@Override
public Image getImage() {
doSync();
return super.getImage();
}
@Override
public AttributeSet getAttributes() {
doSync();
return super.getAttributes();
}
@Override
public void paint(Graphics g, Shape a) {
doSync();
super.paint(g, a);
}
@Override
public float getPreferredSpan(int axis) {
doSync();
return super.getPreferredSpan(axis);
}
@Override
public void setSize(float width, float height) {
doSync();
super.setSize(width, height);
}
}