Skip to content

Commit 32ddc3c

Browse files
committed
SwingNumberWidget: improve sliders and scroll bars
This fixes the following issues: * If a slider had a span (max - min) more than Integer.MAX_VALUE, it would hang at 100% CPU forever, probably due to a Java bug. We work around this by refusing to add a slider in this case, and logging a warning to the user about it. * If a slider had a large min and/or max value, the labels would get very long and bleed into each other. We work around this by: a) showing only the min and max labels, without the three intermediate labels, when the min and/or max label string length is 5 or more; and b) hiding the labels altogether when the min and/or max label string length is 10 or more. * If the number of ticks is large, the slider still tries to paint them all. So now we only paint the ticks if there are less than 100. * If a scroll bar has a minimum value of Integer.MIN_VALUE, it does something special, according to the documentation. So we avoid that case by bumping up the min by one if that happens. * If the widget model somehow returns null for min, max or step, it would throw an NPE (although in practice this does not happen with DefaultWidgetModel). But still, the code is defensive about that now. Closes #32.
1 parent 4455641 commit 32ddc3c

File tree

1 file changed

+59
-18
lines changed

1 file changed

+59
-18
lines changed

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

+59-18
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import javax.swing.event.ChangeEvent;
5353
import javax.swing.event.ChangeListener;
5454

55+
import org.scijava.log.LogService;
5556
import org.scijava.module.ModuleService;
5657
import org.scijava.plugin.Parameter;
5758
import org.scijava.plugin.Plugin;
@@ -76,6 +77,9 @@ public class SwingNumberWidget extends SwingInputWidget<Number> implements
7677
@Parameter
7778
private ModuleService moduleService;
7879

80+
@Parameter
81+
private LogService log;
82+
7983
private JScrollBar scrollBar;
8084
private JSlider slider;
8185
private JSpinner spinner;
@@ -101,26 +105,10 @@ public void set(final WidgetModel model) {
101105

102106
// add optional widgets, if specified
103107
if (model.isStyle(NumberWidget.SCROLL_BAR_STYLE)) {
104-
int smx = softMax.intValue();
105-
if (smx < Integer.MAX_VALUE) smx++;
106-
scrollBar =
107-
new JScrollBar(Adjustable.HORIZONTAL, softMin.intValue(), 1, softMin
108-
.intValue(), smx);
109-
scrollBar.setUnitIncrement(stepSize.intValue());
110-
setToolTip(scrollBar);
111-
getComponent().add(scrollBar);
112-
scrollBar.addAdjustmentListener(this);
108+
addScrollBar(softMin, softMax, stepSize);
113109
}
114110
else if (model.isStyle(NumberWidget.SLIDER_STYLE)) {
115-
slider =
116-
new JSlider(softMin.intValue(), softMax.intValue(), softMin.intValue());
117-
slider.setMajorTickSpacing((softMax.intValue() - softMin.intValue()) / 4);
118-
slider.setMinorTickSpacing(stepSize.intValue());
119-
slider.setPaintLabels(true);
120-
slider.setPaintTicks(true);
121-
setToolTip(slider);
122-
getComponent().add(slider);
123-
slider.addChangeListener(this);
111+
addSlider(softMin, softMax, stepSize);
124112
}
125113

126114
// add spinner widget
@@ -174,6 +162,59 @@ else if (source == spinner) {
174162

175163
// -- Helper methods --
176164

165+
private void addScrollBar(final Number min, final Number max,
166+
final Number step)
167+
{
168+
if (min == null || max == null || step == null) {
169+
log.warn("Invalid min/max/step; cannot render scroll bar");
170+
return;
171+
}
172+
int mn = min.intValue();
173+
if (mn == Integer.MIN_VALUE) mn = Integer.MIN_VALUE + 1;
174+
int mx = max.intValue();
175+
if (mx < Integer.MAX_VALUE) mx++;
176+
final int st = step.intValue();
177+
178+
scrollBar = new JScrollBar(Adjustable.HORIZONTAL, mn, 1, mn, mx);
179+
scrollBar.setUnitIncrement(st);
180+
setToolTip(scrollBar);
181+
getComponent().add(scrollBar);
182+
scrollBar.addAdjustmentListener(this);
183+
}
184+
185+
private void addSlider(final Number min, final Number max,
186+
final Number step)
187+
{
188+
if (min == null || max == null || step == null) {
189+
log.warn("Invalid min/max/step; cannot render slider");
190+
return;
191+
}
192+
final int mn = min.intValue();
193+
final int mx = max.intValue();
194+
final int st = step.intValue();
195+
if ((long) mx - mn > Integer.MAX_VALUE) {
196+
log.warn("Slider span too large; max - min < 2^31 required.");
197+
return;
198+
}
199+
final int span = mx - mn;
200+
201+
slider = new JSlider(mn, mx, mn);
202+
203+
// Compute optimal major ticks and labels.
204+
final int labelWidth = Math.max(("" + mn).length(), ("" + mx).length());
205+
slider.setMajorTickSpacing(labelWidth < 5 ? span / 4 : span);
206+
slider.setPaintLabels(labelWidth < 10);
207+
208+
// Compute optimal minor ticks.
209+
final int stepCount = span / st + 1;
210+
slider.setMinorTickSpacing(st);
211+
slider.setPaintTicks(stepCount < 100);
212+
213+
setToolTip(slider);
214+
getComponent().add(slider);
215+
slider.addChangeListener(this);
216+
}
217+
177218
/**
178219
* Limit component width to a certain maximum. This is a HACK to work around
179220
* an issue with Double-based spinners that attempt to size themselves very

0 commit comments

Comments
 (0)