1 package org.opentrafficsim.simulationengine;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Container;
6 import java.awt.Dimension;
7 import java.awt.Font;
8 import java.awt.Frame;
9 import java.awt.Rectangle;
10 import java.awt.event.ContainerEvent;
11 import java.awt.event.ContainerListener;
12 import java.awt.event.MouseAdapter;
13 import java.awt.event.MouseEvent;
14 import java.awt.event.WindowAdapter;
15 import java.awt.event.WindowEvent;
16 import java.awt.geom.Rectangle2D;
17 import java.io.File;
18 import java.io.FileReader;
19 import java.io.FileWriter;
20 import java.io.IOException;
21 import java.io.Serializable;
22 import java.rmi.RemoteException;
23 import java.util.ArrayList;
24 import java.util.Dictionary;
25 import java.util.Enumeration;
26 import java.util.List;
27 import java.util.Properties;
28
29 import javax.naming.NamingException;
30 import javax.swing.BoxLayout;
31 import javax.swing.ButtonGroup;
32 import javax.swing.JCheckBoxMenuItem;
33 import javax.swing.JComponent;
34 import javax.swing.JFrame;
35 import javax.swing.JLabel;
36 import javax.swing.JMenu;
37 import javax.swing.JMenuItem;
38 import javax.swing.JPanel;
39 import javax.swing.JPopupMenu;
40 import javax.swing.JSlider;
41 import javax.swing.MenuElement;
42 import javax.swing.MenuSelectionManager;
43 import javax.swing.SwingUtilities;
44 import javax.swing.UIManager;
45 import javax.swing.WindowConstants;
46 import javax.swing.border.EmptyBorder;
47 import javax.swing.event.ChangeEvent;
48 import javax.swing.event.ChangeListener;
49 import javax.vecmath.Point3d;
50
51 import org.djunits.value.vdouble.scalar.Duration;
52 import org.djunits.value.vdouble.scalar.Time;
53 import org.opentrafficsim.base.modelproperties.Property;
54 import org.opentrafficsim.base.modelproperties.PropertyException;
55 import org.opentrafficsim.core.dsol.OTSModelInterface;
56 import org.opentrafficsim.core.gtu.Try;
57 import org.opentrafficsim.core.gtu.animation.DefaultSwitchableGTUColorer;
58 import org.opentrafficsim.core.gtu.animation.GTUColorer;
59 import org.opentrafficsim.core.network.Link;
60 import org.opentrafficsim.core.network.NetworkException;
61 import org.opentrafficsim.core.network.OTSNetwork;
62 import org.opentrafficsim.gui.Appearance;
63 import org.opentrafficsim.gui.AppearanceControl;
64 import org.opentrafficsim.gui.OTSAnimationPanel;
65 import org.opentrafficsim.gui.SimulatorFrame;
66
67 import nl.tudelft.simulation.dsol.SimRuntimeException;
68 import nl.tudelft.simulation.dsol.animation.Locatable;
69 import nl.tudelft.simulation.dsol.animation.D2.AnimationPanel;
70 import nl.tudelft.simulation.dsol.animation.D2.GisRenderable2D;
71 import nl.tudelft.simulation.language.d3.BoundingBox;
72 import nl.tudelft.simulation.language.d3.DirectedPoint;
73
74
75
76
77
78
79
80
81
82
83
84 public abstract class AbstractWrappableAnimation implements WrappableAnimation, Serializable
85 {
86
87 private static final long serialVersionUID = 20150000L;
88
89
90 @SuppressWarnings("checkstyle:visibilitymodifier")
91 protected List<Property<?>> properties = new ArrayList<>();
92
93
94 @SuppressWarnings("checkstyle:visibilitymodifier")
95 protected List<Property<?>> savedUserModifiedProperties;
96
97
98 protected Properties frameProperties;
99
100
101 @SuppressWarnings("checkstyle:visibilitymodifier")
102 protected boolean exitOnClose;
103
104
105 @SuppressWarnings("checkstyle:visibilitymodifier")
106 protected OTSAnimationPanel panel;
107
108
109 private Time savedStartTime;
110
111
112 private Duration savedWarmupPeriod;
113
114
115 private Duration savedRunLength;
116
117
118 private OTSModelInterface model;
119
120
121 private Integer replication = null;
122
123
124 private Appearance appearance = Appearance.GRAY;
125
126
127 private GTUColorer colorer = new DefaultSwitchableGTUColorer();
128
129
130
131
132
133
134
135
136
137
138
139
140 @SuppressWarnings("checkstyle:designforextension")
141 protected SimpleAnimator buildSimpleAnimator(final Time startTime, final Duration warmupPeriod, final Duration runLength,
142 final OTSModelInterface otsModel) throws SimRuntimeException, NamingException, PropertyException
143 {
144 return new SimpleAnimator(startTime, warmupPeriod, runLength, otsModel);
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158
159 @SuppressWarnings("checkstyle:designforextension")
160 protected SimpleAnimator buildSimpleAnimator(final Time startTime, final Duration warmupPeriod, final Duration runLength,
161 final OTSModelInterface otsModel, final int replicationNumber)
162 throws SimRuntimeException, NamingException, PropertyException
163 {
164 return new SimpleAnimator(startTime, warmupPeriod, runLength, otsModel, replicationNumber);
165 }
166
167
168 @Override
169 @SuppressWarnings("checkstyle:designforextension")
170 public SimpleAnimator buildAnimator(final Time startTime, final Duration warmupPeriod, final Duration runLength,
171 final List<Property<?>> userModifiedProperties, final Rectangle rect, final boolean eoc)
172 throws SimRuntimeException, NamingException, OTSSimulationException, PropertyException
173 {
174
175 this.savedUserModifiedProperties = userModifiedProperties;
176 this.exitOnClose = eoc;
177 this.savedStartTime = startTime;
178 this.savedWarmupPeriod = warmupPeriod;
179 this.savedRunLength = runLength;
180 this.model = makeModel();
181 if (null == this.model)
182 {
183 return null;
184 }
185
186
187 final SimpleAnimator simulator =
188 null == this.replication ? buildSimpleAnimator(startTime, warmupPeriod, runLength, this.model)
189 : buildSimpleAnimator(startTime, warmupPeriod, runLength, this.model, this.replication);
190 try
191 {
192 this.panel = new OTSAnimationPanel(makeAnimationRectangle(), new Dimension(1024, 768), simulator, this,
193 getColorer(), this.model.getNetwork());
194 }
195 catch (RemoteException exception)
196 {
197 throw new SimRuntimeException(exception);
198 }
199
200
201 addAnimationToggles();
202 addTabs(simulator);
203
204
205 SimulatorFrame frame = new SimulatorFrame(shortName(), this.panel);
206 if (rect != null)
207 {
208 frame.setBounds(rect);
209 }
210 else
211 {
212 frame.setExtendedState(Frame.MAXIMIZED_BOTH);
213 }
214 frame.setDefaultCloseOperation(this.exitOnClose ? WindowConstants.EXIT_ON_CLOSE : WindowConstants.DISPOSE_ON_CLOSE);
215
216
217
218
219
220
221 String sep = System.getProperty("file.separator");
222 String propertiesFile = System.getProperty("user.home") + sep + "OTS" + sep + "properties.ini";
223 frame.addWindowListener(new WindowAdapter()
224 {
225
226 @Override
227 public void windowClosing(final WindowEvent windowEvent)
228 {
229 try
230 {
231 File f = new File(propertiesFile);
232 f.getParentFile().mkdirs();
233 FileWriter writer = new FileWriter(f);
234 AbstractWrappableAnimation.this.frameProperties.store(writer, "OTS user settings");
235 }
236 catch (@SuppressWarnings("unused") IOException exception)
237 {
238 System.err.println("Could not store properties at " + propertiesFile + ".");
239 }
240 }
241 });
242
243
244 Properties defaults = new Properties();
245 defaults.setProperty("Appearance", "GRAY");
246 defaults.setProperty("LookAndFeel", "javax.swing.plaf.metal.MetalLookAndFeel");
247 this.frameProperties = new Properties(defaults);
248 try
249 {
250 FileReader reader = new FileReader(propertiesFile);
251 this.frameProperties.load(reader);
252 }
253 catch (@SuppressWarnings("unused") IOException ioe)
254 {
255
256 }
257 this.appearance = Appearance.valueOf(this.frameProperties.getProperty("Appearance").toUpperCase());
258
259
260 class AppearanceControlMenu extends JMenu implements AppearanceControl
261 {
262
263 private static final long serialVersionUID = 20180206L;
264
265
266
267
268
269 AppearanceControlMenu(final String string)
270 {
271 super(string);
272 }
273
274
275 @Override
276 public boolean isFont()
277 {
278 return true;
279 }
280
281
282 @Override
283 public String toString()
284 {
285 return "AppearanceControlMenu []";
286 }
287 }
288
289
290 JMenu laf = new AppearanceControlMenu("Look and feel");
291 laf.addMouseListener(new SubMenuShower(laf));
292 ButtonGroup lafGroup = new ButtonGroup();
293 lafGroup.add(addLookAndFeel(frame, laf, "javax.swing.plaf.metal.MetalLookAndFeel", "Metal"));
294 lafGroup.add(addLookAndFeel(frame, laf, "com.sun.java.swing.plaf.motif.MotifLookAndFeel", "Motif"));
295 lafGroup.add(addLookAndFeel(frame, laf, "javax.swing.plaf.nimbus.NimbusLookAndFeel", "Nimbus"));
296 lafGroup.add(addLookAndFeel(frame, laf, "com.sun.java.swing.plaf.windows.WindowsLookAndFeel", "Windows"));
297 lafGroup.add(
298 addLookAndFeel(frame, laf, "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel", "Windows classic"));
299 lafGroup.add(addLookAndFeel(frame, laf, UIManager.getSystemLookAndFeelClassName(), "System default"));
300
301
302 JMenu app = new AppearanceControlMenu("Appearance");
303 app.addMouseListener(new SubMenuShower(app));
304 ButtonGroup appGroup = new ButtonGroup();
305 for (Appearance appearanceValue : Appearance.values())
306 {
307 appGroup.add(addAppearance(app, appearanceValue));
308 }
309
310
311 class AppearanceControlPopupMenu extends JPopupMenu implements AppearanceControl
312 {
313
314 private static final long serialVersionUID = 20180206L;
315
316
317 @Override
318 public boolean isFont()
319 {
320 return true;
321 }
322
323
324 @Override
325 public String toString()
326 {
327 return "AppearanceControlPopupMenu []";
328 }
329 }
330
331
332 JPopupMenu popMenu = new AppearanceControlPopupMenu();
333 popMenu.add(laf);
334 popMenu.add(app);
335 this.getPanel().getOtsControlPanel().setComponentPopupMenu(popMenu);
336
337
338 setAppearance(getAppearance());
339 Try.execute(() -> UIManager.setLookAndFeel(this.frameProperties.getProperty("LookAndFeel")),
340 "Could not set look-and-feel %s", laf);
341 SwingUtilities.invokeLater(() -> SwingUtilities.updateComponentTreeUI(frame));
342
343
344 this.demoPanel = null;
345 setupDemo(this, this.model.getNetwork());
346
347 return simulator;
348 }
349
350
351
352
353
354
355
356
357
358 private JCheckBoxMenuItem addLookAndFeel(final JFrame frame, final JMenu group, final String laf, final String name)
359 {
360 boolean checked = this.frameProperties.getProperty("LookAndFeel").equals(laf);
361 JCheckBoxMenuItem check = new StayOpenCheckBoxMenuItem(name, checked);
362 check.addMouseListener(new MouseAdapter()
363 {
364
365 @Override
366 public void mouseClicked(final MouseEvent e)
367 {
368 Try.execute(() -> UIManager.setLookAndFeel(laf), "Could not set look-and-feel %s", laf);
369 SwingUtilities.updateComponentTreeUI(frame);
370 AbstractWrappableAnimation.this.frameProperties.setProperty("LookAndFeel", laf);
371 }
372 });
373 group.add(check);
374 return check;
375 }
376
377
378
379
380
381
382
383 private JMenuItem addAppearance(final JMenu group, final Appearance appear)
384 {
385 JCheckBoxMenuItem check = new StayOpenCheckBoxMenuItem(appear.getName(), appear.equals(getAppearance()));
386 check.addMouseListener(new MouseAdapter()
387 {
388
389 @Override
390 public void mouseClicked(final MouseEvent e)
391 {
392 setAppearance(appear);
393 }
394 });
395 return group.add(check);
396 }
397
398
399
400
401
402 public void setAppearance(final Appearance appearance)
403 {
404 this.appearance = appearance;
405 setAppearance(this.panel.getParent(), appearance);
406 this.frameProperties.setProperty("Appearance", appearance.toString());
407 }
408
409
410
411
412
413
414 private void setAppearance(final Component c, final Appearance appear)
415 {
416 if (c instanceof AppearanceControl)
417 {
418 AppearanceControl ac = (AppearanceControl) c;
419 if (ac.isBackground())
420 {
421 c.setBackground(appear.getBackground());
422 }
423 if (ac.isForeground())
424 {
425 c.setForeground(appear.getForeground());
426 }
427 if (ac.isFont())
428 {
429 changeFont(c, appear.getFont());
430 }
431 }
432 else if (c instanceof AnimationPanel)
433 {
434
435 c.setBackground(appear.getBackdrop());
436 c.setForeground(appear.getForeground());
437 changeFont(c, appear.getFont());
438 }
439 else
440 {
441
442 c.setBackground(appear.getBackground());
443 c.setForeground(appear.getForeground());
444 changeFont(c, appear.getFont());
445 }
446 if (c instanceof JSlider)
447 {
448
449 Dictionary<?, ?> dictionary = ((JSlider) c).getLabelTable();
450 Enumeration<?> keys = dictionary.keys();
451 while (keys.hasMoreElements())
452 {
453 JLabel label = (JLabel) dictionary.get(keys.nextElement());
454 label.setForeground(appear.getForeground());
455 label.setBackground(appear.getBackground());
456 }
457 }
458
459 if (c instanceof JComponent)
460 {
461 for (Component child : ((JComponent) c).getComponents())
462 {
463 setAppearance(child, appear);
464 }
465 }
466 }
467
468
469
470
471
472
473 private void changeFont(final Component c, final String font)
474 {
475 Font prev = c.getFont();
476 c.setFont(new Font(font, prev.getStyle(), prev.getSize()));
477 }
478
479
480
481
482
483 public Appearance getAppearance()
484 {
485 return this.appearance;
486 }
487
488
489
490
491
492 @SuppressWarnings("checkstyle:designforextension")
493 public GTUColorer getColorer()
494 {
495 return this.colorer;
496 }
497
498
499
500
501
502
503
504 protected void addTabs(final SimpleSimulatorInterface simulator) throws OTSSimulationException, PropertyException
505 {
506
507 }
508
509
510
511
512 @SuppressWarnings("checkstyle:designforextension")
513 protected void addAnimationToggles()
514 {
515
516 }
517
518
519
520
521
522
523 protected void setupDemo(final AbstractWrappableAnimation animation, final OTSNetwork net)
524 {
525
526 }
527
528
529
530
531
532
533
534
535
536
537
538
539 public final void addToggleAnimationButtonIcon(final String name, final Class<? extends Locatable> locatableClass,
540 final String iconPath, final String toolTipText, final boolean initiallyVisible, final boolean idButton)
541 {
542 this.panel.addToggleAnimationButtonIcon(name, locatableClass, iconPath, toolTipText, initiallyVisible, idButton);
543 }
544
545
546
547
548
549
550
551
552 public final void addToggleAnimationButtonText(final String name, final Class<? extends Locatable> locatableClass,
553 final String toolTipText, final boolean initiallyVisible)
554 {
555 this.panel.addToggleAnimationButtonText(name, locatableClass, toolTipText, initiallyVisible);
556 }
557
558
559
560
561
562 public final void showAnimationClass(final Class<? extends Locatable> locatableClass)
563 {
564 this.panel.getAnimationPanel().showClass(locatableClass);
565 this.panel.updateAnimationClassCheckBox(locatableClass);
566 }
567
568
569
570
571
572 public final void hideAnimationClass(final Class<? extends Locatable> locatableClass)
573 {
574 this.panel.getAnimationPanel().hideClass(locatableClass);
575 this.panel.updateAnimationClassCheckBox(locatableClass);
576 }
577
578
579
580
581
582 public final void toggleAnimationClass(final Class<? extends Locatable> locatableClass)
583 {
584 this.panel.getAnimationPanel().toggleClass(locatableClass);
585 this.panel.updateAnimationClassCheckBox(locatableClass);
586 }
587
588
589
590
591
592
593
594 public final void addToggleGISButtonText(final String header, final GisRenderable2D gisMap, final String toolTipText)
595 {
596 this.panel.addToggleText(" ");
597 this.panel.addToggleText(header);
598 try
599 {
600 for (String layerName : gisMap.getMap().getLayerMap().keySet())
601 {
602 this.panel.addToggleGISButtonText(layerName, layerName, gisMap, toolTipText);
603 }
604 }
605 catch (RemoteException exception)
606 {
607 exception.printStackTrace();
608 }
609 }
610
611
612
613
614
615 public final void showGISLayer(final String layerName)
616 {
617 this.panel.showGISLayer(layerName);
618 }
619
620
621
622
623
624 public final void hideGISLayer(final String layerName)
625 {
626 this.panel.hideGISLayer(layerName);
627 }
628
629
630
631
632
633 public final void toggleGISLayer(final String layerName)
634 {
635 this.panel.toggleGISLayer(layerName);
636 }
637
638
639
640
641
642 protected abstract OTSModelInterface makeModel() throws OTSSimulationException;
643
644
645
646
647
648
649 @SuppressWarnings("checkstyle:designforextension")
650 protected Rectangle2D makeAnimationRectangle()
651 {
652 double minX = Double.MAX_VALUE;
653 double maxX = -Double.MAX_VALUE;
654 double minY = Double.MAX_VALUE;
655 double maxY = -Double.MAX_VALUE;
656 Point3d p3dL = new Point3d();
657 Point3d p3dU = new Point3d();
658 try
659 {
660 for (Link link : this.model.getNetwork().getLinkMap().values())
661 {
662 DirectedPoint l = link.getLocation();
663 BoundingBox b = new BoundingBox(link.getBounds());
664 b.getLower(p3dL);
665 b.getUpper(p3dU);
666 minX = Math.min(minX, l.x + Math.min(p3dL.x, p3dU.x));
667 minY = Math.min(minY, l.y + Math.min(p3dL.y, p3dU.y));
668 maxX = Math.max(maxX, l.x + Math.max(p3dL.x, p3dU.x));
669 maxY = Math.max(maxY, l.y + Math.max(p3dL.y, p3dU.y));
670 }
671 }
672 catch (@SuppressWarnings("unused") Exception e)
673 {
674
675 }
676 double relativeMargin = 0.05;
677 double xMargin = relativeMargin * (maxX - minX);
678 double yMargin = relativeMargin * (maxY - minY);
679 minX = minX - xMargin;
680 minY = minY - yMargin;
681 maxX = maxX + xMargin;
682 maxY = maxY + yMargin;
683
684 return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
685 }
686
687
688 @Override
689 public final ArrayList<Property<?>> getProperties()
690 {
691 return new ArrayList<>(this.properties);
692 }
693
694
695 @Override
696 public final SimpleSimulatorInterface rebuildSimulator(final Rectangle rect)
697 throws SimRuntimeException, NetworkException, NamingException, OTSSimulationException, PropertyException
698 {
699 return buildAnimator(this.savedStartTime, this.savedWarmupPeriod, this.savedRunLength, this.savedUserModifiedProperties,
700 rect, this.exitOnClose);
701 }
702
703
704 @Override
705 public final List<Property<?>> getUserModifiedProperties()
706 {
707 return this.savedUserModifiedProperties;
708 }
709
710
711 @Override
712 @SuppressWarnings("checkstyle:designforextension")
713 public void stopTimersThreads()
714 {
715 if (this.panel != null && this.panel.getStatusBar() != null)
716 {
717 this.panel.getStatusBar().cancelTimer();
718 }
719 this.panel = null;
720 }
721
722
723
724
725 public final OTSAnimationPanel getPanel()
726 {
727 return this.panel;
728 }
729
730
731
732
733
734
735
736
737 public final void addTab(final int index, final String caption, final Container container)
738 {
739 this.panel.getTabbedPane().addTab(index, caption, container);
740 }
741
742
743
744
745
746
747 public final int getTabCount()
748 {
749 return this.panel.getTabbedPane().getTabCount();
750 }
751
752
753 @Override
754 public final void setNextReplication(final Integer nextReplication)
755 {
756 this.replication = nextReplication;
757 }
758
759
760
761
762 private JPanel demoPanel;
763
764
765
766
767
768 public JPanel getDemoPanel()
769 {
770 if (this.demoPanel == null)
771 {
772 this.demoPanel = new JPanel();
773 this.demoPanel.setLayout(new BoxLayout(this.demoPanel, BoxLayout.Y_AXIS));
774 this.demoPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
775 this.demoPanel.setPreferredSize(new Dimension(300, 300));
776 this.getPanel().getAnimationPanel().getParent().add(this.demoPanel, BorderLayout.EAST);
777 this.demoPanel.addContainerListener(new ContainerListener()
778 {
779 @Override
780 public void componentAdded(final ContainerEvent e)
781 {
782 try
783 {
784 setAppearance(getAppearance());
785 }
786 catch (@SuppressWarnings("unused") NullPointerException exception)
787 {
788
789 }
790 }
791
792 @Override
793 public void componentRemoved(final ContainerEvent e)
794 {
795
796 }
797 });
798 }
799 return this.demoPanel;
800 }
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815 private class SubMenuShower extends MouseAdapter
816 {
817
818 private JMenu menu;
819
820
821
822
823
824 SubMenuShower(final JMenu menu)
825 {
826 this.menu = menu;
827 }
828
829
830 @Override
831 public void mouseEntered(final MouseEvent e)
832 {
833 MenuSelectionManager.defaultManager().setSelectedPath(
834 new MenuElement[] { (MenuElement) this.menu.getParent(), this.menu, this.menu.getPopupMenu() });
835 }
836
837
838 @Override
839 public String toString()
840 {
841 return "SubMenuShower [menu=" + this.menu + "]";
842 }
843 }
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858 private static class StayOpenCheckBoxMenuItem extends JCheckBoxMenuItem implements AppearanceControl
859 {
860
861 private static final long serialVersionUID = 20180206L;
862
863
864 private static MenuElement[] path;
865
866 {
867 getModel().addChangeListener(new ChangeListener()
868 {
869
870 @Override
871 public void stateChanged(final ChangeEvent e)
872 {
873 if (getModel().isArmed() && isShowing())
874 {
875 setPath(MenuSelectionManager.defaultManager().getSelectedPath());
876 }
877 }
878 });
879 }
880
881
882
883
884
885 public static void setPath(final MenuElement[] path)
886 {
887 StayOpenCheckBoxMenuItem.path = path;
888 }
889
890
891
892
893
894
895 StayOpenCheckBoxMenuItem(final String text, final boolean selected)
896 {
897 super(text, selected);
898 }
899
900
901 @Override
902 public void doClick(final int pressTime)
903 {
904 super.doClick(pressTime);
905 for (MenuElement element : path)
906 {
907 if (element instanceof JComponent)
908 {
909 ((JComponent) element).setVisible(true);
910 }
911 }
912 JMenu menu = (JMenu) path[path.length - 3];
913 MenuSelectionManager.defaultManager()
914 .setSelectedPath(new MenuElement[] { (MenuElement) menu.getParent(), menu, menu.getPopupMenu() });
915 }
916
917
918 @Override
919 public boolean isFont()
920 {
921 return true;
922 }
923
924
925 @Override
926 public String toString()
927 {
928 return "StayOpenCheckBoxMenuItem []";
929 }
930 }
931
932 }