java/src/jmri/jmrix/nce/consist/NceConsistEditPanel.java

Summary

Maintainability
F
2 wks
Test Coverage
F
8%
package jmri.jmrix.nce.consist;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionListener;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nonnull;
import javax.swing.*;

import jmri.DccLocoAddress;
import jmri.InstanceManager;
import jmri.jmrit.roster.RosterEntry;
import jmri.jmrit.roster.swing.RosterEntryComboBox;
import jmri.jmrit.throttle.ThrottleFrameManager;
import jmri.jmrix.nce.*;
import jmri.util.swing.JmriJOptionPane;

/**
 * Pane for user edit of NCE Consists
 *
 * NCE Consists are stored in Command Station (CS) memory starting at address
 * xF500 and ending xFAFF (PH5 0x4E00 - 0x53FF). NCE supports up to 127 consists, numbered 1 to 127.
 * They track the lead loco, rear loco, and four mid locos in the consist file.
 * NCE cabs start at consist 127 when building and reviewing consists, so we
 * also start with 127. Consist lead locos are stored in memory locations xF500
 * through xF5FF (PH5 0x4E00 - 0x4EFF). Consist rear locos are stored in memory locations xF600
 * through xF6FF (PH5 0x4F00 - 0x4FFF). Mid consist locos (four max) are stored in memory locations
 * xF700 through xFAFF (PH5 0x500 - 0x53FF). If a long address is in use, bits 6 and 7 of the high
 * byte are set. Example: Long address 3 = 0xc0 0x03 Short address 3 = 0x00 0x03
 *
 * NCE file format:
 *
 * :F500 (con 0 lead loco) (con 1 lead loco) ....... (con 7 lead loco) :F510
 * (con 8 lead loco) ........ (con 15 lead loco) . . :F5F0 (con 120 lead loco)
 * ..... (con 127 lead loco)
 *
 * :F600 (con 0 rear loco) (con 1 rear loco) ....... (con 7 rear loco) . . :F6F0
 * (con 120 rear loco) ..... (con 127 rear loco)
 *
 * :F700 (con 0 mid loco1) (con 0 mid loco2) (con 0 mid loco3) (con 0 mid loco4)
 * . . :FAF0 (con 126 mid loco1) .. (con 126 mid loco4)(con 127 mid loco1) ..
 * (con 127 mid loco4) :0000
 *
 * @author Dan Boudreau Copyright (C) 2007 2008 Cloned from NceConsistEditFrame
 * by
 * @author kcameron Copyright (C) 2010, 2023
 */
public class NceConsistEditPanel extends jmri.jmrix.nce.swing.NcePanel implements
        jmri.jmrix.nce.NceListener {
    
    NceConsistRoster nceConsistRoster = InstanceManager.getDefault(NceConsistRoster.class);

    private int CONSIST_MIN = 1;    // NCE doesn't use consist 0
    private int CONSIST_MAX = 127;
    private static final int LOC_ADR_MIN = 0;    // loco address range
    private static final int LOC_ADR_MAX = 9999; // max range for NCE
    private static final int LOC_ADR_REPLACE = 0x3FFF;  // dummy loco address

    private int consistNum = 0;     // consist being worked
    private boolean newConsist = true;    // new consist is displayed

    private int locoPosition = LEAD;     // which loco memory bank, 0 = lead, 1 = rear, 2 = mid
    private static final int LEAD = 0;
    private static final int REAR = 1;
    private static final int MID = 2;

    // Verify that loco isn't already a lead or rear loco
    private int consistNumVerify;     // which consist number we're checking
    private final int[] locoVerifyList = new int[6]; // list of locos to verify
    private int verifyType;      // type of verification
    private static final int VERIFY_DONE = 0;
    private static final int VERIFY_LEAD_REAR = 1;  // lead or rear loco
    private static final int VERIFY_MID_FWD = 2;  // mid loco foward
    private static final int VERIFY_MID_REV = 4;  // mid loco reverse
    private static final int VERIFY_ALL = 8;  // verify all locos

    private int replyLen = 0;      // expected byte length
    private int waiting = 0;      // to catch responses not intended for this module

    // the 16 byte reply states
    private boolean consistSearchNext = false;   // next search
    private boolean consistSearchPrevious = false;  // previous search
    private boolean locoSearch = false;    // when true searching for lead or rear loco in consist

    private boolean emptyConsistSearch = false;  // when true searching for an empty consist
    private boolean verifyRosterMatch = false;   // when true verify that roster matches consist in NCE CS

    private static final int CONSIST_ERROR = -1;
    private static final int ADDRESS_ERROR = -1;

    private int consistCount = 0;      // search count not to exceed CONSIST_MAX

    private boolean refresh = false;     // when true, refresh loco info from CS
    
    private static int DELAY_AFTER_CLEAR = 1000;    // number of mSec to wait after sending a del loco

    // member declarations
    JLabel textConsist = new JLabel();
    JLabel textStatus = new JLabel();
    JLabel consistStatus = new JLabel();

    // major buttons
    JButton previousButton = new JButton();
    JButton nextButton = new JButton();
    JButton getButton = new JButton();
    JButton throttleButton = new JButton();
    JButton clearCancelButton = new JButton();
    JButton saveLoadButton = new JButton();
    JButton deleteButton = new JButton();
    JButton backUpButton = new JButton();
    JButton restoreButton = new JButton();

    // check boxes
    JCheckBox checkBoxEmpty = new JCheckBox();
    JCheckBox checkBoxVerify = new JCheckBox();
    JCheckBox checkBoxConsist = new JCheckBox();

    // consist text field
    JTextField consistTextField = new JTextField(4);

    // labels
    JLabel textLocomotive = new JLabel();
    JLabel textRoster = new JLabel();
    JLabel textAddress = new JLabel();
    JLabel textAddrType = new JLabel();
    JLabel textDirection = new JLabel();

    JLabel textConRoster = new JLabel();
    JLabel textConRoadName = new JLabel();
    JLabel textConRoadNumber = new JLabel();
    JLabel textConModel = new JLabel();

    JComboBox<String> conRosterBox = nceConsistRoster.fullRosterComboBox();

    // for padding out panel
    JLabel space1 = new JLabel("            ");
    JLabel space2 = new JLabel(" ");
    JLabel space3a = new JLabel("                            ");
    JLabel space3b = new JLabel("                            ");
    JLabel space3c = new JLabel("                            ");
    JLabel space3d = new JLabel("                            ");

    JLabel space15 = new JLabel(" ");

    // lead loco
    JLabel textLoco1 = new JLabel();
    JTextField locoTextField1 = new JTextField(4);
    JComboBox<Object> locoRosterBox1 = new RosterEntryComboBox();
    JButton adrButton1 = new JButton();
    JButton cmdButton1 = new JButton();
    JButton dirButton1 = new JButton();

    // rear loco
    JLabel textLoco2 = new JLabel();
    JTextField locoTextField2 = new JTextField(4);
    JComboBox<Object> locoRosterBox2 = new RosterEntryComboBox();
    JButton adrButton2 = new JButton();
    JButton cmdButton2 = new JButton();
    JButton dirButton2 = new JButton();

    // mid loco
    JLabel textLoco3 = new JLabel();
    JTextField locoTextField3 = new JTextField(4);
    JComboBox<Object> locoRosterBox3 = new RosterEntryComboBox();
    JButton adrButton3 = new JButton();
    JButton cmdButton3 = new JButton();
    JButton dirButton3 = new JButton();

    // mid loco
    JLabel textLoco4 = new JLabel();
    JTextField locoTextField4 = new JTextField(4);
    JComboBox<Object> locoRosterBox4 = new RosterEntryComboBox();
    JButton adrButton4 = new JButton();
    JButton cmdButton4 = new JButton();
    JButton dirButton4 = new JButton();

    // mid loco
    JLabel textLoco5 = new JLabel();
    JTextField locoTextField5 = new JTextField(4);
    JComboBox<Object> locoRosterBox5 = new RosterEntryComboBox();
    JButton adrButton5 = new JButton();
    JButton cmdButton5 = new JButton();
    JButton dirButton5 = new JButton();

    // mid loco
    JLabel textLoco6 = new JLabel();
    JTextField locoTextField6 = new JTextField(4);
    JComboBox<Object> locoRosterBox6 = new RosterEntryComboBox();
    JButton adrButton6 = new JButton();
    JButton cmdButton6 = new JButton();
    JButton dirButton6 = new JButton();

    private NceTrafficController tc = null;

    public NceConsistEditPanel() {
        super();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initContext(Object context) {
        if (context instanceof NceSystemConnectionMemo) {
            try {
                initComponents((NceSystemConnectionMemo) context);
            } catch (Exception e) {
                log.error("NceConsistEdit initContext failed"); // NOI18N
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getHelpTarget() {
        return "package.jmri.jmrix.nce.consist.NceConsistEditFrame";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTitle() {
        StringBuilder x = new StringBuilder();
        if (memo != null) {
            x.append(memo.getUserName());
        } else {
            x.append("NCE_"); // NOI18N
        }
        x.append(": ");
        x.append(Bundle.getMessage("NceConsistEditTitle"));
        return x.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Nonnull
    public List<JMenu> getMenus() {
        // build menu
        JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools"));
        toolMenu.add(new NceConsistRosterMenu(Bundle.getMessage("RosterTitle"),
                jmri.jmrit.roster.swing.RosterMenu.MAINMENU, this));
        List<JMenu> l = new ArrayList<>();
        l.add(toolMenu);
        return l;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initComponents(NceSystemConnectionMemo m) {
        this.memo = m;
        this.tc = m.getNceTrafficController();
        CONSIST_MIN = tc.csm.getConsistMin();
        CONSIST_MAX = tc.csm.getConsistMax();
        // the following code sets the frame's initial state

        textConsist.setText(Bundle.getMessage("L_Consist"));

        textStatus.setText(Bundle.getMessage("L_Status"));

        consistStatus.setText(Bundle.getMessage("EditStateUNKNOWN"));

        previousButton.setText(Bundle.getMessage("KeyPREVIOUS"));
        previousButton.setToolTipText(Bundle.getMessage("ToolTipPrevious"));

        nextButton.setText(Bundle.getMessage("KeyNEXT"));
        nextButton.setToolTipText(Bundle.getMessage("ToolTipNext"));

        getButton.setText(Bundle.getMessage("KeyGET"));
        getButton.setToolTipText(Bundle.getMessage("ToolTipGet"));

        consistTextField.setText(Integer.toString(CONSIST_MAX));
        consistTextField.setToolTipText(MessageFormat.format(Bundle.getMessage("ToolTipConsist"), CONSIST_MIN, CONSIST_MAX));
        consistTextField.setMaximumSize(new Dimension(consistTextField
                .getMaximumSize().width,
                consistTextField.getPreferredSize().height));

        textLocomotive.setText(Bundle.getMessage("L_Loco"));
        textRoster.setText(Bundle.getMessage("L_Roster"));
        textAddress.setText(Bundle.getMessage("L_Address"));
        textAddrType.setText(Bundle.getMessage("L_Type"));
        textDirection.setText(Bundle.getMessage("L_Direction"));

        textConRoster.setText(Bundle.getMessage("L_Consist"));
        textConRoadName.setText("");
        textConRoadNumber.setText("");
        textConModel.setText("");

        throttleButton.setText(Bundle.getMessage("L_Throttle"));
        throttleButton.setEnabled(true);
        throttleButton.setToolTipText(Bundle.getMessage("ToolTipThrottle"));

        clearCancelButton.setText(Bundle.getMessage("KeyCLEAR"));
        clearCancelButton.setEnabled(false);
        clearCancelButton.setToolTipText(Bundle.getMessage("ToolTipClear"));

        saveLoadButton.setText(Bundle.getMessage("KeySAVE"));
        saveLoadButton.setVisible(false);
        saveLoadButton.setEnabled(false);
        saveLoadButton.setToolTipText(Bundle.getMessage("ToolTipSave"));

        deleteButton.setText(Bundle.getMessage("KeyDELETE"));
        deleteButton.setVisible(false);
        deleteButton.setEnabled(false);
        deleteButton.setToolTipText(Bundle.getMessage("ToolTipDelete"));

        backUpButton.setText(Bundle.getMessage("KeyBACKUP"));
        backUpButton.setToolTipText(Bundle.getMessage("ToolTipBackup"));

        restoreButton.setText(Bundle.getMessage("KeyRESTORE"));
        restoreButton.setToolTipText(Bundle.getMessage("ToolTipRestore"));

        checkBoxEmpty.setText(Bundle.getMessage("KeyEMPTY"));
        checkBoxEmpty.setToolTipText(Bundle.getMessage("ToolTipEmpty"));

        checkBoxVerify.setText(Bundle.getMessage("KeyVERIFY"));
        checkBoxVerify.setSelected(true);
        checkBoxVerify.setToolTipText(Bundle.getMessage("ToolTipVerify"));

        checkBoxConsist.setText(Bundle.getMessage("KeyCONSIST"));
        checkBoxConsist.setSelected(true);
        checkBoxConsist.setToolTipText(Bundle.getMessage("ToolTipConsistCkBox"));

        initLocoFields();

        setLayout(new GridBagLayout());

        // Layout the panel by rows
        // row 0
        addItem(textConsist, 2, 0);
        // row 1
        addItem(previousButton, 1, 1);
        addItem(consistTextField, 2, 1);
        addItem(nextButton, 3, 1);
        addItem(checkBoxEmpty, 5, 1);
        // row 2
        addItem(textStatus, 0, 2);
        addItem(consistStatus, 1, 2);
        addItem(getButton, 2, 2);
        addItem(checkBoxVerify, 5, 2);
        // row 3
        addItem(space3a, 1, 3);
        addItem(space3b, 2, 3);
        addItem(space3c, 3, 3);
        addItem(space3d, 4, 3);
        // row 4
        addItem(textConRoster, 1, 4);
        // row 5
        addItem(conRosterBox, 1, 5);
        addItem(textConRoadName, 2, 5);
        addItem(textConRoadNumber, 3, 5);
        addItem(textConModel, 4, 5);
        addItem(checkBoxConsist, 5, 5);
        initConsistRoster(conRosterBox);

        // row 6 padding for looks
        addItem(space1, 1, 6);
        // row 7 labels
        addItem(textLocomotive, 0, 7);
        addItem(textRoster, 1, 7);
        addItem(textAddress, 2, 7);
        addItem(textAddrType, 3, 7);
        addItem(textDirection, 4, 7);

        // row 8 Lead Locomotive
        addLocoRow(textLoco1, locoRosterBox1, locoTextField1, adrButton1,
                dirButton1, cmdButton1, 8);
        // row 9 Rear Locomotive
        addLocoRow(textLoco2, locoRosterBox2, locoTextField2, adrButton2,
                dirButton2, cmdButton2, 9);
        // row 10 Mid Locomotive
        addLocoRow(textLoco3, locoRosterBox3, locoTextField3, adrButton3,
                dirButton3, cmdButton3, 10);
        // row 11 Mid Locomotive
        addLocoRow(textLoco4, locoRosterBox4, locoTextField4, adrButton4,
                dirButton4, cmdButton4, 11);
        // row 12 Mid Locomotive
        addLocoRow(textLoco5, locoRosterBox5, locoTextField5, adrButton5,
                dirButton5, cmdButton5, 12);
        // row 13 Mid Locomotive
        addLocoRow(textLoco6, locoRosterBox6, locoTextField6, adrButton6,
                dirButton6, cmdButton6, 13);

        // row 15 padding for looks
        addItem(space15, 2, 15);
        // row 16
        addItem(throttleButton, 0, 16);
        addItem(clearCancelButton, 1, 16);
        addItem(saveLoadButton, 2, 16);
        addItem(deleteButton, 3, 16);
        addItem(backUpButton, 4, 16);
        addItem(restoreButton, 5, 16);

        // setup buttons
        addButtonAction(previousButton);
        addButtonAction(nextButton);
        addButtonAction(getButton);
        addButtonAction(throttleButton);
        addButtonAction(clearCancelButton);
        addButtonAction(saveLoadButton);
        addButtonAction(deleteButton);
        addButtonAction(backUpButton);
        addButtonAction(restoreButton);

        // setup checkboxes
        addCheckBoxAction(checkBoxConsist);
        checkBoxConsist();
    }

    // Previous, Next, Get, Throttle, Clear/Cancel, Save/Load, Delete, Restore & Backup buttons
    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
        // if we're searching ignore user
        if (consistSearchNext || consistSearchPrevious || locoSearch) {
            return;
        }
        // throttle button
        if (ae.getSource() == throttleButton) {
            if (!validConsist()) {
                return;
            }
            int locoAddr = validLocoAdr(locoTextField1.getText());
            boolean isLong = (adrButton1.getText().equals(Bundle.getMessage("KeyLONG")));
            if (locoAddr < 0) {
                return;
            }
            consistNum = validConsist(consistTextField.getText());
            jmri.jmrit.throttle.ThrottleFrame tf
                    = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
            tf.getAddressPanel().setAddress(consistNum, false); // use consist address
            if (JmriJOptionPane.showConfirmDialog(null,
                    Bundle.getMessage("DIALOG_Funct2Lead"), Bundle.getMessage("DIALOG_NceThrottle"),
                    JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
                tf.getAddressPanel().setAddress(locoAddr, isLong);  // use lead loco address
            }
            tf.toFront();
            return;
        }
        // clear or cancel button
        if (ae.getSource() == clearCancelButton) {
            // button can be Clear or Cancel
            if (clearCancelButton.getText().equals(Bundle.getMessage("KeyCLEAR"))) {
                updateRoster(Bundle.getMessage("CLEARED"));
                // set refresh flag to update panel
                refresh = true;
                killConsist();

                // must be cancel button
            } else {
                changeButtons(false);
                consistNum = getConsist(); // reload panel
            }
        }

        // save or load button
        if (ae.getSource() == saveLoadButton) {
            if (!validConsist()) {
                return;
            }
            // check to see if user modified the roster
            if (canLoad()) {
                consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
            } else {
                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
                saveLoadButton.setEnabled(false);
                return;
            }
            enableAllLocoRows(false);
            if (saveLoadButton.getText().equals(Bundle.getMessage("KeyLOAD"))) {
                loadShift(); // get rid of empty mids!
                updateRoster(consistTextField.getText());
                consistNum = validConsist(consistTextField.getText());
                // load right away or verify?
                if (!verifyAllLocoAddr()) {
                    fullLoad();
                }
            } else if (updateRoster(consistTextField.getText())) {
                saveLoadButton.setEnabled(false);
                consistNum = getConsist(); // reload panel
            }
            return;
        }

        // delete button
        if (ae.getSource() == deleteButton) {
            if (JmriJOptionPane.showConfirmDialog(null,
                    Bundle.getMessage("DIALOG_ConfirmDelete", Objects.requireNonNull(conRosterBox.getSelectedItem())),
                    Bundle.getMessage("DIALOG_NceDelete"),
                    JmriJOptionPane.OK_CANCEL_OPTION) != JmriJOptionPane.OK_OPTION) {
                return;
            }
            deleteRoster();
            changeButtons(false); // yes, clear delete button
            return;
        }
        if (ae.getSource() == previousButton) {
            consistSearchPrevious = true;
            consistNum = getConsist(); // check for valid and kick off read
        }
        if (ae.getSource() == nextButton) {
            consistSearchNext = true;
            consistNum = getConsist(); // check for valid and kick off read
        }
        if (ae.getSource() == getButton) {
            // Get Consist
            consistNum = getConsist();
        }
        if (ae.getSource() == backUpButton) {
            Thread mb = new NceConsistBackup(tc);
            mb.setName("Consist Backup");
            mb.start();
        }
        if (ae.getSource() == restoreButton) {
            Thread mr = new NceConsistRestore(tc);
            mr.setName("Consist Restore");
            mr.start();
        }
    }

    // One of six loco command buttons, add, replace or delete
    public void buttonActionCmdPerformed(java.awt.event.ActionEvent ae) {
        // if we're searching ignore user
        if (consistSearchNext || consistSearchPrevious || locoSearch) {
            return;
        }
        if (consistChanged()) {
            return;
        }
        if (ae.getSource() == cmdButton1) {
            modifyLocoFields(locoRosterBox1, locoTextField1, adrButton1,
                    dirButton1, cmdButton1);
        }
        if (ae.getSource() == cmdButton2) {
            modifyLocoFields(locoRosterBox2, locoTextField2, adrButton2,
                    dirButton2, cmdButton2);
        }
        if (ae.getSource() == cmdButton3) {
            modifyLocoFields(locoRosterBox3, locoTextField3, adrButton3,
                    dirButton3, cmdButton3);
        }
        if (ae.getSource() == cmdButton4) {
            modifyLocoFields(locoRosterBox4, locoTextField4, adrButton4,
                    dirButton4, cmdButton4);
        }
        if (ae.getSource() == cmdButton5) {
            modifyLocoFields(locoRosterBox5, locoTextField5, adrButton5,
                    dirButton5, cmdButton5);
        }
        if (ae.getSource() == cmdButton6) {
            modifyLocoFields(locoRosterBox6, locoTextField6, adrButton6,
                    dirButton6, cmdButton6);
        }
        if (updateRoster(consistTextField.getText())) {
            saveLoadButton.setEnabled(false);
        }
    }

    // one of six loco address type buttons
    public void buttonActionAdrPerformed(java.awt.event.ActionEvent ae) {
        // if we're searching ignore user
        if (consistSearchNext || consistSearchPrevious || locoSearch) {
            return;
        }
        if (consistChanged()) {
            return;
        }
        if (ae.getSource() == adrButton1) {
            toggleAdrButton(locoTextField1, adrButton1);
        }
        if (ae.getSource() == adrButton2) {
            toggleAdrButton(locoTextField2, adrButton2);
        }
        if (ae.getSource() == adrButton3) {
            toggleAdrButton(locoTextField3, adrButton3);
        }
        if (ae.getSource() == adrButton4) {
            toggleAdrButton(locoTextField4, adrButton4);
        }
        if (ae.getSource() == adrButton5) {
            toggleAdrButton(locoTextField5, adrButton5);
        }
        if (ae.getSource() == adrButton6) {
            toggleAdrButton(locoTextField6, adrButton6);
        }
    }

    private void toggleAdrButton(JTextField locoTextField, JButton adrButton) {
        if (validLocoAdr(locoTextField.getText()) < 0) {
            return;
        }
        if (locoTextField.getText().equals("")) {
            JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_EnterLocoB4AddrChg"),
                    Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
            return;
        } else {
            if (adrButton.getText().equals(Bundle.getMessage("KeyLONG"))) {
                if ((Integer.parseInt(locoTextField.getText()) < 128)
                        && (Integer.parseInt(locoTextField.getText()) > 0)) {
                    adrButton.setText(Bundle.getMessage("KeySHORT"));
                }
            } else {
                adrButton.setText(Bundle.getMessage("KeyLONG"));
            }
        }
    }

    // one of six loco direction buttons
    public void buttonActionDirPerformed(java.awt.event.ActionEvent ae) {
        // if we're searching ignore user
        if (consistSearchNext || consistSearchPrevious || locoSearch) {
            return;
        }
        if (consistChanged()) {
            return;
        }
        if (ae.getSource() == dirButton1) {
            toggleDirButton(locoTextField1, dirButton1, cmdButton1);
        }
        if (ae.getSource() == dirButton2) {
            toggleDirButton(locoTextField2, dirButton2, cmdButton2);
        }
        if (ae.getSource() == dirButton3) {
            toggleDirButton(locoTextField3, dirButton3, cmdButton3);
        }
        if (ae.getSource() == dirButton4) {
            toggleDirButton(locoTextField4, dirButton4, cmdButton4);
        }
        if (ae.getSource() == dirButton5) {
            toggleDirButton(locoTextField5, dirButton5, cmdButton5);
        }
        if (ae.getSource() == dirButton6) {
            toggleDirButton(locoTextField6, dirButton6, cmdButton6);
        }
        saveLoadButton.setEnabled(canLoad());
    }

    private void toggleDirButton(JTextField locoTextField, JButton dirButton,
            JButton cmdButton) {
        if (validLocoAdr(locoTextField.getText()) < 0) {
            return;
        }
        if (locoTextField.getText().equals("")) {
            JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_EnterLocoB4DirChg"),
                    Bundle.getMessage("DIALOG_NceConsist"), JmriJOptionPane.ERROR_MESSAGE);
            return;
        }
        cmdButton.setEnabled(true);
        if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
            dirButton.setText(Bundle.getMessage("KeyREV"));
        } else {
            dirButton.setText(Bundle.getMessage("KeyFWD"));
        }
    }

    // one of six roster select, load loco number and address length
    public void locoSelected(java.awt.event.ActionEvent ae) {
        if (ae.getSource() == locoRosterBox1) {
            rosterBoxSelect(locoRosterBox1, locoTextField1, adrButton1);
        }
        if (ae.getSource() == locoRosterBox2) {
            rosterBoxSelect(locoRosterBox2, locoTextField2, adrButton2);
        }
        if (ae.getSource() == locoRosterBox3) {
            rosterBoxSelect(locoRosterBox3, locoTextField3, adrButton3);
        }
        if (ae.getSource() == locoRosterBox4) {
            rosterBoxSelect(locoRosterBox4, locoTextField4, adrButton4);
        }
        if (ae.getSource() == locoRosterBox5) {
            rosterBoxSelect(locoRosterBox5, locoTextField5, adrButton5);
        }
        if (ae.getSource() == locoRosterBox6) {
            rosterBoxSelect(locoRosterBox6, locoTextField6, adrButton6);
        }
    }

    // load a loco from roster
    private void rosterBoxSelect(JComboBox<Object> locoRosterBox,
            JTextField locoTextField, JButton adrButton) {
        RosterEntry entry = null;
        Object o = locoRosterBox.getSelectedItem();
        if (o.getClass().equals(RosterEntry.class)) {
            entry = (RosterEntry) o;
        }
        if (entry != null) {
            DccLocoAddress a = entry.getDccLocoAddress();

            locoTextField.setText("" + a.getNumber());
            if (a.isLongAddress()) {
                adrButton.setText(Bundle.getMessage("KeyLONG"));
            } else {
                adrButton.setText(Bundle.getMessage("KeySHORT"));
            }
            // if lead loco get road number and name
            if (locoRosterBox == locoRosterBox1) {
                textConRoadName.setText(entry.getRoadName());
                textConRoadNumber.setText(entry.getRoadNumber());
                textConModel.setText(entry.getModel());
            }
        }
    }

    // load a consist from roster
    public void consistRosterSelected(java.awt.event.ActionEvent ae) {
        if (consistSearchNext || consistSearchPrevious || locoSearch) {
            return;
        }
        String entry = "";
        entry = conRosterBox.getSelectedItem().toString();
        log.debug("load consist {} from roster ", entry);
        if (entry.equals("")) {
            changeButtons(false);
            consistNum = getConsist(); // reload panel
            return;
        }
        changeButtons(true);
        loadRosterEntry(entry);
    }

    // checkbox modified
    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
        if (ae.getSource() == checkBoxConsist) {
            checkBoxConsist();
        }
    }

    private void checkBoxConsist() {
        if (checkBoxConsist.isSelected()) {
            conRosterBox.setEnabled(true);
            saveLoadButton.setVisible(true);
            saveLoadButton.setEnabled(canLoad());
            deleteButton.setVisible(true);
        } else {
            conRosterBox.setEnabled(false);
            conRosterBox.removeActionListener(consistRosterListener);
            conRosterBox.setSelectedIndex(0);
            conRosterBox.addActionListener(consistRosterListener);
            saveLoadButton.setVisible(false);
            saveLoadButton.setEnabled(false);
            deleteButton.setVisible(false);
            deleteButton.setEnabled(false);
        }
    }

    // gets the user supplied consist number and then reads NCE CS memory
    private int getConsist() {
        newConsist = true;
        int consistNumber = validConsist(consistTextField.getText());
        if (consistNumber == CONSIST_ERROR) {
            consistStatus.setText(Bundle.getMessage("EditStateERROR"));
            consistSearchPrevious = false;
            consistSearchNext = false;
            return consistNumber;
        }
        if (consistSearchNext || consistSearchPrevious) {
            consistCount = 0; // used to determine if all 127 consist have been read
            consistStatus.setText(Bundle.getMessage("EditStateSEARCH"));
        } else {
            consistStatus.setText(Bundle.getMessage("EditStateWAIT"));
            if (consistNumber == consistNum) {
                newConsist = false;
            }
        }

        // if busy don't request
        if (waiting > 0) {
            return consistNumber;
        }

        if (consistSearchNext) {
            readConsistMemory(consistNumber - 7, LEAD);
        } else {
            readConsistMemory(consistNumber, LEAD); // Get or Previous button
        }
        return consistNumber;
    }

    /**
     * Check for valid consist, popup error message if not
     *
     * @return true if valid
     */
    private boolean validConsist() {
        int consistNumber = validConsist(consistTextField.getText());
        if (consistNumber == CONSIST_ERROR) {
            consistStatus.setText(Bundle.getMessage("EditStateERROR"));
            JmriJOptionPane.showMessageDialog(this,
                    MessageFormat.format(Bundle.getMessage("ToolTipConsist"), new Object[] {CONSIST_MIN, CONSIST_MAX}), Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
            return false;
        }
        return true;
    }

    // Check for valid consist number, return number if valid, -1 or CONSIST_ERROR if not.
    private int validConsist(String s) {
        int consistNumber;
        try {
            consistNumber = Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return CONSIST_ERROR;
        }
        if (consistNumber < CONSIST_MIN || consistNumber > CONSIST_MAX) {
            JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_ConsistOutOfRange", s, CONSIST_MIN, CONSIST_MAX),
                    Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
            return CONSIST_ERROR;
        } else {
            return consistNumber;
        }
    }

    /**
     * @param s loco address
     * @return number if valid, -1 or ADDRESS_ERROR if not
     */
    private int validLocoAdr(String s) {
        int locoAddress;
        try {
            locoAddress = Integer.parseInt(s);
        } catch (NumberFormatException e) {
            locoAddress = ADDRESS_ERROR;
        }
        if (locoAddress < LOC_ADR_MIN || locoAddress > LOC_ADR_MAX) {
            JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_AddrRange"), Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
            return ADDRESS_ERROR;
        } else {
            return locoAddress;
        }
    }

    // check to see if user modified consist number
    private boolean consistChanged() {
        if (consistNum != validConsist(consistTextField.getText())) {
            JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_PressRead", Bundle.getMessage("KeyGET")),
                    Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
            return true;
        } else {
            newConsist = false;
            return false;
        }
    }

    /**
     * Reads 16 bytes of NCE consist memory based on consist number and loco
     * position in the consist 0=lead 1=rear 2=mid
     */
    private void readConsistMemory(int consistNum, int engPosition) {
        locoPosition = engPosition;
        int nceMemAddr = (consistNum * 2) + tc.csm.getConsistHeadAddr();
        if (locoPosition == REAR) {
            nceMemAddr = (consistNum * 2) + tc.csm.getConsistTailAddr();
        }
        if (locoPosition == MID) {
            nceMemAddr = (consistNum * 8) + tc.csm.getConsistMidAddr();
        }
        log.debug("Read consist ({}) position ({}) NCE memory address ({})", consistNum, engPosition, Integer.toHexString(nceMemAddr));
        byte[] bl = NceBinaryCommand.accMemoryRead(nceMemAddr);
        sendNceMessage(bl, NceMessage.REPLY_16);
    }

    NceConsistRosterEntry nceConsistRosterEntry;

    private void loadRosterEntry(String entry) {
        nceConsistRosterEntry = nceConsistRoster.entryFromTitle(entry);
        consistTextField.setText(nceConsistRosterEntry.getConsistNumber());
        int cNum = validConsist(nceConsistRosterEntry.getConsistNumber());

        if (0 < cNum) {
            log.debug("verify consist matches roster selection");
            verifyRosterMatch = true;
            consistNum = getConsist();
        } else {
            if (nceConsistRosterEntry.getConsistNumber().equals(Bundle.getMessage("CLEARED")) || nceConsistRosterEntry.getConsistNumber().equals("0")) {
                log.debug("search for empty consist");
                consistTextField.setText(Integer.toString(CONSIST_MAX));
                emptyConsistSearch = true;
                consistSearchNext = true;
                consistNum = getConsist();
                loadFullRoster(nceConsistRosterEntry);
                saveLoadButton.setEnabled(false);
            } else {
                JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_ConsistOutOfRange", CONSIST_MIN, CONSIST_MAX, consistNum),
                    Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
            }
        }
    }

    private void loadFullRoster(NceConsistRosterEntry nceConsistRosterEntry) {
        // get road name, number and model
        textConRoadName.setText(nceConsistRosterEntry.getRoadName());
        textConRoadNumber.setText(nceConsistRosterEntry.getRoadNumber());
        textConModel.setText(nceConsistRosterEntry.getModel());

        // load lead loco
        locoTextField1.setText(nceConsistRosterEntry.getLoco1DccAddress());
        adrButton1.setText(nceConsistRosterEntry.isLoco1LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
        dirButton1.setText(convertDTD(nceConsistRosterEntry.getLoco1Direction()));
        locoRosterBox1.setEnabled(true);
        locoTextField1.setEnabled(true);
        adrButton1.setEnabled(true);
        dirButton1.setEnabled(true);

        // load rear loco
        locoTextField2.setText(nceConsistRosterEntry.getLoco2DccAddress());
        adrButton2.setText(nceConsistRosterEntry.isLoco2LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
        dirButton2.setText(convertDTD(nceConsistRosterEntry.getLoco2Direction()));
        locoRosterBox2.setEnabled(true);
        locoTextField2.setEnabled(true);
        adrButton2.setEnabled(true);
        dirButton2.setEnabled(true);

        // load Mid1 loco
        locoTextField3.setText(nceConsistRosterEntry.getLoco3DccAddress());
        adrButton3.setText(nceConsistRosterEntry.isLoco3LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
        dirButton3.setText(convertDTD(nceConsistRosterEntry.getLoco3Direction()));
        locoRosterBox3.setEnabled(true);
        locoTextField3.setEnabled(true);
        adrButton3.setEnabled(true);
        dirButton3.setEnabled(true);

        // load Mid2 loco
        locoTextField4.setText(nceConsistRosterEntry.getLoco4DccAddress());
        adrButton4.setText(nceConsistRosterEntry.isLoco4LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
        dirButton4.setText(convertDTD(nceConsistRosterEntry.getLoco4Direction()));
        locoRosterBox4.setEnabled(true);
        locoTextField4.setEnabled(true);
        adrButton4.setEnabled(true);
        dirButton4.setEnabled(true);

        // load Mid3 loco
        locoTextField5.setText(nceConsistRosterEntry.getLoco5DccAddress());
        adrButton5.setText(nceConsistRosterEntry.isLoco5LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
        dirButton5.setText(convertDTD(nceConsistRosterEntry.getLoco5Direction()));
        locoRosterBox5.setEnabled(true);
        locoTextField5.setEnabled(true);
        adrButton5.setEnabled(true);
        dirButton5.setEnabled(true);

        // load Mid4 loco
        locoTextField6.setText(nceConsistRosterEntry.getLoco6DccAddress());
        adrButton6.setText(nceConsistRosterEntry.isLoco6LongAddress() ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
        dirButton6.setText(convertDTD(nceConsistRosterEntry.getLoco6Direction()));
        locoRosterBox6.setEnabled(true);
        locoTextField6.setEnabled(true);
        adrButton6.setEnabled(true);
        dirButton6.setEnabled(true);
    }

    /**
     * checks to see if all loco addresses in NCE consist match roster updates
     * road name, road number, and loco direction fields
     *
     * @return true if match
     */
    private boolean consistRosterMatch(NceConsistRosterEntry nceConsistRosterEntry) {
        if (consistTextField.getText().equals(nceConsistRosterEntry.getConsistNumber())
                && locoTextField1.getText().equals(nceConsistRosterEntry.getLoco1DccAddress())
                && locoTextField2.getText().equals(nceConsistRosterEntry.getLoco2DccAddress())
                && locoTextField3.getText().equals(nceConsistRosterEntry.getLoco3DccAddress())
                && locoTextField4.getText().equals(nceConsistRosterEntry.getLoco4DccAddress())
                && locoTextField5.getText().equals(nceConsistRosterEntry.getLoco5DccAddress())
                && locoTextField6.getText().equals(nceConsistRosterEntry.getLoco6DccAddress())) {
            // match!  Only load the elements needed
            if (newConsist) {
                textConRoadName.setText(nceConsistRosterEntry.getRoadName());
                textConRoadNumber.setText(nceConsistRosterEntry.getRoadNumber());
                textConModel.setText(nceConsistRosterEntry.getModel());
                dirButton1.setText(convertDTD(nceConsistRosterEntry.getLoco1Direction()));
                dirButton2.setText(convertDTD(nceConsistRosterEntry.getLoco2Direction()));
                dirButton3.setText(convertDTD(nceConsistRosterEntry.getLoco3Direction()));
                dirButton4.setText(convertDTD(nceConsistRosterEntry.getLoco4Direction()));
                dirButton5.setText(convertDTD(nceConsistRosterEntry.getLoco5Direction()));
                dirButton6.setText(convertDTD(nceConsistRosterEntry.getLoco6Direction()));
            }
            return true;
        } else {
            return false;
        }
    }

    private final boolean enablePartialMatch = true;

    /**
     * checks to see if some loco addresses in NCE consist match roster updates
     * road name, road number, and loco direction fields
     *
     * @return true if there was at least one match
     */
    private boolean consistRosterPartialMatch(NceConsistRosterEntry cre) {
        if (!enablePartialMatch) {
            return false;
        }
        // does loco1 match?
        if (consistTextField.getText().equals(cre.getConsistNumber())
                && locoTextField1.getText().equals(cre.getLoco1DccAddress())) {
            dirButton1.setText(convertDTD(cre.getLoco1Direction()));
            textConRoadName.setText(cre.getRoadName());
            textConRoadNumber.setText(cre.getRoadNumber());
            textConModel.setText(cre.getModel());
        } else {
            consistStatus.setText(Bundle.getMessage("EditStateUNKNOWN"));
            return false;
        }
        if (locoTextField2.getText().equals(cre.getLoco2DccAddress())) {
            dirButton2.setText(convertDTD(cre.getLoco2Direction()));
        }
        if (locoTextField3.getText().equals(cre.getLoco3DccAddress())) {
            dirButton3.setText(convertDTD(cre.getLoco3Direction()));
        }
        if (locoTextField4.getText().equals(cre.getLoco4DccAddress())) {
            dirButton4.setText(convertDTD(cre.getLoco4Direction()));
        }
        if (locoTextField5.getText().equals(cre.getLoco5DccAddress())) {
            dirButton5.setText(convertDTD(cre.getLoco5Direction()));
        }
        if (locoTextField6.getText().equals(cre.getLoco6DccAddress())) {
            dirButton6.setText(convertDTD(cre.getLoco6Direction()));
        }
        consistStatus.setText(Bundle.getMessage("EditStateMODIFIED"));
        return true;
    }

    protected List<NceConsistRosterEntry> consistList = new ArrayList<>();

    /**
     * returns true if update successful
     */
    private boolean updateRoster(String consistNumber) {
        if (!checkBoxConsist.isSelected()) {
            return false;
        }
        String id = locoTextField1.getText(); // lead loco is the consist id
        if (id.equals("")) {
            log.debug("Attempt to modify consist without valid id");
            return false;
        }
        // need rear loco to form a consist
        if (locoTextField2.getText().equals("")) {
            return false;
        }
        NceConsistRosterEntry nceConsistRosterEntry;
        consistList = nceConsistRoster.matchingList(null, null,
                null, null, null, null, null, null, null, id);
        // if consist doesn't exist in roster ask user if they want to create one
        if (consistList.isEmpty()) {
            if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("DIALOG_ConfirmAdd", id),
                    Bundle.getMessage("DIALOG_NceSave"),
                    JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
                return false;
            }
            nceConsistRosterEntry = new NceConsistRosterEntry();
            nceConsistRoster.addEntry(nceConsistRosterEntry);
            // roster entry exists, does it match?
        } else {
            nceConsistRosterEntry = nceConsistRoster.entryFromTitle(id);
            // if all of the loco addresses match, just update without telling user
            consistList = nceConsistRoster
                    .matchingList(null, null, null, locoTextField1.getText(),
                            locoTextField2.getText(), locoTextField3.getText(),
                            locoTextField4.getText(), locoTextField5.getText(),
                            locoTextField6.getText(), id);
            // if it doesn't match, do we want to modify it?
            if (consistList.isEmpty()) {
                if (JmriJOptionPane.showConfirmDialog(null,
                        Bundle.getMessage("DIALOG_ConfirmUpdate", id, getRosterText(nceConsistRosterEntry)),
                        Bundle.getMessage("DIALOG_NceUpdate"),
                        JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.NO_OPTION ) {
                    // update consist if command was to clear
                    if (consistNumber.equals(Bundle.getMessage("CLEARED"))) {
                        nceConsistRosterEntry.setConsistNumber(consistNumber);
                        writeRosterFile();
                    }
                    return false;
                }
            }
            log.debug("Modify consist {}", id);
        }
        // save all elements of a consist roster
        nceConsistRosterEntry.setId(id);
        nceConsistRosterEntry.setConsistNumber(consistNumber);
        nceConsistRosterEntry.setRoadName(textConRoadName.getText());
        nceConsistRosterEntry.setRoadNumber(textConRoadNumber.getText());
        nceConsistRosterEntry.setModel(textConModel.getText());
        // save lead loco
        nceConsistRosterEntry.setLoco1DccAddress(locoTextField1.getText());
        nceConsistRosterEntry.setLoco1LongAddress(adrButton1.getText().equals(Bundle.getMessage("KeyLONG")));
        nceConsistRosterEntry.setLoco1Direction(directionDTD(dirButton1));
        // save rear loco
        nceConsistRosterEntry.setLoco2DccAddress(locoTextField2.getText());
        nceConsistRosterEntry.setLoco2LongAddress(adrButton2.getText().equals(Bundle.getMessage("KeyLONG")));
        nceConsistRosterEntry.setLoco2Direction(directionDTD(dirButton2));
        // save Mid1 loco
        nceConsistRosterEntry.setLoco3DccAddress(locoTextField3.getText());
        nceConsistRosterEntry.setLoco3LongAddress(adrButton3.getText().equals(Bundle.getMessage("KeyLONG")));
        nceConsistRosterEntry.setLoco3Direction(directionDTD(dirButton3));
        // save Mid2 loco
        nceConsistRosterEntry.setLoco4DccAddress(locoTextField4.getText());
        nceConsistRosterEntry.setLoco4LongAddress(adrButton4.getText().equals(Bundle.getMessage("KeyLONG")));
        nceConsistRosterEntry.setLoco4Direction(directionDTD(dirButton4));
        // save Mid3 loco
        nceConsistRosterEntry.setLoco5DccAddress(locoTextField5.getText());
        nceConsistRosterEntry.setLoco5LongAddress(adrButton5.getText().equals(Bundle.getMessage("KeyLONG")));
        nceConsistRosterEntry.setLoco5Direction(directionDTD(dirButton5));
        // save Mid4 loco
        nceConsistRosterEntry.setLoco6DccAddress(locoTextField6.getText());
        nceConsistRosterEntry.setLoco6LongAddress(adrButton6.getText().equals(Bundle.getMessage("KeyLONG")));
        nceConsistRosterEntry.setLoco6Direction(directionDTD(dirButton6));

        writeRosterFile();
        return true;
    }

    /**
     * @return DTD direction format based on the loco direction button
     */
    private String directionDTD(JButton dirButton) {
        String formatDTD = Bundle.getMessage("DTD_UNKNOWN");
        if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
            formatDTD = Bundle.getMessage("DTD_FORWARD");
        }
        if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
            formatDTD = Bundle.getMessage("DTD_REVERSE");
        }
        return formatDTD;
    }

    /**
     * @return converts DTD direction to FWD, REV, and ??
     */
    private String convertDTD(String formatDTD) {
        String word = Bundle.getMessage("KeyQUESTION");
        if (formatDTD.equals(Bundle.getMessage("DTD_FORWARD"))) {
            word = Bundle.getMessage("KeyFWD");
        }
        if (formatDTD.equals(Bundle.getMessage("DTD_REVERSE"))) {
            word = Bundle.getMessage("KeyREV");
        }
        return word;
    }

    /**
     * @return converts DTD direction to FWD, REV, and ""
     */
    private String shortHandConvertDTD(String formatDTD) {
        String word = "";
        if (formatDTD.equals(Bundle.getMessage("DTD_FORWARD"))) {
            word = Bundle.getMessage("KeyFWD");
        }
        if (formatDTD.equals(Bundle.getMessage("DTD_REVERSE"))) {
            word = Bundle.getMessage("KeyREV");
        }
        return word;
    }

    // remove selected consist from roster
    private void deleteRoster() {
        String entry = conRosterBox.getSelectedItem().toString();
        log.debug("remove consist {} from roster ", entry);
        // delete it from roster
        nceConsistRoster.removeEntry(nceConsistRoster.entryFromTitle(entry));
        writeRosterFile();
    }

    private void writeRosterFile() {
        conRosterBox.removeActionListener(consistRosterListener);
        nceConsistRoster.writeRosterFile();
        nceConsistRoster.updateComboBox(conRosterBox);
        conRosterBox.insertItemAt("", 0);
        conRosterBox.setSelectedIndex(0);
        conRosterBox.addActionListener(consistRosterListener);
    }

    // can the consist be loaded into NCE memory?
    private boolean canLoad() {
        if (locoTextField1.getText().equals("")) {
            return false;
        }
        if (dirButton1.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
            return false;
        }
        if (locoTextField2.getText().equals("")) {
            return false;
        }
        if (dirButton2.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
            return false;
        }
        if (!locoTextField3.getText().equals("")
                && dirButton3.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
            return false;
        }
        if (!locoTextField4.getText().equals("")
                && dirButton4.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
            return false;
        }
        if (!locoTextField5.getText().equals("")
                && dirButton5.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
            return false;
        }
        if (!locoTextField6.getText().equals("")
                && dirButton6.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
            return false;
        }
        // okay to load, clean up empty loco fields
        if (locoTextField3.getText().equals("")) {
            dirButton3.setText(Bundle.getMessage("KeyQUESTION"));
        }
        if (locoTextField4.getText().equals("")) {
            dirButton4.setText(Bundle.getMessage("KeyQUESTION"));
        }
        if (locoTextField5.getText().equals("")) {
            dirButton5.setText(Bundle.getMessage("KeyQUESTION"));
        }
        if (locoTextField6.getText().equals("")) {
            dirButton6.setText(Bundle.getMessage("KeyQUESTION"));
        }
        if (saveLoadButton.getText().equals(Bundle.getMessage("KeyLOAD"))) {
            return true;
        } else if (exactMatch) {
            return false; // no need to save, exact match!
        } else {
            return true;
        }
    }

    // mimic NCE mid loco shift when there's empties
    private void loadShift() {
        for (int i = 0; i < 3; i++) {
            shiftOneLine(locoTextField5, adrButton5, dirButton5, locoTextField6,
                    adrButton6, dirButton6);
            shiftOneLine(locoTextField4, adrButton4, dirButton4, locoTextField5,
                    adrButton5, dirButton5);
            shiftOneLine(locoTextField3, adrButton3, dirButton3, locoTextField4,
                    adrButton4, dirButton4);
            shiftOneLine(locoTextField2, adrButton2, dirButton2, locoTextField3,
                    adrButton3, dirButton3);
        }
    }

    private void shiftOneLine(JTextField locoTextFieldLow, JButton adrButtonLow,
            JButton dirButtonLow, JTextField locoTextFieldHigh,
            JButton adrButtonHigh, JButton dirButtonHigh) {
        if (locoTextFieldLow.getText().equals("") && !locoTextFieldHigh.getText().equals((""))) {
            locoTextFieldLow.setText(locoTextFieldHigh.getText());
            adrButtonLow.setText(adrButtonHigh.getText());
            dirButtonLow.setText(dirButtonHigh.getText());
            dirButtonHigh.setText(Bundle.getMessage("KeyQUESTION"));
            locoTextFieldHigh.setText("");
        } else {
            return;
        }
    }

    // change button operation during load consist from roster
    private void changeButtons(boolean rosterDisplay) {
        if (rosterDisplay) {
            clearCancelButton.setText(Bundle.getMessage("KeyCANCEL"));
            clearCancelButton.setToolTipText(Bundle.getMessage("ToolTipCancel"));
            clearCancelButton.setEnabled(true);
            saveLoadButton.setText(Bundle.getMessage("KeyLOAD"));
            saveLoadButton.setToolTipText(Bundle.getMessage("ToolTipLoad"));
        } else {
            clearCancelButton.setText(Bundle.getMessage("KeyCLEAR"));
            clearCancelButton.setToolTipText(Bundle.getMessage("ToolTipClear"));
            saveLoadButton.setText(Bundle.getMessage("KeySAVE"));
            saveLoadButton.setToolTipText(Bundle.getMessage("ToolTipSave"));
            clearCancelButton.setEnabled(!locoTextField1.getText().equals(""));
        }

        // toggle (on if we're loading a consist from roster)
        deleteButton.setEnabled(rosterDisplay);

        // toggle (off if we're loading a consist from roster)
        previousButton.setEnabled(!rosterDisplay);
        nextButton.setEnabled(!rosterDisplay);
        getButton.setEnabled(!rosterDisplay);
        backUpButton.setEnabled(!rosterDisplay);
        restoreButton.setEnabled(!rosterDisplay);
        saveLoadButton.setEnabled(!rosterDisplay);

        cmdButton1.setVisible(!rosterDisplay);
        cmdButton2.setVisible(!rosterDisplay);
        cmdButton3.setVisible(!rosterDisplay);
        cmdButton4.setVisible(!rosterDisplay);
        cmdButton5.setVisible(!rosterDisplay);
        cmdButton6.setVisible(!rosterDisplay);
    }

    /**
     * Kills consist using lead loco address
     */
    private void killConsist() {
        if (validLocoAdr(locoTextField1.getText()) < 0) // special case where lead or rear loco was being replaced
        {
            return;
        }
        int locoAddr = getLocoAddr(locoTextField1, adrButton1);
        sendNceBinaryCommand(locoAddr, NceMessage.LOCO_CMD_KILL_CONSIST,
                (byte) 0);
    }

    private void sendNceBinaryCommand(int locoAddr, byte nceLocoCmd,
            byte consistNumber) {
        byte[] bl = NceBinaryCommand.nceLocoCmd(locoAddr, nceLocoCmd,
                consistNumber);
        sendNceMessage(bl, NceMessage.REPLY_1);
    }

    @Override
    public void message(NceMessage m) {
    } // ignore replies

    // NCE CS response from add, delete, save, get, next, previous, etc
    // A single byte response is expected from commands
    // A 16 byte response is expected when loading a consist or searching
    @Override
    public void reply(NceReply nceReply) {
        if (waiting <= 0) {
            log.error("unexpected response");
            return;
        }
        waiting--;

        if (nceReply.getNumDataElements() != replyLen) {
            consistStatus.setText(Bundle.getMessage("EditStateERROR"));
            log.error("reply length error, expecting: {} got: {}", replyLen, nceReply.getNumDataElements());
            return;
        }

        // response to commands
        if (replyLen == NceMessage.REPLY_1) {
            // Looking for proper response
            int recChar = nceReply.getElement(0);
            log.debug("command reply: {}", recChar);
            if (recChar == NceMessage.NCE_OKAY) {
                if (locoSearch && waiting == 0) {
                    readConsistMemory(consistNumVerify, LEAD);
                    consistStatus.setText(Bundle.getMessage("EditStateVERIFY"));
                    return;
                }
                if (refresh && waiting == 0) {
                    refresh = false;
                    // update panel
                    readConsistMemory(consistNum, LEAD);
                    return;
                }
                consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
            } else {
                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
            }
            return;
        }

        // Consist memory read
        if (replyLen == NceMessage.REPLY_16) {
            // are we verifying that loco isn't already part of a consist?
            if (locoSearch) {
                // search the 16 bytes for a loco match
                for (int i = 0; i < NceMessage.REPLY_16;) {
                    int recChar_High = nceReply.getElement(i++);
                    recChar_High = (recChar_High << 8) & 0xFF00;
                    int recChar_Low = nceReply.getElement(i++);
                    recChar_Low = recChar_Low & 0xFF;
                    int locoAddress = recChar_High + recChar_Low;
                    // does it match any of the locos?
                    for (int j = 0; j < locoVerifyList.length; j++) {
                        if (locoVerifyList[j] == 0) {
                            break;  // done searching
                        }
                        if (locoAddress == locoVerifyList[j]) {
                            // ignore matching the consist that we're adding the
                            // loco
                            if (consistNumVerify != consistNum) {
                                locoSearch = false; // quit the search
                                consistStatus.setText(Bundle.getMessage("EditStateERROR"));
                                locoNumInUse = locoAddress & 0x3FFF;
                                queueError(ERROR_LOCO_IN_USE);
                                return;
                            }
                        }
                    }
                    consistNumVerify++;
                }
                if (consistNumVerify > CONSIST_MAX) {
                    if (locoPosition == LEAD) {
                        // now verify the rear loco consist
                        locoPosition = REAR;
                        consistNumVerify = 0;
                    } else {
                        // verify complete, loco address is unique
                        locoSearch = false;
                        consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
                        // determine the type of verification
                        if (verifyType == VERIFY_LEAD_REAR) {
                            if (refresh && waiting == 0) {
                                refresh = false;
                                // update panel
                                readConsistMemory(consistNum, LEAD);
                            }
                        } else if (verifyType == VERIFY_MID_FWD) {
                            sendNceBinaryCommand(locoVerifyList[0],
                                    NceMessage.LOCO_CMD_FWD_CONSIST_MID,
                                    (byte) consistNum);
                        } else if (verifyType == VERIFY_MID_REV) {
                            sendNceBinaryCommand(locoVerifyList[0],
                                    NceMessage.LOCO_CMD_REV_CONSIST_MID,
                                    (byte) consistNum);
                        } else if (verifyType == VERIFY_ALL) {
                            fullLoad();
                        } else {
                            log.debug("verifyType out of range");
                        }
                        verifyType = VERIFY_DONE;
                        return;
                    }
                }
                // continue verify
                readConsistMemory(consistNumVerify, locoPosition);
                return;
            }
            // are we searching for a consist?
            if (consistSearchNext) {
                for (int i = NceMessage.REPLY_16 - 1; i > 0;) {
                    int recChar_Low = nceReply.getElement(i--);
                    recChar_Low = recChar_Low & 0xFF;
                    int recChar_High = nceReply.getElement(i--);
                    recChar_High = (recChar_High << 8) & 0xFF00;
                    int locoAddress = recChar_High + recChar_Low;

                    if (emptyConsistSearch) {
                        if (locoAddress == 0) {
                            // found an empty consist!
                            consistSearchNext = false;
                            emptyConsistSearch = false;
                            consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
                            saveLoadButton.setEnabled(canLoad());
                            return;
                        }
                    } else if (checkBoxEmpty.isSelected()) {
                        if (locoAddress == 0 && consistCount > 0) {
                            // found an empty consist!
                            log.debug("Empty consist ({})", consistNum);
                            consistSearchNext = false;
                            // update the panel
                            readConsistMemory(consistNum, LEAD);
                            return;
                        }
                    } else if (locoAddress != 0 && consistCount > 0) {
                        // found a consist!
                        consistSearchNext = false;
                        readConsistMemory(consistNum, LEAD);
                        return;
                    }
                    if (++consistCount > CONSIST_MAX) {
                        // could not find a consist
                        consistSearchNext = false;
                        consistStatus.setText(Bundle.getMessage("EditStateNONE"));
                        if (emptyConsistSearch) {
                            emptyConsistSearch = false;
                            queueError(ERROR_NO_EMPTY_CONSIST);
                        }
                        return;  // don't update panel
                    }
                    // look for next consist
                    consistNum--;
                    if (consistNum < CONSIST_MIN) {
                        consistNum = CONSIST_MAX;
                    }
                    consistTextField.setText(Integer.toString(consistNum));
                    if (consistNum == CONSIST_MAX) {
                        // we need to read NCE memory to continue
                        break;
                    }
                }
                // continue searching
                readConsistMemory(consistNum - 7, LEAD);
                return;
            }
            // are we searching?
            if (consistSearchPrevious) {
                for (int i = 0; i < NceMessage.REPLY_16;) {
                    int recChar_High = nceReply.getElement(i++);
                    recChar_High = (recChar_High << 8) & 0xFF00;
                    int recChar_Low = nceReply.getElement(i++);
                    recChar_Low = recChar_Low & 0xFF;
                    int locoAddress = recChar_High + recChar_Low;

                    if (checkBoxEmpty.isSelected()) {
                        if (locoAddress == 0 && consistCount > 0) {
                            consistSearchPrevious = false;
                            break;
                        }
                    } else if (locoAddress != 0 && consistCount > 0) {
                            consistSearchPrevious = false;
                            break;
                    }
                    if (++consistCount > CONSIST_MAX) {
                        consistStatus.setText(Bundle.getMessage("EditStateNONE"));
                        consistSearchPrevious = false;
                        return;  // don't update the panel
                    }
                    consistNum++;
                    if (consistNum > CONSIST_MAX) {
                        consistNum = CONSIST_MIN;
                    }
                    consistTextField.setText(Integer.toString(consistNum));
                    // have we wrapped? if yes, need to read NCE memory
                    if (consistNum == CONSIST_MIN) {
                        break;
                    }
                }
                readConsistMemory(consistNum, LEAD);
                return;
            }

            // Panel update, load lead loco
            if (locoPosition == LEAD) {
                boolean loco1exists = updateLocoFields(nceReply, 0, locoRosterBox1,
                        locoTextField1, adrButton1, dirButton1, cmdButton1);
                if (clearCancelButton.getText().equals(Bundle.getMessage("KeyCLEAR"))) {
                    clearCancelButton.setEnabled(loco1exists);
                }

                // load rear loco
            } else if (locoPosition == REAR) {
                updateLocoFields(nceReply, 0, locoRosterBox2, locoTextField2,
                        adrButton2, dirButton2, cmdButton2);

                // load mid locos
            } else {
                updateLocoFields(nceReply, 0, locoRosterBox3, locoTextField3,
                        adrButton3, dirButton3, cmdButton3);
                updateLocoFields(nceReply, 2, locoRosterBox4, locoTextField4,
                        adrButton4, dirButton4, cmdButton4);
                updateLocoFields(nceReply, 4, locoRosterBox5, locoTextField5,
                        adrButton5, dirButton5, cmdButton5);
                updateLocoFields(nceReply, 6, locoRosterBox6, locoTextField6,
                        adrButton6, dirButton6, cmdButton6);
                consistStatus.setText(Bundle.getMessage("EditStateOKAY"));
                checkForRosterMatch();
                saveLoadButton.setEnabled(canLoad());
            }
            // read the next loco number in the consist
            if (locoPosition == LEAD || locoPosition == REAR) {
                locoPosition++;
                readConsistMemory(consistNum, locoPosition);
            }
        }
    }

    private boolean exactMatch = false;

    private void checkForRosterMatch() {
        exactMatch = false;
        if (!verifyRosterMatch) {
            nceConsistRosterEntry = nceConsistRoster.entryFromTitle(locoTextField1.getText());
        }
        if (nceConsistRosterEntry == null) {
            if (checkBoxConsist.isSelected() && !locoTextField1.getText().equals("")) {
                consistStatus.setText(Bundle.getMessage("EditStateUNKNOWN"));
            } else {
                textConRoadName.setText("");
            }
            textConRoadNumber.setText("");
            textConModel.setText("");
            return;
        }
        if (consistRosterMatch(nceConsistRosterEntry)) {
            exactMatch = true;
            // exact match!
            if (verifyRosterMatch) {
                queueError(WARN_CONSIST_ALREADY_LOADED);
            }
            verifyRosterMatch = false;
        } else {
            // not an exact match!
            if (verifyRosterMatch) {
                queueError(ERROR_CONSIST_DOESNT_MATCH);
            }
            verifyRosterMatch = false;
            if (!consistRosterPartialMatch(nceConsistRosterEntry)) {
                textConRoadName.setText("");
                textConRoadNumber.setText("");
                textConModel.setText("");
            }
        }
    }

    // update loco fields, returns false if loco address is null
    private boolean updateLocoFields(NceReply r, int index,
            JComboBox<Object> locoRosterBox, JTextField locoTextField,
            JButton adrButton, JButton dirButton, JButton cmdButton) {
        // index = 0 for lead and rear locos, 0,2,4,6 for mid
        String locoAddrText = getLocoAddrText(r, index);
        boolean locoType = getLocoAddressType(r, index); // Long or short address?
        String locoDirection = getLocoDirection(dirButton);

        locoTextField.setText(locoAddrText);
        locoRosterBox.setSelectedIndex(0);

        if (locoAddrText.equals("") || locoAddrText.equals(Bundle.getMessage("REPLACE_LOCO"))) {
            locoRosterBox.setEnabled(true);
            locoTextField.setEnabled(true);
            cmdButton.setText(Bundle.getMessage("KeyADD"));
            cmdButton.setVisible(true);
            cmdButton.setEnabled(false);
            cmdButton.setToolTipText(Bundle.getMessage("ToolTipAdd"));
            dirButton.setText(Bundle.getMessage("KeyQUESTION"));
            dirButton.setEnabled(true);
            adrButton.setText(Bundle.getMessage("KeyLONG"));
            adrButton.setEnabled(true);
            return false;
        } else {
            locoTextField.setText(locoAddrText);
            locoRosterBox.setEnabled(false);
            locoTextField.setEnabled(false);
            cmdButton.setEnabled(true);
            dirButton.setText(locoDirection);
            dirButton.setEnabled(false);
            adrButton.setText((locoType) ? Bundle.getMessage("KeyLONG") : Bundle.getMessage("KeySHORT"));
            adrButton.setEnabled(false);

            // can not delete lead or rear locos, but can replace
            if (locoTextField == locoTextField1 || locoTextField == locoTextField2) {
                cmdButton.setText(Bundle.getMessage("KeyREPLACE"));
                cmdButton.setToolTipText("Press to delete and replace this loco");
            } else {
                cmdButton.setText(Bundle.getMessage("KeyDELETE"));
                cmdButton.setToolTipText("Press to delete this loco from consist");
            }
            return true;
        }
    }

    // modify loco fields because an Add, Replace, Delete button has been pressed
    private void modifyLocoFields(JComboBox<Object> locoRosterBox,
            JTextField locoTextField, JButton adrButton, JButton dirButton,
            JButton cmdButton) {
        if (validLocoAdr(locoTextField.getText()) < 0) {
            return;
        }
        byte consistNumber = (byte) validConsist(consistTextField.getText());
        if (consistNumber < 0) {
            return;
        }
        if (locoTextField.getText().equals("")) {
            JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_EnterLocoB4Add"),
                    Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
            return;
        }
        // set reflesh flag to update panel
        refresh = true;
        int locoAddr = getLocoAddr(locoTextField, adrButton);

        if (cmdButton.getText().equals(Bundle.getMessage("KeyDELETE"))) {
            sendNceBinaryCommand(locoAddr,
                    NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);

        } else if (cmdButton.getText().equals(Bundle.getMessage("KeyREPLACE"))) {

            // Kill refresh flag, no update when replacing loco
            refresh = false;

            // allow user to add loco to lead or rear consist
            locoRosterBox.setEnabled(true);
            locoTextField.setText("");
            locoTextField.setEnabled(true);
            adrButton.setText(Bundle.getMessage("KeyLONG"));
            adrButton.setEnabled(true);
            dirButton.setText(Bundle.getMessage("KeyQUESTION"));
            dirButton.setEnabled(true);
            cmdButton.setText(Bundle.getMessage("KeyADD"));
            cmdButton.setToolTipText(Bundle.getMessage("ToolTipAdd"));

            // now update CS memory in case user doesn't use the Add button
            // this will also allow us to delete the loco from the layout
            if (locoTextField == locoTextField1) {
                // replace lead loco
                sendNceBinaryCommand(LOC_ADR_REPLACE,
                        NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, consistNumber);
                // no lead loco so we can't kill the consist
                clearCancelButton.setEnabled(false);
            } else {
                // replace rear loco
                sendNceBinaryCommand(LOC_ADR_REPLACE,
                        NceMessage.LOCO_CMD_FWD_CONSIST_REAR, consistNumber);
            }
            // now delete lead or rear loco from layout
            sendNceBinaryCommand(locoAddr,
                    NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);
        } else {
            // ADD button has been pressed
            if (dirButton.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
                JmriJOptionPane.showMessageDialog(this,
                        Bundle.getMessage("DIALOG_SetDirB4Consist"),
                        Bundle.getMessage("DIALOG_NceConsist"), JmriJOptionPane.ERROR_MESSAGE);

                // kill refresh flag, no update if Add button is enabled
                // and loco direction isn't known (lead, rear, replacement)
                refresh = false;
                return;
            }
            // delete loco from any existing consists
            sendNceBinaryCommand(locoAddr,
                    NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);
            synchronized (this) {
                try {
                    wait(DELAY_AFTER_CLEAR);  // needed for booster to reset
                } catch (InterruptedException ignored) {
                }
            }
            // check to see if loco is already a lead or rear in another consist
            verifyLocoAddr(locoAddr);

            // now we need to determine if lead, rear, or mid loco
            // lead loco?
            if (locoTextField == locoTextField1) {
                if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
                    sendNceBinaryCommand(locoAddr,
                            NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, consistNumber);
                }
                if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
                    sendNceBinaryCommand(locoAddr,
                            NceMessage.LOCO_CMD_REV_CONSIST_LEAD, consistNumber);
                }
                // rear loco?
            } else if (locoTextField == locoTextField2) {
                if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
                    sendNceBinaryCommand(locoAddr,
                            NceMessage.LOCO_CMD_FWD_CONSIST_REAR, consistNumber);
                }
                if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
                    sendNceBinaryCommand(locoAddr,
                            NceMessage.LOCO_CMD_REV_CONSIST_REAR, consistNumber);
                }
                // must be mid loco
            } else {
                // wait for verify to complete before updating mid loco
                if (locoSearch) {
                    if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
                        verifyType = VERIFY_MID_FWD;
                    } else {
                        verifyType = VERIFY_MID_REV;
                    }
                    // no verify, just load and go!
                } else {
                    if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
                        sendNceBinaryCommand(locoAddr,
                                NceMessage.LOCO_CMD_FWD_CONSIST_MID, consistNumber);
                    }
                    if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
                        sendNceBinaryCommand(locoAddr,
                                NceMessage.LOCO_CMD_REV_CONSIST_MID, consistNumber);
                    }
                }
            }
        }
    }

    private void fullLoad() {
        refresh = true;
        loadOneLine(locoRosterBox1, locoTextField1, adrButton1,
                dirButton1, cmdButton1);
        loadOneLine(locoRosterBox2, locoTextField2, adrButton2,
                dirButton2, cmdButton2);
        loadOneLine(locoRosterBox3, locoTextField3, adrButton3,
                dirButton3, cmdButton3);
        loadOneLine(locoRosterBox4, locoTextField4, adrButton4,
                dirButton4, cmdButton4);
        loadOneLine(locoRosterBox5, locoTextField5, adrButton5,
                dirButton5, cmdButton5);
        loadOneLine(locoRosterBox6, locoTextField6, adrButton6,
                dirButton6, cmdButton6);
        changeButtons(false);
    }

    /**
     * updates NCE CS based on the loco line supplied called by load button
     *
     */
    private void loadOneLine(JComboBox<Object> locoRosterBox, JTextField locoTextField,
            JButton adrButton, JButton dirButton, JButton cmdButton) {
        if (locoTextField.getText().equals("")) {
            return;
        }
        if (validLocoAdr(locoTextField.getText()) < 0) {
            return;
        }
        byte cN = (byte) validConsist(consistTextField.getText());
        if (cN < 0) {
            return;
        }

        int locoAddr = getLocoAddr(locoTextField, adrButton);

        // ADD loco to consist
        if (dirButton.getText().equals(Bundle.getMessage("KeyQUESTION"))) {
            JmriJOptionPane.showMessageDialog(this,
                    Bundle.getMessage("DIALOG_SetDirB4Consist"), Bundle.getMessage("DIALOG_NceConsist"),
                    JmriJOptionPane.ERROR_MESSAGE);
            return;
        }

        // delete loco from any existing consists
        sendNceBinaryCommand(locoAddr,
                NceMessage.LOCO_CMD_DELETE_LOCO_CONSIST, (byte) 0);
        synchronized (this) {
            try {
                wait(DELAY_AFTER_CLEAR);  // needed for booster to reset
            } catch (InterruptedException ignored) {
            }
        }
        // now we need to determine if lead, rear, or mid loco
        // lead loco?
        if (locoTextField == locoTextField1) {
            // kill the consist first to clear NCE CS memory
            sendNceBinaryCommand(locoAddr,
                    NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, cN);
            sendNceBinaryCommand(locoAddr, NceMessage.LOCO_CMD_KILL_CONSIST,
                    (byte) 0);
            // now load
            if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
                sendNceBinaryCommand(locoAddr,
                        NceMessage.LOCO_CMD_FWD_CONSIST_LEAD, cN);
            }
            if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
                sendNceBinaryCommand(locoAddr,
                        NceMessage.LOCO_CMD_REV_CONSIST_LEAD, cN);
            }
            // rear loco?
        } else if (locoTextField == locoTextField2) {
            if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
                sendNceBinaryCommand(locoAddr,
                        NceMessage.LOCO_CMD_FWD_CONSIST_REAR, cN);
            }
            if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
                sendNceBinaryCommand(locoAddr,
                        NceMessage.LOCO_CMD_REV_CONSIST_REAR, cN);
            }
            // must be mid loco
        } else {
            if (dirButton.getText().equals(Bundle.getMessage("KeyFWD"))) {
                sendNceBinaryCommand(locoAddr,
                        NceMessage.LOCO_CMD_FWD_CONSIST_MID, cN);
            }
            if (dirButton.getText().equals(Bundle.getMessage("KeyREV"))) {
                sendNceBinaryCommand(locoAddr,
                        NceMessage.LOCO_CMD_REV_CONSIST_MID, cN);
            }
        }
    }

    private int getLocoAddr(JTextField locoTextField, JButton adrButton) {
        int locoAddr = Integer.parseInt(locoTextField.getText());
        if (locoAddr >= 128) {
            locoAddr += 0xC000;
        } else if (adrButton.getText().equals(Bundle.getMessage("KeyLONG"))) {
            locoAddr += 0xC000;
        }
        return locoAddr;
    }

    private void sendNceMessage(byte[] b, int replyLength) {
        NceMessage m = NceMessage.createBinaryMessage(tc, b, replyLength);
        waiting++;
        replyLen = replyLength; // Expect n byte response
        tc.sendNceMessage(m, this);
    }

    // get loco address type, returns true if long
    private boolean getLocoAddressType(NceReply r, int i) {
        int rC = r.getElement(i);
        rC = rC & 0xC0; // long address if 2 msb are set
        if (rC == 0xC0) {
            return true;
        } else {
            return false;
        }
    }

    private String getLocoAddrText(NceReply r, int i) {
        int rC_u = r.getElement(i++);
        int rC = (rC_u << 8) & 0x3F00;
        int rC_l = r.getElement(i);
        rC = rC + (rC_l & 0xFF);
        String locoAddrText = "";
        if ((rC_u != 0) || (rC_l != 0)) {
            locoAddrText = Integer.toString(rC);
        }
        if (rC == LOC_ADR_REPLACE) {
            locoAddrText = Bundle.getMessage("REPLACE_LOCO");
        }
        return locoAddrText;
    }

    private String getLocoDirection(JButton dirButton) {
        if (newConsist) {
            return Bundle.getMessage("KeyQUESTION");
        } else {
            return dirButton.getText();
        }
    }

    // check command station memory for lead or rear loco match
    private void verifyLocoAddr(int locoAddr) {
        verifyType = VERIFY_LEAD_REAR;
        if (checkBoxVerify.isSelected()) {
            locoVerifyList[0] = locoAddr;
            locoVerifyList[1] = 0;  // end of list
            locoSearch = true;
            consistNumVerify = 0;
        }
    }

    // check command station memory for lead or rear loco match
    private boolean verifyAllLocoAddr() {
        verifyType = VERIFY_ALL;
        if (checkBoxVerify.isSelected()) {
            int i = 0;
            if (!locoTextField1.getText().equals("") && validLocoAdr(locoTextField1.getText()) > 0) {
                locoVerifyList[i++] = getLocoAddr(locoTextField1, adrButton1);
            }
            if (!locoTextField2.getText().equals("") && validLocoAdr(locoTextField2.getText()) > 0) {
                locoVerifyList[i++] = getLocoAddr(locoTextField2, adrButton2);
            }
            if (!locoTextField3.getText().equals("") && validLocoAdr(locoTextField3.getText()) > 0) {
                locoVerifyList[i++] = getLocoAddr(locoTextField3, adrButton3);
            }
            if (!locoTextField4.getText().equals("") && validLocoAdr(locoTextField4.getText()) > 0) {
                locoVerifyList[i++] = getLocoAddr(locoTextField4, adrButton4);
            }
            if (!locoTextField5.getText().equals("") && validLocoAdr(locoTextField5.getText()) > 0) {
                locoVerifyList[i++] = getLocoAddr(locoTextField5, adrButton5);
            }
            if (!locoTextField6.getText().equals("") && validLocoAdr(locoTextField6.getText()) > 0) {
                locoVerifyList[i++] = getLocoAddr(locoTextField6, adrButton6);
            } else {
                locoVerifyList[i] = 0;
            }
            locoSearch = true;
            consistNumVerify = 0;
            consistStatus.setText(Bundle.getMessage("EditStateVERIFY"));
            readConsistMemory(consistNumVerify, LEAD);
            return true;
        }
        return false;
    }

    private void addLocoRow(JComponent col1, JComponent col2, JComponent col3,
            JComponent col4, JComponent col5, JComponent col6, int row) {
        addItem(col1, 0, row);
        addItem(col2, 1, row);
        addItem(col3, 2, row);
        addItem(col4, 3, row);
        addItem(col5, 4, row);
        addItem(col6, 5, row);
    }

    private void addItem(JComponent c, int x, int y) {
        GridBagConstraints gc = new GridBagConstraints();
        gc.gridx = x;
        gc.gridy = y;
        gc.weightx = 100.0;
        gc.weighty = 100.0;
        add(c, gc);
    }

    private void addButtonAction(JButton b) {
        b.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent e) {
                buttonActionPerformed(e);
            }
        });
    }

    private void addCheckBoxAction(JCheckBox cb) {
        cb.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent e) {
                checkBoxActionPerformed(e);
            }
        });
    }

    private void enableAllLocoRows(boolean flag) {
        enableLocoRow(flag, locoTextField1, locoRosterBox1,
                adrButton1, dirButton1, cmdButton1);
        enableLocoRow(flag, locoTextField2, locoRosterBox2,
                adrButton2, dirButton2, cmdButton2);
        enableLocoRow(flag, locoTextField3, locoRosterBox3,
                adrButton3, dirButton3, cmdButton3);
        enableLocoRow(flag, locoTextField4, locoRosterBox4,
                adrButton4, dirButton4, cmdButton4);
        enableLocoRow(flag, locoTextField5, locoRosterBox5,
                adrButton5, dirButton5, cmdButton5);
        enableLocoRow(flag, locoTextField6, locoRosterBox6,
                adrButton6, dirButton6, cmdButton6);
    }

    private void enableLocoRow(boolean flag, JTextField locoTextField,
            JComboBox<Object> locoRosterBox, JButton adrButton, JButton dirButton,
            JButton cmdButton) {
        locoTextField.setEnabled(flag);
        locoRosterBox.setEnabled(flag);
        adrButton.setEnabled(flag);
        dirButton.setEnabled(flag);
        cmdButton.setEnabled(flag);
    }

    // initialize loco fields
    private void initLocoFields() {
        initLocoRow(1, Bundle.getMessage("LeadLabel"), textLoco1, locoTextField1, locoRosterBox1,
                adrButton1, dirButton1, cmdButton1);
        initLocoRow(2, Bundle.getMessage("RearLabel"), textLoco2, locoTextField2, locoRosterBox2,
                adrButton2, dirButton2, cmdButton2);
        initLocoRow(3, Bundle.getMessage("MidLabel", "1"), textLoco3, locoTextField3, locoRosterBox3,
                adrButton3, dirButton3, cmdButton3);
        initLocoRow(4, Bundle.getMessage("MidLabel", "2"), textLoco4, locoTextField4, locoRosterBox4,
                adrButton4, dirButton4, cmdButton4);
        initLocoRow(5, Bundle.getMessage("MidLabel", "3"), textLoco5, locoTextField5, locoRosterBox5,
                adrButton5, dirButton5, cmdButton5);
        initLocoRow(6, Bundle.getMessage("MidLabel", "4"), textLoco6, locoTextField6, locoRosterBox6,
                adrButton6, dirButton6, cmdButton6);
    }

    private void initLocoRow(int row, String s, JLabel textLoco,
            JTextField locoTextField, JComboBox<Object> locoRosterBox,
            JButton adrButton, JButton dirButton, JButton cmdButton) {

        textLoco.setText(s);
        textLoco.setVisible(true);

        adrButton.setText(Bundle.getMessage("KeyLONG"));
        adrButton.setVisible(true);
        adrButton.setEnabled(false);
        adrButton.setToolTipText(Bundle.getMessage("ToolTipAddressType"));
        adrButton.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent e) {
                buttonActionAdrPerformed(e);
            }
        });

        locoRosterBox.setVisible(true);
        locoRosterBox.setEnabled(false);
        locoRosterBox.setToolTipText(Bundle.getMessage("ToolTipSelectLoco"));
        locoRosterBox.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent e) {
                locoSelected(e);
            }
        });

        dirButton.setText(Bundle.getMessage("KeyQUESTION"));
        dirButton.setVisible(true);
        dirButton.setEnabled(false);
        dirButton.setToolTipText(Bundle.getMessage("ToolTipDirection"));
        dirButton.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent e) {
                buttonActionDirPerformed(e);
            }
        });

        cmdButton.setText(Bundle.getMessage("KeyADD"));
        cmdButton.setVisible(true);
        cmdButton.setEnabled(false);
        cmdButton.setToolTipText(Bundle.getMessage("ToolTipAdd"));
        cmdButton.addActionListener(this::buttonActionCmdPerformed);

        locoTextField.setText("");
        locoTextField.setEnabled(false);
        locoTextField.setToolTipText(Bundle.getMessage("ToolTipEnterLoco"));
        locoTextField.setMaximumSize(new Dimension(
                locoTextField.getMaximumSize().width, locoTextField
                .getPreferredSize().height));
    }

    ActionListener consistRosterListener;

    private void initConsistRoster(JComboBox<String> conRosterBox) {
        conRosterBox.insertItemAt("", 0);
        conRosterBox.setSelectedIndex(0);
        conRosterBox.setVisible(true);
        conRosterBox.setEnabled(false);
        conRosterBox.setToolTipText(Bundle.getMessage("ToolTipSelectConsist"));
        conRosterBox.addActionListener(consistRosterListener = this::consistRosterSelected);
    }

    private static final int ERROR_LOCO_IN_USE = 1;
    private static final int ERROR_NO_EMPTY_CONSIST = 2;
    private static final int ERROR_CONSIST_DOESNT_MATCH = 3;
    private static final int WARN_CONSIST_ALREADY_LOADED = 4;
    private int locoNumInUse;       // report loco alreay in use
    private int errorCode = 0;

    private void queueError(int errorCode) {
        log.debug("queue warning/error message: {}", errorCode);
        if (this.errorCode != 0) {
            log.debug("multiple errors reported {}", this.errorCode);
            return;
        }
        this.errorCode = errorCode;
        // Bad to stop receive thread with JmriJOptionPane error message
        // so start up a new thread to report error
        Thread errorThread = new Thread(new Runnable() {
            @Override
            public void run() {
                reportError();
            }
        });
        errorThread.setName("Report Error"); // NOI18N
        errorThread.start();
    }

    public void reportError() {
        switch (errorCode) {

            case ERROR_LOCO_IN_USE:
                JmriJOptionPane.showMessageDialog(this,
                        Bundle.getMessage("DIALOG_LocoInUse", locoNumInUse, consistNumVerify),
                        Bundle.getMessage("DIALOG_NceConsist"),
                        JmriJOptionPane.ERROR_MESSAGE);
                break;

            case ERROR_NO_EMPTY_CONSIST:
                JmriJOptionPane.showMessageDialog(this,
                        Bundle.getMessage("DIALOG_NoEmptyConsist"),
                        Bundle.getMessage("DIALOG_NceConsist"),
                        JmriJOptionPane.ERROR_MESSAGE);
                break;

            case ERROR_CONSIST_DOESNT_MATCH:
                if (JmriJOptionPane.showConfirmDialog(null,
                        Bundle.getMessage("DIALOG_RosterNotMatch") + " "
                        + getRosterText(nceConsistRosterEntry),
                        Bundle.getMessage("DIALOG_NceContinue"),
                        JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
                    if (JmriJOptionPane.showConfirmDialog(null,
                            Bundle.getMessage("DIALOG_RosterNotMatch1",
                                    nceConsistRosterEntry.getId(), nceConsistRosterEntry.getConsistNumber())
                            + "\n " + Bundle.getMessage("DIALOG_RosterNotMatch2"),
                            Bundle.getMessage("DIALOG_NceReset"),
                            JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
                        nceConsistRosterEntry.setConsistNumber(Bundle.getMessage("CLEARED"));
                    }
                    changeButtons(false);
                    saveLoadButton.setEnabled(canLoad());
                    break;
                }
                changeButtons(true);
                loadFullRoster(nceConsistRosterEntry);
                saveLoadButton.setEnabled(canLoad());
                break;
            case WARN_CONSIST_ALREADY_LOADED:
                JmriJOptionPane.showMessageDialog(this,
                        Bundle.getMessage("DIALOG_ConsistWasLoaded"),
                        Bundle.getMessage("DIALOG_NceConsist"), JmriJOptionPane.WARNING_MESSAGE);
                break;
            default:
                JmriJOptionPane.showMessageDialog(this,
                        Bundle.getMessage("DIALOG_ErrorUnknown", errorCode),
                        Bundle.getMessage("DIALOG_NceConsist"),
                        JmriJOptionPane.ERROR_MESSAGE);
                log.error("Error code out of range: {}", errorCode);
        }
        errorCode = 0;
    }

    private String getRosterText(NceConsistRosterEntry nceConsistRosterEntry) {
        return "\n"
                + "\n"
                + Bundle.getMessage("ROSTER_ConsistNum")
                + " " + nceConsistRosterEntry.getConsistNumber()
                + "\n"
                + Bundle.getMessage("ROSTER_LeadLoco")
                + " " + nceConsistRosterEntry.getLoco1DccAddress()
                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco1Direction())
                + "\n"
                + Bundle.getMessage("ROSTER_RearLoco")
                + " " + nceConsistRosterEntry.getLoco2DccAddress()
                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco2Direction())
                + "\n"
                + Bundle.getMessage("ROSTER_Mid1Loco")
                + " " + nceConsistRosterEntry.getLoco3DccAddress()
                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco3Direction())
                + "\n"
                + Bundle.getMessage("ROSTER_Mid2Loco")
                + " " + nceConsistRosterEntry.getLoco4DccAddress()
                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco4Direction())
                + "\n"
                + Bundle.getMessage("ROSTER_Mid3Loco")
                + " " + nceConsistRosterEntry.getLoco5DccAddress()
                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco5Direction())
                + "\n"
                + Bundle.getMessage("ROSTER_Mid4Loco")
                + " " + nceConsistRosterEntry.getLoco6DccAddress()
                + " " + shortHandConvertDTD(nceConsistRosterEntry.getLoco6Direction());
    }

    /**
     * Nested class to create one of these using old-style defaults
     */
    static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction {

        public Default() {
            super("Open NCE Consist Editor",
                    new jmri.util.swing.sdi.JmriJFrameInterface(),
                    NceConsistEditPanel.class.getName(),
                    jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class));
        }
    }

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConsistEditPanel.class);

}