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}