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