001package jmri.jmrit.dualdecoder;
002
003import java.awt.GridLayout;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import javax.swing.BoxLayout;
007import javax.swing.ButtonGroup;
008import javax.swing.JButton;
009import javax.swing.JLabel;
010import javax.swing.JPanel;
011import javax.swing.JRadioButton;
012import javax.swing.JSeparator;
013import javax.swing.JToggleButton;
014import jmri.ProgListener;
015import jmri.Programmer;
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * Pane for selecting an active decoder from multiple ones in a loco
021 *
022 * @author Bob Jacobsen Copyright (C) 2003
023 */
024public class DualDecoderSelectPane extends javax.swing.JPanel implements jmri.ProgListener {
025
026    boolean scanning = false;
027    int next = 0;
028
029    final int NENTRIES = 8;
030
031    JLabel[] labels = new JLabel[NENTRIES];
032    JRadioButton[] buttons = new JRadioButton[NENTRIES];
033
034    JLabel status = new JLabel(Bundle.getMessage("ButtonIdle")); // is in jmrit.Bundle
035    JToggleButton searchButton = new JToggleButton(Bundle.getMessage("ButtonSearch"));
036    jmri.jmrit.progsupport.ProgModePane modePane = new jmri.jmrit.progsupport.ProgModePane(BoxLayout.Y_AXIS);
037
038    public DualDecoderSelectPane() {
039        // general GUI config
040        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
041        ButtonGroup g = new ButtonGroup();
042        JPanel pane1 = new JPanel();
043        pane1.setLayout(new GridLayout(NENTRIES, 1));
044        for (int i = 0; i < NENTRIES; i++) {
045            JPanel p = new JPanel();
046            String name = Bundle.getMessage("IDnumber", i);
047            if (i == NENTRIES - 1) {
048                name = Bundle.getMessage("Legacy");
049            }
050            p.add(labels[i] = new JLabel(name));
051            JRadioButton b = new JRadioButton();
052            buttons[i] = b;
053            b.setActionCommand("" + i);
054            b.addActionListener(new ActionListener() {
055                @Override
056                public void actionPerformed(ActionEvent e) {
057                    select(e.getActionCommand());
058                }
059            });
060            p.add(b);
061            g.add(b);
062            pane1.add(p);
063        }
064        add(pane1);
065        add(new JSeparator(JSeparator.HORIZONTAL));
066
067        JPanel pane2 = new JPanel();
068        JButton t;
069        pane2.add(searchButton);
070        searchButton.addActionListener(new ActionListener() {
071            @Override
072            public void actionPerformed(ActionEvent e) {
073                search();
074            }
075        });
076        pane2.add(t = new JButton(Bundle.getMessage("ButtonReset")));
077        t.addActionListener(new ActionListener() {
078            @Override
079            public void actionPerformed(ActionEvent e) {
080                reset();
081            }
082        });
083        add(pane2);
084
085        JPanel pane3 = new JPanel();
086        pane3.add(t = new JButton(Bundle.getMessage("InitDH163")));
087        t.setToolTipText(Bundle.getMessage("InitDH163Tooltip"));
088        t.addActionListener(new ActionListener() {
089            @Override
090            public void actionPerformed(ActionEvent e) {
091                doInit();
092            }
093        });
094        add(pane3);
095        add(new JSeparator(JSeparator.HORIZONTAL));
096
097        JPanel pane4 = new JPanel();
098        pane4.add(status);
099        add(pane4);
100        add(new JSeparator(JSeparator.HORIZONTAL));
101
102        add(modePane);
103    }
104
105    public void dispose() {
106        modePane.dispose();
107        buttons = null;
108        labels = null;
109        status = null;
110        searchButton = null;
111    }
112
113    void reset() {
114        for (int i = 0; i < NENTRIES; i++) {
115            labels[i].setEnabled(true);
116            buttons[i].setSelected(false);
117            buttons[i].setEnabled(true);
118        }
119    }
120
121    void search() {
122        mode = SEARCH;
123        reset();
124        searchButton.setSelected(true);
125        scanning = true;
126        next = 0;
127        select("0");
128    }
129
130    void select(String number) {
131        mode = SEARCH;
132        next = Integer.parseInt(number);
133        // write that CV value
134        state = WROTECV15;
135        writeCV15(next);
136    }
137
138    void writeCV15(int value) {
139        writeCV("15", value);
140    }
141
142    void writeCV16(int value) {
143        writeCV("16", value);
144    }
145
146    void writeCV(String cv, int value) {
147        Programmer p = modePane.getProgrammer();
148        if (p == null) {
149            state = IDLE;
150            status.setText(Bundle.getMessage("NoProgrammerConnected"));
151        } else {
152            try {
153                status.setText(Bundle.getMessage("StateWriting"));
154                p.writeCV(cv, value, this);
155            } catch (jmri.ProgrammerException ex) {
156                state = IDLE;
157                status.setText("" + ex);
158            }
159        }
160    }
161
162    void readCV16() {
163        Programmer p = modePane.getProgrammer();
164        if (p == null) {
165            state = IDLE;
166            status.setText(Bundle.getMessage("NoProgrammerConnected"));
167        } else {
168            try {
169                status.setText(Bundle.getMessage("StateReading"));
170                state = READCV16;
171                p.readCV("16", this);
172            } catch (jmri.ProgrammerException ex) {
173                state = IDLE;
174                status.setText("" + ex);
175            }
176        }
177    }
178
179    // modes
180    final int SEARCH = 10;
181    final int INIT = 20;
182    int mode = SEARCH;
183
184    // search, select operation states
185    final int IDLE = 0;
186    final int WROTECV15 = 1;
187    final int READCV16 = 2;
188    final int FIRSTCV16 = 11;
189    final int FIRSTCV15 = 12;
190    final int SECONDCV16 = 13;
191    int state = IDLE;
192
193    @Override
194    public void programmingOpReply(int value, int retcode) {
195        switch (mode) {
196            case SEARCH:
197                searchReply(value, retcode);
198                break;
199            case INIT:
200                initReply(value, retcode);
201                break;
202            default:
203                log.warn("unexpected mode: {}", mode);
204                break;
205        }
206    }
207
208    void searchReply(int value, int retcode) {
209        switch (state) {
210            case IDLE:
211            default:
212                // shouldn't happen, reset and ignore
213                log.warn("Unexpected search programming reply: {} {}", value, retcode);
214                state = IDLE;
215                break;
216            case WROTECV15:
217                //confirm OK
218                readCV16();
219                break;
220            case READCV16:
221                // was it OK?
222                String result = Bundle.getMessage("ButtonOK");
223                if (retcode != ProgListener.OK) {
224                    log.debug("Readback error: {} {}", retcode, value);
225                    labels[next].setEnabled(false);
226                    buttons[next].setEnabled(false);
227                    result = "Could not confirm: " + modePane.getProgrammer().decodeErrorCode(retcode);
228                } else if (value != next) {
229                    log.debug("Readback error: {} {}", retcode, value);
230                    if (scanning) {
231                        labels[next].setEnabled(false);
232                        buttons[next].setEnabled(false);
233                    }
234                    result = Bundle.getMessage("UnexpectedID_Read", value);
235                }
236                // go on to next?
237                if (scanning) {
238                    next++;
239                    if (next >= NENTRIES) {
240                        state = IDLE;
241                        next = 0;
242                        scanning = false;
243                        status.setText(Bundle.getMessage("StateIdle"));
244                        searchButton.setSelected(false);
245                        break;
246                    }
247                    select("" + next);
248                    break;
249                } else {
250                    status.setText(result);
251                    break;
252                }
253        }
254    }
255
256    /**
257     * Start process of initializing a Digitrax and legacy decoder. Operations
258     * are:
259     * <ol>
260     * <li>Write 1 to CV16, which will write both decoders
261     * <li>Write 7 to CV15, which will turn off Digitrax
262     * <li>Write 7 to CV16, which will be stored in the legacy decoder only
263     * </ol>
264     */
265    void doInit() {
266        mode = INIT;
267        state = FIRSTCV16;
268        writeCV16(1);
269    }
270
271    void initReply(int value, int retcode) {
272        switch (state) {
273            case IDLE:
274            default:
275                // shouldn't happen, reset and ignore
276                log.warn("Unexpected init programming reply: {} {}", value, retcode);
277                state = IDLE;
278                break;
279            case FIRSTCV16:
280                state = FIRSTCV15;
281                if (retcode != ProgListener.OK) {
282                    log.debug("Readback error: {} {}", retcode, value);
283                    status.setText(Bundle.getMessage("WriteCVFailed", 15, 7));
284                    state = IDLE;
285                } else { // is OK
286                    writeCV15(7);
287                }
288                break;
289            case FIRSTCV15:
290                state = SECONDCV16;
291                if (retcode != ProgListener.OK) {
292                    log.debug("Readback error: {} {}", retcode, value);
293                    status.setText(Bundle.getMessage("WriteCVFailed", 16, 7));
294                    state = IDLE;
295                } else { // is OK
296                    writeCV16(7);
297                }
298                break;
299            case SECONDCV16:
300                if (retcode != ProgListener.OK) {
301                    log.debug("Readback error: {} {}", retcode, value);
302                    status.setText(Bundle.getMessage("WriteCVFailed", 16, 1));
303                    state = IDLE;
304                } else { // is OK
305                    state = IDLE;
306                    status.setText(Bundle.getMessage("StateInitialized"));
307                }
308                break;
309        }
310    }
311
312    private final static Logger log = LoggerFactory.getLogger(DualDecoderSelectPane.class);
313
314}