package org.opentrafficsim.demo.carFollowing;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.naming.NamingException;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import nl.tudelft.simulation.dsol.SimRuntimeException;
import org.djunits.locale.DefaultLocale;
import org.djunits.unit.UNITS;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Time;
import org.opentrafficsim.base.modelproperties.BooleanProperty;
import org.opentrafficsim.base.modelproperties.CompoundProperty;
import org.opentrafficsim.base.modelproperties.ContinuousProperty;
import org.opentrafficsim.base.modelproperties.IntegerProperty;
import org.opentrafficsim.base.modelproperties.ProbabilityDistributionProperty;
import org.opentrafficsim.base.modelproperties.Property;
import org.opentrafficsim.base.modelproperties.PropertyException;
import org.opentrafficsim.base.modelproperties.SelectionProperty;
import org.opentrafficsim.base.modelproperties.StringProperty;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.demo.trafficcontrol.TrafCODDemo2;
import org.opentrafficsim.gui.LabeledPanel;
import org.opentrafficsim.gui.ProbabilityDistributionEditor;
import org.opentrafficsim.gui.SimulatorFrame;
import org.opentrafficsim.road.modelproperties.IDMPropertySet;
import org.opentrafficsim.simulationengine.OTSSimulationException;
import org.opentrafficsim.simulationengine.WrappableAnimation;
/**
* Several demos in one application.
*
* Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* $LastChangedDate$, @version $Revision$, by $Author$,
* initial version 17 dec. 2014
* @author Peter Knoppers
*/
public class SuperDemo implements UNITS
{
/** The JPanel that holds the user settable properties. */
private JPanel propertyPanel;
/** The JPanel that holds the simulation selection radio buttons. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected JPanel simulationSelection;
/** Properties of the currently selected demonstration. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected List> activeProperties = null;
/** Panel with the description of the currently selected demonstration. */
private LabeledPanel descriptionPanel;
/**
* Start the application.
* @param args String[]; the command line arguments (not used)
*/
public static void main(final String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
try
{
JFrame frame = new SimulatorFrame("Open Traffic Simulator Demonstrations", new SuperDemo().buildGUI());
frame.setExtendedState(frame.getExtendedState() & ~Frame.MAXIMIZED_BOTH);
// frame.setExtendedState(frame.getExtendedState() | Frame.MAXIMIZED_VERT);
// The code above does not work; the code below does work. Code found on
// http://stackoverflow.com/questions/5195634/how-to-maximize-a-the-height-of-a-jframe
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration());
int taskHeight = screenInsets.bottom;
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
frame.setSize(d.width / 2, d.height - taskHeight);
frame.setLocation(0, 0);
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
});
}
/**
* Build the GUI.
* @return JPanel; the JPanel that holds the application
* @throws PropertyException when one of the demonstrations has a user modified property with the empty string as key
*/
public final JPanel buildGUI() throws PropertyException
{
final JPanel mainPanel = new JPanel(new BorderLayout());
// Ensure that the window does not shrink into (almost) nothingness when un-maximized
mainPanel.setPreferredSize(new Dimension(800, 800));
final ArrayList demonstrations = new ArrayList<>();
demonstrations.add(new Straight());
demonstrations.add(new SequentialLanes());
demonstrations.add(new CircularLane());
demonstrations.add(new CircularRoad());
demonstrations.add(new XMLNetworks());
demonstrations.add(new OpenStreetMap());
demonstrations.add(new CrossingTrafficLights());
demonstrations.add(new TrafCODDemo2());
// final JPanel left = new LabeledPanel("Simulation Settings");
this.simulationSelection = new LabeledPanel("Network");
this.simulationSelection.setLayout(new GridBagLayout());
GridBagConstraints gbcSimulation = new GridBagConstraints();
gbcSimulation.gridx = 0;
gbcSimulation.gridy = -1;
gbcSimulation.anchor = GridBagConstraints.LINE_START;
gbcSimulation.weighty = 0;
gbcSimulation.fill = GridBagConstraints.HORIZONTAL;
final JPanel left = new JPanel(new GridBagLayout());
GridBagConstraints gbcLeft = new GridBagConstraints();
gbcLeft.gridx = 0;
gbcLeft.gridy = 0;
gbcLeft.anchor = GridBagConstraints.LINE_START;
gbcLeft.weighty = 0;
gbcLeft.fill = GridBagConstraints.HORIZONTAL;
final JLabel description = new JLabel("Please select a demonstration from the buttons on the left");
final JPanel centerPanel = new JPanel(new BorderLayout());
this.propertyPanel = new JPanel();
this.propertyPanel.setLayout(new BoxLayout(this.propertyPanel, BoxLayout.Y_AXIS));
rebuildPropertyPanel(new ArrayList>());
mainPanel.add(centerPanel, BorderLayout.CENTER);
this.descriptionPanel = new LabeledPanel("Description");
this.descriptionPanel.setLayout(new BorderLayout());
this.descriptionPanel.add(description);
centerPanel.add(this.descriptionPanel, BorderLayout.CENTER);
final JButton startButton = new JButton("Start simulation");
ButtonGroup buttonGroup = new ButtonGroup();
for (final WrappableAnimation demo : demonstrations)
{
CleverRadioButton button = new CleverRadioButton(demo);
// button.setPreferredSize(new Dimension(Integer.MAX_VALUE, button.getPreferredSize().height));
button.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(final ActionEvent e)
{
// System.out.println("selected " + demo.shortName());
description.setText(demo.description());
startButton.setEnabled(true);
startButton.setVisible(true);
rebuildPropertyPanel(demo.getProperties());
}
});
buttonGroup.add(button);
gbcSimulation.gridy++;
this.simulationSelection.add(button, gbcSimulation);
}
JScrollPane scrollPane = new JScrollPane(left);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
mainPanel.add(scrollPane, BorderLayout.LINE_START);
startButton.setEnabled(false);
startButton.setVisible(false);
startButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(final ActionEvent e)
{
System.out.println("Starting simulation");
WrappableAnimation simulation = null;
for (Component c : SuperDemo.this.simulationSelection.getComponents())
{
if (c instanceof CleverRadioButton)
{
CleverRadioButton crb = (CleverRadioButton) c;
if (crb.isSelected())
{
simulation = crb.getAnimation();
}
}
}
if (null == simulation)
{
throw new Error("Cannot find a selected button");
}
try
{
System.out.println("Active properties: " + SuperDemo.this.activeProperties);
simulation.buildAnimator(new Time(0.0, SECOND), new Duration(0.0, SECOND), new Duration(3600.0, SECOND),
SuperDemo.this.activeProperties, null, false);
}
catch (SimRuntimeException | NetworkException | NamingException | OTSSimulationException
| PropertyException exception)
{
exception.printStackTrace();
}
}
});
gbcLeft.gridy++;
left.add(this.propertyPanel, gbcLeft);
gbcLeft.gridy++;
gbcLeft.weighty = 1;
JPanel filler = new JPanel();
filler.setMinimumSize(new Dimension(400, 0));
filler.setPreferredSize(new Dimension(400, 0));
left.add(filler, gbcLeft); // add a filler that also enforces a reasonable width
gbcLeft.weighty = 0;
gbcLeft.anchor = GridBagConstraints.SOUTH;
left.add(startButton, gbcLeft);
JPanel rightFiller = new JPanel();
rightFiller.setPreferredSize(new Dimension((int) new JScrollBar().getPreferredSize().getWidth(), 0));
gbcLeft.gridx = 2;
left.add(rightFiller, gbcLeft);
return mainPanel;
}
/**
* Regenerate the contents of the propertyPanel.
* @param properties ArrayList<AbstractProperty<?>>; the demo-specific properties to display
*/
final void rebuildPropertyPanel(final List> properties)
{
this.propertyPanel.removeAll();
try
{
CompoundProperty simulationSettings =
new CompoundProperty("SimulationSettings", "Simulation settings",
"Select the simulation network and traffic composition", null, false, 0);
/*
* This is ugly, but it gets the job done... Insert a dummy property at the top and later replace the property
* editor for the dummy property by the simulationSelection JPanel.
*/
BooleanProperty dummy = new BooleanProperty("Dummy", "Dummy", "Dummy", false, false, 0);
simulationSettings.add(dummy);
if (properties.size() > 0)
{
while (true)
{
boolean movedAny = false;
// Move the properties that has display priority < 100 into the simulationSettings group.
for (Property> ap : properties)
{
if (ap.getDisplayPriority() < 100)
{
// Move it into the simulationSettings group
simulationSettings.add(ap);
properties.remove(ap);
movedAny = true;
break;
}
}
if (!movedAny)
{
break;
}
}
simulationSettings.add(new ProbabilityDistributionProperty("TrafficComposition", "Traffic composition",
"Mix of passenger cars and trucks", new String[] { "passenger car", "truck" },
new Double[] { 0.8, 0.2 }, false, 5));
CompoundProperty modelSelection =
new CompoundProperty("ModelSelection", "Model selection", "Modeling specific settings", null, false,
300);
modelSelection.add(new SelectionProperty("SimulationScale", "Simulation scale",
"Level of detail of the simulation", new String[] { "Micro", "Macro", "Meta" }, 0, true, 0));
modelSelection.add(new SelectionProperty("CarFollowingModel", "Car following model",
"The car following model determines "
+ "the acceleration that a vehicle will make taking into account "
+ "nearby vehicles, infrastructural restrictions (e.g. speed limit, "
+ "curvature of the road) capabilities of the vehicle and personality "
+ "of the driver.", new String[] { "IDM", "IDM+" }, 1, false, 1));
modelSelection.add(IDMPropertySet.makeIDMPropertySet("IDMCar", "Car",
new Acceleration(1.56, METER_PER_SECOND_2), new Acceleration(2.09, METER_PER_SECOND_2),
new Length(3.0, METER), new Duration(1.2, SECOND), 2));
modelSelection.add(IDMPropertySet.makeIDMPropertySet("IDMTruck", "Truck", new Acceleration(0.75,
METER_PER_SECOND_2), new Acceleration(1.25, METER_PER_SECOND_2), new Length(3.0, METER), new Duration(
1.2, SECOND), 3));
properties.add(properties.size() > 0 ? 1 : 0, modelSelection);
}
properties.add(0, simulationSettings);
boolean fixedDummy = false;
for (Property> p : new CompoundProperty("", "", "", properties, false, 0).displayOrderedValue())
{
JPanel propertySubPanel = makePropertyEditor(p);
if (!fixedDummy)
{
// Replace the dummy property editor by the simulationSelection JPanel.
JPanel subPanel = (JPanel) propertySubPanel.getComponent(0);
subPanel.removeAll();
subPanel.add(this.simulationSelection);
fixedDummy = true;
}
this.propertyPanel.add(propertySubPanel);
}
simulationSettings.remove(dummy);
SuperDemo.this.activeProperties = properties;
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
/**
* Create a graphical editor for an AbstractProperty.
* @param ap AbstractProperty; the abstract property for which an editor must be created
* @return JPanel
*/
@SuppressWarnings("checkstyle:methodlength")
final JPanel makePropertyEditor(final Property> ap)
{
JPanel result;
if (ap instanceof SelectionProperty)
{
result = new JPanel();
result.setLayout(new BorderLayout());
final SelectionProperty sp = (SelectionProperty) ap;
final JComboBox comboBox = new JComboBox(sp.getOptionNames());
comboBox.setSelectedItem(sp.getValue());
comboBox.setToolTipText(sp.getDescription());
comboBox.addItemListener(new ItemListener()
{
@Override
public void itemStateChanged(final ItemEvent itemEvent)
{
if (itemEvent.getStateChange() == ItemEvent.SELECTED)
{
String itemText = (String) itemEvent.getItem();
try
{
sp.setValue(itemText);
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
}
});
if (ap.isReadOnly())
{
comboBox.removeItemListener(comboBox.getItemListeners()[0]);
comboBox.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(final ActionEvent actionEvent)
{
if (comboBox.getSelectedIndex() != 0)
{
comboBox.setSelectedIndex(0);
}
}
});
}
result.setToolTipText(sp.getDescription());
result.add(new JLabel(ap.getShortName() + ": "), BorderLayout.LINE_START);
result.add(comboBox, BorderLayout.CENTER);
result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) comboBox.getPreferredSize().getHeight()));
}
else if (ap instanceof ProbabilityDistributionProperty)
{
result = new LabeledPanel(ap.getShortName());
result.setLayout(new BorderLayout());
final ProbabilityDistributionProperty pdp = (ProbabilityDistributionProperty) ap;
final ProbabilityDistributionEditor pdpe = new ProbabilityDistributionEditor(pdp.getElementNames(), pdp.getValue());
pdpe.addPropertyChangeListener(new PropertyChangeListener()
{
@Override
public void propertyChange(final PropertyChangeEvent arg0)
{
try
{
pdp.setValue(pdpe.getProbabilities());
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
});
result.add(pdpe, BorderLayout.LINE_END);
result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) new JLabel("ABC").getPreferredSize().getHeight()));
result.setToolTipText(pdp.getDescription());
}
else if (ap instanceof IntegerProperty)
{
final IntegerProperty ip = (IntegerProperty) ap;
result = new LabeledPanel(ap.getShortName());
result.setLayout(new BorderLayout());
final JSlider slider = new JSlider();
slider.setMaximum(ip.getMaximumValue());
slider.setMinimum(ip.getMinimumValue());
slider.setValue(ip.getValue());
slider.setPaintTicks(true);
final JLabel currentValue =
new JLabel(String.format(DefaultLocale.getLocale(), ip.getFormatString(), ip.getValue()),
SwingConstants.RIGHT);
slider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(final ChangeEvent changeEvent)
{
int value = slider.getValue();
currentValue.setText(String.format(DefaultLocale.getLocale(), ip.getFormatString(), value));
if (slider.getValueIsAdjusting())
{
return;
}
try
{
ip.setValue(value);
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
});
result.setToolTipText(ap.getDescription());
result.add(slider, BorderLayout.CENTER);
result.add(currentValue, BorderLayout.SOUTH);
result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) slider.getPreferredSize().getHeight()));
}
else if (ap instanceof ContinuousProperty)
{
final ContinuousProperty cp = (ContinuousProperty) ap;
result = new LabeledPanel(ap.getShortName());
result.setLayout(new BorderLayout());
final JSlider slider = new JSlider();
final int useSteps = 1000;
slider.setMaximum(useSteps);
slider.setMinimum(0);
slider.setValue((int) (useSteps * (cp.getValue() - cp.getMinimumValue()) / (cp.getMaximumValue() - cp
.getMinimumValue())));
final JLabel currentValue =
new JLabel(String.format(DefaultLocale.getLocale(), cp.getFormatString(), cp.getValue()),
SwingConstants.RIGHT);
slider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(final ChangeEvent changeEvent)
{
double value =
slider.getValue() * (cp.getMaximumValue() - cp.getMinimumValue()) / useSteps + cp.getMinimumValue();
currentValue.setText(String.format(DefaultLocale.getLocale(), cp.getFormatString(), value));
if (slider.getValueIsAdjusting())
{
return;
}
try
{
cp.setValue(value);
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
});
result.setToolTipText(ap.getDescription());
result.add(slider, BorderLayout.CENTER);
result.add(currentValue, BorderLayout.SOUTH);
result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) slider.getPreferredSize().getHeight() * 4));
}
else if (ap instanceof BooleanProperty)
{
final BooleanProperty bp = (BooleanProperty) ap;
result = new JPanel(new BorderLayout());
final JCheckBox checkBox = new JCheckBox(bp.getShortName(), bp.getValue());
checkBox.setToolTipText(bp.getDescription());
checkBox.setEnabled(!bp.isReadOnly());
checkBox.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(final ChangeEvent arg0)
{
try
{
bp.setValue(checkBox.isSelected());
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
});
JPanel filler = new JPanel();
filler.setPreferredSize(new Dimension(0, (int) checkBox.getPreferredSize().getHeight()));
result.add(checkBox, BorderLayout.CENTER);
result.add(filler, BorderLayout.LINE_END);
}
else if (ap instanceof CompoundProperty)
{
CompoundProperty cp = (CompoundProperty) ap;
result = new LabeledPanel(ap.getShortName());
result.setLayout(new BoxLayout(result, BoxLayout.Y_AXIS));
for (Property> subProperty : cp.displayOrderedValue())
{
result.add(makePropertyEditor(subProperty));
}
}
else if (ap instanceof StringProperty)
{
StringProperty sp = (StringProperty) ap;
result = new LabeledPanel(sp.getShortName());
result.setLayout(new BorderLayout());
JTextField textField = new JTextField(sp.getValue(), 30);
// TODO add a listener that detects editing
textField.getDocument().addDocumentListener(new DocumentListener()
{
@Override
public void insertUpdate(final DocumentEvent e)
{
try
{
sp.setValue(textField.getText());
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
@Override
public void removeUpdate(final DocumentEvent e)
{
try
{
sp.setValue(textField.getText());
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
@Override
public void changedUpdate(final DocumentEvent e)
{
try
{
sp.setValue(textField.getText());
}
catch (PropertyException exception)
{
exception.printStackTrace();
}
}
});
result.add(textField, BorderLayout.CENTER);
}
else
{
throw new Error("Unhandled property: " + ap.getDescription());
}
return result;
}
}
/** JRadioButton that also stores a WrappableAnimation. */
class CleverRadioButton extends JRadioButton
{
/** */
private static final long serialVersionUID = 20141217L;
/** The WrappableAnimation. */
private final WrappableAnimation animation;
/**
* Construct a JRadioButton that also stores a WrappableAnimation.
* @param animation WrappableAnimation; the simulation to run if this radio button is selected and the start simulation
* button is clicked
*/
CleverRadioButton(final WrappableAnimation animation)
{
super(animation.shortName());
this.animation = animation;
}
/**
* Retrieve the simulation.
* @return WrappableAnimation
*/
public final WrappableAnimation getAnimation()
{
return this.animation;
}
}