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

Last change on this file since 4272 was 4272, checked in by bastiK, 13 years ago

mapcss: proper support for scaled icons (fixes #6560)

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