View Javadoc
1   package org.opentrafficsim.gui;
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.Graphics;
8   import java.awt.event.ActionEvent;
9   import java.awt.event.ActionListener;
10  import java.awt.event.MouseAdapter;
11  import java.awt.event.MouseEvent;
12  import java.awt.event.MouseListener;
13  import java.awt.event.WindowEvent;
14  import java.awt.event.WindowListener;
15  import java.awt.geom.Point2D;
16  import java.awt.geom.Rectangle2D;
17  import java.lang.reflect.Field;
18  import java.rmi.RemoteException;
19  import java.text.NumberFormat;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.swing.BoxLayout;
26  import javax.swing.Icon;
27  import javax.swing.JButton;
28  import javax.swing.JCheckBox;
29  import javax.swing.JFrame;
30  import javax.swing.JLabel;
31  import javax.swing.JPanel;
32  import javax.swing.JTextField;
33  import javax.swing.JToggleButton;
34  
35  import org.opentrafficsim.base.modelproperties.PropertyException;
36  import org.opentrafficsim.core.gtu.GTU;
37  import org.opentrafficsim.core.gtu.Try;
38  import org.opentrafficsim.core.gtu.animation.GTUColorer;
39  import org.opentrafficsim.core.network.Network;
40  import org.opentrafficsim.core.network.OTSNetwork;
41  import org.opentrafficsim.simulationengine.SimpleAnimator;
42  import org.opentrafficsim.simulationengine.WrappableAnimation;
43  
44  import nl.javel.gisbeans.map.MapInterface;
45  import nl.tudelft.simulation.dsol.animation.Locatable;
46  import nl.tudelft.simulation.dsol.animation.D2.AnimationPanel;
47  import nl.tudelft.simulation.dsol.animation.D2.GisRenderable2D;
48  import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface;
49  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
50  import nl.tudelft.simulation.event.Event;
51  import nl.tudelft.simulation.event.EventInterface;
52  import nl.tudelft.simulation.event.EventListenerInterface;
53  import nl.tudelft.simulation.language.d3.DirectedPoint;
54  import nl.tudelft.simulation.language.reflection.ClassUtil;
55  
56  /**
57   * Animation panel with various controls.
58   * <p>
59   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
60   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
61   * <p>
62   * $LastChangedDate: 2018-09-19 13:55:45 +0200 (Wed, 19 Sep 2018) $, @version $Revision: 4006 $, by $Author: averbraeck $,
63   * initial version Jun 18, 2015 <br>
64   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
65   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
66   */
67  public class OTSAnimationPanel extends OTSSimulationPanel implements ActionListener, WindowListener, EventListenerInterface
68  {
69      /** */
70      private static final long serialVersionUID = 20150617L;
71  
72      /** The animation panel on tab position 0. */
73      private final AutoAnimationPanel animationPanel;
74  
75      /** Border panel in which the animation is shown. */
76      private final JPanel borderPanel;
77  
78      /** Toggle panel with which animation features can be shown/hidden. */
79      private final JPanel togglePanel;
80  
81      /** Map of toggle names to toggle animation classes. */
82      private Map<String, Class<? extends Locatable>> toggleLocatableMap = new HashMap<>();
83  
84      /** Set of animation classes to toggle buttons. */
85      private Map<Class<? extends Locatable>, JToggleButton> toggleButtons = new HashMap<>();
86  
87      /** Set of GIS layer names to toggle GIS layers . */
88      private Map<String, MapInterface> toggleGISMap = new HashMap<>();
89  
90      /** Set of GIS layer names to toggle buttons. */
91      private Map<String, JToggleButton> toggleGISButtons = new HashMap<>();
92  
93      /** The switchableGTUColorer used to color the GTUs. */
94      private GTUColorer gtuColorer = null;
95  
96      /** The ColorControlPanel that allows the user to operate the SwitchableGTUColorer. */
97      private ColorControlPanel colorControlPanel = null;
98  
99      /** The coordinates of the cursor. */
100     private final JLabel coordinateField;
101 
102     /** The GTU count field. */
103     private final JLabel gtuCountField;
104 
105     /** The GTU count. */
106     private int gtuCount = 0;
107 
108     /** The animation buttons. */
109     private final ArrayList<JButton> buttons = new ArrayList<>();
110 
111     /** The formatter for the world coordinates. */
112     private static final NumberFormat FORMATTER = NumberFormat.getInstance();
113 
114     /** Has the window close handler been registered? */
115     @SuppressWarnings("checkstyle:visibilitymodifier")
116     protected boolean closeHandlerRegistered = false;
117 
118     /** Indicate the window has been closed and the timer thread can stop. */
119     @SuppressWarnings("checkstyle:visibilitymodifier")
120     protected boolean windowExited = false;
121 
122     /** Autopan. */
123     private boolean autoPan = false;
124 
125     /** Autopan toggle. */
126     private final JCheckBox autoPanToggle;
127 
128     /** Autopan Id text field. */
129     private final JTextField autoPanField;
130 
131     /** Initialize the formatter. */
132     static
133     {
134         FORMATTER.setMaximumFractionDigits(3);
135     }
136 
137     /**
138      * Construct a panel that looks like the DSOLPanel for quick building of OTS applications.
139      * @param extent Rectangle2D; bottom left corner, length and width of the area (world) to animate.
140      * @param size the size to be used for the animation.
141      * @param simulator the simulator or animator of the model.
142      * @param wrappableAnimation the builder and rebuilder of the simulation, based on properties.
143      * @param gtuColorer the colorer to use for the GTUs.
144      * @param network OTSNetwork; network
145      * @throws RemoteException when notification of the animation panel fails
146      * @throws PropertyException when one of the user modified properties has the empty string as key
147      */
148     public OTSAnimationPanel(final Rectangle2D extent, final Dimension size, final SimpleAnimator simulator,
149             final WrappableAnimation wrappableAnimation, final GTUColorer gtuColorer, final OTSNetwork network)
150             throws RemoteException, PropertyException
151     {
152         super(simulator, wrappableAnimation);
153 
154         // Add the animation panel as a tab.
155 
156         this.animationPanel = new AutoAnimationPanel(extent, size, simulator, network);
157         this.animationPanel.showGrid(false);
158         this.borderPanel = new JPanel(new BorderLayout());
159         this.borderPanel.add(this.animationPanel, BorderLayout.CENTER);
160         getTabbedPane().addTab(0, "animation", this.borderPanel);
161         getTabbedPane().setSelectedIndex(0); // Show the animation panel as the default tab
162 
163         // Include the GTU colorer control panel NORTH of the animation.
164         this.gtuColorer = gtuColorer;
165         this.colorControlPanel = new ColorControlPanel(this.gtuColorer);
166         JPanel buttonPanel = new JPanel();
167         buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
168         this.borderPanel.add(buttonPanel, BorderLayout.NORTH);
169         buttonPanel.add(this.colorControlPanel);
170 
171         // Include the TogglePanel WEST of the animation.
172         this.togglePanel = new JPanel();
173         this.togglePanel.setLayout(new BoxLayout(this.togglePanel, BoxLayout.Y_AXIS));
174         this.borderPanel.add(this.togglePanel, BorderLayout.WEST);
175 
176         // add the buttons for home, zoom all, grid, and mouse coordinates
177         buttonPanel.add(new JLabel("   "));
178         buttonPanel.add(makeButton("allButton", "/Expand.png", "ZoomAll", "Zoom whole network", true));
179         buttonPanel.add(makeButton("homeButton", "/Home.png", "Home", "Zoom to original extent", true));
180         buttonPanel.add(makeButton("gridButton", "/Grid.png", "Grid", "Toggle grid on/off", true));
181         buttonPanel.add(new JLabel("   "));
182 
183         // add info labels next to buttons
184         JPanel infoTextPanel = new JPanel();
185         buttonPanel.add(infoTextPanel);
186         infoTextPanel.setMinimumSize(new Dimension(250, 20));
187         infoTextPanel.setPreferredSize(new Dimension(250, 20));
188         infoTextPanel.setLayout(new BoxLayout(infoTextPanel, BoxLayout.Y_AXIS));
189         this.coordinateField = new JLabel("Mouse: ");
190         this.coordinateField.setMinimumSize(new Dimension(250, 10));
191         this.coordinateField.setPreferredSize(new Dimension(250, 10));
192         infoTextPanel.add(this.coordinateField);
193         // gtu fields
194         JPanel gtuPanel = new JPanel();
195         gtuPanel.setAlignmentX(0.0f);
196         gtuPanel.setLayout(new BoxLayout(gtuPanel, BoxLayout.X_AXIS));
197         gtuPanel.setMinimumSize(new Dimension(250, 10));
198         gtuPanel.setPreferredSize(new Dimension(250, 10));
199         infoTextPanel.add(gtuPanel);
200         if (null != network)
201         {
202             network.addListener(this, Network.GTU_ADD_EVENT);
203             network.addListener(this, Network.GTU_REMOVE_EVENT);
204         }
205         // gtu follow
206         /** Text field with appearance control. */
207         class AppearanceTextField extends JTextField implements AppearanceControl
208         {
209             private static final long serialVersionUID = 20180430L;
210 
211             /** {@inheritDoc} */
212             @Override
213             public String toString()
214             {
215                 return "AppearanceTextField []";
216             }
217         }
218         this.autoPanField = new AppearanceTextField();
219         this.autoPanField.setMaximumSize(new Dimension(100, 20));
220         this.autoPanField.setVisible(false);
221         this.autoPanField.addActionListener(new ActionListener()
222         {
223             @SuppressWarnings("synthetic-access")
224             @Override
225             public void actionPerformed(final ActionEvent e)
226             {
227                 OTSAnimationPanel.this.animationPanel.repaint();
228             }
229         });
230         this.autoPanToggle = new JCheckBox();
231         this.autoPanToggle.setToolTipText("Pan to GTU");
232         this.autoPanToggle.addActionListener(new ActionListener()
233         {
234             @SuppressWarnings("synthetic-access")
235             @Override
236             public void actionPerformed(final ActionEvent e)
237             {
238                 OTSAnimationPanel.this.autoPan = !OTSAnimationPanel.this.autoPanField.isVisible();
239                 OTSAnimationPanel.this.autoPanField.setVisible(OTSAnimationPanel.this.autoPan);
240                 if (OTSAnimationPanel.this.autoPan)
241                 {
242                     OTSAnimationPanel.this.autoPanField.requestFocusInWindow();
243                     OTSAnimationPanel.this.autoPanField.selectAll();
244                 }
245                 gtuPanel.revalidate();
246             }
247         });
248         gtuPanel.add(this.autoPanToggle);
249         gtuPanel.add(this.autoPanField);
250         // gtu counter
251         this.gtuCountField = new JLabel("0 GTU's");
252         this.gtuCount = null == network ? 0 : network.getGTUs().size();
253         gtuPanel.add(this.gtuCountField);
254         setGtuCountText();
255 
256         // Tell the animation to build the list of animation objects.
257         this.animationPanel.notify(new Event(SimulatorInterface.START_REPLICATION_EVENT, simulator, null));
258 
259         // switch off the X and Y coordinates in a tooltip.
260         this.animationPanel.setShowToolTip(false);
261 
262         // run the update task for the mouse coordinate panel
263         new UpdateTimer().start();
264 
265         // make sure the thread gets killed when the window closes.
266         installWindowCloseHandler();
267     }
268 
269     /**
270      * Create a button.
271      * @param name String; name of the button
272      * @param iconPath String; path to the resource
273      * @param actionCommand String; the action command
274      * @param toolTipText String; the hint to show when the mouse hovers over the button
275      * @param enabled boolean; true if the new button must initially be enable; false if it must initially be disabled
276      * @return JButton
277      */
278     private JButton makeButton(final String name, final String iconPath, final String actionCommand, final String toolTipText,
279             final boolean enabled)
280     {
281         // JButton result = new JButton(new ImageIcon(this.getClass().getResource(iconPath)));
282         JButton result = new JButton(OTSControlPanel.loadIcon(iconPath));
283         result.setPreferredSize(new Dimension(34, 32));
284         result.setName(name);
285         result.setEnabled(enabled);
286         result.setActionCommand(actionCommand);
287         result.setToolTipText(toolTipText);
288         result.addActionListener(this);
289         this.buttons.add(result);
290         return result;
291     }
292 
293     /**
294      * Add a button for toggling an animatable class on or off. Button icons for which 'idButton' is true will be placed to the
295      * right of the previous button, which should be the corresponding button without the id. An example is an icon for
296      * showing/hiding the class 'Lane' followed by the button to show/hide the Lane ids.
297      * @param name the name of the button
298      * @param locatableClass the class for which the button holds (e.g., GTU.class)
299      * @param iconPath the path to the 24x24 icon to display
300      * @param toolTipText the tool tip text to show when hovering over the button
301      * @param initiallyVisible whether the class is initially shown or not
302      * @param idButton id button that needs to be placed next to the previous button
303      */
304     public final void addToggleAnimationButtonIcon(final String name, final Class<? extends Locatable> locatableClass,
305             final String iconPath, final String toolTipText, final boolean initiallyVisible, final boolean idButton)
306     {
307         JToggleButton button;
308         Icon icon = OTSControlPanel.loadIcon(iconPath);
309         Icon unIcon = OTSControlPanel.loadGrayscaleIcon(iconPath);
310         button = new JCheckBox();
311         button.setSelectedIcon(icon);
312         button.setIcon(unIcon);
313         button.setPreferredSize(new Dimension(32, 28));
314         button.setName(name);
315         button.setEnabled(true);
316         button.setSelected(initiallyVisible);
317         button.setActionCommand(name);
318         button.setToolTipText(toolTipText);
319         button.addActionListener(this);
320 
321         // place an Id button to the right of the corresponding content button
322         if (idButton && this.togglePanel.getComponentCount() > 0)
323         {
324             JPanel lastToggleBox = (JPanel) this.togglePanel.getComponent(this.togglePanel.getComponentCount() - 1);
325             lastToggleBox.add(button);
326         }
327         else
328         {
329             JPanel toggleBox = new JPanel();
330             toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
331             toggleBox.add(button);
332             this.togglePanel.add(toggleBox);
333             toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
334         }
335 
336         if (initiallyVisible)
337         {
338             this.animationPanel.showClass(locatableClass);
339         }
340         else
341         {
342             this.animationPanel.hideClass(locatableClass);
343         }
344         this.toggleLocatableMap.put(name, locatableClass);
345         this.toggleButtons.put(locatableClass, button);
346     }
347 
348     /**
349      * Add a button for toggling an animatable class on or off.
350      * @param name the name of the button
351      * @param locatableClass the class for which the button holds (e.g., GTU.class)
352      * @param toolTipText the tool tip text to show when hovering over the button
353      * @param initiallyVisible whether the class is initially shown or not
354      */
355     public final void addToggleAnimationButtonText(final String name, final Class<? extends Locatable> locatableClass,
356             final String toolTipText, final boolean initiallyVisible)
357     {
358         JToggleButton button;
359         button = new JCheckBox(name);
360         button.setName(name);
361         button.setEnabled(true);
362         button.setSelected(initiallyVisible);
363         button.setActionCommand(name);
364         button.setToolTipText(toolTipText);
365         button.addActionListener(this);
366 
367         JPanel toggleBox = new JPanel();
368         toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
369         toggleBox.add(button);
370         this.togglePanel.add(toggleBox);
371         toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
372 
373         if (initiallyVisible)
374         {
375             this.animationPanel.showClass(locatableClass);
376         }
377         else
378         {
379             this.animationPanel.hideClass(locatableClass);
380         }
381         this.toggleLocatableMap.put(name, locatableClass);
382         this.toggleButtons.put(locatableClass, button);
383     }
384 
385     /**
386      * Add a text to explain animatable classes.
387      * @param text the text to show
388      */
389     public final void addToggleText(final String text)
390     {
391         JPanel textBox = new JPanel();
392         textBox.setLayout(new BoxLayout(textBox, BoxLayout.X_AXIS));
393         textBox.add(new JLabel(text));
394         this.togglePanel.add(textBox);
395         textBox.setAlignmentX(Component.LEFT_ALIGNMENT);
396     }
397 
398     /**
399      * Add a button to toggle a GIS Layer on or off.
400      * @param layerName the name of the layer
401      * @param displayName the name to display next to the tick box
402      * @param gisMap the map
403      * @param toolTipText the tool tip text
404      */
405     public final void addToggleGISButtonText(final String layerName, final String displayName, final GisRenderable2D gisMap,
406             final String toolTipText)
407     {
408         JToggleButton button;
409         button = new JCheckBox(displayName);
410         button.setName(layerName);
411         button.setEnabled(true);
412         button.setSelected(true);
413         button.setActionCommand(layerName);
414         button.setToolTipText(toolTipText);
415         button.addActionListener(this);
416 
417         JPanel toggleBox = new JPanel();
418         toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
419         toggleBox.add(button);
420         this.togglePanel.add(toggleBox);
421         toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
422 
423         this.toggleGISMap.put(layerName, gisMap.getMap());
424         this.toggleGISButtons.put(layerName, button);
425     }
426 
427     /**
428      * Set a GIS layer to be shown in the animation to true.
429      * @param layerName the name of the GIS-layer that has to be shown.
430      */
431     public final void showGISLayer(final String layerName)
432     {
433         MapInterface gisMap = this.toggleGISMap.get(layerName);
434         if (gisMap != null)
435         {
436             try
437             {
438                 gisMap.showLayer(layerName);
439                 this.toggleGISButtons.get(layerName).setSelected(true);
440                 this.animationPanel.repaint();
441             }
442             catch (RemoteException exception)
443             {
444                 exception.printStackTrace();
445             }
446         }
447     }
448 
449     /**
450      * Set a GIS layer to be hidden in the animation to true.
451      * @param layerName the name of the GIS-layer that has to be hidden.
452      */
453     public final void hideGISLayer(final String layerName)
454     {
455         MapInterface gisMap = this.toggleGISMap.get(layerName);
456         if (gisMap != null)
457         {
458             try
459             {
460                 gisMap.hideLayer(layerName);
461                 this.toggleGISButtons.get(layerName).setSelected(false);
462                 this.animationPanel.repaint();
463             }
464             catch (RemoteException exception)
465             {
466                 exception.printStackTrace();
467             }
468         }
469     }
470 
471     /**
472      * Toggle a GIS layer to be displayed in the animation to its reverse value.
473      * @param layerName the name of the GIS-layer that has to be turned off or vice versa.
474      */
475     public final void toggleGISLayer(final String layerName)
476     {
477         MapInterface gisMap = this.toggleGISMap.get(layerName);
478         if (gisMap != null)
479         {
480             try
481             {
482                 if (gisMap.getVisibleLayers().contains(gisMap.getLayerMap().get(layerName)))
483                 {
484                     gisMap.hideLayer(layerName);
485                     this.toggleGISButtons.get(layerName).setSelected(false);
486                 }
487                 else
488                 {
489                     gisMap.showLayer(layerName);
490                     this.toggleGISButtons.get(layerName).setSelected(true);
491                 }
492                 this.animationPanel.repaint();
493             }
494             catch (RemoteException exception)
495             {
496                 exception.printStackTrace();
497             }
498         }
499     }
500 
501     /** {@inheritDoc} */
502     @Override
503     public final void actionPerformed(final ActionEvent actionEvent)
504     {
505         String actionCommand = actionEvent.getActionCommand();
506         try
507         {
508             if (actionCommand.equals("Home"))
509             {
510                 this.animationPanel.home();
511             }
512             if (actionCommand.equals("ZoomAll"))
513             {
514                 this.animationPanel.zoomAll();
515             }
516             if (actionCommand.equals("Grid"))
517             {
518                 this.animationPanel.showGrid(!this.animationPanel.isShowGrid());
519             }
520 
521             if (this.toggleLocatableMap.containsKey(actionCommand))
522             {
523                 Class<? extends Locatable> locatableClass = this.toggleLocatableMap.get(actionCommand);
524                 this.animationPanel.toggleClass(locatableClass);
525                 this.togglePanel.repaint();
526             }
527 
528             if (this.toggleGISMap.containsKey(actionCommand))
529             {
530                 this.toggleGISLayer(actionCommand);
531                 this.togglePanel.repaint();
532             }
533         }
534         catch (Exception exception)
535         {
536             exception.printStackTrace();
537         }
538     }
539 
540     /**
541      * Easy access to the AnimationPanel.
542      * @return AnimationPanel
543      */
544     public final AnimationPanel getAnimationPanel()
545     {
546         return this.animationPanel;
547     }
548 
549     /**
550      * Update the checkmark related to a programmatically changed animation state.
551      * @param locatableClass Class; class to show the checkmark for
552      */
553     public final void updateAnimationClassCheckBox(final Class<? extends Locatable> locatableClass)
554     {
555         JToggleButton button = this.toggleButtons.get(locatableClass);
556         if (button == null)
557         {
558             return;
559         }
560         // TODO complete hack, but everything is final...
561         Field field = Try.assign(() -> ClassUtil.resolveField(AnimationPanel.class, "visibilityMap"), "No field visibilityMap");
562         field.setAccessible(true);
563         @SuppressWarnings("unchecked")
564         Map<Class<? extends Locatable>, Boolean> map =
565                 Try.assign(() -> (Map<Class<? extends Locatable>, Boolean>) field.get(this.getAnimationPanel()),
566                         "visibilityMap not a map?");
567         Boolean show = map.get(locatableClass);
568         if (show == null)
569         {
570             return;
571         }
572         button.setSelected(show);
573     }
574 
575     /**
576      * Display the latest world coordinate based on the mouse position on the screen.
577      */
578     protected final void updateWorldCoordinate()
579     {
580         String worldPoint = "(x=" + FORMATTER.format(this.animationPanel.getWorldCoordinate().getX()) + " ; y="
581                 + FORMATTER.format(this.animationPanel.getWorldCoordinate().getY()) + ")";
582         this.coordinateField.setText("Mouse: " + worldPoint);
583         int requiredWidth = this.coordinateField.getGraphics().getFontMetrics().stringWidth(this.coordinateField.getText());
584         if (this.coordinateField.getPreferredSize().width < requiredWidth)
585         {
586             Dimension requiredSize = new Dimension(requiredWidth, this.coordinateField.getPreferredSize().height);
587             this.coordinateField.setPreferredSize(requiredSize);
588             this.coordinateField.setMinimumSize(requiredSize);
589             Container parent = this.coordinateField.getParent();
590             parent.setPreferredSize(requiredSize);
591             parent.setMinimumSize(requiredSize);
592             // System.out.println("Increased minimum width to " + requiredSize.width);
593             parent.revalidate();
594         }
595         this.coordinateField.repaint();
596     }
597 
598     /**
599      * Access the GTUColorer of this animation ControlPanel.
600      * @return GTUColorer the colorer used. If it is a SwitchableGTUColorer, the wrapper with the list will be returned, not the
601      *         actual colorer in use.
602      */
603     public final GTUColorer getGTUColorer()
604     {
605         return this.gtuColorer;
606     }
607 
608     /**
609      * Access the ColorControlPanel of this ControlPanel. If the simulator is not a SimpleAnimator, no ColorControlPanel was
610      * constructed and this method will return null.
611      * @return ColorControlPanel
612      */
613     public final ColorControlPanel getColorControlPanel()
614     {
615         return this.colorControlPanel;
616     }
617 
618     /**
619      * Install a handler for the window closed event that stops the simulator (if it is running).
620      */
621     public final void installWindowCloseHandler()
622     {
623         if (this.closeHandlerRegistered)
624         {
625             return;
626         }
627 
628         // make sure the root frame gets disposed of when the closing X icon is pressed.
629         new DisposeOnCloseThread(this).start();
630     }
631 
632     /** Install the dispose on close when the OTSControlPanel is registered as part of a frame. */
633     protected class DisposeOnCloseThread extends Thread
634     {
635         /** The current container. */
636         private OTSAnimationPanel panel;
637 
638         /**
639          * @param panel the OTSControlpanel container.
640          */
641         public DisposeOnCloseThread(final OTSAnimationPanel panel)
642         {
643             super();
644             this.panel = panel;
645         }
646 
647         /** {@inheritDoc} */
648         @Override
649         public final void run()
650         {
651             Container root = this.panel;
652             while (!(root instanceof JFrame))
653             {
654                 try
655                 {
656                     Thread.sleep(10);
657                 }
658                 catch (@SuppressWarnings("unused") InterruptedException exception)
659                 {
660                     // nothing to do
661                 }
662 
663                 // Search towards the root of the Swing components until we find a JFrame
664                 root = this.panel;
665                 while (null != root.getParent() && !(root instanceof JFrame))
666                 {
667                     root = root.getParent();
668                 }
669             }
670             JFrame frame = (JFrame) root;
671             frame.addWindowListener(this.panel);
672             this.panel.closeHandlerRegistered = true;
673         }
674 
675         /** {@inheritDoc} */
676         @Override
677         public final String toString()
678         {
679             return "DisposeOnCloseThread of OTSAnimationPanel [panel=" + this.panel + "]";
680         }
681     }
682 
683     /** {@inheritDoc} */
684     @Override
685     public void windowOpened(final WindowEvent e)
686     {
687         // No action
688     }
689 
690     /** {@inheritDoc} */
691     @Override
692     public final void windowClosing(final WindowEvent e)
693     {
694         // No action
695     }
696 
697     /** {@inheritDoc} */
698     @Override
699     public final void windowClosed(final WindowEvent e)
700     {
701         this.windowExited = true;
702     }
703 
704     /** {@inheritDoc} */
705     @Override
706     public final void windowIconified(final WindowEvent e)
707     {
708         // No action
709     }
710 
711     /** {@inheritDoc} */
712     @Override
713     public final void windowDeiconified(final WindowEvent e)
714     {
715         // No action
716     }
717 
718     /** {@inheritDoc} */
719     @Override
720     public final void windowActivated(final WindowEvent e)
721     {
722         // No action
723     }
724 
725     /** {@inheritDoc} */
726     @Override
727     public final void windowDeactivated(final WindowEvent e)
728     {
729         // No action
730     }
731 
732     /** {@inheritDoc} */
733     @Override
734     public void notify(final EventInterface event) throws RemoteException
735     {
736         if (event.getType().equals(Network.GTU_ADD_EVENT))
737         {
738             this.gtuCount++;
739             setGtuCountText();
740         }
741         else if (event.getType().equals(Network.GTU_REMOVE_EVENT))
742         {
743             this.gtuCount--;
744             setGtuCountText();
745         }
746     }
747 
748     /**
749      * Updates the text of the GTU counter.
750      */
751     private void setGtuCountText()
752     {
753         this.gtuCountField.setText(this.gtuCount + " GTU's");
754     }
755 
756     /**
757      * UpdateTimer class to update the coordinate on the screen.
758      */
759     protected class UpdateTimer extends Thread
760     {
761         /** {@inheritDoc} */
762         @Override
763         public final void run()
764         {
765             while (!OTSAnimationPanel.this.windowExited)
766             {
767                 if (OTSAnimationPanel.this.isShowing())
768                 {
769                     OTSAnimationPanel.this.updateWorldCoordinate();
770                 }
771                 try
772                 {
773                     Thread.sleep(50); // 20 times per second
774                 }
775                 catch (@SuppressWarnings("unused") InterruptedException exception)
776                 {
777                     // do nothing
778                 }
779             }
780         }
781 
782         /** {@inheritDoc} */
783         @Override
784         public final String toString()
785         {
786             return "UpdateTimer thread for OTSAnimationPanel";
787         }
788 
789     }
790 
791     /**
792      * Animation panel that adds autopan functionality.
793      * <p>
794      * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
795      * <br>
796      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
797      * <p>
798      * @version $Revision: 4006 $, $LastChangedDate: 2018-09-19 13:55:45 +0200 (Wed, 19 Sep 2018) $, by $Author: averbraeck $,
799      *          initial version 30 apr. 2018 <br>
800      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
801      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
802      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
803      */
804     private class AutoAnimationPanel extends AnimationPanel
805     {
806 
807         /** */
808         private static final long serialVersionUID = 20180430L;
809 
810         /** Network. */
811         private final OTSNetwork network;
812 
813         /** Last GTU that was followed. */
814         private GTU lastGtu;
815 
816         /**
817          * Constructor.
818          * @param extent Rectangle2D; home extent
819          * @param size Dimension; size
820          * @param simulator SimulatorInterface; simulator
821          * @param network OTSNetwork; network
822          * @throws RemoteException on remote animation error
823          */
824         AutoAnimationPanel(final Rectangle2D extent, final Dimension size, final SimulatorInterface<?, ?, ?> simulator,
825                 final OTSNetwork network) throws RemoteException
826         {
827             super(extent, size, simulator);
828             this.network = network;
829             MouseListener[] listeners = getMouseListeners();
830             for (MouseListener listener : listeners)
831             {
832                 removeMouseListener(listener);
833             }
834             this.addMouseListener(new MouseAdapter()
835             {
836                 /** {@inheritDoc} */
837                 @SuppressWarnings("synthetic-access")
838                 @Override
839                 public void mouseReleased(final MouseEvent e)
840                 {
841                     if (e.isControlDown())
842                     {
843                         GTU gtu = getSelectedGTU(e.getPoint());
844                         if (gtu != null)
845                         {
846                             OTSAnimationPanel.this.autoPanField.setText(gtu.getId());
847                             OTSAnimationPanel.this.autoPanField.setVisible(true);
848                             OTSAnimationPanel.this.autoPanToggle.setSelected(true);
849                             OTSAnimationPanel.this.autoPanToggle.getParent().validate();
850                             OTSAnimationPanel.this.autoPan = true;
851                             e.consume(); // sadly doesn't work to prevent a pop up
852                         }
853                     }
854                     e.consume();
855                 }
856             });
857             for (MouseListener listener : listeners)
858             {
859                 addMouseListener(listener);
860             }
861         }
862 
863         /**
864          * returns the list of selected objects at a certain mousePoint.
865          * @param mousePoint the mousePoint
866          * @return the selected objects
867          */
868         @SuppressWarnings("synthetic-access")
869         protected GTU getSelectedGTU(final Point2D mousePoint)
870         {
871             List<GTU> targets = new ArrayList<>();
872             Point2D point = Renderable2DInterface.Util.getWorldCoordinates(mousePoint,
873                     OTSAnimationPanel.this.animationPanel.getExtent(), OTSAnimationPanel.this.animationPanel.getSize());
874             for (Renderable2DInterface<?> renderable : OTSAnimationPanel.this.animationPanel.getElements())
875             {
876                 if (OTSAnimationPanel.this.animationPanel.isShowElement(renderable) && renderable.contains(point,
877                         OTSAnimationPanel.this.animationPanel.getExtent(), OTSAnimationPanel.this.animationPanel.getSize()))
878                 {
879                     if (renderable.getSource() instanceof GTU)
880                     {
881                         targets.add((GTU) renderable.getSource());
882                     }
883                 }
884             }
885             if (targets.size() == 1)
886             {
887                 return targets.get(0);
888             }
889             return null;
890         }
891 
892         /** {@inheritDoc} */
893         @SuppressWarnings("synthetic-access")
894         @Override
895         public void paint(final Graphics g)
896         {
897             if (OTSAnimationPanel.this.autoPan)
898             {
899                 String id = OTSAnimationPanel.this.autoPanField.getText();
900                 if (this.lastGtu == null || !this.lastGtu.getId().equals(id) || this.lastGtu.isDestroyed())
901                 {
902                     this.lastGtu = this.network.getGTU(id);
903                 }
904                 DirectedPoint point;
905                 try
906                 {
907                     point = this.lastGtu != null ? this.lastGtu.getLocation() : null;
908                 }
909                 catch (@SuppressWarnings("unused") RemoteException exception)
910                 {
911                     System.err.println("Could not pan to GTU location.");
912                     return;
913                 }
914                 if (point != null)
915                 {
916                     double w = this.extent.getWidth();
917                     double h = this.extent.getHeight();
918                     this.extent = new Rectangle2D.Double(point.getX() - w / 2, point.getY() - h / 2, w, h);
919                 }
920             }
921             super.paint(g);
922         }
923 
924         /** {@inheritDoc} */
925         @Override
926         public String toString()
927         {
928             return "AutoAnimationPanel [network=" + this.network + ", lastGtu=" + this.lastGtu + "]";
929         }
930     }
931 
932 }