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