1 package org.opentrafficsim.editor.extensions.map;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.Dimension;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.io.IOException;
10 import java.rmi.RemoteException;
11 import java.util.Iterator;
12 import java.util.LinkedHashMap;
13 import java.util.LinkedHashSet;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.WeakHashMap;
17 import java.util.function.Function;
18
19 import javax.naming.NamingException;
20 import javax.swing.Box;
21 import javax.swing.Box.Filler;
22 import javax.swing.BoxLayout;
23 import javax.swing.ButtonGroup;
24 import javax.swing.ButtonModel;
25 import javax.swing.DefaultComboBoxModel;
26 import javax.swing.Icon;
27 import javax.swing.JButton;
28 import javax.swing.JCheckBox;
29 import javax.swing.JComboBox;
30 import javax.swing.JLabel;
31 import javax.swing.JPanel;
32 import javax.swing.JToggleButton;
33 import javax.swing.SwingUtilities;
34
35 import org.djutils.draw.bounds.Bounds2d;
36 import org.djutils.event.Event;
37 import org.djutils.event.EventListener;
38 import org.djutils.event.reference.ReferenceType;
39 import org.djutils.exceptions.Try;
40 import org.opentrafficsim.core.geometry.Flattener;
41 import org.opentrafficsim.core.geometry.Flattener.NumSegments;
42 import org.opentrafficsim.draw.network.LinkAnimation;
43 import org.opentrafficsim.draw.network.LinkAnimation.LinkData;
44 import org.opentrafficsim.draw.network.NodeAnimation;
45 import org.opentrafficsim.draw.network.NodeAnimation.NodeData;
46 import org.opentrafficsim.draw.road.BusStopAnimation;
47 import org.opentrafficsim.draw.road.BusStopAnimation.BusStopData;
48 import org.opentrafficsim.draw.road.CrossSectionElementAnimation.ShoulderData;
49 import org.opentrafficsim.draw.road.GtuGeneratorPositionAnimation;
50 import org.opentrafficsim.draw.road.GtuGeneratorPositionAnimation.GtuGeneratorPositionData;
51 import org.opentrafficsim.draw.road.LaneAnimation;
52 import org.opentrafficsim.draw.road.LaneAnimation.CenterLine;
53 import org.opentrafficsim.draw.road.LaneAnimation.LaneData;
54 import org.opentrafficsim.draw.road.LaneDetectorAnimation;
55 import org.opentrafficsim.draw.road.LaneDetectorAnimation.LoopDetectorData;
56 import org.opentrafficsim.draw.road.LaneDetectorAnimation.SinkData;
57 import org.opentrafficsim.draw.road.LaneDetectorAnimation.SinkData.SinkText;
58 import org.opentrafficsim.draw.road.PriorityAnimation.PriorityData;
59 import org.opentrafficsim.draw.road.StripeAnimation.StripeData;
60 import org.opentrafficsim.draw.road.TrafficLightAnimation;
61 import org.opentrafficsim.draw.road.TrafficLightAnimation.TrafficLightData;
62 import org.opentrafficsim.editor.OtsEditor;
63 import org.opentrafficsim.editor.XsdPaths;
64 import org.opentrafficsim.editor.XsdTreeNode;
65 import org.opentrafficsim.editor.XsdTreeNodeRoot;
66 import org.opentrafficsim.swing.gui.AppearanceControlComboBox;
67 import org.opentrafficsim.swing.gui.OtsControlPanel;
68
69 import nl.tudelft.simulation.dsol.animation.Locatable;
70 import nl.tudelft.simulation.dsol.animation.d2.Renderable2d;
71 import nl.tudelft.simulation.dsol.animation.d2.RenderableScale;
72 import nl.tudelft.simulation.dsol.swing.animation.d2.AnimationUpdaterThread;
73 import nl.tudelft.simulation.dsol.swing.animation.d2.VisualizationPanel;
74 import nl.tudelft.simulation.naming.context.ContextInterface;
75 import nl.tudelft.simulation.naming.context.Contextualized;
76 import nl.tudelft.simulation.naming.context.JvmContext;
77
78
79
80
81
82
83
84
85
86 public class EditorMap extends JPanel implements EventListener
87 {
88
89
90 private static final long serialVersionUID = 20231010L;
91
92
93 private static final Color BAR_COLOR = Color.LIGHT_GRAY;
94
95
96 private static final Set<String> TYPES = Set.of(XsdPaths.NODE, XsdPaths.LINK, XsdPaths.TRAFFIC_LIGHT, XsdPaths.SINK,
97 XsdPaths.GENERATOR, XsdPaths.LIST_GENERATOR);
98
99
100 private final Contextualized contextualized;
101
102
103 private final OtsEditor editor;
104
105
106 private final VisualizationPanel animationPanel;
107
108
109 private final JPanel toolPanel;
110
111
112 private final JPanel togglePanel;
113
114
115 private final LinkedHashMap<XsdTreeNode, MapData> datas = new LinkedHashMap<>();
116
117
118 private final WeakHashMap<MapLinkData, Object> links = new WeakHashMap<>();
119
120
121 private final LinkedHashMap<XsdTreeNode, RoadLayoutListener> roadLayoutListeners = new LinkedHashMap<>();
122
123
124 private FlattenerListener networkFlattenerListener;
125
126
127 private final LinkedHashMap<XsdTreeNode, Renderable2d<?>> animations = new LinkedHashMap<>();
128
129
130 private boolean ignoreKeepScale = false;
131
132
133 private Double lastXScale = null;
134
135
136 private Double lastYScale = null;
137
138
139 private Dimension lastScreen = null;
140
141
142 private Map<String, Class<? extends Locatable>> toggleLocatableMap = new LinkedHashMap<>();
143
144
145
146
147
148
149
150
151
152 private EditorMap(final AnimationUpdaterThread animator, final Contextualized contextualized, final OtsEditor editor)
153 throws RemoteException, NamingException
154 {
155 super(new BorderLayout());
156 this.contextualized = contextualized;
157 this.editor = editor;
158 this.animationPanel = new VisualizationPanel(new Bounds2d(500, 500), animator, contextualized.getContext())
159 {
160
161 private static final long serialVersionUID = 20231016L;
162
163
164 @Override
165 public void setExtent(final Bounds2d extent)
166 {
167 if (EditorMap.this.lastScreen != null)
168 {
169
170 EditorMap.this.lastXScale = this.getRenderableScale().getXScale(extent, EditorMap.this.lastScreen);
171 EditorMap.this.lastYScale = this.getRenderableScale().getYScale(extent, EditorMap.this.lastScreen);
172 }
173 super.setExtent(extent);
174 }
175
176
177 @Override
178 public synchronized void zoomAll()
179 {
180 EditorMap.this.ignoreKeepScale = true;
181 Bounds2d extent = EditorMap.this.animationPanel.fullExtent();
182 if (Double.isFinite(extent.getMaxX()))
183 {
184 super.zoomAll();
185 }
186 else if (getSize().height != 0)
187 {
188
189 super.home();
190 }
191 EditorMap.this.ignoreKeepScale = false;
192 }
193
194
195 @Override
196 public synchronized void home()
197 {
198 EditorMap.this.ignoreKeepScale = true;
199 super.home();
200 EditorMap.this.ignoreKeepScale = false;
201 }
202 };
203 this.animationPanel.setBackground(Color.GRAY);
204 this.animationPanel.setShowToolTip(false);
205 editor.addListener(this, OtsEditor.NEW_FILE);
206 this.animationPanel.setRenderableScale(new RenderableScale()
207 {
208
209 @Override
210 public Bounds2d computeVisibleExtent(final Bounds2d extent, final Dimension screen)
211 {
212 if (EditorMap.this.ignoreKeepScale)
213 {
214 return super.computeVisibleExtent(extent, screen);
215 }
216
217 double xScale = getXScale(extent, screen);
218 double yScale = getYScale(extent, screen);
219 Bounds2d result;
220 if (EditorMap.this.lastYScale != null && yScale == EditorMap.this.lastYScale)
221 {
222 result = new Bounds2d(extent.midPoint().getX() - 0.5 * screen.getWidth() * yScale,
223 extent.midPoint().getX() + 0.5 * screen.getWidth() * yScale, extent.getMinY(), extent.getMaxY());
224 xScale = yScale;
225 }
226 else if (EditorMap.this.lastXScale != null && xScale == EditorMap.this.lastXScale)
227 {
228 result = new Bounds2d(extent.getMinX(), extent.getMaxX(),
229 extent.midPoint().getY() - 0.5 * screen.getHeight() * xScale * getYScaleRatio(),
230 extent.midPoint().getY() + 0.5 * screen.getHeight() * xScale * getYScaleRatio());
231 yScale = xScale;
232 }
233 else
234 {
235 double scale = EditorMap.this.lastXScale == null ? Math.min(xScale, yScale)
236 : EditorMap.this.lastXScale * EditorMap.this.lastScreen.getWidth() / screen.getWidth();
237 result = new Bounds2d(extent.midPoint().getX() - 0.5 * screen.getWidth() * scale,
238 extent.midPoint().getX() + 0.5 * screen.getWidth() * scale,
239 extent.midPoint().getY() - 0.5 * screen.getHeight() * scale * getYScaleRatio(),
240 extent.midPoint().getY() + 0.5 * screen.getHeight() * scale * getYScaleRatio());
241 yScale = scale;
242 xScale = scale;
243 }
244 EditorMap.this.lastXScale = xScale;
245 EditorMap.this.lastYScale = yScale;
246 EditorMap.this.lastScreen = screen;
247 return result;
248 }
249 });
250 add(this.animationPanel, BorderLayout.CENTER);
251
252 this.toolPanel = new JPanel();
253 setupTools();
254
255 this.togglePanel = new JPanel();
256 this.togglePanel.setBackground(BAR_COLOR);
257 setAnimationToggles();
258 this.togglePanel.setLayout(new BoxLayout(this.togglePanel, BoxLayout.Y_AXIS));
259 add(this.togglePanel, BorderLayout.WEST);
260 }
261
262
263
264
265 private void setupTools()
266 {
267 this.toolPanel.setBackground(BAR_COLOR);
268 this.toolPanel.setMinimumSize(new Dimension(350, 28));
269 this.toolPanel.setPreferredSize(new Dimension(350, 28));
270 this.toolPanel.setLayout(new BoxLayout(this.toolPanel, BoxLayout.X_AXIS));
271
272 this.toolPanel.add(Box.createHorizontalStrut(5));
273 this.toolPanel.add(new JLabel("Add tools:"));
274
275 this.toolPanel.add(Box.createHorizontalStrut(5));
276
277 ButtonGroup group = new ButtonGroup()
278 {
279
280 private static final long serialVersionUID = 20240227L;
281
282
283 @Override
284 public void setSelected(final ButtonModel model, final boolean selected)
285 {
286 if (selected)
287 {
288 super.setSelected(model, selected);
289 }
290 else
291 {
292 clearSelection();
293 }
294 }
295 };
296
297 JToggleButton nodeButton = new JToggleButton(loadIcon("./OTS_node.png"));
298 nodeButton.setPreferredSize(new Dimension(24, 24));
299 nodeButton.setMinimumSize(new Dimension(24, 24));
300 nodeButton.setMaximumSize(new Dimension(24, 24));
301 nodeButton.setToolTipText("Add node");
302 group.add(nodeButton);
303 this.toolPanel.add(nodeButton);
304
305 JToggleButton linkButton = new JToggleButton(loadIcon("./OTS_link.png"));
306 linkButton.setPreferredSize(new Dimension(24, 24));
307 linkButton.setMinimumSize(new Dimension(24, 24));
308 linkButton.setMaximumSize(new Dimension(24, 24));
309 linkButton.setToolTipText("Add link");
310 group.add(linkButton);
311 this.toolPanel.add(linkButton);
312
313 JToggleButton centroidButton = new JToggleButton(loadIcon("./OTS_centroid.png"));
314 centroidButton.setPreferredSize(new Dimension(24, 24));
315 centroidButton.setMinimumSize(new Dimension(24, 24));
316 centroidButton.setMaximumSize(new Dimension(24, 24));
317 centroidButton.setToolTipText("Add centroid");
318 group.add(centroidButton);
319 this.toolPanel.add(centroidButton);
320
321 JToggleButton connectorButton = new JToggleButton(loadIcon("./OTS_connector.png"));
322 connectorButton.setPreferredSize(new Dimension(24, 24));
323 connectorButton.setMinimumSize(new Dimension(24, 24));
324 connectorButton.setMaximumSize(new Dimension(24, 24));
325 connectorButton.setToolTipText("Add connector");
326 group.add(connectorButton);
327 this.toolPanel.add(connectorButton);
328
329 this.toolPanel.add(Box.createHorizontalStrut(5));
330 JComboBox<String> shape = new AppearanceControlComboBox<>();
331 shape.setModel(new DefaultComboBoxModel<>(new String[] {"Straight", "Bezier", "Clothoid", "Arc", "PolyLine"}));
332 shape.setMinimumSize(new Dimension(50, 22));
333 shape.setMaximumSize(new Dimension(90, 22));
334 shape.setPreferredSize(new Dimension(90, 22));
335 shape.setToolTipText("Standard shape for new links");
336 this.toolPanel.add(shape);
337
338 this.toolPanel.add(Box.createHorizontalStrut(5));
339 JComboBox<String> roadLayout = new AppearanceControlComboBox<>();
340 roadLayout.setModel(new DefaultComboBoxModel<>(new String[] {}));
341 roadLayout.setMinimumSize(new Dimension(50, 22));
342 roadLayout.setMaximumSize(new Dimension(125, 22));
343 roadLayout.setPreferredSize(new Dimension(125, 22));
344 roadLayout.setToolTipText("Standard defined road layout for new links");
345 roadLayout.setEnabled(false);
346 this.toolPanel.add(roadLayout);
347
348 this.toolPanel.add(Box.createHorizontalStrut(5));
349 JComboBox<String> linkType = new AppearanceControlComboBox<>();
350 linkType.setModel(new DefaultComboBoxModel<>(new String[] {}));
351 linkType.setMinimumSize(new Dimension(50, 22));
352 linkType.setMaximumSize(new Dimension(125, 22));
353 linkType.setPreferredSize(new Dimension(125, 22));
354 linkType.setToolTipText("Standard link type for new links and connectors");
355 linkType.setEnabled(false);
356 this.toolPanel.add(linkType);
357
358 this.toolPanel.add(Box.createHorizontalStrut(5));
359 Dimension minDim = new Dimension(0, 1);
360 Dimension prefDim = new Dimension(0, 1);
361 Dimension maxDim = new Dimension(5000, 1);
362 this.toolPanel.add(new Filler(minDim, prefDim, maxDim));
363
364 this.toolPanel.add(new JLabel("Show:"));
365
366 this.toolPanel.add(Box.createHorizontalStrut(5));
367 JButton extent = new JButton(loadIcon("./Expand.png"));
368 extent.setMinimumSize(new Dimension(24, 24));
369 extent.setMaximumSize(new Dimension(24, 24));
370 extent.setPreferredSize(new Dimension(24, 24));
371 extent.setToolTipText("Zoom whole network");
372 extent.addActionListener((e) -> this.animationPanel.zoomAll());
373 this.toolPanel.add(extent);
374
375 JButton grid = new JButton(loadIcon("./Grid.png"));
376 grid.setMinimumSize(new Dimension(24, 24));
377 grid.setMaximumSize(new Dimension(24, 24));
378 grid.setPreferredSize(new Dimension(24, 24));
379 grid.setToolTipText("Toggle grid on/off");
380 grid.addActionListener((e) -> this.animationPanel.setShowGrid(!this.animationPanel.isShowGrid()));
381 this.toolPanel.add(grid);
382
383 this.toolPanel.add(Box.createHorizontalStrut(5));
384
385 add(this.toolPanel, BorderLayout.NORTH);
386 }
387
388
389
390
391
392
393 private Icon loadIcon(final String file)
394 {
395 try
396 {
397 return OtsEditor.loadIcon(file, 16, 16, -1, -1);
398 }
399 catch (IOException ioe)
400 {
401
402 return null;
403 }
404 }
405
406
407
408
409 private void setAnimationToggles()
410 {
411 addToggle("Node", NodeData.class, "/icons/Node24.png", "Show/hide nodes", true, false);
412 addToggle("NodeId", NodeAnimation.Text.class, "/icons/Id24.png", "Show/hide node ids", false, true);
413 addToggle("Link", LinkData.class, "/icons/Link24.png", "Show/hide links", true, false);
414 addToggle("LinkId", LinkAnimation.Text.class, "/icons/Id24.png", "Show/hide link ids", false, true);
415 addToggle("Priority", PriorityData.class, "/icons/Priority24.png", "Show/hide link priority", true, false);
416 addToggle("Lane", LaneData.class, "/icons/Lane24.png", "Show/hide lanes", true, false);
417 addToggle("LaneId", LaneAnimation.Text.class, "/icons/Id24.png", "Show/hide lane ids", false, true);
418 addToggle("LaneCenter", CenterLine.class, "/icons/CenterLine24.png", "Show/hide lane center lines", false, false);
419 addToggle("Stripe", StripeData.class, "/icons/Stripe24.png", "Show/hide stripes", true, false);
420 addToggle("Shoulder", ShoulderData.class, "/icons/Shoulder24.png", "Show/hide shoulders", true, false);
421
422 addToggle("Generator", GtuGeneratorPositionData.class, "/icons/Generator24.png", "Show/hide generators", true, false);
423 addToggle("Sink", SinkData.class, "/icons/Sink24.png", "Show/hide sinks", true, true);
424 addToggle("Detector", LoopDetectorData.class, "/icons/Detector24.png", "Show/hide loop detectors", true, false);
425 addToggle("DetectorId", LoopDetectorData.Text.class, "/icons/Id24.png", "Show/hide loop detector ids", false, true);
426 addToggle("Light", TrafficLightData.class, "/icons/TrafficLight24.png", "Show/hide traffic lights", true, false);
427 addToggle("LightId", TrafficLightAnimation.Text.class, "/icons/Id24.png", "Show/hide traffic light ids", false, true);
428 addToggle("Bus", BusStopData.class, "/icons/BusStop24.png", "Show/hide bus stops", true, false);
429 addToggle("BusId", BusStopAnimation.Text.class, "/icons/Id24.png", "Show/hide bus stop ids", false, true);
430 }
431
432
433
434
435
436
437
438
439
440
441
442
443 public final void addToggle(final String name, final Class<? extends Locatable> locatableClass, final String iconPath,
444 final String toolTipText, final boolean initiallyVisible, final boolean idButton)
445 {
446 JToggleButton button;
447 Icon icon = OtsControlPanel.loadIcon(iconPath);
448 Icon unIcon = OtsControlPanel.loadGrayscaleIcon(iconPath);
449 button = new JCheckBox();
450 button.setSelectedIcon(icon);
451 button.setIcon(unIcon);
452 button.setPreferredSize(new Dimension(32, 28));
453 button.setName(name);
454 button.setEnabled(true);
455 button.setSelected(initiallyVisible);
456 button.setActionCommand(name);
457 button.setToolTipText(toolTipText);
458 button.addActionListener(new ActionListener()
459 {
460
461 @Override
462 public void actionPerformed(final ActionEvent e)
463 {
464 String actionCommand = e.getActionCommand();
465 if (EditorMap.this.toggleLocatableMap.containsKey(actionCommand))
466 {
467 Class<? extends Locatable> locatableClass = EditorMap.this.toggleLocatableMap.get(actionCommand);
468 EditorMap.this.animationPanel.toggleClass(locatableClass);
469 EditorMap.this.togglePanel.repaint();
470 }
471 }
472 });
473
474
475 if (idButton && this.togglePanel.getComponentCount() > 0)
476 {
477 JPanel lastToggleBox = (JPanel) this.togglePanel.getComponent(this.togglePanel.getComponentCount() - 1);
478 lastToggleBox.add(button);
479 }
480 else
481 {
482 JPanel toggleBox = new JPanel();
483 toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
484 toggleBox.add(button);
485 this.togglePanel.add(toggleBox);
486 toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
487 }
488
489 if (initiallyVisible)
490 {
491 this.animationPanel.showClass(locatableClass);
492 }
493 else
494 {
495 this.animationPanel.hideClass(locatableClass);
496 }
497 this.toggleLocatableMap.put(name, locatableClass);
498 }
499
500
501
502
503
504
505
506
507 public static EditorMap build(final OtsEditor editor) throws RemoteException, NamingException
508 {
509 AnimationUpdaterThread animator = new AnimationUpdaterThread();
510 animator.start();
511 ContextInterface context = new JvmContext("ots-context");
512 Contextualized contextualized = new Contextualized()
513 {
514
515 @Override
516 public ContextInterface getContext()
517 {
518 return context;
519 }
520 };
521 return new EditorMap(animator, contextualized, editor);
522 }
523
524
525 @Override
526 public void notify(final Event event) throws RemoteException
527 {
528 if (event.getType().equals(OtsEditor.NEW_FILE))
529 {
530 for (XsdTreeNode node : new LinkedHashSet<>(this.datas.keySet()))
531 {
532 remove(node);
533 }
534 this.datas.clear();
535 this.links.clear();
536 for (Renderable2d<?> animation : this.animations.values())
537 {
538 animation.destroy(this.contextualized);
539 removeAnimation(animation);
540 }
541 this.animations.clear();
542 for (RoadLayoutListener roadLayoutListener : this.roadLayoutListeners.values())
543 {
544 roadLayoutListener.destroy();
545 }
546 this.roadLayoutListeners.clear();
547 if (this.networkFlattenerListener != null)
548 {
549 this.networkFlattenerListener.destroy();
550 this.networkFlattenerListener = null;
551 }
552 XsdTreeNodeRoot root = (XsdTreeNodeRoot) event.getContent();
553 root.addListener(this, XsdTreeNodeRoot.NODE_CREATED);
554 root.addListener(this, XsdTreeNodeRoot.NODE_REMOVED);
555 SwingUtilities.invokeLater(() -> this.animationPanel.zoomAll());
556 }
557 else if (event.getType().equals(XsdTreeNodeRoot.NODE_CREATED))
558 {
559 Object[] content = (Object[]) event.getContent();
560 XsdTreeNode node = (XsdTreeNode) content[0];
561 if (isType(node))
562 {
563 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED);
564 node.addListener(this, XsdTreeNode.OPTION_CHANGED);
565 if (node.isActive())
566 {
567 add(node);
568 }
569 }
570 else if (node.getPathString().equals(XsdPaths.POLYLINE_COORDINATE))
571 {
572 for (MapLinkData linkData : this.links.keySet())
573 {
574 linkData.addCoordinate(node);
575 }
576 }
577 else if (node.getPathString().equals(XsdPaths.DEFINED_ROADLAYOUT))
578 {
579 addRoadLayout(node);
580 }
581 else if (node.getPathString().equals(XsdPaths.NETWORK + ".Flattener"))
582 {
583 setNetworkFlattener(node);
584 }
585 }
586 else if (event.getType().equals(XsdTreeNodeRoot.NODE_REMOVED))
587 {
588 Object[] content = (Object[]) event.getContent();
589 XsdTreeNode node = (XsdTreeNode) content[0];
590 if (this.datas.containsKey(node))
591 {
592 remove(node);
593 }
594 else if (node.getPathString().equals(XsdPaths.POLYLINE_COORDINATE))
595 {
596 for (MapLinkData linkData : this.links.keySet())
597 {
598 linkData.removeCoordinate(node);
599 }
600 }
601 else if (node.getPathString().equals(XsdPaths.DEFINED_ROADLAYOUT))
602 {
603 removeRoadLayout(node);
604 }
605 else if (node.getPathString().equals(XsdPaths.NETWORK + ".Flattener"))
606 {
607 removeNetworkFlattener();
608 }
609 }
610 else if (event.getType().equals(XsdTreeNode.ACTIVATION_CHANGED))
611 {
612 Object[] content = (Object[]) event.getContent();
613 XsdTreeNode node = (XsdTreeNode) content[0];
614 if (isType(node))
615 {
616 if ((boolean) content[1])
617 {
618 add(node);
619 }
620 else
621 {
622 remove(node);
623 }
624 }
625 else if (node.getPathString().equals(XsdPaths.DEFINED_ROADLAYOUT))
626 {
627 if ((boolean) content[1])
628 {
629 addRoadLayout(node);
630 }
631 else
632 {
633 removeRoadLayout(node);
634 }
635 }
636 else if (node.getPathString().equals(XsdPaths.NETWORK + ".Flattener"))
637 {
638 if ((boolean) content[1])
639 {
640 setNetworkFlattener(node);
641 }
642 else
643 {
644 removeNetworkFlattener();
645 }
646 }
647 }
648 else if (event.getType().equals(XsdTreeNode.OPTION_CHANGED))
649 {
650 Object[] content = (Object[]) event.getContent();
651 XsdTreeNode node = (XsdTreeNode) content[0];
652 XsdTreeNode selected = (XsdTreeNode) content[1];
653 XsdTreeNode previous = (XsdTreeNode) content[2];
654 if (node.equals(selected))
655 {
656 if (isType(previous))
657 {
658 remove(previous);
659 }
660 if (isType(selected) && selected.isActive())
661 {
662 add(selected);
663 }
664 }
665 }
666 else if (event.getType().equals(XsdTreeNode.ATTRIBUTE_CHANGED))
667 {
668 for (MapLinkData linkData : this.links.keySet())
669 {
670 linkData.notifyNodeIdChanged(linkData.getNode());
671 }
672 }
673 }
674
675
676
677
678
679
680 private boolean isType(final XsdTreeNode node)
681 {
682 for (String type : TYPES)
683 {
684 if (node.isType(type))
685 {
686 return true;
687 }
688 }
689 return false;
690 }
691
692
693
694
695
696 public void setValid(final MapData data)
697 {
698 XsdTreeNode node = data.getNode();
699 if (this.animations.containsKey(node))
700 {
701 return;
702 }
703 Renderable2d<?> animation;
704 if (node.getPathString().equals(XsdPaths.NODE))
705 {
706 animation = Try.assign(() -> new NodeAnimation((MapNodeData) data, this.contextualized), "");
707 }
708 else if (node.getPathString().equals(XsdPaths.LINK))
709 {
710 animation = Try.assign(() -> new LinkAnimation((MapLinkData) data, this.contextualized, 0.5f).setDynamic(true), "");
711 }
712 else if (node.getPathString().equals(XsdPaths.TRAFFIC_LIGHT))
713 {
714 animation = Try.assign(() -> new TrafficLightAnimation((MapTrafficLightData) data, this.contextualized), "");
715 }
716 else if (node.getPathString().equals(XsdPaths.SINK))
717 {
718 Function<LaneDetectorAnimation<SinkData, SinkText>, SinkText> textSupplier = (s) -> Try
719 .assign(() -> new SinkText(s.getSource(), (float) s.getHalfLength() + 0.2f, this.contextualized), "");
720 animation = Try.assign(() -> new LaneDetectorAnimation<SinkData, SinkText>((SinkData) data, this.contextualized,
721 Color.ORANGE, textSupplier), "");
722 }
723 else if (node.getPathString().equals(XsdPaths.GENERATOR) || node.getPathString().equals(XsdPaths.LIST_GENERATOR))
724 {
725 animation = Try
726 .assign(() -> new GtuGeneratorPositionAnimation((GtuGeneratorPositionData) data, this.contextualized), "");
727 }
728 else
729 {
730 throw new UnsupportedOperationException("Data cannot be added by the map editor.");
731 }
732 this.animations.put(node, animation);
733 }
734
735
736
737
738
739
740 public void setInvalid(final MapData data)
741 {
742
743 }
744
745
746
747
748
749
750
751 private void add(final XsdTreeNode node) throws RemoteException
752 {
753 MapData data;
754 if (this.datas.containsKey(node))
755 {
756 return;
757 }
758 if (node.getPathString().equals(XsdPaths.NODE))
759 {
760 data = new MapNodeData(this, node, this.editor);
761 node.addListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
762 }
763 else if (node.getPathString().equals(XsdPaths.LINK))
764 {
765 MapLinkData linkData = new MapLinkData(this, node, this.editor);
766 data = linkData;
767 for (RoadLayoutListener roadLayoutListener : this.roadLayoutListeners.values())
768 {
769 roadLayoutListener.addListener(linkData, ChangeListener.CHANGE_EVENT, ReferenceType.WEAK);
770 }
771 if (this.networkFlattenerListener != null)
772 {
773 this.networkFlattenerListener.addListener(linkData, ChangeListener.CHANGE_EVENT, ReferenceType.WEAK);
774 }
775 this.links.put(linkData, null);
776 }
777 else if (node.getPathString().equals(XsdPaths.TRAFFIC_LIGHT))
778 {
779 MapTrafficLightData trafficLightData = new MapTrafficLightData(this, node, this.editor);
780 data = trafficLightData;
781 }
782 else if (node.getPathString().equals(XsdPaths.SINK))
783 {
784 MapSinkData sinkData = new MapSinkData(this, node, this.editor);
785 data = sinkData;
786 }
787 else if (node.getPathString().equals(XsdPaths.GENERATOR) || node.getPathString().equals(XsdPaths.LIST_GENERATOR))
788 {
789 MapGeneratorData generatorData = new MapGeneratorData(this, node, this.editor);
790 data = generatorData;
791 }
792 else
793 {
794 throw new UnsupportedOperationException("Node cannot be added by the map editor.");
795 }
796 this.datas.put(node, data);
797 }
798
799
800
801
802
803 private void remove(final XsdTreeNode node)
804 {
805 if (node.getPathString().equals(XsdPaths.NODE))
806 {
807 node.removeListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
808 }
809 if (node.getPathString().equals(XsdPaths.LINK))
810 {
811 Iterator<MapLinkData> it = this.links.keySet().iterator();
812 while (it.hasNext())
813 {
814 MapLinkData link = it.next();
815 if (link.getNode().equals(node))
816 {
817 for (RoadLayoutListener roadLayoutListener : this.roadLayoutListeners.values())
818 {
819 roadLayoutListener.removeListener(link, ChangeListener.CHANGE_EVENT);
820 }
821 if (this.networkFlattenerListener != null)
822 {
823 this.networkFlattenerListener.removeListener(link, ChangeListener.CHANGE_EVENT);
824 }
825 it.remove();
826 break;
827 }
828 }
829 }
830 MapData data = this.datas.remove(node);
831 if (data != null)
832 {
833 data.destroy();
834 }
835 removeAnimation(this.animations.remove(node));
836 }
837
838
839
840
841
842
843
844 public void reinitialize(final XsdTreeNode node)
845 {
846 remove(node);
847 Try.execute(() -> add(node), RuntimeException.class, "Unable to bind to context.");
848 }
849
850
851
852
853
854
855 public MapData getData(final XsdTreeNode node)
856 {
857 return this.datas.get(node);
858 }
859
860
861
862
863
864 private void addRoadLayout(final XsdTreeNode node)
865 {
866 RoadLayoutListener roadLayoutListener = new RoadLayoutListener(node, () -> this.editor.getEval());
867 for (MapLinkData linkData : this.links.keySet())
868 {
869 roadLayoutListener.addListener(linkData, ChangeListener.CHANGE_EVENT, ReferenceType.WEAK);
870 }
871 this.roadLayoutListeners.put(node, roadLayoutListener);
872 }
873
874
875
876
877
878 private void removeRoadLayout(final XsdTreeNode node)
879 {
880 RoadLayoutListener roadLayoutListener = this.roadLayoutListeners.remove(node);
881 roadLayoutListener.destroy();
882 }
883
884
885
886
887
888 private void setNetworkFlattener(final XsdTreeNode node)
889 {
890 this.networkFlattenerListener = new FlattenerListener(node, () -> this.editor.getEval());
891 for (MapLinkData linkData : this.links.keySet())
892 {
893 this.networkFlattenerListener.addListener(linkData, ChangeListener.CHANGE_EVENT, ReferenceType.WEAK);
894 this.editor.addEvalListener(this.networkFlattenerListener);
895 }
896 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED, ReferenceType.WEAK);
897 }
898
899
900
901
902 private void removeNetworkFlattener()
903 {
904 if (this.networkFlattenerListener != null)
905 {
906 this.editor.removeEvalListener(this.networkFlattenerListener);
907 }
908 this.networkFlattenerListener.destroy();
909 for (MapLinkData linkData : this.links.keySet())
910 {
911 Try.execute(() -> linkData.notify(new Event(ChangeListener.CHANGE_EVENT, this.networkFlattenerListener.getNode())),
912 "Remove event exception.");
913 }
914 this.networkFlattenerListener = null;
915 }
916
917
918
919
920
921
922 RoadLayoutListener getRoadLayoutListener(final XsdTreeNode node)
923 {
924 return this.roadLayoutListeners.get(node);
925 }
926
927
928
929
930
931 void removeAnimation(final Renderable2d<?> animation)
932 {
933 if (animation != null)
934 {
935 this.animationPanel.objectRemoved(animation);
936 animation.destroy(this.contextualized);
937 }
938 }
939
940
941
942
943
944 Contextualized getContextualized()
945 {
946 return this.contextualized;
947 }
948
949
950
951
952
953 public Flattener getNetworkFlattener()
954 {
955 if (this.networkFlattenerListener != null)
956 {
957 Flattener flattener = this.networkFlattenerListener.getData();
958 if (flattener != null)
959 {
960 return flattener;
961 }
962 }
963 return new NumSegments(64);
964 }
965
966 }