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