001package jmri.jmrix.loconet.slotmon;
002
003import java.awt.Component;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import java.awt.event.ItemListener;
007import java.util.ArrayList;
008import java.util.List;
009import javax.swing.*;
010import javax.swing.event.ListSelectionEvent;
011import javax.swing.event.ListSelectionListener;
012import javax.swing.table.DefaultTableCellRenderer;
013import javax.swing.table.TableCellEditor;
014import javax.swing.table.TableColumn;
015import javax.swing.table.TableColumnModel;
016import javax.swing.table.TableRowSorter;
017
018import jmri.InstanceManager;
019import jmri.UserPreferencesManager;
020import jmri.jmrix.loconet.LnConstants;
021import jmri.jmrix.loconet.LocoNetSlot;
022import jmri.jmrix.loconet.SlotListener;
023import jmri.jmrix.loconet.SlotMapEntry.SlotType;
024import jmri.swing.JmriJTablePersistenceManager;
025import jmri.util.MenuScroller;
026import jmri.util.swing.JmriMouseAdapter;
027import jmri.util.swing.JmriMouseEvent;
028import jmri.util.swing.JmriMouseListener;
029import jmri.util.swing.WrapLayout;
030import jmri.util.swing.XTableColumnModel;
031import jmri.util.table.*;
032
033/**
034 * Frame providing a command station slot manager.
035 * <p>
036 * Slots 102 through 127 are normally not used for loco control, so are shown
037 * separately.
038 *
039 * @author Bob Jacobsen Copyright (C) 2001
040 */
041public class SlotMonPane extends jmri.jmrix.loconet.swing.LnPanel implements SlotListener {
042
043    /**
044     * Controls whether not-in-use slots are shown
045     */
046    protected final JCheckBox showUnusedCheckBox = new JCheckBox();
047    /**
048     * Controls whether system slots (0, 121-127) are shown
049     */
050    protected final JCheckBox showSystemCheckBox = new JCheckBox();
051
052    private JLabel dcsCSLabel = new JLabel(Bundle.getMessage("SlotMonCSLabel"));
053    private JTextField dcsType = new JTextField();
054    private JLabel dcsSlotsLabel = new JLabel(Bundle.getMessage("SlotMonTotalSlots"));
055    private JTextField dcsSlots = new JTextField();
056
057    private final JButton estopAllButton = new JButton(Bundle.getMessage("ButtonSlotMonEStopAll"));
058
059    private JLabel inUseCount = new JLabel("      ");
060
061    //Added by Jeffrey Machacek 2013
062    private final JButton clearAllButton = new JButton(Bundle.getMessage("ButtonSlotMonClearAll"));
063    private final JButton refreshAllButton = new JButton(Bundle.getMessage("ButtonSlotRefresh"));
064
065    private JPanel topPanel;  // the panel across the top that holds buttons
066
067    private SlotMonDataModel slotModel;
068    private JTable slotTable;
069    private JScrollPane slotScroll;
070    private transient TableRowSorter<SlotMonDataModel> sorter;
071
072    // Options menu
073    private JCheckBoxMenuItem optionHideTopPanel = new JCheckBoxMenuItem(Bundle.getMessage("SlotMonHideTopPanel"));
074
075    // filter menu
076    private JCheckBoxMenuItem filterShowIdle = new JCheckBoxMenuItem(Bundle.getMessage("SlotMonShowIdle"));
077    private JCheckBoxMenuItem filterShowUnUsed = new JCheckBoxMenuItem(Bundle.getMessage("TextSlotMonShowUnused"));
078    private JCheckBoxMenuItem filterShowSystem = new JCheckBoxMenuItem(Bundle.getMessage("TextSlotMonShowSystem"));
079
080    // Actions menu
081    private JMenuItem actionRefreshAll = new JMenuItem(Bundle.getMessage("ButtonSlotRefresh"));
082    private JMenuItem actionEStopAll = new JMenuItem(Bundle.getMessage("ButtonSlotMonEStopAll"));
083    private JMenuItem actionSaveFiltersAndOptions = new JMenuItem(Bundle.getMessage("SlotMonSaveFiltersAndOptions"));
084    private JMenuItem actionResetColumnsLayout = new JMenuItem(Bundle.getMessage("SlotMonResetColumnsLayout"));
085
086    // popup menu
087    private JPopupMenu popUp = new JPopupMenu();
088    private JMenuItem popUpRefresh = new JMenuItem("Refresh");
089    private int selectedRow = -1;
090
091    private final String slotMonHideTopPanel = this.getClass().getName() + "SlotMonHideTopPanel"; // NOI18N
092    private final String slotMonShowIdle = this.getClass().getName() + "SlotMonShowIdle"; // NOI18N
093    private final String slotMonShowUnused = this.getClass().getName() + "SlotMonShowUnused"; // NOI18N
094    private final String slotMonShowSystem = this.getClass().getName() + "SlotMonShowSystem"; // NOI18N
095
096    private void restorePreferences() {
097        UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class);
098        optionHideTopPanel.setSelected(pm.getSimplePreferenceState(slotMonHideTopPanel));
099        filterShowIdle.setSelected(pm.getSimplePreferenceState(slotMonShowIdle));
100        filterShowUnUsed.setSelected(pm.getSimplePreferenceState(slotMonShowUnused));
101        filterShowSystem.setSelected(pm.getSimplePreferenceState(slotMonShowSystem));
102    }
103
104    private void resetColumnLayout() {
105        //slotTable.getColumnModel();
106        slotTable.createDefaultColumnsFromModel();
107        InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent( tpm ->
108        tpm.cacheState(slotTable) );
109    }
110
111    private void savePreferences() {
112        UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class);
113        pm.setSimplePreferenceState(slotMonHideTopPanel, optionHideTopPanel.isSelected());
114        pm.setSimplePreferenceState(slotMonShowIdle, filterShowIdle.isSelected());
115        pm.setSimplePreferenceState(slotMonShowUnused, filterShowUnUsed.isSelected());
116        pm.setSimplePreferenceState(slotMonShowSystem, filterShowSystem.isSelected());
117    }
118
119    public SlotMonPane() {
120        super();
121    }
122
123    @Override
124    public boolean isMultipleInstances() {
125        return false;
126    }
127
128    private boolean runningFilterSystem = false;
129    private boolean runningFilterUnUsed = false;
130
131    @Override
132    public void initComponents(jmri.jmrix.loconet.LocoNetSystemConnectionMemo memo) {
133        super.initComponents(memo);
134        boolean hideColumnsF9toF28 = true; //(memo.getSlotManager().getLoconetProtocol() != LnConstants.LOCONETPROTOCOL_TWO);
135        slotModel = new SlotMonDataModel(memo.getSlotManager().getNumSlots(), memo);
136        slotTable = new JTable(slotModel);
137        slotTable.setColumnModel(new XTableColumnModel());
138        slotTable.createDefaultColumnsFromModel();
139        //XTableColumnModel SlotMonDataModel = (XTableColumnModel)slotTable.getColumnModel();
140
141        slotTable.setName(this.getTitle());
142
143        sorter = new TableRowSorter<>(slotModel);
144        slotTable.setRowSorter(sorter);
145        slotScroll = new JScrollPane(slotTable);
146
147        // configure items for GUI
148        showUnusedCheckBox.setText(Bundle.getMessage("TextSlotMonShowUnused"));
149        showUnusedCheckBox.setVisible(true);
150        showUnusedCheckBox.setSelected(false);
151        showUnusedCheckBox.setToolTipText(Bundle.getMessage("TooltipSlotMonShowUnused"));
152
153        showSystemCheckBox.setText(Bundle.getMessage("TextSlotMonShowSystem"));
154        showSystemCheckBox.setVisible(true);
155        showSystemCheckBox.setSelected(false);
156        showSystemCheckBox.setToolTipText(Bundle.getMessage("TooltipSlotMonShowSystem"));
157
158        // allow reordering of the columns
159        slotTable.getTableHeader().setReorderingAllowed(true);
160
161        // shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
162        slotTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
163
164        // resize columns as requested
165        for (int i = 0; i < slotTable.getColumnCount(); i++) {
166            int width = slotModel.getPreferredWidth(i);
167            slotTable.getColumnModel().getColumn(i).setPreferredWidth(width);
168        }
169        slotTable.sizeColumnsToFit(-1);
170
171        // install a button renderer & editor in the "DISP" column for freeing a slot
172        setColumnToHoldButton(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.ColumnNumber.DISPCOLUMN.ordinal()));
173
174        // install a button renderer & editor in the "ESTOP" column for stopping a loco
175        setColumnToHoldEStopButton(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.ColumnNumber.ESTOPCOLUMN.ordinal()));
176
177        // Install a numeric format for ConsistAddress
178        slotTable.getColumnModel().getColumn(SlotMonDataModel.ColumnNumber.CONSISTADDRESS.ordinal()).setCellRenderer(new TableRendererToBlankToNA());
179        slotTable.getColumnModel().getColumn(slotTable.convertColumnIndexToView(SlotMonDataModel.ColumnNumber.JMRITHROTTLECOUNT.ordinal())).setCellRenderer(new TableRendererToBlankToNA());
180
181        InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent((tpm) -> {
182            try {
183                tpm.persist(slotTable, true);
184            } catch (IllegalArgumentException Ex) {
185                log.warn("SlotMon Can only save layout changes for second and subsequent invocations");
186            }
187        });
188
189        // Hide functions F9 thru F28 if not used.
190        if (hideColumnsF9toF28) {
191            XTableColumnModel tcm = (XTableColumnModel) slotTable.getColumnModel();
192            for (int ixCol = SlotMonDataModel.ColumnNumber.F9COLUMN.ordinal() ;
193                    ixCol <= SlotMonDataModel.ColumnNumber.F28COLUMN.ordinal() ;
194                    ixCol ++ ) {
195                TableColumn tc = tcm.getColumnByModelIndex(ixCol);
196                tcm.setColumnVisible(tc, false);
197            }
198        }
199
200        ActionListener refreshListener = e -> {
201            slotModel.refreshSlots();
202        };
203        // add listener object so checkboxes functio
204        refreshAllButton.addActionListener(refreshListener);
205        actionRefreshAll.addActionListener(refreshListener);
206
207        ItemListener filterShowSystemListen = e -> {
208            if (runningFilterSystem) {
209                return;
210            }
211            runningFilterSystem = true;
212            boolean state;
213            if (e.getSource() instanceof JCheckBox) {
214                state = ((JCheckBox) e.getSource()).isSelected();
215            } else {
216                state = ((JCheckBoxMenuItem) e.getSource()).isSelected();
217            }
218            filterShowSystem.setSelected(state);
219            showSystemCheckBox.setSelected(state);
220            runningFilterSystem = false;
221            filter();
222        } ;
223        showSystemCheckBox.addItemListener(filterShowSystemListen);
224        filterShowSystem.addItemListener(filterShowSystemListen);
225
226        ItemListener filterShowUnUsedListen = e -> {
227            if (runningFilterUnUsed) {
228                return;
229            }
230            runningFilterUnUsed = true;
231            boolean state;
232            if (e.getSource() instanceof JCheckBox) {
233                state = ((JCheckBox) e.getSource()).isSelected();
234            } else {
235                state = ((JCheckBoxMenuItem) e.getSource()).isSelected();
236            }
237            filterShowUnUsed.setSelected(state);
238            showUnusedCheckBox.setSelected(state);
239            runningFilterUnUsed = false;
240            filter();
241        };
242        filterShowUnUsed.addItemListener(filterShowUnUsedListen);
243        showUnusedCheckBox.addItemListener(filterShowUnUsedListen);
244
245        // add listener object so stop all button functions
246        optionHideTopPanel.addActionListener((ActionEvent e) -> {
247            topPanel.setVisible(!optionHideTopPanel.getState());
248        });
249
250        estopAllButton.addActionListener((ActionEvent e) -> {
251            slotModel.estopAll();
252        });
253        actionEStopAll.addActionListener((ActionEvent e) -> {
254            slotModel.estopAll();
255        });
256        actionSaveFiltersAndOptions.addActionListener((ActionEvent e) -> {
257            savePreferences();
258        });
259        actionResetColumnsLayout.addActionListener((ActionEvent e) -> {
260            resetColumnLayout();
261        });
262
263        //Jeffrey 6/29/2013
264        clearAllButton.addActionListener((ActionEvent e) -> {
265            slotModel.clearAllSlots();
266        });
267
268        // adjust model to default settings
269        filter();
270
271        // This listener is needed when there are a large number of slots being updated
272        //
273        slotTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
274            @Override
275            public void valueChanged(ListSelectionEvent e) {
276                if (slotTable.getSelectedRow() != -1) { // Ensure a row is actually selected
277                    selectedRow = slotTable.getSelectedRow();
278                    Object value = slotTable.getValueAt(selectedRow, 0);
279                    log.debug("Selected Row:[{}] Value[{}]", selectedRow, value);
280                }
281            }
282        });
283        popUp.add(popUpRefresh);
284        popUpRefresh.addActionListener((ActionEvent e) -> {
285            int row = slotTable.getSelectedRow();
286            if (row != -1) {
287                memo.getSlotManager().sendReadSlot((int)slotTable.getValueAt(row, 0));
288            } else {
289                String response = jmri.util.swing.JmriJOptionPane.showInputDialog(this,
290                        "Enter Slot", Integer.toString(selectedRow) );
291                try {
292                    int slotNo = Integer.parseInt(response) ;
293                    memo.getSlotManager().sendReadSlot(slotNo);
294                } catch (NumberFormatException ex) {
295                    log.error("Non Number[{}], ignored",response);
296                }
297            }
298        });
299        slotTable.setComponentPopupMenu(popUp);
300
301        // general GUI config
302        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
303
304        // install items in GUI
305        topPanel = new JPanel();
306        topPanel.setLayout(new WrapLayout());
307
308        topPanel.add(dcsCSLabel);
309        dcsType.setEditable(false);
310        topPanel.add(dcsType);
311        topPanel.add(dcsSlotsLabel);
312        dcsSlots.setEditable(false);
313        topPanel.add(dcsSlots);
314        showHideSlot250Data(false);
315        topPanel.add(refreshAllButton);
316        topPanel.add(showUnusedCheckBox);
317        topPanel.add(showSystemCheckBox);
318        topPanel.add(estopAllButton);
319        topPanel.add(clearAllButton);
320        topPanel.add(clearAllButton);
321        topPanel.add(new JLabel(Bundle.getMessage("SlotMonUnUseCountLabel")));
322        topPanel.add(inUseCount);
323
324        add(topPanel);
325        add(slotScroll);
326 
327        restorePreferences();
328
329        addMouseListenerToHeader(slotTable);
330
331
332        memo.getSlotManager().addSlotListener(this);
333
334        // set top panel size
335        if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) {
336            topPanel.setMaximumSize(topPanel.getPreferredSize());
337        }
338        topPanel.setVisible(!optionHideTopPanel.getState());
339    }
340
341    void setColumnToHoldButton(JTable slotTable, int column) {
342        TableColumnModel tcm = slotTable.getColumnModel();
343        // install the button renderers & editors in this column
344        ButtonRenderer buttonRenderer = new ButtonRenderer();
345        tcm.getColumn(column).setCellRenderer(buttonRenderer);
346        // ensure the table rows, columns have enough room for buttons
347        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
348        slotTable.setDefaultEditor(JButton.class, buttonEditor);
349        slotTable.setRowHeight(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().height);
350        slotTable.getColumnModel().getColumn(column)
351                .setPreferredWidth(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().width);
352    }
353
354
355    static class TableRendererToBlankToNA extends DefaultTableCellRenderer {
356        @Override
357        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
358                int row, int column) {
359            if ((Integer) value == 0) {
360                setText("");
361            } else if ((Integer) value == -1) {
362                setText(Bundle.getMessage("SlotDataNotApplicable"));
363            } else {
364                setText(Integer.toString((Integer) value));
365            }
366            return this;
367        }
368    }
369
370    void setColumnToHoldEStopButton(JTable slotTable, int column) {
371        TableColumnModel tcm = slotTable.getColumnModel();
372        // install the button renderers & editors in this column
373        ButtonRenderer buttonRenderer = new ButtonRenderer();
374        tcm.getColumn(column).setCellRenderer(buttonRenderer);
375        // ensure the table rows, columns have enough room for buttons
376        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
377        slotTable.setDefaultEditor(JButton.class, buttonEditor);
378        slotTable.setRowHeight(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().height);
379        slotTable.getColumnModel().getColumn(column)
380                .setPreferredWidth(new JButton("  " + slotModel.getValueAt(1, column)).getPreferredSize().width);
381    }
382
383    @Override
384    public String getHelpTarget() {
385        return "package.jmri.jmrix.loconet.slotmon.SlotMonFrame"; // NOI18N
386    }
387
388    @Override
389    public String getTitle() {
390        return getTitle(Bundle.getMessage("MenuItemSlotMonitor"));
391    }
392
393    @Override
394    public void dispose() {
395        InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent( tpm ->
396            tpm.stopPersisting(slotTable) );
397        savePreferences();
398        slotModel.dispose();
399        slotModel = null;
400        slotTable = null;
401        slotScroll = null;
402        super.dispose();
403    }
404
405
406    private void filter() {
407        RowFilter<SlotMonDataModel, Integer> rf = new RowFilter<SlotMonDataModel, Integer>() {
408            @Override
409            public boolean include(RowFilter.Entry<? extends SlotMonDataModel, ? extends Integer> entry) {
410                // default filter is IN-USE and regular systems slot
411                // the default is whatever the person last closed it with
412                jmri.jmrix.loconet.LocoNetSlot slot =  entry.getModel().getSlot(entry.getIdentifier());
413                boolean include = entry.getModel().getSlot(entry.getIdentifier()).slotStatus() != LnConstants.LOCO_FREE
414                        && slot.getSlotType() == SlotType.LOCO;
415                if (slot.getSlotType() == SlotType.UNKNOWN) {
416                    return false;        // dont ever show unknown
417                }
418                if (!include && showUnusedCheckBox.isSelected() && !slot.isSystemSlot()) {
419                    include = true;
420                }
421                if (!include && showSystemCheckBox.isSelected() && slot.isSystemSlot()) {
422                    include = true;
423                }
424                return include;
425            }
426        };
427        sorter.setRowFilter(rf);
428    }
429
430    @Override
431    public List<JMenu> getMenus() {
432        List<JMenu> menuList = new ArrayList<>();
433        menuList.add(getFileMenu());
434        menuList.add(getOptionsMenu());
435        menuList.add(getFiltersMenu());
436        menuList.add(getActionsMenu());
437        return menuList;
438    }
439
440    private JMenu getOptionsMenu() {
441        JMenu optionsMenu = new JMenu(Bundle.getMessage("MenuOptions"));
442        optionsMenu.add(optionHideTopPanel);
443        return optionsMenu;
444    }
445
446    private JMenu getFiltersMenu() {
447        JMenu filtersMenu = new JMenu(Bundle.getMessage("MenuFilters"));
448        filtersMenu.add(filterShowIdle);
449        filtersMenu.add(filterShowSystem);
450        filtersMenu.add(filterShowUnUsed);
451        return filtersMenu;
452    }
453
454    private JMenu getActionsMenu() {
455        JMenu actionsMenu = new JMenu(Bundle.getMessage("MenuActions"));
456        actionsMenu.add(actionRefreshAll);
457        actionsMenu.add(actionEStopAll);
458        actionsMenu.add(actionResetColumnsLayout);
459        actionsMenu.add(actionSaveFiltersAndOptions);
460        return actionsMenu;
461    }
462
463    private JMenu getFileMenu(){
464        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile")); // NOI18N
465        fileMenu.add(new JTableToCsvAction((Bundle.getMessage("ExportCsvAll")),
466            null, slotModel, "Slot_Monitor_All.csv", new int[]{
467            SlotMonDataModel.ColumnNumber.ESTOPCOLUMN.ordinal()})); // NOI18N
468        fileMenu.add(new JTableToCsvAction((Bundle.getMessage("ExportCsvView")),
469            slotTable, slotModel, "Slot_Monitor_View.csv", new int[]{
470            SlotMonDataModel.ColumnNumber.ESTOPCOLUMN.ordinal()})); // NOI18N
471     return fileMenu;
472    }
473
474    // methods to communicate with SlotManager
475    @Override
476    public synchronized void notifyChangedSlot(LocoNetSlot s) {
477        // update model from this slot
478        if (s.getSlot() == 250) {
479            if (memo.getSlotManager().getSlot250CSSlots() > 0) {
480                showHideSlot250Data(true);
481                dcsSlots.setText(Integer.toString(memo.getSlotManager().getSlot250CSSlots()));
482                dcsType.setText(memo.getSlotManager().getSlot248CommandStationType());
483
484                // set scroll size
485                if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) {
486                    topPanel.setMaximumSize(topPanel.getPreferredSize());
487                }
488                topPanel.revalidate();
489
490            }
491        }
492        
493        // update count
494        int inUseSlotCount = 0;
495        for (var slot : memo.getSlotManager().getSlots()) {
496            if (!slot.isSystemSlot() && slot.slotStatus() == LnConstants.LOCO_IN_USE) {
497                inUseSlotCount++;
498            }
499        }
500        inUseCount.setText(""+inUseSlotCount);
501    }
502
503    void showHideSlot250Data(boolean b) {
504        dcsCSLabel.setVisible(b);
505        dcsSlots.setVisible(b);
506        dcsSlotsLabel.setVisible(b);
507        dcsType.setVisible(b);
508        // set scroll size
509        if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) {
510            topPanel.setMaximumSize(topPanel.getPreferredSize());
511        }
512        topPanel.revalidate();
513    }
514
515    /*
516     * Mouse popup stuff
517     */
518
519    /**
520     * Process the column header click
521     * @param e     the evnt data
522     * @param table the JTable
523     */
524    protected void showTableHeaderPopup(JmriMouseEvent e, JTable table) {
525        JPopupMenu popupMenu = new JPopupMenu();
526        XTableColumnModel tcm = (XTableColumnModel) table.getColumnModel();
527        for (int i = 0; i < tcm.getColumnCount(false); i++) {
528            TableColumn tc = tcm.getColumnByModelIndex(i);
529            String columnName = table.getModel().getColumnName(i);
530            if (columnName != null && !columnName.equals("")) {
531                JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(table.getModel().getColumnName(i), tcm.isColumnVisible(tc));
532                menuItem.addActionListener(new HeaderActionListener(tc, tcm));
533                popupMenu.add(menuItem);
534            }
535        }
536        MenuScroller.setScrollerFor(popupMenu,10,200,0,0);
537        popupMenu.show(e.getComponent(), e.getX(), e.getY());
538
539    }
540
541    /**
542     * Adds the column header pop listener to a JTable using XTableColumnModel
543     * @param table The JTable effected.
544     */
545    protected void addMouseListenerToHeader(JTable table) {
546        JmriMouseListener mouseHeaderListener = new TableHeaderListener(table);
547        table.getTableHeader().addMouseListener(JmriMouseListener.adapt(mouseHeaderListener));
548    }
549
550    protected static class HeaderActionListener implements ActionListener {
551
552        TableColumn tc;
553        XTableColumnModel tcm;
554
555        HeaderActionListener(TableColumn tc, XTableColumnModel tcm) {
556            this.tc = tc;
557            this.tcm = tcm;
558        }
559
560        @Override
561        public void actionPerformed(ActionEvent e) {
562            JCheckBoxMenuItem check = (JCheckBoxMenuItem) e.getSource();
563            //Do not allow the last column to be hidden
564            if (!check.isSelected() && tcm.getColumnCount(true) == 1) {
565                return;
566            }
567            tcm.setColumnVisible(tc, check.isSelected());
568        }
569    }
570
571    /**
572     * Class to support Columnheader popup menu on XTableColum model.
573     */
574    class TableHeaderListener extends JmriMouseAdapter {
575
576        JTable table;
577
578        TableHeaderListener(JTable tbl) {
579            super();
580            table = tbl;
581        }
582
583        /**
584         * {@inheritDoc}
585         */
586        @Override
587        public void mousePressed(JmriMouseEvent e) {
588            if (e.isPopupTrigger()) {
589                showTableHeaderPopup(e, table);
590            }
591        }
592
593        /**
594         * {@inheritDoc}
595         */
596        @Override
597        public void mouseReleased(JmriMouseEvent e) {
598            if (e.isPopupTrigger()) {
599                showTableHeaderPopup(e, table);
600            }
601        }
602
603        /**
604         * {@inheritDoc}
605         */
606        @Override
607        public void mouseClicked(JmriMouseEvent e) {
608            if (e.isPopupTrigger()) {
609                showTableHeaderPopup(e, table);
610            }
611        }
612    }
613
614    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SlotMonPane.class);
615
616}