001package jmri.jmrit.symbolicprog; 002 003import java.awt.Color; 004import java.awt.Component; 005import java.awt.event.*; 006import java.util.HashMap; 007 008import javax.annotation.Nonnull; 009import javax.swing.JLabel; 010import javax.swing.JTextField; 011import javax.swing.text.Document; 012 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016/** 017 * Extends VariableValue to represent an NMRA long address. 018 * 019 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2016 020 */ 021public class LongAddrVariableValue extends VariableValue 022 implements ActionListener, FocusListener { 023 024 public LongAddrVariableValue(@Nonnull String name, @Nonnull String comment, @Nonnull String cvName, 025 boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, 026 @Nonnull String cvNum, @Nonnull String mask, int minVal, int maxVal, 027 @Nonnull HashMap<String, CvValue> v, @Nonnull JLabel status, 028 @Nonnull String stdname, @Nonnull CvValue mHighCV) { 029 super(name, comment, cvName, readOnly, infoOnly, writeOnly, opsOnly, cvNum, mask, v, status, stdname); 030 _maxVal = maxVal; 031 _minVal = minVal; 032 033 _value = new JTextField("0", 5); 034 _value.getAccessibleContext().setAccessibleName(label()); 035 036 _defaultColor = _value.getBackground(); 037 _value.setBackground(ValueState.UNKNOWN.getColor()); 038 // connect to the JTextField value, cv 039 _value.addActionListener(this); 040 _value.addFocusListener(this); 041 // connect for notification 042 CvValue cv = (_cvMap.get(getCvNum())); 043 cv.addPropertyChangeListener(this); 044 cv.setState(ValueState.FROMFILE); 045 046 highCV = mHighCV; 047 highCV.addPropertyChangeListener(this); 048 highCV.setState(ValueState.FROMFILE); 049 // simplifyMask(); // not required as mask is ignored 050 } 051 052 CvValue highCV; 053 054 @Override 055 public CvValue[] usesCVs() { 056 return new CvValue[]{ 057 _cvMap.get(getCvNum()), 058 highCV}; 059 } 060 061 /** 062 * Provide a user-readable description of the CVs accessed by this variable. 063 */ 064 @Override 065 public String getCvDescription() { 066 return "CV" + getCvNum() + " & CV" + (highCV.number()); 067 } 068 069 @Override 070 public void setToolTipText(String t) { 071 super.setToolTipText(t); // do default stuff 072 _value.setToolTipText(t); // set our value 073 } 074 075 // the connection is to cvNum and highCV 076 int _maxVal; 077 int _minVal; 078 079 @Override 080 public Object rangeVal() { 081 return "Long address"; 082 } 083 084 String oldContents = ""; 085 086 void enterField() { 087 oldContents = _value.getText(); 088 } 089 090 void exitField() { 091 // this _can_ be invoked after dispose, so protect 092 if (_value != null && !oldContents.equals(_value.getText())) { 093 int newVal = Integer.parseInt(_value.getText()); 094 int oldVal = Integer.parseInt(oldContents); 095 updatedTextField(); 096 prop.firePropertyChange("Value", Integer.valueOf(oldVal), Integer.valueOf(newVal)); 097 } 098 } 099 100 @Override 101 void updatedTextField() { 102 if (log.isDebugEnabled()) { 103 log.debug("actionPerformed"); 104 } 105 // called for new values - set the CV as needed 106 CvValue cv17 = _cvMap.get(getCvNum()); 107 CvValue cv18 = highCV; 108 // no masking involved for long address 109 int newVal; 110 try { 111 newVal = Integer.parseInt(_value.getText()); 112 } catch (java.lang.NumberFormatException ex) { 113 newVal = 0; 114 } 115 116 // no masked combining of old value required, as this fills the two CVs 117 int newCv17 = ((newVal / 256) & 0x3F) | 0xc0; 118 int newCv18 = newVal & 0xFF; 119 cv17.setValue(newCv17); 120 cv18.setValue(newCv18); 121 if (log.isDebugEnabled()) { 122 log.debug("new value {} gives CV17={} CV18={}", newVal, newCv17, newCv18); 123 } 124 } 125 126 /** 127 * ActionListener implementations 128 */ 129 @Override 130 public void actionPerformed(ActionEvent e) { 131 if (log.isDebugEnabled()) { 132 log.debug("actionPerformed"); 133 } 134 int newVal = Integer.parseInt(_value.getText()); 135 updatedTextField(); 136 prop.firePropertyChange("Value", null, newVal); 137 } 138 139 /** 140 * FocusListener implementations 141 */ 142 @Override 143 public void focusGained(FocusEvent e) { 144 log.debug("focusGained"); 145 enterField(); 146 } 147 148 @Override 149 public void focusLost(FocusEvent e) { 150 log.debug("focusLost"); 151 exitField(); 152 } 153 154 // to complete this class, fill in the routines to handle "Value" parameter 155 // and to read/write/hear parameter changes. 156 @Override 157 public String getValueString() { 158 return _value.getText(); 159 } 160 161 @Override 162 public void setIntValue(int i) { 163 setValue(i); 164 } 165 166 @Override 167 public int getIntValue() { 168 return Integer.parseInt(_value.getText()); 169 } 170 171 @Override 172 public Object getValueObject() { 173 return Integer.valueOf(_value.getText()); 174 } 175 176 @Override 177 public Component getCommonRep() { 178 if (getReadOnly()) { 179 JLabel r = new JLabel(_value.getText()); 180 updateRepresentation(r); 181 return r; 182 } else { 183 return _value; 184 } 185 } 186 187 public void setValue(int value) { 188 int oldVal; 189 try { 190 oldVal = Integer.parseInt(_value.getText()); 191 } catch (java.lang.NumberFormatException ex) { 192 oldVal = -999; 193 } 194 log.debug("setValue with new value {} old value {}", value, oldVal); 195 _value.setText("" + value); 196 if (oldVal != value || getState() == ValueState.UNKNOWN) { 197 actionPerformed(null); 198 } 199 prop.firePropertyChange("Value", Integer.valueOf(oldVal), Integer.valueOf(value)); 200 } 201 202 Color _defaultColor; 203 204 // implement an abstract member to set colors 205 @Override 206 void setColor(Color c) { 207 if (c != null) { 208 _value.setBackground(c); 209 } else { 210 _value.setBackground(_defaultColor); 211 } 212 // prop.firePropertyChange("Value", null, null); 213 } 214 215 @Override 216 public Component getNewRep(String format) { 217 var retval = updateRepresentation(new VarTextField(_value.getDocument(), _value.getText(), 5, this)); 218 retval.getAccessibleContext().setAccessibleName(label()); 219 return retval; 220 } 221 private int _progState = 0; 222 private static final int IDLE = 0; 223 private static final int READING_FIRST = 1; 224 private static final int READING_SECOND = 2; 225 private static final int WRITING_FIRST = 3; 226 private static final int WRITING_SECOND = 4; 227 228 /** 229 * Notify the connected CVs of a state change from above 230 * 231 */ 232 @Override 233 public void setCvState(ValueState state) { 234 (_cvMap.get(getCvNum())).setState(state); 235 } 236 237 @Override 238 public boolean isChanged() { 239 CvValue cv1 = _cvMap.get(getCvNum()); 240 CvValue cv2 = highCV; 241 return (considerChanged(cv1) || considerChanged(cv2)); 242 } 243 244 @Override 245 public void setToRead(boolean state) { 246 _cvMap.get(getCvNum()).setToRead(state); 247 highCV.setToRead(state); 248 } 249 250 @Override 251 public boolean isToRead() { 252 return _cvMap.get(getCvNum()).isToRead() || highCV.isToRead(); 253 } 254 255 @Override 256 public void setToWrite(boolean state) { 257 _cvMap.get(getCvNum()).setToWrite(state); 258 highCV.setToWrite(state); 259 } 260 261 @Override 262 public boolean isToWrite() { 263 return _cvMap.get(getCvNum()).isToWrite() || highCV.isToWrite(); 264 } 265 266 @Override 267 public void readChanges() { 268 if (isChanged()) { 269 readAll(); 270 } 271 } 272 273 @Override 274 public void writeChanges() { 275 if (isChanged()) { 276 writeAll(); 277 } 278 } 279 280 @Override 281 public void readAll() { 282 log.debug("longAddr read() invoked"); 283 setToRead(false); 284 setBusy(true); // will be reset when value changes 285 if (_progState != IDLE) { 286 log.warn("Programming state {}, not IDLE, in read()", _progState); 287 } 288 _progState = READING_FIRST; 289 log.debug("invoke CV read"); 290 (_cvMap.get(getCvNum())).read(_status); 291 } 292 293 @Override 294 public void writeAll() { 295 log.debug("write() invoked"); 296 if (getReadOnly()) { 297 log.error("unexpected write operation when readOnly is set"); 298 } 299 setToWrite(false); 300 setBusy(true); // will be reset when value changes 301 if (_progState != IDLE) { 302 log.warn("Programming state {}, not IDLE, in write()", _progState); 303 } 304 _progState = WRITING_FIRST; 305 log.debug("invoke CV write"); 306 (_cvMap.get(getCvNum())).write(_status); 307 } 308 309 // handle incoming parameter notification 310 @Override 311 public void propertyChange(java.beans.PropertyChangeEvent e) { 312 if (log.isDebugEnabled()) { 313 log.debug("property changed event - name: {}", e.getPropertyName()); 314 } 315 // notification from CV; check for Value being changed 316 if (e.getPropertyName().equals("Busy") && ((Boolean) e.getNewValue()).equals(Boolean.FALSE)) { 317 // busy transitions drive the state 318 switch (_progState) { 319 case IDLE: // no, just a CV update 320 log.error("Busy goes false with state IDLE"); 321 return; 322 case READING_FIRST: // read first CV, now read second 323 log.debug("Busy goes false with state READING_FIRST"); 324 _progState = READING_SECOND; 325 highCV.read(_status); 326 return; 327 case READING_SECOND: // finally done, set not busy 328 log.debug("Busy goes false with state READING_SECOND"); 329 _progState = IDLE; 330 (_cvMap.get(getCvNum())).setState(ValueState.READ); 331 highCV.setState(ValueState.READ); 332 //super.setState(READ); 333 setBusy(false); 334 return; 335 case WRITING_FIRST: // no, just a CV update 336 log.debug("Busy goes false with state WRITING_FIRST"); 337 _progState = WRITING_SECOND; 338 highCV.write(_status); 339 return; 340 case WRITING_SECOND: // now done with complete request 341 log.debug("Busy goes false with state WRITING_SECOND"); 342 _progState = IDLE; 343 super.setState(ValueState.STORED); 344 setBusy(false); 345 return; 346 default: // unexpected! 347 log.error("Unexpected state found: {}", _progState); 348 _progState = IDLE; 349 return; 350 } 351 } else if (e.getPropertyName().equals("State")) { 352 CvValue cv = _cvMap.get(getCvNum()); 353 if (log.isDebugEnabled()) { 354 log.debug("CV State changed to {}", cv.getState()); 355 } 356 setState(cv.getState()); 357 } else if (e.getPropertyName().equals("Value")) { 358 // update value of Variable 359 CvValue cv0 = _cvMap.get(getCvNum()); 360 CvValue cv1 = highCV; 361 int newVal = (cv0.getValue() & 0x3f) * 256 + cv1.getValue(); 362 setValue(newVal); // check for duplicate done inside setValue 363 // state change due to CV state change, so propagate that 364 setState(cv0.getState()); 365 // see if this was a read or write operation 366 switch (_progState) { 367 case IDLE: // no, just a CV update 368 log.debug("Value changed with state IDLE"); 369 return; 370 case READING_FIRST: // yes, now read second 371 log.debug("Value changed with state READING_FIRST"); 372 return; 373 case READING_SECOND: // now done with complete request 374 log.debug("Value changed with state READING_SECOND"); 375 return; 376 default: // unexpected! 377 log.error("Unexpected state found: {}", _progState); 378 _progState = IDLE; 379 return; 380 } 381 } 382 } 383 384 // stored value 385 JTextField _value = null; 386 387 /* Internal class extends a JTextField so that its color is consistent with 388 * an underlying variable 389 * 390 * @author Bob Jacobsen Copyright (C) 2001 391 */ 392 public class VarTextField extends JTextField { 393 394 VarTextField(Document doc, String text, int col, LongAddrVariableValue var) { 395 super(doc, text, col); 396 _var = var; 397 // get the original color right 398 setBackground(_var._value.getBackground()); 399 // listen for changes to ourself 400 addActionListener(new java.awt.event.ActionListener() { 401 @Override 402 public void actionPerformed(java.awt.event.ActionEvent e) { 403 thisActionPerformed(e); 404 } 405 }); 406 addFocusListener(new java.awt.event.FocusListener() { 407 @Override 408 public void focusGained(FocusEvent e) { 409 log.debug("focusGained"); 410 enterField(); 411 } 412 413 @Override 414 public void focusLost(FocusEvent e) { 415 log.debug("focusLost"); 416 exitField(); 417 } 418 }); 419 // listen for changes to original state 420 _var.addPropertyChangeListener(new java.beans.PropertyChangeListener() { 421 @Override 422 public void propertyChange(java.beans.PropertyChangeEvent e) { 423 originalPropertyChanged(e); 424 } 425 }); 426 } 427 428 LongAddrVariableValue _var; 429 430 void thisActionPerformed(java.awt.event.ActionEvent e) { 431 // tell original 432 _var.actionPerformed(e); 433 } 434 435 void originalPropertyChanged(java.beans.PropertyChangeEvent e) { 436 // update this color from original state 437 if (e.getPropertyName().equals("State")) { 438 setBackground(_var._value.getBackground()); 439 } 440 } 441 442 } 443 444 // clean up connections when done 445 @Override 446 public void dispose() { 447 log.debug("dispose"); 448 if (_value != null) { 449 _value.removeActionListener(this); 450 } 451 (_cvMap.get(getCvNum())).removePropertyChangeListener(this); 452 highCV.removePropertyChangeListener(this); 453 454 _value = null; 455 // do something about the VarTextField 456 } 457 458 // initialize logging 459 private final static Logger log = LoggerFactory.getLogger(LongAddrVariableValue.class); 460 461}