001package jmri.jmrit.display.logixng; 002 003// import java.beans.PropertyChangeEvent; 004// import java.beans.PropertyVetoException; 005import java.beans.VetoableChangeListener; 006import java.util.*; 007 008import javax.annotation.CheckForNull; 009import javax.annotation.Nonnull; 010 011import jmri.*; 012import jmri.jmrit.display.Editor; 013import jmri.jmrit.display.Positionable; 014import jmri.jmrit.display.layoutEditor.LayoutEditor; 015import jmri.jmrit.logixng.*; 016import jmri.jmrit.logixng.actions.AbstractDigitalAction; 017import jmri.jmrit.logixng.util.ReferenceUtil; 018import jmri.jmrit.logixng.util.parser.*; 019import jmri.jmrit.logixng.util.parser.ExpressionNode; 020import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 021import jmri.util.ThreadingUtil; 022import jmri.util.TypeConversionUtil; 023 024/** 025 * This action controls various things of a Positionable on a panel. 026 * 027 * @author Daniel Bergqvist Copyright 2021 028 */ 029public class ActionPositionable extends AbstractDigitalAction implements VetoableChangeListener { 030 031 private String _editorName; 032 private Editor _editor; 033 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 034// private NamedBeanHandle<Turnout> _turnoutHandle; 035 private String _positionableName; 036 private Positionable _positionable; 037 private String _reference = ""; 038 private String _localVariable = ""; 039 private String _formula = ""; 040 private ExpressionNode _expressionNode; 041 private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct; 042 private Operation _operation = Operation.Enable; 043 private String _stateReference = ""; 044 private String _stateLocalVariable = ""; 045 private String _stateFormula = ""; 046 private ExpressionNode _stateExpressionNode; 047 048 public ActionPositionable(String sys, String user) 049 throws BadUserNameException, BadSystemNameException { 050 super(sys, user); 051 } 052 053 @Override 054 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 055 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 056 String sysName = systemNames.get(getSystemName()); 057 String userName = userNames.get(getSystemName()); 058 if (sysName == null) sysName = manager.getAutoSystemName(); 059 ActionPositionable copy = new ActionPositionable(sysName, userName); 060 copy.setComment(getComment()); 061 copy.setEditor(_editorName); 062 copy.setPositionable(_positionableName); 063 copy.setOperation(_operation); 064 copy.setAddressing(_addressing); 065 copy.setFormula(_formula); 066 copy.setLocalVariable(_localVariable); 067 copy.setReference(_reference); 068 copy.setStateAddressing(_stateAddressing); 069 copy.setStateFormula(_stateFormula); 070 copy.setStateLocalVariable(_stateLocalVariable); 071 copy.setStateReference(_stateReference); 072 return manager.registerAction(copy); 073 } 074 075 public void setEditor(@CheckForNull String editorName) { 076 assertListenersAreNotRegistered(log, "setEditor"); 077 _editorName = editorName; 078 if (editorName != null) { 079 _editor = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager.class).getByName(editorName); 080 } else { 081 _editor = null; 082 } 083// InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this); 084 } 085 086 public String getEditorName() { 087 return _editorName; 088 } 089 090 public void setPositionable(@CheckForNull String positionableName) { 091 assertListenersAreNotRegistered(log, "setPositionable"); 092 _positionableName = positionableName; 093 if ((positionableName != null) && (_editor != null)) { 094 _positionable = _editor.getIdContents().get(_positionableName); 095 } else { 096 _positionable = null; 097 } 098// InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this); 099 } 100 101 public String getPositionableName() { 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 /** {@inheritDoc} */ 230 @Override 231 public LogixNG_Category getCategory() { 232 return CategoryDisplay.DISPLAY; 233 } 234 235 private String getNewState() throws JmriException { 236 237 switch (_stateAddressing) { 238 case Reference: 239 return ReferenceUtil.getReference( 240 getConditionalNG().getSymbolTable(), _stateReference); 241 242 case LocalVariable: 243 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 244 return TypeConversionUtil 245 .convertToString(symbolTable.getValue(_stateLocalVariable), false); 246 247 case Formula: 248 return _stateExpressionNode != null 249 ? TypeConversionUtil.convertToString( 250 _stateExpressionNode.calculate( 251 getConditionalNG().getSymbolTable()), false) 252 : null; 253 254 default: 255 throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name()); 256 } 257 } 258 259 private void throwErrorPositionableDoesNotExists() throws JmriException { 260 var lng = getConditionalNG(); 261 var cng = getConditionalNG(); 262 var m = getModule(); 263 String errorMessage; 264 if (m != null) { 265 errorMessage = Bundle.getMessage( 266 "ActionPositionable_ErrorNoPositionable_Module", 267 getLongDescription(), m.getDisplayName(), getSystemName()); 268 } else { 269 errorMessage = Bundle.getMessage( 270 "ActionPositionable_ErrorNoPositionable_LogixNG", 271 getLongDescription(), lng.getDisplayName(), cng.getDisplayName(), getSystemName()); 272 } 273 List<String> list = Arrays.asList(errorMessage.split("\n")); 274 throw new JmriException(Bundle.getMessage("ActionPositionable_ErrorNoPositionable"), list); 275 } 276 277 /** {@inheritDoc} */ 278 @Override 279 public void execute() throws JmriException { 280 Positionable positionable; 281 282// System.out.format("ActionPositionable.execute: %s%n", getLongDescription()); 283 284 switch (_addressing) { 285 case Direct: 286 positionable = this._positionable; 287 if (positionable == null && (_positionableName != null && !_positionableName.isBlank())) { 288 throwErrorPositionableDoesNotExists(); 289 } 290 break; 291 292 case Reference: 293 String ref = ReferenceUtil.getReference( 294 getConditionalNG().getSymbolTable(), _reference); 295 positionable = _editor.getIdContents().get(ref); 296 break; 297 298 case LocalVariable: 299 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 300 positionable = _editor.getIdContents().get(TypeConversionUtil 301 .convertToString(symbolTable.getValue(_localVariable), false)); 302 break; 303 304 case Formula: 305 positionable = _expressionNode != null ? 306 _editor.getIdContents().get(TypeConversionUtil 307 .convertToString(_expressionNode.calculate( 308 getConditionalNG().getSymbolTable()), false)) 309 : null; 310 break; 311 312 default: 313 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 314 } 315 316// System.out.format("ActionPositionable.execute: positionable: %s%n", positionable); 317 318 if (positionable == null) { 319 log.error("positionable is null"); 320 return; 321 } 322 323 String name = (_stateAddressing != NamedBeanAddressing.Direct) 324 ? getNewState() : null; 325 326 Operation operation; 327 if ((_stateAddressing == NamedBeanAddressing.Direct)) { 328 operation = _operation; 329 } else { 330 operation = Operation.valueOf(name); 331 } 332 333 ThreadingUtil.runOnGUI(() -> { 334 switch (operation) { 335 case Disable: 336 positionable.setControlling(false); 337 break; 338 case Enable: 339 positionable.setControlling(true); 340 break; 341 case Hide: 342 positionable.setHidden(true); 343 break; 344 case Show: 345 positionable.setHidden(false); 346 break; 347 default: 348 throw new RuntimeException("operation has invalid value: "+operation.name()); 349 } 350 if (_editor instanceof LayoutEditor) { 351 // Insure that the panel has been updated 352 ((LayoutEditor) _editor).redrawPanel(); 353 } 354 }); 355 } 356 357 @Override 358 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 359 throw new UnsupportedOperationException("Not supported."); 360 } 361 362 @Override 363 public int getChildCount() { 364 return 0; 365 } 366 367 @Override 368 public String getShortDescription(Locale locale) { 369 return Bundle.getMessage(locale, "ActionPositionable_Short"); 370 } 371 372 @Override 373 public String getLongDescription(Locale locale) { 374 String editorName = _editorName != null ? _editorName : Bundle.getMessage(locale, "BeanNotSelected"); 375 String positonableName; 376 String state; 377 378 switch (_addressing) { 379 case Direct: 380 String positionableName; 381 if (this._positionableName != null) { 382 positionableName = this._positionableName; 383 } else { 384 positionableName = Bundle.getMessage(locale, "BeanNotSelected"); 385 } 386 positonableName = Bundle.getMessage(locale, "AddressByDirect", positionableName); 387 break; 388 389 case Reference: 390 positonableName = Bundle.getMessage(locale, "AddressByReference", _reference); 391 break; 392 393 case LocalVariable: 394 positonableName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 395 break; 396 397 case Formula: 398 positonableName = Bundle.getMessage(locale, "AddressByFormula", _formula); 399 break; 400 401 default: 402 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 403 } 404 405 switch (_stateAddressing) { 406 case Direct: 407 state = Bundle.getMessage(locale, "AddressByDirect", _operation._text); 408 break; 409 410 case Reference: 411 state = Bundle.getMessage(locale, "AddressByReference", _stateReference); 412 break; 413 414 case LocalVariable: 415 state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable); 416 break; 417 418 case Formula: 419 state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula); 420 break; 421 422 default: 423 throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name()); 424 } 425 426 return Bundle.getMessage(locale, "ActionPositionable_Long", editorName, positonableName, state); 427 } 428 429 /** {@inheritDoc} */ 430 @Override 431 public void setup() { 432 if ((_editorName != null) && (_editor == null)) { 433 setEditor(_editorName); 434 } 435 if ((_positionableName != null) && (_positionable == null)) { 436 setPositionable(_positionableName); 437 } 438 } 439 440 /** {@inheritDoc} */ 441 @Override 442 public void registerListenersForThisClass() { 443 } 444 445 /** {@inheritDoc} */ 446 @Override 447 public void unregisterListenersForThisClass() { 448 } 449 450 /** {@inheritDoc} */ 451 @Override 452 public void disposeMe() { 453 } 454 455 456 public enum Operation { 457 Disable(Bundle.getMessage("ActionPositionable_Disable")), 458 Enable(Bundle.getMessage("ActionPositionable_Enable")), 459 Hide(Bundle.getMessage("ActionPositionable_Hide")), 460 Show(Bundle.getMessage("ActionPositionable_Show")); 461 462 private final String _text; 463 464 private Operation(String text) { 465 this._text = text; 466 } 467 468 @Override 469 public String toString() { 470 return _text; 471 } 472 473 } 474 475 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPositionable.class); 476 477}