1 package org.opentrafficsim.simulationengine;
2
3 import java.awt.BorderLayout;
4 import java.awt.Font;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.beans.PropertyChangeEvent;
8 import java.beans.PropertyChangeListener;
9 import java.text.ParseException;
10 import java.util.ArrayList;
11 import java.util.Timer;
12 import java.util.TimerTask;
13 import java.util.logging.Level;
14 import java.util.logging.Logger;
15 import java.util.regex.Matcher;
16 import java.util.regex.Pattern;
17
18 import javax.swing.ImageIcon;
19 import javax.swing.JButton;
20 import javax.swing.JFormattedTextField;
21 import javax.swing.JLabel;
22 import javax.swing.JPanel;
23 import javax.swing.SwingUtilities;
24 import javax.swing.text.DefaultFormatter;
25 import javax.swing.text.MaskFormatter;
26
27 import nl.tudelft.simulation.dsol.SimRuntimeException;
28 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
29 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
30 import nl.tudelft.simulation.dsol.gui.swing.DSOLPanel;
31 import nl.tudelft.simulation.dsol.gui.swing.SimulatorControlPanel;
32 import nl.tudelft.simulation.dsol.simulators.DEVSSimulator;
33 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
34 import nl.tudelft.simulation.language.io.URLResource;
35
36 import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
37 import org.opentrafficsim.core.unit.TimeUnit;
38 import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
39
40
41
42
43
44
45
46
47
48
49
50
51 public class ControlPanel implements ActionListener, PropertyChangeListener
52 {
53
54 private final DEVSSimulator<DoubleScalar.Abs<TimeUnit>, DoubleScalar.Rel<TimeUnit>, OTSSimTimeDouble> simulator;
55
56
57 private final SimulatorInterface<?, ?, ?> target;
58
59
60 private final Logger logger;
61
62
63 private final ClockPanel clockPanel;
64
65
66 private final ArrayList<JButton> buttons = new ArrayList<JButton>();
67
68
69 private final Font timeFont = new Font("SansSerif", Font.BOLD, 18);
70
71
72 private final TimeEdit timeEdit;
73
74
75 private SimEvent<OTSSimTimeDouble> stopAtEvent = null;
76
77
78
79
80
81 public ControlPanel(final SimpleSimulator simulator)
82 {
83 this.simulator = simulator.getSimulator();
84 this.target = simulator.getSimulator();
85 this.logger = Logger.getLogger("nl.tudelft.opentrafficsim");
86
87 DSOLPanel<DoubleScalar.Abs<TimeUnit>, DoubleScalar.Rel<TimeUnit>, OTSSimTimeDouble> panel =
88 simulator.getPanel();
89 SimulatorControlPanel controlPanel =
90 (SimulatorControlPanel) ((BorderLayout) panel.getLayout()).getLayoutComponent(BorderLayout.NORTH);
91 JPanel buttonPanel = (JPanel) controlPanel.getComponent(0);
92 buttonPanel.removeAll();
93 buttonPanel.add(makeButton("stepButton", "/Last_recor.png", "Step", "Execute one event", true));
94 buttonPanel.add(makeButton("nextTimeButton", "/NextTrack.png", "NextTime",
95 "Execute all events scheduled for the current time", true));
96 buttonPanel.add(makeButton("runButton", "/Play.png", "Run", "Run the simulation at maximum speed", true));
97 buttonPanel.add(makeButton("pauseButton", "/Pause.png", "Pause", "Pause the simulator", false));
98 buttonPanel.add(makeButton("resetButton", "/Undo.png", "Reset", null, false));
99 this.clockPanel = new ClockPanel();
100 buttonPanel.add(this.clockPanel);
101 this.timeEdit = new TimeEdit(new DoubleScalar.Abs<TimeUnit>(0, TimeUnit.SECOND));
102 this.timeEdit.addPropertyChangeListener("value", this);
103 buttonPanel.add(this.timeEdit);
104 }
105
106
107
108
109
110
111
112
113
114
115 private JButton makeButton(final String name, final String iconPath, final String actionCommand,
116 final String toolTipText, final boolean enabled)
117 {
118
119 JButton result = new JButton(new ImageIcon(URLResource.getResource(iconPath)));
120 result.setName(name);
121 result.setEnabled(enabled);
122 result.setActionCommand(actionCommand);
123 result.setToolTipText(toolTipText);
124 result.addActionListener(this);
125 this.buttons.add(result);
126 return result;
127 }
128
129
130 @Override
131 public final void actionPerformed(final ActionEvent actionEvent)
132 {
133 String actionCommand = actionEvent.getActionCommand();
134
135 try
136 {
137 if (actionCommand.equals("Step"))
138 {
139 if (this.simulator.isRunning())
140 {
141 this.simulator.stop();
142 }
143 this.target.step();
144 }
145 if (actionCommand.equals("Run"))
146 {
147 this.target.start();
148 }
149 if (actionCommand.equals("NextTime"))
150 {
151 if (this.simulator.isRunning())
152 {
153 this.simulator.stop();
154 }
155 double now = this.simulator.getSimulatorTime().get().getSI();
156
157 this.stopAtEvent =
158 new SimEvent<OTSSimTimeDouble>(new OTSSimTimeDouble(new DoubleScalar.Abs<TimeUnit>(now,
159 TimeUnit.SECOND)), SimEventInterface.MIN_PRIORITY, this, this, "autoPauseSimulator",
160 null);
161 try
162 {
163 this.simulator.scheduleEvent(this.stopAtEvent);
164 }
165 catch (SimRuntimeException exception)
166 {
167 this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator", "Caught an exception "
168 + "while trying to schedule an autoPauseSimulator event at the current simulator time");
169 }
170 this.target.start();
171 }
172 if (actionCommand.equals("Pause"))
173 {
174 this.target.stop();
175 }
176 if (actionCommand.equals("Reset"))
177 {
178 if (this.simulator.isRunning())
179 {
180 this.simulator.stop();
181 }
182
183 }
184 fixButtons();
185 }
186 catch (Exception exception)
187 {
188 this.logger.logp(Level.SEVERE, "ControlPanel", "actionPerformed", "", exception);
189 }
190 }
191
192
193
194
195 protected final void fixButtons()
196 {
197 System.out.println("FixButtons entered");
198 final boolean moreWorkToDo = this.simulator.getEventList().size() > 0;
199 for (JButton button : this.buttons)
200 {
201 final String actionCommand = button.getActionCommand();
202 if (actionCommand.equals("Step"))
203 {
204 button.setEnabled(moreWorkToDo);
205 }
206 else if (actionCommand.equals("Run"))
207 {
208 button.setEnabled(moreWorkToDo && !this.simulator.isRunning());
209 }
210 else if (actionCommand.equals("NextTime"))
211 {
212 button.setEnabled(moreWorkToDo);
213 }
214 else if (actionCommand.equals("Pause"))
215 {
216 button.setEnabled(this.simulator.isRunning());
217 }
218 else if (actionCommand.equals("Reset"))
219 {
220 button.setEnabled(true);
221 }
222 else
223 {
224 this.logger.logp(Level.SEVERE, "ControlPanel", "fixButtons", "", new Exception("Unknown button?"));
225 }
226 }
227 System.out.println("FixButtons finishing");
228 }
229
230
231
232
233 public final void autoPauseSimulator()
234 {
235 if (this.simulator.isRunning())
236 {
237 this.simulator.stop();
238 double currentTick = this.simulator.getSimulatorTime().get().getSI();
239 double nextTick = this.simulator.getEventList().first().getAbsoluteExecutionTime().get().getSI();
240
241
242 if (nextTick > currentTick)
243 {
244
245
246
247 this.stopAtEvent =
248 new SimEvent<OTSSimTimeDouble>(new OTSSimTimeDouble(new DoubleScalar.Abs<TimeUnit>(nextTick,
249 TimeUnit.SECOND)), SimEventInterface.MAX_PRIORITY, this, this, "autoPauseSimulator",
250 null);
251
252 try
253 {
254 this.simulator.scheduleEvent(this.stopAtEvent);
255 this.simulator.start();
256 }
257 catch (SimRuntimeException exception)
258 {
259 this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator",
260 "Caught an exception while trying to re-schedule an autoPauseEvent at the next real event");
261 }
262 }
263 else
264 {
265
266 if (SwingUtilities.isEventDispatchThread())
267 {
268 System.out.println("Already on EventDispatchThread");
269 fixButtons();
270 }
271 else
272 {
273 try
274 {
275 System.out.println("Current thread is NOT EventDispatchThread: " + Thread.currentThread());
276 SwingUtilities.invokeAndWait(new Runnable()
277 {
278 @Override
279 public void run()
280 {
281 System.out.println("Runnable started");
282 fixButtons();
283 System.out.println("Runnable finishing");
284 }
285 });
286 }
287 catch (Exception e)
288 {
289 if (e instanceof InterruptedException)
290 {
291 System.out.println("Caught " + e);
292 e.printStackTrace();
293 }
294 else
295 {
296 e.printStackTrace();
297 }
298 }
299 }
300 }
301 }
302 }
303
304
305 @Override
306 public final void propertyChange(final PropertyChangeEvent evt)
307 {
308
309 if (null != this.stopAtEvent)
310 {
311 this.simulator.cancelEvent(this.stopAtEvent);
312 this.stopAtEvent = null;
313 }
314 String newValue = (String) evt.getNewValue();
315 String[] fields = newValue.split("[:\\.]");
316 int hours = Integer.parseInt(fields[0]);
317 int minutes = Integer.parseInt(fields[1]);
318 int seconds = Integer.parseInt(fields[2]);
319 int fraction = Integer.parseInt(fields[3]);
320 double stopTime = hours * 3600 + minutes * 60 + seconds + fraction / 1000d;
321 if (stopTime < this.simulator.getSimulatorTime().get().getSI())
322 {
323 return;
324 }
325 else
326 {
327 this.stopAtEvent =
328 new SimEvent<OTSSimTimeDouble>(new OTSSimTimeDouble(new DoubleScalar.Abs<TimeUnit>(stopTime,
329 TimeUnit.SECOND)), SimEventInterface.MAX_PRIORITY, this, this, "autoPauseSimulator", null);
330 try
331 {
332 this.simulator.scheduleEvent(this.stopAtEvent);
333 }
334 catch (SimRuntimeException exception)
335 {
336 this.logger.logp(Level.SEVERE, "ControlPanel", "propertyChange",
337 "Caught an exception while trying to schedule an autoPauseSimulator event");
338 }
339 }
340
341 }
342
343
344
345
346 public final DEVSSimulator<DoubleScalar.Abs<TimeUnit>, DoubleScalar.Rel<TimeUnit>, OTSSimTimeDouble> getSimulator()
347 {
348 return this.simulator;
349 }
350
351
352
353
354 public final Font getTimeFont()
355 {
356 return this.timeFont;
357 }
358
359
360 class ClockPanel extends JLabel
361 {
362
363 private static final long serialVersionUID = 20141211L;
364
365
366 private final JLabel clockLabel;
367
368
369 private final static long UPDATEINTERVAL = 1000;
370
371
372 ClockPanel()
373 {
374 super("00:00:00.000");
375 this.clockLabel = this;
376 this.setFont(getTimeFont());
377 Timer timer = new Timer();
378 timer.scheduleAtFixedRate(new TimeUpdateTask(), 0, ClockPanel.UPDATEINTERVAL);
379
380 }
381
382
383 private class TimeUpdateTask extends TimerTask
384 {
385
386
387
388 public TimeUpdateTask()
389 {
390 }
391
392
393 @Override
394 public void run()
395 {
396 double now = Math.round(getSimulator().getSimulatorTime().get().getSI() * 1000) / 1000d;
397 int seconds = (int) Math.floor(now);
398 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
399 getClockLabel().setText(
400 String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60, seconds % 60,
401 fractionalSeconds));
402 getClockLabel().repaint();
403 }
404 }
405
406
407
408
409 protected JLabel getClockLabel()
410 {
411 return this.clockLabel;
412 }
413
414 }
415
416
417 class TimeEdit extends JFormattedTextField
418 {
419
420 private static final long serialVersionUID = 20141212L;
421
422
423
424
425
426 TimeEdit(final DoubleScalar.Abs<TimeUnit> initialValue)
427 {
428 super(new RegexFormatter("\\d\\d\\d\\d:[0-5]\\d:[0-5]\\d\\.\\d\\d\\d"));
429 MaskFormatter mf = null;
430 try
431 {
432 mf = new MaskFormatter("####:##:##.###");
433 mf.setPlaceholderCharacter('0');
434 mf.setAllowsInvalid(false);
435 mf.setCommitsOnValidEdit(true);
436 mf.setOverwriteMode(true);
437 mf.install(this);
438 }
439 catch (ParseException exception)
440 {
441 exception.printStackTrace();
442 }
443 setTime(initialValue);
444 setFont(getTimeFont());
445 }
446
447
448
449
450
451 public void setTime(final DoubleScalar.Abs<TimeUnit> newValue)
452 {
453 double v = newValue.getSI();
454 int integerPart = (int) Math.floor(v);
455 int fraction = (int) Math.floor((v - integerPart) * 1000);
456 String text =
457 String.format("%04d:%02d:%02d.%03d", integerPart / 3600, integerPart / 60 % 60, integerPart % 60,
458 fraction);
459 this.setText(text);
460 }
461 }
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476 static class RegexFormatter extends DefaultFormatter
477 {
478
479 private static final long serialVersionUID = 20141212L;
480
481
482 private Pattern pattern;
483
484
485
486
487
488 public RegexFormatter(final String pattern)
489 {
490 this.pattern = Pattern.compile(pattern);
491 }
492
493 @Override
494 public Object stringToValue(final String text) throws ParseException
495 {
496 Matcher matcher = this.pattern.matcher(text);
497 if (matcher.matches())
498 {
499
500 return super.stringToValue(text);
501 }
502
503 throw new ParseException("Pattern did not match", 0);
504 }
505 }
506
507 }