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