OTSControlPanel.java
- package org.opentrafficsim.gui;
- import java.awt.Container;
- import java.awt.Dimension;
- import java.awt.FlowLayout;
- import java.awt.Font;
- import java.awt.Rectangle;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.awt.event.WindowEvent;
- import java.awt.event.WindowListener;
- import java.beans.PropertyChangeEvent;
- import java.beans.PropertyChangeListener;
- import java.io.Serializable;
- import java.rmi.RemoteException;
- import java.text.DecimalFormat;
- import java.text.NumberFormat;
- import java.text.ParseException;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.Hashtable;
- import java.util.Map;
- import java.util.Timer;
- import java.util.TimerTask;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import javax.swing.BoxLayout;
- import javax.swing.ImageIcon;
- import javax.swing.JButton;
- import javax.swing.JFormattedTextField;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JPanel;
- import javax.swing.JSlider;
- import javax.swing.SwingConstants;
- import javax.swing.SwingUtilities;
- import javax.swing.WindowConstants;
- import javax.swing.event.ChangeEvent;
- import javax.swing.event.ChangeListener;
- import javax.swing.text.DefaultFormatter;
- import javax.swing.text.MaskFormatter;
- import org.djunits.unit.TimeUnit;
- import org.djunits.value.vdouble.scalar.Duration;
- import org.djunits.value.vdouble.scalar.Time;
- import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
- import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
- import org.opentrafficsim.simulationengine.WrappableAnimation;
- import nl.tudelft.simulation.dsol.SimRuntimeException;
- import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
- import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
- import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeClock;
- import nl.tudelft.simulation.dsol.simulators.DEVSSimulator;
- import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
- import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
- import nl.tudelft.simulation.event.EventInterface;
- import nl.tudelft.simulation.event.EventListenerInterface;
- import nl.tudelft.simulation.language.io.URLResource;
- /**
- * Peter's improved simulation control panel.
- * <p>
- * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
- * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
- * <p>
- * $LastChangedDate: 2016-10-16 14:55:54 +0200 (Sun, 16 Oct 2016) $, @version $Revision: 2386 $, by $Author: averbraeck $,
- * initial version 11 dec. 2014 <br>
- * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
- * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
- */
- public class OTSControlPanel extends JPanel implements ActionListener, PropertyChangeListener, WindowListener,
- EventListenerInterface
- {
- /** */
- private static final long serialVersionUID = 20150617L;
- /** The simulator. */
- private OTSDEVSSimulatorInterface simulator;
- /** The WrappableAnimation (needed for restart operation). */
- private final WrappableAnimation wrappableAnimation;
- /** Logger. */
- private final Logger logger;
- /** The clock. */
- private final ClockPanel clockPanel;
- /** The time warp control. */
- private final TimeWarpPanel timeWarpPanel;
- /** The control buttons. */
- private final ArrayList<JButton> buttons = new ArrayList<JButton>();
- /** Font used to display the clock and the stop time. */
- private final Font timeFont = new Font("SansSerif", Font.BOLD, 18);
- /** The TimeEdit that lets the user set a time when the simulation will be stopped. */
- private final TimeEdit timeEdit;
- /** The currently registered stop at event. */
- private SimEvent<OTSSimTimeDouble> stopAtEvent = null;
- /** Has the window close handler been registered? */
- protected boolean closeHandlerRegistered = false;
- /** Has cleanup taken place? */
- private boolean isCleanUp = false;
- /**
- * Decorate a SimpleSimulator with a different set of control buttons.
- * @param simulator SimpleSimulator; the simulator
- * @param wrappableAnimation WrappableAnimation; if non-null, the restart button should work
- * @throws RemoteException when simulator cannot be accessed for listener attachment
- */
- public OTSControlPanel(final OTSDEVSSimulatorInterface simulator, final WrappableAnimation wrappableAnimation)
- throws RemoteException
- {
- this.simulator = simulator;
- this.wrappableAnimation = wrappableAnimation;
- this.logger = Logger.getLogger("nl.tudelft.opentrafficsim");
- this.setLayout(new FlowLayout(FlowLayout.LEFT));
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
- buttonPanel.add(makeButton("stepButton", "/Last_recor.png", "Step", "Execute one event", true));
- buttonPanel.add(makeButton("nextTimeButton", "/NextTrack.png", "NextTime",
- "Execute all events scheduled for the current time", true));
- buttonPanel.add(makeButton("runPauseButton", "/Play.png", "RunPause", "XXX", true));
- this.timeWarpPanel = new TimeWarpPanel(0.1, 1000, 1, 3, simulator);
- buttonPanel.add(this.timeWarpPanel);
- buttonPanel.add(makeButton("resetButton", "/Undo.png", "Reset", "Reset the simulation", false));
- this.clockPanel = new ClockPanel();
- buttonPanel.add(this.clockPanel);
- this.timeEdit = new TimeEdit(new Time(0, TimeUnit.SECOND));
- this.timeEdit.addPropertyChangeListener("value", this);
- buttonPanel.add(this.timeEdit);
- this.add(buttonPanel);
- fixButtons();
- installWindowCloseHandler();
- this.simulator.addListener(this, SimulatorInterface.END_OF_REPLICATION_EVENT);
- this.simulator.addListener(this, SimulatorInterface.START_EVENT);
- this.simulator.addListener(this, SimulatorInterface.STOP_EVENT);
- this.simulator.addListener(this, DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT);
- }
- /**
- * Create a button.
- * @param name String; name of the button
- * @param iconPath String; path to the resource
- * @param actionCommand String; the action command
- * @param toolTipText String; the hint to show when the mouse hovers over the button
- * @param enabled boolean; true if the new button must initially be enable; false if it must initially be disabled
- * @return JButton
- */
- private JButton makeButton(final String name, final String iconPath, final String actionCommand, final String toolTipText,
- final boolean enabled)
- {
- // JButton result = new JButton(new ImageIcon(this.getClass().getResource(iconPath)));
- JButton result = new JButton(new ImageIcon(URLResource.getResource(iconPath)));
- result.setName(name);
- result.setEnabled(enabled);
- result.setActionCommand(actionCommand);
- result.setToolTipText(toolTipText);
- result.addActionListener(this);
- this.buttons.add(result);
- return result;
- }
- /**
- * Construct and schedule a SimEvent using a Time to specify the execution time.
- * @param executionTime Time; the time at which the event must happen
- * @param priority short; should be between <cite>SimEventInterface.MAX_PRIORITY</cite> and
- * <cite>SimEventInterface.MIN_PRIORITY</cite>; most normal events should use
- * <cite>SimEventInterface.NORMAL_PRIORITY</cite>
- * @param source Object; the object that creates/schedules the event
- * @param eventTarget Object; the object that must execute the event
- * @param method String; the name of the method of <code>target</code> that must execute the event
- * @param args Object[]; the arguments of the <code>method</code> that must execute the event
- * @return SimEvent<OTSSimTimeDouble>; the event that was scheduled (the caller should save this if a need to cancel
- * the event may arise later)
- * @throws SimRuntimeException when the <code>executionTime</code> is in the past
- */
- private SimEvent<OTSSimTimeDouble> scheduleEvent(final Time executionTime, final short priority, final Object source,
- final Object eventTarget, final String method, final Object[] args) throws SimRuntimeException
- {
- SimEvent<OTSSimTimeDouble> simEvent =
- new SimEvent<OTSSimTimeDouble>(new OTSSimTimeDouble(new Time(executionTime.getSI(), TimeUnit.SECOND)),
- priority, source, eventTarget, method, args);
- this.simulator.scheduleEvent(simEvent);
- return simEvent;
- }
- /**
- * Install a handler for the window closed event that stops the simulator (if it is running).
- */
- public final void installWindowCloseHandler()
- {
- if (this.closeHandlerRegistered)
- {
- return;
- }
- // make sure the root frame gets disposed of when the closing X icon is pressed.
- new DisposeOnCloseThread(this).start();
- }
- /** Install the dispose on close when the OTSControlPanel is registered as part of a frame. */
- protected class DisposeOnCloseThread extends Thread
- {
- /** The current container. */
- private OTSControlPanel panel;
- /**
- * @param panel the OTSControlpanel container.
- */
- public DisposeOnCloseThread(final OTSControlPanel panel)
- {
- super();
- this.panel = panel;
- }
- /** {@inheritDoc} */
- @Override
- public final void run()
- {
- Container root = this.panel;
- while (!(root instanceof JFrame))
- {
- try
- {
- Thread.sleep(10);
- }
- catch (InterruptedException exception)
- {
- // nothing to do
- }
- // Search towards the root of the Swing components until we find a JFrame
- root = this.panel;
- while (null != root.getParent() && !(root instanceof JFrame))
- {
- root = root.getParent();
- }
- }
- JFrame frame = (JFrame) root;
- frame.addWindowListener(this.panel);
- this.panel.closeHandlerRegistered = true;
- // frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "DisposeOnCloseThread [panel=" + this.panel + "]";
- }
- }
- /** {@inheritDoc} */
- @Override
- public final void actionPerformed(final ActionEvent actionEvent)
- {
- String actionCommand = actionEvent.getActionCommand();
- // System.out.println("actionCommand: " + actionCommand);
- try
- {
- if (actionCommand.equals("Step"))
- {
- if (getSimulator().isRunning())
- {
- getSimulator().stop();
- }
- this.simulator.step();
- }
- if (actionCommand.equals("RunPause"))
- {
- if (this.simulator.isRunning())
- {
- this.simulator.stop();
- }
- else if (getSimulator().getEventList().size() > 0)
- {
- this.simulator.start();
- }
- }
- if (actionCommand.equals("NextTime"))
- {
- if (getSimulator().isRunning())
- {
- getSimulator().stop();
- }
- double now = getSimulator().getSimulatorTime().getTime().getSI();
- // System.out.println("now is " + now);
- try
- {
- this.stopAtEvent =
- scheduleEvent(new Time(now, TimeUnit.SI), SimEventInterface.MIN_PRIORITY, this, this,
- "autoPauseSimulator", null);
- }
- catch (SimRuntimeException exception)
- {
- this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator", "Caught an exception "
- + "while trying to schedule an autoPauseSimulator event at the current simulator time");
- }
- this.simulator.start();
- }
- if (actionCommand.equals("Reset"))
- {
- if (getSimulator().isRunning())
- {
- getSimulator().stop();
- }
- if (null == OTSControlPanel.this.wrappableAnimation)
- {
- throw new RuntimeException("Do not know how to restart this simulation");
- }
- // find the JFrame position and dimensions
- Container root = OTSControlPanel.this;
- while (!(root instanceof JFrame))
- {
- root = root.getParent();
- }
- JFrame frame = (JFrame) root;
- Rectangle rect = frame.getBounds();
- frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
- frame.dispose();
- OTSControlPanel.this.cleanup();
- try
- {
- OTSControlPanel.this.wrappableAnimation.rebuildSimulator(rect);
- }
- catch (Exception exception)
- {
- exception.printStackTrace();
- }
- }
- fixButtons();
- }
- catch (Exception exception)
- {
- exception.printStackTrace();
- }
- }
- /**
- * clean up timers, contexts, threads, etc. that could prevent garbage collection.
- */
- private void cleanup()
- {
- if (!this.isCleanUp)
- {
- this.isCleanUp = true;
- try
- {
- if (this.simulator != null)
- {
- if (this.simulator.isRunning())
- {
- this.simulator.stop();
- }
- // unbind the old animation and statistics
- getSimulator().getReplication().getExperiment().removeFromContext(); // clean up the context
- getSimulator().cleanUp();
- }
- if (this.clockPanel != null)
- {
- this.clockPanel.cancelTimer(); // cancel the timer on the clock panel.
- }
- if (this.wrappableAnimation != null)
- {
- this.wrappableAnimation.stopTimersThreads(); // stop the linked timers and other threads
- }
- }
- catch (Throwable exception)
- {
- exception.printStackTrace();
- }
- }
- }
- /**
- * Update the enabled state of all the buttons.
- */
- protected final void fixButtons()
- {
- // System.out.println("FixButtons entered");
- final boolean moreWorkToDo = getSimulator().getEventList().size() > 0;
- for (JButton button : this.buttons)
- {
- final String actionCommand = button.getActionCommand();
- if (actionCommand.equals("Step"))
- {
- button.setEnabled(moreWorkToDo);
- }
- else if (actionCommand.equals("RunPause"))
- {
- button.setEnabled(moreWorkToDo);
- if (this.simulator.isRunning())
- {
- button.setToolTipText("Pause the simulation");
- button.setIcon(new ImageIcon(URLResource.getResource("/Pause.png")));
- }
- else
- {
- button.setToolTipText("Run the simulation at the indicated speed");
- button.setIcon(new ImageIcon(URLResource.getResource("/Play.png")));
- }
- button.setEnabled(moreWorkToDo);
- }
- else if (actionCommand.equals("NextTime"))
- {
- button.setEnabled(moreWorkToDo);
- }
- else if (actionCommand.equals("Reset"))
- {
- button.setEnabled(true); // FIXME: should be disabled when the simulator was just reset or initialized
- }
- else
- {
- this.logger.logp(Level.SEVERE, "ControlPanel", "fixButtons", "", new Exception("Unknown button?"));
- }
- }
- // System.out.println("FixButtons finishing");
- }
- /**
- * Pause the simulator.
- */
- public final void autoPauseSimulator()
- {
- if (getSimulator().isRunning())
- {
- getSimulator().stop();
- double currentTick = getSimulator().getSimulatorTime().getTime().getSI();
- double nextTick = getSimulator().getEventList().first().getAbsoluteExecutionTime().get().getSI();
- // System.out.println("currentTick is " + currentTick);
- // System.out.println("nextTick is " + nextTick);
- if (nextTick > currentTick)
- {
- // The clock is now just beyond where it was when the user requested the NextTime operation
- // Insert another autoPauseSimulator event just before what is now the time of the next event
- // and let the simulator time increment to that time
- // System.out.println("Re-Scheduling at " + nextTick);
- try
- {
- this.stopAtEvent =
- scheduleEvent(new Time(nextTick, TimeUnit.SI), SimEventInterface.MAX_PRIORITY, this, this,
- "autoPauseSimulator", null);
- getSimulator().start();
- }
- catch (SimRuntimeException exception)
- {
- this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator",
- "Caught an exception while trying to re-schedule an autoPauseEvent at the next real event");
- }
- }
- else
- {
- // System.out.println("Not re-scheduling");
- if (SwingUtilities.isEventDispatchThread())
- {
- // System.out.println("Already on EventDispatchThread");
- fixButtons();
- }
- else
- {
- try
- {
- // System.out.println("Current thread is NOT EventDispatchThread: " + Thread.currentThread());
- SwingUtilities.invokeAndWait(new Runnable()
- {
- @Override
- public void run()
- {
- // System.out.println("Runnable started");
- fixButtons();
- // System.out.println("Runnable finishing");
- }
- });
- }
- catch (Exception e)
- {
- if (e instanceof InterruptedException)
- {
- System.out.println("Caught " + e);
- // e.printStackTrace();
- }
- else
- {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- /** {@inheritDoc} */
- @Override
- public final void propertyChange(final PropertyChangeEvent evt)
- {
- // System.out.println("PropertyChanged: " + evt);
- if (null != this.stopAtEvent)
- {
- getSimulator().cancelEvent(this.stopAtEvent); // silently ignore false result
- this.stopAtEvent = null;
- }
- String newValue = (String) evt.getNewValue();
- String[] fields = newValue.split("[:\\.]");
- int hours = Integer.parseInt(fields[0]);
- int minutes = Integer.parseInt(fields[1]);
- int seconds = Integer.parseInt(fields[2]);
- int fraction = Integer.parseInt(fields[3]);
- double stopTime = hours * 3600 + minutes * 60 + seconds + fraction / 1000d;
- if (stopTime < getSimulator().getSimulatorTime().getTime().getSI())
- {
- return;
- }
- else
- {
- try
- {
- this.stopAtEvent =
- scheduleEvent(new Time(stopTime, TimeUnit.SECOND), SimEventInterface.MAX_PRIORITY, this, this,
- "autoPauseSimulator", null);
- }
- catch (SimRuntimeException exception)
- {
- this.logger.logp(Level.SEVERE, "ControlPanel", "propertyChange",
- "Caught an exception while trying to schedule an autoPauseSimulator event");
- }
- }
- }
- /**
- * @return simulator.
- */
- @SuppressWarnings("unchecked")
- public final DEVSSimulator<Time, Duration, OTSSimTimeDouble> getSimulator()
- {
- return (DEVSSimulator<Time, Duration, OTSSimTimeDouble>) this.simulator;
- }
- /** {@inheritDoc} */
- @Override
- public void windowOpened(final WindowEvent e)
- {
- // No action
- }
- /** {@inheritDoc} */
- @Override
- public final void windowClosing(final WindowEvent e)
- {
- if (this.simulator != null)
- {
- try
- {
- if (this.simulator.isRunning())
- {
- this.simulator.stop();
- }
- }
- catch (SimRuntimeException exception)
- {
- exception.printStackTrace();
- }
- }
- }
- /** {@inheritDoc} */
- @Override
- public final void windowClosed(final WindowEvent e)
- {
- cleanup();
- }
- /** {@inheritDoc} */
- @Override
- public final void windowIconified(final WindowEvent e)
- {
- // No action
- }
- /** {@inheritDoc} */
- @Override
- public final void windowDeiconified(final WindowEvent e)
- {
- // No action
- }
- /** {@inheritDoc} */
- @Override
- public final void windowActivated(final WindowEvent e)
- {
- // No action
- }
- /** {@inheritDoc} */
- @Override
- public final void windowDeactivated(final WindowEvent e)
- {
- // No action
- }
- /**
- * @return timeFont.
- */
- public final Font getTimeFont()
- {
- return this.timeFont;
- }
- /** JPanel that contains a JSider that uses a logarithmic scale. */
- static class TimeWarpPanel extends JPanel
- {
- /** */
- private static final long serialVersionUID = 20150408L;
- /** The JSlider that the user sees. */
- private final JSlider slider;
- /** The ratios used in each decade. */
- private final int[] ratios;
- /** The values at each tick. */
- private Map<Integer, Double> tickValues = new HashMap<>();
- /**
- * Construct a new TimeWarpPanel.
- * @param minimum double; the minimum value on the scale (the displayed scale may extend a little further than this
- * value)
- * @param maximum double; the maximum value on the scale (the displayed scale may extend a little further than this
- * value)
- * @param initialValue double; the initially selected value on the scale
- * @param ticksPerDecade int; the number of steps per decade
- * @param simulator SimpleSimulator; the simulator to change the speed of
- */
- TimeWarpPanel(final double minimum, final double maximum, final double initialValue, final int ticksPerDecade,
- final DEVSSimulatorInterface<?, ?, ?> simulator)
- {
- if (minimum <= 0 || minimum > initialValue || initialValue > maximum)
- {
- throw new RuntimeException("Bad (combination of) minimum, maximum and initialValue; "
- + "(restrictions: 0 < minimum <= initialValue <= maximum)");
- }
- switch (ticksPerDecade)
- {
- case 1:
- this.ratios = new int[] { 1 };
- break;
- case 2:
- this.ratios = new int[] { 1, 3 };
- break;
- case 3:
- this.ratios = new int[] { 1, 2, 5 };
- break;
- default:
- throw new RuntimeException("Bad ticksPerDecade value (must be 1, 2 or 3)");
- }
- int minimumTick = (int) Math.floor(Math.log10(minimum / initialValue) * ticksPerDecade);
- int maximumTick = (int) Math.ceil(Math.log10(maximum / initialValue) * ticksPerDecade);
- this.slider = new JSlider(SwingConstants.HORIZONTAL, minimumTick, maximumTick + 1, 0);
- this.slider.setPreferredSize(new Dimension(350, 45));
- Hashtable<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
- for (int step = 0; step <= maximumTick; step++)
- {
- StringBuilder text = new StringBuilder();
- text.append(this.ratios[step % this.ratios.length]);
- for (int decade = 0; decade < step / this.ratios.length; decade++)
- {
- text.append("0");
- }
- this.tickValues.put(step, Double.parseDouble(text.toString()));
- labels.put(step, new JLabel(text.toString().replace("000", "K")));
- // System.out.println("Label " + step + " is \"" + text.toString() + "\"");
- }
- // Figure out the DecimalSymbol
- String decimalSeparator =
- "" + ((DecimalFormat) NumberFormat.getInstance()).getDecimalFormatSymbols().getDecimalSeparator();
- for (int step = -1; step >= minimumTick; step--)
- {
- StringBuilder text = new StringBuilder();
- text.append("0");
- text.append(decimalSeparator);
- for (int decade = (step + 1) / this.ratios.length; decade < 0; decade++)
- {
- text.append("0");
- }
- int index = step % this.ratios.length;
- if (index < 0)
- {
- index += this.ratios.length;
- }
- text.append(this.ratios[index]);
- labels.put(step, new JLabel(text.toString()));
- this.tickValues.put(step, Double.parseDouble(text.toString()));
- // System.out.println("Label " + step + " is \"" + text.toString() + "\"");
- }
- labels.put(maximumTick + 1, new JLabel("\u221E"));
- this.tickValues.put(maximumTick + 1, 1E9);
- this.slider.setLabelTable(labels);
- this.slider.setPaintLabels(true);
- this.slider.setPaintTicks(true);
- this.slider.setMajorTickSpacing(1);
- this.add(this.slider);
- /*- Uncomment to verify the stepToFactor method.
- for (int i = this.slider.getMinimum(); i <= this.slider.getMaximum(); i++)
- {
- System.out.println("pos=" + i + " value is " + stepToFactor(i));
- }
- */
- // initial value of simulation speed
- if (simulator instanceof DEVSRealTimeClock)
- {
- DEVSRealTimeClock<?, ?, ?> clock = (DEVSRealTimeClock<?, ?, ?>) simulator;
- clock.setSpeedFactor(TimeWarpPanel.this.tickValues.get(this.slider.getValue()));
- }
- // adjust the simulation speed
- this.slider.addChangeListener(new ChangeListener()
- {
- public void stateChanged(final ChangeEvent ce)
- {
- JSlider source = (JSlider) ce.getSource();
- if (!source.getValueIsAdjusting() && simulator instanceof DEVSRealTimeClock)
- {
- DEVSRealTimeClock<?, ?, ?> clock = (DEVSRealTimeClock<?, ?, ?>) simulator;
- clock.setSpeedFactor(((TimeWarpPanel) source.getParent()).getTickValues().get(source.getValue()));
- }
- }
- });
- }
- /**
- * Access to tickValues map from within the event handler.
- * @return Map<Integer, Double> the tickValues map of this TimeWarpPanel
- */
- protected Map<Integer, Double> getTickValues()
- {
- return this.tickValues;
- }
- /**
- * Convert a position on the slider to a factor.
- * @param step int; the position on the slider
- * @return double; the factor that corresponds to step
- */
- private double stepToFactor(final int step)
- {
- int index = step % this.ratios.length;
- if (index < 0)
- {
- index += this.ratios.length;
- }
- double result = this.ratios[index];
- // Make positive to avoid trouble with negative values that round towards 0 on division
- int power = (step + 1000 * this.ratios.length) / this.ratios.length - 1000; // This is ugly
- while (power > 0)
- {
- result *= 10;
- power--;
- }
- while (power < 0)
- {
- result /= 10;
- power++;
- }
- return result;
- }
- /**
- * Retrieve the current TimeWarp factor.
- * @return double; the current TimeWarp factor
- */
- public final double getFactor()
- {
- return stepToFactor(this.slider.getValue());
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "TimeWarpPanel [timeWarp=" + this.getFactor() + "]";
- }
- /**
- * Set the time warp factor to the best possible approximation of a given value.
- * @param factor double; the requested speed factor
- */
- public void setSpeedFactor(final double factor)
- {
- int bestStep = -1;
- double bestError = Double.MAX_VALUE;
- for (int step = this.slider.getMinimum(); step < this.slider.getMaximum(); step++)
- {
- double ratio = getTickValues().get(step);// stepToFactor(step);
- double logError = Math.abs(Math.log(factor / ratio));
- if (logError < bestError)
- {
- bestStep = step;
- bestError = logError;
- }
- }
- System.out.println("setSpeedfactor: factor is " + factor + ", best slider value is " + bestStep
- + " current value is " + this.slider.getValue());
- if (this.slider.getValue() != bestStep)
- {
- this.slider.setValue(bestStep);
- }
- }
- }
- /** JLabel that displays the simulation time. */
- class ClockPanel extends JLabel
- {
- /** */
- private static final long serialVersionUID = 20141211L;
- /** The JLabel that displays the time. */
- private final JLabel clockLabel;
- /** The timer (so we can cancel it). */
- private Timer timer;
- /** Timer update interval in msec. */
- private static final long UPDATEINTERVAL = 1000;
- /** Construct a clock panel. */
- ClockPanel()
- {
- super("00:00:00.000");
- this.clockLabel = this;
- this.setFont(getTimeFont());
- this.timer = new Timer();
- this.timer.scheduleAtFixedRate(new TimeUpdateTask(), 0, ClockPanel.UPDATEINTERVAL);
- }
- /**
- * Cancel the timer task.
- */
- public void cancelTimer()
- {
- if (this.timer != null)
- {
- this.timer.cancel();
- }
- this.timer = null;
- }
- /** Updater for the clock panel. */
- private class TimeUpdateTask extends TimerTask implements Serializable
- {
- /** */
- private static final long serialVersionUID = 20140000L;
- /**
- * Create a TimeUpdateTask.
- */
- TimeUpdateTask()
- {
- }
- /** {@inheritDoc} */
- @Override
- public void run()
- {
- double now = Math.round(getSimulator().getSimulatorTime().getTime().getSI() * 1000) / 1000d;
- int seconds = (int) Math.floor(now);
- int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
- getClockLabel().setText(
- String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60, seconds % 60,
- fractionalSeconds));
- getClockLabel().repaint();
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "TimeUpdateTask of ClockPanel";
- }
- }
- /**
- * @return clockLabel.
- */
- protected JLabel getClockLabel()
- {
- return this.clockLabel;
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "ClockPanel [clockLabel=" + this.clockLabel + ", time=" + getText() + "]";
- }
- }
- /** Entry field for time. */
- class TimeEdit extends JFormattedTextField
- {
- /** */
- private static final long serialVersionUID = 20141212L;
- /**
- * Construct a new TimeEdit.
- * @param initialValue Time; the initial value for the TimeEdit
- */
- TimeEdit(final Time initialValue)
- {
- super(new RegexFormatter("\\d\\d\\d\\d:[0-5]\\d:[0-5]\\d\\.\\d\\d\\d"));
- MaskFormatter mf = null;
- try
- {
- mf = new MaskFormatter("####:##:##.###");
- mf.setPlaceholderCharacter('0');
- mf.setAllowsInvalid(false);
- mf.setCommitsOnValidEdit(true);
- mf.setOverwriteMode(true);
- mf.install(this);
- }
- catch (ParseException exception)
- {
- exception.printStackTrace();
- }
- setTime(initialValue);
- setFont(getTimeFont());
- }
- /**
- * Set or update the time shown in this TimeEdit.
- * @param newValue Time; the (new) value to set/show in this TimeEdit
- */
- public void setTime(final Time newValue)
- {
- double v = newValue.getSI();
- int integerPart = (int) Math.floor(v);
- int fraction = (int) Math.floor((v - integerPart) * 1000);
- String text =
- String.format("%04d:%02d:%02d.%03d", integerPart / 3600, integerPart / 60 % 60, integerPart % 60, fraction);
- this.setText(text);
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "TimeEdit [time=" + getText() + "]";
- }
- }
- /**
- * Extension of a DefaultFormatter that uses a regular expression. <br>
- * Derived from <a href="http://www.java2s.com/Tutorial/Java/0240__Swing/RegexFormatterwithaJFormattedTextField.htm">
- * http://www.java2s.com/Tutorial/Java/0240__Swing/RegexFormatterwithaJFormattedTextField.htm</a>
- * <p>
- * $LastChangedDate: 2016-10-16 14:55:54 +0200 (Sun, 16 Oct 2016) $, @version $Revision: 2386 $, by $Author: averbraeck $,
- * initial version 2 dec. 2014 <br>
- * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
- */
- static class RegexFormatter extends DefaultFormatter
- {
- /** */
- private static final long serialVersionUID = 20141212L;
- /** The regular expression pattern. */
- private Pattern pattern;
- /**
- * Create a new RegexFormatter.
- * @param pattern String; regular expression pattern that defines what this RexexFormatter will accept
- */
- RegexFormatter(final String pattern)
- {
- this.pattern = Pattern.compile(pattern);
- }
- @Override
- public Object stringToValue(final String text) throws ParseException
- {
- Matcher matcher = this.pattern.matcher(text);
- if (matcher.matches())
- {
- // System.out.println("String \"" + text + "\" matches");
- return super.stringToValue(text);
- }
- // System.out.println("String \"" + text + "\" does not match");
- throw new ParseException("Pattern did not match", 0);
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "RegexFormatter [pattern=" + this.pattern + "]";
- }
- }
- /** {@inheritDoc} */
- @Override
- public final void notify(final EventInterface event) throws RemoteException
- {
- if (event.getType().equals(SimulatorInterface.END_OF_REPLICATION_EVENT)
- || event.getType().equals(SimulatorInterface.START_EVENT)
- || event.getType().equals(SimulatorInterface.STOP_EVENT)
- || event.getType().equals(DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT))
- {
- if (event.getType().equals(DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT))
- {
- this.timeWarpPanel.setSpeedFactor((Double) event.getContent());
- }
- fixButtons();
- }
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "OTSControlPanel [simulatorTime=" + this.simulator.getSimulatorTime().getTime() + ", timeWarp="
- + this.timeWarpPanel.getFactor() + ", stopAtEvent=" + this.stopAtEvent + "]";
- }
- }