1 package org.opentrafficsim.swing.gui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Container;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.Font;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.awt.event.WindowEvent;
11 import java.awt.event.WindowListener;
12 import java.beans.PropertyChangeEvent;
13 import java.beans.PropertyChangeListener;
14 import java.io.IOException;
15 import java.io.Serializable;
16 import java.rmi.RemoteException;
17 import java.text.DecimalFormat;
18 import java.text.NumberFormat;
19 import java.text.ParseException;
20 import java.util.ArrayList;
21 import java.util.Hashtable;
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24 import java.util.Timer;
25 import java.util.TimerTask;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import javax.imageio.ImageIO;
30 import javax.swing.BoxLayout;
31 import javax.swing.GrayFilter;
32 import javax.swing.Icon;
33 import javax.swing.ImageIcon;
34 import javax.swing.JButton;
35 import javax.swing.JFormattedTextField;
36 import javax.swing.JFrame;
37 import javax.swing.JLabel;
38 import javax.swing.JPanel;
39 import javax.swing.JSlider;
40 import javax.swing.SwingConstants;
41 import javax.swing.SwingUtilities;
42 import javax.swing.WindowConstants;
43 import javax.swing.event.ChangeEvent;
44 import javax.swing.event.ChangeListener;
45 import javax.swing.text.DefaultFormatter;
46 import javax.swing.text.MaskFormatter;
47
48 import org.djunits.unit.TimeUnit;
49 import org.djunits.value.vdouble.scalar.Duration;
50 import org.djunits.value.vdouble.scalar.Time;
51 import org.djutils.event.Event;
52 import org.djutils.event.EventListener;
53 import org.opentrafficsim.core.dsol.OtsModelInterface;
54 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
55
56 import nl.tudelft.simulation.dsol.SimRuntimeException;
57 import nl.tudelft.simulation.dsol.experiment.ReplicationInterface;
58 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
59 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
60 import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeAnimator;
61 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
62
63
64
65
66
67
68
69
70
71
72 public class OtsControlPanel extends JPanel implements ActionListener, PropertyChangeListener, WindowListener, EventListener
73 {
74
75 private static final long serialVersionUID = 20150617L;
76
77
78 private OtsSimulatorInterface simulator;
79
80
81 private final OtsModelInterface model;
82
83
84 private final ClockLabel clockPanel;
85
86
87 private final TimeWarpPanel timeWarpPanel;
88
89
90 private final ArrayList<JButton> buttons = new ArrayList<>();
91
92
93 private final Font timeFont = new Font("SansSerif", Font.BOLD, 18);
94
95
96 private final TimeEdit timeEdit;
97
98
99 private final OtsSearchPanel otsSearchPanel;
100
101
102 private SimEvent<Duration> stopAtEvent = null;
103
104
105 private boolean buttonsEnabled = false;
106
107
108 @SuppressWarnings("checkstyle:visibilitymodifier")
109 protected boolean closeHandlerRegistered = false;
110
111
112 private boolean isCleanUp = false;
113
114
115
116
117
118
119
120
121 public OtsControlPanel(final OtsSimulatorInterface simulator, final OtsModelInterface model,
122 final OtsAnimationPanel otsAnimationPanel) throws RemoteException
123 {
124 this.simulator = simulator;
125 this.model = model;
126
127 this.setLayout(new FlowLayout(FlowLayout.LEFT));
128 JPanel buttonPanel = new JPanel();
129 buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
130 buttonPanel.add(makeButton("stepButton", "/Last_recor.png", "Step", "Execute one event", true));
131 buttonPanel.add(makeButton("nextTimeButton", "/NextTrack.png", "NextTime",
132 "Execute all events scheduled for the current time", true));
133 buttonPanel.add(makeButton("runPauseButton", "/Play.png", "RunPause", "XXX", true));
134 this.timeWarpPanel = new TimeWarpPanel(0.1, 1000, 1, 3, simulator);
135 buttonPanel.add(this.timeWarpPanel);
136
137
138 class AppearanceControlLabel extends JLabel implements AppearanceControl
139 {
140
141 private static final long serialVersionUID = 20180207L;
142
143
144 @Override
145 public boolean isForeground()
146 {
147 return true;
148 }
149
150
151 @Override
152 public boolean isBackground()
153 {
154 return true;
155 }
156
157
158 @Override
159 public String toString()
160 {
161 return "AppearanceControlLabel []";
162 }
163 }
164 JLabel speedLabel = new AppearanceControlLabel();
165 this.clockPanel = new ClockLabel(speedLabel);
166 this.clockPanel.setMaximumSize(new Dimension(133, 35));
167 buttonPanel.add(this.clockPanel);
168 speedLabel.setMaximumSize(new Dimension(66, 35));
169 buttonPanel.add(speedLabel);
170 this.timeEdit = new TimeEdit(new Time(0, TimeUnit.DEFAULT));
171 this.timeEdit.setMaximumSize(new Dimension(133, 35));
172 this.timeEdit.addPropertyChangeListener("value", this);
173 buttonPanel.add(this.timeEdit);
174 this.add(buttonPanel);
175 this.otsSearchPanel = new OtsSearchPanel(otsAnimationPanel);
176 this.add(this.otsSearchPanel, BorderLayout.SOUTH);
177 fixButtons();
178 installWindowCloseHandler();
179 this.simulator.addListener(this, ReplicationInterface.END_REPLICATION_EVENT);
180 this.simulator.addListener(this, SimulatorInterface.START_EVENT);
181 this.simulator.addListener(this, SimulatorInterface.STOP_EVENT);
182 this.simulator.addListener(this, DEVSRealTimeAnimator.CHANGE_SPEED_FACTOR_EVENT);
183 }
184
185
186
187
188
189 public void setSimulationControlButtons(final boolean newState)
190 {
191 this.buttonsEnabled = newState;
192 fixButtons();
193 }
194
195
196
197
198
199 public OtsSearchPanel getOtsSearchPanel()
200 {
201 return this.otsSearchPanel;
202 }
203
204
205
206
207
208
209
210
211
212
213 private JButton makeButton(final String name, final String iconPath, final String actionCommand, final String toolTipText,
214 final boolean enabled)
215 {
216
217 class AppearanceControlButton extends JButton implements AppearanceControl
218 {
219
220 private static final long serialVersionUID = 20180206L;
221
222
223
224
225 AppearanceControlButton(final Icon loadIcon)
226 {
227 super(loadIcon);
228 }
229
230
231 @Override
232 public boolean isFont()
233 {
234 return true;
235 }
236
237
238 @Override
239 public String toString()
240 {
241 return "AppearanceControlButton []";
242 }
243 }
244 JButton result = new AppearanceControlButton(loadIcon(iconPath));
245 result.setName(name);
246 result.setEnabled(enabled);
247 result.setActionCommand(actionCommand);
248 result.setToolTipText(toolTipText);
249 result.addActionListener(this);
250 this.buttons.add(result);
251 return result;
252 }
253
254
255
256
257
258
259 public static final Icon loadIcon(final String iconPath)
260 {
261 try
262 {
263 return new ImageIcon(ImageIO.read(Resource.getResourceAsStream(iconPath)));
264 }
265 catch (NullPointerException | IOException npe)
266 {
267 System.err.println("Could not load icon from path " + iconPath);
268 return null;
269 }
270 }
271
272
273
274
275
276
277 public static final Icon loadGrayscaleIcon(final String iconPath)
278 {
279 try
280 {
281 return new ImageIcon(GrayFilter.createDisabledImage(ImageIO.read(Resource.getResourceAsStream(iconPath))));
282 }
283 catch (NullPointerException | IOException e)
284 {
285 System.err.println("Could not load icon from path " + iconPath);
286 return null;
287 }
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304 private SimEvent<Duration> scheduleEvent(final Time executionTime, final short priority, final Object source,
305 final Object eventTarget, final String method, final Object[] args) throws SimRuntimeException
306 {
307 SimEvent<Duration> simEvent =
308 new SimEvent<>(executionTime.minus(getSimulator().getStartTimeAbs()), priority, eventTarget, method, args);
309 this.simulator.scheduleEvent(simEvent);
310 return simEvent;
311 }
312
313
314
315
316 public final void installWindowCloseHandler()
317 {
318 if (this.closeHandlerRegistered)
319 {
320 return;
321 }
322
323
324 new DisposeOnCloseThread(this).start();
325 }
326
327
328 protected class DisposeOnCloseThread extends Thread
329 {
330
331 private OtsControlPanel panel;
332
333
334
335
336 public DisposeOnCloseThread(final OtsControlPanel panel)
337 {
338 this.panel = panel;
339 }
340
341
342 @Override
343 public final void run()
344 {
345 Container root = this.panel;
346 while (!(root instanceof JFrame))
347 {
348 try
349 {
350 Thread.sleep(10);
351 }
352 catch (InterruptedException exception)
353 {
354
355 }
356
357
358 root = this.panel;
359 while (null != root.getParent() && !(root instanceof JFrame))
360 {
361 root = root.getParent();
362 }
363 }
364 JFrame frame = (JFrame) root;
365 frame.addWindowListener(this.panel);
366 this.panel.closeHandlerRegistered = true;
367
368 }
369
370
371 @Override
372 public final String toString()
373 {
374 return "DisposeOnCloseThread [panel=" + this.panel + "]";
375 }
376 }
377
378
379 @Override
380 public final void actionPerformed(final ActionEvent actionEvent)
381 {
382 String actionCommand = actionEvent.getActionCommand();
383
384 try
385 {
386 if (actionCommand.equals("Step"))
387 {
388 if (getSimulator().isStartingOrRunning())
389 {
390 getSimulator().stop();
391 }
392 this.simulator.step();
393 }
394 if (actionCommand.equals("RunPause"))
395 {
396 if (this.simulator.isStartingOrRunning())
397 {
398
399 this.simulator.stop();
400 }
401 else if (getSimulator().getEventList().size() > 0)
402 {
403
404 this.simulator.start();
405 }
406 }
407 if (actionCommand.equals("NextTime"))
408 {
409 if (getSimulator().isStartingOrRunning())
410 {
411
412 getSimulator().stop();
413 }
414 double now = getSimulator().getSimulatorTime().getSI();
415
416 try
417 {
418 this.stopAtEvent = scheduleEvent(new Time(now, TimeUnit.DEFAULT), SimEventInterface.MIN_PRIORITY, this,
419 this, "autoPauseSimulator", null);
420 }
421 catch (SimRuntimeException exception)
422 {
423 this.simulator.getLogger().always()
424 .error("Caught an exception while trying to schedule an autoPauseSimulator event "
425 + "at the current simulator time");
426 }
427
428 this.simulator.start();
429 }
430 if (actionCommand.equals("Reset"))
431 {
432 if (getSimulator().isStartingOrRunning())
433 {
434 getSimulator().stop();
435 }
436
437 if (null == OtsControlPanel.this.model)
438 {
439 throw new RuntimeException("Do not know how to restart this simulation");
440 }
441
442
443 Container root = OtsControlPanel.this;
444 while (!(root instanceof JFrame))
445 {
446 root = root.getParent();
447 }
448 JFrame frame = (JFrame) root;
449 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
450 frame.dispose();
451 OtsControlPanel.this.cleanup();
452
453 }
454 fixButtons();
455 }
456 catch (Exception exception)
457 {
458 exception.printStackTrace();
459 }
460 }
461
462
463
464
465 private void cleanup()
466 {
467 if (!this.isCleanUp)
468 {
469 this.isCleanUp = true;
470 try
471 {
472 if (this.simulator != null)
473 {
474 if (this.simulator.isStartingOrRunning())
475 {
476 this.simulator.stop();
477 }
478
479
480
481
482 if (getSimulator().getReplication().getContext().hasKey("animation"))
483 {
484 getSimulator().getReplication().getContext().destroySubcontext("animation");
485 }
486 if (getSimulator().getReplication().getContext().hasKey("statistics"))
487 {
488 getSimulator().getReplication().getContext().destroySubcontext("statistics");
489 }
490 if (getSimulator().getReplication().getContext().hasKey("statistics"))
491 {
492 getSimulator().getReplication().getContext().destroySubcontext("statistics");
493 }
494
495 getSimulator().cleanUp();
496 }
497
498 if (this.clockPanel != null)
499 {
500 this.clockPanel.cancelTimer();
501 }
502
503 }
504 catch (Throwable exception)
505 {
506 exception.printStackTrace();
507 }
508 }
509 }
510
511
512
513
514 protected final void fixButtons()
515 {
516
517 final boolean moreWorkToDo = getSimulator().getEventList().size() > 0;
518 for (JButton button : this.buttons)
519 {
520 final String actionCommand = button.getActionCommand();
521 if (actionCommand.equals("Step"))
522 {
523 button.setEnabled(moreWorkToDo && this.buttonsEnabled);
524 }
525 else if (actionCommand.equals("RunPause"))
526 {
527 button.setEnabled(moreWorkToDo && this.buttonsEnabled);
528 if (this.simulator.isStartingOrRunning())
529 {
530 button.setToolTipText("Pause the simulation");
531 button.setIcon(OtsControlPanel.loadIcon("/Pause.png"));
532 }
533 else
534 {
535 button.setToolTipText("Run the simulation at the indicated speed");
536 button.setIcon(loadIcon("/Play.png"));
537 }
538 button.setEnabled(moreWorkToDo && this.buttonsEnabled);
539 }
540 else if (actionCommand.equals("NextTime"))
541 {
542 button.setEnabled(moreWorkToDo && this.buttonsEnabled);
543 }
544
545
546
547
548 else
549 {
550 this.simulator.getLogger().always().error(new Exception("Unknown button?"));
551 }
552 }
553
554 }
555
556
557
558
559 public final void autoPauseSimulator()
560 {
561
562 if (getSimulator().isStartingOrRunning())
563 {
564 try
565 {
566
567 getSimulator().stop();
568 }
569 catch (SimRuntimeException exception1)
570 {
571 exception1.printStackTrace();
572 }
573 double currentTick = getSimulator().getSimulatorTime().getSI();
574 double nextTick = getSimulator().getEventList().first().getAbsoluteExecutionTime().getSI();
575
576
577 if (nextTick > currentTick)
578 {
579
580
581
582
583 try
584 {
585 this.stopAtEvent = scheduleEvent(new Time(nextTick, TimeUnit.DEFAULT), SimEventInterface.MAX_PRIORITY, this,
586 this, "autoPauseSimulator", null);
587
588 getSimulator().start();
589 }
590 catch (SimRuntimeException exception)
591 {
592 this.simulator.getLogger().always()
593 .error("Caught an exception while trying to re-schedule an autoPauseEvent at the next real event");
594 }
595 }
596 else
597 {
598
599 if (SwingUtilities.isEventDispatchThread())
600 {
601
602 fixButtons();
603 }
604 else
605 {
606 try
607 {
608
609 SwingUtilities.invokeAndWait(new Runnable()
610 {
611 @Override
612 public void run()
613 {
614
615 fixButtons();
616
617 }
618 });
619 }
620 catch (Exception e)
621 {
622 if (e instanceof InterruptedException)
623 {
624 System.out.println("Caught " + e);
625
626 }
627 else
628 {
629 e.printStackTrace();
630 }
631 }
632 }
633 }
634 }
635
636 }
637
638
639 @Override
640 public final void propertyChange(final PropertyChangeEvent evt)
641 {
642
643 if (null != this.stopAtEvent)
644 {
645 getSimulator().cancelEvent(this.stopAtEvent);
646 this.stopAtEvent = null;
647 }
648 String newValue = (String) evt.getNewValue();
649 String[] fields = newValue.split("[:\\.]");
650 int hours = Integer.parseInt(fields[0]);
651 int minutes = Integer.parseInt(fields[1]);
652 int seconds = Integer.parseInt(fields[2]);
653 int fraction = Integer.parseInt(fields[3]);
654 double stopTime = hours * 3600 + minutes * 60 + seconds + fraction / 1000d;
655 if (stopTime < getSimulator().getSimulatorTime().getSI())
656 {
657 return;
658 }
659 else
660 {
661 try
662 {
663 this.stopAtEvent = scheduleEvent(new Time(stopTime, TimeUnit.DEFAULT), SimEventInterface.MAX_PRIORITY, this,
664 this, "autoPauseSimulator", null);
665 }
666 catch (SimRuntimeException exception)
667 {
668 this.simulator.getLogger().always()
669 .error("Caught an exception while trying to schedule an autoPauseSimulator event");
670 }
671 }
672 }
673
674
675
676
677 @SuppressWarnings("unchecked")
678 public final OtsSimulatorInterface getSimulator()
679 {
680 return this.simulator;
681 }
682
683
684 @Override
685 public void windowOpened(final WindowEvent e)
686 {
687
688 }
689
690
691 @Override
692 public final void windowClosing(final WindowEvent e)
693 {
694 if (this.simulator != null)
695 {
696 try
697 {
698 if (this.simulator.isStartingOrRunning())
699 {
700 this.simulator.stop();
701 }
702 }
703 catch (SimRuntimeException exception)
704 {
705 exception.printStackTrace();
706 }
707 }
708 }
709
710
711 @Override
712 public final void windowClosed(final WindowEvent e)
713 {
714 cleanup();
715 }
716
717
718 @Override
719 public final void windowIconified(final WindowEvent e)
720 {
721
722 }
723
724
725 @Override
726 public final void windowDeiconified(final WindowEvent e)
727 {
728
729 }
730
731
732 @Override
733 public final void windowActivated(final WindowEvent e)
734 {
735
736 }
737
738
739 @Override
740 public final void windowDeactivated(final WindowEvent e)
741 {
742
743 }
744
745
746
747
748 public final Font getTimeFont()
749 {
750 return this.timeFont;
751 }
752
753
754 static class TimeWarpPanel extends JPanel
755 {
756
757 private static final long serialVersionUID = 20150408L;
758
759
760 private final JSlider slider;
761
762
763 private final int[] ratios;
764
765
766 private Map<Integer, Double> tickValues = new LinkedHashMap<>();
767
768
769
770
771
772
773
774
775
776
777
778 TimeWarpPanel(final double minimum, final double maximum, final double initialValue, final int ticksPerDecade,
779 final OtsSimulatorInterface simulator)
780 {
781 if (minimum <= 0 || minimum > initialValue || initialValue > maximum)
782 {
783 throw new RuntimeException("Bad (combination of) minimum, maximum and initialValue; "
784 + "(restrictions: 0 < minimum <= initialValue <= maximum)");
785 }
786 switch (ticksPerDecade)
787 {
788 case 1:
789 this.ratios = new int[] {1};
790 break;
791 case 2:
792 this.ratios = new int[] {1, 3};
793 break;
794 case 3:
795 this.ratios = new int[] {1, 2, 5};
796 break;
797 default:
798 throw new RuntimeException("Bad ticksPerDecade value (must be 1, 2 or 3)");
799 }
800 int minimumTick = (int) Math.floor(Math.log10(minimum / initialValue) * ticksPerDecade);
801 int maximumTick = (int) Math.ceil(Math.log10(maximum / initialValue) * ticksPerDecade);
802 this.slider = new JSlider(SwingConstants.HORIZONTAL, minimumTick, maximumTick + 1, 0);
803 this.slider.setPreferredSize(new Dimension(350, 45));
804 Hashtable<Integer, JLabel> labels = new Hashtable<>();
805 for (int step = 0; step <= maximumTick; step++)
806 {
807 StringBuilder text = new StringBuilder();
808 text.append(this.ratios[step % this.ratios.length]);
809 for (int decade = 0; decade < step / this.ratios.length; decade++)
810 {
811 text.append("0");
812 }
813 this.tickValues.put(step, Double.parseDouble(text.toString()));
814 labels.put(step, new JLabel(text.toString().replace("000", "K")));
815
816 }
817
818 String decimalSeparator =
819 "" + ((DecimalFormat) NumberFormat.getInstance()).getDecimalFormatSymbols().getDecimalSeparator();
820 for (int step = -1; step >= minimumTick; step--)
821 {
822 StringBuilder text = new StringBuilder();
823 text.append("0");
824 text.append(decimalSeparator);
825 for (int decade = (step + 1) / this.ratios.length; decade < 0; decade++)
826 {
827 text.append("0");
828 }
829 int index = step % this.ratios.length;
830 if (index < 0)
831 {
832 index += this.ratios.length;
833 }
834 text.append(this.ratios[index]);
835 labels.put(step, new JLabel(text.toString()));
836 this.tickValues.put(step, Double.parseDouble(text.toString()));
837
838 }
839 labels.put(maximumTick + 1, new JLabel("\u221E"));
840 this.tickValues.put(maximumTick + 1, 1E9);
841 this.slider.setLabelTable(labels);
842 this.slider.setPaintLabels(true);
843 this.slider.setPaintTicks(true);
844 this.slider.setMajorTickSpacing(1);
845 this.add(this.slider);
846
847
848
849
850
851
852
853
854 if (simulator instanceof DEVSRealTimeAnimator)
855 {
856 DEVSRealTimeAnimator<Duration> clock = (DEVSRealTimeAnimator<Duration>) simulator;
857 clock.setSpeedFactor(TimeWarpPanel.this.tickValues.get(this.slider.getValue()));
858 }
859
860
861 this.slider.addChangeListener(new ChangeListener()
862 {
863 @Override
864 public void stateChanged(final ChangeEvent ce)
865 {
866 JSlider source = (JSlider) ce.getSource();
867 if (!source.getValueIsAdjusting() && simulator instanceof DEVSRealTimeAnimator)
868 {
869 DEVSRealTimeAnimator<Duration> clock = (DEVSRealTimeAnimator<Duration>) simulator;
870 clock.setSpeedFactor(((TimeWarpPanel) source.getParent()).getTickValues().get(source.getValue()));
871 }
872 }
873 });
874 }
875
876
877
878
879
880 protected Map<Integer, Double> getTickValues()
881 {
882 return this.tickValues;
883 }
884
885
886
887
888
889
890 private double stepToFactor(final int step)
891 {
892 int index = step % this.ratios.length;
893 if (index < 0)
894 {
895 index += this.ratios.length;
896 }
897 double result = this.ratios[index];
898
899 int power = (step + 1000 * this.ratios.length) / this.ratios.length - 1000;
900 while (power > 0)
901 {
902 result *= 10;
903 power--;
904 }
905 while (power < 0)
906 {
907 result /= 10;
908 power++;
909 }
910 return result;
911 }
912
913
914
915
916
917 public final double getFactor()
918 {
919 return stepToFactor(this.slider.getValue());
920 }
921
922
923 @Override
924 public final String toString()
925 {
926 return "TimeWarpPanel [timeWarp=" + this.getFactor() + "]";
927 }
928
929
930
931
932
933 public void setSpeedFactor(final double factor)
934 {
935 int bestStep = -1;
936 double bestError = Double.MAX_VALUE;
937 double logOfFactor = Math.log(factor);
938 for (int step = this.slider.getMinimum(); step <= this.slider.getMaximum(); step++)
939 {
940 double ratio = getTickValues().get(step);
941 double logError = Math.abs(logOfFactor - Math.log(ratio));
942 if (logError < bestError)
943 {
944 bestStep = step;
945 bestError = logError;
946 }
947 }
948
949
950 if (this.slider.getValue() != bestStep)
951 {
952 this.slider.setValue(bestStep);
953 }
954 }
955 }
956
957
958 public class ClockLabel extends JLabel implements AppearanceControl
959 {
960
961 private static final long serialVersionUID = 20141211L;
962
963
964 private final JLabel speedLabel;
965
966
967 private Timer timer;
968
969
970 private static final long UPDATEINTERVAL = 1000;
971
972
973 private double prevSimTime = 0;
974
975
976
977
978
979 ClockLabel(final JLabel speedLabel)
980 {
981 super("00:00:00.000");
982 this.speedLabel = speedLabel;
983 speedLabel.setFont(getTimeFont());
984 this.setFont(getTimeFont());
985 this.timer = new Timer();
986 this.timer.scheduleAtFixedRate(new TimeUpdateTask(), 0, ClockLabel.UPDATEINTERVAL);
987 }
988
989
990
991
992 public void cancelTimer()
993 {
994 if (this.timer != null)
995 {
996 this.timer.cancel();
997 }
998 this.timer = null;
999 }
1000
1001
1002 private class TimeUpdateTask extends TimerTask implements Serializable
1003 {
1004
1005 private static final long serialVersionUID = 20140000L;
1006
1007
1008
1009
1010 TimeUpdateTask()
1011 {
1012 }
1013
1014
1015 @Override
1016 public void run()
1017 {
1018 double now = Math.round(getSimulator().getSimulatorTime().getSI() * 1000) / 1000d;
1019 int seconds = (int) Math.floor(now);
1020 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
1021 ClockLabel.this.setText(String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60,
1022 seconds % 60, fractionalSeconds));
1023 ClockLabel.this.repaint();
1024 double speed = getSpeed(now);
1025 if (Double.isNaN(speed))
1026 {
1027 getSpeedLabel().setText("");
1028 }
1029 else
1030 {
1031 getSpeedLabel().setText(String.format("% 5.2fx ", speed));
1032 }
1033 getSpeedLabel().repaint();
1034 }
1035
1036
1037 @Override
1038 public final String toString()
1039 {
1040 return "TimeUpdateTask of ClockPanel";
1041 }
1042 }
1043
1044
1045
1046
1047 protected JLabel getSpeedLabel()
1048 {
1049 return this.speedLabel;
1050 }
1051
1052
1053
1054
1055
1056
1057 protected double getSpeed(final double t)
1058 {
1059 double speed = (t - this.prevSimTime) / (0.001 * UPDATEINTERVAL);
1060 this.prevSimTime = t;
1061 return speed;
1062 }
1063
1064
1065 @Override
1066 public boolean isForeground()
1067 {
1068 return true;
1069 }
1070
1071
1072 @Override
1073 public final String toString()
1074 {
1075 return "ClockPanel";
1076 }
1077
1078 }
1079
1080
1081 public class TimeEdit extends JFormattedTextField implements AppearanceControl
1082 {
1083
1084 private static final long serialVersionUID = 20141212L;
1085
1086
1087
1088
1089
1090 TimeEdit(final Time initialValue)
1091 {
1092 super(new RegexFormatter("\\d\\d\\d\\d:[0-5]\\d:[0-5]\\d\\.\\d\\d\\d"));
1093 MaskFormatter mf = null;
1094 try
1095 {
1096 mf = new MaskFormatter("####:##:##.###");
1097 mf.setPlaceholderCharacter('0');
1098 mf.setAllowsInvalid(false);
1099 mf.setCommitsOnValidEdit(true);
1100 mf.setOverwriteMode(true);
1101 mf.install(this);
1102 }
1103 catch (ParseException exception)
1104 {
1105 exception.printStackTrace();
1106 }
1107 setTime(initialValue);
1108 setFont(getTimeFont());
1109 }
1110
1111
1112
1113
1114
1115 public void setTime(final Time newValue)
1116 {
1117 double v = newValue.getSI();
1118 int integerPart = (int) Math.floor(v);
1119 int fraction = (int) Math.floor((v - integerPart) * 1000);
1120 String text =
1121 String.format("%04d:%02d:%02d.%03d", integerPart / 3600, integerPart / 60 % 60, integerPart % 60, fraction);
1122 this.setText(text);
1123 }
1124
1125
1126 @Override
1127 public final String toString()
1128 {
1129 return "TimeEdit [time=" + getText() + "]";
1130 }
1131 }
1132
1133
1134
1135
1136
1137
1138
1139 static class RegexFormatter extends DefaultFormatter
1140 {
1141
1142 private static final long serialVersionUID = 20141212L;
1143
1144
1145 private Pattern pattern;
1146
1147
1148
1149
1150
1151 RegexFormatter(final String pattern)
1152 {
1153 this.pattern = Pattern.compile(pattern);
1154 }
1155
1156 @Override
1157 public Object stringToValue(final String text) throws ParseException
1158 {
1159 Matcher matcher = this.pattern.matcher(text);
1160 if (matcher.matches())
1161 {
1162
1163 return super.stringToValue(text);
1164 }
1165
1166 throw new ParseException("Pattern did not match", 0);
1167 }
1168
1169
1170 @Override
1171 public final String toString()
1172 {
1173 return "RegexFormatter [pattern=" + this.pattern + "]";
1174 }
1175 }
1176
1177
1178 @Override
1179 public final void notify(final Event event) throws RemoteException
1180 {
1181 if (event.getType().equals(ReplicationInterface.END_REPLICATION_EVENT)
1182 || event.getType().equals(SimulatorInterface.START_EVENT)
1183 || event.getType().equals(SimulatorInterface.STOP_EVENT)
1184 || event.getType().equals(DEVSRealTimeAnimator.CHANGE_SPEED_FACTOR_EVENT))
1185 {
1186
1187 if (event.getType().equals(DEVSRealTimeAnimator.CHANGE_SPEED_FACTOR_EVENT))
1188 {
1189 this.timeWarpPanel.setSpeedFactor((Double) event.getContent());
1190 }
1191 fixButtons();
1192 }
1193 }
1194
1195
1196 @Override
1197 public final String toString()
1198 {
1199 return "OtsControlPanel [simulatorTime=" + this.simulator.getSimulatorTime() + ", timeWarp="
1200 + this.timeWarpPanel.getFactor() + ", stopAtEvent=" + this.stopAtEvent + "]";
1201 }
1202
1203 }