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