1 package org.opentrafficsim.swing.gui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.Container;
7 import java.awt.Dimension;
8 import java.awt.Font;
9 import java.awt.Graphics;
10 import java.awt.Insets;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.awt.event.MouseAdapter;
14 import java.awt.event.MouseEvent;
15 import java.awt.event.MouseListener;
16 import java.awt.geom.Point2D;
17 import java.awt.geom.Rectangle2D;
18 import java.awt.geom.RectangularShape;
19 import java.rmi.RemoteException;
20 import java.util.ArrayList;
21 import java.util.LinkedHashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.OptionalInt;
26 import java.util.Properties;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import javax.swing.AbstractAction;
31 import javax.swing.Box;
32 import javax.swing.BoxLayout;
33 import javax.swing.Icon;
34 import javax.swing.JButton;
35 import javax.swing.JCheckBox;
36 import javax.swing.JLabel;
37 import javax.swing.JPanel;
38 import javax.swing.JToggleButton;
39 import javax.swing.SwingConstants;
40 import javax.swing.UIManager;
41 import javax.swing.border.EmptyBorder;
42 import javax.swing.event.ChangeEvent;
43 import javax.swing.event.ChangeListener;
44
45 import org.djutils.draw.bounds.Bounds2d;
46 import org.djutils.draw.point.Point;
47 import org.djutils.draw.point.Point2d;
48 import org.djutils.event.Event;
49 import org.djutils.event.EventListener;
50 import org.djutils.event.TimedEvent;
51 import org.djutils.exceptions.Throw;
52 import org.opentrafficsim.animation.ColorInterpolator;
53 import org.opentrafficsim.animation.data.AnimationGtuData;
54 import org.opentrafficsim.animation.data.gtu.GtuColorerManager;
55 import org.opentrafficsim.animation.data.util.IconUtil;
56 import org.opentrafficsim.base.logger.Logger;
57 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
58 import org.opentrafficsim.core.gtu.Gtu;
59 import org.opentrafficsim.core.network.Network;
60 import org.opentrafficsim.road.gtu.LaneBasedGtu;
61
62 import nl.tudelft.simulation.dsol.animation.Locatable;
63 import nl.tudelft.simulation.dsol.animation.d2.Renderable2dInterface;
64 import nl.tudelft.simulation.dsol.animation.gis.GisMapInterface;
65 import nl.tudelft.simulation.dsol.animation.gis.GisRenderable2d;
66 import nl.tudelft.simulation.dsol.experiment.Replication;
67 import nl.tudelft.simulation.dsol.swing.animation.d2.AnimationPanel;
68 import nl.tudelft.simulation.dsol.swing.gui.ConsoleOutput;
69 import nl.tudelft.simulation.dsol.swing.gui.TabbedContentPane;
70 import nl.tudelft.simulation.language.DsolException;
71
72
73
74
75
76
77
78
79
80
81
82 public class OtsSimulationPanel extends JPanel implements ActionListener, EventListener
83 {
84
85 private static final long serialVersionUID = 20150617L;
86
87 static
88 {
89
90 UIManager.put("TabbedPane.contentBorderInsets", new Insets(1, 0, 1, 0));
91 }
92
93
94 public static final PropertiesStore PROPERTIES =
95 new PropertiesStore(new Properties(), "simulation", "simulation user settings");
96
97
98 private final OtsSimulatorInterface simulator;
99
100
101 private final OtsSimulationControlPanel otsSimulationControlPanel;
102
103
104 private final TabbedContentPane tabbedPane;
105
106
107 private static final Pattern UPPER_PATTERN = Pattern.compile("(?=\\p{Lu})(?<=\\p{Ll})|(?=\\p{Lu}\\p{Ll})");
108
109
110 private static final String COORD_FORMAT = "%09.2f";
111
112
113 private static final Pattern LEADING_ZEROS = Pattern.compile("^([+-]?)(0*)((:?0|[1-9]\\d*)(:?[\\.,]\\d+)?)$");
114
115
116 private static final Icon COLLAPSED_ICON = IconUtil.of("Collapsed24.png").imageSize(12, 12).get();
117
118
119 private static final Icon EXPANDED_ICON = IconUtil.of("Expanded24.png").imageSize(12, 12).get();
120
121
122 private final OtsSearchPanel otsSearchPanel;
123
124
125 private final OtsAnimationPanel otsAnimationPanel;
126
127
128 private final JPanel togglePanel;
129
130
131 private JPanel demoPanel = null;
132
133
134 private boolean toggleSectionVisible = true;
135
136
137 private Map<AppearanceJToggleButton, ToggleSectionData> toggleSectionData = new LinkedHashMap<>();
138
139
140 private Map<String, Class<? extends Locatable>> toggleLocatableMap = new LinkedHashMap<>();
141
142
143 private Map<Class<? extends Locatable>, JToggleButton> toggleButtons = new LinkedHashMap<>();
144
145
146 private Map<String, GisMapInterface> toggleGisMap = new LinkedHashMap<>();
147
148
149 private Map<String, JToggleButton> toggleGisButtons = new LinkedHashMap<>();
150
151
152 private OtsGtuColorPanel gtuColorPanel = null;
153
154
155 private final JLabel coordinateField;
156
157
158 private final JLabel gtuCountField;
159
160
161 private int gtuCount = 0;
162
163
164 private String autoPanId = null;
165
166
167 private OtsSearchPanel.ObjectKind<?> autoPanKind = null;
168
169
170 private boolean autoPanTrack = false;
171
172
173 private boolean autoPanOnNextPaintComponent = false;
174
175
176
177
178
179
180
181 public OtsSimulationPanel(final Network network) throws RemoteException, DsolException
182 {
183 this(network.getExtent(), network);
184 }
185
186
187
188
189
190
191
192
193 public OtsSimulationPanel(final Rectangle2D extent, final Network network) throws RemoteException, DsolException
194 {
195 this(extent, network, new OtsSimulationPanelDecorator()
196 {
197 });
198 }
199
200
201
202
203
204
205
206
207
208 public OtsSimulationPanel(final Network network, final OtsSimulationPanelDecorator decorator)
209 throws RemoteException, DsolException
210 {
211 this(network.getExtent(), network, decorator);
212 }
213
214
215
216
217
218
219
220
221
222
223 public OtsSimulationPanel(final Rectangle2D extent, final Network network, final OtsSimulationPanelDecorator decorator)
224 throws RemoteException, DsolException
225 {
226 Throw.whenNull(network, "network");
227 Throw.whenNull(decorator, "decorator");
228
229 AppearanceApplication.setDefaultFont();
230
231 this.simulator = network.getSimulator();
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269 setLayout(new BorderLayout());
270
271
272 JPanel topPanel = new JPanel();
273 topPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
274 topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS));
275 this.otsSimulationControlPanel = new OtsSimulationControlPanel(this.simulator, this);
276 this.otsSearchPanel = new OtsSearchPanel(this);
277 topPanel.add(this.otsSimulationControlPanel);
278 topPanel.add(Box.createHorizontalGlue());
279 topPanel.add(this.otsSearchPanel);
280 add(topPanel, BorderLayout.NORTH);
281
282
283 this.tabbedPane = new AppearanceControlTabbedContentPane(SwingConstants.BOTTOM);
284 JPanel borderLayoutPanel = new JPanel(new BorderLayout());
285 this.tabbedPane.addTab(0, "animation", borderLayoutPanel);
286 add(this.tabbedPane, BorderLayout.CENTER);
287
288
289 JPanel animationTopBarPanel = new JPanel();
290 animationTopBarPanel.setLayout(new BoxLayout(animationTopBarPanel, BoxLayout.X_AXIS));
291 borderLayoutPanel.add(animationTopBarPanel, BorderLayout.NORTH);
292 this.togglePanel = new JPanel();
293 this.togglePanel.setLayout(new BoxLayout(this.togglePanel, BoxLayout.Y_AXIS));
294 JPanel resetBox = new JPanel();
295 resetBox.setLayout(new BoxLayout(resetBox, BoxLayout.X_AXIS));
296 resetBox.setAlignmentX(Component.LEFT_ALIGNMENT);
297 this.togglePanel.add(resetBox);
298 Dimension dim = new Dimension(16, 16);
299 resetBox.add(Box.createHorizontalGlue());
300 createToggleSectionResetButton("Expand", EXPANDED_ICON, true, dim, resetBox);
301 createToggleSectionResetButton("Collapse", COLLAPSED_ICON, false, dim, resetBox);
302 createToggleSectionResetButton("Reset", IconUtil.of("Restore24.png").imageSize(12, 12).get(), null, dim, resetBox);
303 resetBox.add(Box.createHorizontalGlue());
304
305 borderLayoutPanel.add(this.togglePanel, BorderLayout.WEST);
306 this.otsAnimationPanel = new OtsAnimationPanel(extent, this.simulator, network);
307 this.otsAnimationPanel.showGrid(PROPERTIES.getOptionalBoolean("grid").orElse(false));
308 borderLayoutPanel.add(this.otsAnimationPanel, BorderLayout.CENTER);
309
310
311 this.gtuColorPanel = new OtsGtuColorPanel();
312 decorator.getGtuColorers().forEach((colorer) -> this.gtuColorPanel.addGtuColorer(colorer));
313 animationTopBarPanel.add(this.gtuColorPanel);
314 animationTopBarPanel.add(Box.createHorizontalStrut(10));
315 animationTopBarPanel.add(makeButton("yZoomButton", "UpDown24.png", "Reset Y-zoom", "Reset Y-zoom", true));
316 animationTopBarPanel.add(makeButton("allButton", "ZoomAll24.png", "ZoomAll", "Zoom whole network", true));
317 animationTopBarPanel.add(makeButton("homeButton", "Home24.png", "Home", "Zoom to original extent", true));
318 animationTopBarPanel.add(makeButton("gridButton", "Grid24.png", "Grid", "Toggle grid on/off", true));
319 animationTopBarPanel.add(Box.createHorizontalStrut(10));
320 JPanel infoTextPanel = new JPanel();
321 animationTopBarPanel.add(infoTextPanel);
322 infoTextPanel.setMinimumSize(new Dimension(250, 30));
323 infoTextPanel.setPreferredSize(new Dimension(250, 30));
324 infoTextPanel.setMaximumSize(new Dimension(250, 30));
325 infoTextPanel.setLayout(new BoxLayout(infoTextPanel, BoxLayout.Y_AXIS));
326
327
328 this.coordinateField = new JLabel();
329 this.coordinateField.setMinimumSize(new Dimension(150, 15));
330 this.coordinateField.setPreferredSize(new Dimension(150, 15));
331 this.coordinateField.setMaximumSize(new Dimension(150, 15));
332 this.coordinateField.setFont(new Font("Consolas", Font.PLAIN, 12));
333 infoTextPanel.add(this.coordinateField);
334 this.gtuCountField = new JLabel();
335 this.gtuCountField.setMinimumSize(new Dimension(150, 15));
336 this.gtuCountField.setPreferredSize(new Dimension(150, 15));
337 this.gtuCountField.setMaximumSize(new Dimension(150, 15));
338 this.gtuCount = null == network ? 0 : network.getGTUs().size();
339 infoTextPanel.add(this.gtuCountField);
340 setGtuCountText();
341
342
343 this.tabbedPane.addChangeListener(new ChangeListener()
344 {
345 @Override
346 public void stateChanged(final ChangeEvent e)
347 {
348 int index = OtsSimulationPanel.this.tabbedPane.getSelectedIndex();
349 Component component = OtsSimulationPanel.this.tabbedPane.getComponentAt(index);
350 OtsSimulationPanel.this.otsSearchPanel.setVisible(borderLayoutPanel.equals(component));
351 }
352 });
353
354
355 if (null != network)
356 {
357 network.addListener(this, Network.GTU_ADD_EVENT);
358 network.addListener(this, Network.GTU_REMOVE_EVENT);
359 }
360
361
362 this.otsAnimationPanel
363 .notify(new TimedEvent<>(Replication.START_REPLICATION_EVENT, null, getSimulator().getSimulatorTime()));
364
365
366 this.otsAnimationPanel.setShowToolTip(false);
367
368
369 decorator.decorate(this, network);
370 }
371
372
373
374
375
376
377
378
379
380 private void createToggleSectionResetButton(final String action, final Icon icon, final Boolean selected,
381 final Dimension dim, final JPanel resetBox)
382 {
383 JButton expand = new JButton(new AbstractAction("", icon)
384 {
385 private static final long serialVersionUID = 20260507L;
386
387 @Override
388 public void actionPerformed(final ActionEvent e)
389 {
390 setToggleSectionButtonsState(selected);
391 }
392 });
393 expand.setToolTipText(action + " all animation toggle sections");
394 expand.setContentAreaFilled(false);
395 expand.setBorder(null);
396 expand.setMinimumSize(dim);
397 expand.setPreferredSize(dim);
398 expand.setMaximumSize(dim);
399 resetBox.add(expand);
400 }
401
402
403
404
405
406 private void setToggleSectionButtonsState(final Boolean selected)
407 {
408 for (Component component : this.togglePanel.getComponents())
409 {
410 if (component instanceof AppearanceJToggleButton button)
411 {
412 ToggleSectionData sectionData = this.toggleSectionData.get(button);
413 boolean state = selected == null ? sectionData.defaultVisible() : selected;
414 button.setSelected(state);
415 toggleSectionToggleChanged(sectionData.name(), button, getToggleSectionKey(sectionData.name()));
416 }
417 }
418 }
419
420
421
422
423 public void addConsoleTab()
424 {
425 ConsoleOutput console = new ConsoleOutput();
426 console.setBorder(null);
427 this.tabbedPane.addTab("console", console);
428 }
429
430
431
432
433
434 public TabbedContentPane getTabbedPane()
435 {
436 return this.tabbedPane;
437 }
438
439
440
441
442
443 public OtsSimulatorInterface getSimulator()
444 {
445 return this.simulator;
446 }
447
448
449
450
451
452
453 public void enableSimulationControlButtons()
454 {
455 this.otsSimulationControlPanel.setSimulationControlButtons(true);
456 }
457
458
459
460
461 public void disableSimulationControlButtons()
462 {
463 this.otsSimulationControlPanel.setSimulationControlButtons(false);
464 }
465
466
467
468
469
470
471
472 public void setAutoPan(final String newAutoPanId, final OtsSearchPanel.ObjectKind<?> newAutoPanKind,
473 final boolean newAutoPanTrack)
474 {
475 this.autoPanId = newAutoPanId;
476 this.autoPanKind = newAutoPanKind;
477 this.autoPanTrack = newAutoPanTrack;
478 this.autoPanOnNextPaintComponent = true;
479 Logger.ots().trace("AutoPan id=" + newAutoPanId + ", kind=" + newAutoPanKind + ", track=" + newAutoPanTrack);
480 if (null != this.autoPanId && null != OtsSimulationPanel.this.otsAnimationPanel && this.autoPanId.length() > 0
481 && null != this.autoPanKind)
482 {
483 OtsSimulationPanel.this.otsAnimationPanel.repaint();
484 }
485 }
486
487
488
489
490
491
492
493
494
495
496 private JButton makeButton(final String name, final String iconFile, final String actionCommand, final String toolTipText,
497 final boolean enabled)
498 {
499 JButton result = new JButton(IconUtil.of(iconFile).get());
500 result.setMinimumSize(new Dimension(34, 32));
501 result.setPreferredSize(new Dimension(34, 32));
502 result.setMaximumSize(new Dimension(34, 32));
503 result.setName(name);
504 result.setEnabled(enabled);
505 result.setActionCommand(actionCommand);
506 result.setToolTipText(toolTipText);
507 result.addActionListener(this);
508 return result;
509 }
510
511
512
513
514
515
516 public void startToggleSection(final String name, final boolean visibleDefault)
517 {
518 AppearanceJToggleButton toggle = new AppearanceJToggleButton();
519 toggle.setSelectedIcon(EXPANDED_ICON);
520 toggle.setIcon(COLLAPSED_ICON);
521 String key = getToggleSectionKey(name);
522 boolean toggleOn = PROPERTIES.getOptionalBoolean(key).orElse(true);
523 toggle.setSelected(toggleOn);
524 PROPERTIES.setBoolean(key, toggleOn);
525 this.toggleSectionVisible = toggleOn;
526 toggle.setText(toggleOn ? null : name);
527 toggle.setContentAreaFilled(false);
528 toggle.setBorder(null);
529 Dimension dim = new Dimension(64, 14);
530 toggle.setMinimumSize(dim);
531 toggle.setPreferredSize(dim);
532 toggle.setMaximumSize(dim);
533 toggle.addActionListener(new ActionListener()
534 {
535 @Override
536 public void actionPerformed(final ActionEvent e)
537 {
538 toggleSectionToggleChanged(name, toggle, key);
539 }
540 });
541 this.togglePanel.add(toggle);
542 this.toggleSectionData.put(toggle, new ToggleSectionData(name, visibleDefault));
543 }
544
545
546
547
548
549
550 private String getToggleSectionKey(final String name)
551 {
552 return "toggle.section." + PropertiesStore.key(name);
553 }
554
555
556
557
558
559
560
561 private void toggleSectionToggleChanged(final String name, final AppearanceJToggleButton sectionToggle, final String key)
562 {
563 PROPERTIES.setBoolean(key, sectionToggle.isSelected());
564 sectionToggle.setText(sectionToggle.isSelected() ? null : name);
565 boolean inSection = false;
566 for (Component component : OtsSimulationPanel.this.togglePanel.getComponents())
567 {
568 if (component.equals(sectionToggle))
569 {
570 inSection = true;
571 }
572 else if (inSection && component instanceof AppearanceJToggleButton)
573 {
574 return;
575 }
576 else if (inSection)
577 {
578 component.setVisible(sectionToggle.isSelected());
579 }
580 }
581 }
582
583
584
585
586
587
588
589
590
591
592
593
594
595 public void addToggleAnimationButtonIcon(final String name, final Class<? extends Locatable> locatableClass,
596 final String iconPath, final String toolTipText, final boolean initiallyVisible, final boolean nextToPrevious)
597 {
598 JToggleButton button;
599 Icon icon = IconUtil.of(iconPath).get();
600 Icon unIcon = IconUtil.of(iconPath).gray().get();
601 button = new JCheckBox();
602 button.setSelectedIcon(icon);
603 button.setIcon(unIcon);
604 button.setPreferredSize(new Dimension(32, 28));
605 button.setName(name);
606 String key = "toggle." + PropertiesStore.key(name);
607 boolean toggleOn = PROPERTIES.getOptionalBoolean(key).orElse(initiallyVisible);
608 button.setSelected(toggleOn);
609 PROPERTIES.setBoolean(key, toggleOn);
610 button.setActionCommand(name);
611 button.setToolTipText(toolTipText);
612 button.addActionListener(this);
613
614
615 if (nextToPrevious && this.togglePanel.getComponentCount() > 0)
616 {
617 JPanel lastToggleBox = (JPanel) this.togglePanel.getComponent(this.togglePanel.getComponentCount() - 1);
618 button.setVisible(true);
619 lastToggleBox.add(button);
620 }
621 else
622 {
623 JPanel toggleBox = new JPanel();
624 toggleBox.setVisible(this.toggleSectionVisible);
625 button.setVisible(true);
626 toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
627 toggleBox.add(button);
628 this.togglePanel.add(toggleBox);
629 toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
630 }
631
632 if (toggleOn)
633 {
634 this.otsAnimationPanel.showClass(locatableClass);
635 }
636 else
637 {
638 this.otsAnimationPanel.hideClass(locatableClass);
639 }
640 this.toggleLocatableMap.put(name, locatableClass);
641 this.toggleButtons.put(locatableClass, button);
642 }
643
644
645
646
647
648
649
650
651 public void addToggleAnimationButtonText(final String name, final Class<? extends Locatable> locatableClass,
652 final String toolTipText, final boolean initiallyVisible)
653 {
654 JToggleButton button;
655 button = new JCheckBox(name);
656 button.setText(separatedName(name));
657 button.setVisible(this.toggleSectionVisible);
658 String key = "toggle." + PropertiesStore.key(name);
659 boolean toggleOn = PROPERTIES.getOptionalBoolean(key).orElse(initiallyVisible);
660 button.setSelected(toggleOn);
661 PROPERTIES.setBoolean(key, toggleOn);
662 button.setActionCommand(name);
663 button.setToolTipText(toolTipText);
664 button.addActionListener(this);
665 button.setPreferredSize(new Dimension(113, 19));
666 button.setMaximumSize(new Dimension(113, 19));
667
668 this.togglePanel.add(button);
669
670 if (toggleOn)
671 {
672 this.otsAnimationPanel.showClass(locatableClass);
673 }
674 else
675 {
676 this.otsAnimationPanel.hideClass(locatableClass);
677 }
678 this.toggleLocatableMap.put(name, locatableClass);
679 this.toggleButtons.put(locatableClass, button);
680 }
681
682
683
684
685
686 public void addToggleText(final String text)
687 {
688 JPanel textBox = new JPanel();
689 textBox.setVisible(this.toggleSectionVisible);
690 textBox.setLayout(new BoxLayout(textBox, BoxLayout.X_AXIS));
691 textBox.add(new JLabel(text));
692 this.togglePanel.add(textBox);
693 textBox.setAlignmentX(Component.LEFT_ALIGNMENT);
694 }
695
696
697
698
699
700
701
702 public void addAllToggleGisButtonText(final String header, final GisRenderable2d gisMap, final String toolTipText)
703 {
704 addToggleText(" ");
705 addToggleText(header);
706 for (String layerName : gisMap.getMap().getLayerMap().keySet())
707 {
708 addToggleGisButtonText(layerName, layerName, gisMap, toolTipText, true);
709 }
710 }
711
712
713
714
715
716
717
718
719
720 public void addToggleGisButtonText(final String layerName, final String displayName, final GisRenderable2d gisMap,
721 final String toolTipText, final boolean initiallyVisible)
722 {
723 JToggleButton button;
724 button = new JCheckBox(displayName);
725 button.setName(layerName);
726 button.setVisible(this.toggleSectionVisible);
727 String key = "toggle.gis." + PropertiesStore.key(layerName);
728 boolean toggleOn = PROPERTIES.getOptionalBoolean(key).orElse(initiallyVisible);
729 button.setSelected(toggleOn);
730 PROPERTIES.setBoolean(key, toggleOn);
731 button.setSelected(toggleOn);
732 button.setActionCommand(layerName);
733 button.setToolTipText(toolTipText);
734 button.addActionListener(this);
735
736 JPanel toggleBox = new JPanel();
737 toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
738 toggleBox.add(button);
739 this.togglePanel.add(toggleBox);
740 toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
741
742 this.toggleGisMap.put(layerName, gisMap.getMap());
743 this.toggleGisButtons.put(layerName, button);
744 }
745
746
747
748
749
750 public void showGisLayer(final String layerName)
751 {
752 GisMapInterface gisMap = this.toggleGisMap.get(layerName);
753 if (gisMap != null && !gisMap.getVisibleLayers().contains(gisMap.getLayerMap().get(layerName)))
754 {
755 toggleGisLayer(layerName);
756 }
757 }
758
759
760
761
762
763 public void hideGisLayer(final String layerName)
764 {
765 GisMapInterface gisMap = this.toggleGisMap.get(layerName);
766 if (gisMap != null && gisMap.getVisibleLayers().contains(gisMap.getLayerMap().get(layerName)))
767 {
768 toggleGisLayer(layerName);
769 }
770 }
771
772
773
774
775
776 public void toggleGisLayer(final String layerName)
777 {
778 GisMapInterface gisMap = this.toggleGisMap.get(layerName);
779 if (gisMap != null)
780 {
781 boolean show = !gisMap.getVisibleLayers().contains(gisMap.getLayerMap().get(layerName));
782 String key = "toggle.gis." + PropertiesStore.key(layerName);
783 PROPERTIES.setBoolean(key, show);
784 if (show)
785 {
786 gisMap.showLayer(layerName);
787 this.toggleGisButtons.get(layerName).setSelected(true);
788 }
789 else
790 {
791 gisMap.hideLayer(layerName);
792 this.toggleGisButtons.get(layerName).setSelected(false);
793 }
794 this.otsAnimationPanel.repaint();
795 }
796 }
797
798 @Override
799 public void actionPerformed(final ActionEvent actionEvent)
800 {
801 String actionCommand = actionEvent.getActionCommand();
802 Logger.ots().trace("Action command is " + actionCommand);
803 try
804 {
805 if (actionCommand.equals("Reset Y-zoom"))
806 {
807 this.otsAnimationPanel.resetZoomY();
808 }
809 else if (actionCommand.equals("Home"))
810 {
811 this.otsAnimationPanel.resetZoomY();
812 this.otsAnimationPanel.home();
813 }
814 else if (actionCommand.equals("ZoomAll"))
815 {
816 this.otsAnimationPanel.resetZoomY();
817 this.otsAnimationPanel.zoomAll();
818 }
819 else if (actionCommand.equals("Grid"))
820 {
821 this.otsAnimationPanel.showGrid(!this.otsAnimationPanel.isShowGrid());
822 PROPERTIES.setBoolean("grid", this.otsAnimationPanel.isShowGrid());
823 }
824 else if (this.toggleLocatableMap.containsKey(actionCommand))
825 {
826 Class<? extends Locatable> locatableClass = this.toggleLocatableMap.get(actionCommand);
827 this.otsAnimationPanel.toggleClass(locatableClass);
828 String key = "toggle." + PropertiesStore.key(actionCommand);
829 PROPERTIES.setBoolean(key, this.otsAnimationPanel.isShowClass(locatableClass));
830 this.togglePanel.repaint();
831 }
832 else if (this.toggleGisMap.containsKey(actionCommand))
833 {
834 this.toggleGisLayer(actionCommand);
835 this.togglePanel.repaint();
836 }
837 }
838 catch (Exception exception)
839 {
840 exception.printStackTrace();
841 }
842 }
843
844
845
846
847
848 public AnimationPanel getAnimationPanel()
849 {
850 return this.otsAnimationPanel;
851 }
852
853
854
855
856
857
858 public void createDemoPanel(final DemoPanelPosition position)
859 {
860 Throw.when(this.demoPanel != null, IllegalStateException.class,
861 "Attempt to create demo panel, but it's already created");
862 Throw.whenNull(position, "Position may not be null.");
863 Container parent = this.otsAnimationPanel.getParent();
864 parent.remove(this.otsAnimationPanel);
865
866 JPanel splitPanel = new JPanel(new BorderLayout());
867 parent.add(splitPanel);
868 splitPanel.add(this.otsAnimationPanel, BorderLayout.CENTER);
869
870 this.demoPanel = new JPanel();
871 this.demoPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
872 splitPanel.add(this.demoPanel, position.getBorderLayoutPosition());
873 }
874
875
876
877
878
879
880 public JPanel getDemoPanel()
881 {
882 if (this.demoPanel == null)
883 {
884 createDemoPanel(DemoPanelPosition.RIGHT);
885 }
886 return this.demoPanel;
887 }
888
889
890
891
892
893 public void updateAnimationClassCheckBox(final Class<? extends Locatable> locatableClass)
894 {
895 JToggleButton button = this.toggleButtons.get(locatableClass);
896 if (button == null)
897 {
898 return;
899 }
900 button.setSelected(getAnimationPanel().isShowClass(locatableClass));
901 }
902
903
904
905
906 private void updateWorldCoordinate()
907 {
908 String x = String.format(COORD_FORMAT, this.otsAnimationPanel.getWorldCoordinate().getX());
909 String y = String.format(COORD_FORMAT, this.otsAnimationPanel.getWorldCoordinate().getY());
910 String worldPoint = "<html>(x=" + fadeLeadingZeros(x) + "; y=" + fadeLeadingZeros(y) + ")</html>";
911 this.coordinateField.setText(worldPoint);
912 String worldPointNoHtml = "(x=" + x + "; y=" + y + ")";
913 if (this.coordinateField.getGraphics() == null)
914 {
915
916 return;
917 }
918
919
920 int requiredWidth = this.coordinateField.getGraphics().getFontMetrics().stringWidth(worldPointNoHtml) + 10;
921 if (this.coordinateField.getPreferredSize().width < requiredWidth)
922 {
923 Dimension requiredSize = new Dimension(requiredWidth, this.coordinateField.getPreferredSize().height);
924 this.coordinateField.setMinimumSize(requiredSize);
925 this.coordinateField.setPreferredSize(requiredSize);
926 this.coordinateField.setMaximumSize(requiredSize);
927 Container parent = this.coordinateField.getParent();
928 requiredSize = new Dimension(requiredWidth, parent.getPreferredSize().height);
929 parent.setMinimumSize(requiredSize);
930 parent.setPreferredSize(requiredSize);
931 parent.setMaximumSize(requiredSize);
932 Logger.ots().trace("Increased minimum width to " + requiredSize.width);
933 parent.revalidate();
934 }
935 this.coordinateField.repaint();
936 }
937
938
939
940
941
942
943 private String fadeLeadingZeros(final String formatted)
944 {
945 Matcher m = LEADING_ZEROS.matcher(formatted);
946 if (!m.matches())
947 {
948 return formatted;
949 }
950 String sign = m.group(1) == null ? "" : m.group(1);
951 String zeros = m.group(2) == null ? "" : m.group(2);
952 String digits = m.group(3);
953 Color faded = ColorInterpolator.interpolateColor(this.coordinateField.getBackground(),
954 this.gtuCountField.getForeground(), 0.2);
955 String zerosColor = String.format("#%02x%02x%02x", faded.getRed(), faded.getGreen(), faded.getBlue());
956 return sign + "<span style='color:" + zerosColor + ";'>" + zeros + "</span>" + digits;
957 }
958
959
960
961
962
963 public GtuColorerManager getGtuColorerManager()
964 {
965 return this.gtuColorPanel.getGtuColorerManager();
966 }
967
968 @Override
969 public void notify(final Event event)
970 {
971 if (event.getType().equals(Network.GTU_ADD_EVENT))
972 {
973 this.gtuCount++;
974 setGtuCountText();
975 }
976 else if (event.getType().equals(Network.GTU_REMOVE_EVENT))
977 {
978 this.gtuCount--;
979 setGtuCountText();
980 }
981 }
982
983
984
985
986 private void setGtuCountText()
987 {
988 String text = this.gtuCount + " GTU's";
989 this.gtuCountField.setText(text);
990 }
991
992
993
994
995
996
997 public static String separatedName(final String name)
998 {
999 String[] parts = UPPER_PATTERN.split(name);
1000 if (parts.length == 1)
1001 {
1002 return parts[0];
1003 }
1004 String separator = "";
1005 StringBuilder stringBuilder = new StringBuilder();
1006 for (String part : parts)
1007 {
1008 stringBuilder.append(separator).append(part);
1009 separator = "\u2009";
1010 }
1011 return stringBuilder.toString();
1012 }
1013
1014
1015
1016
1017 private final class AppearanceJToggleButton extends JCheckBox implements AppearanceControl
1018 {
1019
1020 private static final long serialVersionUID = 1L;
1021
1022 @Override
1023 public boolean isForeground()
1024 {
1025 return true;
1026 }
1027
1028 @Override
1029 public OptionalInt getFontSize()
1030 {
1031 return OptionalInt.empty();
1032 }
1033 }
1034
1035
1036
1037
1038
1039
1040 private record ToggleSectionData(String name, boolean defaultVisible)
1041 {
1042 }
1043
1044
1045
1046
1047 private class OtsAnimationPanel extends AnimationPanel
1048 {
1049
1050
1051 private static final long serialVersionUID = 20180430L;
1052
1053
1054 private final Network network;
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064 OtsAnimationPanel(final Rectangle2D extent, final OtsSimulatorInterface simulator, final Network network)
1065 throws RemoteException, DsolException
1066 {
1067 super(new Bounds2d(extent.getMinX(), extent.getMaxX(), extent.getMinY(), extent.getMaxY()), simulator);
1068 setPreferredSize(new Dimension(800, 600));
1069 this.network = network;
1070 MouseListener[] listeners = getMouseListeners();
1071 for (MouseListener listener : listeners)
1072 {
1073 removeMouseListener(listener);
1074 }
1075 this.addMouseListener(new MouseAdapter()
1076 {
1077 @Override
1078 public void mouseClicked(final MouseEvent e)
1079 {
1080 if (e.isControlDown())
1081 {
1082 Gtu gtu = getSelectedGtu(e.getPoint());
1083 if (gtu != null)
1084 {
1085 OtsSimulationPanel.this.otsSearchPanel.selectAndTrackObject("GTU", gtu.getId(), true);
1086 }
1087 }
1088 e.consume();
1089 }
1090 });
1091 for (MouseListener listener : listeners)
1092 {
1093 addMouseListener(listener);
1094 }
1095 }
1096
1097
1098
1099
1100
1101 @Override
1102 public synchronized void setWorldCoordinate(final Point2d point)
1103 {
1104 super.setWorldCoordinate(point);
1105 updateWorldCoordinate();
1106 }
1107
1108
1109
1110
1111
1112
1113 private Gtu getSelectedGtu(final Point2D mousePoint)
1114 {
1115 List<LaneBasedGtu> targets = new ArrayList<>();
1116 Point2d point = getRenderableScale().getWorldCoordinates(mousePoint, getExtent(), getSize());
1117 for (Renderable2dInterface<?> renderable : getElements())
1118 {
1119 if (isShowElement(renderable) && renderable.contains(point, getExtent()))
1120 {
1121 if (renderable.getSource() instanceof AnimationGtuData animData)
1122 {
1123 targets.add(animData.getObject());
1124 }
1125 }
1126 }
1127 if (targets.size() == 1)
1128 {
1129 return targets.get(0);
1130 }
1131 return null;
1132 }
1133
1134 @Override
1135 public void paintComponent(final Graphics g)
1136 {
1137 final OtsSearchPanel.ObjectKind<?> panKind = OtsSimulationPanel.this.autoPanKind;
1138 final String panId = OtsSimulationPanel.this.autoPanId;
1139 final boolean doPan = OtsSimulationPanel.this.autoPanOnNextPaintComponent;
1140 OtsSimulationPanel.this.autoPanOnNextPaintComponent = OtsSimulationPanel.this.autoPanTrack;
1141 if (doPan && panKind != null && panId != null)
1142 {
1143 Optional<? extends Locatable> locatable = panKind.searchNetwork(this.network, panId);
1144 if (locatable.isPresent())
1145 {
1146 Point<?> point = locatable.get().getLocation();
1147 if (point != null)
1148 {
1149 double w = getExtent().getDeltaX();
1150 double h = getExtent().getDeltaY();
1151 setExtent(new Bounds2d(point.getX() - w / 2, point.getX() + w / 2, point.getY() - h / 2,
1152 point.getY() + h / 2));
1153 }
1154 }
1155 }
1156 super.paintComponent(g);
1157 }
1158
1159 @Override
1160 public void setBackground(final Color bg)
1161 {
1162 int threshold = 64;
1163 int alternative = 96;
1164 if (bg.getRed() <= threshold && bg.getGreen() <= threshold && bg.getBlue() <= threshold)
1165 {
1166 setGridColor(new Color(alternative, alternative, alternative));
1167 }
1168 else
1169 {
1170 setGridColor(Color.BLACK);
1171 }
1172 super.setBackground(bg);
1173 }
1174
1175
1176
1177 @Override
1178 protected synchronized void drawGrid(final Graphics g)
1179 {
1180
1181 g.setFont(g.getFont().deriveFont(11.0f));
1182 g.setColor(this.getGridColor());
1183 double scaleX = this.getRenderableScale().getXScale(this.getExtent(), this.getSize());
1184 double scaleY = this.getRenderableScale().getYScale(this.getExtent(), this.getSize());
1185
1186 int count = 0;
1187 double gridSizePixelsX = this.gridSizeX / scaleX;
1188 while (gridSizePixelsX < 40)
1189 {
1190 this.gridSizeX = 10 * this.gridSizeX;
1191 int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSizeX) / Math.log(10)));
1192 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
1193 gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
1194 if (count++ > 10)
1195 {
1196 break;
1197 }
1198 }
1199
1200 count = 0;
1201 while (gridSizePixelsX > 10 * 40)
1202 {
1203 int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSizeX) / Math.log(10)));
1204 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
1205 this.gridSizeX = this.gridSizeX / 10;
1206 gridSizePixelsX = (int) Math.round(this.gridSizeX / scaleX);
1207 if (count++ > 10)
1208 {
1209 break;
1210 }
1211 }
1212
1213 double gridSizePixelsY = this.gridSizeY / scaleY;
1214 while (gridSizePixelsY < 40)
1215 {
1216 this.gridSizeY = 10 * this.gridSizeY;
1217 int maximumNumberOfDigits = (int) Math.max(0, 1 + Math.ceil(Math.log(1 / this.gridSizeY) / Math.log(10)));
1218 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
1219 gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
1220 if (count++ > 10)
1221 {
1222 break;
1223 }
1224 }
1225
1226 count = 0;
1227 while (gridSizePixelsY > 10 * 40)
1228 {
1229 int maximumNumberOfDigits = (int) Math.max(0, 2 + Math.ceil(Math.log(1 / this.gridSizeY) / Math.log(10)));
1230 this.formatter.setMaximumFractionDigits(maximumNumberOfDigits);
1231 this.gridSizeY = this.gridSizeY / 10;
1232 gridSizePixelsY = (int) Math.round(this.gridSizeY / scaleY);
1233 if (count++ > 10)
1234 {
1235 break;
1236 }
1237 }
1238
1239
1240 double mod = this.getExtent().getMinX() % this.gridSizeX;
1241 double x = -mod / scaleX;
1242 while (x < this.getWidth())
1243 {
1244 Point2d point = this.getRenderableScale().getWorldCoordinates(new Point2D.Double(x, 0), this.getExtent(),
1245 this.getSize());
1246 if (point != null)
1247 {
1248 String label = this.formatter.format(Math.round(point.getX() / this.gridSizeX) * this.gridSizeX);
1249 double labelWidth = this.getFontMetrics(this.getFont()).getStringBounds(label, g).getWidth();
1250 if (x > labelWidth + 4)
1251 {
1252 int xInt = (int) Math.round(x);
1253 g.drawLine(xInt, 15, xInt, this.getHeight());
1254 g.drawString(label, (int) Math.round(x - 0.5 * labelWidth), 11);
1255 }
1256 }
1257 x = x + gridSizePixelsX;
1258 }
1259
1260
1261 mod = this.getExtent().getMinY() % this.gridSizeY;
1262 double y = this.getSize().getHeight() + (mod / scaleY);
1263 while (y > 15)
1264 {
1265 Point2d point = this.getRenderableScale().getWorldCoordinates(new Point2D.Double(0, y), this.getExtent(),
1266 this.getSize());
1267 if (point != null)
1268 {
1269 String label = this.formatter.format(Math.round(point.getY() / this.gridSizeY) * this.gridSizeY);
1270 RectangularShape labelBounds = this.getFontMetrics(this.getFont()).getStringBounds(label, g);
1271 int yInt = (int) Math.round(y);
1272 g.drawLine((int) Math.round(labelBounds.getWidth() + 4), yInt, this.getWidth(), yInt);
1273 g.drawString(label, 2, (int) Math.round(y + labelBounds.getHeight() * 0.3));
1274 }
1275 y = y - gridSizePixelsY;
1276 }
1277 }
1278
1279 @Override
1280 public String toString()
1281 {
1282 return "OtsAnimationPanel [network=" + this.network + "]";
1283 }
1284 }
1285
1286
1287
1288
1289
1290 public enum DemoPanelPosition
1291 {
1292
1293
1294 TOP("First"),
1295
1296
1297 BOTTOM("Last"),
1298
1299
1300 LEFT("Before"),
1301
1302
1303 RIGHT("After");
1304
1305
1306 private final String direction;
1307
1308
1309
1310
1311
1312 DemoPanelPosition(final String direction)
1313 {
1314 this.direction = direction;
1315 }
1316
1317
1318
1319
1320
1321 public String getBorderLayoutPosition()
1322 {
1323 return this.direction;
1324 }
1325
1326 }
1327
1328 }