Ticket #6107: mapcss-patch.txt

File mapcss-patch.txt, 42.6 KB (added by anonymous, 13 years ago)
Line 
1Index: src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java
2===================================================================
3--- src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java (revision 3980)
4+++ src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java (working copy)
5@@ -38,12 +38,11 @@
6 import org.openstreetmap.josm.gui.NavigatableComponent;
7 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle;
8 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.HorizontalTextAlignment;
9-import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
10 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.NodeTextElement;
11+import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
12 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.VerticalTextAlignment;
13 import org.openstreetmap.josm.gui.mappaint.TextElement;
14 import org.openstreetmap.josm.tools.ImageProvider;
15-import org.openstreetmap.josm.tools.LanguageInfo;
16 import org.openstreetmap.josm.tools.Pair;
17
18 public class MapPainter {
19@@ -73,8 +72,6 @@
20
21 private final boolean leftHandTraffic;
22
23- private final Collection<String> regionalNameOrder;
24-
25 private static final double PHI = Math.toRadians(20);
26 private static final double cosPHI = Math.cos(PHI);
27 private static final double sinPHI = Math.sin(PHI);
28@@ -103,8 +100,6 @@
29 this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
30 this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
31
32- String[] names = {"name:" + LanguageInfo.getJOSMLocaleCode(), "name", "int_name", "ref", "operator", "brand", "addr:housenumber"};
33- this.regionalNameOrder = Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(names));
34 this.circum = circum;
35 this.leftHandTraffic = leftHandTraffic;
36 }
37@@ -117,7 +112,7 @@
38 * e.g. oneway street or waterway
39 * @param onewayReversed for oneway=-1 and similar
40 */
41- public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor,
42+ public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor,
43 TextElement text, boolean showOrientation, boolean showHeadArrowOnly,
44 boolean showOneway, boolean onewayReversed) {
45
46@@ -252,7 +247,7 @@
47 private void drawTextOnPath(Way way, TextElement text) {
48 if (text == null)
49 return;
50- String name = text.getString(way, this);
51+ String name = text.getString(way);
52 if (name == null || name.equals(""))
53 return;
54
55@@ -291,8 +286,8 @@
56 double tStart;
57
58 if (p1[0] < p2[0] &&
59- p1[2] < Math.PI/2 &&
60- p1[2] > -Math.PI/2) {
61+ p1[2] < Math.PI/2 &&
62+ p1[2] > -Math.PI/2) {
63 angleOffset = 0;
64 offsetSign = 1;
65 tStart = t1;
66@@ -346,8 +341,8 @@
67 continue;
68 }
69 return new double[] {poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
70- poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
71- Math.atan2(dy, dx)};
72+ poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
73+ Math.atan2(dy, dx)};
74 }
75 return null;
76 }
77@@ -494,9 +489,12 @@
78 if (!isShowNames() || text == null)
79 return;
80
81- String s = text.textKey == null ? getNodeName(n) : n.get(text.textKey);
82- if (s == null)
83- return;
84+ /*
85+ * abort if we can't compose the label to be rendered
86+ */
87+ if (text.labelCompositionStrategy == null) return;
88+ String s = text.labelCompositionStrategy.compose(n);
89+ if (s == null) return;
90
91 Font defaultFont = g.getFont();
92 g.setFont(text.font);
93@@ -597,9 +595,12 @@
94 }
95
96 if (text != null && isShowNames()) {
97- String name = text.textKey == null ? getAreaName(osm) : osm.get(text.textKey);
98- if (name == null)
99- return;
100+ /*
101+ * abort if we can't compose the label to be rendered
102+ */
103+ if (text.labelCompositionStrategy == null) return;
104+ String name = text.labelCompositionStrategy.compose(osm);
105+ if (name == null) return;
106
107 Rectangle pb = polygon.getBounds();
108 FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
109@@ -930,34 +931,6 @@
110 }
111 }
112
113- //TODO Not a good place for this method
114- public String getNodeName(Node n) {
115- String name = null;
116- if (n.hasKeys()) {
117- for (String rn : regionalNameOrder) {
118- name = n.get(rn);
119- if (name != null) {
120- break;
121- }
122- }
123- }
124- return name;
125- }
126-
127- //TODO Not a good place for this method
128- public String getAreaName(OsmPrimitive w) {
129- String name = null;
130- if (w.hasKeys()) {
131- for (String rn : regionalNameOrder) {
132- name = w.get(rn);
133- if (name != null) {
134- break;
135- }
136- }
137- }
138- return name;
139- }
140-
141 public boolean isInactive() {
142 return inactive;
143 }
144Index: src/org/openstreetmap/josm/gui/mappaint/ElemStyle.java
145===================================================================
146--- src/org/openstreetmap/josm/gui/mappaint/ElemStyle.java (revision 3980)
147+++ src/org/openstreetmap/josm/gui/mappaint/ElemStyle.java (working copy)
148@@ -16,7 +16,7 @@
149 public float z_index;
150 public float object_z_index;
151 public boolean isModifier; // false, if style can serve as main style for the
152- // primitive; true, if it is a highlight or modifier
153+ // primitive; true, if it is a highlight or modifier
154
155 public ElemStyle(float z_index, float object_z_index, boolean isModifier) {
156 this.z_index = z_index;
157@@ -63,7 +63,7 @@
158 String name = c.get("font-family", Main.pref.get("mappaint.font", "Helvetica"), String.class);
159 float size = c.get("font-size", (float) Main.pref.getInteger("mappaint.fontsize", 8), Float.class);
160 int weight = Font.PLAIN;
161- Keyword weightKW = c.get("font-wheight", null, Keyword.class);
162+ Keyword weightKW = c.get("font-weight", null, Keyword.class);
163 if (weightKW != null && equal(weightKW, "bold")) {
164 weight = Font.BOLD;
165 }
166Index: src/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategy.java
167===================================================================
168--- src/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategy.java (revision 0)
169+++ src/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategy.java (revision 0)
170@@ -0,0 +1,244 @@
171+// License: GPL. For details, see LICENSE file.
172+package org.openstreetmap.josm.gui.mappaint;
173+import java.util.ArrayList;
174+import java.util.Arrays;
175+import java.util.Collections;
176+import java.util.List;
177+
178+import org.openstreetmap.josm.Main;
179+import org.openstreetmap.josm.data.osm.OsmPrimitive;
180+import org.openstreetmap.josm.tools.LanguageInfo;
181+
182+/**
183+ * <p>Provides an abstract parent class and three concrete sub classes for various
184+ * strategies on how to compose the text label which can be rendered close to a node
185+ * or within an area in an OSM map.</p>
186+ *
187+ * <p>The three strategies below support three rules for composing a label:
188+ * <ul>
189+ * <li>{@link StaticLabelCompositionStrategy} - the label is given by a static text
190+ * specified in the MapCSS style file</li>
191+ *
192+ * <li>{@link TagLookupCompositionStrategy} - the label is given by the content of a
193+ * tag whose name specified in the MapCSS style file</li>
194+ *
195+ * <li>{@link DeriveLabelFromNameTagsCompositionStrategy} - the label is given by the value
196+ * of one
197+ * of the configured "name tags". The list of relevant name tags can be configured
198+ * in the JOSM preferences
199+ * content of a tag whose name specified in the MapCSS style file, see the preference
200+ * option <tt>mappaint.nameOrder</tt>.</li>
201+ * </ul>
202+ * </p>
203+ *
204+ */
205+public abstract class LabelCompositionStrategy {
206+
207+ static public class StaticLabelCompositionStrategy extends LabelCompositionStrategy {
208+ private String defaultLabel;
209+ public StaticLabelCompositionStrategy(String defaultLabel){
210+ this.defaultLabel = defaultLabel;
211+ }
212+
213+ @Override
214+ public String compose(OsmPrimitive primitive) {
215+ return defaultLabel;
216+ }
217+
218+ public String getDefaultLabel() {
219+ return defaultLabel;
220+ }
221+
222+ @Override
223+ public String toString() {
224+ return "{" + getClass().getSimpleName() + " defaultLabel=" + defaultLabel + "}";
225+ }
226+
227+ @Override
228+ public int hashCode() {
229+ final int prime = 31;
230+ int result = 1;
231+ result = prime * result + ((defaultLabel == null) ? 0 : defaultLabel.hashCode());
232+ return result;
233+ }
234+
235+ @Override
236+ public boolean equals(Object obj) {
237+ if (this == obj)
238+ return true;
239+ if (obj == null)
240+ return false;
241+ if (getClass() != obj.getClass())
242+ return false;
243+ StaticLabelCompositionStrategy other = (StaticLabelCompositionStrategy) obj;
244+ if (defaultLabel == null) {
245+ if (other.defaultLabel != null)
246+ return false;
247+ } else if (!defaultLabel.equals(other.defaultLabel))
248+ return false;
249+ return true;
250+ }
251+ }
252+
253+ static public class TagLookupCompositionStrategy extends LabelCompositionStrategy {
254+
255+ private String defaultLabelTag;
256+ public TagLookupCompositionStrategy(String defaultLabelTag){
257+ if (defaultLabelTag != null) {
258+ defaultLabelTag = defaultLabelTag.trim();
259+ if (defaultLabelTag.isEmpty()) {
260+ defaultLabelTag = null;
261+ }
262+ }
263+ this.defaultLabelTag = defaultLabelTag;
264+ }
265+
266+ @Override
267+ public String compose(OsmPrimitive primitive) {
268+ if (defaultLabelTag == null) return null;
269+ if (primitive == null) return null;
270+ return primitive.get(defaultLabelTag);
271+ }
272+
273+ public String getDefaultLabelTag() {
274+ return defaultLabelTag;
275+ }
276+
277+ @Override
278+ public String toString() {
279+ return "{" + getClass().getSimpleName() + " defaultLabelTag=" + defaultLabelTag + "}";
280+ }
281+
282+ @Override
283+ public int hashCode() {
284+ final int prime = 31;
285+ int result = 1;
286+ result = prime * result + ((defaultLabelTag == null) ? 0 : defaultLabelTag.hashCode());
287+ return result;
288+ }
289+
290+ @Override
291+ public boolean equals(Object obj) {
292+ if (this == obj)
293+ return true;
294+ if (obj == null)
295+ return false;
296+ if (getClass() != obj.getClass())
297+ return false;
298+ TagLookupCompositionStrategy other = (TagLookupCompositionStrategy) obj;
299+ if (defaultLabelTag == null) {
300+ if (other.defaultLabelTag != null)
301+ return false;
302+ } else if (!defaultLabelTag.equals(other.defaultLabelTag))
303+ return false;
304+ return true;
305+ }
306+ }
307+
308+ static public class DeriveLabelFromNameTagsCompositionStrategy extends LabelCompositionStrategy {
309+
310+ /**
311+ * The list of default name tags from which a label candidate is derived.
312+ */
313+ static public final String[] DEFAULT_NAME_TAGS = {
314+ "name:" + LanguageInfo.getJOSMLocaleCode(),
315+ "name",
316+ "int_name",
317+ "ref",
318+ "operator",
319+ "brand",
320+ "addr:housenumber"
321+ };
322+
323+ private List<String> nameTags = new ArrayList<String>();
324+
325+ /**
326+ * <p>Creates the strategy and initializes its name tags from the preferences.</p>
327+ *
328+ * <p><strong>Note:</strong> If the list of name tags in the preferences changes, strategy instances
329+ * are not notified. It's up to the client to listen to preference changes and
330+ * invoke {@link #initNameTagsFromPreferences()} accordingly.</p>
331+ *
332+ */
333+ public DeriveLabelFromNameTagsCompositionStrategy() {
334+ initNameTagsFromPreferences();
335+ }
336+
337+ /**
338+ * Sets the name tags to be looked up in order to build up the label
339+ *
340+ * @param nameTags the name tags. null values are ignore.
341+ */
342+ public void setNameTags(List<String> nameTags){
343+ if (nameTags == null) {
344+ nameTags = Collections.emptyList();
345+ }
346+ this.nameTags = new ArrayList<String>();
347+ for(String tag: nameTags) {
348+ if (tag == null) {
349+ continue;
350+ }
351+ tag = tag.trim();
352+ if (tag.isEmpty()) {
353+ continue;
354+ }
355+ this.nameTags.add(tag);
356+ }
357+ }
358+
359+ /**
360+ * Replies an unmodifiable list of the name tags used to compose the label.
361+ *
362+ * @return the list of name tags
363+ */
364+ public List<String> getNameTags() {
365+ return Collections.unmodifiableList(nameTags);
366+ }
367+
368+ /**
369+ * Initializes the name tags to use from a list of default name tags (see
370+ * {@link #DEFAULT_NAME_TAGS}) and from name tags configured in the preferences
371+ * using the preference key <tt>mappaint.nameOrder</tt>.
372+ */
373+ public void initNameTagsFromPreferences() {
374+ if (Main.pref == null){
375+ this.nameTags = new ArrayList<String>(Arrays.asList(DEFAULT_NAME_TAGS));
376+ } else {
377+ this.nameTags = new ArrayList<String>(
378+ Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(DEFAULT_NAME_TAGS))
379+ );
380+ }
381+ }
382+
383+ private String getPrimitiveName(OsmPrimitive n) {
384+ String name = null;
385+ if (!n.hasKeys()) return null;
386+ for (String rn : nameTags) {
387+ name = n.get(rn);
388+ if (name != null) return name;
389+ }
390+ return null;
391+ }
392+
393+ @Override
394+ public String compose(OsmPrimitive primitive) {
395+ if (primitive == null) return null;
396+ return getPrimitiveName(primitive);
397+ }
398+
399+ @Override
400+ public String toString() {
401+ return "{" + getClass().getSimpleName() +"}";
402+ }
403+ }
404+
405+ /**
406+ * Replies the text value to be rendered as label for the primitive {@code primitive}.
407+ *
408+ * @param primitive the primitive
409+ *
410+ * @return the text value to be rendered or null, if primitive is null or
411+ * if no suitable value could be composed
412+ */
413+ abstract public String compose(OsmPrimitive primitive);
414+}
415Index: src/org/openstreetmap/josm/gui/mappaint/MultiCascade.java
416===================================================================
417--- src/org/openstreetmap/josm/gui/mappaint/MultiCascade.java (revision 3980)
418+++ src/org/openstreetmap/josm/gui/mappaint/MultiCascade.java (working copy)
419@@ -6,13 +6,15 @@
420 import java.util.Map;
421 import java.util.Map.Entry;
422
423+import org.openstreetmap.josm.tools.CheckParameterUtil;
424+
425 /**
426 * Several layers / cascades, e.g. one for the main Line and one for each overlay.
427 * The range is (0,Infinity) at first and it shrinks in the process when
428 * StyleSources apply zoom level dependent properties.
429 */
430 public class MultiCascade {
431-
432+
433 private Map<String, Cascade> layers;
434 public Range range;
435
436@@ -27,8 +29,7 @@
437 * a clone of the "*" layer, if it exists.
438 */
439 public Cascade getOrCreateCascade(String layer) {
440- if (layer == null)
441- throw new IllegalArgumentException();
442+ CheckParameterUtil.ensureParameterNotNull(layer);
443 Cascade c = layers.get(layer);
444 if (c == null) {
445 if (layers.containsKey("*")) {
446Index: src/org/openstreetmap/josm/gui/mappaint/NodeElemStyle.java
447===================================================================
448--- src/org/openstreetmap/josm/gui/mappaint/NodeElemStyle.java (revision 3980)
449+++ src/org/openstreetmap/josm/gui/mappaint/NodeElemStyle.java (working copy)
450@@ -25,6 +25,7 @@
451 * applies for Nodes and turn restriction relations
452 */
453 public class NodeElemStyle extends ElemStyle {
454+ //static private final Logger logger = Logger.getLogger(NodeElemStyle.class.getName());
455
456 public ImageIcon icon;
457 public int iconAlpha;
458@@ -62,10 +63,10 @@
459 return false;
460 final Symbol other = (Symbol) obj;
461 return symbol == other.symbol &&
462- size == other.size &&
463- equal(stroke, other.stroke) &&
464- equal(strokeColor, other.strokeColor) &&
465- equal(fillColor, other.fillColor);
466+ size == other.size &&
467+ equal(stroke, other.stroke) &&
468+ equal(strokeColor, other.strokeColor) &&
469+ equal(fillColor, other.fillColor);
470 }
471
472 @Override
473@@ -82,8 +83,8 @@
474 @Override
475 public String toString() {
476 return "symbol=" + symbol + " size=" + size +
477- (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") +
478- (fillColor != null ? (" fillColor=" + fillColor) : "");
479+ (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") +
480+ (fillColor != null ? (" fillColor=" + fillColor) : "");
481 }
482 }
483
484@@ -107,7 +108,7 @@
485 return false;
486 final NodeTextElement other = (NodeTextElement) obj;
487 return hAlign == other.hAlign &&
488- vAlign == other.vAlign;
489+ vAlign == other.vAlign;
490 }
491
492 @Override
493@@ -162,9 +163,6 @@
494 symbol = createSymbol(env);
495 }
496
497- if (icon == null && symbol == null && !allowOnlyText)
498- return null;
499-
500 NodeTextElement text = null;
501 TextElement te = TextElement.create(c, PaintColors.TEXT.get());
502 if (te != null) {
503@@ -192,7 +190,7 @@
504 }
505 text = new NodeTextElement(te, hAlign, vAlign);
506 }
507-
508+
509 return new NodeElemStyle(c, icon, iconAlpha, symbol, text);
510 }
511
512@@ -224,7 +222,7 @@
513 shape = SymbolShape.DECAGON;
514 } else
515 return null;
516-
517+
518 Float sizeOnDefault = c_def.get("symbol-size", null, Float.class);
519 if (sizeOnDefault != null && sizeOnDefault <= 0) {
520 sizeOnDefault = null;
521@@ -258,8 +256,9 @@
522 }
523
524 Color fillColor = c.get("symbol-fill-color", null, Color.class);
525- if (stroke == null && fillColor == null)
526+ if (stroke == null && fillColor == null) {
527 fillColor = Color.BLUE;
528+ }
529
530 if (fillColor != null) {
531 float fillAlpha = c.get("symbol-fill-opacity", 1f, Float.class);
532@@ -335,14 +334,14 @@
533 }
534
535 final int size = Utils.max((selected ? settings.getSelectedNodeSize() : 0),
536- (n.isTagged() ? settings.getTaggedNodeSize() : 0),
537- (isConnection ? settings.getConnectionNodeSize() : 0),
538- settings.getUnselectedNodeSize());
539+ (n.isTagged() ? settings.getTaggedNodeSize() : 0),
540+ (isConnection ? settings.getConnectionNodeSize() : 0),
541+ settings.getUnselectedNodeSize());
542
543 final boolean fill = (selected && settings.isFillSelectedNode()) ||
544- (n.isTagged() && settings.isFillTaggedNode()) ||
545- (isConnection && settings.isFillConnectionNode()) ||
546- settings.isFillUnselectedNode();
547+ (n.isTagged() && settings.isFillTaggedNode()) ||
548+ (isConnection && settings.isFillConnectionNode()) ||
549+ settings.isFillUnselectedNode();
550
551 painter.drawNode(n, color, size, fill, text);
552 }
553@@ -394,8 +393,8 @@
554 @Override
555 public String toString() {
556 return "NodeElemStyle{" + super.toString() +
557- (icon != null ? ("icon=" + icon + " iconAlpha=" + iconAlpha) : "") +
558- (symbol != null ? (" symbol=[" + symbol + "]") : "") + '}';
559+ (icon != null ? ("icon=" + icon + " iconAlpha=" + iconAlpha) : "") +
560+ (symbol != null ? (" symbol=[" + symbol + "]") : "") + '}';
561 }
562
563 }
564Index: src/org/openstreetmap/josm/gui/mappaint/TextElement.java
565===================================================================
566--- src/org/openstreetmap/josm/gui/mappaint/TextElement.java (revision 3980)
567+++ src/org/openstreetmap/josm/gui/mappaint/TextElement.java (working copy)
568@@ -1,31 +1,53 @@
569 // License: GPL. For details, see LICENSE file.
570 package org.openstreetmap.josm.gui.mappaint;
571
572-import static org.openstreetmap.josm.tools.Utils.equal;
573-
574 import java.awt.Color;
575 import java.awt.Font;
576+
577 import org.openstreetmap.josm.data.osm.OsmPrimitive;
578-import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
579-
580+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy;
581+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy;
582+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy;
583 import org.openstreetmap.josm.tools.CheckParameterUtil;
584 import org.openstreetmap.josm.tools.Utils;
585
586+/**
587+ * Represents the rendering style for a textual label placed somewhere on the map.
588+ *
589+ */
590 public class TextElement {
591- // textKey == null means automatic generation of text string, otherwise
592- // the corresponding tag value is used
593- public String textKey;
594+ //static private final Logger logger = Logger.getLogger(TextElement.class.getName());
595+
596+ static private final LabelCompositionStrategy AUTO_LABEL_COMPOSITION_STRATEGY = new DeriveLabelFromNameTagsCompositionStrategy();
597+
598+ /** the font to be used when rendering*/
599 public Font font;
600 public int xOffset;
601 public int yOffset;
602 public Color color;
603 public Float haloRadius;
604 public Color haloColor;
605+ /** the strategy for building the actual label value for a given a {@link OsmPrimitive}.
606+ * Check for null before accessing.
607+ */
608+ public LabelCompositionStrategy labelCompositionStrategy;
609
610- public TextElement(String textKey, Font font, int xOffset, int yOffset, Color color, Float haloRadius, Color haloColor) {
611+ /**
612+ * Creates a new text element
613+ *
614+ * @param strategy the strategy indicating how the text is composed for a specific {@link OsmPrimitive} to be rendered.
615+ * If null, no label is rendered.
616+ * @param font the font to be used. Must not be null.
617+ * @param xOffset
618+ * @param yOffset
619+ * @param color the color to be used. Must not be null
620+ * @param haloRadius
621+ * @param haloColor
622+ */
623+ public TextElement(LabelCompositionStrategy strategy, Font font, int xOffset, int yOffset, Color color, Float haloRadius, Color haloColor) {
624 CheckParameterUtil.ensureParameterNotNull(font);
625 CheckParameterUtil.ensureParameterNotNull(color);
626- this.textKey = textKey;
627+ labelCompositionStrategy = strategy;
628 this.font = font;
629 this.xOffset = xOffset;
630 this.yOffset = yOffset;
631@@ -34,8 +56,13 @@
632 this.haloColor = haloColor;
633 }
634
635+ /**
636+ * Copy constructor
637+ *
638+ * @param other the other element.
639+ */
640 public TextElement(TextElement other) {
641- this.textKey = other.textKey;
642+ this.labelCompositionStrategy = other.labelCompositionStrategy;
643 this.font = other.font;
644 this.xOffset = other.xOffset;
645 this.yOffset = other.yOffset;
646@@ -44,17 +71,47 @@
647 this.haloRadius = other.haloRadius;
648 }
649
650- public static TextElement create(Cascade c, Color defTextColor) {
651-
652- String textKey = null;
653+ /**
654+ * Derives a suitable label composition strategy from the style properties in
655+ * {@code c}.
656+ *
657+ * @param c the style properties
658+ * @return the label composition strategy
659+ */
660+ protected static LabelCompositionStrategy buildLabelCompositionStrategy(Cascade c){
661+ LabelCompositionStrategy strategy;
662 Keyword textKW = c.get("text", null, Keyword.class, true);
663 if (textKW == null) {
664- textKey = c.get("text", null, String.class);
665- if (textKey == null)
666- return null;
667+ String textKey = c.get("text", null, String.class);
668+ if (textKey == null) return null;
669+ strategy = new StaticLabelCompositionStrategy(textKey);
670 } else if (!textKW.val.equals("auto"))
671 return null;
672+ else {
673+ String defaultLabelTag = c.get("text-from-tag", null, String.class);
674+ if (defaultLabelTag == null){
675+ strategy = AUTO_LABEL_COMPOSITION_STRATEGY;
676+ } else {
677+ strategy = new TagLookupCompositionStrategy(defaultLabelTag);
678+ }
679+ }
680+ return strategy;
681+ }
682
683+ /**
684+ * Builds a text element from style properties in {@code c} and the
685+ * default text color {@code defaultTextColor}
686+ *
687+ * @param c the style properties
688+ * @param defaultTextColor the default text color. Must not be null.
689+ * @return the text element or null, if the style properties don't include
690+ * properties for text rendering
691+ * @throws IllegalArgumentException thrown if {@code defaultTextColor} is null
692+ */
693+ public static TextElement create(Cascade c, Color defaultTextColor) throws IllegalArgumentException{
694+ CheckParameterUtil.ensureParameterNotNull(defaultTextColor, "defaultTextColor");
695+
696+ LabelCompositionStrategy strategy = buildLabelCompositionStrategy(c);
697 Font font = ElemStyle.getFont(c);
698
699 float xOffset = 0;
700@@ -70,8 +127,8 @@
701 }
702 xOffset = c.get("text-offset-x", xOffset, Float.class);
703 yOffset = c.get("text-offset-y", yOffset, Float.class);
704-
705- Color color = c.get("text-color", defTextColor, Color.class);
706+
707+ Color color = c.get("text-color", defaultTextColor, Color.class);
708 float alpha = c.get("text-opacity", 1f, Float.class);
709 color = new Color(color.getRed(), color.getGreen(),
710 color.getBlue(), Utils.color_float2int(alpha));
711@@ -88,42 +145,86 @@
712 haloColor.getBlue(), Utils.color_float2int(haloAlpha));
713 }
714
715- return new TextElement(textKey, font, (int) xOffset, - (int) yOffset, color, haloRadius, haloColor);
716+ return new TextElement(strategy, font, (int) xOffset, - (int) yOffset, color, haloRadius, haloColor);
717 }
718
719+ /**
720+ * Replies the label to be rendered for the primitive {@code osm}.
721+ *
722+ * @param osm the the OSM object
723+ * @return the label, or null, if {@code osm} is null or if no label can be
724+ * derived for {@code osm}
725+ */
726+ public String getString(OsmPrimitive osm) {
727+ if (labelCompositionStrategy == null) return null;
728+ return labelCompositionStrategy.compose(osm);
729+ }
730+
731 @Override
732- public boolean equals(Object obj) {
733- if (obj == null || getClass() != obj.getClass())
734- return false;
735- final TextElement other = (TextElement) obj;
736- return equal(textKey, other.textKey) &&
737- equal(font, other.font) &&
738- xOffset == other.xOffset &&
739- yOffset == other.yOffset &&
740- equal(color, other.color) &&
741- equal(haloRadius, other.haloRadius) &&
742- equal(haloColor, other.haloColor);
743+ public String toString() {
744+ StringBuilder sb = new StringBuilder();
745+ sb.append("{TextElement ");
746+ sb.append("strategy=");
747+ sb.append(labelCompositionStrategy == null ? "null" : labelCompositionStrategy.toString());
748+ sb.append("}");
749+ return sb.toString();
750 }
751
752+ /* -------------------------------------------------------------------------------- */
753+ /* equals and hashCode (generated by Eclipse, regenerate if necessary) */
754+ /* -------------------------------------------------------------------------------- */
755 @Override
756 public int hashCode() {
757- int hash = 3;
758- hash = 79 * hash + (textKey != null ? textKey.hashCode() : 0);
759- hash = 79 * hash + font.hashCode();
760- hash = 79 * hash + xOffset;
761- hash = 79 * hash + yOffset;
762- hash = 79 * hash + color.hashCode();
763- hash = 79 * hash + (haloRadius != null ? Float.floatToIntBits(haloRadius) : 0);
764- hash = 79 * hash + (haloColor != null ? haloColor.hashCode() : 0);
765- return hash;
766+ final int prime = 31;
767+ int result = 1;
768+ result = prime * result + ((color == null) ? 0 : color.hashCode());
769+ result = prime * result + ((font == null) ? 0 : font.hashCode());
770+ result = prime * result + ((haloColor == null) ? 0 : haloColor.hashCode());
771+ result = prime * result + ((haloRadius == null) ? 0 : haloRadius.hashCode());
772+ result = prime * result + ((labelCompositionStrategy == null) ? 0 : labelCompositionStrategy.hashCode());
773+ result = prime * result + xOffset;
774+ result = prime * result + yOffset;
775+ return result;
776 }
777
778- public String getString(OsmPrimitive osm, MapPainter painter) {
779- if (textKey == null)
780- return painter.getAreaName(osm);
781- else
782- return osm.get(textKey);
783+ @Override
784+ public boolean equals(Object obj) {
785+ if (this == obj)
786+ return true;
787+ if (obj == null)
788+ return false;
789+ if (getClass() != obj.getClass())
790+ return false;
791+ TextElement other = (TextElement) obj;
792+ if (color == null) {
793+ if (other.color != null)
794+ return false;
795+ } else if (!color.equals(other.color))
796+ return false;
797+ if (font == null) {
798+ if (other.font != null)
799+ return false;
800+ } else if (!font.equals(other.font))
801+ return false;
802+ if (haloColor == null) {
803+ if (other.haloColor != null)
804+ return false;
805+ } else if (!haloColor.equals(other.haloColor))
806+ return false;
807+ if (haloRadius == null) {
808+ if (other.haloRadius != null)
809+ return false;
810+ } else if (!haloRadius.equals(other.haloRadius))
811+ return false;
812+ if (labelCompositionStrategy == null) {
813+ if (other.labelCompositionStrategy != null)
814+ return false;
815+ } else if (!labelCompositionStrategy.equals(other.labelCompositionStrategy))
816+ return false;
817+ if (xOffset != other.xOffset)
818+ return false;
819+ if (yOffset != other.yOffset)
820+ return false;
821+ return true;
822 }
823-
824-
825 }
826Index: src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java
827===================================================================
828--- src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java (revision 3980)
829+++ src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java (working copy)
830@@ -27,7 +27,8 @@
831 import org.openstreetmap.josm.tools.Utils;
832
833 public class MapCSSStyleSource extends StyleSource {
834-
835+ //static private final Logger logger = Logger.getLogger(MapCSSStyleSource.class.getName());
836+
837 final public List<MapCSSRule> rules;
838 private Color backgroundColorOverride;
839
840@@ -65,6 +66,7 @@
841 }
842 }
843
844+ @Override
845 public InputStream getSourceInputStream() throws IOException {
846 MirroredInputStream in = new MirroredInputStream(url);
847 InputStream zip = in.getZipEntry("mapcss", "style");
848@@ -107,26 +109,28 @@
849 Environment env = new Environment(n, mc, "default", this);
850
851 NEXT_RULE:
852- for (MapCSSRule r : rules) {
853- for (Selector s : r.selectors) {
854- if ((s instanceof GeneralSelector)) {
855- GeneralSelector gs = (GeneralSelector) s;
856- if (gs.base.equals(type))
857- {
858- for (Condition cnd : gs.conds) {
859- if (!cnd.applies(env))
860- continue NEXT_RULE;
861+ for (MapCSSRule r : rules) {
862+ for (Selector s : r.selectors) {
863+ if ((s instanceof GeneralSelector)) {
864+ GeneralSelector gs = (GeneralSelector) s;
865+ if (gs.base.equals(type))
866+ {
867+ for (Condition cnd : gs.conds) {
868+ if (!cnd.applies(env)) {
869+ continue NEXT_RULE;
870+ }
871+ }
872+ for (Instruction i : r.declaration) {
873+ i.execute(env);
874+ }
875 }
876- for (Instruction i : r.declaration) {
877- i.execute(env);
878- }
879 }
880 }
881 }
882- }
883 return mc.getCascade("default");
884 }
885
886+ @Override
887 public Color getBackgroundColorOverride() {
888 return backgroundColorOverride;
889 }
890@@ -159,7 +163,7 @@
891 i.execute(env);
892 }
893 }
894- }
895+ }
896 env.layer = sub;
897 for (Instruction i : r.declaration) {
898 i.execute(env);
899Index: test/data/styles/label-from-tag.mapcss
900===================================================================
901--- test/data/styles/label-from-tag.mapcss (revision 0)
902+++ test/data/styles/label-from-tag.mapcss (revision 0)
903@@ -0,0 +1,21 @@
904+/*
905+ * Simple test style sheet. Includes a style with the new style declaration 'text-from-tag'.
906+ *
907+ */
908+
909+meta {
910+ title: "Test style - Deriving labels from tags";
911+}
912+
913+canvas {
914+ background-color: #000000;
915+}
916+
917+node {
918+ text: auto;
919+ text-color: white;
920+ font-size: 12;
921+ /* the value of the tag 'my_label_tag' will be rendered as text */
922+ text-from-tag: my_label_tag;
923+}
924+
925Index: test/functional/org/openstreetmap/josm/fixtures/JOSMFixture.java
926===================================================================
927--- test/functional/org/openstreetmap/josm/fixtures/JOSMFixture.java (revision 3980)
928+++ test/functional/org/openstreetmap/josm/fixtures/JOSMFixture.java (working copy)
929@@ -10,7 +10,7 @@
930 import java.util.logging.Logger;
931
932 import org.openstreetmap.josm.Main;
933-import org.openstreetmap.josm.data.osm.DataSetMergerTest;
934+import org.openstreetmap.josm.data.Preferences;
935 import org.openstreetmap.josm.data.projection.Mercator;
936 import org.openstreetmap.josm.io.OsmApi;
937 import org.openstreetmap.josm.tools.I18n;
938@@ -39,7 +39,7 @@
939 // load properties
940 //
941 try {
942- testProperties.load(DataSetMergerTest.class.getResourceAsStream(testPropertiesResourceName));
943+ testProperties.load(JOSMFixture.class.getResourceAsStream(testPropertiesResourceName));
944 } catch(Exception e){
945 logger.log(Level.SEVERE, MessageFormat.format("failed to load property file ''{0}''", testPropertiesResourceName));
946 fail(MessageFormat.format("failed to load property file ''{0}''. \nMake sure the path ''$project_root/test/config'' is on the classpath.", testPropertiesResourceName));
947@@ -57,6 +57,7 @@
948 }
949 }
950 System.setProperty("josm.home", josmHome);
951+ Main.pref = new Preferences();
952 I18n.init();
953 // initialize the plaform hook, and
954 Main.determinePlatformHook();
955Index: test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy
956===================================================================
957--- test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy (revision 0)
958+++ test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy (revision 0)
959@@ -0,0 +1,15 @@
960+// License: GPL. For details, see LICENSE file.
961+package org.openstreetmap.josm.gui.mappaint
962+
963+import junit.framework.TestCase;
964+
965+import org.junit.runner.RunWith;
966+import org.junit.runners.Suite;
967+
968+@RunWith(Suite.class)
969+@Suite.SuiteClasses([
970+ LabelCompositionStrategyTest.class,
971+ MapCSSWithExtendedTextDirectivesTest.class
972+])
973+public class AllMappaintTests extends TestCase{}
974+
975Index: test/unit/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategyTest.groovy
976===================================================================
977--- test/unit/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategyTest.groovy (revision 0)
978+++ test/unit/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategyTest.groovy (revision 0)
979@@ -0,0 +1,66 @@
980+// License: GPL. For details, see LICENSE file.
981+package org.openstreetmap.josm.gui.mappaint
982+
983+import org.junit.*
984+import org.openstreetmap.josm.fixtures.JOSMFixture;
985+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy
986+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy;
987+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy
988+import org.openstreetmap.josm.data.osm.Node;
989+
990+class LabelCompositionStrategyTest {
991+
992+ @BeforeClass
993+ public static void createJOSMFixture(){
994+ JOSMFixture.createUnitTestFixture().init()
995+ }
996+
997+ @Test
998+ public void createStaticLabelCompositionStrategy() {
999+ def n = new Node()
1000+
1001+ def strat = new StaticLabelCompositionStrategy(null)
1002+ assert strat.compose(n) == null
1003+
1004+ strat = new StaticLabelCompositionStrategy("a label")
1005+ assert strat.compose(n) == "a label"
1006+ }
1007+
1008+ @Test
1009+ public void createTagLookupCompositionStrategy() {
1010+ def n = new Node()
1011+ n.put("my-tag", "my-value")
1012+
1013+ def strat = new TagLookupCompositionStrategy(null)
1014+ assert strat.compose(n) == null
1015+
1016+ strat = new TagLookupCompositionStrategy("name")
1017+ assert strat.compose(n) == null
1018+
1019+ strat = new TagLookupCompositionStrategy("my-tag")
1020+ assert strat.compose(n) == "my-value"
1021+ }
1022+
1023+ @Test
1024+ public void createDeriveLabelFromNameTagsCompositionStrategy() {
1025+ def n
1026+ def strat
1027+
1028+ strat = new DeriveLabelFromNameTagsCompositionStrategy()
1029+ strat.setNameTags(null)
1030+ assert strat.getNameTags() == []
1031+
1032+ strat = new DeriveLabelFromNameTagsCompositionStrategy()
1033+ strat.setNameTags(["name", "brand"])
1034+ assert strat.getNameTags() == ["name", "brand"]
1035+
1036+ n = new Node()
1037+ n.put("brand", "my brand")
1038+ assert strat.compose(n) == "my brand"
1039+
1040+ n = new Node()
1041+ n.put("name", "my name")
1042+ n.put("brand", "my brand")
1043+ assert strat.compose(n) == "my name"
1044+ }
1045+}
1046Index: test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy
1047===================================================================
1048--- test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy (revision 0)
1049+++ test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy (revision 0)
1050@@ -0,0 +1,58 @@
1051+// License: GPL. For details, see LICENSE file.
1052+package org.openstreetmap.josm.gui.mappaint
1053+
1054+import java.awt.Color;
1055+
1056+import org.junit.*;
1057+import org.openstreetmap.josm.fixtures.JOSMFixture
1058+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy
1059+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy
1060+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy
1061+class MapCSSWithExtendedTextDirectivesTest {
1062+
1063+
1064+ @BeforeClass
1065+ public static void createJOSMFixture(){
1066+ JOSMFixture.createUnitTestFixture().init()
1067+ }
1068+
1069+ @Test
1070+ public void createAutoTextElement() {
1071+ Cascade c = new Cascade()
1072+ c.put("text", new Keyword("auto"))
1073+
1074+ TextElement te = TextElement.create(c, Color.WHITE)
1075+ assert te.labelCompositionStrategy != null
1076+ assert te.labelCompositionStrategy instanceof DeriveLabelFromNameTagsCompositionStrategy
1077+ }
1078+
1079+ @Test
1080+ public void createTextElementComposingTextFromTag() {
1081+ Cascade c = new Cascade()
1082+ c.put("text", new Keyword("auto"))
1083+ c.put("text-from-tag", "my_name")
1084+
1085+ TextElement te = TextElement.create(c, Color.WHITE)
1086+ assert te.labelCompositionStrategy != null
1087+ assert te.labelCompositionStrategy instanceof TagLookupCompositionStrategy
1088+ assert te.labelCompositionStrategy.getDefaultLabelTag() == "my_name"
1089+ }
1090+
1091+ @Test
1092+ public void createTextElementWithStaticText() {
1093+ Cascade c = new Cascade()
1094+ c.put("text","my static label")
1095+
1096+ TextElement te = TextElement.create(c, Color.WHITE)
1097+ assert te.labelCompositionStrategy != null
1098+ assert te.labelCompositionStrategy instanceof StaticLabelCompositionStrategy
1099+ }
1100+
1101+ @Test
1102+ public void createNullStrategy() {
1103+ Cascade c = new Cascade()
1104+
1105+ TextElement te = TextElement.create(c, Color.WHITE)
1106+ assert te.labelCompositionStrategy == null
1107+ }
1108+}