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