diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java index 1d2b85b41..db9b4a9fa 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java @@ -33,14 +33,17 @@ import org.apache.commons.lang3.StringUtils; +import fiji.plugin.trackmate.util.cli.CommandCLIConfigurator.ExecutablePath; +import fiji.plugin.trackmate.util.cli.CondaCLIConfigurator.CondaEnvironmentCommand; + public abstract class CLIConfigurator { - protected final List< Argument< ? > > arguments = new ArrayList<>(); + protected final List< Argument< ?, ? > > arguments = new ArrayList<>(); protected final List< SelectableArguments > selectables = new ArrayList<>(); - protected final Map< Command< ? >, Function< Object, List< String > > > translators = new HashMap<>(); + protected final Map< Argument< ?, ? >, Function< Object, List< String > > > translators = new HashMap<>(); /* * GETTERS @@ -54,7 +57,7 @@ public abstract class CLIConfigurator * * @return the list of arguments. */ - public List< Command< ? > > getArguments() + public List< Argument< ?, ? > > getArguments() { return Collections.unmodifiableList( arguments ); } @@ -77,9 +80,9 @@ public List< SelectableArguments > getSelectables() * * @return the selected arguments. */ - public List< Argument< ? > > getSelectedArguments() + public List< Argument< ?, ? > > getSelectedArguments() { - final List< Argument< ? > > selectedArguments = new ArrayList<>( arguments ); + final List< Argument< ?, ? > > selectedArguments = new ArrayList<>( arguments ); for ( final SelectableArguments selectable : selectables ) selectable.filter( selectedArguments ); return selectedArguments; @@ -89,7 +92,7 @@ public List< SelectableArguments > getSelectables() * VALUE TRANSLATOR. */ - protected void setTranslator( final Command< ? > arg, final Function< Object, List< String > > translator ) + protected void setTranslator( final Argument< ?, ? > arg, final Function< Object, List< String > > translator ) { translators.put( arg, translator ); } @@ -116,21 +119,34 @@ protected SelectableArguments addSelectableArguments() public static class SelectableArguments { - private final List< Argument< ? > > args = new ArrayList<>(); + private final List< Argument< ?, ? > > args = new ArrayList<>(); + + private String key; private int selected = 0; - public SelectableArguments add( final Argument< ? > arg ) + public SelectableArguments add( final Argument< ?, ? > arg ) { if ( !args.contains( arg ) ) args.add( arg ); return this; } - private void filter( final List< Argument< ? > > arguments ) + public SelectableArguments key( final String key ) { - final Set< Argument< ? > > toRemove = new HashSet<>(); - for ( final Argument< ? > arg : arguments ) + this.key = key; + return this; + } + + public String getKey() + { + return key; + } + + private void filter( final List< Argument< ?, ? > > arguments ) + { + final Set< Argument< ?, ? > > toRemove = new HashSet<>(); + for ( final Argument< ?, ? > arg : arguments ) { if ( !args.contains( arg ) ) continue; // Unknown of this selectable, keep it. @@ -145,23 +161,49 @@ private void filter( final List< Argument< ? > > arguments ) arguments.removeAll( toRemove ); } - public void select( final Argument< ? > arg ) + public void select( final int selection ) + { + this.selected = Math.max( 0, Math.min( args.size() - 1, selection ) ); + } + + public void select( final Argument< ?, ? > arg ) { final int sel = args.indexOf( arg ); if ( sel < 0 ) - throw new IllegalArgumentException( "Unknown argument '" + arg.getName() + "' for this selectable." ); + { + this.selected = 0; + return; + } this.selected = sel; } - public Argument< ? > getSelection() + public void select( final String key ) + { + for ( int i = 0; i < args.size(); i++ ) + { + if ( args.get( i ).getKey().equals( key ) ) + { + this.selected = i; + return; + } + } + this.selected = 0; + } + + public Argument< ?, ? > getSelection() { return args.get( selected ); } + public int getSelected() + { + return selected; + } + /** * Exposes all members of the selectable. */ - public List< Argument< ? > > getArguments() + public List< Argument< ?, ? > > getArguments() { return args; } @@ -219,7 +261,7 @@ public default void visit( final CondaEnvironmentCommand condaEnvironmentCommand */ @SuppressWarnings( "unchecked" ) - abstract class Adder< A extends ValueArgument< A, O >, T extends Adder< A, T, O >, O > + abstract class Adder< A extends Argument< A, O >, T extends Adder< A, T, O >, O > { protected String name; @@ -539,7 +581,7 @@ protected ChoiceAdder addChoiceArgument() * the argument to add to this CLI config. * @return the argument */ - protected < T extends Argument< ? > > T addExtraArgument( final T extraArg ) + protected < T extends Argument< ?, ? > > T addExtraArgument( final T extraArg ) { this.arguments.add( extraArg ); return extraArg; @@ -549,7 +591,7 @@ protected ChoiceAdder addChoiceArgument() * ARGUMENT CLASSES. */ - public static class Flag extends ValueArgument< Flag, Boolean > + public static class Flag extends Argument< Flag, Boolean > { Flag() {} @@ -603,7 +645,7 @@ public void accept( final ArgumentVisitor visitor ) } } - public static abstract class AbstractStringArgument< T extends AbstractStringArgument< T > > extends ValueArgument< T, String > + public static abstract class AbstractStringArgument< T extends AbstractStringArgument< T > > extends Argument< T, String > { @Override @@ -738,107 +780,8 @@ public String toString() } } - /** - * Base class for arguments that accept a value after the switch. - * - * @param - */ - @SuppressWarnings( "unchecked" ) - public static abstract class ValueArgument< T extends ValueArgument< T, O >, O > extends Argument< T > - { - - private O value; - - private O defaultValue; - - /** - * Arguments flagged as not required, but without default value, will be - * prompted to the user. - */ - private boolean required = false; - - private String units; - - T required( final boolean required ) - { - this.required = required; - return ( T ) this; - } - - public boolean isRequired() - { - return required; - } - - T units( final String units ) - { - this.units = units; - return ( T ) this; - } - - public String getUnits() - { - return units; - } - - T defaultValue( final O defaultValue ) - { - this.defaultValue = defaultValue; - return ( T ) this; - } - - public O getDefaultValue() - { - return defaultValue; - } - - public boolean hasDefaultValue() - { - return defaultValue != null; - } - - public void set( final O value ) - { - this.value = value; - } - - public O getValue() - { - return value; - } - - @Override - public boolean isSet() - { - return value != null; - } - - @Override - public Object getValueObject() - { - return getValue(); - } - - @Override - public String toString() - { - final String str = super.toString(); - return str - + " - is set: " + isSet() + "\n" - + ( isSet() - ? " - value: " + getValue() + "\n" - : "" ) - + " - has default value: " + hasDefaultValue() + "\n" - + ( hasDefaultValue() - ? " - default value: " + getDefaultValue() + "\n" - : "" ) - + " - required: " + isRequired() + "\n" - + " - units: " + getUnits() + "\n"; - } - } - @SuppressWarnings( "unchecked" ) - public static abstract class BoundedValueArgument< T extends BoundedValueArgument< T, O >, O > extends ValueArgument< T, O > + public static abstract class BoundedValueArgument< T extends BoundedValueArgument< T, O >, O > extends Argument< T, O > { private BoundedValueArgument() @@ -897,12 +840,16 @@ public String toString() } /** - * Mother class for executable and argument objects. + * Mother class for command arguments. Typically in the command line they + * appear after the executable name with '--something'. * * @param + * the implementing type of the argument. + * @param + * the type of value this argument accepts. */ @SuppressWarnings( "unchecked" ) - public static abstract class Command< T extends Command< T > > + public static abstract class Argument< T extends Argument< T, O >, O > { protected boolean visible = true; @@ -913,313 +860,210 @@ public static abstract class Command< T extends Command< T > > private String key; - /** - * If false, this argument won't be shown in UIs. It will - * be used for the command line builder nonetheless. - * - * @param visible - * whether this argument should be visible in the UI or not. - * By default: true. - * @see CliGuiBuilder - * @return the argument. - */ - T visible( final boolean visible ) - { - this.visible = visible; - return ( T ) this; - } - - public boolean isVisible() - { - return visible; - } - - T name( final String name ) - { - this.name = name; - return ( T ) this; - } + private String argument; - T help( final String help ) - { - this.help = help; - return ( T ) this; - } + private boolean inCLI = true; - /** - * Sets the String key to use in TrackMate settings map - * de/serialization. - * - * @param key - * the key to use. By default: the {@link #name} of this - * argument. - * @return the argument. - * @see TrackMateSettingsBuilder - */ - T key( final String key ) + T argument( final String argument ) { - - this.key = key; + this.argument = argument; return ( T ) this; } - public String getName() - { - return name; - } - - public String getHelp() - { - return help; - } - - public String getKey() + public String getArgument() { - return ( key == null ) ? getName() : key; + return argument; } - public abstract void accept( final ArgumentVisitor visitor ); - - public abstract boolean isSet(); - - @Override - public String toString() - { - return this.getClass().getSimpleName() - + " (" + getName() + ")\n" - + " - help: " + getHelp() + "\n" - + " - key: " + getKey() + "\n"; - } + private O value; - /** - * Sets the value of this argument via the specified object. This is - * used when deserializing TrackMate settings map. - * - * @param val - * the object to set the value from - * @see TrackMateSettingsBuilder - */ - public abstract void setValueObject( Object val ); + private O defaultValue; /** - * Returns an object built from the value of this argument, if it has - * one. This is used in TrackMate settings map serialization. - * - * @return the value object. - * @see TrackMateSettingsBuilder + * Arguments flagged as not required, but without default value, will be + * prompted to the user. */ - public abstract Object getValueObject(); - } - - public static class CondaEnvironmentCommand extends Command< CondaEnvironmentCommand > - { - - private final List< String > envs = new ArrayList<>(); + private boolean required = false; - private String value; + private String units; - protected CondaEnvironmentCommand() + T required( final boolean required ) { - name( "Conda environment" ); - help( "The conda environment in which the tool is configured." ); + this.required = required; + return ( T ) this; } - protected CondaEnvironmentCommand addEnvironment( final String env ) + public boolean isRequired() { - if ( !envs.contains( env ) ) - envs.add( env ); - return this; + return required; } - public String getValue() + T units( final String units ) { - return value; + this.units = units; + return ( T ) this; } - public void set( final String env ) + public String getUnits() { - final int sel = envs.indexOf( env ); - if ( sel < 0 ) - throw new IllegalArgumentException( "Unknown conda environment '" + env + - "'. Must be one of " + StringUtils.join( envs, ", " ) + "." ); - this.value = env; + return units; } - - public void set( final int selected ) + + T defaultValue( final O defaultValue ) { - if ( selected < 0 || selected >= envs.size() ) - throw new IllegalArgumentException( "Invalid index for selection of conda environment. " - + "Must be in scale " + 0 + " to " + ( envs.size() - 1 ) + " in " - + StringUtils.join( envs, ", " ) + "." ); - set( envs.get( selected ) ); + this.defaultValue = defaultValue; + return ( T ) this; } - @Override - public boolean isSet() + public O getDefaultValue() { - return value != null; + return defaultValue; } - public List< String > getEnvironment() // TODO rename + public boolean hasDefaultValue() { - return envs; + return defaultValue != null; } - @Override - public void accept( final ArgumentVisitor visitor ) + public void set( final O value ) { - visitor.visit( this ); + this.value = value; } - @Override - public String toString() + public O getValue() { - final String str = super.toString(); - return str - + " - envs: " + getEnvironment() + "\n"; + return value; } - @Override - public void setValueObject( final Object val ) + public boolean isSet() { - if ( !String.class.isInstance( val ) ) - throw new IllegalArgumentException( "Argument '" + name + "' expects String. Got " + val.getClass().getSimpleName() ); - - final String v = ( ( String ) val ); - if ( envs.contains( v ) ) - set( v ); + return value != null; } - @Override public Object getValueObject() { return getValue(); } - } - public static class ExecutablePath extends Command< ExecutablePath > - { - - private String value = null; - - public void set( final String value ) - { - this.value = value; - } - - @Override - public boolean isSet() - { - return value != null; - } - public String getValue() - { - return value; - } + /** + * Sets the value of this argument via the specified object. This is + * used when deserializing TrackMate settings map. + * + * @param val + * the object to set the value from + * @see TrackMateSettingsBuilder + */ + public abstract void setValueObject( Object val ); - @Override - public ExecutablePath name( final String name ) + /** + * If false, this argument won't be used in the command + * line generator. This is useful to add extra parameters to the GUI + * that are required by TrackMate but not by the CLI tool. + * + * @param inCLI + * whether this argument should be used when generating + * commands. By default: true. + * @see CommandBuilder + * @return the argument. + */ + T inCLI( final boolean inCLI ) { - return super.name( name ); + this.inCLI = inCLI; + return ( T ) this; } - @Override - public ExecutablePath help( final String help ) + public boolean isInCLI() { - return super.help( help ); + return inCLI; } - @Override - public ExecutablePath key( final String key ) + /** + * If false, this argument won't be shown in UIs. It will + * be used for the command line builder nonetheless. + * + * @param visible + * whether this argument should be visible in the UI or not. + * By default: true. + * @see CliGuiBuilder + * @return the argument. + */ + T visible( final boolean visible ) { - return super.key( key ); + this.visible = visible; + return ( T ) this; } - @Override - public void accept( final ArgumentVisitor visitor ) + public boolean isVisible() { - visitor.visit( this ); + return visible; } - @Override - public String toString() + T name( final String name ) { - final String str = super.toString(); - return str + " - value: " + getValue() + "\n"; + this.name = name; + return ( T ) this; } - @Override - public void setValueObject( final Object val ) + T help( final String help ) { - if ( !String.class.isInstance( val ) ) - throw new IllegalArgumentException( "Argument '" + name + "' expects String. Got " + val.getClass().getSimpleName() ); - - final String v = ( ( String ) val ); - set( v ); + this.help = help; + return ( T ) this; } - @Override - public Object getValueObject() + /** + * Sets the String key to use in TrackMate settings map + * de/serialization. + * + * @param key + * the key to use. By default: the {@link #name} of this + * argument. + * @return the argument. + * @see TrackMateSettingsBuilder + */ + T key( final String key ) { - return getValue(); - } - } - - /** - * Mother class for command arguments. Typically in the command line they - * appear after the executable name with '--something'. - * - * @param - */ - @SuppressWarnings( "unchecked" ) - public static abstract class Argument< T extends Argument< T > > extends Command< T > - { - - private String argument; - private boolean inCLI = true; - - T argument( final String argument ) - { - this.argument = argument; + this.key = key; return ( T ) this; } - public String getArgument() + public String getName() { - return argument; + return name; } - /** - * If false, this argument won't be used in the command - * line generator. This is useful to add extra parameters to the GUI - * that are required by TrackMate but not by the CLI tool. - * - * @param inCLI - * whether this argument should be used when generating - * commands. By default: true. - * @see CommandBuilder - * @return the argument. - */ - T inCLI( final boolean inCLI ) + public String getHelp() { - this.inCLI = inCLI; - return ( T ) this; + return help; } - public boolean isInCLI() + public String getKey() { - return inCLI; + return ( key == null ) ? getName() : key; } + public abstract void accept( final ArgumentVisitor visitor ); + @Override public String toString() { - final String str = super.toString(); - return str + return this.getClass().getSimpleName() + + " (" + getName() + ")\n" + + " - help: " + getHelp() + "\n" + + " - key: " + getKey() + "\n" + " - argument: " + getArgument() + "\n" - + " - visible: " + isVisible() + "\n"; + + " - visible: " + isVisible() + "\n" + + " - is set: " + isSet() + "\n" + + ( isSet() + ? " - value: " + getValue() + "\n" + : "" ) + + " - has default value: " + hasDefaultValue() + "\n" + + ( hasDefaultValue() + ? " - default value: " + getDefaultValue() + "\n" + : "" ) + + " - required: " + isRequired() + "\n" + + " - units: " + getUnits() + "\n"; } } @@ -1235,17 +1079,13 @@ public String toString() public String check() { final StringBuilder str = new StringBuilder(); - for ( final Argument< ? > arg : getSelectedArguments() ) + for ( final Argument< ?, ? > arg : getSelectedArguments() ) { if ( arg.isInCLI() && arg.getArgument() == null ) str.append( "Argument '" + arg.getName() + "' does not define the argument switch.\n" ); - if ( ValueArgument.class.isInstance( arg ) ) - { - final ValueArgument< ?, ? > varg = ( ValueArgument< ?, ? > ) arg; - if ( varg.isRequired() && !varg.isSet() && !varg.hasDefaultValue() ) - str.append( "Argument '" + arg.getName() + "' is required but is not set and does not define a default value.\n" ); - } + if ( arg.isRequired() && !arg.isSet() && !arg.hasDefaultValue() ) + str.append( "Argument '" + arg.getName() + "' is required but is not set and does not define a default value.\n" ); } return str.length() == 0 ? null : str.toString(); } @@ -1253,7 +1093,7 @@ public String check() /** * Returns the command object of this tool. * - * @return the command object. + * @return the command object, as an argument. */ - public abstract Command< ? > getCommandArg(); + public abstract Argument< ?, ? > getCommandArg(); } diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java index 4f781ab33..41af6992c 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java @@ -1,5 +1,5 @@ /*- - * #%L +f * #%L * TrackMate: your buddy for everyday tracking. * %% * Copyright (C) 2010 - 2024 TrackMate developers. @@ -37,13 +37,19 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.io.File; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.function.Consumer; +import java.util.function.IntSupplier; +import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; @@ -60,6 +66,8 @@ import javax.swing.SwingConstants; import fiji.plugin.trackmate.gui.Fonts; +import fiji.plugin.trackmate.gui.displaysettings.BoundedValue; +import fiji.plugin.trackmate.gui.displaysettings.BoundedValue.UpdateListener; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BooleanElement; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.DoubleElement; @@ -72,16 +80,14 @@ import fiji.plugin.trackmate.util.cli.CLIConfigurator.Argument; import fiji.plugin.trackmate.util.cli.CLIConfigurator.ArgumentVisitor; import fiji.plugin.trackmate.util.cli.CLIConfigurator.ChoiceArgument; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.Command; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.CondaEnvironmentCommand; import fiji.plugin.trackmate.util.cli.CLIConfigurator.DoubleArgument; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.ExecutablePath; import fiji.plugin.trackmate.util.cli.CLIConfigurator.Flag; import fiji.plugin.trackmate.util.cli.CLIConfigurator.IntArgument; import fiji.plugin.trackmate.util.cli.CLIConfigurator.PathArgument; import fiji.plugin.trackmate.util.cli.CLIConfigurator.SelectableArguments; import fiji.plugin.trackmate.util.cli.CLIConfigurator.StringArgument; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.ValueArgument; +import fiji.plugin.trackmate.util.cli.CommandCLIConfigurator.ExecutablePath; +import fiji.plugin.trackmate.util.cli.CondaCLIConfigurator.CondaEnvironmentCommand; public class CliGuiBuilder implements ArgumentVisitor { @@ -92,18 +98,14 @@ public class CliGuiBuilder implements ArgumentVisitor private final GridBagConstraints c; - private ButtonGroup currentButtonGroup; - private int topInset = 5; private int bottomInset = 5; - private boolean selectedInCurrent = false; - - private SelectableArguments selectable; - private final List< StyleElement > elements = new ArrayList<>(); + private JRadioButton rdbtn; + private CliGuiBuilder() { this.panel = new CliConfigPanel(); @@ -118,9 +120,10 @@ private CliGuiBuilder() c.gridy = 0; } - private void setCurrentButtonGroup( final ButtonGroup buttonGroup ) + + private void setCurrentRadioButton( final JRadioButton radioButton ) { - if ( buttonGroup == null || ( this.currentButtonGroup != buttonGroup ) ) + if ( radioButton == null || ( this.rdbtn != radioButton ) ) { topInset = 5; bottomInset = 5; @@ -130,17 +133,7 @@ private void setCurrentButtonGroup( final ButtonGroup buttonGroup ) topInset = 0; bottomInset = 0; } - this.currentButtonGroup = buttonGroup; - } - - private void setCurrentSelected( final boolean selectedInCurrent ) - { - this.selectedInCurrent = selectedInCurrent; - } - - private void setCurrentSelectable( final SelectableArguments selectable ) - { - this.selectable = selectable; + this.rdbtn = radioButton; } /* @@ -150,6 +143,14 @@ private void setCurrentSelectable( final SelectableArguments selectable ) @Override public void visit( final ExecutablePath arg ) { + if ( !arg.isSet() ) + { + if ( !arg.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); + arg.set( arg.getDefaultValue() ); + } + final StringElement element = stringElement( arg.getName(), arg::getValue, arg::set ); elements.add( element ); addPathToLayout( @@ -167,10 +168,16 @@ public void visit( final ExecutablePath arg ) @Override public void visit( final Flag flag ) { + if ( !flag.isSet() ) + { + if ( !flag.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + flag.getName() + "' misses both." ); + flag.set( flag.getDefaultValue() ); + } + final BooleanElement element = booleanElement( flag.getName(), flag::getValue, flag::set ); elements.add( element ); - if ( !flag.isSet() ) - element.set( flag.getDefaultValue() ); final JCheckBox checkbox = linkedCheckBox( element, "" ); checkbox.setHorizontalAlignment( SwingConstants.LEADING ); addToLayout( @@ -183,6 +190,14 @@ public void visit( final Flag flag ) @Override public void visit( final IntArgument arg ) { + if ( !arg.isSet() ) + { + if ( !arg.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); + arg.set( arg.getDefaultValue() ); + } + final IntElement element = intElement( arg.getName(), arg.getMin(), arg.getMax(), arg::getValue, arg::set ); elements.add( element ); @@ -209,15 +224,19 @@ public void visit( final IntArgument arg ) @Override public void visit( final DoubleArgument arg ) { + if ( !arg.isSet() ) + { + if ( !arg.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); + arg.set( arg.getDefaultValue() ); + } + if ( arg.hasMin() && arg.hasMax() ) { final BoundedDoubleElement element = boundedDoubleElement( arg.getName(), arg.getMin(), arg.getMax(), arg::getValue, arg::set ); elements.add( element ); - if ( arg.isSet() ) - element.set( arg.getValue() ); - else if ( arg.hasDefaultValue() ) - element.set( arg.getDefaultValue() ); addToLayout( arg.getHelp(), new JLabel( element.getLabel() ), @@ -245,6 +264,14 @@ else if ( arg.hasDefaultValue() ) @Override public void visit( final StringArgument arg ) { + if ( !arg.isSet() ) + { + if ( !arg.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); + arg.set( arg.getDefaultValue() ); + } + final StringElement element = stringElement( arg.getName(), arg::getValue, arg::set ); elements.add( element ); addToLayoutTwoLines( @@ -257,12 +284,16 @@ public void visit( final StringArgument arg ) @Override public void visit( final PathArgument arg ) { + if ( !arg.isSet() ) + { + if ( !arg.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); + arg.set( arg.getDefaultValue() ); + } + final StringElement element = stringElement( arg.getName(), arg::getValue, arg::set ); elements.add( element ); - if ( arg.isSet() ) - element.set( arg.getValue() ); - else if ( arg.hasDefaultValue() ) - element.set( arg.getDefaultValue() ); addPathToLayout( arg.getHelp(), new JLabel( element.getLabel() ), @@ -273,13 +304,18 @@ else if ( arg.hasDefaultValue() ) @Override public void visit( final ChoiceArgument arg ) { + if ( !arg.isSet() ) + { + if ( !arg.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); + arg.set( arg.getDefaultValue() ); + } + final ListElement< String > element = listElement( arg.getName(), arg.getChoices(), arg::getValue, arg::set ); elements.add( element ); final JComboBox< String > comboBox = linkedComboBoxSelector( element ); - if ( arg.isSet() ) - comboBox.setSelectedItem( arg.getValue() ); - else if ( arg.hasDefaultValue() ) - comboBox.setSelectedItem( arg.getDefaultValue() ); + comboBox.setSelectedItem( arg.getValue() ); addToLayout( arg.getHelp(), new JLabel( element.getLabel() ), @@ -291,13 +327,18 @@ else if ( arg.hasDefaultValue() ) @Override public void visit( final CondaEnvironmentCommand arg ) { - final ListElement< String > element = listElement( arg.getName(), arg.getEnvironment(), arg::getValue, arg::set ); + if ( !arg.isSet() ) + { + if ( !arg.hasDefaultValue() ) + throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " + + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); + arg.set( arg.getDefaultValue() ); + } + + final ListElement< String > element = listElement( arg.getName(), arg.getEnvironments(), arg::getValue, arg::set ); elements.add( element ); final JComboBox< String > comboBox = linkedComboBoxSelector( element ); - if ( arg.isSet() ) - comboBox.setSelectedItem( arg.getValue() ); - else - comboBox.setSelectedItem( 0 ); + comboBox.setSelectedItem( arg.getValue() ); addToLayout( arg.getHelp(), new JLabel( element.getLabel() ), @@ -309,23 +350,16 @@ public void visit( final CondaEnvironmentCommand arg ) * UI STUFF. */ - private void addToLayoutTwoLines( final String help, final JLabel lbl, final JComponent comp, final Argument< ? > arg ) + private void addToLayoutTwoLines( final String help, final JLabel lbl, final JComponent comp, final Argument< ?, ? > arg ) { lbl.setText( lbl.getText() + " " ); lbl.setFont( Fonts.SMALL_FONT ); comp.setFont( Fonts.SMALL_FONT ); final JComponent item; - if ( currentButtonGroup != null ) + if ( rdbtn != null ) { - final JRadioButton rdbtn = new JRadioButton(); - currentButtonGroup.add( rdbtn ); - final SelectableArguments localSelectable = selectable; - rdbtn.addItemListener( e -> { - comp.setEnabled( rdbtn.isSelected() ); - if ( rdbtn.isSelected() ) - localSelectable.select( arg ); - } ); - rdbtn.setSelected( selectedInCurrent ); + final JRadioButton btn = rdbtn; + rdbtn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); comp.setEnabled( rdbtn.isSelected() ); item = new JPanel(); item.setLayout( new BoxLayout( item, BoxLayout.LINE_AXIS ) ); @@ -353,7 +387,7 @@ private void addToLayoutTwoLines( final String help, final JLabel lbl, final JCo } } - private void addPathToLayout( final String help, final JLabel lbl, final JTextField tf, final Argument< ? > arg ) + private void addPathToLayout( final String help, final JLabel lbl, final JTextField tf, final Argument< ?, ? > arg ) { final JPanel p = new JPanel(); final BoxLayout bl = new BoxLayout( p, BoxLayout.LINE_AXIS ); @@ -372,19 +406,14 @@ private void addPathToLayout( final String help, final JLabel lbl, final JTextFi tf.postActionEvent(); } ); - if ( currentButtonGroup != null ) + if ( rdbtn != null ) { - final JRadioButton rdbtn = new JRadioButton(); - currentButtonGroup.add( rdbtn ); - final SelectableArguments localSelectable = selectable; + final JRadioButton btn = rdbtn; rdbtn.addItemListener( e -> { - tf.setEnabled( rdbtn.isSelected() ); - browseButton.setEnabled( rdbtn.isSelected() ); - if ( rdbtn.isSelected() ) - localSelectable.select( arg ); + tf.setEnabled( btn.isSelected() ); + browseButton.setEnabled( btn.isSelected() ); } ); - rdbtn.setSelected( selectedInCurrent ); tf.setEnabled( rdbtn.isSelected() ); browseButton.setEnabled( rdbtn.isSelected() ); p.add( rdbtn ); @@ -410,7 +439,7 @@ private void addPathToLayout( final String help, final JLabel lbl, final JTextFi } } - private void addToLayout( final String help, final JLabel lbl, final JComponent comp, final Argument< ? > arg ) + private void addToLayout( final String help, final JLabel lbl, final JComponent comp, final Argument< ?, ? > arg ) { lbl.setText( lbl.getText() + " " ); lbl.setFont( Fonts.SMALL_FONT ); @@ -418,17 +447,10 @@ private void addToLayout( final String help, final JLabel lbl, final JComponent comp.setFont( Fonts.SMALL_FONT ); final JComponent header; - if ( arg != null && currentButtonGroup != null ) + if ( arg != null && rdbtn != null ) { - final JRadioButton rdbtn = new JRadioButton(); - currentButtonGroup.add( rdbtn ); - final SelectableArguments localSelectable = selectable; - rdbtn.addItemListener( e -> { - comp.setEnabled( rdbtn.isSelected() ); - if ( rdbtn.isSelected() ) - localSelectable.select( arg ); - } ); - rdbtn.setSelected( selectedInCurrent ); + final JRadioButton btn = rdbtn; + rdbtn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); comp.setEnabled( rdbtn.isSelected() ); header = new JPanel(); header.setLayout( new BoxLayout( header, BoxLayout.LINE_AXIS ) ); @@ -461,7 +483,7 @@ private void addToLayout( final String help, final JLabel lbl, final JComponent } } - private void addToLayout( final String help, final JLabel lbl, final JComponent comp, final String units, final Argument< ? > arg ) + private void addToLayout( final String help, final JLabel lbl, final JComponent comp, final String units, final Argument< ?, ? > arg ) { if ( units == null ) { @@ -475,12 +497,10 @@ private void addToLayout( final String help, final JLabel lbl, final JComponent comp.setFont( Fonts.SMALL_FONT ); final JComponent header; - if ( currentButtonGroup != null ) + if ( rdbtn != null ) { - final JRadioButton rdbtn = new JRadioButton(); - currentButtonGroup.add( rdbtn ); - rdbtn.addItemListener( e -> comp.setEnabled( rdbtn.isSelected() ) ); - rdbtn.setSelected( selectedInCurrent ); + final JRadioButton btn = rdbtn; + rdbtn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); header = new JPanel(); header.setLayout( new BoxLayout( header, BoxLayout.LINE_AXIS ) ); header.add( rdbtn ); @@ -520,12 +540,10 @@ private void addToLayout( final String help, final JLabel lbl, final JComponent private void addToLayout( final String help, final JComponent comp ) { final JComponent header; - if ( currentButtonGroup != null ) + if ( rdbtn != null ) { - final JRadioButton rdbtn = new JRadioButton(); - currentButtonGroup.add( rdbtn ); - rdbtn.addItemListener( e -> comp.setEnabled( rdbtn.isSelected() ) ); - rdbtn.setSelected( selectedInCurrent ); + final JRadioButton btn = rdbtn; + rdbtn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); header = new JPanel(); header.setLayout( new BoxLayout( header, BoxLayout.LINE_AXIS ) ); header.add( rdbtn ); @@ -560,67 +578,48 @@ private void addLastRow() public static CliConfigPanel build( final CLIConfigurator cli ) { - /* - * Check that the visible arguments are all set. - */ - - final List< Command< ? > > all = new ArrayList<>( cli.getArguments() ); - all.add( cli.getCommandArg() ); - final List< Command< ? > > valueNotSet = all.stream() - .filter( Command::isVisible ) - .filter( ValueArgument.class::isInstance ) - .filter( arg -> !arg.isSet() ) - .collect( Collectors.toList() ); - if ( !valueNotSet.isEmpty() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value set. The following miss one: " + - ( valueNotSet.stream().map( arg -> arg.getName() ).collect( Collectors.toList() ) ) ); - /* * Create the builder. */ - + final CliGuiBuilder builder = new CliGuiBuilder(); cli.getCommandArg().accept( builder ); /* * Iterate over CLI arguments. */ - + // Map a selectable group to a button group in the GUI - final Map< SelectableArguments, ButtonGroup > buttonGroups = new HashMap<>(); + final Map< Argument< ?, ? >, JRadioButton > buttons = new HashMap<>(); + for ( final SelectableArguments selectable : cli.getSelectables() ) + { + final List< Argument< ?, ? > > args = selectable.getArguments(); + final int nItems = args.size(); + final String label = selectable.getKey(); + final IntSupplier get = selectable::getSelected; + final Consumer< Integer > set = selectable::select; + final IntElement element = intElement( label, 0, nItems - 1, get, set ); + builder.elements.add( element ); + final ButtonGroup buttonGroup = linkedButtonGroup( element ); + // Link radio buttons to arguments. + final Enumeration< AbstractButton > enumeration = buttonGroup.getElements(); + final Iterator< Argument< ?, ? > > it = args.iterator(); + while ( enumeration.hasMoreElements() ) + { + final JRadioButton btn = ( JRadioButton ) enumeration.nextElement(); + final Argument< ?, ? > arg = it.next(); + buttons.put( arg, btn ); + btn.setSelected( selectable.getSelection().equals( arg ) ); + } + } // Iterate over arguments, taking care of selectable group. - for ( final Command< ? > arg : cli.getArguments() ) + for ( final Argument< ?, ? > arg : cli.getArguments() ) { if ( !arg.isVisible() ) continue; - /* - * Assume we are not in a selectable group. In the case the current - * button group is null and we won't be adding a radio button. - */ - builder.setCurrentButtonGroup( null ); - builder.setCurrentSelected( false ); - builder.setCurrentSelectable( null ); - for ( final SelectableArguments selectable : cli.getSelectables() ) - { - if ( selectable.getArguments().contains( arg ) ) - { - // We are in a selectable group. We will add a radio button - // to the current button group. - final ButtonGroup buttonGroup = buttonGroups.computeIfAbsent( selectable, k -> new ButtonGroup() ); - builder.setCurrentButtonGroup( buttonGroup ); - builder.setCurrentSelectable( selectable ); - if ( selectable.getSelection().equals( arg ) ) - { - // We are the selected one. - builder.setCurrentSelected( true ); - } - - break; - } - } + builder.setCurrentRadioButton( buttons.get( arg ) ); arg.accept( builder ); } @@ -632,6 +631,40 @@ public static CliConfigPanel build( final CLIConfigurator cli ) return builder.panel; } + private static ButtonGroup linkedButtonGroup( final IntElement element ) + { + final BoundedValue value = element.getValue(); + final ButtonGroup buttonGroup = new ButtonGroup(); + final List< JRadioButton > buttons = new ArrayList<>(); + for ( int i = 0; i <= value.getRangeMax(); i++ ) + { + final JRadioButton btn = new JRadioButton(); + buttons.add( btn ); + final int selected = i; + btn.addItemListener( new ItemListener() + { + + @Override + public void itemStateChanged( final ItemEvent e ) + { + if (btn.isSelected()) + element.set( selected ); + } + } ); + buttonGroup.add( btn ); + } + element.getValue().setUpdateListener( new UpdateListener() + { + + @Override + public void update() + { + final int selected = element.get(); + buttons.get( selected ).setSelected( true ); + } + } ); + return buttonGroup; + } public class CliConfigPanel extends JPanel { diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java index 911276df2..ebe01db0e 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java @@ -33,23 +33,22 @@ import fiji.plugin.trackmate.util.cli.CLIConfigurator.Argument; import fiji.plugin.trackmate.util.cli.CLIConfigurator.ArgumentVisitor; import fiji.plugin.trackmate.util.cli.CLIConfigurator.ChoiceArgument; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.Command; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.CondaEnvironmentCommand; import fiji.plugin.trackmate.util.cli.CLIConfigurator.DoubleArgument; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.ExecutablePath; import fiji.plugin.trackmate.util.cli.CLIConfigurator.Flag; import fiji.plugin.trackmate.util.cli.CLIConfigurator.IntArgument; import fiji.plugin.trackmate.util.cli.CLIConfigurator.PathArgument; import fiji.plugin.trackmate.util.cli.CLIConfigurator.StringArgument; +import fiji.plugin.trackmate.util.cli.CommandCLIConfigurator.ExecutablePath; +import fiji.plugin.trackmate.util.cli.CondaCLIConfigurator.CondaEnvironmentCommand; public class CommandBuilder implements ArgumentVisitor { private final List< String > tokens = new ArrayList<>(); - private final Map< Command< ? >, Function< Object, List< String > > > translators; + private final Map< Argument< ?, ? >, Function< Object, List< String > > > translators; - protected CommandBuilder( final Map< Command< ? >, Function< Object, List< String > > > translators ) + protected CommandBuilder( final Map< Argument< ?, ? >, Function< Object, List< String > > > translators ) { this.translators = translators; } @@ -60,7 +59,7 @@ public String toString() return StringUtils.join( tokens, " " ); } - private void check( final Argument< ? > arg ) + private void check( final Argument< ?, ? > arg ) { if ( arg.isInCLI() && arg.getArgument() == null ) throw new IllegalArgumentException( "Incorrect configuration for argument '" + arg.getName() diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java index 95a2faeb2..36802c61f 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java @@ -32,6 +32,34 @@ public abstract class CommandCLIConfigurator extends CLIConfigurator protected final ExecutablePath executable; + public static class ExecutablePath extends AbstractStringArgument< ExecutablePath > + { + + @Override + public ExecutablePath name( final String name ) + { + return super.name( name ); + } + + @Override + public ExecutablePath help( final String help ) + { + return super.help( help ); + } + + @Override + public ExecutablePath key( final String key ) + { + return super.key( key ); + } + + @Override + public void accept( final ArgumentVisitor visitor ) + { + visitor.visit( this ); + } + } + protected CommandCLIConfigurator() { this.executable = new ExecutablePath(); diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java index cb32ab721..094a110e8 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java @@ -34,6 +34,57 @@ public abstract class CondaCLIConfigurator extends CLIConfigurator private final CondaEnvironmentCommand condaEnv; + public static class CondaEnvironmentCommand extends AbstractStringArgument< CondaEnvironmentCommand > + { + + private final List< String > envs = new ArrayList<>(); + + protected CondaEnvironmentCommand() + { + name( "Conda environment" ); + help( "The conda environment in which the tool is configured." ); + key( KEY_CONDA_ENV ); + } + + protected CondaEnvironmentCommand addEnvironment( final String env ) + { + if ( !envs.contains( env ) ) + envs.add( env ); + return this; + } + + @Override + public void set( final String env ) + { + final int sel = envs.indexOf( env ); + if ( sel < 0 ) + { + super.set( envs.get( 0 ) ); + return; + } + super.set( env ); + } + + public void set( final int selected ) + { + if ( selected < 0 || selected >= envs.size() ) + set( envs.get( 0 ) ); + else + set( envs.get( selected ) ); + } + + public List< String > getEnvironments() + { + return envs; + } + + @Override + public void accept( final ArgumentVisitor visitor ) + { + visitor.visit( this ); + } + } + protected CondaCLIConfigurator() { super(); @@ -52,7 +103,7 @@ protected CondaCLIConfigurator() final String condaPath = CLIUtils.getCondaPath(); // Conda and executable stuff. final String envname = ( String ) s; - if (IJ.isWindows()) + if ( IJ.isWindows() ) { /* * In Windows: Launch a shell, change the conda environment, @@ -87,7 +138,7 @@ protected CondaCLIConfigurator() } @Override - public Command< ? > getCommandArg() + public CondaEnvironmentCommand getCommandArg() { return condaEnv; } diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java index 0ac6ba8d1..5bf12fc2e 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java @@ -23,7 +23,8 @@ import java.util.Map; -import fiji.plugin.trackmate.util.cli.CLIConfigurator.Command; +import fiji.plugin.trackmate.util.cli.CLIConfigurator.Argument; +import fiji.plugin.trackmate.util.cli.CLIConfigurator.SelectableArguments; public class TrackMateSettingsBuilder { @@ -31,19 +32,32 @@ public class TrackMateSettingsBuilder private TrackMateSettingsBuilder() {} - private static void toMap( final Command< ? > arg, final Map< String, Object > settings ) + private static void toMap( final Argument< ?, ? > arg, final Map< String, Object > settings ) { if ( arg.getKey() != null ) settings.put( arg.getKey(), arg.getValueObject() ); } - private static void fromMap( final Map< String, Object > settings, final Command< ? > arg ) + private static void toMap( final SelectableArguments selectable, final Map< String, Object > settings ) + { + if ( selectable.getKey() != null ) + settings.put( selectable.getKey(), selectable.getSelection().getKey() ); + } + + private static void fromMap( final Map< String, Object > settings, final Argument< ?, ? > arg ) { final Object val = settings.get( arg.getKey() ); if ( val != null ) arg.setValueObject( val ); } + private static void fromMap( final Map< String, Object > settings, final SelectableArguments selectable ) + { + final Object val = settings.get( selectable.getKey() ); + if ( val != null ) + selectable.select( ( String ) val ); + } + /** * Serializes the specified CLI config and extra parameters to a TrackMate * settings map. @@ -57,6 +71,7 @@ public static void toTrackMateSettings( final Map< String, Object > settings, fi { toMap( cli.getCommandArg(), settings ); cli.getArguments().forEach( arg -> toMap( arg, settings ) ); + cli.getSelectables().forEach( selectable -> toMap( selectable, settings ) ); } /** @@ -73,5 +88,6 @@ public static final void fromTrackMateSettings( final Map< String, Object > sett { fromMap( settings, cli.getCommandArg() ); cli.getArguments().forEach( arg -> fromMap( settings, arg ) ); + cli.getSelectables().forEach( selectable -> fromMap( settings, selectable ) ); } }