001package jmri.jmrit.logixng.actions; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.time.LocalTime; 006import java.time.format.DateTimeFormatter; 007import java.time.format.DateTimeParseException; 008import java.util.*; 009 010import jmri.*; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.util.LogixNG_SelectEnum; 013import jmri.jmrit.logixng.util.LogixNG_SelectInteger; 014import jmri.jmrit.logixng.util.parser.ParserException; 015import jmri.util.ThreadingUtil; 016 017/** 018 * This action provides the ability to set the fast clock time and start and stop the fast clock. 019 * 020 * @author Daniel Bergqvist Copyright 2021 021 * @author Dave Sand Copyright 2021 022 */ 023public class ActionClock extends AbstractDigitalAction 024 implements PropertyChangeListener { 025 026 private final LogixNG_SelectEnum<ClockState> _selectEnum = 027 new LogixNG_SelectEnum<>(this, ClockState.values(), ClockState.SetClock, this); 028 private final LogixNG_SelectInteger _selectValue = 029 new LogixNG_SelectInteger(this, this, new TimeFormatterParserValidator()); 030 031 032 public ActionClock(String sys, String user) 033 throws BadUserNameException, BadSystemNameException { 034 super(sys, user); 035 } 036 037 @Override 038 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 039 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 040 String sysName = systemNames.get(getSystemName()); 041 String userName = userNames.get(getSystemName()); 042 if (sysName == null) sysName = manager.getAutoSystemName(); 043 ActionClock copy = new ActionClock(sysName, userName); 044 copy.setComment(getComment()); 045 _selectEnum.copy(copy._selectEnum); 046 _selectValue.copy(copy._selectValue); 047 return manager.registerAction(copy); 048 } 049 050 public LogixNG_SelectEnum<ClockState> getSelectEnum() { 051 return _selectEnum; 052 } 053 054 public LogixNG_SelectInteger getSelectTime() { 055 return _selectValue; 056 } 057 058 /** 059 * Convert minutes since midnight to hh:mm. 060 * @param minutes The number of minutes from 0 to 1439. 061 * @return time formatted as hh:mm. 062 */ 063 public static String formatTime(int minutes) { 064 String hhmm = "00:00"; 065 if (minutes >= 0 && minutes < 1440) { 066 hhmm = String.format("%02d:%02d", 067 minutes / 60, 068 minutes % 60); 069 } 070 return hhmm; 071 } 072 073 /** {@inheritDoc} */ 074 @Override 075 public Category getCategory() { 076 return Category.ITEM; 077 } 078 079 /** {@inheritDoc} */ 080 @Override 081 public void execute() throws JmriException { 082 083 ClockState theState = _selectEnum.evaluateEnum(getConditionalNG()); 084 int theValue = _selectValue.evaluateValue(getConditionalNG()); 085 086 jmri.Timebase timebase = InstanceManager.getDefault(jmri.Timebase.class); 087 088 ThreadingUtil.runOnLayoutWithJmriException(() -> { 089 switch(theState) { 090 case SetClock: 091 Calendar cal = Calendar.getInstance(); 092 cal.setTime(timebase.getTime()); 093 cal.set(Calendar.HOUR_OF_DAY, theValue / 60); 094 cal.set(Calendar.MINUTE, theValue % 60); 095 cal.set(Calendar.SECOND, 0); 096 timebase.userSetTime(cal.getTime()); 097 break; 098 099 case StartClock: 100 timebase.setRun(true); 101 break; 102 103 case StopClock: 104 timebase.setRun(false); 105 break; 106 107 default: 108 throw new IllegalArgumentException("Invalid clock state: " + theState.name()); 109 } 110 }); 111 } 112 113 @Override 114 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 115 throw new UnsupportedOperationException("Not supported."); 116 } 117 118 @Override 119 public int getChildCount() { 120 return 0; 121 } 122 123 @Override 124 public String getShortDescription(Locale locale) { 125 return Bundle.getMessage(locale, "ActionClock_Short"); 126 } 127 128 @Override 129 public String getLongDescription(Locale locale) { 130 String value; 131 if (_selectValue.isDirectAddressing()) { 132 value = formatTime(_selectValue.getValue()); 133 } else { 134 value = _selectValue.getDescription(locale); 135 } 136 if (_selectEnum.isDirectAddressing()) { 137 if (_selectEnum.getEnum() == ClockState.SetClock) { 138 return Bundle.getMessage(locale, "ActionClock_LongTime", _selectEnum.getDescription(locale), value); 139 } 140 return Bundle.getMessage(locale, "ActionClock_Long", _selectEnum.getDescription(locale), value); 141 } else { 142 return Bundle.getMessage(locale, "ActionClock_LongTimeIndirect", _selectEnum.getDescription(locale), value); 143 } 144 } 145 146 /** {@inheritDoc} */ 147 @Override 148 public void setup() { 149 // Do nothing 150 } 151 152 /** {@inheritDoc} */ 153 @Override 154 public void registerListenersForThisClass() { 155 _selectEnum.registerListeners(); 156 _selectValue.registerListeners(); 157 } 158 159 /** {@inheritDoc} */ 160 @Override 161 public void unregisterListenersForThisClass() { 162 _selectEnum.unregisterListeners(); 163 _selectValue.unregisterListeners(); 164 } 165 166 /** {@inheritDoc} */ 167 @Override 168 public void propertyChange(PropertyChangeEvent evt) { 169 getConditionalNG().execute(); 170 } 171 172 /** {@inheritDoc} */ 173 @Override 174 public void disposeMe() { 175 } 176 177 178 public enum ClockState { 179 SetClock(Bundle.getMessage("ActionClock_SetClock")), 180 StartClock(Bundle.getMessage("ActionClock_StartClock")), 181 StopClock(Bundle.getMessage("ActionClock_StopClock")); 182 183 private final String _text; 184 185 private ClockState(String text) { 186 this._text = text; 187 } 188 189 @Override 190 public String toString() { 191 return _text; 192 } 193 194 } 195 196 197 private static class TimeFormatterParserValidator 198 implements LogixNG_SelectInteger.FormatterParserValidator { 199 200 @Override 201 public int getInitialValue() { 202 return 0; 203 } 204 205 @Override 206 public String format(int value) { 207 return ActionClock.formatTime(value); 208 } 209 210 @Override 211 public int parse(String str) { 212 int minutes; 213 214 try { 215 minutes = Integer.parseInt(str); 216 if (minutes < 0 || minutes > 1439) { 217 return 0; 218 } 219 return minutes; 220 } catch (NumberFormatException e) { 221 // Do nothing 222 } 223 224 LocalTime newHHMM; 225 try { 226 newHHMM = LocalTime.parse(str.trim(), DateTimeFormatter.ofPattern("H:mm")); 227 minutes = newHHMM.getHour() * 60 + newHHMM.getMinute(); 228 if (minutes < 0 || minutes > 1439) { 229 return 0; 230 } 231 return minutes; 232 } catch (DateTimeParseException ex) { 233 return 0; 234 } 235 } 236 237 @Override 238 public String validate(String str) { 239 int minutes; 240 241 try { 242 minutes = Integer.parseInt(str); 243 if (minutes < 0 || minutes > 1439) { 244 return Bundle.getMessage("ActionClock_RangeError"); 245 } 246 return null; 247 } catch (NumberFormatException e) { 248 // Do nothing 249 } 250 251 LocalTime newHHMM; 252 try { 253 newHHMM = LocalTime.parse(str.trim(), DateTimeFormatter.ofPattern("H:mm")); 254 minutes = newHHMM.getHour() * 60 + newHHMM.getMinute(); 255 if (minutes < 0 || minutes > 1439) { 256 return Bundle.getMessage("ActionClock_RangeError"); 257 } 258 } catch (DateTimeParseException ex) { 259 return Bundle.getMessage("ActionClock_ParseError", ex.getParsedString()); 260 } 261 return null; 262 } 263 264 } 265 266// private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPower.class); 267 268}