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