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