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