1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.gui.widgets;
|
---|
3 |
|
---|
4 | import java.awt.Graphics;
|
---|
5 | import java.awt.Image;
|
---|
6 | import java.awt.Shape;
|
---|
7 | import java.lang.reflect.Field;
|
---|
8 | import java.lang.reflect.InvocationTargetException;
|
---|
9 | import java.lang.reflect.Method;
|
---|
10 | import java.net.URL;
|
---|
11 |
|
---|
12 | import javax.swing.ImageIcon;
|
---|
13 | import javax.swing.text.AttributeSet;
|
---|
14 | import javax.swing.text.Element;
|
---|
15 | import javax.swing.text.html.ImageView;
|
---|
16 |
|
---|
17 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
18 | import org.openstreetmap.josm.tools.Logging;
|
---|
19 | import org.openstreetmap.josm.tools.Utils;
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * Specialized Image View allowing to display SVG images.
|
---|
23 | * @since 8933
|
---|
24 | */
|
---|
25 | public class JosmImageView extends ImageView {
|
---|
26 |
|
---|
27 | private static final int LOADING_FLAG = 1;
|
---|
28 | private static final int WIDTH_FLAG = 4;
|
---|
29 | private static final int HEIGHT_FLAG = 8;
|
---|
30 | private static final int RELOAD_FLAG = 16;
|
---|
31 | private static final int RELOAD_IMAGE_FLAG = 32;
|
---|
32 |
|
---|
33 | private final Field imageField;
|
---|
34 | private final Field stateField;
|
---|
35 | private final Field widthField;
|
---|
36 | private final Field heightField;
|
---|
37 |
|
---|
38 | /**
|
---|
39 | * Constructs a new {@code JosmImageView}.
|
---|
40 | * @param elem the element to create a view for
|
---|
41 | * @throws SecurityException see {@link Class#getDeclaredField} for details
|
---|
42 | * @throws NoSuchFieldException see {@link Class#getDeclaredField} for details
|
---|
43 | */
|
---|
44 | public JosmImageView(Element elem) throws NoSuchFieldException {
|
---|
45 | super(elem);
|
---|
46 | imageField = ImageView.class.getDeclaredField("image");
|
---|
47 | stateField = ImageView.class.getDeclaredField("state");
|
---|
48 | widthField = ImageView.class.getDeclaredField("width");
|
---|
49 | heightField = ImageView.class.getDeclaredField("height");
|
---|
50 | Utils.setObjectsAccessible(imageField, stateField, widthField, heightField);
|
---|
51 | }
|
---|
52 |
|
---|
53 | /**
|
---|
54 | * Makes sure the necessary properties and image is loaded.
|
---|
55 | */
|
---|
56 | private void doSync() {
|
---|
57 | try {
|
---|
58 | int s = (int) stateField.get(this);
|
---|
59 | if ((s & RELOAD_IMAGE_FLAG) != 0) {
|
---|
60 | doRefreshImage();
|
---|
61 | }
|
---|
62 | s = (int) stateField.get(this);
|
---|
63 | if ((s & RELOAD_FLAG) != 0) {
|
---|
64 | synchronized (this) {
|
---|
65 | stateField.set(this, ((int) stateField.get(this) | RELOAD_FLAG) ^ RELOAD_FLAG);
|
---|
66 | }
|
---|
67 | setPropertiesFromAttributes();
|
---|
68 | }
|
---|
69 | } catch (IllegalArgumentException | ReflectiveOperationException | SecurityException e) {
|
---|
70 | Logging.error(e);
|
---|
71 | }
|
---|
72 | }
|
---|
73 |
|
---|
74 | /**
|
---|
75 | * Loads the image and updates the size accordingly. This should be
|
---|
76 | * invoked instead of invoking <code>loadImage</code> or
|
---|
77 | * <code>updateImageSize</code> directly.
|
---|
78 | * @throws IllegalAccessException see {@link Field#set} and {@link Method#invoke} for details
|
---|
79 | * @throws IllegalArgumentException see {@link Field#set} and {@link Method#invoke} for details
|
---|
80 | * @throws InvocationTargetException see {@link Method#invoke} for details
|
---|
81 | * @throws NoSuchMethodException see {@link Class#getDeclaredMethod} for details
|
---|
82 | * @throws SecurityException see {@link Class#getDeclaredMethod} for details
|
---|
83 | */
|
---|
84 | private void doRefreshImage() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
---|
85 | synchronized (this) {
|
---|
86 | // clear out width/height/reloadimage flag and set loading flag
|
---|
87 | stateField.set(this, ((int) stateField.get(this) | LOADING_FLAG | RELOAD_IMAGE_FLAG | WIDTH_FLAG |
|
---|
88 | HEIGHT_FLAG) ^ (WIDTH_FLAG | HEIGHT_FLAG |
|
---|
89 | RELOAD_IMAGE_FLAG));
|
---|
90 | imageField.set(this, null);
|
---|
91 | widthField.set(this, 0);
|
---|
92 | heightField.set(this, 0);
|
---|
93 | }
|
---|
94 |
|
---|
95 | try {
|
---|
96 | // Load the image
|
---|
97 | doLoadImage();
|
---|
98 |
|
---|
99 | // 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 | }
|
---|