30
30
31
31
package org .scijava .ui .swing .widget ;
32
32
33
- import java .awt .Adjustable ;
34
33
import java .awt .Component ;
35
34
import java .awt .Dimension ;
36
35
import java .awt .event .AdjustmentEvent ;
43
42
import java .math .BigInteger ;
44
43
import java .text .DecimalFormat ;
45
44
import java .text .ParsePosition ;
45
+ import java .util .Hashtable ;
46
46
47
47
import javax .swing .JComponent ;
48
+ import javax .swing .JLabel ;
48
49
import javax .swing .JPanel ;
49
50
import javax .swing .JScrollBar ;
50
51
import javax .swing .JSlider ;
@@ -82,8 +83,8 @@ public class SwingNumberWidget extends SwingInputWidget<Number> implements
82
83
@ Parameter
83
84
private LogService log ;
84
85
85
- private JScrollBar scrollBar ;
86
- private JSlider slider ;
86
+ private CalibratedScrollBar scrollBar ;
87
+ private CalibratedSlider slider ;
87
88
private JSpinner spinner ;
88
89
89
90
// -- InputWidget methods --
@@ -119,6 +120,9 @@ else if (model.isStyle(NumberWidget.SLIDER_STYLE)) {
119
120
final SpinnerNumberModel spinnerModel =
120
121
new SpinnerNumberModelFactory ().createModel (value , min , max , stepSize );
121
122
spinner = new JSpinner (spinnerModel );
123
+ Dimension spinnerSize = spinner .getSize ();
124
+ spinnerSize .width = 50 ;
125
+ spinner .setPreferredSize (spinnerSize );
122
126
fixSpinner (type );
123
127
setToolTip (spinner );
124
128
getComponent ().add (spinner );
@@ -141,7 +145,7 @@ public boolean supports(final WidgetModel model) {
141
145
@ Override
142
146
public void adjustmentValueChanged (final AdjustmentEvent e ) {
143
147
// sync spinner with scroll bar value
144
- final int value = scrollBar .getValue ();
148
+ final Number value = scrollBar .getCalibratedValue ();
145
149
spinner .setValue (value );
146
150
}
147
151
@@ -152,7 +156,7 @@ public void stateChanged(final ChangeEvent e) {
152
156
final Object source = e .getSource ();
153
157
if (source == slider ) {
154
158
// sync spinner with slider value
155
- final int value = slider .getValue ();
159
+ final Number value = slider .getCalibratedValue ();
156
160
spinner .setValue (value );
157
161
}
158
162
else if (source == spinner ) {
@@ -166,9 +170,9 @@ else if (source == spinner) {
166
170
167
171
@ Override
168
172
public void mouseWheelMoved (final MouseWheelEvent e ) {
169
- int value = getValue ().intValue () + e .getWheelRotation ();
170
- value = Math .min (value , this .get ().getMax ().intValue ());
171
- value = Math .max (value , this .get ().getMin ().intValue ());
173
+ Number value = getValue ().doubleValue () + e .getWheelRotation () * get (). getStepSize (). doubleValue ();
174
+ value = Math .min (value . doubleValue () , this .get ().getMax ().doubleValue ());
175
+ value = Math .max (value . doubleValue () , this .get ().getMin ().doubleValue ());
172
176
spinner .setValue (value );
173
177
syncSliders ();
174
178
}
@@ -182,14 +186,17 @@ private void addScrollBar(final Number min, final Number max,
182
186
log .warn ("Invalid min/max/step; cannot render scroll bar" );
183
187
return ;
184
188
}
185
- int mn = min .intValue ();
186
- if (mn == Integer .MIN_VALUE ) mn = Integer .MIN_VALUE + 1 ;
187
- int mx = max .intValue ();
188
- if (mx < Integer .MAX_VALUE ) mx ++;
189
- final int st = step .intValue ();
190
-
191
- scrollBar = new JScrollBar (Adjustable .HORIZONTAL , mn , 1 , mn , mx );
192
- scrollBar .setUnitIncrement (st );
189
+
190
+ // TODO Integer cases can possibly be handled in a simpler way
191
+ int sMin = 0 ;
192
+ int sMax = (int ) ((max .doubleValue () - min .doubleValue ()) / step .doubleValue ());
193
+ long range = sMax - sMin ;
194
+ if (range > Integer .MAX_VALUE ) {
195
+ log .warn ("Scrollbar span too large; max - min < 2^31 required." );
196
+ return ;
197
+ }
198
+
199
+ scrollBar = new CalibratedScrollBar (min , max , step );
193
200
setToolTip (scrollBar );
194
201
getComponent ().add (scrollBar );
195
202
scrollBar .addAdjustmentListener (this );
@@ -203,26 +210,17 @@ private void addSlider(final Number min, final Number max,
203
210
log .warn ("Invalid min/max/step; cannot render slider" );
204
211
return ;
205
212
}
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 ) {
213
+
214
+ // TODO Integer cases can possibly be handled in a simpler way
215
+ int sMin = 0 ;
216
+ int sMax = (int ) ((max .doubleValue () - min .doubleValue ()) / step .doubleValue ());
217
+ long range = sMax - sMin ;
218
+ if (range > Integer .MAX_VALUE ) {
210
219
log .warn ("Slider span too large; max - min < 2^31 required." );
211
220
return ;
212
221
}
213
- final int span = mx - mn ;
214
222
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
-
222
- // Compute optimal minor ticks.
223
- final int stepCount = span / st + 1 ;
224
- slider .setMinorTickSpacing (st );
225
- slider .setPaintTicks (stepCount < 100 );
223
+ slider = new CalibratedSlider (min , max , step );
226
224
227
225
setToolTip (slider );
228
226
getComponent ().add (slider );
@@ -314,20 +312,20 @@ public void run() {
314
312
private void syncSliders () {
315
313
if (slider != null ) {
316
314
// 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 ();
315
+ Number value = getValue ();
316
+ if (value . doubleValue () < slider .getCalibratedMinimum (). doubleValue ()) value = slider .getCalibratedMinimum ();
317
+ else if (value . doubleValue () > slider .getCalibratedMaximum (). doubleValue ()) value = slider .getCalibratedMaximum ();
320
318
slider .removeChangeListener (this );
321
- slider .setValue (value );
319
+ slider .setCalibratedValue (value );
322
320
slider .addChangeListener (this );
323
321
}
324
322
if (scrollBar != null ) {
325
323
// clamp value within scroll bar bounds
326
- int value = getValue (). intValue ();
327
- if (value < scrollBar .getMinimum ()) value = scrollBar .getMinimum ();
328
- else if (value > scrollBar .getMaximum ()) value = scrollBar .getMaximum ();
324
+ Number value = getValue ();
325
+ if (value . doubleValue () < scrollBar .getCalibratedMinimum (). doubleValue ()) value = scrollBar .getCalibratedMinimum ();
326
+ else if (value . doubleValue () > scrollBar .getCalibratedMaximum (). doubleValue ()) value = scrollBar .getCalibratedMaximum ();
329
327
scrollBar .removeAdjustmentListener (this );
330
- scrollBar .setValue ( getValue (). intValue () );
328
+ scrollBar .setCalibratedValue ( value );
331
329
scrollBar .addAdjustmentListener (this );
332
330
}
333
331
}
@@ -340,4 +338,143 @@ public void doRefresh() {
340
338
if (spinner .getValue ().equals (value )) return ; // no change
341
339
spinner .setValue (value );
342
340
}
341
+
342
+ private class CalibratedSlider extends JSlider {
343
+
344
+ private Number min ;
345
+ private Number max ;
346
+ private Number stepSize ;
347
+
348
+ private CalibratedSlider (final Number min , final Number max , final Number stepSize ) {
349
+ super ();
350
+
351
+ this .min = min ;
352
+ this .max = max ;
353
+ this .stepSize = stepSize ;
354
+
355
+ int sMin = 0 ;
356
+ int sMax = (int ) ((max .doubleValue () - min .doubleValue ()) / stepSize .doubleValue ());
357
+
358
+ // Adjust max to be an integer multiple of stepSize
359
+ this .max = min .doubleValue () + (sMax -sMin ) * stepSize .doubleValue ();
360
+
361
+ setMinimum (sMin );
362
+ setMaximum (sMax );
363
+ setValue (sMin );
364
+
365
+ // Compute label width to determine number of labels
366
+ int scale = Math .max (0 , new BigDecimal (stepSize .toString ()).stripTrailingZeros ().scale ());
367
+ JLabel minLabel = makeLabel (min , scale );
368
+ JLabel maxLabel = makeLabel (max , scale );
369
+ final int labelWidth = Math .max (minLabel .getText ().length (), maxLabel .getText ().length ());
370
+
371
+ // Add labels
372
+ Hashtable <Integer , JLabel > labelTable = new Hashtable <>(2 );
373
+ labelTable .put (sMin , minLabel );
374
+ labelTable .put (sMax , maxLabel );
375
+ if (labelWidth < 5 && sMax % 5 == 0 ) {
376
+ // Put four intermediate labels
377
+ labelTable .put (1 * sMax / 5 ,
378
+ makeLabel (toCalibrated (1 * sMax / 5 ), scale ));
379
+ labelTable .put (2 * sMax / 5 ,
380
+ makeLabel (toCalibrated (2 * sMax / 5 ), scale ));
381
+ labelTable .put (3 * sMax / 5 ,
382
+ makeLabel (toCalibrated (3 * sMax / 5 ), scale ));
383
+ labelTable .put (4 * sMax / 5 ,
384
+ makeLabel (toCalibrated (4 * sMax / 5 ), scale ));
385
+ } else if (labelWidth < 6 ) {
386
+ // Put three intermediate labels
387
+ labelTable .put (1 * sMax / 4 ,
388
+ makeLabel (toCalibrated (1 * sMax / 4 ), scale ));
389
+ labelTable .put (2 * sMax / 4 ,
390
+ makeLabel (toCalibrated (2 * sMax / 4 ), scale ));
391
+ labelTable .put (3 * sMax / 4 ,
392
+ makeLabel (toCalibrated (3 * sMax / 4 ), scale ));
393
+ }
394
+ setLabelTable (labelTable );
395
+ setPaintLabels (true );
396
+ setMinorTickSpacing (1 );
397
+ setPaintTicks (sMax < 100 );
398
+ }
399
+
400
+ private void setCalibratedValue (Number value ) {
401
+ setValue (fromCalibrated (value ));
402
+ }
403
+
404
+ private Number getCalibratedValue () {
405
+ return toCalibrated (getValue ());
406
+ }
407
+
408
+ private Number getCalibratedMinimum () {
409
+ return min ;
410
+ }
411
+
412
+ private Number getCalibratedMaximum () {
413
+ return max ;
414
+ }
415
+
416
+ private int fromCalibrated (Number n ) {
417
+ return (int ) Math .round ((n .doubleValue () - min .doubleValue ()) / stepSize .doubleValue ());
418
+ }
419
+
420
+ private Number toCalibrated (int n ) {
421
+ return n * stepSize .doubleValue () + min .doubleValue ();
422
+ }
423
+
424
+ private JLabel makeLabel (Number n , int scale ) {
425
+ return new JLabel (String .format ("%." + scale + "f" , n .doubleValue ()));
426
+ }
427
+
428
+ }
429
+
430
+ private class CalibratedScrollBar extends JScrollBar {
431
+
432
+ private Number min ;
433
+ private Number max ;
434
+ private Number stepSize ;
435
+
436
+ private CalibratedScrollBar (final Number min , final Number max , final Number stepSize ) {
437
+ // set extent to 1 to make sure the scroll bar is visible
438
+ super (HORIZONTAL , 0 , 1 , 0 , 1 );
439
+
440
+ this .min = min ;
441
+ this .max = max ;
442
+ this .stepSize = stepSize ;
443
+
444
+ int sMin = 0 ;
445
+ int sMax = (int ) ((max .doubleValue () - min .doubleValue ()) / stepSize .doubleValue ()) + 1 ;
446
+
447
+ // Adjust max to be an integer multiple of stepSize
448
+ this .max = min .doubleValue () + (sMax -sMin ) * stepSize .doubleValue ();
449
+
450
+ setMinimum (sMin );
451
+ setMaximum (sMax );
452
+ setValue (sMin );
453
+ }
454
+
455
+ private void setCalibratedValue (Number value ) {
456
+ setValue (fromCalibrated (value ));
457
+ }
458
+
459
+ private Number getCalibratedValue () {
460
+ return toCalibrated (getValue ());
461
+ }
462
+
463
+ private Number getCalibratedMinimum () {
464
+ return min ;
465
+ }
466
+
467
+ private Number getCalibratedMaximum () {
468
+ return max ;
469
+ }
470
+
471
+ private int fromCalibrated (Number n ) {
472
+ return (int ) Math .round ((n .doubleValue () - min .doubleValue ()) / stepSize .doubleValue ());
473
+ }
474
+
475
+ private Number toCalibrated (int n ) {
476
+ return n * stepSize .doubleValue () + min .doubleValue ();
477
+ }
478
+
479
+ }
343
480
}
0 commit comments