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.logixng.*; 015import jmri.jmrit.logixng.actions.AbstractDigitalAction; 016import jmri.jmrit.logixng.util.ReferenceUtil; 017import jmri.jmrit.logixng.util.parser.*; 018import jmri.jmrit.logixng.util.parser.ExpressionNode; 019import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 020import jmri.util.ThreadingUtil; 021import jmri.util.TypeConversionUtil; 022 023/** 024 * This action controls various things of Positionables with a particular 025 * class name on a panel. 026 * 027 * @author Daniel Bergqvist Copyright 2023 028 */ 029public class ActionPositionableByClass 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 _className; 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 ActionPositionableByClass(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 ActionPositionableByClass copy = new ActionPositionableByClass(sysName, userName); 060 copy.setComment(getComment()); 061 copy.setEditor(_editorName); 062 copy.setClassName(_className); 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 setClassName(@CheckForNull String className) { 091 assertListenersAreNotRegistered(log, "setPositionable"); 092 _className = className; 093// InstanceManager.turnoutManagerInstance().addVetoableChangeListener(this); 094 } 095 096 public String getClassName() { 097 return _className; 098 } 099 100 public void setAddressing(NamedBeanAddressing addressing) throws ParserException { 101 _addressing = addressing; 102 parseFormula(); 103 } 104 105 public NamedBeanAddressing getAddressing() { 106 return _addressing; 107 } 108 109 public void setReference(@Nonnull String reference) { 110 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 111 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 112 } 113 _reference = reference; 114 } 115 116 public String getReference() { 117 return _reference; 118 } 119 120 public void setLocalVariable(@Nonnull String localVariable) { 121 _localVariable = localVariable; 122 } 123 124 public String getLocalVariable() { 125 return _localVariable; 126 } 127 128 public void setFormula(@Nonnull String formula) throws ParserException { 129 _formula = formula; 130 parseFormula(); 131 } 132 133 public String getFormula() { 134 return _formula; 135 } 136 137 private void parseFormula() throws ParserException { 138 if (_addressing == NamedBeanAddressing.Formula) { 139 Map<String, Variable> variables = new HashMap<>(); 140 141 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 142 _expressionNode = parser.parseExpression(_formula); 143 } else { 144 _expressionNode = null; 145 } 146 } 147 148 public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException { 149 _stateAddressing = addressing; 150 parseStateFormula(); 151 } 152 153 public NamedBeanAddressing getStateAddressing() { 154 return _stateAddressing; 155 } 156 157 public void setOperation(Operation isControlling) { 158 _operation = isControlling; 159 } 160 161 public Operation getOperation() { 162 return _operation; 163 } 164 165 public void setStateReference(@Nonnull String reference) { 166 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 167 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 168 } 169 _stateReference = reference; 170 } 171 172 public String getStateReference() { 173 return _stateReference; 174 } 175 176 public void setStateLocalVariable(@Nonnull String localVariable) { 177 _stateLocalVariable = localVariable; 178 } 179 180 public String getStateLocalVariable() { 181 return _stateLocalVariable; 182 } 183 184 public void setStateFormula(@Nonnull String formula) throws ParserException { 185 _stateFormula = formula; 186 parseStateFormula(); 187 } 188 189 public String getStateFormula() { 190 return _stateFormula; 191 } 192 193 private void parseStateFormula() throws ParserException { 194 if (_stateAddressing == NamedBeanAddressing.Formula) { 195 Map<String, Variable> variables = new HashMap<>(); 196 197 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 198 _stateExpressionNode = parser.parseExpression(_stateFormula); 199 } else { 200 _stateExpressionNode = null; 201 } 202 } 203 204 @Override 205 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 206/* 207 if ("CanDelete".equals(evt.getPropertyName())) { // No I18N 208 if (evt.getOldValue() instanceof Turnout) { 209 if (evt.getOldValue().equals(getTurnout().getBean())) { 210 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 211 throw new PropertyVetoException(Bundle.getMessage("Turnout_TurnoutInUseTurnoutExpressionVeto", getDisplayName()), e); // NOI18N 212 } 213 } 214 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 215 if (evt.getOldValue() instanceof Turnout) { 216 if (evt.getOldValue().equals(getTurnout().getBean())) { 217 removeTurnout(); 218 } 219 } 220 } 221*/ 222 } 223 224 /** {@inheritDoc} */ 225 @Override 226 public Category getCategory() { 227 return CategoryDisplay.DISPLAY; 228 } 229 230 private String getNewState() throws JmriException { 231 232 switch (_stateAddressing) { 233 case Reference: 234 return ReferenceUtil.getReference( 235 getConditionalNG().getSymbolTable(), _stateReference); 236 237 case LocalVariable: 238 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 239 return TypeConversionUtil 240 .convertToString(symbolTable.getValue(_stateLocalVariable), false); 241 242 case Formula: 243 return _stateExpressionNode != null 244 ? TypeConversionUtil.convertToString( 245 _stateExpressionNode.calculate( 246 getConditionalNG().getSymbolTable()), false) 247 : null; 248 249 default: 250 throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name()); 251 } 252 } 253 254 /** {@inheritDoc} */ 255 @Override 256 public void execute() throws JmriException { 257 String className; 258 259// System.out.format("ActionPositionableByClass.execute: %s%n", getLongDescription()); 260 261 switch (_addressing) { 262 case Direct: 263 className = _className; 264 break; 265 266 case Reference: 267 className = ReferenceUtil.getReference( 268 getConditionalNG().getSymbolTable(), _reference); 269 break; 270 271 case LocalVariable: 272 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 273 className = TypeConversionUtil.convertToString( 274 symbolTable.getValue(_localVariable), false); 275 break; 276 277 case Formula: 278 className = _expressionNode != null ? 279 TypeConversionUtil.convertToString(_expressionNode.calculate( 280 getConditionalNG().getSymbolTable()), false) 281 : null; 282 break; 283 284 default: 285 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 286 } 287 288// System.out.format("ActionPositionableByClass.execute: positionable: %s%n", positionable); 289 290 if (className == null) { 291 log.error("className is null"); 292 return; 293 } 294 295 Set<Positionable> positionableSet = _editor.getPositionablesByClassName(className); 296 297 if (positionableSet == null) { 298 var lng = getConditionalNG(); 299 var cng = getConditionalNG(); 300 var m = getModule(); 301 String errorMessage; 302 if (m != null) { 303 errorMessage = Bundle.getMessage( 304 "ActionPositionableByClass_ErrorNoPositionables_Module", 305 getLongDescription(), m.getDisplayName(), getSystemName()); 306 } else { 307 errorMessage = Bundle.getMessage( 308 "ActionPositionableByClass_ErrorNoPositionables_LogixNG", 309 getLongDescription(), lng.getDisplayName(), cng.getDisplayName(), getSystemName()); 310 } 311 List<String> list = Arrays.asList(errorMessage.split("\n")); 312 throw new JmriException(Bundle.getMessage("ActionPositionableByClass_ErrorNoPositionables"), list); 313 } 314 315 String name = (_stateAddressing != NamedBeanAddressing.Direct) 316 ? getNewState() : null; 317 318 Operation operation; 319 if ((_stateAddressing == NamedBeanAddressing.Direct)) { 320 operation = _operation; 321 } else { 322 operation = Operation.valueOf(name); 323 } 324 325 ThreadingUtil.runOnGUI(() -> { 326 for (Positionable p : positionableSet) { 327 switch (operation) { 328 case Disable: 329 p.setControlling(false); 330 break; 331 case Enable: 332 p.setControlling(true); 333 break; 334 case Hide: 335 p.setHidden(true); 336 break; 337 case Show: 338 p.setHidden(false); 339 break; 340 default: 341 throw new RuntimeException("operation has invalid value: "+operation.name()); 342 } 343 } 344 }); 345 } 346 347 @Override 348 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 349 throw new UnsupportedOperationException("Not supported."); 350 } 351 352 @Override 353 public int getChildCount() { 354 return 0; 355 } 356 357 @Override 358 public String getShortDescription(Locale locale) { 359 return Bundle.getMessage(locale, "ActionPositionableByClass_Short"); 360 } 361 362 @Override 363 public String getLongDescription(Locale locale) { 364 String editorName = _editorName != null ? _editorName : Bundle.getMessage(locale, "BeanNotSelected"); 365 String positonableName; 366 String state; 367 368 switch (_addressing) { 369 case Direct: 370 String className; 371 if (this._className != null) { 372 className = this._className; 373 } else { 374 className = Bundle.getMessage(locale, "BeanNotSelected"); 375 } 376 positonableName = Bundle.getMessage(locale, "AddressByDirect", className); 377 break; 378 379 case Reference: 380 positonableName = Bundle.getMessage(locale, "AddressByReference", _reference); 381 break; 382 383 case LocalVariable: 384 positonableName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 385 break; 386 387 case Formula: 388 positonableName = Bundle.getMessage(locale, "AddressByFormula", _formula); 389 break; 390 391 default: 392 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 393 } 394 395 switch (_stateAddressing) { 396 case Direct: 397 state = Bundle.getMessage(locale, "AddressByDirect", _operation._text); 398 break; 399 400 case Reference: 401 state = Bundle.getMessage(locale, "AddressByReference", _stateReference); 402 break; 403 404 case LocalVariable: 405 state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable); 406 break; 407 408 case Formula: 409 state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula); 410 break; 411 412 default: 413 throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name()); 414 } 415 416 return Bundle.getMessage(locale, "ActionPositionableByClass_Long", editorName, positonableName, state); 417 } 418 419 /** {@inheritDoc} */ 420 @Override 421 public void setup() { 422 if ((_editorName != null) && (_editor == null)) { 423 setEditor(_editorName); 424 } 425 } 426 427 /** {@inheritDoc} */ 428 @Override 429 public void registerListenersForThisClass() { 430 } 431 432 /** {@inheritDoc} */ 433 @Override 434 public void unregisterListenersForThisClass() { 435 } 436 437 /** {@inheritDoc} */ 438 @Override 439 public void disposeMe() { 440 } 441 442 443 public enum Operation { 444 Disable(Bundle.getMessage("ActionPositionableByClass_Disable")), 445 Enable(Bundle.getMessage("ActionPositionableByClass_Enable")), 446 Hide(Bundle.getMessage("ActionPositionableByClass_Hide")), 447 Show(Bundle.getMessage("ActionPositionableByClass_Show")); 448 449 private final String _text; 450 451 private Operation(String text) { 452 this._text = text; 453 } 454 455 @Override 456 public String toString() { 457 return _text; 458 } 459 460 } 461 462 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionPositionableByClass.class); 463 464}