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