1 package org.opentrafficsim.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.simulationengine.Resource;
54 import org.opentrafficsim.simulationengine.WrappableAnimation;
55
56 import nl.tudelft.simulation.dsol.SimRuntimeException;
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 import nl.tudelft.simulation.event.EventInterface;
65 import nl.tudelft.simulation.event.EventListenerInterface;
66
67
68
69
70
71
72
73
74
75
76
77
78 public class OTSControlPanel extends JPanel
79 implements ActionListener, PropertyChangeListener, WindowListener, EventListenerInterface
80 {
81
82 private static final long serialVersionUID = 20150617L;
83
84
85 private DEVSSimulatorInterface.TimeDoubleUnit simulator;
86
87
88 private final WrappableAnimation wrappableAnimation;
89
90
91 private final Logger logger;
92
93
94 private final ClockLabel clockPanel;
95
96
97 private final TimeWarpPanel timeWarpPanel;
98
99
100 private final ArrayList<JButton> buttons = new ArrayList<>();
101
102
103 private final Font timeFont = new Font("SansSerif", Font.BOLD, 18);
104
105
106 private final TimeEdit timeEdit;
107
108
109 private SimEvent<SimTimeDoubleUnit> stopAtEvent = null;
110
111
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 WrappableAnimation wrappableAnimation)
124 throws RemoteException
125 {
126 this.simulator = simulator;
127 this.wrappableAnimation = wrappableAnimation;
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_OF_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 (@SuppressWarnings("unused") 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 (@SuppressWarnings("unused") 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 (@SuppressWarnings("unused") 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 this.simulator.stop();
383 }
384 else if (getSimulator().getEventList().size() > 0)
385 {
386 this.simulator.start();
387 }
388 }
389 if (actionCommand.equals("NextTime"))
390 {
391 if (getSimulator().isRunning())
392 {
393 getSimulator().stop();
394 }
395 double now = getSimulator().getSimulatorTime().getSI();
396
397 try
398 {
399 this.stopAtEvent = scheduleEvent(new Time(now, TimeUnit.BASE), SimEventInterface.MIN_PRIORITY, this, this,
400 "autoPauseSimulator", null);
401 }
402 catch (@SuppressWarnings("unused") SimRuntimeException exception)
403 {
404 this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator", "Caught an exception "
405 + "while trying to schedule an autoPauseSimulator event at the current simulator time");
406 }
407 this.simulator.start();
408 }
409 if (actionCommand.equals("Reset"))
410 {
411 if (getSimulator().isRunning())
412 {
413 getSimulator().stop();
414 }
415
416 if (null == OTSControlPanel.this.wrappableAnimation)
417 {
418 throw new RuntimeException("Do not know how to restart this simulation");
419 }
420
421
422 Container root = OTSControlPanel.this;
423 while (!(root instanceof JFrame))
424 {
425 root = root.getParent();
426 }
427 JFrame frame = (JFrame) root;
428 Rectangle rect = frame.getBounds();
429 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
430 frame.dispose();
431 OTSControlPanel.this.cleanup();
432 try
433 {
434 OTSControlPanel.this.wrappableAnimation.rebuildSimulator(rect);
435 }
436 catch (Exception exception)
437 {
438 exception.printStackTrace();
439 }
440 }
441 fixButtons();
442 }
443 catch (Exception exception)
444 {
445 exception.printStackTrace();
446 }
447 }
448
449
450
451
452 private void cleanup()
453 {
454 if (!this.isCleanUp)
455 {
456 this.isCleanUp = true;
457 try
458 {
459 if (this.simulator != null)
460 {
461 if (this.simulator.isRunning())
462 {
463 this.simulator.stop();
464 }
465
466
467 getSimulator().getReplication().getExperiment().removeFromContext();
468 getSimulator().cleanUp();
469 }
470
471 if (this.clockPanel != null)
472 {
473 this.clockPanel.cancelTimer();
474 }
475
476 if (this.wrappableAnimation != null)
477 {
478 this.wrappableAnimation.stopTimersThreads();
479 }
480 }
481 catch (Throwable exception)
482 {
483 exception.printStackTrace();
484 }
485 }
486 }
487
488
489
490
491 protected final void fixButtons()
492 {
493
494 final boolean moreWorkToDo = getSimulator().getEventList().size() > 0;
495 for (JButton button : this.buttons)
496 {
497 final String actionCommand = button.getActionCommand();
498 if (actionCommand.equals("Step"))
499 {
500 button.setEnabled(moreWorkToDo);
501 }
502 else if (actionCommand.equals("RunPause"))
503 {
504 button.setEnabled(moreWorkToDo);
505 if (this.simulator.isRunning())
506 {
507 button.setToolTipText("Pause the simulation");
508 button.setIcon(OTSControlPanel.loadIcon("/Pause.png"));
509 }
510 else
511 {
512 button.setToolTipText("Run the simulation at the indicated speed");
513 button.setIcon(loadIcon("/Play.png"));
514 }
515 button.setEnabled(moreWorkToDo);
516 }
517 else if (actionCommand.equals("NextTime"))
518 {
519 button.setEnabled(moreWorkToDo);
520 }
521 else if (actionCommand.equals("Reset"))
522 {
523 button.setEnabled(true);
524 }
525 else
526 {
527 this.logger.logp(Level.SEVERE, "ControlPanel", "fixButtons", "", new Exception("Unknown button?"));
528 }
529 }
530
531 }
532
533
534
535
536 public final void autoPauseSimulator()
537 {
538 if (getSimulator().isRunning())
539 {
540 getSimulator().stop();
541 double currentTick = getSimulator().getSimulatorTime().getSI();
542 double nextTick = getSimulator().getEventList().first().getAbsoluteExecutionTime().get().getSI();
543
544
545 if (nextTick > currentTick)
546 {
547
548
549
550
551 try
552 {
553 this.stopAtEvent = scheduleEvent(new Time(nextTick, TimeUnit.BASE), SimEventInterface.MAX_PRIORITY, this,
554 this, "autoPauseSimulator", null);
555 getSimulator().start();
556 }
557 catch (@SuppressWarnings("unused") SimRuntimeException exception)
558 {
559 this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator",
560 "Caught an exception while trying to re-schedule an autoPauseEvent at the next real event");
561 }
562 }
563 else
564 {
565
566 if (SwingUtilities.isEventDispatchThread())
567 {
568
569 fixButtons();
570 }
571 else
572 {
573 try
574 {
575
576 SwingUtilities.invokeAndWait(new Runnable()
577 {
578 @Override
579 public void run()
580 {
581
582 fixButtons();
583
584 }
585 });
586 }
587 catch (Exception e)
588 {
589 if (e instanceof InterruptedException)
590 {
591 System.out.println("Caught " + e);
592
593 }
594 else
595 {
596 e.printStackTrace();
597 }
598 }
599 }
600 }
601 }
602 }
603
604
605 @Override
606 public final void propertyChange(final PropertyChangeEvent evt)
607 {
608
609 if (null != this.stopAtEvent)
610 {
611 getSimulator().cancelEvent(this.stopAtEvent);
612 this.stopAtEvent = null;
613 }
614 String newValue = (String) evt.getNewValue();
615 String[] fields = newValue.split("[:\\.]");
616 int hours = Integer.parseInt(fields[0]);
617 int minutes = Integer.parseInt(fields[1]);
618 int seconds = Integer.parseInt(fields[2]);
619 int fraction = Integer.parseInt(fields[3]);
620 double stopTime = hours * 3600 + minutes * 60 + seconds + fraction / 1000d;
621 if (stopTime < getSimulator().getSimulatorTime().getSI())
622 {
623 return;
624 }
625 else
626 {
627 try
628 {
629 this.stopAtEvent = scheduleEvent(new Time(stopTime, TimeUnit.BASE), SimEventInterface.MAX_PRIORITY, this, this,
630 "autoPauseSimulator", null);
631 }
632 catch (@SuppressWarnings("unused") SimRuntimeException exception)
633 {
634 this.logger.logp(Level.SEVERE, "ControlPanel", "propertyChange",
635 "Caught an exception while trying to schedule an autoPauseSimulator event");
636 }
637 }
638 }
639
640
641
642
643 @SuppressWarnings("unchecked")
644 public final DEVSSimulator<Time, Duration, SimTimeDoubleUnit> getSimulator()
645 {
646 return (DEVSSimulator<Time, Duration, SimTimeDoubleUnit>) this.simulator;
647 }
648
649
650 @Override
651 public void windowOpened(final WindowEvent e)
652 {
653
654 }
655
656
657 @Override
658 public final void windowClosing(final WindowEvent e)
659 {
660 if (this.simulator != null)
661 {
662 try
663 {
664 if (this.simulator.isRunning())
665 {
666 this.simulator.stop();
667 }
668 }
669 catch (SimRuntimeException exception)
670 {
671 exception.printStackTrace();
672 }
673 }
674 }
675
676
677 @Override
678 public final void windowClosed(final WindowEvent e)
679 {
680 cleanup();
681 }
682
683
684 @Override
685 public final void windowIconified(final WindowEvent e)
686 {
687
688 }
689
690
691 @Override
692 public final void windowDeiconified(final WindowEvent e)
693 {
694
695 }
696
697
698 @Override
699 public final void windowActivated(final WindowEvent e)
700 {
701
702 }
703
704
705 @Override
706 public final void windowDeactivated(final WindowEvent e)
707 {
708
709 }
710
711
712
713
714 public final Font getTimeFont()
715 {
716 return this.timeFont;
717 }
718
719
720 static class TimeWarpPanel extends JPanel
721 {
722
723 private static final long serialVersionUID = 20150408L;
724
725
726 private final JSlider slider;
727
728
729 private final int[] ratios;
730
731
732 private Map<Integer, Double> tickValues = new HashMap<>();
733
734
735
736
737
738
739
740
741
742
743
744 TimeWarpPanel(final double minimum, final double maximum, final double initialValue, final int ticksPerDecade,
745 final DEVSSimulatorInterface<?, ?, ?> simulator)
746 {
747 if (minimum <= 0 || minimum > initialValue || initialValue > maximum)
748 {
749 throw new RuntimeException("Bad (combination of) minimum, maximum and initialValue; "
750 + "(restrictions: 0 < minimum <= initialValue <= maximum)");
751 }
752 switch (ticksPerDecade)
753 {
754 case 1:
755 this.ratios = new int[] { 1 };
756 break;
757 case 2:
758 this.ratios = new int[] { 1, 3 };
759 break;
760 case 3:
761 this.ratios = new int[] { 1, 2, 5 };
762 break;
763 default:
764 throw new RuntimeException("Bad ticksPerDecade value (must be 1, 2 or 3)");
765 }
766 int minimumTick = (int) Math.floor(Math.log10(minimum / initialValue) * ticksPerDecade);
767 int maximumTick = (int) Math.ceil(Math.log10(maximum / initialValue) * ticksPerDecade);
768 this.slider = new JSlider(SwingConstants.HORIZONTAL, minimumTick, maximumTick + 1, 0);
769 this.slider.setPreferredSize(new Dimension(350, 45));
770 Hashtable<Integer, JLabel> labels = new Hashtable<>();
771 for (int step = 0; step <= maximumTick; step++)
772 {
773 StringBuilder text = new StringBuilder();
774 text.append(this.ratios[step % this.ratios.length]);
775 for (int decade = 0; decade < step / this.ratios.length; decade++)
776 {
777 text.append("0");
778 }
779 this.tickValues.put(step, Double.parseDouble(text.toString()));
780 labels.put(step, new JLabel(text.toString().replace("000", "K")));
781
782 }
783
784 String decimalSeparator =
785 "" + ((DecimalFormat) NumberFormat.getInstance()).getDecimalFormatSymbols().getDecimalSeparator();
786 for (int step = -1; step >= minimumTick; step--)
787 {
788 StringBuilder text = new StringBuilder();
789 text.append("0");
790 text.append(decimalSeparator);
791 for (int decade = (step + 1) / this.ratios.length; decade < 0; decade++)
792 {
793 text.append("0");
794 }
795 int index = step % this.ratios.length;
796 if (index < 0)
797 {
798 index += this.ratios.length;
799 }
800 text.append(this.ratios[index]);
801 labels.put(step, new JLabel(text.toString()));
802 this.tickValues.put(step, Double.parseDouble(text.toString()));
803
804 }
805 labels.put(maximumTick + 1, new JLabel("\u221E"));
806 this.tickValues.put(maximumTick + 1, 1E9);
807 this.slider.setLabelTable(labels);
808 this.slider.setPaintLabels(true);
809 this.slider.setPaintTicks(true);
810 this.slider.setMajorTickSpacing(1);
811 this.add(this.slider);
812
813
814
815
816
817
818
819
820 if (simulator instanceof DEVSRealTimeClock)
821 {
822 DEVSRealTimeClock<?, ?, ?> clock = (DEVSRealTimeClock<?, ?, ?>) simulator;
823 clock.setSpeedFactor(TimeWarpPanel.this.tickValues.get(this.slider.getValue()));
824 }
825
826
827 this.slider.addChangeListener(new ChangeListener()
828 {
829 @Override
830 public void stateChanged(final ChangeEvent ce)
831 {
832 JSlider source = (JSlider) ce.getSource();
833 if (!source.getValueIsAdjusting() && simulator instanceof DEVSRealTimeClock)
834 {
835 DEVSRealTimeClock<?, ?, ?> clock = (DEVSRealTimeClock<?, ?, ?>) simulator;
836 clock.setSpeedFactor(((TimeWarpPanel) source.getParent()).getTickValues().get(source.getValue()));
837 }
838 }
839 });
840 }
841
842
843
844
845
846 protected Map<Integer, Double> getTickValues()
847 {
848 return this.tickValues;
849 }
850
851
852
853
854
855
856 private double stepToFactor(final int step)
857 {
858 int index = step % this.ratios.length;
859 if (index < 0)
860 {
861 index += this.ratios.length;
862 }
863 double result = this.ratios[index];
864
865 int power = (step + 1000 * this.ratios.length) / this.ratios.length - 1000;
866 while (power > 0)
867 {
868 result *= 10;
869 power--;
870 }
871 while (power < 0)
872 {
873 result /= 10;
874 power++;
875 }
876 return result;
877 }
878
879
880
881
882
883 public final double getFactor()
884 {
885 return stepToFactor(this.slider.getValue());
886 }
887
888
889 @Override
890 public final String toString()
891 {
892 return "TimeWarpPanel [timeWarp=" + this.getFactor() + "]";
893 }
894
895
896
897
898
899 public void setSpeedFactor(final double factor)
900 {
901 int bestStep = -1;
902 double bestError = Double.MAX_VALUE;
903 for (int step = this.slider.getMinimum(); step < this.slider.getMaximum(); step++)
904 {
905 double ratio = getTickValues().get(step);
906 double logError = Math.abs(Math.log(factor / ratio));
907 if (logError < bestError)
908 {
909 bestStep = step;
910 bestError = logError;
911 }
912 }
913
914
915 if (this.slider.getValue() != bestStep && factor < 1.0E6)
916 {
917 this.slider.setValue(bestStep);
918 }
919 }
920 }
921
922
923 public class ClockLabel extends JLabel implements AppearanceControl
924 {
925
926 private static final long serialVersionUID = 20141211L;
927
928
929 private final JLabel speedLabel;
930
931
932 private Timer timer;
933
934
935 private static final long UPDATEINTERVAL = 1000;
936
937
938 private double prevSimTime = 0;
939
940
941
942
943
944 ClockLabel(final JLabel speedLabel)
945 {
946 super("00:00:00.000");
947 this.speedLabel = speedLabel;
948 speedLabel.setFont(getTimeFont());
949 this.setFont(getTimeFont());
950 this.timer = new Timer();
951 this.timer.scheduleAtFixedRate(new TimeUpdateTask(), 0, ClockLabel.UPDATEINTERVAL);
952 }
953
954
955
956
957 public void cancelTimer()
958 {
959 if (this.timer != null)
960 {
961 this.timer.cancel();
962 }
963 this.timer = null;
964 }
965
966
967 private class TimeUpdateTask extends TimerTask implements Serializable
968 {
969
970 private static final long serialVersionUID = 20140000L;
971
972
973
974
975 TimeUpdateTask()
976 {
977 }
978
979
980 @Override
981 public void run()
982 {
983 double now = Math.round(getSimulator().getSimulatorTime().getSI() * 1000) / 1000d;
984 int seconds = (int) Math.floor(now);
985 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
986 ClockLabel.this.setText(String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60,
987 seconds % 60, fractionalSeconds));
988 ClockLabel.this.repaint();
989 double speed = getSpeed(now);
990 if (Double.isNaN(speed))
991 {
992 getSpeedLabel().setText("");
993 }
994 else
995 {
996 getSpeedLabel().setText(String.format("% 5.2fx ", speed));
997 }
998 getSpeedLabel().repaint();
999 }
1000
1001
1002 @Override
1003 public final String toString()
1004 {
1005 return "TimeUpdateTask of ClockPanel";
1006 }
1007 }
1008
1009
1010
1011
1012 protected JLabel getSpeedLabel()
1013 {
1014 return this.speedLabel;
1015 }
1016
1017
1018
1019
1020
1021
1022 protected double getSpeed(final double t)
1023 {
1024 double speed = (t - this.prevSimTime) / (0.001 * UPDATEINTERVAL);
1025 this.prevSimTime = t;
1026 return speed;
1027 }
1028
1029
1030 @Override
1031 public boolean isForeground()
1032 {
1033 return true;
1034 }
1035
1036
1037 @Override
1038 public final String toString()
1039 {
1040 return "ClockPanel";
1041 }
1042
1043 }
1044
1045
1046 public class TimeEdit extends JFormattedTextField implements AppearanceControl
1047 {
1048
1049 private static final long serialVersionUID = 20141212L;
1050
1051
1052
1053
1054
1055 TimeEdit(final Time initialValue)
1056 {
1057 super(new RegexFormatter("\\d\\d\\d\\d:[0-5]\\d:[0-5]\\d\\.\\d\\d\\d"));
1058 MaskFormatter mf = null;
1059 try
1060 {
1061 mf = new MaskFormatter("####:##:##.###");
1062 mf.setPlaceholderCharacter('0');
1063 mf.setAllowsInvalid(false);
1064 mf.setCommitsOnValidEdit(true);
1065 mf.setOverwriteMode(true);
1066 mf.install(this);
1067 }
1068 catch (ParseException exception)
1069 {
1070 exception.printStackTrace();
1071 }
1072 setTime(initialValue);
1073 setFont(getTimeFont());
1074 }
1075
1076
1077
1078
1079
1080 public void setTime(final Time newValue)
1081 {
1082 double v = newValue.getSI();
1083 int integerPart = (int) Math.floor(v);
1084 int fraction = (int) Math.floor((v - integerPart) * 1000);
1085 String text =
1086 String.format("%04d:%02d:%02d.%03d", integerPart / 3600, integerPart / 60 % 60, integerPart % 60, fraction);
1087 this.setText(text);
1088 }
1089
1090
1091 @Override
1092 public final String toString()
1093 {
1094 return "TimeEdit [time=" + getText() + "]";
1095 }
1096 }
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107 static class RegexFormatter extends DefaultFormatter
1108 {
1109
1110 private static final long serialVersionUID = 20141212L;
1111
1112
1113 private Pattern pattern;
1114
1115
1116
1117
1118
1119 RegexFormatter(final String pattern)
1120 {
1121 this.pattern = Pattern.compile(pattern);
1122 }
1123
1124 @Override
1125 public Object stringToValue(final String text) throws ParseException
1126 {
1127 Matcher matcher = this.pattern.matcher(text);
1128 if (matcher.matches())
1129 {
1130
1131 return super.stringToValue(text);
1132 }
1133
1134 throw new ParseException("Pattern did not match", 0);
1135 }
1136
1137
1138 @Override
1139 public final String toString()
1140 {
1141 return "RegexFormatter [pattern=" + this.pattern + "]";
1142 }
1143 }
1144
1145
1146 @Override
1147 public final void notify(final EventInterface event) throws RemoteException
1148 {
1149 if (event.getType().equals(SimulatorInterface.END_OF_REPLICATION_EVENT)
1150 || event.getType().equals(SimulatorInterface.START_EVENT)
1151 || event.getType().equals(SimulatorInterface.STOP_EVENT)
1152 || event.getType().equals(DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT))
1153 {
1154 if (event.getType().equals(DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT))
1155 {
1156 this.timeWarpPanel.setSpeedFactor((Double) event.getContent());
1157 }
1158 fixButtons();
1159 }
1160 }
1161
1162
1163 @Override
1164 public final String toString()
1165 {
1166 return "OTSControlPanel [simulatorTime=" + this.simulator.getSimulatorTime() + ", timeWarp="
1167 + this.timeWarpPanel.getFactor() + ", stopAtEvent=" + this.stopAtEvent + "]";
1168 }
1169
1170 }