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    /**
076     * Gives access to the programmer used to reach these CVs, so you can check
077     * on mode, capabilities, etc.
078     *
079     * @return Programmer object for the CVs
080     */
081    public Programmer getProgrammer() {
082        return mProgrammer;
083    }
084
085    public void setProgrammer(Programmer p) {
086        mProgrammer = p;
087        // tell all variables
088        for (CvValue cv : allCvMap().values()) {
089            if (cv != null) {
090                cv.setProgrammer(p);
091            }
092        }
093        for (CvValue cv : _cvDisplayVector) {
094            if (cv != null) {
095                cv.setProgrammer(p);
096            }
097        }
098    }
099
100    // basic methods for AbstractTableModel implementation
101    @Override
102    public int getRowCount() {
103        return _numRows;
104    }
105
106    @Override
107    public int getColumnCount() {
108        if (getProgrammer() != null) {
109            return HIGHESTCOLUMN;
110        } else {
111            return HIGHESTNOPROG;
112        }
113    }
114
115    @Override
116    public String getColumnName(int col) {
117        switch (col) {
118            case NUMCOLUMN:
119                return Bundle.getMessage("ColumnNameNumber");
120            case VALCOLUMN:
121                return Bundle.getMessage("ColumnNameValue");
122            case STATECOLUMN:
123                return Bundle.getMessage("ColumnNameState");
124            case READCOLUMN:
125                return Bundle.getMessage("ColumnNameRead");
126            case WRITECOLUMN:
127                return Bundle.getMessage("ColumnNameWrite");
128            case COMPARECOLUMN:
129                return Bundle.getMessage("ColumnNameCompare");
130            default:
131                return "unknown";
132        }
133    }
134
135    @Override
136    public Class<?> getColumnClass(int col) {
137        switch (col) {
138            case NUMCOLUMN:
139                return Integer.class;
140            case VALCOLUMN:
141                return JTextField.class;
142            case STATECOLUMN:
143                return String.class;
144            case READCOLUMN:
145                return JButton.class;
146            case WRITECOLUMN:
147                return JButton.class;
148            case COMPARECOLUMN:
149                return JButton.class;
150            default:
151                return null;
152        }
153    }
154
155    @Override
156    public boolean isCellEditable(int row, int col) {
157        switch (col) {
158            case NUMCOLUMN:
159                return false;
160            case VALCOLUMN:
161                if (_cvDisplayVector.elementAt(row).getReadOnly()
162                        || _cvDisplayVector.elementAt(row).getInfoOnly()) {
163                    return false;
164                } else {
165                    return true;
166                }
167            case STATECOLUMN:
168                return false;
169            case READCOLUMN:
170                return true;
171            case WRITECOLUMN:
172                return true;
173            case COMPARECOLUMN:
174                return true;
175            default:
176                return false;
177        }
178    }
179
180    public String getName(int row) {  // name is text number
181        return "" + _cvDisplayVector.elementAt(row).number();
182    }
183
184    public String getValString(int row) {
185        return "" + _cvDisplayVector.elementAt(row).getValue();
186    }
187
188    public CvValue getCvByRow(int row) {
189        return _cvDisplayVector.elementAt(row);
190    }
191
192    public CvValue getCvByNumber(String number) {
193        return _cvAllMap.get(number);
194    }
195
196    @Override
197    public Object getValueAt(int row, int col) {
198        switch (col) {
199            case NUMCOLUMN:
200                return _cvDisplayVector.elementAt(row).number();
201            case VALCOLUMN:
202                return _cvDisplayVector.elementAt(row).getTableEntry();
203            case STATECOLUMN:
204                AbstractValue.ValueState state = _cvDisplayVector.elementAt(row).getState();
205                switch (state) {
206                    case UNKNOWN:
207                        return Bundle.getMessage("CvStateUnknown");
208                    case READ:
209                        return Bundle.getMessage("CvStateRead");
210                    case EDITED:
211                        return Bundle.getMessage("CvStateEdited");
212                    case STORED:
213                        return Bundle.getMessage("CvStateStored");
214                    case FROMFILE:
215                        return Bundle.getMessage("CvStateFromFile");
216                    case SAME:
217                        return Bundle.getMessage("CvStateSame");
218                    case DIFFERENT:
219                        return Bundle.getMessage("CvStateDiff") + " "
220                                + _cvDisplayVector.elementAt(row).getDecoderValue();
221                    default:
222                        return "inconsistent";
223                }
224            case READCOLUMN:
225                return _readButtons.elementAt(row);
226            case WRITECOLUMN:
227                return _writeButtons.elementAt(row);
228            case COMPARECOLUMN:
229                return _compareButtons.elementAt(row);
230            default:
231                return "unknown";
232        }
233    }
234
235    @Override
236    public void setValueAt(Object value, int row, int col) {
237        switch (col) {
238            case VALCOLUMN: // Object is actually an Integer
239                if (_cvDisplayVector.elementAt(row).getValue() != ((Integer) value).intValue()) {
240                    _cvDisplayVector.elementAt(row).setValue(((Integer) value).intValue());
241                }
242                break;
243            default:
244                break;
245        }
246    }
247
248    @Override
249    public void actionPerformed(ActionEvent e) {
250        if (log.isDebugEnabled()) {
251            log.debug("action command: {}", e.getActionCommand());
252        }
253        char b = e.getActionCommand().charAt(0);
254        int row = Integer.parseInt(e.getActionCommand().substring(1));
255        if (log.isDebugEnabled()) {
256            log.debug("event on {} row {}", b, row);
257        }
258        if (b == 'R') {
259            // read command
260            _cvDisplayVector.elementAt(row).read(_status);
261        } else if (b == 'C') {
262            // compare command
263            _cvDisplayVector.elementAt(row).confirm(_status);
264        } else {
265            // write command
266            _cvDisplayVector.elementAt(row).write(_status);
267        }
268    }
269
270    @Override
271    public void propertyChange(PropertyChangeEvent e) {
272        // don't need to forward Busy, do need to forward Value
273        // not sure about any others
274        if (!e.getPropertyName().equals("Busy")) {
275            fireTableDataChanged();
276        }
277    }
278
279    public void addCV(String s, boolean readOnly, boolean infoOnly, boolean writeOnly) {
280        if (_cvAllMap.get(s) == null) {
281            CvValue cv = new CvValue(s, mProgrammer);
282            cv.setReadOnly(readOnly);
283            _cvAllMap.put(s, cv);
284            _cvDisplayVector.addElement(cv);
285            // connect to this CV to ensure the table display updates
286            cv.addPropertyChangeListener(this);
287            JButton bw = new JButton(Bundle.getMessage("ButtonWrite"));
288            _writeButtons.addElement(bw);
289            JButton br = new JButton(Bundle.getMessage("ButtonRead"));
290            _readButtons.addElement(br);
291            JButton bc = new JButton(Bundle.getMessage("ButtonCompare"));
292            _compareButtons.addElement(bc);
293            if (infoOnly || readOnly) {
294                if (writeOnly) {
295                    bw.setEnabled(true);
296                    bw.setActionCommand("W" + _numRows);
297                    bw.addActionListener(this);
298                } else {
299                    bw.setEnabled(false);
300                }
301                if (infoOnly) {
302                    br.setEnabled(false);
303                    bc.setEnabled(false);
304                } else {
305                    br.setEnabled(true);
306                    br.setActionCommand("R" + _numRows);
307                    br.addActionListener(this);
308                    bc.setEnabled(true);
309                    bc.setActionCommand("C" + _numRows);
310                    bc.addActionListener(this);
311                }
312            } else {
313                bw.setEnabled(true);
314                bw.setActionCommand("W" + _numRows);
315                bw.addActionListener(this);
316                if (writeOnly) {
317                    br.setEnabled(false);
318                    bc.setEnabled(false);
319                } else {
320                    br.setEnabled(true);
321                    br.setActionCommand("R" + _numRows);
322                    br.addActionListener(this);
323                    bc.setEnabled(true);
324                    bc.setActionCommand("C" + _numRows);
325                    bc.addActionListener(this);
326                }
327            }
328            _numRows++;
329            fireTableDataChanged();
330        }
331        // make sure readonly set true if required
332        CvValue cv = _cvAllMap.get(s);
333        if (readOnly) {
334            cv.setReadOnly(readOnly);
335        }
336        if (infoOnly) {
337            cv.setReadOnly(!infoOnly);
338            cv.setWriteOnly(!infoOnly);
339            cv.setInfoOnly(infoOnly);
340        }
341        if (writeOnly) {
342            cv.setWriteOnly(writeOnly);
343        }
344    }
345
346    public boolean decoderDirty() {
347        int len = _cvDisplayVector.size();
348        for (int i = 0; i < len; i++) {
349            if (_cvDisplayVector.elementAt(i).getState() == AbstractValue.ValueState.EDITED) {
350                if (log.isDebugEnabled()) {
351                    log.debug("CV decoder dirty due to {}", _cvDisplayVector.elementAt(i).number());
352                }
353                return true;
354            }
355        }
356        return false;
357    }
358
359    /**
360     * Register a VariableValue in a common store mapping CV numbers to
361     * variable names. This is for use by e.g. a CVTable to show tooltips
362     * efficiently.
363     * @param cv specific CV number that the variable references
364     * @param variableName from the variable being defined
365     */
366    public void registerCvToVariableMapping(String cv, String variableName) {
367        // is there already a Set for these?
368        if ( ! cvToVarMap.containsKey(cv)) {
369            // no, create one
370            cvToVarMap.put(cv, Collections.newSetFromMap(new HashMap<String, Boolean>()));
371        }
372        // add the String
373        cvToVarMap.get(cv).add(variableName);
374    }
375
376    public Set<String> getCvToVariableMapping(String cv) { return cvToVarMap.get(cv); }
377
378    private HashMap<String, Set<String>> cvToVarMap = new HashMap<>();
379
380    public void dispose() {
381        if (log.isDebugEnabled()) {
382            log.debug("dispose");
383        }
384
385        // remove buttons
386        for (int i = 0; i < _writeButtons.size(); i++) {
387            _writeButtons.elementAt(i).removeActionListener(this);
388        }
389        for (int i = 0; i < _readButtons.size(); i++) {
390            _readButtons.elementAt(i).removeActionListener(this);
391        }
392        for (int i = 0; i < _compareButtons.size(); i++) {
393            _compareButtons.elementAt(i).removeActionListener(this);
394        }
395
396        // remove CV listeners
397        for (int i = 0; i < _cvDisplayVector.size(); i++) {
398            _cvDisplayVector.elementAt(i).removePropertyChangeListener(this);
399        }
400
401        // null references, so that they can be gc'd even if this isn't.
402        cvToVarMap = null;
403
404        _cvDisplayVector.removeAllElements();
405        _cvDisplayVector = null;
406
407        _writeButtons.removeAllElements();
408        _writeButtons = null;
409
410        _readButtons.removeAllElements();
411        _readButtons = null;
412
413        _compareButtons.removeAllElements();
414        _compareButtons = null;
415
416        _cvAllMap.clear();
417        _cvAllMap = null;
418
419        _status = null;
420
421    }
422
423    int holdsAddress() {
424        int shortAddr = getCvByNumber("1").getValue();
425        int longAddr = ((getCvByNumber("17").getValue()-192)<<8)+getCvByNumber("18").getValue();
426        int addr = holdsLongAddress() ? longAddr : shortAddr;
427        return addr;
428    }
429
430    boolean holdsLongAddress() {
431        return (getCvByNumber("29").getValue() & 0x20) != 0;
432    }
433
434    private final static Logger log = LoggerFactory.getLogger(CvTableModel.class);
435}