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