View Javadoc
1   package org.opentrafficsim.gui;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Container;
5   import java.awt.Dimension;
6   import java.awt.event.ActionEvent;
7   import java.awt.event.ActionListener;
8   import java.awt.event.WindowEvent;
9   import java.awt.event.WindowListener;
10  import java.awt.geom.Rectangle2D;
11  import java.rmi.RemoteException;
12  import java.text.NumberFormat;
13  import java.util.ArrayList;
14  
15  import javax.swing.BoxLayout;
16  import javax.swing.ImageIcon;
17  import javax.swing.JButton;
18  import javax.swing.JFrame;
19  import javax.swing.JLabel;
20  import javax.swing.JPanel;
21  
22  import org.opentrafficsim.base.modelproperties.PropertyException;
23  import org.opentrafficsim.core.gtu.animation.GTUColorer;
24  import org.opentrafficsim.simulationengine.SimpleAnimator;
25  import org.opentrafficsim.simulationengine.WrappableAnimation;
26  
27  import nl.tudelft.simulation.dsol.animation.D2.AnimationPanel;
28  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
29  import nl.tudelft.simulation.event.Event;
30  import nl.tudelft.simulation.language.io.URLResource;
31  
32  /**
33   * Animation panel with various controls.
34   * <p>
35   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
36   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
37   * <p>
38   * $LastChangedDate: 2016-10-26 11:50:35 +0200 (Wed, 26 Oct 2016) $, @version $Revision: 2422 $, by $Author: averbraeck $,
39   * initial version Jun 18, 2015 <br>
40   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
41   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
42   */
43  public class OTSAnimationPanel extends OTSSimulationPanel implements ActionListener, WindowListener
44  {
45      /** */
46      private static final long serialVersionUID = 20150617L;
47  
48      /** The animation panel on tab position 0. */
49      private final AnimationPanel animationPanel;
50  
51      /** Border panel in which the animation is shown. */
52      private final JPanel borderPanel;
53  
54      /** The switchableGTUColorer used to color the GTUs. */
55      private GTUColorer gtuColorer = null;
56  
57      /** The ColorControlPanel that allows the user to operate the SwitchableGTUColorer. */
58      private ColorControlPanel colorControlPanel = null;
59  
60      /** The coordinates of the cursor. */
61      private final JLabel coordinateField;
62  
63      /** The animation buttons. */
64      private final ArrayList<JButton> buttons = new ArrayList<JButton>();
65  
66      /** The formatter for the world coordinates. */
67      private static final NumberFormat FORMATTER = NumberFormat.getInstance();
68  
69      /** Has the window close handler been registered? */
70      @SuppressWarnings("checkstyle:visibilitymodifier")
71      protected boolean closeHandlerRegistered = false;
72  
73      /** Indicate the window has been closed and the timer thread can stop. */
74      @SuppressWarnings("checkstyle:visibilitymodifier")
75      protected boolean windowExited = false;
76  
77      /** Initialize the formatter. */
78      static
79      {
80          FORMATTER.setMaximumFractionDigits(3);
81      }
82  
83      /**
84       * Construct a panel that looks like the DSOLPanel for quick building of OTS applications.
85       * @param extent Rectangle2D; bottom left corner, length and width of the area (world) to animate.
86       * @param size the size to be used for the animation.
87       * @param simulator the simulator or animator of the model.
88       * @param wrappableAnimation the builder and rebuilder of the simulation, based on properties.
89       * @param gtuColorer the colorer to use for the GTUs.
90       * @throws RemoteException when notification of the animation panel fails
91       * @throws PropertyException when one of the user modified properties has the empty string as key
92       */
93      public OTSAnimationPanel(final Rectangle2D extent, final Dimension size, final SimpleAnimator simulator,
94          final WrappableAnimation wrappableAnimation, final GTUColorer gtuColorer) throws RemoteException, PropertyException
95      {
96          super(simulator, wrappableAnimation);
97  
98          // Add the animation panel as a tab.
99          this.animationPanel = new AnimationPanel(extent, size, simulator);
100         this.borderPanel = new JPanel(new BorderLayout());
101         this.borderPanel.add(this.animationPanel, BorderLayout.CENTER);
102         getTabbedPane().addTab(0, "animation", this.borderPanel);
103         getTabbedPane().setSelectedIndex(0); // Show the animation panel as the default tab
104 
105         // Include the GTU colorer control panel NORTH of the animation.
106         this.gtuColorer = gtuColorer;
107         this.colorControlPanel = new ColorControlPanel(this.gtuColorer);
108         JPanel buttonPanel = new JPanel();
109         buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
110         this.borderPanel.add(buttonPanel, BorderLayout.NORTH);
111         buttonPanel.add(this.colorControlPanel);
112 
113         // add the buttons for home, zoom all, grid, and mouse coordinates
114         buttonPanel.add(new JLabel("   "));
115         buttonPanel.add(makeButton("allButton", "/Expand.png", "ZoomAll", "Zoom whole network", true));
116         buttonPanel.add(makeButton("homeButton", "/Home.png", "Home", "Zoom to original extent", true));
117         buttonPanel.add(makeButton("gridButton", "/Grid.png", "Grid", "Toggle grid on/off", true));
118         buttonPanel.add(new JLabel("   "));
119         this.coordinateField = new JLabel("Mouse: ");
120         this.coordinateField.setMinimumSize(new Dimension(250, 10));
121         this.coordinateField.setPreferredSize(new Dimension(250, 10));
122         buttonPanel.add(this.coordinateField);
123 
124         // Tell the animation to build the list of animation objects.
125         this.animationPanel.notify(new Event(SimulatorInterface.START_REPLICATION_EVENT, simulator, null));
126 
127         // switch off the X and Y coordinates in a tooltip.
128         this.animationPanel.setShowToolTip(false);
129 
130         // run the update task for the mouse coordinate panel
131         new UpdateTimer().start();
132 
133         // make sure the thread gets killed when the window closes.
134         installWindowCloseHandler();
135     }
136 
137     /**
138      * Create a button.
139      * @param name String; name of the button
140      * @param iconPath String; path to the resource
141      * @param actionCommand String; the action command
142      * @param toolTipText String; the hint to show when the mouse hovers over the button
143      * @param enabled boolean; true if the new button must initially be enable; false if it must initially be disabled
144      * @return JButton
145      */
146     private JButton makeButton(final String name, final String iconPath, final String actionCommand,
147         final String toolTipText, final boolean enabled)
148     {
149         // JButton result = new JButton(new ImageIcon(this.getClass().getResource(iconPath)));
150         JButton result = new JButton(new ImageIcon(URLResource.getResource(iconPath)));
151         result.setName(name);
152         result.setEnabled(enabled);
153         result.setActionCommand(actionCommand);
154         result.setToolTipText(toolTipText);
155         result.addActionListener(this);
156         this.buttons.add(result);
157         return result;
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     public final void actionPerformed(final ActionEvent actionEvent)
163     {
164         String actionCommand = actionEvent.getActionCommand();
165         try
166         {
167             if (actionCommand.equals("Home"))
168             {
169                 this.animationPanel.home();
170             }
171             if (actionCommand.equals("ZoomAll"))
172             {
173                 this.animationPanel.zoomAll();
174             }
175             if (actionCommand.equals("Grid"))
176             {
177                 this.animationPanel.showGrid(!this.animationPanel.isShowGrid());
178             }
179         }
180         catch (Exception exception)
181         {
182             exception.printStackTrace();
183         }
184     }
185 
186     /**
187      * Easy access to the AnimationPanel.
188      * @return AnimationPanel
189      */
190     public final AnimationPanel getAnimationPanel()
191     {
192         return this.animationPanel;
193     }
194 
195     /**
196      * Display the latest world coordinate based on the mouse position on the screen.
197      */
198     protected final void updateWorldCoordinate()
199     {
200         String worldPoint =
201             "(x=" + FORMATTER.format(this.animationPanel.getWorldCoordinate().getX()) + " ; y="
202                 + FORMATTER.format(this.animationPanel.getWorldCoordinate().getY()) + ")";
203         this.coordinateField.setText("Mouse: " + worldPoint);
204         this.coordinateField.repaint();
205     }
206 
207     /**
208      * Access the GTUColorer of this animation ControlPanel.
209      * @return GTUColorer the colorer used. If it is a SwitchableGTUColorer, the wrapper with the list will be returned, not the
210      *         actual colorer in use.
211      */
212     public final GTUColorer getGTUColorer()
213     {
214         return this.gtuColorer;
215     }
216 
217     /**
218      * Access the ColorControlPanel of this ControlPanel. If the simulator is not a SimpleAnimator, no ColorControlPanel was
219      * constructed and this method will return null.
220      * @return ColorControlPanel
221      */
222     public final ColorControlPanel getColorControlPanel()
223     {
224         return this.colorControlPanel;
225     }
226 
227     /**
228      * Install a handler for the window closed event that stops the simulator (if it is running).
229      */
230     public final void installWindowCloseHandler()
231     {
232         if (this.closeHandlerRegistered)
233         {
234             return;
235         }
236 
237         // make sure the root frame gets disposed of when the closing X icon is pressed.
238         new DisposeOnCloseThread(this).start();
239     }
240 
241     /** Install the dispose on close when the OTSControlPanel is registered as part of a frame. */
242     protected class DisposeOnCloseThread extends Thread
243     {
244         /** The current container. */
245         private OTSAnimationPanel panel;
246 
247         /**
248          * @param panel the OTSControlpanel container.
249          */
250         public DisposeOnCloseThread(final OTSAnimationPanel panel)
251         {
252             super();
253             this.panel = panel;
254         }
255 
256         /** {@inheritDoc} */
257         @Override
258         public final void run()
259         {
260             Container root = this.panel;
261             while (!(root instanceof JFrame))
262             {
263                 try
264                 {
265                     Thread.sleep(10);
266                 }
267                 catch (InterruptedException exception)
268                 {
269                     // nothing to do
270                 }
271 
272                 // Search towards the root of the Swing components until we find a JFrame
273                 root = this.panel;
274                 while (null != root.getParent() && !(root instanceof JFrame))
275                 {
276                     root = root.getParent();
277                 }
278             }
279             JFrame frame = (JFrame) root;
280             frame.addWindowListener(this.panel);
281             this.panel.closeHandlerRegistered = true;
282         }
283 
284         /** {@inheritDoc} */
285         @Override
286         public final String toString()
287         {
288             return "DisposeOnCloseThread of OTSAnimationPanel [panel=" + this.panel + "]";
289         }
290     }
291 
292     /** {@inheritDoc} */
293     @Override
294     public void windowOpened(final WindowEvent e)
295     {
296         // No action
297     }
298 
299     /** {@inheritDoc} */
300     @Override
301     public final void windowClosing(final WindowEvent e)
302     {
303         // No action
304     }
305 
306     /** {@inheritDoc} */
307     @Override
308     public final void windowClosed(final WindowEvent e)
309     {
310         this.windowExited = true;
311     }
312 
313     /** {@inheritDoc} */
314     @Override
315     public final void windowIconified(final WindowEvent e)
316     {
317         // No action
318     }
319 
320     /** {@inheritDoc} */
321     @Override
322     public final void windowDeiconified(final WindowEvent e)
323     {
324         // No action
325     }
326 
327     /** {@inheritDoc} */
328     @Override
329     public final void windowActivated(final WindowEvent e)
330     {
331         // No action
332     }
333 
334     /** {@inheritDoc} */
335     @Override
336     public final void windowDeactivated(final WindowEvent e)
337     {
338         // No action
339     }
340 
341     /**
342      * UpdateTimer class to update the coordinate on the screen.
343      */
344     protected class UpdateTimer extends Thread
345     {
346         /** {@inheritDoc} */
347         @Override
348         public final void run()
349         {
350             while (!OTSAnimationPanel.this.windowExited)
351             {
352                 if (OTSAnimationPanel.this.isShowing())
353                 {
354                     OTSAnimationPanel.this.updateWorldCoordinate();
355                 }
356                 try
357                 {
358                     Thread.sleep(50); // 20 times per second
359                 }
360                 catch (InterruptedException exception)
361                 {
362                     // do nothing
363                 }
364             }
365         }
366 
367         /** {@inheritDoc} */
368         @Override
369         public final String toString()
370         {
371             return "UpdateTimer thread for OTSAnimationPanel";
372         }
373 
374     }
375 }