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 SAME:
229                        return Bundle.getMessage("CvStateSame");
230                    case DIFFERENT:
231                        return Bundle.getMessage("CvStateDiff") + " "
232                                + _cvDisplayVector.elementAt(row).getDecoderValue();
233                    default:
234                        return "inconsistent";
235                }
236            case READCOLUMN:
237                return _readButtons.elementAt(row);
238            case WRITECOLUMN:
239                return _writeButtons.elementAt(row);
240            case COMPARECOLUMN:
241                return _compareButtons.elementAt(row);
242            default:
243                return "unknown";
244        }
245    }
246
247    @Override
248    public void setValueAt(Object value, int row, int col) {
249        switch (col) {
250            case VALCOLUMN: // Object is actually an Integer
251                if (_cvDisplayVector.elementAt(row).getValue() != ((Integer) value).intValue()) {
252                    _cvDisplayVector.elementAt(row).setValue(((Integer) value).intValue());
253                }
254                break;
255            default:
256                break;
257        }
258    }
259
260    @Override
261    public void actionPerformed(ActionEvent e) {
262        if (log.isDebugEnabled()) {
263            log.debug("action command: {}", e.getActionCommand());
264        }
265        char b = e.getActionCommand().charAt(0);
266        int row = Integer.parseInt(e.getActionCommand().substring(1));
267        if (log.isDebugEnabled()) {
268            log.debug("event on {} row {}", b, row);
269        }
270        if (b == 'R') {
271            // read command
272            _cvDisplayVector.elementAt(row).read(_status);
273        } else if (b == 'C') {
274            // compare command
275            _cvDisplayVector.elementAt(row).confirm(_status);
276        } else {
277            // write command
278            _cvDisplayVector.elementAt(row).write(_status);
279        }
280    }
281
282    @Override
283    public void propertyChange(PropertyChangeEvent e) {
284        // don't need to forward Busy, do need to forward Value
285        // not sure about any others
286        if (!e.getPropertyName().equals("Busy")) {
287            fireTableDataChanged();
288        }
289    }
290
291    public void addCV(String s, boolean readOnly, boolean infoOnly, boolean writeOnly) {
292        if (_cvAllMap.get(s) == null) {
293            CvValue cv = new CvValue(s, mProgrammer);
294            cv.setReadOnly(readOnly);
295            _cvAllMap.put(s, cv);
296            _cvDisplayVector.addElement(cv);
297            // connect to this CV to ensure the table display updates
298            cv.addPropertyChangeListener(this);
299            JButton bw = new JButton(Bundle.getMessage("ButtonWrite"));
300            _writeButtons.addElement(bw);
301            JButton br = new JButton(Bundle.getMessage("ButtonRead"));
302            _readButtons.addElement(br);
303            JButton bc = new JButton(Bundle.getMessage("ButtonCompare"));
304            _compareButtons.addElement(bc);
305            if (infoOnly || readOnly) {
306                if (writeOnly) {
307                    bw.setEnabled(true);
308                    bw.setActionCommand("W" + _numRows);
309                    bw.addActionListener(this);
310                } else {
311                    bw.setEnabled(false);
312                }
313                if (infoOnly) {
314                    br.setEnabled(false);
315                    bc.setEnabled(false);
316                } else {
317                    br.setEnabled(true);
318                    br.setActionCommand("R" + _numRows);
319                    br.addActionListener(this);
320                    bc.setEnabled(true);
321                    bc.setActionCommand("C" + _numRows);
322                    bc.addActionListener(this);
323                }
324            } else {
325                bw.setEnabled(true);
326                bw.setActionCommand("W" + _numRows);
327                bw.addActionListener(this);
328                if (writeOnly) {
329                    br.setEnabled(false);
330                    bc.setEnabled(false);
331                } else {
332                    br.setEnabled(true);
333                    br.setActionCommand("R" + _numRows);
334                    br.addActionListener(this);
335                    bc.setEnabled(true);
336                    bc.setActionCommand("C" + _numRows);
337                    bc.addActionListener(this);
338                }
339            }
340            _numRows++;
341            fireTableDataChanged();
342        }
343        // make sure readonly set true if required
344        CvValue cv = _cvAllMap.get(s);
345        if (readOnly) {
346            cv.setReadOnly(readOnly);
347        }
348        if (infoOnly) {
349            cv.setReadOnly(!infoOnly);
350            cv.setWriteOnly(!infoOnly);
351            cv.setInfoOnly(infoOnly);
352        }
353        if (writeOnly) {
354            cv.setWriteOnly(writeOnly);
355        }
356    }
357
358    public boolean decoderDirty() {
359        int len = _cvDisplayVector.size();
360        for (int i = 0; i < len; i++) {
361            if (_cvDisplayVector.elementAt(i).getState() == AbstractValue.ValueState.EDITED) {
362                if (log.isDebugEnabled()) {
363                    log.debug("CV decoder dirty due to {}", _cvDisplayVector.elementAt(i).number());
364                }
365                return true;
366            }
367        }
368        return false;
369    }
370
371    /**
372     * Register a VariableValue in a common store mapping CV numbers to
373     * variable names. This is for use by e.g. a CVTable to show tooltips
374     * efficiently.
375     * @param cv specific CV number that the variable references
376     * @param variableName from the variable being defined
377     */
378    public void registerCvToVariableMapping(String cv, String variableName) {
379        // is there already a Set for these?
380        if ( ! cvToVarMap.containsKey(cv)) {
381            // no, create one
382            cvToVarMap.put(cv, Collections.newSetFromMap(new HashMap<String, Boolean>()));
383        }
384        // add the String
385        cvToVarMap.get(cv).add(variableName);
386    }
387
388    public Set<String> getCvToVariableMapping(String cv) { return cvToVarMap.get(cv); }
389
390    private HashMap<String, Set<String>> cvToVarMap = new HashMap<>();
391
392    public void dispose() {
393        if (log.isDebugEnabled()) {
394            log.debug("dispose");
395        }
396
397        // remove buttons
398        for (int i = 0; i < _writeButtons.size(); i++) {
399            _writeButtons.elementAt(i).removeActionListener(this);
400        }
401        for (int i = 0; i < _readButtons.size(); i++) {
402            _readButtons.elementAt(i).removeActionListener(this);
403        }
404        for (int i = 0; i < _compareButtons.size(); i++) {
405            _compareButtons.elementAt(i).removeActionListener(this);
406        }
407
408        // remove CV listeners
409        for (int i = 0; i < _cvDisplayVector.size(); i++) {
410            _cvDisplayVector.elementAt(i).removePropertyChangeListener(this);
411        }
412
413        // null references, so that they can be gc'd even if this isn't.
414        cvToVarMap = null;
415
416        _cvDisplayVector.removeAllElements();
417        _cvDisplayVector = null;
418
419        _writeButtons.removeAllElements();
420        _writeButtons = null;
421
422        _readButtons.removeAllElements();
423        _readButtons = null;
424
425        _compareButtons.removeAllElements();
426        _compareButtons = null;
427
428        _cvAllMap.clear();
429        _cvAllMap = null;
430
431        _status = null;
432
433    }
434
435    int holdsAddress() {
436        int shortAddr = getCvByNumber("1").getValue();
437        int longAddr = ((getCvByNumber("17").getValue()-192)<<8)+getCvByNumber("18").getValue();
438        int addr = holdsLongAddress() ? longAddr : shortAddr;
439        return addr;
440    }
441
442    boolean holdsLongAddress() {
443        return (getCvByNumber("29").getValue() & 0x20) != 0;
444    }
445
446    private final static Logger log = LoggerFactory.getLogger(CvTableModel.class);
447}