OTSAnimationPanel.java
package org.opentrafficsim.gui;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.Rectangle2D;
import java.rmi.RemoteException;
import java.text.NumberFormat;
import java.util.ArrayList;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.opentrafficsim.base.modelproperties.PropertyException;
import org.opentrafficsim.core.gtu.animation.GTUColorer;
import org.opentrafficsim.simulationengine.SimpleAnimator;
import org.opentrafficsim.simulationengine.WrappableAnimation;
import nl.tudelft.simulation.dsol.animation.D2.AnimationPanel;
import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
import nl.tudelft.simulation.event.Event;
import nl.tudelft.simulation.language.io.URLResource;
/**
* Animation panel with various controls.
* <p>
* Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* <p>
* $LastChangedDate: 2016-10-26 11:50:35 +0200 (Wed, 26 Oct 2016) $, @version $Revision: 2422 $, by $Author: averbraeck $,
* initial version Jun 18, 2015 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
*/
public class OTSAnimationPanel extends OTSSimulationPanel implements ActionListener, WindowListener
{
/** */
private static final long serialVersionUID = 20150617L;
/** The animation panel on tab position 0. */
private final AnimationPanel animationPanel;
/** Border panel in which the animation is shown. */
private final JPanel borderPanel;
/** The switchableGTUColorer used to color the GTUs. */
private GTUColorer gtuColorer = null;
/** The ColorControlPanel that allows the user to operate the SwitchableGTUColorer. */
private ColorControlPanel colorControlPanel = null;
/** The coordinates of the cursor. */
private final JLabel coordinateField;
/** The animation buttons. */
private final ArrayList<JButton> buttons = new ArrayList<JButton>();
/** The formatter for the world coordinates. */
private static final NumberFormat FORMATTER = NumberFormat.getInstance();
/** Has the window close handler been registered? */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected boolean closeHandlerRegistered = false;
/** Indicate the window has been closed and the timer thread can stop. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected boolean windowExited = false;
/** Initialize the formatter. */
static
{
FORMATTER.setMaximumFractionDigits(3);
}
/**
* Construct a panel that looks like the DSOLPanel for quick building of OTS applications.
* @param extent Rectangle2D; bottom left corner, length and width of the area (world) to animate.
* @param size the size to be used for the animation.
* @param simulator the simulator or animator of the model.
* @param wrappableAnimation the builder and rebuilder of the simulation, based on properties.
* @param gtuColorer the colorer to use for the GTUs.
* @throws RemoteException when notification of the animation panel fails
* @throws PropertyException when one of the user modified properties has the empty string as key
*/
public OTSAnimationPanel(final Rectangle2D extent, final Dimension size, final SimpleAnimator simulator,
final WrappableAnimation wrappableAnimation, final GTUColorer gtuColorer) throws RemoteException, PropertyException
{
super(simulator, wrappableAnimation);
// Add the animation panel as a tab.
this.animationPanel = new AnimationPanel(extent, size, simulator);
this.borderPanel = new JPanel(new BorderLayout());
this.borderPanel.add(this.animationPanel, BorderLayout.CENTER);
getTabbedPane().addTab(0, "animation", this.borderPanel);
getTabbedPane().setSelectedIndex(0); // Show the animation panel as the default tab
// Include the GTU colorer control panel NORTH of the animation.
this.gtuColorer = gtuColorer;
this.colorControlPanel = new ColorControlPanel(this.gtuColorer);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
this.borderPanel.add(buttonPanel, BorderLayout.NORTH);
buttonPanel.add(this.colorControlPanel);
// add the buttons for home, zoom all, grid, and mouse coordinates
buttonPanel.add(new JLabel(" "));
buttonPanel.add(makeButton("allButton", "/Expand.png", "ZoomAll", "Zoom whole network", true));
buttonPanel.add(makeButton("homeButton", "/Home.png", "Home", "Zoom to original extent", true));
buttonPanel.add(makeButton("gridButton", "/Grid.png", "Grid", "Toggle grid on/off", true));
buttonPanel.add(new JLabel(" "));
this.coordinateField = new JLabel("Mouse: ");
this.coordinateField.setMinimumSize(new Dimension(250, 10));
this.coordinateField.setPreferredSize(new Dimension(250, 10));
buttonPanel.add(this.coordinateField);
// Tell the animation to build the list of animation objects.
this.animationPanel.notify(new Event(SimulatorInterface.START_REPLICATION_EVENT, simulator, null));
// switch off the X and Y coordinates in a tooltip.
this.animationPanel.setShowToolTip(false);
// run the update task for the mouse coordinate panel
new UpdateTimer().start();
// make sure the thread gets killed when the window closes.
installWindowCloseHandler();
}
/**
* Create a button.
* @param name String; name of the button
* @param iconPath String; path to the resource
* @param actionCommand String; the action command
* @param toolTipText String; the hint to show when the mouse hovers over the button
* @param enabled boolean; true if the new button must initially be enable; false if it must initially be disabled
* @return JButton
*/
private JButton makeButton(final String name, final String iconPath, final String actionCommand,
final String toolTipText, final boolean enabled)
{
// JButton result = new JButton(new ImageIcon(this.getClass().getResource(iconPath)));
JButton result = new JButton(new ImageIcon(URLResource.getResource(iconPath)));
result.setName(name);
result.setEnabled(enabled);
result.setActionCommand(actionCommand);
result.setToolTipText(toolTipText);
result.addActionListener(this);
this.buttons.add(result);
return result;
}
/** {@inheritDoc} */
@Override
public final void actionPerformed(final ActionEvent actionEvent)
{
String actionCommand = actionEvent.getActionCommand();
try
{
if (actionCommand.equals("Home"))
{
this.animationPanel.home();
}
if (actionCommand.equals("ZoomAll"))
{
this.animationPanel.zoomAll();
}
if (actionCommand.equals("Grid"))
{
this.animationPanel.showGrid(!this.animationPanel.isShowGrid());
}
}
catch (Exception exception)
{
exception.printStackTrace();
}
}
/**
* Easy access to the AnimationPanel.
* @return AnimationPanel
*/
public final AnimationPanel getAnimationPanel()
{
return this.animationPanel;
}
/**
* Display the latest world coordinate based on the mouse position on the screen.
*/
protected final void updateWorldCoordinate()
{
String worldPoint =
"(x=" + FORMATTER.format(this.animationPanel.getWorldCoordinate().getX()) + " ; y="
+ FORMATTER.format(this.animationPanel.getWorldCoordinate().getY()) + ")";
this.coordinateField.setText("Mouse: " + worldPoint);
this.coordinateField.repaint();
}
/**
* Access the GTUColorer of this animation ControlPanel.
* @return GTUColorer the colorer used. If it is a SwitchableGTUColorer, the wrapper with the list will be returned, not the
* actual colorer in use.
*/
public final GTUColorer getGTUColorer()
{
return this.gtuColorer;
}
/**
* Access the ColorControlPanel of this ControlPanel. If the simulator is not a SimpleAnimator, no ColorControlPanel was
* constructed and this method will return null.
* @return ColorControlPanel
*/
public final ColorControlPanel getColorControlPanel()
{
return this.colorControlPanel;
}
/**
* Install a handler for the window closed event that stops the simulator (if it is running).
*/
public final void installWindowCloseHandler()
{
if (this.closeHandlerRegistered)
{
return;
}
// make sure the root frame gets disposed of when the closing X icon is pressed.
new DisposeOnCloseThread(this).start();
}
/** Install the dispose on close when the OTSControlPanel is registered as part of a frame. */
protected class DisposeOnCloseThread extends Thread
{
/** The current container. */
private OTSAnimationPanel panel;
/**
* @param panel the OTSControlpanel container.
*/
public DisposeOnCloseThread(final OTSAnimationPanel panel)
{
super();
this.panel = panel;
}
/** {@inheritDoc} */
@Override
public final void run()
{
Container root = this.panel;
while (!(root instanceof JFrame))
{
try
{
Thread.sleep(10);
}
catch (InterruptedException exception)
{
// nothing to do
}
// Search towards the root of the Swing components until we find a JFrame
root = this.panel;
while (null != root.getParent() && !(root instanceof JFrame))
{
root = root.getParent();
}
}
JFrame frame = (JFrame) root;
frame.addWindowListener(this.panel);
this.panel.closeHandlerRegistered = true;
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return "DisposeOnCloseThread of OTSAnimationPanel [panel=" + this.panel + "]";
}
}
/** {@inheritDoc} */
@Override
public void windowOpened(final WindowEvent e)
{
// No action
}
/** {@inheritDoc} */
@Override
public final void windowClosing(final WindowEvent e)
{
// No action
}
/** {@inheritDoc} */
@Override
public final void windowClosed(final WindowEvent e)
{
this.windowExited = true;
}
/** {@inheritDoc} */
@Override
public final void windowIconified(final WindowEvent e)
{
// No action
}
/** {@inheritDoc} */
@Override
public final void windowDeiconified(final WindowEvent e)
{
// No action
}
/** {@inheritDoc} */
@Override
public final void windowActivated(final WindowEvent e)
{
// No action
}
/** {@inheritDoc} */
@Override
public final void windowDeactivated(final WindowEvent e)
{
// No action
}
/**
* UpdateTimer class to update the coordinate on the screen.
*/
protected class UpdateTimer extends Thread
{
/** {@inheritDoc} */
@Override
public final void run()
{
while (!OTSAnimationPanel.this.windowExited)
{
if (OTSAnimationPanel.this.isShowing())
{
OTSAnimationPanel.this.updateWorldCoordinate();
}
try
{
Thread.sleep(50); // 20 times per second
}
catch (InterruptedException exception)
{
// do nothing
}
}
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return "UpdateTimer thread for OTSAnimationPanel";
}
}
}