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.Main; 018import org.openstreetmap.josm.tools.ImageProvider; 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 | IllegalAccessException | 070 InvocationTargetException | NoSuchMethodException | SecurityException e) { 071 Main.error(e); 072 } 073 } 074 075 /** 076 * Loads the image and updates the size accordingly. This should be 077 * invoked instead of invoking <code>loadImage</code> or 078 * <code>updateImageSize</code> directly. 079 * @throws IllegalAccessException see {@link Field#set} and {@link Method#invoke} for details 080 * @throws IllegalArgumentException see {@link Field#set} and {@link Method#invoke} for details 081 * @throws InvocationTargetException see {@link Method#invoke} for details 082 * @throws NoSuchMethodException see {@link Class#getDeclaredMethod} for details 083 * @throws SecurityException see {@link Class#getDeclaredMethod} for details 084 */ 085 private void doRefreshImage() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 086 synchronized (this) { 087 // clear out width/height/reloadimage flag and set loading flag 088 stateField.set(this, ((int) stateField.get(this) | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG | 089 HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG | 090 RELOAD_IMAGE_FLAG)); 091 imageField.set(this, null); 092 widthField.set(this, 0); 093 heightField.set(this, 0); 094 } 095 096 try { 097 // Load the image 098 doLoadImage(); 099 100 // And update the size params 101 Method updateImageSize = ImageView.class.getDeclaredMethod("updateImageSize"); 102 Utils.setObjectsAccessible(updateImageSize); 103 updateImageSize.invoke(this); 104 } finally { 105 synchronized (this) { 106 // Clear out state in case someone threw an exception. 107 stateField.set(this, ((int) stateField.get(this) | LOADING_FLAG) ^ LOADING_FLAG); 108 } 109 } 110 } 111 112 /** 113 * Loads the image from the URL <code>getImageURL</code>. This should 114 * only be invoked from <code>refreshImage</code>. 115 * @throws IllegalAccessException see {@link Field#set} and {@link Method#invoke} for details 116 * @throws IllegalArgumentException see {@link Field#set} and {@link Method#invoke} for details 117 * @throws InvocationTargetException see {@link Method#invoke} for details 118 * @throws NoSuchMethodException see {@link Class#getDeclaredMethod} for details 119 * @throws SecurityException see {@link Class#getDeclaredMethod} for details 120 */ 121 private void doLoadImage() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 122 URL src = getImageURL(); 123 if (src != null) { 124 String urlStr = src.toExternalForm(); 125 if (urlStr.endsWith(".svg") || urlStr.endsWith(".svg?format=raw")) { 126 ImageIcon imgIcon = new ImageProvider(urlStr).setOptional(true).get(); 127 imageField.set(this, imgIcon != null ? imgIcon.getImage() : null); 128 } else { 129 Method loadImage = ImageView.class.getDeclaredMethod("loadImage"); 130 Utils.setObjectsAccessible(loadImage); 131 loadImage.invoke(this); 132 } 133 } else { 134 imageField.set(this, null); 135 } 136 } 137 138 @Override 139 public Image getImage() { 140 doSync(); 141 return super.getImage(); 142 } 143 144 @Override 145 public AttributeSet getAttributes() { 146 doSync(); 147 return super.getAttributes(); 148 } 149 150 @Override 151 public void paint(Graphics g, Shape a) { 152 doSync(); 153 super.paint(g, a); 154 } 155 156 @Override 157 public float getPreferredSpan(int axis) { 158 doSync(); 159 return super.getPreferredSpan(axis); 160 } 161 162 @Override 163 public void setSize(float width, float height) { 164 doSync(); 165 super.setSize(width, height); 166 } 167}