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