source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/BoxTextElement.java@ 10600

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

see #11390 - sonar - squid:S1609 - Java 8: @FunctionalInterface annotation should be used to flag Single Abstract Method interfaces

  • Property svn:eol-style set to native
File size: 10.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.styleelement;
3
4import java.awt.Color;
5import java.awt.Rectangle;
6import java.util.Objects;
7
8import org.openstreetmap.josm.data.osm.Node;
9import org.openstreetmap.josm.data.osm.OsmPrimitive;
10import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
11import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
12import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
13import org.openstreetmap.josm.gui.mappaint.Cascade;
14import org.openstreetmap.josm.gui.mappaint.Environment;
15import org.openstreetmap.josm.gui.mappaint.Keyword;
16import org.openstreetmap.josm.gui.mappaint.MultiCascade;
17import org.openstreetmap.josm.tools.CheckParameterUtil;
18
19/**
20 * Text style attached to a style with a bounding box, like an icon or a symbol.
21 */
22public class BoxTextElement extends StyleElement {
23
24 /**
25 * MapCSS text-anchor-horizontal
26 */
27 public enum HorizontalTextAlignment { LEFT, CENTER, RIGHT }
28
29 /**
30 * MapCSS text-anchor-vertical
31 */
32 public enum VerticalTextAlignment { ABOVE, TOP, CENTER, BOTTOM, BELOW }
33
34 /**
35 * Something that provides us with a {@link BoxProviderResult}
36 * @since 10600 (functional interface)
37 */
38 @FunctionalInterface
39 public interface BoxProvider {
40 /**
41 * Compute and get the {@link BoxProviderResult}. The temporary flag is set if the result of the computation may change in the future.
42 * @return The result of the computation.
43 */
44 BoxProviderResult get();
45 }
46
47 /**
48 * A box rectangle with a flag if it is temporary.
49 */
50 public static class BoxProviderResult {
51 private final Rectangle box;
52 private final boolean temporary;
53
54 public BoxProviderResult(Rectangle box, boolean temporary) {
55 this.box = box;
56 this.temporary = temporary;
57 }
58
59 /**
60 * Returns the box.
61 * @return the box
62 */
63 public Rectangle getBox() {
64 return box;
65 }
66
67 /**
68 * Determines if the box can change in future calls of the {@link BoxProvider#get()} method
69 * @return {@code true} if the box can change in future calls of the {@code BoxProvider#get()} method
70 */
71 public boolean isTemporary() {
72 return temporary;
73 }
74 }
75
76 /**
77 * A {@link BoxProvider} that always returns the same non-temporary rectangle
78 */
79 public static class SimpleBoxProvider implements BoxProvider {
80 private final Rectangle box;
81
82 /**
83 * Constructs a new {@code SimpleBoxProvider}.
84 * @param box the box
85 */
86 public SimpleBoxProvider(Rectangle box) {
87 this.box = box;
88 }
89
90 @Override
91 public BoxProviderResult get() {
92 return new BoxProviderResult(box, false);
93 }
94
95 @Override
96 public int hashCode() {
97 return Objects.hash(box);
98 }
99
100 @Override
101 public boolean equals(Object obj) {
102 if (this == obj) return true;
103 if (obj == null || getClass() != obj.getClass()) return false;
104 SimpleBoxProvider that = (SimpleBoxProvider) obj;
105 return Objects.equals(box, that.box);
106 }
107 }
108
109 /**
110 * A rectangle with size 0x0
111 */
112 public static final Rectangle ZERO_BOX = new Rectangle(0, 0, 0, 0);
113
114 /**
115 * The default style a simple node should use for it's text
116 */
117 public static final BoxTextElement SIMPLE_NODE_TEXT_ELEMSTYLE;
118 static {
119 MultiCascade mc = new MultiCascade();
120 Cascade c = mc.getOrCreateCascade("default");
121 c.put(TEXT, Keyword.AUTO);
122 Node n = new Node();
123 n.put("name", "dummy");
124 SIMPLE_NODE_TEXT_ELEMSTYLE = create(new Environment(n, mc, "default", null), NodeElement.SIMPLE_NODE_ELEMSTYLE.getBoxProvider());
125 if (SIMPLE_NODE_TEXT_ELEMSTYLE == null) throw new AssertionError();
126 }
127
128 /**
129 * Caches the default text color from the preferences.
130 *
131 * FIXME: the cache isn't updated if the user changes the preference during a JOSM
132 * session. There should be preference listener updating this cache.
133 */
134 private static volatile Color defaultTextColorCache;
135
136 /**
137 * The text this element should display.
138 */
139 public TextLabel text;
140 // Either boxProvider or box is not null. If boxProvider is different from
141 // null, this means, that the box can still change in future, otherwise
142 // it is fixed.
143 protected BoxProvider boxProvider;
144 protected Rectangle box;
145 /**
146 * The {@link HorizontalTextAlignment} for this text.
147 */
148 public HorizontalTextAlignment hAlign;
149 /**
150 * The {@link VerticalTextAlignment} for this text.
151 */
152 public VerticalTextAlignment vAlign;
153
154 /**
155 * Create a new {@link BoxTextElement}
156 * @param c The current cascade
157 * @param text The text to display
158 * @param boxProvider The box provider to use
159 * @param box The initial box to use.
160 * @param hAlign The {@link HorizontalTextAlignment}
161 * @param vAlign The {@link VerticalTextAlignment}
162 */
163 public BoxTextElement(Cascade c, TextLabel text, BoxProvider boxProvider, Rectangle box,
164 HorizontalTextAlignment hAlign, VerticalTextAlignment vAlign) {
165 super(c, 5f);
166 CheckParameterUtil.ensureParameterNotNull(text);
167 CheckParameterUtil.ensureParameterNotNull(hAlign);
168 CheckParameterUtil.ensureParameterNotNull(vAlign);
169 this.text = text;
170 this.boxProvider = boxProvider;
171 this.box = box == null ? ZERO_BOX : box;
172 this.hAlign = hAlign;
173 this.vAlign = vAlign;
174 }
175
176 /**
177 * Create a new {@link BoxTextElement} with a dynamic box
178 * @param env The MapCSS environment
179 * @param boxProvider The box provider that computes the box.
180 * @return A new {@link BoxTextElement} or <code>null</code> if the creation failed.
181 */
182 public static BoxTextElement create(Environment env, BoxProvider boxProvider) {
183 return create(env, boxProvider, null);
184 }
185
186 /**
187 * Create a new {@link BoxTextElement} with a fixed box
188 * @param env The MapCSS environment
189 * @param box The box
190 * @return A new {@link BoxTextElement} or <code>null</code> if the creation failed.
191 */
192 public static BoxTextElement create(Environment env, Rectangle box) {
193 return create(env, null, box);
194 }
195
196 /**
197 * Create a new {@link BoxTextElement} with a boxprovider and a box.
198 * @param env The MapCSS environment
199 * @param boxProvider The box provider.
200 * @param box The box. Only considered if boxProvider is null.
201 * @return A new {@link BoxTextElement} or <code>null</code> if the creation failed.
202 */
203 public static BoxTextElement create(Environment env, BoxProvider boxProvider, Rectangle box) {
204 initDefaultParameters();
205
206 TextLabel text = TextLabel.create(env, defaultTextColorCache, false);
207 if (text == null) return null;
208 // Skip any primitives that don't have text to draw. (Styles are recreated for any tag change.)
209 // The concrete text to render is not cached in this object, but computed for each
210 // repaint. This way, one BoxTextElement object can be used by multiple primitives (to save memory).
211 if (text.labelCompositionStrategy.compose(env.osm) == null) return null;
212
213 Cascade c = env.mc.getCascade(env.layer);
214
215 HorizontalTextAlignment hAlign;
216 switch (c.get(TEXT_ANCHOR_HORIZONTAL, Keyword.RIGHT, Keyword.class).val) {
217 case "left":
218 hAlign = HorizontalTextAlignment.LEFT;
219 break;
220 case "center":
221 hAlign = HorizontalTextAlignment.CENTER;
222 break;
223 case "right":
224 default:
225 hAlign = HorizontalTextAlignment.RIGHT;
226 }
227 VerticalTextAlignment vAlign;
228 switch (c.get(TEXT_ANCHOR_VERTICAL, Keyword.BOTTOM, Keyword.class).val) {
229 case "above":
230 vAlign = VerticalTextAlignment.ABOVE;
231 break;
232 case "top":
233 vAlign = VerticalTextAlignment.TOP;
234 break;
235 case "center":
236 vAlign = VerticalTextAlignment.CENTER;
237 break;
238 case "below":
239 vAlign = VerticalTextAlignment.BELOW;
240 break;
241 case "bottom":
242 default:
243 vAlign = VerticalTextAlignment.BOTTOM;
244 }
245
246 return new BoxTextElement(c, text, boxProvider, box, hAlign, vAlign);
247 }
248
249 /**
250 * Get the box in which the content should be drawn.
251 * @return The box.
252 */
253 public Rectangle getBox() {
254 if (boxProvider != null) {
255 BoxProviderResult result = boxProvider.get();
256 if (!result.isTemporary()) {
257 box = result.getBox();
258 boxProvider = null;
259 }
260 return result.getBox();
261 }
262 return box;
263 }
264
265 private static void initDefaultParameters() {
266 if (defaultTextColorCache != null) return;
267 defaultTextColorCache = PaintColors.TEXT.get();
268 }
269
270 @Override
271 public void paintPrimitive(OsmPrimitive osm, MapPaintSettings settings, StyledMapRenderer painter,
272 boolean selected, boolean outermember, boolean member) {
273 if (osm instanceof Node) {
274 painter.drawBoxText((Node) osm, this);
275 }
276 }
277
278 @Override
279 public boolean equals(Object obj) {
280 if (this == obj) return true;
281 if (obj == null || getClass() != obj.getClass()) return false;
282 if (!super.equals(obj)) return false;
283 BoxTextElement that = (BoxTextElement) obj;
284 return Objects.equals(text, that.text) &&
285 Objects.equals(boxProvider, that.boxProvider) &&
286 Objects.equals(box, that.box) &&
287 hAlign == that.hAlign &&
288 vAlign == that.vAlign;
289 }
290
291 @Override
292 public int hashCode() {
293 return Objects.hash(super.hashCode(), text, boxProvider, box, hAlign, vAlign);
294 }
295
296 @Override
297 public String toString() {
298 return "BoxTextElemStyle{" + super.toString() + ' ' + text.toStringImpl()
299 + " box=" + box + " hAlign=" + hAlign + " vAlign=" + vAlign + '}';
300 }
301}
Note: See TracBrowser for help on using the repository browser.