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 org.djunits.unit.TimeUnit;
48 import org.djunits.value.vdouble.scalar.Duration;
49 import org.djunits.value.vdouble.scalar.Time;
50 import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
51 import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
52 import org.opentrafficsim.simulationengine.WrappableAnimation;
53
54 import nl.tudelft.simulation.dsol.SimRuntimeException;
55 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
56 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
57 import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeClock;
58 import nl.tudelft.simulation.dsol.simulators.DEVSSimulator;
59 import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
60 import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
61 import nl.tudelft.simulation.event.EventInterface;
62 import nl.tudelft.simulation.event.EventListenerInterface;
63 import nl.tudelft.simulation.language.io.URLResource;
64
65
66
67
68
69
70
71
72
73
74
75
76 public class OTSControlPanel extends JPanel
77 implements ActionListener, PropertyChangeListener, WindowListener, 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.BASE));
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.BASE)), priority,
212 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 = scheduleEvent(new Time(now, TimeUnit.BASE), SimEventInterface.MIN_PRIORITY, this, this,
321 "autoPauseSimulator", null);
322 }
323 catch (SimRuntimeException exception)
324 {
325 this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator", "Caught an exception "
326 + "while trying to schedule an autoPauseSimulator event at the current simulator time");
327 }
328 this.simulator.start();
329 }
330 if (actionCommand.equals("Reset"))
331 {
332 if (getSimulator().isRunning())
333 {
334 getSimulator().stop();
335 }
336
337 if (null == OTSControlPanel.this.wrappableAnimation)
338 {
339 throw new RuntimeException("Do not know how to restart this simulation");
340 }
341
342
343 Container root = OTSControlPanel.this;
344 while (!(root instanceof JFrame))
345 {
346 root = root.getParent();
347 }
348 JFrame frame = (JFrame) root;
349 Rectangle rect = frame.getBounds();
350 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
351 frame.dispose();
352 OTSControlPanel.this.cleanup();
353 try
354 {
355 OTSControlPanel.this.wrappableAnimation.rebuildSimulator(rect);
356 }
357 catch (Exception exception)
358 {
359 exception.printStackTrace();
360 }
361 }
362 fixButtons();
363 }
364 catch (Exception exception)
365 {
366 exception.printStackTrace();
367 }
368 }
369
370
371
372
373 private void cleanup()
374 {
375 if (!this.isCleanUp)
376 {
377 this.isCleanUp = true;
378 try
379 {
380 if (this.simulator != null)
381 {
382 if (this.simulator.isRunning())
383 {
384 this.simulator.stop();
385 }
386
387
388 getSimulator().getReplication().getExperiment().removeFromContext();
389 getSimulator().cleanUp();
390 }
391
392 if (this.clockPanel != null)
393 {
394 this.clockPanel.cancelTimer();
395 }
396
397 if (this.wrappableAnimation != null)
398 {
399 this.wrappableAnimation.stopTimersThreads();
400 }
401 }
402 catch (Throwable exception)
403 {
404 exception.printStackTrace();
405 }
406 }
407 }
408
409
410
411
412 protected final void fixButtons()
413 {
414
415 final boolean moreWorkToDo = getSimulator().getEventList().size() > 0;
416 for (JButton button : this.buttons)
417 {
418 final String actionCommand = button.getActionCommand();
419 if (actionCommand.equals("Step"))
420 {
421 button.setEnabled(moreWorkToDo);
422 }
423 else if (actionCommand.equals("RunPause"))
424 {
425 button.setEnabled(moreWorkToDo);
426 if (this.simulator.isRunning())
427 {
428 button.setToolTipText("Pause the simulation");
429 button.setIcon(OTSControlPanel.loadIcon("/Pause.png"));
430 }
431 else
432 {
433 button.setToolTipText("Run the simulation at the indicated speed");
434 button.setIcon(loadIcon("/Play.png"));
435 }
436 button.setEnabled(moreWorkToDo);
437 }
438 else if (actionCommand.equals("NextTime"))
439 {
440 button.setEnabled(moreWorkToDo);
441 }
442 else if (actionCommand.equals("Reset"))
443 {
444 button.setEnabled(true);
445 }
446 else
447 {
448 this.logger.logp(Level.SEVERE, "ControlPanel", "fixButtons", "", new Exception("Unknown button?"));
449 }
450 }
451
452 }
453
454
455
456
457 public final void autoPauseSimulator()
458 {
459 if (getSimulator().isRunning())
460 {
461 getSimulator().stop();
462 double currentTick = getSimulator().getSimulatorTime().getTime().getSI();
463 double nextTick = getSimulator().getEventList().first().getAbsoluteExecutionTime().get().getSI();
464
465
466 if (nextTick > currentTick)
467 {
468
469
470
471
472 try
473 {
474 this.stopAtEvent = scheduleEvent(new Time(nextTick, TimeUnit.BASE), SimEventInterface.MAX_PRIORITY, this,
475 this, "autoPauseSimulator", null);
476 getSimulator().start();
477 }
478 catch (SimRuntimeException exception)
479 {
480 this.logger.logp(Level.SEVERE, "ControlPanel", "autoPauseSimulator",
481 "Caught an exception while trying to re-schedule an autoPauseEvent at the next real event");
482 }
483 }
484 else
485 {
486
487 if (SwingUtilities.isEventDispatchThread())
488 {
489
490 fixButtons();
491 }
492 else
493 {
494 try
495 {
496
497 SwingUtilities.invokeAndWait(new Runnable()
498 {
499 @Override
500 public void run()
501 {
502
503 fixButtons();
504
505 }
506 });
507 }
508 catch (Exception e)
509 {
510 if (e instanceof InterruptedException)
511 {
512 System.out.println("Caught " + e);
513
514 }
515 else
516 {
517 e.printStackTrace();
518 }
519 }
520 }
521 }
522 }
523 }
524
525
526 @Override
527 public final void propertyChange(final PropertyChangeEvent evt)
528 {
529
530 if (null != this.stopAtEvent)
531 {
532 getSimulator().cancelEvent(this.stopAtEvent);
533 this.stopAtEvent = null;
534 }
535 String newValue = (String) evt.getNewValue();
536 String[] fields = newValue.split("[:\\.]");
537 int hours = Integer.parseInt(fields[0]);
538 int minutes = Integer.parseInt(fields[1]);
539 int seconds = Integer.parseInt(fields[2]);
540 int fraction = Integer.parseInt(fields[3]);
541 double stopTime = hours * 3600 + minutes * 60 + seconds + fraction / 1000d;
542 if (stopTime < getSimulator().getSimulatorTime().getTime().getSI())
543 {
544 return;
545 }
546 else
547 {
548 try
549 {
550 this.stopAtEvent = scheduleEvent(new Time(stopTime, TimeUnit.BASE), SimEventInterface.MAX_PRIORITY, this, this,
551 "autoPauseSimulator", null);
552 }
553 catch (SimRuntimeException exception)
554 {
555 this.logger.logp(Level.SEVERE, "ControlPanel", "propertyChange",
556 "Caught an exception while trying to schedule an autoPauseSimulator event");
557 }
558 }
559 }
560
561
562
563
564 @SuppressWarnings("unchecked")
565 public final DEVSSimulator<Time, Duration, OTSSimTimeDouble> getSimulator()
566 {
567 return (DEVSSimulator<Time, Duration, OTSSimTimeDouble>) this.simulator;
568 }
569
570
571 @Override
572 public void windowOpened(final WindowEvent e)
573 {
574
575 }
576
577
578 @Override
579 public final void windowClosing(final WindowEvent e)
580 {
581 if (this.simulator != null)
582 {
583 try
584 {
585 if (this.simulator.isRunning())
586 {
587 this.simulator.stop();
588 }
589 }
590 catch (SimRuntimeException exception)
591 {
592 exception.printStackTrace();
593 }
594 }
595 }
596
597
598 @Override
599 public final void windowClosed(final WindowEvent e)
600 {
601 cleanup();
602 }
603
604
605 @Override
606 public final void windowIconified(final WindowEvent e)
607 {
608
609 }
610
611
612 @Override
613 public final void windowDeiconified(final WindowEvent e)
614 {
615
616 }
617
618
619 @Override
620 public final void windowActivated(final WindowEvent e)
621 {
622
623 }
624
625
626 @Override
627 public final void windowDeactivated(final WindowEvent e)
628 {
629
630 }
631
632
633
634
635 public final Font getTimeFont()
636 {
637 return this.timeFont;
638 }
639
640
641 static class TimeWarpPanel extends JPanel
642 {
643
644 private static final long serialVersionUID = 20150408L;
645
646
647 private final JSlider slider;
648
649
650 private final int[] ratios;
651
652
653 private Map<Integer, Double> tickValues = new HashMap<>();
654
655
656
657
658
659
660
661
662
663
664
665 TimeWarpPanel(final double minimum, final double maximum, final double initialValue, final int ticksPerDecade,
666 final DEVSSimulatorInterface<?, ?, ?> simulator)
667 {
668 if (minimum <= 0 || minimum > initialValue || initialValue > maximum)
669 {
670 throw new RuntimeException("Bad (combination of) minimum, maximum and initialValue; "
671 + "(restrictions: 0 < minimum <= initialValue <= maximum)");
672 }
673 switch (ticksPerDecade)
674 {
675 case 1:
676 this.ratios = new int[] { 1 };
677 break;
678 case 2:
679 this.ratios = new int[] { 1, 3 };
680 break;
681 case 3:
682 this.ratios = new int[] { 1, 2, 5 };
683 break;
684 default:
685 throw new RuntimeException("Bad ticksPerDecade value (must be 1, 2 or 3)");
686 }
687 int minimumTick = (int) Math.floor(Math.log10(minimum / initialValue) * ticksPerDecade);
688 int maximumTick = (int) Math.ceil(Math.log10(maximum / initialValue) * ticksPerDecade);
689 this.slider = new JSlider(SwingConstants.HORIZONTAL, minimumTick, maximumTick + 1, 0);
690 this.slider.setPreferredSize(new Dimension(350, 45));
691 Hashtable<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
692 for (int step = 0; step <= maximumTick; step++)
693 {
694 StringBuilder text = new StringBuilder();
695 text.append(this.ratios[step % this.ratios.length]);
696 for (int decade = 0; decade < step / this.ratios.length; decade++)
697 {
698 text.append("0");
699 }
700 this.tickValues.put(step, Double.parseDouble(text.toString()));
701 labels.put(step, new JLabel(text.toString().replace("000", "K")));
702
703 }
704
705 String decimalSeparator =
706 "" + ((DecimalFormat) NumberFormat.getInstance()).getDecimalFormatSymbols().getDecimalSeparator();
707 for (int step = -1; step >= minimumTick; step--)
708 {
709 StringBuilder text = new StringBuilder();
710 text.append("0");
711 text.append(decimalSeparator);
712 for (int decade = (step + 1) / this.ratios.length; decade < 0; decade++)
713 {
714 text.append("0");
715 }
716 int index = step % this.ratios.length;
717 if (index < 0)
718 {
719 index += this.ratios.length;
720 }
721 text.append(this.ratios[index]);
722 labels.put(step, new JLabel(text.toString()));
723 this.tickValues.put(step, Double.parseDouble(text.toString()));
724
725 }
726 labels.put(maximumTick + 1, new JLabel("\u221E"));
727 this.tickValues.put(maximumTick + 1, 1E9);
728 this.slider.setLabelTable(labels);
729 this.slider.setPaintLabels(true);
730 this.slider.setPaintTicks(true);
731 this.slider.setMajorTickSpacing(1);
732 this.add(this.slider);
733
734
735
736
737
738
739
740
741 if (simulator instanceof DEVSRealTimeClock)
742 {
743 DEVSRealTimeClock<?, ?, ?> clock = (DEVSRealTimeClock<?, ?, ?>) simulator;
744 clock.setSpeedFactor(TimeWarpPanel.this.tickValues.get(this.slider.getValue()));
745 }
746
747
748 this.slider.addChangeListener(new ChangeListener()
749 {
750 public void stateChanged(final ChangeEvent ce)
751 {
752 JSlider source = (JSlider) ce.getSource();
753 if (!source.getValueIsAdjusting() && simulator instanceof DEVSRealTimeClock)
754 {
755 DEVSRealTimeClock<?, ?, ?> clock = (DEVSRealTimeClock<?, ?, ?>) simulator;
756 clock.setSpeedFactor(((TimeWarpPanel) source.getParent()).getTickValues().get(source.getValue()));
757 }
758 }
759 });
760 }
761
762
763
764
765
766 protected Map<Integer, Double> getTickValues()
767 {
768 return this.tickValues;
769 }
770
771
772
773
774
775
776 private double stepToFactor(final int step)
777 {
778 int index = step % this.ratios.length;
779 if (index < 0)
780 {
781 index += this.ratios.length;
782 }
783 double result = this.ratios[index];
784
785 int power = (step + 1000 * this.ratios.length) / this.ratios.length - 1000;
786 while (power > 0)
787 {
788 result *= 10;
789 power--;
790 }
791 while (power < 0)
792 {
793 result /= 10;
794 power++;
795 }
796 return result;
797 }
798
799
800
801
802
803 public final double getFactor()
804 {
805 return stepToFactor(this.slider.getValue());
806 }
807
808
809 @Override
810 public final String toString()
811 {
812 return "TimeWarpPanel [timeWarp=" + this.getFactor() + "]";
813 }
814
815
816
817
818
819 public void setSpeedFactor(final double factor)
820 {
821 int bestStep = -1;
822 double bestError = Double.MAX_VALUE;
823 for (int step = this.slider.getMinimum(); step < this.slider.getMaximum(); step++)
824 {
825 double ratio = getTickValues().get(step);
826 double logError = Math.abs(Math.log(factor / ratio));
827 if (logError < bestError)
828 {
829 bestStep = step;
830 bestError = logError;
831 }
832 }
833 System.out.println("setSpeedfactor: factor is " + factor + ", best slider value is " + bestStep
834 + " current value is " + this.slider.getValue());
835 if (this.slider.getValue() != bestStep && factor < 1.0E6)
836 {
837 this.slider.setValue(bestStep);
838 }
839 }
840 }
841
842
843 class ClockPanel extends JLabel
844 {
845
846 private static final long serialVersionUID = 20141211L;
847
848
849 private final JLabel clockLabel;
850
851
852 private Timer timer;
853
854
855 private static final long UPDATEINTERVAL = 1000;
856
857
858 ClockPanel()
859 {
860 super("00:00:00.000");
861 this.clockLabel = this;
862 this.setFont(getTimeFont());
863 this.timer = new Timer();
864 this.timer.scheduleAtFixedRate(new TimeUpdateTask(), 0, ClockPanel.UPDATEINTERVAL);
865 }
866
867
868
869
870 public void cancelTimer()
871 {
872 if (this.timer != null)
873 {
874 this.timer.cancel();
875 }
876 this.timer = null;
877 }
878
879
880 private class TimeUpdateTask extends TimerTask implements Serializable
881 {
882
883 private static final long serialVersionUID = 20140000L;
884
885
886
887
888 TimeUpdateTask()
889 {
890 }
891
892
893 @Override
894 public void run()
895 {
896 double now = Math.round(getSimulator().getSimulatorTime().getTime().getSI() * 1000) / 1000d;
897 int seconds = (int) Math.floor(now);
898 int fractionalSeconds = (int) Math.floor(1000 * (now - seconds));
899 getClockLabel().setText(String.format(" %02d:%02d:%02d.%03d ", seconds / 3600, seconds / 60 % 60,
900 seconds % 60, fractionalSeconds));
901 getClockLabel().repaint();
902 }
903
904
905 @Override
906 public final String toString()
907 {
908 return "TimeUpdateTask of ClockPanel";
909 }
910 }
911
912
913
914
915 protected JLabel getClockLabel()
916 {
917 return this.clockLabel;
918 }
919
920
921 @Override
922 public final String toString()
923 {
924 return "ClockPanel [clockLabel=" + this.clockLabel + ", time=" + getText() + "]";
925 }
926
927 }
928
929
930 class TimeEdit extends JFormattedTextField
931 {
932
933 private static final long serialVersionUID = 20141212L;
934
935
936
937
938
939 TimeEdit(final Time initialValue)
940 {
941 super(new RegexFormatter("\\d\\d\\d\\d:[0-5]\\d:[0-5]\\d\\.\\d\\d\\d"));
942 MaskFormatter mf = null;
943 try
944 {
945 mf = new MaskFormatter("####:##:##.###");
946 mf.setPlaceholderCharacter('0');
947 mf.setAllowsInvalid(false);
948 mf.setCommitsOnValidEdit(true);
949 mf.setOverwriteMode(true);
950 mf.install(this);
951 }
952 catch (ParseException exception)
953 {
954 exception.printStackTrace();
955 }
956 setTime(initialValue);
957 setFont(getTimeFont());
958 }
959
960
961
962
963
964 public void setTime(final Time newValue)
965 {
966 double v = newValue.getSI();
967 int integerPart = (int) Math.floor(v);
968 int fraction = (int) Math.floor((v - integerPart) * 1000);
969 String text =
970 String.format("%04d:%02d:%02d.%03d", integerPart / 3600, integerPart / 60 % 60, integerPart % 60, fraction);
971 this.setText(text);
972 }
973
974
975 @Override
976 public final String toString()
977 {
978 return "TimeEdit [time=" + getText() + "]";
979 }
980 }
981
982
983
984
985
986
987
988
989
990
991 static class RegexFormatter extends DefaultFormatter
992 {
993
994 private static final long serialVersionUID = 20141212L;
995
996
997 private Pattern pattern;
998
999
1000
1001
1002
1003 RegexFormatter(final String pattern)
1004 {
1005 this.pattern = Pattern.compile(pattern);
1006 }
1007
1008 @Override
1009 public Object stringToValue(final String text) throws ParseException
1010 {
1011 Matcher matcher = this.pattern.matcher(text);
1012 if (matcher.matches())
1013 {
1014
1015 return super.stringToValue(text);
1016 }
1017
1018 throw new ParseException("Pattern did not match", 0);
1019 }
1020
1021
1022 @Override
1023 public final String toString()
1024 {
1025 return "RegexFormatter [pattern=" + this.pattern + "]";
1026 }
1027 }
1028
1029
1030 @Override
1031 public final void notify(final EventInterface event) throws RemoteException
1032 {
1033 if (event.getType().equals(SimulatorInterface.END_OF_REPLICATION_EVENT)
1034 || event.getType().equals(SimulatorInterface.START_EVENT)
1035 || event.getType().equals(SimulatorInterface.STOP_EVENT)
1036 || event.getType().equals(DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT))
1037 {
1038 if (event.getType().equals(DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT))
1039 {
1040 this.timeWarpPanel.setSpeedFactor((Double) event.getContent());
1041 }
1042 fixButtons();
1043 }
1044 }
1045
1046
1047 @Override
1048 public final String toString()
1049 {
1050 return "OTSControlPanel [simulatorTime=" + this.simulator.getSimulatorTime().getTime() + ", timeWarp="
1051 + this.timeWarpPanel.getFactor() + ", stopAtEvent=" + this.stopAtEvent + "]";
1052 }
1053
1054 }