source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/ElemStyles.java@ 17318

Last change on this file since 17318 was 16590, checked in by simon04, 4 years ago

fix #19197 - MapCSS JOSM_pref: check if a pref could be converted to a color instead of a string (patch by taylor.smock, modified)

  • Property svn:eol-style set to native
File size: 26.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import java.awt.Color;
5import java.util.ArrayList;
6import java.util.Collection;
7import java.util.Collections;
8import java.util.HashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.Map.Entry;
12import java.util.Objects;
13import java.util.Optional;
14
15import org.openstreetmap.josm.data.osm.INode;
16import org.openstreetmap.josm.data.osm.IPrimitive;
17import org.openstreetmap.josm.data.osm.IRelation;
18import org.openstreetmap.josm.data.osm.IWay;
19import org.openstreetmap.josm.data.osm.Relation;
20import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
21import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
22import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
23import org.openstreetmap.josm.data.preferences.NamedColorProperty;
24import org.openstreetmap.josm.gui.MainApplication;
25import org.openstreetmap.josm.gui.NavigatableComponent;
26import org.openstreetmap.josm.gui.layer.OsmDataLayer;
27import org.openstreetmap.josm.gui.mappaint.DividedScale.RangeViolatedError;
28import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
29import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement;
30import org.openstreetmap.josm.gui.mappaint.styleelement.AreaIconElement;
31import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement;
32import org.openstreetmap.josm.gui.mappaint.styleelement.DefaultStyles;
33import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement;
34import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
35import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement;
36import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
37import org.openstreetmap.josm.gui.mappaint.styleelement.TextElement;
38import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel;
39import org.openstreetmap.josm.gui.util.GuiHelper;
40import org.openstreetmap.josm.spi.preferences.Config;
41import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
42import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
43import org.openstreetmap.josm.tools.ColorHelper;
44import org.openstreetmap.josm.tools.Pair;
45
46/**
47 * Generates a list of {@link StyleElement}s for a primitive, to
48 * be drawn on the map.
49 * There are several steps to derive the list of elements for display:
50 * <ol>
51 * <li>{@link #generateStyles(IPrimitive, double, boolean)} applies the
52 * {@link StyleSource}s one after another to get a key-value map of MapCSS
53 * properties. Then a preliminary set of StyleElements is derived from the
54 * properties map.</li>
55 * <li>{@link #getImpl(IPrimitive, double, NavigatableComponent)} handles the
56 * different forms of multipolygon tagging.</li>
57 * <li>{@link #getStyleCacheWithRange(IPrimitive, double, NavigatableComponent)}
58 * adds a default StyleElement for primitives that would be invisible otherwise.
59 * (For example untagged nodes and ways.)</li>
60 * </ol>
61 * The results are cached with respect to the current scale.
62 *
63 * Use {@link #setStyleSources(Collection)} to select the StyleSources that are applied.
64 */
65public class ElemStyles implements PreferenceChangedListener {
66 private final List<StyleSource> styleSources = Collections.synchronizedList(new ArrayList<>());
67 private boolean drawMultipolygon;
68
69 private short cacheIdx = 1;
70
71 private boolean defaultNodes;
72 private boolean defaultLines;
73
74 private short defaultNodesIdx;
75 private short defaultLinesIdx;
76
77 private final Map<String, String> preferenceCache = Collections.synchronizedMap(new HashMap<>());
78
79 private volatile Color backgroundColorCache;
80
81 /**
82 * Constructs a new {@code ElemStyles}.
83 */
84 public ElemStyles() {
85 Config.getPref().addPreferenceChangeListener(this);
86 }
87
88 /**
89 * Clear the style cache for all primitives of all DataSets.
90 */
91 public void clearCached() {
92 // run in EDT to make sure this isn't called during rendering run
93 GuiHelper.runInEDT(() -> {
94 cacheIdx++;
95 preferenceCache.clear();
96 backgroundColorCache = null;
97 MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).forEach(
98 dl -> dl.data.clearMappaintCache());
99 });
100 }
101
102 /**
103 * Returns the list of style sources.
104 * @return the list of style sources
105 */
106 public List<StyleSource> getStyleSources() {
107 return Collections.<StyleSource>unmodifiableList(styleSources);
108 }
109
110 /**
111 * Returns the background color.
112 * @return the background color
113 */
114 public Color getBackgroundColor() {
115 if (backgroundColorCache != null)
116 return backgroundColorCache;
117 for (StyleSource s : styleSources) {
118 if (!s.active) {
119 continue;
120 }
121 Color backgroundColorOverride = s.getBackgroundColorOverride();
122 if (backgroundColorOverride != null) {
123 backgroundColorCache = backgroundColorOverride;
124 }
125 }
126 return Optional.ofNullable(backgroundColorCache).orElseGet(PaintColors.BACKGROUND::get);
127 }
128
129 /**
130 * Create the list of styles for one primitive.
131 *
132 * @param osm the primitive
133 * @param scale the scale (in meters per 100 pixel)
134 * @param nc display component
135 * @return list of styles
136 * @since 13810 (signature)
137 */
138 public StyleElementList get(IPrimitive osm, double scale, NavigatableComponent nc) {
139 return getStyleCacheWithRange(osm, scale, nc).a;
140 }
141
142 /**
143 * Create the list of styles and its valid scale range for one primitive.
144 *
145 * Automatically adds default styles in case no proper style was found.
146 * Uses the cache, if possible, and saves the results to the cache.
147 * @param osm OSM primitive
148 * @param scale scale
149 * @param nc navigatable component
150 * @return pair containing style list and range
151 * @since 13810 (signature)
152 */
153 public Pair<StyleElementList, Range> getStyleCacheWithRange(IPrimitive osm, double scale, NavigatableComponent nc) {
154 if (!osm.isCachedStyleUpToDate() || scale <= 0) {
155 osm.setCachedStyle(StyleCache.EMPTY_STYLECACHE);
156 } else {
157 Pair<StyleElementList, Range> lst = osm.getCachedStyle().getWithRange(scale, osm.isSelected());
158 if (lst.a != null)
159 return lst;
160 }
161 Pair<StyleElementList, Range> p = getImpl(osm, scale, nc);
162 if (osm instanceof INode && isDefaultNodes()) {
163 if (p.a.isEmpty()) {
164 if (TextLabel.AUTO_LABEL_COMPOSITION_STRATEGY.compose(osm) != null) {
165 p.a = DefaultStyles.DEFAULT_NODE_STYLELIST_TEXT;
166 } else {
167 p.a = DefaultStyles.DEFAULT_NODE_STYLELIST;
168 }
169 } else {
170 boolean hasNonModifier = false;
171 boolean hasText = false;
172 for (StyleElement s : p.a) {
173 if (s instanceof BoxTextElement) {
174 hasText = true;
175 } else {
176 if (!s.isModifier) {
177 hasNonModifier = true;
178 }
179 }
180 }
181 if (!hasNonModifier) {
182 p.a = new StyleElementList(p.a, DefaultStyles.SIMPLE_NODE_ELEMSTYLE);
183 if (!hasText && TextLabel.AUTO_LABEL_COMPOSITION_STRATEGY.compose(osm) != null) {
184 p.a = new StyleElementList(p.a, DefaultStyles.SIMPLE_NODE_TEXT_ELEMSTYLE);
185 }
186 }
187 }
188 } else if (osm instanceof IWay && isDefaultLines()) {
189 boolean hasProperLineStyle = false;
190 for (StyleElement s : p.a) {
191 if (s.isProperLineStyle()) {
192 hasProperLineStyle = true;
193 break;
194 }
195 }
196 if (!hasProperLineStyle) {
197 LineElement line = LineElement.UNTAGGED_WAY;
198 for (StyleElement element : p.a) {
199 if (element instanceof AreaElement) {
200 line = LineElement.createSimpleLineStyle(((AreaElement) element).color, true);
201 break;
202 }
203 }
204 p.a = new StyleElementList(p.a, line);
205 }
206 }
207 StyleCache style = osm.getCachedStyle() != null ? osm.getCachedStyle() : StyleCache.EMPTY_STYLECACHE;
208 try {
209 osm.setCachedStyle(style.put(p.a, p.b, osm.isSelected()));
210 } catch (RangeViolatedError e) {
211 throw new AssertionError("Range violated: " + e.getMessage()
212 + " (object: " + osm.getPrimitiveId() + ", current style: "+osm.getCachedStyle()
213 + ", scale: " + scale + ", new stylelist: " + p.a + ", new range: " + p.b + ')', e);
214 }
215 osm.declareCachedStyleUpToDate();
216 return p;
217 }
218
219 /**
220 * Create the list of styles and its valid scale range for one primitive.
221 *
222 * This method does multipolygon handling.
223 *
224 * If the primitive is a way, look for multipolygon parents. In case it
225 * is indeed member of some multipolygon as role "outer", all area styles
226 * are removed. (They apply to the multipolygon area.)
227 * Outer ways can have their own independent line styles, e.g. a road as
228 * boundary of a forest. Otherwise, in case, the way does not have an
229 * independent line style, take a line style from the multipolygon.
230 * If the multipolygon does not have a line style either, at least create a
231 * default line style from the color of the area.
232 *
233 * Now consider the case that the way is not an outer way of any multipolygon,
234 * but is member of a multipolygon as "inner".
235 * First, the style list is regenerated, considering only tags of this way.
236 * Then check, if the way describes something in its own right. (linear feature
237 * or area) If not, add a default line style from the area color of the multipolygon.
238 *
239 * @param osm OSM primitive
240 * @param scale scale
241 * @param nc navigatable component
242 * @return pair containing style list and range
243 */
244 private Pair<StyleElementList, Range> getImpl(IPrimitive osm, double scale, NavigatableComponent nc) {
245 if (osm instanceof INode)
246 return generateStyles(osm, scale, false);
247 else if (osm instanceof IWay) {
248 Pair<StyleElementList, Range> p = generateStyles(osm, scale, false);
249
250 boolean isOuterWayOfSomeMP = false;
251 Color wayColor = null;
252
253 // FIXME: Maybe in the future outer way styles apply to outers ignoring the multipolygon?
254 for (IPrimitive referrer : osm.getReferrers()) {
255 IRelation<?> r = (IRelation<?>) referrer;
256 if (!drawMultipolygon || !r.isMultipolygon() || !r.isUsable() || !(r instanceof Relation)) {
257 continue;
258 }
259 Multipolygon multipolygon = MultipolygonCache.getInstance().get((Relation) r);
260
261 if (multipolygon.getOuterWays().contains(osm)) {
262 boolean hasIndependentLineStyle = false;
263 if (!isOuterWayOfSomeMP) { // do this only one time
264 List<StyleElement> tmp = new ArrayList<>(p.a.size());
265 for (StyleElement s : p.a) {
266 if (s instanceof AreaElement) {
267 wayColor = ((AreaElement) s).color;
268 } else {
269 tmp.add(s);
270 if (s.isProperLineStyle()) {
271 hasIndependentLineStyle = true;
272 }
273 }
274 }
275 p.a = new StyleElementList(tmp);
276 isOuterWayOfSomeMP = true;
277 }
278
279 if (!hasIndependentLineStyle) {
280 Pair<StyleElementList, Range> mpElemStyles;
281 synchronized (r) {
282 mpElemStyles = getStyleCacheWithRange(r, scale, nc);
283 }
284 StyleElement mpLine = null;
285 for (StyleElement s : mpElemStyles.a) {
286 if (s.isProperLineStyle()) {
287 mpLine = s;
288 break;
289 }
290 }
291 p.b = Range.cut(p.b, mpElemStyles.b);
292 if (mpLine != null) {
293 p.a = new StyleElementList(p.a, mpLine);
294 break;
295 } else if (wayColor == null && isDefaultLines()) {
296 for (StyleElement element : mpElemStyles.a) {
297 if (element instanceof AreaElement) {
298 wayColor = ((AreaElement) element).color;
299 break;
300 }
301 }
302 }
303 }
304 }
305 }
306 if (isOuterWayOfSomeMP) {
307 if (isDefaultLines()) {
308 boolean hasLineStyle = false;
309 for (StyleElement s : p.a) {
310 if (s.isProperLineStyle()) {
311 hasLineStyle = true;
312 break;
313 }
314 }
315 if (!hasLineStyle) {
316 p.a = new StyleElementList(p.a, LineElement.createSimpleLineStyle(wayColor, true));
317 }
318 }
319 return p;
320 }
321
322 if (!isDefaultLines()) return p;
323
324 for (IPrimitive referrer : osm.getReferrers()) {
325 IRelation<?> ref = (IRelation<?>) referrer;
326 if (!drawMultipolygon || !ref.isMultipolygon() || !ref.isUsable() || !(ref instanceof Relation)) {
327 continue;
328 }
329 final Multipolygon multipolygon = MultipolygonCache.getInstance().get((Relation) ref);
330
331 if (multipolygon.getInnerWays().contains(osm)) {
332 p = generateStyles(osm, scale, false);
333 boolean hasIndependentElemStyle = false;
334 for (StyleElement s : p.a) {
335 if (s.isProperLineStyle() || s instanceof AreaElement) {
336 hasIndependentElemStyle = true;
337 break;
338 }
339 }
340 if (!hasIndependentElemStyle && !multipolygon.getOuterWays().isEmpty()) {
341 Color mpColor = null;
342 StyleElementList mpElemStyles;
343 synchronized (ref) {
344 mpElemStyles = get(ref, scale, nc);
345 }
346 for (StyleElement mpS : mpElemStyles) {
347 if (mpS instanceof AreaElement) {
348 mpColor = ((AreaElement) mpS).color;
349 break;
350 }
351 }
352 p.a = new StyleElementList(p.a, LineElement.createSimpleLineStyle(mpColor, true));
353 }
354 return p;
355 }
356 }
357 return p;
358 } else if (osm instanceof IRelation) {
359 return generateStyles(osm, scale, true);
360 }
361 return null;
362 }
363
364 /**
365 * Create the list of styles and its valid scale range for one primitive.
366 *
367 * Loops over the list of style sources, to generate the map of properties.
368 * From these properties, it generates the different types of styles.
369 *
370 * @param osm the primitive to create styles for
371 * @param scale the scale (in meters per 100 px), must be &gt; 0
372 * @param pretendWayIsClosed For styles that require the way to be closed,
373 * we pretend it is. This is useful for generating area styles from the (segmented)
374 * outer ways of a multipolygon.
375 * @return the generated styles and the valid range as a pair
376 * @since 13810 (signature)
377 */
378 public Pair<StyleElementList, Range> generateStyles(IPrimitive osm, double scale, boolean pretendWayIsClosed) {
379
380 List<StyleElement> sl = new ArrayList<>();
381 MultiCascade mc = new MultiCascade();
382 Environment env = new Environment(osm, mc, null, null);
383
384 for (StyleSource s : styleSources) {
385 if (s.active) {
386 s.apply(mc, osm, scale, pretendWayIsClosed);
387 }
388 }
389
390 for (Entry<String, Cascade> e : mc.getLayers()) {
391 if ("*".equals(e.getKey())) {
392 continue;
393 }
394 env.layer = e.getKey();
395 if (osm instanceof IWay) {
396 AreaElement areaStyle = AreaElement.create(env);
397 addIfNotNull(sl, areaStyle);
398 addIfNotNull(sl, RepeatImageElement.create(env));
399 addIfNotNull(sl, LineElement.createLine(env));
400 addIfNotNull(sl, LineElement.createLeftCasing(env));
401 addIfNotNull(sl, LineElement.createRightCasing(env));
402 addIfNotNull(sl, LineElement.createCasing(env));
403 addIfNotNull(sl, AreaIconElement.create(env));
404 addIfNotNull(sl, TextElement.create(env));
405 if (areaStyle != null) {
406 //TODO: Warn about this, or even remove it completely
407 addIfNotNull(sl, TextElement.createForContent(env));
408 }
409 } else if (osm instanceof INode) {
410 NodeElement nodeStyle = NodeElement.create(env);
411 if (nodeStyle != null) {
412 sl.add(nodeStyle);
413 addIfNotNull(sl, BoxTextElement.create(env, nodeStyle.getBoxProvider()));
414 } else {
415 addIfNotNull(sl, BoxTextElement.create(env, DefaultStyles.SIMPLE_NODE_ELEMSTYLE_BOXPROVIDER));
416 }
417 } else if (osm instanceof IRelation) {
418 if (((IRelation<?>) osm).isMultipolygon()) {
419 AreaElement areaStyle = AreaElement.create(env);
420 addIfNotNull(sl, areaStyle);
421 addIfNotNull(sl, RepeatImageElement.create(env));
422 addIfNotNull(sl, LineElement.createLine(env));
423 addIfNotNull(sl, LineElement.createCasing(env));
424 addIfNotNull(sl, AreaIconElement.create(env));
425 addIfNotNull(sl, TextElement.create(env));
426 if (areaStyle != null) {
427 //TODO: Warn about this, or even remove it completely
428 addIfNotNull(sl, TextElement.createForContent(env));
429 }
430 } else if (osm.hasTag("type", "restriction")) {
431 addIfNotNull(sl, NodeElement.create(env));
432 }
433 }
434 }
435 return new Pair<>(new StyleElementList(sl), mc.range);
436 }
437
438 private static <T> void addIfNotNull(List<T> list, T obj) {
439 if (obj != null) {
440 list.add(obj);
441 }
442 }
443
444 /**
445 * Draw a default node symbol for nodes that have no style?
446 * @return {@code true} if default node symbol must be drawn
447 */
448 private boolean isDefaultNodes() {
449 if (defaultNodesIdx == cacheIdx)
450 return defaultNodes;
451 defaultNodes = fromCanvas("default-points", Boolean.TRUE, Boolean.class);
452 defaultNodesIdx = cacheIdx;
453 return defaultNodes;
454 }
455
456 /**
457 * Draw a default line for ways that do not have an own line style?
458 * @return {@code true} if default line must be drawn
459 */
460 private boolean isDefaultLines() {
461 if (defaultLinesIdx == cacheIdx)
462 return defaultLines;
463 defaultLines = fromCanvas("default-lines", Boolean.TRUE, Boolean.class);
464 defaultLinesIdx = cacheIdx;
465 return defaultLines;
466 }
467
468 private <T> T fromCanvas(String key, T def, Class<T> c) {
469 MultiCascade mc = new MultiCascade();
470 Relation r = new Relation();
471 r.put("#canvas", "query");
472
473 for (StyleSource s : styleSources) {
474 if (s.active) {
475 s.apply(mc, r, 1, false);
476 }
477 }
478 return mc.getCascade("default").get(key, def, c);
479 }
480
481 /**
482 * Determines whether multipolygons must be drawn.
483 * @return whether multipolygons must be drawn.
484 */
485 public boolean isDrawMultipolygon() {
486 return drawMultipolygon;
487 }
488
489 /**
490 * Sets whether multipolygons must be drawn.
491 * @param drawMultipolygon whether multipolygons must be drawn
492 */
493 public void setDrawMultipolygon(boolean drawMultipolygon) {
494 this.drawMultipolygon = drawMultipolygon;
495 }
496
497 /**
498 * remove all style sources; only accessed from MapPaintStyles
499 */
500 void clear() {
501 styleSources.clear();
502 }
503
504 /**
505 * add a style source; only accessed from MapPaintStyles
506 * @param style style source to add
507 */
508 void add(StyleSource style) {
509 styleSources.add(Objects.requireNonNull(style));
510 }
511
512 /**
513 * remove a style source; only accessed from MapPaintStyles
514 * @param style style source to remove
515 * @return {@code true} if this list contained the specified element
516 */
517 boolean remove(StyleSource style) {
518 return styleSources.remove(Objects.requireNonNull(style));
519 }
520
521 /**
522 * set the style sources; only accessed from MapPaintStyles
523 * @param sources new style sources
524 */
525 void setStyleSources(Collection<StyleSource> sources) {
526 styleSources.clear();
527 sources.forEach(this::add);
528 }
529
530 /**
531 * Returns the first AreaElement for a given primitive.
532 * @param p the OSM primitive
533 * @param pretendWayIsClosed For styles that require the way to be closed,
534 * we pretend it is. This is useful for generating area styles from the (segmented)
535 * outer ways of a multipolygon.
536 * @return first AreaElement found or {@code null}.
537 * @since 13810 (signature)
538 */
539 public static AreaElement getAreaElemStyle(IPrimitive p, boolean pretendWayIsClosed) {
540 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock();
541 try {
542 if (MapPaintStyles.getStyles() == null)
543 return null;
544 for (StyleElement s : MapPaintStyles.getStyles().generateStyles(p, 1.0, pretendWayIsClosed).a) {
545 if (s instanceof AreaElement)
546 return (AreaElement) s;
547 }
548 return null;
549 } finally {
550 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock();
551 }
552 }
553
554 /**
555 * Determines whether primitive has an AreaElement.
556 * @param p the OSM primitive
557 * @param pretendWayIsClosed For styles that require the way to be closed,
558 * we pretend it is. This is useful for generating area styles from the (segmented)
559 * outer ways of a multipolygon.
560 * @return {@code true} if primitive has an AreaElement
561 * @since 13810 (signature)
562 */
563 public static boolean hasAreaElemStyle(IPrimitive p, boolean pretendWayIsClosed) {
564 return getAreaElemStyle(p, pretendWayIsClosed) != null;
565 }
566
567 /**
568 * Determines whether primitive has area-type {@link StyleElement}s, but
569 * no line-type StyleElements.
570 *
571 * {@link TextElement} is ignored, as it can be both line and area-type.
572 * @param p the OSM primitive
573 * @return {@code true} if primitive has area elements, but no line elements
574 * @since 12700
575 * @since 13810 (signature)
576 */
577 public static boolean hasOnlyAreaElements(IPrimitive p) {
578 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock();
579 try {
580 if (MapPaintStyles.getStyles() == null)
581 return false;
582 StyleElementList styles = MapPaintStyles.getStyles().generateStyles(p, 1.0, false).a;
583 boolean hasAreaElement = false;
584 for (StyleElement s : styles) {
585 if (s instanceof TextElement) {
586 continue;
587 }
588 if (s instanceof AreaElement) {
589 hasAreaElement = true;
590 } else {
591 return false;
592 }
593 }
594 return hasAreaElement;
595 } finally {
596 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock();
597 }
598 }
599
600 /**
601 * Looks up a preference value and ensures the style cache is invalidated
602 * as soon as this preference value is changed by the user.
603 *
604 * In addition, it adds an intermediate cache for the preference values,
605 * as frequent preference lookup (using <code>Config.getPref().get()</code>) for
606 * each primitive can be slow during rendering.
607 *
608 * If the default value can be {@linkplain Cascade#convertTo converted} to a {@link Color},
609 * the {@link NamedColorProperty} is retrieved as string.
610 *
611 * @param source style source
612 * @param key preference key
613 * @param def default value
614 * @return the corresponding preference value
615 * @see org.openstreetmap.josm.data.Preferences#get(String, String)
616 */
617 public String getPreferenceCached(StyleSource source, String key, String def) {
618 String res;
619 if (preferenceCache.containsKey(key)) {
620 res = preferenceCache.get(key);
621 } else {
622 Color realDef = Cascade.convertTo(def, Color.class);
623 if (realDef != null) {
624 String prefName = source != null ? source.getFileNamePart() : "unknown";
625 NamedColorProperty property = new NamedColorProperty(NamedColorProperty.COLOR_CATEGORY_MAPPAINT, prefName, key, realDef);
626 res = ColorHelper.color2html(property.get());
627 } else {
628 res = Config.getPref().get(key, null);
629 }
630 preferenceCache.put(key, res);
631 }
632 return res != null ? res : def;
633 }
634
635 @Override
636 public void preferenceChanged(PreferenceChangeEvent e) {
637 if (preferenceCache.containsKey(e.getKey())) {
638 clearCached();
639 }
640 }
641}
Note: See TracBrowser for help on using the repository browser.