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