source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyles.java@ 7448

Last change on this file since 7448 was 7448, checked in by bastiK, 10 years ago

see #10425 - fix [7447]: lock prevented mappaint code in StyledMapRenderer from running in parallel

  • Property svn:eol-style set to native
File size: 15.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.File;
7import java.io.IOException;
8import java.io.InputStreamReader;
9import java.nio.charset.StandardCharsets;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Set;
17import java.util.concurrent.CopyOnWriteArrayList;
18
19import javax.swing.ImageIcon;
20import javax.swing.SwingUtilities;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.data.coor.LatLon;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.Tag;
26import org.openstreetmap.josm.gui.PleaseWaitRunnable;
27import org.openstreetmap.josm.gui.mappaint.StyleCache.StyleList;
28import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
29import org.openstreetmap.josm.gui.mappaint.xml.XmlStyleSource;
30import org.openstreetmap.josm.gui.preferences.SourceEntry;
31import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference.MapPaintPrefHelper;
32import org.openstreetmap.josm.gui.progress.ProgressMonitor;
33import org.openstreetmap.josm.io.CachedFile;
34import org.openstreetmap.josm.tools.ImageProvider;
35import org.openstreetmap.josm.tools.Utils;
36
37/**
38 * This class manages the ElemStyles instance. The object you get with
39 * getStyles() is read only, any manipulation happens via one of
40 * the wrapper methods here. (readFromPreferences, moveStyles, ...)
41 *
42 * On change, mapPaintSylesUpdated() is fired for all listeners.
43 */
44public final class MapPaintStyles {
45
46 private static ElemStyles styles = new ElemStyles();
47
48 /**
49 * Returns the {@link ElemStyles} instance.
50 * @return the {@code ElemStyles} instance
51 */
52 public static ElemStyles getStyles() {
53 return styles;
54 }
55
56 private MapPaintStyles() {
57 // Hide default constructor for utils classes
58 }
59
60 /**
61 * Value holder for a reference to a tag name. A style instruction
62 * <pre>
63 * text: a_tag_name;
64 * </pre>
65 * results in a tag reference for the tag <tt>a_tag_name</tt> in the
66 * style cascade.
67 */
68 public static class TagKeyReference {
69 public final String key;
70 public TagKeyReference(String key) {
71 this.key = key;
72 }
73
74 @Override
75 public String toString() {
76 return "TagKeyReference{" + "key='" + key + "'}";
77 }
78 }
79
80 /**
81 * IconReference is used to remember the associated style source for
82 * each icon URL.
83 * This is necessary because image URLs can be paths relative
84 * to the source file and we have cascading of properties from different
85 * source files.
86 */
87 public static class IconReference {
88
89 public final String iconName;
90 public final StyleSource source;
91
92 public IconReference(String iconName, StyleSource source) {
93 this.iconName = iconName;
94 this.source = source;
95 }
96
97 @Override
98 public String toString() {
99 return "IconReference{" + "iconName='" + iconName + "' source='" + source.getDisplayString() + "'}";
100 }
101 }
102
103 public static ImageIcon getIcon(IconReference ref, int width, int height) {
104 final String namespace = ref.source.getPrefName();
105 ImageIcon i = new ImageProvider(ref.iconName)
106 .setDirs(getIconSourceDirs(ref.source))
107 .setId("mappaint."+namespace)
108 .setArchive(ref.source.zipIcons)
109 .setInArchiveDir(ref.source.getZipEntryDirName())
110 .setWidth(width)
111 .setHeight(height)
112 .setOptional(true).get();
113 if (i == null) {
114 Main.warn("Mappaint style \""+namespace+"\" ("+ref.source.getDisplayString()+") icon \"" + ref.iconName + "\" not found.");
115 return null;
116 }
117 return i;
118 }
119
120 /**
121 * No icon with the given name was found, show a dummy icon instead
122 * @return the icon misc/no_icon.png, in descending priority:
123 * - relative to source file
124 * - from user icon paths
125 * - josm's default icon
126 * can be null if the defaults are turned off by user
127 */
128 public static ImageIcon getNoIcon_Icon(StyleSource source) {
129 return new ImageProvider("misc/no_icon.png")
130 .setDirs(getIconSourceDirs(source))
131 .setId("mappaint."+source.getPrefName())
132 .setArchive(source.zipIcons)
133 .setInArchiveDir(source.getZipEntryDirName())
134 .setOptional(true).get();
135 }
136
137 public static ImageIcon getNodeIcon(Tag tag) {
138 return getNodeIcon(tag, true);
139 }
140
141 public static ImageIcon getNodeIcon(Tag tag, boolean includeDeprecatedIcon) {
142 if (tag != null) {
143 Node virtualNode = new Node(LatLon.ZERO);
144 virtualNode.put(tag.getKey(), tag.getValue());
145 StyleList styleList;
146 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock();
147 try {
148 styleList = getStyles().generateStyles(virtualNode, 0.5, null, false).a;
149 } finally {
150 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock();
151 }
152 if (styleList != null) {
153 for (ElemStyle style : styleList) {
154 if (style instanceof NodeElemStyle) {
155 MapImage mapImage = ((NodeElemStyle) style).mapImage;
156 if (mapImage != null) {
157 if (includeDeprecatedIcon || mapImage.name == null || !"misc/deprecated.png".equals(mapImage.name)) {
158 return new ImageIcon(mapImage.getDisplayedNodeIcon(false));
159 } else {
160 return null; // Deprecated icon found but not wanted
161 }
162 }
163 }
164 }
165 }
166 }
167 return null;
168 }
169
170 public static List<String> getIconSourceDirs(StyleSource source) {
171 List<String> dirs = new LinkedList<>();
172
173 File sourceDir = source.getLocalSourceDir();
174 if (sourceDir != null) {
175 dirs.add(sourceDir.getPath());
176 }
177
178 Collection<String> prefIconDirs = Main.pref.getCollection("mappaint.icon.sources");
179 for (String fileset : prefIconDirs) {
180 String[] a;
181 if(fileset.indexOf('=') >= 0) {
182 a = fileset.split("=", 2);
183 } else {
184 a = new String[] {"", fileset};
185 }
186
187 /* non-prefixed path is generic path, always take it */
188 if(a[0].length() == 0 || source.getPrefName().equals(a[0])) {
189 dirs.add(a[1]);
190 }
191 }
192
193 if (Main.pref.getBoolean("mappaint.icon.enable-defaults", true)) {
194 /* don't prefix icon path, as it should be generic */
195 dirs.add("resource://images/styles/standard/");
196 dirs.add("resource://images/styles/");
197 }
198
199 return dirs;
200 }
201
202 public static void readFromPreferences() {
203 styles.clear();
204
205 Collection<? extends SourceEntry> sourceEntries = MapPaintPrefHelper.INSTANCE.get();
206
207 for (SourceEntry entry : sourceEntries) {
208 StyleSource source = fromSourceEntry(entry);
209 if (source != null) {
210 styles.add(source);
211 }
212 }
213 for (StyleSource source : styles.getStyleSources()) {
214 loadStyleForFirstTime(source);
215 }
216 fireMapPaintSylesUpdated();
217 }
218
219 private static void loadStyleForFirstTime(StyleSource source) {
220 final long startTime = System.currentTimeMillis();
221 source.loadStyleSource();
222 if (Main.pref.getBoolean("mappaint.auto_reload_local_styles", true) && source.isLocal()) {
223 try {
224 Main.fileWatcher.registerStyleSource(source);
225 } catch (IOException e) {
226 Main.error(e);
227 }
228 }
229 if (Main.isDebugEnabled()) {
230 final long elapsedTime = System.currentTimeMillis() - startTime;
231 Main.debug("Initializing map style " + source.url + " completed in " + Utils.getDurationString(elapsedTime));
232 }
233 }
234
235 private static StyleSource fromSourceEntry(SourceEntry entry) {
236 CachedFile cf = null;
237 try {
238 Set<String> mimes = new HashSet<>();
239 mimes.addAll(Arrays.asList(XmlStyleSource.XML_STYLE_MIME_TYPES.split(", ")));
240 mimes.addAll(Arrays.asList(MapCSSStyleSource.MAPCSS_STYLE_MIME_TYPES.split(", ")));
241 cf = new CachedFile(entry.url).setHttpAccept(Utils.join(", ", mimes));
242 String zipEntryPath = cf.findZipEntryPath("mapcss", "style");
243 if (zipEntryPath != null) {
244 entry.isZip = true;
245 entry.zipEntryPath = zipEntryPath;
246 return new MapCSSStyleSource(entry);
247 }
248 zipEntryPath = cf.findZipEntryPath("xml", "style");
249 if (zipEntryPath != null)
250 return new XmlStyleSource(entry);
251 if (entry.url.toLowerCase().endsWith(".mapcss"))
252 return new MapCSSStyleSource(entry);
253 if (entry.url.toLowerCase().endsWith(".xml"))
254 return new XmlStyleSource(entry);
255 else {
256 try (InputStreamReader reader = new InputStreamReader(cf.getInputStream(), StandardCharsets.UTF_8)) {
257 WHILE: while (true) {
258 int c = reader.read();
259 switch (c) {
260 case -1:
261 break WHILE;
262 case ' ':
263 case '\t':
264 case '\n':
265 case '\r':
266 continue;
267 case '<':
268 return new XmlStyleSource(entry);
269 default:
270 return new MapCSSStyleSource(entry);
271 }
272 }
273 }
274 Main.warn("Could not detect style type. Using default (xml).");
275 return new XmlStyleSource(entry);
276 }
277 } catch (IOException e) {
278 Main.warn(tr("Failed to load Mappaint styles from ''{0}''. Exception was: {1}", entry.url, e.toString()));
279 Main.error(e);
280 }
281 return null;
282 }
283
284 /**
285 * reload styles
286 * preferences are the same, but the file source may have changed
287 * @param sel the indices of styles to reload
288 */
289 public static void reloadStyles(final int... sel) {
290 List<StyleSource> toReload = new ArrayList<>();
291 List<StyleSource> data = styles.getStyleSources();
292 for (int i : sel) {
293 toReload.add(data.get(i));
294 }
295 Main.worker.submit(new MapPaintStyleLoader(toReload));
296 }
297
298 public static class MapPaintStyleLoader extends PleaseWaitRunnable {
299 private boolean canceled;
300 private Collection<StyleSource> sources;
301
302 public MapPaintStyleLoader(Collection<StyleSource> sources) {
303 super(tr("Reloading style sources"));
304 this.sources = sources;
305 }
306
307 @Override
308 protected void cancel() {
309 canceled = true;
310 }
311
312 @Override
313 protected void finish() {
314 SwingUtilities.invokeLater(new Runnable() {
315 @Override
316 public void run() {
317 fireMapPaintSylesUpdated();
318 styles.clearCached();
319 if (Main.isDisplayingMapView()) {
320 Main.map.mapView.preferenceChanged(null);
321 Main.map.mapView.repaint();
322 }
323 }
324 });
325 }
326
327 @Override
328 protected void realRun() {
329 ProgressMonitor monitor = getProgressMonitor();
330 monitor.setTicksCount(sources.size());
331 for (StyleSource s : sources) {
332 if (canceled)
333 return;
334 monitor.subTask(tr("loading style ''{0}''...", s.getDisplayString()));
335 s.loadStyleSource();
336 monitor.worked(1);
337 }
338 }
339 }
340
341 /**
342 * Move position of entries in the current list of StyleSources
343 * @param sel The indices of styles to be moved.
344 * @param delta The number of lines it should move. positive int moves
345 * down and negative moves up.
346 */
347 public static void moveStyles(int[] sel, int delta) {
348 if (!canMoveStyles(sel, delta))
349 return;
350 int[] selSorted = Utils.copyArray(sel);
351 Arrays.sort(selSorted);
352 List<StyleSource> data = new ArrayList<>(styles.getStyleSources());
353 for (int row: selSorted) {
354 StyleSource t1 = data.get(row);
355 StyleSource t2 = data.get(row + delta);
356 data.set(row, t2);
357 data.set(row + delta, t1);
358 }
359 styles.setStyleSources(data);
360 MapPaintPrefHelper.INSTANCE.put(data);
361 fireMapPaintSylesUpdated();
362 styles.clearCached();
363 Main.map.mapView.repaint();
364 }
365
366 public static boolean canMoveStyles(int[] sel, int i) {
367 if (sel.length == 0)
368 return false;
369 int[] selSorted = Utils.copyArray(sel);
370 Arrays.sort(selSorted);
371
372 if (i < 0) // Up
373 return selSorted[0] >= -i;
374 else if (i > 0) // Down
375 return selSorted[selSorted.length-1] <= styles.getStyleSources().size() - 1 - i;
376 else
377 return true;
378 }
379
380 public static void toggleStyleActive(int... sel) {
381 List<StyleSource> data = styles.getStyleSources();
382 for (int p : sel) {
383 StyleSource s = data.get(p);
384 s.active = !s.active;
385 }
386 MapPaintPrefHelper.INSTANCE.put(data);
387 if (sel.length == 1) {
388 fireMapPaintStyleEntryUpdated(sel[0]);
389 } else {
390 fireMapPaintSylesUpdated();
391 }
392 styles.clearCached();
393 Main.map.mapView.repaint();
394 }
395
396 public static void addStyle(SourceEntry entry) {
397 StyleSource source = fromSourceEntry(entry);
398 if (source != null) {
399 styles.add(source);
400 loadStyleForFirstTime(source);
401 MapPaintPrefHelper.INSTANCE.put(styles.getStyleSources());
402 fireMapPaintSylesUpdated();
403 styles.clearCached();
404 Main.map.mapView.repaint();
405 }
406 }
407
408 /***********************************
409 * MapPaintSylesUpdateListener &amp; related code
410 * (get informed when the list of MapPaint StyleSources changes)
411 */
412
413 public interface MapPaintSylesUpdateListener {
414 public void mapPaintStylesUpdated();
415 public void mapPaintStyleEntryUpdated(int idx);
416 }
417
418 protected static final CopyOnWriteArrayList<MapPaintSylesUpdateListener> listeners
419 = new CopyOnWriteArrayList<>();
420
421 public static void addMapPaintSylesUpdateListener(MapPaintSylesUpdateListener listener) {
422 if (listener != null) {
423 listeners.addIfAbsent(listener);
424 }
425 }
426
427 public static void removeMapPaintSylesUpdateListener(MapPaintSylesUpdateListener listener) {
428 listeners.remove(listener);
429 }
430
431 public static void fireMapPaintSylesUpdated() {
432 for (MapPaintSylesUpdateListener l : listeners) {
433 l.mapPaintStylesUpdated();
434 }
435 }
436
437 public static void fireMapPaintStyleEntryUpdated(int idx) {
438 for (MapPaintSylesUpdateListener l : listeners) {
439 l.mapPaintStyleEntryUpdated(idx);
440 }
441 }
442}
Note: See TracBrowser for help on using the repository browser.