001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.widgets; 003 004import java.awt.Graphics; 005import java.awt.Image; 006import java.awt.Shape; 007import java.lang.reflect.Field; 008import java.lang.reflect.InvocationTargetException; 009import java.lang.reflect.Method; 010import java.net.URL; 011 012import javax.swing.ImageIcon; 013import javax.swing.text.AttributeSet; 014import javax.swing.text.Element; 015import javax.swing.text.html.ImageView; 016 017import org.openstreetmap.josm.tools.ImageProvider; 018import org.openstreetmap.josm.tools.Logging; 019import org.openstreetmap.josm.tools.Utils; 020 021/** 022 * Specialized Image View allowing to display SVG images. 023 * @since 8933 024 */ 025public class JosmImageView extends ImageView { 026 027 private static final int LOADING_FLAG = 1; 028 private static final int WIDTH_FLAG = 4; 029 private static final int HEIGHT_FLAG = 8; 030 private static final int RELOAD_FLAG = 16; 031 private static final int RELOAD_IMAGE_FLAG = 32; 032 033 private final Field imageField; 034 private final Field stateField; 035 private final Field widthField; 036 private final Field heightField; 037 038 /** 039 * Constructs a new {@code JosmImageView}. 040 * @param elem the element to create a view for 041 * @throws SecurityException see {@link Class#getDeclaredField} for details 042 * @throws NoSuchFieldException see {@link Class#getDeclaredField} for details 043 */ 044 public JosmImageView(Element elem) throws NoSuchFieldException { 045 super(elem); 046 imageField = ImageView.class.getDeclaredField("image"); 047 stateField = ImageView.class.getDeclaredField("state"); 048 widthField = ImageView.class.getDeclaredField("width"); 049 heightField = ImageView.class.getDeclaredField("height"); 050 Utils.setObjectsAccessible(imageField, stateField, widthField, heightField); 051 } 052 053 /** 054 * Makes sure the necessary properties and image is loaded. 055 */ 056 private void doSync() { 057 try { 058 int s = (int) stateField.get(this); 059 if ((s & RELOAD_IMAGE_FLAG) != 0) { 060 doRefreshImage(); 061 } 062 s = (int) stateField.get(this); 063 if ((s & RELOAD_FLAG) != 0) { 064 synchronized (this) { 065 stateField.set(this, ((int) stateField.get(this) | RELOAD_FLAG) ^ RELOAD_FLAG); 066 } 067 setPropertiesFromAttributes(); 068 } 069 } catch (IllegalArgumentException | ReflectiveOperationException | SecurityException e) { 070 Logging.error(e); 071 } 072 } 073 074 /** 075 * Loads the image and updates the size accordingly. This should be 076 * invoked instead of invoking <code>loadImage</code> or 077 * <code>updateImageSize</code> directly. 078 * @throws IllegalAccessException see {@link Field#set} and {@link Method#invoke} for details 079 * @throws IllegalArgumentException see {@link Field#set} and {@link Method#invoke} for details 080 * @throws InvocationTargetException see {@link Method#invoke} for details 081 * @throws NoSuchMethodException see {@link Class#getDeclaredMethod} for details 082 * @throws SecurityException see {@link Class#getDeclaredMethod} for details 083 */ 084 private void doRefreshImage() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 085 synchronized (this) { 086 // clear out width/height/reloadimage flag and set loading flag 087 stateField.set(this, ((int) stateField.get(this) | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG | 088 HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG | 089 RELOAD_IMAGE_FLAG)); 090 imageField.set(this, null); 091 widthField.set(this, 0); 092 heightField.set(this, 0); 093 } 094 095 try { 096 // Load the image 097 doLoadImage(); 098 099 // And update the size params 100 Method updateImageSize = ImageView.class.getDeclaredMethod("updateImageSize"); 101 Utils.setObjectsAccessible(updateImageSize); 102 updateImageSize.invoke(this); 103 } finally { 104 synchronized (this) { 105 // Clear out state in case someone threw an exception. 106 stateField.set(this, ((int) stateField.get(this) | LOADING_FLAG) ^ LOADING_FLAG); 107 } 108 } 109 } 110 111 /** 112 * Loads the image from the URL <code>getImageURL</code>. This should 113 * only be invoked from <code>refreshImage</code>. 114 * @throws IllegalAccessException see {@link Field#set} and {@link Method#invoke} for details 115 * @throws IllegalArgumentException see {@link Field#set} and {@link Method#invoke} for details 116 * @throws InvocationTargetException see {@link Method#invoke} for details 117 * @throws NoSuchMethodException see {@link Class#getDeclaredMethod} for details 118 * @throws SecurityException see {@link Class#getDeclaredMethod} for details 119 */ 120 private void doLoadImage() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 121 URL src = getImageURL(); 122 if (src != null) { 123 String urlStr = src.toExternalForm(); 124 if (urlStr.endsWith(".svg") || urlStr.endsWith(".svg?format=raw")) { 125 ImageIcon imgIcon = new ImageProvider(urlStr).setOptional(true).get(); 126 imageField.set(this, imgIcon != null ? imgIcon.getImage() : null); 127 } else { 128 Method loadImage = ImageView.class.getDeclaredMethod("loadImage"); 129 Utils.setObjectsAccessible(loadImage); 130 loadImage.invoke(this); 131 } 132 } else { 133 imageField.set(this, null); 134 } 135 } 136 137 @Override 138 public Image getImage() { 139 doSync(); 140 return super.getImage(); 141 } 142 143 @Override 144 public AttributeSet getAttributes() { 145 doSync(); 146 return super.getAttributes(); 147 } 148 149 @Override 150 public void paint(Graphics g, Shape a) { 151 doSync(); 152 super.paint(g, a); 153 } 154 155 @Override 156 public float getPreferredSpan(int axis) { 157 doSync(); 158 return super.getPreferredSpan(axis); 159 } 160 161 @Override 162 public void setSize(float width, float height) { 163 doSync(); 164 super.setSize(width, height); 165 } 166}