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

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

see #8465 - replace Utils.UTF_8 by StandardCharsets.UTF_8, new in Java 7

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