View Javadoc
1   package org.opentrafficsim.demo.carFollowing;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Component;
5   import java.awt.Dimension;
6   import java.awt.Frame;
7   import java.awt.GridBagConstraints;
8   import java.awt.GridBagLayout;
9   import java.awt.Insets;
10  import java.awt.Toolkit;
11  import java.awt.event.ActionEvent;
12  import java.awt.event.ActionListener;
13  import java.awt.event.ItemEvent;
14  import java.awt.event.ItemListener;
15  import java.beans.PropertyChangeEvent;
16  import java.beans.PropertyChangeListener;
17  import java.util.ArrayList;
18  
19  import javax.naming.NamingException;
20  import javax.swing.BoxLayout;
21  import javax.swing.ButtonGroup;
22  import javax.swing.JButton;
23  import javax.swing.JCheckBox;
24  import javax.swing.JComboBox;
25  import javax.swing.JFrame;
26  import javax.swing.JLabel;
27  import javax.swing.JPanel;
28  import javax.swing.JRadioButton;
29  import javax.swing.JScrollBar;
30  import javax.swing.JScrollPane;
31  import javax.swing.JSlider;
32  import javax.swing.ScrollPaneConstants;
33  import javax.swing.SwingConstants;
34  import javax.swing.SwingUtilities;
35  import javax.swing.event.ChangeEvent;
36  import javax.swing.event.ChangeListener;
37  
38  import nl.tudelft.simulation.dsol.SimRuntimeException;
39  
40  import org.djunits.locale.DefaultLocale;
41  import org.djunits.unit.UNITS;
42  import org.djunits.value.vdouble.scalar.Acceleration;
43  import org.djunits.value.vdouble.scalar.Duration;
44  import org.djunits.value.vdouble.scalar.Length;
45  import org.djunits.value.vdouble.scalar.Time;
46  import org.opentrafficsim.core.network.NetworkException;
47  import org.opentrafficsim.gui.LabeledPanel;
48  import org.opentrafficsim.gui.ProbabilityDistributionEditor;
49  import org.opentrafficsim.gui.SimulatorFrame;
50  import org.opentrafficsim.simulationengine.OTSSimulationException;
51  import org.opentrafficsim.simulationengine.WrappableAnimation;
52  import org.opentrafficsim.simulationengine.properties.AbstractProperty;
53  import org.opentrafficsim.simulationengine.properties.BooleanProperty;
54  import org.opentrafficsim.simulationengine.properties.CompoundProperty;
55  import org.opentrafficsim.simulationengine.properties.ContinuousProperty;
56  import org.opentrafficsim.simulationengine.properties.IDMPropertySet;
57  import org.opentrafficsim.simulationengine.properties.IntegerProperty;
58  import org.opentrafficsim.simulationengine.properties.ProbabilityDistributionProperty;
59  import org.opentrafficsim.simulationengine.properties.PropertyException;
60  import org.opentrafficsim.simulationengine.properties.SelectionProperty;
61  
62  /**
63   * Several demos in one application.
64   * <p>
65   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
66   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
67   * <p>
68   * $LastChangedDate: 2016-05-28 11:35:05 +0200 (Sat, 28 May 2016) $, @version $Revision: 2052 $, by $Author: averbraeck $,
69   * initial version 17 dec. 2014 <br>
70   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
71   */
72  public class SuperDemo implements UNITS
73  {
74      /** The JPanel that holds the user settable properties. */
75      private JPanel propertyPanel;
76  
77      /** The JPanel that holds the simulation selection radio buttons. */
78      @SuppressWarnings("checkstyle:visibilitymodifier")
79      protected JPanel simulationSelection;
80  
81      /** Properties of the currently selected demonstration. */
82      @SuppressWarnings("checkstyle:visibilitymodifier")
83      protected ArrayList<AbstractProperty<?>> activeProperties = null;
84  
85      /**
86       * Start the application.
87       * @param args String[]; the command line arguments (not used)
88       */
89      public static void main(final String[] args)
90      {
91          SwingUtilities.invokeLater(new Runnable()
92          {
93              @Override
94              public void run()
95              {
96                  try
97                  {
98                      JFrame frame = new SimulatorFrame("Open Traffic Simulator Demonstrations", new SuperDemo().buildGUI());
99                      frame.setExtendedState(frame.getExtendedState() & ~Frame.MAXIMIZED_BOTH);
100                     // frame.setExtendedState(frame.getExtendedState() | Frame.MAXIMIZED_VERT);
101                     // The code above does not work; the code below does work. Code found on
102                     // http://stackoverflow.com/questions/5195634/how-to-maximize-a-the-height-of-a-jframe
103                     Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration());
104                     int taskHeight = screenInsets.bottom;
105                     Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
106                     frame.setSize(d.width / 2, d.height - taskHeight);
107                     frame.setLocation(0, 0);
108                 }
109                 catch (PropertyException exception)
110                 {
111                     exception.printStackTrace();
112                 }
113             }
114         });
115     }
116 
117     /**
118      * Build the GUI.
119      * @return JPanel; the JPanel that holds the application
120      * @throws PropertyException when one of the demonstrations has a user modified property with the empty string as key
121      */
122     public final JPanel buildGUI() throws PropertyException
123     {
124         final JPanel mainPanel = new JPanel(new BorderLayout());
125         // Ensure that the window does not shrink into (almost) nothingness when un-maximized
126         mainPanel.setPreferredSize(new Dimension(800, 800));
127         final ArrayList<WrappableAnimation> demonstrations = new ArrayList<WrappableAnimation>();
128         demonstrations.add(new Straight());
129         demonstrations.add(new SequentialLanes());
130         demonstrations.add(new CircularLane());
131         demonstrations.add(new CircularRoad());
132         demonstrations.add(new XMLNetworks());
133         demonstrations.add(new OpenStreetMap());
134         // final JPanel left = new LabeledPanel("Simulation Settings");
135         this.simulationSelection = new LabeledPanel("Network");
136         this.simulationSelection.setLayout(new GridBagLayout());
137         GridBagConstraints gbcSimulation = new GridBagConstraints();
138         gbcSimulation.gridx = 0;
139         gbcSimulation.gridy = -1;
140         gbcSimulation.anchor = GridBagConstraints.LINE_START;
141         gbcSimulation.weighty = 0;
142         gbcSimulation.fill = GridBagConstraints.HORIZONTAL;
143         final JPanel left = new JPanel(new GridBagLayout());
144         GridBagConstraints gbcLeft = new GridBagConstraints();
145         gbcLeft.gridx = 0;
146         gbcLeft.gridy = 0;
147         gbcLeft.anchor = GridBagConstraints.LINE_START;
148         gbcLeft.weighty = 0;
149         gbcLeft.fill = GridBagConstraints.HORIZONTAL;
150         final JLabel description = new JLabel("Please select a demonstration from the buttons on the left");
151         this.propertyPanel = new JPanel();
152         this.propertyPanel.setLayout(new BoxLayout(this.propertyPanel, BoxLayout.Y_AXIS));
153         rebuildPropertyPanel(new ArrayList<AbstractProperty<?>>());
154         final JPanel descriptionPanel = new LabeledPanel("Description");
155         descriptionPanel.add(description);
156         mainPanel.add(descriptionPanel, BorderLayout.CENTER);
157         final JButton startButton = new JButton("Start simulation");
158         ButtonGroup buttonGroup = new ButtonGroup();
159         for (final WrappableAnimation demo : demonstrations)
160         {
161             CleverRadioButton button = new CleverRadioButton(demo);
162             // button.setPreferredSize(new Dimension(Integer.MAX_VALUE, button.getPreferredSize().height));
163             button.addActionListener(new ActionListener()
164             {
165                 @Override
166                 public void actionPerformed(final ActionEvent e)
167                 {
168                     // System.out.println("selected " + demo.shortName());
169                     // Clear out the main panel and put the description of the selected simulator in it.
170                     mainPanel.remove(((BorderLayout) mainPanel.getLayout()).getLayoutComponent(BorderLayout.CENTER));
171                     mainPanel.add(description, BorderLayout.CENTER);
172                     description.setText(demo.description());
173                     startButton.setEnabled(true);
174                     startButton.setVisible(true);
175                     rebuildPropertyPanel(demo.getProperties());
176                 }
177 
178             });
179             buttonGroup.add(button);
180             gbcSimulation.gridy++;
181             this.simulationSelection.add(button, gbcSimulation);
182         }
183         JScrollPane scrollPane = new JScrollPane(left);
184         scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
185         mainPanel.add(scrollPane, BorderLayout.LINE_START);
186         startButton.setEnabled(false);
187         startButton.setVisible(false);
188         startButton.addActionListener(new ActionListener()
189         {
190             @Override
191             public void actionPerformed(final ActionEvent e)
192             {
193                 WrappableAnimation simulation = null;
194                 for (Component c : SuperDemo.this.simulationSelection.getComponents())
195                 {
196                     if (c instanceof CleverRadioButton)
197                     {
198                         CleverRadioButton crb = (CleverRadioButton) c;
199                         if (crb.isSelected())
200                         {
201                             simulation = crb.getAnimation();
202                         }
203                     }
204                 }
205 
206                 if (null == simulation)
207                 {
208                     throw new Error("Cannot find a selected button");
209                 }
210 
211                 try
212                 {
213                     System.out.println("Active properties: " + SuperDemo.this.activeProperties);
214                     simulation.buildAnimator(new Time(0.0, SECOND), new Duration(0.0, SECOND), new Duration(3600.0, SECOND),
215                             SuperDemo.this.activeProperties, null, false);
216                 }
217                 catch (SimRuntimeException | NetworkException | NamingException | OTSSimulationException | PropertyException exception)
218                 {
219                     exception.printStackTrace();
220                 }
221             }
222         });
223         gbcLeft.gridy++;
224         left.add(this.propertyPanel, gbcLeft);
225         gbcLeft.gridy++;
226         gbcLeft.weighty = 1;
227         JPanel filler = new JPanel();
228         filler.setMinimumSize(new Dimension(400, 0));
229         filler.setPreferredSize(new Dimension(400, 0));
230         left.add(filler, gbcLeft); // add a filler that also enforces a reasonable width
231         gbcLeft.weighty = 0;
232         gbcLeft.anchor = GridBagConstraints.SOUTH;
233         left.add(startButton, gbcLeft);
234         JPanel rightFiller = new JPanel();
235         rightFiller.setPreferredSize(new Dimension((int) new JScrollBar().getPreferredSize().getWidth(), 0));
236         gbcLeft.gridx = 2;
237         left.add(rightFiller, gbcLeft);
238         return mainPanel;
239     }
240 
241     /**
242      * Regenerate the contents of the propertyPanel.
243      * @param properties ArrayList&lt;AbstractProperty&lt;?&gt;&gt;; the demo-specific properties to display
244      */
245     final void rebuildPropertyPanel(final ArrayList<AbstractProperty<?>> properties)
246     {
247         this.propertyPanel.removeAll();
248         try
249         {
250             CompoundProperty simulationSettings =
251                     new CompoundProperty("SimulationSettings", "Simulation settings",
252                             "Select the simulation network and traffic composition", null, false, 0);
253             /*
254              * This is ugly, but it gets the job done... Insert a dummy property at the top and later replace the property
255              * editor for the dummy property by the simulationSelection JPanel.
256              */
257             BooleanProperty dummy = new BooleanProperty("Dummy", "Dummy", "Dummy", false, false, 0);
258             simulationSettings.add(dummy);
259             if (properties.size() > 0)
260             {
261                 while (true)
262                 {
263                     boolean movedAny = false;
264                     // Move the properties that has display priority < 100 into the simulationSettings group.
265                     for (AbstractProperty<?> ap : properties)
266                     {
267                         if (ap.getDisplayPriority() < 100)
268                         {
269                             // Move it into the simulationSettings group
270                             simulationSettings.add(ap);
271                             properties.remove(ap);
272                             movedAny = true;
273                             break;
274                         }
275                     }
276                     if (!movedAny)
277                     {
278                         break;
279                     }
280                 }
281                 simulationSettings.add(new ProbabilityDistributionProperty("TrafficComposition", "Traffic composition",
282                         "<html>Mix of passenger cars and trucks</html>", new String[] { "passenger car", "truck" },
283                         new Double[] { 0.8, 0.2 }, false, 5));
284                 CompoundProperty modelSelection =
285                         new CompoundProperty("ModelSelection", "Model selection", "Modeling specific settings", null, false,
286                                 300);
287                 modelSelection.add(new SelectionProperty("SimulationScale", "Simulation scale",
288                         "Level of detail of the simulation", new String[] { "Micro", "Macro", "Meta" }, 0, true, 0));
289                 modelSelection.add(new SelectionProperty("CarFollowingModel", "Car following model",
290                         "<html>The car following model determines "
291                                 + "the acceleration that a vehicle will make taking into account "
292                                 + "nearby vehicles, infrastructural restrictions (e.g. speed limit, "
293                                 + "curvature of the road) capabilities of the vehicle and personality "
294                                 + "of the driver.</html>", new String[] { "IDM", "IDM+" }, 1, false, 1));
295                 modelSelection.add(IDMPropertySet.makeIDMPropertySet("IDMCar", "Car",
296                         new Acceleration(1.0, METER_PER_SECOND_2), new Acceleration(1.5, METER_PER_SECOND_2), new Length(2.0,
297                                 METER), new Duration(1.0, SECOND), 2));
298                 modelSelection.add(IDMPropertySet.makeIDMPropertySet("IDMTruck", "Truck", new Acceleration(0.5,
299                         METER_PER_SECOND_2), new Acceleration(1.25, METER_PER_SECOND_2), new Length(2.0, METER), new Duration(
300                         1.0, SECOND), 3));
301                 properties.add(properties.size() > 0 ? 1 : 0, modelSelection);
302             }
303             properties.add(0, simulationSettings);
304             boolean fixedDummy = false;
305             for (AbstractProperty<?> p : new CompoundProperty("", "", "", properties, false, 0).displayOrderedValue())
306             {
307                 JPanel propertySubPanel = makePropertyEditor(p);
308                 if (!fixedDummy)
309                 {
310                     // Replace the dummy property editor by the simulationSelection JPanel.
311                     JPanel subPanel = (JPanel) propertySubPanel.getComponent(0);
312                     subPanel.removeAll();
313                     subPanel.add(this.simulationSelection);
314                     fixedDummy = true;
315                 }
316                 this.propertyPanel.add(propertySubPanel);
317             }
318             simulationSettings.remove(dummy);
319             SuperDemo.this.activeProperties = properties;
320         }
321         catch (PropertyException exception)
322         {
323             exception.printStackTrace();
324         }
325     }
326 
327     /**
328      * Create a graphical editor for an AbstractProperty.
329      * @param ap AbstractProperty; the abstract property for which an editor must be created
330      * @return JPanel
331      */
332     final JPanel makePropertyEditor(final AbstractProperty<?> ap)
333     {
334         JPanel result;
335         if (ap instanceof SelectionProperty)
336         {
337             result = new JPanel();
338             result.setLayout(new BorderLayout());
339             final SelectionProperty sp = (SelectionProperty) ap;
340             final JComboBox<String> comboBox = new JComboBox<String>(sp.getOptionNames());
341             comboBox.setSelectedItem(sp.getValue());
342             comboBox.setToolTipText(sp.getDescription());
343             comboBox.addItemListener(new ItemListener()
344             {
345                 @Override
346                 public void itemStateChanged(final ItemEvent itemEvent)
347                 {
348                     if (itemEvent.getStateChange() == ItemEvent.SELECTED)
349                     {
350                         String itemText = (String) itemEvent.getItem();
351                         try
352                         {
353                             sp.setValue(itemText);
354                         }
355                         catch (PropertyException exception)
356                         {
357                             exception.printStackTrace();
358                         }
359                     }
360                 }
361             });
362             if (ap.isReadOnly())
363             {
364                 comboBox.removeItemListener(comboBox.getItemListeners()[0]);
365                 comboBox.addActionListener(new ActionListener()
366                 {
367 
368                     @Override
369                     public void actionPerformed(final ActionEvent actionEvent)
370                     {
371                         if (comboBox.getSelectedIndex() != 0)
372                         {
373                             comboBox.setSelectedIndex(0);
374                         }
375                     }
376                 });
377             }
378             result.setToolTipText(sp.getDescription());
379             result.add(new JLabel(ap.getShortName() + ": "), BorderLayout.LINE_START);
380             result.add(comboBox, BorderLayout.CENTER);
381             result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) comboBox.getPreferredSize().getHeight()));
382         }
383         else if (ap instanceof ProbabilityDistributionProperty)
384         {
385             result = new LabeledPanel(ap.getShortName());
386             result.setLayout(new BorderLayout());
387             final ProbabilityDistributionProperty pdp = (ProbabilityDistributionProperty) ap;
388             final ProbabilityDistributionEditor pdpe = new ProbabilityDistributionEditor(pdp.getElementNames(), pdp.getValue());
389             pdpe.addPropertyChangeListener(new PropertyChangeListener()
390             {
391                 @Override
392                 public void propertyChange(final PropertyChangeEvent arg0)
393                 {
394                     try
395                     {
396                         pdp.setValue(pdpe.getProbabilities());
397                     }
398                     catch (PropertyException exception)
399                     {
400                         exception.printStackTrace();
401                     }
402                 }
403 
404             });
405             result.add(pdpe, BorderLayout.LINE_END);
406             result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) new JLabel("ABC").getPreferredSize().getHeight()));
407             result.setToolTipText(pdp.getDescription());
408         }
409         else if (ap instanceof IntegerProperty)
410         {
411             final IntegerProperty ip = (IntegerProperty) ap;
412             result = new LabeledPanel(ap.getShortName());
413             result.setLayout(new BorderLayout());
414             final JSlider slider = new JSlider();
415             slider.setMaximum(ip.getMaximumValue());
416             slider.setMinimum(ip.getMinimumValue());
417             slider.setValue(ip.getValue());
418             slider.setPaintTicks(true);
419             final JLabel currentValue =
420                     new JLabel(String.format(DefaultLocale.getLocale(), ip.getFormatString(), ip.getValue()),
421                             SwingConstants.RIGHT);
422             slider.addChangeListener(new ChangeListener()
423             {
424                 @Override
425                 public void stateChanged(final ChangeEvent changeEvent)
426                 {
427                     int value = slider.getValue();
428                     currentValue.setText(String.format(DefaultLocale.getLocale(), ip.getFormatString(), value));
429                     if (slider.getValueIsAdjusting())
430                     {
431                         return;
432                     }
433                     try
434                     {
435                         ip.setValue(value);
436                     }
437                     catch (PropertyException exception)
438                     {
439                         exception.printStackTrace();
440                     }
441                 }
442             });
443             result.setToolTipText(ap.getDescription());
444             result.add(slider, BorderLayout.CENTER);
445             result.add(currentValue, BorderLayout.SOUTH);
446             result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) slider.getPreferredSize().getHeight()));
447         }
448         else if (ap instanceof ContinuousProperty)
449         {
450             final ContinuousProperty cp = (ContinuousProperty) ap;
451             result = new LabeledPanel(ap.getShortName());
452             result.setLayout(new BorderLayout());
453             final JSlider slider = new JSlider();
454             final int useSteps = 1000;
455             slider.setMaximum(useSteps);
456             slider.setMinimum(0);
457             slider.setValue((int) (useSteps * (cp.getValue() - cp.getMinimumValue()) / (cp.getMaximumValue() - cp
458                     .getMinimumValue())));
459             final JLabel currentValue =
460                     new JLabel(String.format(DefaultLocale.getLocale(), cp.getFormatString(), cp.getValue()),
461                             SwingConstants.RIGHT);
462             slider.addChangeListener(new ChangeListener()
463             {
464                 @Override
465                 public void stateChanged(final ChangeEvent changeEvent)
466                 {
467                     double value =
468                             slider.getValue() * (cp.getMaximumValue() - cp.getMinimumValue()) / useSteps + cp.getMinimumValue();
469                     currentValue.setText(String.format(DefaultLocale.getLocale(), cp.getFormatString(), value));
470                     if (slider.getValueIsAdjusting())
471                     {
472                         return;
473                     }
474                     try
475                     {
476                         cp.setValue(value);
477                     }
478                     catch (PropertyException exception)
479                     {
480                         exception.printStackTrace();
481                     }
482                 }
483             });
484             result.setToolTipText(ap.getDescription());
485             result.add(slider, BorderLayout.CENTER);
486             result.add(currentValue, BorderLayout.SOUTH);
487             result.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) slider.getPreferredSize().getHeight() * 4));
488         }
489         else if (ap instanceof BooleanProperty)
490         {
491             final BooleanProperty bp = (BooleanProperty) ap;
492             result = new JPanel(new BorderLayout());
493             final JCheckBox checkBox = new JCheckBox(bp.getShortName(), bp.getValue());
494             checkBox.setToolTipText(bp.getDescription());
495             checkBox.setEnabled(!bp.isReadOnly());
496             checkBox.addChangeListener(new ChangeListener()
497             {
498                 @Override
499                 public void stateChanged(final ChangeEvent arg0)
500                 {
501                     try
502                     {
503                         bp.setValue(checkBox.isSelected());
504                     }
505                     catch (PropertyException exception)
506                     {
507                         exception.printStackTrace();
508                     }
509                 }
510             });
511             JPanel filler = new JPanel();
512             filler.setPreferredSize(new Dimension(0, (int) checkBox.getPreferredSize().getHeight()));
513             result.add(checkBox, BorderLayout.CENTER);
514             result.add(filler, BorderLayout.LINE_END);
515         }
516         else if (ap instanceof CompoundProperty)
517         {
518             CompoundProperty cp = (CompoundProperty) ap;
519             result = new LabeledPanel(ap.getShortName());
520             result.setLayout(new BoxLayout(result, BoxLayout.Y_AXIS));
521             for (AbstractProperty<?> subProperty : cp.displayOrderedValue())
522             {
523                 result.add(makePropertyEditor(subProperty));
524             }
525         }
526         else
527         {
528             throw new Error("Unhandled property: " + ap.getDescription());
529         }
530         return result;
531     }
532 }
533 
534 /** JRadioButton that also stores a WrappableAnimation. */
535 class CleverRadioButton extends JRadioButton
536 {
537     /** */
538     private static final long serialVersionUID = 20141217L;
539 
540     /** The WrappableAnimation. */
541     private final WrappableAnimation simulation;
542 
543     /**
544      * Construct a JRadioButton that also stores a WrappableAnimation.
545      * @param animation WrappableAnimation; the simulation to run if this radio button is selected and the start simulation
546      *            button is clicked
547      */
548     CleverRadioButton(final WrappableAnimation animation)
549     {
550         super(animation.shortName());
551         this.simulation = animation;
552     }
553 
554     /**
555      * Retrieve the simulation.
556      * @return WrappableAnimation
557      */
558     public final WrappableAnimation getAnimation()
559     {
560         return this.simulation;
561     }
562 }