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