001package jmri.jmrit.symbolicprog;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.beans.PropertyChangeEvent;
006import java.beans.PropertyChangeListener;
007import java.util.Collections;
008import java.util.HashMap;
009import java.util.Set;
010import java.util.Vector;
011
012import javax.swing.JButton;
013import javax.swing.JLabel;
014import javax.swing.JTextField;
015import jmri.Programmer;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * Table data model for display of CvValues in symbolic programmer.
021 * <p>
022 * This represents the contents of a single decoder, so the Programmer used to
023 * access it is a data member.
024 *
025 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2006
026 * @author Howard G. Penny Copyright (C) 2005
027 */
028public class CvTableModel extends javax.swing.table.AbstractTableModel implements ActionListener, PropertyChangeListener {
029
030    private int _numRows = 0;                // must be zero until Vectors are initialized
031    static final int MAXCVNUM = 1024;
032    private Vector<CvValue> _cvDisplayVector = new Vector<CvValue>();  // vector of CvValue objects, in display-row order, for doing row mapping
033
034    private HashMap<String, CvValue> _cvAllMap = new HashMap<String, CvValue>();
035
036    public HashMap<String, CvValue> allCvMap() {
037        return _cvAllMap;
038    }
039
040    private Vector<JButton> _writeButtons = new Vector<JButton>();
041    private Vector<JButton> _readButtons = new Vector<JButton>();
042    private Vector<JButton> _compareButtons = new Vector<JButton>();
043    private Programmer mProgrammer;
044
045    // Defines the columns
046    public static final int NUMCOLUMN = 0;
047    private static final int VALCOLUMN = 1;
048    private static final int STATECOLUMN = 2;
049    private static final int READCOLUMN = 3;
050    private static final int WRITECOLUMN = 4;
051    private static final int COMPARECOLUMN = 5;
052
053    private static final int HIGHESTCOLUMN = COMPARECOLUMN + 1;
054    private static final int HIGHESTNOPROG = STATECOLUMN + 1;
055
056    private JLabel _status = null;
057
058    public JLabel getStatusLabel() {
059        return _status;
060    }
061
062    public CvTableModel(JLabel status, Programmer pProgrammer) {
063        super();
064
065        mProgrammer = pProgrammer;
066        // save a place for notification
067        _status = status;
068
069        // define just address CV at start, pending some variables
070        // boudreau: not sure why we need the statement below,
071        // messes up building CV table for CV #1 when in ops mode.
072        //addCV("1", false, false, false);
073    }
074
075    public void setNoDecoder() {
076        for (var b : _readButtons) {
077            b.setEnabled(false);
078        }
079        for (var b : _writeButtons) {
080            b.setEnabled(false);
081        }
082        for (var b : _compareButtons) {
083            b.setEnabled(false);
084        }
085    }
086
087    /**
088     * Gives access to the programmer used to reach these CVs, so you can check
089     * on mode, capabilities, etc.
090     *
091     * @return Programmer object for the CVs
092     */
093    public Programmer getProgrammer() {
094        return mProgrammer;
095    }
096
097    public void setProgrammer(Programmer p) {
098        mProgrammer = p;
099        // tell all variables
100        for (CvValue cv : allCvMap().values()) {
101            if (cv != null) {
102                cv.setProgrammer(p);
103            }
104        }
105        for (CvValue cv : _cvDisplayVector) {
106            if (cv != null) {
107                cv.setProgrammer(p);
108            }
109        }
110    }
111
112    // basic methods for AbstractTableModel implementation
113    @Override
114    public int getRowCount() {
115        return _numRows;
116    }
117
118    @Override
119    public int getColumnCount() {
120        if (getProgrammer() != null) {
121            return HIGHESTCOLUMN;
122        } else {
123            return HIGHESTNOPROG;
124        }
125    }
126
127    @Override
128    public String getColumnName(int col) {
129        switch (col) {
130            case NUMCOLUMN:
131                return Bundle.getMessage("ColumnNameNumber");
132            case VALCOLUMN:
133                return Bundle.getMessage("ColumnNameValue");
134            case STATECOLUMN:
135                return Bundle.getMessage("ColumnNameState");
136            case READCOLUMN:
137                return Bundle.getMessage("ColumnNameRead");
138            case WRITECOLUMN:
139                return Bundle.getMessage("ColumnNameWrite");
140            case COMPARECOLUMN:
141                return Bundle.getMessage("ColumnNameCompare");
142            default:
143                return "unknown";
144        }
145    }
146
147    @Override
148    public Class<?> getColumnClass(int col) {
149        switch (col) {
150            case NUMCOLUMN:
151                return Integer.class;
152            case VALCOLUMN:
153                return JTextField.class;
154            case STATECOLUMN:
155                return String.class;
156            case READCOLUMN:
157                return JButton.class;
158            case WRITECOLUMN:
159                return JButton.class;
160            case COMPARECOLUMN:
161                return JButton.class;
162            default:
163                return null;
164        }
165    }
166
167    @Override
168    public boolean isCellEditable(int row, int col) {
169        switch (col) {
170            case NUMCOLUMN:
171                return false;
172            case VALCOLUMN:
173                if (_cvDisplayVector.elementAt(row).getReadOnly()
174                        || _cvDisplayVector.elementAt(row).getInfoOnly()) {
175                    return false;
176                } else {
177                    return true;
178                }
179            case STATECOLUMN:
180                return false;
181            case READCOLUMN:
182                return true;
183            case WRITECOLUMN:
184                return true;
185            case COMPARECOLUMN:
186                return true;
187            default:
188                return false;
189        }
190    }
191
192    public String getName(int row) {  // name is text number
193        return "" + _cvDisplayVector.elementAt(row).number();
194    }
195
196    public String getValString(int row) {
197        return "" + _cvDisplayVector.elementAt(row).getValue();
198    }
199
200    public CvValue getCvByRow(int row) {
201        return _cvDisplayVector.elementAt(row);
202    }
203
204    public CvValue getCvByNumber(String number) {
205        return _cvAllMap.get(number);
206    }
207
208    @Override
209    public Object getValueAt(int row, int col) {
210        switch (col) {
211            case NUMCOLUMN:
212                return _cvDisplayVector.elementAt(row).number();
213            case VALCOLUMN:
214                return _cvDisplayVector.elementAt(row).getTableEntry();
215            case STATECOLUMN:
216                AbstractValue.ValueState state = _cvDisplayVector.elementAt(row).getState();
217                switch (state) {
218                    case UNKNOWN:
219                        return Bundle.getMessage("CvStateUnknown");
220                    case READ:
221                        return Bundle.getMessage("CvStateRead");
222                    case EDITED:
223                        return Bundle.getMessage("CvStateEdited");
224                    case STORED:
225                        return Bundle.getMessage("CvStateStored");
226                    case FROMFILE:
227                        return Bundle.getMessage("CvStateFromFile");
228                    case FROMFILEUNKNOWN:
229                        return Bundle.getMessage("CvStateFromFileUnknown");
230                    case SAME:
231                        return Bundle.getMessage("CvStateSame");
232                    case DIFFERENT:
233                        return Bundle.getMessage("CvStateDiff") + " "
234                                + _cvDisplayVector.elementAt(row).getDecoderValue();
235                    case FROMDEFAULT:
236                        return Bundle.getMessage("CvStateFromDefault");
237                    default:
238                        return Bundle.getMessage("CvStateInconsistent");
239                }
240            case READCOLUMN:
241                return _readButtons.elementAt(row);
242            case WRITECOLUMN:
243                return _writeButtons.elementAt(row);
244            case COMPARECOLUMN:
245                return _compareButtons.elementAt(row);
246            default:
247                return "unknown";
248        }
249    }
250
251    @Override
252    public void setValueAt(Object value, int row, int col) {
253        switch (col) {
254            case VALCOLUMN: // Object is actually an Integer
255                if (_cvDisplayVector.elementAt(row).getValue() != ((Integer) value).intValue()) {
256                    _cvDisplayVector.elementAt(row).setValue(((Integer) value).intValue());
257                }
258                break;
259            default:
260                break;
261        }
262    }
263
264    @Override
265    public void actionPerformed(ActionEvent e) {
266        if (log.isDebugEnabled()) {
267            log.debug("action command: {}", e.getActionCommand());
268        }
269        char b = e.getActionCommand().charAt(0);
270        int row = Integer.parseInt(e.getActionCommand().substring(1));
271        if (log.isDebugEnabled()) {
272            log.debug("event on {} row {}", b, row);
273        }
274        if (b == 'R') {
275            // read command
276            _cvDisplayVector.elementAt(row).read(_status);
277        } else if (b == 'C') {
278            // compare command
279            _cvDisplayVector.elementAt(row).confirm(_status);
280        } else {
281            // write command
282            _cvDisplayVector.elementAt(row).write(_status);
283        }
284    }
285
286    @Override
287    public void propertyChange(PropertyChangeEvent e) {
288        // don't need to forward Busy, do need to forward Value
289        // not sure about any others
290        if (!e.getPropertyName().equals("Busy")) {
291            fireTableDataChanged();
292        }
293    }
294
295    public void addCV(String s, boolean readOnly, boolean infoOnly, boolean writeOnly) {
296        if (_cvAllMap.get(s) == null) {
297            CvValue cv = new CvValue(s, mProgrammer);
298            cv.setReadOnly(readOnly);
299            _cvAllMap.put(s, cv);
300            _cvDisplayVector.addElement(cv);
301            // connect to this CV to ensure the table display updates
302            cv.addPropertyChangeListener(this);
303            JButton bw = new JButton(Bundle.getMessage("ButtonWrite"));
304            _writeButtons.addElement(bw);
305            JButton br = new JButton(Bundle.getMessage("ButtonRead"));
306            _readButtons.addElement(br);
307            JButton bc = new JButton(Bundle.getMessage("ButtonCompare"));
308            _compareButtons.addElement(bc);
309            if (infoOnly || readOnly) {
310                if (writeOnly) {
311                    bw.setEnabled(true);
312                    bw.setActionCommand("W" + _numRows);
313                    bw.addActionListener(this);
314                } else {
315                    bw.setEnabled(false);
316                }
317                if (infoOnly) {
318                    br.setEnabled(false);
319                    bc.setEnabled(false);
320                } else {
321                    br.setEnabled(true);
322                    br.setActionCommand("R" + _numRows);
323                    br.addActionListener(this);
324                    bc.setEnabled(true);
325                    bc.setActionCommand("C" + _numRows);
326                    bc.addActionListener(this);
327                }
328            } else {
329                bw.setEnabled(true);
330                bw.setActionCommand("W" + _numRows);
331                bw.addActionListener(this);
332                if (writeOnly) {
333                    br.setEnabled(false);
334                    bc.setEnabled(false);
335                } else {
336                    br.setEnabled(true);
337                    br.setActionCommand("R" + _numRows);
338                    br.addActionListener(this);
339                    bc.setEnabled(true);
340                    bc.setActionCommand("C" + _numRows);
341                    bc.addActionListener(this);
342                }
343            }
344            _numRows++;
345            fireTableDataChanged();
346        }
347        // make sure readonly set true if required
348        CvValue cv = _cvAllMap.get(s);
349        if (readOnly) {
350            cv.setReadOnly(readOnly);
351        }
352        if (infoOnly) {
353            cv.setReadOnly(!infoOnly);
354            cv.setWriteOnly(!infoOnly);
355            cv.setInfoOnly(infoOnly);
356        }
357        if (writeOnly) {
358            cv.setWriteOnly(writeOnly);
359        }
360    }
361
362    public boolean decoderDirty() {
363        int len = _cvDisplayVector.size();
364        for (int i = 0; i < len; i++) {
365            if (_cvDisplayVector.elementAt(i).getState() == AbstractValue.ValueState.EDITED) {
366                if (log.isDebugEnabled()) {
367                    log.debug("CV decoder dirty due to {}", _cvDisplayVector.elementAt(i).number());
368                }
369                return true;
370            }
371        }
372        return false;
373    }
374
375    /**
376     * Register a VariableValue in a common store mapping CV numbers to
377     * variable names. This is for use by e.g. a CVTable to show tooltips
378     * efficiently.
379     * @param cv specific CV number that the variable references
380     * @param variableName from the variable being defined
381     */
382    public void registerCvToVariableMapping(String cv, String variableName) {
383        // is there already a Set for these?
384        if ( ! cvToVarMap.containsKey(cv)) {
385            // no, create one
386            cvToVarMap.put(cv, Collections.newSetFromMap(new HashMap<String, Boolean>()));
387        }
388        // add the String
389        cvToVarMap.get(cv).add(variableName);
390    }
391
392    public Set<String> getCvToVariableMapping(String cv) { return cvToVarMap.get(cv); }
393
394    private HashMap<String, Set<String>> cvToVarMap = new HashMap<>();
395
396    public void dispose() {
397        if (log.isDebugEnabled()) {
398            log.debug("dispose");
399        }
400
401        // remove buttons
402        for (int i = 0; i < _writeButtons.size(); i++) {
403            _writeButtons.elementAt(i).removeActionListener(this);
404        }
405        for (int i = 0; i < _readButtons.size(); i++) {
406            _readButtons.elementAt(i).removeActionListener(this);
407        }
408        for (int i = 0; i < _compareButtons.size(); i++) {
409            _compareButtons.elementAt(i).removeActionListener(this);
410        }
411
412        // remove CV listeners
413        for (int i = 0; i < _cvDisplayVector.size(); i++) {
414            _cvDisplayVector.elementAt(i).removePropertyChangeListener(this);
415        }
416
417        // null references, so that they can be gc'd even if this isn't.
418        cvToVarMap = null;
419
420        _cvDisplayVector.removeAllElements();
421        _cvDisplayVector = null;
422
423        _writeButtons.removeAllElements();
424        _writeButtons = null;
425
426        _readButtons.removeAllElements();
427        _readButtons = null;
428
429        _compareButtons.removeAllElements();
430        _compareButtons = null;
431
432        _cvAllMap.clear();
433        _cvAllMap = null;
434
435        _status = null;
436
437    }
438
439    int holdsAddress() {
440        int shortAddr = getCvByNumber("1").getValue();
441        int longAddr = ((getCvByNumber("17").getValue()-192)<<8)+getCvByNumber("18").getValue();
442        int addr = holdsLongAddress() ? longAddr : shortAddr;
443        return addr;
444    }
445
446    boolean holdsLongAddress() {
447        return (getCvByNumber("29").getValue() & 0x20) != 0;
448    }
449
450    private static final Logger log = LoggerFactory.getLogger(CvTableModel.class);
451}