001package jmri.jmrix.nce.macro;
002
003import java.awt.*;
004
005import javax.swing.*;
006
007import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
008import jmri.InstanceManager;
009import jmri.jmrix.nce.*;
010import jmri.util.swing.JmriJOptionPane;
011
012/**
013 * Frame for user edit of NCE macros NCE macros are stored in Command Station
014 * (CS) memory starting at address xC800 (PH5 0x6000). Each macro consists of 20
015 * bytes. The last macro 255 is at address xDBEC.
016 * <p>
017 * Macro addr 0 xC800
018 * <p>
019 * Macro addr 1 xC814
020 * <p>
021 * Macro addr 2 xC828
022 * <p>
023 * Macro addr 3 xC83C
024 * <p>
025 * . . . .
026 * <p>
027 * Macro addr 255 xDBEC
028 * <p>
029 * Each macro can close or throw up to ten accessories. Macros can also be
030 * linked together. Two bytes (16 bit word) define an accessory address and
031 * command, or the address of the next macro to be executed. If the upper byte
032 * of the macro data word is xFF, then the next byte contains the address of the
033 * next macro to be executed by the NCE CS. For example, xFF08 means link to
034 * macro 8. NCE uses the NMRA DCC accessory decoder packet format for the word
035 * definition of their macros.
036 * <p>
037 * Macro data byte: bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
038 * <p>
039 * _ _ _ _ _ _ _ _ _ _ _ 1 0 A A A A A A 1 A A A C D D D
040 * <p>
041 * addr bit 7 6 5 4 3 2 10 9 8 1 0 turnout T
042 * <p>
043 * By convention, MSB address bits 10 - 8 are one's complement. NCE macros
044 * always set the C bit to 1. The LSB "D" (0) determines if the accessory is to
045 * be thrown (0) or closed (1). The next two bits "D D" are the LSBs of the
046 * accessory address. Note that NCE display addresses are 1 greater than NMRA
047 * DCC. Note that address bit 2 isn't supposed to be inverted, but it is the way
048 * NCE implemented their macros. Examples: 81F8 = accessory 1 thrown 9FFC =
049 * accessory 123 thrown B5FD = accessory 211 close BF8F = accessory 2044 close
050 * FF10 = link macro 16
051 * <p>
052 * Updated for including the USB 7.* for 1.65 command station Variables found on
053 * cab context page 14 (Cab address 14) macro table 0xE00-0xEFF, cab address =
054 * 0x0E 16 entries of 16 bytes organized as:
055 * <p>
056 * macro 0, high byte, low byte - 7 more times (8 accy commands total)
057 * <p>
058 * macro 1, high byte, low byte - 7 more times (8 accy commands total)
059 * <p>
060 * . . . .
061 * <p>
062 * macro 16, high byte, low byte - 7 more times (8 accy commands total)
063 * 
064 * @author Dan Boudreau Copyright (C) 2007, 2024
065 * @author Ken Cameron Copyright (C) 2013, 2023
066 */
067public class NceMacroEditPanel extends jmri.jmrix.nce.swing.NcePanel implements jmri.jmrix.nce.NceListener {
068
069    private NceTrafficController tc = null;
070    private int memBase;
071    private int maxNumMacros;
072    private int macroSize;
073    private boolean isUsb;
074
075    private int macroNum = 0; // macro being worked
076    private int replyLen = 0; // expected byte length
077    private int waiting = 0; // to catch responses not intended for this module
078    //    private static final int firstTimeSleep = 3000;  // delay first operation to let panel build
079    //    private final boolean firstTime = true; // wait for panel to display
080
081    private static final String QUESTION = Bundle.getMessage("Add");// The three possible states for a turnout
082    private static final String CLOSED = InstanceManager.turnoutManagerInstance().getClosedText();
083    private static final String THROWN = InstanceManager.turnoutManagerInstance().getThrownText();
084    private static final String CLOSED_NCE = Bundle.getMessage("Normal");
085    private static final String THROWN_NCE = Bundle.getMessage("Reverse");
086
087    private static final String DELETE = Bundle.getMessage("Delete");
088
089    private static final String EMPTY = Bundle.getMessage("empty"); // One of two accessory states
090    private static final String ACCESSORY = Bundle.getMessage("accessory");
091
092    private static final String LINK = Bundle.getMessage("LinkMacro");// Line 10 alternative to Delete
093
094    private static final int FAILED = -1;
095
096    Thread nceMemoryThread;
097    private boolean readRequested = false;
098    private boolean writeRequested = false;
099
100    private boolean macroSearchInc = false; // next search
101    private boolean macroSearchDec = false; // previous search
102    private boolean macroValid = false; // when true, NCE CS has responded to macro read
103    private boolean macroModified = false; // when true, macro has been modified by user
104
105    // member declarations
106    JLabel textMacro = new JLabel(Bundle.getMessage("MacroLabel"));
107    JLabel textReply = new JLabel(Bundle.getMessage("ReplyLabel"));
108    JLabel macroReply = new JLabel();
109
110    // major buttons
111    JButton previousButton = new JButton(Bundle.getMessage("Previous"));
112    JButton nextButton = new JButton(Bundle.getMessage("Next"));
113    JButton getButton = new JButton(Bundle.getMessage("Get"));
114    JButton saveButton = new JButton(Bundle.getMessage("Save"));
115    JButton backUpButton = new JButton(Bundle.getMessage("Backup"));
116    JButton restoreButton = new JButton(Bundle.getMessage("Restore"));
117
118    // check boxes
119    JCheckBox checkBoxEmpty = new JCheckBox(Bundle.getMessage("EmptyMacro"));
120    JCheckBox checkBoxNce = new JCheckBox(Bundle.getMessage("NCETurnout"));
121
122    // macro text field
123    JTextField macroTextField = new JTextField(4);
124
125    // for padding out panel
126    JLabel space2 = new JLabel("                          ");
127    JLabel space3 = new JLabel("                          ");
128    JLabel space4 = new JLabel("                          ");
129    JLabel space15 = new JLabel(" ");
130
131    // accessory row 1
132    JLabel num1 = new JLabel();
133    JLabel textAccy1 = new JLabel();
134    JTextField accyTextField1 = new JTextField(4);
135    JButton cmdButton1 = new JButton();
136    JButton deleteButton1 = new JButton();
137
138    //  accessory row 2
139    JLabel num2 = new JLabel();
140    JLabel textAccy2 = new JLabel();
141    JTextField accyTextField2 = new JTextField(4);
142    JButton cmdButton2 = new JButton();
143    JButton deleteButton2 = new JButton();
144
145    //  accessory row 3
146    JLabel num3 = new JLabel();
147    JLabel textAccy3 = new JLabel();
148    JTextField accyTextField3 = new JTextField(4);
149    JButton cmdButton3 = new JButton();
150    JButton deleteButton3 = new JButton();
151
152    //  accessory row 4
153    JLabel num4 = new JLabel();
154    JLabel textAccy4 = new JLabel();
155    JTextField accyTextField4 = new JTextField(4);
156    JButton cmdButton4 = new JButton();
157    JButton deleteButton4 = new JButton();
158
159    //  accessory row 5
160    JLabel num5 = new JLabel();
161    JLabel textAccy5 = new JLabel();
162    JTextField accyTextField5 = new JTextField(4);
163    JButton cmdButton5 = new JButton();
164    JButton deleteButton5 = new JButton();
165
166    //  accessory row 6
167    JLabel num6 = new JLabel();
168    JLabel textAccy6 = new JLabel();
169    JTextField accyTextField6 = new JTextField(4);
170    JButton cmdButton6 = new JButton();
171    JButton deleteButton6 = new JButton();
172
173    //  accessory row 7
174    JLabel num7 = new JLabel();
175    JLabel textAccy7 = new JLabel();
176    JTextField accyTextField7 = new JTextField(4);
177    JButton cmdButton7 = new JButton();
178    JButton deleteButton7 = new JButton();
179
180    //  accessory row 8
181    JLabel num8 = new JLabel();
182    JLabel textAccy8 = new JLabel();
183    JTextField accyTextField8 = new JTextField(4);
184    JButton cmdButton8 = new JButton();
185    JButton deleteButton8 = new JButton();
186
187    //  accessory row 9
188    JLabel num9 = new JLabel();
189    JLabel textAccy9 = new JLabel();
190    JTextField accyTextField9 = new JTextField(4);
191    JButton cmdButton9 = new JButton();
192    JButton deleteButton9 = new JButton();
193
194    //  accessory row 10
195    JLabel num10 = new JLabel();
196    JLabel textAccy10 = new JLabel();
197    JTextField accyTextField10 = new JTextField(4);
198    JButton cmdButton10 = new JButton();
199    JButton deleteButton10 = new JButton();
200
201    public NceMacroEditPanel() {
202        super();
203    }
204
205    /**
206     * {@inheritDoc}
207     */
208    @Override
209    public void initContext(Object context) {
210        if (context instanceof NceSystemConnectionMemo) {
211            initComponents((NceSystemConnectionMemo) context);
212        }
213    }
214
215    /**
216     * {@inheritDoc}
217     */
218    @Override
219    public String getHelpTarget() {
220        return "package.jmri.jmrix.nce.macro.NceMacroEditFrame";
221    }
222
223    /**
224     * {@inheritDoc}
225     */
226    @Override
227    public String getTitle() {
228        StringBuilder x = new StringBuilder();
229        if (memo != null) {
230            x.append(memo.getUserName());
231        } else {
232            x.append("NCE_");
233        }
234        x.append(": ");
235        x.append(Bundle.getMessage("TitleEditNCEMacro"));
236        return x.toString();
237    }
238
239    /**
240     * The minimum frame size for font size 16
241     */
242    @Override
243    public Dimension getMinimumDimension() {
244        return new Dimension(500, 500);
245    }
246
247    /**
248     * {@inheritDoc}
249     */
250    @Override
251    public void initComponents(NceSystemConnectionMemo memo) {
252        this.memo = memo;
253        this.tc = memo.getNceTrafficController();
254        memBase = tc.csm.getMacroAddr();
255        maxNumMacros = tc.csm.getMacroLimit();
256        macroSize = tc.csm.getMacroSize();
257        if ((tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) &&
258                (tc.getCmdGroups() & NceTrafficController.CMDS_MEM) != 0) {
259            isUsb = true;
260        }
261
262        // the following code sets the frame's initial state
263        // default at startup
264        macroReply.setText(Bundle.getMessage("unknown"));
265        macroTextField.setText("");
266        saveButton.setEnabled(false);
267
268        // load tool tips
269        previousButton.setToolTipText(Bundle.getMessage("toolTipSearchDecrementing"));
270        nextButton.setToolTipText(Bundle.getMessage("toolTipSearchIncrementing"));
271        getButton.setToolTipText(Bundle.getMessage("toolTipReadMacro"));
272        macroTextField.setToolTipText(Bundle.getMessage("toolTipEnterMacro", maxNumMacros - 1));
273        saveButton.setToolTipText(Bundle.getMessage("toolTipUpdateMacro"));
274        backUpButton.setToolTipText(Bundle.getMessage("toolTipBackUp"));
275        restoreButton.setToolTipText(Bundle.getMessage("toolTipRestore"));
276        checkBoxEmpty.setToolTipText(Bundle.getMessage("toolTipSearchEmpty"));
277        checkBoxNce.setToolTipText(Bundle.getMessage("toolTipUseNce"));
278
279        initAccyFields();
280
281        setLayout(new GridBagLayout());
282
283        // Layout the panel by rows
284        // row 0
285        addItem(textMacro, 2, 0);
286
287        // row 1
288        addItem(previousButton, 1, 1);
289        addItem(macroTextField, 2, 1);
290        addItem(nextButton, 3, 1);
291        addItem(checkBoxEmpty, 4, 1);
292
293        // row 2
294        addItem(textReply, 0, 2);
295        addItem(macroReply, 1, 2);
296        addItem(getButton, 2, 2);
297        addItem(checkBoxNce, 4, 2);
298
299        // row 3 padding for looks
300        addItem(space2, 1, 3);
301        addItem(space3, 2, 3);
302        addItem(space4, 3, 3);
303
304        // row 4 RFU
305        int rNum = 5;
306        // row 5 accessory 1
307        addAccyRow(num1, textAccy1, accyTextField1, cmdButton1, deleteButton1, rNum++);
308
309        // row 6 accessory 2
310        addAccyRow(num2, textAccy2, accyTextField2, cmdButton2, deleteButton2, rNum++);
311
312        // row 7 accessory 3
313        addAccyRow(num3, textAccy3, accyTextField3, cmdButton3, deleteButton3, rNum++);
314
315        // row 8 accessory 4
316        addAccyRow(num4, textAccy4, accyTextField4, cmdButton4, deleteButton4, rNum++);
317
318        // row 9 accessory 5
319        addAccyRow(num5, textAccy5, accyTextField5, cmdButton5, deleteButton5, rNum++);
320
321        // row 10 accessory 6
322        addAccyRow(num6, textAccy6, accyTextField6, cmdButton6, deleteButton6, rNum++);
323
324        // row 11 accessory 7
325        addAccyRow(num7, textAccy7, accyTextField7, cmdButton7, deleteButton7, rNum++);
326
327        if (!isUsb) {
328            // row 12 accessory 8
329            addAccyRow(num8, textAccy8, accyTextField8, cmdButton8, deleteButton8, rNum++);
330
331            // row 13 accessory 9
332            addAccyRow(num9, textAccy9, accyTextField9, cmdButton9, deleteButton9, rNum++);
333        }
334
335        // row 14 accessory 10
336        addAccyRow(num10, textAccy10, accyTextField10, cmdButton10, deleteButton10, rNum++);
337
338        // row 15 padding for looks
339        addItem(space15, 2, rNum++);
340
341        // row 16
342        addItem(saveButton, 2, rNum);
343        if (isUsb) {
344            backUpButton.setEnabled(false);
345            restoreButton.setEnabled(false);
346        }
347        addItem(backUpButton, 3, rNum);
348        addItem(restoreButton, 4, rNum);
349
350        // setup buttons
351        addButtonAction(previousButton);
352        addButtonAction(nextButton);
353        addButtonAction(getButton);
354        addButtonAction(saveButton);
355        addButtonAction(backUpButton);
356        addButtonAction(restoreButton);
357
358        // accessory command buttons
359        addButtonCmdAction(cmdButton1);
360        addButtonCmdAction(cmdButton2);
361        addButtonCmdAction(cmdButton3);
362        addButtonCmdAction(cmdButton4);
363        addButtonCmdAction(cmdButton5);
364        addButtonCmdAction(cmdButton6);
365        addButtonCmdAction(cmdButton7);
366        addButtonCmdAction(cmdButton8);
367        addButtonCmdAction(cmdButton9);
368        addButtonCmdAction(cmdButton10);
369
370        // accessory delete buttons
371        addButtonDelAction(deleteButton1);
372        addButtonDelAction(deleteButton2);
373        addButtonDelAction(deleteButton3);
374        addButtonDelAction(deleteButton4);
375        addButtonDelAction(deleteButton5);
376        addButtonDelAction(deleteButton6);
377        addButtonDelAction(deleteButton7);
378        addButtonDelAction(deleteButton8);
379        addButtonDelAction(deleteButton9);
380        addButtonDelAction(deleteButton10);
381
382        // NCE checkbox
383        addCheckBoxAction(checkBoxNce);
384    }
385
386    // Previous, Next, Get, Save, Restore & Backup buttons
387    public void buttonActionPerformed(java.awt.event.ActionEvent ae) {
388
389        // if we're searching ignore user
390        if (macroSearchInc || macroSearchDec) {
391            return;
392        }
393
394        if (ae.getSource() == saveButton) {
395            boolean status = saveMacro();
396            // was save successful?
397            if (status) {
398                setSaveButton(false); // yes, disable save button
399            }
400            return;
401        }
402
403        if (macroModified) {
404            // warn user that macro has been modified
405            JmriJOptionPane.showMessageDialog(this,
406                    Bundle.getMessage("MacroModified"), Bundle.getMessage("NceMacro"),
407                    JmriJOptionPane.WARNING_MESSAGE);
408            macroModified = false; // only one warning!!!
409
410        } else {
411
412            setSaveButton(false); // disable save button
413
414            if (ae.getSource() == previousButton) {
415                macroSearchDec = true;
416                macroNum = getMacro(); // check for valid and kick off read process
417                if (macroNum < 0) { // Error user input incorrect
418                    macroSearchDec = false;
419                } else {
420                    processMemory(true, false, macroNum, null);
421                }
422            }
423            if (ae.getSource() == nextButton) {
424                macroSearchInc = true;
425                macroNum = getMacro(); // check for valid
426                if (macroNum < 0) { // Error user input incorrect
427                    macroSearchInc = false;
428                } else {
429                    processMemory(true, false, macroNum, null);
430                }
431            }
432
433            if (ae.getSource() == getButton) {
434                // Get Macro
435                macroNum = getMacro();
436                if (macroNum >= 0) {
437                    processMemory(true, false, macroNum, null);
438                }
439            }
440
441            if (!isUsb && (ae.getSource() == backUpButton)) {
442
443                Thread mb = new NceMacroBackup(tc);
444                mb.setName("Macro Backup");
445                mb.start();
446            }
447
448            if (!isUsb && (ae.getSource() == restoreButton)) {
449                Thread mr = new NceMacroRestore(tc);
450                mr.setName("Macro Restore");
451                mr.start();
452            }
453        }
454    }
455
456    // One of the ten accessory command buttons pressed
457    public void buttonActionCmdPerformed(java.awt.event.ActionEvent ae) {
458
459        // if we're searching ignore user
460        if (macroSearchInc || macroSearchDec) {
461            return;
462        }
463
464        if (ae.getSource() == cmdButton1) {
465            updateAccyCmdPerformed(accyTextField1, cmdButton1, textAccy1,
466                    deleteButton1);
467        }
468        if (ae.getSource() == cmdButton2) {
469            updateAccyCmdPerformed(accyTextField2, cmdButton2, textAccy2,
470                    deleteButton2);
471        }
472        if (ae.getSource() == cmdButton3) {
473            updateAccyCmdPerformed(accyTextField3, cmdButton3, textAccy3,
474                    deleteButton3);
475        }
476        if (ae.getSource() == cmdButton4) {
477            updateAccyCmdPerformed(accyTextField4, cmdButton4, textAccy4,
478                    deleteButton4);
479        }
480        if (ae.getSource() == cmdButton5) {
481            updateAccyCmdPerformed(accyTextField5, cmdButton5, textAccy5,
482                    deleteButton5);
483        }
484        if (ae.getSource() == cmdButton6) {
485            updateAccyCmdPerformed(accyTextField6, cmdButton6, textAccy6,
486                    deleteButton6);
487        }
488        if (ae.getSource() == cmdButton7) {
489            updateAccyCmdPerformed(accyTextField7, cmdButton7, textAccy7,
490                    deleteButton7);
491        }
492        if (ae.getSource() == cmdButton8) {
493            updateAccyCmdPerformed(accyTextField8, cmdButton8, textAccy8,
494                    deleteButton8);
495        }
496        if (ae.getSource() == cmdButton9) {
497            updateAccyCmdPerformed(accyTextField9, cmdButton9, textAccy9,
498                    deleteButton9);
499        }
500        if (ae.getSource() == cmdButton10) {
501            updateAccyCmdPerformed(accyTextField10, cmdButton10, textAccy10,
502                    deleteButton10);
503        }
504    }
505
506    // One of ten Delete buttons pressed
507    public void buttonActionDeletePerformed(java.awt.event.ActionEvent ae) {
508
509        // if we're searching ignore user
510        if (macroSearchInc || macroSearchDec) {
511            return;
512        }
513
514        if (ae.getSource() == deleteButton1) {
515            updateAccyDelPerformed(accyTextField1, cmdButton1, textAccy1,
516                    deleteButton1);
517        }
518        if (ae.getSource() == deleteButton2) {
519            updateAccyDelPerformed(accyTextField2, cmdButton2, textAccy2,
520                    deleteButton2);
521        }
522        if (ae.getSource() == deleteButton3) {
523            updateAccyDelPerformed(accyTextField3, cmdButton3, textAccy3,
524                    deleteButton3);
525        }
526        if (ae.getSource() == deleteButton4) {
527            updateAccyDelPerformed(accyTextField4, cmdButton4, textAccy4,
528                    deleteButton4);
529        }
530        if (ae.getSource() == deleteButton5) {
531            updateAccyDelPerformed(accyTextField5, cmdButton5, textAccy5,
532                    deleteButton5);
533        }
534        if (ae.getSource() == deleteButton6) {
535            updateAccyDelPerformed(accyTextField6, cmdButton6, textAccy6,
536                    deleteButton6);
537        }
538        if (ae.getSource() == deleteButton7) {
539            updateAccyDelPerformed(accyTextField7, cmdButton7, textAccy7,
540                    deleteButton7);
541        }
542        if (ae.getSource() == deleteButton8) {
543            updateAccyDelPerformed(accyTextField8, cmdButton8, textAccy8,
544                    deleteButton8);
545        }
546        if (ae.getSource() == deleteButton9) {
547            updateAccyDelPerformed(accyTextField9, cmdButton9, textAccy9,
548                    deleteButton9);
549        }
550        // row ten delete button behaves differently
551        // could be link button
552        if (ae.getSource() == deleteButton10) {
553
554            // is the user trying to link a macro?
555            if (deleteButton10.getText().equals(LINK)) {
556                if (!macroValid) { // Error user input incorrect
557                    JmriJOptionPane.showMessageDialog(this,
558                            Bundle.getMessage("GetMacroNumber", maxNumMacros - 1),
559                            Bundle.getMessage("NceMacro"),
560                            JmriJOptionPane.ERROR_MESSAGE);
561                    return;
562                }
563                int linkMacro = validMacro(accyTextField10.getText());
564                if (linkMacro == -1) {
565                    JmriJOptionPane.showMessageDialog(this,
566                            Bundle.getMessage("EnterMacroNumberLastLine", maxNumMacros - 1),
567                            Bundle.getMessage("NceMacro"),
568                            JmriJOptionPane.ERROR_MESSAGE);
569                    return;
570                }
571                // success, link a macro
572                setSaveButton(true);
573                textAccy10.setText(LINK);
574                cmdButton10.setVisible(false);
575                deleteButton10.setText(DELETE);
576                deleteButton10.setToolTipText(Bundle.getMessage("toolTipRemoveMacroLink"));
577
578                // user wants to delete a accessory address or a link
579            } else {
580                updateAccyDelPerformed(accyTextField10, cmdButton10, textAccy10,
581                        deleteButton10);
582                initAccyRow10();
583            }
584        }
585    }
586
587    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
588        processMemory(true, false, macroNum, null);
589    }
590
591    // gets the user supplied macro number
592    private int getMacro() {
593        // Set all fields to default and build from there
594        initAccyFields();
595        String m = macroTextField.getText();
596        if (m.isEmpty()) {
597            m = "0";
598        }
599        int mN = validMacro(m);
600        if (mN < 0) {
601            macroReply.setText(Bundle.getMessage("error"));
602            JmriJOptionPane.showMessageDialog(this,
603                    Bundle.getMessage("EnterMacroNumber", maxNumMacros - 1),
604                    Bundle.getMessage("NceMacro"),
605                    JmriJOptionPane.ERROR_MESSAGE);
606            macroValid = false;
607            return mN;
608        }
609        if (macroSearchInc || macroSearchDec) {
610            macroReply.setText(Bundle.getMessage("searching"));
611            if (macroSearchInc) {
612                mN++;
613                if (mN >= maxNumMacros) {
614                    mN = 0;
615                }
616            }
617            if (macroSearchDec) {
618                mN--;
619                if (mN <= -1) {
620                    mN = maxNumMacros - 1;
621                }
622            }
623        } else {
624            macroReply.setText(Bundle.getMessage("waiting"));
625        }
626
627        return mN;
628    }
629
630    /**
631     * Writes all bytes to NCE CS memory as long as there are no user input
632     * errors
633     */
634    private boolean saveMacro() {
635        byte[] macroAccy = new byte[macroSize]; // NCE Macro data
636        int index = 0;
637        int accyNum = 0;
638        // test the inputs, convert from text
639        accyNum = getAccyRow(macroAccy, index, textAccy1, accyTextField1, cmdButton1);
640        if (accyNum < 0) {
641            return false; //error
642        }
643        if (accyNum > 0) {
644            index += 2;
645        }
646        accyNum = getAccyRow(macroAccy, index, textAccy2, accyTextField2, cmdButton2);
647        if (accyNum < 0) {
648            return false;
649        }
650        if (accyNum > 0) {
651            index += 2;
652        }
653        accyNum = getAccyRow(macroAccy, index, textAccy3, accyTextField3, cmdButton3);
654        if (accyNum < 0) {
655            return false;
656        }
657        if (accyNum > 0) {
658            index += 2;
659        }
660        accyNum = getAccyRow(macroAccy, index, textAccy4, accyTextField4, cmdButton4);
661        if (accyNum < 0) {
662            return false;
663        }
664        if (accyNum > 0) {
665            index += 2;
666        }
667        accyNum = getAccyRow(macroAccy, index, textAccy5, accyTextField5, cmdButton5);
668        if (accyNum < 0) {
669            return false;
670        }
671        if (accyNum > 0) {
672            index += 2;
673        }
674        accyNum = getAccyRow(macroAccy, index, textAccy6, accyTextField6, cmdButton6);
675        if (accyNum < 0) {
676            return false;
677        }
678        if (accyNum > 0) {
679            index += 2;
680        }
681        accyNum = getAccyRow(macroAccy, index, textAccy7, accyTextField7, cmdButton7);
682        if (accyNum < 0) {
683            return false;
684        }
685        if (accyNum > 0) {
686            index += 2;
687        }
688        if (!isUsb) {
689            accyNum = getAccyRow(macroAccy, index, textAccy8, accyTextField8, cmdButton8);
690            if (accyNum < 0) {
691                return false;
692            }
693            if (accyNum > 0) {
694                index += 2;
695            }
696            accyNum = getAccyRow(macroAccy, index, textAccy9, accyTextField9, cmdButton9);
697            if (accyNum < 0) {
698                return false;
699            }
700            if (accyNum > 0) {
701                index += 2;
702            }
703        }
704        accyNum = getAccyRow(macroAccy, index, textAccy10, accyTextField10, cmdButton10);
705        if (accyNum < 0) {
706            JmriJOptionPane.showMessageDialog(this,
707                    Bundle.getMessage("EnterMacroNumberLastLine", maxNumMacros - 1),
708                    Bundle.getMessage("NceMacro"),
709                    JmriJOptionPane.ERROR_MESSAGE);
710            return false;
711        }
712
713        processMemory(false, true, macroNum, macroAccy);
714        return true;
715    }
716
717    private void processMemory(boolean doRead, boolean doWrite, int macroId, byte[] macroArray) {
718        final byte[] macroData = new byte[macroSize];
719        macroValid = false;
720        readRequested = false;
721        writeRequested = false;
722
723        if (doRead) {
724            readRequested = true;
725        }
726        if (doWrite) {
727            writeRequested = true;
728            System.arraycopy(macroArray, 0, macroData, 0, macroSize);
729        }
730
731        // Set up a separate thread to access CS memory
732        if (nceMemoryThread != null && nceMemoryThread.isAlive()) {
733            return; // thread is already running
734        }
735        nceMemoryThread = new Thread(new Runnable() {
736            @Override
737            public void run() {
738                if (readRequested) {
739                    macroNum = macroId;
740                    int macroCount = 0;
741                    while (true) {
742                        int entriesRead = readMacroMemory(macroNum);
743                        macroTextField.setText(Integer.toString(macroNum));
744                        if (entriesRead == 0) {
745                            // Macro is empty so init the accessory fields
746                            initAccyFields();
747                            macroReply.setText(Bundle.getMessage("macroEmpty"));
748                            if (checkBoxEmpty.isSelected()) {
749                                macroValid = true;
750                                macroSearchInc = false;
751                                macroSearchDec = false;
752                                break;
753                            }
754                        } else if (entriesRead < 0) {
755                            macroReply.setText(Bundle.getMessage("error"));
756                            macroValid = false;
757                            macroSearchInc = false;
758                            macroSearchDec = false;
759                            break;
760                        } else {
761                            macroReply.setText(Bundle.getMessage("macroFound"));
762                            if (!checkBoxEmpty.isSelected()) {
763                                macroSearchInc = false;
764                                macroSearchDec = false;
765                                macroValid = true;
766                                break;
767                            }
768                        }
769                        if ((macroSearchInc || macroSearchDec) && !macroValid) {
770                            macroCount++;
771                            if (macroCount > maxNumMacros) {
772                                macroSearchInc = false;
773                                macroSearchDec = false;
774                                break;
775                            }
776                            macroNum = getMacro();
777                        }
778                        if (!(macroSearchInc || macroSearchDec)) {
779                            // we were doing a get, not a search
780                            macroValid = true;
781                            break;
782                        }
783                    }
784                }
785                if (writeRequested) {
786                    writeMacroMemory(macroId, macroData);
787                }
788            }
789        });
790        nceMemoryThread.setName(Bundle.getMessage("ThreadTitle"));
791        nceMemoryThread.setPriority(Thread.MIN_PRIORITY);
792        nceMemoryThread.start();
793    }
794
795    // Reads entire macro from NCE memory
796    private int readMacroMemory(int mN) {
797        if (isUsb) {
798            return readUsbMacroMemory(mN);
799        }
800        return readCommandStationMacroMemory(mN);
801    }
802
803    /**
804     * Reads 16 byes of NCE macro memory
805     * 
806     * @param mN macro number
807     * @return number of accessory addresses read
808     */
809    private int readUsbMacroMemory(int mN) {
810        setUsbCabMemoryPointer(memBase, (mN * macroSize));
811        if (!waitNce()) {
812            return FAILED;
813        }
814        int entriesRead = 0;
815        // 1st word of macro
816        readUsbMemoryN(2);
817        if (!waitNce()) {
818            return FAILED;
819        }
820        int accyAddr = getMacroAccyAdr(recChars);
821        if (accyAddr <= 0) {
822            return entriesRead;
823        }
824        entriesRead++;
825        setAccy(accyAddr, getAccyCmd(recChars), textAccy1, accyTextField1, cmdButton1,
826                deleteButton1);
827        // 2nd word of macro
828        readUsbMemoryN(2);
829        if (!waitNce()) {
830            return FAILED;
831        }
832        accyAddr = getMacroAccyAdr(recChars);
833        if (accyAddr <= 0) {
834            return entriesRead;
835        }
836        entriesRead++;
837        setAccy(accyAddr, getAccyCmd(recChars), textAccy2, accyTextField2, cmdButton2,
838                deleteButton2);
839        // 3rd word of macro
840        readUsbMemoryN(2);
841        if (!waitNce()) {
842            return FAILED;
843        }
844        accyAddr = getMacroAccyAdr(recChars);
845        if (accyAddr <= 0) {
846            return entriesRead;
847        }
848        entriesRead++;
849        setAccy(accyAddr, getAccyCmd(recChars), textAccy3, accyTextField3, cmdButton3,
850                deleteButton3);
851        // 4th word of macro
852        readUsbMemoryN(2);
853        if (!waitNce()) {
854            return FAILED;
855        }
856        accyAddr = getMacroAccyAdr(recChars);
857        if (accyAddr <= 0) {
858            return entriesRead;
859        }
860        entriesRead++;
861        setAccy(accyAddr, getAccyCmd(recChars), textAccy4, accyTextField4, cmdButton4,
862                deleteButton4);
863        // 5th word of macro
864        readUsbMemoryN(2);
865        if (!waitNce()) {
866            return FAILED;
867        }
868        accyAddr = getMacroAccyAdr(recChars);
869        if (accyAddr <= 0) {
870            return entriesRead;
871        }
872        entriesRead++;
873        setAccy(accyAddr, getAccyCmd(recChars), textAccy5, accyTextField5, cmdButton5,
874                deleteButton5);
875        // 6th word of macro
876        readUsbMemoryN(2);
877        if (!waitNce()) {
878            return FAILED;
879        }
880        accyAddr = getMacroAccyAdr(recChars);
881        if (accyAddr <= 0) {
882            return entriesRead;
883        }
884        entriesRead++;
885        setAccy(accyAddr, getAccyCmd(recChars), textAccy6, accyTextField6, cmdButton6,
886                deleteButton6);
887        // 7th word of macro
888        readUsbMemoryN(2);
889        if (!waitNce()) {
890            return FAILED;
891        }
892        accyAddr = getMacroAccyAdr(recChars);
893        if (accyAddr <= 0) {
894            return entriesRead;
895        }
896        entriesRead++;
897        setAccy(accyAddr, getAccyCmd(recChars), textAccy7, accyTextField7, cmdButton7,
898                deleteButton7);
899        // 8th word of macro
900        readUsbMemoryN(2);
901        if (!waitNce()) {
902            return FAILED;
903        }
904        accyAddr = getMacroAccyAdr(recChars);
905        if (accyAddr <= 0) {
906            return entriesRead;
907        }
908        entriesRead++;
909        setAccy(accyAddr, getAccyCmd(recChars), textAccy10, accyTextField10, cmdButton10,
910                deleteButton10);
911        return entriesRead;
912    }
913
914    /**
915     * Reads 20 byes of NCE command station macro memory
916     * 
917     * @param mN macro number
918     * @return number of accessory addresses read
919     */
920    private int readCommandStationMacroMemory(int mN) {
921        int memPtr = memBase + (mN * macroSize);
922        int readPtr = 0;
923        int[] workBuf = new int[2];
924        int entriesRead = 0;
925
926        // 1st word of macro
927        readSerialMemory16(memPtr);
928        if (!waitNce()) {
929            return FAILED;
930        }
931        workBuf[0] = recChars[readPtr++];
932        workBuf[1] = recChars[readPtr++];
933        int accyAddr = getMacroAccyAdr(workBuf);
934        if (accyAddr <= 0) {
935            return entriesRead;
936        }
937        entriesRead++;
938        setAccy(accyAddr, getAccyCmd(workBuf), textAccy1, accyTextField1, cmdButton1,
939                deleteButton1);
940        // 2nd word of macro
941        workBuf[0] = recChars[readPtr++];
942        workBuf[1] = recChars[readPtr++];
943        accyAddr = getMacroAccyAdr(workBuf);
944        if (accyAddr <= 0) {
945            return entriesRead;
946        }
947        entriesRead++;
948        setAccy(accyAddr, getAccyCmd(workBuf), textAccy2, accyTextField2, cmdButton2,
949                deleteButton2);
950        // 3rd word of macro
951        workBuf[0] = recChars[readPtr++];
952        workBuf[1] = recChars[readPtr++];
953        accyAddr = getMacroAccyAdr(workBuf);
954        if (accyAddr <= 0) {
955            return entriesRead;
956        }
957        entriesRead++;
958        setAccy(accyAddr, getAccyCmd(workBuf), textAccy3, accyTextField3, cmdButton3,
959                deleteButton3);
960        // 4th word of macro
961        workBuf[0] = recChars[readPtr++];
962        workBuf[1] = recChars[readPtr++];
963        accyAddr = getMacroAccyAdr(workBuf);
964        if (accyAddr <= 0) {
965            return entriesRead;
966        }
967        entriesRead++;
968        setAccy(accyAddr, getAccyCmd(workBuf), textAccy4, accyTextField4, cmdButton4,
969                deleteButton4);
970        // 5th word of macro
971        workBuf[0] = recChars[readPtr++];
972        workBuf[1] = recChars[readPtr++];
973        accyAddr = getMacroAccyAdr(workBuf);
974        if (accyAddr <= 0) {
975            return entriesRead;
976        }
977        entriesRead++;
978        setAccy(accyAddr, getAccyCmd(workBuf), textAccy5, accyTextField5, cmdButton5,
979                deleteButton5);
980        // 6th word of macro
981        workBuf[0] = recChars[readPtr++];
982        workBuf[1] = recChars[readPtr++];
983        accyAddr = getMacroAccyAdr(workBuf);
984        if (accyAddr <= 0) {
985            return entriesRead;
986        }
987        entriesRead++;
988        setAccy(accyAddr, getAccyCmd(workBuf), textAccy6, accyTextField6, cmdButton6,
989                deleteButton6);
990        // 7th word of macro
991        workBuf[0] = recChars[readPtr++];
992        workBuf[1] = recChars[readPtr++];
993        accyAddr = getMacroAccyAdr(workBuf);
994        if (accyAddr <= 0) {
995            return entriesRead;
996        }
997        entriesRead++;
998        setAccy(accyAddr, getAccyCmd(workBuf), textAccy7, accyTextField7, cmdButton7,
999                deleteButton7);
1000        // 8th word of macro
1001        workBuf[0] = recChars[readPtr++];
1002        workBuf[1] = recChars[readPtr++];
1003        accyAddr = getMacroAccyAdr(workBuf);
1004        if (accyAddr <= 0) {
1005            return entriesRead;
1006        }
1007        entriesRead++;
1008        setAccy(accyAddr, getAccyCmd(workBuf), textAccy8, accyTextField8, cmdButton8,
1009                deleteButton8);
1010
1011        // 9th word of macro
1012        memPtr += 16;
1013        readPtr = 0;
1014        readSerialMemory16(memPtr);
1015        if (!waitNce()) {
1016            return FAILED;
1017        }
1018        workBuf[0] = recChars[readPtr++];
1019        workBuf[1] = recChars[readPtr++];
1020        accyAddr = getMacroAccyAdr(workBuf);
1021        if (accyAddr <= 0) {
1022            return entriesRead;
1023        }
1024        entriesRead++;
1025        setAccy(accyAddr, getAccyCmd(workBuf), textAccy9, accyTextField9, cmdButton9,
1026                deleteButton9);
1027        // 10th word of macro
1028        workBuf[0] = recChars[readPtr++];
1029        workBuf[1] = recChars[readPtr++];
1030        accyAddr = getMacroAccyAdr(workBuf);
1031        if (accyAddr <= 0) {
1032            return entriesRead;
1033        }
1034        entriesRead++;
1035        setAccy(accyAddr, getAccyCmd(workBuf), textAccy10, accyTextField10, cmdButton10,
1036                deleteButton10);
1037        return entriesRead;
1038    }
1039
1040    // Updates the accessory line when the user hits the command button
1041    private void updateAccyCmdPerformed(JTextField accyTextField, JButton cmdButton, JLabel textAccy,
1042            JButton deleteButton) {
1043        if (!macroValid) { // Error user input incorrect
1044            JmriJOptionPane.showMessageDialog(this,
1045                    Bundle.getMessage("GetMacroNumber"), Bundle.getMessage("NceMacro"),
1046                    JmriJOptionPane.ERROR_MESSAGE);
1047        } else {
1048            String accyText = accyTextField.getText();
1049            int accyNum = 0;
1050            try {
1051                accyNum = Integer.parseInt(accyText);
1052            } catch (NumberFormatException e) {
1053                accyNum = -1;
1054            }
1055
1056            if (accyNum < 1 || accyNum > 2044) {
1057                JmriJOptionPane.showMessageDialog(this,
1058                        Bundle.getMessage("EnterAccessoryNumber"), Bundle.getMessage("NceMacroAddress"),
1059                        JmriJOptionPane.ERROR_MESSAGE);
1060                return;
1061            }
1062
1063            String accyCmd = cmdButton.getText();
1064
1065            // Use JMRI or NCE turnout terminology
1066            if (checkBoxNce.isSelected()) {
1067
1068                if (!accyCmd.equals(THROWN_NCE)) {
1069                    cmdButton.setText(THROWN_NCE);
1070                }
1071                if (!accyCmd.equals(CLOSED_NCE)) {
1072                    cmdButton.setText(CLOSED_NCE);
1073                }
1074
1075            } else {
1076
1077                if (!accyCmd.equals(THROWN)) {
1078                    cmdButton.setText(THROWN);
1079                }
1080                if (!accyCmd.equals(CLOSED)) {
1081                    cmdButton.setText(CLOSED);
1082                }
1083            }
1084
1085            setSaveButton(true);
1086            textAccy.setText(ACCESSORY);
1087            deleteButton.setText(DELETE);
1088            deleteButton.setToolTipText(Bundle.getMessage("toolTipRemoveAcessory"));
1089            deleteButton.setEnabled(true);
1090        }
1091    }
1092
1093    // Delete an accessory from the macro
1094    private void updateAccyDelPerformed(JTextField accyTextField, JButton cmdButton, JLabel textAccy,
1095            JButton deleteButton) {
1096        setSaveButton(true);
1097        textAccy.setText(EMPTY);
1098        accyTextField.setText("");
1099        cmdButton.setText(QUESTION);
1100        deleteButton.setEnabled(false);
1101    }
1102
1103    private int getAccyRow(byte[] b, int i, JLabel textAccy, JTextField accyTextField, JButton cmdButton) {
1104        int accyNum = 0;
1105        if (textAccy.getText().equals(ACCESSORY)) {
1106            accyNum = getAccyNum(accyTextField.getText());
1107            if (accyNum < 0) {
1108                return accyNum;
1109            }
1110            accyNum = accyNum + 3; // adjust for NCE's way of encoding
1111            int upperByte = (accyNum & 0xFF);
1112            upperByte = (upperByte >> 2) + 0x80;
1113            b[i] = (byte) upperByte;
1114            int lowerByteH = (((accyNum ^ 0x0700) & 0x0700) >> 4);// 3 MSB 1s complement
1115            int lowerByteL = ((accyNum & 0x3) << 1); // 2 LSB
1116            int lowerByte = (lowerByteH + lowerByteL + 0x88);
1117            // adjust for turnout command
1118            if (cmdButton.getText().equals(CLOSED) || cmdButton.getText().equals(CLOSED_NCE)) {
1119                lowerByte++;
1120            }
1121            b[i + 1] = (byte) (lowerByte);
1122        }
1123        if (textAccy.getText().equals(LINK)) {
1124            int macroLink = validMacro(accyTextField.getText());
1125            if (macroLink < 0) {
1126                return macroLink;
1127            }
1128            b[i] = (byte) 0xFF; // NCE macro link command
1129            b[i + 1] = (byte) macroLink; // link macro number
1130        }
1131        return accyNum;
1132    }
1133
1134    private int getAccyNum(String accyText) {
1135        int accyNum = 0;
1136        try {
1137            accyNum = Integer.parseInt(accyText);
1138        } catch (NumberFormatException e) {
1139            accyNum = -1;
1140        }
1141        if (accyNum < 1 || accyNum > 2044) {
1142            JmriJOptionPane.showMessageDialog(this,
1143                    Bundle.getMessage("EnterAccessoryNumber"), Bundle.getMessage("NceMacroAddress"),
1144                    JmriJOptionPane.ERROR_MESSAGE);
1145            accyNum = -1;
1146        }
1147        return accyNum;
1148    }
1149
1150    // display save button
1151    private void setSaveButton(boolean display) {
1152        macroModified = display;
1153        saveButton.setEnabled(display);
1154        if (!isUsb) {
1155            backUpButton.setEnabled(!display);
1156            restoreButton.setEnabled(!display);
1157        }
1158    }
1159
1160    // Convert NCE macro hex data to accessory address
1161    // returns 0 if macro address is empty
1162    // returns a negative address if link address
1163    // & loads accessory 10 with link macro
1164    private int getMacroAccyAdr(int[] b) {
1165        int accyAddrL = b[0];
1166        int accyAddr = 0;
1167        // check for null
1168        if ((accyAddrL == 0) && (b[1] == 0)) {
1169            return accyAddr;
1170        }
1171        // Check to see if link address
1172        if ((accyAddrL & 0xFF) == 0xFF) {
1173            // Link address
1174            accyAddr = b[1];
1175            linkAccessory10(accyAddr & 0xFF);
1176            accyAddr = -accyAddr;
1177
1178            // must be an accessory address
1179        } else {
1180            accyAddrL = (accyAddrL << 2) & 0xFC; // accessory address bits 7 - 2
1181            int accyLSB = b[1];
1182            accyLSB = (accyLSB & 0x06) >> 1; // accessory address bits 1 - 0
1183            int accyAddrH = b[1];
1184            accyAddrH = (0x70 - (accyAddrH & 0x70)) << 4; // One's completent of MSB of address 10 - 8
1185            // & multiply by 16
1186            accyAddr = accyAddrH + accyAddrL + accyLSB - 3; // adjust for the way NCE displays addresses
1187        }
1188        return accyAddr;
1189    }
1190
1191    // whenever link macro is found, put it in the last location
1192    // this makes it easier for the user to edit the macro
1193    private void linkAccessory10(int accyAddr) {
1194        textAccy10.setText(LINK);
1195        accyTextField10.setText(Integer.toString(accyAddr));
1196        cmdButton10.setVisible(false);
1197        deleteButton10.setText(DELETE);
1198        deleteButton10.setToolTipText(Bundle.getMessage("toolTipRemoveMacroLink"));
1199    }
1200
1201    // loads one row with a macro's accessory address and command
1202    private void setAccy(int accyAddr, String accyCmd, JLabel textAccy,
1203            JTextField accyTextField, JButton cmdButton, JButton deleteButton) {
1204        textAccy.setText(ACCESSORY);
1205        accyTextField.setText(Integer.toString(accyAddr));
1206        deleteButton.setEnabled(true);
1207        cmdButton.setText(accyCmd);
1208    }
1209
1210    // returns the accessory command
1211    private String getAccyCmd(int[] b) {
1212        int accyCmd = b[1];
1213        String s = THROWN;
1214        if (checkBoxNce.isSelected()) {
1215            s = THROWN_NCE;
1216        }
1217        accyCmd = accyCmd & 0x01;
1218        if (accyCmd == 0) {
1219            return s;
1220        } else {
1221            s = CLOSED;
1222            if (checkBoxNce.isSelected()) {
1223                s = CLOSED_NCE;
1224            }
1225        }
1226        return s;
1227    }
1228
1229    /**
1230     * Check for valid macro, return number if valid, -1 if not.
1231     *
1232     * @param s string of macro number
1233     * @return mN - int of macro number or -1 if invalid
1234     */
1235    private int validMacro(String s) {
1236        int mN;
1237        try {
1238            mN = Integer.parseInt(s);
1239        } catch (NumberFormatException e) {
1240            return -1;
1241        }
1242        if (mN < 0 || mN > maxNumMacros - 1) {
1243            return -1;
1244        } else {
1245            return mN;
1246        }
1247    }
1248
1249    /**
1250     * writes bytes of NCE macro memory
1251     */
1252    private boolean writeMacroMemory(int macroNum, byte[] b) {
1253        if (isUsb) {
1254            setUsbCabMemoryPointer(memBase, (macroNum * macroSize));
1255            if (!waitNce()) {
1256                return false;
1257            }
1258            // write one byte at a time
1259            for (int i = 0; i < macroSize; i++) {
1260                writeUsbMemory1(b[i]);
1261                if (!waitNce()) {
1262                    return false;
1263                }
1264            }
1265        } else {
1266            int nceMemoryAddr = (macroNum * macroSize) + memBase;
1267            // write 16 bytes
1268            byte[] buf = new byte[16];
1269            for (int i = 0; i < 16; i++) {
1270                buf[i] = b[i];
1271            }
1272            writeSerialMemoryN(nceMemoryAddr, buf);
1273            if (!waitNce()) {
1274                return false;
1275            }
1276            // write 4 bytes
1277            buf = new byte[4];
1278            for (int i = 0; i < 4; i++) {
1279                buf[i] = b[i + 16];
1280            }
1281            writeSerialMemory4(nceMemoryAddr + 16, buf);
1282            if (!waitNce()) {
1283                return false;
1284            }
1285        }
1286        return true;
1287    }
1288
1289    // puts the thread to sleep while we wait for the read CS memory to complete
1290    private boolean waitNce() {
1291        int count = 100;
1292        if (log.isDebugEnabled()) {
1293            log.debug("Going to sleep");
1294        }
1295        while (waiting > 0) {
1296            synchronized (this) {
1297                try {
1298                    wait(100);
1299                } catch (InterruptedException e) {
1300                    //nothing to see here, move along
1301                }
1302            }
1303            count--;
1304            if (count < 0) {
1305                macroReply.setText("Error");
1306                return false;
1307            }
1308        }
1309        if (log.isDebugEnabled()) {
1310            log.debug("awake!");
1311        }
1312        return true;
1313    }
1314
1315    @Override
1316    public void message(NceMessage m) {
1317    } // ignore replies
1318
1319    /**
1320     * response from read
1321     */
1322    int recChar = 0;
1323    int[] recChars = new int[16];
1324
1325    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification = "Thread wait from main transfer loop")
1326    @Override
1327    public void reply(NceReply r) {
1328        if (log.isDebugEnabled()) {
1329            log.debug("Receive character");
1330        }
1331        if (waiting <= 0) {
1332            log.error("unexpected response. Len: {} code: {}", r.getNumDataElements(), r.getElement(0));
1333            return;
1334        }
1335        waiting--;
1336        if (r.getNumDataElements() != replyLen) {
1337            macroReply.setText("error");
1338            return;
1339        }
1340        for (int i = 0; i < replyLen; i++) {
1341            recChars[i] = r.getElement(i);
1342            log.debug("{} recieve: {}", i, String.format("%1X", r.getElement(i)));
1343        }
1344        // wake up thread
1345        synchronized (this) {
1346            notify();
1347        }
1348    }
1349
1350    // USB set cab memory pointer
1351    private void setUsbCabMemoryPointer(int cab, int offset) {
1352        log.debug("Macro base address: {}, offset: {}", Integer.toHexString(cab), offset);
1353        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1354        waiting++;
1355        byte[] bl = NceBinaryCommand.usbMemoryPointer(cab, offset);
1356        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1357        tc.sendNceMessage(m, this);
1358    }
1359
1360    // USB Read N bytes of NCE cab memory
1361    private void readUsbMemoryN(int num) {
1362        switch (num) {
1363            case 1:
1364                replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1365                break;
1366            case 2:
1367                replyLen = NceMessage.REPLY_2; // Expect 2 byte response
1368                break;
1369            case 4:
1370                replyLen = NceMessage.REPLY_4; // Expect 4 byte response
1371                break;
1372            default:
1373                log.error("Invalid usb read byte count");
1374                return;
1375        }
1376        waiting++;
1377        byte[] bl = NceBinaryCommand.usbMemoryRead((byte) num);
1378        NceMessage m = NceMessage.createBinaryMessage(tc, bl, replyLen);
1379        tc.sendNceMessage(m, this);
1380    }
1381
1382    /**
1383     * USB Write 1 byte of NCE memory
1384     *
1385     * @param value byte being written+
1386     */
1387    private void writeUsbMemory1(byte value) {
1388        log.debug("Write byte: {}", String.format("%2X", value));
1389        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1390        waiting++;
1391        byte[] bl = NceBinaryCommand.usbMemoryWrite1(value);
1392        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1393        tc.sendNceMessage(m, this);
1394    }
1395
1396    // Reads 16 bytes of NCE memory
1397    private void readSerialMemory16(int nceCabAddr) {
1398        replyLen = NceMessage.REPLY_16; // Expect 16 byte response
1399        waiting++;
1400        byte[] bl = NceBinaryCommand.accMemoryRead(nceCabAddr);
1401        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_16);
1402        tc.sendNceMessage(m, this);
1403    }
1404
1405    // Write N bytes of NCE memory
1406    private void writeSerialMemoryN(int nceMacroAddr, byte[] b) {
1407        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1408        waiting++;
1409        byte[] bl = NceBinaryCommand.accMemoryWriteN(nceMacroAddr, b);
1410        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1411        tc.sendNceMessage(m, this);
1412    }
1413
1414    // Write 4 bytes of NCE memory
1415    private void writeSerialMemory4(int nceMacroAddr, byte[] b) {
1416        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1417        waiting++;
1418        byte[] bl = NceBinaryCommand.accMemoryWrite4(nceMacroAddr, b);
1419        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1420        tc.sendNceMessage(m, this);
1421    }
1422
1423    private void addAccyRow(JComponent col1, JComponent col2, JComponent col3, JComponent col4, JComponent col5,
1424            int row) {
1425        addItem(col1, 0, row);
1426        addItem(col2, 1, row);
1427        addItem(col3, 2, row);
1428        addItem(col4, 3, row);
1429        addItem(col5, 4, row);
1430    }
1431
1432    private void addItem(JComponent c, int x, int y) {
1433        GridBagConstraints gc = new GridBagConstraints();
1434        gc.gridx = x;
1435        gc.gridy = y;
1436        gc.weightx = 100.0;
1437        gc.weighty = 100.0;
1438        add(c, gc);
1439    }
1440
1441    private void addButtonAction(JButton b) {
1442        b.addActionListener(new java.awt.event.ActionListener() {
1443            @Override
1444            public void actionPerformed(java.awt.event.ActionEvent e) {
1445                buttonActionPerformed(e);
1446            }
1447        });
1448    }
1449
1450    private void addButtonCmdAction(JButton b) {
1451        b.addActionListener(new java.awt.event.ActionListener() {
1452            @Override
1453            public void actionPerformed(java.awt.event.ActionEvent e) {
1454                buttonActionCmdPerformed(e);
1455            }
1456        });
1457    }
1458
1459    private void addButtonDelAction(JButton b) {
1460        b.addActionListener(new java.awt.event.ActionListener() {
1461            @Override
1462            public void actionPerformed(java.awt.event.ActionEvent e) {
1463                buttonActionDeletePerformed(e);
1464            }
1465        });
1466    }
1467
1468    private void addCheckBoxAction(JCheckBox cb) {
1469        cb.addActionListener(new java.awt.event.ActionListener() {
1470            @Override
1471            public void actionPerformed(java.awt.event.ActionEvent e) {
1472                checkBoxActionPerformed(e);
1473            }
1474        });
1475    }
1476
1477    //  initialize accessories 1 to 10
1478    private void initAccyFields() {
1479        initAccyRow(1, num1, textAccy1, accyTextField1, cmdButton1, deleteButton1);
1480        initAccyRow(2, num2, textAccy2, accyTextField2, cmdButton2, deleteButton2);
1481        initAccyRow(3, num3, textAccy3, accyTextField3, cmdButton3, deleteButton3);
1482        initAccyRow(4, num4, textAccy4, accyTextField4, cmdButton4, deleteButton4);
1483        initAccyRow(5, num5, textAccy5, accyTextField5, cmdButton5, deleteButton5);
1484        initAccyRow(6, num6, textAccy6, accyTextField6, cmdButton6, deleteButton6);
1485        initAccyRow(7, num7, textAccy7, accyTextField7, cmdButton7, deleteButton7);
1486        initAccyRow(8, num8, textAccy8, accyTextField8, cmdButton8, deleteButton8);
1487        initAccyRow(9, num9, textAccy9, accyTextField9, cmdButton9, deleteButton9);
1488        initAccyRow(isUsb ? 8 : 10, num10, textAccy10, accyTextField10, cmdButton10, deleteButton10);
1489    }
1490
1491    private void initAccyRow(int row, JLabel num, JLabel textAccy, JTextField accyTextField, JButton cmdButton,
1492            JButton deleteButton) {
1493        num.setText(Integer.toString(row));
1494        num.setVisible(true);
1495        textAccy.setText(EMPTY);
1496        textAccy.setVisible(true);
1497        cmdButton.setText(QUESTION);
1498        cmdButton.setVisible(true);
1499        cmdButton.setToolTipText(Bundle.getMessage("toolTipSetCommand"));
1500        deleteButton.setText(DELETE);
1501        deleteButton.setVisible(true);
1502        deleteButton.setEnabled(false);
1503        deleteButton.setToolTipText(Bundle.getMessage("toolTipRemoveAcessory"));
1504        accyTextField.setText("");
1505        accyTextField.setToolTipText(Bundle.getMessage("EnterAccessoryNumber"));
1506        accyTextField.setMaximumSize(new Dimension(accyTextField
1507                .getMaximumSize().width,
1508                accyTextField.getPreferredSize().height));
1509        if (isUsb && row == 8 || row == 10) {
1510            initAccyRow10();
1511        }
1512    }
1513
1514    private void initAccyRow10() {
1515        cmdButton10.setVisible(true);
1516        deleteButton10.setText(LINK);
1517        deleteButton10.setEnabled(true);
1518        deleteButton10.setToolTipText(Bundle.getMessage("toolTipLink"));
1519        accyTextField10.setToolTipText(Bundle.getMessage("toolTip10", maxNumMacros - 1));
1520    }
1521
1522    /**
1523     * Nested class to create one of these using old-style defaults
1524     */
1525    static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction {
1526
1527        public Default() {
1528            super("Open NCE Macro Editor",
1529                    new jmri.util.swing.sdi.JmriJFrameInterface(),
1530                    NceMacroEditPanel.class.getName(),
1531                    jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class));
1532        }
1533    }
1534
1535    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceMacroEditPanel.class);
1536
1537}