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