001package jmri.jmrit.logixng.util; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.beans.PropertyVetoException; 006import java.beans.VetoableChangeListener; 007import java.util.HashMap; 008import java.util.Locale; 009import java.util.Map; 010 011import javax.annotation.Nonnull; 012 013import jmri.*; 014import jmri.jmrit.logixng.*; 015import jmri.jmrit.logixng.implementation.AbstractBase; 016import jmri.jmrit.logixng.util.parser.*; 017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 018import jmri.util.TypeConversionUtil; 019 020/** 021 * Select an combo box value for LogixNG actions and expressions. 022 * 023 * @author Daniel Bergqvist (C) 2024 024 */ 025public class LogixNG_SelectComboBox implements VetoableChangeListener { 026 027 private final AbstractBase _base; 028 private final InUse _inUse; 029 private String[] _valuesArray; 030 private final LogixNG_SelectTable _selectTable; 031 private final PropertyChangeListener _listener; 032 private boolean _listenToMemory; 033 private boolean _listenersAreRegistered; 034 035 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 036 private String _value; 037 private String _reference = ""; 038 private NamedBeanHandle<Memory> _memoryHandle; 039 private String _localVariable = ""; 040 private String _formula = ""; 041 private ExpressionNode _expressionNode; 042 043 044 public LogixNG_SelectComboBox(AbstractBase base, String[] valuesArray, 045 String initialValue, PropertyChangeListener listener) { 046 _base = base; 047 _inUse = () -> true; 048 _valuesArray = valuesArray; 049 _value = initialValue; 050 _selectTable = new LogixNG_SelectTable(_base, _inUse); 051 _listener = listener; 052 } 053 054 055 public void copy(LogixNG_SelectComboBox copy) throws ParserException { 056 copy.setAddressing(_addressing); 057 copy.setValue(_value); 058 copy.setLocalVariable(_localVariable); 059 copy.setReference(_reference); 060 copy.setMemory(_memoryHandle); 061 copy.setListenToMemory(_listenToMemory); 062 copy.setFormula(_formula); 063 _selectTable.copy(copy._selectTable); 064 } 065 066 public void setValues(String[] valuesArray) { 067 _valuesArray = valuesArray; 068 069 // Ensure the selected value is in the array 070 boolean found = false; 071 for (String value : _valuesArray) { 072 if (value.equals(this._value)) { 073 found = true; 074 } 075 } 076 if (!found) { 077 _value = _valuesArray[0]; 078 } 079 } 080 081 public String[] getValues() { 082 return _valuesArray; 083 } 084 085 public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException { 086 this._addressing = addressing; 087 parseFormula(); 088 } 089 090 public boolean isDirectAddressing() { 091 return _addressing == NamedBeanAddressing.Direct; 092 } 093 094 public NamedBeanAddressing getAddressing() { 095 return _addressing; 096 } 097 098 public void setValue(@Nonnull String value) { 099 _base.assertListenersAreNotRegistered(log, "setString"); 100 _value = value; 101 } 102 103 public String getValue() { 104 return _value; 105 } 106 107 public String getValue(String name) { 108 for (String value : _valuesArray) { 109 if (value.equals(name)) return value; 110 } 111 return null; 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 setMemory(@Nonnull String memoryName) { 126 Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName); 127 if (memory != null) { 128 setMemory(memory); 129 } else { 130 removeMemory(); 131 log.warn("memory \"{}\" is not found", memoryName); 132 } 133 } 134 135 public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) { 136 _memoryHandle = handle; 137 InstanceManager.memoryManagerInstance().addVetoableChangeListener(this); 138 addRemoveVetoListener(); 139 } 140 141 public void setMemory(@Nonnull Memory memory) { 142 setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class) 143 .getNamedBeanHandle(memory.getDisplayName(), memory)); 144 } 145 146 public void removeMemory() { 147 if (_memoryHandle != null) { 148 _memoryHandle = null; 149 addRemoveVetoListener(); 150 } 151 } 152 153 public NamedBeanHandle<Memory> getMemory() { 154 return _memoryHandle; 155 } 156 157 public void setListenToMemory(boolean listenToMemory) { 158 _listenToMemory = listenToMemory; 159 } 160 161 public boolean getListenToMemory() { 162 return _listenToMemory; 163 } 164 165 public void setLocalVariable(@Nonnull String localVariable) { 166 _localVariable = localVariable; 167 } 168 169 public String getLocalVariable() { 170 return _localVariable; 171 } 172 173 public void setFormula(@Nonnull String formula) throws ParserException { 174 _formula = formula; 175 parseFormula(); 176 } 177 178 public String getFormula() { 179 return _formula; 180 } 181 182 private void parseFormula() throws ParserException { 183 if (_addressing == NamedBeanAddressing.Formula) { 184 Map<String, Variable> variables = new HashMap<>(); 185 186 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 187 _expressionNode = parser.parseExpression(_formula); 188 } else { 189 _expressionNode = null; 190 } 191 } 192 193 public LogixNG_SelectTable getSelectTable() { 194 return _selectTable; 195 } 196 197 private void addRemoveVetoListener() { 198 if (_memoryHandle != null) { 199 InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this); 200 } else { 201 InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this); 202 } 203 } 204 205 public String evaluateValue(ConditionalNG conditionalNG) throws JmriException { 206 207 if (_addressing == NamedBeanAddressing.Direct) { 208 return _value; 209 } else { 210 String name; 211 212 switch (_addressing) { 213 case Reference: 214 name = ReferenceUtil.getReference( 215 conditionalNG.getSymbolTable(), _reference); 216 break; 217 218 case Memory: 219 name = TypeConversionUtil 220 .convertToString(_memoryHandle.getBean().getValue(), false); 221 break; 222 223 case LocalVariable: 224 SymbolTable symbolNamedBean = conditionalNG.getSymbolTable(); 225 name = TypeConversionUtil 226 .convertToString(symbolNamedBean.getValue(_localVariable), false); 227 break; 228 229 case Formula: 230 name = _expressionNode != null 231 ? TypeConversionUtil.convertToString( 232 _expressionNode.calculate( 233 conditionalNG.getSymbolTable()), false) 234 : null; 235 break; 236 237 case Table: 238 name = TypeConversionUtil.convertToString( 239 _selectTable.evaluateTableData(conditionalNG), false); 240 break; 241 242 default: 243 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 244 } 245 246 return LogixNG_SelectComboBox.this.getValue(name); 247 } 248 } 249 250 public String getDescription(Locale locale) { 251 String description; 252 253 String memoryName; 254 if (_memoryHandle != null) { 255 memoryName = _memoryHandle.getName(); 256 } else { 257 memoryName = Bundle.getMessage(locale, "BeanNotSelected"); 258 } 259 260 switch (_addressing) { 261 case Direct: 262 description = Bundle.getMessage(locale, "AddressByDirect", _value); 263 break; 264 265 case Reference: 266 description = Bundle.getMessage(locale, "AddressByReference", _reference); 267 break; 268 269 case Memory: 270 description = Bundle.getMessage(locale, "AddressByMemory_Listen", memoryName, Base.getListenString(_listenToMemory)); 271 break; 272 273 case LocalVariable: 274 description = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 275 break; 276 277 case Formula: 278 description = Bundle.getMessage(locale, "AddressByFormula", _formula); 279 break; 280 281 case Table: 282 description = Bundle.getMessage( 283 locale, 284 "AddressByTable", 285 _selectTable.getTableNameDescription(locale), 286 _selectTable.getTableRowDescription(locale), 287 _selectTable.getTableColumnDescription(locale)); 288 break; 289 290 default: 291 throw new IllegalArgumentException("invalid _addressing: " + _addressing.name()); 292 } 293 return description; 294 } 295 296 /** 297 * Register listeners if this object needs that. 298 */ 299 public void registerListeners() { 300 if (!_listenersAreRegistered 301 && (_addressing == NamedBeanAddressing.Memory) 302 && (_memoryHandle != null) 303 && _listenToMemory) { 304 _memoryHandle.getBean().addPropertyChangeListener("value", _listener); 305 _listenersAreRegistered = true; 306 } 307 } 308 309 /** 310 * Unregister listeners if this object needs that. 311 */ 312 public void unregisterListeners() { 313 if (_listenersAreRegistered 314 && (_addressing == NamedBeanAddressing.Memory) 315 && (_memoryHandle != null) 316 && _listenToMemory) { 317 _memoryHandle.getBean().removePropertyChangeListener("value", _listener); 318 _listenersAreRegistered = false; 319 } 320 } 321 322 @Override 323 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 324 if ("CanDelete".equals(evt.getPropertyName()) && _inUse.isInUse()) { // No I18N 325 if (evt.getOldValue() instanceof Memory) { 326 boolean doVeto = false; 327 if ((_addressing == NamedBeanAddressing.Memory) && (_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) { 328 doVeto = true; 329 } 330 if (doVeto) { 331 PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null); 332 throw new PropertyVetoException(Bundle.getMessage("MemoryInUseMemoryExpressionVeto", _base.getDisplayName()), e); // NOI18N 333 } 334 } 335 } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 336 if (evt.getOldValue() instanceof Memory) { 337 if (evt.getOldValue().equals(_memoryHandle.getBean())) { 338 removeMemory(); 339 } 340 } 341 } 342 } 343 344 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNG_SelectComboBox.class); 345}