001package jmri.jmrix.nce.macro;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.Dimension;
006import java.awt.GridBagConstraints;
007import java.awt.GridBagLayout;
008
009import javax.swing.JButton;
010import javax.swing.JCheckBox;
011import javax.swing.JComponent;
012import javax.swing.JLabel;
013import javax.swing.JTextField;
014
015import jmri.InstanceManager;
016import jmri.jmrix.nce.NceBinaryCommand;
017import jmri.jmrix.nce.NceMessage;
018import jmri.jmrix.nce.NceReply;
019import jmri.jmrix.nce.NceSystemConnectionMemo;
020import jmri.jmrix.nce.NceTrafficController;
021import jmri.util.swing.JmriJOptionPane;
022
023/**
024 * Frame for user edit of NCE macros
025 *
026 * NCE macros are stored in Command Station (CS) memory starting at address
027 * xC800 (PH5 0x6000). Each macro consists of 20 bytes. The last macro 255 is at address
028 * xDBEC.
029 *
030 * Macro addr 0 xC800 1 xC814 2 xC828 3 xC83C . . . . 255 xDBEC
031 *
032 * Each macro can close or throw up to ten accessories. Macros can also be
033 * linked together. Two bytes (16 bit word) define an accessory address and
034 * command, or the address of the next macro to be executed. If the upper byte
035 * of the macro data word is xFF, then the next byte contains the address of the
036 * next macro to be executed by the NCE CS. For example, xFF08 means link to
037 * macro 8. NCE uses the NMRA DCC accessory decoder packet format for the word
038 * definition of their macros.
039 *
040 * Macro data byte:
041 *
042 * bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 _ _ _ _ 1 0 A A A A A A 1 A A A C D
043 * D D addr bit 7 6 5 4 3 2 10 9 8 1 0 turnout T
044 *
045 * By convention, MSB address bits 10 - 8 are one's complement. NCE macros
046 * always set the C bit to 1. The LSB "D" (0) determines if the accessory is to
047 * be thrown (0) or closed (1). The next two bits "D D" are the LSBs of the
048 * accessory address. Note that NCE display addresses are 1 greater than NMRA
049 * DCC. Note that address bit 2 isn't supposed to be inverted, but it is the way
050 * NCE implemented their macros.
051 *
052 * Examples:
053 *
054 * 81F8 = accessory 1 thrown 9FFC = accessory 123 thrown B5FD = accessory 211
055 * close BF8F = accessory 2044 close
056 *
057 * FF10 = link macro 16
058 *
059 * Updated for including the USB 7.* for 1.65 command station
060 *
061 * Variables found on cab context page 14 (Cab address 14)
062 *
063 * ;macro table MACRO_TBL ;table of macros, 16 entries of 16 bytes organized as:
064 * ; macro 0, high byte, low byte - 7 more times (8 accy commands total) ; macro
065 * 1, high byte, low byte - 7 more times (8 accy commands total) ; ; macro 16,
066 * high byte, low byte - 7 more times (8 accy commands total)
067 *
068 *
069 * @author Dan Boudreau Copyright (C) 2007
070 * @author Ken Cameron Copyright (C) 2013, 2023
071 */
072public class NceMacroEditPanel extends jmri.jmrix.nce.swing.NcePanel implements jmri.jmrix.nce.NceListener {
073    
074    private NceTrafficController tc = null;
075    private int memBase;
076    private int maxNumMacros;
077    private int macroSize;
078    private boolean isUsb;
079
080    private int macroNum = 0; // macro being worked
081    private int replyLen = 0; // expected byte length
082    private int waiting = 0; // to catch responses not intended for this module
083    //    private static final int firstTimeSleep = 3000;  // delay first operation to let panel build
084    //    private final boolean firstTime = true; // wait for panel to display
085
086    private static final String QUESTION = Bundle.getMessage("Add");// The three possible states for a turnout
087    private static final String CLOSED = InstanceManager.turnoutManagerInstance().getClosedText();
088    private static final String THROWN = InstanceManager.turnoutManagerInstance().getThrownText();
089    private static final String CLOSED_NCE = Bundle.getMessage("Normal");
090    private static final String THROWN_NCE = Bundle.getMessage("Reverse");
091
092    private static final String DELETE = Bundle.getMessage("Delete");
093
094    private static final String EMPTY = Bundle.getMessage("empty"); // One of two accessory states
095    private static final String ACCESSORY = Bundle.getMessage("accessory");
096
097    private static final String LINK = Bundle.getMessage("LinkMacro");// Line 10 alternative to Delete
098
099    Thread nceMemoryThread;
100    private boolean readRequested = false;
101    private boolean writeRequested = false;
102
103    private boolean macroSearchInc = false; // next search
104    private boolean macroSearchDec = false; // previous search
105    private boolean macroValid = false; // when true, NCE CS has responded to macro read
106    private boolean macroModified = false; // when true, macro has been modified by user
107
108    // member declarations
109    JLabel textMacro = new JLabel(Bundle.getMessage("MacroLabel"));
110    JLabel textReply = new JLabel(Bundle.getMessage("ReplyLabel"));
111    JLabel macroReply = new JLabel();
112
113    // major buttons
114    JButton previousButton = new JButton(Bundle.getMessage("Previous"));
115    JButton nextButton = new JButton(Bundle.getMessage("Next"));
116    JButton getButton = new JButton(Bundle.getMessage("Get"));
117    JButton saveButton = new JButton(Bundle.getMessage("Save"));
118    JButton backUpButton = new JButton(Bundle.getMessage("Backup"));
119    JButton restoreButton = new JButton(Bundle.getMessage("Restore"));
120
121    // check boxes
122    JCheckBox checkBoxEmpty = new JCheckBox(Bundle.getMessage("EmptyMacro"));
123    JCheckBox checkBoxNce = new JCheckBox(Bundle.getMessage("NCETurnout"));
124
125    // macro text field
126    JTextField macroTextField = new JTextField(4);
127
128    // for padding out panel
129    JLabel space2 = new JLabel("                          ");
130    JLabel space3 = new JLabel("                          ");
131    JLabel space4 = new JLabel("                          ");
132    JLabel space15 = new JLabel(" ");
133
134    // accessory row 1
135    JLabel num1 = new JLabel();
136    JLabel textAccy1 = new JLabel();
137    JTextField accyTextField1 = new JTextField(4);
138    JButton cmdButton1 = new JButton();
139    JButton deleteButton1 = new JButton();
140
141    //  accessory row 2
142    JLabel num2 = new JLabel();
143    JLabel textAccy2 = new JLabel();
144    JTextField accyTextField2 = new JTextField(4);
145    JButton cmdButton2 = new JButton();
146    JButton deleteButton2 = new JButton();
147
148    //  accessory row 3
149    JLabel num3 = new JLabel();
150    JLabel textAccy3 = new JLabel();
151    JTextField accyTextField3 = new JTextField(4);
152    JButton cmdButton3 = new JButton();
153    JButton deleteButton3 = new JButton();
154
155    //  accessory row 4
156    JLabel num4 = new JLabel();
157    JLabel textAccy4 = new JLabel();
158    JTextField accyTextField4 = new JTextField(4);
159    JButton cmdButton4 = new JButton();
160    JButton deleteButton4 = new JButton();
161
162    //  accessory row 5
163    JLabel num5 = new JLabel();
164    JLabel textAccy5 = new JLabel();
165    JTextField accyTextField5 = new JTextField(4);
166    JButton cmdButton5 = new JButton();
167    JButton deleteButton5 = new JButton();
168
169    //  accessory row 6
170    JLabel num6 = new JLabel();
171    JLabel textAccy6 = new JLabel();
172    JTextField accyTextField6 = new JTextField(4);
173    JButton cmdButton6 = new JButton();
174    JButton deleteButton6 = new JButton();
175
176    //  accessory row 7
177    JLabel num7 = new JLabel();
178    JLabel textAccy7 = new JLabel();
179    JTextField accyTextField7 = new JTextField(4);
180    JButton cmdButton7 = new JButton();
181    JButton deleteButton7 = new JButton();
182
183    //  accessory row 8
184    JLabel num8 = new JLabel();
185    JLabel textAccy8 = new JLabel();
186    JTextField accyTextField8 = new JTextField(4);
187    JButton cmdButton8 = new JButton();
188    JButton deleteButton8 = new JButton();
189
190    //  accessory row 9
191    JLabel num9 = new JLabel();
192    JLabel textAccy9 = new JLabel();
193    JTextField accyTextField9 = new JTextField(4);
194    JButton cmdButton9 = new JButton();
195    JButton deleteButton9 = new JButton();
196
197    //  accessory row 10
198    JLabel num10 = new JLabel();
199    JLabel textAccy10 = new JLabel();
200    JTextField accyTextField10 = new JTextField(4);
201    JButton cmdButton10 = new JButton();
202    JButton deleteButton10 = new JButton();
203
204    public NceMacroEditPanel() {
205        super();
206    }
207
208    /**
209     * {@inheritDoc}
210     */
211    @Override
212    public void initContext(Object context) {
213        if (context instanceof NceSystemConnectionMemo) {
214            initComponents((NceSystemConnectionMemo) context);
215        }
216    }
217
218    /**
219     * {@inheritDoc}
220     */
221    @Override
222    public String getHelpTarget() {
223        return "package.jmri.jmrix.nce.macro.NceMacroEditFrame";
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_");
236        }
237        x.append(": ");
238        x.append(Bundle.getMessage("TitleEditNCEMacro"));
239        return x.toString();
240    }
241
242    /**
243     * {@inheritDoc}
244     */
245    @Override
246    public void initComponents(NceSystemConnectionMemo memo) {
247        this.memo = memo;
248        this.tc = memo.getNceTrafficController();
249        memBase = tc.csm.getMacroAddr();
250        maxNumMacros = tc.csm.getMacroLimit();
251        macroSize = tc.csm.getMacroSize();
252        if ((tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) &&
253                (tc.getCmdGroups() & NceTrafficController.CMDS_MEM) != 0) {
254            isUsb = true;
255            memBase = -1;
256        }
257
258        // the following code sets the frame's initial state
259        // default at startup
260        macroReply.setText(Bundle.getMessage("unknown"));
261        macroTextField.setText("");
262        saveButton.setEnabled(false);
263
264        // load tool tips
265        previousButton.setToolTipText(Bundle.getMessage("toolTipSearchDecrementing"));
266        nextButton.setToolTipText(Bundle.getMessage("toolTipSearchIncrementing"));
267        getButton.setToolTipText(Bundle.getMessage("toolTipReadMacro"));
268        if (isUsb) {
269            macroTextField.setToolTipText(Bundle.getMessage("toolTipEnterMacroUsb"));
270        } else {
271            macroTextField.setToolTipText(Bundle.getMessage("toolTipEnterMacroSerial"));
272        }
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"),
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("EnterMacroNumberLine10"),
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        //        if (firstTime) {
596        //            try {
597        //                Thread.sleep(firstTimeSleep); // wait for panel to display
598        //            } catch (InterruptedException e) {
599        //                log.error("Thread unexpectedly interrupted", e);
600        //            }
601        //        }
602        //
603        //        firstTime = false;
604        String m = macroTextField.getText();
605        if (m.isEmpty()) {
606            m = "0";
607        }
608        int mN = validMacro(m);
609        if (mN < 0) {
610            macroReply.setText(Bundle.getMessage("error"));
611            JmriJOptionPane.showMessageDialog(this,
612                    Bundle.getMessage("EnterMacroNumber"),
613                    Bundle.getMessage("NceMacro"),
614                    JmriJOptionPane.ERROR_MESSAGE);
615            macroValid = false;
616            return mN;
617        }
618        if (macroSearchInc || macroSearchDec) {
619            macroReply.setText(Bundle.getMessage("searching"));
620            if (macroSearchInc) {
621                mN++;
622                if (mN >= maxNumMacros + 1) {
623                    mN = 0;
624                }
625            }
626            if (macroSearchDec) {
627                mN--;
628                if (mN <= -1) {
629                    mN = maxNumMacros;
630                }
631            }
632        } else {
633            macroReply.setText(Bundle.getMessage("waiting"));
634        }
635
636        return mN;
637    }
638
639    /**
640     * Writes all bytes to NCE CS memory as long as there are no user input
641     * errors
642     *
643     */
644    private boolean saveMacro() {
645        //        if (firstTime) {
646        //            try {
647        //                Thread.sleep(firstTimeSleep); // wait for panel to display
648        //            } catch (InterruptedException e) {
649        //                log.error("Thread unexpectedly interrupted", e);
650        //            }
651        //        }
652        //
653        //        firstTime = false;
654        byte[] macroAccy = new byte[macroSize]; // NCE Macro data
655        int index = 0;
656        int accyNum = 0;
657        // test the inputs, convert from text
658        accyNum = getAccyRow(macroAccy, index, textAccy1, accyTextField1, cmdButton1);
659        if (accyNum < 0) //error
660        {
661            return false;
662        }
663        if (accyNum > 0) {
664            index += 2;
665        }
666        accyNum = getAccyRow(macroAccy, index, textAccy2, accyTextField2, cmdButton2);
667        if (accyNum < 0) {
668            return false;
669        }
670        if (accyNum > 0) {
671            index += 2;
672        }
673        accyNum = getAccyRow(macroAccy, index, textAccy3, accyTextField3, cmdButton3);
674        if (accyNum < 0) {
675            return false;
676        }
677        if (accyNum > 0) {
678            index += 2;
679        }
680        accyNum = getAccyRow(macroAccy, index, textAccy4, accyTextField4, cmdButton4);
681        if (accyNum < 0) {
682            return false;
683        }
684        if (accyNum > 0) {
685            index += 2;
686        }
687        accyNum = getAccyRow(macroAccy, index, textAccy5, accyTextField5, cmdButton5);
688        if (accyNum < 0) {
689            return false;
690        }
691        if (accyNum > 0) {
692            index += 2;
693        }
694        accyNum = getAccyRow(macroAccy, index, textAccy6, accyTextField6, cmdButton6);
695        if (accyNum < 0) {
696            return false;
697        }
698        if (accyNum > 0) {
699            index += 2;
700        }
701        accyNum = getAccyRow(macroAccy, index, textAccy7, accyTextField7, cmdButton7);
702        if (accyNum < 0) {
703            return false;
704        }
705        if (accyNum > 0) {
706            index += 2;
707        }
708        if (!isUsb) {
709            accyNum = getAccyRow(macroAccy, index, textAccy8, accyTextField8, cmdButton8);
710            if (accyNum < 0) {
711                return false;
712            }
713            if (accyNum > 0) {
714                index += 2;
715            }
716            accyNum = getAccyRow(macroAccy, index, textAccy9, accyTextField9, cmdButton9);
717            if (accyNum < 0) {
718                return false;
719            }
720            if (accyNum > 0) {
721                index += 2;
722            }
723        }
724        accyNum = getAccyRow(macroAccy, index, textAccy10, accyTextField10, cmdButton10);
725        if (accyNum < 0) {
726            JmriJOptionPane.showMessageDialog(this,
727                    Bundle.getMessage("EnterMacroNumberLine10"),
728                    Bundle.getMessage("NceMacro"),
729                    JmriJOptionPane.ERROR_MESSAGE);
730            return false;
731        }
732
733        processMemory(false, true, macroNum, macroAccy);
734        return true;
735    }
736
737    private void processMemory(boolean doRead, boolean doWrite, int macroId, byte[] macroArray) {
738        final byte[] macroData = new byte[macroSize];
739        macroValid = false;
740        readRequested = false;
741        writeRequested = false;
742
743        if (doRead) {
744            readRequested = true;
745        }
746        if (doWrite) {
747            writeRequested = true;
748            System.arraycopy(macroArray, 0, macroData, 0, macroSize);
749        }
750
751        // Set up a separate thread to access CS memory
752        if (nceMemoryThread != null && nceMemoryThread.isAlive()) {
753            return; // thread is already running
754        }
755        nceMemoryThread = new Thread(new Runnable() {
756            @Override
757            public void run() {
758                if (readRequested) {
759                    macroNum = macroId;
760                    int macroCount = 0;
761                    while (true) {
762                        int entriesRead = readMacroMemory(macroNum);
763                        macroTextField.setText(Integer.toString(macroNum));
764                        if (entriesRead == 0) {
765                            // Macro is empty so init the accessory fields
766                            initAccyFields();
767                            macroReply.setText(Bundle.getMessage("macroEmpty"));
768                            if (checkBoxEmpty.isSelected()) {
769                                macroValid = true;
770                                macroSearchInc = false;
771                                macroSearchDec = false;
772                                break;
773                            }
774                        } else if (entriesRead < 0) {
775                            macroReply.setText(Bundle.getMessage("error"));
776                            macroValid = false;
777                            macroSearchInc = false;
778                            macroSearchDec = false;
779                            break;
780                        } else {
781                            macroReply.setText(Bundle.getMessage("macroFound"));
782                            if (!checkBoxEmpty.isSelected()) {
783                                macroSearchInc = false;
784                                macroSearchDec = false;
785                                macroValid = true;
786                                break;
787                            }
788                        }
789                        if ((macroSearchInc || macroSearchDec) && !macroValid) {
790                            macroCount++;
791                            if (macroCount > maxNumMacros) {
792                                macroSearchInc = false;
793                                macroSearchDec = false;
794                                break;
795                            }
796                            macroNum = getMacro();
797                        }
798                        if (!(macroSearchInc || macroSearchDec)) {
799                            // we were doing a get, not a search
800                            macroValid = true;
801                            break;
802                        }
803                    }
804                }
805                if (writeRequested) {
806                    writeMacroMemory(macroId, macroData);
807                }
808            }
809        });
810        nceMemoryThread.setName(Bundle.getMessage("ThreadTitle"));
811        nceMemoryThread.setPriority(Thread.MIN_PRIORITY);
812        nceMemoryThread.start();
813    }
814
815    // Reads 16/20 bytes of NCE macro memory
816    private int readMacroMemory(int mN) {
817        int entriesRead = 0;
818        if (isUsb) {
819            setUsbCabMemoryPointer(tc.csm.getMacroAddr(), (mN * macroSize));
820            if (!waitNce()) {
821                return -1;
822            }
823            // 1st word of macro
824            readUsbMemoryN(2);
825            if (!waitNce()) {
826                return -1;
827            }
828            int accyAddr = getMacroAccyAdr(recChars);
829            if (accyAddr <= 0) {
830                return entriesRead;
831            }
832            entriesRead++;
833            setAccy(accyAddr, getAccyCmd(recChars), textAccy1, accyTextField1, cmdButton1,
834                    deleteButton1);
835            // 2nd word of macro
836            readUsbMemoryN(2);
837            if (!waitNce()) {
838                return -1;
839            }
840            accyAddr = getMacroAccyAdr(recChars);
841            if (accyAddr <= 0) {
842                return entriesRead;
843            }
844            entriesRead++;
845            setAccy(accyAddr, getAccyCmd(recChars), textAccy2, accyTextField2, cmdButton2,
846                    deleteButton2);
847            // 3rd word of macro
848            readUsbMemoryN(2);
849            if (!waitNce()) {
850                return -1;
851            }
852            accyAddr = getMacroAccyAdr(recChars);
853            if (accyAddr <= 0) {
854                return entriesRead;
855            }
856            entriesRead++;
857            setAccy(accyAddr, getAccyCmd(recChars), textAccy3, accyTextField3, cmdButton3,
858                    deleteButton3);
859            // 4th word of macro
860            readUsbMemoryN(2);
861            if (!waitNce()) {
862                return -1;
863            }
864            accyAddr = getMacroAccyAdr(recChars);
865            if (accyAddr <= 0) {
866                return entriesRead;
867            }
868            entriesRead++;
869            setAccy(accyAddr, getAccyCmd(recChars), textAccy4, accyTextField4, cmdButton4,
870                    deleteButton4);
871            // 5th word of macro
872            readUsbMemoryN(2);
873            if (!waitNce()) {
874                return -1;
875            }
876            accyAddr = getMacroAccyAdr(recChars);
877            if (accyAddr <= 0) {
878                return entriesRead;
879            }
880            entriesRead++;
881            setAccy(accyAddr, getAccyCmd(recChars), textAccy5, accyTextField5, cmdButton5,
882                    deleteButton5);
883            // 6th word of macro
884            readUsbMemoryN(2);
885            if (!waitNce()) {
886                return -1;
887            }
888            accyAddr = getMacroAccyAdr(recChars);
889            if (accyAddr <= 0) {
890                return entriesRead;
891            }
892            entriesRead++;
893            setAccy(accyAddr, getAccyCmd(recChars), textAccy6, accyTextField6, cmdButton6,
894                    deleteButton6);
895            // 7th word of macro
896            readUsbMemoryN(2);
897            if (!waitNce()) {
898                return -1;
899            }
900            accyAddr = getMacroAccyAdr(recChars);
901            if (accyAddr <= 0) {
902                return entriesRead;
903            }
904            entriesRead++;
905            setAccy(accyAddr, getAccyCmd(recChars), textAccy7, accyTextField7, cmdButton7,
906                    deleteButton7);
907            // 8th word of macro
908            readUsbMemoryN(2);
909            if (!waitNce()) {
910                return -1;
911            }
912            accyAddr = getMacroAccyAdr(recChars);
913            if (accyAddr <= 0) {
914                return entriesRead;
915            }
916            entriesRead++;
917            setAccy(accyAddr, getAccyCmd(recChars), textAccy8, accyTextField8, cmdButton8,
918                    deleteButton8);
919            return entriesRead;
920        } else {
921            int memPtr = tc.csm.getMacroAddr() + (mN * macroSize);
922            int readPtr = 0;
923            int[] workBuf = new int[2];
924            // 1st word of macro
925            readSerialMemory16(memPtr);
926            if (!waitNce()) {
927                return -1;
928            }
929            workBuf[0] = recChars[readPtr++];
930            workBuf[1] = recChars[readPtr++];
931            int accyAddr = getMacroAccyAdr(workBuf);
932            if (accyAddr <= 0) {
933                return entriesRead;
934            }
935            entriesRead++;
936            setAccy(accyAddr, getAccyCmd(workBuf), textAccy1, accyTextField1, cmdButton1,
937                    deleteButton1);
938            // 2nd word of macro
939            workBuf[0] = recChars[readPtr++];
940            workBuf[1] = recChars[readPtr++];
941            accyAddr = getMacroAccyAdr(workBuf);
942            if (accyAddr <= 0) {
943                return entriesRead;
944            }
945            entriesRead++;
946            setAccy(accyAddr, getAccyCmd(workBuf), textAccy2, accyTextField2, cmdButton2,
947                    deleteButton2);
948            // 3rd word of macro
949            workBuf[0] = recChars[readPtr++];
950            workBuf[1] = recChars[readPtr++];
951            accyAddr = getMacroAccyAdr(workBuf);
952            if (accyAddr <= 0) {
953                return entriesRead;
954            }
955            entriesRead++;
956            setAccy(accyAddr, getAccyCmd(workBuf), textAccy3, accyTextField3, cmdButton3,
957                    deleteButton3);
958            // 4th word of macro
959            workBuf[0] = recChars[readPtr++];
960            workBuf[1] = recChars[readPtr++];
961            accyAddr = getMacroAccyAdr(workBuf);
962            if (accyAddr <= 0) {
963                return entriesRead;
964            }
965            entriesRead++;
966            setAccy(accyAddr, getAccyCmd(workBuf), textAccy4, accyTextField4, cmdButton4,
967                    deleteButton4);
968            // 5th word of macro
969            workBuf[0] = recChars[readPtr++];
970            workBuf[1] = recChars[readPtr++];
971            accyAddr = getMacroAccyAdr(workBuf);
972            if (accyAddr <= 0) {
973                return entriesRead;
974            }
975            entriesRead++;
976            setAccy(accyAddr, getAccyCmd(workBuf), textAccy5, accyTextField5, cmdButton5,
977                    deleteButton5);
978            // 6th word of macro
979            workBuf[0] = recChars[readPtr++];
980            workBuf[1] = recChars[readPtr++];
981            accyAddr = getMacroAccyAdr(workBuf);
982            if (accyAddr <= 0) {
983                return entriesRead;
984            }
985            entriesRead++;
986            setAccy(accyAddr, getAccyCmd(workBuf), textAccy6, accyTextField6, cmdButton6,
987                    deleteButton6);
988            // 7th word of macro
989            workBuf[0] = recChars[readPtr++];
990            workBuf[1] = recChars[readPtr++];
991            accyAddr = getMacroAccyAdr(workBuf);
992            if (accyAddr <= 0) {
993                return entriesRead;
994            }
995            entriesRead++;
996            setAccy(accyAddr, getAccyCmd(workBuf), textAccy7, accyTextField7, cmdButton7,
997                    deleteButton7);
998            // 8th word of macro
999            workBuf[0] = recChars[readPtr++];
1000            workBuf[1] = recChars[readPtr++];
1001            accyAddr = getMacroAccyAdr(workBuf);
1002            if (accyAddr <= 0) {
1003                return entriesRead;
1004            }
1005            entriesRead++;
1006            setAccy(accyAddr, getAccyCmd(workBuf), textAccy8, accyTextField8, cmdButton8,
1007                    deleteButton8);
1008            // 9th word of macro
1009            memPtr += 16;
1010            readPtr = 0;
1011            readSerialMemory16(memPtr);
1012            if (!waitNce()) {
1013                return -1;
1014            }
1015            workBuf[0] = recChars[readPtr++];
1016            workBuf[1] = recChars[readPtr++];
1017            accyAddr = getMacroAccyAdr(workBuf);
1018            if (accyAddr <= 0) {
1019                return entriesRead;
1020            }
1021            entriesRead++;
1022            setAccy(accyAddr, getAccyCmd(workBuf), textAccy9, accyTextField9, cmdButton9,
1023                    deleteButton9);
1024            // 10th word of macro
1025            workBuf[0] = recChars[readPtr++];
1026            workBuf[1] = recChars[readPtr++];
1027            accyAddr = getMacroAccyAdr(workBuf);
1028            if (accyAddr <= 0) {
1029                return entriesRead;
1030            }
1031            entriesRead++;
1032            setAccy(accyAddr, getAccyCmd(workBuf), textAccy10, accyTextField10, cmdButton10,
1033                    deleteButton10);
1034            return entriesRead;
1035        }
1036    }
1037
1038    // Updates the accessory line when the user hits the command button
1039    private void updateAccyCmdPerformed(JTextField accyTextField, JButton cmdButton, JLabel textAccy,
1040            JButton deleteButton) {
1041        if (!macroValid) { // Error user input incorrect
1042            JmriJOptionPane.showMessageDialog(this,
1043                    Bundle.getMessage("GetMacroNumber"), Bundle.getMessage("NceMacro"),
1044                    JmriJOptionPane.ERROR_MESSAGE);
1045        } else {
1046            String accyText = accyTextField.getText();
1047            int accyNum = 0;
1048            try {
1049                accyNum = Integer.parseInt(accyText);
1050            } catch (NumberFormatException e) {
1051                accyNum = -1;
1052            }
1053
1054            if (accyNum < 1 || accyNum > 2044) {
1055                JmriJOptionPane.showMessageDialog(this,
1056                        Bundle.getMessage("EnterAccessoryNumber"), Bundle.getMessage("NceMacroAddress"),
1057                        JmriJOptionPane.ERROR_MESSAGE);
1058                return;
1059            }
1060
1061            String accyCmd = cmdButton.getText();
1062
1063            // Use JMRI or NCE turnout terminology
1064            if (checkBoxNce.isSelected()) {
1065
1066                if (!accyCmd.equals(THROWN_NCE)) {
1067                    cmdButton.setText(THROWN_NCE);
1068                }
1069                if (!accyCmd.equals(CLOSED_NCE)) {
1070                    cmdButton.setText(CLOSED_NCE);
1071                }
1072
1073            } else {
1074
1075                if (!accyCmd.equals(THROWN)) {
1076                    cmdButton.setText(THROWN);
1077                }
1078                if (!accyCmd.equals(CLOSED)) {
1079                    cmdButton.setText(CLOSED);
1080                }
1081            }
1082
1083            setSaveButton(true);
1084            textAccy.setText(ACCESSORY);
1085            deleteButton.setText(DELETE);
1086            deleteButton.setToolTipText(Bundle.getMessage("toolTipRemoveAcessory"));
1087            deleteButton.setEnabled(true);
1088        }
1089    }
1090
1091    // Delete an accessory from the macro
1092    private void updateAccyDelPerformed(JTextField accyTextField, JButton cmdButton, JLabel textAccy,
1093            JButton deleteButton) {
1094        setSaveButton(true);
1095        textAccy.setText(EMPTY);
1096        accyTextField.setText("");
1097        cmdButton.setText(QUESTION);
1098        deleteButton.setEnabled(false);
1099    }
1100
1101    private int getAccyRow(byte[] b, int i, JLabel textAccy, JTextField accyTextField, JButton cmdButton) {
1102        int accyNum = 0;
1103        if (textAccy.getText().equals(ACCESSORY)) {
1104            accyNum = getAccyNum(accyTextField.getText());
1105            if (accyNum < 0) {
1106                return accyNum;
1107            }
1108            accyNum = accyNum + 3; // adjust for NCE's way of encoding
1109            int upperByte = (accyNum & 0xFF);
1110            upperByte = (upperByte >> 2) + 0x80;
1111            b[i] = (byte) upperByte;
1112            int lowerByteH = (((accyNum ^ 0x0700) & 0x0700) >> 4);// 3 MSB 1s complement
1113            int lowerByteL = ((accyNum & 0x3) << 1); // 2 LSB
1114            int lowerByte = (lowerByteH + lowerByteL + 0x88);
1115            // adjust for turnout command
1116            if (cmdButton.getText().equals(CLOSED) || cmdButton.getText().equals(CLOSED_NCE)) {
1117                lowerByte++;
1118            }
1119            b[i + 1] = (byte) (lowerByte);
1120        }
1121        if (textAccy.getText().equals(LINK)) {
1122            int macroLink = validMacro(accyTextField.getText());
1123            if (macroLink < 0) {
1124                return macroLink;
1125            }
1126            b[i] = (byte) 0xFF; // NCE macro link command
1127            b[i + 1] = (byte) macroLink; // link macro number
1128        }
1129        return accyNum;
1130    }
1131
1132    private int getAccyNum(String accyText) {
1133        int accyNum = 0;
1134        try {
1135            accyNum = Integer.parseInt(accyText);
1136        } catch (NumberFormatException e) {
1137            accyNum = -1;
1138        }
1139        if (accyNum < 1 || accyNum > 2044) {
1140            JmriJOptionPane.showMessageDialog(this,
1141                    Bundle.getMessage("EnterAccessoryNumber"), Bundle.getMessage("NceMacroAddress"),
1142                    JmriJOptionPane.ERROR_MESSAGE);
1143            accyNum = -1;
1144        }
1145        return accyNum;
1146    }
1147
1148    // display save button
1149    private void setSaveButton(boolean display) {
1150        macroModified = display;
1151        saveButton.setEnabled(display);
1152        if (!isUsb) {
1153            backUpButton.setEnabled(!display);
1154            restoreButton.setEnabled(!display);
1155        }
1156    }
1157
1158    // Convert NCE macro hex data to accessory address
1159    // returns 0 if macro address is empty
1160    // returns a negative address if link address
1161    // & loads accessory 10 with link macro
1162    private int getMacroAccyAdr(int[] b) {
1163        int accyAddrL = b[0];
1164        int accyAddr = 0;
1165        // check for null
1166        if ((accyAddrL == 0) && (b[1] == 0)) {
1167            return accyAddr;
1168        }
1169        // Check to see if link address
1170        if ((accyAddrL & 0xFF) == 0xFF) {
1171            // Link address
1172            accyAddr = b[1];
1173            linkAccessory10(accyAddr & 0xFF);
1174            accyAddr = -accyAddr;
1175
1176            // must be an accessory address
1177        } else {
1178            accyAddrL = (accyAddrL << 2) & 0xFC; // accessory address bits 7 - 2
1179            int accyLSB = b[1];
1180            accyLSB = (accyLSB & 0x06) >> 1; // accessory address bits 1 - 0
1181            int accyAddrH = b[1];
1182            accyAddrH = (0x70 - (accyAddrH & 0x70)) << 4; // One's completent of MSB of address 10 - 8
1183            // & multiply by 16
1184            accyAddr = accyAddrH + accyAddrL + accyLSB - 3; // adjust for the way NCE displays addresses
1185        }
1186        return accyAddr;
1187    }
1188
1189    // whenever link macro is found, put it in the last location
1190    // this makes it easier for the user to edit the macro
1191    private void linkAccessory10(int accyAddr) {
1192        textAccy10.setText(LINK);
1193        accyTextField10.setText(Integer.toString(accyAddr));
1194        cmdButton10.setVisible(false);
1195        deleteButton10.setText(DELETE);
1196        deleteButton10.setToolTipText(Bundle.getMessage("toolTipRemoveMacroLink"));
1197    }
1198
1199    // loads one row with a macro's accessory address and command
1200    private void setAccy(int accyAddr, String accyCmd, JLabel textAccy,
1201            JTextField accyTextField, JButton cmdButton, JButton deleteButton) {
1202        textAccy.setText(ACCESSORY);
1203        accyTextField.setText(Integer.toString(accyAddr));
1204        deleteButton.setEnabled(true);
1205        cmdButton.setText(accyCmd);
1206    }
1207
1208    // returns the accessory command
1209    private String getAccyCmd(int[] b) {
1210        int accyCmd = b[1];
1211        String s = THROWN;
1212        if (checkBoxNce.isSelected()) {
1213            s = THROWN_NCE;
1214        }
1215        accyCmd = accyCmd & 0x01;
1216        if (accyCmd == 0) {
1217            return s;
1218        } else {
1219            s = CLOSED;
1220            if (checkBoxNce.isSelected()) {
1221                s = CLOSED_NCE;
1222            }
1223        }
1224        return s;
1225    }
1226
1227    /**
1228     * Check for valid macro, return number if valid, -1 if not.
1229     *
1230     * @param s  string of macro number
1231     * @return mN - int of macro number or -1 if invalid
1232     */
1233    private int validMacro(String s) {
1234        int mN;
1235        try {
1236            mN = Integer.parseInt(s);
1237        } catch (NumberFormatException e) {
1238            return -1;
1239        }
1240        if (mN < 0 || mN > maxNumMacros) {
1241            return -1;
1242        } else {
1243            return mN;
1244        }
1245    }
1246
1247    /**
1248     * writes bytes of NCE macro memory
1249     *
1250     */
1251    private boolean writeMacroMemory(int macroNum, byte[] b) {
1252        if (isUsb) {
1253            setUsbCabMemoryPointer(tc.csm.getMacroAddr(), (macroNum * macroSize));
1254            if (!waitNce()) {
1255                return false;
1256            }
1257            for (int i = 0; i < macroSize; i++) {
1258                writeUsbMemory1(b[i]);
1259                if (!waitNce()) {
1260                    return false;
1261                }
1262            }
1263        } else {
1264            int nceMemoryAddr = (macroNum * macroSize) + memBase;
1265            byte[] buf = new byte[16];            
1266            for (int i = 0; i < 16; i++) {
1267                buf[i] = b[i];
1268            }
1269            writeSerialMemoryN(nceMemoryAddr, buf);
1270            if (!waitNce()) {
1271                return false;
1272            }
1273            buf = new byte[4];
1274            for (int i = 0; i < 4; i++) {
1275                buf[i] = b[i + 16];
1276            }
1277            writeSerialMemory4(nceMemoryAddr + 16, buf);
1278            if (!waitNce()) {
1279                return false;
1280            }
1281        }
1282        return true;
1283    }
1284
1285    // puts the thread to sleep while we wait for the read CS memory to complete
1286    private boolean waitNce() {
1287        int count = 100;
1288        if (log.isDebugEnabled()) {
1289            log.debug("Going to sleep");
1290        }
1291        while (waiting > 0) {
1292            synchronized (this) {
1293                try {
1294                    wait(100);
1295                } catch (InterruptedException e) {
1296                    //nothing to see here, move along
1297                }
1298            }
1299            count--;
1300            if (count < 0) {
1301                macroReply.setText("Error");
1302                return false;
1303            }
1304        }
1305        if (log.isDebugEnabled()) {
1306            log.debug("awake!");
1307        }
1308        return true;
1309    }
1310
1311    @Override
1312    public void message(NceMessage m) {
1313    } // ignore replies
1314
1315    // public void replyOrig(NceReply r) {
1316    //  // Macro command
1317    //  if (replyLen == NceMessage.REPLY_1) {
1318    //   // Looking for proper response
1319    //   int recChar = r.getElement(0);
1320    //   if (recChar == '!')
1321    //    macroReply.setText(Bundle.getMessage("okay"));
1322    //   if (recChar == '0')
1323    //    macroReply.setText(Bundle.getMessage("macroEmpty"));
1324    //  }
1325    //  // Macro memory read
1326    //  if (replyLen == NceMessage.REPLY_16) {
1327    //   // NCE macros consists of 20 bytes on serial, 16 on USB
1328    //   // so either 4 or 5 reads
1329    //   if (secondRead) {
1330    //    // Second memory read for accessories 9 and 10
1331    //    secondRead = false;
1332    //    loadAccy9and10(r);
1333    //
1334    //   } else {
1335    //    int recChar = r.getElement(0);
1336    //    recChar = recChar << 8;
1337    //    recChar = recChar + r.getElement(1);
1338    //    if (recChar == 0) {
1339    //     if (checkBoxEmpty.isSelected()) {
1340    //      if (macroCount > 0) {
1341    //       macroSearchInc = false;
1342    //       macroSearchDec = false;
1343    //      }
1344    //     }
1345    //     // Macro is empty so init the accessory fields
1346    //     macroReply.setText(Bundle.getMessage("macroEmpty"));
1347    //     initAccyFields();
1348    //     macroValid = true;
1349    //    } else {
1350    //     if (checkBoxEmpty.isSelected() == false) {
1351    //      if (macroCount > 0) {
1352    //       macroSearchInc = false;
1353    //       macroSearchDec = false;
1354    //      }
1355    //     }
1356    //     macroReply.setText(Bundle.getMessage("macroFound"));
1357    //     secondRead = loadAccy1to8(r);
1358    //     macroValid = true;
1359    //    }
1360    //    // if we're searching, don't bother with second read
1361    //    if (macroSearchInc || macroSearchDec)
1362    //     secondRead = false;
1363    //    // Do we need to read more CS memory?
1364    //    if (secondRead)
1365    //     // force second read of CS memory
1366    //     getMacro2ndHalf(macroNum);
1367    //    // when searching, have we read all of the possible
1368    //    // macros?
1369    //    macroCount++;
1370    //    if (macroCount > maxNumMacros) {
1371    //     macroSearchInc = false;
1372    //     macroSearchDec = false;
1373    //    }
1374    //    if (macroSearchInc) {
1375    //     macroNum++;
1376    //     if (macroNum == maxNumMacros + 1)
1377    //      macroNum = 0;
1378    //    }
1379    //    if (macroSearchDec) {
1380    //     macroNum--;
1381    //     if (macroNum == -1)
1382    //      macroNum = maxNumMacros;
1383    //    }
1384    //    if (macroSearchInc || macroSearchDec) {
1385    //     macroTextField.setText(Integer.toString(macroNum));
1386    //     macroNum = getMacro();
1387    //    }
1388    //   }
1389    //  }
1390    // }
1391    /**
1392     * response from read
1393     *
1394     */
1395    int recChar = 0;
1396    int[] recChars = new int[16];
1397
1398    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification = "Thread wait from main transfer loop")
1399    @Override
1400    public void reply(NceReply r) {
1401        if (log.isDebugEnabled()) {
1402            log.debug("Receive character");
1403        }
1404        if (waiting <= 0) {
1405            log.error("unexpected response. Len: {} code: {}", r.getNumDataElements(), r.getElement(0));
1406            return;
1407        }
1408        waiting--;
1409        if (r.getNumDataElements() != replyLen) {
1410            macroReply.setText("error");
1411            return;
1412        }
1413        for (int i = 0; i < replyLen; i++) {
1414            recChars[i] = r.getElement(i);
1415        }
1416        // wake up thread
1417        synchronized (this) {
1418            notify();
1419        }
1420    }
1421
1422    // USB set cab memory pointer
1423    private void setUsbCabMemoryPointer(int cab, int offset) {
1424        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1425        waiting++;
1426        byte[] bl = NceBinaryCommand.usbMemoryPointer(cab, offset);
1427        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1428        tc.sendNceMessage(m, this);
1429    }
1430
1431    // USB Read N bytes of NCE cab memory
1432    private void readUsbMemoryN(int num) {
1433        switch (num) {
1434            case 1:
1435                replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1436                break;
1437            case 2:
1438                replyLen = NceMessage.REPLY_2; // Expect 2 byte response
1439                break;
1440            case 4:
1441                replyLen = NceMessage.REPLY_4; // Expect 4 byte response
1442                break;
1443            default:
1444                log.error("Invalid usb read byte count");
1445                return;
1446        }
1447        waiting++;
1448        byte[] bl = NceBinaryCommand.usbMemoryRead((byte) num);
1449        NceMessage m = NceMessage.createBinaryMessage(tc, bl, replyLen);
1450        tc.sendNceMessage(m, this);
1451    }
1452
1453    /**
1454     * USB Write 1 byte of NCE memory
1455     *
1456     * @param value  byte being written
1457     */
1458    private void writeUsbMemory1(int value) {
1459        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1460        waiting++;
1461        byte[] bl = NceBinaryCommand.usbMemoryWrite1((byte) value);
1462        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1463        tc.sendNceMessage(m, this);
1464    }
1465
1466    // Reads 16 bytes of NCE memory
1467    private void readSerialMemory16(int nceCabAddr) {
1468        replyLen = NceMessage.REPLY_16; // Expect 16 byte response
1469        waiting++;
1470        byte[] bl = NceBinaryCommand.accMemoryRead(nceCabAddr);
1471        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_16);
1472        tc.sendNceMessage(m, this);
1473    }
1474
1475    // Write N bytes of NCE memory
1476    private void writeSerialMemoryN(int nceMacroAddr, byte[] b) {
1477        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1478        waiting++;
1479        byte[] bl = NceBinaryCommand.accMemoryWriteN(nceMacroAddr, b);
1480        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1481        tc.sendNceMessage(m, this);
1482    }
1483
1484    // Write 4 bytes of NCE memory
1485    private void writeSerialMemory4(int nceMacroAddr, byte[] b) {
1486        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1487        waiting++;
1488        byte[] bl = NceBinaryCommand.accMemoryWrite4(nceMacroAddr, b);
1489        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1490        tc.sendNceMessage(m, this);
1491    }
1492
1493    private void addAccyRow(JComponent col1, JComponent col2, JComponent col3, JComponent col4, JComponent col5,
1494            int row) {
1495        addItem(col1, 0, row);
1496        addItem(col2, 1, row);
1497        addItem(col3, 2, row);
1498        addItem(col4, 3, row);
1499        addItem(col5, 4, row);
1500    }
1501
1502    private void addItem(JComponent c, int x, int y) {
1503        GridBagConstraints gc = new GridBagConstraints();
1504        gc.gridx = x;
1505        gc.gridy = y;
1506        gc.weightx = 100.0;
1507        gc.weighty = 100.0;
1508        add(c, gc);
1509    }
1510
1511    private void addButtonAction(JButton b) {
1512        b.addActionListener(new java.awt.event.ActionListener() {
1513            @Override
1514            public void actionPerformed(java.awt.event.ActionEvent e) {
1515                buttonActionPerformed(e);
1516            }
1517        });
1518    }
1519
1520    private void addButtonCmdAction(JButton b) {
1521        b.addActionListener(new java.awt.event.ActionListener() {
1522            @Override
1523            public void actionPerformed(java.awt.event.ActionEvent e) {
1524                buttonActionCmdPerformed(e);
1525            }
1526        });
1527    }
1528
1529    private void addButtonDelAction(JButton b) {
1530        b.addActionListener(new java.awt.event.ActionListener() {
1531            @Override
1532            public void actionPerformed(java.awt.event.ActionEvent e) {
1533                buttonActionDeletePerformed(e);
1534            }
1535        });
1536    }
1537
1538    private void addCheckBoxAction(JCheckBox cb) {
1539        cb.addActionListener(new java.awt.event.ActionListener() {
1540            @Override
1541            public void actionPerformed(java.awt.event.ActionEvent e) {
1542                checkBoxActionPerformed(e);
1543            }
1544        });
1545    }
1546
1547    //  initialize accessories 1 to 10
1548    private void initAccyFields() {
1549        initAccyRow(1, num1, textAccy1, accyTextField1, cmdButton1, deleteButton1);
1550        initAccyRow(2, num2, textAccy2, accyTextField2, cmdButton2, deleteButton2);
1551        initAccyRow(3, num3, textAccy3, accyTextField3, cmdButton3, deleteButton3);
1552        initAccyRow(4, num4, textAccy4, accyTextField4, cmdButton4, deleteButton4);
1553        initAccyRow(5, num5, textAccy5, accyTextField5, cmdButton5, deleteButton5);
1554        initAccyRow(6, num6, textAccy6, accyTextField6, cmdButton6, deleteButton6);
1555        initAccyRow(7, num7, textAccy7, accyTextField7, cmdButton7, deleteButton7);
1556        initAccyRow(8, num8, textAccy8, accyTextField8, cmdButton8, deleteButton8);
1557        initAccyRow(9, num9, textAccy9, accyTextField9, cmdButton9, deleteButton9);
1558        initAccyRow(10, num10, textAccy10, accyTextField10, cmdButton10, deleteButton10);
1559    }
1560
1561    private void initAccyRow(int row, JLabel num, JLabel textAccy, JTextField accyTextField, JButton cmdButton,
1562            JButton deleteButton) {
1563        num.setText(Integer.toString(row));
1564        num.setVisible(true);
1565        textAccy.setText(EMPTY);
1566        textAccy.setVisible(true);
1567        cmdButton.setText(QUESTION);
1568        cmdButton.setVisible(true);
1569        cmdButton.setToolTipText(Bundle.getMessage("toolTipSetCommand"));
1570        deleteButton.setText(DELETE);
1571        deleteButton.setVisible(true);
1572        deleteButton.setEnabled(false);
1573        deleteButton.setToolTipText(Bundle.getMessage("toolTipRemoveAcessory"));
1574        accyTextField.setText("");
1575        accyTextField.setToolTipText(Bundle.getMessage("EnterAccessoryNumber"));
1576        accyTextField.setMaximumSize(new Dimension(accyTextField
1577                .getMaximumSize().width,
1578                accyTextField.getPreferredSize().height));
1579        if (row == 10) {
1580            initAccyRow10();
1581        }
1582    }
1583
1584    private void initAccyRow10() {
1585        cmdButton10.setVisible(true);
1586        deleteButton10.setText(LINK);
1587        deleteButton10.setEnabled(true);
1588        deleteButton10.setToolTipText(Bundle.getMessage("toolTipLink"));
1589        accyTextField10.setToolTipText(Bundle.getMessage("toolTip10"));
1590    }
1591
1592    /**
1593     * Nested class to create one of these using old-style defaults
1594     */
1595    static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction {
1596
1597        public Default() {
1598            super("Open NCE Macro Editor",
1599                    new jmri.util.swing.sdi.JmriJFrameInterface(),
1600                    NceMacroEditPanel.class.getName(),
1601                    jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class));
1602        }
1603    }
1604
1605    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceMacroEditPanel.class);
1606
1607}