1 package nl.tudelft.simulation.dsol.swing.gui.control;
2
3 import java.awt.Dimension;
4 import java.text.DecimalFormat;
5 import java.text.NumberFormat;
6 import java.util.Hashtable;
7 import java.util.LinkedHashMap;
8 import java.util.Map;
9
10 import javax.swing.JLabel;
11 import javax.swing.JPanel;
12 import javax.swing.JSlider;
13 import javax.swing.SwingConstants;
14 import javax.swing.event.ChangeEvent;
15 import javax.swing.event.ChangeListener;
16
17 import nl.tudelft.simulation.dsol.simulators.DevsRealTimeAnimator;
18 import nl.tudelft.simulation.dsol.simulators.DevsSimulatorInterface;
19
20
21
22
23
24
25
26
27
28
29
30 public class RunSpeedSliderPanel extends JPanel
31 {
32
33 private static final long serialVersionUID = 20150408L;
34
35
36 private final JSlider slider;
37
38
39 private final int[] ratios;
40
41
42 private Map<Integer, Double> tickValues = new LinkedHashMap<>();
43
44
45
46
47
48
49
50
51
52
53
54 RunSpeedSliderPanel(final double minimum, final double maximum, final double initialValue, final int ticksPerDecade,
55 final DevsSimulatorInterface<?> simulator)
56 {
57 if (minimum <= 0 || minimum > initialValue || initialValue > maximum)
58 {
59 throw new RuntimeException("Bad (combination of) minimum, maximum and initialValue; "
60 + "(restrictions: 0 < minimum <= initialValue <= maximum)");
61 }
62 switch (ticksPerDecade)
63 {
64 case 1:
65 this.ratios = new int[] {1};
66 break;
67 case 2:
68 this.ratios = new int[] {1, 3};
69 break;
70 case 3:
71 this.ratios = new int[] {1, 2, 5};
72 break;
73 default:
74 throw new RuntimeException("Bad ticksPerDecade value (must be 1, 2 or 3)");
75 }
76 int minimumTick = (int) Math.floor(Math.log10(minimum / initialValue) * ticksPerDecade);
77 int maximumTick = (int) Math.ceil(Math.log10(maximum / initialValue) * ticksPerDecade);
78 this.slider = new JSlider(SwingConstants.HORIZONTAL, minimumTick, maximumTick + 1, 0);
79 this.slider.setPreferredSize(new Dimension(350, 45));
80 Hashtable<Integer, JLabel> labels = new Hashtable<>();
81 for (int step = 0; step <= maximumTick; step++)
82 {
83 StringBuilder text = new StringBuilder();
84 text.append(this.ratios[step % this.ratios.length]);
85 for (int decade = 0; decade < step / this.ratios.length; decade++)
86 {
87 text.append("0");
88 }
89 this.tickValues.put(step, Double.parseDouble(text.toString()));
90 labels.put(step, new JLabel(text.toString().replace("000", "K")));
91
92 }
93
94 String decimalSeparator =
95 "" + ((DecimalFormat) NumberFormat.getInstance()).getDecimalFormatSymbols().getDecimalSeparator();
96 for (int step = -1; step >= minimumTick; step--)
97 {
98 StringBuilder text = new StringBuilder();
99 text.append("0");
100 text.append(decimalSeparator);
101 for (int decade = (step + 1) / this.ratios.length; decade < 0; decade++)
102 {
103 text.append("0");
104 }
105 int index = step % this.ratios.length;
106 if (index < 0)
107 {
108 index += this.ratios.length;
109 }
110 text.append(this.ratios[index]);
111 labels.put(step, new JLabel(text.toString()));
112 this.tickValues.put(step, Double.parseDouble(text.toString()));
113
114 }
115 labels.put(maximumTick + 1, new JLabel("\u221E"));
116 this.tickValues.put(maximumTick + 1, 1E9);
117 this.slider.setLabelTable(labels);
118 this.slider.setPaintLabels(true);
119 this.slider.setPaintTicks(true);
120 this.slider.setMajorTickSpacing(1);
121 this.add(this.slider);
122
123
124
125
126
127
128
129
130 if (simulator instanceof DevsRealTimeAnimator)
131 {
132 DevsRealTimeAnimator<?> clock = (DevsRealTimeAnimator<?>) simulator;
133 clock.setSpeedFactor(RunSpeedSliderPanel.this.tickValues.get(this.slider.getValue()));
134 }
135
136
137 this.slider.addChangeListener(new ChangeListener()
138 {
139 @Override
140 public void stateChanged(final ChangeEvent ce)
141 {
142 JSlider source = (JSlider) ce.getSource();
143 if (!source.getValueIsAdjusting() && simulator instanceof DevsRealTimeAnimator)
144 {
145 DevsRealTimeAnimator<?> clock = (DevsRealTimeAnimator<?>) simulator;
146 clock.setSpeedFactor(((RunSpeedSliderPanel) source.getParent()).getTickValues().get(source.getValue()));
147 }
148 }
149 });
150 }
151
152
153
154
155
156 protected Map<Integer, Double> getTickValues()
157 {
158 return this.tickValues;
159 }
160
161
162
163
164
165
166 private double stepToFactor(final int step)
167 {
168 int index = step % this.ratios.length;
169 if (index < 0)
170 {
171 index += this.ratios.length;
172 }
173 double result = this.ratios[index];
174
175 int power = (step + 1000 * this.ratios.length) / this.ratios.length - 1000;
176 while (power > 0)
177 {
178 result *= 10;
179 power--;
180 }
181 while (power < 0)
182 {
183 result /= 10;
184 power++;
185 }
186 return result;
187 }
188
189
190
191
192
193 public double getFactor()
194 {
195 return stepToFactor(this.slider.getValue());
196 }
197
198
199 @Override
200 public String toString()
201 {
202 return "TimeWarpPanel [timeWarp=" + this.getFactor() + "]";
203 }
204
205
206
207
208
209 public void setSpeedFactor(final double factor)
210 {
211 int bestStep = -1;
212 double bestError = Double.MAX_VALUE;
213 double logOfFactor = Math.log(factor);
214 for (int step = this.slider.getMinimum(); step <= this.slider.getMaximum(); step++)
215 {
216 double ratio = getTickValues().get(step);
217 double logError = Math.abs(logOfFactor - Math.log(ratio));
218 if (logError < bestError)
219 {
220 bestStep = step;
221 bestError = logError;
222 }
223 }
224
225
226 if (this.slider.getValue() != bestStep)
227 {
228 this.slider.setValue(bestStep);
229 }
230 }
231 }