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.event.ActionEvent;
8   import java.awt.event.ActionListener;
9   import java.awt.event.WindowEvent;
10  import java.awt.event.WindowListener;
11  import java.awt.geom.Rectangle2D;
12  import java.rmi.RemoteException;
13  import java.text.NumberFormat;
14  import java.util.ArrayList;
15  import java.util.HashMap;
16  import java.util.Map;
17  
18  import javax.swing.BoxLayout;
19  import javax.swing.ImageIcon;
20  import javax.swing.JButton;
21  import javax.swing.JCheckBox;
22  import javax.swing.JFrame;
23  import javax.swing.JLabel;
24  import javax.swing.JPanel;
25  import javax.swing.JToggleButton;
26  
27  import org.opentrafficsim.base.modelproperties.PropertyException;
28  import org.opentrafficsim.core.gtu.animation.GTUColorer;
29  import org.opentrafficsim.simulationengine.SimpleAnimator;
30  import org.opentrafficsim.simulationengine.WrappableAnimation;
31  
32  import nl.tudelft.simulation.dsol.animation.Locatable;
33  import nl.tudelft.simulation.dsol.animation.D2.AnimationPanel;
34  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
35  import nl.tudelft.simulation.event.Event;
36  import nl.tudelft.simulation.language.io.URLResource;
37  
38  /**
39   * Animation panel with various controls.
40   * <p>
41   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
42   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
43   * <p>
44   * $LastChangedDate: 2017-01-16 01:51:17 +0100 (Mon, 16 Jan 2017) $, @version $Revision: 3282 $, by $Author: averbraeck $,
45   * initial version Jun 18, 2015 <br>
46   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
47   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
48   */
49  public class OTSAnimationPanel extends OTSSimulationPanel implements ActionListener, WindowListener
50  {
51      /** */
52      private static final long serialVersionUID = 20150617L;
53  
54      /** The animation panel on tab position 0. */
55      private final AnimationPanel animationPanel;
56  
57      /** Border panel in which the animation is shown. */
58      private final JPanel borderPanel;
59  
60      /** Toggle panel with which animation features can be shown/hidden. */
61      private final JPanel togglePanel;
62  
63      /** Map of toggle names to toggle animation classes. */
64      private Map<String, Class<? extends Locatable>> toggleLocatableMap = new HashMap<>();
65  
66      /** The switchableGTUColorer used to color the GTUs. */
67      private GTUColorer gtuColorer = null;
68  
69      /** The ColorControlPanel that allows the user to operate the SwitchableGTUColorer. */
70      private ColorControlPanel colorControlPanel = null;
71  
72      /** The coordinates of the cursor. */
73      private final JLabel coordinateField;
74  
75      /** The animation buttons. */
76      private final ArrayList<JButton> buttons = new ArrayList<JButton>();
77  
78      /** The formatter for the world coordinates. */
79      private static final NumberFormat FORMATTER = NumberFormat.getInstance();
80  
81      /** Has the window close handler been registered? */
82      @SuppressWarnings("checkstyle:visibilitymodifier")
83      protected boolean closeHandlerRegistered = false;
84  
85      /** Indicate the window has been closed and the timer thread can stop. */
86      @SuppressWarnings("checkstyle:visibilitymodifier")
87      protected boolean windowExited = false;
88  
89      /** Initialize the formatter. */
90      static
91      {
92          FORMATTER.setMaximumFractionDigits(3);
93      }
94  
95      /**
96       * Construct a panel that looks like the DSOLPanel for quick building of OTS applications.
97       * @param extent Rectangle2D; bottom left corner, length and width of the area (world) to animate.
98       * @param size the size to be used for the animation.
99       * @param simulator the simulator or animator of the model.
100      * @param wrappableAnimation the builder and rebuilder of the simulation, based on properties.
101      * @param gtuColorer the colorer to use for the GTUs.
102      * @throws RemoteException when notification of the animation panel fails
103      * @throws PropertyException when one of the user modified properties has the empty string as key
104      */
105     public OTSAnimationPanel(final Rectangle2D extent, final Dimension size, final SimpleAnimator simulator,
106             final WrappableAnimation wrappableAnimation, final GTUColorer gtuColorer) throws RemoteException, PropertyException
107     {
108         super(simulator, wrappableAnimation);
109 
110         // Add the animation panel as a tab.
111         this.animationPanel = new AnimationPanel(extent, size, simulator);
112         this.borderPanel = new JPanel(new BorderLayout());
113         this.borderPanel.add(this.animationPanel, BorderLayout.CENTER);
114         getTabbedPane().addTab(0, "animation", this.borderPanel);
115         getTabbedPane().setSelectedIndex(0); // Show the animation panel as the default tab
116 
117         // Include the GTU colorer control panel NORTH of the animation.
118         this.gtuColorer = gtuColorer;
119         this.colorControlPanel = new ColorControlPanel(this.gtuColorer);
120         JPanel buttonPanel = new JPanel();
121         buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
122         this.borderPanel.add(buttonPanel, BorderLayout.NORTH);
123         buttonPanel.add(this.colorControlPanel);
124 
125         // Include the TogglePanel WEST of the animation.
126         this.togglePanel = new JPanel();
127         this.togglePanel.setLayout(new BoxLayout(this.togglePanel, BoxLayout.Y_AXIS));
128         this.borderPanel.add(this.togglePanel, BorderLayout.WEST);
129 
130         // add the buttons for home, zoom all, grid, and mouse coordinates
131         buttonPanel.add(new JLabel("   "));
132         buttonPanel.add(makeButton("allButton", "/Expand.png", "ZoomAll", "Zoom whole network", true));
133         buttonPanel.add(makeButton("homeButton", "/Home.png", "Home", "Zoom to original extent", true));
134         buttonPanel.add(makeButton("gridButton", "/Grid.png", "Grid", "Toggle grid on/off", true));
135         buttonPanel.add(new JLabel("   "));
136         this.coordinateField = new JLabel("Mouse: ");
137         this.coordinateField.setMinimumSize(new Dimension(250, 10));
138         this.coordinateField.setPreferredSize(new Dimension(250, 10));
139         buttonPanel.add(this.coordinateField);
140 
141         // Tell the animation to build the list of animation objects.
142         this.animationPanel.notify(new Event(SimulatorInterface.START_REPLICATION_EVENT, simulator, null));
143 
144         // switch off the X and Y coordinates in a tooltip.
145         this.animationPanel.setShowToolTip(false);
146 
147         // run the update task for the mouse coordinate panel
148         new UpdateTimer().start();
149 
150         // make sure the thread gets killed when the window closes.
151         installWindowCloseHandler();
152     }
153 
154     /**
155      * Create a button.
156      * @param name String; name of the button
157      * @param iconPath String; path to the resource
158      * @param actionCommand String; the action command
159      * @param toolTipText String; the hint to show when the mouse hovers over the button
160      * @param enabled boolean; true if the new button must initially be enable; false if it must initially be disabled
161      * @return JButton
162      */
163     private JButton makeButton(final String name, final String iconPath, final String actionCommand, final String toolTipText,
164             final boolean enabled)
165     {
166         // JButton result = new JButton(new ImageIcon(this.getClass().getResource(iconPath)));
167         JButton result = new JButton(new ImageIcon(URLResource.getResource(iconPath)));
168         result.setPreferredSize(new Dimension(34, 32));
169         result.setName(name);
170         result.setEnabled(enabled);
171         result.setActionCommand(actionCommand);
172         result.setToolTipText(toolTipText);
173         result.addActionListener(this);
174         this.buttons.add(result);
175         return result;
176     }
177 
178     /**
179      * Add a button for toggling an animatable class on or off. Button icons for which 'idButton' is true will be placed to the
180      * right of the previous button, which should be the corresponding button without the id. An example is an icon for
181      * showing/hiding the class 'Lane' followed by the button to show/hide the Lane ids.
182      * @param name the name of the button
183      * @param locatableClass the class for which the button holds (e.g., GTU.class)
184      * @param iconPath the path to the 24x24 icon to display
185      * @param toolTipText the tool tip text to show when hovering over the button
186      * @param initiallyVisible whether the class is initially shown or not
187      * @param idButton id button that needs to be placed next to the previous button
188      */
189     public final void addToggleAnimationButtonIcon(final String name, final Class<? extends Locatable> locatableClass,
190             final String iconPath, final String toolTipText, final boolean initiallyVisible, final boolean idButton)
191     {
192         JToggleButton button;
193         button = new JCheckBox(new ImageIcon(URLResource.getResource(iconPath)));
194         button.setPreferredSize(new Dimension(32, 28));
195         button.setName(name);
196         button.setEnabled(true);
197         button.setSelected(initiallyVisible);
198         button.setActionCommand(name);
199         button.setToolTipText(toolTipText);
200         button.addActionListener(this);
201 
202         // place an Id button to the right of the corresponding content button
203         if (idButton && this.togglePanel.getComponentCount() > 0)
204         {
205             JPanel lastToggleBox = (JPanel) this.togglePanel.getComponent(this.togglePanel.getComponentCount() - 1);
206             lastToggleBox.add(button);
207         }
208         else
209         {
210             JPanel toggleBox = new JPanel();
211             toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
212             toggleBox.add(button);
213             this.togglePanel.add(toggleBox);
214             toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
215         }
216 
217         if (initiallyVisible)
218         {
219             this.animationPanel.showClass(locatableClass);
220         }
221         else
222         {
223             this.animationPanel.hideClass(locatableClass);
224         }
225         this.toggleLocatableMap.put(name, locatableClass);
226     }
227 
228     /**
229      * Add a button for toggling an animatable class on or off.
230      * @param name the name of the button
231      * @param locatableClass the class for which the button holds (e.g., GTU.class)
232      * @param toolTipText the tool tip text to show when hovering over the button
233      * @param initiallyVisible whether the class is initially shown or not
234      */
235     public final void addToggleAnimationButtonText(final String name, final Class<? extends Locatable> locatableClass,
236             final String toolTipText, final boolean initiallyVisible)
237     {
238         JToggleButton button;
239         button = new JCheckBox(name);
240         button.setName(name);
241         button.setEnabled(true);
242         button.setSelected(initiallyVisible);
243         button.setActionCommand(name);
244         button.setToolTipText(toolTipText);
245         button.addActionListener(this);
246 
247         JPanel toggleBox = new JPanel();
248         toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
249         toggleBox.add(button);
250         this.togglePanel.add(toggleBox);
251         toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
252 
253         if (initiallyVisible)
254         {
255             this.animationPanel.showClass(locatableClass);
256         }
257         else
258         {
259             this.animationPanel.hideClass(locatableClass);
260         }
261         this.toggleLocatableMap.put(name, locatableClass);
262     }
263 
264     /** {@inheritDoc} */
265     @Override
266     public final void actionPerformed(final ActionEvent actionEvent)
267     {
268         String actionCommand = actionEvent.getActionCommand();
269         try
270         {
271             if (actionCommand.equals("Home"))
272             {
273                 this.animationPanel.home();
274             }
275             if (actionCommand.equals("ZoomAll"))
276             {
277                 this.animationPanel.zoomAll();
278             }
279             if (actionCommand.equals("Grid"))
280             {
281                 this.animationPanel.showGrid(!this.animationPanel.isShowGrid());
282             }
283 
284             if (this.toggleLocatableMap.containsKey(actionCommand))
285             {
286                 Class<? extends Locatable> locatableClass = this.toggleLocatableMap.get(actionCommand);
287                 this.animationPanel.toggleClass(locatableClass);
288                 this.togglePanel.repaint();
289             }
290         }
291         catch (Exception exception)
292         {
293             exception.printStackTrace();
294         }
295     }
296 
297     /**
298      * Easy access to the AnimationPanel.
299      * @return AnimationPanel
300      */
301     public final AnimationPanel getAnimationPanel()
302     {
303         return this.animationPanel;
304     }
305 
306     /**
307      * Display the latest world coordinate based on the mouse position on the screen.
308      */
309     protected final void updateWorldCoordinate()
310     {
311         String worldPoint = "(x=" + FORMATTER.format(this.animationPanel.getWorldCoordinate().getX()) + " ; y="
312                 + FORMATTER.format(this.animationPanel.getWorldCoordinate().getY()) + ")";
313         this.coordinateField.setText("Mouse: " + worldPoint);
314         this.coordinateField.repaint();
315     }
316 
317     /**
318      * Access the GTUColorer of this animation ControlPanel.
319      * @return GTUColorer the colorer used. If it is a SwitchableGTUColorer, the wrapper with the list will be returned, not the
320      *         actual colorer in use.
321      */
322     public final GTUColorer getGTUColorer()
323     {
324         return this.gtuColorer;
325     }
326 
327     /**
328      * Access the ColorControlPanel of this ControlPanel. If the simulator is not a SimpleAnimator, no ColorControlPanel was
329      * constructed and this method will return null.
330      * @return ColorControlPanel
331      */
332     public final ColorControlPanel getColorControlPanel()
333     {
334         return this.colorControlPanel;
335     }
336 
337     /**
338      * Install a handler for the window closed event that stops the simulator (if it is running).
339      */
340     public final void installWindowCloseHandler()
341     {
342         if (this.closeHandlerRegistered)
343         {
344             return;
345         }
346 
347         // make sure the root frame gets disposed of when the closing X icon is pressed.
348         new DisposeOnCloseThread(this).start();
349     }
350 
351     /** Install the dispose on close when the OTSControlPanel is registered as part of a frame. */
352     protected class DisposeOnCloseThread extends Thread
353     {
354         /** The current container. */
355         private OTSAnimationPanel panel;
356 
357         /**
358          * @param panel the OTSControlpanel container.
359          */
360         public DisposeOnCloseThread(final OTSAnimationPanel panel)
361         {
362             super();
363             this.panel = panel;
364         }
365 
366         /** {@inheritDoc} */
367         @Override
368         public final void run()
369         {
370             Container root = this.panel;
371             while (!(root instanceof JFrame))
372             {
373                 try
374                 {
375                     Thread.sleep(10);
376                 }
377                 catch (InterruptedException exception)
378                 {
379                     // nothing to do
380                 }
381 
382                 // Search towards the root of the Swing components until we find a JFrame
383                 root = this.panel;
384                 while (null != root.getParent() && !(root instanceof JFrame))
385                 {
386                     root = root.getParent();
387                 }
388             }
389             JFrame frame = (JFrame) root;
390             frame.addWindowListener(this.panel);
391             this.panel.closeHandlerRegistered = true;
392         }
393 
394         /** {@inheritDoc} */
395         @Override
396         public final String toString()
397         {
398             return "DisposeOnCloseThread of OTSAnimationPanel [panel=" + this.panel + "]";
399         }
400     }
401 
402     /** {@inheritDoc} */
403     @Override
404     public void windowOpened(final WindowEvent e)
405     {
406         // No action
407     }
408 
409     /** {@inheritDoc} */
410     @Override
411     public final void windowClosing(final WindowEvent e)
412     {
413         // No action
414     }
415 
416     /** {@inheritDoc} */
417     @Override
418     public final void windowClosed(final WindowEvent e)
419     {
420         this.windowExited = true;
421     }
422 
423     /** {@inheritDoc} */
424     @Override
425     public final void windowIconified(final WindowEvent e)
426     {
427         // No action
428     }
429 
430     /** {@inheritDoc} */
431     @Override
432     public final void windowDeiconified(final WindowEvent e)
433     {
434         // No action
435     }
436 
437     /** {@inheritDoc} */
438     @Override
439     public final void windowActivated(final WindowEvent e)
440     {
441         // No action
442     }
443 
444     /** {@inheritDoc} */
445     @Override
446     public final void windowDeactivated(final WindowEvent e)
447     {
448         // No action
449     }
450 
451     /**
452      * UpdateTimer class to update the coordinate on the screen.
453      */
454     protected class UpdateTimer extends Thread
455     {
456         /** {@inheritDoc} */
457         @Override
458         public final void run()
459         {
460             while (!OTSAnimationPanel.this.windowExited)
461             {
462                 if (OTSAnimationPanel.this.isShowing())
463                 {
464                     OTSAnimationPanel.this.updateWorldCoordinate();
465                 }
466                 try
467                 {
468                     Thread.sleep(50); // 20 times per second
469                 }
470                 catch (InterruptedException exception)
471                 {
472                     // do nothing
473                 }
474             }
475         }
476 
477         /** {@inheritDoc} */
478         @Override
479         public final String toString()
480         {
481             return "UpdateTimer thread for OTSAnimationPanel";
482         }
483 
484     }
485 }