Skip to content

Commit 3eeab7c

Browse files
committed
Merge branch 'widget-models'
This makes the creation of WidgetModels extensible.
2 parents b1cb331 + f23e67a commit 3eeab7c

File tree

5 files changed

+389
-237
lines changed

5 files changed

+389
-237
lines changed

src/main/java/org/scijava/widget/AbstractInputHarvester.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private <T> WidgetModel addInput(final InputPanel<P, W> inputPanel,
126126

127127
final Class<T> type = item.getType();
128128
final WidgetModel model =
129-
new WidgetModel(getContext(), inputPanel, module, item, getObjects(type));
129+
widgetService.createModel(inputPanel, module, item, getObjects(type));
130130

131131
final Class<W> widgetType = inputPanel.getWidgetComponentType();
132132
final InputWidget<?, ?> widget = widgetService.create(model);
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2014 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.widget;
33+
34+
import java.util.Arrays;
35+
import java.util.List;
36+
import java.util.Map;
37+
import java.util.WeakHashMap;
38+
39+
import org.scijava.AbstractContextual;
40+
import org.scijava.Context;
41+
import org.scijava.ItemVisibility;
42+
import org.scijava.convert.ConvertService;
43+
import org.scijava.log.LogService;
44+
import org.scijava.module.MethodCallException;
45+
import org.scijava.module.Module;
46+
import org.scijava.module.ModuleItem;
47+
import org.scijava.plugin.Parameter;
48+
import org.scijava.thread.ThreadService;
49+
import org.scijava.util.ClassUtils;
50+
import org.scijava.util.ConversionUtils;
51+
import org.scijava.util.MiscUtils;
52+
import org.scijava.util.NumberUtils;
53+
54+
/**
55+
* The backing data model for a particular {@link InputWidget}.
56+
*
57+
* @author Curtis Rueden
58+
*/
59+
public class DefaultWidgetModel extends AbstractContextual implements WidgetModel {
60+
61+
private final InputPanel<?, ?> inputPanel;
62+
private final Module module;
63+
private final ModuleItem<?> item;
64+
private final List<?> objectPool;
65+
private final Map<Object, Object> convertedObjects;
66+
67+
@Parameter
68+
private ThreadService threadService;
69+
70+
@Parameter
71+
private ConvertService convertService;
72+
73+
@Parameter(required = false)
74+
private LogService log;
75+
76+
private boolean initialized;
77+
78+
public DefaultWidgetModel(final Context context, final InputPanel<?, ?> inputPanel,
79+
final Module module, final ModuleItem<?> item, final List<?> objectPool)
80+
{
81+
setContext(context);
82+
this.inputPanel = inputPanel;
83+
this.module = module;
84+
this.item = item;
85+
this.objectPool = objectPool;
86+
convertedObjects = new WeakHashMap<Object, Object>();
87+
}
88+
89+
@Override
90+
public InputPanel<?, ?> getPanel() {
91+
return inputPanel;
92+
}
93+
94+
@Override
95+
public Module getModule() {
96+
return module;
97+
}
98+
99+
@Override
100+
public ModuleItem<?> getItem() {
101+
return item;
102+
}
103+
104+
@Override
105+
public List<?> getObjectPool() {
106+
return objectPool;
107+
}
108+
109+
@Override
110+
public String getWidgetLabel() {
111+
// Do this dynamically. Don't cache this result.
112+
// Some controls change their labels at runtime.
113+
final String label = item.getLabel();
114+
if (label != null && !label.isEmpty()) return label;
115+
116+
final String name = item.getName();
117+
return name.substring(0, 1).toUpperCase() + name.substring(1);
118+
}
119+
120+
@Override
121+
public boolean isStyle(final String style) {
122+
final String widgetStyle = getItem().getWidgetStyle();
123+
if (widgetStyle == null) return style == null;
124+
for (final String s : widgetStyle.split(",")) {
125+
if (s.equals(style)) return true;
126+
}
127+
return false;
128+
}
129+
130+
@Override
131+
public Object getValue() {
132+
final Object value = item.getValue(module);
133+
134+
if (isMultipleChoice()) return ensureValidChoice(value);
135+
if (getObjectPool().size() > 0) return ensureValidObject(value);
136+
return value;
137+
}
138+
139+
@Override
140+
public void setValue(final Object value) {
141+
final String name = item.getName();
142+
if (MiscUtils.equal(item.getValue(module), value)) return; // no change
143+
144+
// Check if a converted value is present
145+
Object convertedInput = convertedObjects.get(value);
146+
if (convertedInput != null &&
147+
MiscUtils.equal(item.getValue(module), convertedInput))
148+
{
149+
return; // no change
150+
}
151+
152+
// Pass the value through the convertService
153+
convertedInput = convertService.convert(value, item.getType());
154+
155+
// If we get a different (converted) value back, cache it weakly.
156+
if (convertedInput != value) {
157+
convertedObjects.put(value, convertedInput);
158+
}
159+
160+
module.setInput(name, convertedInput);
161+
162+
if (initialized) {
163+
threadService.run(new Runnable() {
164+
165+
@Override
166+
public void run() {
167+
callback();
168+
inputPanel.refresh(); // must be on AWT thread?
169+
module.preview();
170+
}
171+
});
172+
}
173+
}
174+
175+
@Override
176+
public void callback() {
177+
try {
178+
item.callback(module);
179+
}
180+
catch (final MethodCallException exc) {
181+
if (log != null) log.error(exc);
182+
}
183+
}
184+
185+
@Override
186+
public Number getMin() {
187+
final Number min = toNumber(item.getMinimumValue());
188+
if (min != null) return min;
189+
return NumberUtils.getMinimumNumber(item.getType());
190+
}
191+
192+
@Override
193+
public Number getMax() {
194+
final Number max = toNumber(item.getMaximumValue());
195+
if (max != null) return max;
196+
return NumberUtils.getMaximumNumber(item.getType());
197+
}
198+
199+
@Override
200+
public Number getSoftMin() {
201+
final Number softMin = toNumber(item.getSoftMinimum());
202+
if (softMin != null) return softMin;
203+
return getMin();
204+
}
205+
206+
@Override
207+
public Number getSoftMax() {
208+
final Number softMax = toNumber(item.getSoftMaximum());
209+
if (softMax != null) return softMax;
210+
return getMax();
211+
}
212+
213+
@Override
214+
public Number getStepSize() {
215+
final Number stepSize = toNumber(item.getStepSize());
216+
if (stepSize != null) return stepSize;
217+
return NumberUtils.toNumber("1", item.getType());
218+
}
219+
220+
@Override
221+
public String[] getChoices() {
222+
final List<?> choicesList = item.getChoices();
223+
final String[] choices = new String[choicesList.size()];
224+
for (int i = 0; i < choices.length; i++) {
225+
choices[i] = choicesList.get(i).toString();
226+
}
227+
return choices;
228+
}
229+
230+
@Override
231+
public String getText() {
232+
final Object value = getValue();
233+
if (value == null) return "";
234+
final String text = value.toString();
235+
if (text.equals("\0")) return ""; // render null character as empty
236+
return text;
237+
}
238+
239+
@Override
240+
public boolean isMessage() {
241+
return getItem().getVisibility() == ItemVisibility.MESSAGE;
242+
}
243+
244+
@Override
245+
public boolean isText() {
246+
return ClassUtils.isText(getItem().getType());
247+
}
248+
249+
@Override
250+
public boolean isCharacter() {
251+
return ClassUtils.isCharacter(getItem().getType());
252+
}
253+
254+
@Override
255+
public boolean isNumber() {
256+
return ClassUtils.isNumber(getItem().getType());
257+
}
258+
259+
@Override
260+
public boolean isBoolean() {
261+
return ClassUtils.isBoolean(getItem().getType());
262+
}
263+
264+
@Override
265+
public boolean isMultipleChoice() {
266+
final List<?> choices = item.getChoices();
267+
return choices != null && !choices.isEmpty();
268+
}
269+
270+
@Override
271+
public boolean isType(final Class<?> type) {
272+
return type.isAssignableFrom(getItem().getType());
273+
}
274+
275+
@Override
276+
public void setInitialized(final boolean initialized) {
277+
this.initialized = initialized;
278+
}
279+
280+
@Override
281+
public boolean isInitialized() {
282+
return initialized;
283+
}
284+
285+
// -- Helper methods --
286+
287+
/**
288+
* For multiple choice widgets, ensure the value is a valid choice.
289+
*
290+
* @see #getChoices()
291+
* @see ChoiceWidget
292+
*/
293+
private Object ensureValidChoice(final Object value) {
294+
return ensureValid(value, Arrays.asList(getChoices()));
295+
}
296+
297+
/**
298+
* For object widgets, ensure the value is a valid object.
299+
*
300+
* @see #getObjectPool()
301+
* @see ObjectWidget
302+
*/
303+
private Object ensureValidObject(final Object value) {
304+
return ensureValid(value, getObjectPool());
305+
}
306+
307+
/** Ensures the value is on the given list. */
308+
private Object ensureValid(final Object value, final List<?> list) {
309+
for (final Object o : list) {
310+
if (o.equals(value)) return value; // value is valid
311+
// check if value was converted and cached
312+
final Object convertedValue = convertedObjects.get(o);
313+
if (convertedValue != null && value.equals(convertedValue)) {
314+
return convertedValue;
315+
}
316+
}
317+
318+
// value is not valid; override with the first item on the list instead
319+
final Object validValue = list.get(0);
320+
// CTR TODO: Mutating the model in a getter is dirty. Find a better way?
321+
setValue(validValue);
322+
return validValue;
323+
}
324+
325+
/** Converts the given object to a number matching the input type. */
326+
private Number toNumber(final Object value) {
327+
final Class<?> type = item.getType();
328+
final Class<?> saneType = ConversionUtils.getNonprimitiveType(type);
329+
return NumberUtils.toNumber(value, saneType);
330+
}
331+
332+
}

src/main/java/org/scijava/widget/DefaultWidgetService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131

3232
package org.scijava.widget;
3333

34+
import java.util.List;
35+
3436
import org.scijava.log.LogService;
37+
import org.scijava.module.Module;
38+
import org.scijava.module.ModuleItem;
3539
import org.scijava.plugin.AbstractWrapperService;
3640
import org.scijava.plugin.Parameter;
3741
import org.scijava.plugin.Plugin;
@@ -52,6 +56,16 @@ public class DefaultWidgetService extends
5256
@Parameter
5357
private LogService log;
5458

59+
// -- WidgetService methods --
60+
61+
@Override
62+
public WidgetModel createModel(InputPanel<?, ?> inputPanel, Module module,
63+
ModuleItem<?> item, List<?> objectPool)
64+
{
65+
return new DefaultWidgetModel(getContext(), inputPanel, module, item,
66+
objectPool);
67+
}
68+
5569
// -- WrapperService methods --
5670

5771
@Override

0 commit comments

Comments
 (0)