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