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