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("MacroLabel"));
111    JLabel textReply = new JLabel(Bundle.getMessage("ReplyLabel"));
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) { // Error user input incorrect
558                    JOptionPane.showMessageDialog(this,
559                            Bundle.getMessage("GetMacroNumber"),
560                            Bundle.getMessage("NceMacro"),
561                            JOptionPane.ERROR_MESSAGE);
562                    return;
563                }
564                int linkMacro = validMacro(accyTextField10.getText());
565                if (linkMacro == -1) {
566                    JOptionPane.showMessageDialog(this,
567                            Bundle.getMessage("EnterMacroNumberLine10"),
568                            Bundle.getMessage("NceMacro"),
569                            JOptionPane.ERROR_MESSAGE);
570                    return;
571                }
572                // success, link a macro
573                setSaveButton(true);
574                textAccy10.setText(LINK);
575                cmdButton10.setVisible(false);
576                deleteButton10.setText(DELETE);
577                deleteButton10.setToolTipText(Bundle.getMessage("toolTipRemoveMacroLink"));
578
579                // user wants to delete a accessory address or a link
580            } else {
581                updateAccyDelPerformed(accyTextField10, cmdButton10, textAccy10,
582                        deleteButton10);
583                initAccyRow10();
584            }
585        }
586    }
587
588    public void checkBoxActionPerformed(java.awt.event.ActionEvent ae) {
589        processMemory(true, false, macroNum, null);
590    }
591
592    // gets the user supplied macro number
593    private int getMacro() {
594        // Set all fields to default and build from there
595        initAccyFields();
596        //        if (firstTime) {
597        //            try {
598        //                Thread.sleep(firstTimeSleep); // wait for panel to display
599        //            } catch (InterruptedException e) {
600        //                log.error("Thread unexpectedly interrupted", e);
601        //            }
602        //        }
603        //
604        //        firstTime = false;
605        String m = macroTextField.getText();
606        if (m.isEmpty()) {
607            m = "0";
608        }
609        int mN = validMacro(m);
610        if (mN < 0) {
611            macroReply.setText(Bundle.getMessage("error"));
612            JOptionPane.showMessageDialog(this,
613                    Bundle.getMessage("EnterMacroNumber"),
614                    Bundle.getMessage("NceMacro"),
615                    JOptionPane.ERROR_MESSAGE);
616            macroValid = false;
617            return mN;
618        }
619        if (macroSearchInc || macroSearchDec) {
620            macroReply.setText(Bundle.getMessage("searching"));
621            if (macroSearchInc) {
622                mN++;
623                if (mN >= maxNumMacros + 1) {
624                    mN = 0;
625                }
626            }
627            if (macroSearchDec) {
628                mN--;
629                if (mN <= -1) {
630                    mN = maxNumMacros;
631                }
632            }
633        } else {
634            macroReply.setText(Bundle.getMessage("waiting"));
635        }
636
637        return mN;
638    }
639
640    /**
641     * Writes all bytes to NCE CS memory as long as there are no user input
642     * errors
643     *
644     */
645    private boolean saveMacro() {
646        //        if (firstTime) {
647        //            try {
648        //                Thread.sleep(firstTimeSleep); // wait for panel to display
649        //            } catch (InterruptedException e) {
650        //                log.error("Thread unexpectedly interrupted", e);
651        //            }
652        //        }
653        //
654        //        firstTime = false;
655        byte[] macroAccy = new byte[macroSize]; // NCE Macro data
656        int index = 0;
657        int accyNum = 0;
658        // test the inputs, convert from text
659        accyNum = getAccyRow(macroAccy, index, textAccy1, accyTextField1, cmdButton1);
660        if (accyNum < 0) //error
661        {
662            return false;
663        }
664        if (accyNum > 0) {
665            index += 2;
666        }
667        accyNum = getAccyRow(macroAccy, index, textAccy2, accyTextField2, cmdButton2);
668        if (accyNum < 0) {
669            return false;
670        }
671        if (accyNum > 0) {
672            index += 2;
673        }
674        accyNum = getAccyRow(macroAccy, index, textAccy3, accyTextField3, cmdButton3);
675        if (accyNum < 0) {
676            return false;
677        }
678        if (accyNum > 0) {
679            index += 2;
680        }
681        accyNum = getAccyRow(macroAccy, index, textAccy4, accyTextField4, cmdButton4);
682        if (accyNum < 0) {
683            return false;
684        }
685        if (accyNum > 0) {
686            index += 2;
687        }
688        accyNum = getAccyRow(macroAccy, index, textAccy5, accyTextField5, cmdButton5);
689        if (accyNum < 0) {
690            return false;
691        }
692        if (accyNum > 0) {
693            index += 2;
694        }
695        accyNum = getAccyRow(macroAccy, index, textAccy6, accyTextField6, cmdButton6);
696        if (accyNum < 0) {
697            return false;
698        }
699        if (accyNum > 0) {
700            index += 2;
701        }
702        accyNum = getAccyRow(macroAccy, index, textAccy7, accyTextField7, cmdButton7);
703        if (accyNum < 0) {
704            return false;
705        }
706        if (accyNum > 0) {
707            index += 2;
708        }
709        if (!isUsb) {
710            accyNum = getAccyRow(macroAccy, index, textAccy8, accyTextField8, cmdButton8);
711            if (accyNum < 0) {
712                return false;
713            }
714            if (accyNum > 0) {
715                index += 2;
716            }
717            accyNum = getAccyRow(macroAccy, index, textAccy9, accyTextField9, cmdButton9);
718            if (accyNum < 0) {
719                return false;
720            }
721            if (accyNum > 0) {
722                index += 2;
723            }
724        }
725        accyNum = getAccyRow(macroAccy, index, textAccy10, accyTextField10, cmdButton10);
726        if (accyNum < 0) {
727            JOptionPane.showMessageDialog(this,
728                    Bundle.getMessage("EnterMacroNumberLine10"),
729                    Bundle.getMessage("NceMacro"),
730                    JOptionPane.ERROR_MESSAGE);
731            return false;
732        }
733
734        processMemory(false, true, macroNum, macroAccy);
735        return true;
736    }
737
738    private void processMemory(boolean doRead, boolean doWrite, int macroId, byte[] macroArray) {
739        final byte[] macroData = new byte[macroSize];
740        macroValid = false;
741        readRequested = false;
742        writeRequested = false;
743
744        if (doRead) {
745            readRequested = true;
746        }
747        if (doWrite) {
748            writeRequested = true;
749            System.arraycopy(macroArray, 0, macroData, 0, macroSize);
750        }
751
752        // Set up a separate thread to access CS memory
753        if (nceMemoryThread != null && nceMemoryThread.isAlive()) {
754            return; // thread is already running
755        }
756        nceMemoryThread = new Thread(new Runnable() {
757            @Override
758            public void run() {
759                if (readRequested) {
760                    macroNum = macroId;
761                    int macroCount = 0;
762                    while (true) {
763                        int entriesRead = readMacroMemory(macroNum);
764                        macroTextField.setText(Integer.toString(macroNum));
765                        if (entriesRead == 0) {
766                            // Macro is empty so init the accessory fields
767                            initAccyFields();
768                            macroReply.setText(Bundle.getMessage("macroEmpty"));
769                            if (checkBoxEmpty.isSelected()) {
770                                macroValid = true;
771                                macroSearchInc = false;
772                                macroSearchDec = false;
773                                break;
774                            }
775                        } else if (entriesRead < 0) {
776                            macroReply.setText(Bundle.getMessage("error"));
777                            macroValid = false;
778                            macroSearchInc = false;
779                            macroSearchDec = false;
780                            break;
781                        } else {
782                            macroReply.setText(Bundle.getMessage("macroFound"));
783                            if (!checkBoxEmpty.isSelected()) {
784                                macroSearchInc = false;
785                                macroSearchDec = false;
786                                macroValid = true;
787                                break;
788                            }
789                        }
790                        if ((macroSearchInc || macroSearchDec) && !macroValid) {
791                            macroCount++;
792                            if (macroCount > maxNumMacros) {
793                                macroSearchInc = false;
794                                macroSearchDec = false;
795                                break;
796                            }
797                            macroNum = getMacro();
798                        }
799                        if (!(macroSearchInc || macroSearchDec)) {
800                            // we were doing a get, not a search
801                            macroValid = true;
802                            break;
803                        }
804                    }
805                }
806                if (writeRequested) {
807                    writeMacroMemory(macroId, macroData);
808                }
809            }
810        });
811        nceMemoryThread.setName(Bundle.getMessage("ThreadTitle"));
812        nceMemoryThread.setPriority(Thread.MIN_PRIORITY);
813        nceMemoryThread.start();
814    }
815
816    // Reads 16/20 bytes of NCE macro memory
817    private int readMacroMemory(int mN) {
818        int entriesRead = 0;
819        if (isUsb) {
820            setUsbCabMemoryPointer(CabMemoryUsb.CAB_NUM_MACRO, (mN * macroSize));
821            if (!waitNce()) {
822                return -1;
823            }
824            // 1st word of macro
825            readUsbMemoryN(2);
826            if (!waitNce()) {
827                return -1;
828            }
829            int accyAddr = getMacroAccyAdr(recChars);
830            if (accyAddr <= 0) {
831                return entriesRead;
832            }
833            entriesRead++;
834            setAccy(accyAddr, getAccyCmd(recChars), textAccy1, accyTextField1, cmdButton1,
835                    deleteButton1);
836            // 2nd word of macro
837            readUsbMemoryN(2);
838            if (!waitNce()) {
839                return -1;
840            }
841            accyAddr = getMacroAccyAdr(recChars);
842            if (accyAddr <= 0) {
843                return entriesRead;
844            }
845            entriesRead++;
846            setAccy(accyAddr, getAccyCmd(recChars), textAccy2, accyTextField2, cmdButton2,
847                    deleteButton2);
848            // 3rd word of macro
849            readUsbMemoryN(2);
850            if (!waitNce()) {
851                return -1;
852            }
853            accyAddr = getMacroAccyAdr(recChars);
854            if (accyAddr <= 0) {
855                return entriesRead;
856            }
857            entriesRead++;
858            setAccy(accyAddr, getAccyCmd(recChars), textAccy3, accyTextField3, cmdButton3,
859                    deleteButton3);
860            // 4th word of macro
861            readUsbMemoryN(2);
862            if (!waitNce()) {
863                return -1;
864            }
865            accyAddr = getMacroAccyAdr(recChars);
866            if (accyAddr <= 0) {
867                return entriesRead;
868            }
869            entriesRead++;
870            setAccy(accyAddr, getAccyCmd(recChars), textAccy4, accyTextField4, cmdButton4,
871                    deleteButton4);
872            // 5th word of macro
873            readUsbMemoryN(2);
874            if (!waitNce()) {
875                return -1;
876            }
877            accyAddr = getMacroAccyAdr(recChars);
878            if (accyAddr <= 0) {
879                return entriesRead;
880            }
881            entriesRead++;
882            setAccy(accyAddr, getAccyCmd(recChars), textAccy5, accyTextField5, cmdButton5,
883                    deleteButton5);
884            // 6th word of macro
885            readUsbMemoryN(2);
886            if (!waitNce()) {
887                return -1;
888            }
889            accyAddr = getMacroAccyAdr(recChars);
890            if (accyAddr <= 0) {
891                return entriesRead;
892            }
893            entriesRead++;
894            setAccy(accyAddr, getAccyCmd(recChars), textAccy6, accyTextField6, cmdButton6,
895                    deleteButton6);
896            // 7th word of macro
897            readUsbMemoryN(2);
898            if (!waitNce()) {
899                return -1;
900            }
901            accyAddr = getMacroAccyAdr(recChars);
902            if (accyAddr <= 0) {
903                return entriesRead;
904            }
905            entriesRead++;
906            setAccy(accyAddr, getAccyCmd(recChars), textAccy7, accyTextField7, cmdButton7,
907                    deleteButton7);
908            // 8th word of macro
909            readUsbMemoryN(2);
910            if (!waitNce()) {
911                return -1;
912            }
913            accyAddr = getMacroAccyAdr(recChars);
914            if (accyAddr <= 0) {
915                return entriesRead;
916            }
917            entriesRead++;
918            setAccy(accyAddr, getAccyCmd(recChars), textAccy8, accyTextField8, cmdButton8,
919                    deleteButton8);
920            return entriesRead;
921        } else {
922            int memPtr = CabMemorySerial.CS_MACRO_MEM + (mN * macroSize);
923            int readPtr = 0;
924            int[] workBuf = new int[2];
925            // 1st word of macro
926            readSerialMemory16(memPtr);
927            if (!waitNce()) {
928                return -1;
929            }
930            workBuf[0] = recChars[readPtr++];
931            workBuf[1] = recChars[readPtr++];
932            int accyAddr = getMacroAccyAdr(workBuf);
933            if (accyAddr <= 0) {
934                return entriesRead;
935            }
936            entriesRead++;
937            setAccy(accyAddr, getAccyCmd(workBuf), textAccy1, accyTextField1, cmdButton1,
938                    deleteButton1);
939            // 2nd word of macro
940            workBuf[0] = recChars[readPtr++];
941            workBuf[1] = recChars[readPtr++];
942            accyAddr = getMacroAccyAdr(workBuf);
943            if (accyAddr <= 0) {
944                return entriesRead;
945            }
946            entriesRead++;
947            setAccy(accyAddr, getAccyCmd(workBuf), textAccy2, accyTextField2, cmdButton2,
948                    deleteButton2);
949            // 3rd word of macro
950            workBuf[0] = recChars[readPtr++];
951            workBuf[1] = recChars[readPtr++];
952            accyAddr = getMacroAccyAdr(workBuf);
953            if (accyAddr <= 0) {
954                return entriesRead;
955            }
956            entriesRead++;
957            setAccy(accyAddr, getAccyCmd(workBuf), textAccy3, accyTextField3, cmdButton3,
958                    deleteButton3);
959            // 4th word of macro
960            workBuf[0] = recChars[readPtr++];
961            workBuf[1] = recChars[readPtr++];
962            accyAddr = getMacroAccyAdr(workBuf);
963            if (accyAddr <= 0) {
964                return entriesRead;
965            }
966            entriesRead++;
967            setAccy(accyAddr, getAccyCmd(workBuf), textAccy4, accyTextField4, cmdButton4,
968                    deleteButton4);
969            // 5th word of macro
970            workBuf[0] = recChars[readPtr++];
971            workBuf[1] = recChars[readPtr++];
972            accyAddr = getMacroAccyAdr(workBuf);
973            if (accyAddr <= 0) {
974                return entriesRead;
975            }
976            entriesRead++;
977            setAccy(accyAddr, getAccyCmd(workBuf), textAccy5, accyTextField5, cmdButton5,
978                    deleteButton5);
979            // 6th word of macro
980            workBuf[0] = recChars[readPtr++];
981            workBuf[1] = recChars[readPtr++];
982            accyAddr = getMacroAccyAdr(workBuf);
983            if (accyAddr <= 0) {
984                return entriesRead;
985            }
986            entriesRead++;
987            setAccy(accyAddr, getAccyCmd(workBuf), textAccy6, accyTextField6, cmdButton6,
988                    deleteButton6);
989            // 7th word of macro
990            workBuf[0] = recChars[readPtr++];
991            workBuf[1] = recChars[readPtr++];
992            accyAddr = getMacroAccyAdr(workBuf);
993            if (accyAddr <= 0) {
994                return entriesRead;
995            }
996            entriesRead++;
997            setAccy(accyAddr, getAccyCmd(workBuf), textAccy7, accyTextField7, cmdButton7,
998                    deleteButton7);
999            // 8th word of macro
1000            workBuf[0] = recChars[readPtr++];
1001            workBuf[1] = recChars[readPtr++];
1002            accyAddr = getMacroAccyAdr(workBuf);
1003            if (accyAddr <= 0) {
1004                return entriesRead;
1005            }
1006            entriesRead++;
1007            setAccy(accyAddr, getAccyCmd(workBuf), textAccy8, accyTextField8, cmdButton8,
1008                    deleteButton8);
1009            // 9th word of macro
1010            memPtr += 16;
1011            readPtr = 0;
1012            readSerialMemory16(memPtr);
1013            if (!waitNce()) {
1014                return -1;
1015            }
1016            workBuf[0] = recChars[readPtr++];
1017            workBuf[1] = recChars[readPtr++];
1018            accyAddr = getMacroAccyAdr(workBuf);
1019            if (accyAddr <= 0) {
1020                return entriesRead;
1021            }
1022            entriesRead++;
1023            setAccy(accyAddr, getAccyCmd(workBuf), textAccy9, accyTextField9, cmdButton9,
1024                    deleteButton9);
1025            // 10th word of macro
1026            workBuf[0] = recChars[readPtr++];
1027            workBuf[1] = recChars[readPtr++];
1028            accyAddr = getMacroAccyAdr(workBuf);
1029            if (accyAddr <= 0) {
1030                return entriesRead;
1031            }
1032            entriesRead++;
1033            setAccy(accyAddr, getAccyCmd(workBuf), textAccy10, accyTextField10, cmdButton10,
1034                    deleteButton10);
1035            return entriesRead;
1036        }
1037    }
1038
1039    // Updates the accessory line when the user hits the command button
1040    private void updateAccyCmdPerformed(JTextField accyTextField, JButton cmdButton, JLabel textAccy,
1041            JButton deleteButton) {
1042        if (!macroValid) { // Error user input incorrect
1043            JOptionPane.showMessageDialog(this,
1044                    Bundle.getMessage("GetMacroNumber"), Bundle.getMessage("NceMacro"),
1045                    JOptionPane.ERROR_MESSAGE);
1046        } else {
1047            String accyText = accyTextField.getText();
1048            int accyNum = 0;
1049            try {
1050                accyNum = Integer.parseInt(accyText);
1051            } catch (NumberFormatException e) {
1052                accyNum = -1;
1053            }
1054
1055            if (accyNum < 1 || accyNum > 2044) {
1056                JOptionPane.showMessageDialog(this,
1057                        Bundle.getMessage("EnterAccessoryNumber"), Bundle.getMessage("NceMacroAddress"),
1058                        JOptionPane.ERROR_MESSAGE);
1059                return;
1060            }
1061
1062            String accyCmd = cmdButton.getText();
1063
1064            // Use JMRI or NCE turnout terminology
1065            if (checkBoxNce.isSelected()) {
1066
1067                if (!accyCmd.equals(THROWN_NCE)) {
1068                    cmdButton.setText(THROWN_NCE);
1069                }
1070                if (!accyCmd.equals(CLOSED_NCE)) {
1071                    cmdButton.setText(CLOSED_NCE);
1072                }
1073
1074            } else {
1075
1076                if (!accyCmd.equals(THROWN)) {
1077                    cmdButton.setText(THROWN);
1078                }
1079                if (!accyCmd.equals(CLOSED)) {
1080                    cmdButton.setText(CLOSED);
1081                }
1082            }
1083
1084            setSaveButton(true);
1085            textAccy.setText(ACCESSORY);
1086            deleteButton.setText(DELETE);
1087            deleteButton.setToolTipText(Bundle.getMessage("toolTipRemoveAcessory"));
1088            deleteButton.setEnabled(true);
1089        }
1090    }
1091
1092    // Delete an accessory from the macro
1093    private void updateAccyDelPerformed(JTextField accyTextField, JButton cmdButton, JLabel textAccy,
1094            JButton deleteButton) {
1095        setSaveButton(true);
1096        textAccy.setText(EMPTY);
1097        accyTextField.setText("");
1098        cmdButton.setText(QUESTION);
1099        deleteButton.setEnabled(false);
1100    }
1101
1102    private int getAccyRow(byte[] b, int i, JLabel textAccy, JTextField accyTextField, JButton cmdButton) {
1103        int accyNum = 0;
1104        if (textAccy.getText().equals(ACCESSORY)) {
1105            accyNum = getAccyNum(accyTextField.getText());
1106            if (accyNum < 0) {
1107                return accyNum;
1108            }
1109            accyNum = accyNum + 3; // adjust for NCE's way of encoding
1110            int upperByte = (accyNum & 0xFF);
1111            upperByte = (upperByte >> 2) + 0x80;
1112            b[i] = (byte) upperByte;
1113            int lowerByteH = (((accyNum ^ 0x0700) & 0x0700) >> 4);// 3 MSB 1s complement
1114            int lowerByteL = ((accyNum & 0x3) << 1); // 2 LSB
1115            int lowerByte = (lowerByteH + lowerByteL + 0x88);
1116            // adjust for turnout command
1117            if (cmdButton.getText().equals(CLOSED) || cmdButton.getText().equals(CLOSED_NCE)) {
1118                lowerByte++;
1119            }
1120            b[i + 1] = (byte) (lowerByte);
1121        }
1122        if (textAccy.getText().equals(LINK)) {
1123            int macroLink = validMacro(accyTextField.getText());
1124            if (macroLink < 0) {
1125                return macroLink;
1126            }
1127            b[i] = (byte) 0xFF; // NCE macro link command
1128            b[i + 1] = (byte) macroLink; // link macro number
1129        }
1130        return accyNum;
1131    }
1132
1133    private int getAccyNum(String accyText) {
1134        int accyNum = 0;
1135        try {
1136            accyNum = Integer.parseInt(accyText);
1137        } catch (NumberFormatException e) {
1138            accyNum = -1;
1139        }
1140        if (accyNum < 1 || accyNum > 2044) {
1141            JOptionPane.showMessageDialog(this,
1142                    Bundle.getMessage("EnterAccessoryNumber"), Bundle.getMessage("NceMacroAddress"),
1143                    JOptionPane.ERROR_MESSAGE);
1144            accyNum = -1;
1145        }
1146        return accyNum;
1147    }
1148
1149    // display save button
1150    private void setSaveButton(boolean display) {
1151        macroModified = display;
1152        saveButton.setEnabled(display);
1153        if (!isUsb) {
1154            backUpButton.setEnabled(!display);
1155            restoreButton.setEnabled(!display);
1156        }
1157    }
1158
1159    // Convert NCE macro hex data to accessory address
1160    // returns 0 if macro address is empty
1161    // returns a negative address if link address
1162    // & loads accessory 10 with link macro
1163    private int getMacroAccyAdr(int[] b) {
1164        int accyAddrL = b[0];
1165        int accyAddr = 0;
1166        // check for null
1167        if ((accyAddrL == 0) && (b[1] == 0)) {
1168            return accyAddr;
1169        }
1170        // Check to see if link address
1171        if ((accyAddrL & 0xFF) == 0xFF) {
1172            // Link address
1173            accyAddr = b[1];
1174            linkAccessory10(accyAddr & 0xFF);
1175            accyAddr = -accyAddr;
1176
1177            // must be an accessory address
1178        } else {
1179            accyAddrL = (accyAddrL << 2) & 0xFC; // accessory address bits 7 - 2
1180            int accyLSB = b[1];
1181            accyLSB = (accyLSB & 0x06) >> 1; // accessory address bits 1 - 0
1182            int accyAddrH = b[1];
1183            accyAddrH = (0x70 - (accyAddrH & 0x70)) << 4; // One's completent of MSB of address 10 - 8
1184            // & multiply by 16
1185            accyAddr = accyAddrH + accyAddrL + accyLSB - 3; // adjust for the way NCE displays addresses
1186        }
1187        return accyAddr;
1188    }
1189
1190    // whenever link macro is found, put it in the last location
1191    // this makes it easier for the user to edit the macro
1192    private void linkAccessory10(int accyAddr) {
1193        textAccy10.setText(LINK);
1194        accyTextField10.setText(Integer.toString(accyAddr));
1195        cmdButton10.setVisible(false);
1196        deleteButton10.setText(DELETE);
1197        deleteButton10.setToolTipText(Bundle.getMessage("toolTipRemoveMacroLink"));
1198    }
1199
1200    // loads one row with a macro's accessory address and command
1201    private void setAccy(int accyAddr, String accyCmd, JLabel textAccy,
1202            JTextField accyTextField, JButton cmdButton, JButton deleteButton) {
1203        textAccy.setText(ACCESSORY);
1204        accyTextField.setText(Integer.toString(accyAddr));
1205        deleteButton.setEnabled(true);
1206        cmdButton.setText(accyCmd);
1207    }
1208
1209    // returns the accessory command
1210    private String getAccyCmd(int[] b) {
1211        int accyCmd = b[1];
1212        String s = THROWN;
1213        if (checkBoxNce.isSelected()) {
1214            s = THROWN_NCE;
1215        }
1216        accyCmd = accyCmd & 0x01;
1217        if (accyCmd == 0) {
1218            return s;
1219        } else {
1220            s = CLOSED;
1221            if (checkBoxNce.isSelected()) {
1222                s = CLOSED_NCE;
1223            }
1224        }
1225        return s;
1226    }
1227
1228    /**
1229     * Check for valid macro, return number if valid, -1 if not.
1230     *
1231     * @param s  string of macro number
1232     * @return mN - int of macro number or -1 if invalid
1233     */
1234    private int validMacro(String s) {
1235        int mN;
1236        try {
1237            mN = Integer.parseInt(s);
1238        } catch (NumberFormatException e) {
1239            return -1;
1240        }
1241        if (mN < 0 || mN > maxNumMacros) {
1242            return -1;
1243        } else {
1244            return mN;
1245        }
1246    }
1247
1248    /**
1249     * writes bytes of NCE macro memory
1250     *
1251     */
1252    private boolean writeMacroMemory(int macroNum, byte[] b) {
1253        if (isUsb) {
1254            setUsbCabMemoryPointer(CabMemoryUsb.CAB_NUM_MACRO, (macroNum * macroSize));
1255            if (!waitNce()) {
1256                return false;
1257            }
1258            for (int i = 0; i < macroSize; i++) {
1259                writeUsbMemory1(b[i]);
1260                if (!waitNce()) {
1261                    return false;
1262                }
1263            }
1264        } else {
1265            int nceMemoryAddr = (macroNum * macroSize) + memBase;
1266            byte[] buf = new byte[16];            
1267            for (int i = 0; i < 16; i++) {
1268                buf[i] = b[i];
1269            }
1270            writeSerialMemoryN(nceMemoryAddr, buf);
1271            if (!waitNce()) {
1272                return false;
1273            }
1274            buf = new byte[4];
1275            for (int i = 0; i < 4; i++) {
1276                buf[i] = b[i + 16];
1277            }
1278            writeSerialMemory4(nceMemoryAddr + 16, buf);
1279            if (!waitNce()) {
1280                return false;
1281            }
1282        }
1283        return true;
1284    }
1285
1286    // puts the thread to sleep while we wait for the read CS memory to complete
1287    private boolean waitNce() {
1288        int count = 100;
1289        if (log.isDebugEnabled()) {
1290            log.debug("Going to sleep");
1291        }
1292        while (waiting > 0) {
1293            synchronized (this) {
1294                try {
1295                    wait(100);
1296                } catch (InterruptedException e) {
1297                    //nothing to see here, move along
1298                }
1299            }
1300            count--;
1301            if (count < 0) {
1302                macroReply.setText("Error");
1303                return false;
1304            }
1305        }
1306        if (log.isDebugEnabled()) {
1307            log.debug("awake!");
1308        }
1309        return true;
1310    }
1311
1312    @Override
1313    public void message(NceMessage m) {
1314    } // ignore replies
1315
1316    // public void replyOrig(NceReply r) {
1317    //  // Macro command
1318    //  if (replyLen == NceMessage.REPLY_1) {
1319    //   // Looking for proper response
1320    //   int recChar = r.getElement(0);
1321    //   if (recChar == '!')
1322    //    macroReply.setText(Bundle.getMessage("okay"));
1323    //   if (recChar == '0')
1324    //    macroReply.setText(Bundle.getMessage("macroEmpty"));
1325    //  }
1326    //  // Macro memory read
1327    //  if (replyLen == NceMessage.REPLY_16) {
1328    //   // NCE macros consists of 20 bytes on serial, 16 on USB
1329    //   // so either 4 or 5 reads
1330    //   if (secondRead) {
1331    //    // Second memory read for accessories 9 and 10
1332    //    secondRead = false;
1333    //    loadAccy9and10(r);
1334    //
1335    //   } else {
1336    //    int recChar = r.getElement(0);
1337    //    recChar = recChar << 8;
1338    //    recChar = recChar + r.getElement(1);
1339    //    if (recChar == 0) {
1340    //     if (checkBoxEmpty.isSelected()) {
1341    //      if (macroCount > 0) {
1342    //       macroSearchInc = false;
1343    //       macroSearchDec = false;
1344    //      }
1345    //     }
1346    //     // Macro is empty so init the accessory fields
1347    //     macroReply.setText(Bundle.getMessage("macroEmpty"));
1348    //     initAccyFields();
1349    //     macroValid = true;
1350    //    } else {
1351    //     if (checkBoxEmpty.isSelected() == false) {
1352    //      if (macroCount > 0) {
1353    //       macroSearchInc = false;
1354    //       macroSearchDec = false;
1355    //      }
1356    //     }
1357    //     macroReply.setText(Bundle.getMessage("macroFound"));
1358    //     secondRead = loadAccy1to8(r);
1359    //     macroValid = true;
1360    //    }
1361    //    // if we're searching, don't bother with second read
1362    //    if (macroSearchInc || macroSearchDec)
1363    //     secondRead = false;
1364    //    // Do we need to read more CS memory?
1365    //    if (secondRead)
1366    //     // force second read of CS memory
1367    //     getMacro2ndHalf(macroNum);
1368    //    // when searching, have we read all of the possible
1369    //    // macros?
1370    //    macroCount++;
1371    //    if (macroCount > maxNumMacros) {
1372    //     macroSearchInc = false;
1373    //     macroSearchDec = false;
1374    //    }
1375    //    if (macroSearchInc) {
1376    //     macroNum++;
1377    //     if (macroNum == maxNumMacros + 1)
1378    //      macroNum = 0;
1379    //    }
1380    //    if (macroSearchDec) {
1381    //     macroNum--;
1382    //     if (macroNum == -1)
1383    //      macroNum = maxNumMacros;
1384    //    }
1385    //    if (macroSearchInc || macroSearchDec) {
1386    //     macroTextField.setText(Integer.toString(macroNum));
1387    //     macroNum = getMacro();
1388    //    }
1389    //   }
1390    //  }
1391    // }
1392    /**
1393     * response from read
1394     *
1395     */
1396    int recChar = 0;
1397    int[] recChars = new int[16];
1398
1399    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY", justification = "Thread wait from main transfer loop")
1400    @Override
1401    public void reply(NceReply r) {
1402        if (log.isDebugEnabled()) {
1403            log.debug("Receive character");
1404        }
1405        if (waiting <= 0) {
1406            log.error("unexpected response. Len: {} code: {}", r.getNumDataElements(), r.getElement(0));
1407            return;
1408        }
1409        waiting--;
1410        if (r.getNumDataElements() != replyLen) {
1411            macroReply.setText("error");
1412            return;
1413        }
1414        for (int i = 0; i < replyLen; i++) {
1415            recChars[i] = r.getElement(i);
1416        }
1417        // wake up thread
1418        synchronized (this) {
1419            notify();
1420        }
1421    }
1422
1423    // USB set cab memory pointer
1424    private void setUsbCabMemoryPointer(int cab, int offset) {
1425        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1426        waiting++;
1427        byte[] bl = NceBinaryCommand.usbMemoryPointer(cab, offset);
1428        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1429        tc.sendNceMessage(m, this);
1430    }
1431
1432    // USB Read N bytes of NCE cab memory
1433    private void readUsbMemoryN(int num) {
1434        switch (num) {
1435            case 1:
1436                replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1437                break;
1438            case 2:
1439                replyLen = NceMessage.REPLY_2; // Expect 2 byte response
1440                break;
1441            case 4:
1442                replyLen = NceMessage.REPLY_4; // Expect 4 byte response
1443                break;
1444            default:
1445                log.error("Invalid usb read byte count");
1446                return;
1447        }
1448        waiting++;
1449        byte[] bl = NceBinaryCommand.usbMemoryRead((byte) num);
1450        NceMessage m = NceMessage.createBinaryMessage(tc, bl, replyLen);
1451        tc.sendNceMessage(m, this);
1452    }
1453
1454    /**
1455     * USB Write 1 byte of NCE memory
1456     *
1457     * @param value  byte being written
1458     */
1459    private void writeUsbMemory1(int value) {
1460        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1461        waiting++;
1462        byte[] bl = NceBinaryCommand.usbMemoryWrite1((byte) value);
1463        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1464        tc.sendNceMessage(m, this);
1465    }
1466
1467    // Reads 16 bytes of NCE memory
1468    private void readSerialMemory16(int nceCabAddr) {
1469        replyLen = NceMessage.REPLY_16; // Expect 16 byte response
1470        waiting++;
1471        byte[] bl = NceBinaryCommand.accMemoryRead(nceCabAddr);
1472        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_16);
1473        tc.sendNceMessage(m, this);
1474    }
1475
1476    // Write N bytes of NCE memory
1477    private void writeSerialMemoryN(int nceMacroAddr, byte[] b) {
1478        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1479        waiting++;
1480        byte[] bl = NceBinaryCommand.accMemoryWriteN(nceMacroAddr, b);
1481        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1482        tc.sendNceMessage(m, this);
1483    }
1484
1485    // Write 4 bytes of NCE memory
1486    private void writeSerialMemory4(int nceMacroAddr, byte[] b) {
1487        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
1488        waiting++;
1489        byte[] bl = NceBinaryCommand.accMemoryWrite4(nceMacroAddr, b);
1490        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
1491        tc.sendNceMessage(m, this);
1492    }
1493
1494    private void addAccyRow(JComponent col1, JComponent col2, JComponent col3, JComponent col4, JComponent col5,
1495            int row) {
1496        addItem(col1, 0, row);
1497        addItem(col2, 1, row);
1498        addItem(col3, 2, row);
1499        addItem(col4, 3, row);
1500        addItem(col5, 4, row);
1501    }
1502
1503    private void addItem(JComponent c, int x, int y) {
1504        GridBagConstraints gc = new GridBagConstraints();
1505        gc.gridx = x;
1506        gc.gridy = y;
1507        gc.weightx = 100.0;
1508        gc.weighty = 100.0;
1509        add(c, gc);
1510    }
1511
1512    private void addButtonAction(JButton b) {
1513        b.addActionListener(new java.awt.event.ActionListener() {
1514            @Override
1515            public void actionPerformed(java.awt.event.ActionEvent e) {
1516                buttonActionPerformed(e);
1517            }
1518        });
1519    }
1520
1521    private void addButtonCmdAction(JButton b) {
1522        b.addActionListener(new java.awt.event.ActionListener() {
1523            @Override
1524            public void actionPerformed(java.awt.event.ActionEvent e) {
1525                buttonActionCmdPerformed(e);
1526            }
1527        });
1528    }
1529
1530    private void addButtonDelAction(JButton b) {
1531        b.addActionListener(new java.awt.event.ActionListener() {
1532            @Override
1533            public void actionPerformed(java.awt.event.ActionEvent e) {
1534                buttonActionDeletePerformed(e);
1535            }
1536        });
1537    }
1538
1539    private void addCheckBoxAction(JCheckBox cb) {
1540        cb.addActionListener(new java.awt.event.ActionListener() {
1541            @Override
1542            public void actionPerformed(java.awt.event.ActionEvent e) {
1543                checkBoxActionPerformed(e);
1544            }
1545        });
1546    }
1547
1548    //  initialize accessories 1 to 10
1549    private void initAccyFields() {
1550        initAccyRow(1, num1, textAccy1, accyTextField1, cmdButton1, deleteButton1);
1551        initAccyRow(2, num2, textAccy2, accyTextField2, cmdButton2, deleteButton2);
1552        initAccyRow(3, num3, textAccy3, accyTextField3, cmdButton3, deleteButton3);
1553        initAccyRow(4, num4, textAccy4, accyTextField4, cmdButton4, deleteButton4);
1554        initAccyRow(5, num5, textAccy5, accyTextField5, cmdButton5, deleteButton5);
1555        initAccyRow(6, num6, textAccy6, accyTextField6, cmdButton6, deleteButton6);
1556        initAccyRow(7, num7, textAccy7, accyTextField7, cmdButton7, deleteButton7);
1557        initAccyRow(8, num8, textAccy8, accyTextField8, cmdButton8, deleteButton8);
1558        initAccyRow(9, num9, textAccy9, accyTextField9, cmdButton9, deleteButton9);
1559        initAccyRow(10, num10, textAccy10, accyTextField10, cmdButton10, deleteButton10);
1560    }
1561
1562    private void initAccyRow(int row, JLabel num, JLabel textAccy, JTextField accyTextField, JButton cmdButton,
1563            JButton deleteButton) {
1564        num.setText(Integer.toString(row));
1565        num.setVisible(true);
1566        textAccy.setText(EMPTY);
1567        textAccy.setVisible(true);
1568        cmdButton.setText(QUESTION);
1569        cmdButton.setVisible(true);
1570        cmdButton.setToolTipText(Bundle.getMessage("toolTipSetCommand"));
1571        deleteButton.setText(DELETE);
1572        deleteButton.setVisible(true);
1573        deleteButton.setEnabled(false);
1574        deleteButton.setToolTipText(Bundle.getMessage("toolTipRemoveAcessory"));
1575        accyTextField.setText("");
1576        accyTextField.setToolTipText(Bundle.getMessage("EnterAccessoryNumber"));
1577        accyTextField.setMaximumSize(new Dimension(accyTextField
1578                .getMaximumSize().width,
1579                accyTextField.getPreferredSize().height));
1580        if (row == 10) {
1581            initAccyRow10();
1582        }
1583    }
1584
1585    private void initAccyRow10() {
1586        cmdButton10.setVisible(true);
1587        deleteButton10.setText(LINK);
1588        deleteButton10.setEnabled(true);
1589        deleteButton10.setToolTipText(Bundle.getMessage("toolTipLink"));
1590        accyTextField10.setToolTipText(Bundle.getMessage("toolTip10"));
1591    }
1592
1593    /**
1594     * Nested class to create one of these using old-style defaults
1595     */
1596    static public class Default extends jmri.jmrix.nce.swing.NceNamedPaneAction {
1597
1598        public Default() {
1599            super("Open NCE Macro Editor",
1600                    new jmri.util.swing.sdi.JmriJFrameInterface(),
1601                    NceMacroEditPanel.class.getName(),
1602                    jmri.InstanceManager.getDefault(NceSystemConnectionMemo.class));
1603        }
1604    }
1605
1606    private final static Logger log = LoggerFactory.getLogger(NceMacroEditPanel.class);
1607
1608}