001package jmri.jmrit.logixng.actions;
002
003import java.util.*;
004
005import jmri.*;
006import jmri.jmrit.logixng.*;
007import jmri.jmrit.logixng.util.LogixNG_SelectEnum;
008import jmri.jmrit.logixng.util.LogixNG_SelectDouble;
009import jmri.jmrit.logixng.util.parser.ParserException;
010import static jmri.jmrit.simpleclock.SimpleTimebase.MAXIMUM_RATE;
011import static jmri.jmrit.simpleclock.SimpleTimebase.MINIMUM_RATE;
012import jmri.util.ThreadingUtil;
013
014/**
015 * This action provides the ability to set the fast clock speed.
016 *
017 * @author Daniel Bergqvist Copyright 2021
018 * @author Dave Sand Copyright 2021
019 * @author Daniel Bergqvist Copyright 2022
020 */
021public class ActionClockRate extends AbstractDigitalAction {
022
023    private final LogixNG_SelectEnum<ClockState> _selectEnum =
024            new LogixNG_SelectEnum<>(this, ClockState.values(), ClockState.SetClockRate);
025    private final LogixNG_SelectDouble _selectSpeed =
026            new LogixNG_SelectDouble(this, 3, new DefaultFormatterParserValidator());
027
028
029    public ActionClockRate(String sys, String user)
030            throws BadUserNameException, BadSystemNameException {
031        super(sys, user);
032    }
033
034    @Override
035    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
036        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
037        String sysName = systemNames.get(getSystemName());
038        String userName = userNames.get(getSystemName());
039        if (sysName == null) sysName = manager.getAutoSystemName();
040        ActionClockRate copy = new ActionClockRate(sysName, userName);
041        copy.setComment(getComment());
042        _selectEnum.copy(copy._selectEnum);
043        _selectSpeed.copy(copy._selectSpeed);
044        return manager.registerAction(copy);
045    }
046
047    public LogixNG_SelectEnum<ClockState> getSelectEnum() {
048        return _selectEnum;
049    }
050
051    public LogixNG_SelectDouble getSelectSpeed() {
052        return _selectSpeed;
053    }
054
055    /**
056     * Convert speed to an I18N decimal string.
057     * @param locale The Locale to use for the String conversion.
058     * @param speed The speed
059     * @return speed formatted as %1.3f
060     */
061    public static String formatSpeed(Locale locale, double speed) {
062        return String.format(locale,"%1.3f", speed);
063    }
064
065    /** {@inheritDoc} */
066    @Override
067    public LogixNG_Category getCategory() {
068        return LogixNG_Category.ITEM;
069    }
070
071    /** {@inheritDoc} */
072    @Override
073    public void execute() throws JmriException {
074
075        ClockState theState = _selectEnum.evaluateEnum(getConditionalNG());
076        double theValue = _selectSpeed.evaluateValue(getConditionalNG());
077
078        jmri.Timebase timebase = InstanceManager.getDefault(jmri.Timebase.class);
079
080        ThreadingUtil.runOnLayoutWithJmriException(() -> {
081            switch(theState) {
082                case SetClockRate:
083                    try {
084                        timebase.userSetRate(theValue);
085                    } catch (TimebaseRateException e) {
086                        // Do nothing. This error is already logged as an error
087                    }
088                    break;
089
090                case IncreaseClockRate:
091                    try {
092                        timebase.userSetRate(timebase.userGetRate() + theValue);
093                    } catch (TimebaseRateException e) {
094                        // Do nothing. This error is already logged as an error
095                    }
096                    break;
097
098                case DecreaseClockRate:
099                    try {
100                        timebase.userSetRate(timebase.userGetRate() - theValue);
101                    } catch (TimebaseRateException e) {
102                        // Do nothing. This error is already logged as an error
103                    }
104                    break;
105
106                default:
107                    throw new IllegalArgumentException("Invalid clock state: " + theState.name());
108            }
109        });
110    }
111
112    @Override
113    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
114        throw new UnsupportedOperationException("Not supported.");
115    }
116
117    @Override
118    public int getChildCount() {
119        return 0;
120    }
121
122    @Override
123    public String getShortDescription(Locale locale) {
124        return Bundle.getMessage(locale, "ActionClockRate_Short");
125    }
126
127    @Override
128    public String getLongDescription(Locale locale) {
129        String value;
130        if (_selectSpeed.isDirectAddressing()) {
131            value = formatSpeed( locale, _selectSpeed.getValue());
132        } else {
133            value = _selectSpeed.getDescription(locale);
134        }
135        if (_selectEnum.isDirectAddressing()) {
136            if (_selectEnum.getEnum() == ClockState.SetClockRate) {
137                return Bundle.getMessage(locale, "ActionClockRate_LongTo", _selectEnum.getDescription(locale), value);
138            }
139            return Bundle.getMessage(locale, "ActionClockRate_LongWith", _selectEnum.getDescription(locale), value);
140        } else {
141            return Bundle.getMessage(locale, "ActionClockRate_LongTo", _selectEnum.getDescription(locale), value);
142        }
143    }
144
145    /** {@inheritDoc} */
146    @Override
147    public void setup() {
148        // Do nothing
149    }
150
151    /** {@inheritDoc} */
152    @Override
153    public void disposeMe() {
154    }
155
156
157    public enum ClockState {
158        SetClockRate(Bundle.getMessage("ActionClockRate_SetClockRate")),
159        IncreaseClockRate(Bundle.getMessage("ActionClockRate_IncreaseClockRate")),
160        DecreaseClockRate(Bundle.getMessage("ActionClockRate_DecreaseClockRate"));
161
162        private final String _text;
163
164        private ClockState(String text) {
165            this._text = text;
166        }
167
168        @Override
169        public String toString() {
170            return _text;
171        }
172
173    }
174
175
176    private static class DefaultFormatterParserValidator
177            extends LogixNG_SelectDouble.DefaultFormatterParserValidator {
178
179        @Override
180        public double getInitialValue() {
181            return 1.0;
182        }
183
184        @Override
185        public double parse(String str) {
186            try {
187                double value = Double.parseDouble(str);
188                if (value < MINIMUM_RATE || value > MAXIMUM_RATE) {
189                    return MINIMUM_RATE;
190                }
191                return value;
192            } catch (NumberFormatException ex) {
193                return MINIMUM_RATE;
194            }
195        }
196
197        @Override
198        public String validate(String str) {
199            try {
200                double value = Double.parseDouble(str);
201                if (value < MINIMUM_RATE || value > MAXIMUM_RATE) {
202                    return Bundle.getMessage("ActionClockRate_RangeError",
203                            MINIMUM_RATE, MAXIMUM_RATE);
204                }
205                return null;
206            } catch (NumberFormatException ex) {
207                return Bundle.getMessage("ActionClockRate_ParseError", str);
208            }
209        }
210
211    }
212
213//    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPower.class);
214
215}