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