Skip to content

Commit 539fd61

Browse files
committedApr 30, 2018
Let sliders in number widget support floating point numbers
1 parent 6e2c988 commit 539fd61

File tree

1 file changed

+104
-23
lines changed

1 file changed

+104
-23
lines changed
 

‎src/main/java/org/scijava/ui/swing/widget/SwingNumberWidget.java

+104-23
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@
4343
import java.math.BigInteger;
4444
import java.text.DecimalFormat;
4545
import java.text.ParsePosition;
46+
import java.util.Hashtable;
4647

4748
import javax.swing.JComponent;
49+
import javax.swing.JLabel;
4850
import javax.swing.JPanel;
4951
import javax.swing.JScrollBar;
5052
import javax.swing.JSlider;
@@ -83,7 +85,7 @@ public class SwingNumberWidget extends SwingInputWidget<Number> implements
8385
private LogService log;
8486

8587
private JScrollBar scrollBar;
86-
private JSlider slider;
88+
private CalibratedSlider slider;
8789
private JSpinner spinner;
8890

8991
// -- InputWidget methods --
@@ -152,7 +154,7 @@ public void stateChanged(final ChangeEvent e) {
152154
final Object source = e.getSource();
153155
if (source == slider) {
154156
// sync spinner with slider value
155-
final int value = slider.getValue();
157+
final Number value = slider.getCalibratedValue();
156158
spinner.setValue(value);
157159
}
158160
else if (source == spinner) {
@@ -166,7 +168,7 @@ else if (source == spinner) {
166168

167169
@Override
168170
public void mouseWheelMoved(final MouseWheelEvent e) {
169-
int value = getValue().intValue() + e.getWheelRotation();
171+
int value = getValue().intValue() + e.getWheelRotation(); // TODO convert from wheel rotations to steps on the slider
170172
value = Math.min(value, this.get().getMax().intValue());
171173
value = Math.max(value, this.get().getMin().intValue());
172174
spinner.setValue(value);
@@ -203,26 +205,17 @@ private void addSlider(final Number min, final Number max,
203205
log.warn("Invalid min/max/step; cannot render slider");
204206
return;
205207
}
206-
final int mn = min.intValue();
207-
final int mx = max.intValue();
208-
final int st = step.intValue();
209-
if ((long) mx - mn > Integer.MAX_VALUE) {
208+
// TODO Integer cases can be handled in a simpler way
209+
int sMin = 0;
210+
int sMax = (int) ((max.doubleValue() - min.doubleValue()) / step.doubleValue());
211+
long range = sMax - sMin;
212+
if (range > Integer.MAX_VALUE) {
210213
log.warn("Slider span too large; max - min < 2^31 required.");
211214
return;
212215
}
213-
final int span = mx - mn;
214-
215-
slider = new JSlider(mn, mx, mn);
216-
217-
// Compute optimal major ticks and labels.
218-
final int labelWidth = Math.max(("" + mn).length(), ("" + mx).length());
219-
slider.setMajorTickSpacing(labelWidth < 5 ? span / 4 : span);
220-
slider.setPaintLabels(labelWidth < 10);
221216

222-
// Compute optimal minor ticks.
223-
final int stepCount = span / st + 1;
224-
slider.setMinorTickSpacing(st);
225-
slider.setPaintTicks(stepCount < 100);
217+
// slider = new JSlider(sMin, sMax, sMin);
218+
slider = new CalibratedSlider(min, max, step);
226219

227220
setToolTip(slider);
228221
getComponent().add(slider);
@@ -314,11 +307,11 @@ public void run() {
314307
private void syncSliders() {
315308
if (slider != null) {
316309
// clamp value within slider bounds
317-
int value = getValue().intValue();
318-
if (value < slider.getMinimum()) value = slider.getMinimum();
319-
else if (value > slider.getMaximum()) value = slider.getMaximum();
310+
Number value = getValue().intValue();
311+
if (value.doubleValue() < slider.getCalibratedMinimum().doubleValue()) value = slider.getCalibratedMinimum();
312+
else if (value.doubleValue() > slider.getCalibratedMaximum().doubleValue()) value = slider.getCalibratedMaximum();
320313
slider.removeChangeListener(this);
321-
slider.setValue(value);
314+
slider.setCalibratedValue(value);
322315
slider.addChangeListener(this);
323316
}
324317
if (scrollBar != null) {
@@ -340,4 +333,92 @@ public void doRefresh() {
340333
if (spinner.getValue().equals(value)) return; // no change
341334
spinner.setValue(value);
342335
}
336+
337+
private class CalibratedSlider extends JSlider {
338+
339+
private Number min;
340+
private Number max;
341+
private Number stepSize;
342+
343+
private CalibratedSlider(final Number min, final Number max, final Number stepSize) {
344+
super();
345+
346+
this.min = min;
347+
this.max = max;
348+
this.stepSize = stepSize;
349+
350+
int sMin = 0;
351+
int sMax = (int) ((max.doubleValue() - min.doubleValue()) / stepSize.doubleValue());
352+
353+
// Adjust max to be an integer multiple of stepSize
354+
this.max = min.doubleValue() + (sMax-sMin) * stepSize.doubleValue();
355+
356+
setMinimum(sMin);
357+
setMaximum(sMax);
358+
setValue(sMin);
359+
360+
// Compute label width to determine number of labels
361+
int scale = Math.max(0, new BigDecimal(stepSize.toString()).stripTrailingZeros().scale());
362+
JLabel minLabel = makeLabel(min, scale);
363+
JLabel maxLabel = makeLabel(max, scale);
364+
final int labelWidth = Math.max(minLabel.getText().length(), maxLabel.getText().length());
365+
366+
// Add labels
367+
Hashtable<Integer, JLabel> labelTable = new Hashtable<>(2);
368+
labelTable.put(sMin, minLabel);
369+
labelTable.put(sMax, maxLabel);
370+
if (labelWidth < 5 && sMax % 5 == 0) {
371+
// Put four intermediate labels
372+
labelTable.put(1 * sMax / 5,
373+
makeLabel(toCalibrated(1 * sMax / 5), scale));
374+
labelTable.put(2 * sMax / 5,
375+
makeLabel(toCalibrated(2 * sMax / 5), scale));
376+
labelTable.put(3 * sMax / 5,
377+
makeLabel(toCalibrated(3 * sMax / 5), scale));
378+
labelTable.put(4 * sMax / 5,
379+
makeLabel(toCalibrated(4 * sMax / 5), scale));
380+
} else if (labelWidth < 6) {
381+
// Put three intermediate labels
382+
labelTable.put(1 * sMax / 4,
383+
makeLabel(toCalibrated(1 * sMax / 4), scale));
384+
labelTable.put(2 * sMax / 4,
385+
makeLabel(toCalibrated(2 * sMax / 4), scale));
386+
labelTable.put(3 * sMax / 4,
387+
makeLabel(toCalibrated(3 * sMax / 4), scale));
388+
}
389+
setLabelTable(labelTable);
390+
setPaintLabels(true);
391+
setMinorTickSpacing(1);
392+
setPaintTicks(sMax < 100);
393+
}
394+
395+
private void setCalibratedValue(Number value) {
396+
setValue(fromCalibrated(value));
397+
}
398+
399+
private Number getCalibratedValue() {
400+
return toCalibrated(getValue());
401+
}
402+
403+
private Number getCalibratedMinimum() {
404+
return min;
405+
}
406+
407+
private Number getCalibratedMaximum() {
408+
return max;
409+
}
410+
411+
private int fromCalibrated(Number n) {
412+
return (int) ((n.doubleValue() - min.doubleValue()) / stepSize.doubleValue());
413+
}
414+
415+
private Number toCalibrated(int n) {
416+
return n * stepSize.doubleValue() + min.doubleValue();
417+
}
418+
419+
private JLabel makeLabel(Number n, int scale) {
420+
return new JLabel(String.format("%." + scale + "f", n.doubleValue()));
421+
}
422+
423+
}
343424
}

0 commit comments

Comments
 (0)
Please sign in to comment.