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

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

see #8465 - use diamond operator where applicable

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