001package jmri.jmrix.loconet.swing.lnsv1prog;
002
003import jmri.InstanceManager;
004import jmri.Programmer;
005import jmri.jmrit.decoderdefn.DecoderFile;
006import jmri.jmrit.decoderdefn.DecoderIndexFile;
007import jmri.jmrit.roster.Roster;
008import jmri.jmrit.roster.RosterEntry;
009import jmri.jmrit.symbolicprog.tabbedframe.PaneOpsProgFrame;
010import jmri.jmrix.ProgrammingTool;
011import jmri.jmrix.loconet.Lnsv1DevicesManager;
012import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
013import jmri.jmrix.loconet.lnsvf1.Lnsv1Device;
014import jmri.util.swing.JmriJOptionPane;
015
016import javax.annotation.Nonnull;
017import javax.swing.table.AbstractTableModel;
018import javax.swing.table.TableColumn;
019import javax.swing.table.TableColumnModel;
020import java.beans.PropertyChangeEvent;
021import java.beans.PropertyChangeListener;
022
023/**
024 * Table model for the programmed LNSV1 Modules table.
025 * See Svf1 Programing tool
026 *
027 * @author Egbert Broerse Copyright (C) 2020, 2025
028 */
029public class Lnsv1ProgTableModel extends AbstractTableModel implements PropertyChangeListener, ProgrammingTool {
030
031    public static final int COUNT_COLUMN = 0;
032    public static final int MODADDR_COLUMN = 1;
033    public static final int MODADDRSPLIT_COLUMN = 2;
034    public static final int VERSION_COLUMN = 3;
035    public static final int CV_COLUMN = 4;
036    public static final int VALUE_COLUMN = 5;
037    public static final int ROSTERENTRY_COLUMN = 6;
038    public static final int ROSTERSV1MODE_COLUMN = 7;
039    public static final int ROSTERNAME_COLUMN = 8;
040    public static final int OPENPRGMRBUTTON_COLUMN = 9;
041    static public final int NUMCOLUMNS = 10;
042    private final Lnsv1ProgPane parent;
043    private final transient LocoNetSystemConnectionMemo memo;
044    protected Roster _roster;
045    protected Lnsv1DevicesManager lnsv1dm;
046
047    Lnsv1ProgTableModel(Lnsv1ProgPane parent, @Nonnull LocoNetSystemConnectionMemo memo) {
048        this.parent = parent;
049        this.memo = memo;
050        lnsv1dm = memo.getLnsv1DevicesManager();
051        _roster = Roster.getDefault();
052        lnsv1dm.addPropertyChangeListener(this);
053    }
054
055    public void initTable(javax.swing.JTable lnsv1ModulesTable) {
056       TableColumnModel assignmentColumnModel = lnsv1ModulesTable.getColumnModel();
057       TableColumn idColumn = assignmentColumnModel.getColumn(0);
058       idColumn.setMaxWidth(3);
059    }
060
061    @Override
062    public String getColumnName(int c) {
063        switch (c) {
064            case MODADDR_COLUMN:
065                return Bundle.getMessage("HeadingDccAddress");
066            case MODADDRSPLIT_COLUMN:
067                return Bundle.getMessage("HeadingAddressSplit");
068            case VERSION_COLUMN:
069                return Bundle.getMessage("HeadingVersion");
070            case CV_COLUMN:
071                return Bundle.getMessage("HeadingCvLastRead");
072            case VALUE_COLUMN:
073                return Bundle.getMessage("HeadingValue");
074            case ROSTERENTRY_COLUMN:
075                return Bundle.getMessage("HeadingDeviceId");
076            case ROSTERNAME_COLUMN:
077                return Bundle.getMessage("HeadingDeviceModel");
078            case ROSTERSV1MODE_COLUMN:
079                return Bundle.getMessage("HeadingIsSv1");
080            case OPENPRGMRBUTTON_COLUMN:
081                return Bundle.getMessage("ButtonProgram");
082            case COUNT_COLUMN:
083            default:
084                return "#";
085        }
086    }
087
088    @Override
089    public Class<?> getColumnClass(int c) {
090        switch (c) {
091            case MODADDR_COLUMN:
092            case COUNT_COLUMN:
093            case VERSION_COLUMN:
094            case CV_COLUMN:
095            case VALUE_COLUMN:
096                return Integer.class;
097            case OPENPRGMRBUTTON_COLUMN:
098                return javax.swing.JButton.class;
099            case ROSTERSV1MODE_COLUMN:
100                return Boolean.class;
101            case MODADDRSPLIT_COLUMN:
102            case ROSTERNAME_COLUMN:
103            case ROSTERENTRY_COLUMN:
104            default:
105                return String.class;
106       }
107   }
108
109    @Override
110    public boolean isCellEditable(int r, int c) {
111       return (c == OPENPRGMRBUTTON_COLUMN);
112   }
113
114    @Override
115    public int getColumnCount() {
116      return NUMCOLUMNS;
117   }
118
119    @Override
120    public int getRowCount() {
121        if (lnsv1dm == null) {
122            return 0;
123        } else {
124            return lnsv1dm.getDeviceCount();
125        }
126    }
127
128    @Override
129    public Object getValueAt(int r, int c) {
130        Lnsv1Device dev = memo.getLnsv1DevicesManager().getDeviceList().getDevice(r);
131        try {
132            switch (c) {
133                case MODADDR_COLUMN:
134                    assert dev != null;
135                    return dev.getDestAddr();
136                case MODADDRSPLIT_COLUMN:
137                    assert dev != null;
138                    return dev.getDestAddrLow() + "/" + dev.getDestAddrHigh();
139                case VERSION_COLUMN:
140                    assert dev != null;
141                    return dev.getSwVersion();
142                case CV_COLUMN:
143                    assert dev != null;
144                    return dev.getCvNum();
145                case VALUE_COLUMN:
146                    assert dev != null;
147                    return dev.getCvValue();
148                case ROSTERENTRY_COLUMN:
149                    assert dev != null;
150                    return dev.getRosterEntry().getId();
151                case ROSTERSV1MODE_COLUMN:
152                    boolean isLnsv1 = false;
153                    if (dev != null && dev.getDecoderFile() != null) {
154                        isLnsv1 = dev.getDecoderFile().isProgrammingMode("LOCONETSV1MODE");
155                        // can't access LnProgrammerManager.LOCONETSV1MODE constant
156                    }
157                    return isLnsv1;
158                case ROSTERNAME_COLUMN:
159                    assert dev != null;
160                    if (dev.getRosterEntry() != null) {
161                        return dev.getRosterEntry().getDecoderModel();
162                    } else {
163                        return "";
164                    }
165                case OPENPRGMRBUTTON_COLUMN:
166                    if (dev != null && !dev.getRosterName().isEmpty()) {
167                        if (dev.getDecoderFile().isProgrammingMode("LOCONETSV1MODE")) {
168                            return Bundle.getMessage("ButtonProgram");
169                        } else {
170                            return Bundle.getMessage("ButtonWrongMode");
171                        }
172                    }
173                    return Bundle.getMessage("ButtonNoMatchInRoster");
174                default: // column 0
175                    return r + 1;
176            }
177        } catch (NullPointerException npe) {
178            log.warn("No match for Module {}, c{}", r, c);
179            return "";
180        }
181    }
182
183    @Override
184    public void setValueAt(Object value, int r, int c) {
185        if (getRowCount() < r + 1) {
186            // prevent update of a row that does not (yet) exist
187            return;
188        }
189        if (c == OPENPRGMRBUTTON_COLUMN) {
190            if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonProgram")) == 0) {
191                openProgrammer(r);
192            } else if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonWrongMode")) == 0) {
193                infoNotForLnsv1(getValueAt(r, 1).toString()); // TODO once we check for LNSV1 progMode this can be removed
194            } else if (((String) getValueAt(r, c)).compareTo(Bundle.getMessage("ButtonNoMatchInRoster")) == 0){
195                // no match, info add roster entry
196                infoNoMatch(getValueAt(r, 1).toString());
197            }
198        } else {
199            // no change, so do not fire a property change event
200            return;
201        }
202        if (getRowCount() >= 1) {
203            this.fireTableRowsUpdated(r, r);
204        }
205    }
206
207    private void openProgrammer(int r) {
208        Lnsv1Device dev = memo.getLnsv1DevicesManager().getDeviceList().getDevice(r);
209
210        Lnsv1DevicesManager.ProgrammingResult result = lnsv1dm.prepareForSymbolicProgrammer(dev, this);
211        switch (result) {
212            case SUCCESS_PROGRAMMER_OPENED:
213                return;
214            case FAIL_NO_SUCH_DEVICE:
215                JmriJOptionPane.showMessageDialog(parent,
216                        Bundle.getMessage("FAIL_NO_SUCH_DEVICE"),
217                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
218                return;
219            case FAIL_NO_APPROPRIATE_PROGRAMMER:
220                JmriJOptionPane.showMessageDialog(parent,
221                        Bundle.getMessage("FAIL_NO_APPROPRIATE_PROGRAMMER"),
222                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
223                return;
224            case FAIL_NO_MATCHING_ROSTER_ENTRY:
225                JmriJOptionPane.showMessageDialog(parent,
226                        Bundle.getMessage("FAIL_NO_MATCHING_ROSTER_ENTRY"),
227                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
228                return;
229            case FAIL_DESTINATION_ADDRESS_IS_ZERO:
230                JmriJOptionPane.showMessageDialog(parent,
231                        Bundle.getMessage("FAIL_DESTINATION_ADDRESS_IS_ZERO"),
232                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
233                return;
234            case FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS:
235                JmriJOptionPane.showMessageDialog(parent,
236                        Bundle.getMessage("FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS", dev.getDestAddr()),
237                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
238                return;
239            case FAIL_NO_ADDRESSED_PROGRAMMER:
240                JmriJOptionPane.showMessageDialog(parent,
241                        Bundle.getMessage("FAIL_NO_ADDRESSED_PROGRAMMER"),
242                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
243                return;
244            case FAIL_NO_LNSV1_PROGRAMMER:
245                JmriJOptionPane.showMessageDialog(parent,
246                        Bundle.getMessage("FAIL_NO_LNSV1_PROGRAMMER"),
247                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
248                return;
249            default:
250                JmriJOptionPane.showMessageDialog(parent,
251                        Bundle.getMessage("FAIL_UNKNOWN"),
252                        Bundle.getMessage("TitleOpenRosterEntry"), JmriJOptionPane.ERROR_MESSAGE);
253        }
254    }
255
256    /**
257     * {@inheritDoc}
258     */
259    @Override
260    public void openPaneOpsProgFrame(RosterEntry re, String name,
261                                     String programmerFile, Programmer p) {
262        // would be better if this was a new task on the GUI thread...
263        log.debug("attempting to open programmer, re={}, name={}, programmerFile={}, programmer={}",
264                re, name, programmerFile, p);
265
266        DecoderFile decoderFile = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(re.getDecoderModel());
267
268        PaneOpsProgFrame progFrame =
269                new PaneOpsProgFrame(decoderFile, re, name, programmerFile, p);
270
271        progFrame.pack();
272        progFrame.setVisible(true);
273    }
274
275    /**
276     * Show dialog to instruct to add a roster entry supporting LNSV1 prog mode.
277     */
278    private void infoNoMatch(String address) {
279        //log.debug("address = {}", address);
280        Object[] dialogBoxButtonOptions = {
281                Bundle.getMessage("ButtonOK")};
282        JmriJOptionPane.showOptionDialog(parent,
283                Bundle.getMessage("DialogInfoNoRosterMatch", address), // not displaying addr?
284                Bundle.getMessage("TitleOpenRosterEntry"),
285                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.INFORMATION_MESSAGE,
286                null, dialogBoxButtonOptions, dialogBoxButtonOptions[0]);
287    }
288
289    /**
290     * Show dialog to inform that address matched decoder doesn't support LNSV1 mode.
291     */
292    private void infoNotForLnsv1(String address) {
293        Object[] dialogBoxButtonOptions = {
294                Bundle.getMessage("ButtonOK")};
295        JmriJOptionPane.showOptionDialog(parent,
296                Bundle.getMessage("DialogInfoMatchNotX", address, "LNSV1"),
297                Bundle.getMessage("TitleOpenRosterEntry"),
298                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.INFORMATION_MESSAGE,
299                null, dialogBoxButtonOptions, dialogBoxButtonOptions[0]);
300    }
301
302    /*
303     * Process the "property change" events from Lnsv1DevicesManager.
304     *
305     * @param evt event
306     */
307    @Override
308    public void propertyChange(PropertyChangeEvent evt) {
309        // these messages can arrive without a complete
310        // GUI, in which case we just ignore them
311        //String eventName = evt.getPropertyName();
312        /* always use fireTableDataChanged() because it does not always
313            resize columns to "preferred" widths!
314            This may slow things down, but that is a small price to pay!
315        */
316        fireTableDataChanged();
317    }
318
319    public void dispose() {
320        if ((memo != null) && (memo.getLnsv1DevicesManager() != null)) {
321            memo.getLnsv1DevicesManager().removePropertyChangeListener(this);
322        }
323    }
324
325    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Lnsv1ProgTableModel.class);
326
327}