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