43
43
import java .math .BigInteger ;
44
44
import java .text .DecimalFormat ;
45
45
import java .text .ParsePosition ;
46
+ import java .util .Hashtable ;
46
47
47
48
import javax .swing .JComponent ;
49
+ import javax .swing .JLabel ;
48
50
import javax .swing .JPanel ;
49
51
import javax .swing .JScrollBar ;
50
52
import javax .swing .JSlider ;
@@ -83,7 +85,7 @@ public class SwingNumberWidget extends SwingInputWidget<Number> implements
83
85
private LogService log ;
84
86
85
87
private JScrollBar scrollBar ;
86
- private JSlider slider ;
88
+ private CalibratedSlider slider ;
87
89
private JSpinner spinner ;
88
90
89
91
// -- InputWidget methods --
@@ -152,7 +154,7 @@ public void stateChanged(final ChangeEvent e) {
152
154
final Object source = e .getSource ();
153
155
if (source == slider ) {
154
156
// sync spinner with slider value
155
- final int value = slider .getValue ();
157
+ final Number value = slider .getCalibratedValue ();
156
158
spinner .setValue (value );
157
159
}
158
160
else if (source == spinner ) {
@@ -166,7 +168,7 @@ else if (source == spinner) {
166
168
167
169
@ Override
168
170
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
170
172
value = Math .min (value , this .get ().getMax ().intValue ());
171
173
value = Math .max (value , this .get ().getMin ().intValue ());
172
174
spinner .setValue (value );
@@ -203,26 +205,17 @@ private void addSlider(final Number min, final Number max,
203
205
log .warn ("Invalid min/max/step; cannot render slider" );
204
206
return ;
205
207
}
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 ) {
210
213
log .warn ("Slider span too large; max - min < 2^31 required." );
211
214
return ;
212
215
}
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 );
221
216
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 );
226
219
227
220
setToolTip (slider );
228
221
getComponent ().add (slider );
@@ -314,11 +307,11 @@ public void run() {
314
307
private void syncSliders () {
315
308
if (slider != null ) {
316
309
// 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 ();
320
313
slider .removeChangeListener (this );
321
- slider .setValue (value );
314
+ slider .setCalibratedValue (value );
322
315
slider .addChangeListener (this );
323
316
}
324
317
if (scrollBar != null ) {
@@ -340,4 +333,92 @@ public void doRefresh() {
340
333
if (spinner .getValue ().equals (value )) return ; // no change
341
334
spinner .setValue (value );
342
335
}
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
+ }
343
424
}
0 commit comments