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