001package jmri.jmrix.nce.consist;
002
003import java.awt.Dimension;
004import java.awt.GridBagConstraints;
005import java.awt.GridBagLayout;
006import java.awt.event.ActionListener;
007import java.text.MessageFormat;
008import java.util.ArrayList;
009import java.util.List;
010import java.util.Objects;
011
012import javax.annotation.Nonnull;
013import javax.swing.*;
014
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018import jmri.DccLocoAddress;
019import jmri.InstanceManager;
020import jmri.jmrit.roster.RosterEntry;
021import jmri.jmrit.roster.swing.RosterEntryComboBox;
022import jmri.jmrit.throttle.ThrottleFrameManager;
023import jmri.jmrix.nce.*;
024
025/**
026 * Pane for user edit of NCE Consists
027 *
028 * NCE Consists are stored in Command Station (CS) memory starting at address
029 * xF500 and ending xFAFF. NCE supports up to 127 consists, numbered 1 to 127.
030 * They track the lead loco, rear loco, and four mid locos in the consist file.
031 * NCE cabs start at consist 127 when building and reviewing consists, so we
032 * also start with 127. Consist lead locos are stored in memory locations xF500
033 * through xF5FF. Consist rear locos are stored in memory locations xF600
034 * through xF6FF. Mid consist locos (four max) are stored in memory locations
035 * xF700 through xFAFF. If a long address is in use, bits 6 and 7 of the high
036 * byte are set. Example: Long address 3 = 0xc0 0x03 Short address 3 = 0x00 0x03
037 *
038 * NCE file format:
039 *
040 * :F500 (con 0 lead loco) (con 1 lead loco) ....... (con 7 lead loco) :F510
041 * (con 8 lead loco) ........ (con 15 lead loco) . . :F5F0 (con 120 lead loco)
042 * ..... (con 127 lead loco)
043 *
044 * :F600 (con 0 rear loco) (con 1 rear loco) ....... (con 7 rear loco) . . :F6F0
045 * (con 120 rear loco) ..... (con 127 rear loco)
046 *
047 * :F700 (con 0 mid loco1) (con 0 mid loco2) (con 0 mid loco3) (con 0 mid loco4)
048 * . . :FAF0 (con 126 mid loco1) .. (con 126 mid loco4)(con 127 mid loco1) ..
049 * (con 127 mid loco4) :0000
050 *
051 * @author Dan Boudreau Copyright (C) 2007 2008 Cloned from NceConsistEditFrame
052 * by
053 * @author kcameron Copyright (C) 2010
054 */
055public class NceConsistEditPanel extends jmri.jmrix.nce.swing.NcePanel implements
056        jmri.jmrix.nce.NceListener {
057    
058    NceConsistRoster nceConsistRoster = InstanceManager.getDefault(NceConsistRoster.class);
059
060    private static final int CONSIST_MIN = 1;    // NCE doesn't use consist 0
061    private static final int CONSIST_MAX = 127;
062    private static final int LOC_ADR_MIN = 0;    // loco address range
063    private static final int LOC_ADR_MAX = 9999; // max range for NCE
064    private static final int LOC_ADR_REPLACE = 0x3FFF;  // dummy loco address
065
066    private int consistNum = 0;     // consist being worked
067    private boolean newConsist = true;    // new consist is displayed
068
069    private int locoPosition = LEAD;     // which loco memory bank, 0 = lead, 1 = rear, 2 = mid
070    private static final int LEAD = 0;
071    private static final int REAR = 1;
072    private static final int MID = 2;
073
074    // Verify that loco isn't already a lead or rear loco
075    private int consistNumVerify;     // which consist number we're checking
076    private final int[] locoVerifyList = new int[6]; // list of locos to verify
077    private int verifyType;      // type of verification
078    private static final int VERIFY_DONE = 0;
079    private static final int VERIFY_LEAD_REAR = 1;  // lead or rear loco
080    private static final int VERIFY_MID_FWD = 2;  // mid loco foward
081    private static final int VERIFY_MID_REV = 4;  // mid loco reverse
082    private static final int VERIFY_ALL = 8;  // verify all locos
083
084    private int replyLen = 0;      // expected byte length
085    private int waiting = 0;      // to catch responses not intended for this module
086
087    // the 16 byte reply states
088    private boolean consistSearchNext = false;   // next search
089    private boolean consistSearchPrevious = false;  // previous search
090    private boolean locoSearch = false;    // when true searching for lead or rear loco in consist
091
092    private boolean emptyConsistSearch = false;  // when true searching for an empty consist
093    private boolean verifyRosterMatch = false;   // when true verify that roster matches consist in NCE CS
094
095    private static final int CONSIST_ERROR = -1;
096    private static final int ADDRESS_ERROR = -1;
097
098    private int consistCount = 0;      // search count not to exceed CONSIST_MAX
099
100    private boolean refresh = false;     // when true, refresh loco info from CS
101
102    // member declarations
103    JLabel textConsist = new JLabel();
104    JLabel textStatus = new JLabel();
105    JLabel consistStatus = new JLabel();
106
107    // major buttons
108    JButton previousButton = new JButton();
109    JButton nextButton = new JButton();
110    JButton getButton = new JButton();
111    JButton throttleButton = new JButton();
112    JButton clearCancelButton = new JButton();
113    JButton saveLoadButton = new JButton();
114    JButton deleteButton = new JButton();
115    JButton backUpButton = new JButton();
116    JButton restoreButton = new JButton();
117
118    // check boxes
119    JCheckBox checkBoxEmpty = new JCheckBox();
120    JCheckBox checkBoxVerify = new JCheckBox();
121    JCheckBox checkBoxConsist = new JCheckBox();
122
123    // consist text field
124    JTextField consistTextField = new JTextField(4);
125
126    // labels
127    JLabel textLocomotive = new JLabel();
128    JLabel textRoster = new JLabel();
129    JLabel textAddress = new JLabel();
130    JLabel textAddrType = new JLabel();
131    JLabel textDirection = new JLabel();
132
133    JLabel textConRoster = new JLabel();
134    JLabel textConRoadName = new JLabel();
135    JLabel textConRoadNumber = new JLabel();
136    JLabel textConModel = new JLabel();
137
138    JComboBox<String> conRosterBox = nceConsistRoster.fullRosterComboBox();
139
140    // for padding out panel
141    JLabel space1 = new JLabel("            ");
142    JLabel space2 = new JLabel(" ");
143    JLabel space3a = new JLabel("                            ");
144    JLabel space3b = new JLabel("                            ");
145    JLabel space3c = new JLabel("                            ");
146    JLabel space3d = new JLabel("                            ");
147
148    JLabel space15 = new JLabel(" ");
149
150    // lead loco
151    JLabel textLoco1 = new JLabel();
152    JTextField locoTextField1 = new JTextField(4);
153    JComboBox<Object> locoRosterBox1 = new RosterEntryComboBox();
154    JButton adrButton1 = new JButton();
155    JButton cmdButton1 = new JButton();
156    JButton dirButton1 = new JButton();
157
158    // rear loco
159    JLabel textLoco2 = new JLabel();
160    JTextField locoTextField2 = new JTextField(4);
161    JComboBox<Object> locoRosterBox2 = new RosterEntryComboBox();
162    JButton adrButton2 = new JButton();
163    JButton cmdButton2 = new JButton();
164    JButton dirButton2 = new JButton();
165
166    // mid loco
167    JLabel textLoco3 = new JLabel();
168    JTextField locoTextField3 = new JTextField(4);
169    JComboBox<Object> locoRosterBox3 = new RosterEntryComboBox();
170    JButton adrButton3 = new JButton();
171    JButton cmdButton3 = new JButton();
172    JButton dirButton3 = new JButton();
173
174    // mid loco
175    JLabel textLoco4 = new JLabel();
176    JTextField locoTextField4 = new JTextField(4);
177    JComboBox<Object> locoRosterBox4 = new RosterEntryComboBox();
178    JButton adrButton4 = new JButton();
179    JButton cmdButton4 = new JButton();
180    JButton dirButton4 = new JButton();
181
182    // mid loco
183    JLabel textLoco5 = new JLabel();
184    JTextField locoTextField5 = new JTextField(4);
185    JComboBox<Object> locoRosterBox5 = new RosterEntryComboBox();
186    JButton adrButton5 = new JButton();
187    JButton cmdButton5 = new JButton();
188    JButton dirButton5 = new JButton();
189
190    // mid loco
191    JLabel textLoco6 = new JLabel();
192    JTextField locoTextField6 = new JTextField(4);
193    JComboBox<Object> locoRosterBox6 = new RosterEntryComboBox();
194    JButton adrButton6 = new JButton();
195    JButton cmdButton6 = new JButton();
196    JButton dirButton6 = new JButton();
197
198    private NceTrafficController tc = null;
199
200    public NceConsistEditPanel() {
201        super();
202    }
203
204    /**
205     * {@inheritDoc}
206     */
207    @Override
208    public void initContext(Object context) {
209        if (context instanceof NceSystemConnectionMemo) {
210            try {
211                initComponents((NceSystemConnectionMemo) context);
212            } catch (Exception e) {
213                log.error("NceConsistEdit initContext failed"); // NOI18N
214            }
215        }
216    }
217
218    /**
219     * {@inheritDoc}
220     */
221    @Override
222    public String getHelpTarget() {
223        return "package.jmri.jmrix.nce.consist.NceConsistEditFrame";
224    }
225
226    /**
227     * {@inheritDoc}
228     */
229    @Override
230    public String getTitle() {
231        StringBuilder x = new StringBuilder();
232        if (memo != null) {
233            x.append(memo.getUserName());
234        } else {
235            x.append("NCE_"); // NOI18N
236        }
237        x.append(": ");
238        x.append(Bundle.getMessage("NceConsistEditTitle"));
239        return x.toString();
240    }
241
242    /**
243     * {@inheritDoc}
244     */
245    @Override
246    @Nonnull
247    public List<JMenu> getMenus() {
248        // build menu
249        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
250        toolMenu.add(new NceConsistRosterMenu(Bundle.getMessage("RosterTitle"),
251                jmri.jmrit.roster.swing.RosterMenu.MAINMENU, this));
252        List<JMenu> l = new ArrayList<>();
253        l.add(toolMenu);
254        return l;
255    }
256
257    /**
258     * {@inheritDoc}
259     */
260    @Override
261    public void initComponents(NceSystemConnectionMemo m) {
262        this.memo = m;
263        this.tc = m.getNceTrafficController();
264        // the following code sets the frame's initial state
265
266        textConsist.setText(Bundle.getMessage("L_Consist"));
267
268        textStatus.setText(Bundle.getMessage("L_Status"));
269
270        consistStatus.setText(Bundle.getMessage("EditStateUNKNOWN"));
271
272        previousButton.setText(Bundle.getMessage("KeyPREVIOUS"));
273        previousButton.setToolTipText(Bundle.getMessage("ToolTipPrevious"));
274
275        nextButton.setText(Bundle.getMessage("KeyNEXT"));
276        nextButton.setToolTipText(Bundle.getMessage("ToolTipNext"));
277
278        getButton.setText(Bundle.getMessage("KeyGET"));
279        getButton.setToolTipText(Bundle.getMessage("ToolTipGet"));
280
281        consistTextField.setText(Integer.toString(CONSIST_MAX));
282        consistTextField.setToolTipText(MessageFormat.format(Bundle.getMessage("ToolTipConsist"), CONSIST_MIN, CONSIST_MAX));
283        consistTextField.setMaximumSize(new Dimension(consistTextField
284                .getMaximumSize().width,
285                consistTextField.getPreferredSize().height));
286
287        textLocomotive.setText(Bundle.getMessage("L_Loco"));
288        textRoster.setText(Bundle.getMessage("L_Roster"));
289        textAddress.setText(Bundle.getMessage("L_Address"));
290        textAddrType.setText(Bundle.getMessage("L_Type"));
291        textDirection.setText(Bundle.getMessage("L_Direction"));
292
293        textConRoster.setText(Bundle.getMessage("L_Consist"));
294        textConRoadName.setText("");
295        textConRoadNumber.setText("");
296        textConModel.setText("");
297
298        throttleButton.setText(Bundle.getMessage("L_Throttle"));
299        throttleButton.setEnabled(true);
300        throttleButton.setToolTipText(Bundle.getMessage("ToolTipThrottle"));
301
302        clearCancelButton.setText(Bundle.getMessage("KeyCLEAR"));
303        clearCancelButton.setEnabled(false);
304        clearCancelButton.setToolTipText(Bundle.getMessage("ToolTipClear"));
305
306        saveLoadButton.setText(Bundle.getMessage("KeySAVE"));
307        saveLoadButton.setVisible(false);
308        saveLoadButton.setEnabled(false);
309        saveLoadButton.setToolTipText(Bundle.getMessage("ToolTipSave"));
310
311        deleteButton.setText(Bundle.getMessage("KeyDELETE"));
312        deleteButton.setVisible(false);
313        deleteButton.setEnabled(false);
314        deleteButton.setToolTipText(Bundle.getMessage("ToolTipDelete"));
315
316        backUpButton.setText(Bundle.getMessage("KeyBACKUP"));
317        backUpButton.setToolTipText(Bundle.getMessage("ToolTipBackup"));
318
319        restoreButton.setText(Bundle.getMessage("KeyRESTORE"));
320        restoreButton.setToolTipText(Bundle.getMessage("ToolTipRestore"));
321
322        checkBoxEmpty.setText(Bundle.getMessage("KeyEMPTY"));
323        checkBoxEmpty.setToolTipText(Bundle.getMessage("ToolTipEmpty"));
324
325        checkBoxVerify.setText(Bundle.getMessage("KeyVERIFY"));
326        checkBoxVerify.setSelected(true);
327        checkBoxVerify.setToolTipText(Bundle.getMessage("ToolTipVerify"));
328
329        checkBoxConsist.setText(Bundle.getMessage("KeyCONSIST"));
330        checkBoxConsist.setSelected(true);
331        checkBoxConsist.setToolTipText(Bundle.getMessage("ToolTipConsistCkBox"));
332
333        initLocoFields();
334
335        setLayout(new GridBagLayout());
336
337        // Layout the panel by rows
338        // row 0
339        addItem(textConsist, 2, 0);
340        // row 1
341        addItem(previousButton, 1, 1);
342        addItem(consistTextField, 2, 1);
343        addItem(nextButton, 3, 1);
344        addItem(checkBoxEmpty, 5, 1);
345        // row 2
346        addItem(textStatus, 0, 2);
347        addItem(consistStatus, 1, 2);
348        addItem(getButton, 2, 2);
349        addItem(checkBoxVerify, 5, 2);
350        // row 3
351        addItem(space3a, 1, 3);
352        addItem(space3b, 2, 3);
353        addItem(space3c, 3, 3);
354        addItem(space3d, 4, 3);
355        // row 4
356        addItem(textConRoster, 1, 4);
357        // row 5
358        addItem(conRosterBox, 1, 5);
359        addItem(textConRoadName, 2, 5);
360        addItem(textConRoadNumber, 3, 5);
361        addItem(textConModel, 4, 5);
362        addItem(checkBoxConsist, 5, 5);
363        initConsistRoster(conRosterBox);
364
365        // row 6 padding for looks
366        addItem(space1, 1, 6);
367        // row 7 labels
368        addItem(textLocomotive, 0, 7);
369        addItem(textRoster, 1, 7);
370        addItem(textAddress, 2, 7);
371        addItem(textAddrType, 3, 7);
372        addItem(textDirection, 4, 7);
373
374        // row 8 Lead Locomotive
375        addLocoRow(textLoco1, locoRosterBox1, locoTextField1, adrButton1,
376                dirButton1, cmdButton1, 8);
377        // row 9 Rear Locomotive
378        addLocoRow(textLoco2, locoRosterBox2, locoTextField2, adrButton2,
379                dirButton2, cmdButton2, 9);
380        // row 10 Mid Locomotive
381        addLocoRow(textLoco3, locoRosterBox3, locoTextField3, adrButton3,
382                dirButton3, cmdButton3, 10);
383        // row 11 Mid Locomotive
384        addLocoRow(textLoco4, locoRosterBox4, locoTextField4, adrButton4,
385                dirButton4, cmdButton4, 11);
386        // row 12 Mid Locomotive
387        addLocoRow(textLoco5, locoRosterBox5, locoTextField5, adrButton5,
388                dirButton5, cmdButton5, 12);
389        // row 13 Mid Locomotive
390        addLocoRow(textLoco6, locoRosterBox6, locoTextField6, adrButton6,
391                dirButton6, cmdButton6, 13);
392
393        // row 15 padding for looks
394        addItem(space15, 2, 15);
395        // row 16
396        addItem(throttleButton, 0, 16);
397        addItem(clearCancelButton, 1, 16);
398        addItem(saveLoadButton, 2, 16);
399        addItem(deleteButton, 3, 16);
400        addItem(backUpButton, 4, 16);
401        addItem(restoreButton, 5, 16);
402
403        // setup buttons
404        addButtonAction(previousButton);
405        addButtonAction(nextButton);
406        addButtonAction(getButton);
407        addButtonAction(throttleButton);
408        addButtonAction(clearCancelButton);
409        addButtonAction(saveLoadButton);
410        addButtonAction(deleteButton);
411        addButtonAction(backUpButton);
412        addButtonAction(restoreButton);
413
414        // setup checkboxes
415        addCheckBoxAction(checkBoxConsist);
416        checkBoxConsist();
417    }
418
419    // Previous, Next, Get, Throttle, Clear/Cancel, Save/Load, Delete, Restore & Backup buttons
420    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
421        // if we're searching ignore user
422        if (consistSearchNext || consistSearchPrevious || locoSearch) {
423            return;
424        }
425        // throttle button
426        if (ae.getSource() == throttleButton) {
427            if (!validConsist()) {
428                return;
429            }
430            int locoAddr = validLocoAdr(locoTextField1.getText());
431            boolean isLong = (adrButton1.getText().equals(Bundle.getMessage("KeyLONG")));
432            if (locoAddr < 0) {
433                return;
434            }
435            consistNum = validConsist(consistTextField.getText());
436            jmri.jmrit.throttle.ThrottleFrame tf
437                    = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
438            tf.getAddressPanel().setAddress(consistNum, false); // use consist address
439            if (JOptionPane.showConfirmDialog(null,
440                    Bundle.getMessage("DIALOG_Funct2Lead"), Bundle.getMessage("DIALOG_NceThrottle"),
441                    JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
442                tf.getAddressPanel().setAddress(locoAddr, isLong);  // use lead loco address
443            }
444            tf.toFront();
445            return;
446        }
447        // clear or cancel button
448        if (ae.getSource() == clearCancelButton) {
449            // button can be Clear or Cancel
450            if (clearCancelButton.getText().equals(Bundle.getMessage("KeyCLEAR"))) {
451                updateRoster(Bundle.getMessage("CLEARED"));
452                // set refresh flag to update panel
453                refresh = true;
454                killConsist();
455
456                // must be cancel button
457            } else {
458                changeButtons(false);
459                consistNum = getConsist(); // reload panel
460            }
461        }
462
463        // save or load button
464        if (ae.getSource() == saveLoadButton) {
465            if (!validConsist()) {
466                return;
467            }
468            // check to see if user modified the roster
469            if (canLoad()) {
470                consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
471            } else {
472                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
473                saveLoadButton.setEnabled(false);
474                return;
475            }
476            enableAllLocoRows(false);
477            if (saveLoadButton.getText().equals(Bundle.getMessage("KeyLOAD"))) {
478                loadShift(); // get rid of empty mids!
479                updateRoster(consistTextField.getText());
480                consistNum = validConsist(consistTextField.getText());
481                // load right away or verify?
482                if (!verifyAllLocoAddr()) {
483                    fullLoad();
484                }
485            } else if (updateRoster(consistTextField.getText())) {
486                saveLoadButton.setEnabled(false);
487                consistNum = getConsist(); // reload panel
488            }
489            return;
490        }
491
492        // delete button
493        if (ae.getSource() == deleteButton) {
494            if (JOptionPane.showConfirmDialog(null,
495                    Bundle.getMessage("DIALOG_ConfirmDelete", Objects.requireNonNull(conRosterBox.getSelectedItem())),
496                    Bundle.getMessage("DIALOG_NceDelete"),
497                    JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) {
498                return;
499            }
500            deleteRoster();
501            changeButtons(false); // yes, clear delete button
502            return;
503        }
504        if (ae.getSource() == previousButton) {
505            consistSearchPrevious = true;
506            consistNum = getConsist(); // check for valid and kick off read
507        }
508        if (ae.getSource() == nextButton) {
509            consistSearchNext = true;
510            consistNum = getConsist(); // check for valid and kick off read
511        }
512        if (ae.getSource() == getButton) {
513            // Get Consist
514            consistNum = getConsist();
515        }
516        if (ae.getSource() == backUpButton) {
517            Thread mb = new NceConsistBackup(tc);
518            mb.setName("Consist Backup");
519            mb.start();
520        }
521        if (ae.getSource() == restoreButton) {
522            Thread mr = new NceConsistRestore(tc);
523            mr.setName("Consist Restore");
524            mr.start();
525        }
526    }
527
528    // One of six loco command buttons, add, replace or delete
529    public void buttonActionCmdPerformed(java.awt.event.ActionEvent ae) {
530        // if we're searching ignore user
531        if (consistSearchNext || consistSearchPrevious || locoSearch) {
532            return;
533        }
534        if (consistChanged()) {
535            return;
536        }
537        if (ae.getSource() == cmdButton1) {
538            modifyLocoFields(locoRosterBox1, locoTextField1, adrButton1,
539                    dirButton1, cmdButton1);
540        }
541        if (ae.getSource() == cmdButton2) {
542            modifyLocoFields(locoRosterBox2, locoTextField2, adrButton2,
543                    dirButton2, cmdButton2);
544        }
545        if (ae.getSource() == cmdButton3) {
546            modifyLocoFields(locoRosterBox3, locoTextField3, adrButton3,
547                    dirButton3, cmdButton3);
548        }
549        if (ae.getSource() == cmdButton4) {
550            modifyLocoFields(locoRosterBox4, locoTextField4, adrButton4,
551                    dirButton4, cmdButton4);
552        }
553        if (ae.getSource() == cmdButton5) {
554            modifyLocoFields(locoRosterBox5, locoTextField5, adrButton5,
555                    dirButton5, cmdButton5);
556        }
557        if (ae.getSource() == cmdButton6) {
558            modifyLocoFields(locoRosterBox6, locoTextField6, adrButton6,
559                    dirButton6, cmdButton6);
560        }
561        if (updateRoster(consistTextField.getText())) {
562            saveLoadButton.setEnabled(false);
563        }
564    }
565
566    // one of six loco address type buttons
567    public void buttonActionAdrPerformed(java.awt.event.ActionEvent ae) {
568        // if we're searching ignore user
569        if (consistSearchNext || consistSearchPrevious || locoSearch) {
570            return;
571        }
572        if (consistChanged()) {
573            return;
574        }
575        if (ae.getSource() == adrButton1) {
576            toggleAdrButton(locoTextField1, adrButton1);
577        }
578        if (ae.getSource() == adrButton2) {
579            toggleAdrButton(locoTextField2, adrButton2);
580        }
581        if (ae.getSource() == adrButton3) {
582            toggleAdrButton(locoTextField3, adrButton3);
583        }
584        if (ae.getSource() == adrButton4) {
585            toggleAdrButton(locoTextField4, adrButton4);
586        }
587        if (ae.getSource() == adrButton5) {
588            toggleAdrButton(locoTextField5, adrButton5);
589        }
590        if (ae.getSource() == adrButton6) {
591            toggleAdrButton(locoTextField6, adrButton6);
592        }
593    }
594
595    private void toggleAdrButton(JTextField locoTextField, JButton adrButton) {
596        if (validLocoAdr(locoTextField.getText()) < 0) {
597            return;
598        }
599        if (locoTextField.getText().equals("")) {
600            JOptionPane.showMessageDialog(this,
601                    Bundle.getMessage("DIALOG_EnterLocoB4AddrChg"),
602                    Bundle.getMessage("DIALOG_NceConsist"),
603                    JOptionPane.ERROR_MESSAGE);
604            return;
605        } else {
606            if (adrButton.getText().equals(Bundle.getMessage("KeyLONG"))) {
607                if ((Integer.parseInt(locoTextField.getText()) < 128)
608                        && (Integer.parseInt(locoTextField.getText()) > 0)) {
609                    adrButton.setText(Bundle.getMessage("KeySHORT"));
610                }
611            } else {
612                adrButton.setText(Bundle.getMessage("KeyLONG"));
613            }
614        }
615    }
616
617    // one of six loco direction buttons
618    public void buttonActionDirPerformed(java.awt.event.ActionEvent ae) {
619        // if we're searching ignore user
620        if (consistSearchNext || consistSearchPrevious || locoSearch) {
621            return;
622        }
623        if (consistChanged()) {
624            return;
625        }
626        if (ae.getSource() == dirButton1) {
627            toggleDirButton(locoTextField1, dirButton1, cmdButton1);
628        }
629        if (ae.getSource() == dirButton2) {
630            toggleDirButton(locoTextField2, dirButton2, cmdButton2);
631        }
632        if (ae.getSource() == dirButton3) {
633            toggleDirButton(locoTextField3, dirButton3, cmdButton3);
634        }
635        if (ae.getSource() == dirButton4) {
636            toggleDirButton(locoTextField4, dirButton4, cmdButton4);
637        }
638        if (ae.getSource() == dirButton5) {
639            toggleDirButton(locoTextField5, dirButton5, cmdButton5);
640        }
641        if (ae.getSource() == dirButton6) {
642            toggleDirButton(locoTextField6, dirButton6, cmdButton6);
643        }
644        saveLoadButton.setEnabled(canLoad());
645    }
646
647    private void toggleDirButton(JTextField locoTextField, JButton dirButton,
648            JButton cmdButton) {
649        if (validLocoAdr(locoTextField.getText()) < 0) {
650            return;
651        }
652        if (locoTextField.getText().equals("")) {
653            JOptionPane.showMessageDialog(this,
654                    Bundle.getMessage("DIALOG_EnterLocoB4DirChg"),
655                    Bundle.getMessage("DIALOG_NceConsist"), JOptionPane.ERROR_MESSAGE);
656            return;
657        }
658        cmdButton.setEnabled(true);
659        if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
660            dirButton.setText(Bundle.getMessage("KeyREV"));
661        } else {
662            dirButton.setText(Bundle.getMessage("KeyFWD"));
663        }
664    }
665
666    // one of six roster select, load loco number and address length
667    public void locoSelected(java.awt.event.ActionEvent ae) {
668        if (ae.getSource() == locoRosterBox1) {
669            rosterBoxSelect(locoRosterBox1, locoTextField1, adrButton1);
670        }
671        if (ae.getSource() == locoRosterBox2) {
672            rosterBoxSelect(locoRosterBox2, locoTextField2, adrButton2);
673        }
674        if (ae.getSource() == locoRosterBox3) {
675            rosterBoxSelect(locoRosterBox3, locoTextField3, adrButton3);
676        }
677        if (ae.getSource() == locoRosterBox4) {
678            rosterBoxSelect(locoRosterBox4, locoTextField4, adrButton4);
679        }
680        if (ae.getSource() == locoRosterBox5) {
681            rosterBoxSelect(locoRosterBox5, locoTextField5, adrButton5);
682        }
683        if (ae.getSource() == locoRosterBox6) {
684            rosterBoxSelect(locoRosterBox6, locoTextField6, adrButton6);
685        }
686    }
687
688    // load a loco from roster
689    private void rosterBoxSelect(JComboBox<Object> locoRosterBox,
690            JTextField locoTextField, JButton adrButton) {
691        RosterEntry entry = null;
692        Object o = locoRosterBox.getSelectedItem();
693        if (o.getClass().equals(RosterEntry.class)) {
694            entry = (RosterEntry) o;
695        }
696        if (entry != null) {
697            DccLocoAddress a = entry.getDccLocoAddress();
698
699            locoTextField.setText("" + a.getNumber());
700            if (a.isLongAddress()) {
701                adrButton.setText(Bundle.getMessage("KeyLONG"));
702            } else {
703                adrButton.setText(Bundle.getMessage("KeySHORT"));
704            }
705            // if lead loco get road number and name
706            if (locoRosterBox == locoRosterBox1) {
707                textConRoadName.setText(entry.getRoadName());
708                textConRoadNumber.setText(entry.getRoadNumber());
709                textConModel.setText(entry.getModel());
710            }
711        }
712    }
713
714    // load a consist from roster
715    public void consistRosterSelected(java.awt.event.ActionEvent ae) {
716        if (consistSearchNext || consistSearchPrevious || locoSearch) {
717            return;
718        }
719        String entry = "";
720        entry = conRosterBox.getSelectedItem().toString();
721        log.debug("load consist {} from roster ", entry);
722        if (entry.equals("")) {
723            changeButtons(false);
724            consistNum = getConsist(); // reload panel
725            return;
726        }
727        changeButtons(true);
728        loadRosterEntry(entry);
729    }
730
731    // checkbox modified
732    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
733        if (ae.getSource() == checkBoxConsist) {
734            checkBoxConsist();
735        }
736    }
737
738    private void checkBoxConsist() {
739        if (checkBoxConsist.isSelected()) {
740            conRosterBox.setEnabled(true);
741            saveLoadButton.setVisible(true);
742            saveLoadButton.setEnabled(canLoad());
743            deleteButton.setVisible(true);
744        } else {
745            conRosterBox.setEnabled(false);
746            conRosterBox.removeActionListener(consistRosterListener);
747            conRosterBox.setSelectedIndex(0);
748            conRosterBox.addActionListener(consistRosterListener);
749            saveLoadButton.setVisible(false);
750            saveLoadButton.setEnabled(false);
751            deleteButton.setVisible(false);
752            deleteButton.setEnabled(false);
753        }
754    }
755
756    // gets the user supplied consist number and then reads NCE CS memory
757    private int getConsist() {
758        newConsist = true;
759        int consistNumber = validConsist(consistTextField.getText());
760        if (consistNumber == CONSIST_ERROR) {
761            consistSearchPrevious = false;
762            consistSearchNext = false;
763            return consistNumber;
764        }
765        if (consistSearchNext || consistSearchPrevious) {
766            consistCount = 0; // used to determine if all 127 consist have been read
767            consistStatus.setText(Bundle.getMessage("EditStateSEARCH"));
768        } else {
769            consistStatus.setText(Bundle.getMessage("EditStateWAIT"));
770            if (consistNumber == consistNum) {
771                newConsist = false;
772            }
773        }
774
775        // if busy don't request
776        if (waiting > 0) {
777            return consistNumber;
778        }
779
780        if (consistSearchNext) {
781            readConsistMemory(consistNumber - 7, LEAD);
782        } else {
783            readConsistMemory(consistNumber, LEAD); // Get or Previous button
784        }
785        return consistNumber;
786    }
787
788    /**
789     * Check for valid consist, popup error message if not
790     *
791     * @return true if valid
792     */
793    private boolean validConsist() {
794        int consistNumber = validConsist(consistTextField.getText());
795        if (consistNumber == CONSIST_ERROR) {
796            consistStatus.setText(Bundle.getMessage("EditStateERROR"));
797            JOptionPane.showMessageDialog(this,
798                    MessageFormat.format(Bundle.getMessage("ToolTipConsist"), new Object[] {CONSIST_MIN, CONSIST_MAX}), Bundle.getMessage("DIALOG_NceConsist"),
799                    JOptionPane.ERROR_MESSAGE);
800            return false;
801        }
802        return true;
803    }
804
805    // Check for valid consist number, return number if valid, -1 or CONSIST_ERROR if not.
806    private int validConsist(String s) {
807        int consistNumber;
808        try {
809            consistNumber = Integer.parseInt(s);
810        } catch (NumberFormatException e) {
811            return CONSIST_ERROR;
812        }
813        if (consistNumber < CONSIST_MIN || consistNumber > CONSIST_MAX) {
814            return CONSIST_ERROR;
815        } else {
816            return consistNumber;
817        }
818    }
819
820    /**
821     * @param s loco address
822     * @return number if valid, -1 or ADDRESS_ERROR if not
823     */
824    private int validLocoAdr(String s) {
825        int locoAddress;
826        try {
827            locoAddress = Integer.parseInt(s);
828        } catch (NumberFormatException e) {
829            locoAddress = ADDRESS_ERROR;
830        }
831        if (locoAddress < LOC_ADR_MIN || locoAddress > LOC_ADR_MAX) {
832            JOptionPane.showMessageDialog(this,
833                    Bundle.getMessage("DIALOG_AddrRange"), Bundle.getMessage("DIALOG_NceConsist"),
834                    JOptionPane.ERROR_MESSAGE);
835            return ADDRESS_ERROR;
836        } else {
837            return locoAddress;
838        }
839    }
840
841    // check to see if user modified consist number
842    private boolean consistChanged() {
843        if (consistNum != validConsist(consistTextField.getText())) {
844            JOptionPane.showMessageDialog(this,
845                    Bundle.getMessage("DIALOG_PressRead", Bundle.getMessage("KeyGET")),
846                    Bundle.getMessage("DIALOG_NceConsist"),
847                    JOptionPane.ERROR_MESSAGE);
848            return true;
849        } else {
850            newConsist = false;
851            return false;
852        }
853    }
854
855    /**
856     * Reads 16 bytes of NCE consist memory based on consist number and loco
857     * position in the consist 0=lead 1=rear 2=mid
858     */
859    private void readConsistMemory(int consistNum, int engPosition) {
860        locoPosition = engPosition;
861        int nceMemAddr = (consistNum * 2) + NceCmdStationMemory.CabMemorySerial.CS_CONSIST_MEM;
862        if (locoPosition == REAR) {
863            nceMemAddr = (consistNum * 2) + NceCmdStationMemory.CabMemorySerial.CS_CON_MEM_REAR;
864        }
865        if (locoPosition == MID) {
866            nceMemAddr = (consistNum * 8) + NceCmdStationMemory.CabMemorySerial.CS_CON_MEM_MID;
867        }
868        log.debug("Read consist ({}) position ({}) NCE memory address ({})", consistNum, engPosition, Integer.toHexString(nceMemAddr));
869        byte[] bl = NceBinaryCommand.accMemoryRead(nceMemAddr);
870        sendNceMessage(bl, NceMessage.REPLY_16);
871    }
872
873    NceConsistRosterEntry nceConsistRosterEntry;
874
875    private void loadRosterEntry(String entry) {
876        nceConsistRosterEntry = nceConsistRoster.entryFromTitle(entry);
877        consistTextField.setText(nceConsistRosterEntry.getConsistNumber());
878        int cNum = validConsist(nceConsistRosterEntry.getConsistNumber());
879
880        if (0 < cNum) {
881            log.debug("verify consist matches roster selection");
882            verifyRosterMatch = true;
883            consistNum = getConsist();
884        } else {
885            if (nceConsistRosterEntry.getConsistNumber().equals(Bundle.getMessage("CLEARED")) || nceConsistRosterEntry.getConsistNumber().equals("0")) {
886                log.debug("search for empty consist");
887                consistTextField.setText(Integer.toString(CONSIST_MAX));
888                emptyConsistSearch = true;
889                consistSearchNext = true;
890                consistNum = getConsist();
891                loadFullRoster(nceConsistRosterEntry);
892                saveLoadButton.setEnabled(false);
893            } else {
894                log.error("roster consist number is out of range: {}", consistNum);
895                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
896            }
897        }
898    }
899
900    private void loadFullRoster(NceConsistRosterEntry nceConsistRosterEntry) {
901        // get road name, number and model
902        textConRoadName.setText(nceConsistRosterEntry.getRoadName());
903        textConRoadNumber.setText(nceConsistRosterEntry.getRoadNumber());
904        textConModel.setText(nceConsistRosterEntry.getModel());
905
906        // load lead loco
907        locoTextField1.setText(nceConsistRosterEntry.getLoco1DccAddress());
908        adrButton1.setText(nceConsistRosterEntry.isLoco1LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
909        dirButton1.setText(convertDTD(nceConsistRosterEntry.getLoco1Direction()));
910        locoRosterBox1.setEnabled(true);
911        locoTextField1.setEnabled(true);
912        adrButton1.setEnabled(true);
913        dirButton1.setEnabled(true);
914
915        // load rear loco
916        locoTextField2.setText(nceConsistRosterEntry.getLoco2DccAddress());
917        adrButton2.setText(nceConsistRosterEntry.isLoco2LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
918        dirButton2.setText(convertDTD(nceConsistRosterEntry.getLoco2Direction()));
919        locoRosterBox2.setEnabled(true);
920        locoTextField2.setEnabled(true);
921        adrButton2.setEnabled(true);
922        dirButton2.setEnabled(true);
923
924        // load Mid1 loco
925        locoTextField3.setText(nceConsistRosterEntry.getLoco3DccAddress());
926        adrButton3.setText(nceConsistRosterEntry.isLoco3LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
927        dirButton3.setText(convertDTD(nceConsistRosterEntry.getLoco3Direction()));
928        locoRosterBox3.setEnabled(true);
929        locoTextField3.setEnabled(true);
930        adrButton3.setEnabled(true);
931        dirButton3.setEnabled(true);
932
933        // load Mid2 loco
934        locoTextField4.setText(nceConsistRosterEntry.getLoco4DccAddress());
935        adrButton4.setText(nceConsistRosterEntry.isLoco4LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
936        dirButton4.setText(convertDTD(nceConsistRosterEntry.getLoco4Direction()));
937        locoRosterBox4.setEnabled(true);
938        locoTextField4.setEnabled(true);
939        adrButton4.setEnabled(true);
940        dirButton4.setEnabled(true);
941
942        // load Mid3 loco
943        locoTextField5.setText(nceConsistRosterEntry.getLoco5DccAddress());
944        adrButton5.setText(nceConsistRosterEntry.isLoco5LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
945        dirButton5.setText(convertDTD(nceConsistRosterEntry.getLoco5Direction()));
946        locoRosterBox5.setEnabled(true);
947        locoTextField5.setEnabled(true);
948        adrButton5.setEnabled(true);
949        dirButton5.setEnabled(true);
950
951        // load Mid4 loco
952        locoTextField6.setText(nceConsistRosterEntry.getLoco6DccAddress());
953        adrButton6.setText(nceConsistRosterEntry.isLoco6LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
954        dirButton6.setText(convertDTD(nceConsistRosterEntry.getLoco6Direction()));
955        locoRosterBox6.setEnabled(true);
956        locoTextField6.setEnabled(true);
957        adrButton6.setEnabled(true);
958        dirButton6.setEnabled(true);
959    }
960
961    /**
962     * checks to see if all loco addresses in NCE consist match roster updates
963     * road name, road number, and loco direction fields
964     *
965     * @return true if match
966     */
967    private boolean consistRosterMatch(NceConsistRosterEntry nceConsistRosterEntry) {
968        if (consistTextField.getText().equals(nceConsistRosterEntry.getConsistNumber())
969                && locoTextField1.getText().equals(nceConsistRosterEntry.getLoco1DccAddress())
970                && locoTextField2.getText().equals(nceConsistRosterEntry.getLoco2DccAddress())
971                && locoTextField3.getText().equals(nceConsistRosterEntry.getLoco3DccAddress())
972                && locoTextField4.getText().equals(nceConsistRosterEntry.getLoco4DccAddress())
973                && locoTextField5.getText().equals(nceConsistRosterEntry.getLoco5DccAddress())
974                && locoTextField6.getText().equals(nceConsistRosterEntry.getLoco6DccAddress())) {
975            // match!  Only load the elements needed
976            if (newConsist) {
977                textConRoadName.setText(nceConsistRosterEntry.getRoadName());
978                textConRoadNumber.setText(nceConsistRosterEntry.getRoadNumber());
979                textConModel.setText(nceConsistRosterEntry.getModel());
980                dirButton1.setText(convertDTD(nceConsistRosterEntry.getLoco1Direction()));
981                dirButton2.setText(convertDTD(nceConsistRosterEntry.getLoco2Direction()));
982                dirButton3.setText(convertDTD(nceConsistRosterEntry.getLoco3Direction()));
983                dirButton4.setText(convertDTD(nceConsistRosterEntry.getLoco4Direction()));
984                dirButton5.setText(convertDTD(nceConsistRosterEntry.getLoco5Direction()));
985                dirButton6.setText(convertDTD(nceConsistRosterEntry.getLoco6Direction()));
986            }
987            return true;
988        } else {
989            return false;
990        }
991    }
992
993    private final boolean enablePartialMatch = true;
994
995    /**
996     * checks to see if some loco addresses in NCE consist match roster updates
997     * road name, road number, and loco direction fields
998     *
999     * @return true if there was at least one match
1000     */
1001    private boolean consistRosterPartialMatch(NceConsistRosterEntry cre) {
1002        if (!enablePartialMatch) {
1003            return false;
1004        }
1005        // does loco1 match?
1006        if (consistTextField.getText().equals(cre.getConsistNumber())
1007                && locoTextField1.getText().equals(cre.getLoco1DccAddress())) {
1008            dirButton1.setText(convertDTD(cre.getLoco1Direction()));
1009            textConRoadName.setText(cre.getRoadName());
1010            textConRoadNumber.setText(cre.getRoadNumber());
1011            textConModel.setText(cre.getModel());
1012        } else {
1013            consistStatus.setText(Bundle.getMessage("EditStateUNKNOWN"));
1014            return false;
1015        }
1016        if (locoTextField2.getText().equals(cre.getLoco2DccAddress())) {
1017            dirButton2.setText(convertDTD(cre.getLoco2Direction()));
1018        }
1019        if (locoTextField3.getText().equals(cre.getLoco3DccAddress())) {
1020            dirButton3.setText(convertDTD(cre.getLoco3Direction()));
1021        }
1022        if (locoTextField4.getText().equals(cre.getLoco4DccAddress())) {
1023            dirButton4.setText(convertDTD(cre.getLoco4Direction()));
1024        }
1025        if (locoTextField5.getText().equals(cre.getLoco5DccAddress())) {
1026            dirButton5.setText(convertDTD(cre.getLoco5Direction()));
1027        }
1028        if (locoTextField6.getText().equals(cre.getLoco6DccAddress())) {
1029            dirButton6.setText(convertDTD(cre.getLoco6Direction()));
1030        }
1031        consistStatus.setText(Bundle.getMessage("EditStateMODIFIED"));
1032        return true;
1033    }
1034
1035    protected List<NceConsistRosterEntry> consistList = new ArrayList<>();
1036
1037    /**
1038     * returns true if update successful
1039     */
1040    private boolean updateRoster(String consistNumber) {
1041        if (!checkBoxConsist.isSelected()) {
1042            return false;
1043        }
1044        String id = locoTextField1.getText(); // lead loco is the consist id
1045        if (id.equals("")) {
1046            log.debug("Attempt to modify consist without valid id");
1047            return false;
1048        }
1049        // need rear loco to form a consist
1050        if (locoTextField2.getText().equals("")) {
1051            return false;
1052        }
1053        NceConsistRosterEntry nceConsistRosterEntry;
1054        consistList = nceConsistRoster.matchingList(null, null,
1055                null, null, null, null, null, null, null, id);
1056        // if consist doesn't exist in roster ask user if they want to create one
1057        if (consistList.isEmpty()) {
1058            if (JOptionPane.showConfirmDialog(null, Bundle.getMessage("DIALOG_ConfirmAdd", id),
1059                    Bundle.getMessage("DIALOG_NceSave"),
1060                    JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
1061                return false;
1062            }
1063            nceConsistRosterEntry = new NceConsistRosterEntry();
1064            nceConsistRoster.addEntry(nceConsistRosterEntry);
1065            // roster entry exists, does it match?
1066        } else {
1067            nceConsistRosterEntry = nceConsistRoster.entryFromTitle(id);
1068            // if all of the loco addresses match, just update without telling user
1069            consistList = nceConsistRoster
1070                    .matchingList(null, null, null, locoTextField1.getText(),
1071                            locoTextField2.getText(), locoTextField3.getText(),
1072                            locoTextField4.getText(), locoTextField5.getText(),
1073                            locoTextField6.getText(), id);
1074            // if it doesn't match, do we want to modify it?
1075            if (consistList.isEmpty()) {
1076                if (JOptionPane.showConfirmDialog(null,
1077                        Bundle.getMessage("DIALOG_ConfirmUpdate", id, getRosterText(nceConsistRosterEntry)),
1078                        Bundle.getMessage("DIALOG_NceUpdate"),
1079                        JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
1080                    // update consist if command was to clear
1081                    if (consistNumber.equals(Bundle.getMessage("CLEARED"))) {
1082                        nceConsistRosterEntry.setConsistNumber(consistNumber);
1083                        writeRosterFile();
1084                    }
1085                    return false;
1086                }
1087            }
1088            log.debug("Modify consist {}", id);
1089        }
1090        // save all elements of a consist roster
1091        nceConsistRosterEntry.setId(id);
1092        nceConsistRosterEntry.setConsistNumber(consistNumber);
1093        nceConsistRosterEntry.setRoadName(textConRoadName.getText());
1094        nceConsistRosterEntry.setRoadNumber(textConRoadNumber.getText());
1095        nceConsistRosterEntry.setModel(textConModel.getText());
1096        // save lead loco
1097        nceConsistRosterEntry.setLoco1DccAddress(locoTextField1.getText());
1098        nceConsistRosterEntry.setLoco1LongAddress(adrButton1.getText().equals(Bundle.getMessage("KeyLONG")));
1099        nceConsistRosterEntry.setLoco1Direction(directionDTD(dirButton1));
1100        // save rear loco
1101        nceConsistRosterEntry.setLoco2DccAddress(locoTextField2.getText());
1102        nceConsistRosterEntry.setLoco2LongAddress(adrButton2.getText().equals(Bundle.getMessage("KeyLONG")));
1103        nceConsistRosterEntry.setLoco2Direction(directionDTD(dirButton2));
1104        // save Mid1 loco
1105        nceConsistRosterEntry.setLoco3DccAddress(locoTextField3.getText());
1106        nceConsistRosterEntry.setLoco3LongAddress(adrButton3.getText().equals(Bundle.getMessage("KeyLONG")));
1107        nceConsistRosterEntry.setLoco3Direction(directionDTD(dirButton3));
1108        // save Mid2 loco
1109        nceConsistRosterEntry.setLoco4DccAddress(locoTextField4.getText());
1110        nceConsistRosterEntry.setLoco4LongAddress(adrButton4.getText().equals(Bundle.getMessage("KeyLONG")));
1111        nceConsistRosterEntry.setLoco4Direction(directionDTD(dirButton4));
1112        // save Mid3 loco
1113        nceConsistRosterEntry.setLoco5DccAddress(locoTextField5.getText());
1114        nceConsistRosterEntry.setLoco5LongAddress(adrButton5.getText().equals(Bundle.getMessage("KeyLONG")));
1115        nceConsistRosterEntry.setLoco5Direction(directionDTD(dirButton5));
1116        // save Mid4 loco
1117        nceConsistRosterEntry.setLoco6DccAddress(locoTextField6.getText());
1118        nceConsistRosterEntry.setLoco6LongAddress(adrButton6.getText().equals(Bundle.getMessage("KeyLONG")));
1119        nceConsistRosterEntry.setLoco6Direction(directionDTD(dirButton6));
1120
1121        writeRosterFile();
1122        return true;
1123    }
1124
1125    /**
1126     * @return DTD direction format based on the loco direction button
1127     */
1128    private String directionDTD(JButton dirButton) {
1129        String formatDTD = Bundle.getMessage("DTD_UNKNOWN");
1130        if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1131            formatDTD = Bundle.getMessage("DTD_FORWARD");
1132        }
1133        if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
1134            formatDTD = Bundle.getMessage("DTD_REVERSE");
1135        }
1136        return formatDTD;
1137    }
1138
1139    /**
1140     * @return converts DTD direction to FWD, REV, and ??
1141     */
1142    private String convertDTD(String formatDTD) {
1143        String word = Bundle.getMessage("KeyQUESTION");
1144        if (formatDTD.equals(Bundle.getMessage("DTD_FORWARD"))) {
1145            word = Bundle.getMessage("KeyFWD");
1146        }
1147        if (formatDTD.equals(Bundle.getMessage("DTD_REVERSE"))) {
1148            word = Bundle.getMessage("KeyREV");
1149        }
1150        return word;
1151    }
1152
1153    /**
1154     * @return converts DTD direction to FWD, REV, and ""
1155     */
1156    private String shortHandConvertDTD(String formatDTD) {
1157        String word = "";
1158        if (formatDTD.equals(Bundle.getMessage("DTD_FORWARD"))) {
1159            word = Bundle.getMessage("KeyFWD");
1160        }
1161        if (formatDTD.equals(Bundle.getMessage("DTD_REVERSE"))) {
1162            word = Bundle.getMessage("KeyREV");
1163        }
1164        return word;
1165    }
1166
1167    // remove selected consist from roster
1168    private void deleteRoster() {
1169        String entry = conRosterBox.getSelectedItem().toString();
1170        log.debug("remove consist {} from roster ", entry);
1171        // delete it from roster
1172        nceConsistRoster.removeEntry(nceConsistRoster.entryFromTitle(entry));
1173        writeRosterFile();
1174    }
1175
1176    private void writeRosterFile() {
1177        conRosterBox.removeActionListener(consistRosterListener);
1178        nceConsistRoster.writeRosterFile();
1179        nceConsistRoster.updateComboBox(conRosterBox);
1180        conRosterBox.insertItemAt("", 0);
1181        conRosterBox.setSelectedIndex(0);
1182        conRosterBox.addActionListener(consistRosterListener);
1183    }
1184
1185    // can the consist be loaded into NCE memory?
1186    private boolean canLoad() {
1187        if (locoTextField1.getText().equals("")) {
1188            return false;
1189        }
1190        if (dirButton1.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1191            return false;
1192        }
1193        if (locoTextField2.getText().equals("")) {
1194            return false;
1195        }
1196        if (dirButton2.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1197            return false;
1198        }
1199        if (!locoTextField3.getText().equals("")
1200                && dirButton3.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1201            return false;
1202        }
1203        if (!locoTextField4.getText().equals("")
1204                && dirButton4.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1205            return false;
1206        }
1207        if (!locoTextField5.getText().equals("")
1208                && dirButton5.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1209            return false;
1210        }
1211        if (!locoTextField6.getText().equals("")
1212                && dirButton6.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1213            return false;
1214        }
1215        // okay to load, clean up empty loco fields
1216        if (locoTextField3.getText().equals("")) {
1217            dirButton3.setText(Bundle.getMessage("KeyQUESTION"));
1218        }
1219        if (locoTextField4.getText().equals("")) {
1220            dirButton4.setText(Bundle.getMessage("KeyQUESTION"));
1221        }
1222        if (locoTextField5.getText().equals("")) {
1223            dirButton5.setText(Bundle.getMessage("KeyQUESTION"));
1224        }
1225        if (locoTextField6.getText().equals("")) {
1226            dirButton6.setText(Bundle.getMessage("KeyQUESTION"));
1227        }
1228        if (saveLoadButton.getText().equals(Bundle.getMessage("KeyLOAD"))) {
1229            return true;
1230        } else if (exactMatch) {
1231            return false; // no need to save, exact match!
1232        } else {
1233            return true;
1234        }
1235    }
1236
1237    // mimic NCE mid loco shift when there's empties
1238    private void loadShift() {
1239        for (int i = 0; i < 3; i++) {
1240            shiftOneLine(locoTextField5, adrButton5, dirButton5, locoTextField6,
1241                    adrButton6, dirButton6);
1242            shiftOneLine(locoTextField4, adrButton4, dirButton4, locoTextField5,
1243                    adrButton5, dirButton5);
1244            shiftOneLine(locoTextField3, adrButton3, dirButton3, locoTextField4,
1245                    adrButton4, dirButton4);
1246            shiftOneLine(locoTextField2, adrButton2, dirButton2, locoTextField3,
1247                    adrButton3, dirButton3);
1248        }
1249    }
1250
1251    private void shiftOneLine(JTextField locoTextFieldLow, JButton adrButtonLow,
1252            JButton dirButtonLow, JTextField locoTextFieldHigh,
1253            JButton adrButtonHigh, JButton dirButtonHigh) {
1254        if (locoTextFieldLow.getText().equals("") && !locoTextFieldHigh.getText().equals((""))) {
1255            locoTextFieldLow.setText(locoTextFieldHigh.getText());
1256            adrButtonLow.setText(adrButtonHigh.getText());
1257            dirButtonLow.setText(dirButtonHigh.getText());
1258            dirButtonHigh.setText(Bundle.getMessage("KeyQUESTION"));
1259            locoTextFieldHigh.setText("");
1260        } else {
1261            return;
1262        }
1263    }
1264
1265    // change button operation during load consist from roster
1266    private void changeButtons(boolean rosterDisplay) {
1267        if (rosterDisplay) {
1268            clearCancelButton.setText(Bundle.getMessage("KeyCANCEL"));
1269            clearCancelButton.setToolTipText(Bundle.getMessage("ToolTipCancel"));
1270            clearCancelButton.setEnabled(true);
1271            saveLoadButton.setText(Bundle.getMessage("KeyLOAD"));
1272            saveLoadButton.setToolTipText(Bundle.getMessage("ToolTipLoad"));
1273        } else {
1274            clearCancelButton.setText(Bundle.getMessage("KeyCLEAR"));
1275            clearCancelButton.setToolTipText(Bundle.getMessage("ToolTipClear"));
1276            saveLoadButton.setText(Bundle.getMessage("KeySAVE"));
1277            saveLoadButton.setToolTipText(Bundle.getMessage("ToolTipSave"));
1278            clearCancelButton.setEnabled(!locoTextField1.getText().equals(""));
1279        }
1280
1281        // toggle (on if we're loading a consist from roster)
1282        deleteButton.setEnabled(rosterDisplay);
1283
1284        // toggle (off if we're loading a consist from roster)
1285        previousButton.setEnabled(!rosterDisplay);
1286        nextButton.setEnabled(!rosterDisplay);
1287        getButton.setEnabled(!rosterDisplay);
1288        backUpButton.setEnabled(!rosterDisplay);
1289        restoreButton.setEnabled(!rosterDisplay);
1290        saveLoadButton.setEnabled(!rosterDisplay);
1291
1292        cmdButton1.setVisible(!rosterDisplay);
1293        cmdButton2.setVisible(!rosterDisplay);
1294        cmdButton3.setVisible(!rosterDisplay);
1295        cmdButton4.setVisible(!rosterDisplay);
1296        cmdButton5.setVisible(!rosterDisplay);
1297        cmdButton6.setVisible(!rosterDisplay);
1298    }
1299
1300    /**
1301     * Kills consist using lead loco address
1302     */
1303    private void killConsist() {
1304        if (validLocoAdr(locoTextField1.getText()) < 0) // special case where lead or rear loco was being replaced
1305        {
1306            return;
1307        }
1308        int locoAddr = getLocoAddr(locoTextField1, adrButton1);
1309        sendNceBinaryCommand(locoAddr, NceMessage.LOCO_CMD_KILL_CONSIST,
1310                (byte) 0);
1311    }
1312
1313    private void sendNceBinaryCommand(int locoAddr, byte nceLocoCmd,
1314            byte consistNumber) {
1315        byte[] bl = NceBinaryCommand.nceLocoCmd(locoAddr, nceLocoCmd,
1316                consistNumber);
1317        sendNceMessage(bl, NceMessage.REPLY_1);
1318    }
1319
1320    @Override
1321    public void message(NceMessage m) {
1322    } // ignore replies
1323
1324    // NCE CS response from add, delete, save, get, next, previous, etc
1325    // A single byte response is expected from commands
1326    // A 16 byte response is expected when loading a consist or searching
1327    @Override
1328    public void reply(NceReply nceReply) {
1329        if (waiting <= 0) {
1330            log.error("unexpected response");
1331            return;
1332        }
1333        waiting--;
1334
1335        if (nceReply.getNumDataElements() != replyLen) {
1336            consistStatus.setText(Bundle.getMessage("EditStateERROR"));
1337            log.error("reply length error, expecting: {} got: {}", replyLen, nceReply.getNumDataElements());
1338            return;
1339        }
1340
1341        // response to commands
1342        if (replyLen == NceMessage.REPLY_1) {
1343            // Looking for proper response
1344            int recChar = nceReply.getElement(0);
1345            log.debug("command reply: {}", recChar);
1346            if (recChar == NceMessage.NCE_OKAY) {
1347                if (locoSearch && waiting == 0) {
1348                    readConsistMemory(consistNumVerify, LEAD);
1349                    consistStatus.setText(Bundle.getMessage("EditStateVERIFY"));
1350                    return;
1351                }
1352                if (refresh && waiting == 0) {
1353                    refresh = false;
1354                    // update panel
1355                    readConsistMemory(consistNum, LEAD);
1356                    return;
1357                }
1358                consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
1359            } else {
1360                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
1361            }
1362            return;
1363        }
1364
1365        // Consist memory read
1366        if (replyLen == NceMessage.REPLY_16) {
1367            // are we verifying that loco isn't already part of a consist?
1368            if (locoSearch) {
1369                // search the 16 bytes for a loco match
1370                for (int i = 0; i < NceMessage.REPLY_16;) {
1371                    int recChar_High = nceReply.getElement(i++);
1372                    recChar_High = (recChar_High << 8) & 0xFF00;
1373                    int recChar_Low = nceReply.getElement(i++);
1374                    recChar_Low = recChar_Low & 0xFF;
1375                    int locoAddress = recChar_High + recChar_Low;
1376                    // does it match any of the locos?
1377                    for (int j = 0; j < locoVerifyList.length; j++) {
1378                        if (locoVerifyList[j] == 0) {
1379                            break;  // done searching
1380                        }
1381                        if (locoAddress == locoVerifyList[j]) {
1382                            // ignore matching the consist that we're adding the
1383                            // loco
1384                            if (consistNumVerify != consistNum) {
1385                                locoSearch = false; // quit the search
1386                                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
1387                                locoNumInUse = locoAddress & 0x3FFF;
1388                                queueError(ERROR_LOCO_IN_USE);
1389                                return;
1390                            }
1391                        }
1392                    }
1393                    consistNumVerify++;
1394                }
1395                if (consistNumVerify > CONSIST_MAX) {
1396                    if (locoPosition == LEAD) {
1397                        // now verify the rear loco consist
1398                        locoPosition = REAR;
1399                        consistNumVerify = 0;
1400                    } else {
1401                        // verify complete, loco address is unique
1402                        locoSearch = false;
1403                        consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
1404                        // determine the type of verification
1405                        if (verifyType == VERIFY_LEAD_REAR) {
1406                            if (refresh && waiting == 0) {
1407                                refresh = false;
1408                                // update panel
1409                                readConsistMemory(consistNum, LEAD);
1410                            }
1411                        } else if (verifyType == VERIFY_MID_FWD) {
1412                            sendNceBinaryCommand(locoVerifyList[0],
1413                                    NceMessage.LOCO_CMD_FWD_CONSIST_MID,
1414                                    (byte) consistNum);
1415                        } else if (verifyType == VERIFY_MID_REV) {
1416                            sendNceBinaryCommand(locoVerifyList[0],
1417                                    NceMessage.LOCO_CMD_REV_CONSIST_MID,
1418                                    (byte) consistNum);
1419                        } else if (verifyType == VERIFY_ALL) {
1420                            fullLoad();
1421                        } else {
1422                            log.debug("verifyType out of range");
1423                        }
1424                        verifyType = VERIFY_DONE;
1425                        return;
1426                    }
1427                }
1428                // continue verify
1429                readConsistMemory(consistNumVerify, locoPosition);
1430                return;
1431            }
1432            // are we searching for a consist?
1433            if (consistSearchNext) {
1434                for (int i = NceMessage.REPLY_16 - 1; i > 0;) {
1435                    int recChar_Low = nceReply.getElement(i--);
1436                    recChar_Low = recChar_Low & 0xFF;
1437                    int recChar_High = nceReply.getElement(i--);
1438                    recChar_High = (recChar_High << 8) & 0xFF00;
1439                    int locoAddress = recChar_High + recChar_Low;
1440
1441                    if (emptyConsistSearch) {
1442                        if (locoAddress == 0) {
1443                            // found an empty consist!
1444                            consistSearchNext = false;
1445                            emptyConsistSearch = false;
1446                            consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
1447                            saveLoadButton.setEnabled(canLoad());
1448                            return;
1449                        }
1450                    } else if (checkBoxEmpty.isSelected()) {
1451                        if (locoAddress == 0 && consistCount > 0) {
1452                            // found an empty consist!
1453                            log.debug("Empty consist ({})", consistNum);
1454                            consistSearchNext = false;
1455                            // update the panel
1456                            readConsistMemory(consistNum, LEAD);
1457                            return;
1458                        }
1459                    } else if (locoAddress != 0 && consistCount > 0) {
1460                        // found a consist!
1461                        consistSearchNext = false;
1462                        readConsistMemory(consistNum, LEAD);
1463                        return;
1464                    }
1465                    if (++consistCount > CONSIST_MAX) {
1466                        // could not find a consist
1467                        consistSearchNext = false;
1468                        consistStatus.setText(Bundle.getMessage("EditStateNONE"));
1469                        if (emptyConsistSearch) {
1470                            emptyConsistSearch = false;
1471                            queueError(ERROR_NO_EMPTY_CONSIST);
1472                        }
1473                        return;  // don't update panel
1474                    }
1475                    // look for next consist
1476                    consistNum--;
1477                    if (consistNum < CONSIST_MIN) {
1478                        consistNum = CONSIST_MAX;
1479                    }
1480                    consistTextField.setText(Integer.toString(consistNum));
1481                    if (consistNum == CONSIST_MAX) {
1482                        // we need to read NCE memory to continue
1483                        break;
1484                    }
1485                }
1486                // continue searching
1487                readConsistMemory(consistNum - 7, LEAD);
1488                return;
1489            }
1490            // are we searching?
1491            if (consistSearchPrevious) {
1492                for (int i = 0; i < NceMessage.REPLY_16;) {
1493                    int recChar_High = nceReply.getElement(i++);
1494                    recChar_High = (recChar_High << 8) & 0xFF00;
1495                    int recChar_Low = nceReply.getElement(i++);
1496                    recChar_Low = recChar_Low & 0xFF;
1497                    int locoAddress = recChar_High + recChar_Low;
1498
1499                    if (checkBoxEmpty.isSelected()) {
1500                        if (locoAddress == 0 && consistCount > 0) {
1501                            consistSearchPrevious = false;
1502                            break;
1503                        }
1504                    } else if (locoAddress != 0 && consistCount > 0) {
1505                            consistSearchPrevious = false;
1506                            break;
1507                    }
1508                    if (++consistCount > CONSIST_MAX) {
1509                        consistStatus.setText(Bundle.getMessage("EditStateNONE"));
1510                        consistSearchPrevious = false;
1511                        return;  // don't update the panel
1512                    }
1513                    consistNum++;
1514                    if (consistNum > CONSIST_MAX) {
1515                        consistNum = CONSIST_MIN;
1516                    }
1517                    consistTextField.setText(Integer.toString(consistNum));
1518                    // have we wrapped? if yes, need to read NCE memory
1519                    if (consistNum == CONSIST_MIN) {
1520                        break;
1521                    }
1522                }
1523                readConsistMemory(consistNum, LEAD);
1524                return;
1525            }
1526
1527            // Panel update, load lead loco
1528            if (locoPosition == LEAD) {
1529                boolean loco1exists = updateLocoFields(nceReply, 0, locoRosterBox1,
1530                        locoTextField1, adrButton1, dirButton1, cmdButton1);
1531                if (clearCancelButton.getText().equals(Bundle.getMessage("KeyCLEAR"))) {
1532                    clearCancelButton.setEnabled(loco1exists);
1533                }
1534
1535                // load rear loco
1536            } else if (locoPosition == REAR) {
1537                updateLocoFields(nceReply, 0, locoRosterBox2, locoTextField2,
1538                        adrButton2, dirButton2, cmdButton2);
1539
1540                // load mid locos
1541            } else {
1542                updateLocoFields(nceReply, 0, locoRosterBox3, locoTextField3,
1543                        adrButton3, dirButton3, cmdButton3);
1544                updateLocoFields(nceReply, 2, locoRosterBox4, locoTextField4,
1545                        adrButton4, dirButton4, cmdButton4);
1546                updateLocoFields(nceReply, 4, locoRosterBox5, locoTextField5,
1547                        adrButton5, dirButton5, cmdButton5);
1548                updateLocoFields(nceReply, 6, locoRosterBox6, locoTextField6,
1549                        adrButton6, dirButton6, cmdButton6);
1550                consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
1551                checkForRosterMatch();
1552                saveLoadButton.setEnabled(canLoad());
1553            }
1554            // read the next loco number in the consist
1555            if (locoPosition == LEAD || locoPosition == REAR) {
1556                locoPosition++;
1557                readConsistMemory(consistNum, locoPosition);
1558            }
1559        }
1560    }
1561
1562    private boolean exactMatch = false;
1563
1564    private void checkForRosterMatch() {
1565        exactMatch = false;
1566        if (!verifyRosterMatch) {
1567            nceConsistRosterEntry = nceConsistRoster.entryFromTitle(locoTextField1.getText());
1568        }
1569        if (nceConsistRosterEntry == null) {
1570            if (checkBoxConsist.isSelected() && !locoTextField1.getText().equals("")) {
1571                consistStatus.setText(Bundle.getMessage("EditStateUNKNOWN"));
1572            } else {
1573                textConRoadName.setText("");
1574            }
1575            textConRoadNumber.setText("");
1576            textConModel.setText("");
1577            return;
1578        }
1579        if (consistRosterMatch(nceConsistRosterEntry)) {
1580            exactMatch = true;
1581            // exact match!
1582            if (verifyRosterMatch) {
1583                queueError(WARN_CONSIST_ALREADY_LOADED);
1584            }
1585            verifyRosterMatch = false;
1586        } else {
1587            // not an exact match!
1588            if (verifyRosterMatch) {
1589                queueError(ERROR_CONSIST_DOESNT_MATCH);
1590            }
1591            verifyRosterMatch = false;
1592            if (!consistRosterPartialMatch(nceConsistRosterEntry)) {
1593                textConRoadName.setText("");
1594                textConRoadNumber.setText("");
1595                textConModel.setText("");
1596            }
1597        }
1598    }
1599
1600    // update loco fields, returns false if loco address is null
1601    private boolean updateLocoFields(NceReply r, int index,
1602            JComboBox<Object> locoRosterBox, JTextField locoTextField,
1603            JButton adrButton, JButton dirButton, JButton cmdButton) {
1604        // index = 0 for lead and rear locos, 0,2,4,6 for mid
1605        String locoAddrText = getLocoAddrText(r, index);
1606        boolean locoType = getLocoAddressType(r, index); // Long or short address?
1607        String locoDirection = getLocoDirection(dirButton);
1608
1609        locoTextField.setText(locoAddrText);
1610        locoRosterBox.setSelectedIndex(0);
1611
1612        if (locoAddrText.equals("") || locoAddrText.equals(Bundle.getMessage("REPLACE_LOCO"))) {
1613            locoRosterBox.setEnabled(true);
1614            locoTextField.setEnabled(true);
1615            cmdButton.setText(Bundle.getMessage("KeyADD"));
1616            cmdButton.setVisible(true);
1617            cmdButton.setEnabled(false);
1618            cmdButton.setToolTipText(Bundle.getMessage("ToolTipAdd"));
1619            dirButton.setText(Bundle.getMessage("KeyQUESTION"));
1620            dirButton.setEnabled(true);
1621            adrButton.setText(Bundle.getMessage("KeyLONG"));
1622            adrButton.setEnabled(true);
1623            return false;
1624        } else {
1625            locoTextField.setText(locoAddrText);
1626            locoRosterBox.setEnabled(false);
1627            locoTextField.setEnabled(false);
1628            cmdButton.setEnabled(true);
1629            dirButton.setText(locoDirection);
1630            dirButton.setEnabled(false);
1631            adrButton.setText((locoType) ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
1632            adrButton.setEnabled(false);
1633
1634            // can not delete lead or rear locos, but can replace
1635            if (locoTextField == locoTextField1 || locoTextField == locoTextField2) {
1636                cmdButton.setText(Bundle.getMessage("KeyREPLACE"));
1637                cmdButton.setToolTipText("Press to delete and replace this loco");
1638            } else {
1639                cmdButton.setText(Bundle.getMessage("KeyDELETE"));
1640                cmdButton.setToolTipText("Press to delete this loco from consist");
1641            }
1642            return true;
1643        }
1644    }
1645
1646    // modify loco fields because an Add, Replace, Delete button has been pressed
1647    private void modifyLocoFields(JComboBox<Object> locoRosterBox,
1648            JTextField locoTextField, JButton adrButton, JButton dirButton,
1649            JButton cmdButton) {
1650        if (validLocoAdr(locoTextField.getText()) < 0) {
1651            return;
1652        }
1653        byte consistNumber = (byte) validConsist(consistTextField.getText());
1654        if (consistNumber < 0) {
1655            return;
1656        }
1657        if (locoTextField.getText().equals("")) {
1658            JOptionPane.showMessageDialog(this,
1659                    Bundle.getMessage("DIALOG_EnterLocoB4Add"),
1660                    Bundle.getMessage("DIALOG_NceConsist"),
1661                    JOptionPane.ERROR_MESSAGE);
1662            return;
1663        }
1664        // set reflesh flag to update panel
1665        refresh = true;
1666        int locoAddr = getLocoAddr(locoTextField, adrButton);
1667
1668        if (cmdButton.getText().equals(Bundle.getMessage("KeyDELETE"))) {
1669            sendNceBinaryCommand(locoAddr,
1670                    NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);
1671
1672        } else if (cmdButton.getText().equals(Bundle.getMessage("KeyREPLACE"))) {
1673
1674            // Kill refresh flag, no update when replacing loco
1675            refresh = false;
1676
1677            // allow user to add loco to lead or rear consist
1678            locoRosterBox.setEnabled(true);
1679            locoTextField.setText("");
1680            locoTextField.setEnabled(true);
1681            adrButton.setText(Bundle.getMessage("KeyLONG"));
1682            adrButton.setEnabled(true);
1683            dirButton.setText(Bundle.getMessage("KeyQUESTION"));
1684            dirButton.setEnabled(true);
1685            cmdButton.setText(Bundle.getMessage("KeyADD"));
1686            cmdButton.setToolTipText(Bundle.getMessage("ToolTipAdd"));
1687
1688            // now update CS memory in case user doesn't use the Add button
1689            // this will also allow us to delete the loco from the layout
1690            if (locoTextField == locoTextField1) {
1691                // replace lead loco
1692                sendNceBinaryCommand(LOC_ADR_REPLACE,
1693                        NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, consistNumber);
1694                // no lead loco so we can't kill the consist
1695                clearCancelButton.setEnabled(false);
1696            } else {
1697                // replace rear loco
1698                sendNceBinaryCommand(LOC_ADR_REPLACE,
1699                        NceMessage.LOCO_CMD_FWD_CONSIST_REAR, consistNumber);
1700            }
1701            // now delete lead or rear loco from layout
1702            sendNceBinaryCommand(locoAddr,
1703                    NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);
1704        } else {
1705            // ADD button has been pressed
1706            if (dirButton.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1707                JOptionPane.showMessageDialog(this,
1708                        Bundle.getMessage("DIALOG_SetDirB4Consist"),
1709                        Bundle.getMessage("DIALOG_NceConsist"), JOptionPane.ERROR_MESSAGE);
1710
1711                // kill refresh flag, no update if Add button is enabled
1712                // and loco direction isn't known (lead, rear, replacement)
1713                refresh = false;
1714                return;
1715            }
1716            // delete loco from any existing consists
1717            sendNceBinaryCommand(locoAddr,
1718                    NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);
1719
1720            // check to see if loco is already a lead or rear in another consist
1721            verifyLocoAddr(locoAddr);
1722
1723            // now we need to determine if lead, rear, or mid loco
1724            // lead loco?
1725            if (locoTextField == locoTextField1) {
1726                if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1727                    sendNceBinaryCommand(locoAddr,
1728                            NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, consistNumber);
1729                }
1730                if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
1731                    sendNceBinaryCommand(locoAddr,
1732                            NceMessage.LOCO_CMD_REV_CONSIST_LEAD, consistNumber);
1733                }
1734                // rear loco?
1735            } else if (locoTextField == locoTextField2) {
1736                if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1737                    sendNceBinaryCommand(locoAddr,
1738                            NceMessage.LOCO_CMD_FWD_CONSIST_REAR, consistNumber);
1739                }
1740                if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
1741                    sendNceBinaryCommand(locoAddr,
1742                            NceMessage.LOCO_CMD_REV_CONSIST_REAR, consistNumber);
1743                }
1744                // must be mid loco
1745            } else {
1746                // wait for verify to complete before updating mid loco
1747                if (locoSearch) {
1748                    if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1749                        verifyType = VERIFY_MID_FWD;
1750                    } else {
1751                        verifyType = VERIFY_MID_REV;
1752                    }
1753                    // no verify, just load and go!
1754                } else {
1755                    if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1756                        sendNceBinaryCommand(locoAddr,
1757                                NceMessage.LOCO_CMD_FWD_CONSIST_MID, consistNumber);
1758                    }
1759                    if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
1760                        sendNceBinaryCommand(locoAddr,
1761                                NceMessage.LOCO_CMD_REV_CONSIST_MID, consistNumber);
1762                    }
1763                }
1764            }
1765        }
1766    }
1767
1768    private void fullLoad() {
1769        refresh = true;
1770        loadOneLine(locoRosterBox1, locoTextField1, adrButton1,
1771                dirButton1, cmdButton1);
1772        loadOneLine(locoRosterBox2, locoTextField2, adrButton2,
1773                dirButton2, cmdButton2);
1774        loadOneLine(locoRosterBox3, locoTextField3, adrButton3,
1775                dirButton3, cmdButton3);
1776        loadOneLine(locoRosterBox4, locoTextField4, adrButton4,
1777                dirButton4, cmdButton4);
1778        loadOneLine(locoRosterBox5, locoTextField5, adrButton5,
1779                dirButton5, cmdButton5);
1780        loadOneLine(locoRosterBox6, locoTextField6, adrButton6,
1781                dirButton6, cmdButton6);
1782        changeButtons(false);
1783    }
1784
1785    /**
1786     * updates NCE CS based on the loco line supplied called by load button
1787     *
1788     */
1789    private void loadOneLine(JComboBox<Object> locoRosterBox, JTextField locoTextField,
1790            JButton adrButton, JButton dirButton, JButton cmdButton) {
1791        if (locoTextField.getText().equals("")) {
1792            return;
1793        }
1794        if (validLocoAdr(locoTextField.getText()) < 0) {
1795            return;
1796        }
1797        byte cN = (byte) validConsist(consistTextField.getText());
1798        if (cN < 0) {
1799            return;
1800        }
1801
1802        int locoAddr = getLocoAddr(locoTextField, adrButton);
1803
1804        // ADD loco to consist
1805        if (dirButton.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
1806            JOptionPane.showMessageDialog(this,
1807                    Bundle.getMessage("DIALOG_SetDirB4Consist"), Bundle.getMessage("DIALOG_NceConsist"),
1808                    JOptionPane.ERROR_MESSAGE);
1809            return;
1810        }
1811
1812        // delete loco from any existing consists
1813        sendNceBinaryCommand(locoAddr,
1814                NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);
1815        // now we need to determine if lead, rear, or mid loco
1816        // lead loco?
1817        if (locoTextField == locoTextField1) {
1818            // kill the consist first to clear NCE CS memory
1819            sendNceBinaryCommand(locoAddr,
1820                    NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, cN);
1821            sendNceBinaryCommand(locoAddr, NceMessage.LOCO_CMD_KILL_CONSIST,
1822                    (byte) 0);
1823            // now load
1824            if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1825                sendNceBinaryCommand(locoAddr,
1826                        NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, cN);
1827            }
1828            if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
1829                sendNceBinaryCommand(locoAddr,
1830                        NceMessage.LOCO_CMD_REV_CONSIST_LEAD, cN);
1831            }
1832            // rear loco?
1833        } else if (locoTextField == locoTextField2) {
1834            if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1835                sendNceBinaryCommand(locoAddr,
1836                        NceMessage.LOCO_CMD_FWD_CONSIST_REAR, cN);
1837            }
1838            if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
1839                sendNceBinaryCommand(locoAddr,
1840                        NceMessage.LOCO_CMD_REV_CONSIST_REAR, cN);
1841            }
1842            // must be mid loco
1843        } else {
1844            if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
1845                sendNceBinaryCommand(locoAddr,
1846                        NceMessage.LOCO_CMD_FWD_CONSIST_MID, cN);
1847            }
1848            if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
1849                sendNceBinaryCommand(locoAddr,
1850                        NceMessage.LOCO_CMD_REV_CONSIST_MID, cN);
1851            }
1852        }
1853    }
1854
1855    private int getLocoAddr(JTextField locoTextField, JButton adrButton) {
1856        int locoAddr = Integer.parseInt(locoTextField.getText());
1857        if (locoAddr >= 128) {
1858            locoAddr += 0xC000;
1859        } else if (adrButton.getText().equals(Bundle.getMessage("KeyLONG"))) {
1860            locoAddr += 0xC000;
1861        }
1862        return locoAddr;
1863    }
1864
1865    private void sendNceMessage(byte[] b, int replyLength) {
1866        NceMessage m = NceMessage.createBinaryMessage(tc, b, replyLength);
1867        waiting++;
1868        replyLen = replyLength; // Expect n byte response
1869        tc.sendNceMessage(m, this);
1870    }
1871
1872    // get loco address type, returns true if long
1873    private boolean getLocoAddressType(NceReply r, int i) {
1874        int rC = r.getElement(i);
1875        rC = rC & 0xC0; // long address if 2 msb are set
1876        if (rC == 0xC0) {
1877            return true;
1878        } else {
1879            return false;
1880        }
1881    }
1882
1883    private String getLocoAddrText(NceReply r, int i) {
1884        int rC_u = r.getElement(i++);
1885        int rC = (rC_u << 8) & 0x3F00;
1886        int rC_l = r.getElement(i);
1887        rC = rC + (rC_l & 0xFF);
1888        String locoAddrText = "";
1889        if ((rC_u != 0) || (rC_l != 0)) {
1890            locoAddrText = Integer.toString(rC);
1891        }
1892        if (rC == LOC_ADR_REPLACE) {
1893            locoAddrText = Bundle.getMessage("REPLACE_LOCO");
1894        }
1895        return locoAddrText;
1896    }
1897
1898    private String getLocoDirection(JButton dirButton) {
1899        if (newConsist) {
1900            return Bundle.getMessage("KeyQUESTION");
1901        } else {
1902            return dirButton.getText();
1903        }
1904    }
1905
1906    // check command station memory for lead or rear loco match
1907    private void verifyLocoAddr(int locoAddr) {
1908        verifyType = VERIFY_LEAD_REAR;
1909        if (checkBoxVerify.isSelected()) {
1910            locoVerifyList[0] = locoAddr;
1911            locoVerifyList[1] = 0;  // end of list
1912            locoSearch = true;
1913            consistNumVerify = 0;
1914        }
1915    }
1916
1917    // check command station memory for lead or rear loco match
1918    private boolean verifyAllLocoAddr() {
1919        verifyType = VERIFY_ALL;
1920        if (checkBoxVerify.isSelected()) {
1921            int i = 0;
1922            if (!locoTextField1.getText().equals("") && validLocoAdr(locoTextField1.getText()) > 0) {
1923                locoVerifyList[i++] = getLocoAddr(locoTextField1, adrButton1);
1924            }
1925            if (!locoTextField2.getText().equals("") && validLocoAdr(locoTextField2.getText()) > 0) {
1926                locoVerifyList[i++] = getLocoAddr(locoTextField2, adrButton2);
1927            }
1928            if (!locoTextField3.getText().equals("") && validLocoAdr(locoTextField3.getText()) > 0) {
1929                locoVerifyList[i++] = getLocoAddr(locoTextField3, adrButton3);
1930            }
1931            if (!locoTextField4.getText().equals("") && validLocoAdr(locoTextField4.getText()) > 0) {
1932                locoVerifyList[i++] = getLocoAddr(locoTextField4, adrButton4);
1933            }
1934            if (!locoTextField5.getText().equals("") && validLocoAdr(locoTextField5.getText()) > 0) {
1935                locoVerifyList[i++] = getLocoAddr(locoTextField5, adrButton5);
1936            }
1937            if (!locoTextField6.getText().equals("") && validLocoAdr(locoTextField6.getText()) > 0) {
1938                locoVerifyList[i++] = getLocoAddr(locoTextField6, adrButton6);
1939            } else {
1940                locoVerifyList[i] = 0;
1941            }
1942            locoSearch = true;
1943            consistNumVerify = 0;
1944            consistStatus.setText(Bundle.getMessage("EditStateVERIFY"));
1945            readConsistMemory(consistNumVerify, LEAD);
1946            return true;
1947        }
1948        return false;
1949    }
1950
1951    private void addLocoRow(JComponent col1, JComponent col2, JComponent col3,
1952            JComponent col4, JComponent col5, JComponent col6, int row) {
1953        addItem(col1, 0, row);
1954        addItem(col2, 1, row);
1955        addItem(col3, 2, row);
1956        addItem(col4, 3, row);
1957        addItem(col5, 4, row);
1958        addItem(col6, 5, row);
1959    }
1960
1961    private void addItem(JComponent c, int x, int y) {
1962        GridBagConstraints gc = new GridBagConstraints();
1963        gc.gridx = x;
1964        gc.gridy = y;
1965        gc.weightx = 100.0;
1966        gc.weighty = 100.0;
1967        add(c, gc);
1968    }
1969
1970    private void addButtonAction(JButton b) {
1971        b.addActionListener(new java.awt.event.ActionListener() {
1972            @Override
1973            public void actionPerformed(java.awt.event.ActionEvent e) {
1974                buttonActionPerformed(e);
1975            }
1976        });
1977    }
1978
1979    private void addCheckBoxAction(JCheckBox cb) {
1980        cb.addActionListener(new java.awt.event.ActionListener() {
1981            @Override
1982            public void actionPerformed(java.awt.event.ActionEvent e) {
1983                checkBoxActionPerformed(e);
1984            }
1985        });
1986    }
1987
1988    private void enableAllLocoRows(boolean flag) {
1989        enableLocoRow(flag, locoTextField1, locoRosterBox1,
1990                adrButton1, dirButton1, cmdButton1);
1991        enableLocoRow(flag, locoTextField2, locoRosterBox2,
1992                adrButton2, dirButton2, cmdButton2);
1993        enableLocoRow(flag, locoTextField3, locoRosterBox3,
1994                adrButton3, dirButton3, cmdButton3);
1995        enableLocoRow(flag, locoTextField4, locoRosterBox4,
1996                adrButton4, dirButton4, cmdButton4);
1997        enableLocoRow(flag, locoTextField5, locoRosterBox5,
1998                adrButton5, dirButton5, cmdButton5);
1999        enableLocoRow(flag, locoTextField6, locoRosterBox6,
2000                adrButton6, dirButton6, cmdButton6);
2001    }
2002
2003    private void enableLocoRow(boolean flag, JTextField locoTextField,
2004            JComboBox<Object> locoRosterBox, JButton adrButton, JButton dirButton,
2005            JButton cmdButton) {
2006        locoTextField.setEnabled(flag);
2007        locoRosterBox.setEnabled(flag);
2008        adrButton.setEnabled(flag);
2009        dirButton.setEnabled(flag);
2010        cmdButton.setEnabled(flag);
2011    }
2012
2013    // initialize loco fields
2014    private void initLocoFields() {
2015        initLocoRow(1, Bundle.getMessage("LeadLabel"), textLoco1, locoTextField1, locoRosterBox1,
2016                adrButton1, dirButton1, cmdButton1);
2017        initLocoRow(2, Bundle.getMessage("RearLabel"), textLoco2, locoTextField2, locoRosterBox2,
2018                adrButton2, dirButton2, cmdButton2);
2019        initLocoRow(3, Bundle.getMessage("MidLabel", "1"), textLoco3, locoTextField3, locoRosterBox3,
2020                adrButton3, dirButton3, cmdButton3);
2021        initLocoRow(4, Bundle.getMessage("MidLabel", "2"), textLoco4, locoTextField4, locoRosterBox4,
2022                adrButton4, dirButton4, cmdButton4);
2023        initLocoRow(5, Bundle.getMessage("MidLabel", "3"), textLoco5, locoTextField5, locoRosterBox5,
2024                adrButton5, dirButton5, cmdButton5);
2025        initLocoRow(6, Bundle.getMessage("MidLabel", "4"), textLoco6, locoTextField6, locoRosterBox6,
2026                adrButton6, dirButton6, cmdButton6);
2027    }
2028
2029    private void initLocoRow(int row, String s, JLabel textLoco,
2030            JTextField locoTextField, JComboBox<Object> locoRosterBox,
2031            JButton adrButton, JButton dirButton, JButton cmdButton) {
2032
2033        textLoco.setText(s);
2034        textLoco.setVisible(true);
2035
2036        adrButton.setText(Bundle.getMessage("KeyLONG"));
2037        adrButton.setVisible(true);
2038        adrButton.setEnabled(false);
2039        adrButton.setToolTipText(Bundle.getMessage("ToolTipAddressType"));
2040        adrButton.addActionListener(new java.awt.event.ActionListener() {
2041            @Override
2042            public void actionPerformed(java.awt.event.ActionEvent e) {
2043                buttonActionAdrPerformed(e);
2044            }
2045        });
2046
2047        locoRosterBox.setVisible(true);
2048        locoRosterBox.setEnabled(false);
2049        locoRosterBox.setToolTipText(Bundle.getMessage("ToolTipSelectLoco"));
2050        locoRosterBox.addActionListener(new java.awt.event.ActionListener() {
2051            @Override
2052            public void actionPerformed(java.awt.event.ActionEvent e) {
2053                locoSelected(e);
2054            }
2055        });
2056
2057        dirButton.setText(Bundle.getMessage("KeyQUESTION"));
2058        dirButton.setVisible(true);
2059        dirButton.setEnabled(false);
2060        dirButton.setToolTipText(Bundle.getMessage("ToolTipDirection"));
2061        dirButton.addActionListener(new java.awt.event.ActionListener() {
2062            @Override
2063            public void actionPerformed(java.awt.event.ActionEvent e) {
2064                buttonActionDirPerformed(e);
2065            }
2066        });
2067
2068        cmdButton.setText(Bundle.getMessage("KeyADD"));
2069        cmdButton.setVisible(true);
2070        cmdButton.setEnabled(false);
2071        cmdButton.setToolTipText(Bundle.getMessage("ToolTipAdd"));
2072        cmdButton.addActionListener(this::buttonActionCmdPerformed);
2073
2074        locoTextField.setText("");
2075        locoTextField.setEnabled(false);
2076        locoTextField.setToolTipText(Bundle.getMessage("ToolTipEnterLoco"));
2077        locoTextField.setMaximumSize(new Dimension(
2078                locoTextField.getMaximumSize().width, locoTextField
2079                .getPreferredSize().height));
2080    }
2081
2082    ActionListener consistRosterListener;
2083
2084    private void initConsistRoster(JComboBox<String> conRosterBox) {
2085        conRosterBox.insertItemAt("", 0);
2086        conRosterBox.setSelectedIndex(0);
2087        conRosterBox.setVisible(true);
2088        conRosterBox.setEnabled(false);
2089        conRosterBox.setToolTipText(Bundle.getMessage("ToolTipSelectConsist"));
2090        conRosterBox.addActionListener(consistRosterListener = this::consistRosterSelected);
2091    }
2092
2093    private static final int ERROR_LOCO_IN_USE = 1;
2094    private static final int ERROR_NO_EMPTY_CONSIST = 2;
2095    private static final int ERROR_CONSIST_DOESNT_MATCH = 3;
2096    private static final int WARN_CONSIST_ALREADY_LOADED = 4;
2097    private int locoNumInUse;       // report loco alreay in use
2098    private int errorCode = 0;
2099
2100    private void queueError(int errorCode) {
2101        log.debug("queue warning/error message: {}", errorCode);
2102        if (this.errorCode != 0) {
2103            log.debug("multiple errors reported {}", this.errorCode);
2104            return;
2105        }
2106        this.errorCode = errorCode;
2107        // Bad to stop receive thread with JOptionPane error message
2108        // so start up a new thread to report error
2109        Thread errorThread = new Thread(new Runnable() {
2110            @Override
2111            public void run() {
2112                reportError();
2113            }
2114        });
2115        errorThread.setName("Report Error"); // NOI18N
2116        errorThread.start();
2117    }
2118
2119    public void reportError() {
2120        switch (errorCode) {
2121
2122            case ERROR_LOCO_IN_USE:
2123                JOptionPane.showMessageDialog(this,
2124                        Bundle.getMessage("DIALOG_LocoInUse", locoNumInUse, consistNumVerify),
2125                        Bundle.getMessage("DIALOG_NceConsist"),
2126                        JOptionPane.ERROR_MESSAGE);
2127                break;
2128
2129            case ERROR_NO_EMPTY_CONSIST:
2130                JOptionPane.showMessageDialog(this,
2131                        Bundle.getMessage("DIALOG_NoEmptyConsist"),
2132                        Bundle.getMessage("DIALOG_NceConsist"),
2133                        JOptionPane.ERROR_MESSAGE);
2134                break;
2135
2136            case ERROR_CONSIST_DOESNT_MATCH:
2137                if (JOptionPane.showConfirmDialog(null,
2138                        Bundle.getMessage("DIALOG_RosterNotMatch") + " "
2139                        + getRosterText(nceConsistRosterEntry),
2140                        Bundle.getMessage("DIALOG_NceContinue"),
2141                        JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
2142                    if (JOptionPane.showConfirmDialog(null,
2143                            Bundle.getMessage("DIALOG_RosterNotMatch1",
2144                                    nceConsistRosterEntry.getId(), nceConsistRosterEntry.getConsistNumber())
2145                            + "\n " + Bundle.getMessage("DIALOG_RosterNotMatch2"),
2146                            Bundle.getMessage("DIALOG_NceReset"),
2147                            JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
2148                        nceConsistRosterEntry.setConsistNumber(Bundle.getMessage("CLEARED"));
2149                    }
2150                    changeButtons(false);
2151                    saveLoadButton.setEnabled(canLoad());
2152                    break;
2153                }
2154                changeButtons(true);
2155                loadFullRoster(nceConsistRosterEntry);
2156                saveLoadButton.setEnabled(canLoad());
2157                break;
2158            case WARN_CONSIST_ALREADY_LOADED:
2159                JOptionPane.showMessageDialog(this,
2160                        Bundle.getMessage("DIALOG_ConsistWasLoaded"),
2161                        Bundle.getMessage("DIALOG_NceConsist"), JOptionPane.WARNING_MESSAGE);
2162                break;
2163            default:
2164                log.error("Error code out of range");
2165        }
2166        errorCode = 0;
2167    }
2168
2169    private String getRosterText(NceConsistRosterEntry nceConsistRosterEntry) {
2170        return "\n"
2171                + "\n"
2172                + Bundle.getMessage("ROSTER_ConsistNum")
2173                + " " + nceConsistRosterEntry.getConsistNumber()
2174                + "\n"
2175                + Bundle.getMessage("ROSTER_LeadLoco")
2176                + " " + nceConsistRosterEntry.getLoco1DccAddress()
2177                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco1Direction())
2178                + "\n"
2179                + Bundle.getMessage("ROSTER_RearLoco")
2180                + " " + nceConsistRosterEntry.getLoco2DccAddress()
2181                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco2Direction())
2182                + "\n"
2183                + Bundle.getMessage("ROSTER_Mid1Loco")
2184                + " " + nceConsistRosterEntry.getLoco3DccAddress()
2185                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco3Direction())
2186                + "\n"
2187                + Bundle.getMessage("ROSTER_Mid2Loco")
2188                + " " + nceConsistRosterEntry.getLoco4DccAddress()
2189                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco4Direction())
2190                + "\n"
2191                + Bundle.getMessage("ROSTER_Mid3Loco")
2192                + " " + nceConsistRosterEntry.getLoco5DccAddress()
2193                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco5Direction())
2194                + "\n"
2195                + Bundle.getMessage("ROSTER_Mid4Loco")
2196                + " " + nceConsistRosterEntry.getLoco6DccAddress()
2197                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco6Direction());
2198    }
2199
2200    /**
2201     * Nested class to create one of these using old-style defaults
2202     */
2203    static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction {
2204
2205        public Default() {
2206            super("Open NCE Consist Editor",
2207                    new jmri.util.swing.sdi.JmriJFrameInterface(),
2208                    NceConsistEditPanel.class.getName(),
2209                    jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class));
2210        }
2211    }
2212
2213    private final static Logger log = LoggerFactory
2214            .getLogger(NceConsistEditPanel.class);
2215}