1 package org.opentrafficsim.editor.extensions.map.edit;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Graphics2D;
6 import java.awt.Shape;
7 import java.awt.image.ImageObserver;
8 import java.util.LinkedHashSet;
9 import java.util.Set;
10 import java.util.function.Consumer;
11 import java.util.function.Function;
12 import java.util.function.UnaryOperator;
13
14 import org.djutils.draw.bounds.Bounds2d;
15 import org.djutils.draw.line.PolyLine2d;
16 import org.djutils.draw.line.Polygon2d;
17 import org.djutils.draw.point.DirectedPoint2d;
18 import org.djutils.draw.point.Point2d;
19 import org.opentrafficsim.animation.DrawLevel;
20 import org.opentrafficsim.animation.PaintLine;
21 import org.opentrafficsim.base.geometry.OtsShape;
22 import org.opentrafficsim.editor.OtsEditor;
23 import org.opentrafficsim.editor.OtsEditorProperties;
24 import org.opentrafficsim.editor.XsdTreeNode;
25 import org.opentrafficsim.editor.extensions.map.EditorMap;
26 import org.opentrafficsim.editor.extensions.map.edit.DraggableAnnotation.Draggable;
27
28 import nl.tudelft.simulation.dsol.animation.d2.RenderableScale;
29 import nl.tudelft.simulation.naming.context.Contextualized;
30
31
32
33
34
35
36
37
38 public class DraggableAnnotation extends MapAnnotation<Draggable<?>>
39 {
40
41
42 private static final Color FILL_COLOR =
43 OtsEditorProperties.PROPERTIES_STORE.getColorOrDefault("map.draggableFillColor", Color.WHITE);
44
45
46 private static final Color EXPRESSION_COLOR =
47 OtsEditorProperties.PROPERTIES_STORE.getColorOrDefault("map.draggableExpressionColor", Color.RED);
48
49
50 private static final Color EDGE_COLOR =
51 OtsEditorProperties.PROPERTIES_STORE.getColorOrDefault("map.draggableEdgeColor", Color.BLACK);
52
53
54 private static final int DRAGGABLE_SELECT_MARGIN_PX =
55 OtsEditorProperties.PROPERTIES_STORE.getIntegerOrDefault("map.draggableSelectMarginPx", 3);
56
57
58 private final Set<HelperAnnotation> staticAnnotations = new LinkedHashSet<>();
59
60
61 private final Set<HelperAnnotation> dynamicAnnotations = new LinkedHashSet<>();
62
63
64 private boolean highlight;
65
66
67
68
69
70
71 public DraggableAnnotation(final Draggable<?> source, final EditorMap map)
72 {
73 super(source, map);
74 setScale(false);
75 setRotate(false);
76 }
77
78
79
80
81
82 public void setHighlightIfDeletable(final boolean selected)
83 {
84 this.highlight = selected && isDeletable();
85 }
86
87
88
89
90
91 public boolean isDeletable()
92 {
93 return getSource().deleter != null;
94 }
95
96
97
98
99
100 public void showValue(final OtsEditor editor)
101 {
102 if (getSource().node != null)
103 {
104 editor.show(getSource().node, getSource().attribute);
105 }
106 }
107
108
109
110
111 public void delete()
112 {
113 if (isDeletable())
114 {
115 getSource().deleter.run();
116 }
117 }
118
119 @Override
120 public void paint(final Graphics2D graphics, final ImageObserver observer)
121 {
122 setRendering(graphics);
123 graphics.setStroke(new BasicStroke(1.0f));
124 graphics.setColor(getSource().isExpression ? EXPRESSION_COLOR : FILL_COLOR);
125 Shape shape = PaintLine.getPath(ZERO, getSource().annotation);
126 graphics.fill(shape);
127 graphics.setColor(this.highlight ? SelectionAnnotation.SELECTION_COLOR : EDGE_COLOR);
128 graphics.draw(shape);
129 resetRendering(graphics);
130 }
131
132
133
134
135
136 public void mouseDragged(final Point2d mousePoint)
137 {
138 getSource().setPointSnapped(mousePoint);
139 for (HelperAnnotation annotation : this.dynamicAnnotations)
140 {
141 annotation.getSource().setLocation(getSource().getLocation());
142 }
143 }
144
145
146
147
148
149 public void mouseReleased(final Point2d mousePoint)
150 {
151 getSource().setPointSnapped(mousePoint);
152 getSource().setValue();
153
154 }
155
156
157
158
159
160
161
162 public void addHelperAnnotation(final HelperAnnotation annotation, final UpdateMode updateMode)
163 {
164 (updateMode == UpdateMode.DYNAMIC ? this.dynamicAnnotations : this.staticAnnotations).add(annotation);
165 }
166
167 @Override
168 public synchronized void destroy(final Contextualized contextProvider)
169 {
170 super.destroy(contextProvider);
171 this.staticAnnotations.forEach((a) ->
172 {
173 a.destroy(contextProvider);
174 });
175 this.dynamicAnnotations.forEach((a) ->
176 {
177 a.destroy(contextProvider);
178 });
179 }
180
181 @Override
182 public boolean contains(final Point2d pointRelativeTo00, final RenderableScale scale, final double worldMargin,
183 final double pixelMargin, final double xScale, final double yScale)
184 {
185 return getSource().signedDistance(pointRelativeTo00.x / xScale,
186 pointRelativeTo00.y / yScale) < DRAGGABLE_SELECT_MARGIN_PX;
187 }
188
189
190
191
192
193 public static class Draggable<T> implements OtsShape
194 {
195
196
197 private DirectedPoint2d location;
198
199
200 private final PolyLine2d annotation;
201
202
203 private final UnaryOperator<Point2d> snapper;
204
205
206 private final Function<Point2d, T> valueFunction;
207
208
209 private final Consumer<T> valueWriter;
210
211
212 private XsdTreeNode node;
213
214
215 private String attribute;
216
217
218 private Runnable deleter = null;
219
220
221 private T value;
222
223
224 private boolean defaultValueSet = false;
225
226
227 private T defaultValue = null;
228
229
230 private boolean isExpression;
231
232
233
234
235
236
237
238
239
240 public Draggable(final Point2d initialLocation, final PolyLine2d annotation, final UnaryOperator<Point2d> snapper,
241 final Function<Point2d, T> valueFunction, final Consumer<T> valueWriter)
242 {
243 this.location = new DirectedPoint2d(initialLocation, 0.0);
244 this.annotation = annotation;
245 this.snapper = snapper;
246 this.valueFunction = valueFunction;
247 this.valueWriter = valueWriter;
248 }
249
250
251
252
253
254
255 public Draggable<T> setDefaultValue(final T defaultValue)
256 {
257 this.defaultValue = defaultValue;
258 this.defaultValueSet = true;
259 return this;
260 }
261
262
263
264
265
266
267
268 public Draggable<T> setNode(final XsdTreeNode node)
269 {
270 this.node = node;
271 this.attribute = null;
272 this.isExpression = node.valueIsExpression();
273 return this;
274 }
275
276
277
278
279
280 XsdTreeNode getNode()
281 {
282 return this.node;
283 }
284
285
286
287
288
289
290
291
292 @SuppressWarnings("hiddenfield")
293 public Draggable<T> setNodeAttribute(final XsdTreeNode node, final String attribute)
294 {
295 this.node = node;
296 this.attribute = attribute;
297 this.isExpression = node.attributeIsExpression(node.getAttributeIndexByName(attribute));
298 return this;
299 }
300
301
302
303
304
305
306 public Draggable<T> setDeleter(final Runnable deleter)
307 {
308 this.deleter = deleter;
309 return this;
310 }
311
312
313
314
315 public void writeDefaultValue()
316 {
317 if (this.defaultValueSet && !this.isExpression)
318 {
319 this.value = this.defaultValue;
320 this.valueWriter.accept(this.value);
321 }
322 }
323
324
325
326
327
328
329 public void setPointSnapped(final Point2d mousePoint)
330 {
331 if (this.isExpression)
332 {
333 return;
334 }
335 Point2d point = this.snapper.apply(mousePoint);
336 this.location = new DirectedPoint2d(point, 0.0);
337 this.value = this.valueFunction.apply(point);
338 }
339
340
341
342
343 public void setValue()
344 {
345 if (this.isExpression)
346 {
347 return;
348 }
349 this.valueWriter.accept(this.value);
350 }
351
352
353
354
355
356
357
358
359
360 public T getValue()
361 {
362 return this.value;
363 }
364
365 @Override
366 public double getZ()
367 {
368 return DrawLevel.ANNOTATION.getZ();
369 }
370
371 @Override
372 public DirectedPoint2d getLocation()
373 {
374 return this.location;
375 }
376
377 @Override
378 public Bounds2d getRelativeBounds()
379 {
380 return this.annotation.getAbsoluteBounds();
381 }
382
383 @Override
384 public Polygon2d getRelativeContour()
385 {
386 return new Polygon2d(this.annotation.getPointList());
387 }
388
389 }
390
391
392
393
394 public enum UpdateMode
395 {
396
397 DYNAMIC,
398
399
400 STATIC;
401 }
402
403 }