001package jmri.jmrit.display.logixng; 002 003import java.beans.VetoableChangeListener; 004import java.util.*; 005 006import javax.annotation.CheckForNull; 007import javax.annotation.Nonnull; 008 009import jmri.*; 010import jmri.jmrit.display.Editor; 011import jmri.jmrit.display.AudioIcon; 012import jmri.jmrit.display.Positionable; 013import jmri.jmrit.logixng.*; 014import jmri.jmrit.logixng.actions.AbstractDigitalAction; 015import jmri.jmrit.logixng.util.ReferenceUtil; 016import jmri.jmrit.logixng.util.parser.*; 017import jmri.jmrit.logixng.util.parser.ExpressionNode; 018import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 019import jmri.util.ThreadingUtil; 020import jmri.util.TypeConversionUtil; 021 022/** 023 * This action controls various things of a AudioIcon on a panel. 024 * 025 * @author Daniel Bergqvist Copyright 2023 026 */ 027public class ActionAudioIcon extends AbstractDigitalAction implements VetoableChangeListener { 028 029 private String _editorName; 030 private Editor _editor; 031 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 032 private String _positionableName; 033 private AudioIcon _audioIcon; 034 private String _reference = ""; 035 private String _localVariable = ""; 036 private String _formula = ""; 037 private ExpressionNode _expressionNode; 038 private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct; 039 private Operation _operation = Operation.Play; 040 private String _stateReference = ""; 041 private String _stateLocalVariable = ""; 042 private String _stateFormula = ""; 043 private ExpressionNode _stateExpressionNode; 044 045 public ActionAudioIcon(String sys, String user) 046 throws BadUserNameException, BadSystemNameException { 047 super(sys, user, CategoryDisplay.DISPLAY); 048 } 049 050 @Override 051 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 052 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 053 String sysName = systemNames.get(getSystemName()); 054 String userName = userNames.get(getSystemName()); 055 if (sysName == null) sysName = manager.getAutoSystemName(); 056 ActionAudioIcon copy = new ActionAudioIcon(sysName, userName); 057 copy.setComment(getComment()); 058 copy.setEditor(_editorName); 059 copy.setAudioIcon(_positionableName); 060 copy.setOperation(_operation); 061 copy.setAddressing(_addressing); 062 copy.setFormula(_formula); 063 copy.setLocalVariable(_localVariable); 064 copy.setReference(_reference); 065 copy.setStateAddressing(_stateAddressing); 066 copy.setStateFormula(_stateFormula); 067 copy.setStateLocalVariable(_stateLocalVariable); 068 copy.setStateReference(_stateReference); 069 return manager.registerAction(copy); 070 } 071 072 public void setEditor(@CheckForNull String editorName) { 073 assertListenersAreNotRegistered(log, "setEditor"); 074 _editorName = editorName; 075 if (editorName != null) { 076 _editor = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager.class).getByName(editorName); 077 } else { 078 _editor = null; 079 } 080 } 081 082 public String getEditorName() { 083 return _editorName; 084 } 085 086 public void setAudioIcon(@CheckForNull String positionableName) { 087 assertListenersAreNotRegistered(log, "setAudioIcon"); 088 _positionableName = positionableName; 089 if ((positionableName != null) && (_editor != null)) { 090 Positionable pos = _editor.getIdContents().get(_positionableName); 091 if (pos instanceof AudioIcon) { 092 _audioIcon = (AudioIcon)pos; 093 } else { 094 throw new IllegalArgumentException("positionableName is not an AudioIcon"); 095 } 096 } else { 097 _audioIcon = null; 098 } 099 } 100 101 public String getAudioIconName() { 102 return _positionableName; 103 } 104 105 public void setAddressing(NamedBeanAddressing addressing) throws ParserException { 106 _addressing = addressing; 107 parseFormula(); 108 } 109 110 public NamedBeanAddressing getAddressing() { 111 return _addressing; 112 } 113 114 public void setReference(@Nonnull String reference) { 115 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 116 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 117 } 118 _reference = reference; 119 } 120 121 public String getReference() { 122 return _reference; 123 } 124 125 public void setLocalVariable(@Nonnull String localVariable) { 126 _localVariable = localVariable; 127 } 128 129 public String getLocalVariable() { 130 return _localVariable; 131 } 132 133 public void setFormula(@Nonnull String formula) throws ParserException { 134 _formula = formula; 135 parseFormula(); 136 } 137 138 public String getFormula() { 139 return _formula; 140 } 141 142 private void parseFormula() throws ParserException { 143 if (_addressing == NamedBeanAddressing.Formula) { 144 Map<String, Variable> variables = new HashMap<>(); 145 146 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 147 _expressionNode = parser.parseExpression(_formula); 148 } else { 149 _expressionNode = null; 150 } 151 } 152 153 public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException { 154 _stateAddressing = addressing; 155 parseStateFormula(); 156 } 157 158 public NamedBeanAddressing getStateAddressing() { 159 return _stateAddressing; 160 } 161 162 public void setOperation(Operation isControlling) { 163 _operation = isControlling; 164 } 165 166 public Operation getOperation() { 167 return _operation; 168 } 169 170 public void setStateReference(@Nonnull String reference) { 171 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 172 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 173 } 174 _stateReference = reference; 175 } 176 177 public String getStateReference() { 178 return _stateReference; 179 } 180 181 public void setStateLocalVariable(@Nonnull String localVariable) { 182 _stateLocalVariable = localVariable; 183 } 184 185 public String getStateLocalVariable() { 186 return _stateLocalVariable; 187 } 188 189 public void setStateFormula(@Nonnull String formula) throws ParserException { 190 _stateFormula = formula; 191 parseStateFormula(); 192 } 193 194 public String getStateFormula() { 195 return _stateFormula; 196 } 197 198 private void parseStateFormula() throws ParserException { 199 if (_stateAddressing == NamedBeanAddressing.Formula) { 200 Map<String, Variable> variables = new HashMap<>(); 201 202 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 203 _stateExpressionNode = parser.parseExpression(_stateFormula); 204 } else { 205 _stateExpressionNode = null; 206 } 207 } 208 209 @Override 210 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 211/* 212 if ("CanDelete".equals(evt.getPropertyName())) { // No I18N 213 if (evt.getOldValue() instanceof Turnout) { 214 if (evt.getOldValue().equals(getTurnout().getBean())) { 215 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 216 throw new PropertyVetoException(Bundle.getMessage("Turnout_TurnoutInUseTurnoutExpressionVeto", getDisplayName()), e); // NOI18N 217 } 218 } 219 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 220 if (evt.getOldValue() instanceof Turnout) { 221 if (evt.getOldValue().equals(getTurnout().getBean())) { 222 removeTurnout(); 223 } 224 } 225 } 226*/ 227 } 228 229 private String getNewState() throws JmriException { 230 231 switch (_stateAddressing) { 232 case Reference: 233 return ReferenceUtil.getReference( 234 getConditionalNG().getSymbolTable(), _stateReference); 235 236 case LocalVariable: 237 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 238 return TypeConversionUtil 239 .convertToString(symbolTable.getValue(_stateLocalVariable), false); 240 241 case Formula: 242 return _stateExpressionNode != null 243 ? TypeConversionUtil.convertToString( 244 _stateExpressionNode.calculate( 245 getConditionalNG().getSymbolTable()), false) 246 : null; 247 248 default: 249 throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name()); 250 } 251 } 252 253 /** {@inheritDoc} */ 254 @Override 255 public void execute() throws JmriException { 256 Positionable positionable; 257 258 switch (_addressing) { 259 case Direct: 260 positionable = this._audioIcon; 261 break; 262 263 case Reference: 264 String ref = ReferenceUtil.getReference( 265 getConditionalNG().getSymbolTable(), _reference); 266 positionable = _editor.getIdContents().get(ref); 267 break; 268 269 case LocalVariable: 270 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 271 positionable = _editor.getIdContents().get(TypeConversionUtil 272 .convertToString(symbolTable.getValue(_localVariable), false)); 273 break; 274 275 case Formula: 276 positionable = _expressionNode != null ? 277 _editor.getIdContents().get(TypeConversionUtil 278 .convertToString(_expressionNode.calculate( 279 getConditionalNG().getSymbolTable()), false)) 280 : null; 281 break; 282 283 default: 284 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 285 } 286 287 if (positionable == null) { 288 log.error("positionable is null"); 289 return; 290 } 291 292 AudioIcon audioIcon; 293 if (positionable instanceof AudioIcon) { 294 audioIcon = (AudioIcon)positionable; 295 } else { 296 throw new IllegalArgumentException("positionableName is not an AudioIcon"); 297 } 298 299 300 String name = (_stateAddressing != NamedBeanAddressing.Direct) 301 ? getNewState() : null; 302 303 Operation operation; 304 if ((_stateAddressing == NamedBeanAddressing.Direct)) { 305 operation = _operation; 306 } else { 307 operation = Operation.valueOf(name); 308 } 309 310 ThreadingUtil.runOnLayout(() -> { 311 switch (operation) { 312 case Play: 313 audioIcon.play(); 314 break; 315 case Stop: 316 audioIcon.stop(); 317 break; 318 default: 319 throw new RuntimeException("operation has invalid value: "+operation.name()); 320 } 321 }); 322 } 323 324 @Override 325 public String getShortDescription(Locale locale) { 326 return Bundle.getMessage(locale, "ActionAudioIcon_Short"); 327 } 328 329 @Override 330 public String getLongDescription(Locale locale) { 331 String editorName = _editorName != null ? _editorName : Bundle.getMessage(locale, "BeanNotSelected"); 332 String positonableName; 333 String state; 334 335 switch (_addressing) { 336 case Direct: 337 String positionableName; 338 if (this._positionableName != null) { 339 positionableName = this._positionableName; 340 } else { 341 positionableName = Bundle.getMessage(locale, "BeanNotSelected"); 342 } 343 positonableName = Bundle.getMessage(locale, "AddressByDirect", positionableName); 344 break; 345 346 case Reference: 347 positonableName = Bundle.getMessage(locale, "AddressByReference", _reference); 348 break; 349 350 case LocalVariable: 351 positonableName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 352 break; 353 354 case Formula: 355 positonableName = Bundle.getMessage(locale, "AddressByFormula", _formula); 356 break; 357 358 default: 359 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 360 } 361 362 switch (_stateAddressing) { 363 case Direct: 364 state = Bundle.getMessage(locale, "AddressByDirect", _operation._text); 365 break; 366 367 case Reference: 368 state = Bundle.getMessage(locale, "AddressByReference", _stateReference); 369 break; 370 371 case LocalVariable: 372 state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable); 373 break; 374 375 case Formula: 376 state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula); 377 break; 378 379 default: 380 throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name()); 381 } 382 383 return Bundle.getMessage(locale, "ActionAudioIcon_Long", editorName, positonableName, state); 384 } 385 386 /** {@inheritDoc} */ 387 @Override 388 public void setup() { 389 if ((_editorName != null) && (_editor == null)) { 390 setEditor(_editorName); 391 } 392 if ((_positionableName != null) && (_audioIcon == null)) { 393 setAudioIcon(_positionableName); 394 } 395 } 396 397 public enum Operation { 398 Play(Bundle.getMessage("ActionAudioIcon_Play")), 399 Stop(Bundle.getMessage("ActionAudioIcon_Stop")); 400 401 private final String _text; 402 403 private Operation(String text) { 404 this._text = text; 405 } 406 407 @Override 408 public String toString() { 409 return _text; 410 } 411 412 } 413 414 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionAudioIcon.class); 415 416}