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