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

Last change on this file since 9299 was 9299, checked in by Don-vip, 8 years ago

checkstyle

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