001package jmri.jmrix.dccpp.swing;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.Component;
006import java.awt.Dimension;
007import java.awt.BorderLayout;
008import java.awt.event.ActionEvent;
009import java.awt.event.ActionListener;
010import java.awt.event.KeyEvent;
011import java.util.ArrayList;
012import java.util.HashMap;
013import java.util.List;
014import java.util.Map;
015
016import javax.swing.BoxLayout;
017import javax.swing.DefaultCellEditor;
018import javax.swing.JButton;
019import javax.swing.JCheckBox;
020import javax.swing.JLabel;
021import javax.swing.JMenu;
022import javax.swing.JMenuBar;
023import javax.swing.JMenuItem;
024import javax.swing.JPanel;
025import javax.swing.JScrollPane;
026import javax.swing.JTabbedPane;
027import javax.swing.JTable;
028import javax.swing.RowSorter;
029import javax.swing.SortOrder;
030import javax.swing.UIManager;
031import javax.swing.event.ChangeEvent;
032import javax.swing.event.EventListenerList;
033import javax.swing.table.TableCellRenderer;
034import javax.swing.table.TableModel;
035import javax.swing.table.TableRowSorter;
036
037import jmri.jmrix.dccpp.DCCppCommandStation;
038import jmri.jmrix.dccpp.DCCppConstants;
039import jmri.jmrix.dccpp.DCCppListener;
040import jmri.jmrix.dccpp.DCCppMessage;
041import jmri.jmrix.dccpp.DCCppReply;
042import jmri.jmrix.dccpp.DCCppSystemConnectionMemo;
043import jmri.jmrix.dccpp.DCCppTrafficController;
044import jmri.util.JmriJFrame;
045import jmri.util.swing.JmriJOptionPane;
046
047/*
048 * <hr>
049 * This file is part of JMRI.
050 * <p>
051 * JMRI is free software; you can redistribute it and/or modify it under
052 * the terms of version 2 of the GNU General Public License as published
053 * by the Free Software Foundation. See the "COPYING" file for a copy
054 * of this license.
055 * <p>
056 * JMRI is distributed in the hope that it will be useful, but WITHOUT
057 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
058 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
059 * for more details.
060 *
061 * @author   Mark Underwood Copyright (C) 2011
062 */
063public class ConfigBaseStationFrame extends JmriJFrame implements DCCppListener {
064
065    // Map of Mnemonic KeyEvent values to GUI Components
066    private static final Map<String, Integer> Mnemonics = new HashMap<>();
067
068    static {
069        Mnemonics.put("SensorTab", KeyEvent.VK_E); // NOI18N
070        Mnemonics.put("DCCTurnoutTab", KeyEvent.VK_T); // NOI18N
071        Mnemonics.put("ServoTurnoutTab", KeyEvent.VK_R); // NOI18N
072        Mnemonics.put("VpinTurnoutTab", KeyEvent.VK_V); // NOI18N
073        Mnemonics.put("OutputTab", KeyEvent.VK_O); // NOI18N
074        Mnemonics.put("ButtonAdd", KeyEvent.VK_A); // NOI18N
075        Mnemonics.put("CloseButton", KeyEvent.VK_X); // NOI18N
076        Mnemonics.put("SaveButton", KeyEvent.VK_S); // NOI18N
077    }
078
079    protected EventListenerList listenerList = new javax.swing.event.EventListenerList();
080
081    private final DCCppTrafficController _tc;
082
083    private JTabbedPane tabbedPane;
084    private JLabel versionLabel = new JLabel("");
085
086    private SensorTableModel sensorModel;
087    private DccTurnoutTableModel dccTurnoutModel;
088    private ServoTurnoutTableModel servoTurnoutModel;
089    private VpinTurnoutTableModel  vpinTurnoutModel;
090    private OutputTableModel outputModel;
091    private JTable sensorTable;
092    private JTable dccTurnoutTable;
093    private JTable servoTurnoutTable;
094    private JTable vpinTurnoutTable;
095    private JTable outputTable;
096    private TableRowSorter<TableModel> sensorSorter;
097    private TableRowSorter<TableModel> dccTurnoutSorter;
098    private TableRowSorter<TableModel> servoTurnoutSorter;
099    private TableRowSorter<TableModel> vpinTurnoutSorter;
100    private TableRowSorter<TableModel> outputSorter;
101
102    private enum CurrentTab {
103        SENSOR, DCCTURNOUT, SERVOTURNOUT, VPINTURNOUT, OUTPUT
104    }
105    private CurrentTab cTab;
106
107    private DCCppSystemConnectionMemo _memo;
108
109    private static final int SENSOR_TAB_NUM     = 0;
110    private static final int DCCTURNOUT_TAB_NUM = 1;
111    private static final int SERVOTURNOUT_TAB_NUM=2;
112    private static final int VPINTURNOUT_TAB_NUM= 3;
113    private static final int OUTPUT_TAB_NUM     = 4;
114
115    @SuppressFBWarnings(value = "EI_EXPOSE_REP2",
116            justification = "2D array of different types passed as complex parameter. "
117            + "Better to switch to passing use-specific objects rather than "
118            + "papering this over with a deep copy of the arguments. "
119            + "In any case, there's no risk of exposure here.")
120    public ConfigBaseStationFrame(DCCppSystemConnectionMemo memo) {
121        super(false, false);
122        _memo = memo;
123        _tc = memo.getDCCppTrafficController();
124        initGui();
125    }
126
127    private void initGui() {
128
129        // NOTE: Look at jmri.jmrit.vsdecoder.swing.ManageLocationsFrame
130        // for how to add a tab for turnouts and other things.
131        this.setTitle(Bundle.getMessage("FieldManageBaseStationFrameTitle") + " (" + _memo.getSystemPrefix() + ")");
132        this.buildMenu();
133
134        //create Add, Save and Close buttons
135        JButton addButton = new JButton(Bundle.getMessage("ButtonAddX", Bundle.getMessage("BeanNameSensor")));
136        addButton.setToolTipText(Bundle.getMessage("ToolTipButtonMSFAdd"));
137        addButton.setMnemonic(Mnemonics.get("ButtonAdd")); // NOI18N
138        addButton.addActionListener((ActionEvent e) -> {
139            addButtonPressed(e);
140        });
141        JButton closeButton = new JButton(Bundle.getMessage("ButtonClose"));
142        closeButton.setToolTipText(Bundle.getMessage("ToolTipButtonClose"));
143        closeButton.setMnemonic(Mnemonics.get("CloseButton")); // NOI18N
144        closeButton.addActionListener((ActionEvent e) -> {
145            closeButtonPressed(e);
146        });
147        JButton saveButton = new JButton(Bundle.getMessage("ButtonSaveX", Bundle.getMessage("Sensors")));
148        saveButton.setToolTipText(Bundle.getMessage("ToolTipButtonMSFSave"));
149        saveButton.setMnemonic(Mnemonics.get("SaveButton")); // NOI18N
150        saveButton.addActionListener((ActionEvent e) -> {
151            saveButtonPressed(e);
152        });
153
154        //SENSOR TAB ---------------------
155        JScrollPane sensorScrollPanel = new JScrollPane();
156        sensorModel = new SensorTableModel();
157        sensorTable = new JTable(sensorModel);
158        sensorTable.setFillsViewportHeight(true);
159        sensorScrollPanel.getViewport().add(sensorTable);
160        sensorTable.setPreferredScrollableViewportSize(new Dimension(520, 200));
161        sensorTable.getColumn(Bundle.getMessage("ColumnDelete")).setCellRenderer(new ButtonRenderer());
162        sensorTable.removeColumn(sensorTable.getColumn("isNew"));
163        sensorTable.removeColumn(sensorTable.getColumn("isDirty"));
164        sensorTable.removeColumn(sensorTable.getColumn("isDelete"));
165        sensorTable.addMouseListener(new java.awt.event.MouseAdapter() {
166            @Override
167            public void mouseClicked(java.awt.event.MouseEvent evt) {
168                handleTableMouseClick(sensorTable, evt);
169            }
170        });
171        sensorTable.setAutoCreateRowSorter(true);
172        sensorSorter = new TableRowSorter<>(sensorTable.getModel());
173        sensorTable.setRowSorter(sensorSorter);
174        List<RowSorter.SortKey> sensorSortKeys = new ArrayList<>();
175        sensorSortKeys.add(new RowSorter.SortKey(sensorTable.getColumn(Bundle.getMessage("IDCol")).getModelIndex(), SortOrder.ASCENDING));
176        sensorSorter.setSortKeys(sensorSortKeys);
177        sensorSorter.sort();
178        sensorSorter.setSortable(sensorTable.getColumn(Bundle.getMessage("ColumnDelete")).getModelIndex(), false);
179
180        //TURNOUT TAB ---------------------
181        JScrollPane dccTurnoutScrollPanel = new JScrollPane();
182        dccTurnoutModel = new DccTurnoutTableModel();
183        dccTurnoutTable = new JTable(dccTurnoutModel);
184        dccTurnoutTable.setFillsViewportHeight(true);
185        dccTurnoutScrollPanel.getViewport().add(dccTurnoutTable);
186        dccTurnoutTable.setPreferredScrollableViewportSize(new Dimension(520, 200));
187        dccTurnoutTable.getColumn(Bundle.getMessage("ColumnDelete")).setCellRenderer(new ButtonRenderer());
188        dccTurnoutTable.removeColumn(dccTurnoutTable.getColumn("isNew"));
189        dccTurnoutTable.removeColumn(dccTurnoutTable.getColumn("isDirty"));
190        dccTurnoutTable.removeColumn(dccTurnoutTable.getColumn("isDelete"));
191        dccTurnoutTable.addMouseListener(new java.awt.event.MouseAdapter() {
192            @Override
193            public void mouseClicked(java.awt.event.MouseEvent evt) {
194                handleTableMouseClick(dccTurnoutTable, evt);
195            }
196        });
197        dccTurnoutTable.setAutoCreateRowSorter(true);
198        dccTurnoutSorter = new TableRowSorter<>(dccTurnoutTable.getModel());
199        dccTurnoutTable.setRowSorter(dccTurnoutSorter);
200        List<RowSorter.SortKey> dccTurnoutSortKeys = new ArrayList<>();
201        dccTurnoutSortKeys.add(new RowSorter.SortKey(dccTurnoutTable.getColumn(Bundle.getMessage("IDCol")).getModelIndex(), SortOrder.ASCENDING));
202        dccTurnoutSorter.setSortKeys(dccTurnoutSortKeys);
203        dccTurnoutSorter.setSortable(dccTurnoutTable.getColumn(Bundle.getMessage("ColumnDelete")).getModelIndex(), false);
204        dccTurnoutSorter.sort();
205
206        //SERVO TURNOUT TAB ---------------------
207        JScrollPane servoTurnoutScrollPanel = new JScrollPane();
208        servoTurnoutModel = new ServoTurnoutTableModel();
209        servoTurnoutTable = new JTable(servoTurnoutModel);
210        servoTurnoutTable.setFillsViewportHeight(true);
211        servoTurnoutScrollPanel.getViewport().add(servoTurnoutTable);
212        servoTurnoutTable.setPreferredScrollableViewportSize(new Dimension(520, 200));
213        servoTurnoutTable.getColumn(Bundle.getMessage("ColumnDelete")).setCellRenderer(new ButtonRenderer());
214        servoTurnoutTable.removeColumn(servoTurnoutTable.getColumn("isNew"));
215        servoTurnoutTable.removeColumn(servoTurnoutTable.getColumn("isDirty"));
216        servoTurnoutTable.removeColumn(servoTurnoutTable.getColumn("isDelete"));
217        servoTurnoutTable.addMouseListener(new java.awt.event.MouseAdapter() {
218            @Override
219            public void mouseClicked(java.awt.event.MouseEvent evt) {
220                handleTableMouseClick(servoTurnoutTable, evt);
221            }
222        });
223        servoTurnoutTable.setAutoCreateRowSorter(true);
224        servoTurnoutSorter = new TableRowSorter<>(servoTurnoutTable.getModel());
225        servoTurnoutTable.setRowSorter(servoTurnoutSorter);
226        List<RowSorter.SortKey> servoTurnoutSortKeys = new ArrayList<>();
227        servoTurnoutSortKeys.add(new RowSorter.SortKey(servoTurnoutTable.getColumn(Bundle.getMessage("IDCol")).getModelIndex(), SortOrder.ASCENDING));
228        servoTurnoutSorter.setSortKeys(servoTurnoutSortKeys);
229        servoTurnoutSorter.setSortable(servoTurnoutTable.getColumn(Bundle.getMessage("ColumnDelete")).getModelIndex(), false);
230        servoTurnoutSorter.sort();
231
232        //VPIN TURNOUT TAB ---------------------
233        JScrollPane vpinTurnoutScrollPanel = new JScrollPane();
234        vpinTurnoutModel = new  VpinTurnoutTableModel();
235        vpinTurnoutTable = new JTable(vpinTurnoutModel);
236        vpinTurnoutTable.setFillsViewportHeight(true);
237        vpinTurnoutScrollPanel.getViewport().add(vpinTurnoutTable);
238        vpinTurnoutTable.setPreferredScrollableViewportSize(new Dimension(520, 200));
239        vpinTurnoutTable.getColumn(Bundle.getMessage("ColumnDelete")).setCellRenderer(new ButtonRenderer());
240        vpinTurnoutTable.removeColumn(vpinTurnoutTable.getColumn("isNew"));
241        vpinTurnoutTable.removeColumn(vpinTurnoutTable.getColumn("isDirty"));
242        vpinTurnoutTable.removeColumn(vpinTurnoutTable.getColumn("isDelete"));
243        vpinTurnoutTable.addMouseListener(new java.awt.event.MouseAdapter() {
244            @Override
245            public void mouseClicked(java.awt.event.MouseEvent evt) {
246                handleTableMouseClick(vpinTurnoutTable, evt);
247            }
248        });
249        vpinTurnoutTable.setAutoCreateRowSorter(true);
250        vpinTurnoutSorter = new TableRowSorter<>(vpinTurnoutTable.getModel());
251        vpinTurnoutTable.setRowSorter(vpinTurnoutSorter);
252        List<RowSorter.SortKey> vpinTurnoutSortKeys = new ArrayList<>();
253        vpinTurnoutSortKeys.add(new RowSorter.SortKey(vpinTurnoutTable.getColumn(Bundle.getMessage("IDCol")).getModelIndex(), SortOrder.ASCENDING));
254        vpinTurnoutSorter.setSortKeys(vpinTurnoutSortKeys);
255        vpinTurnoutSorter.setSortable(vpinTurnoutTable.getColumn(Bundle.getMessage("ColumnDelete")).getModelIndex(), false);
256        vpinTurnoutSorter.sort();
257
258        //OUTPUT TAB ---------------------
259        JScrollPane outputScrollPanel = new JScrollPane();
260        outputModel = new OutputTableModel();
261        outputTable = new JTable(outputModel);
262        outputTable.setFillsViewportHeight(true);
263        outputScrollPanel.getViewport().add(outputTable);
264        outputTable.setPreferredScrollableViewportSize(new Dimension(520, 200));
265        outputTable.getColumn(Bundle.getMessage("ColumnDelete")).setCellRenderer(new ButtonRenderer());
266        outputTable.removeColumn(outputTable.getColumn("isNew"));
267        outputTable.removeColumn(outputTable.getColumn("isDirty"));
268        outputTable.removeColumn(outputTable.getColumn("isDelete"));
269        outputTable.addMouseListener(new java.awt.event.MouseAdapter() {
270            @Override
271            public void mouseClicked(java.awt.event.MouseEvent evt) {
272                handleTableMouseClick(outputTable, evt);
273            }
274        });
275        outputTable.setAutoCreateRowSorter(true);
276        outputSorter = new TableRowSorter<>(outputTable.getModel());
277        outputTable.setRowSorter(outputSorter);
278        List<RowSorter.SortKey> outputSortKeys = new ArrayList<>();
279        outputSortKeys.add(new RowSorter.SortKey(sensorTable.getColumn(Bundle.getMessage("IDCol")).getModelIndex(), SortOrder.ASCENDING));
280        outputSorter.setSortKeys(outputSortKeys);
281        outputSorter.setSortable(sensorTable.getColumn(Bundle.getMessage("ColumnDelete")).getModelIndex(), false);
282        outputSorter.sort();
283
284        // add the 5 tabs to the pane
285        tabbedPane = new JTabbedPane();
286        tabbedPane.addTab(Bundle.getMessage("Sensors"), sensorScrollPanel);
287        tabbedPane.setToolTipTextAt(SENSOR_TAB_NUM, Bundle.getMessage("ToolTipSensorTab"));
288        tabbedPane.setMnemonicAt(SENSOR_TAB_NUM, Mnemonics.get("SensorTab")); // NOI18N
289        tabbedPane.addTab(Bundle.getMessage("DCCTurnouts"), dccTurnoutScrollPanel);
290        tabbedPane.setToolTipTextAt(DCCTURNOUT_TAB_NUM, Bundle.getMessage("ToolTipDccTurnoutTab"));
291        tabbedPane.setMnemonicAt(DCCTURNOUT_TAB_NUM, Mnemonics.get("DCCTurnoutTab")); // NOI18N
292        tabbedPane.addTab(Bundle.getMessage("ServoTurnouts"), servoTurnoutScrollPanel);
293        tabbedPane.setToolTipTextAt(SERVOTURNOUT_TAB_NUM, Bundle.getMessage("ToolTipServoTurnoutTab"));
294        tabbedPane.setMnemonicAt(SERVOTURNOUT_TAB_NUM, Mnemonics.get("ServoTurnoutTab")); // NOI18N
295        tabbedPane.addTab(Bundle.getMessage("VpinTurnouts"), vpinTurnoutScrollPanel);
296        tabbedPane.setToolTipTextAt(VPINTURNOUT_TAB_NUM, Bundle.getMessage("ToolTipVpinTurnoutTab"));
297        tabbedPane.setMnemonicAt(VPINTURNOUT_TAB_NUM, Mnemonics.get("VpinTurnoutTab")); // NOI18N
298        tabbedPane.addTab(Bundle.getMessage("FieldOutputsTabTitle"), outputScrollPanel);
299        tabbedPane.setToolTipTextAt(OUTPUT_TAB_NUM, Bundle.getMessage("ToolTipOutputTab"));
300        tabbedPane.setMnemonicAt(OUTPUT_TAB_NUM, Mnemonics.get("OutputTab")); // NOI18N
301        cTab = CurrentTab.SENSOR;
302        tabbedPane.setSelectedIndex(0);
303        tabbedPane.addChangeListener((ChangeEvent e) -> {
304            switch (tabbedPane.getSelectedIndex()) { // set button text and tooltips for selected tabs
305                case 4:
306                    cTab = CurrentTab.OUTPUT;
307                    addButton.setText(Bundle.getMessage("ButtonAddX", Bundle.getMessage("Output")));
308                    addButton.setToolTipText(Bundle.getMessage("ToolTipButtonMOFAdd"));
309                    saveButton.setText(Bundle.getMessage("ButtonSaveX", Bundle.getMessage("FieldOutputsTabTitle")));
310                    saveButton.setToolTipText(Bundle.getMessage("ToolTipButtonMOFSave"));
311                    log.debug("Current Tab is: {}", tabbedPane.getSelectedIndex());
312                    break;
313                case 3:
314                    cTab = CurrentTab.VPINTURNOUT;
315                    addButton.setText(Bundle.getMessage("ButtonAddX", Bundle.getMessage("VpinTurnout")));
316                    addButton.setToolTipText(Bundle.getMessage("ToolTipButtonMTFAdd"));
317                    saveButton.setText(Bundle.getMessage("ButtonSaveX", Bundle.getMessage("VpinTurnouts")));
318                    saveButton.setToolTipText(Bundle.getMessage("ToolTipButtonMTFSave"));
319                    log.debug("Current Tab is: {}", tabbedPane.getSelectedIndex());
320                    break;
321                case 2:
322                    cTab = CurrentTab.SERVOTURNOUT;
323                    addButton.setText(Bundle.getMessage("ButtonAddX", Bundle.getMessage("ServoTurnout")));
324                    addButton.setToolTipText(Bundle.getMessage("ToolTipButtonMTFAdd"));
325                    saveButton.setText(Bundle.getMessage("ButtonSaveX", Bundle.getMessage("ServoTurnouts")));
326                    saveButton.setToolTipText(Bundle.getMessage("ToolTipButtonMTFSave"));
327                    log.debug("Current Tab is: {}", tabbedPane.getSelectedIndex());
328                    break;
329                case 1:
330                    cTab = CurrentTab.DCCTURNOUT;
331                    addButton.setText(Bundle.getMessage("ButtonAddX", Bundle.getMessage("DCCTurnout")));
332                    addButton.setToolTipText(Bundle.getMessage("ToolTipButtonMTFAdd"));
333                    saveButton.setText(Bundle.getMessage("ButtonSaveX", Bundle.getMessage("DCCTurnouts")));
334                    saveButton.setToolTipText(Bundle.getMessage("ToolTipButtonMTFSave"));
335                    log.debug("Current Tab is: {}", tabbedPane.getSelectedIndex());
336                    break;
337                case 0:
338                default:
339                    cTab = CurrentTab.SENSOR;
340                    addButton.setText(Bundle.getMessage("ButtonAddX", Bundle.getMessage("BeanNameSensor")));
341                    addButton.setToolTipText(Bundle.getMessage("ToolTipButtonMSFAdd"));
342                    saveButton.setText(Bundle.getMessage("ButtonSaveX", Bundle.getMessage("Sensors")));                    
343                    saveButton.setToolTipText(Bundle.getMessage("ToolTipButtonMSFSave"));
344                    log.debug("Current Tab is: {}", tabbedPane.getSelectedIndex());
345            }
346        });
347
348        //Create and add various parts to the window
349        JPanel bottomPanel = new JPanel(new BorderLayout());
350        JPanel bottomPanelLeft = new JPanel();
351        JPanel bottomPanelRight = new JPanel();
352
353        bottomPanelLeft.add(addButton);
354        bottomPanelLeft.add(saveButton);
355        bottomPanel.add(bottomPanelLeft, BorderLayout.WEST);
356
357        bottomPanelRight.add(versionLabel);
358        bottomPanelRight.add(closeButton);
359        bottomPanel.add(bottomPanelRight, BorderLayout.EAST);   
360
361        this.getContentPane().setLayout(new BoxLayout(this.getContentPane(), BoxLayout.Y_AXIS));
362        this.getContentPane().add(tabbedPane);
363        this.getContentPane().add(bottomPanel);
364        this.pack();
365        this.setVisible(true);
366    }
367
368    private void buildMenu() {
369        this.setJMenuBar(new JMenuBar()); //set up the menuBar for the window
370
371        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
372        this.getJMenuBar().add(fileMenu);
373
374        JMenu mSend = new JMenu(Bundle.getMessage("MenuSend"));
375        JMenuItem iRequestDefs = new JMenuItem(Bundle.getMessage("RequestDefs"));       
376        iRequestDefs.addActionListener(new ActionListener() {
377            @Override
378            public void actionPerformed(ActionEvent event) {
379                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.SENSOR_CMD)), null); 
380                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.TURNOUT_CMD)), null); 
381                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.OUTPUT_CMD)), null); 
382                _tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), null); 
383            }
384        });
385        mSend.add(iRequestDefs);
386
387        JMenuItem iRequestStates = new JMenuItem(Bundle.getMessage("RequestStates"));       
388        iRequestStates.addActionListener(new ActionListener() {
389            @Override
390            public void actionPerformed(ActionEvent event) {
391                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.READ_CS_STATUS)), null); 
392            }
393        });
394        mSend.add(iRequestStates);
395
396        JMenuItem iSaveToEeprom = new JMenuItem(Bundle.getMessage("SaveToEEPROM"));       
397        iSaveToEeprom.addActionListener(new ActionListener() {
398            @Override
399            public void actionPerformed(ActionEvent event) {
400                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.WRITE_TO_EEPROM_CMD)), null); 
401            }
402        });
403        mSend.add(iSaveToEeprom);
404
405        JMenuItem iEraseEeprom = new JMenuItem(Bundle.getMessage("ClearEEPROM"));       
406        iEraseEeprom.addActionListener(new ActionListener() {
407            @Override
408            public void actionPerformed(ActionEvent event) {
409                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.CLEAR_EEPROM_CMD)), null); 
410            }
411        });
412        mSend.add(iEraseEeprom);
413
414        JMenuItem iReadLocoId = new JMenuItem(Bundle.getMessage("ReadLocoId"));       
415        iReadLocoId.addActionListener(new ActionListener() {
416            @Override
417            public void actionPerformed(ActionEvent event) {
418                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.PROG_READ_CV)), null); 
419            }
420        });
421        mSend.add(iReadLocoId);
422
423        JMenuItem iTrackManagerCmd = new JMenuItem(Bundle.getMessage("TrackManagerCmd"));       
424        iTrackManagerCmd.addActionListener(new ActionListener() {
425            @Override
426            public void actionPerformed(ActionEvent event) {
427                _tc.sendDCCppMessage(DCCppMessage.makeTrackManagerRequestMsg(), null); 
428            }
429        });
430        mSend.add(iTrackManagerCmd);
431
432        this.getJMenuBar().add(mSend);
433
434        JMenu dccppMenu = new DCCppMenu(_memo);
435        dccppMenu.setText(Bundle.getMessage("MenuDCC++")); // always use generic text
436        this.getJMenuBar().add(dccppMenu);
437    }
438
439    // handle incoming object creation messages by adding them to the appropriate table
440    // handle incoming status message by disabling unsupported functions
441    @Override
442    public void message(DCCppReply r) {
443        // When we get a SensorDefReply message, add the
444        // sensor information to the data map for the model.
445        if (r.isSensorDefReply()) {
446            List<Object> v = new ArrayList<>();
447            v.add(r.getSensorDefNumInt());
448            v.add(r.getSensorDefPinInt());
449            v.add(r.getSensorDefPullupBool());
450            sensorModel.insertData(v, false);
451            sensorSorter.sort();
452        } else if (r.isTurnoutDefReply() || r.isTurnoutDefDCCReply()) {
453            List<Object> v = new ArrayList<>();
454            v.add(r.getTOIDInt());
455            v.add(r.getTurnoutDefAddrInt());
456            v.add(r.getTurnoutDefSubAddrInt());
457            dccTurnoutModel.insertData(v, false);
458            dccTurnoutSorter.sort();
459        } else if (r.isTurnoutDefServoReply()) {
460            List<Object> v = new ArrayList<>();
461            v.add(r.getTOIDInt());
462            v.add(r.getTOPinInt());
463            v.add(r.getTOThrownPositionInt());
464            v.add(r.getTOClosedPositionInt());
465            v.add(r.getTOProfileInt());
466            servoTurnoutModel.insertData(v, false);
467            servoTurnoutSorter.sort();
468        } else if (r.isTurnoutDefVpinReply()) {
469            List<Object> v = new ArrayList<>();
470            v.add(r.getTOIDInt());
471            v.add(r.getTOPinInt());
472            vpinTurnoutModel.insertData(v, false);
473            vpinTurnoutSorter.sort();
474        } else if (r.isOutputDefReply()) {
475            List<Object> v = new ArrayList<>();
476            v.add(r.getOutputNumInt());
477            v.add(r.getOutputListPinInt());
478            v.add((r.getOutputListIFlagInt() & 0x01) == 1); // (bool) Invert
479            v.add((r.getOutputListIFlagInt() & 0x02) == 2); // (bool) Restore State
480            v.add((r.getOutputListIFlagInt() & 0x04) == 4); // (bool) Force High
481            outputModel.insertData(v, false);
482            outputSorter.sort();
483        } else if (r.isStatusReply()) { 
484            DCCppCommandStation cs = _tc.getCommandStation(); 
485            //enable or disable some tabs based on support by command station
486            if (cs.isServoTurnoutCreationSupported()) {
487                tabbedPane.setEnabledAt(SERVOTURNOUT_TAB_NUM, true);
488                tabbedPane.setEnabledAt(VPINTURNOUT_TAB_NUM,  true);
489            } else {
490                tabbedPane.setEnabledAt(SERVOTURNOUT_TAB_NUM, false);
491                tabbedPane.setEnabledAt(VPINTURNOUT_TAB_NUM,  false);                
492            }
493            //populate the version line
494            String v = cs.getStationType() + " " + cs.getVersion() + " " + cs.getBuild(); 
495            if (v.length() > 40) {
496                v = ""; //don't try to show really long version strings here
497            }
498            versionLabel.setText(v);
499        }
500    }
501
502    @Override
503    public void message(DCCppMessage m) {
504        // Do nothing
505    }
506
507    @Override
508    public void notifyTimeout(DCCppMessage m) {
509        // Do nothing
510    }
511
512    /**
513     * Handle mouse clicks within a table.
514     * <p>
515     * This is currently the workings behind the "Delete" button in the table.
516     *
517     * @param table the table where the event occurred
518     * @param evt   the mouse click
519     */
520    private void handleTableMouseClick(JTable table, java.awt.event.MouseEvent evt) {
521        int row = table.rowAtPoint(evt.getPoint());
522        int col = table.columnAtPoint(evt.getPoint());
523        if (row < 0 || col < 0) {
524            return;
525        }
526        DCCppTableModel model = (DCCppTableModel) table.getModel();
527        if (col == table.convertColumnIndexToView(model.getDeleteColumn())) {
528            // This is a row delete action.  Handle it as such.
529            int sel = table.convertRowIndexToModel(row);
530            int idx = (int) model.getValueAt(sel, 0);
531            log.debug("idx = {}", sel);
532            int value = JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("DeleteWarningMessage", Integer.toString(idx)),
533                    Bundle.getMessage("WarningTitle"),
534                    JmriJOptionPane.OK_CANCEL_OPTION);
535            if (value == JmriJOptionPane.OK_OPTION) {
536               if (null != cTab) {
537                    switch (cTab) {
538                        case SENSOR:
539                            _tc.sendDCCppMessage(DCCppMessage.makeSensorDeleteMsg(idx), this);
540                            sensorModel.removeRow(sel);
541                            log.debug("Delete sensor {}", idx);
542                            break;
543                        case DCCTURNOUT:
544                            DCCppMessage m = new DCCppMessage("T " + Integer.toString(idx));
545                            _tc.sendDCCppMessage(m, this);
546                            log.debug("Sending: {}", m);
547                            dccTurnoutModel.removeRow(sel);
548                            break;
549                        case SERVOTURNOUT:
550                            m = new DCCppMessage("T " + Integer.toString(idx));
551                            _tc.sendDCCppMessage(m, this);
552                            log.debug("Sending: {}", m);
553                            servoTurnoutModel.removeRow(sel);
554                            break;
555                        case VPINTURNOUT:
556                            m = new DCCppMessage("T " + Integer.toString(idx));
557                            _tc.sendDCCppMessage(m, this);
558                            log.debug("Sending: {}", m);
559                            vpinTurnoutModel.removeRow(sel);
560                            break;
561                        case OUTPUT:
562                            _tc.sendDCCppMessage(DCCppMessage.makeOutputDeleteMsg(idx), this);
563                            outputModel.removeRow(sel);
564                            break;
565                        default:
566                            jmri.util.LoggingUtil.warnOnce(log, "Unexpected cTab value = {}", cTab);
567                            break;
568                   }
569                }
570            }
571
572        }
573    }
574
575    /**
576     * Responder for pressing the "Add" button. Response depends on which tab is
577     * active.
578     *
579     * @param e the press event
580     */
581    private void addButtonPressed(ActionEvent e) {
582        if (null != cTab) {
583            switch (cTab) {
584                case SENSOR: {
585                    List<Object> v = new ArrayList<>();
586                    v.add(0);     // Index
587                    v.add(0);     // Pin
588                    v.add(false); // Pullup
589                    sensorModel.insertData(v, true);
590                    break;
591                }
592                case DCCTURNOUT: {
593                    List<Object> v = new ArrayList<>();
594                    v.add(0); // ID
595                    v.add(0); // Address
596                    v.add(0); // Subaddress
597                    dccTurnoutModel.insertData(v, true);
598                    break;
599                }
600                case SERVOTURNOUT: {
601                    List<Object> v = new ArrayList<>();
602                    v.add(0); // ID
603                    v.add(0); // pin
604                    v.add(0); // thrownposition
605                    v.add(0); // closedposition
606                    v.add(0); // profile
607                    servoTurnoutModel.insertData(v, true);
608                    break;
609                }
610                case VPINTURNOUT: {
611                    List<Object> v = new ArrayList<>();
612                    v.add(0); // ID
613                    v.add(0); // Pin
614                    vpinTurnoutModel.insertData(v, true);
615                    break;
616                }
617                case OUTPUT: {
618                    List<Object> v = new ArrayList<>();
619                    v.add(0); // Index
620                    v.add(0); // Pin
621                    v.add(false); // Invert
622                    v.add(false); // Restore state
623                    v.add(false); // Force high/low
624                    outputModel.insertData(v, true);
625                    break;
626                }
627                default:
628                    break;
629            }
630        }
631    }
632
633    /**
634     * Respond to the user pressing the "Save Sensors/Turnouts/Outputs" button.
635     *
636     * @param e the button press event
637     */
638    private void saveButtonPressed(ActionEvent e) {
639        int value = JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("FieldMCFSaveDialogConfirmMessage"),
640                Bundle.getMessage("ConfirmSaveDialogTitle"),
641                JmriJOptionPane.YES_NO_OPTION);
642        if (sensorTable.getCellEditor() != null) {
643            sensorTable.getCellEditor().stopCellEditing();
644        }
645        if (dccTurnoutTable.getCellEditor() != null) {
646            dccTurnoutTable.getCellEditor().stopCellEditing();
647        }
648        if (servoTurnoutTable.getCellEditor() != null) {
649            servoTurnoutTable.getCellEditor().stopCellEditing();
650        }
651        if (vpinTurnoutTable.getCellEditor() != null) {
652            vpinTurnoutTable.getCellEditor().stopCellEditing();
653        }
654        if (outputTable.getCellEditor() != null) {
655            outputTable.getCellEditor().stopCellEditing();
656        }
657        if (value == JmriJOptionPane.YES_OPTION) {
658            saveTableValues();
659        }
660    }
661
662    /**
663     * Save the values for the currently selected tab.
664     */
665    private void saveTableValues() {
666        if (null != cTab) {
667            switch (cTab) {
668                case SENSOR:
669                    for (int i = 0; i < sensorModel.getRowData().size(); i++) {
670
671                        List<Object> r = sensorModel.getRowData().get(i);
672                        boolean isnew = (boolean) r.get(4);
673                        boolean isdirty = (boolean) r.get(5);
674                        boolean isdelete = (boolean) r.get(6);
675                        int row = sensorModel.getRowData().indexOf(r);
676                        if (isnew) {
677                            _tc.sendDCCppMessage(DCCppMessage.makeSensorAddMsg((int) r.get(0),
678                                    (int) r.get(1),
679                                    ((boolean) r.get(2) ? 1 : 0)), this);
680                            sensorModel.setNewRow(row, false);
681                        } else if (isdelete) {
682                            _tc.sendDCCppMessage(DCCppMessage.makeSensorDeleteMsg((int) r.get(0)), this);
683                            sensorModel.getRowData().remove(r);
684                        } else if (isdirty) {
685                            // Send a Delete, then an Add (for now).
686                            _tc.sendDCCppMessage(DCCppMessage.makeSensorDeleteMsg((int) r.get(0)), this);
687                            // WARNING: Conversions here are brittle. Be careful.
688                            _tc.sendDCCppMessage(DCCppMessage.makeSensorAddMsg((int) r.get(0),
689                                    (int) r.get(1),
690                                    ((boolean) r.get(2) ? 1 : 0)), this);
691                            sensorModel.setNewRow(row, false);
692                            sensorModel.setDirtyRow(row, false);
693                        }
694                    }
695                    _tc.sendDCCppMessage(DCCppMessage.makeSensorListMsg(), this); //request updated definitions list 
696                    break;
697                case DCCTURNOUT:
698                    for (int i = 0; i < dccTurnoutModel.getRowData().size(); i++) {
699
700                        List<Object> r = dccTurnoutModel.getRowData().get(i);
701                        boolean isnew = (boolean) r.get(4);
702                        boolean isdirty = (boolean) r.get(5);
703                        boolean isdelete = (boolean) r.get(6);
704                        int row = dccTurnoutModel.getRowData().indexOf(r);
705                        if (isnew) {
706                            // WARNING: Conversions here are brittle. Be careful.
707                            _tc.sendDCCppMessage(DCCppMessage.makeTurnoutAddMsg((int) r.get(0),
708                                    (int) r.get(1), (int) r.get(2)), this);
709                            dccTurnoutModel.setNewRow(row, false);
710                        } else if (isdelete) {
711                            DCCppMessage m = new DCCppMessage("T " + Integer.toString((int) r.get(0)));
712                            _tc.sendDCCppMessage(m, this);
713                            log.debug("Sending: {}", m);
714                            dccTurnoutModel.getRowData().remove(r);
715                        } else if (isdirty) {
716                            _tc.sendDCCppMessage(DCCppMessage.makeTurnoutDeleteMsg((int) r.get(0)), this);
717                            // Send a Delete, then an Add (for now).
718                            // WARNING: Conversions here are brittle. Be careful.
719                            _tc.sendDCCppMessage(DCCppMessage.makeTurnoutAddMsg((int) r.get(0),
720                                    (int) r.get(1), (int) r.get(2)), this);
721                            dccTurnoutModel.setNewRow(row, false);
722                            dccTurnoutModel.setDirtyRow(row, false);
723                        }
724                    }
725                    //request updated definitions list
726                    if (_tc.getCommandStation().isTurnoutIDsMessageRequired()) {
727                        _tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this);
728                    } else {
729                        _tc.sendDCCppMessage(DCCppMessage.makeTurnoutListMsg(), this); 
730                    }                    
731                    break;
732                case SERVOTURNOUT:
733                    for (int i = 0; i < servoTurnoutModel.getRowData().size(); i++) {
734
735                        List<Object> r = servoTurnoutModel.getRowData().get(i);
736                        boolean isnew = (boolean) r.get(6);
737                        boolean isdirty = (boolean) r.get(7);
738                        boolean isdelete = (boolean) r.get(8);
739                        int row = servoTurnoutModel.getRowData().indexOf(r);
740                        if (isnew) {
741                            // WARNING: Conversions here are brittle. Be careful.
742                            DCCppMessage m = new DCCppMessage("T "+(int)r.get(0)+" SERVO "+(int)r.get(1)+" "+
743                                    +(int)r.get(2)+" "+(int)r.get(3)+" "+(int)r.get(4));
744                            log.debug("Sending: {}", m);
745                            _tc.sendDCCppMessage(m, this);
746                            servoTurnoutModel.setNewRow(row, false);
747                        } else if (isdelete) {
748                            DCCppMessage m = new DCCppMessage("T " + Integer.toString((int) r.get(0)));
749                            _tc.sendDCCppMessage(m, this);
750                            log.debug("Sending: {}", m);
751                            servoTurnoutModel.getRowData().remove(r);
752                        } else if (isdirty) {
753                            _tc.sendDCCppMessage(DCCppMessage.makeTurnoutDeleteMsg((int) r.get(0)), this);
754                            // Send a Delete, then an Add (for now).
755                            // WARNING: Conversions here are brittle. Be careful.
756                            _tc.sendDCCppMessage(DCCppMessage.makeTurnoutAddMsg((int) r.get(0),
757                                    (int) r.get(1), (int) r.get(2)), this);
758                            servoTurnoutModel.setNewRow(row, false);
759                            servoTurnoutModel.setDirtyRow(row, false);
760                        }
761                    }
762                    //request updated definitions list
763                    if (_tc.getCommandStation().isTurnoutIDsMessageRequired()) {
764                        _tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this);
765                    } else {
766                        _tc.sendDCCppMessage(DCCppMessage.makeTurnoutListMsg(), this); 
767                    }                    
768                    break;
769                case VPINTURNOUT:
770                    for (int i = 0; i < vpinTurnoutModel.getRowData().size(); i++) {
771
772                        List<Object> r = vpinTurnoutModel.getRowData().get(i);
773                        boolean isnew = (boolean) r.get(3);
774                        boolean isdirty = (boolean) r.get(4);
775                        boolean isdelete = (boolean) r.get(5);
776                        int row = vpinTurnoutModel.getRowData().indexOf(r);
777                        if (isnew) {
778                            // WARNING: Conversions here are brittle. Be careful.
779                            DCCppMessage m = new DCCppMessage("T "+(int)r.get(0)+" VPIN "+(int)r.get(1));
780                            log.debug("Sending: {}", m);
781                            _tc.sendDCCppMessage(m, this);
782                            vpinTurnoutModel.setNewRow(row, false);
783                        } else if (isdelete) {
784                            DCCppMessage m = new DCCppMessage("T " + (int)r.get(0));
785                            log.debug("Sending: {}", m);
786                            _tc.sendDCCppMessage(m, this);
787                            vpinTurnoutModel.getRowData().remove(r);
788                        } else if (isdirty) {
789                            // Send a Delete, then an Add (for now).
790                            DCCppMessage m = new DCCppMessage("T " + (int)r.get(0));
791                            log.debug("Sending: {}", m);
792                            _tc.sendDCCppMessage(m, this);
793                            vpinTurnoutModel.setNewRow(row, false);
794                            vpinTurnoutModel.setDirtyRow(row, false);
795                        }
796                    }
797                    //request updated definitions list
798                    if (_tc.getCommandStation().isTurnoutIDsMessageRequired()) {
799                        _tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this);
800                    } else {
801                        _tc.sendDCCppMessage(DCCppMessage.makeTurnoutListMsg(), this); 
802                    }                    
803                    break;
804                case OUTPUT:
805                    for (int i = 0; i < outputModel.getRowData().size(); i++) {
806
807                        List<Object> r = outputModel.getRowData().get(i);
808                        boolean isnew = (boolean) r.get(6);
809                        boolean isdirty = (boolean) r.get(7);
810                        boolean isdelete = (boolean) r.get(8);
811                        int row = outputModel.getRowData().indexOf(r);
812                        if (isnew) {
813                            // WARNING: Conversions here are brittle. Be careful.
814                            int f = ((boolean) r.get(2) ? 1 : 0); // Invert
815                            f += ((boolean) r.get(3) ? 2 : 0); // Restore
816                            f += ((boolean) r.get(4) ? 4 : 0); // Force
817                            _tc.sendDCCppMessage(DCCppMessage.makeOutputAddMsg((int) r.get(0),
818                                    (int) r.get(1), f), this);
819                            outputModel.setNewRow(row, false);
820                        } else if (isdelete) {
821                            _tc.sendDCCppMessage(DCCppMessage.makeOutputDeleteMsg((int) r.get(0)), this);
822                            outputModel.getRowData().remove(r);
823                        } else if (isdirty) {
824                            // Send a Delete, then an Add (for now).
825                            _tc.sendDCCppMessage(DCCppMessage.makeOutputDeleteMsg((int) r.get(0)), this);
826                            int f = ((boolean) r.get(2) ? 1 : 0); // Invert
827                            f += ((boolean) r.get(3) ? 2 : 0); // Restore
828                            f += ((boolean) r.get(4) ? 4 : 0); // Force
829                            _tc.sendDCCppMessage(DCCppMessage.makeOutputAddMsg((int) r.get(0),
830                                    (int) r.get(1), f), this);
831                            outputModel.setNewRow(row, false);
832                            outputModel.setDirtyRow(row, false);
833                        }
834                    }
835                    _tc.sendDCCppMessage(DCCppMessage.makeOutputListMsg(), this); //request updated definitions list 
836                    break;
837                default:
838                    break;
839            }
840        }
841
842        // Offer to write the changes to EEPROM
843        int value = JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("FieldMCFCloseDialogConfirmMessage"),
844                Bundle.getMessage("FieldMCFCloseDialogTitle"),
845                JmriJOptionPane.YES_NO_OPTION);
846
847        if (value == JmriJOptionPane.YES_OPTION) {
848            _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.WRITE_TO_EEPROM_CMD)), this);
849            log.debug("Sending: <E> (Write To EEPROM)");
850        }
851    }
852
853    /**
854     * Respond to the user pressing the "Close" button.
855     *
856     * @param e the button press event
857     */
858    private void closeButtonPressed(ActionEvent e) {
859        // If clicked while editing, stop the cell editor(s)
860        if (sensorTable.getCellEditor() != null) {
861            sensorTable.getCellEditor().stopCellEditing();
862        }
863        if (dccTurnoutTable.getCellEditor() != null) {
864            dccTurnoutTable.getCellEditor().stopCellEditing();
865        }
866        if (servoTurnoutTable.getCellEditor() != null) {
867            servoTurnoutTable.getCellEditor().stopCellEditing();
868        }
869        if (vpinTurnoutTable.getCellEditor() != null) {
870            vpinTurnoutTable.getCellEditor().stopCellEditing();
871        }
872        if (outputTable.getCellEditor() != null) {
873            outputTable.getCellEditor().stopCellEditing();
874        }
875
876        // If clicked while changes not saved to BaseStation, offer
877        // the option of saving.
878        if (sensorModel.isDirty() || dccTurnoutModel.isDirty() || servoTurnoutModel.isDirty() 
879                || vpinTurnoutModel.isDirty() || outputModel.isDirty()) {
880            int value = JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("FieldMCFSaveDialogConfirmMessage"),
881                    Bundle.getMessage("ConfirmSaveDialogTitle"),
882                    JmriJOptionPane.YES_NO_OPTION);
883            if (value == JmriJOptionPane.YES_OPTION) {
884                saveTableValues();
885            }
886
887            // Offer to write the changes to EEPROM
888            value = JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("FieldMCFCloseDialogConfirmMessage"),
889                    Bundle.getMessage("FieldMCFCloseDialogTitle"),
890                    JmriJOptionPane.YES_NO_OPTION);
891
892            if (value == JmriJOptionPane.YES_OPTION) {
893                _tc.sendDCCppMessage(new DCCppMessage(String.valueOf(DCCppConstants.WRITE_TO_EEPROM_CMD)), this);
894                log.debug("Sending: <E> (Write To EEPROM)");
895            }
896
897        } else {
898            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("FieldMCFCloseNoChangesDialog"));
899        }
900
901        // Close the window
902        dispose();
903    }
904
905    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConfigBaseStationFrame.class);
906
907    /**
908     * Private class to serve as TableModel for Sensors.
909     */
910    private static class SensorTableModel extends DCCppTableModel {
911
912        public SensorTableModel() {
913            super(4, 5, 6, 7);
914            // Use i18n-ized column titles.
915            columnNames = new String[7];
916            columnNames[0] = Bundle.getMessage("IDCol");
917            columnNames[1] = Bundle.getMessage("FieldTablePinColumn");
918            columnNames[2] = Bundle.getMessage("FieldTablePullupColumn");
919            columnNames[3] = Bundle.getMessage("ColumnDelete");
920            columnNames[4] = "isNew";       // hidden column // NOI18N
921            columnNames[5] = "isDirty";     // hidden column // NOI18N
922            columnNames[6] = "isDelete";    // hidden column // NOI18N
923        }
924
925        @Override
926        public Class<?> getColumnClass(int columnIndex) {
927            switch (columnIndex) {
928                case 0:
929                case 1:
930                    return Integer.class;
931                case 2:
932                    return Boolean.class;
933                case 3:
934                    return ButtonEditor.class;
935                case 4:
936                case 5:
937                case 6:
938                    return Boolean.class;
939                default:
940                    return super.getColumnClass(columnIndex);
941            }
942        }
943    }
944
945    /**
946     * Private class to serve as TableModel for DCC Turnouts
947     */
948    private static class DccTurnoutTableModel extends DCCppTableModel {
949
950        public DccTurnoutTableModel() {
951            super(4, 5, 6, 7);
952            // Use i18n-ized column titles.
953            columnNames = new String[7];
954            columnNames[0] = Bundle.getMessage("IDCol");
955            columnNames[1] = Bundle.getMessage("AddressCol");
956            columnNames[2] = Bundle.getMessage("FieldTableSubaddrColumn");
957            columnNames[3] = Bundle.getMessage("ColumnDelete");
958            columnNames[4] = "isNew";        // hidden column // NOI18N
959            columnNames[5] = "isDirty";      // hidden column // NOI18N
960            columnNames[6] = "isDelete";     // hidden column // NOI18N
961        }
962
963        @Override
964        public Class<?> getColumnClass(int columnIndex) {
965            switch (columnIndex) {
966                case 0:
967                case 1:
968                case 2:
969                    return Integer.class;
970                case 3:
971                    return ConfigBaseStationFrame.ButtonEditor.class;
972                case 4:
973                case 5:
974                case 6:
975                    return Boolean.class;
976                default:
977                    return super.getColumnClass(columnIndex);
978            }
979        }
980    }
981
982    /**
983     * Private class to serve as TableModel for Servo Turnouts
984     */
985    private static class ServoTurnoutTableModel extends DCCppTableModel {
986
987        public ServoTurnoutTableModel() {
988            super(6, 7, 8, 9);
989            // Use i18n-ized column titles.
990            columnNames = new String[9];
991            columnNames[0] = Bundle.getMessage("IDCol");
992            columnNames[1] = Bundle.getMessage("PinCol");
993            columnNames[2] = Bundle.getMessage("ThrownPosCol");
994            columnNames[3] = Bundle.getMessage("ClosedPosCol");
995            columnNames[4] = Bundle.getMessage("ProfileCol");
996            columnNames[5] = Bundle.getMessage("ColumnDelete");
997            columnNames[6] = "isNew";        // hidden column // NOI18N
998            columnNames[7] = "isDirty";      // hidden column // NOI18N
999            columnNames[8] = "isDelete";     // hidden column // NOI18N
1000        }
1001        @Override
1002        public int getDeleteColumn() {
1003            return (5);
1004        }
1005
1006        @Override
1007        public Class<?> getColumnClass(int columnIndex) {
1008            switch (columnIndex) {
1009                case 0:
1010                case 1:
1011                case 2:
1012                case 3:
1013                case 4:
1014                    return Integer.class;
1015                case 5:
1016                    return ConfigBaseStationFrame.ButtonEditor.class;
1017                case 6:
1018                case 7:
1019                case 8:
1020                    return Boolean.class;
1021                default:
1022                    return super.getColumnClass(columnIndex);
1023            }
1024        }
1025    }
1026
1027    /**
1028     * Private class to serve as TableModel for Vpin Turnouts
1029     */
1030    private static class VpinTurnoutTableModel extends DCCppTableModel {
1031
1032        public VpinTurnoutTableModel() {
1033            super(3, 4, 5, 6);
1034            // Use i18n-ized column titles.
1035            columnNames = new String[6];
1036            columnNames[0] = Bundle.getMessage("IDCol");
1037            columnNames[1] = Bundle.getMessage("PinCol");
1038            columnNames[2] = Bundle.getMessage("ColumnDelete");
1039            columnNames[3] = "isNew";        // hidden column // NOI18N
1040            columnNames[4] = "isDirty";      // hidden column // NOI18N
1041            columnNames[5] = "isDelete";     // hidden column // NOI18N
1042        }
1043
1044        @Override
1045        public int getDeleteColumn() {
1046            return (2);
1047        }
1048
1049        @Override
1050        public Class<?> getColumnClass(int columnIndex) {
1051            switch (columnIndex) {
1052                case 0:
1053                case 1:
1054                    return Integer.class;
1055                case 2:
1056                    return ConfigBaseStationFrame.ButtonEditor.class;
1057                case 3:
1058                case 4:
1059                case 5:
1060                    return Boolean.class;
1061                default:
1062                    return super.getColumnClass(columnIndex);
1063            }
1064        }
1065    }
1066
1067    /**
1068     * Private class to serve as TableModel for Outputs
1069     */
1070    private static class OutputTableModel extends DCCppTableModel {
1071
1072        public OutputTableModel() {
1073            super(6, 7, 8, 9);
1074            // Use i18n-ized column titles.
1075            columnNames = new String[9];
1076            columnNames[0] = Bundle.getMessage("IDCol");
1077            columnNames[1] = Bundle.getMessage("FieldTablePinColumn");
1078            columnNames[2] = Bundle.getMessage("FieldTableInvertColumn");
1079            columnNames[3] = Bundle.getMessage("FieldTableOutputRestoreStateColumn");
1080            columnNames[4] = Bundle.getMessage("FieldTableOutputForceToColumn");
1081            columnNames[5] = Bundle.getMessage("ColumnDelete");
1082            columnNames[6] = "isNew";        // hidden column // NOI18N
1083            columnNames[7] = "isDirty";      // hidden column // NOI18N
1084            columnNames[8] = "isDelete";     // hidden column // NOI18N
1085        }
1086
1087        @Override
1088        public int getDeleteColumn() {
1089            return (5);
1090        }
1091
1092        @Override
1093        public Class<?> getColumnClass(int columnIndex) {
1094            switch (columnIndex) {
1095                case 0:
1096                case 1:
1097                    return Integer.class;
1098                case 2:
1099                case 3:
1100                case 4:
1101                    return Boolean.class;
1102                case 5:
1103                    return ConfigBaseStationFrame.ButtonEditor.class;
1104                case 6:
1105                case 7:
1106                case 8:
1107                    return Boolean.class;
1108                default:
1109                    return super.getColumnClass(columnIndex);
1110            }
1111        }
1112    }
1113
1114    static class ButtonRenderer extends JButton implements TableCellRenderer {
1115
1116        public ButtonRenderer() {
1117            super.setOpaque(true);
1118            super.setSelected(false);
1119        }
1120
1121        @Override
1122        public Component getTableCellRendererComponent(JTable table, Object value,
1123                boolean isSelected, boolean hasFocus, int row, int column) {
1124            if (isSelected) {
1125                setForeground(table.getSelectionForeground());
1126                setBackground(table.getSelectionBackground());
1127            } else {
1128                setForeground(table.getForeground());
1129                setBackground(UIManager.getColor("Button.background"));
1130            }
1131            setText((value == null) ? "" : value.toString());
1132            return this;
1133        }
1134    }
1135
1136    /**
1137     * Button Editor class to replace the DefaultCellEditor in the table for the
1138     * delete button.
1139     * <p>
1140     * NOTE: This isn't actually used anymore except as being a unique class
1141     * type that can be returned from the TableModel classes for the column that
1142     * includes the Delete buttons.
1143     */
1144    class ButtonEditor extends DefaultCellEditor {
1145
1146        protected JButton button;
1147        private String label;
1148        public ButtonEditor(JCheckBox checkBox, JTable t) {
1149            super(checkBox);
1150            button = new JButton();
1151            button.setOpaque(true);
1152            button.addActionListener((ActionEvent e) -> {
1153                //fireEditingStopped();
1154            });
1155        }
1156
1157        @Override
1158        public Component getTableCellEditorComponent(JTable table, Object value,
1159                boolean isSelected, int row, int column) {
1160            if (isSelected) {
1161                button.setForeground(table.getSelectionForeground());
1162                button.setBackground(table.getSelectionBackground());
1163            } else {
1164                button.setForeground(table.getForeground());
1165                button.setBackground(table.getBackground());
1166            }
1167            label = (value == null) ? "" : value.toString();
1168            button.setText(label);
1169            return button;
1170        }
1171
1172        @Override
1173        public Object getCellEditorValue() {
1174            return label;
1175        }
1176    }
1177
1178}