diff --git a/src/main/java/org/scijava/ItemVisibility.java b/src/main/java/org/scijava/ItemVisibility.java
index 749e91ae3..6f8691aa3 100644
--- a/src/main/java/org/scijava/ItemVisibility.java
+++ b/src/main/java/org/scijava/ItemVisibility.java
@@ -61,6 +61,14 @@ public enum ItemVisibility {
* intended as a message to the user (e.g., in the input harvester panel)
* rather than an actual parameter to the module execution.
*/
- MESSAGE
-
+ MESSAGE,
+
+ /**
+ * Indicates that the item's value defines the name of a widget group
+ * (e.g., in the input harvester panel) rather than an actual parameter
+ * to the module execution. Widget groups organize related input widgets.
+ * Group members are added to a group by providing a group parameter annotation
+ * having the common group name.
+ */
+ GROUP
}
diff --git a/src/main/java/org/scijava/command/CommandModuleItem.java b/src/main/java/org/scijava/command/CommandModuleItem.java
index 7be4d3e9f..4210921f7 100644
--- a/src/main/java/org/scijava/command/CommandModuleItem.java
+++ b/src/main/java/org/scijava/command/CommandModuleItem.java
@@ -134,6 +134,16 @@ public String getCallback() {
public String getWidgetStyle() {
return getParameter().style();
}
+
+ @Override
+ public String getWidgetGroup() {
+ return getParameter().group();
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return getParameter().expanded();
+ }
@Override
public T getMinimumValue() {
diff --git a/src/main/java/org/scijava/module/AbstractModuleItem.java b/src/main/java/org/scijava/module/AbstractModuleItem.java
index da260b12a..de98530a9 100644
--- a/src/main/java/org/scijava/module/AbstractModuleItem.java
+++ b/src/main/java/org/scijava/module/AbstractModuleItem.java
@@ -74,6 +74,8 @@ public String toString() {
sm.append("persistKey", getPersistKey());
sm.append("callback", getCallback());
sm.append("widgetStyle", getWidgetStyle());
+ sm.append("widgetGroup", getWidgetGroup());
+ sm.append("expanded", isExpanded());
sm.append("default", getDefaultValue());
sm.append("min", getMinimumValue());
sm.append("max", getMaximumValue());
@@ -231,6 +233,16 @@ public void callback(final Module module) throws MethodCallException {
public String getWidgetStyle() {
return null;
}
+
+ @Override
+ public String getWidgetGroup() {
+ return null;
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return true;
+ }
@Override
public T getDefaultValue() {
diff --git a/src/main/java/org/scijava/module/DefaultMutableModuleItem.java b/src/main/java/org/scijava/module/DefaultMutableModuleItem.java
index 4c8354d14..5619b5b81 100644
--- a/src/main/java/org/scijava/module/DefaultMutableModuleItem.java
+++ b/src/main/java/org/scijava/module/DefaultMutableModuleItem.java
@@ -59,6 +59,8 @@ public class DefaultMutableModuleItem extends AbstractModuleItem
private String validater;
private String callback;
private String widgetStyle;
+ private String widgetGroup;
+ private boolean expanded;
private T defaultValue;
private T minimumValue;
private T maximumValue;
@@ -94,6 +96,8 @@ public DefaultMutableModuleItem(final ModuleInfo info, final String name,
validater = super.getValidater();
callback = super.getCallback();
widgetStyle = super.getWidgetStyle();
+ widgetGroup = super.getWidgetGroup();
+ expanded = super.isExpanded();
minimumValue = super.getMinimumValue();
maximumValue = super.getMaximumValue();
stepSize = super.getStepSize();
@@ -122,6 +126,8 @@ public DefaultMutableModuleItem(final ModuleInfo info,
validater = item.getValidater();
callback = item.getCallback();
widgetStyle = item.getWidgetStyle();
+ widgetGroup = item.getWidgetGroup();
+ expanded = item.isExpanded();
minimumValue = item.getMinimumValue();
maximumValue = item.getMaximumValue();
softMinimum = item.getSoftMinimum();
@@ -185,6 +191,16 @@ public void setCallback(final String callback) {
public void setWidgetStyle(final String widgetStyle) {
this.widgetStyle = widgetStyle;
}
+
+ @Override
+ public void setWidgetGroup(final String widgetGroup) {
+ this.widgetGroup = widgetGroup;
+ }
+
+ @Override
+ public void setExpanded(final boolean expanded) {
+ this.expanded = expanded;
+ }
@Override
public void setDefaultValue(final T defaultValue) {
@@ -288,6 +304,16 @@ public String getCallback() {
public String getWidgetStyle() {
return widgetStyle;
}
+
+ @Override
+ public String getWidgetGroup() {
+ return widgetGroup;
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return expanded;
+ }
@Override
public T getDefaultValue() {
diff --git a/src/main/java/org/scijava/module/ModuleItem.java b/src/main/java/org/scijava/module/ModuleItem.java
index 4abd391ea..c9ca93191 100644
--- a/src/main/java/org/scijava/module/ModuleItem.java
+++ b/src/main/java/org/scijava/module/ModuleItem.java
@@ -153,6 +153,15 @@ public interface ModuleItem extends BasicDetails {
* interface.
*/
String getWidgetStyle();
+
+ /**
+ * Gets the name of the group the widget belongs to so it can be displayed within
+ * the group.
+ */
+ String getWidgetGroup();
+
+ /** Returns the state of the widget group, expanded and visible or not expanded and hidden. */
+ boolean isExpanded();
/** Gets the default value. */
T getDefaultValue();
diff --git a/src/main/java/org/scijava/module/MutableModuleItem.java b/src/main/java/org/scijava/module/MutableModuleItem.java
index 58c146a81..36381fb4c 100644
--- a/src/main/java/org/scijava/module/MutableModuleItem.java
+++ b/src/main/java/org/scijava/module/MutableModuleItem.java
@@ -61,6 +61,10 @@ public interface MutableModuleItem extends ModuleItem {
void setCallback(String callback);
void setWidgetStyle(String widgetStyle);
+
+ void setWidgetGroup(String widgetGroup);
+
+ void setExpanded(boolean expanded);
void setDefaultValue(T defaultValue);
diff --git a/src/main/java/org/scijava/plugin/Parameter.java b/src/main/java/org/scijava/plugin/Parameter.java
index ac24dd5e6..6c110f5fe 100644
--- a/src/main/java/org/scijava/plugin/Parameter.java
+++ b/src/main/java/org/scijava/plugin/Parameter.java
@@ -92,6 +92,9 @@
* output, such as a "verbose" flag.
* MESSAGE: parameter value is intended as a message only, not editable by
* the user nor included as an input or output parameter.
+ * GROUP: parameter value specifies a widget group, not editable by
+ * the user nor included as an input or output parameter. Members are added
+ * to the group using the group parameter annotation.
*
*/
ItemVisibility visibility() default ItemVisibility.NORMAL;
@@ -142,6 +145,15 @@
*
*/
String style() default "";
+
+ /** Defines the widget group. */
+ String group() default "";
+
+ /**
+ * Defines the state of the widget group. When true the group is expanded and visible.
+ * Otherwise, group members are hidden.
+ * */
+ boolean expanded() default true;
/** Defines the minimum allowed value (numeric parameters only). */
String min() default "";
diff --git a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java
index 0ed4d7212..b9028e6c0 100644
--- a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java
+++ b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java
@@ -277,6 +277,8 @@ private void assignAttribute(final DefaultMutableModuleItem item,
else if (is(k, "softMin")) item.setSoftMinimum(as(v, item.getType()));
else if (is(k, "stepSize")) item.setStepSize(as(v, double.class));
else if (is(k, "style")) item.setWidgetStyle(as(v, String.class));
+ else if (is(k, "group")) item.setWidgetGroup(as(v, String.class));
+ else if (is(k, "expanded")) item.setExpanded(as(v, boolean.class));
else if (is(k, "visibility")) item.setVisibility(as(v, ItemVisibility.class));
else if (is(k, "value")) item.setDefaultValue(as(v, item.getType()));
else item.set(k, v.toString());
diff --git a/src/main/java/org/scijava/widget/DefaultWidgetModel.java b/src/main/java/org/scijava/widget/DefaultWidgetModel.java
index 1a768d5d7..452e189fc 100644
--- a/src/main/java/org/scijava/widget/DefaultWidgetModel.java
+++ b/src/main/java/org/scijava/widget/DefaultWidgetModel.java
@@ -131,6 +131,11 @@ public String getWidgetLabel() {
public boolean isStyle(final String style) {
return WidgetStyle.isStyle(getItem(), style);
}
+
+ @Override
+ public String getGroup() {
+ return getItem().getWidgetGroup();
+ }
@Override
public Object getValue() {
diff --git a/src/main/java/org/scijava/widget/WidgetModel.java b/src/main/java/org/scijava/widget/WidgetModel.java
index 80f033de0..fc85d633b 100644
--- a/src/main/java/org/scijava/widget/WidgetModel.java
+++ b/src/main/java/org/scijava/widget/WidgetModel.java
@@ -80,6 +80,13 @@ public interface WidgetModel extends Contextual {
* {@code style.equals(getItem().getWidgetStyle())}.
*/
boolean isStyle(String style);
+
+ /**
+ * Gets group that the widget belongs to.
+ *
+ * @return Group that the widget belongs to.
+ */
+ String getGroup();
/**
* Gets the current value of the module input.
diff --git a/src/test/java/org/scijava/script/ScriptInfoTest.java b/src/test/java/org/scijava/script/ScriptInfoTest.java
index 678996628..f1a4b0f61 100644
--- a/src/test/java/org/scijava/script/ScriptInfoTest.java
+++ b/src/test/java/org/scijava/script/ScriptInfoTest.java
@@ -266,18 +266,18 @@ public void testParameters() {
final ModuleItem> log = info.getInput("log");
assertItem("log", LogService.class, null, ItemIO.INPUT, false, true, null,
- null, null, null, null, null, null, null, noChoices, log);
+ null, null, null, null, null, null, null, null, noChoices, log);
final ModuleItem> sliderValue = info.getInput("sliderValue");
assertItem("sliderValue", int.class, "Slider Value", ItemIO.INPUT, true,
- true, null, " slidEr,", 11, null, null, 5, 15, 3.0, noChoices, sliderValue);
+ true, null, " slidEr,", null, 11, null, null, 5, 15, 3.0, noChoices, sliderValue);
assertTrue("Case-insensitive trimmed style", WidgetStyle.isStyle(sliderValue, "slider"));
final ModuleItem> animal = info.getInput("animal");
final List animalChoices = //
Arrays.asList("quick brown fox", "lazy dog");
assertItem("animal", String.class, null, ItemIO.INPUT, true, false,
- null, null, null, null, null, null, null, null, animalChoices, animal);
+ null, null, null, null, null, null, null, null, null, animalChoices, animal);
assertEquals(animal.get("family"), "Carnivora"); // test custom attribute
final ModuleItem> notAutoFilled = info.getInput("notAutoFilled");
@@ -288,7 +288,7 @@ public void testParameters() {
final ModuleItem> buffer = info.getOutput("buffer");
assertItem("buffer", StringBuilder.class, null, ItemIO.BOTH, true, true,
- null, null, null, null, null, null, null, null, noChoices, buffer);
+ null, null, null, null, null, null, null, null, null, noChoices, buffer);
int inputCount = 0;
final ModuleItem>[] inputs = { log, sliderValue, animal, notAutoFilled, msg, buffer };
@@ -367,7 +367,7 @@ private String id(final String path, final String script) {
private void assertItem(final String name, final Class> type,
final String label, final ItemIO ioType, final boolean required,
final boolean persist, final String persistKey, final String style,
- final Object value, final Object min, final Object max,
+ final String group, final Object value, final Object min, final Object max,
final Object softMin, final Object softMax, final Number stepSize,
final List> choices, final ModuleItem> item)
{
@@ -379,6 +379,7 @@ private void assertItem(final String name, final Class> type,
assertEquals(persist, item.isPersisted());
assertEquals(persistKey, item.getPersistKey());
assertEquals(style, item.getWidgetStyle());
+ assertEquals(group, item.getWidgetGroup());
assertEquals(value, item.getDefaultValue());
assertEquals(min, item.getMinimumValue());
assertEquals(max, item.getMaximumValue());