001package jmri.jmrit.logixng.actions; 002 003import java.util.*; 004 005import javax.annotation.Nonnull; 006 007import jmri.InstanceManager; 008import jmri.JmriException; 009import jmri.jmrit.Sound; 010import jmri.jmrit.logixng.*; 011import jmri.jmrit.logixng.util.LogixNG_SelectEnum; 012import jmri.jmrit.logixng.util.ReferenceUtil; 013import jmri.jmrit.logixng.util.parser.*; 014import jmri.util.ThreadingUtil; 015import jmri.util.TypeConversionUtil; 016 017/** 018 * Plays a sound. 019 * 020 * @author Daniel Bergqvist Copyright 2021 021 */ 022public class ActionSound extends AbstractDigitalAction { 023 024 private final LogixNG_SelectEnum<Operation> _selectEnum = 025 new LogixNG_SelectEnum<>(this, Operation.values(), Operation.Play); 026 027 private NamedBeanAddressing _soundAddressing = NamedBeanAddressing.Direct; 028 private String _sound = ""; 029 private String _soundReference = ""; 030 private String _soundLocalVariable = ""; 031 private String _soundFormula = ""; 032 private ExpressionNode _soundExpressionNode; 033 034 public ActionSound(String sys, String user) 035 throws BadUserNameException, BadSystemNameException { 036 super(sys, user); 037 } 038 039 @Override 040 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 041 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 042 String sysName = systemNames.get(getSystemName()); 043 String userName = userNames.get(getSystemName()); 044 if (sysName == null) sysName = manager.getAutoSystemName(); 045 ActionSound copy = new ActionSound(sysName, userName); 046 copy.setComment(getComment()); 047 copy.setSound(_sound); 048 _selectEnum.copy(copy._selectEnum); 049 copy.setSoundAddressing(_soundAddressing); 050 copy.setSoundFormula(_soundFormula); 051 copy.setSoundLocalVariable(_soundLocalVariable); 052 copy.setSoundReference(_soundReference); 053 return manager.registerAction(copy); 054 } 055 056 public LogixNG_SelectEnum<Operation> getSelectEnum() { 057 return _selectEnum; 058 } 059 060 public void setSoundAddressing(NamedBeanAddressing addressing) throws ParserException { 061 _soundAddressing = addressing; 062 parseSoundFormula(); 063 } 064 065 public NamedBeanAddressing getSoundAddressing() { 066 return _soundAddressing; 067 } 068 069 public void setSound(String sound) { 070 if (sound == null) _sound = ""; 071 else _sound = sound; 072 } 073 074 public String getSound() { 075 return _sound; 076 } 077 078 public void setSoundReference(@Nonnull String reference) { 079 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 080 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 081 } 082 _soundReference = reference; 083 } 084 085 public String getSoundReference() { 086 return _soundReference; 087 } 088 089 public void setSoundLocalVariable(@Nonnull String localVariable) { 090 _soundLocalVariable = localVariable; 091 } 092 093 public String getSoundLocalVariable() { 094 return _soundLocalVariable; 095 } 096 097 public void setSoundFormula(@Nonnull String formula) throws ParserException { 098 _soundFormula = formula; 099 parseSoundFormula(); 100 } 101 102 public String getSoundFormula() { 103 return _soundFormula; 104 } 105 106 private void parseSoundFormula() throws ParserException { 107 if (_soundAddressing == NamedBeanAddressing.Formula) { 108 Map<String, Variable> variables = new HashMap<>(); 109 110 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 111 _soundExpressionNode = parser.parseExpression(_soundFormula); 112 } else { 113 _soundExpressionNode = null; 114 } 115 } 116 117 /** {@inheritDoc} */ 118 @Override 119 public LogixNG_Category getCategory() { 120 return LogixNG_Category.ITEM; 121 } 122 123 private String getTheSound() throws JmriException { 124 125 switch (_soundAddressing) { 126 case Direct: 127 return _sound; 128 129 case Reference: 130 return ReferenceUtil.getReference(getConditionalNG().getSymbolTable(), _soundReference); 131 132 case LocalVariable: 133 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 134 return TypeConversionUtil 135 .convertToString(symbolTable.getValue(_soundLocalVariable), false); 136 137 case Formula: 138 return _soundExpressionNode != null 139 ? TypeConversionUtil.convertToString( 140 _soundExpressionNode.calculate( 141 getConditionalNG().getSymbolTable()), false) 142 : ""; 143 144 default: 145 throw new IllegalArgumentException("invalid _soundAddressing state: " + _soundAddressing.name()); 146 } 147 } 148 149 /** {@inheritDoc} */ 150 @Override 151 public void execute() throws JmriException { 152 153 Operation operation = _selectEnum.evaluateEnum(getConditionalNG()); 154 String path = getTheSound(); 155 156 ThreadingUtil.runOnLayoutWithJmriException(() -> { 157 switch (operation) { 158 case Play: 159 if (!path.equals("")) { 160 try { 161 new Sound(path).play(true); 162 } catch (NullPointerException ex) { 163 throw new JmriException(Bundle.getMessage("ActionSound_Error_SoundNotFound", path)); 164 } 165 } 166 break; 167 168 default: 169 throw new IllegalArgumentException("invalid operation: " + operation.name()); 170 } 171 }); 172 } 173 174 @Override 175 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 176 throw new UnsupportedOperationException("Not supported."); 177 } 178 179 @Override 180 public int getChildCount() { 181 return 0; 182 } 183 184 @Override 185 public String getShortDescription(Locale locale) { 186 return Bundle.getMessage(locale, "ActionSound_Short"); 187 } 188 189 @Override 190 public String getLongDescription(Locale locale) { 191 String operation = _selectEnum.getDescription(locale); 192 String sound; 193 194 switch (_soundAddressing) { 195 case Direct: 196 sound = Bundle.getMessage(locale, "AddressByDirect", _sound); 197 break; 198 199 case Reference: 200 sound = Bundle.getMessage(locale, "AddressByReference", _soundReference); 201 break; 202 203 case LocalVariable: 204 sound = Bundle.getMessage(locale, "AddressByLocalVariable", _soundLocalVariable); 205 break; 206 207 case Formula: 208 sound = Bundle.getMessage(locale, "AddressByFormula", _soundFormula); 209 break; 210 211 default: 212 throw new IllegalArgumentException("invalid _stateAddressing state: " + _soundAddressing.name()); 213 } 214 215 if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) { 216 if (_selectEnum.getEnum() == Operation.Play) { 217 return Bundle.getMessage(locale, "ActionSound_Long_Play", sound); 218 } else { 219 return Bundle.getMessage(locale, "ActionSound_Long", operation, sound); 220 } 221 } else { 222 return Bundle.getMessage(locale, "ActionSound_LongUnknownOper", operation, sound); 223 } 224 } 225 226 /** {@inheritDoc} */ 227 @Override 228 public void setup() { 229 // Do nothing 230 } 231 232 /** {@inheritDoc} */ 233 @Override 234 public void firePropertyChange(String p, Object old, Object n) { 235 super.firePropertyChange(p, old, n); 236 } 237 238 /** {@inheritDoc} */ 239 @Override 240 public void disposeMe() { 241 // Do nothing 242 } 243 244 245 public enum Operation { 246 Play(Bundle.getMessage("ActionSound_Operation_Play")); 247 248 private final String _text; 249 250 private Operation(String text) { 251 this._text = text; 252 } 253 254 @Override 255 public String toString() { 256 return _text; 257 } 258 259 } 260 261// private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionSound.class); 262 263}