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}