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