001package jmri.jmrix.cmri.serial.nodeconfigmanager;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.io.IOException;
006import java.util.ArrayList;
007
008import javax.swing.*;
009import javax.swing.border.Border;
010import javax.swing.border.TitledBorder;
011import javax.swing.table.*;
012
013import jmri.jmrix.cmri.CMRISystemConnectionMemo;
014import jmri.jmrix.cmri.serial.SerialNode;
015import jmri.jmrix.cmri.serial.nodeiolist.NodeIOListFrame;
016import jmri.util.davidflanagan.CompatibleHardcopyWriter;
017import jmri.util.swing.JmriJOptionPane;
018
019/**
020 *
021 * Frames for a table view to manage CMRInet node configuration management. c2
022 * Created a table view for node configuration operations. Add, Edit, Delete and
023 * Update are executed from the NodeTableManager. This class was derived from
024 * the NodeConfig class.
025 *
026 * @author Bob Jacobsen Copyright (C) 2004
027 * @author Dave Duchamp Copyright (C) 2004
028 * @author Chuck Catania Copyright (C) 2013, 2014, 2015, 2016, 2017, 2018
029 */
030public class NodeConfigManagerFrame extends jmri.util.JmriJFrame {
031
032    ArrayList<SerialNode> cmriNode = new ArrayList<>();
033    public int numConfigNodes = 0;
034
035    public int numBits = 48;  // number of bits in assignment table
036    public int numInputBits = 24;  // number of input bits for selected node
037    public int numOutputBits = 48; // number of output bits for selected node
038
039    protected int selectedNodeAddr = -1;
040    protected int selectedTableRow = -1;
041    protected boolean doingPrint = false;
042
043    // node select pane items
044    JLabel nodeLabel = new JLabel(Bundle.getMessage("NodeBoxLabel") + " ");
045
046    // node table pane items
047    protected JPanel nodeTablePanel = null;
048    protected Border inputBorder = BorderFactory.createEtchedBorder();
049
050    protected NodeTableModel nodeTableModel = null;
051    protected JTable nodeTable = null;
052
053    // button pane items
054    JButton addButton = new JButton(Bundle.getMessage("ButtonAdd"));
055    JButton doneButton = new JButton(Bundle.getMessage("ButtonDone"));
056    JButton printButton = new JButton(Bundle.getMessage("PrintButtonText"));
057
058    NodeConfigManagerFrame curFrame;
059
060    protected JTextField nodeAddrField = new JTextField(3);
061    protected JLabel nodeAddrStatic = new JLabel("000");
062    protected JComboBox<String> nodeTypeBox;
063    protected JTextField receiveDelayField = new JTextField(3);
064    protected JTextField nodeDescription = new JTextField(32);
065    protected JTextField pulseWidthField = new JTextField(4);
066    protected JComboBox<String> cardSizeBox;
067    protected JComboBox<String> cardSize8Box;
068    protected JLabel cardSizeText = new JLabel("   " + Bundle.getMessage("LabelCardSize"));
069    protected JLabel onBoardBytesText = new JLabel(Bundle.getMessage("LabelOnBoardBytes") + " 3 Input Bytes, 6 Output Bytes");
070
071    protected JButton addNodeButton = new JButton(Bundle.getMessage("ButtonAdd"));
072    protected JButton editNodeButton = new JButton(Bundle.getMessage("ButtonEdit"));
073    protected JButton deleteNodeButton = new JButton(Bundle.getMessage("ButtonDelete"));
074    protected JButton doneNodeButton = new JButton(Bundle.getMessage("ButtonDone"));
075    protected JButton updateNodeButton = new JButton(Bundle.getMessage("ButtonUpdate"));
076    protected JButton cancelNodeButton = new JButton(Bundle.getMessage("ButtonCancel"));
077
078    protected JLabel statusText1 = new JLabel();
079    protected JLabel statusText2 = new JLabel();
080    protected JLabel statusText3 = new JLabel();
081
082    protected JPanel panel2 = new JPanel();
083    protected JPanel panel2a = new JPanel();
084    protected JPanel panel2b = new JPanel();
085    protected JPanel panel2c = new JPanel();  //c2 IOX config
086    protected JPanel panelnodeDescBox = new JPanel();   //c2 node desctipion box
087    protected JPanel panelnodeDesc = new JPanel();  //c2 node description
088    protected JPanel panelnetOpt = new JPanel();  //c2 CMRInet options
089    protected JPanel panelnetOptBox = new JPanel();  //c2 CMRInet options frame
090    protected JPanel panelnodeOpt = new JPanel();   //c2 node options
091
092    protected boolean changedNode = false;  // true if a node was changed, deleted, or added
093    protected boolean editMode = false;     // true if in edit mode
094
095    protected SerialNode curNode = null;    // Serial Node being edited
096    protected int nodeAddress = 0;          // Node address
097    protected int nodeType = SerialNode.SMINI; // Node type - default SMINI
098    protected int bitsPerCard = 24;         // number of bits per card
099    protected int receiveDelay = 0;         // transmission delay
100    protected int pulseWidth = 500;         // pulse width for turnout control (milliseconds)
101    protected int num2LSearchLights = 0;    // number of 2-lead oscillating searchlights
102
103    protected int numCards = 0;             //set by consistency check routine
104    protected int cpNodeOnboard = 4;        //Number of fixed bytes(cards) for a cpNode
105    protected int osNodeOnboard = 8;        //Number of fixed bytes(cards) for a osNode
106
107    protected boolean errorInStatus1 = false;
108    protected boolean errorInStatus2 = false;
109
110    protected String editStatus1 = Bundle.getMessage("NotesEdit1");
111    protected String editStatus2 = Bundle.getMessage("NotesEdit2");
112    protected String editStatus3 = Bundle.getMessage("NotesEdit3");
113    protected String addStatus1 = Bundle.getMessage("NotesAdd1");
114    protected String addStatus2 = Bundle.getMessage("NotesAdd2");
115    protected String addStatus3 = Bundle.getMessage("NotesAdd3");
116    protected String delStatus1 = Bundle.getMessage("NotesDel1");
117    protected String delStatus2 = Bundle.getMessage("NotesDel2");
118    protected String delStatus3 = Bundle.getMessage("NotesDel3");
119
120    protected String nodeDescText = "";
121    protected int deleteNodeAddress = 0;
122
123    HandlerClass nodeOptHandler = new HandlerClass();
124
125    // --------------------------
126    // CMRInet Options CheckBoxes
127    // --------------------------
128    protected JCheckBox cbx_cmrinetopt_AUTOPOLL = new JCheckBox(Bundle.getMessage("cmrinetOpt0"), true);
129    protected JCheckBox cbx_cmrinetopt_USECMRIX = new JCheckBox(Bundle.getMessage("cmrinetOpt1"));
130    protected JCheckBox cbx_cmrinetopt_USEBCC = new JCheckBox(Bundle.getMessage("cmrinetOpt2"));
131    protected JCheckBox cbx_cmrinetopt_BIT8 = new JCheckBox(Bundle.getMessage("cmrinetOpt8"));
132    protected JCheckBox cbx_cmrinetopt_BIT15 = new JCheckBox(Bundle.getMessage("cmrinetOpt15"));
133
134    // -------------------------
135    // cpNode Options CheckBoxes
136    // -------------------------
137    protected JCheckBox cbx_cpnodeopt_SENDEOT = new JCheckBox(Bundle.getMessage("cpnodeOpt0"));
138    protected JCheckBox cbx_cpnodeopt_BIT1 = new JCheckBox(Bundle.getMessage("cpnodeOpt1"));
139    protected JCheckBox cbx_cpnodeopt_BIT2 = new JCheckBox(Bundle.getMessage("cpnodeOpt2"));
140    protected JCheckBox cbx_cpnodeopt_BIT8 = new JCheckBox(Bundle.getMessage("cpnodeOpt8"));
141    protected JCheckBox cbx_cpnodeopt_BIT15 = new JCheckBox(Bundle.getMessage("cpnodeOpt15"));
142
143    private CMRISystemConnectionMemo _memo = null;
144
145    /**
146     * Constructor method.
147     * @param memo system connection.
148     */
149    public NodeConfigManagerFrame(CMRISystemConnectionMemo memo) {
150        super();
151        _memo = memo;
152        curFrame = this;
153
154        // Clear information arrays
155        for (int i = 0; i < 64; i++) {
156            cardType[i] = Bundle.getMessage("CardTypeNone"); // NOI18N
157        }
158        for (int i = 0; i < 48; i++) {
159            searchlightBits[i] = false;
160            firstSearchlight[i] = false;
161        }
162        // addHelpMenu("package.jmri.jmrix.cmri.serial.nodeconfigmanager.NodeConfigManagerFrame", true); // NOI18N duplicate, see initComponents
163    }
164
165    /**
166     * {@inheritDoc}
167     */
168    @Override
169    public void initComponents() {
170        // set the frame's initial state
171        setTitle(Bundle.getMessage("WindowTitle") + Bundle.getMessage("WindowConnectionMemo")+_memo.getUserName());  // NOI18N
172        setSize(500, 150);
173
174        Container contentPane = getContentPane();
175        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
176
177        // load the SerialNode data
178        initializeNodes();
179
180        // Set up the assignment panel
181        nodeTablePanel = new JPanel();
182        nodeTablePanel.setLayout(new BoxLayout(nodeTablePanel, BoxLayout.Y_AXIS));
183
184        nodeTableModel = new NodeTableModel();
185        nodeTable = new JTable(nodeTableModel);
186
187        nodeTable.setShowGrid(true);
188        nodeTable.setGridColor(Color.black);
189        nodeTable.setRowSelectionAllowed(false);
190        nodeTable.setFont(new Font("Arial", Font.PLAIN, 14));
191        nodeTable.setRowHeight(30);
192
193        nodeTable.getTableHeader().setReorderingAllowed(false);
194        nodeTable.setPreferredScrollableViewportSize(new java.awt.Dimension(300, 350));
195        TableColumnModel assignmentColumnModel = nodeTable.getColumnModel();
196
197        DefaultTableCellRenderer dtcen = new DefaultTableCellRenderer();
198        dtcen.setHorizontalAlignment(SwingConstants.CENTER);
199        DefaultTableCellRenderer dtrt = new DefaultTableCellRenderer();
200        dtrt.setHorizontalAlignment(SwingConstants.RIGHT);
201
202        TableCellRenderer rendererFromHeader = nodeTable.getTableHeader().getDefaultRenderer();
203        JLabel headerLabel = (JLabel) rendererFromHeader;
204        headerLabel.setHorizontalAlignment(JLabel.CENTER);
205        headerLabel.setBackground(Color.LIGHT_GRAY);
206
207        TableColumn nodenumColumn = assignmentColumnModel.getColumn(NodeTableModel.NODENUM_COLUMN);
208        nodenumColumn.setMinWidth(40);
209        nodenumColumn.setMaxWidth(80);
210        nodenumColumn.setCellRenderer(dtcen);
211        nodenumColumn.setResizable(false);
212
213        TableColumn nodetypeColumn = assignmentColumnModel.getColumn(NodeTableModel.NODETYPE_COLUMN);
214        nodetypeColumn.setMinWidth(40);
215        nodetypeColumn.setMaxWidth(80);
216        nodetypeColumn.setCellRenderer(dtcen);
217        nodetypeColumn.setResizable(false);
218
219        TableColumn numbitsColumn = assignmentColumnModel.getColumn(NodeTableModel.NUMBITS_COLUMN);
220        numbitsColumn.setMinWidth(40);
221        numbitsColumn.setMaxWidth(120);
222        numbitsColumn.setCellRenderer(dtcen);
223        numbitsColumn.setResizable(false);
224
225        TableColumn numinputsColumn = assignmentColumnModel.getColumn(NodeTableModel.NUMINCARDS_COLUMN);
226        numinputsColumn.setMinWidth(40);
227        numinputsColumn.setMaxWidth(80);
228        numinputsColumn.setCellRenderer(dtcen);
229        numinputsColumn.setResizable(false);
230
231        TableColumn numoutputsColumn = assignmentColumnModel.getColumn(NodeTableModel.NUMOUTCARDS_COLUMN);
232        numoutputsColumn.setMinWidth(10);
233        numoutputsColumn.setMaxWidth(100);
234        numoutputsColumn.setCellRenderer(dtcen);
235        numoutputsColumn.setResizable(false);
236
237        TableColumn numinbytesColumn = assignmentColumnModel.getColumn(NodeTableModel.NUMINBYTES_COLUMN);
238        numinbytesColumn.setMinWidth(10);
239        numinbytesColumn.setMaxWidth(80);
240        numinbytesColumn.setCellRenderer(dtcen);
241        numinbytesColumn.setResizable(false);
242
243        TableColumn numoutbytesColumn = assignmentColumnModel.getColumn(NodeTableModel.NUMOUTBYTES_COLUMN);
244        numoutbytesColumn.setMinWidth(10);
245        numoutbytesColumn.setMaxWidth(100);
246        numoutbytesColumn.setCellRenderer(dtcen);
247        numoutbytesColumn.setResizable(false);
248
249        TableColumn selectColumn = assignmentColumnModel.getColumn(NodeTableModel.SELECT_COLUMN);
250        JComboBox<String> comboBox = new JComboBox<>();
251        comboBox.addItem(Bundle.getMessage("SelectSelect"));
252        comboBox.addItem(Bundle.getMessage("SelectEdit"));
253        comboBox.addItem(Bundle.getMessage("SelectInfo"));
254        comboBox.addItem(Bundle.getMessage("SelectDelete"));
255        selectColumn.setCellEditor(new DefaultCellEditor(comboBox));
256
257        selectColumn.setMinWidth(40);
258        selectColumn.setMaxWidth(90);
259        selectColumn.setCellRenderer(dtcen);
260        selectColumn.setResizable(false);
261
262        TableColumn nodedescColumn = assignmentColumnModel.getColumn(NodeTableModel.NODEDESC_COLUMN);
263        nodedescColumn.setMinWidth(40);
264        nodedescColumn.setMaxWidth(350);
265        nodedescColumn.setResizable(true);
266        JScrollPane nodeTableScrollPane = new JScrollPane(nodeTable);
267
268        Border inputBorderTitled = BorderFactory.createTitledBorder(inputBorder,
269                " ",
270                TitledBorder.LEFT, TitledBorder.ABOVE_TOP);
271        nodeTablePanel.add(nodeTableScrollPane, BorderLayout.CENTER);
272        nodeTablePanel.setBorder(inputBorderTitled);
273        setPreferredSize(new Dimension(950, 550));
274
275        nodeTable.setAutoCreateRowSorter(true);
276        nodeTable.getRowSorter().toggleSortOrder(NodeTableModel.NODENUM_COLUMN);
277
278        contentPane.add(nodeTablePanel);
279
280        // Setup main window buttons
281        JPanel panel3 = new JPanel();
282        panel3.setLayout(new BoxLayout(panel3, FlowLayout.RIGHT));
283        panel3.setPreferredSize(new Dimension(950, 50));
284
285        // Set up Add button
286        addButton.setVisible(true);
287        addButton.setToolTipText(Bundle.getMessage("AddButtonTip"));
288        addButton.addActionListener((java.awt.event.ActionEvent e) -> {
289            addButtonActionPerformed(e);
290        });
291        panel3.add(addButton);
292
293        // Set up Print button
294        printButton.setVisible(true);
295        printButton.setToolTipText(Bundle.getMessage("PrintButtonTip"));
296        if (numConfigNodes > 0) {
297            printButton.addActionListener((java.awt.event.ActionEvent e) -> {
298                printButtonActionPerformed(e);
299            });
300        }
301        panel3.add(printButton);
302
303        // Set up Done button
304        doneButton.setVisible(true);
305        doneButton.setToolTipText(Bundle.getMessage("DoneButtonTip"));
306        doneButton.addActionListener((java.awt.event.ActionEvent e) -> {
307            doneButtonActionPerformed();
308        });
309        panel3.add(doneButton);
310
311        contentPane.add(panel3);
312        addHelpMenu("package.jmri.jmrix.cmri.serial.nodeconfigmanager.NodeConfigManagerFrame", true);
313        // pack for display
314        pack();
315        nodeTablePanel.setVisible(true);
316    }
317
318    /**
319     * Get the selected node address from the node table.
320     * @return selected node ID.
321     */
322    public int getSelectedNodeAddr() {
323        return (Integer) nodeTable.getValueAt(nodeTable.getSelectedRow(), 0);
324    }
325
326    /**
327     * Handle the done button click.
328     */
329    public void doneButtonActionPerformed() {
330        changedNode = false;
331        setVisible(false);
332        dispose();
333    }
334
335    public void addButtonActionPerformed(ActionEvent e) {
336        NodeConfigManagerFrame f = new NodeConfigManagerFrame(_memo);
337        try {
338            f.initNodeConfigWindow();
339        } catch (Exception ex) {
340            log.info("addButtonActionPerformed Exception-C2: {}", ex.toString());
341        }
342        f.nodeTableModel = nodeTableModel;
343        f.initNodeVariables();
344        f.buttonSet_ADD();
345        f.setLocation(100, 100);
346        f.setVisible(true);
347    }
348
349    /**
350     * Print.
351     *
352     * @param e the triggering event
353     */
354    public void printButtonActionPerformed(java.awt.event.ActionEvent e) {
355        int[] colWidth = new int[10];
356        // initialize column widths
357        TableColumnModel nodeTableColumnModel = nodeTable.getColumnModel();
358        colWidth[0] = nodeTableColumnModel.getColumn(NodeTableModel.NODENUM_COLUMN).getWidth();
359        colWidth[1] = nodeTableColumnModel.getColumn(NodeTableModel.NODETYPE_COLUMN).getWidth();
360        colWidth[2] = nodeTableColumnModel.getColumn(NodeTableModel.NUMBITS_COLUMN).getWidth();
361        colWidth[3] = nodeTableColumnModel.getColumn(NodeTableModel.NUMINCARDS_COLUMN).getWidth();
362        colWidth[4] = nodeTableColumnModel.getColumn(NodeTableModel.NUMOUTCARDS_COLUMN).getWidth();
363        colWidth[5] = nodeTableColumnModel.getColumn(NodeTableModel.SELECT_COLUMN).getWidth();
364        colWidth[6] = nodeTableColumnModel.getColumn(NodeTableModel.NUMBITS_COLUMN).getWidth();
365        colWidth[7] = nodeTableColumnModel.getColumn(NodeTableModel.NUMBITS_COLUMN).getWidth();
366        colWidth[8] = nodeTableColumnModel.getColumn(NodeTableModel.NUMBITS_COLUMN).getWidth();
367        colWidth[9] = nodeTableColumnModel.getColumn(NodeTableModel.NUMBITS_COLUMN).getWidth();
368
369        // set up a page title
370        String head = "CMRInet Node Table";
371        // initialize a printer writer
372        CompatibleHardcopyWriter writer = null;
373        try {
374            writer = new CompatibleHardcopyWriter(curFrame, head, 10, .8, .5, .5, .5, false);
375        } catch (CompatibleHardcopyWriter.PrintCanceledException ex) {
376            return;
377        }
378        writer.increaseLineSpacing(20);
379        // print the assignments
380        (nodeTableModel).printTable(writer, colWidth);
381    }
382
383    /**
384     * Edit node table selection.
385     */
386    public void editActionSelected() {
387        selectedNodeAddr = getSelectedNodeAddr();
388
389        NodeConfigManagerFrame f = new NodeConfigManagerFrame(_memo);
390        f.nodeTableModel = nodeTableModel;
391        f.selectedTableRow = nodeTable.convertRowIndexToModel(nodeTable.getSelectedRow());
392
393        try {
394            f.initNodeConfigWindow();
395            f.editNodeButtonActionPerformed(selectedNodeAddr);
396        } catch (Exception ex) {
397            log.info("editActionSelected", ex);
398
399        }
400        f.setLocation(200, 200);
401        f.buttonSet_EDIT();
402        f.setVisible(true);
403    }
404
405    /**
406     * Handle the delete button click
407     */
408    public void deleteActionSelected() {
409        selectedNodeAddr = getSelectedNodeAddr();
410
411        NodeConfigManagerFrame f = new NodeConfigManagerFrame(_memo);
412        f.nodeTableModel = nodeTableModel;
413        f.selectedTableRow = nodeTable.convertRowIndexToModel(nodeTable.getSelectedRow());
414        try {
415            f.initNodeConfigWindow();
416            f.deleteNodeButtonActionPerformed(selectedNodeAddr);
417        } catch (Exception ex) {
418            log.info("deleteActionSelected", ex);
419
420        }
421        f.setLocation(200, 200);
422        f.buttonSet_DELETE();
423        f.setVisible(true);
424    }
425
426    /**
427     * Handle info action.
428     */
429    public void infoActionSelected() {
430        selectedNodeAddr = getSelectedNodeAddr();
431
432        NodeIOListFrame f = new NodeIOListFrame(_memo);
433        try {
434            f.initComponents();
435            f.displayNodeIOBits(selectedNodeAddr);
436        } catch (Exception ex) {
437            log.info("infoActionSelected Exception-C2: {}", ex.toString());
438
439        }
440        f.setLocation(100, 100);
441        f.setVisible(true);
442    }
443
444    /**
445     * Method to initialize configured nodes and sets up the node select combo
446     * box
447     */
448    public void initializeNodes() {
449        // get all configured nodes
450        if (!cmriNode.isEmpty()) {
451            cmriNode.clear();
452        }
453
454        SerialNode node = (SerialNode) _memo.getTrafficController().getNode(0);
455        int index = 1;
456        while (node != null) {
457            cmriNode.add(node);
458            node = (SerialNode) _memo.getTrafficController().getNode(index);
459            index++;
460        }
461
462        numConfigNodes = cmriNode.size();
463    }
464
465    /**
466     * Set up table for displaying bit assignments
467     */
468    public class NodeTableModel extends AbstractTableModel {
469
470        @Override
471        public String getColumnName(int c) {
472            return nodeTableColumnsNames[c];
473        }
474
475        @Override
476        public Class<?> getColumnClass(int c) {
477            switch (c) {
478                case NODENUM_COLUMN:
479                    return Integer.class;
480
481                case NODETYPE_COLUMN:
482                    return String.class;
483
484                case NUMBITS_COLUMN:
485                case NUMINCARDS_COLUMN:
486                case NUMOUTCARDS_COLUMN:
487                case NUMINBYTES_COLUMN:
488                case NUMOUTBYTES_COLUMN:
489                    return Integer.class;
490
491                case SELECT_COLUMN:
492                    return String.class;
493
494                case NODEDESC_COLUMN:
495                default:
496                    return String.class;
497            }
498        }
499
500        @Override
501        public boolean isCellEditable(int r, int c) {
502            if (c == SELECT_COLUMN) {
503                return true;
504            } else {
505                return false;
506            }
507        }
508
509        @Override
510        public int getColumnCount() {
511            return NUM_COLUMNS;
512        }
513
514        @Override
515        public int getRowCount() {
516            return cmriNode.size();
517        }
518
519        public void removeRow(int row) {
520            cmriNode.remove(row);
521            numConfigNodes = cmriNode.size();
522            fireTableRowsDeleted(row, row);
523        }
524
525        public void addRow(SerialNode newNode) {
526            cmriNode.add(newNode);
527            numConfigNodes = cmriNode.size();
528            fireTableDataChanged();
529        }
530
531        public void changeRow(int row, SerialNode aNode) {
532            cmriNode.set(row, aNode);
533            fireTableDataChanged();
534        }
535
536        @Override
537        public void setValueAt(Object value, int row, int col) {
538            if (col == SELECT_COLUMN) {
539                if (Bundle.getMessage("SelectEdit").equals(value)) {
540                    editActionSelected();
541                } else if (Bundle.getMessage("SelectInfo").equals(value)) {
542                    infoActionSelected();
543                } else if (Bundle.getMessage("SelectDelete").equals(value)) {
544                    deleteActionSelected();
545                }
546            } else {
547                log.info("setValueAt Row{} value {}", row, value);
548            }
549            fireTableDataChanged();
550        }
551
552        @Override
553        public Object getValueAt(int r, int c) {
554            switch (c) {
555                case NODENUM_COLUMN:
556                    if (!doingPrint) {
557                        return cmriNode.get(r).getNodeAddress();
558                    } else {
559                        return Integer.toString(cmriNode.get(r).getNodeAddress());
560                    }
561
562                case NODETYPE_COLUMN:
563                    return "  " + nodeTableTypes[cmriNode.get(r).getNodeType()];
564
565                case NUMBITS_COLUMN:
566                    return Integer.toString(cmriNode.get(r).getNumBitsPerCard());
567
568                case NUMINCARDS_COLUMN:
569                    if (cmriNode.get(r).getNodeType() == SerialNode.SMINI) {
570                        return Integer.toString(cmriNode.get(r).numInputCards() * 3);
571                    } else {
572                        return Integer.toString(cmriNode.get(r).numInputCards());
573                    }
574
575                case NUMOUTCARDS_COLUMN:
576                    if (cmriNode.get(r).getNodeType() == SerialNode.SMINI) {
577                        return Integer.toString(cmriNode.get(r).numOutputCards() * 3);
578                    } else {
579                        return Integer.toString(cmriNode.get(r).numOutputCards());
580                    }
581
582                case NUMINBYTES_COLUMN:
583                    return Integer.toString((cmriNode.get(r).getNumBitsPerCard()) * cmriNode.get(r).numInputCards());
584
585                case NUMOUTBYTES_COLUMN:
586                    return Integer.toString((cmriNode.get(r).getNumBitsPerCard()) * cmriNode.get(r).numOutputCards());
587
588                case SELECT_COLUMN:
589
590                    return "Select";
591                case NODEDESC_COLUMN:
592
593                    return " " + cmriNode.get(r).getcmriNodeDesc();
594                default:
595                    return "";
596            }
597        }
598
599        public static final int NODENUM_COLUMN = 0;
600        public static final int NODETYPE_COLUMN = 1;
601        public static final int NUMBITS_COLUMN = 2;
602        public static final int NUMINCARDS_COLUMN = 3;
603        public static final int NUMOUTCARDS_COLUMN = 4;
604        public static final int NUMINBYTES_COLUMN = 5;
605        public static final int NUMOUTBYTES_COLUMN = 6;
606        public static final int SELECT_COLUMN = 7;
607        public static final int NODEDESC_COLUMN = 8;
608        public static final int NUM_COLUMNS = NODEDESC_COLUMN + 1;
609
610//        private String[] pollStatus = {"ERROR", "IDLE", "POLLING", "TIMEOUT", "SLOW POLL"};
611        /**
612         * Method to print or print preview the assignment table. Printed in
613         * proportionately sized columns across the page with headings and
614         * vertical lines between each column. Data is word wrapped within a
615         * column. Can only handle 4 columns of data as strings. Adapted from
616         * routines in BeanTableDataModel.java by Bob Jacobsen and Dennis Miller
617         * @param w CompatibleHardcopyWriter instance.
618         * @param colWidth column width array.
619        */
620        public void printTable(CompatibleHardcopyWriter w, int colWidth[]) {
621            // determine the column sizes - proportionately sized, with space between for lines
622            int[] columnSize = new int[NUM_COLUMNS];
623            int charPerLine = w.getCharactersPerLine();
624            int tableLineWidth = 0;  // table line width in characters
625            int totalColWidth = 0;
626
627            doingPrint = true;
628            for (int j = 0; j < NUM_COLUMNS; j++) {
629                if (j != SELECT_COLUMN) {
630                    totalColWidth += colWidth[j];
631                }
632            }
633            float ratio = ((float) charPerLine) / ((float) totalColWidth);
634            for (int j = 0; j < NUM_COLUMNS; j++) {
635                if (j != SELECT_COLUMN) {
636                    columnSize[j] = ((int) (colWidth[j] * ratio)) - 1;
637                    tableLineWidth += (columnSize[j] + 1);
638                }
639            }
640
641            // Draw horizontal dividing line
642            w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), tableLineWidth);
643
644            // print the column header labels
645            String[] columnStrings = new String[NUM_COLUMNS];
646            // Put each column header in the array
647            for (int i = 0; i < NUM_COLUMNS; i++) {
648                if (i != SELECT_COLUMN) {
649                    columnStrings[i] = this.getColumnName(i);
650                }
651            }
652            //          w.setFontStyle(Font.BOLD);
653            printColumns(w, columnStrings, columnSize);
654            w.setFontStyle(Font.PLAIN);
655
656            // draw horizontal line
657            w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), tableLineWidth);
658            // now print each row of data
659            String[] spaces = new String[NUM_COLUMNS];
660            // create base strings the width of each of the columns
661            for (int k = 0; k < NUM_COLUMNS; k++) {
662                if (k != SELECT_COLUMN) {
663                    spaces[k] = "";
664                    for (int i = 0; i < columnSize[k]; i++) {
665                        spaces[k] = spaces[k] + " ";
666                    }
667                }
668            }
669            for (int i = 0; i < this.getRowCount(); i++) {
670                for (int j = 0; j < NUM_COLUMNS; j++) {
671                    if (j != SELECT_COLUMN) {
672                        //check for special, null contents
673                        if (this.getValueAt(i, j) == null) {
674                            columnStrings[j] = spaces[j];
675                        } else {
676                            columnStrings[j] = (String) this.getValueAt(i, j);
677                        }
678                    }
679                }
680                printColumns(w, columnStrings, columnSize);
681                // draw horizontal line
682                w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), tableLineWidth);
683            }
684            doingPrint = false;
685            w.close();
686        }
687
688        protected void printColumns(CompatibleHardcopyWriter w, String columnStrings[], int columnSize[]) {
689            String columnString = "";
690            StringBuilder lineString = new StringBuilder("");
691            String[] spaces = new String[NUM_COLUMNS];
692            // create base strings the width of each of the columns
693            for (int k = 0; k < NUM_COLUMNS; k++) {
694                if (k != SELECT_COLUMN) {
695                    spaces[k] = "";
696                    for (int i = 0; i < columnSize[k]; i++) {
697                        spaces[k] = spaces[k] + " ";
698                    }
699                }
700            }
701            // loop through each column
702            boolean complete = false;
703            while (!complete) {
704                complete = true;
705                for (int i = 0; i < NUM_COLUMNS; i++) {
706                    if (i != SELECT_COLUMN) {
707                        // if the column string is too wide cut it at word boundary (valid delimiters are space, - and _)
708                        // use the initial part of the text,pad it with spaces and place the remainder back in the array
709                        // for further processing on next line
710                        // if column string isn't too wide, pad it to column width with spaces if needed
711                        if (columnStrings[i].length() > columnSize[i]) {
712                            // this column string will not fit on one line
713                            boolean noWord = true;
714                            for (int k = columnSize[i]; k >= 1; k--) {
715                                if (columnStrings[i].substring(k - 1, k).equals(" ")
716                                        || columnStrings[i].substring(k - 1, k).equals("-")
717                                        || columnStrings[i].substring(k - 1, k).equals("_")) {
718                                    columnString = columnStrings[i].substring(0, k)
719                                            + spaces[i].substring(columnStrings[i].substring(0, k).length());
720                                    columnStrings[i] = columnStrings[i].substring(k);
721                                    noWord = false;
722                                    complete = false;
723                                    break;
724                                }
725                            }
726                            if (noWord) {
727                                columnString = columnStrings[i].substring(0, columnSize[i]);
728                                columnStrings[i] = columnStrings[i].substring(columnSize[i]);
729                                complete = false;
730                            }
731                        } else {
732                            // this column string will fit on one line
733                            columnString = columnStrings[i] + spaces[i].substring(columnStrings[i].length());
734                            columnStrings[i] = "";
735                        }
736                        lineString.append(columnString).append(" ");
737                    }
738                }
739                try {
740                    w.write(lineString.toString());
741                    //write vertical dividing lines
742                    int iLine = w.getCurrentLineNumber();
743                    for (int i = 0, k = 0; i < w.getCharactersPerLine(); k++) {
744                        if (k != SELECT_COLUMN) {
745                            w.write(iLine, i, iLine + 1, i);
746                            if (k < NUM_COLUMNS) {
747                                i = i + columnSize[k] + 1;
748                            } else {
749                                i = w.getCharactersPerLine();
750                            }
751                        }
752                    }
753                    w.write("\n");
754                    lineString = new StringBuilder("");
755                } catch (IOException e) {
756                    log.warn("error during printing: ", e);
757                }
758            }
759        }
760    }
761
762    private String[] nodeTableColumnsNames
763            = {"Address", "   Type", "Bits per Card", "IN Cards", "OUT Cards", "IN Bits", "OUT Bits", " ", "  Description"};
764
765    private String[] nodeTableTypes = {"--", "SMINI", "SUSIC", "CPNODE", "CPMEGA", "ESP32Node"};
766
767    /*
768     * ----------------------------------------------------------
769     *    ------ Node Configuration Management Routines ------
770     * ----------------------------------------------------------
771     */
772    public void initNodeVariables() {
773        // Clear information arrays
774        for (int i = 0; i < 64; i++) {
775            cardType[i] = Bundle.getMessage("CardTypeNone");
776        }
777
778        //cpMega onboard bytes held in a separate array and will be copied
779        //to CardArray.
780        for (int i = 0; i < 16; i++) {
781            onBoardType[i] = Bundle.getMessage("CardTypeNone");
782        }
783
784        for (int i = 0; i < 48; i++) {
785            searchlightBits[i] = false;
786            firstSearchlight[i] = false;
787        }
788        for (int i = 0; i < SerialNode.NUMCMRINETOPTS; i++) {
789            cmrinetOpts[i] = 0;
790        }
791        for (int i = 0; i < SerialNode.NUMCPNODEOPTS; i++) {
792            cpnodeOpts[i] = 0;
793        }
794        nodeDescText = "";
795    }
796
797    /**
798     * Initialize the node configuration window This window is a template for
799     * ADD,EDIT,DELETE node operations
800     */
801    public void initNodeConfigWindow() {
802        setTitle(Bundle.getMessage("WindowTitle"));
803        Container contentPane = getContentPane();
804        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
805
806        // Set up node address and node type
807        JPanel panel1 = new JPanel();
808        panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
809        JPanel panel11 = new JPanel();
810        panel11.setLayout(new FlowLayout());
811        panel11.add(new JLabel(Bundle.getMessage("LabelNodeAddress") + " "));
812        panel11.add(nodeAddrField);
813        nodeAddrField.setToolTipText(Bundle.getMessage("TipNodeAddress"));
814        nodeAddrField.setText("-1");
815        panel11.add(nodeAddrStatic);
816        nodeAddrStatic.setVisible(false);
817        panel11.add(new JLabel("   " + Bundle.getMessage("LabelNodeType") + " "));
818        nodeTypeBox = new JComboBox<>();
819        panel11.add(nodeTypeBox);
820        nodeTypeBox.addItem("SMINI");
821        nodeTypeBox.addItem("USIC_SUSIC");
822        nodeTypeBox.addItem("CPNODE");
823        nodeTypeBox.addItem("CPMEGA");
824        nodeTypeBox.addItem("ESP32Node");
825
826        /*
827         * Here add code for other types of nodes
828         */
829        nodeTypeBox.addActionListener(new java.awt.event.ActionListener() {
830            @Override
831            public void actionPerformed(java.awt.event.ActionEvent event) {
832                String s = (String) nodeTypeBox.getSelectedItem();
833
834                if (s.equals("SMINI")) {
835                    panel2.setVisible(false);
836                    panel2a.setVisible(true);
837                    panel2b.setVisible(false);
838                    panel2c.setVisible(false);
839                    cardSizeText.setVisible(false);
840                    cardSizeBox.setVisible(false);
841                    cardSize8Box.setVisible(false);
842                    panelnodeDescBox.setVisible(true);
843                    panelnodeDesc.setVisible(true);
844                    panelnetOpt.setVisible(true);
845                    panelnetOptBox.setVisible(true);
846                    panelnodeOpt.setVisible(false);
847                    nodeType = SerialNode.SMINI;
848                    onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " 3 Input, 6 Output");
849                } else if (s.equals("USIC_SUSIC")) {
850                    panel2.setVisible(true);
851                    panel2a.setVisible(false);
852                    panel2b.setVisible(false);
853                    panel2c.setVisible(false);
854                    cardSizeText.setVisible(true);
855                    cardSizeBox.setVisible(true);
856                    cardSize8Box.setVisible(false);
857                    panelnodeDescBox.setVisible(true);
858                    panelnodeDesc.setVisible(true);
859                    panelnetOpt.setVisible(true);
860                    panelnetOptBox.setVisible(true);
861                    panelnodeOpt.setVisible(false);
862                    nodeType = SerialNode.USIC_SUSIC;
863                    onBoardBytesText.setText("  ");
864                } else if (s.equals("CPNODE")) {
865                    panel2.setVisible(false);
866                    panel2a.setVisible(false);
867                    panel2b.setVisible(true);
868                    panel2c.setVisible(false);   // IOX bytes
869                    cardSizeText.setVisible(true);
870                    cardSizeBox.setVisible(false);
871                    cardSize8Box.setVisible(true);
872                    panelnodeDescBox.setVisible(true);
873                    panelnodeDesc.setVisible(true);
874                    panelnetOpt.setVisible(true);
875                    panelnetOptBox.setVisible(true);
876                    panelnodeOpt.setVisible(true);
877                    nodeType = SerialNode.CPNODE;
878                    cpNodeOnboard = 4;
879                    onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " 2 Bytes");
880                } else if (s.equals("CPMEGA")) {
881                    panel2.setVisible(false);
882                    panel2a.setVisible(false);
883                    panel2b.setVisible(true);
884                    panel2c.setVisible(true);   // IOX bytes
885                    cardSizeText.setVisible(true);
886                    cardSizeBox.setVisible(false);
887                    cardSize8Box.setVisible(true);
888                    panelnodeDescBox.setVisible(true);
889                    panelnodeDesc.setVisible(true);
890                    panelnetOpt.setVisible(true);
891                    panelnetOptBox.setVisible(true);
892                    panelnodeOpt.setVisible(true);
893                    nodeType = SerialNode.CPMEGA;
894                    cpNodeOnboard = 4;
895                    onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " 8 Bytes");
896                } else if (s.equals("ESP32Node")) {
897                    panel2.setVisible(false);
898                    panel2a.setVisible(false);
899                    panel2b.setVisible(true);   // IOX-style card grid -- ALL 16 cards, no onboard reservation
900                    panel2c.setVisible(false);  // no separate onboard-byte grid -- ESP32Node has no onboard I/O
901                    cardSizeText.setVisible(true);
902                    cardSizeBox.setVisible(false);
903                    cardSize8Box.setVisible(true);
904                    panelnodeDescBox.setVisible(true);
905                    panelnodeDesc.setVisible(true);
906                    panelnetOpt.setVisible(true);
907                    panelnetOptBox.setVisible(true);
908                    panelnodeOpt.setVisible(true);
909                    nodeType = SerialNode.ESP32NODE;
910                    cpNodeOnboard = 0;
911                    onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " None -- 8 MCP23017 chips (0x20-0x27), 16 bytes");
912                }
913                /*
914                 * Here add code for other types of nodes
915                 */
916
917                // reset notes as appropriate
918                resetNotes();
919            }
920        });
921        nodeTypeBox.setToolTipText(Bundle.getMessage("TipNodeType"));
922
923        JPanel panel12 = new JPanel();
924        panel12.setLayout(new FlowLayout());
925        panel12.add(new JLabel(Bundle.getMessage("LabelDelay") + " "));
926        panel12.add(receiveDelayField);
927        receiveDelayField.setToolTipText(Bundle.getMessage("TipDelay"));
928        receiveDelayField.setText("0");
929        panel12.add(cardSizeText);
930        cardSizeBox = new JComboBox<>();
931        cardSize8Box = new JComboBox<>();
932        panel12.add(cardSizeBox);
933        panel12.add(cardSize8Box);
934        cardSizeBox.addItem(Bundle.getMessage("CardSize24"));
935        cardSizeBox.addItem(Bundle.getMessage("CardSize32"));
936        cardSize8Box.addItem(Bundle.getMessage("CardSize8"));
937        /*
938         * Here add code for other types of nodes
939         */
940
941        cardSizeBox.addActionListener(new java.awt.event.ActionListener() {
942            @Override
943            public void actionPerformed(java.awt.event.ActionEvent event) {
944                String s = (String) cardSizeBox.getSelectedItem();
945                if (s.equals(Bundle.getMessage("CardSize24"))) {
946                    bitsPerCard = 24;
947                } else if (s.equals(Bundle.getMessage("CardSize32"))) {
948                    bitsPerCard = 32;
949                } else if (s.equals(Bundle.getMessage("CardSize8"))) {
950                    bitsPerCard = 8;
951                }
952                // here add code for other node types, if required
953            }
954        });
955        cardSizeBox.setToolTipText(Bundle.getMessage("TipCardSize"));
956        cardSizeText.setVisible(false);
957        cardSizeBox.setVisible(false);
958        cardSize8Box.setVisible(false);
959
960        JPanel panel13 = new JPanel();
961        panel13.setLayout(new FlowLayout());
962        panel13.add(new JLabel(Bundle.getMessage("LabelPulseWidth") + " "));
963        panel13.add(pulseWidthField);
964        pulseWidthField.setToolTipText(Bundle.getMessage("TipPulseWidth"));
965        pulseWidthField.setText("500");
966        panel13.add(new JLabel(Bundle.getMessage("LabelMilliseconds")));
967
968        JPanel panel14 = new JPanel();
969        panel14.add(onBoardBytesText);
970        panel14.setVisible(true);
971
972        // Load the top half of the common window
973        panel1.add(panel11);
974        panel1.add(panel12);
975        panel1.add(panel13);
976        panel1.add(panel14);
977        contentPane.add(panel1);
978
979        // Set up USIC/SUSIC card type configuration table
980        JPanel panel21 = new JPanel();
981        panel21.setLayout(new BoxLayout(panel21, BoxLayout.Y_AXIS));
982        panel21.add(new JLabel(Bundle.getMessage("HintCardTypePartA")));
983        panel21.add(new JLabel(" " + Bundle.getMessage("HintCardTypePartB")));
984        panel21.add(new JLabel(" " + Bundle.getMessage("HintCardTypePartC")));
985        panel21.add(new JLabel("   "));
986        panel21.add(new JLabel(Bundle.getMessage("HintCardTypePartD")));
987        panel21.add(new JLabel(" " + Bundle.getMessage("HintCardTypePartE")));
988        panel21.add(new JLabel(" " + Bundle.getMessage("HintCardTypePartF")));
989        panel2.add(panel21);
990        TableModel cardConfigModel = new CardConfigModel();
991        JTable cardConfigTable = new JTable(cardConfigModel);
992        cardConfigTable.setRowSelectionAllowed(false);
993        cardConfigTable.setPreferredScrollableViewportSize(new java.awt.Dimension(180, 95));
994
995        JComboBox<String> cardTypeCombo = new JComboBox<>();
996        cardTypeCombo.addItem(Bundle.getMessage("CardTypeOutput"));
997        cardTypeCombo.addItem(Bundle.getMessage("CardTypeInput"));
998        cardTypeCombo.addItem(Bundle.getMessage("CardTypeNone"));
999
1000        TableColumnModel typeColumnModel = cardConfigTable.getColumnModel();
1001        TableColumn addressColumn = typeColumnModel.getColumn(CardConfigModel.ADDRESS_COLUMN);
1002        addressColumn.setMinWidth(70);
1003        addressColumn.setMaxWidth(80);
1004        TableColumn cardTypeColumn = typeColumnModel.getColumn(CardConfigModel.TYPE_COLUMN);
1005        cardTypeColumn.setCellEditor(new DefaultCellEditor(cardTypeCombo));
1006        cardTypeColumn.setResizable(false);
1007        cardTypeColumn.setMinWidth(90);
1008        cardTypeColumn.setMaxWidth(100);
1009
1010        JScrollPane cardScrollPane = new JScrollPane(cardConfigTable);
1011        panel2.add(cardScrollPane, BorderLayout.CENTER);
1012        contentPane.add(panel2);
1013        panel2.setVisible(false);
1014
1015        // Set up SMINI oscillating 2-lead searchlight configuration table
1016        JPanel panel2a1 = new JPanel();
1017        panel2a1.setLayout(new BoxLayout(panel2a1, BoxLayout.Y_AXIS));
1018        panel2a1.add(new JLabel(Bundle.getMessage("HintSearchlightPartA")));
1019        panel2a1.add(new JLabel(" " + Bundle.getMessage("HintSearchlightPartB")));
1020        panel2a1.add(new JLabel(" " + Bundle.getMessage("HintSearchlightPartC")));
1021        panel2a1.add(new JLabel("   "));
1022        panel2a1.add(new JLabel(Bundle.getMessage("HintSearchlightPartD")));
1023        panel2a1.add(new JLabel(" " + Bundle.getMessage("HintSearchlightPartE")));
1024        panel2a1.add(new JLabel(" " + Bundle.getMessage("HintSearchlightPartF")));
1025        panel2a.add(panel2a1);
1026
1027        TableModel searchlightConfigModel = new SearchlightConfigModel();
1028        JTable searchlightConfigTable = new JTable(searchlightConfigModel);
1029        searchlightConfigTable.setRowSelectionAllowed(false);
1030        searchlightConfigTable.setPreferredScrollableViewportSize(new java.awt.Dimension(208, 100));
1031        TableColumnModel searchlightColumnModel = searchlightConfigTable.getColumnModel();
1032        TableColumn portColumn = searchlightColumnModel.getColumn(SearchlightConfigModel.PORT_COLUMN);
1033        portColumn.setMinWidth(90);
1034        portColumn.setMaxWidth(100);
1035        JScrollPane searchlightScrollPane = new JScrollPane(searchlightConfigTable);
1036        panel2a.add(searchlightScrollPane, BorderLayout.CENTER);
1037        contentPane.add(panel2a);
1038        panel2.setVisible(false);
1039
1040        // Set up CPMEGA on board byte I/O assignments
1041        JPanel panel2b3 = new JPanel();
1042        panel2b3.setLayout(new BoxLayout(panel2b3, BoxLayout.Y_AXIS));
1043        panel2b3.add(new JLabel("Assign Onboard Bytes"));
1044        panel2c.add(panel2b3);
1045
1046        TableModel osnodeConfigModel = new OSnodeConfigModel();
1047        JTable osnodeConfigTable = new JTable(osnodeConfigModel);
1048        osnodeConfigTable.setRowSelectionAllowed(false);
1049        osnodeConfigTable.setPreferredScrollableViewportSize(new java.awt.Dimension(170, 95)); //160
1050
1051        JComboBox<String> osnodeTypeCombo = new JComboBox<>();
1052        osnodeTypeCombo.addItem(Bundle.getMessage("CardTypeOutput"));
1053        osnodeTypeCombo.addItem(Bundle.getMessage("CardTypeInput"));
1054        osnodeTypeCombo.addItem(Bundle.getMessage("CardTypeNone"));
1055
1056        TableColumnModel osnodePortModel = osnodeConfigTable.getColumnModel();
1057        TableColumn x11Column = osnodePortModel.getColumn(OSnodeConfigModel.CARDNUM_COLUMN);
1058        x11Column.setMinWidth(50);
1059        x11Column.setMaxWidth(90);
1060        TableColumn x21Column = osnodePortModel.getColumn(OSnodeConfigModel.CARDTYPE_COLUMN);
1061        x21Column.setCellEditor(new DefaultCellEditor(osnodeTypeCombo));
1062        x21Column.setResizable(false);
1063        x21Column.setMinWidth(80);
1064        x21Column.setMaxWidth(100);
1065
1066        JScrollPane osnodeScrollPane = new JScrollPane(osnodeConfigTable);
1067        panel2c.add(osnodeScrollPane, BorderLayout.CENTER);
1068        contentPane.add(panel2c);
1069        panel2.setVisible(false);
1070
1071        // Set up I/O Expander (IOX) port assignments
1072        JPanel panel2b1 = new JPanel();
1073        panel2b1.setLayout(new BoxLayout(panel2b1, BoxLayout.Y_AXIS));
1074        panel2b1.add(new JLabel("Assign IOX Ports"));
1075
1076        panel2b.add(panel2b1);
1077
1078        TableModel cpnodeConfigModel = new CPnodeConfigModel();
1079        JTable cpnodeConfigTable = new JTable(cpnodeConfigModel);
1080        cpnodeConfigTable.setRowSelectionAllowed(false);
1081        cpnodeConfigTable.setPreferredScrollableViewportSize(new java.awt.Dimension(240, 130)); //160
1082
1083        JComboBox<String> cpnodeTypeCombo = new JComboBox<>();
1084        cpnodeTypeCombo.addItem(Bundle.getMessage("CardTypeOutput"));
1085        cpnodeTypeCombo.addItem(Bundle.getMessage("CardTypeInput"));
1086        cpnodeTypeCombo.addItem(Bundle.getMessage("CardTypeNone"));
1087
1088        TableColumnModel cpnodePortModel = cpnodeConfigTable.getColumnModel();
1089        TableColumn x0Column = cpnodePortModel.getColumn(CPnodeConfigModel.CARD_COLUMN);
1090        x0Column.setMinWidth(30);
1091        x0Column.setMaxWidth(50);
1092        TableColumn x1Column = cpnodePortModel.getColumn(CPnodeConfigModel.CARDNUM_COLUMN);
1093        x1Column.setMinWidth(70);
1094        x1Column.setMaxWidth(120);
1095        TableColumn x2Column = cpnodePortModel.getColumn(CPnodeConfigModel.CARDTYPE_COLUMN);
1096        x2Column.setCellEditor(new DefaultCellEditor(cpnodeTypeCombo));
1097        x2Column.setResizable(false);
1098        x2Column.setMinWidth(80);
1099        x2Column.setMaxWidth(100);
1100
1101        JScrollPane cpnodeScrollPane = new JScrollPane(cpnodeConfigTable);
1102        panel2b.add(cpnodeScrollPane, BorderLayout.CENTER);
1103        contentPane.add(panel2b);
1104        panel2b.setVisible(false);
1105
1106        // node Description field - all node types have this field
1107        panelnodeDescBox.setLayout(new BoxLayout(panelnodeDescBox, BoxLayout.Y_AXIS));
1108        panelnodeDesc.setLayout(new FlowLayout());
1109        panelnodeDesc.add(new JLabel("Description:"));
1110        nodeDescription.setVisible(true);
1111        panelnodeDesc.add(nodeDescription);
1112        panelnodeDesc.setVisible(true);
1113        contentPane.add(panelnodeDesc);
1114
1115        // Set up CMRInet Options
1116        panelnetOpt.setLayout(new GridLayout(0, 2));
1117
1118        panelnetOpt.add(cbx_cmrinetopt_AUTOPOLL);
1119        cbx_cmrinetopt_AUTOPOLL.addItemListener(nodeOptHandler);
1120
1121        panelnetOpt.add(cbx_cmrinetopt_USECMRIX);
1122        cbx_cmrinetopt_USECMRIX.addItemListener(nodeOptHandler);
1123        cbx_cmrinetopt_USECMRIX.setVisible(false);
1124
1125        panelnetOpt.add(cbx_cmrinetopt_USEBCC);
1126        cbx_cmrinetopt_USEBCC.addItemListener(nodeOptHandler);
1127        cbx_cmrinetopt_USEBCC.setVisible(false);
1128
1129        panelnetOpt.add(cbx_cmrinetopt_BIT8);
1130        cbx_cmrinetopt_BIT8.addItemListener(nodeOptHandler);
1131        cbx_cmrinetopt_BIT8.setVisible(false);
1132
1133        panelnetOpt.add(cbx_cmrinetopt_BIT15);
1134        cbx_cmrinetopt_BIT15.addItemListener(nodeOptHandler);
1135        cbx_cmrinetopt_BIT15.setVisible(false);
1136
1137        Border panelnetOptBorder = BorderFactory.createEtchedBorder();
1138        Border panelnetOptTitled = BorderFactory.createTitledBorder(panelnetOptBorder, "CMRInet Options");
1139        panelnetOpt.setBorder(panelnetOptTitled);
1140        panelnetOpt.setVisible(true);
1141        contentPane.add(panelnetOpt);
1142
1143        panelnetOptBox.add(Box.createHorizontalStrut(50));
1144        contentPane.add(panelnetOptBox);
1145
1146        // Set up cpNode Options
1147        panelnodeOpt.setLayout(new GridLayout(0, 2));
1148        panelnodeOpt.add(cbx_cpnodeopt_SENDEOT);
1149        cbx_cpnodeopt_SENDEOT.addItemListener(nodeOptHandler);
1150        cbx_cpnodeopt_SENDEOT.setVisible(true);
1151
1152        panelnodeOpt.add(cbx_cpnodeopt_BIT1);
1153        cbx_cpnodeopt_BIT1.addItemListener(nodeOptHandler);
1154        cbx_cpnodeopt_BIT1.setVisible(false);
1155
1156        panelnodeOpt.add(cbx_cpnodeopt_BIT2);
1157        cbx_cpnodeopt_BIT2.addItemListener(nodeOptHandler);
1158        cbx_cpnodeopt_BIT2.setVisible(false);
1159
1160        panelnodeOpt.add(cbx_cpnodeopt_BIT8);
1161        cbx_cpnodeopt_BIT8.addItemListener(nodeOptHandler);
1162        cbx_cpnodeopt_BIT8.setVisible(false);
1163
1164        panelnodeOpt.add(cbx_cpnodeopt_BIT15);
1165        cbx_cpnodeopt_BIT15.addItemListener(nodeOptHandler);
1166        cbx_cpnodeopt_BIT15.setVisible(false);
1167
1168        Border panelnodeOptBorder = BorderFactory.createEtchedBorder();
1169        Border panelnodeOptTitled = BorderFactory.createTitledBorder(panelnodeOptBorder, "cpNode Options");
1170        panelnodeOpt.setBorder(panelnodeOptTitled);
1171        panelnodeOpt.setVisible(false);
1172        contentPane.add(panelnodeOpt);
1173
1174        // Set up the notes area panel for various message displays
1175        JPanel panel3 = new JPanel();
1176        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
1177        JPanel panel31 = new JPanel();
1178        panel31.setLayout(new FlowLayout());
1179        statusText1.setVisible(false);
1180        panel31.add(statusText1);
1181        JPanel panel32 = new JPanel();
1182        panel32.setLayout(new FlowLayout());
1183        statusText2.setVisible(false);
1184        panel32.add(statusText2);
1185        JPanel panel33 = new JPanel();
1186        panel33.setLayout(new FlowLayout());
1187        statusText3.setVisible(false);
1188        panel33.add(statusText3);
1189        panel3.add(panel31);
1190        panel3.add(panel32);
1191        panel3.add(panel33);
1192        Border panel3Border = BorderFactory.createEtchedBorder();
1193        Border panel3Titled = BorderFactory.createTitledBorder(panel3Border, Bundle.getMessage("BoxLabelNotes"));
1194        panel3.setBorder(panel3Titled);
1195        contentPane.add(panel3);
1196
1197        // Set up the functions buttons
1198        JPanel panel4 = new JPanel();
1199        panel4.setLayout(new FlowLayout());
1200
1201        addNodeButton.setText(Bundle.getMessage("ButtonAdd"));
1202        addNodeButton.setVisible(false);
1203        addNodeButton.setToolTipText(Bundle.getMessage("TipAddButton"));
1204        addNodeButton.addActionListener((java.awt.event.ActionEvent e) -> {
1205            addNodeButtonActionPerformed();
1206        });
1207        panel4.add(addNodeButton);
1208
1209        deleteNodeButton.setText(Bundle.getMessage("ButtonDelete"));
1210        deleteNodeButton.setVisible(false);
1211        deleteNodeButton.setToolTipText(Bundle.getMessage("TipDeleteButton"));
1212        deleteNodeButton.addActionListener((java.awt.event.ActionEvent e) -> {
1213            deleteNodeButtonActionConfirm();
1214        });
1215        panel4.add(deleteNodeButton);
1216
1217        updateNodeButton.setText(Bundle.getMessage("ButtonUpdate"));
1218        updateNodeButton.setVisible(false);
1219        updateNodeButton.setToolTipText(Bundle.getMessage("TipUpdateButton"));
1220        updateNodeButton.addActionListener((java.awt.event.ActionEvent e) -> {
1221            updateNodeButtonActionPerformed();
1222        });
1223        panel4.add(updateNodeButton);
1224
1225        cancelNodeButton.setText(Bundle.getMessage("ButtonCancel"));
1226        cancelNodeButton.setVisible(false);
1227        cancelNodeButton.setToolTipText(Bundle.getMessage("TipCancelButton"));
1228        cancelNodeButton.addActionListener((java.awt.event.ActionEvent e) -> {
1229            cancelNodeButtonActionPerformed();
1230        });
1231        panel4.add(cancelNodeButton);
1232
1233        doneNodeButton.setText(Bundle.getMessage("ButtonDone"));
1234        doneNodeButton.setVisible(false);
1235        doneNodeButton.setToolTipText(Bundle.getMessage("TipDoneButton"));
1236        doneNodeButton.addActionListener((java.awt.event.ActionEvent e) -> {
1237            doneNodeButtonActionPerformed();
1238        });
1239        panel4.add(doneNodeButton);
1240
1241        contentPane.add(panel4);
1242        pack();
1243    }
1244
1245    /*
1246     * -------------------------------------------------------
1247     * Methods to set the correct states of the window buttons
1248     * -------------------------------------------------------
1249     */
1250    public void buttonSet_ADD() {
1251        setTitle("ADD NODE");
1252        statusText1.setText(addStatus1);
1253        statusText1.setVisible(true);
1254        statusText2.setText(addStatus2);
1255        statusText2.setVisible(true);
1256        statusText3.setText(addStatus3);
1257        statusText3.setVisible(false);
1258
1259        panel2c.setVisible(false);
1260
1261        addNodeButton.setVisible(true);
1262        deleteNodeButton.setVisible(false);
1263        updateNodeButton.setVisible(false);
1264        doneNodeButton.setVisible(false);
1265        cancelNodeButton.setVisible(true);
1266    }
1267
1268    public void buttonSet_EDIT() {
1269        setTitle("EDIT NODE");
1270        statusText1.setText(editStatus1);
1271        statusText1.setVisible(true);
1272        statusText2.setText(editStatus2);
1273        statusText2.setVisible(true);
1274        statusText3.setText(editStatus3);
1275        statusText3.setVisible(false);
1276
1277        addNodeButton.setVisible(false);
1278        deleteNodeButton.setVisible(false);
1279        updateNodeButton.setVisible(true);
1280        doneNodeButton.setVisible(false);
1281        cancelNodeButton.setVisible(true);
1282    }
1283
1284    public void buttonSet_DELETE() {
1285        setTitle("DELETE NODE");
1286        statusText1.setText(delStatus1);
1287        statusText1.setVisible(true);
1288        statusText2.setText(delStatus2);
1289        statusText2.setVisible(true);
1290        statusText3.setText(delStatus3);
1291        statusText3.setVisible(false);
1292
1293        addNodeButton.setVisible(false);
1294        deleteNodeButton.setVisible(true);
1295        updateNodeButton.setVisible(false);
1296        doneNodeButton.setVisible(false);
1297        cancelNodeButton.setVisible(true);
1298    }
1299
1300    public void addNodeButtonActionPerformed() {
1301        // Check that a node with this address does not exist
1302        curNode = null;
1303        int nodeAddress = readNodeAddress();
1304        if (nodeAddress < 0) {
1305            return;
1306        }
1307
1308        // get a SerialNode corresponding to this node address if one exists
1309        curNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(nodeAddress);
1310        // curNode can never be null at this point. Was this intended to catch
1311        // an exception?
1312        if (curNode != null) {
1313            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error1", Integer.toString(nodeAddress)),
1314                "", JmriJOptionPane.ERROR_MESSAGE);
1315
1316            statusText1.setText(Bundle.getMessage("Error1", Integer.toString(nodeAddress)));
1317            statusText1.setVisible(true);
1318            errorInStatus1 = true;
1319            resetNotes2();
1320            return;
1321        }
1322
1323        // get node information from window and check for data consistency
1324        if (!readReceiveDelay()) {
1325            return;
1326        }
1327        if (!readPulseWidth()) {
1328            return;
1329        }
1330        if (!checkConsistency()) {
1331            return;
1332        }
1333
1334        // all ready, create the new node
1335        curNode = new SerialNode(nodeAddress, nodeType, _memo.getTrafficController());
1336        nodeTableModel.addRow(curNode);
1337
1338        // configure the new node
1339        setNodeParameters();
1340
1341        // register any orphan sensors that this node may have
1342        if (_memo.getSensorManager() != null) {
1343            (_memo.getSensorManager()).registerSensorsForNode(curNode);
1344        }
1345
1346        // reset text displays after succefully adding node
1347        resetNotes();
1348        changedNode = true;
1349        log.info("changedNode = {}", changedNode);
1350        // provide user feedback
1351        statusText1.setText(Bundle.getMessage("FeedBackAdd") + " " + Integer.toString(nodeAddress));
1352        statusText2.setVisible(false);
1353        statusText3.setVisible(true);
1354        doneNodeButton.setVisible(true);
1355        cancelNodeButton.setVisible(false);
1356        errorInStatus1 = true;
1357    }
1358
1359    /**
1360     * Load all of the configured node information from the serial node class.
1361     *
1362     * @param nodeaddr the node address
1363     */
1364    public void setupNodeInformationWindow(int nodeaddr) {
1365        // Find Serial Node address
1366        nodeAddress = nodeaddr;
1367        if (nodeAddress < 0) {
1368            return;
1369        }
1370
1371        // get the SerialNode corresponding to this node address
1372        curNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(nodeAddress);
1373        if (curNode == null) {
1374            statusText1.setText(Bundle.getMessage("Error4"));
1375            statusText1.setVisible(true);
1376            errorInStatus1 = true;
1377            resetNotes2();
1378            return;
1379        }
1380
1381        // Set up static node address from the table, cannot be changed
1382        nodeAddrStatic.setText(Integer.toString(nodeAddress));
1383        nodeAddrField.setVisible(false);
1384        nodeAddrStatic.setVisible(true);
1385
1386        // get information for this node and set up combo box
1387        nodeType = curNode.getNodeType();
1388        switch (nodeType) {
1389
1390            // SMINI
1391            case SerialNode.SMINI:
1392                nodeTypeBox.setSelectedItem("SMINI");
1393                bitsPerCard = 24;
1394                cardSizeBox.setSelectedItem(Bundle.getMessage("CardSize24"));
1395                onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " 3 Input Bytes, 6 Output Bytes");
1396                // set up the searchlight arrays
1397                num2LSearchLights = 0;
1398                for (int i = 0; i < 48; i++) {
1399                    if (curNode.isSearchLightBit(i)) {
1400                        searchlightBits[i] = true;
1401                        searchlightBits[i + 1] = true;
1402                        firstSearchlight[i] = true;
1403                        firstSearchlight[i + 1] = false;
1404                        num2LSearchLights++;
1405                        i++;
1406                    } else {
1407                        searchlightBits[i] = false;
1408                        firstSearchlight[i] = false;
1409                    }
1410                }
1411                break;
1412
1413            // USIC/SUSIC
1414            case SerialNode.USIC_SUSIC:
1415                nodeTypeBox.setSelectedItem("USIC_SUSIC");
1416                bitsPerCard = curNode.getNumBitsPerCard();
1417                if (bitsPerCard == 24) {
1418                    cardSizeBox.setSelectedItem(Bundle.getMessage("CardSize24"));
1419                }
1420                if (bitsPerCard == 32) {
1421                    cardSizeBox.setSelectedItem(Bundle.getMessage("CardSize32"));
1422                }
1423                onBoardBytesText.setText("  ");
1424
1425                break;
1426
1427            // CPNODE
1428            case SerialNode.CPNODE:
1429                nodeTypeBox.setSelectedItem("CPNODE");
1430                bitsPerCard = 8;
1431                cardSize8Box.setSelectedItem(Bundle.getMessage("CardSize8"));
1432                onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " 2 Bytes");
1433
1434                // cpNode Options
1435                cbx_cpnodeopt_SENDEOT.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_SENDEOT));
1436                cbx_cpnodeopt_BIT1.setSelected(false);
1437                cbx_cpnodeopt_BIT2.setSelected(false);
1438                cbx_cpnodeopt_BIT8.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_BIT8));
1439                cbx_cpnodeopt_BIT15.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_BIT15));
1440                break;
1441
1442            // CPMEGA
1443            case SerialNode.CPMEGA:
1444                nodeTypeBox.setSelectedItem("CPMEGA");
1445                bitsPerCard = 8;
1446                cardSize8Box.setSelectedItem(Bundle.getMessage("CardSize8"));
1447                onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " 8 Bytes");
1448
1449                // cpMega Options
1450                cbx_cpnodeopt_SENDEOT.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_SENDEOT));
1451                cbx_cpnodeopt_BIT1.setSelected(false);
1452                cbx_cpnodeopt_BIT2.setSelected(false);
1453                cbx_cpnodeopt_BIT8.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_BIT8));
1454                cbx_cpnodeopt_BIT15.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_BIT15));
1455                break;
1456
1457            // ESP32Node
1458            case SerialNode.ESP32NODE:
1459                nodeTypeBox.setSelectedItem("ESP32Node");
1460                bitsPerCard = 8;
1461                cardSize8Box.setSelectedItem(Bundle.getMessage("CardSize8"));
1462                cpNodeOnboard = 0;
1463                onBoardBytesText.setText(Bundle.getMessage("LabelOnBoardBytes") + " None -- 8 MCP23017 chips (0x20-0x27), 16 bytes");
1464
1465                // ESP32Node Options -- same option bits as CPNODE/CPMEGA
1466                cbx_cpnodeopt_SENDEOT.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_SENDEOT));
1467                cbx_cpnodeopt_BIT1.setSelected(false);
1468                cbx_cpnodeopt_BIT2.setSelected(false);
1469                cbx_cpnodeopt_BIT8.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_BIT8));
1470                cbx_cpnodeopt_BIT15.setSelected(curNode.iscpnodeBit(SerialNode.optbitNode_BIT15));
1471                break;
1472
1473            default:
1474                log.error("Unknown Node Type {}", nodeType);
1475                break;
1476        }
1477
1478        // CMRInet Options for all node types
1479        cbx_cmrinetopt_AUTOPOLL.setSelected(curNode.isCMRInetBit(SerialNode.optbitNet_AUTOPOLL));
1480        cbx_cmrinetopt_USECMRIX.setSelected(curNode.isCMRInetBit(SerialNode.optbitNet_USECMRIX));
1481        cbx_cmrinetopt_USEBCC.setSelected(curNode.isCMRInetBit(SerialNode.optbitNet_USEBCC));
1482        cbx_cmrinetopt_BIT8.setSelected(curNode.isCMRInetBit(SerialNode.optbitNet_BIT8));
1483        cbx_cmrinetopt_BIT15.setSelected(curNode.isCMRInetBit(SerialNode.optbitNet_BIT15));
1484
1485        // set up receive delay
1486        receiveDelay = curNode.getTransmissionDelay();
1487        receiveDelayField.setText(Integer.toString(receiveDelay));
1488
1489        // set up pulse width
1490        pulseWidth = curNode.getPulseWidth();
1491        pulseWidthField.setText(Integer.toString(pulseWidth));
1492
1493        // node description
1494        nodeDescText = curNode.getcmriNodeDesc();
1495        nodeDescription.setText(nodeDescText);
1496
1497        // set up card types
1498        for (int i = 0; i < 64; i++) {
1499            if (curNode.isOutputCard(i)) {
1500                cardType[i] = Bundle.getMessage("CardTypeOutput");
1501            } else if (curNode.isInputCard(i)) {
1502                cardType[i] = Bundle.getMessage("CardTypeInput");
1503            } else {
1504                cardType[i] = Bundle.getMessage("CardTypeNone");
1505            }
1506        }
1507
1508        if (nodeType == SerialNode.CPMEGA) {
1509            for (int i = 0; i < 8; i++) // Remap the onboard bytes
1510            {
1511                if (curNode.isOutputCard(i)) {
1512                    onBoardType[i] = Bundle.getMessage("CardTypeOutput");
1513                } else if (curNode.isInputCard(i)) {
1514                    onBoardType[i] = Bundle.getMessage("CardTypeInput");
1515                } else {
1516                    onBoardType[i] = Bundle.getMessage("CardTypeNone");
1517                }
1518            }
1519        }
1520
1521        // ensure that table displays correctly
1522        panel2.setVisible(false);
1523        panel2a.setVisible(false);
1524        if (nodeType == SerialNode.USIC_SUSIC) {
1525            panel2.setVisible(true);
1526        } else if (nodeType == SerialNode.SMINI) {
1527            panel2a.setVisible(true);
1528        } else if (nodeType == SerialNode.CPNODE) {
1529            panel2c.setVisible(false);
1530            panel2b.setVisible(true);
1531        } else if (nodeType == SerialNode.CPMEGA) {
1532            panel2c.setVisible(true);
1533            panel2b.setVisible(false);
1534        } else if (nodeType == SerialNode.ESP32NODE) {
1535            panel2c.setVisible(false);
1536            panel2b.setVisible(true);
1537        }
1538
1539    }
1540
1541    /**
1542     * Create and load edit function window.
1543     *
1544     * @param nodeaddr the node address
1545     */
1546    public void editNodeButtonActionPerformed(int nodeaddr) {
1547        // Find Serial Node address
1548
1549        nodeAddress = nodeaddr;
1550        if (nodeAddress < 0) {
1551            return;
1552        }
1553
1554        // get the SerialNode corresponding to this node address
1555        curNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(nodeAddress);
1556        if (curNode == null) {
1557            statusText1.setText(Bundle.getMessage("Error4"));
1558            statusText1.setVisible(true);
1559            errorInStatus1 = true;
1560            resetNotes2();
1561            return;
1562        }
1563
1564        // Load the node data into the window
1565        setupNodeInformationWindow(nodeAddress);
1566
1567        // Switch to edit notes
1568        editMode = true;
1569        statusText1.setText(editStatus1);
1570        statusText1.setVisible(true);
1571        statusText2.setText(editStatus2);
1572        statusText2.setVisible(true);
1573        statusText3.setText(editStatus3);
1574        statusText3.setVisible(true);
1575    }
1576
1577    /**
1578     * Handle update button clicked.
1579     */
1580    public void updateNodeButtonActionPerformed() {
1581        // get node information from window
1582        if (!readReceiveDelay()) {
1583            return;
1584        }
1585        if (!readPulseWidth()) {
1586            return;
1587        }
1588
1589        // check consistency of node information
1590        if (!checkConsistency()) {
1591            return;
1592        }
1593
1594        // update node information
1595        if (curNode.getNodeType() != nodeType) {
1596            // node type has changed
1597            curNode.setNodeType(nodeType);
1598        }
1599
1600        // cmri node description  c2
1601        curNode.setcmriNodeDesc(nodeDescription.getText());
1602        setNodeParameters();
1603        nodeTableModel.changeRow(selectedTableRow, curNode);
1604
1605        // Switch buttons
1606        changedNode = true;
1607        doneNodeButton.setVisible(true);
1608        updateNodeButton.setVisible(true);
1609        cancelNodeButton.setVisible(true);
1610        nodeAddrField.setVisible(false);
1611        nodeAddrStatic.setVisible(true);
1612
1613        // provide user feedback
1614        statusText1.setText(Bundle.getMessage("FeedBackUpdate") + " " + Integer.toString(nodeAddress));
1615        statusText2.setVisible(false);
1616        cancelNodeButton.setVisible(false);
1617        errorInStatus1 = true;
1618    }
1619
1620    /**
1621     * Handle delete button pressed.
1622     */
1623    public void deleteNodeButtonActionConfirm() {
1624        // confirm deletion with the user
1625
1626        if (JmriJOptionPane.OK_OPTION == JmriJOptionPane.showConfirmDialog(
1627                this, Bundle.getMessage("ConfirmDelete1") + "\n"
1628                + Bundle.getMessage("ConfirmDelete2"), Bundle.getMessage("ConfirmDeleteTitle"),
1629                JmriJOptionPane.OK_CANCEL_OPTION,
1630                JmriJOptionPane.WARNING_MESSAGE)) {
1631
1632            // delete this node
1633            _memo.getTrafficController().deleteNode(deleteNodeAddress);
1634            nodeTableModel.removeRow(selectedTableRow);
1635
1636            initializeNodes();
1637
1638            // provide user feedback
1639            resetNotes();
1640            statusText1.setText(Bundle.getMessage("FeedBackDelete") + " " + Integer.toString(deleteNodeAddress));
1641            statusText1.setVisible(true);
1642            errorInStatus1 = true;
1643            changedNode = true;
1644            deleteNodeButton.setVisible(false);
1645            doneNodeButton.setVisible(true);
1646            cancelNodeButton.setVisible(false);
1647            statusText2.setVisible(false);
1648        } else {
1649            // reset as needed
1650            resetNotes();
1651        }
1652    }
1653
1654    /**
1655     * Set up delete node window.
1656     *
1657     * @param nodeAddr the node address
1658     */
1659    public void deleteNodeButtonActionPerformed(int nodeAddr) {
1660        // Set up static node address
1661        nodeAddress = nodeAddr;
1662        nodeAddrStatic.setText(Integer.toString(nodeAddress));
1663        nodeAddrField.setVisible(false);
1664        nodeAddrStatic.setVisible(true);
1665
1666        // Find Serial Node address
1667        if (nodeAddress < 0) {
1668            log.info("nodeAddress < 0");
1669            return;
1670        }
1671
1672        // get the SerialNode corresponding to this node address
1673        curNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(nodeAddress);
1674        deleteNodeAddress = nodeAddr;
1675        // Load the node data into the window
1676
1677        setupNodeInformationWindow(nodeAddress);
1678
1679        // get the node corresponding to this node address
1680        if (curNode == null) {
1681            statusText1.setText(Bundle.getMessage("Error4"));
1682            statusText1.setVisible(true);
1683            errorInStatus1 = true;
1684            resetNotes2();
1685        } else {
1686            statusText1.setText(Bundle.getMessage("NotesDel3"));
1687            statusText1.setVisible(true);
1688        }
1689    }
1690
1691    /**
1692     * Handle done button clicked.
1693     */
1694    public void doneNodeButtonActionPerformed() {
1695        if (editMode) {
1696            // Reset
1697            editMode = false;
1698            curNode = null;
1699            // Switch buttons
1700            addNodeButton.setVisible(false);
1701            editNodeButton.setVisible(false);
1702            deleteNodeButton.setVisible(false);
1703            doneNodeButton.setVisible(true);
1704            updateNodeButton.setVisible(false);
1705            cancelNodeButton.setVisible(false);
1706            nodeAddrField.setVisible(false);
1707            nodeAddrStatic.setVisible(false);
1708        }
1709        if (changedNode) {
1710            JmriJOptionPane.showMessageDialog(this,
1711                    Bundle.getMessage("Reminder1") + "\n" + Bundle.getMessage("Reminder2"),
1712                    Bundle.getMessage("ReminderTitle"),
1713                    JmriJOptionPane.INFORMATION_MESSAGE);
1714        }
1715        changedNode = false;
1716
1717        setVisible(false);
1718        dispose();
1719    }
1720
1721    /**
1722     * Handle cancel button clicked.
1723     */
1724    public void cancelNodeButtonActionPerformed() {
1725        // Reset
1726        editMode = false;
1727        changedNode = false;
1728        curNode = null;
1729        // Switch buttons
1730        addNodeButton.setVisible(false);
1731        editNodeButton.setVisible(false);
1732        deleteNodeButton.setVisible(false);
1733        doneNodeButton.setVisible(false);
1734        updateNodeButton.setVisible(false);
1735        cancelNodeButton.setVisible(false);
1736        // make node address editable again
1737        nodeAddrField.setVisible(false);
1738        nodeAddrStatic.setVisible(false);
1739        // refresh notes panel
1740        // statusText1.setText(stdStatus1);
1741        // statusText2.setText(stdStatus2);
1742        // statusText3.setText(stdStatus3);
1743        setVisible(false);
1744        dispose();
1745    }
1746
1747    @Override
1748    public void windowClosing(java.awt.event.WindowEvent e) {
1749        doneNodeButtonActionPerformed();
1750        super.windowClosing(e);
1751    }
1752
1753    /**
1754     * Set the node parameters by type. Some parameters are specific to a
1755     * particular node type.
1756     */
1757    void setNodeParameters() {
1758        // receive delay is common for all node types
1759        int numInput = 0;
1760        int numOutput = 0;
1761        curNode.setTransmissionDelay(receiveDelay);
1762
1763        // pulse width is common for all node types
1764        curNode.setPulseWidth(pulseWidth);
1765
1766        // continue in a node specific way
1767        switch (nodeType) {
1768
1769            // SMINI
1770            case SerialNode.SMINI:
1771
1772                // Note: most parameters are set by default on creation
1773                int numSet = 0;
1774                // Configure 2-lead oscillating searchlights - first clear unneeded searchlights
1775                for (int j = 0; j < 47; j++) {
1776                    if (curNode.isSearchLightBit(j)) {
1777                        if (!firstSearchlight[j]) {
1778                            // this is the first bit of a deleted searchlight - clear it
1779                            curNode.clear2LeadSearchLight(j);
1780                            // skip over the second bit of the cleared searchlight
1781                            j++;
1782                        } else {
1783                            // this is the first bit of a kept searchlight - skip second bit
1784                            j++;
1785                        }
1786                    }
1787                }
1788                // Add needed searchlights that are not already configured
1789                for (int i = 0; i < 47; i++) {
1790                    if (firstSearchlight[i]) {
1791                        if (!curNode.isSearchLightBit(i)) {
1792                            // this is the first bit of an added searchlight
1793                            curNode.set2LeadSearchLight(i);
1794                        }
1795                        numSet++;
1796                    }
1797                }
1798                // consistency check
1799                if (numSet != num2LSearchLights) {
1800                    log.error("Inconsistent numbers of 2-lead searchlights. numSet = {}, num2LSearchLights = {}", Integer.toString(numSet), Integer.toString(num2LSearchLights));
1801                }
1802                break;
1803
1804            // USIC/SUSIC
1805            case SerialNode.USIC_SUSIC:
1806                // set number of bits per card
1807                curNode.setNumBitsPerCard(bitsPerCard);
1808                // configure the input/output cards
1809                numInput = 0;
1810                numOutput = 0;
1811                for (int i = 0; i < 64; i++) {
1812                    if ("No Card".equals(cardType[i])) {
1813                        curNode.setCardTypeByAddress(i, SerialNode.NO_CARD);
1814                    } else if ("Input Card".equals(cardType[i])) {
1815                        curNode.setCardTypeByAddress(i, SerialNode.INPUT_CARD);
1816                        numInput++;
1817                    } else if ("Output Card".equals(cardType[i])) {
1818                        curNode.setCardTypeByAddress(i, SerialNode.OUTPUT_CARD);
1819                        numOutput++;
1820                    } else {
1821                        log.error("Unexpected card type - {}", cardType[i]);
1822                    }
1823                }
1824                // consistency check
1825                if (numCards != (numOutput + numInput)) {
1826                    log.error("Inconsistent numbers of cards - setNodeParameters.");
1827                }
1828
1829                // Force created node to be polled as the default
1830                curNode.setOptNet_AUTOPOLL(1);
1831                break;
1832
1833            // CPNODE
1834            case SerialNode.CPNODE:
1835                // set number of bits per card
1836                bitsPerCard = 8;
1837                curNode.setNumBitsPerCard(bitsPerCard);
1838                numInput = 2;
1839                numOutput = 2;
1840                // configure the input/output cards
1841                for (int i = 4; i < 64; i++) // Skip the onboard bytes
1842                {
1843                    if ("No Card".equals(cardType[i])) {
1844                        curNode.setCardTypeByAddress(i, SerialNode.NO_CARD);
1845                    } else if ("Input Card".equals(cardType[i])) {
1846                        curNode.setCardTypeByAddress(i, SerialNode.INPUT_CARD);
1847                        numInput++;
1848                    } else if ("Output Card".equals(cardType[i])) {
1849                        curNode.setCardTypeByAddress(i, SerialNode.OUTPUT_CARD);
1850                        numOutput++;
1851                    } else {
1852                        log.error("Unexpected card type - {}", cardType[i]);
1853                    }
1854                }
1855
1856                // Set the node option bits.  Some are moved from the CMRInet options
1857                curNode.setOptNet_AUTOPOLL(cbx_cmrinetopt_AUTOPOLL.isSelected() ? 1 : 0);
1858
1859                curNode.setOptNode_SENDEOT(cbx_cpnodeopt_SENDEOT.isSelected() ? 1 : 0);
1860                curNode.setOptNode_USECMRIX(cbx_cmrinetopt_USECMRIX.isSelected() ? 1 : 0); // Copy from CMRInet
1861                curNode.setOptNode_USEBCC(cbx_cmrinetopt_USEBCC.isSelected() ? 1 : 0);     // Copy from CMRInet
1862                curNode.setOptNode_BIT8(cbx_cpnodeopt_BIT8.isSelected() ? 1 : 0);
1863                curNode.setOptNode_BIT15(cbx_cpnodeopt_BIT15.isSelected() ? 1 : 0);
1864
1865                break;
1866
1867            // CPMEGA
1868            case SerialNode.CPMEGA:
1869                // set number of bits per card
1870                bitsPerCard = 8;
1871                curNode.setNumBitsPerCard(bitsPerCard);
1872                numInput = 0;
1873                numOutput = 0;
1874
1875                for (int i = 0; i < 8; i++) // Pick up the onboard bytes
1876                {
1877                    if ("No Card".equals(onBoardType[i])) {
1878                        curNode.setCardTypeByAddress(i, SerialNode.NO_CARD);
1879                    } else if ("Input Card".equals(onBoardType[i])) {
1880                        curNode.setCardTypeByAddress(i, SerialNode.INPUT_CARD);
1881                        numInput++;
1882                    } else if ("Output Card".equals(onBoardType[i])) {
1883                        curNode.setCardTypeByAddress(i, SerialNode.OUTPUT_CARD);
1884                        numOutput++;
1885                    } else {
1886                        log.error("Unexpected card type - {}", onBoardType[i]);
1887                    }
1888                }
1889
1890                // configure the IOX cards
1891                for (int i = 8; i < 64; i++) // Skip the onboard bytes
1892                {
1893                    if ("No Card".equals(cardType[i])) {
1894                        curNode.setCardTypeByAddress(i, SerialNode.NO_CARD);
1895                    } else if ("Input Card".equals(cardType[i])) {
1896                        curNode.setCardTypeByAddress(i, SerialNode.INPUT_CARD);
1897                        numInput++;
1898                    } else if ("Output Card".equals(cardType[i])) {
1899                        curNode.setCardTypeByAddress(i, SerialNode.OUTPUT_CARD);
1900                        numOutput++;
1901                    } else {
1902                        log.error("Unexpected card type - {}", cardType[i]);
1903                    }
1904                }
1905
1906                // Set the node option bits.  Some are moved from the CMRInet options
1907                curNode.setOptNet_AUTOPOLL(1);  // Default node to be polled
1908
1909                curNode.setOptNode_SENDEOT(cbx_cpnodeopt_SENDEOT.isSelected() ? 1 : 0);
1910                curNode.setOptNode_USECMRIX(cbx_cmrinetopt_USECMRIX.isSelected() ? 1 : 0); // Copy from CMRInet
1911                curNode.setOptNode_USEBCC(cbx_cmrinetopt_USEBCC.isSelected() ? 1 : 0);     // Copy from CMRInet
1912                curNode.setOptNode_BIT8(cbx_cpnodeopt_BIT8.isSelected() ? 1 : 0);
1913                curNode.setOptNode_BIT15(cbx_cpnodeopt_BIT15.isSelected() ? 1 : 0);
1914
1915                break;
1916
1917            // ESP32Node -- no onboard bytes at all (cpNodeOnboard=0, see the
1918            // ActionListener above), so every one of the 16 cards comes from
1919            // cardType[] with no skip, unlike CPNODE (skips 4) or CPMEGA
1920            // (onboard bytes handled separately, IOX skips 8).
1921            case SerialNode.ESP32NODE:
1922                // set number of bits per card
1923                bitsPerCard = 8;
1924                curNode.setNumBitsPerCard(bitsPerCard);
1925                numInput = 0;
1926                numOutput = 0;
1927
1928                for (int i = 0; i < 64; i++) {
1929                    if ("No Card".equals(cardType[i])) {
1930                        curNode.setCardTypeByAddress(i, SerialNode.NO_CARD);
1931                    } else if ("Input Card".equals(cardType[i])) {
1932                        curNode.setCardTypeByAddress(i, SerialNode.INPUT_CARD);
1933                        numInput++;
1934                    } else if ("Output Card".equals(cardType[i])) {
1935                        curNode.setCardTypeByAddress(i, SerialNode.OUTPUT_CARD);
1936                        numOutput++;
1937                    } else {
1938                        log.error("Unexpected card type - {}", cardType[i]);
1939                    }
1940                }
1941
1942                // Set the node option bits.  Some are moved from the CMRInet options
1943                curNode.setOptNet_AUTOPOLL(1);  // Default node to be polled
1944
1945                curNode.setOptNode_SENDEOT(cbx_cpnodeopt_SENDEOT.isSelected() ? 1 : 0);
1946                curNode.setOptNode_USECMRIX(cbx_cmrinetopt_USECMRIX.isSelected() ? 1 : 0); // Copy from CMRInet
1947                curNode.setOptNode_USEBCC(cbx_cmrinetopt_USEBCC.isSelected() ? 1 : 0);     // Copy from CMRInet
1948                curNode.setOptNode_BIT8(cbx_cpnodeopt_BIT8.isSelected() ? 1 : 0);
1949                curNode.setOptNode_BIT15(cbx_cpnodeopt_BIT15.isSelected() ? 1 : 0);
1950
1951                break;
1952
1953            default:
1954                log.error("Unexpected node type in setNodeParameters- {}", Integer.toString(nodeType));
1955                break;
1956        }
1957
1958        // Set the node description for all types
1959        curNode.setcmriNodeDesc(nodeDescription.getText());
1960
1961        // Cause reinitialization of this Node to reflect these parameters
1962        _memo.getTrafficController().initializeSerialNode(curNode);
1963    }
1964
1965    /**
1966     * Reset the notes error after error display.
1967     */
1968    private void resetNotes() {
1969        if (errorInStatus1) {
1970            if (editMode) {
1971                statusText1.setText(editStatus1);
1972            } else {
1973                //  statusText1.setText(stdStatus1);
1974            }
1975            errorInStatus1 = false;
1976        }
1977        resetNotes2();
1978    }
1979
1980    /**
1981     * Reset the second line of Notes area.
1982     */
1983    private void resetNotes2() {
1984        if (errorInStatus2) {
1985            if (editMode) {
1986                statusText1.setText(editStatus2);
1987            } else {
1988                //                statusText2.setText(stdStatus2);
1989            }
1990            errorInStatus2 = false;
1991        }
1992    }
1993
1994    /**
1995     * Read node address and check for legal range. Sets the error message in
1996     * {@link #statusText1} if not legal.
1997     *
1998     * @return A node address in the range 0-127 if legal; -1 if not legal
1999     */
2000    private int readNodeAddress() {
2001        int addr = -1;
2002        try {
2003            addr = Integer.parseInt(nodeAddrField.getText());
2004        } catch (NumberFormatException e) {
2005            statusText1.setText(Bundle.getMessage("Error5"));
2006            statusText1.setVisible(true);
2007            errorInStatus1 = true;
2008            resetNotes2();
2009            return -1;
2010        }
2011
2012        if ((addr < 0) || (addr > 127)) {
2013//            statusText1.setText(Bundle.getMessage("Error6"));
2014//            statusText1.setVisible(true);
2015            errorInStatus1 = true;
2016            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error6"), "", JmriJOptionPane.ERROR_MESSAGE);
2017            resetNotes2();
2018            return -1;
2019        }
2020        return (addr);
2021    }
2022
2023    /**
2024     * Read receive delay from window. If an error is detected, a suitable error
2025     * message is placed in the Notes area.
2026     *
2027     * @return true if successful; false otherwise
2028     */
2029    protected boolean readReceiveDelay() {
2030        // get the transmission delay
2031        try {
2032            receiveDelay = Integer.parseInt(receiveDelayField.getText());
2033        } catch (NumberFormatException e) {
2034            statusText1.setText(Bundle.getMessage("Error7"));
2035            statusText1.setVisible(true);
2036            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error7"),
2037                "", JmriJOptionPane.ERROR_MESSAGE);
2038            receiveDelay = 0;
2039            errorInStatus1 = true;
2040            resetNotes2();
2041            return (false);
2042        }
2043        if (receiveDelay < 0) {
2044            statusText1.setText(Bundle.getMessage("Error8"));
2045            statusText1.setVisible(true);
2046            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error8"),
2047                "", JmriJOptionPane.ERROR_MESSAGE);
2048            receiveDelay = 0;
2049            errorInStatus1 = true;
2050            resetNotes2();
2051            return (false);
2052        }
2053        if (receiveDelay > 65535) {
2054            statusText1.setText(Bundle.getMessage("Error9"));
2055            statusText1.setVisible(true);
2056            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error9"),
2057                "", JmriJOptionPane.ERROR_MESSAGE);
2058            receiveDelay = 0;
2059            errorInStatus1 = true;
2060            resetNotes2();
2061            return (false);
2062        }
2063        // successful
2064        return true;
2065    }
2066
2067    /**
2068     * Read pulse width from window. If an error is detected, a suitable error
2069     * message is placed in the Notes area.
2070     *
2071     * @return true if successful; false otherwise
2072     */
2073    protected boolean readPulseWidth() {
2074        // get the pulse width
2075        try {
2076            pulseWidth = Integer.parseInt(pulseWidthField.getText());
2077        } catch (NumberFormatException e) {
2078            statusText1.setText(Bundle.getMessage("Error18"));
2079            statusText1.setVisible(true);
2080            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error18"),
2081                "", JmriJOptionPane.ERROR_MESSAGE);
2082            pulseWidth = 500;
2083            errorInStatus1 = true;
2084            resetNotes2();
2085            return (false);
2086        }
2087        if (pulseWidth < 100) {
2088            statusText1.setText(Bundle.getMessage("Error16"));
2089            statusText1.setVisible(true);
2090            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error16"),
2091                "", JmriJOptionPane.ERROR_MESSAGE);
2092            pulseWidth = 100;
2093            pulseWidthField.setText(Integer.toString(pulseWidth));
2094            errorInStatus1 = true;
2095            resetNotes2();
2096            return (false);
2097        }
2098        if (pulseWidth > 10000) {
2099            statusText1.setText(Bundle.getMessage("Error17"));
2100            statusText1.setVisible(true);
2101            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error17"),
2102                "", JmriJOptionPane.ERROR_MESSAGE);
2103            pulseWidth = 500;
2104            pulseWidthField.setText(Integer.toString(pulseWidth));
2105            errorInStatus1 = true;
2106            resetNotes2();
2107            return (false);
2108        }
2109        // successful
2110        return true;
2111    }
2112
2113    /**
2114     * Check for consistency errors by node type. If an error is detected, a
2115     * suitable error message is placed in the Notes area.
2116     *
2117     * @return true if successful; false otherwise
2118     */
2119    protected boolean checkConsistency() {
2120        switch (nodeType) {
2121            case SerialNode.SMINI:
2122                // ensure that number of searchlight bits is consistent
2123                int numBits = 0;
2124                for (int i = 0; i < 48; i++) {
2125                    if (searchlightBits[i]) {
2126                        numBits++;
2127                    }
2128                }
2129                if ((2 * num2LSearchLights) != numBits) {
2130                    statusText1.setText(Bundle.getMessage("Error10"));
2131                    statusText1.setVisible(true);
2132                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error10"),
2133                        "", JmriJOptionPane.ERROR_MESSAGE);
2134                    errorInStatus1 = true;
2135                    resetNotes2();
2136                    return (false);
2137                }
2138                break;
2139            case SerialNode.USIC_SUSIC:
2140                // ensure that at least one card is defined
2141                numCards = 0;
2142                boolean atNoCard = false;
2143                for (int i = 0; i < 64; i++) {
2144                    if ((cardType[i].equals(Bundle.getMessage("CardTypeOutput")))
2145                            || (cardType[i].equals(Bundle.getMessage("CardTypeInput")))) {
2146                        if (atNoCard) {
2147                            // gap error
2148                            statusText1.setText(Bundle.getMessage("Error11"));
2149                            statusText1.setVisible(true);
2150                            statusText2.setText(Bundle.getMessage("Error12"));
2151                            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error11") + Bundle.getMessage("Error12"),
2152                                "", JmriJOptionPane.ERROR_MESSAGE);
2153                            errorInStatus1 = true;
2154                            errorInStatus2 = true;
2155                            return (false);
2156                        } else {
2157                            numCards++;
2158                        }
2159                    } else if (cardType[i].equals(Bundle.getMessage("CardTypeNone"))) {
2160                        atNoCard = true;
2161                    }
2162                }
2163                // ensure that at least one card has been defined
2164                if (numCards <= 0) {
2165                    // no card error
2166                    statusText1.setText(Bundle.getMessage("Error13"));
2167                    statusText2.setText(Bundle.getMessage("Error14"));
2168                    statusText1.setVisible(true);
2169                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error13") + Bundle.getMessage("Error14"),
2170                        "", JmriJOptionPane.ERROR_MESSAGE);
2171                    errorInStatus1 = true;
2172                    errorInStatus2 = true;
2173                    return (false);
2174                }
2175                // check that card size is 24 or 32 bit
2176                if ((bitsPerCard != 24) && (bitsPerCard != 32)) {
2177                    // card size error
2178                    statusText1.setText(Bundle.getMessage("Error15"));
2179                    statusText1.setVisible(true);
2180                    errorInStatus1 = true;
2181                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("Error15"),
2182                        "", JmriJOptionPane.ERROR_MESSAGE);
2183                    resetNotes2();
2184                    return (false);
2185                }
2186                // further checking if in Edit mode
2187                if (editMode) {
2188                    // get pre-edit numbers of cards
2189                    int numOutput = curNode.numOutputCards();
2190                    int numInput = curNode.numInputCards();
2191                    // will the number of cards be reduced by this edit?
2192                    if (numCards < (numOutput + numInput)) {
2193                        if ( JmriJOptionPane.YES_OPTION != JmriJOptionPane.showConfirmDialog(this,
2194                                Bundle.getMessage("ConfirmUpdate1") + "\n"
2195                                + Bundle.getMessage("ConfirmUpdate2") + "\n"
2196                                + Bundle.getMessage("ConfirmUpdate3"),
2197                                Bundle.getMessage("ConfirmUpdateTitle"),
2198                                JmriJOptionPane.YES_NO_OPTION,
2199                                JmriJOptionPane.WARNING_MESSAGE)) {
2200                            // user did not click yes - cancel the update
2201                            return (false);
2202                        }
2203                    }
2204                }
2205                break;
2206            case SerialNode.CPNODE:
2207                for (int j = 0; j < 64; j++) {
2208                    if ((cardType[j].equals(Bundle.getMessage("CardTypeOutput")))
2209                            || (cardType[j].equals(Bundle.getMessage("CardTypeInput")))) {
2210                        numCards++;
2211                    }
2212                }
2213                break;
2214            case SerialNode.CPMEGA:
2215                for (int j = 0; j < 64; j++) {
2216                    if ((cardType[j].equals(Bundle.getMessage("CardTypeOutput")))
2217                            || (cardType[j].equals(Bundle.getMessage("CardTypeInput")))) {
2218                        numCards++;
2219                    }
2220                }
2221                break;
2222            case SerialNode.ESP32NODE:
2223                for (int j = 0; j < 64; j++) {
2224                    if ((cardType[j].equals(Bundle.getMessage("CardTypeOutput")))
2225                            || (cardType[j].equals(Bundle.getMessage("CardTypeInput")))) {
2226                        numCards++;
2227                    }
2228                }
2229                break;
2230
2231            // here add code for other types of nodes
2232            default:
2233                log.warn("Unexpected node type - {}", nodeType);
2234                break;
2235        }
2236        return true;
2237    }
2238
2239    /**
2240     * Set up table for selecting card type by address for USIC_SUSIC nodes
2241     */
2242    public class CardConfigModel extends AbstractTableModel {
2243
2244        @Override
2245        public String getColumnName(int c) {
2246            return cardConfigColumnNames[c];
2247        }
2248
2249        @Override
2250        public Class<?> getColumnClass(int c) {
2251            return String.class;
2252        }
2253
2254        @Override
2255        public int getColumnCount() {
2256            return 2;
2257        }
2258
2259        @Override
2260        public int getRowCount() {
2261            return 64;
2262        }
2263
2264        @Override
2265        public Object getValueAt(int r, int c) {
2266            if (c == 0) {
2267                return "       " + Integer.toString(r);
2268            } else if (c == 1) {
2269                return "  " + cardType[r];
2270            }
2271            return "";
2272        }
2273
2274        @Override
2275        public void setValueAt(Object type, int r, int c) {
2276            if (c == 1) {
2277                cardType[r] = (String) type;
2278            }
2279        }
2280
2281        @Override
2282        public boolean isCellEditable(int r, int c) {
2283            return (c == 1);
2284        }
2285
2286        public static final int ADDRESS_COLUMN = 0;
2287        public static final int TYPE_COLUMN = 1;
2288    }
2289    private final String[] cardConfigColumnNames = {
2290        Bundle.getMessage("HeadingCardAddress"),
2291        Bundle.getMessage("HeadingCardType")
2292    };
2293    private final String[] cardType = new String[64];
2294    private final String[] onBoardType = new String[16];
2295
2296    /**
2297     * Set up model for SMINI table for designating oscillating 2-lead
2298     * searchlights
2299     */
2300    public class SearchlightConfigModel extends AbstractTableModel {
2301
2302        @Override
2303        public String getColumnName(int c) {
2304            return searchlightConfigColumnNames[c];
2305        }
2306
2307        @Override
2308        public Class<?> getColumnClass(int c) {
2309            if (c > 0) {
2310                return Boolean.class;
2311            } else {
2312                return String.class;
2313            }
2314        }
2315
2316        @Override
2317        public int getColumnCount() {
2318            return 9;
2319        }
2320
2321        @Override
2322        public int getRowCount() {
2323            return 6;
2324        }
2325
2326        @Override
2327        public Object getValueAt(int r, int c) {
2328            if (c == 0) {
2329                switch (r) {
2330                    case 0:
2331                        return ("Card 0 Port A");
2332                    case 1:
2333                        return ("Card 0 Port B");
2334                    case 2:
2335                        return ("Card 0 Port C");
2336                    case 3:
2337                        return ("Card 1 Port A");
2338                    case 4:
2339                        return ("Card 1 Port B");
2340                    case 5:
2341                        return ("Card 1 Port C");
2342                    default:
2343                        return ("");
2344                }
2345            } else {
2346                int index = (r * 8) + (c - 1);
2347                return searchlightBits[index];
2348            }
2349        }
2350
2351        @Override
2352        public void setValueAt(Object type, int r, int c) {
2353            if (c > 0) {
2354                int index = (r * 8) + (c - 1);
2355                if (!((Boolean) type)) {
2356                    searchlightBits[index] = false;
2357                    if (firstSearchlight[index]) {
2358                        searchlightBits[index + 1] = false;
2359                        firstSearchlight[index] = false;
2360                    } else {
2361                        searchlightBits[index - 1] = false;
2362                        firstSearchlight[index - 1] = false;
2363                    }
2364                    num2LSearchLights--;
2365                } else {
2366                    if (index < 47) {
2367                        if (!searchlightBits[index] && !searchlightBits[index + 1]) {
2368                            searchlightBits[index] = true;
2369                            searchlightBits[index + 1] = true;
2370                            firstSearchlight[index] = true;
2371                            firstSearchlight[index + 1] = false;
2372                            if (index > 0) {
2373                                firstSearchlight[index - 1] = false;
2374                            }
2375                            num2LSearchLights++;
2376                        }
2377                    }
2378                }
2379                panel2a.setVisible(false);
2380                panel2a.setVisible(true);
2381            }
2382        }
2383
2384        @Override
2385        public boolean isCellEditable(int r, int c) {
2386            return (c != 0);
2387        }
2388
2389        public static final int PORT_COLUMN = 0;
2390    }
2391    private final String[] searchlightConfigColumnNames = {Bundle.getMessage("HeadingPort"), "0", "1", "2", "3", "4", "5", "6", "7"};
2392    private final boolean[] searchlightBits = new boolean[48];   // true if this bit is a searchlight bit
2393    private final boolean[] firstSearchlight = new boolean[48];  // true if first of a pair of searchlight bits
2394
2395    /**
2396     * Handles checkboxes for cpNode options
2397     */
2398    private class HandlerClass implements ItemListener {
2399
2400        @Override
2401        public void itemStateChanged(ItemEvent e) {
2402            JCheckBox checkbox = (JCheckBox) e.getSource();
2403
2404            if (checkbox == cbx_cmrinetopt_AUTOPOLL) {
2405                curNode.setCMRInetOpts(SerialNode.optbitNet_AUTOPOLL, (cbx_cmrinetopt_AUTOPOLL.isSelected() ? 1 : 0));
2406            } else if (checkbox == cbx_cmrinetopt_USECMRIX) {
2407                curNode.setCMRInetOpts(SerialNode.optbitNet_USECMRIX, (cbx_cmrinetopt_USECMRIX.isSelected() ? 1 : 0));
2408                curNode.setcpnodeOpts(SerialNode.optbitNet_USECMRIX, curNode.getCMRInetOpts(SerialNode.optbitNet_USECMRIX));
2409            } else if (checkbox == cbx_cmrinetopt_USEBCC) {
2410                curNode.setCMRInetOpts(SerialNode.optbitNet_USEBCC, (cbx_cmrinetopt_USEBCC.isSelected() ? 1 : 0));
2411                curNode.setcpnodeOpts(SerialNode.optbitNet_USEBCC, curNode.getCMRInetOpts(SerialNode.optbitNet_USEBCC));
2412            } else if (checkbox == cbx_cmrinetopt_BIT8) {
2413                curNode.setCMRInetOpts(SerialNode.optbitNet_BIT8, (cbx_cmrinetopt_BIT8.isSelected() ? 1 : 0));
2414            } else if (checkbox == cbx_cmrinetopt_BIT15) {
2415                curNode.setCMRInetOpts(SerialNode.optbitNet_BIT15, (cbx_cmrinetopt_BIT15.isSelected() ? 1 : 0));
2416            } else if (checkbox == cbx_cpnodeopt_SENDEOT) {
2417                curNode.setcpnodeOpts(SerialNode.optbitNode_SENDEOT, (cbx_cpnodeopt_SENDEOT.isSelected() ? 1 : 0));
2418            } else if (checkbox == cbx_cpnodeopt_BIT1) {
2419                cbx_cpnodeopt_BIT1.setSelected(false);
2420            } else if (checkbox == cbx_cpnodeopt_BIT2) {
2421                cbx_cpnodeopt_BIT2.setSelected(false);
2422            } else if (checkbox == cbx_cpnodeopt_BIT8) {
2423                curNode.setcpnodeOpts(SerialNode.optbitNode_BIT8, (cbx_cpnodeopt_BIT8.isSelected() ? 1 : 0));
2424            } else if (checkbox == cbx_cpnodeopt_BIT15) {
2425                curNode.setcpnodeOpts(SerialNode.optbitNode_BIT15, (cbx_cpnodeopt_BIT15.isSelected() ? 1 : 0));
2426            }
2427            changedNode = true;
2428        }
2429    }
2430
2431    /**
2432     * Set up table for selecting card type by address for CPNODE/CPMEGA nodes
2433     */
2434    public class CPnodeConfigModel extends AbstractTableModel {
2435
2436        @Override
2437        public String getColumnName(int c) {
2438            return cpnodeConfigColumnNames[c];
2439        }
2440
2441        @Override
2442        public Class<?> getColumnClass(int c) {
2443            return String.class;
2444        }
2445
2446        @Override
2447        public int getColumnCount() {
2448            return CARDTYPE_COLUMN+1;
2449        }
2450
2451        @Override
2452        public int getRowCount() {
2453            return 16;
2454        }
2455
2456        @Override
2457        public Object getValueAt(int r, int c) {
2458            String[] cdPort = {"  A", "  B"};
2459            String val = "     ";
2460            // ESP32Node has no onboard-reserved cards (unlike CPNODE's 2 onboard
2461            // OUTPUT bytes, which is why the diagnostic dialog and this label both
2462            // add 2 for CPNODE) -- but it's still numbered 1-based (Card 1 = first
2463            // real card) to match how people naturally count cards, hence +1 here
2464            // (cpNodeOnboard, the actual array-index skip used below for cardType[],
2465            // stays 0 for ESP32Node -- this offset is display-only).
2466            int cardLabelOffset = (nodeType == SerialNode.ESP32NODE) ? 1 : 2;
2467            switch (c) {
2468                case CARD_COLUMN:
2469                    val = Integer.toString(r+cardLabelOffset);
2470                    return "   " + val;
2471                case CARDNUM_COLUMN:
2472                    int i = r / 2;
2473                    if (r % 2 == 0) {
2474                        val = Integer.toHexString(0x20 + i) + " ";
2475                    }
2476                    return "    " + val + "     " + cdPort[(r % 2)];
2477                case CARDTYPE_COLUMN:
2478                    return " " + cardType[r + cpNodeOnboard];
2479                default:
2480                    return "";
2481            }
2482        }
2483
2484        @Override
2485        public void setValueAt(Object type, int r, int c) {
2486            if (c == CARDTYPE_COLUMN) {
2487                cardType[r + cpNodeOnboard] = (String) type;
2488            }
2489        }
2490
2491        @Override
2492        public boolean isCellEditable(int r, int c) {
2493            return (c == CARDTYPE_COLUMN);
2494        }
2495
2496        public static final int CARD_COLUMN = 0;
2497        public static final int CARDNUM_COLUMN = 1;
2498        public static final int CARDTYPE_COLUMN = 2;
2499    }
2500    private final String[] cpnodeConfigColumnNames = {"Card", "IOX Addr Port", "Port Type"};
2501
2502    /**
2503     * Set up table for selecting card type by address for CPNODE/CPMEGA nodes
2504     */
2505    public class OSnodeConfigModel extends AbstractTableModel {
2506
2507        @Override
2508        public String getColumnName(int c) {
2509            return osnodeConfigColumnNames[c];
2510        }
2511
2512        @Override
2513        public Class<?> getColumnClass(int c) {
2514            return String.class;
2515        }
2516
2517        @Override
2518        public int getColumnCount() {
2519            return 2;
2520        }
2521
2522        @Override
2523        public int getRowCount() {
2524            return 8;
2525        }
2526
2527        @Override
2528        public Object getValueAt(int r, int c) {
2529            switch (c) {
2530                case CARDNUM_COLUMN:
2531                    int i = r;
2532                    return "    " + i + " ";
2533                case CARDTYPE_COLUMN:
2534                    return " " + onBoardType[r];
2535                default:
2536                    return "";
2537            }
2538        }
2539
2540        @Override
2541        public void setValueAt(Object type, int r, int c) {
2542            if (c == CARDTYPE_COLUMN) {
2543                onBoardType[r] = (String) type;
2544            }
2545        }
2546
2547        @Override
2548        public boolean isCellEditable(int r, int c) {
2549            return (c == 1);
2550        }
2551
2552        public static final int CARDNUM_COLUMN = 0;
2553        public static final int CARDTYPE_COLUMN = 1;
2554    }
2555    private final String[] osnodeConfigColumnNames = {"  Byte ", "Port Type"};
2556
2557    private final int[] cpnodeOpts = new int[16];  // Local storage for node options
2558    private final int[] cmrinetOpts = new int[16];  // Local storage for node options
2559
2560    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NodeConfigManagerFrame.class);
2561
2562}