4343import java .math .BigInteger ;
4444import java .text .DecimalFormat ;
4545import java .text .ParsePosition ;
46+ import java .util .Hashtable ;
4647
4748import javax .swing .JComponent ;
49+ import javax .swing .JLabel ;
4850import javax .swing .JPanel ;
4951import javax .swing .JScrollBar ;
5052import 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